Ejemplo n.º 1
0
def _get_solar_2d_vectors(solar_zenith, solar_azimuth, axis_azimuth):
    """Projection of 3d solar vector onto the cross section of the systems:
    which is the 2D plane we are considering.
    This is needed to calculate shadows.
    Remember that the 2D plane is such that the direction of the torque
    tube vector (or rotation axis) goes into (and normal to) the 2D plane,
    such that positive rotation angles will have the PV surfaces tilted to the
    LEFT and vice versa.

    Parameters
    ----------
    solar_zenith : float or numpy array
        Solar zenith angle [deg]
    solar_azimuth : float or numpy array
        Solar azimuth angle [deg]
    axis_azimuth : float
        Axis azimuth of the PV surface, i.e. direction of axis of rotation
        [deg]

    Returns
    -------
    solar_2d_vector : numpy array
        Two vector components of the solar vector in the 2D plane, with the
        form [x, y], where x and y can be arrays
    """
    solar_2d_vector = np.array([
        # a drawing really helps understand the following
        sind(solar_zenith) * cosd(solar_azimuth - axis_azimuth - 90.),
        cosd(solar_zenith)
    ])

    return solar_2d_vector
Ejemplo n.º 2
0
def aoi_projection(surf_tilt, surf_az, sun_zen, sun_az):
    """
    Calculates the dot product of the solar vector and the surface normal.

    Input all angles in degrees.

    Parameters
    ==========

    surf_tilt : float or Series.
        Panel tilt from horizontal.
    surf_az : float or Series.
        Panel azimuth from north.
    sun_zen : float or Series.
        Solar zenith angle.
    sun_az : float or Series.
        Solar azimuth angle.

    Returns
    =======
    float or Series. Dot product of panel normal and solar angle.
    """

    projection = (
        tools.cosd(surf_tilt) * tools.cosd(sun_zen) + tools.sind(surf_tilt) *
        tools.sind(sun_zen) * tools.cosd(sun_az - surf_az))

    try:
        projection.name = 'aoi_projection'
    except AttributeError:
        pass

    return projection
Ejemplo n.º 3
0
    def _calculate_full_coords(xy_center, width, rotation):
        """Method to calculate the full PV row coordinaltes.

        Parameters
        ----------
        xy_center : tuple of float
            x and y coordinates of the PV row center point (invariant)
        width : float
            width of the PV rows [m]
        rotation : np.ndarray
            Timeseries rotation values of the PV row [deg]

        Returns
        -------
        coords: :py:class:`~pvfactors.geometry.timeseries.TsLineCoords`
            Timeseries coordinates of full PV row
        """
        x_center, y_center = xy_center
        radius = width / 2.
        # Calculate coords
        x1 = radius * cosd(rotation + 180.) + x_center
        y1 = radius * sind(rotation + 180.) + y_center
        x2 = radius * cosd(rotation) + x_center
        y2 = radius * sind(rotation) + y_center
        coords = TsLineCoords.from_array(np.array([[x1, y1], [x2, y2]]))
        return coords
Ejemplo n.º 4
0
def test__vf_row_sky_integ(test_system):
    ts, _, _ = test_system
    gcr = ts['gcr']
    surface_tilt = ts['surface_tilt']
    f_x = np.array([0., 0.5, 1.])
    shaded = []
    noshade = []
    for x in f_x:
        s, ns = infinite_sheds._vf_row_sky_integ(x,
                                                 surface_tilt,
                                                 gcr,
                                                 npoints=100)
        shaded.append(s)
        noshade.append(ns)

    def analytic(gcr, surface_tilt, x):
        c = cosd(surface_tilt)
        a = 1. / gcr
        dx = np.sqrt(a**2 - 2 * a * c * x + x**2)
        return -a * (c**2 - 1) * np.arctanh((x - a * c) / dx) - c * dx

    expected_shade = 0.5 * (f_x * cosd(surface_tilt) - analytic(
        gcr, surface_tilt, 1 - f_x) + analytic(gcr, surface_tilt, 1.))
    expected_noshade = 0.5 * ((1 - f_x) * cosd(surface_tilt) + analytic(
        gcr, surface_tilt, 1. - f_x) - analytic(gcr, surface_tilt, 0.))
    shaded = np.array(shaded)
    noshade = np.array(noshade)
    assert np.allclose(shaded, expected_shade)
    assert np.allclose(noshade, expected_noshade)
Ejemplo n.º 5
0
def _calc_tracker_norm(ba, bg, dg):
    """
    Calculate tracker normal, v, cross product of tracker axis and unit normal,
    N, to the system slope plane.

    Parameters
    ----------
    ba : float
        axis tilt [degrees]
    bg : float
        ground tilt [degrees]
    dg : float
        delta gamma, difference between axis and ground azimuths [degrees]

    Returns
    -------
    vector : tuple
        vx, vy, vz
    """
    cos_ba = cosd(ba)
    cos_bg = cosd(bg)
    sin_bg = sind(bg)
    sin_dg = sind(dg)
    vx = sin_dg * cos_ba * cos_bg
    vy = sind(ba) * sin_bg + cosd(dg) * cos_ba * cos_bg
    vz = -sin_dg * sin_bg * cos_ba
    return vx, vy, vz
Ejemplo n.º 6
0
def _vf_row_ground(x, surface_tilt, gcr):
    """
    View factor from a point x on the row to the ground.

    Parameters
    ----------
    x : numeric
        Fraction of row slant height from the bottom. [unitless]
    surface_tilt : numeric
        Surface tilt angle in degrees from horizontal, e.g., surface facing up
        = 0, surface facing horizon = 90. [degree]
    gcr : float
        Ground coverage ratio, ratio of row slant length to row spacing.
        [unitless]

    Returns
    -------
    vf : numeric
        View factor from the point at x to the ground. [unitless]

    """
    cst = cosd(surface_tilt)
    # angle from horizontal at the point x on the row slant height to the
    # bottom of the facing row
    psi_t_shaded = _ground_angle(x, surface_tilt, gcr)
    # view factor from the point on the row to the ground
    return 0.5 * (cosd(psi_t_shaded) - cst)
Ejemplo n.º 7
0
def aoi_projection(surf_tilt, surf_az, sun_zen, sun_az):
    """
    Calculates the dot product of the solar vector and the surface normal.

    Input all angles in degrees.

    Parameters
    ==========

    surf_tilt : float or Series.
        Panel tilt from horizontal.
    surf_az : float or Series.
        Panel azimuth from north.
    sun_zen : float or Series.
        Solar zenith angle.
    sun_az : float or Series.
        Solar azimuth angle.

    Returns
    =======
    float or Series. Dot product of panel normal and solar angle.
    """

    projection = (tools.cosd(surf_tilt) * tools.cosd(sun_zen) +
                  tools.sind(surf_tilt) * tools.sind(sun_zen) *
                  tools.cosd(sun_az - surf_az))

    try:
        projection.name = 'aoi_projection'
    except AttributeError:
        pass

    return projection
Ejemplo n.º 8
0
def _vf_row_sky_integ(f_x, surface_tilt, gcr, npoints=100):
    """
    Integrated view factors from the shaded and unshaded parts of
    the row slant height to the sky.

    Parameters
    ----------
    f_x : numeric
        Fraction of row slant height from the bottom that is shaded. [unitless]
    surface_tilt : numeric
        Surface tilt angle in degrees from horizontal, e.g., surface facing up
        = 0, surface facing horizon = 90. [degree]
    gcr : float
        Ratio of row slant length to row spacing (pitch). [unitless]
    npoints : int, default 100
        Number of points for integration. [unitless]

    Returns
    -------
    vf_shade_sky_integ : numeric
        Integrated view factor from the shaded part of the row to the sky.
        [unitless]
    vf_noshade_sky_integ : numeric
        Integrated view factor from the unshaded part of the row to the sky.
        [unitless]

    Notes
    -----
    The view factor to the sky at a point x along the row slant height is
    given by

    .. math ::
        \\large{f_{sky} = \frac{1}{2} \\left(\\cos\\left(\\psi_t\\right) +
        \\cos \\left(\\beta\\right) \\right)

    where :math:`\\psi_t` is the angle from horizontal of the line from point
    x to the top of the facing row, and :math:`\\beta` is the surface tilt.

    View factors are integrated separately over shaded and unshaded portions
    of the row slant height.

    """
    # handle Series inputs
    surface_tilt = np.array(surface_tilt)
    cst = cosd(surface_tilt)
    # shaded portion
    x = np.linspace(0, f_x, num=npoints)
    psi_t_shaded = masking_angle(surface_tilt, gcr, x)
    y = 0.5 * (cosd(psi_t_shaded) + cst)
    # integrate view factors from each point in the discretization. This is an
    # improvement over the algorithm described in [2]
    vf_shade_sky_integ = np.trapz(y, x, axis=0)
    # unshaded portion
    x = np.linspace(f_x, 1., num=npoints)
    psi_t_unshaded = masking_angle(surface_tilt, gcr, x)
    y = 0.5 * (cosd(psi_t_unshaded) + cst)
    vf_noshade_sky_integ = np.trapz(y, x, axis=0)
    return vf_shade_sky_integ, vf_noshade_sky_integ
Ejemplo n.º 9
0
def _vf_ground_sky_2d(x, rotation, gcr, pitch, height, max_rows=10):
    r"""
    Calculate the fraction of the sky dome visible from point x on the ground.

    The view factor accounts for the obstruction of the sky by array rows that
    are assumed to be infinitely long.  View factors are thus calculated in
    a 2D geometry. The ground is assumed to be flat and level.

    Parameters
    ----------
    x : numeric
        Position on the ground between two rows, as a fraction of the pitch.
        x = 0 corresponds to the point on the ground directly below the
        center point of a row. Positive x is towards the right. [unitless]
    rotation : float
        Rotation angle of the row's right edge relative to row center.
        [degree]
    gcr : float
        Ratio of the row slant length to the row spacing (pitch). [unitless]
    height : float
        Height of the center point of the row above the ground; must be in the
        same units as ``pitch``.
    pitch : float
        Distance between two rows; must be in the same units as ``height``.
    max_rows : int, default 10
        Maximum number of rows to consider on either side of the current
        row. [unitless]

    Returns
    -------
    vf : numeric
        Fraction of sky dome visible from each point on the ground. [unitless]
    wedge_angles : array
        Angles defining each wedge of sky that is blocked by a row. Shape is
        (2, len(x), 2*max_rows+1). ``wedge_angles[0,:,:]`` is the
        starting angle of each wedge, ``wedge_angles[1,:,:]`` is the end angle.
        [degree]
    """
    x = np.atleast_1d(x)  # handle float
    all_k = np.arange(-max_rows, max_rows + 1)
    width = gcr * pitch / 2.
    # angles from x to right edge of each row
    a1 = height + width * sind(rotation)
    b1 = (all_k - x[:, np.newaxis]) * pitch + width * cosd(rotation)
    phi_1 = np.degrees(np.arctan2(a1, b1))
    # angles from x to left edge of each row
    a2 = height - width * sind(rotation)
    b2 = (all_k - x[:, np.newaxis]) * pitch - width * cosd(rotation)
    phi_2 = np.degrees(np.arctan2(a2, b2))
    phi = np.stack([phi_1, phi_2])
    swap = phi[0, :, :] > phi[1, :, :]
    # swap where phi_1 > phi_2 so that phi_1[0,:,:] is the lesser angle
    phi = np.where(swap, phi[::-1], phi)
    # right edge of next row - left edge of previous row
    wedge_vfs = 0.5 * (cosd(phi[1, :, 1:]) - cosd(phi[0, :, :-1]))
    vf = np.sum(np.where(wedge_vfs > 0, wedge_vfs, 0.), axis=1)
    return vf, phi
Ejemplo n.º 10
0
def _coords_from_center_tilt_length(xy_center, tilt, length, surface_azimuth,
                                    axis_azimuth):
    """Calculate ``shapely`` :py:class:`LineString` coordinates from
    center coords, surface angles and length of line.
    The axis azimuth indicates the axis of rotation of the pvrows (if single-
    axis trackers). In the 2D plane, the axis of rotation will be the vector
    normal to that 2D plane and going into the 2D plane (when plotting it).
    The surface azimuth should always be 90 degrees away from the axis azimuth,
    either in the positive or negative direction.
    For instance, a single axis trk with axis azimuth = 0 deg (North), will
    have surface azimuth values equal to 90 deg (East) or 270 deg (West).
    Tilt angles need to always be positive. Given the axis azimuth and surface
    azimuth, a rotation angle will be derived. Positive rotation angles will
    indicate pvrows pointing to the left, and negative rotation angles will
    indicate pvrows pointing to the right (no matter what the the axis azimuth
    is).
    All of these conventions are necessary to make sure that no matter what
    the tilt and surface angles are, we can still identify correctly
    the same pv rows: the leftmost PV row will have index 0, and the rightmost
    will have index -1.

    Parameters
    ----------
    xy_center : tuple
        x, y coordinates of center point of desired linestring
    tilt : float or np.ndarray
        Surface tilt angles desired [deg]. Values should all be positive.
    length : float
        desired length of linestring [m]
    surface_azimuth : float or np.ndarray
        Surface azimuth angles of PV surface [deg]
    axis_azimuth : float
        Axis azimuth of the PV surface, i.e. direction of axis of rotation
        [deg]

    Returns
    -------
    list
        List of linestring coordinates obtained from inputs (could be vectors)
        in the form of [[x1, y1], [x2, y2]], where xi and yi could be arrays
        or scalar values.
    """
    # PV row params
    x_center, y_center = xy_center
    radius = length / 2.
    # Get rotation
    rotation = _get_rotation_from_tilt_azimuth(surface_azimuth, axis_azimuth,
                                               tilt)
    # Calculate coords
    x1 = radius * cosd(rotation + 180.) + x_center
    y1 = radius * sind(rotation + 180.) + y_center
    x2 = radius * cosd(rotation) + x_center
    y2 = radius * sind(rotation) + y_center

    return [[x1, y1], [x2, y2]]
Ejemplo n.º 11
0
def calc_surface_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0):
    """
    Calculate the surface tilt and azimuth angles for a given tracker rotation.

    Parameters
    ----------
    tracker_theta : numeric
        Tracker rotation angle as a right-handed rotation around
        the axis defined by ``axis_tilt`` and ``axis_azimuth``.  For example,
        with ``axis_tilt=0`` and ``axis_azimuth=180``, ``tracker_theta > 0``
        results in ``surface_azimuth`` to the West while ``tracker_theta < 0``
        results in ``surface_azimuth`` to the East. [degree]
    axis_tilt : float, default 0
        The tilt of the axis of rotation with respect to horizontal. [degree]
    axis_azimuth : float, default 0
        A value denoting the compass direction along which the axis of
        rotation lies. Measured east of north. [degree]

    Returns
    -------
    dict or DataFrame
        Contains keys ``'surface_tilt'`` and ``'surface_azimuth'`` representing
        the module orientation accounting for tracker rotation and axis
        orientation. [degree]

    References
    ----------
    .. [1] William F. Marion and Aron P. Dobos, "Rotation Angle for the Optimum
       Tracking of One-Axis Trackers", Technical Report NREL/TP-6A20-58891,
       July 2013. :doi:`10.2172/1089596`
    """
    with np.errstate(invalid='ignore', divide='ignore'):
        surface_tilt = acosd(cosd(tracker_theta) * cosd(axis_tilt))

        # clip(..., -1, +1) to prevent arcsin(1 + epsilon) issues:
        azimuth_delta = asind(np.clip(sind(tracker_theta) / sind(surface_tilt),
                                      a_min=-1, a_max=1))
        # Combine Eqs 2, 3, and 4:
        azimuth_delta = np.where(abs(tracker_theta) < 90,
                                 azimuth_delta,
                                 -azimuth_delta + np.sign(tracker_theta) * 180)
        # handle surface_tilt=0 case:
        azimuth_delta = np.where(sind(surface_tilt) != 0, azimuth_delta, 90)
        surface_azimuth = (axis_azimuth + azimuth_delta) % 360

    out = {
        'surface_tilt': surface_tilt,
        'surface_azimuth': surface_azimuth,
    }
    if hasattr(tracker_theta, 'index'):
        out = pd.DataFrame(out)
    return out
