Esempio n. 1
0
def calculate_circumsolar_shading(percentage_distance_covered,
                                  model='uniform_disk'):
    """
    Select the model to calculate circumsolar shading based on the current PV
    array condition.

    :param float percentage_distance_covered: this represents how much of the
        circumsolar diameter is covered by the neighboring row [in %]
    :param str model: name of the circumsolar shading model to use:
        'uniform_disk' and 'gaussian' are the two models currently available
    :return: a ``float`` representing the percentage shading of the
        circumsolar disk [in %]
    """
    if model == 'uniform_disk':
        perc_shading = uniform_circumsolar_disk_shading(
            percentage_distance_covered)

    elif model == 'gaussian':
        perc_shading = gaussian_shading(percentage_distance_covered)

    else:
        raise PVFactorsError(
            'calculate_circumsolar_shading: model does not exist: ' +
            '%s' % model)

    return perc_shading
Esempio n. 2
0
    def __init__(self, faoi_fn_front, faoi_fn_back, n_integral_sections=300):
        """Instantiate class with faoi function and number of sections to use
        to calculate integrals of view factors with faoi losses

        Parameters
        ----------
        faoi_fn_front : function
            Function which takes a list (or numpy array) of incidence angles
            measured from the surface horizontal
            (with values from 0 to 180 deg) and returns the fAOI values for
            the front side of PV rows
        faoi_fn_back : function
            Function which takes a list (or numpy array) of incidence angles
            measured from the surface horizontal
            (with values from 0 to 180 deg) and returns the fAOI values for
            the back side of PV rows
        n_integral_sections : int, optional
            Number of integral divisions of the 0 to 180 deg interval
            to use for the fAOI loss integral (default = 300)
        """
        # Check that faoi fn where passed
        faoi_fns_ok = callable(faoi_fn_front) and callable(faoi_fn_back)
        if not faoi_fns_ok:
            raise PVFactorsError("The faoi_fn passed to the AOI methods are "
                                 "not callable. Please check the fAOI "
                                 "functions again")
        self.faoi_fn_front = faoi_fn_front
        self.faoi_fn_back = faoi_fn_back
        self.n_integral_sections = n_integral_sections
        # The following will be updated at fitting time
        self.interval = None
        self.aoi_angles_low = None
        self.aoi_angles_high = None
        self.integrand_front = None
        self.integrand_back = None
Esempio n. 3
0
def calculate_circumsolar_shading(percentage_distance_covered,
                                  model='uniform_disk'):
    """Select the model to calculate circumsolar shading based on the current PV
    array condition.

    Parameters
    ----------
    percentage_distance_covered : float
        this represents how much of the
        circumsolar diameter is covered by the neighboring row [in %]
    model : str, optional
        name of the circumsolar shading model to use:
        'uniform_disk' and 'gaussian' are the two models currently available
        (Default value = 'uniform_disk')

    Returns
    -------
    float
        shading percentage of circumsolar area

    """
    if model == 'uniform_disk':
        perc_shading = uniform_circumsolar_disk_shading(
            percentage_distance_covered)

    elif model == 'gaussian':
        perc_shading = gaussian_shading(percentage_distance_covered)

    else:
        raise PVFactorsError(
            'calculate_circumsolar_shading: model does not exist: ' +
            '%s' % model)

    return perc_shading
Esempio n. 4
0
    def __init__(self,
                 geometry=None,
                 style='-',
                 line_type=None,
                 shaded=None,
                 pvrow_index=None):
        """
        ``LinePVArray`` is the general class that is used to instantiate all the
        initial line objects of the pv array before putting them into the
        surface registry.
        It is a sub-class of a dictionary with already defined keys.

        :param geometry: ``shapely`` geometry object
        :param str style: ``matplotlib`` plotting style for the line. E.g. '--'.
        :param str line_type: type of surface in the :class:`pvarray.Array`,
            e.g. 'pvrow' or 'ground'
        :param bool shaded: specifies if surface is shaded (from direct shading)
        :param pvrow_index: if the surface's ``line_type`` is a 'pvrow', this
            will be its pv row index (which is different from its
            :attr:`pvarray.Array.surface_registry` index)
        """
        if line_type in self._list_line_types:
            super(LinePVArray, self).__init__(geometry=geometry,
                                              style=style,
                                              line_type=line_type,
                                              shaded=shaded,
                                              pvrow_index=pvrow_index)
        else:
            raise PVFactorsError("'line_type' cannot be: %s, \n possible "
                                 "values are: %s" %
                                 (str(line_type), str(self._list_line_types)))
