Ejemplo n.º 1
0
def test_shadows_coords_left_right_of_cut_point():
    """Test that coords left and right of cut point are created correctly"""
    # Ground inputs
    shadow_coords = np.array(
        [[[[0], [0]], [[2], [0]]], [[[3], [0]], [[5], [0]]]], dtype=float)
    overlap = [False]

    # --- Create timeseries ground
    cut_point = TsPointCoords([2.5], [0])
    ts_ground = TsGround.from_ordered_shadows_coords(
        shadow_coords, flag_overlap=overlap, cut_point_coords=[cut_point])

    # Get left and right shadows
    shadows_left = ts_ground.shadow_coords_left_of_cut_point(0)
    shadows_right = ts_ground.shadow_coords_right_of_cut_point(0)

    # Reformat for testing
    shadows_left = [shadow.as_array for shadow in shadows_left]
    shadows_right = [shadow.as_array for shadow in shadows_right]

    expected_shadows_left = [
        shadow_coords[0], [cut_point.as_array, cut_point.as_array]
    ]
    expected_shadows_right = [[cut_point.as_array, cut_point.as_array],
                              shadow_coords[1]]

    # Test that correct
    np.testing.assert_allclose(shadows_left, expected_shadows_left)
    np.testing.assert_allclose(shadows_right, expected_shadows_right)

    # --- Case where pv rows are flat, cut point are inf
    cut_point = TsPointCoords([np.inf], [0])
    ts_ground = TsGround.from_ordered_shadows_coords(
        shadow_coords, flag_overlap=overlap, cut_point_coords=[cut_point])

    # Get right shadows
    shadows_right = ts_ground.shadow_coords_right_of_cut_point(0)

    # Test that correct
    maxi = MAX_X_GROUND
    expected_shadows_right = np.array([[[[maxi], [0.]], [[maxi], [0.]]],
                                       [[[maxi], [0.]], [[maxi], [0.]]]])
    shadows_right = [shadow.as_array for shadow in shadows_right]
    np.testing.assert_allclose(shadows_right, expected_shadows_right)

    # --- Case where pv rows are flat, cut point are - inf
    cut_point = TsPointCoords([-np.inf], [0])
    ts_ground = TsGround.from_ordered_shadows_coords(
        shadow_coords, flag_overlap=overlap, cut_point_coords=[cut_point])

    # Get left shadows
    shadows_left = ts_ground.shadow_coords_left_of_cut_point(0)

    # Test that correct
    mini = MIN_X_GROUND
    expected_shadows_left = np.array([[[[mini], [0.]], [[mini], [0.]]],
                                      [[[mini], [0.]], [[mini], [0.]]]])
    shadows_left = [shadow.as_array for shadow in shadows_left]
    np.testing.assert_allclose(shadows_left, expected_shadows_left)
Ejemplo n.º 2
0
def test_calculate_aoi_angles():
    """Make sure calculation of AOI angles is correct"""
    u_vector = np.array([[1, 2, 3], [0, 0, 0]])
    centroid = TsPointCoords(np.array([0.5, 1, 1]), np.array([0, 0, 0]))
    point = TsPointCoords(np.array([1, 0, 1]), np.array([0.5, 1, 5]))

    aoi_angles = AOIMethods._calculate_aoi_angles(u_vector, centroid, point)
    expected_aoi_angles = [45, 135, 90]
    np.testing.assert_allclose(aoi_angles, expected_aoi_angles)
