def calc_surface_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0): """ Calculate the surface tilt and azimuth angles for a given tracker rotation. Parameters ---------- tracker_theta : numeric Tracker rotation angle as a right-handed rotation around the axis defined by ``axis_tilt`` and ``axis_azimuth``. For example, with ``axis_tilt=0`` and ``axis_azimuth=180``, ``tracker_theta > 0`` results in ``surface_azimuth`` to the West while ``tracker_theta < 0`` results in ``surface_azimuth`` to the East. [degree] axis_tilt : float, default 0 The tilt of the axis of rotation with respect to horizontal. [degree] axis_azimuth : float, default 0 A value denoting the compass direction along which the axis of rotation lies. Measured east of north. [degree] Returns ------- dict or DataFrame Contains keys ``'surface_tilt'`` and ``'surface_azimuth'`` representing the module orientation accounting for tracker rotation and axis orientation. [degree] References ---------- .. [1] William F. Marion and Aron P. Dobos, "Rotation Angle for the Optimum Tracking of One-Axis Trackers", Technical Report NREL/TP-6A20-58891, July 2013. :doi:`10.2172/1089596` """ with np.errstate(invalid='ignore', divide='ignore'): surface_tilt = acosd(cosd(tracker_theta) * cosd(axis_tilt)) # clip(..., -1, +1) to prevent arcsin(1 + epsilon) issues: azimuth_delta = asind(np.clip(sind(tracker_theta) / sind(surface_tilt), a_min=-1, a_max=1)) # Combine Eqs 2, 3, and 4: azimuth_delta = np.where(abs(tracker_theta) < 90, azimuth_delta, -azimuth_delta + np.sign(tracker_theta) * 180) # handle surface_tilt=0 case: azimuth_delta = np.where(sind(surface_tilt) != 0, azimuth_delta, 90) surface_azimuth = (axis_azimuth + azimuth_delta) % 360 out = { 'surface_tilt': surface_tilt, 'surface_azimuth': surface_azimuth, } if hasattr(tracker_theta, 'index'): out = pd.DataFrame(out) return out
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