-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Incidence Angle Modifier zero instead of np.nan (#338) #339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
24d8fbc
acacf3a
6167d7f
d92f8fb
fbbff59
51340e2
990acf1
3d3e8b5
31461b1
28c5e17
23e67e1
a607a10
a329a8a
fd593a2
6ca99af
a52dba0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
.. _whatsnew_0500: | ||
|
||
v0.5.0 () | ||
--------- | ||
|
||
|
||
Bug fixes | ||
~~~~~~~~~ | ||
|
||
|
||
Enhancements | ||
~~~~~~~~~~~~ | ||
|
||
|
||
API Changes | ||
~~~~~~~~~~~ | ||
|
||
* Changes calculation of the Incidence Angle Modifier to return 0 instead of np.nan for angles >= 90°. | ||
This improves the calculation of effective irradiance close to sunrise and sunset. (:issue:`338`) | ||
|
||
|
||
Documentation | ||
~~~~~~~~~~~~~ | ||
|
||
|
||
Contributors | ||
~~~~~~~~~~~~ | ||
|
||
* Johannes Kaufmann |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -700,7 +700,7 @@ def ashraeiam(aoi, b=0.05): | |
---------- | ||
aoi : numeric | ||
The angle of incidence between the module normal vector and the | ||
sun-beam vector in degrees. | ||
sun-beam vector in degrees. Angles of nan will result in nan. | ||
|
||
b : float, default 0.05 | ||
A parameter to adjust the modifier as a function of angle of | ||
|
@@ -712,7 +712,7 @@ def ashraeiam(aoi, b=0.05): | |
The incident angle modifier calculated as 1-b*(sec(aoi)-1) as | ||
described in [2,3]. | ||
|
||
Returns nan for all abs(aoi) >= 90 and for all IAM values that | ||
Returns zeros for all abs(aoi) >= 90 and for all IAM values that | ||
would be less than 0. | ||
|
||
References | ||
|
@@ -735,7 +735,7 @@ def ashraeiam(aoi, b=0.05): | |
|
||
iam = 1 - b*((1/np.cos(np.radians(aoi)) - 1)) | ||
|
||
iam = np.where(np.abs(aoi) >= 90, np.nan, iam) | ||
iam = np.where(np.abs(aoi) >= 90, 0, iam) | ||
iam = np.maximum(0, iam) | ||
|
||
if isinstance(iam, pd.Series): | ||
|
@@ -764,7 +764,8 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): | |
---------- | ||
aoi : numeric | ||
The angle of incidence between the module normal vector and the | ||
sun-beam vector in degrees. | ||
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] | ||
|
@@ -814,27 +815,40 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): | |
spa | ||
ashraeiam | ||
''' | ||
zeroang = 1e-06 | ||
|
||
aoi = np.where(aoi == 0, zeroang, aoi) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you add this line? I understand what it does, but not why we need it now when we didn't previously. It's not clear to me that we actually need zeroang at all. I think its only purpose is to avoid infs, but maybe those be more cleanly handled with np.where or similar. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I understood it, if the input angle is 0, tau will be nan, which is why we substitute it with zeroang. I guess we could define tau0 as a constant in the function and handle aoi == 0 with np.where after the calculations are done, but I feel like the current function is more intuitive to understand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should keep zeroang and for input theta < zeroang, physicaliam should return tau0. Passing theta=0 through will return nan rather than zero. Line 821 computes tau with the leading factor exp(-KL/cos(theta)) which is why we should return tau0 for small theta. tau0 is computed awkwardly. I'd replace line 829 - 833 with the limit value which we should have implemented in Matlab: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, now I see that you've changed the test so that an input of 0 returns 1 instead of np.nan. I should have caught this when I wrote the test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am surprised that the model would not work for an angle of exactly zero degrees. Could this have something to do with the equation discrepancy mentioned in the comments further above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it has to do with the function discrepancy. The unmodified function for theta_r is not defined for angles > ~40°. The tau(theta)-function in turn simply has a discontinuity for theta = 0. It seems this was ignored in the paper... |
||
|
||
# angle of reflection | ||
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)))))) | ||
# reflectance and transmittance for normal incidence light | ||
rho_zero = ((1-n) / (1+n)) ** 2 | ||
tau_zero = np.exp(-K*L) | ||
|
||
zeroang = 1e-06 | ||
# reflectance for parallel and perpendicular polarized light | ||
rho_para = (tools.tand(thetar_deg - aoi) / | ||
tools.tand(thetar_deg + aoi)) ** 2 | ||
rho_perp = (tools.sind(thetar_deg - aoi) / | ||
tools.sind(thetar_deg + aoi)) ** 2 | ||
|
||
# transmittance for non-normal light | ||
tau = np.exp(-K*L / tools.cosd(thetar_deg)) | ||
|
||
thetar_deg0 = tools.asind(1.0 / n*(tools.sind(zeroang))) | ||
# 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) | ||
|
||
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)))))) | ||
# 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) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with your suggestions, especially after having consulted my D&B book. I would suggest calculating reflectance and absorption separately before combining them in order to clarify the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I applied both your suggestions correctly. Could you take a look? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It wasn't quite correct, so I thought it might be easier to communicate it this way:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the detailed explanation! So does this refer to reference 2 in the Docstring (I don't have access so cannot check)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ref [1] simply describes what is in ref [2]. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I implemented your suggestion except for setting negative angles to np.nan, to keep consistency with the other IAM functions for now... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consistency has some appeal... |
||
iam = tau / tau0 | ||
# 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) | ||
|
||
iam = np.where((np.abs(aoi) >= 90) | (iam < 0), np.nan, iam) | ||
# for light coming from behind the plane, none can enter the module | ||
iam = np.where(aoi > 90, 0, iam) | ||
|
||
if isinstance(aoi, pd.Series): | ||
iam = pd.Series(iam, index=aoi.index) | ||
|
@@ -1465,7 +1479,7 @@ def sapm_aoi_loss(aoi, module, upper=None): | |
---------- | ||
aoi : numeric | ||
Angle of incidence in degrees. Negative input angles will return | ||
nan values. | ||
zeros. | ||
|
||
module : dict-like | ||
A dict, Series, or DataFrame defining the SAPM performance | ||
|
@@ -1507,7 +1521,7 @@ def sapm_aoi_loss(aoi, module, upper=None): | |
|
||
aoi_loss = np.polyval(aoi_coeff, aoi) | ||
aoi_loss = np.clip(aoi_loss, 0, upper) | ||
aoi_loss = np.where(aoi < 0, np.nan, aoi_loss) | ||
aoi_loss = np.where(aoi < 0, 0, aoi_loss) | ||
|
||
if isinstance(aoi, pd.Series): | ||
aoi_loss = pd.Series(aoi_loss, aoi.index) | ||
|
@@ -2084,8 +2098,8 @@ def adrinverter(v_dc, p_dc, inverter, vtol=0.10): | |
See Notes for required keys. | ||
|
||
vtol : numeric, default 0.1 | ||
A unit-less fraction that determines how far the efficiency model is | ||
allowed to extrapolate beyond the inverter's normal input voltage | ||
A unit-less fraction that determines how far the efficiency model is | ||
allowed to extrapolate beyond the inverter's normal input voltage | ||
operating range. 0.0 <= vtol <= 1.0 | ||
|
||
Returns | ||
|
@@ -2109,21 +2123,21 @@ def adrinverter(v_dc, p_dc, inverter, vtol=0.10): | |
Column Description | ||
======= ============================================================ | ||
p_nom The nominal power value used to normalize all power values, | ||
typically the DC power needed to produce maximum AC power | ||
typically the DC power needed to produce maximum AC power | ||
output, (W). | ||
|
||
v_nom The nominal DC voltage value used to normalize DC voltage | ||
values, typically the level at which the highest efficiency | ||
v_nom The nominal DC voltage value used to normalize DC voltage | ||
values, typically the level at which the highest efficiency | ||
is achieved, (V). | ||
|
||
pac_max The maximum AC output power value, used to clip the output | ||
pac_max The maximum AC output power value, used to clip the output | ||
if needed, (W). | ||
|
||
ce_list This is a list of 9 coefficients that capture the influence | ||
of input voltage and power on inverter losses, and thereby | ||
efficiency. | ||
|
||
p_nt ac-power consumed by inverter at night (night tare) to | ||
p_nt ac-power consumed by inverter at night (night tare) to | ||
maintain circuitry required to sense PV array voltage, (W). | ||
======= ============================================================ | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the np.maximum line still necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is, because for angles close to +/-90° the function would return a negative value for the iam (the intercept for b=0.05 is at 87.21°: https://www.desmos.com/calculator/y8wvzrx3b9).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, the np.maximum line is needed