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
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
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
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)))
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")
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)
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
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
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
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
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")
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)
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)