8000 Add GTI DIRINT model by wholmgren · Pull Request #400 · pvlib/pvlib-python · GitHub
[go: up one dir, main page]

Skip to content

Add GTI DIRINT model #400

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

Merged
merged 46 commits into from
Aug 27, 2018
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8a1c3aa
implement gtidirint without iteration
wholmgren Aug 31, 2017
dc4b133
add option to skip aoi > 90
wholmgren Aug 31, 2017
555cd6b
make gti dirint gte 90 function private
wholmgren Aug 31, 2017
fbda8e2
add some gti_dirint tests
wholmgren Aug 31, 2017
bc331f4
implement vectorized iterative solution. fix gt 90 error
wholmgren Sep 7, 2017
003eccb
use azimuth in gt 90 function
wholmgren Sep 7, 2017
880a999
fix some issues near 90 deg
wholmgren Sep 8, 2017
6f881f4
remove unnecessary comment
wholmgren Sep 8, 2017
ac74260
add kt option to disc, dirint to fix gte 90 dni issue
wholmgren Sep 8, 2017
e8b40ea
update whatsnew, add to api.rst
wholmgren Sep 8, 2017
b1a6d48
move to 0.5.2 whatsnew
wholmgren Nov 30, 2017
721f89f
add gti_dirint tutorial
wholmgren Nov 30, 2017
8cd9a74
Merge branch 'master' into gtidirint
wholmgren Jun 16, 2018
abc671c
move changes to 0.6.0 whatsnew
wholmgren Jun 16, 2018
89c60a4
update parameters
wholmgren Jun 16, 2018
376f3f7
fix 0.5.2 whatsnew
wholmgren Jun 16, 2018
5920620
rework max_iterations logic
wholmgren Jun 16, 2018
d856205
flake8
wholmgren Jun 16, 2018
2d99474
update tutorial
wholmgren Jun 16, 2018
0a0feab
api change in whatsnew
wholmgren Jun 16, 2018
ae60316
Merge branch 'master' into gtidirint
wholmgren Aug 14, 2018
870196a
clean up whatsnew
wholmgren Aug 15, 2018
2112825
update to new api. fix parameters
wholmgren Aug 15, 2018
eba99ae
update notebook [skip ci]
wholmgren Aug 15, 2018
a4d2682
refactor
wholmgren Aug 15, 2018
18222d1
more refactoring
wholmgren Aug 15, 2018
4ac71a4
refactor disc and dirint
wholmgren Aug 17, 2018
54521b9
clean up
wholmgren Aug 17, 2018
14915b7
update tutorial
wholmgren Aug 17, 2018
519390a
add stubs. use zeros. more clean up
wholmgren Aug 17, 2018
15ecbf3
review comments
wholmgren Aug 18, 2018
be384b7
Merge remote-tracking branch 'pvlib/master' into gtidirint
wholmgren Aug 19, 2018
c8a470f
rerun on master
wholmgren Aug 19, 2018
6a612dd
some broken tests
wholmgren Aug 21, 2018
cac57cb
update parameters, whatsnew. [skip ci]
wholmgren Aug 21, 2018
c92dd9f
update tests
wholmgren Aug 22, 2018
6c2bf35
update documentation
wholmgren Aug 22, 2018
1b6e5d2
ignore .vscode
wholmgren Aug 24, 2018
e58c7d5
limit clearness index in disc dirint dirindex
wholmgren Aug 24, 2018
0b83533
fix bad change
wholmgren Aug 24, 2018
891f886
update tutorial
wholmgren Aug 24, 2018
ae6a89f
Merge remote-tracking branch 'pvlib/master' into gtidirint
wholmgren Aug 24, 2018
dad4512
remove tutorial
wholmgren Aug 24, 2018
b3f43cb
maybe fix failing py27 test
wholmgren Aug 25, 2018
9eeb67f
doc clean up
wholmgren Aug 25, 2018
6aebf5e
update doi
wholmgren Aug 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/sphinx/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ DNI estimation models
irradiance.dirindex
irradiance.erbs
irradiance.liujordan
irradiance.gti_dirint