Ejemplo n.º 12
0
def king(surf_tilt, DHI, GHI, sun_zen):
    '''
    Determine diffuse irradiance from the sky on a tilted surface using the
    King model.

    King's model determines the diffuse irradiance from the sky
    (ground reflected irradiance is not included in this algorithm) on a
    tilted surface using the surface tilt angle, diffuse horizontal
    irradiance, global horizontal irradiance, and sun zenith angle. Note
    that this model is not well documented and has not been published in
    any fashion (as of January 2012).

    Parameters
    ----------

    surf_tilt : float or Series
          Surface tilt angles in decimal degrees.
          The tilt angle is defined as
          degrees from horizontal (e.g. surface facing up = 0, surface facing
          horizon = 90)

    DHI : float or Series
          diffuse horizontal irradiance in W/m^2.

    GHI : float or Series
          global horizontal irradiance in W/m^2.

    sun_zen : float or Series
          apparent (refraction-corrected) zenith
          angles in decimal degrees.

    Returns
    --------

    SkyDiffuse : float or Series

            the diffuse component of the solar radiation  on an
            arbitrarily tilted surface as given by a model developed by
            David L. King at Sandia National Laboratories.
    '''

    pvl_logger.debug('diffuse_sky.king()')

    sky_diffuse = (DHI * ((1 + tools.cosd(surf_tilt))) / 2 + GHI *
                   ((0.012 * sun_zen - 0.04)) *
                   ((1 - tools.cosd(surf_tilt))) / 2)
    sky_diffuse[sky_diffuse < 0] = 0

    return sky_diffuse
Ejemplo n.º 13
0
def king(surf_tilt, DHI, GHI, sun_zen):
    '''
    Determine diffuse irradiance from the sky on a tilted surface using the
    King model.

    King's model determines the diffuse irradiance from the sky
    (ground reflected irradiance is not included in this algorithm) on a
    tilted surface using the surface tilt angle, diffuse horizontal
    irradiance, global horizontal irradiance, and sun zenith angle. Note
    that this model is not well documented and has not been published in
    any fashion (as of January 2012).

    Parameters
    ----------

    surf_tilt : float or Series
          Surface tilt angles in decimal degrees.
          The tilt angle is defined as
          degrees from horizontal (e.g. surface facing up = 0, surface facing
          horizon = 90)

    DHI : float or Series
          diffuse horizontal irradiance in W/m^2.

    GHI : float or Series
          global horizontal irradiance in W/m^2.

    sun_zen : float or Series
          apparent (refraction-corrected) zenith
          angles in decimal degrees.

    Returns
    --------

    SkyDiffuse : float or Series

            the diffuse component of the solar radiation  on an
            arbitrarily tilted surface as given by a model developed by
            David L. King at Sandia National Laboratories.
    '''

    pvl_logger.debug('diffuse_sky.king()')

    sky_diffuse = (DHI * ((1 + tools.cosd(surf_tilt))) / 2 + GHI *
                   ((0.012 * sun_zen - 0.04)) *
                   ((1 - tools.cosd(surf_tilt))) / 2)
    sky_diffuse[sky_diffuse < 0] = 0

    return sky_diffuse
Ejemplo n.º 14
0
def haurwitz(apparent_zenith):
    """Caluclate global horizontal irradiance from apparent zenith angle of sun
    using Haurwitz method.
    
    Parameters
    ----------
    apparent_zenith : array_like
        Apparent zenith angle of sun in degrees

    Returns
    -------
    array_like
        Global horizontal irradiance
        
    Notes
    -----
    Based on `pvlib.clearsky.haurwitz`
    """
    cos_zenith = tools.cosd(apparent_zenith)
    clearsky_ghi = np.zeros_like(apparent_zenith)
    cos_zen_gte_0 = cos_zenith > 0
    clearsky_ghi[cos_zen_gte_0] = (1098.0 * cos_zenith[cos_zen_gte_0] *
                                   np.exp(-0.059 / cos_zenith[cos_zen_gte_0]))

    return clearsky_ghi
Ejemplo n.º 15
0
def test_noct_sam_against_sam():
    # test is constructed to reproduce output from SAM v2020.11.29.
    # SAM calculation is the default Detailed PV System model (CEC diode model,
    # NOCT cell temperature model), with the only change being the soiling
    # loss is set to 0. Weather input is TMY3 for Phoenix AZ.
    # Values are taken from the Jan 1 12:00:00 timestamp.
    poa_total, temp_air, wind_speed, noct, module_efficiency = (
        860.673, 25, 3, 46.4, 0.20551)
    poa_total_after_refl = 851.458  # from SAM output
    # compute effective irradiance
    # spectral loss coefficients fixed in lib_cec6par.cpp
    a = np.flipud([0.918093, 0.086257, -0.024459, 0.002816, -0.000126])
    # reproduce SAM air mass calculation
    zen = 56.4284
    elev = 358
    air_mass = 1. / (tools.cosd(zen) + 0.5057 * (96.080 - zen)**-1.634)
    air_mass *= np.exp(-0.0001184 * elev)
    f1 = np.polyval(a, air_mass)
    effective_irradiance = f1 * poa_total_after_refl
    transmittance_absorptance = 0.9
    array_height = 1
    mount_standoff = 4.0
    result = temperature.noct_sam(poa_total, temp_air, wind_speed, noct,
                                  module_efficiency, effective_irradiance,
                                  transmittance_absorptance, array_height,
                                  mount_standoff)
    expected = 43.0655
    # rtol from limited SAM output precision
    assert_allclose(result, expected, rtol=1e-5)
Ejemplo n.º 16
0
def _solar_projection_tangent(solar_zenith, solar_azimuth, surface_azimuth):
    """
    Tangent of the angle between the zenith vector and the sun vector
    projected to the plane defined by the zenith vector and surface_azimuth.

    .. math::
        \\tan \\phi = \\cos\\left(\\text{solar azimuth}-\\text{system azimuth}
        \\right)\\tan\\left(\\text{solar zenith}\\right)

    Parameters
    ----------
    solar_zenith : numeric
        Solar zenith angle. [degree].
    solar_azimuth : numeric
        Solar azimuth. [degree].
    surface_azimuth : numeric
        Azimuth of the module surface, i.e., North=0, East=90, South=180,
        West=270. [degree]

    Returns
    -------
    tan_phi : numeric
        Tangent of the angle between vertical and the projection of the
        sun direction onto the YZ plane.
    """
    rotation = solar_azimuth - surface_azimuth
    tan_phi = cosd(rotation) * tand(solar_zenith)
    return tan_phi
Ejemplo n.º 17
0
    def _create_shaded_side_coords(xy_center, width, shaded_length,
                                   mask_tilted_to_left, rotation_vec,
                                   side_lowest_pt):
        """
        Create the timeseries line coordinates for the shaded portion of a
        PV row side, based on inputted shaded length.

        Parameters
        ----------
        xy_center : tuple of float
            x and y coordinates of the PV row center point (invariant)
        width : float
            width of the PV rows [m]
        shaded_length : np.ndarray
            Timeseries values of side shaded length [m]
        tilted_to_left : list of bool
            Flags indicating when the PV rows are strictly tilted to the left
        rotation_vec : np.ndarray
            Timeseries rotation vector of the PV rows in [deg]
        side_lowest_pt : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords`
            Timeseries coordinates of lowest point of considered PV row side

        Returns
        -------
        side_shaded_coords : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords`
            Timeseries coordinates of the shaded portion of the PV row side
        """

        # Get invariant values
        x_center, y_center = xy_center
        radius = width / 2.

        # Calculate coords of shading point
        r_shade = radius - shaded_length
        x_sh = np.where(
            mask_tilted_to_left,
            r_shade * cosd(rotation_vec + 180.) + x_center,
            r_shade * cosd(rotation_vec) + x_center)
        y_sh = np.where(
            mask_tilted_to_left,
            r_shade * sind(rotation_vec + 180.) + y_center,
            r_shade * sind(rotation_vec) + y_center)

        side_shaded_coords = TsLineCoords(TsPointCoords(x_sh, y_sh),
                                          side_lowest_pt)

        return side_shaded_coords
Ejemplo n.º 18
0
def isotropic(surf_tilt, DHI):
    r'''
    Determine diffuse irradiance from the sky on a
    tilted surface using the isotropic sky model.

    .. math::

       I_{d} = DHI \frac{1 + \cos\beta}{2}

    Hottel and Woertz's model treats the sky as a uniform source of diffuse
    irradiance. Thus the diffuse irradiance from the sky (ground reflected
    irradiance is not included in this algorithm) on a tilted surface can
    be found from the diffuse horizontal irradiance and the tilt angle of
    the surface.

    Parameters
    ----------

    surf_tilt : float or Series
        Surface tilt angle in decimal degrees.
        surf_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)

    DHI : float or Series
        Diffuse horizontal irradiance in W/m^2.
        DHI must be >=0.


    Returns
    -------
    float or Series

    The diffuse component of the solar radiation  on an
    arbitrarily tilted surface defined by the isotropic sky model as
    given in Loutzenhiser et. al (2007) equation 3.
    SkyDiffuse is the diffuse component ONLY and does not include the ground
    reflected irradiance or the irradiance due to the beam.
    SkyDiffuse is a column vector vector with a number of elements equal to
    the input vector(s).


    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] Hottel, H.C., Woertz, B.B., 1942. Evaluation of flat-plate solar heat
    collector. Trans. ASME 64, 91.
    '''

    pvl_logger.debug('diffuse_sky.isotropic()')

    sky_diffuse = DHI * (1 + tools.cosd(surf_tilt)) * 0.5

    return sky_diffuse
Ejemplo n.º 19
0
def _shaded_fraction(solar_zenith, solar_azimuth, surface_tilt,
                     surface_azimuth, gcr):
    """
    Calculate fraction (from the bottom) of row slant height that is shaded
    from direct irradiance by the row in front toward the sun.

    See [1], Eq. 14 and also [2], Eq. 32.

    .. math::
        F_x = \\max \\left( 0, \\min \\left(\\frac{\\text{GCR} \\cos \\theta
        + \\left( \\text{GCR} \\sin \\theta - \\tan \\beta_{c} \\right)
        \\tan Z - 1}
        {\\text{GCR} \\left( \\cos \\theta + \\sin \\theta \\tan Z \\right)},
        1 \\right) \\right)

    Parameters
    ----------
    solar_zenith : numeric
        Apparent (refraction-corrected) solar zenith. [degrees]
    solar_azimuth : numeric
        Solar azimuth. [degrees]
    surface_tilt : numeric
        Row tilt from horizontal, e.g. surface facing up = 0, surface facing
        horizon = 90. [degrees]
    surface_azimuth : numeric
        Azimuth angle of the row surface. North=0, East=90, South=180,
        West=270. [degrees]
    gcr : numeric
        Ground coverage ratio, which is the ratio of row slant length to row
        spacing (pitch). [unitless]

    Returns
    -------
    f_x : numeric
        Fraction of row slant height from the bottom that is shaded from
        direct irradiance.

    References
    ----------
    .. [1] Mikofski, M., Darawali, R., Hamer, M., Neubert, A., and Newmiller,
       J. "Bifacial Performance Modeling in Large Arrays". 2019 IEEE 46th
       Photovoltaic Specialists Conference (PVSC), 2019, pp. 1282-1287.
       :doi:`10.1109/PVSC40753.2019.8980572`.
    .. [2] Kevin Anderson and Mark Mikofski, "Slope-Aware Backtracking for
       Single-Axis Trackers", Technical Report NREL/TP-5K00-76626, July 2020.
       https://www.nrel.gov/docs/fy20osti/76626.pdf
    """
    tan_phi = utils._solar_projection_tangent(solar_zenith, solar_azimuth,
                                              surface_azimuth)
    # length of shadow behind a row as a fraction of pitch
    x = gcr * (sind(surface_tilt) * tan_phi + cosd(surface_tilt))
    f_x = 1 - 1. / x
    # set f_x to be 1 when sun is behind the array
    ao = aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth)
    f_x = np.where(ao < 90, f_x, 1.)
    # when x < 1, the shadow is not long enough to fall on the row surface
    f_x = np.where(x > 1., f_x, 0.)
    return f_x
Ejemplo n.º 20
0
def isotropic(surf_tilt, DHI):
    r'''
    Determine diffuse irradiance from the sky on a
    tilted surface using the isotropic sky model.

    .. math::

       I_{d} = DHI \frac{1 + \cos\beta}{2}

    Hottel and Woertz's model treats the sky as a uniform source of diffuse
    irradiance. Thus the diffuse irradiance from the sky (ground reflected
    irradiance is not included in this algorithm) on a tilted surface can
    be found from the diffuse horizontal irradiance and the tilt angle of
    the surface.

    Parameters
    ----------

    surf_tilt : float or Series
        Surface tilt angle in decimal degrees.
        surf_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)

    DHI : float or Series
        Diffuse horizontal irradiance in W/m^2.
        DHI must be >=0.


    Returns
    -------
    float or Series

    The diffuse component of the solar radiation  on an
    arbitrarily tilted surface defined by the isotropic sky model as
    given in Loutzenhiser et. al (2007) equation 3.
    SkyDiffuse is the diffuse component ONLY and does not include the ground
    reflected irradiance or the irradiance due to the beam.
    SkyDiffuse is a column vector vector with a number of elements equal to
    the input vector(s).


    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] Hottel, H.C., Woertz, B.B., 1942. Evaluation of flat-plate solar heat
    collector. Trans. ASME 64, 91.
    '''

    pvl_logger.debug('diffuse_sky.isotropic()')

    sky_diffuse = DHI * (1 + tools.cosd(surf_tilt)) * 0.5

    return sky_diffuse
Ejemplo n.º 21
0
def _unshaded_ground_fraction(surface_tilt,
                              surface_azimuth,
                              solar_zenith,
                              solar_azimuth,
                              gcr,
                              max_zenith=87):
    r"""
    Calculate the fraction of the ground with incident direct irradiance.

    .. math::
        F_{gnd,sky} = 1 - \min{\left(1, \text{GCR} \left|\cos \beta +
        \sin \beta \tan \phi \right|\right)}

    where :math:`\beta` is the surface tilt and :math:`\phi` is the angle
    from vertical of the sun vector projected to a vertical plane that
    contains the row azimuth `surface_azimuth`.

    Parameters
    ----------
    surface_tilt : numeric
        Surface tilt angle. The tilt angle is defined as
        degrees from horizontal, e.g., surface facing up = 0, surface facing
        horizon = 90. [degree]
    surface_azimuth : numeric
        Azimuth of the module surface, i.e., North=0, East=90, South=180,
        West=270. [degree]
    solar_zenith : numeric
        Solar zenith angle. [degree].
    solar_azimuth : numeric
        Solar azimuth. [degree].
    gcr : float
        Ground coverage ratio, which is the ratio of row slant length to row
        spacing (pitch). [unitless]
    max_zenith : numeric, default 87
        Maximum zenith angle. For solar_zenith > max_zenith, unshaded ground
        fraction is set to 0. [degree]

    Returns
    -------
    f_gnd_beam : numeric
        Fraction of distance betwen rows (pitch) with direct irradiance
        (unshaded). [unitless]

    References
    ----------
    .. [1] Mikofski, M., Darawali, R., Hamer, M., Neubert, A., and Newmiller,
       J. "Bifacial Performance Modeling in Large Arrays". 2019 IEEE 46th
       Photovoltaic Specialists Conference (PVSC), 2019, pp. 1282-1287.
       doi: 10.1109/PVSC40753.2019.8980572.
    """
    tan_phi = _solar_projection_tangent(solar_zenith, solar_azimuth,
                                        surface_azimuth)
    f_gnd_beam = 1.0 - np.minimum(
        1.0, gcr * np.abs(cosd(surface_tilt) + sind(surface_tilt) * tan_phi))
    np.where(solar_zenith > max_zenith, 0., f_gnd_beam)  # [1], Eq. 4
    return f_gnd_beam  # 1 - min(1, abs()) < 1 always