Esempio n. 5
0
    def cut_pvrow_geometry(self, list_points, pvrow_index, side):
        """
        Break up pv row lines into multiple segments based on the list of
        points specified. This is the "discretization" of the pvrow segments.
        For now, it only works for pv rows.

        :param list_points: list of :class:`shapely.Point`, breaking points for
            the pv row lines.
        :param int pvrow_index: pv row index to specify the PV row to
            discretize; note that this could return multiple entries from the
            registry.
        :param str side: only do it for one side of the selected PV row. This
            can only be 'front' or 'back'.
        :return: None
        """
        # TODO: is currently not able to work for other surfaces than pv rows..
        for point in list_points:
            df_selected = self._obj.loc[
                (self._obj['pvrow_index'] == pvrow_index)
                & (self._obj['surface_side'] == side), :]
            geoentry_to_break_up = df_selected.loc[
                df_selected.pvgeometry.distance(point) < DISTANCE_TOLERANCE]
            if geoentry_to_break_up.shape[0] == 1:
                self.break_and_add_entries(geoentry_to_break_up, point)
            elif geoentry_to_break_up.shape[0] > 1:
                raise PVFactorsError("geoentry_to_break_up.shape[0] cannot be"
                                     "larger than 1")
Esempio n. 6
0
def check_collinear(list_elements):
    """Raise error if all :py:class:`~pvfactors.pvsurface.PVSegment`
    or :py:class:`~pvfactors.pvsurface.PVSurface` objects in list
    are not collinear"""
    is_col = is_collinear(list_elements)
    if not is_col:
        msg = "All elements should be collinear"
        raise PVFactorsError(msg)
Esempio n. 7
0
 def n_vector(self):
     """Unique normal vector of the shade collection, if it exists."""
     if not self.is_collinear:
         msg = "Cannot request n_vector if all elements not collinear"
         raise PVFactorsError(msg)
     if len(self.list_surfaces):
         return self.list_surfaces[0].n_vector
     else:
         return DEFAULT_NORMAL_VEC
Esempio n. 8
0
    def _illum_elements_from_coords_and_cut_pts(
            list_shadow_elements, cut_pt_coords, param_names, y_ground):
        """Create ground illuminated elements from a list of ordered shadow
        elements (from left to right), and the ground cut point coordinates.
        This method will make sure that the illuminated ground elements are
        all within the ground limits [MIN_X_GROUND, MAX_X_GROUND].

        Parameters
        ----------
        list_shadow_coords : \
        list of :py:class:`~pvfactors.geometry.timeseries.TsLineCoords`
            List of ordered ground shadow coordinates (from left to right)
        cut_point_coords : \
        list of :py:class:`~pvfactors.geometry.timeseries.TsLineCoords`
            List of cut point coordinates (from left to right)
        param_names : list
            List of parameter names for the ground elements

        Returns
        -------
        list_shadow_elements : \
        list of :py:class:`~pvfactors.geometry.pvground.TsGroundElement`
            Ordered list of shadow elements (from left to right)
        """

        list_illum_elements = []
        if len(list_shadow_elements) == 0:
            msg = """There must be at least one shadow element on the ground,
            otherwise it probably means that no PV rows were created, so
            there's no point in running a simulation..."""
            raise PVFactorsError(msg)
        n_steps = len(list_shadow_elements[0].coords.b1.x)
        y_ground_vec = y_ground * np.ones(n_steps)
        # FIXME: x_min and x_max should be passed as inputs
        next_x = MIN_X_GROUND * np.ones(n_steps)
        # Build the groud elements from left to right, starting at x_min
        # and covering the ground with illuminated elements where there's no
        # shadow
        for shadow_element in list_shadow_elements:
            x1 = next_x
            x2 = shadow_element.coords.b1.x
            coords = TsLineCoords.from_array(
                np.array([[x1, y_ground_vec], [x2, y_ground_vec]]))
            list_illum_elements.append(TsGroundElement(
                coords, list_ordered_cut_pts_coords=cut_pt_coords,
                param_names=param_names, shaded=False))
            next_x = shadow_element.coords.b2.x
        # Add the last illuminated element to the list
        coords = TsLineCoords.from_array(
            np.array([[next_x, y_ground_vec],
                      [MAX_X_GROUND * np.ones(n_steps), y_ground_vec]]))
        list_illum_elements.append(TsGroundElement(
            coords, list_ordered_cut_pts_coords=cut_pt_coords,
            param_names=param_names, shaded=False))

        return list_illum_elements
Esempio n. 9
0
 def facing(self):
     """
     This property is mainly used to calculate the view_matrix
     :return: direction that the pvrow front surfaces are facing
     """
     if self.tilt == 0.:
         direction = 'up'
     elif self.tilt > 0:
         direction = 'left'
     elif self.tilt < 0:
         direction = 'right'
     else:
         raise PVFactorsError("Unknown facing condition for pvrow")
     return direction
