Skip to content

Commit f8423c9

Browse files
uvchikwholmgren
authored andcommitted
Safely calculate dni from dhi and ghi (#250)
* creating basic function to calculate DNI from GHI and DHI * replace equation with new function for test purposes * change types to satisfy type checks * remove warning if new function is used * adapt input and output of dni method * adapt docstring to new inputs * add clearsky method to correct calculated dni * add cutoff method to correct calculated dni * add logging warning and debug messages * increase code quality * remove non-ascii characters * Remove user warning * Replace input variable location by zenith and clearsky_dni * Correct docstring * Remove unnecessary calculation * Correct negative DNI Negative values of the calculated DNI will be set to zero or if the new input value 'set_to_nan' is 'True' will be set to 'NaN'. * Implement clearsky method * Include tolerance factor for clearsky method and adapt function call in modelchain * Enable set_to_nan in cutoff method * Raise value error if chosen method does not exist * Debugging info how many values needed correction * Correct parameter types in docstring * Correct test_complete_irradiance * Remove debugging info * Remove method and introduce new parameters for cutoff zenith angles * Adapt docstring to new parameters * Remove commented lines * Adapt complete_irradiance definition of modelchain to new parameters in DNI calculation * Add test function for DNI calculation * Merge upstream changes on master The only conflict was that both me and the upstream master added a test at the end of a test file so the solution was easy: just take both. * Add accidentally un-added files I had a mishap which removed these files from being version controlled. This should fix it. * Remove kwargs from dni function * Remove unnecessary dni_tmp * Remove blank line at end of docstring * Add max_dni to do calculation only once * Correct comment * Correct docstring for lower_cutoff_zenith * Remove kwargs from complete_irradiance * Correct docstrings * Use same default value for clearsky_tolerance as in ModelChain * Change names of lower_ and upper_cutoff_zenith and adapt docstring and comments lower_cutoff_zenith is renamed to zenith_threshold_for_clearsky_limit upper_cutoff_zenith is renamed to zenith_threshold_for_zero_dni * add safely calculate dni from dhi and ghi (#250)
1 parent b4db6e2 commit f8423c9

File tree

6 files changed

+115
-13
lines changed

6 files changed

+115
-13
lines changed

docs/sphinx/source/whatsnew/v0.4.5.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@ Bug fixes
1313
* Added lower accuracy formulas for equation of time, declination, hour angle
1414
and solar zenith.
1515

16+
Enhancements
17+
~~~~~~~~~~~~~
18+
19+
* Added irradiance.dni method that determines DNI from GHI and DHI and corrects
20+
unreasonable DNI values during sunrise/sunset transitions
21+
1622

1723
Contributors
1824
~~~~~~~~~~~~
1925

2026
* Will Holmgren
2127
* Mark Mikofski
28+
* Birgit Schachler

pvlib/irradiance.py

100644100755
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,3 +2048,75 @@ def _get_dirint_coeffs():
20482048
[0.743440, 0.592190, 0.603060, 0.316930, 0.794390]]
20492049

20502050
return coeffs[1:, 1:, :, :]
2051+
2052+
2053+
def dni(ghi, dhi, zenith, clearsky_dni=None, clearsky_tolerance=1.1,
2054+
zenith_threshold_for_zero_dni=88.0,
2055+
zenith_threshold_for_clearsky_limit=80.0):
2056+
"""
2057+
Determine DNI from GHI and DHI.
2058+
2059+
When calculating the DNI from GHI and DHI the calculated DNI may be
2060+
unreasonably high or negative for zenith angles close to 90 degrees
2061+
(sunrise/sunset transitions). This function identifies unreasonable DNI
2062+
values and sets them to NaN. If the clearsky DNI is given unreasonably high
2063+
values are cut off.
2064+
2065+
Parameters
2066+
----------
2067+
ghi : Series
2068+
Global horizontal irradiance.
2069+
2070+
dhi : Series
2071+
Diffuse horizontal irradiance.
2072+
2073+
zenith : Series
2074+
True (not refraction-corrected) zenith angles in decimal
2075+
degrees. Angles must be >=0 and <=180.
2076+
2077+
clearsky_dni : None or Series
2078+
Clearsky direct normal irradiance. Default: None.
2079+
2080+
clearsky_tolerance : float
2081+
If 'clearsky_dni' is given this parameter can be used to allow a
2082+
tolerance by how much the calculated DNI value can be greater than
2083+
the clearsky value before it is identified as an unreasonable value.
2084+
Default: 1.1.
2085+
2086+
zenith_threshold_for_zero_dni : float
2087+
Non-zero DNI values for zenith angles greater than or equal to
2088+
'zenith_threshold_for_zero_dni' will be set to NaN. Default: 88.
2089+
2090+
zenith_threshold_for_clearsky_limit : float
2091+
DNI values for zenith angles greater than or equal to
2092+
'zenith_threshold_for_clearsky_limit' and smaller the
2093+
'zenith_threshold_for_zero_dni' that are greater than the clearsky DNI
2094+
(times allowed tolerance) will be corrected. Only applies if
2095+
'clearsky_dni' is not None. Default: 80.
2096+
2097+
Returns
2098+
-------
2099+
dni : Series
2100+
The modeled direct normal irradiance.
2101+
"""
2102+
2103+
# calculate DNI
2104+
dni = (ghi - dhi) / tools.cosd(zenith)
2105+
2106+
# cutoff negative values
2107+
dni[dni < 0] = float('nan')
2108+
2109+
# set non-zero DNI values for zenith angles >=
2110+
# zenith_threshold_for_zero_dni to NaN
2111+
dni[(zenith >= zenith_threshold_for_zero_dni) & (dni != 0)] = float('nan')
2112+
2113+
# correct DNI values for zenith angles greater or equal to the
2114+
# zenith_threshold_for_clearsky_limit and smaller than the
2115+
# upper_cutoff_zenith that are greater than the clearsky DNI (times
2116+
# clearsky_tolerance)
2117+
if clearsky_dni is not None:
2118+
max_dni = clearsky_dni * clearsky_tolerance
2119+
dni[(zenith >= zenith_threshold_for_clearsky_limit) &
2120+
(zenith < zenith_threshold_for_zero_dni) &
2121+
(dni > max_dni)] = max_dni
2122+
return dni

pvlib/modelchain.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -676,18 +676,22 @@ def complete_irradiance(self, times=None, weather=None):
676676
"Results can be too high or negative.\n" +
677677
"Help to improve this function on github:\n" +
678678
"https://github.com/pvlib/pvlib-python \n")
679-
warnings.warn(wrn_txt, UserWarning)
679+
680680
if {'ghi', 'dhi'} <= icolumns and 'dni' not in icolumns:
681681
logging.debug('Estimate dni from ghi and dhi')
682-
self.weather.loc[:, 'dni'] = (
683-
(self.weather.loc[:, 'ghi'] - self.weather.loc[:, 'dhi']) /
684-
tools.cosd(self.solar_position.loc[:, 'zenith']))
682+
self.weather.loc[:, 'dni'] = pvlib.irradiance.dni(
683+
self.weather.loc[:, 'ghi'], self.weather.loc[:, 'dhi'],
684+
self.solar_position.zenith,
685+
clearsky_dni=self.location.get_clearsky(times).dni,
686+
clearsky_tolerance=1.1)
685687
elif {'dni', 'dhi'} <= icolumns and 'ghi' not in icolumns:
688+
warnings.warn(wrn_txt, UserWarning)
686689
logging.debug('Estimate ghi from dni and dhi')
687690
self.weather.loc[:, 'ghi'] = (
688691
self.weather.dni * tools.cosd(self.solar_position.zenith) +
689692
self.weather.dhi)
690693
elif {'dni', 'ghi'} <= icolumns and 'dhi' not in icolumns:
694+
warnings.warn(wrn_txt, UserWarning)
691695
logging.debug('Estimate dhi from dni and ghi')
692696
self.weather.loc[:, 'dhi'] = (
693697
self.weather.ghi - self.weather.dni *

pvlib/test/test_irradiance.py

100644100755
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ def test_erbs_all_scalar():
369369
for k, v in out.items():
370370
assert_allclose(v, expected[k], 5)
371371

372+
372373
@needs_numpy_1_10
373374
def test_dirindex():
374375
clearsky_data = tus.get_clearsky(times, model='ineichen',
@@ -403,3 +404,21 @@ def test_dirindex():
403404
tol_dirint = 0.2
404405
assert np.allclose(out.values, dirint_close_values, rtol=tol_dirint, atol=0,
405406
equal_nan=True)
407+
408+
409+
def test_dni():
410+
ghi = pd.Series([90, 100, 100, 100, 100])
411+
dhi = pd.Series([100, 90, 50, 50, 50])
412+
zenith = pd.Series([80, 100, 85, 70, 85])
413+
clearsky_dni = pd.Series([50, 50, 200, 50, 300])
414+
415+
dni = irradiance.dni(ghi, dhi, zenith,
416+
clearsky_dni=clearsky_dni, clearsky_tolerance=2)
417+
assert_series_equal(dni,
418+
pd.Series([float('nan'), float('nan'), 400,
419+
146.190220008, 573.685662283]))
420+
421+
dni = irradiance.dni(ghi, dhi, zenith)
422+
assert_series_equal(dni,
423+
pd.Series([float('nan'), float('nan'), 573.685662283,
424+
146.190220008, 573.685662283]))

pvlib/test/test_modelchain.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -479,22 +479,22 @@ def test_complete_irradiance_clean_run(system, location):
479479
def test_complete_irradiance(system, location):
480480
"""Check calculations"""
481481
mc = ModelChain(system, location)
482-
times = pd.date_range('2010-07-05 9:00:00', periods=2, freq='H')
483-
i = pd.DataFrame({'dni': [30.354455, 77.22822],
484-
'dhi': [372.103976116, 497.087579068],
485-
'ghi': [356.543700, 465.44400]}, index=times)
482+
times = pd.date_range('2010-07-05 7:00:00-0700', periods=2, freq='H')
483+
i = pd.DataFrame({'dni': [49.756966, 62.153947],
484+
'ghi': [372.103976116, 497.087579068],
485+
'dhi': [356.543700, 465.44400]}, index=times)
486486

487487
mc.complete_irradiance(times, weather=i[['ghi', 'dni']])
488488
assert_series_equal(mc.weather['dhi'],
489-
pd.Series([372.103976116, 497.087579068],
489+
pd.Series([356.543700, 465.44400],
490490
index=times, name='dhi'))
491491

492492
mc.complete_irradiance(times, weather=i[['dhi', 'dni']])
493493
assert_series_equal(mc.weather['ghi'],
494-
pd.Series([356.543700, 465.44400],
494+
pd.Series([372.103976116, 497.087579068],
495495
index=times, name='ghi'))
496496

497497
mc.complete_irradiance(times, weather=i[['dhi', 'ghi']])
498498
assert_series_equal(mc.weather['dni'],
499-
pd.Series([30.354455, 77.22822],
499+
pd.Series([49.756966, 62.153947],
500500
index=times, name='dni'))

pvlib/tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ def cosd(angle):
1515
1616
Parameters
1717
----------
18-
angle : float
18+
angle : float or array-like
1919
Angle in degrees
2020
2121
Returns
2222
-------
23-
result : float
23+
result : float or array-like
2424
Cosine of the angle
2525
"""
2626

0 commit comments

Comments
 (0)