Ejemplo n.º 22
0
def haurwitz(ApparentZenith):
    '''
    Determine clear sky GHI from Haurwitz model
   
    Implements the Haurwitz clear sky model for global horizontal
    irradiance (GHI) as presented in [1, 2]. A report on clear
    sky models found the Haurwitz model to have the best performance of
    models which require only zenith angle [3]. Extreme care should
    be taken in the interpretation of this result! 

    Parameters
    ----------
    ApparentZenith : DataFrame
        The apparent (refraction corrected) sun zenith angle
        in degrees.

    Returns
    -------        
    pd.Series. The modeled global horizonal irradiance in W/m^2 provided
    by the Haurwitz clear-sky model.

    Initial implementation of this algorithm by Matthew Reno.

    References
    ----------

    [1] B. Haurwitz, "Insolation in Relation to Cloudiness and Cloud 
     Density," Journal of Meteorology, vol. 2, pp. 154-166, 1945.

    [2] B. Haurwitz, "Insolation in Relation to Cloud Type," Journal of 
     Meteorology, vol. 3, pp. 123-124, 1946.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
     Sky Models: Implementation and Analysis", Sandia National
     Laboratories, SAND2012-2389, 2012.

    See Also
    ---------
    maketimestruct    
    makelocationstruct   
    ephemeris   
    spa
    ineichen
    '''

    cos_zenith = tools.cosd(ApparentZenith)

    clearsky_GHI = 1098.0 * cos_zenith * np.exp(-0.059 / cos_zenith)

    clearsky_GHI[clearsky_GHI < 0] = 0

    df_out = pd.DataFrame({'GHI': clearsky_GHI})

    return df_out
Ejemplo n.º 23
0
    def _vf(aoi_1, aoi_2):
        """Calculate view factor from infinitesimal surface to infinite band.

        See illustration: http://www.thermalradiation.net/sectionb/B-71.html
        Here we're using angles measured from the horizontal

        Parameters
        ----------
        aoi_1 : np.ndarray
            Lower angles defining the infinite band
        aoi_2 : np.ndarray
            Higher angles defining the infinite band

        Returns
        -------
        np.ndarray
            View factors from infinitesimal surface to infinite strip

        """
        return 0.5 * np.abs(cosd(aoi_1) - cosd(aoi_2))
Ejemplo n.º 24
0
def haurwitz(ApparentZenith):
    """
    Determine clear sky GHI from Haurwitz model
   
    Implements the Haurwitz clear sky model for global horizontal
    irradiance (GHI) as presented in [1, 2]. A report on clear
    sky models found the Haurwitz model to have the best performance of
    models which require only zenith angle [3]. Extreme care should
    be taken in the interpretation of this result! 

    Parameters
    ----------
    ApparentZenith : DataFrame
        The apparent (refraction corrected) sun zenith angle
        in degrees.

    Returns
    -------        
    pd.Series. The modeled global horizonal irradiance in W/m^2 provided
    by the Haurwitz clear-sky model.

    Initial implementation of this algorithm by Matthew Reno.

    References
    ----------

    [1] B. Haurwitz, "Insolation in Relation to Cloudiness and Cloud 
     Density," Journal of Meteorology, vol. 2, pp. 154-166, 1945.

    [2] B. Haurwitz, "Insolation in Relation to Cloud Type," Journal of 
     Meteorology, vol. 3, pp. 123-124, 1946.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
     Sky Models: Implementation and Analysis", Sandia National
     Laboratories, SAND2012-2389, 2012.

    See Also
    ---------
    maketimestruct    
    makelocationstruct   
    ephemeris   
    spa
    ineichen
    """

    cos_zenith = tools.cosd(ApparentZenith)

    clearsky_GHI = 1098.0 * cos_zenith * np.exp(-0.059 / cos_zenith)

    clearsky_GHI[clearsky_GHI < 0] = 0

    df_out = pd.DataFrame({"GHI": clearsky_GHI})

    return df_out
Ejemplo n.º 25
0
def test__vf_row_ground_integ(test_system):
    ts, _, _ = test_system
    gcr = ts['gcr']
    surface_tilt = ts['surface_tilt']
    f_x = np.array([0., 0.5, 1.0])
    shaded, noshade = infinite_sheds._vf_row_ground_integ(
        f_x, surface_tilt, gcr)

    def analytic(x, surface_tilt, gcr):
        c = cosd(surface_tilt)
        a = 1. / gcr
        dx = np.sqrt(a**2 + 2 * a * c * x + x**2)
        return c * dx - a * (c**2 - 1) * np.arctanh((a * c + x) / dx)

    expected_shade = 0.5 * (analytic(f_x, surface_tilt, gcr) - analytic(
        0., surface_tilt, gcr) - f_x * cosd(surface_tilt))
    expected_noshade = 0.5 * (analytic(1., surface_tilt, gcr) -
                              analytic(f_x, surface_tilt, gcr) -
                              (1. - f_x) * cosd(surface_tilt))
    assert np.allclose(shaded, expected_shade)
    assert np.allclose(noshade, expected_noshade)
Ejemplo n.º 26
0
def _calc_beta_c(v, dg, ba):
    """
    Calculate the cross-axis tilt angle.

    Parameters
    ----------
    v : tuple
        tracker normal
    dg : float
        delta gamma, difference between axis and ground azimuths [degrees]
    ba : float
        axis tilt [degrees]

    Returns
    -------
    beta_c : float
        cross-axis tilt angle [radians]
    """
    vnorm = np.sqrt(np.dot(v, v))
    beta_c = np.arcsin(
        ((v[0]*cosd(dg) - v[1]*sind(dg)) * sind(ba) + v[2]*cosd(ba)) / vnorm)
    return beta_c
Ejemplo n.º 27
0
def haurwitz(apparent_zenith):
    '''
    Determine clear sky GHI from Haurwitz model.

    Implements the Haurwitz clear sky model for global horizontal
    irradiance (GHI) as presented in [1, 2]. A report on clear
    sky models found the Haurwitz model to have the best performance
    in terms of average monthly error among models which require only
    zenith angle [3].

    Parameters
    ----------
    apparent_zenith : Series
        The apparent (refraction corrected) sun zenith angle
        in degrees.

    Returns
    -------
    pd.DataFrame
    The modeled global horizonal irradiance in W/m^2 provided
    by the Haurwitz clear-sky model.

    Initial implementation of this algorithm by Matthew Reno.

    References
    ----------

    [1] B. Haurwitz, "Insolation in Relation to Cloudiness and Cloud
     Density," Journal of Meteorology, vol. 2, pp. 154-166, 1945.

    [2] B. Haurwitz, "Insolation in Relation to Cloud Type," Journal of
     Meteorology, vol. 3, pp. 123-124, 1946.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
     Sky Models: Implementation and Analysis", Sandia National
     Laboratories, SAND2012-2389, 2012.
    '''

    cos_zenith = tools.cosd(apparent_zenith.values)
    clearsky_ghi = np.zeros_like(apparent_zenith.values)
    cos_zen_gte_0 = cos_zenith > 0
    clearsky_ghi[cos_zen_gte_0] = (1098.0 * cos_zenith[cos_zen_gte_0] *
                                   np.exp(-0.059/cos_zenith[cos_zen_gte_0]))

    df_out = pd.DataFrame(index=apparent_zenith.index,
                          data=clearsky_ghi,
                          columns=['ghi'])

    return df_out
Ejemplo n.º 28
0
def haurwitz(apparent_zenith):
    '''
    Determine clear sky GHI from Haurwitz model.

    Implements the Haurwitz clear sky model for global horizontal
    irradiance (GHI) as presented in [1, 2]. A report on clear
    sky models found the Haurwitz model to have the best performance
    in terms of average monthly error among models which require only
    zenith angle [3].

    Parameters
    ----------
    apparent_zenith : Series
        The apparent (refraction corrected) sun zenith angle
        in degrees.

    Returns
    -------
    pd.DataFrame
    The modeled global horizonal irradiance in W/m^2 provided
    by the Haurwitz clear-sky model.

    Initial implementation of this algorithm by Matthew Reno.

    References
    ----------

    [1] B. Haurwitz, "Insolation in Relation to Cloudiness and Cloud
     Density," Journal of Meteorology, vol. 2, pp. 154-166, 1945.

    [2] B. Haurwitz, "Insolation in Relation to Cloud Type," Journal of
     Meteorology, vol. 3, pp. 123-124, 1946.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
     Sky Models: Implementation and Analysis", Sandia National
     Laboratories, SAND2012-2389, 2012.
    '''

    cos_zenith = tools.cosd(apparent_zenith.values)
    clearsky_ghi = np.zeros_like(apparent_zenith.values)
    cos_zen_gte_0 = cos_zenith > 0
    clearsky_ghi[cos_zen_gte_0] = (1098.0 * cos_zenith[cos_zen_gte_0] *
                                   np.exp(-0.059/cos_zenith[cos_zen_gte_0]))

    df_out = pd.DataFrame(index=apparent_zenith.index,
                          data=clearsky_ghi,
                          columns=['ghi'])

    return df_out
Ejemplo n.º 29
0
def masking_angle(surface_tilt, gcr, slant_height):
    """
    The elevation angle below which diffuse irradiance is blocked.

    The ``height`` parameter determines how far up the module's surface to
    evaluate the masking angle.  The lower the point, the steeper the masking
    angle [1]_.  SAM uses a "worst-case" approach where the masking angle
    is calculated for the bottom of the array (i.e. ``slant_height=0``) [2]_.

    Parameters
    ----------
    surface_tilt : numeric
        Panel tilt from horizontal [degrees].

    gcr : float
        The ground coverage ratio of the array [unitless].

    slant_height : numeric
        The distance up the module's slant height to evaluate the masking
        angle, as a fraction [0-1] of the module slant height [unitless].

    Returns
    -------
    mask_angle : numeric
        Angle from horizontal where diffuse light is blocked by the
        preceding row [degrees].

    See Also
    --------
    masking_angle_passias
    sky_diffuse_passias

    References
    ----------
    .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell
       panels", Solar Cells, Volume 11, Pages 281-291.  1984.
       DOI: 10.1016/0379-6787(84)90017-6
    .. [2] Gilman, P. et al., (2018). "SAM Photovoltaic Model Technical
       Reference Update", NREL Technical Report NREL/TP-6A20-67399.
       Available at https://www.nrel.gov/docs/fy18osti/67399.pdf
    """
    # The original equation (8 in [1]) requires pitch and collector width,
    # but it's easy to non-dimensionalize it to make it a function of GCR
    # by factoring out B from the argument to arctan.
    numerator = (1 - slant_height) * sind(surface_tilt)
    denominator = 1/gcr - (1 - slant_height) * cosd(surface_tilt)
    phi = np.arctan(numerator / denominator)
    return np.degrees(phi)
Ejemplo n.º 30
0
def sky_diffuse_passias(masking_angle):
    r"""
    The diffuse irradiance loss caused by row-to-row sky diffuse shading.

    Even when the sun is high in the sky, a row's view of the sky dome will
    be partially blocked by the row in front. This causes a reduction in the
    diffuse irradiance incident on the module. The reduction depends on the
    masking angle, the elevation angle from a point on the shaded module to
    the top of the shading row. In [1]_ the masking angle is calculated as
    the average across the module height. SAM assumes the "worst-case" loss
    where the masking angle is calculated for the bottom of the array [2]_.

    This function, as in [1]_, makes the assumption that sky diffuse
    irradiance is isotropic.

    Parameters
    ----------
    masking_angle : numeric
        The elevation angle below which diffuse irradiance is blocked
        [degrees].

    Returns
    -------
    derate : numeric
        The fraction [0-1] of blocked sky diffuse irradiance.

    See Also
    --------
    masking_angle
    masking_angle_passias

    References
    ----------
    .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell
       panels", Solar Cells, Volume 11, Pages 281-291.  1984.
       DOI: 10.1016/0379-6787(84)90017-6
    .. [2] Gilman, P. et al., (2018). "SAM Photovoltaic Model Technical
       Reference Update", NREL Technical Report NREL/TP-6A20-67399.
       Available at https://www.nrel.gov/docs/fy18osti/67399.pdf
    """
    return 1 - cosd(masking_angle/2)**2
Ejemplo n.º 31
0
def _ground_angle(x, surface_tilt, gcr):
    """
    Angle from horizontal of the line from a point x on the row slant length
    to the bottom of the facing row.

    The angles are clockwise from horizontal, rather than the usual
    counterclockwise direction.

    Parameters
    ----------
    x : numeric
        fraction of row slant length from bottom, ``x = 0`` is at the row
        bottom, ``x = 1`` is at the top of the row.
    surface_tilt : numeric
        Surface tilt angle in degrees from horizontal, e.g., surface facing up
        = 0, surface facing horizon = 90. [degree]
    gcr : float
        ground coverage ratio, ratio of row slant length to row spacing.
        [unitless]

    Returns
    -------
    psi : numeric
        Angle [degree].
    """
    #  : \\            \
    #  :  \\            \
    #  :   \\            \
    #  :    \\            \  facing row
    #  :     \\.___________\
    #  :       \  ^*-.  psi \
    #  :        \  x   *-.   \
    #  :         \  v      *-.\
    #  :          \<-----P---->\

    x1 = x * sind(surface_tilt)
    x2 = (x * cosd(surface_tilt) + 1 / gcr)
    psi = np.arctan2(x1, x2)  # do this first because it handles 0 / 0
    return np.rad2deg(psi)
Ejemplo n.º 32
0
def poa_horizontal_ratio(surf_tilt, surf_az, sun_zen, sun_az):
    """
    Calculates the ratio of the beam components of the
    plane of array irradiance and the horizontal irradiance.

    Input all angles in degrees.

    Parameters
    ==========

    surf_tilt : float or Series.
        Panel tilt from horizontal.
    surf_az : float or Series.
        Panel azimuth from north.
    sun_zen : float or Series.
        Solar zenith angle.
    sun_az : float or Series.
        Solar azimuth angle.

    Returns
    =======
    float or Series. Ratio of the plane of array irradiance to the
    horizontal plane irradiance
    """

    cos_poa_zen = aoi_projection(surf_tilt, surf_az, sun_zen, sun_az)

    cos_sun_zen = tools.cosd(sun_zen)

    # ratio of titled and horizontal beam irradiance
    ratio = cos_poa_zen / cos_sun_zen

    try:
        ratio.name = 'poa_ratio'
    except AttributeError:
        pass

    return ratio
Ejemplo n.º 33
0
def poa_horizontal_ratio(surf_tilt, surf_az, sun_zen, sun_az):
    """
    Calculates the ratio of the beam components of the
    plane of array irradiance and the horizontal irradiance.

    Input all angles in degrees.

    Parameters
    ==========

    surf_tilt : float or Series.
        Panel tilt from horizontal.
    surf_az : float or Series.
        Panel azimuth from north.
    sun_zen : float or Series.
        Solar zenith angle.
    sun_az : float or Series.
        Solar azimuth angle.

    Returns
    =======
    float or Series. Ratio of the plane of array irradiance to the
    horizontal plane irradiance
    """

    cos_poa_zen = aoi_projection(surf_tilt, surf_az, sun_zen, sun_az)

    cos_sun_zen = tools.cosd(sun_zen)

    # ratio of titled and horizontal beam irradiance
    ratio = cos_poa_zen / cos_sun_zen

    try:
        ratio.name = 'poa_ratio'
    except AttributeError:
        pass

    return ratio
Ejemplo n.º 34
0
def calc_axis_tilt(slope_azimuth, slope_tilt, axis_azimuth):
    """
    Calculate tracker axis tilt in the global reference frame when on a sloped
    plane.

    Parameters
    ----------
    slope_azimuth : float
        direction of normal to slope on horizontal [degrees]
    slope_tilt : float
        tilt of normal to slope relative to vertical [degrees]
    axis_azimuth : float
        direction of tracker axes on horizontal [degrees]

    Returns
    -------
    axis_tilt : float
        tilt of tracker [degrees]

    See also
    --------
    pvlib.tracking.singleaxis
    pvlib.tracking.calc_cross_axis_tilt

    Notes
    -----
    See [1]_ for derivation of equations.

    References
    ----------
    .. [1] Kevin Anderson and Mark Mikofski, "Slope-Aware Backtracking for
       Single-Axis Trackers", Technical Report NREL/TP-5K00-76626, July 2020.
       https://www.nrel.gov/docs/fy20osti/76626.pdf
    """
    delta_gamma = axis_azimuth - slope_azimuth
    # equations 18-19
    tan_axis_tilt = cosd(delta_gamma) * tand(slope_tilt)
    return np.degrees(np.arctan(tan_axis_tilt))