Esempio n. 10
0
    def split_pvrow_geometry(self, idx, line_shadow, pvrow_top_point):
        """
        Break up pv row line into two pv row lines, a shaded one and an
        unshaded one. This function requires knowing the pv row line index in
        the registry, the "shadow line" that intersects with the pv row, and
        the top point of the pv row in order to decide which pv row line will
        be shaded or not after break up.

        :param int idx: index of shaded pv row entry
        :param line_shadow: :class:`shapely.LineString` object representing the
            "shadow line" intersecting with the pv row line
        :param pvrow_top_point: the highest point of the pv row line (in the
            elevation direction)
        :param pvrow_top_point: :class:``shapely.Point`` object
        :return: None
        """
        # Define geometry to work on
        geometry = self._obj.loc[idx, 'geometry']
        # Find intersection point
        point_intersect = geometry.intersection(line_shadow)
        # Check that the intersection is not too close to a boundary: if it
        # is it can create a "memory access error" it seems
        is_too_close = [
            point.distance(point_intersect) < THRESHOLD_DISTANCE_TOO_CLOSE
            for point in geometry.boundary
        ]
        if True in is_too_close:
            # Leave it as it is and do not split geometry: it should not
            # affect the calculations for a good threshold
            pass
        else:
            # Cut geometry in two pieces
            list_new_lines = self.cut_linestring(geometry, point_intersect)
            # Add new geometry to index
            new_registry_entry = self._obj.loc[idx, :].copy()
            new_registry_entry['shaded'] = True
            if pvrow_top_point in list_new_lines[0].boundary:
                geometry_ill = pd.Series(list_new_lines[0])
                geometry_shaded = pd.Series(list_new_lines[1])
            elif pvrow_top_point in list_new_lines[1].boundary:
                geometry_ill = pd.Series(list_new_lines[1])
                geometry_shaded = pd.Series(list_new_lines[0])
            else:
                raise PVFactorsError("split_pvrow_geometry: "
                                     "unknown error occured")

            # Update registry
            self._obj.at[idx, 'geometry'] = geometry_ill.values[0]
            new_registry_entry['geometry'] = geometry_shaded.values[0]
            self._obj.loc[self._obj.shape[0] + 1, :] = new_registry_entry
Esempio n. 11
0
    def split_ground_geometry_from_edge_points(self, edge_points):
        """
        Break up ground lines into multiple ones at the pv row "edge points",
        which are the intersections of pv row lines and ground lines. This is
        important to separate the ground lines that a pv row's front surface
         sees from the ones its back surface does.

        :param edge_points: points the specify location where to break lines
        :type edge_points: list of :class:`shapely.Point` objects
        :return: None
        """
        for point in edge_points:
            df_ground = self._obj.loc[self._obj.loc[:, 'line_type'] ==
                                      'ground', :]
            geoentry_to_break_up = df_ground.loc[df_ground.pvgeometry.contains(
                point)]
            if geoentry_to_break_up.shape[0] == 1:
                self.break_and_add_entries(geoentry_to_break_up, point)
            elif geoentry_to_break_up.shape[0] > 1:
                raise PVFactorsError("geoentry_to_break_up.shape[0] cannot be"
                                     " larger than 1")
Esempio n. 12
0
def _check_uniform_shading(list_elements):
    """Check that all :py:class:`~pvfactors.geometry.base.PVSurface` objects in
    list have uniform shading

    Parameters
    ----------
    list_elements : list of :py:class:`~pvfactors.geometry.base.PVSurface`

    Raises
    ------
    PVFactorsError
        if all elements don't have the same shading flag
    """
    shaded = None
    for element in list_elements:
        if shaded is None:
            shaded = element.shaded
        else:
            is_uniform = shaded == element.shaded
            if not is_uniform:
                msg = "All elements should have same shading"
                raise PVFactorsError(msg)
Esempio n. 13
0
    def transform(self, idx):
        """
        Transform the ordered PV array for the given index.
        This means actually building the PV Row and Ground geometries. Note
        that the list of PV rows will be ordered from left to right in the
        geometry (along the x-axis), and indexed from 0 to n_pvrows - 1.
        This can only be run after the ``fit()`` method.

        Object attributes like ``pvrows`` and ``ground`` will be updated each
        time this method is run.

        Parameters
        ----------
        idx : int
            Index for which to build the simulation.
        """

        if idx < self.n_states:
            self.is_flat = self.rotation_vec[idx] == 0

            # Create PV row geometries
            self.pvrows = [ts_pvrow.at(idx) for ts_pvrow in self.ts_pvrows]

            # Create ground geometry with its shadows and cut points
            self.ground = self.ts_ground.at(idx)
            self.edge_points = [
                Point(coord.at(idx))
                for coord in self.ts_ground.cut_point_coords
            ]

            # Build lists of pv row neighbors, used to calculate view matrix
            self.front_neighbors, self.back_neighbors = \
                self._get_neighbors(self.rotation_vec[idx])

        else:
            msg = "Step index {} is out of range: [0 to {}]".format(
                idx, self.n_states - 1)
            raise PVFactorsError(msg)