Ejemplo n.º 3
0
    def from_ts_pvrows_and_angles(cls, list_ts_pvrows, alpha_vec, rotation_vec,
                                  y_ground=Y_GROUND, flag_overlap=None,
                                  param_names=None):
        """Create timeseries ground from list of timeseries PV rows, and
        PV array and solar angles.

        Parameters
        ----------
        list_ts_pvrows : \
        list of :py:class:`~pvfactors.geometry.pvrow.TsPVRow`
            Timeseries PV rows to use to calculate timeseries ground shadows
        alpha_vec : np.ndarray
            Angle made by 2d solar vector and PV array x-axis [rad]
        rotation_vec : np.ndarray
            Timeseries rotation values of the PV row [deg]
        y_ground : float, optional
            Fixed y coordinate of flat ground [m] (Default = Y_GROUND constant)
        flag_overlap : list of bool, optional
            Flags indicating if the ground shadows are overlapping, for all
            time steps (Default=None). I.e. is there direct shading on pv rows?
        param_names : list of str, optional
            List of names of surface parameters to use when creating geometries
            (Default = None)
        """
        rotation_vec = np.deg2rad(rotation_vec)
        n_steps = len(rotation_vec)
        # Calculate coords of ground shadows and cutting points
        ground_shadow_coords = []
        cut_point_coords = []
        for ts_pvrow in list_ts_pvrows:
            # Get pvrow coords
            x1s_pvrow = ts_pvrow.full_pvrow_coords.b1.x
            y1s_pvrow = ts_pvrow.full_pvrow_coords.b1.y
            x2s_pvrow = ts_pvrow.full_pvrow_coords.b2.x
            y2s_pvrow = ts_pvrow.full_pvrow_coords.b2.y
            # --- Shadow coords calculation
            # Calculate x coords of shadow
            x1s_shadow = x1s_pvrow - (y1s_pvrow - y_ground) / np.tan(alpha_vec)
            x2s_shadow = x2s_pvrow - (y2s_pvrow - y_ground) / np.tan(alpha_vec)
            # Order x coords from left to right
            x1s_on_left = x1s_shadow <= x2s_shadow
            xs_left_shadow = np.where(x1s_on_left, x1s_shadow, x2s_shadow)
            xs_right_shadow = np.where(x1s_on_left, x2s_shadow, x1s_shadow)
            # Append shadow coords to list
            ground_shadow_coords.append(
                [[xs_left_shadow, y_ground * np.ones(n_steps)],
                 [xs_right_shadow, y_ground * np.ones(n_steps)]])
            # --- Cutting points coords calculation
            dx = (y1s_pvrow - y_ground) / np.tan(rotation_vec)
            cut_point_coords.append(
                TsPointCoords(x1s_pvrow - dx, y_ground * np.ones(n_steps)))

        ground_shadow_coords = np.array(ground_shadow_coords)
        return cls.from_ordered_shadows_coords(
            ground_shadow_coords, flag_overlap=flag_overlap,
            cut_point_coords=cut_point_coords, param_names=param_names,
            y_ground=y_ground)
Ejemplo n.º 4
0
def test_ts_ground_elements_surfaces():
    """Check timeseries ground elements are created correctly"""

    # Create timeseries coords
    gnd_element_coords = TsLineCoords.from_array(
        np.array([[[-1, -1], [0, 0]], [[1, 1], [0, 0]]]))
    pt_coords_1 = TsPointCoords.from_array(np.array([[-0.5, -1], [0, 0]]))
    pt_coords_2 = TsPointCoords.from_array(np.array([[0.5, 0], [0, 0]]))

    # Create gnd element
    gnd_element = TsGroundElement(
        gnd_element_coords,
        list_ordered_cut_pts_coords=[pt_coords_1, pt_coords_2])

    # Check that structures contain the correct number of ts surfaces
    assert len(gnd_element.surface_list) == 3
    assert len(gnd_element.surface_dict[0]['left']) == 1
    assert len(gnd_element.surface_dict[1]['left']) == 2
    assert len(gnd_element.surface_dict[0]['right']) == 2
    assert len(gnd_element.surface_dict[1]['right']) == 1
    # Check that the objects are the same
    assert (
        gnd_element.surface_list[0] == gnd_element.surface_dict[0]['left'][0])
    assert (
        gnd_element.surface_list[0] == gnd_element.surface_dict[1]['left'][0])
    assert (
        gnd_element.surface_list[1] == gnd_element.surface_dict[0]['right'][0])
    assert (
        gnd_element.surface_list[1] == gnd_element.surface_dict[1]['left'][1])
    assert (
        gnd_element.surface_list[2] == gnd_element.surface_dict[0]['right'][1])
    assert (
        gnd_element.surface_list[2] == gnd_element.surface_dict[1]['right'][0])
    # Now check surfaces lengths
    np.testing.assert_allclose(gnd_element.surface_list[0].length, [0.5, 0])
    np.testing.assert_allclose(gnd_element.surface_list[1].length, [1, 1])
    np.testing.assert_allclose(gnd_element.surface_list[2].length, [0.5, 1])
    # Check coords of surfaces
    np.testing.assert_allclose(gnd_element.surface_list[0].b1.x, [-1, -1])
    np.testing.assert_allclose(gnd_element.surface_list[0].b2.x, [-0.5, -1])