Ejemplo n.º 35
0
def physicaliam(K, L, n, aoi):
    '''
    Determine the incidence angle modifier using refractive 
    index, glazing thickness, and extinction coefficient

    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 upon a physical
    model of absorbtion and transmission through a cover. Required
    information includes, incident angle, cover extinction coefficient,
    cover thickness

    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(theta))

    Parameters
    ----------
    K : float
        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
        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
        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.
        
        Theta must be a numeric scalar or vector.
        For any values of theta where abs(aoi)>90, IAM is set to 0. For any
        values of aoi where -90 < aoi < 0, theta is set to abs(aoi) and
        evaluated.

    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
    '''
    thetar_deg = tools.asind(1.0 / n*(tools.sind(aoi)))

    tau = ( np.exp(- 1.0 * (K*L / tools.cosd(thetar_deg))) *
            ((1 - 0.5*((((tools.sind(thetar_deg - aoi)) ** 2) /
            ((tools.sind(thetar_deg + aoi)) ** 2) +
            ((tools.tand(thetar_deg - aoi)) ** 2) /
            ((tools.tand(thetar_deg + aoi)) ** 2))))) )
    
    zeroang = 1e-06
    
    thetar_deg0 = tools.asind(1.0 / n*(tools.sind(zeroang)))
    
    tau0 = ( np.exp(- 1.0 * (K*L / tools.cosd(thetar_deg0))) *
             ((1 - 0.5*((((tools.sind(thetar_deg0 - zeroang)) ** 2) /
             ((tools.sind(thetar_deg0 + zeroang)) ** 2) +
             ((tools.tand(thetar_deg0 - zeroang)) ** 2) /
             ((tools.tand(thetar_deg0 + zeroang)) ** 2))))) )
    
    IAM = tau / tau0
    
    IAM[abs(aoi) >= 90] = np.nan
    IAM[IAM < 0] = np.nan
    
    return IAM
Ejemplo n.º 36
0
def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
             altitude=0, dni_extra=1364., perez_enhancement=False):
    '''
    Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model.

    Implements the Ineichen and Perez clear sky model for global
    horizontal irradiance (GHI), direct normal irradiance (DNI), and
    calculates the clear-sky diffuse horizontal (DHI) component as the
    difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A
    report on clear sky models found the Ineichen/Perez model to have
    excellent performance with a minimal input data set [3].

    Default values for monthly Linke turbidity provided by SoDa [4, 5].

    Parameters
    -----------
    apparent_zenith : numeric
        Refraction corrected solar zenith angle in degrees.

    airmass_absolute : numeric
        Pressure corrected airmass.

    linke_turbidity : numeric
        Linke Turbidity.

    altitude : numeric, default 0
        Altitude above sea level in meters.

    dni_extra : numeric, default 1364
        Extraterrestrial irradiance. The units of ``dni_extra``
        determine the units of the output.

    perez_enhancement : bool, default False
        Controls if the Perez enhancement factor should be applied.
        Setting to True may produce spurious results for times when
        the Sun is near the horizon and the airmass is high.
        See https://github.com/pvlib/pvlib-python/issues/435

    Returns
    -------
    clearsky : DataFrame (if Series input) or OrderedDict of arrays
        DataFrame/OrderedDict contains the columns/keys
        ``'dhi', 'dni', 'ghi'``.

    See also
    --------
    lookup_linke_turbidity
    pvlib.location.Location.get_clearsky

    References
    ----------
    [1] P. Ineichen and R. Perez, "A New airmass independent formulation for
        the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157,
        2002.

    [2] R. Perez et. al., "A New Operational Model for Satellite-Derived
        Irradiances: Description and Validation", Solar Energy, vol 73, pp.
        307-317, 2002.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
        Sky Models: Implementation and Analysis", Sandia National
        Laboratories, SAND2012-2389, 2012.

    [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained
        July 17, 2012).

    [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc.
        ISES Solar World Congress, June 2003. Goteborg, Sweden.
    '''

    # ghi is calculated using either the equations in [1] by setting
    # perez_enhancement=False (default behavior) or using the model
    # in [2] by setting perez_enhancement=True.

    # The NaN handling is a little subtle. The AM input is likely to
    # have NaNs that we'll want to map to 0s in the output. However, we
    # want NaNs in other inputs to propagate through to the output. This
    # is accomplished by judicious use and placement of np.maximum,
    # np.minimum, and np.fmax

    # use max so that nighttime values will result in 0s instead of
    # negatives. propagates nans.
    cos_zenith = np.maximum(tools.cosd(apparent_zenith), 0)

    tl = linke_turbidity

    fh1 = np.exp(-altitude/8000.)
    fh2 = np.exp(-altitude/1250.)
    cg1 = 5.09e-05 * altitude + 0.868
    cg2 = 3.92e-05 * altitude + 0.0387

    ghi = np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1)))

    # https://github.com/pvlib/pvlib-python/issues/435
    if perez_enhancement:
        ghi *= np.exp(0.01*airmass_absolute**1.8)

    # use fmax to map airmass nans to 0s. multiply and divide by tl to
    # reinsert tl nans
    ghi = cg1 * dni_extra * cos_zenith * tl / tl * np.fmax(ghi, 0)

    # BncI = "normal beam clear sky radiation"
    b = 0.664 + 0.163/fh1
    bnci = b * np.exp(-0.09 * airmass_absolute * (tl - 1))
    bnci = dni_extra * np.fmax(bnci, 0)

    # "empirical correction" SE 73, 157 & SE 73, 312.
    bnci_2 = ((1 - (0.1 - 0.2*np.exp(-tl))/(0.1 + 0.882/fh1)) /
              cos_zenith)
    bnci_2 = ghi * np.fmin(np.fmax(bnci_2, 0), 1e20)

    dni = np.minimum(bnci, bnci_2)

    dhi = ghi - dni*cos_zenith

    irrads = OrderedDict()
    irrads['ghi'] = ghi
    irrads['dni'] = dni
    irrads['dhi'] = dhi

    if isinstance(dni, pd.Series):
        irrads = pd.DataFrame.from_dict(irrads)

    return irrads
Ejemplo n.º 37
0
    'surface_tilt': 60,
    'albedo': 0.2,
    }

# own location parameter
wittenberg = {
    'altitude': 34,
    'name': 'Wittenberg',
    'latitude': my_weather.latitude,
    'longitude': my_weather.longitude,
    }

# the following has been implemented in the pvlib ModelChain in the
# complete_irradiance method (pvlib version > v0.4.5)
if w.get('dni') is None:
    w['dni'] = (w.ghi - w.dhi) / cosd(
        Location(**wittenberg).get_solarposition(times).zenith)

# pvlib's ModelChain
mc = ModelChain(PVSystem(**yingli210),
                Location(**wittenberg),
                orientation_strategy='south_at_latitude_tilt')

mc.run_model(times, weather=w)

if plt:
    mc.dc.p_mp.fillna(0).plot()
    plt.show()
else:
    logging.warning("No plots shown. Install matplotlib to see the plots.")

logging.info('Done!')
Ejemplo n.º 38
0
def singleaxis(apparent_zenith,
               apparent_azimuth,
               axis_tilt=0,
               axis_azimuth=0,
               max_angle=90,
               backtrack=True,
               gcr=2.0 / 7.0):
    """
    Determine the rotation angle of a single axis tracker using the
    equations in [1] when given a particular sun zenith and azimuth
    angle. backtracking may be specified, and if so, a ground coverage
    ratio is required.

    Rotation angle is determined in a panel-oriented coordinate system.
    The tracker azimuth axis_azimuth defines the positive y-axis; the
    positive x-axis is 90 degress clockwise from the y-axis and parallel
    to the earth surface, and the positive z-axis is normal and oriented
    towards the sun. Rotation angle tracker_theta indicates tracker
    position relative to horizontal: tracker_theta = 0 is horizontal,
    and positive tracker_theta is a clockwise rotation around the y axis
    in the x, y, z coordinate system. For example, if tracker azimuth
    axis_azimuth is 180 (oriented south), tracker_theta = 30 is a
    rotation of 30 degrees towards the west, and tracker_theta = -90 is
    a rotation to the vertical plane facing east.

    Parameters
    ----------
    apparent_zenith : float, 1d array, or Series
        Solar apparent zenith angles in decimal degrees.

    apparent_azimuth : float, 1d array, or Series
        Solar apparent azimuth angles in decimal degrees.

    axis_tilt : float, default 0
        The tilt of the axis of rotation (i.e, the y-axis defined by
        axis_azimuth) with respect to horizontal, in decimal degrees.

    axis_azimuth : float, default 0
        A value denoting the compass direction along which the axis of
        rotation lies. Measured in decimal degrees East of North.

    max_angle : float, default 90
        A value denoting the maximum rotation angle, in decimal degrees,
        of the one-axis tracker from its horizontal position (horizontal
        if axis_tilt = 0). A max_angle of 90 degrees allows the tracker
        to rotate to a vertical position to point the panel towards a
        horizon. max_angle of 180 degrees allows for full rotation.

    backtrack : bool, default True
        Controls whether the tracker has the capability to "backtrack"
        to avoid row-to-row shading. False denotes no backtrack
        capability. True denotes backtrack capability.

    gcr : float, default 2.0/7.0
        A value denoting the ground coverage ratio of a tracker system
        which utilizes backtracking; i.e. the ratio between the PV array
        surface area to total ground area. A tracker system with modules
        2 meters wide, centered on the tracking axis, with 6 meters
        between the tracking axes has a gcr of 2/6=0.333. If gcr is not
        provided, a gcr of 2/7 is default. gcr must be <=1.

    Returns
    -------
    dict or DataFrame with the following columns:

    * tracker_theta: The rotation angle of the tracker.
        tracker_theta = 0 is horizontal, and positive rotation angles are
        clockwise.
    * aoi: The angle-of-incidence of direct irradiance onto the
        rotated panel surface.
    * surface_tilt: The angle between the panel surface and the earth
        surface, accounting for panel rotation.
    * surface_azimuth: The azimuth of the rotated panel, determined by
        projecting the vector normal to the panel's surface to the earth's
        surface.

    References
    ----------
    [1] Lorenzo, E et al., 2011, "Tracking and back-tracking", Prog. in
    Photovoltaics: Research and Applications, v. 19, pp. 747-753.
    """

    # MATLAB to Python conversion by
    # Will Holmgren (@wholmgren), U. Arizona. March, 2015.

    if isinstance(apparent_zenith, pd.Series):
        index = apparent_zenith.index
    else:
        index = None

    # convert scalars to arrays
    apparent_azimuth = np.atleast_1d(apparent_azimuth)
    apparent_zenith = np.atleast_1d(apparent_zenith)

    if apparent_azimuth.ndim > 1 or apparent_zenith.ndim > 1:
        raise ValueError('Input dimensions must not exceed 1')

    # Calculate sun position x, y, z using coordinate system as in [1], Eq 2.

    # Positive y axis is oriented parallel to earth surface along tracking axis
    # (for the purpose of illustration, assume y is oriented to the south);
    # positive x axis is orthogonal, 90 deg clockwise from y-axis, and parallel
    # to the earth's surface (if y axis is south, x axis is west);
    # positive z axis is normal to x, y axes, pointed upward.

    # Equations in [1] assume solar azimuth is relative to reference vector
    # pointed south, with clockwise positive.
    # Here, the input solar azimuth is degrees East of North,
    # i.e., relative to a reference vector pointed
    # north with clockwise positive.
    # Rotate sun azimuth to coordinate system as in [1]
    # to calculate sun position.

    az = apparent_azimuth - 180
    apparent_elevation = 90 - apparent_zenith
    x = cosd(apparent_elevation) * sind(az)
    y = cosd(apparent_elevation) * cosd(az)
    z = sind(apparent_elevation)

    # translate array azimuth from compass bearing to [1] coord system
    # wholmgren: strange to see axis_azimuth calculated differently from az,
    # (not that it matters, or at least it shouldn't...).
    axis_azimuth_south = axis_azimuth - 180

    # translate input array tilt angle axis_tilt to [1] coordinate system.

    # In [1] coordinates, axis_tilt is a rotation about the x-axis.
    # For a system with array azimuth (y-axis) oriented south,
    # the x-axis is oriented west, and a positive axis_tilt is a
    # counterclockwise rotation, i.e, lifting the north edge of the panel.
    # Thus, in [1] coordinate system, in the northern hemisphere a positive
    # axis_tilt indicates a rotation toward the equator,
    # whereas in the southern hemisphere rotation toward the equator is
    # indicated by axis_tilt<0.  Here, the input axis_tilt is
    # always positive and is a rotation toward the equator.

    # Calculate sun position (xp, yp, zp) in panel-oriented coordinate system:
    # positive y-axis is oriented along tracking axis at panel tilt;
    # positive x-axis is orthogonal, clockwise, parallel to earth surface;
    # positive z-axis is normal to x-y axes, pointed upward.
    # Calculate sun position (xp,yp,zp) in panel coordinates using [1] Eq 11
    # note that equation for yp (y' in Eq. 11 of Lorenzo et al 2011) is
    # corrected, after conversation with paper's authors.

    xp = x * cosd(axis_azimuth_south) - y * sind(axis_azimuth_south)
    yp = (x * cosd(axis_tilt) * sind(axis_azimuth_south) +
          y * cosd(axis_tilt) * cosd(axis_azimuth_south) - z * sind(axis_tilt))
    zp = (x * sind(axis_tilt) * sind(axis_azimuth_south) +
          y * sind(axis_tilt) * cosd(axis_azimuth_south) + z * cosd(axis_tilt))

    # The ideal tracking angle wid is the rotation to place the sun position
    # vector (xp, yp, zp) in the (y, z) plane; i.e., normal to the panel and
    # containing the axis of rotation.  wid = 0 indicates that the panel is
    # horizontal.  Here, our convention is that a clockwise rotation is
    # positive, to view rotation angles in the same frame of reference as
    # azimuth.  For example, for a system with tracking axis oriented south,
    # a rotation toward the east is negative, and a rotation to the west is
    # positive.

    # Use arctan2 and avoid the tmp corrections.

    # angle from x-y plane to projection of sun vector onto x-z plane
    #     tmp = np.degrees(np.arctan(zp/xp))

    # Obtain wid by translating tmp to convention for rotation angles.
    # Have to account for which quadrant of the x-z plane in which the sun
    # vector lies.  Complete solution here but probably not necessary to
    # consider QIII and QIV.
    #     wid = pd.Series(index=times)
    #     wid[(xp>=0) & (zp>=0)] =  90 - tmp[(xp>=0) & (zp>=0)]  # QI
    #     wid[(xp<0)  & (zp>=0)] = -90 - tmp[(xp<0)  & (zp>=0)]  # QII
    #     wid[(xp<0)  & (zp<0)]  = -90 - tmp[(xp<0)  & (zp<0)]   # QIII
    #     wid[(xp>=0) & (zp<0)]  =  90 - tmp[(xp>=0) & (zp<0)]   # QIV

    # Calculate angle from x-y plane to projection of sun vector onto x-z plane
    # and then obtain wid by translating tmp to convention for rotation angles.
    wid = 90 - np.degrees(np.arctan2(zp, xp))

    # filter for sun above panel horizon
    zen_gt_90 = apparent_zenith > 90
    wid[zen_gt_90] = np.nan

    # Account for backtracking; modified from [1] to account for rotation
    # angle convention being used here.
    if backtrack:
        axes_distance = 1 / gcr
        # clip needed for low angles. GH 656
        temp = np.clip(axes_distance * cosd(wid), -1, 1)

        # backtrack angle
        # (always positive b/c acosd returns values between 0 and 180)
        wc = np.degrees(np.arccos(temp))

        # Eq 4 applied when wid in QIV (wid < 0 evalulates True), QI
        with np.errstate(invalid='ignore'):
            # errstate for GH 622
            tracker_theta = np.where(wid < 0, wid + wc, wid - wc)
    else:
        tracker_theta = wid

    tracker_theta = np.minimum(tracker_theta, max_angle)
    tracker_theta = np.maximum(tracker_theta, -max_angle)

    # calculate panel normal vector in panel-oriented x, y, z coordinates.
    # y-axis is axis of tracker rotation.  tracker_theta is a compass angle
    # (clockwise is positive) rather than a trigonometric angle.
    # the *0 is a trick to preserve NaN values.
    panel_norm = np.array(
        [sind(tracker_theta), tracker_theta * 0,
         cosd(tracker_theta)])

    # sun position in vector format in panel-oriented x, y, z coordinates
    sun_vec = np.array([xp, yp, zp])

    # calculate angle-of-incidence on panel
    aoi = np.degrees(np.arccos(np.abs(np.sum(sun_vec * panel_norm, axis=0))))

    # calculate panel tilt and azimuth
    # in a coordinate system where the panel tilt is the
    # angle from horizontal, and the panel azimuth is
    # the compass angle (clockwise from north) to the projection
    # of the panel's normal to the earth's surface.
    # These outputs are provided for convenience and comparison
    # with other PV software which use these angle conventions.

    # project normal vector to earth surface.
    # First rotate about x-axis by angle -axis_tilt so that y-axis is
    # also parallel to earth surface, then project.

    # Calculate standard rotation matrix
    rot_x = np.array([[1, 0, 0], [0, cosd(-axis_tilt), -sind(-axis_tilt)],
                      [0, sind(-axis_tilt),
                       cosd(-axis_tilt)]])

    # panel_norm_earth contains the normal vector
    # expressed in earth-surface coordinates
    # (z normal to surface, y aligned with tracker axis parallel to earth)
    panel_norm_earth = np.dot(rot_x, panel_norm).T

    # projection to plane tangent to earth surface,
    # in earth surface coordinates
    projected_normal = np.array([
        panel_norm_earth[:, 0], panel_norm_earth[:, 1],
        panel_norm_earth[:, 2] * 0
    ]).T

    # calculate vector magnitudes
    projected_normal_mag = np.sqrt(np.nansum(projected_normal**2, axis=1))

    # renormalize the projected vector
    # avoid creating nan values.
    non_zeros = projected_normal_mag != 0
    projected_normal[non_zeros] = (projected_normal[non_zeros].T /
                                   projected_normal_mag[non_zeros]).T

    # calculation of surface_azimuth
    # 1. Find the angle.
    #     surface_azimuth = pd.Series(
    #         np.degrees(np.arctan(projected_normal[:,1]/projected_normal[:,0])),
    #                                 index=times)
    surface_azimuth = \
        np.degrees(np.arctan2(projected_normal[:, 1], projected_normal[:, 0]))

    # 2. Clean up atan when x-coord or y-coord is zero
    #     surface_azimuth[(projected_normal[:,0]==0) & (projected_normal[:,1]>0)] =  90
    #     surface_azimuth[(projected_normal[:,0]==0) & (projected_normal[:,1]<0)] =  -90
    #     surface_azimuth[(projected_normal[:,1]==0) & (projected_normal[:,0]>0)] =  0
    #     surface_azimuth[(projected_normal[:,1]==0) & (projected_normal[:,0]<0)] = 180

    # 3. Correct atan for QII and QIII
    #     surface_azimuth[(projected_normal[:,0]<0) & (projected_normal[:,1]>0)] += 180 # QII
    #     surface_azimuth[(projected_normal[:,0]<0) & (projected_normal[:,1]<0)] += 180 # QIII

    # 4. Skip to below

    # at this point surface_azimuth contains angles between -90 and +270,
    # where 0 is along the positive x-axis,
    # the y-axis is in the direction of the tracker azimuth,
    # and positive angles are rotations from the positive x axis towards
    # the positive y-axis.
    # Adjust to compass angles
    # (clockwise rotation from 0 along the positive y-axis)
    #    surface_azimuth[surface_azimuth<=90] = 90 - surface_azimuth[surface_azimuth<=90]
    #    surface_azimuth[surface_azimuth>90] = 450 - surface_azimuth[surface_azimuth>90]

    # finally rotate to align y-axis with true north
    # PVLIB_MATLAB has this latitude correction,
    # but I don't think it's latitude dependent if you always
    # specify axis_azimuth with respect to North.
    #     if latitude > 0 or True:
    #         surface_azimuth = surface_azimuth - axis_azimuth
    #     else:
    #         surface_azimuth = surface_azimuth - axis_azimuth - 180
    #     surface_azimuth[surface_azimuth<0] = 360 + surface_azimuth[surface_azimuth<0]

    # the commented code above is mostly part of PVLIB_MATLAB.
    # My (wholmgren) take is that it can be done more simply.
    # Say that we're pointing along the postive x axis (likely west).
    # We just need to rotate 90 degrees to get from the x axis
    # to the y axis (likely south),
    # and then add the axis_azimuth to get back to North.
    # Anything left over is the azimuth that we want,
    # and we can map it into the [0,360) domain.

    # 4. Rotate 0 reference from panel's x axis to it's y axis and
    #    then back to North.
    surface_azimuth = 90 - surface_azimuth + axis_azimuth

    # 5. Map azimuth into [0,360) domain.
    # surface_azimuth[surface_azimuth < 0] += 360
    # surface_azimuth[surface_azimuth >= 360] -= 360
    surface_azimuth = surface_azimuth % 360

    # Calculate surface_tilt
    dotproduct = (panel_norm_earth * projected_normal).sum(axis=1)
    surface_tilt = 90 - np.degrees(np.arccos(dotproduct))

    # Bundle DataFrame for return values and filter for sun below horizon.
    out = {
        'tracker_theta': tracker_theta,
        'aoi': aoi,
        'surface_azimuth': surface_azimuth,
        'surface_tilt': surface_tilt
    }
    if index is not None:
        out = pd.DataFrame(out, index=index)
        out = out[['tracker_theta', 'aoi', 'surface_azimuth', 'surface_tilt']]
        out[zen_gt_90] = np.nan
    else:
        out = {k: np.where(zen_gt_90, np.nan, v) for k, v in out.items()}

    return out
