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
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))
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
def physical(aoi, n=1.526, K=4., L=0.002): r""" Determine the incidence angle modifier using refractive index ``n``, extinction coefficient ``K``, and glazing thickness ``L``. ``iam.physical`` calculates the incidence angle modifier as described in [1]_, Section 3. The calculation is based on a physical model of absorbtion and transmission through a transparent cover. Parameters ---------- aoi : numeric The angle of incidence between the module normal vector and the sun-beam vector in degrees. Angles of 0 are replaced with 1e-06 to ensure non-nan results. Angles of nan will result in nan. n : numeric, default 1.526 The effective index of refraction (unitless). Reference [1]_ indicates that a value of 1.526 is acceptable for glass. K : numeric, default 4.0 The glazing extinction coefficient in units of 1/meters. Reference [1] indicates that a value of 4 is reasonable for "water white" glass. L : numeric, default 0.002 The glazing thickness in units of meters. Reference [1]_ indicates that 0.002 meters (2 mm) is reasonable for most glass-covered PV panels. Returns ------- iam : numeric The incident angle modifier Notes ----- The pvlib python authors believe that Eqn. 14 in [1]_ is incorrect, which presents :math:`\theta_{r} = \arcsin(n \sin(AOI))`. Here, :math:`\theta_{r} = \arcsin(1/n \times \sin(AOI))` 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 -------- pvlib.iam.martin_ruiz pvlib.iam.ashrae pvlib.iam.interp pvlib.iam.sapm """ zeroang = 1e-06 # hold a new reference to the input aoi object since we're going to # overwrite the aoi reference below, but we'll need it for the # series check at the end of the function aoi_input = aoi aoi = np.where(aoi == 0, zeroang, aoi) # angle of reflection thetar_deg = asind(1.0 / n * (sind(aoi))) # reflectance and transmittance for normal incidence light rho_zero = ((1 - n) / (1 + n))**2 tau_zero = np.exp(-K * L) # reflectance for parallel and perpendicular polarized light rho_para = (tand(thetar_deg - aoi) / tand(thetar_deg + aoi))**2 rho_perp = (sind(thetar_deg - aoi) / sind(thetar_deg + aoi))**2 # transmittance for non-normal light tau = np.exp(-K * L / cosd(thetar_deg)) # iam is ratio of non-normal to normal incidence transmitted light # after deducting the reflected portion of each iam = ((1 - (rho_para + rho_perp) / 2) / (1 - rho_zero) * tau / tau_zero) with np.errstate(invalid='ignore'): # angles near zero produce nan, but iam is defined as one small_angle = 1e-06 iam = np.where(np.abs(aoi) < small_angle, 1.0, iam) # angles at 90 degrees can produce tiny negative values, # which should be zero. this is a result of calculation precision # rather than the physical model iam = np.where(iam < 0, 0, iam) # for light coming from behind the plane, none can enter the module iam = np.where(aoi > 90, 0, iam) if isinstance(aoi_input, pd.Series): iam = pd.Series(iam, index=aoi_input.index) return iam
def get_irradiance_poa(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, iam=1.0, npoints=100): r""" Calculate plane-of-array (POA) irradiance on one side of a row of modules. The infinite sheds model [1] assumes the PV system comprises parallel, evenly spaced rows on a level, horizontal surface. Rows can be on fixed racking or single axis trackers. The model calculates irradiance at a location far from the ends of any rows, in effect, assuming that the rows (sheds) are infinitely long. POA irradiance components include direct, diffuse and global (total). Irradiance values are reduced to account for reflection of direct light, but are not adjusted for solar spectrum or reduced by a module's bifaciality factor. Parameters ---------- surface_tilt : numeric Tilt of the surface from horizontal. Must be between 0 and 180. For example, for a fixed tilt module mounted at 30 degrees from horizontal, use ``surface_tilt=30`` to get front-side irradiance and ``surface_tilt=150`` to get rear-side irradiance. [degree] surface_azimuth : numeric Surface azimuth in decimal degrees east of north (e.g. North = 0, South = 180, East = 90, West = 270). [degree] solar_zenith : numeric Refraction-corrected solar zenith. [degree] solar_azimuth : numeric Solar azimuth. [degree] gcr : float Ground coverage ratio, ratio of row slant length to row spacing. [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``. ghi : numeric Global horizontal irradiance. [W/m2] dhi : numeric Diffuse horizontal irradiance. [W/m2] dni : numeric Direct normal irradiance. [W/m2] albedo : numeric Surface albedo. [unitless] iam : numeric, default 1.0 Incidence angle modifier, the fraction of direct irradiance incident on the surface that is not reflected away. [unitless] npoints : int, default 100 Number of points used to discretize distance along the ground. Returns ------- output : dict or DataFrame Output is a DataFrame when input ghi is a Series. See Notes for descriptions of content. Notes ----- Input parameters ``height`` and ``pitch`` must have the same unit. ``output`` always includes: - ``poa_global`` : total POA irradiance. [W/m^2] - ``poa_diffuse`` : total diffuse POA irradiance from all sources. [W/m^2] - ``poa_direct`` : total direct POA irradiance. [W/m^2] - ``poa_sky_diffuse`` : total sky diffuse irradiance on the plane of array. [W/m^2] - ``poa_ground_diffuse`` : total ground-reflected diffuse irradiance on the plane of array. [W/m^2] 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`. See also -------- get_irradiance """ # Calculate some geometric quantities # rows to consider in front and behind current row # ensures that view factors to the sky are computed to within 5 degrees # of the horizon max_rows = np.ceil(height / (pitch * tand(5))) # fraction of ground between rows that is illuminated accounting for # shade from panels. [1], Eq. 4 f_gnd_beam = utils._unshaded_ground_fraction(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr) # integrated view factor from the ground to the sky, integrated between # adjacent rows interior to the array # method differs from [1], Eq. 7 and Eq. 8; height is defined at row # center rather than at row lower edge as in [1]. vf_gnd_sky = _vf_ground_sky_integ(surface_tilt, surface_azimuth, gcr, height, pitch, max_rows, npoints) # fraction of row slant height that is shaded from direct irradiance f_x = _shaded_fraction(solar_zenith, solar_azimuth, surface_tilt, surface_azimuth, gcr) # Integrated view factors to the sky from the shaded and unshaded parts of # the row slant height # Differs from [1] Eq. 15 and Eq. 16. Here, we integrate over each # interval (shaded or unshaded) rather than averaging values at each # interval's end points. vf_shade_sky, vf_noshade_sky = _vf_row_sky_integ(f_x, surface_tilt, gcr, npoints) # view factors from the ground to shaded and unshaded portions of the row # slant height # Differs from [1] Eq. 17 and Eq. 18. Here, we integrate over each # interval (shaded or unshaded) rather than averaging values at each # interval's end points. f_gnd_pv_shade, f_gnd_pv_noshade = _vf_row_ground_integ( f_x, surface_tilt, gcr, npoints) # Total sky diffuse received by both shaded and unshaded portions poa_sky_pv = _poa_sky_diffuse_pv(f_x, dhi, vf_shade_sky, vf_noshade_sky) # irradiance reflected from the ground before accounting for shadows # and restricted views # this is a deviation from [1], because the row to ground view factor # is accounted for in a different manner ground_diffuse = ghi * albedo # diffuse fraction diffuse_fraction = np.clip(dhi / ghi, 0., 1.) # make diffuse fraction 0 when ghi is small diffuse_fraction = np.where(ghi < 0.0001, 0., diffuse_fraction) # Reduce ground-reflected irradiance because other rows in the array # block irradiance from reaching the ground. # [2], Eq. 9 ground_diffuse = _poa_ground_shadows(ground_diffuse, f_gnd_beam, diffuse_fraction, vf_gnd_sky) # Ground-reflected irradiance on the row surface accounting for # the view to the ground. This deviates from [1], Eq. 10, 11 and # subsequent. Here, the row to ground view factor is computed. In [1], # the usual ground-reflected irradiance includes the single row to ground # view factor (1 - cos(tilt))/2, and Eq. 10, 11 and later multiply # this quantity by a ratio of view factors. poa_gnd_pv = _poa_ground_pv(f_x, ground_diffuse, f_gnd_pv_shade, f_gnd_pv_noshade) # add sky and ground-reflected irradiance on the row by irradiance # component poa_diffuse = poa_gnd_pv + poa_sky_pv # beam on plane, make an array for consistency with poa_diffuse poa_beam = np.atleast_1d( beam_component(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, dni)) poa_direct = poa_beam * (1 - f_x) * iam # direct only on the unshaded part poa_global = poa_direct + poa_diffuse output = { 'poa_global': poa_global, 'poa_direct': poa_direct, 'poa_diffuse': poa_diffuse, 'poa_ground_diffuse': poa_gnd_pv, 'poa_sky_diffuse': poa_sky_pv } if isinstance(poa_global, pd.Series): output = pd.DataFrame(output) return output