Source code for pvfactors.viewfactors.vfmethods

"""Module with view factor calculation tools"""

from pvfactors.config import MIN_X_GROUND, MAX_X_GROUND, DISTANCE_TOLERANCE
from pvfactors.geometry.timeseries import TsLineCoords, TsPointCoords
from pvlib.tools import cosd, sind
import numpy as np


[docs]class VFTsMethods(object): """This class contains all the methods used to calculate timeseries view factors for all the surfaces in :py:class:`~pvfactors.geometry.pvarray.OrderedPVArray`""" def vf_pvrow_gnd_surf(self, ts_pvrows, ts_ground, tilted_to_left, vf_matrix): """Calculate the view factors between timeseries PV row and ground surfaces, and assign it 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] """ n_pvrows = len(ts_pvrows) for idx_pvrow, ts_pvrow in enumerate(ts_pvrows): # Separate gnd surfaces depending on side left_gnd_surfaces = ts_ground.ts_surfaces_side_of_cut_point( 'left', idx_pvrow) right_gnd_surfaces = ts_ground.ts_surfaces_side_of_cut_point( 'right', idx_pvrow) # Front side front = ts_pvrow.front for pvrow_surf in front.all_ts_surfaces: if pvrow_surf.is_empty: # do no run calculation for this surface continue ts_length = pvrow_surf.length i = pvrow_surf.index for gnd_surf in left_gnd_surfaces: if gnd_surf.is_empty: # do no run this calculation continue j = gnd_surf.index vf_pvrow_to_gnd, vf_gnd_to_pvrow = ( self.vf_pvrow_surf_to_gnd_surf_obstruction_hottel( pvrow_surf, idx_pvrow, n_pvrows, tilted_to_left, ts_pvrows, gnd_surf, ts_length, is_back=False, is_left=True)) vf_matrix[i, j, :] = vf_pvrow_to_gnd vf_matrix[j, i, :] = vf_gnd_to_pvrow for gnd_surf in right_gnd_surfaces: if gnd_surf.is_empty: # do no run this calculation continue j = gnd_surf.index vf_pvrow_to_gnd, vf_gnd_to_pvrow = ( self.vf_pvrow_surf_to_gnd_surf_obstruction_hottel( pvrow_surf, idx_pvrow, n_pvrows, tilted_to_left, ts_pvrows, gnd_surf, ts_length, is_back=False, is_left=False)) vf_matrix[i, j, :] = vf_pvrow_to_gnd vf_matrix[j, i, :] = vf_gnd_to_pvrow # Back side back = ts_pvrow.back for pvrow_surf in back.all_ts_surfaces: if pvrow_surf.is_empty: # do no run calculation for this surface continue ts_length = pvrow_surf.length i = pvrow_surf.index for gnd_surf in left_gnd_surfaces: if gnd_surf.is_empty: # do no run this calculation continue j = gnd_surf.index vf_pvrow_to_gnd, vf_gnd_to_pvrow = ( self.vf_pvrow_surf_to_gnd_surf_obstruction_hottel( pvrow_surf, idx_pvrow, n_pvrows, tilted_to_left, ts_pvrows, gnd_surf, ts_length, is_back=True, is_left=True)) vf_matrix[i, j, :] = vf_pvrow_to_gnd vf_matrix[j, i, :] = vf_gnd_to_pvrow for gnd_surf in right_gnd_surfaces: if gnd_surf.is_empty: # do no run this calculation continue j = gnd_surf.index vf_pvrow_to_gnd, vf_gnd_to_pvrow = ( self.vf_pvrow_surf_to_gnd_surf_obstruction_hottel( pvrow_surf, idx_pvrow, n_pvrows, tilted_to_left, ts_pvrows, gnd_surf, ts_length, is_back=True, is_left=False)) vf_matrix[i, j, :] = vf_pvrow_to_gnd vf_matrix[j, i, :] = vf_gnd_to_pvrow def vf_pvrow_surf_to_gnd_surf_obstruction_hottel( self, pvrow_surf, pvrow_idx, n_pvrows, tilted_to_left, ts_pvrows, gnd_surf, pvrow_surf_length, is_back=True, is_left=True): """Calculate view factors from timeseries PV row surface to a timeseries ground surface. This will return the calculated view factors from the PV row surface to the ground surface, AND from the ground surface to the PV row surface (using reciprocity). Parameters ---------- pvrow_surf : :py:class:`~pvfactors.geometry.timeseries.TsSurface` Timeseries PV row surface to use for calculation pvrow_idx : int Index of the timeseries PV row on the which the pvrow_surf is n_pvrows : int Number of timeseries PV rows in the PV array, and therefore number of shadows they cast on the ground 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` List of timeseries PV rows in the PV array gnd_surf : :py:class:`~pvfactors.geometry.timeseries.TsSurface` Timeseries ground surface to use for calculation pvrow_surf_length : np.ndarray Length (width) of the timeseries PV row surface [m] is_back : bool Flag specifying whether pv row surface is on back or front surface (Default = True) is_left : bool Flag specifying whether gnd surface is left of pv row cut point or not (Default = True) Returns ------- vf_pvrow_to_gnd_surf : np.ndarray View factors from timeseries PV row surface to timeseries ground surface, dimension is [n_timesteps] vf_gnd_to_pvrow_surf : np.ndarray View factors from timeseries ground surface to timeseries PV row surface, dimension is [n_timesteps] """ pvrow_surf_lowest_pt = pvrow_surf.lowest_point pvrow_surf_highest_pt = pvrow_surf.highest_point no_obstruction = (is_left & (pvrow_idx == 0)) \ or ((not is_left) & (pvrow_idx == n_pvrows - 1)) if no_obstruction: # There is no obstruction to the gnd surface vf_pvrow_to_gnd_surf = self._vf_surface_to_surface( pvrow_surf.coords, gnd_surf, pvrow_surf_length) else: # Get lowest point of obstructing point idx_obstructing_pvrow = pvrow_idx - 1 if is_left else pvrow_idx + 1 pt_obstr = ts_pvrows[idx_obstructing_pvrow ].full_pvrow_coords.lowest_point # Calculate vf from pv row to gnd surface vf_pvrow_to_gnd_surf = self._vf_hottel_gnd_surf( pvrow_surf_highest_pt, pvrow_surf_lowest_pt, gnd_surf.b1, gnd_surf.b2, pt_obstr, pvrow_surf_length, is_left) # Final result depends on whether front or back surface if is_left: vf_pvrow_to_gnd_surf = ( np.where(tilted_to_left, 0., vf_pvrow_to_gnd_surf) if is_back else np.where(tilted_to_left, vf_pvrow_to_gnd_surf, 0.)) else: vf_pvrow_to_gnd_surf = ( np.where(tilted_to_left, vf_pvrow_to_gnd_surf, 0.) if is_back else np.where(tilted_to_left, 0., vf_pvrow_to_gnd_surf)) # Use reciprocity to calculate ts vf from gnd surf to pv row surface gnd_surf_length = gnd_surf.length vf_gnd_to_pvrow_surf = np.where( gnd_surf_length > DISTANCE_TOLERANCE, vf_pvrow_to_gnd_surf * pvrow_surf_length / gnd_surf_length, 0.) return vf_pvrow_to_gnd_surf, vf_gnd_to_pvrow_surf def vf_pvrow_to_pvrow(self, ts_pvrows, tilted_to_left, vf_matrix): """Calculate the view factors between timeseries PV row surfaces, 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 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] """ for idx_pvrow, ts_pvrow in enumerate(ts_pvrows[:-1]): # Get the next pv row right_ts_pvrow = ts_pvrows[idx_pvrow + 1] # front side front = ts_pvrow.front for surf_i in front.all_ts_surfaces: if surf_i.is_empty: # do no run calculation for this surface continue i = surf_i.index length_i = surf_i.length for surf_j in right_ts_pvrow.back.all_ts_surfaces: if surf_j.is_empty: # do no run calculation for this surface continue j = surf_j.index length_j = surf_j.length vf_i_to_j = self._vf_surface_to_surface( surf_i.coords, surf_j.coords, length_i) vf_i_to_j = np.where(tilted_to_left, 0., vf_i_to_j) vf_j_to_i = np.where( surf_j.length > DISTANCE_TOLERANCE, vf_i_to_j * length_i / length_j, 0.) vf_matrix[i, j, :] = vf_i_to_j vf_matrix[j, i, :] = vf_j_to_i # back side back = ts_pvrow.back for surf_i in back.all_ts_surfaces: if surf_i.is_empty: # do no run calculation for this surface continue i = surf_i.index length_i = surf_i.length for surf_j in right_ts_pvrow.front.all_ts_surfaces: if surf_j.is_empty: # do no run calculation for this surface continue j = surf_j.index length_j = surf_j.length vf_i_to_j = self._vf_surface_to_surface( surf_i.coords, surf_j.coords, length_i) vf_i_to_j = np.where(tilted_to_left, vf_i_to_j, 0.) vf_j_to_i = np.where( surf_j.length > DISTANCE_TOLERANCE, vf_i_to_j * length_i / length_j, 0.) vf_matrix[i, j, :] = vf_i_to_j vf_matrix[j, i, :] = vf_j_to_i def calculate_vf_to_pvrow(self, pvrow_element_coords, pvrow_idx, n_pvrows, n_steps, ts_pvrows, pvrow_element_length, tilted_to_left, pvrow_width, rotation_vec): """Calculate view factors from timeseries pvrow element to timeseries PV rows around it. 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 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 ts_pvrows : list of :py:class:`~pvfactors.geometry.timeseries.TsPVRow` Timeseries PV row geometries that will be used in the calculation 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 pvrow_width : float Width of the timeseries PV rows in the PV array [m], which is constant rotation_vec : np.ndarray Rotation angles of the PV rows [deg] Returns ------- vf_to_pvrow : np.ndarray View factors from timeseries pvrow_element to neighboring PV rows vf_to_shaded_pvrow : np.ndarray View factors from timeseries pvrow_element to shaded areas of the neighboring PV rows """ if pvrow_idx == 0: vf_left_pvrow = np.zeros(n_steps) vf_left_shaded_pvrow = np.zeros(n_steps) else: # Get vf to full pvrow left_ts_pvrow = ts_pvrows[pvrow_idx - 1] left_ts_pvrow_coords = left_ts_pvrow.full_pvrow_coords vf_left_pvrow = self._vf_surface_to_surface( pvrow_element_coords, left_ts_pvrow_coords, pvrow_element_length) # Get vf to shaded pvrow shaded_coords = self._create_shaded_side_coords( left_ts_pvrow.xy_center, pvrow_width, left_ts_pvrow.front.shaded_length, tilted_to_left, rotation_vec, left_ts_pvrow.full_pvrow_coords.lowest_point) vf_left_shaded_pvrow = self._vf_surface_to_surface( pvrow_element_coords, shaded_coords, pvrow_element_length) if pvrow_idx == (n_pvrows - 1): vf_right_pvrow = np.zeros(n_steps) vf_right_shaded_pvrow = np.zeros(n_steps) else: # Get vf to full pvrow right_ts_pvrow = ts_pvrows[pvrow_idx + 1] right_ts_pvrow_coords = right_ts_pvrow.full_pvrow_coords vf_right_pvrow = self._vf_surface_to_surface( pvrow_element_coords, right_ts_pvrow_coords, pvrow_element_length) # Get vf to shaded pvrow shaded_coords = self._create_shaded_side_coords( right_ts_pvrow.xy_center, pvrow_width, right_ts_pvrow.front.shaded_length, tilted_to_left, rotation_vec, right_ts_pvrow.full_pvrow_coords.lowest_point) vf_right_shaded_pvrow = self._vf_surface_to_surface( pvrow_element_coords, shaded_coords, pvrow_element_length) vf_to_pvrow = np.where(tilted_to_left, vf_right_pvrow, vf_left_pvrow) vf_to_shaded_pvrow = np.where(tilted_to_left, vf_right_shaded_pvrow, vf_left_shaded_pvrow) return vf_to_pvrow, vf_to_shaded_pvrow 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 def calculate_vf_to_shadow_obstruction_hottel( self, pvrow_element, pvrow_idx, n_shadows, n_steps, tilted_to_left, ts_pvrows, shadow_left, shadow_right, pvrow_element_length): """Calculate view factors from timeseries pvrow_element to the shadow of a specific timeseries PV row which is casted on the ground. Parameters ---------- pvrow_element : :py:class:`~pvfactors.geometry.timeseries.TsDualSegment`\ or :py:class:`~pvfactors.geometry.timeseries.TsSurface` Timeseries pvrow_element to use for calculation pvrow_idx : int Index of the timeseries PV row on the which the pvrow_element is n_shadows : int Number of timeseries PV rows in the PV array, and therefore number of shadows they cast on the ground n_steps : int Number of timesteps for which to calculate the pvfactors 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 shadow_left : :py:class:`~pvfactors.geometry.timeseries.TsLineCoords` Coordinates of the shadow that are on the left side of the cut point of the PV row on which the pvrow_element is shadow_right : :py:class:`~pvfactors.geometry.timeseries.TsLineCoords` Coordinates of the shadow that are on the right side of the cut point of the PV row on which the pvrow_element is pvrow_element_length : float or np.ndarray Length (width) of the timeseries pvrow_element [m] Returns ------- vf_to_shadow : np.ndarray View factors from timeseries pvrow_element to the ground shadow of a specific timeseries PV row """ pvrow_element_lowest_pt = pvrow_element.lowest_point pvrow_element_highest_pt = pvrow_element.highest_point # Calculate view factors to left shadows if pvrow_idx == 0: # There is no obstruction on the left vf_to_left_shadow = self._vf_surface_to_surface( pvrow_element.coords, shadow_left, pvrow_element_length) else: # There is potential obstruction on the left pt_obstr = ts_pvrows[pvrow_idx - 1].full_pvrow_coords.lowest_point is_shadow_left = True vf_to_left_shadow = self._vf_hottel_gnd_surf( pvrow_element_highest_pt, pvrow_element_lowest_pt, shadow_left.b1, shadow_left.b2, pt_obstr, pvrow_element_length, is_shadow_left) # Calculate view factors to right shadows if pvrow_idx == n_shadows - 1: # There is no obstruction on the right vf_to_right_shadow = self._vf_surface_to_surface( pvrow_element.coords, shadow_right, pvrow_element_length) else: # There is potential obstruction on the right pt_obstr = ts_pvrows[pvrow_idx + 1].full_pvrow_coords.lowest_point is_shadow_left = False vf_to_right_shadow = self._vf_hottel_gnd_surf( pvrow_element_highest_pt, pvrow_element_lowest_pt, shadow_right.b1, shadow_right.b2, pt_obstr, pvrow_element_length, is_shadow_left) # Filter since we're considering the back surface only vf_to_shadow = np.where(tilted_to_left, vf_to_right_shadow, vf_to_left_shadow) return vf_to_shadow def _vf_hottel_gnd_surf(self, high_pt_pv, low_pt_pv, left_pt_gnd, right_pt_gnd, obstr_pt, width, shadow_is_left): """ Calculate the timeseries view factors from a PV surface defined by low and high points, to a ground surface defined by left and right points, while accounting for potentially obstructing neighboring PV rows, defined by an obstruction point, and all of this using the Hottel String method. Parameters ---------- high_pt_pv : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Highest point of the PV surface, for each timestamp low_pt_pv : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Lowest point of the PV surface, for each timestamp left_pt_gnd : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Leftmost point of the ground surface, for each timestamp right_pt_gnd : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Rightmost point of the ground surface, for each timestamp obstr_pt : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Obstructing point of neighboring PV row, for each timestamp width : float or np.ndarray Width of the PV row surface considered, from low to high point [m] shadow_is_left : bool Side of the considered shadow (or ground) surface with respect to the edge point of the PV row on which the considered PV surface is located Returns ------- vf_1_to_2 : np.ndarray View factors from PV surface to ground (shadow) surface """ if shadow_is_left: # When the shadow is left # - uncrossed strings are high_pv - left_gnd and low_pv - right_gnd # - crossed strings are high_pv - right_gnd and low_pv - left_gnd l1 = self._hottel_string_length(high_pt_pv, left_pt_gnd, obstr_pt, shadow_is_left) l2 = self._hottel_string_length(low_pt_pv, right_pt_gnd, obstr_pt, shadow_is_left) d1 = self._hottel_string_length(high_pt_pv, right_pt_gnd, obstr_pt, shadow_is_left) d2 = self._hottel_string_length(low_pt_pv, left_pt_gnd, obstr_pt, shadow_is_left) else: # When the shadow is right # - uncrossed strings are high_pv - right_gnd and low_pv - left_gnd # - crossed strings are high_pv - left_gnd and low_pv - right_gnd l1 = self._hottel_string_length(high_pt_pv, right_pt_gnd, obstr_pt, shadow_is_left) l2 = self._hottel_string_length(low_pt_pv, left_pt_gnd, obstr_pt, shadow_is_left) d1 = self._hottel_string_length(high_pt_pv, left_pt_gnd, obstr_pt, shadow_is_left) d2 = self._hottel_string_length(low_pt_pv, right_pt_gnd, obstr_pt, shadow_is_left) vf_1_to_2 = (d1 + d2 - l1 - l2) / (2. * width) # The formula doesn't work if surface is a point vf_1_to_2 = np.where(width > DISTANCE_TOLERANCE, vf_1_to_2, 0.) return vf_1_to_2 def _hottel_string_length(self, pt_pv, pt_gnd, pt_obstr, shadow_is_left): """ Calculate a string length as defined by the Hottel String method in the calculation of view factors, which allows to account for obstructions. Parameters ---------- left_pt_gnd : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Leftmost point of the ground surface, for each timestamp right_pt_gnd : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Rightmost point of the ground surface, for each timestamp obstr_pt : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Obstructing point of neighboring PV row, for each timestamp shadow_is_left : bool Side of the considered shadow (or ground) surface with respect to the edge point of the PV row on which the considered PV surface is located Returns ------- hottel_length : np.ndarray Return timeseries length of the string, while accounting for obstructions, in [m] """ # Calculate length of string without obstruction l_pv = self._distance(pt_pv, pt_gnd) if pt_obstr is None: # There can't be any obstruction hottel_length = l_pv 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(pt_gnd, pt_pv) alpha_ob = self._angle_with_x_axis(pt_gnd, pt_obstr) if shadow_is_left: is_obstructing = alpha_pv > alpha_ob else: is_obstructing = alpha_pv < alpha_ob # Calculate length of string with obstruction l_obstr = (self._distance(pt_gnd, pt_obstr) + self._distance(pt_obstr, pt_pv)) # Merge based on whether there is obstruction or not hottel_length = np.where(is_obstructing, l_obstr, l_pv) return hottel_length def _vf_surface_to_surface(self, line_1, line_2, width_1): """Calculate view factors between timeseries line coords, and using the Hottel String method for calculating view factors (without obstruction). Parameters ---------- line_1 : :py:class:`~pvfactors.geometry.timeseries.TsLineCoords` Timeseries line coordinates of surface 1 line_2 : :py:class:`~pvfactors.geometry.timeseries.TsLineCoords` Timeseries line coordinates of surface 2 width_1 : float or np.ndarray Length of line_1 in [m] Returns ------- vf_1_to_2 : np.ndarray View factors from line_1 to line_2, for each timestep """ length_1 = self._distance(line_1.b1, line_2.b1) length_2 = self._distance(line_1.b2, line_2.b2) length_3 = self._distance(line_1.b1, line_2.b2) length_4 = self._distance(line_1.b2, line_2.b1) sum_1 = length_1 + length_2 sum_2 = length_3 + length_4 vf_1_to_2 = np.abs(sum_2 - sum_1) / (2. * width_1) # The formula doesn't work if the line is a point vf_1_to_2 = np.where(width_1 > DISTANCE_TOLERANCE, vf_1_to_2, 0.) return vf_1_to_2 @staticmethod def _distance(pt_1, pt_2): """Calculate distance between two timeseries points Parameters ---------- pt_1 : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Timeseries point coordinates of point 1 pt_2 : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Timeseries point coordinates of point 2 Returns ------- np.ndarray Distance between the two points, for each timestep """ return np.sqrt((pt_2.y - pt_1.y)**2 + (pt_2.x - pt_1.x)**2) @staticmethod def _angle_with_x_axis(pt_1, pt_2): """Angle with x-axis of vector going from pt_1 to pt_2 Parameters ---------- pt_1 : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Timeseries point coordinates of point 1 pt_2 : :py:class:`~pvfactors.geometry.timeseries.TsPointCoords` Timeseries point coordinates of point 2 Returns ------- np.ndarray Angle between vector pt_1->pt_2 and x-axis """ return np.arctan2(pt_2.y - pt_1.y, pt_2.x - pt_1.x) @staticmethod 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