Ejemplo n.º 5
0
def test_ts_ground_to_geometry():

    # There should be an overlap
    shadow_coords = np.array([[[[0, 0], [0, 0]], [[2, 1], [0, 0]]],
                              [[[1, 2], [0, 0]], [[5, 5], [0, 0]]]])
    overlap = [True, False]
    cut_point_coords = [TsPointCoords.from_array(np.array([[2, 2], [0, 0]]))]

    # Test with overlap
    ts_ground = TsGround.from_ordered_shadows_coords(
        shadow_coords, flag_overlap=overlap, cut_point_coords=cut_point_coords)

    # Run some checks for index 0
    pvground = ts_ground.at(0,
                            merge_if_flag_overlap=False,
                            with_cut_points=False)
    assert pvground.n_surfaces == 4
    assert pvground.list_segments[0].illum_collection.n_surfaces == 2
    assert pvground.list_segments[0].shaded_collection.n_surfaces == 2
    assert pvground.list_segments[0].shaded_collection.length == 5
    np.testing.assert_allclose(pvground.shaded_length, 5)

    # Run some checks for index 1
    pvground = ts_ground.at(1, with_cut_points=False)
    assert pvground.n_surfaces == 5
    assert pvground.list_segments[0].illum_collection.n_surfaces == 3
    assert pvground.list_segments[0].shaded_collection.n_surfaces == 2
    assert pvground.list_segments[0].shaded_collection.length == 4
    np.testing.assert_allclose(pvground.shaded_length, 4)

    # Run some checks for index 0, when merging
    pvground = ts_ground.at(0,
                            merge_if_flag_overlap=True,
                            with_cut_points=False)
    assert pvground.n_surfaces == 3
    assert pvground.list_segments[0].illum_collection.n_surfaces == 2
    assert pvground.list_segments[0].shaded_collection.n_surfaces == 1
    assert pvground.list_segments[0].shaded_collection.length == 5
    np.testing.assert_allclose(pvground.shaded_length, 5)

    # Run some checks for index 0, when merging and with cut points
    pvground = ts_ground.at(0,
                            merge_if_flag_overlap=True,
                            with_cut_points=True)
    assert pvground.n_surfaces == 4
    assert pvground.list_segments[0].illum_collection.n_surfaces == 2
    assert pvground.list_segments[0].shaded_collection.n_surfaces == 2
    assert pvground.list_segments[0].shaded_collection.length == 5
    np.testing.assert_allclose(pvground.shaded_length, 5)
Ejemplo n.º 6
0
    def _calculate_aoi_angles_w_obstruction(self, u_vector, centroid,
                                            point_gnd, point_obstr,
                                            gnd_surf_is_left):
        """Calculate AOI angles for a PV row surface of the
        :py:class:`~pvfactors.geometry.pvarray.OrderedPVArray` that sees
        a ground surface, while being potentially obstructed by another
        PV row

        Parameters
        ----------
        u_vector : np.ndarray
            Direction vector of the surface for which to calculate AOI angles
        centroid : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords`
            Centroid point of PV row surface for which to calculate AOI angles
        point : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords`
            Point of ground surface that will determine AOI angle
        point_obstr: :py:class:`~pvfactors.geometry.timeseries.TsPointCoords`
            Potentially obstructing point for the view aoi angle calculation
        gnd_surf_is_left : bool
            Flag specifying whether ground surface is left of PV row's cut
            point or not

        Returns
        -------
        np.ndarray
            AOI angles formed by remote point and centroid on surface,
            measured against surface direction vector, accounting for
            potential obstruction [degrees]
        """
        if point_obstr is None:
            # There is no obstruction
            point = point_gnd
        else:
            # Determine if there is obstruction by using the angles made by
            # specific strings with the x-axis
            alpha_pv = self._angle_with_x_axis(point_gnd, centroid)
            alpha_ob = self._angle_with_x_axis(point_gnd, point_obstr)
            if gnd_surf_is_left:
                is_obstructing = alpha_pv > alpha_ob
            else:
                is_obstructing = alpha_pv < alpha_ob
            x = np.where(is_obstructing, point_obstr.x, point_gnd.x)
            y = np.where(is_obstructing, point_obstr.y, point_gnd.y)
            point = TsPointCoords(x, y)

        aoi_angles = self._calculate_aoi_angles(u_vector, centroid, point)
        return aoi_angles