Ejemplo n.º 39
0
    def complete_irradiance(self, times=None, weather=None):
        """
        Determine the missing irradiation columns. Only two of the
        following data columns (dni, ghi, dhi) are needed to calculate
        the missing data.

        This function is not safe at the moment. Results can be too high
        or negative. Please contribute and help to improve this function
        on https://github.com/pvlib/pvlib-python

        Parameters
        ----------
        times : None or DatetimeIndex, default None
            Times at which to evaluate the model. Can be None if
            attribute `times` is already set.
        weather : None or pandas.DataFrame, default None
            Table with at least two columns containing one of the
            following data sets: dni, dhi, ghi. Can be None if attribute
            `weather` is already set.

        Returns
        -------
        self

        Assigns attributes: times, weather

        Examples
        --------
        This example does not work until the parameters `my_system`,
        `my_location`, `my_datetime` and `my_weather` are not defined
        properly but shows the basic idea how this method can be used.

        >>> from pvlib.modelchain import ModelChain

        >>> # my_weather containing 'dhi' and 'ghi'.
        >>> mc = ModelChain(my_system, my_location)  # doctest: +SKIP
        >>> mc.complete_irradiance(my_datetime, my_weather)  # doctest: +SKIP
        >>> mc.run_model()  # doctest: +SKIP

        >>> # my_weather containing 'dhi', 'ghi' and 'dni'.
        >>> mc = ModelChain(my_system, my_location)  # doctest: +SKIP
        >>> mc.run_model(my_datetime, my_weather)  # doctest: +SKIP
        """
        if weather is not None:
            self.weather = weather
        if times is not None:
            self.times = times
        self.solar_position = self.location.get_solarposition(
            self.times, method=self.solar_position_method)
        icolumns = set(self.weather.columns)
        wrn_txt = ("This function is not safe at the moment.\n" +
                   "Results can be too high or negative.\n" +
                   "Help to improve this function on github:\n" +
                   "https://github.com/pvlib/pvlib-python \n")

        if {'ghi', 'dhi'} <= icolumns and 'dni' not in icolumns:
            clearsky = self.location.get_clearsky(
                times, solar_position=self.solar_position)
            self.weather.loc[:, 'dni'] = pvlib.irradiance.dni(
                self.weather.loc[:, 'ghi'], self.weather.loc[:, 'dhi'],
                self.solar_position.zenith,
                clearsky_dni=clearsky['dni'],
                clearsky_tolerance=1.1)
        elif {'dni', 'dhi'} <= icolumns and 'ghi' not in icolumns:
            warnings.warn(wrn_txt, UserWarning)
            self.weather.loc[:, 'ghi'] = (
                self.weather.dni * tools.cosd(self.solar_position.zenith) +
                self.weather.dhi)
        elif {'dni', 'ghi'} <= icolumns and 'dhi' not in icolumns:
            warnings.warn(wrn_txt, UserWarning)
            self.weather.loc[:, 'dhi'] = (
                self.weather.ghi - self.weather.dni *
                tools.cosd(self.solar_position.zenith))

        return self
Ejemplo n.º 40
0
def singleaxis(apparent_zenith, apparent_azimuth,
               axis_tilt=0, axis_azimuth=0, max_angle=90,
               backtrack=True, gcr=2.0/7.0):
    """
    Determine the rotation angle of a single axis tracker using the
    equations in [1] when given a particular sun zenith and azimuth
    angle. backtracking may be specified, and if so, a ground coverage
    ratio is required.

    Rotation angle is determined in a panel-oriented coordinate system.
    The tracker azimuth axis_azimuth defines the positive y-axis; the
    positive x-axis is 90 degress clockwise from the y-axis and parallel
    to the earth surface, and the positive z-axis is normal and oriented
    towards the sun. Rotation angle tracker_theta indicates tracker
    position relative to horizontal: tracker_theta = 0 is horizontal,
    and positive tracker_theta is a clockwise rotation around the y axis
    in the x, y, z coordinate system. For example, if tracker azimuth
    axis_azimuth is 180 (oriented south), tracker_theta = 30 is a
    rotation of 30 degrees towards the west, and tracker_theta = -90 is
    a rotation to the vertical plane facing east.

    Parameters
    ----------
    apparent_zenith : float, 1d array, or Series
        Solar apparent zenith angles in decimal degrees.

    apparent_azimuth : float, 1d array, or Series
        Solar apparent azimuth angles in decimal degrees.

    axis_tilt : float, default 0
        The tilt of the axis of rotation (i.e, the y-axis defined by
        axis_azimuth) with respect to horizontal, in decimal degrees.

    axis_azimuth : float, default 0
        A value denoting the compass direction along which the axis of
        rotation lies. Measured in decimal degrees East of North.

    max_angle : float, default 90
        A value denoting the maximum rotation angle, in decimal degrees,
        of the one-axis tracker from its horizontal position (horizontal
        if axis_tilt = 0). A max_angle of 90 degrees allows the tracker
        to rotate to a vertical position to point the panel towards a
        horizon. max_angle of 180 degrees allows for full rotation.

    backtrack : bool, default True
        Controls whether the tracker has the capability to "backtrack"
        to avoid row-to-row shading. False denotes no backtrack
        capability. True denotes backtrack capability.

    gcr : float, default 2.0/7.0
        A value denoting the ground coverage ratio of a tracker system
        which utilizes backtracking; i.e. the ratio between the PV array
        surface area to total ground area. A tracker system with modules
        2 meters wide, centered on the tracking axis, with 6 meters
        between the tracking axes has a gcr of 2/6=0.333. If gcr is not
        provided, a gcr of 2/7 is default. gcr must be <=1.

    Returns
    -------
    dict or DataFrame with the following columns:

    * tracker_theta: The rotation angle of the tracker.
        tracker_theta = 0 is horizontal, and positive rotation angles are
        clockwise.
    * aoi: The angle-of-incidence of direct irradiance onto the
        rotated panel surface.
    * surface_tilt: The angle between the panel surface and the earth
        surface, accounting for panel rotation.
    * surface_azimuth: The azimuth of the rotated panel, determined by
        projecting the vector normal to the panel's surface to the earth's
        surface.

    References
    ----------
    [1] Lorenzo, E et al., 2011, "Tracking and back-tracking", Prog. in
    Photovoltaics: Research and Applications, v. 19, pp. 747-753.
    """

    # MATLAB to Python conversion by
    # Will Holmgren (@wholmgren), U. Arizona. March, 2015.

    if isinstance(apparent_zenith, pd.Series):
        index = apparent_zenith.index
    else:
        index = None

    # convert scalars to arrays
    apparent_azimuth = np.atleast_1d(apparent_azimuth)
    apparent_zenith = np.atleast_1d(apparent_zenith)

    if apparent_azimuth.ndim > 1 or apparent_zenith.ndim > 1:
        raise ValueError('Input dimensions must not exceed 1')

    # Calculate sun position x, y, z using coordinate system as in [1], Eq 2.

    # Positive y axis is oriented parallel to earth surface along tracking axis
    # (for the purpose of illustration, assume y is oriented to the south);
    # positive x axis is orthogonal, 90 deg clockwise from y-axis, and parallel
    # to the earth's surface (if y axis is south, x axis is west);
    # positive z axis is normal to x, y axes, pointed upward.

    # Equations in [1] assume solar azimuth is relative to reference vector
    # pointed south, with clockwise positive.
    # Here, the input solar azimuth is degrees East of North,
    # i.e., relative to a reference vector pointed
    # north with clockwise positive.
    # Rotate sun azimuth to coordinate system as in [1]
    # to calculate sun position.

    az = apparent_azimuth - 180
    apparent_elevation = 90 - apparent_zenith
    x = cosd(apparent_elevation) * sind(az)
    y = cosd(apparent_elevation) * cosd(az)
    z = sind(apparent_elevation)

    # translate array azimuth from compass bearing to [1] coord system
    # wholmgren: strange to see axis_azimuth calculated differently from az,
    # (not that it matters, or at least it shouldn't...).
    axis_azimuth_south = axis_azimuth - 180

    # translate input array tilt angle axis_tilt to [1] coordinate system.

    # In [1] coordinates, axis_tilt is a rotation about the x-axis.
    # For a system with array azimuth (y-axis) oriented south,
    # the x-axis is oriented west, and a positive axis_tilt is a
    # counterclockwise rotation, i.e, lifting the north edge of the panel.
    # Thus, in [1] coordinate system, in the northern hemisphere a positive
    # axis_tilt indicates a rotation toward the equator,
    # whereas in the southern hemisphere rotation toward the equator is
    # indicated by axis_tilt<0.  Here, the input axis_tilt is
    # always positive and is a rotation toward the equator.

    # Calculate sun position (xp, yp, zp) in panel-oriented coordinate system:
    # positive y-axis is oriented along tracking axis at panel tilt;
    # positive x-axis is orthogonal, clockwise, parallel to earth surface;
    # positive z-axis is normal to x-y axes, pointed upward.
    # Calculate sun position (xp,yp,zp) in panel coordinates using [1] Eq 11
    # note that equation for yp (y' in Eq. 11 of Lorenzo et al 2011) is
    # corrected, after conversation with paper's authors.

    xp = x*cosd(axis_azimuth_south) - y*sind(axis_azimuth_south)
    yp = (x*cosd(axis_tilt)*sind(axis_azimuth_south) +
          y*cosd(axis_tilt)*cosd(axis_azimuth_south) -
          z*sind(axis_tilt))
    zp = (x*sind(axis_tilt)*sind(axis_azimuth_south) +
          y*sind(axis_tilt)*cosd(axis_azimuth_south) +
          z*cosd(axis_tilt))

    # The ideal tracking angle wid is the rotation to place the sun position
    # vector (xp, yp, zp) in the (y, z) plane; i.e., normal to the panel and
    # containing the axis of rotation.  wid = 0 indicates that the panel is
    # horizontal.  Here, our convention is that a clockwise rotation is
    # positive, to view rotation angles in the same frame of reference as
    # azimuth.  For example, for a system with tracking axis oriented south,
    # a rotation toward the east is negative, and a rotation to the west is
    # positive.

    # Use arctan2 and avoid the tmp corrections.

    # angle from x-y plane to projection of sun vector onto x-z plane
#     tmp = np.degrees(np.arctan(zp/xp))

    # Obtain wid by translating tmp to convention for rotation angles.
    # Have to account for which quadrant of the x-z plane in which the sun
    # vector lies.  Complete solution here but probably not necessary to
    # consider QIII and QIV.
