1111import numpy as np
1212import pandas as pd
1313import functools
14- from pvlib .tools import cosd , sind , tand , asind
14+ from pvlib .tools import cosd , sind
1515
1616# a dict of required parameter names for each IAM model
1717# keys are the function names for the IAM models
@@ -91,21 +91,22 @@ def ashrae(aoi, b=0.05):
9191 return iam
9292
9393
94- def physical (aoi , n = 1.526 , K = 4. , L = 0.002 ):
94+ def physical (aoi , n = 1.526 , K = 4.0 , L = 0.002 , * , n_ar = None ):
9595 r"""
9696 Determine the incidence angle modifier using refractive index ``n``,
97- extinction coefficient ``K``, and glazing thickness ``L``.
97+ extinction coefficient ``K``, glazing thickness ``L`` and refractive
98+ index ``n_ar`` of an optional anti-reflective coating.
9899
99100 ``iam.physical`` calculates the incidence angle modifier as described in
100- [1]_, Section 3. The calculation is based on a physical model of absorbtion
101+ [1]_, Section 3, with additional support of an anti-reflective coating.
102+ The calculation is based on a physical model of reflections, absorption,
101103 and transmission through a transparent cover.
102104
103105 Parameters
104106 ----------
105107 aoi : numeric
106108 The angle of incidence between the module normal vector and the
107- sun-beam vector in degrees. Angles of 0 are replaced with 1e-06
108- to ensure non-nan results. Angles of nan will result in nan.
109+ sun-beam vector in degrees. Angles of nan will result in nan.
109110
110111 n : numeric, default 1.526
111112 The effective index of refraction (unitless). Reference [1]_
@@ -121,6 +122,11 @@ def physical(aoi, n=1.526, K=4., L=0.002):
121122 indicates that 0.002 meters (2 mm) is reasonable for most
122123 glass-covered PV panels.
123124
125+ n_ar : numeric, optional
126+ The effective index of refraction of the anti-reflective (AR) coating
127+ (unitless). If n_ar is None (default), no AR coating is applied.
128+ A typical value for the effective index of an AR coating is 1.29.
129+
124130 Returns
125131 -------
126132 iam : numeric
@@ -149,48 +155,65 @@ def physical(aoi, n=1.526, K=4., L=0.002):
149155 pvlib.iam.interp
150156 pvlib.iam.sapm
151157 """
152- zeroang = 1e-06
153-
154- # hold a new reference to the input aoi object since we're going to
155- # overwrite the aoi reference below, but we'll need it for the
156- # series check at the end of the function
157- aoi_input = aoi
158-
159- aoi = np .where (aoi == 0 , zeroang , aoi )
160-
161- # angle of reflection
162- thetar_deg = asind (1.0 / n * (sind (aoi )))
163-
164- # reflectance and transmittance for normal incidence light
165- rho_zero = ((1 - n ) / (1 + n )) ** 2
166- tau_zero = np .exp (- K * L )
167-
168- # reflectance for parallel and perpendicular polarized light
169- rho_para = (tand (thetar_deg - aoi ) / tand (thetar_deg + aoi )) ** 2
170- rho_perp = (sind (thetar_deg - aoi ) / sind (thetar_deg + aoi )) ** 2
171-
172- # transmittance for non-normal light
173- tau = np .exp (- K * L / cosd (thetar_deg ))
174-
175- # iam is ratio of non-normal to normal incidence transmitted light
176- # after deducting the reflected portion of each
177- iam = ((1 - (rho_para + rho_perp ) / 2 ) / (1 - rho_zero ) * tau / tau_zero )
178-
179- with np .errstate (invalid = 'ignore' ):
180- # angles near zero produce nan, but iam is defined as one
181- small_angle = 1e-06
182- iam = np .where (np .abs (aoi ) < small_angle , 1.0 , iam )
183-
184- # angles at 90 degrees can produce tiny negative values,
185- # which should be zero. this is a result of calculation precision
186- # rather than the physical model
187- iam = np .where (iam < 0 , 0 , iam )
188-
189- # for light coming from behind the plane, none can enter the module
190- iam = np .where (aoi > 90 , 0 , iam )
191-
192- if isinstance (aoi_input , pd .Series ):
193- iam = pd .Series (iam , index = aoi_input .index )
158+ n1 , n3 = 1 , n
159+ if n_ar is None or np .allclose (n_ar , n1 ):
160+ # no AR coating
161+ n2 = n
162+ else :
163+ n2 = n_ar
164+
165+ # incidence angle
166+ costheta = np .maximum (0 , cosd (aoi )) # always >= 0
167+ sintheta = np .sqrt (1 - costheta ** 2 ) # always >= 0
168+ n1costheta1 = n1 * costheta
169+ n2costheta1 = n2 * costheta
170+
171+ # refraction angle of first interface
172+ sintheta = n1 / n2 * sintheta
173+ costheta = np .sqrt (1 - sintheta ** 2 )
174+ n1costheta2 = n1 * costheta
175+ n2costheta2 = n2 * costheta
176+
177+ # reflectance of s-, p-polarized, and normal light by the first interface
178+ rho12_s = ((n1costheta1 - n2costheta2 ) / (n1costheta1 + n2costheta2 )) ** 2
179+ rho12_p = ((n1costheta2 - n2costheta1 ) / (n1costheta2 + n2costheta1 )) ** 2
180+ rho12_0 = ((n1 - n2 ) / (n1 + n2 )) ** 2
181+
182+ # transmittance through the first interface
183+ tau_s = 1 - rho12_s
184+ tau_p = 1 - rho12_p
185+ tau_0 = 1 - rho12_0
186+
187+ if not np .allclose (n3 , n2 ): # AR coated glass
188+ n3costheta2 = n3 * costheta
189+ # refraction angle of second interface
190+ sintheta = n2 / n3 * sintheta
191+ costheta = np .sqrt (1 - sintheta ** 2 )
192+ n2costheta3 = n2 * costheta
193+ n3costheta3 = n3 * costheta
194+
195+ # reflectance by the second interface
196+ rho23_s = (
197+ (n2costheta2 - n3costheta3 ) / (n2costheta2 + n3costheta3 )
198+ ) ** 2
199+ rho23_p = (
200+ (n2costheta3 - n3costheta2 ) / (n2costheta3 + n3costheta2 )
201+ ) ** 2
202+ rho23_0 = ((n2 - n3 ) / (n2 + n3 )) ** 2
203+
204+ # transmittance through the coating, including internal reflections
205+ # 1 + rho23*rho12 + (rho23*rho12)^2 + ... = 1/(1 - rho23*rho12)
206+ tau_s *= (1 - rho23_s ) / (1 - rho23_s * rho12_s )
207+ tau_p *= (1 - rho23_p ) / (1 - rho23_p * rho12_p )
208+ tau_0 *= (1 - rho23_0 ) / (1 - rho23_0 * rho12_0 )
209+
210+ # transmittance after absorption in the glass
211+ tau_s *= np .exp (- K * L / costheta )
212+ tau_p *= np .exp (- K * L / costheta )
213+ tau_0 *= np .exp (- K * L )
214+
215+ # incidence angle modifier
216+ iam = (tau_s + tau_p ) / 2 / tau_0
194217
195218 return iam
196219
0 commit comments