Ejemplo n.º 7
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.º 8
0
    def vf_aoi_pvrow_to_sky(self, ts_pvrows, ts_ground, tilted_to_left,
                            vf_matrix):
        """Calculate the view factors between timeseries PV row surface and sky
        while accounting for AOI losses,
        and assign values to the passed view factor matrix using
        the surface indices.

        Parameters
        ----------
        ts_pvrows : list of :py:class:`~pvfactors.geometry.timeseries.TsPVRow`
            List of timeseries PV rows in the PV array
        ts_ground : :py:class:`~pvfactors.geometry.timeseries.TsGround`
            Timeseries ground of the PV array
        tilted_to_left : list of bool
            Flags indicating when the PV rows are strictly tilted to the left
        vf_matrix : np.ndarray
            View factor matrix to update during calculation. Should have 3
            dimensions as follows: [n_surfaces, n_surfaces, n_timesteps]
        """
        sky_index = vf_matrix.shape[0] - 1
        # --- Build list of dummy sky surfaces
        # create sky left open area
        pt_1 = TsPointCoords(ts_ground.x_min * np.ones_like(tilted_to_left),
                             ts_ground.y_ground * np.ones_like(tilted_to_left))
        pt_2 = ts_pvrows[0].highest_point
        sky_left = TsSurface(TsLineCoords(pt_1, pt_2))
        # create sky right open area
        pt_1 = TsPointCoords(ts_ground.x_max * np.ones_like(tilted_to_left),
                             ts_ground.y_ground * np.ones_like(tilted_to_left))
        pt_2 = ts_pvrows[-1].highest_point
        sky_right = TsSurface(TsLineCoords(pt_2, pt_1))
        # Add sky surfaces in-between PV rows
        dummy_sky_surfaces = [sky_left]
        for idx_pvrow, ts_pvrow in enumerate(ts_pvrows[:-1]):
            right_ts_pvrow = ts_pvrows[idx_pvrow + 1]
            pt_1 = ts_pvrow.highest_point
            pt_2 = right_ts_pvrow.highest_point
            sky_surface = TsSurface(TsLineCoords(pt_1, pt_2))
            dummy_sky_surfaces.append(sky_surface)
        # Add sky right open area
        dummy_sky_surfaces.append(sky_right)

        # Now calculate vf_aoi for all PV row surfaces to sky
        for idx_pvrow, ts_pvrow in enumerate(ts_pvrows):
            # Get dummy sky surfaces
            sky_left = dummy_sky_surfaces[idx_pvrow]
            sky_right = dummy_sky_surfaces[idx_pvrow + 1]
            # Calculate vf_aoi for surfaces in PV row
            # front side
            front = ts_pvrow.front
            for front_surf in front.all_ts_surfaces:
                vf_aoi_left = self._vf_aoi_surface_to_surface(front_surf,
                                                              sky_left,
                                                              is_back=False)
                vf_aoi_right = self._vf_aoi_surface_to_surface(front_surf,
                                                               sky_right,
                                                               is_back=False)
                vf_aoi = np.where(tilted_to_left, vf_aoi_left, vf_aoi_right)
                vf_matrix[front_surf.index, sky_index, :] = vf_aoi
            # back side
            back = ts_pvrow.back
            for back_surf in back.all_ts_surfaces:
                vf_aoi_left = self._vf_aoi_surface_to_surface(back_surf,
                                                              sky_left,
                                                              is_back=True)
                vf_aoi_right = self._vf_aoi_surface_to_surface(back_surf,
                                                               sky_right,
                                                               is_back=True)
                vf_aoi = np.where(tilted_to_left, vf_aoi_right, vf_aoi_left)
                vf_matrix[back_surf.index, sky_index, :] = vf_aoi