#     wid = pd.Series(index=times)
#     wid[(xp>=0) & (zp>=0)] =  90 - tmp[(xp>=0) & (zp>=0)]  # QI
#     wid[(xp<0)  & (zp>=0)] = -90 - tmp[(xp<0)  & (zp>=0)]  # QII
#     wid[(xp<0)  & (zp<0)]  = -90 - tmp[(xp<0)  & (zp<0)]   # QIII
#     wid[(xp>=0) & (zp<0)]  =  90 - tmp[(xp>=0) & (zp<0)]   # QIV

    # Calculate angle from x-y plane to projection of sun vector onto x-z plane
    # and then obtain wid by translating tmp to convention for rotation angles.
    wid = 90 - np.degrees(np.arctan2(zp, xp))

    # filter for sun above panel horizon
    zen_gt_90 = apparent_zenith > 90
    wid[zen_gt_90] = np.nan

    # Account for backtracking; modified from [1] to account for rotation
    # angle convention being used here.
    if backtrack:
        axes_distance = 1/gcr
        temp = np.minimum(axes_distance*cosd(wid), 1)

        # backtrack angle
        # (always positive b/c acosd returns values between 0 and 180)
        wc = np.degrees(np.arccos(temp))

        # Eq 4 applied when wid in QIV (wid < 0 evalulates True), QI
        tracker_theta = np.where(wid < 0, wid + wc, wid - wc)
    else:
        tracker_theta = wid

    tracker_theta[tracker_theta > max_angle] = max_angle
    tracker_theta[tracker_theta < -max_angle] = -max_angle

    # calculate panel normal vector in panel-oriented x, y, z coordinates.
    # y-axis is axis of tracker rotation.  tracker_theta is a compass angle
    # (clockwise is positive) rather than a trigonometric angle.
    # the *0 is a trick to preserve NaN values.
    panel_norm = np.array([sind(tracker_theta),
                           tracker_theta*0,
                           cosd(tracker_theta)])

    # sun position in vector format in panel-oriented x, y, z coordinates
    sun_vec = np.array([xp, yp, zp])

    # calculate angle-of-incidence on panel
    aoi = np.degrees(np.arccos(np.abs(np.sum(sun_vec*panel_norm, axis=0))))

    # calculate panel tilt and azimuth
    # in a coordinate system where the panel tilt is the
    # angle from horizontal, and the panel azimuth is
    # the compass angle (clockwise from north) to the projection
    # of the panel's normal to the earth's surface.
    # These outputs are provided for convenience and comparison
    # with other PV software which use these angle conventions.

    # project normal vector to earth surface.
    # First rotate about x-axis by angle -axis_tilt so that y-axis is
    # also parallel to earth surface, then project.

    # Calculate standard rotation matrix
    rot_x = np.array([[1, 0, 0],
                      [0, cosd(-axis_tilt), -sind(-axis_tilt)],
                      [0, sind(-axis_tilt), cosd(-axis_tilt)]])

    # panel_norm_earth contains the normal vector
    # expressed in earth-surface coordinates
    # (z normal to surface, y aligned with tracker axis parallel to earth)
    panel_norm_earth = np.dot(rot_x, panel_norm).T

    # projection to plane tangent to earth surface,
    # in earth surface coordinates
    projected_normal = np.array([panel_norm_earth[:, 0],
                                 panel_norm_earth[:, 1],
                                 panel_norm_earth[:, 2]*0]).T

    # calculate vector magnitudes
    projected_normal_mag = np.sqrt(np.nansum(projected_normal**2, axis=1))

    # renormalize the projected vector
    # avoid creating nan values.
    non_zeros = projected_normal_mag != 0
    projected_normal[non_zeros] = (projected_normal[non_zeros].T /
                                   projected_normal_mag[non_zeros]).T

    # calculation of surface_azimuth
    # 1. Find the angle.
#     surface_azimuth = pd.Series(
#         np.degrees(np.arctan(projected_normal[:,1]/projected_normal[:,0])),
#                                 index=times)
    surface_azimuth = \
        np.degrees(np.arctan2(projected_normal[:, 1], projected_normal[:, 0]))

    # 2. Clean up atan when x-coord or y-coord is zero
#     surface_azimuth[(projected_normal[:,0]==0) & (projected_normal[:,1]>0)] =  90
#     surface_azimuth[(projected_normal[:,0]==0) & (projected_normal[:,1]<0)] =  -90
#     surface_azimuth[(projected_normal[:,1]==0) & (projected_normal[:,0]>0)] =  0
#     surface_azimuth[(projected_normal[:,1]==0) & (projected_normal[:,0]<0)] = 180

    # 3. Correct atan for QII and QIII
#     surface_azimuth[(projected_normal[:,0]<0) & (projected_normal[:,1]>0)] += 180 # QII
#     surface_azimuth[(projected_normal[:,0]<0) & (projected_normal[:,1]<0)] += 180 # QIII

    # 4. Skip to below

    # at this point surface_azimuth contains angles between -90 and +270,
    # where 0 is along the positive x-axis,
    # the y-axis is in the direction of the tracker azimuth,
    # and positive angles are rotations from the positive x axis towards
    # the positive y-axis.
    # Adjust to compass angles
    # (clockwise rotation from 0 along the positive y-axis)
#    surface_azimuth[surface_azimuth<=90] = 90 - surface_azimuth[surface_azimuth<=90]
#    surface_azimuth[surface_azimuth>90] = 450 - surface_azimuth[surface_azimuth>90]

    # finally rotate to align y-axis with true north
    # PVLIB_MATLAB has this latitude correction,
    # but I don't think it's latitude dependent if you always
    # specify axis_azimuth with respect to North.
#     if latitude > 0 or True:
#         surface_azimuth = surface_azimuth - axis_azimuth
#     else:
#         surface_azimuth = surface_azimuth - axis_azimuth - 180
#     surface_azimuth[surface_azimuth<0] = 360 + surface_azimuth[surface_azimuth<0]

    # the commented code above is mostly part of PVLIB_MATLAB.
    # My (wholmgren) take is that it can be done more simply.
    # Say that we're pointing along the postive x axis (likely west).
    # We just need to rotate 90 degrees to get from the x axis
    # to the y axis (likely south),
    # and then add the axis_azimuth to get back to North.
    # Anything left over is the azimuth that we want,
    # and we can map it into the [0,360) domain.

    # 4. Rotate 0 reference from panel's x axis to it's y axis and
    #    then back to North.
    surface_azimuth = 90 - surface_azimuth + axis_azimuth

    # 5. Map azimuth into [0,360) domain.
    surface_azimuth[surface_azimuth < 0] += 360
    surface_azimuth[surface_azimuth >= 360] -= 360

    # Calculate surface_tilt
    dotproduct = (panel_norm_earth * projected_normal).sum(axis=1)
    surface_tilt = 90 - np.degrees(np.arccos(dotproduct))

    # Bundle DataFrame for return values and filter for sun below horizon.
    out = {'tracker_theta': tracker_theta, 'aoi': aoi,
           'surface_azimuth': surface_azimuth, 'surface_tilt': surface_tilt}
    if index is not None:
        out = pd.DataFrame(out, index=index)
        out = out[['tracker_theta', 'aoi', 'surface_azimuth', 'surface_tilt']]
        out[zen_gt_90] = np.nan
    else:
        out = {k: np.where(zen_gt_90, np.nan, v) for k, v in out.items()}

    return out
Ejemplo n.º 41
0
def haydavies(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
              solar_zenith=None, solar_azimuth=None, projection_ratio=None):
    r'''
    Determine diffuse irradiance from the sky on a
    tilted surface using Hay & Davies' 1980 model

    .. math::
        I_{d} = DHI ( A R_b + (1 - A) (\frac{1 + \cos\beta}{2}) )

    Hay and Davies' 1980 model determines 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, and sun azimuth angle.

    Parameters
    ----------

    surface_tilt : float or Series
        Surface tilt angles in decimal degrees.
        The tilt angle is defined as
        degrees from horizontal (e.g. surface facing up = 0, surface facing
        horizon = 90)

    surface_azimuth : float or Series
        Surface azimuth angles in decimal degrees.
        The azimuth convention is defined
        as degrees east of north (e.g. North=0, South=180, East=90,
        West=270).

    dhi : float or Series
        Diffuse horizontal irradiance in W/m^2.

    dni : float or Series
        Direct normal irradiance in W/m^2.

    dni_extra : float or Series
        Extraterrestrial normal irradiance in W/m^2.

    solar_zenith : None, float or Series
        Solar apparent (refraction-corrected) zenith
        angles in decimal degrees.
        Must supply ``solar_zenith`` and ``solar_azimuth`` or supply
        ``projection_ratio``.

    solar_azimuth : None, float or Series
        Solar azimuth angles in decimal degrees.
        Must supply ``solar_zenith`` and ``solar_azimuth`` or supply
        ``projection_ratio``.

    projection_ratio : None, float or Series
        Ratio of angle of incidence projection to solar zenith
        angle projection.
        Must supply ``solar_zenith`` and ``solar_azimuth`` or supply
        ``projection_ratio``.

    Returns
    --------

    sky_diffuse : float or Series
        The diffuse component of the solar radiation on an
        arbitrarily tilted surface defined by the Perez model as given in
        reference [3]. Does not include the
        ground reflected irradiance or the irradiance due to the beam.

    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] Hay, J.E., Davies, J.A., 1980. Calculations of the solar radiation
    incident on an inclined surface. In: Hay, J.E., Won, T.K. (Eds.), Proc. of
    First Canadian Solar Radiation Data Workshop, 59. Ministry of Supply
    and Services, Canada.
    '''

    pvl_logger.debug('diffuse_sky.haydavies()')

    # if necessary, calculate ratio of titled and horizontal beam irradiance
    if projection_ratio is None:
        cos_tt = aoi_projection(surface_tilt, surface_azimuth,
                                solar_zenith, solar_azimuth)
        cos_sun_zen = tools.cosd(solar_zenith)
        Rb = cos_tt / cos_sun_zen
    else:
        Rb = projection_ratio

    # Anisotropy Index
    AI = dni / dni_extra

    # these are actually the () and [] sub-terms of the second term of eqn 7
    term1 = 1 - AI
    term2 = 0.5 * (1 + tools.cosd(surface_tilt))

    sky_diffuse = dhi * (AI * Rb + term1 * term2)
    sky_diffuse[sky_diffuse < 0] = 0

    return sky_diffuse
Ejemplo n.º 42
0
def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
             altitude=0, dni_extra=1364.):
    '''
    Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model.

    Implements the Ineichen and Perez clear sky model for global
    horizontal irradiance (GHI), direct normal irradiance (DNI), and
    calculates the clear-sky diffuse horizontal (DHI) component as the
    difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A
    report on clear sky models found the Ineichen/Perez model to have
    excellent performance with a minimal input data set [3].

    Default values for monthly Linke turbidity provided by SoDa [4, 5].

    Parameters
    -----------
    apparent_zenith: numeric
        Refraction corrected solar zenith angle in degrees.

    airmass_absolute: numeric
        Pressure corrected airmass.

    linke_turbidity: numeric
        Linke Turbidity.

    altitude: numeric
        Altitude above sea level in meters.

    dni_extra: numeric
        Extraterrestrial irradiance. The units of ``dni_extra``
        determine the units of the output.

    Returns
    -------
    clearsky : DataFrame (if Series input) or OrderedDict of arrays
        DataFrame/OrderedDict contains the columns/keys
        ``'dhi', 'dni', 'ghi'``.

    See also
    --------
    lookup_linke_turbidity
    pvlib.location.Location.get_clearsky

    References
    ----------
    [1] P. Ineichen and R. Perez, "A New airmass independent formulation for
        the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157,
        2002.

    [2] R. Perez et. al., "A New Operational Model for Satellite-Derived
        Irradiances: Description and Validation", Solar Energy, vol 73, pp.
        307-317, 2002.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
        Sky Models: Implementation and Analysis", Sandia National
        Laboratories, SAND2012-2389, 2012.

    [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained
        July 17, 2012).

    [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc.
        ISES Solar World Congress, June 2003. Goteborg, Sweden.
    '''

    # Dan's note on the TL correction: By my reading of the publication
    # on pages 151-157, Ineichen and Perez introduce (among other
    # things) three things. 1) Beam model in eqn. 8, 2) new turbidity
    # factor in eqn 9 and appendix A, and 3) Global horizontal model in
    # eqn. 11. They do NOT appear to use the new turbidity factor (item
    # 2 above) in either the beam or GHI models. The phrasing of
    # appendix A seems as if there are two separate corrections, the
    # first correction is used to correct the beam/GHI models, and the
    # second correction is used to correct the revised turibidity
    # factor. In my estimation, there is no need to correct the
    # turbidity factor used in the beam/GHI models.

    # Create the corrected TL for TL < 2
    # TLcorr = TL;
    # TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5);

    # This equation is found in Solar Energy 73, pg 311. Full ref: Perez
    # et. al., Vol. 73, pp. 307-317 (2002). It is slightly different
    # than the equation given in Solar Energy 73, pg 156. We used the
    # equation from pg 311 because of the existence of known typos in
    # the pg 156 publication (notably the fh2-(TL-1) should be fh2 *
    # (TL-1)).

    # The NaN handling is a little subtle. The AM input is likely to
    # have NaNs that we'll want to map to 0s in the output. However, we
    # want NaNs in other inputs to propagate through to the output. This
    # is accomplished by judicious use and placement of np.maximum,
    # np.minimum, and np.fmax

    # use max so that nighttime values will result in 0s instead of
    # negatives. propagates nans.
    cos_zenith = np.maximum(tools.cosd(apparent_zenith), 0)

    tl = linke_turbidity

    fh1 = np.exp(-altitude/8000.)
    fh2 = np.exp(-altitude/1250.)
    cg1 = 5.09e-05 * altitude + 0.868
    cg2 = 3.92e-05 * altitude + 0.0387

    ghi = (np.exp(-cg2*airmass_absolute*(fh1 + fh2*(tl - 1))) *
           np.exp(0.01*airmass_absolute**1.8))
    # use fmax to map airmass nans to 0s. multiply and divide by tl to
    # reinsert tl nans
    ghi = cg1 * dni_extra * cos_zenith * tl / tl * np.fmax(ghi, 0)

    # BncI = "normal beam clear sky radiation"
    b = 0.664 + 0.163/fh1
    bnci = b * np.exp(-0.09 * airmass_absolute * (tl - 1))
    bnci = dni_extra * np.fmax(bnci, 0)

    # "empirical correction" SE 73, 157 & SE 73, 312.
    bnci_2 = ((1 - (0.1 - 0.2*np.exp(-tl))/(0.1 + 0.882/fh1)) /
              cos_zenith)
    bnci_2 = ghi * np.fmin(np.fmax(bnci_2, 0), 1e20)

    dni = np.minimum(bnci, bnci_2)

    dhi = ghi - dni*cos_zenith

    irrads = OrderedDict()
    irrads['ghi'] = ghi
    irrads['dni'] = dni
    irrads['dhi'] = dhi

    if isinstance(dni, pd.Series):
        irrads = pd.DataFrame.from_dict(irrads)

    return irrads