PV Modeling
Expand Down
5 changes: 4 additions & 1 deletion docs/sphinx/source/whatsnew/v0.5.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ v0.5.2 (__, 2017)
API Changes
~~~~~~~~~~~
* removed unused 'pressure' arg from irradiance.liujordan function (:issue:`386`)
* Add kt and kt_prime keyword arguments to dirint function. (:issue:`396`)
* Add kt and min_cos_zenith keyword arguments to disc function. (:issue:`396`)
* Change the disc dni filter thresholds. (:issue:`396`)

Enhancements
~~~~~~~~~~~~
*
* Add gti_dirint function. (:issue:`396`)

Bug fixes
~~~~~~~~~
Expand Down
667 changes: 667 additions & 0 deletions docs/tutorials/gti_dirint.ipynb

Large diffs are not rendered by default.

295 changes: 281 additions & 14 deletions pvlib/irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,8 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
return sky_diffuse


def disc(ghi, zenith, datetime_or_doy, pressure=101325):
def disc(ghi, zenith, datetime_or_doy, pressure=101325, min_cos_zenith=0.065,
kt=None):
"""
Estimate Direct Normal Irradiance from Global Horizontal Irradiance
using the DISC model.
Expand Down Expand Up @@ -1127,13 +1128,17 @@ def disc(ghi, zenith, datetime_or_doy, pressure=101325):

# this is the I0 calculation from the reference
I0 = extraradiation(datetime_or_doy, 1370, 'spencer')
I0h = I0 * np.cos(np.radians(zenith))
cos_zenith = np.maximum(min_cos_zenith, np.cos(np.radians(zenith)))
I0h = I0 * cos_zenith

am = atmosphere.relativeairmass(zenith, model='kasten1966')
am = atmosphere.absoluteairmass(am, pressure)

kt = ghi / I0h
if kt is None:
kt = ghi / I0h

kt = np.maximum(kt, 0)
kt = np.minimum(kt, 0.82)
# powers of kt will be used repeatedly, so compute only once
kt2 = kt * kt # about the same as kt ** 2
kt3 = kt2 * kt # 5-10x faster than kt ** 3
Expand All @@ -1156,7 +1161,8 @@ def disc(ghi, zenith, datetime_or_doy, pressure=101325):

dni = Kn * I0

dni = np.where((zenith > 87) | (ghi < 0) | (dni < 0), 0, dni)
dni = np.where((zenith > 90) | (ghi < 0) | (dni < 0) | (dni > I0*0.82),
0, dni)

output = OrderedDict()
output['dni'] = dni
Expand All @@ -1170,7 +1176,7 @@ def disc(ghi, zenith, datetime_or_doy, pressure=101325):


def dirint(ghi, zenith, times, pressure=101325., use_delta_kt_prime=True,
temp_dew=None):
temp_dew=None, kt=None, kt_prime=None, return_kt_prime=False):
"""
Determine DNI from GHI using the DIRINT modification of the DISC
model.
Expand Down Expand Up @@ -1237,19 +1243,15 @@ def dirint(ghi, zenith, times, pressure=101325., use_delta_kt_prime=True,
SERI/TR-215-3087, Golden, CO: Solar Energy Research Institute, 1987.
"""

disc_out = disc(ghi, zenith, times, pressure=pressure)
disc_out = disc(ghi, zenith, times, pressure=pressure, kt=kt)
dni = disc_out['dni']
kt = disc_out['kt']
am = disc_out['airmass']

kt_prime = kt / (1.031 * np.exp(-1.4 / (0.9 + 9.4 / am)) + 0.1)
kt_prime = np.minimum(kt_prime, 0.82) # From SRRL code
if kt_prime is None:
kt_prime = kt / (1.031 * np.exp(-1.4 / (0.9 + 9.4 / am)) + 0.1)
kt_prime = np.minimum(kt_prime, 0.82) # From SRRL code

# wholmgren:
# the use_delta_kt_prime statement is a port of the MATLAB code.
# I am confused by the abs() in the delta_kt_prime calculation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation in _delta_kt_prime_dirint is consistent with the Perez et al reference - delta_kt_prime is not the absolute value of the central difference, it is the sum of the absolute value of the forward and reverse differences.

use_delta_kt_prime is essentially an option selecting between two different models published in the same paper.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. old comment that is being removed.

# It is not the absolute value of the central difference.
# current implementation requires that kt_prime is a Series
if use_delta_kt_prime:
delta_kt_prime = 0.5*((kt_prime - kt_prime.shift(1)).abs().add(
(kt_prime - kt_prime.shift(-1)).abs(),
Expand Down Expand Up @@ -1317,7 +1319,10 @@ def dirint(ghi, zenith, times, pressure=101325., use_delta_kt_prime=True,

dni *= dirint_coeffs

return dni
if return_kt_prime:
return dni, kt_prime
else:
return dni


def dirindex(ghi, ghi_clearsky, dni_clearsky, zenith, times, pressure=101325.,
Expand Down Expand Up @@ -1400,6 +1405,268 @@ def dirindex(ghi, ghi_clearsky, dni_clearsky, zenith, times, pressure=101325.,
return dni_dirindex


def gti_dirint(poa_global, aoi, solar_zenith, solar_azimuth,
surface_tilt, surface_azimuth,
times, pressure=101325.,
use_delta_kt_prime=True, temp_dew=None, albedo=.25,
model='perez', model_perez='allsitescomposite1990',
calculate_gt_90=True, max_iterations=30):
"""
Determine GHI, DNI, DHI from POA global using the GTI DIRINT model.

Parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameters need updating.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated.

----------
poa_global : array-like
Plane of array global irradiance in W/m^2.

aoi : array-like
Angle of incidence of solar rays with respect to the module
surface normal.

zenith : array-like
True (not refraction-corrected) zenith angles in decimal
degrees.

surface_tilt : numeric
Surface tilt angles in decimal degrees. Tilt must be >=0 and
<=180. The tilt angle is defined as degrees from horizontal
(e.g. surface facing up = 0, surface facing horizon = 90).

times : DatetimeIndex

pressure : numeric, default 101325.0
The site pressure in Pascal. Pressure may be measured or an
average pressure may be calculated from site altitude.

use_delta_kt_prime : bool, default True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's update this description here (and in dirint):

Indicates that the stability index delta_kt_prime is included in the model. The stability index adjusts the estimated DNI in response to dynamics in the time series of GHI. A value of False will not use the stability index, any other numeric value uses the index. It is recommended that delta_kt_prime is not used if the time between GHI points is 1.5 hours or greater. If use_delta_kt_prime=True, input data must be Series. If none of the input arguments are vectors, then delta_kt_prime is not used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any other numeric value uses the index

I'm confused about the comment quoted above. Currently we only allow True/False as options. If True, the stability index is calculated and used. If False, it is not. Are you suggesting that we allow people to provide their own stability index as inputs in dirint and gti_dirint?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disregard "any other numeric value" please, sloppy wording on my part.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't realize this was in the existing wording! I'll update it.

Indicates if the user would like to utilize the time-series
nature of the GHI measurements. A value of ``False`` will not
use the time-series improvements, any other numeric value will
use time-series improvements. It is recommended that time-series
data only be used if the time between measured data points is
less than 1.5 hours. If none of the input arguments are vectors,
then time-series improvements are not used (because it's not a
time-series). If True, input data must be Series.

temp_dew : numeric, default None
Surface dew point temperatures, in degrees C. Values of temp_dew
may be numeric or NaN. Any single time period point with a
DewPtTemp=NaN does not have dew point improvements applied. If
DewPtTemp is not provided, then dew point improvements are not
applied.

calculate_gt_90 : bool, default True
Controls if the algorithm evaluates inputs with AOI >= 90 degrees.
If False, returns nan for AOI >= 90 degrees. Significant speed ups
can be achieved by setting this parameter to False.

Returns
-------
data : OrderedDict or DataFrame
Contains the following keys/columns:

* ``ghi``: the modeled global horizontal irradiance in W/m^2.
* ``dni``: the modeled direct normal irradiance in W/m^2.
* ``dhi``: the modeled diffuse horizontal irradiance in
W/m^2.

References
----------
.. [1] B. Marion, A model for deriving the direct normal and
diffuse horizontal irradiance from the global tilted
irradiance, Solar Energy 122, 1037-1046.
http://dx.doi.org/10.1016/j.solener.2015.10.024
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use :doi:`10.1016/j.solener.2015.10.024`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks. updated.

"""

aoi_lt_90 = aoi < 90

# for AOI less than 90 degrees

ghi, dni, dhi, kt_prime = _gti_dirint_lt_90(
poa_global, aoi, aoi_lt_90, solar_zenith, solar_azimuth,
surface_tilt, surface_azimuth, times, pressure=pressure,
use_delta_kt_prime=use_delta_kt_prime, temp_dew=temp_dew,
albedo=albedo, model=model, model_perez=model_perez,
max_iterations=max_iterations)

# for AOI greater than or equal to 90 degrees

if calculate_gt_90:
ghi_gte_90, dni_gte_90, dhi_gte_90 = _gti_dirint_gte_90(
poa_global, aoi, solar_zenith, solar_azimuth,
surface_tilt, times, kt_prime,
pressure=pressure, temp_dew=temp_dew, albedo=albedo)
else:
ghi_gte_90, dni_gte_90, dhi_gte_90 = np.nan, np.nan, np.nan

# put the AOI < 90 and AOI >= 90 conditions together

output = OrderedDict()
output['ghi'] = ghi.where(aoi_lt_90, ghi_gte_90)
output['dni'] = dni.where(aoi_lt_90, dni_gte_90)
output['dhi'] = dhi.where(aoi_lt_90, dhi_gte_90)

output = pd.DataFrame(output, index=times)

return output


def _gti_dirint_lt_90(poa_global, aoi, aoi_lt_90, solar_zenith, solar_azimuth,
surface_tilt, surface_azimuth,
times, pressure=101325.,
use_delta_kt_prime=True, temp_dew=None, albedo=.25,
model='perez', model_perez='allsitescomposite1990',
max_iterations=30):

I0 = extraradiation(times, 1370, 'spencer')
# cos_zenith = np.maximum(0.065, tools.cosd(solar_zenith))
cos_zenith = tools.cosd(solar_zenith)
I0h = I0 * cos_zenith

# these coeffs and diff variables and the loop below
# implement figure 1 of Marion 2015

# make coeffs that is at least 30 elements long
# for loop below will limit iterations if necessary
coeffs = np.empty(max(30, max_iterations))
coeffs[0:3] = 1
coeffs[3:10] = 0.5
coeffs[10:20] = 0.25
coeffs[20:] = 0.125

# initialize diff
diff = pd.Series(9999, index=times)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be a good idea to convert any remaining 999 to nan at the end?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to convert the ghi, dni, dhi to nan where diff remains 9999? alternatively, we could do this for any point that does not converge. This seems to easily occur near aoi = 90.

best_diff = diff

# initialize poa_global_i
poa_global_i = poa_global

for coeff in coeffs[:max_iterations]:
# use slightly > 1 for float errors
best_diff_lte_1 = best_diff <= 1.000001

# only test for aoi less than 90 deg
best_diff_lte_1_lt_90 = best_diff_lte_1[aoi_lt_90]
if best_diff_lte_1_lt_90.all():
break

# calculate kt and DNI using GTI and pvlib implementations
# of Marion eqn 2 and DIRINT
disc_out = disc(poa_global_i, aoi, times, pressure=pressure)
dni, kt_prime = dirint(
poa_global_i, aoi, times, pressure=pressure,
use_delta_kt_prime=use_delta_kt_prime,
temp_dew=temp_dew, return_kt_prime=True)

# calculate DHI using Marion eqn 3 (identify 1st term as GHI)
#ghi = np.minimum(disc_out['kt'], 0.82) * I0h
ghi = disc_out['kt'] * I0h
dhi = ghi - dni * cos_zenith
bad_values = (dhi < 0) | (dni < 0) | (ghi < 0)
dni[bad_values] = np.nan
ghi[bad_values] = np.nan
dhi[bad_values] = np.nan

# use DNI and DHI to model GTI
all_irrad = total_irrad(
surface_tilt, surface_azimuth, solar_zenith, solar_azimuth,
dni, ghi, dhi, dni_extra=I0, airmass=disc_out['airmass'],
albedo=albedo, model=model, model_perez=model_perez)

gti_model = all_irrad['poa_global']

# calculate new diff
diff = gti_model - poa_global

# determine if the new diff is smaller in magnitude
# than the old diff
diff_abs = diff.abs()
smallest_diff = diff_abs < best_diff

# save the best differences
best_diff = diff_abs.where(smallest_diff, best_diff)

# save DNI, DHI, DHI if they provide the best consistency
# otherwise use the older values.
# try/except accounts for first iteration
try:
best_ghi = ghi.where(smallest_diff, best_ghi)
best_dni = dni.where(smallest_diff, best_dni)
best_dhi = dhi.where(smallest_diff, best_dhi)
best_kt_prime = kt_prime.where(smallest_diff, best_kt_prime)
except UnboundLocalError:
best_ghi = ghi
best_dni = dni
best_dhi = dhi
best_kt_prime = kt_prime

# calculate adjusted inputs for next iteration
poa_global_i = np.maximum(1.0, poa_global_i - coeff * diff)
else:
import warnings
failed_points = best_diff[aoi_lt_90][best_diff_lte_1_lt_90 == False]
warnings.warn(
'%s points failed to converge after %s iterations.'
'best_diff:\n%s' %
(len(failed_points), max_iterations, failed_points),
RuntimeWarning)

return best_ghi, best_dni, best_dhi, best_kt_prime


def _gti_dirint_gte_90(poa_global, aoi, solar_zenith, solar_azimuth,
surface_tilt, times, kt_prime,
pressure=101325., temp_dew=None, albedo=.25,):

# set the kt_prime for sunrise to AOI=90 to be equal to
# the kt_prime for 65 < AOI < 80 during the morning.
# similar for the afternoon. repeat for every day.
aoi_gte_90 = aoi >= 90
aoi_lt_90 = aoi < 90
aoi_65_80 = (aoi > 65) & (aoi < 80)
zenith_lt_90 = solar_zenith < 90
morning = solar_azimuth < 180
afternoon = solar_azimuth > 180
aoi_65_80_morning = aoi_65_80 & morning
aoi_65_80_afternoon = aoi_65_80 & afternoon
zenith_lt_90_aoi_gte_90_morning = zenith_lt_90 & aoi_gte_90 & morning
zenith_lt_90_aoi_gte_90_afternoon = zenith_lt_90 & aoi_gte_90 & afternoon

kt_prime_90s = []
for date, data in kt_prime.groupby(times.date):
kt_prime_am_avg = data[aoi_65_80_morning].mean()
kt_prime_pm_avg = data[aoi_65_80_afternoon].mean()

kt_prime_90 = pd.Series(np.nan, index=data.index)
kt_prime_90[zenith_lt_90_aoi_gte_90_morning] = kt_prime_am_avg
kt_prime_90[zenith_lt_90_aoi_gte_90_afternoon] = kt_prime_pm_avg
kt_prime_90s.append(kt_prime_90)
kt_prime_90s = pd.concat(kt_prime_90s)

am = atmosphere.relativeairmass(solar_zenith, model='kasten1966')
am = atmosphere.absoluteairmass(am, pressure)
kt = kt_prime_90s * (1.031 * np.exp(-1.4 / (0.9 + 9.4 / am)) + 0.1)

dni_gte_90 = dirint(
poa_global, solar_zenith, times, pressure=pressure,
use_delta_kt_prime=False,
temp_dew=temp_dew, kt=kt, kt_prime=kt_prime_90s)

cos_zenith = tools.cosd(solar_zenith)

dni_gte_90_proj = dni_gte_90 * cos_zenith

cos_surface_tilt = tools.cosd(surface_tilt)
# isotropic sky plus ground diffuse
dhi_gte_90 = (
(2 * poa_global - dni_gte_90_proj * albedo * (1 - cos_surface_tilt)) /
(1 + cos_surface_tilt + albedo * (1 - cos_surface_tilt)))

ghi_gte_90 = dni_gte_90_proj + dhi_gte_90

return ghi_gte_90, dni_gte_90, dhi_gte_90


def erbs(ghi, zenith, doy):
r"""
Estimate DNI and DHI from GHI using the Erbs model.
Expand Down
Loading
0