Ejemplo n.º 9
0
    def calculate_vf_to_gnd(self, pvrow_element_coords, pvrow_idx, n_pvrows,
                            n_steps, y_ground, cut_point_coords,
                            pvrow_element_length, tilted_to_left, ts_pvrows):
        """Calculate view factors from timeseries pvrow_element to the entire
        ground.

        Parameters
        ----------
        pvrow_element_coords :
        :py:class:`~pvfactors.geometry.timeseries.TsLineCoords`
            Timeseries line coordinates of pvrow element
        pvrow_idx : int
            Index of the timeseries PV row on the which the pvrow_element is
        n_pvrows : int
            Number of timeseries PV rows in the PV array
        n_steps : int
            Number of timesteps for which to calculate the pvfactors
        y_ground : float
            Y-coordinate of the flat ground [m]
        cut_point_coords : list of
        :py:class:`~pvfactors.geometry.timeseries.TsPointCoords`
            List of cut point coordinates, as calculated for timeseries PV rows
        pvrow_element_length : float or np.ndarray
            Length (width) of the timeseries pvrow_element [m]
        tilted_to_left : list of bool
            Flags indicating when the PV rows are strictly tilted to the left
        ts_pvrows : list of :py:class:`~pvfactors.geometry.timeseries.TsPVRow`
            Timeseries PV row geometries that will be used in the calculation

        Returns
        -------
        vf_to_gnd : np.ndarray
            View factors from timeseries pvrow_element to the entire ground
        """

        pvrow_lowest_pt = ts_pvrows[pvrow_idx].full_pvrow_coords.lowest_point
        if pvrow_idx == 0:
            # There is no obstruction to view of the ground on the left
            coords_left_gnd = TsLineCoords(
                TsPointCoords(MIN_X_GROUND * np.ones(n_steps), y_ground),
                TsPointCoords(np.minimum(MAX_X_GROUND, cut_point_coords.x),
                              y_ground))
            vf_left_ground = self._vf_surface_to_surface(
                pvrow_element_coords, coords_left_gnd, pvrow_element_length)
        else:
            # The left PV row obstructs the view of the ground on the left
            left_pt_neighbor = \
                ts_pvrows[pvrow_idx - 1].full_pvrow_coords.lowest_point
            coords_gnd_proxy = TsLineCoords(left_pt_neighbor, pvrow_lowest_pt)
            vf_left_ground = self._vf_surface_to_surface(
                pvrow_element_coords, coords_gnd_proxy, pvrow_element_length)

        if pvrow_idx == (n_pvrows - 1):
            # There is no obstruction of the view of the ground on the right
            coords_right_gnd = TsLineCoords(
                TsPointCoords(np.maximum(MIN_X_GROUND, cut_point_coords.x),
                              y_ground),
                TsPointCoords(MAX_X_GROUND * np.ones(n_steps), y_ground))
            vf_right_ground = self._vf_surface_to_surface(
                pvrow_element_coords, coords_right_gnd, pvrow_element_length)
        else:
            # The right PV row obstructs the view of the ground on the right
            right_pt_neighbor = \
                ts_pvrows[pvrow_idx + 1].full_pvrow_coords.lowest_point
            coords_gnd_proxy = TsLineCoords(pvrow_lowest_pt, right_pt_neighbor)
            vf_right_ground = self._vf_surface_to_surface(
                pvrow_element_coords, coords_gnd_proxy, pvrow_element_length)

        # Merge the views of the ground for the back side
        vf_ground = np.where(tilted_to_left, vf_right_ground, vf_left_ground)

        return vf_ground