Ejemplo n.º 43
0
def ineichen(
    time,
    location,
    linke_turbidity=None,
    solarposition_method="pyephem",
    zenith_data=None,
    airmass_model="young1994",
    airmass_data=None,
    interp_turbidity=True,
):
    """
    Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model

    Implements the Ineichen and Perez clear sky model for global horizontal
    irradiance (GHI), direct normal irradiance (DNI), and calculates
    the clear-sky diffuse horizontal (DHI) component as the difference
    between GHI and DNI*cos(zenith) as presented in [1, 2]. A report on clear
    sky models found the Ineichen/Perez model to have excellent performance
    with a minimal input data set [3]. 
    
    Default values for montly Linke turbidity provided by SoDa [4, 5].

    Parameters
    -----------
    time : pandas.DatetimeIndex
    
    location : pvlib.Location
    
    linke_turbidity : None or float
        If None, uses ``LinkeTurbidities.mat`` lookup table.
    
    solarposition_method : string
        Sets the solar position algorithm. 
        See solarposition.get_solarposition()
    
    zenith_data : None or pandas.Series
        If None, ephemeris data will be calculated using ``solarposition_method``.
    
    airmass_model : string
        See pvlib.airmass.relativeairmass().
    
    airmass_data : None or pandas.Series
        If None, absolute air mass data will be calculated using 
        ``airmass_model`` and location.alitude.
    
    interp_turbidity : bool
        If ``True``, interpolates the monthly Linke turbidity values
        found in ``LinkeTurbidities.mat`` to daily values.

    Returns
    --------
    DataFrame with the following columns: ``GHI, DNI, DHI``.

    Notes
    -----
    If you are using this function
    in a loop, it may be faster to load LinkeTurbidities.mat outside of
    the loop and feed it in as a variable, rather than
    having the function open the file each time it is called.

    References
    ----------

    [1] P. Ineichen and R. Perez, "A New airmass independent formulation for
        the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157, 2002.

    [2] R. Perez et. al., "A New Operational Model for Satellite-Derived
        Irradiances: Description and Validation", Solar Energy, vol 73, pp.
        307-317, 2002.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
        Sky Models: Implementation and Analysis", Sandia National
        Laboratories, SAND2012-2389, 2012.

    [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained
        July 17, 2012).

    [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc.
        ISES Solar World Congress, June 2003. Goteborg, Sweden.
    """
    # Initial implementation of this algorithm by Matthew Reno.
    # Ported to python by Rob Andrews
    # Added functionality by Will Holmgren

    I0 = irradiance.extraradiation(time.dayofyear)

    if zenith_data is None:
        ephem_data = solarposition.get_solarposition(time, location, method=solarposition_method)
        time = ephem_data.index  # fixes issue with time possibly not being tz-aware
        try:
            ApparentZenith = ephem_data["apparent_zenith"]
        except KeyError:
            ApparentZenith = ephem_data["zenith"]
            logger.warning("could not find apparent_zenith. using zenith")
    else:
        ApparentZenith = zenith_data
    # ApparentZenith[ApparentZenith >= 90] = 90 # can cause problems in edge cases

    if linke_turbidity is None:
        # The .mat file 'LinkeTurbidities.mat' contains a single 2160 x 4320 x 12
        # matrix of type uint8 called 'LinkeTurbidity'. The rows represent global
        # latitudes from 90 to -90 degrees; the columns represent global longitudes
        # from -180 to 180; and the depth (third dimension) represents months of
        # the year from January (1) to December (12). To determine the Linke
        # turbidity for a position on the Earth's surface for a given month do the
        # following: LT = LinkeTurbidity(LatitudeIndex, LongitudeIndex, month).
        # Note that the numbers within the matrix are 20 * Linke Turbidity,
        # so divide the number from the file by 20 to get the
        # turbidity.

        try:
            import scipy.io
        except ImportError:
            raise ImportError(
                "The Linke turbidity lookup table requires scipy. "
                + "You can still use clearsky.ineichen if you "
                + "supply your own turbidities."
            )

        # consider putting this code at module level
        this_path = os.path.dirname(os.path.abspath(__file__))
        logger.debug("this_path={}".format(this_path))

        mat = scipy.io.loadmat(os.path.join(this_path, "data", "LinkeTurbidities.mat"))
        linke_turbidity = mat["LinkeTurbidity"]
        LatitudeIndex = np.round_(_linearly_scale(location.latitude, 90, -90, 1, 2160))
        LongitudeIndex = np.round_(_linearly_scale(location.longitude, -180, 180, 1, 4320))
        g = linke_turbidity[LatitudeIndex][LongitudeIndex]
        if interp_turbidity:
            logger.info("interpolating turbidity to the day")
            g2 = np.concatenate([[g[-1]], g, [g[0]]])  # wrap ends around
            days = np.linspace(-15, 380, num=14)  # map day of year onto month (approximate)
            LT = pd.Series(np.interp(time.dayofyear, days, g2), index=time)
        else:
            logger.info("using monthly turbidity")
            ApplyMonth = lambda x: g[x[0] - 1]
            LT = pd.DataFrame(time.month, index=time)
            LT = LT.apply(ApplyMonth, axis=1)
        TL = LT / 20.0
        logger.info("using TL=\n{}".format(TL))
    else:
        TL = linke_turbidity

    # Get the absolute airmass assuming standard local pressure (per
    # alt2pres) using Kasten and Young's 1989 formula for airmass.

    if airmass_data is None:
        AMabsolute = atmosphere.absoluteairmass(
            AMrelative=atmosphere.relativeairmass(ApparentZenith, airmass_model),
            pressure=atmosphere.alt2pres(location.altitude),
        )
    else:
        AMabsolute = airmass_data

    fh1 = np.exp(-location.altitude / 8000.0)
    fh2 = np.exp(-location.altitude / 1250.0)
    cg1 = 5.09e-05 * location.altitude + 0.868
    cg2 = 3.92e-05 * location.altitude + 0.0387
    logger.debug("fh1={}, fh2={}, cg1={}, cg2={}".format(fh1, fh2, cg1, cg2))

    #  Dan's note on the TL correction: By my reading of the publication on
    #  pages 151-157, Ineichen and Perez introduce (among other things) three
    #  things. 1) Beam model in eqn. 8, 2) new turbidity factor in eqn 9 and
    #  appendix A, and 3) Global horizontal model in eqn. 11. They do NOT appear
    #  to use the new turbidity factor (item 2 above) in either the beam or GHI
    #  models. The phrasing of appendix A seems as if there are two separate
    #  corrections, the first correction is used to correct the beam/GHI models,
    #  and the second correction is used to correct the revised turibidity
    #  factor. In my estimation, there is no need to correct the turbidity
    #  factor used in the beam/GHI models.

    #  Create the corrected TL for TL < 2
    #  TLcorr = TL;
    #  TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5);

    #  This equation is found in Solar Energy 73, pg 311.
    #  Full ref: Perez et. al., Vol. 73, pp. 307-317 (2002).
    #  It is slightly different than the equation given in Solar Energy 73, pg 156.
    #  We used the equation from pg 311 because of the existence of known typos
    #  in the pg 156 publication (notably the fh2-(TL-1) should be fh2 * (TL-1)).

    cos_zenith = tools.cosd(ApparentZenith)

    clearsky_GHI = (
        cg1 * I0 * cos_zenith * np.exp(-cg2 * AMabsolute * (fh1 + fh2 * (TL - 1))) * np.exp(0.01 * AMabsolute ** 1.8)
    )
    clearsky_GHI[clearsky_GHI < 0] = 0

    # BncI == "normal beam clear sky radiation"
    b = 0.664 + 0.163 / fh1
    BncI = b * I0 * np.exp(-0.09 * AMabsolute * (TL - 1))
    logger.debug("b={}".format(b))

    # "empirical correction" SE 73, 157 & SE 73, 312.
    BncI_2 = clearsky_GHI * (1 - (0.1 - 0.2 * np.exp(-TL)) / (0.1 + 0.882 / fh1)) / cos_zenith
    # return BncI, BncI_2
    clearsky_DNI = np.minimum(BncI, BncI_2)  # Will H: use np.minimum explicitly

    clearsky_DHI = clearsky_GHI - clearsky_DNI * cos_zenith

    df_out = pd.DataFrame({"GHI": clearsky_GHI, "DNI": clearsky_DNI, "DHI": clearsky_DHI})
    df_out.fillna(0, inplace=True)
    # df_out['BncI'] = BncI
    # df_out['BncI_2'] = BncI

    return df_out
Ejemplo n.º 44
0
def ineichen(time, latitude, longitude, altitude=0, linke_turbidity=None,
             solarposition_method='nrel_numpy', zenith_data=None,
             airmass_model='young1994', airmass_data=None,
             interp_turbidity=True):
    '''
    Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model

    Implements the Ineichen and Perez clear sky model for global horizontal
    irradiance (GHI), direct normal irradiance (DNI), and calculates
    the clear-sky diffuse horizontal (DHI) component as the difference
    between GHI and DNI*cos(zenith) as presented in [1, 2]. A report on clear
    sky models found the Ineichen/Perez model to have excellent performance
    with a minimal input data set [3].

    Default values for montly Linke turbidity provided by SoDa [4, 5].

    Parameters
    -----------
    time : pandas.DatetimeIndex

    latitude : float

    longitude : float

    altitude : float

    linke_turbidity : None or float
        If None, uses ``LinkeTurbidities.mat`` lookup table.

    solarposition_method : string
        Sets the solar position algorithm.
        See solarposition.get_solarposition()

    zenith_data : None or Series
        If None, ephemeris data will be calculated using ``solarposition_method``.

    airmass_model : string
        See pvlib.airmass.relativeairmass().

    airmass_data : None or Series
        If None, absolute air mass data will be calculated using
        ``airmass_model`` and location.alitude.

    interp_turbidity : bool
        If ``True``, interpolates the monthly Linke turbidity values
        found in ``LinkeTurbidities.mat`` to daily values.

    Returns
    --------
    DataFrame with the following columns: ``ghi, dni, dhi``.

    Notes
    -----
    If you are using this function
    in a loop, it may be faster to load LinkeTurbidities.mat outside of
    the loop and feed it in as a keyword argument, rather than
    having the function open and process the file each time it is called.

    References
    ----------

    [1] P. Ineichen and R. Perez, "A New airmass independent formulation for
        the Linke turbidity coefficient", Solar Energy, vol 73, pp. 151-157, 2002.

    [2] R. Perez et. al., "A New Operational Model for Satellite-Derived
        Irradiances: Description and Validation", Solar Energy, vol 73, pp.
        307-317, 2002.

    [3] M. Reno, C. Hansen, and J. Stein, "Global Horizontal Irradiance Clear
        Sky Models: Implementation and Analysis", Sandia National
        Laboratories, SAND2012-2389, 2012.

    [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained
        July 17, 2012).

    [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc.
        ISES Solar World Congress, June 2003. Goteborg, Sweden.
    '''
    # Initial implementation of this algorithm by Matthew Reno.
    # Ported to python by Rob Andrews
    # Added functionality by Will Holmgren (@wholmgren)

    I0 = irradiance.extraradiation(time.dayofyear)

    if zenith_data is None:
        ephem_data = solarposition.get_solarposition(time,
                                                     latitude=latitude,
                                                     longitude=longitude,
                                                     altitude=altitude,
                                                     method=solarposition_method)
        time = ephem_data.index # fixes issue with time possibly not being tz-aware
        try:
            ApparentZenith = ephem_data['apparent_zenith']
        except KeyError:
            ApparentZenith = ephem_data['zenith']
            logger.warning('could not find apparent_zenith. using zenith')
    else:
        ApparentZenith = zenith_data
    #ApparentZenith[ApparentZenith >= 90] = 90 # can cause problems in edge cases


    if linke_turbidity is None:
        TL = lookup_linke_turbidity(time, latitude, longitude,
                                    interp_turbidity=interp_turbidity)
    else:
        TL = linke_turbidity

    # Get the absolute airmass assuming standard local pressure (per
    # alt2pres) using Kasten and Young's 1989 formula for airmass.

    if airmass_data is None:
        AMabsolute = atmosphere.absoluteairmass(airmass_relative=atmosphere.relativeairmass(ApparentZenith, airmass_model),
                                                pressure=atmosphere.alt2pres(altitude))
    else:
        AMabsolute = airmass_data

    fh1 = np.exp(-altitude/8000.)
    fh2 = np.exp(-altitude/1250.)
    cg1 = 5.09e-05 * altitude + 0.868
    cg2 = 3.92e-05 * altitude + 0.0387
    logger.debug('fh1=%s, fh2=%s, cg1=%s, cg2=%s', fh1, fh2, cg1, cg2)

    #  Dan's note on the TL correction: By my reading of the publication on
    #  pages 151-157, Ineichen and Perez introduce (among other things) three
    #  things. 1) Beam model in eqn. 8, 2) new turbidity factor in eqn 9 and
    #  appendix A, and 3) Global horizontal model in eqn. 11. They do NOT appear
    #  to use the new turbidity factor (item 2 above) in either the beam or GHI
    #  models. The phrasing of appendix A seems as if there are two separate
    #  corrections, the first correction is used to correct the beam/GHI models,
    #  and the second correction is used to correct the revised turibidity
    #  factor. In my estimation, there is no need to correct the turbidity
    #  factor used in the beam/GHI models.

    #  Create the corrected TL for TL < 2
    #  TLcorr = TL;
    #  TLcorr(TL < 2) = TLcorr(TL < 2) - 0.25 .* (2-TLcorr(TL < 2)) .^ (0.5);

    #  This equation is found in Solar Energy 73, pg 311.
    #  Full ref: Perez et. al., Vol. 73, pp. 307-317 (2002).
    #  It is slightly different than the equation given in Solar Energy 73, pg 156.
    #  We used the equation from pg 311 because of the existence of known typos
    #  in the pg 156 publication (notably the fh2-(TL-1) should be fh2 * (TL-1)).

    cos_zenith = tools.cosd(ApparentZenith)

    clearsky_GHI = ( cg1 * I0 * cos_zenith *
                     np.exp(-cg2*AMabsolute*(fh1 + fh2*(TL - 1))) *
                     np.exp(0.01*AMabsolute**1.8) )
    clearsky_GHI[clearsky_GHI < 0] = 0

    # BncI == "normal beam clear sky radiation"
    b = 0.664 + 0.163/fh1
    BncI = b * I0 * np.exp( -0.09 * AMabsolute * (TL - 1) )
    logger.debug('b=%s', b)

    # "empirical correction" SE 73, 157 & SE 73, 312.
    BncI_2 = ( clearsky_GHI *
               ( 1 - (0.1 - 0.2*np.exp(-TL))/(0.1 + 0.882/fh1) ) /
               cos_zenith )

    clearsky_DNI = np.minimum(BncI, BncI_2)

    clearsky_DHI = clearsky_GHI - clearsky_DNI*cos_zenith

    df_out = pd.DataFrame({'ghi':clearsky_GHI, 'dni':clearsky_DNI,
                           'dhi':clearsky_DHI})
    df_out.fillna(0, inplace=True)

    return df_out
Ejemplo n.º 45
0
def dirint(ghi, zenith, times, pressure=101325, use_delta_kt_prime=True, 
           temp_dew=None):
    """
    Determine DNI from GHI using the DIRINT modification 
    of the DISC model.
    
    Implements the modified DISC model known as "DIRINT" introduced in [1].
    DIRINT predicts direct normal irradiance (DNI) from measured global
    horizontal irradiance (GHI). DIRINT improves upon the DISC model by
    using time-series GHI data and dew point temperature information. The
    effectiveness of the DIRINT model improves with each piece of
    information provided.

    Parameters
    ----------  
    ghi : pd.Series
        Global horizontal irradiance in W/m^2. 
    
    zenith : pd.Series
        True (not refraction-corrected) zenith
        angles in decimal degrees. If Z is a vector it must be of the
        same size as all other vector inputs. Z must be >=0 and <=180.
    
    times : DatetimeIndex
        
    pressure : float or pd.Series
        The site pressure in Pascal. 
        Pressure may be measured or an average pressure may be 
        calculated from site altitude.
    
    use_delta_kt_prime : bool
        Indicates if the user would like to
        utilize the time-series nature of the GHI measurements. A value of ``False``
        will not use the time-series improvements, any other numeric value
        will use time-series improvements. It is recommended that time-series
        data only be used if the time between measured data points is less
        than 1.5 hours. If none of the input arguments are
        vectors, then time-series improvements are not used (because it's not
        a time-series).
    
    temp_dew : None, float, or pd.Series 
        Surface dew point temperatures, in degrees C. 
        Values of temp_dew may be numeric or NaN. Any
        single time period point with a DewPtTemp=NaN does not have dew point
        improvements applied. If DewPtTemp is not provided, then dew point 
        improvements are not applied.  

    Returns
    -------
    DNI : pd.Series.
        The modeled direct normal irradiance in W/m^2 provided by the
        DIRINT model.

    References
    ----------
    [1] Perez, R., P. Ineichen, E. Maxwell, R. Seals and A. Zelenka, (1992).
    "Dynamic Global-to-Direct Irradiance Conversion Models".  ASHRAE 
    Transactions-Research Series, pp. 354-369

    [2] Maxwell, E. L., "A Quasi-Physical Model for Converting Hourly 
    Global Horizontal to Direct Normal Insolation", Technical 
    Report No. SERI/TR-215-3087, Golden, CO: Solar Energy Research 
    Institute, 1987.

    DIRINT model requires time series data (ie. one of the inputs must be a
    vector of length >2.
    """
    
    logger.debug('clearsky.dirint')
    
    disc_out = disc(ghi, zenith, times)
    kt = disc_out['Kt_gen_DISC']
    
    # Absolute Airmass, per the DISC model
    # Note that we calculate the AM pressure correction slightly differently
    # than Perez. He uses altitude, we use pressure (which we calculate
    # slightly differently)
    airmass = (1./(tools.cosd(zenith) + 0.15*((93.885-zenith)**(-1.253))) * 
               pressure/101325)
    
    coeffs = _get_dirint_coeffs()
    
    kt_prime = kt / (1.031 * np.exp(-1.4/(0.9+9.4/airmass)) + 0.1)
    kt_prime[kt_prime > 0.82] = 0.82 # From SRRL code. consider np.NaN
    kt_prime.fillna(0, inplace=True)
    logger.debug('kt_prime:\n{}'.format(kt_prime))
    
    # wholmgren: 
    # the use_delta_kt_prime statement is a port of the MATLAB code.
    # I am confused by the abs() in the delta_kt_prime calculation.
    # It is not the absolute value of the central difference.
    if use_delta_kt_prime:
        delta_kt_prime = 0.5*( (kt_prime - kt_prime.shift(1)).abs()
                              .add(
                               (kt_prime - kt_prime.shift(-1)).abs(), 
                                   fill_value=0))
    else:
        delta_kt_prime = pd.Series(-1, index=times)
    
    if temp_dew is not None:
        w = pd.Series(np.exp(0.07 * temp_dew - 0.075), index=times)
    else:
        w = pd.Series(-1, index=times)
    
    # @wholmgren: the following bin assignments use MATLAB's 1-indexing.
    # Later, we'll subtract 1 to conform to Python's 0-indexing.
    
    # Create kt_prime bins
    kt_prime_bin = pd.Series(index=times)
    kt_prime_bin[(kt_prime>=0) & (kt_prime<0.24)] = 1
    kt_prime_bin[(kt_prime>=0.24) & (kt_prime<0.4)] = 2
    kt_prime_bin[(kt_prime>=0.4) & (kt_prime<0.56)] = 3
    kt_prime_bin[(kt_prime>=0.56) & (kt_prime<0.7)] = 4
    kt_prime_bin[(kt_prime>=0.7) & (kt_prime<0.8)] = 5
    kt_prime_bin[(kt_prime>=0.8) & (kt_prime<=1)] = 6
    logger.debug('kt_prime_bin:\n{}'.format(kt_prime_bin))
    
    # Create zenith angle bins
    zenith_bin = pd.Series(index=times)
    zenith_bin[(zenith>=0) & (zenith<25)] = 1
    zenith_bin[(zenith>=25) & (zenith<40)] = 2
    zenith_bin[(zenith>=40) & (zenith<55)] = 3
    zenith_bin[(zenith>=55) & (zenith<70)] = 4
    zenith_bin[(zenith>=70) & (zenith<80)] = 5
    zenith_bin[(zenith>=80)] = 6
    logger.debug('zenith_bin:\n{}'.format(zenith_bin))
    
    # Create the bins for w based on dew point temperature
    w_bin = pd.Series(index=times)
    w_bin[(w>=0) & (w<1)] = 1
    w_bin[(w>=1) & (w<2)] = 2
    w_bin[(w>=2) & (w<3)] = 3
    w_bin[(w>=3)] = 4
    w_bin[(w == -1)] = 5
    logger.debug('w_bin:\n{}'.format(w_bin))

    # Create delta_kt_prime binning.
    delta_kt_prime_bin = pd.Series(index=times)
    delta_kt_prime_bin[(delta_kt_prime>=0) & (delta_kt_prime<0.015)] = 1
    delta_kt_prime_bin[(delta_kt_prime>=0.015) & (delta_kt_prime<0.035)] = 2
    delta_kt_prime_bin[(delta_kt_prime>=0.035) & (delta_kt_prime<0.07)] = 3
    delta_kt_prime_bin[(delta_kt_prime>=0.07) & (delta_kt_prime<0.15)] = 4
    delta_kt_prime_bin[(delta_kt_prime>=0.15) & (delta_kt_prime<0.3)] = 5
    delta_kt_prime_bin[(delta_kt_prime>=0.3) & (delta_kt_prime<=1)] = 6
    delta_kt_prime_bin[delta_kt_prime == -1] = 7
    logger.debug('delta_kt_prime_bin:\n{}'.format(delta_kt_prime_bin))
    
    # subtract 1 to account for difference between MATLAB-style bin
    # assignment and Python-style array lookup.
    dirint_coeffs = coeffs[kt_prime_bin-1, zenith_bin-1,
                           delta_kt_prime_bin-1, w_bin-1]
    
    dni = disc_out['DNI_gen_DISC'] * dirint_coeffs

    dni.name = 'DNI_DIRINT'
    
    return dni
Ejemplo n.º 46
0
def perez(surf_tilt, surf_az, DHI, DNI, DNI_ET, sun_zen, sun_az, AM,
          modelt='allsitescomposite1990'):
    '''
    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
    ----------

    surf_tilt : float or Series
          Surface tilt angles in decimal degrees.
          surf_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)

    surf_az : float or Series
          Surface azimuth angles in decimal degrees.
          surf_az 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 : float or Series
          diffuse horizontal irradiance in W/m^2.
          DHI must be >=0.

    DNI : float or Series
          direct normal irradiance in W/m^2.
          DNI must be >=0.

    DNI_ET : float or Series
          extraterrestrial normal irradiance in W/m^2.
           DNI_ET must be >=0.

    sun_zen : float or Series
          apparent (refraction-corrected) zenith
          angles in decimal degrees.
          sun_zen must be >=0 and <=180.

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

    AM : float or Series
          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)

    Other Parameters
    ----------------

    model : string (optional, default='allsitescomposite1990')

          a character 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'

    Returns
    --------

    float or Series

          the diffuse component of the solar radiation  on an
          arbitrarily tilted surface defined by the Perez model as given in
          reference [3].
          SkyDiffuse is the diffuse component ONLY and does not include the
          ground reflected irradiance or the irradiance due to the beam.


    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
    '''

    pvl_logger.debug('diffuse_sky.perez()')

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

    # epsilon is the sky's "clearness"
    eps = ((DHI + DNI) / DHI + kappa * (z ** 3)) / (1 + kappa * (z ** 3))

    # 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 = eps.copy()
    ebin[(eps < 1.065)] = 1
    ebin[(eps >= 1.065) & (eps < 1.23)] = 2
    ebin[(eps >= 1.23) & (eps < 1.5)] = 3
    ebin[(eps >= 1.5) & (eps < 1.95)] = 4
    ebin[(eps >= 1.95) & (eps < 2.8)] = 5
    ebin[(eps >= 2.8) & (eps < 4.5)] = 6
    ebin[(eps >= 4.5) & (eps < 6.2)] = 7
    ebin[eps >= 6.2] = 8

    ebin = ebin - 1  # correct for 0 indexing in coeffecient lookup

    # remove night time values
    ebin = ebin.dropna().astype(int)

    # This is added because in cases where the sun is below the horizon
    # (var.sun_zen > 90) but there is still diffuse horizontal light
    # (var.DHI>0), it is possible that the airmass (var.AM) could be NaN, which
    # messes up later calculations. Instead, if the sun is down, and there is
    # still var.DHI, we set the airmass to the airmass value on the horizon
    # (approximately 37-38).
    # var.AM(var.sun_zen >=90 & var.DHI >0) = 37;

    # var.DNI_ET[var.DNI_ET==0] = .00000001 #very hacky, fix this

    # delta is the sky's "brightness"
    delta = DHI * AM / DNI_ET

    # keep only valid times
    delta = delta[ebin.index]
    z = z[ebin.index]

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

    F1 = F1c[ebin, 0] + F1c[ebin, 1] * delta + F1c[ebin, 2] * z
    F1[F1 < 0] = 0
    F1 = F1.astype(float)

    F2 = F2c[ebin, 0] + F2c[ebin, 1] * delta + F2c[ebin, 2] * z
    F2[F2 < 0] = 0
    F2 = F2.astype(float)

    A = aoi_projection(surf_tilt, surf_az, sun_zen, sun_az)
    A[A < 0] = 0

    B = tools.cosd(sun_zen)
    B[B < tools.cosd(85)] = tools.cosd(85)

    # Calculate Diffuse POA from sky dome

    term1 = 0.5 * (1 - F1) * (1 + tools.cosd(surf_tilt))
    term2 = F1 * A[ebin.index] / B[ebin.index]
    term3 = F2 * tools.sind(surf_tilt)

    sky_diffuse = DHI[ebin.index] * (term1 + term2 + term3)
    sky_diffuse[sky_diffuse < 0] = 0

    return sky_diffuse
Ejemplo n.º 47
0
def klucher(surf_tilt, surf_az, DHI, GHI, sun_zen, sun_az):
    r'''
    Determine diffuse irradiance from the sky on a tilted surface
    using Klucher's 1979 model

    .. math::

       I_{d} = DHI \frac{1 + \cos\beta}{2} (1 + F' \sin^3(\beta/2))
       (1 + F' \cos^2\theta\sin^3\theta_z)

    where

    .. math::

       F' = 1 - (I_{d0} / GHI)

    Klucher's 1979 model determines 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, global
    horizontal irradiance, extraterrestrial irradiance, sun zenith angle,
    and sun azimuth angle.

    Parameters
    ----------

    surf_tilt : float or Series
        Surface tilt angles in decimal degrees.
        surf_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)

    surf_az : float or Series
        Surface azimuth angles in decimal degrees.
        surf_az 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 : float or Series
        diffuse horizontal irradiance in W/m^2.
        DHI must be >=0.

    GHI : float or Series
        Global  irradiance in W/m^2.
        DNI must be >=0.

    sun_zen : float or Series
        apparent (refraction-corrected) zenith
        angles in decimal degrees.
        sun_zen must be >=0 and <=180.

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

    Returns
    -------
    float or Series.

    The diffuse component of the solar radiation on an
    arbitrarily tilted surface defined by the Klucher model as given in
    Loutzenhiser et. al (2007) equation 4.
    SkyDiffuse is the diffuse component ONLY and does not include the ground
    reflected irradiance or the irradiance due to the beam.
    SkyDiffuse is a column vector vector with a number of elements equal to
    the input vector(s).

    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] Klucher, T.M., 1979. Evaluation of models to predict insolation on
    tilted surfaces. Solar Energy 23 (2), 111-114.
    '''

    pvl_logger.debug('diffuse_sky.klucher()')

    # zenith angle with respect to panel normal.
    cos_tt = aoi_projection(surf_tilt, surf_az, sun_zen, sun_az)

    F = 1 - ((DHI / GHI) ** 2)
    try:
        # fails with single point input
        F.fillna(0, inplace=True)
    except AttributeError:
        F = 0

    term1 = 0.5 * (1 + tools.cosd(surf_tilt))
    term2 = 1 + F * (tools.sind(0.5 * surf_tilt) ** 3)
    term3 = 1 + F * (cos_tt ** 2) * (tools.sind(sun_zen) ** 3)

    sky_diffuse = DHI * term1 * term2 * term3

    return sky_diffuse
Ejemplo n.º 48
0
def reindl(surf_tilt, surf_az, DHI, DNI, GHI, DNI_ET, sun_zen, sun_az):
    r'''
    Determine diffuse irradiance from the sky on a
    tilted surface using Reindl's 1990 model

    .. math::

       I_{d} = DHI (A R_b + (1 - A) (\frac{1 + \cos\beta}{2})
       (1 + \sqrt{\frac{I_{hb}}{I_h}} \sin^3(\beta/2)) )

    Reindl's 1990 model determines 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, global
    horizontal irradiance, extraterrestrial irradiance, sun zenith angle,
    and sun azimuth angle.

    Parameters
    ----------

    surf_tilt : float or Series.
        Surface tilt angles in decimal degrees.
        The tilt angle is defined as
        degrees from horizontal (e.g. surface facing up = 0, surface facing
        horizon = 90)

    surf_az : float or Series.
        Surface azimuth angles in decimal degrees.
        The Azimuth convention is defined
        as degrees east of north (e.g. North = 0, South=180 East = 90,
        West = 270).

    DHI : float or Series.
        diffuse horizontal irradiance in W/m^2.

    DNI : float or Series.
        direct normal irradiance in W/m^2.

    GHI: float or Series.
        Global irradiance in W/m^2.

    DNI_ET : float or Series.
        extraterrestrial normal irradiance in W/m^2.

    sun_zen : float or Series.
        apparent (refraction-corrected) zenith
        angles in decimal degrees.

    sun_az : float or Series.
        Sun azimuth angles in decimal degrees.
        The Azimuth convention is defined
        as degrees east of north (e.g. North = 0, East = 90, West = 270).

    Returns
    -------

    SkyDiffuse : float or Series.

        The diffuse component of the solar radiation  on an
        arbitrarily tilted surface defined by the Reindl model as given in
        Loutzenhiser et. al (2007) equation 8.
        SkyDiffuse is the diffuse component ONLY and does not include the
        ground reflected irradiance or the irradiance due to the beam.
        SkyDiffuse is a column vector vector with a number of elements equal to
        the input vector(s).


    Notes
    -----

    The POAskydiffuse calculation is generated from the Loutzenhiser et al.
    (2007) paper, equation 8. Note that I have removed the beam and ground
    reflectance portion of the equation and this generates ONLY the diffuse
    radiation from the sky and circumsolar, so the form of the equation
    varies slightly from equation 8.

    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] Reindl, D.T., Beckmann, W.A., Duffie, J.A., 1990a. Diffuse fraction
    correlations. Solar Energy 45(1), 1-7.

    [3] Reindl, D.T., Beckmann, W.A., Duffie, J.A., 1990b. Evaluation of hourly
    tilted surface radiation models. Solar Energy 45(1), 9-17.
    '''

    pvl_logger.debug('diffuse_sky.reindl()')

    cos_tt = aoi_projection(surf_tilt, surf_az, sun_zen, sun_az)

    cos_sun_zen = tools.cosd(sun_zen)

    # ratio of titled and horizontal beam irradiance
    Rb = cos_tt / cos_sun_zen

    # Anisotropy Index
    AI = DNI / DNI_ET

    # DNI projected onto horizontal
    HB = DNI * cos_sun_zen
    HB[HB < 0] = 0

    # these are actually the () and [] sub-terms of the second term of eqn 8
    term1 = 1 - AI
    term2 = 0.5 * (1 + tools.cosd(surf_tilt))
    term3 = 1 + np.sqrt(HB / GHI) * (tools.sind(0.5 * surf_tilt) ** 3)

    sky_diffuse = DHI * (AI * Rb + term1 * term2 * term3)
    sky_diffuse[sky_diffuse < 0] = 0

    return sky_diffuse
Ejemplo n.º 49
0
def haydavies(surf_tilt, surf_az, DHI, DNI, DNI_ET, sun_zen, sun_az):
    r'''
    Determine diffuse irradiance from the sky on a
    tilted surface using Hay & Davies' 1980 model

    .. math::
        I_{d} = DHI ( A R_b + (1 - A) (\frac{1 + \cos\beta}{2}) )

    Hay and Davies' 1980 model determines 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, and sun azimuth angle.

    Parameters
    ----------

    surf_tilt : float or Series
        Surface tilt angles in decimal degrees.
        The tilt angle is defined as
        degrees from horizontal (e.g. surface facing up = 0, surface facing
        horizon = 90)

    surf_az : float or Series
          Surface azimuth angles in decimal degrees.
          The Azimuth convention is defined
          as degrees east of north (e.g. North = 0, South=180 East = 90,
          West = 270).

    DHI : float or Series
          diffuse horizontal irradiance in W/m^2.

    DNI : float or Series
          direct normal irradiance in W/m^2.

    DNI_ET : float or Series
          extraterrestrial normal irradiance in W/m^2.

    sun_zen : float or Series
          apparent (refraction-corrected) zenith
          angles in decimal degrees.

    sun_az : float or Series
          Sun azimuth angles in decimal degrees.
          The Azimuth convention is defined
          as degrees east of north (e.g. North = 0, East = 90, West = 270).

    Returns
    --------

    SkyDiffuse : float or Series

          the diffuse component of the solar radiation  on an
          arbitrarily tilted surface defined by the Perez model as given in
          reference [3].
          SkyDiffuse is the diffuse component ONLY and does not include the
          ground reflected irradiance or the irradiance due to the beam.

    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] Hay, J.E., Davies, J.A., 1980. Calculations of the solar radiation
    incident on an inclined surface. In: Hay, J.E., Won, T.K. (Eds.), Proc. of
    First Canadian Solar Radiation Data Workshop, 59. Ministry of Supply
    and Services, Canada.
    '''

    pvl_logger.debug('diffuse_sky.haydavies()')

    cos_tt = aoi_projection(surf_tilt, surf_az, sun_zen, sun_az)

    cos_sun_zen = tools.cosd(sun_zen)

    # ratio of titled and horizontal beam irradiance
    Rb = cos_tt / cos_sun_zen

    # Anisotropy Index
    AI = DNI / DNI_ET

    # these are actually the () and [] sub-terms of the second term of eqn 7
    term1 = 1 - AI
    term2 = 0.5 * (1 + tools.cosd(surf_tilt))

    sky_diffuse = DHI * (AI * Rb + term1 * term2)
    sky_diffuse[sky_diffuse < 0] = 0

    return sky_diffuse