Skip to content

Commit bf9eff6

Browse files
AdamRJensencwhansekandersolar
authored
Add outside_domain parameter to irradiance ratio QC tests (#214)
* Add out_of_bounds parameter * Implement out_of_bounds * Update irradiance.py * Change to outside_domain * Update tests * Refactor code * Add whatsnew * Add reviewer suggestion Co-authored-by: Cliff Hansen <[email protected]> * Fix linter * Fix linter * Address reviewer comment * Apply suggestions from code review Co-authored-by: Kevin Anderson <[email protected]> * Remove within_domain_hz overriding line * Change outside_domain to scalar, param as optional * Update warning --------- Co-authored-by: Cliff Hansen <[email protected]> Co-authored-by: Kevin Anderson <[email protected]>
1 parent 2c6a7af commit bf9eff6

File tree

3 files changed

+87
-38
lines changed

3 files changed

+87
-38
lines changed

docs/whatsnew/0.2.2.rst

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
Enhancements
88
~~~~~~~~~~~~
9+
* Added optional keyword `outside_domain` to
10+
:py:func:`~pvanalytics.quality.irradiance.check_irradiance_consistency_qcrad`.
11+
(:pull:`214`)
912

1013

1114
Bug Fixes
@@ -26,3 +29,4 @@ Testing
2629

2730
Contributors
2831
~~~~~~~~~~~~
32+
* Adam R. Jensen (:ghuser:`AdamRJensen`)

pvanalytics/quality/irradiance.py

+48-22
Original file line numberDiff line numberDiff line change
@@ -269,29 +269,32 @@ def _get_bounds(bounds):
269269

270270

271271
def _check_irrad_ratio(ratio, ghi, sza, bounds):
272-
# unpack bounds dict
272+
# unpack bounds
273273
ghi_lb, ghi_ub, sza_lb, sza_ub, ratio_lb, ratio_ub = _get_bounds(bounds)
274-
# for zenith set inclusive_lower to handle edge cases, e.g., zenith=0
275-
return (
274+
275+
within_domain = (
276276
quality.util.check_limits(
277277
sza, lower_bound=sza_lb, upper_bound=sza_ub, inclusive_lower=True)
278278
& quality.util.check_limits(
279-
ghi, lower_bound=ghi_lb, upper_bound=ghi_ub)
280-
& quality.util.check_limits(
281-
ratio, lower_bound=ratio_lb, upper_bound=ratio_ub)
279+
ghi, lower_bound=ghi_lb, upper_bound=ghi_ub, inclusive_lower=True)
282280
)
283281

282+
flag = within_domain & quality.util.check_limits(
283+
ratio, lower_bound=ratio_lb, upper_bound=ratio_ub)
284+
285+
return flag, within_domain
286+
284287

285288
def check_irradiance_consistency_qcrad(solar_zenith, ghi, dhi, dni,
286-
param=None):
287-
"""Check consistency of GHI, DHI and DNI using QCRad criteria.
289+
param=None, outside_domain=False):
290+
r"""Check consistency of GHI, DHI and DNI using QCRad criteria.
288291
289292
Uses criteria given in [1]_ to validate the ratio of irradiance
290293
components.
291294
292-
.. warning:: Not valid for night time. While you can pass data
293-
from night time to this function, be aware that the truth
294-
values returned for that data will not be valid.
295+
.. warning:: Not valid for night time or low irradiance. When the input
296+
data fall outside the test domain, the returned value is set by the
297+
``outside_domain`` parameter.
295298
296299
Parameters
297300
----------
@@ -303,12 +306,15 @@ def check_irradiance_consistency_qcrad(solar_zenith, ghi, dhi, dni,
303306
Diffuse horizontal irradiance in :math:`W/m^2`
304307
dni : Series
305308
Direct normal irradiance in :math:`W/m^2`
306-
param : dict
309+
param : dict, optional
307310
keys are 'ghi_ratio' and 'dhi_ratio'. For each key, value is a dict
308311
with keys 'high_zenith' and 'low_zenith'; for each of these keys,
309312
value is a dict with keys 'zenith_bounds', 'ghi_bounds', and
310313
'ratio_bounds' and value is an ordered pair [lower, upper]
311314
of float.
315+
outside_domain : scalar, default False
316+
Value to return when the tests are not applicable, i.e., when the
317+
input data fall outside the test domain.
312318
313319
Returns
314320
-------
@@ -319,6 +325,15 @@ def check_irradiance_consistency_qcrad(solar_zenith, ghi, dhi, dni,
319325
320326
Notes
321327
-----
328+
The QCRad algorithm checks that the input GHI is consistent with the
329+
component sum :math:`DNI \times \cos ( zenith ) + DHI` of input DNI and
330+
DHI, and that the ratio :math:`\frac{DHI}{GHI}` is reasonable.
331+
332+
In these two parts, the ``ghi_bounds`` are applied differently. In the
333+
components test, the bounds are applied to the component sum of diffuse and
334+
direct irradiance, whereas in the diffuse ratio test the bounds are applied
335+
to the measured ``ghi``.
336+
322337
Copyright (c) 2019 SolarArbiter. See the file
323338
LICENSES/SOLARFORECASTARBITER_LICENSE at the top level directory
324339
of this distribution and at `<https://github.com/pvlib/
@@ -341,18 +356,29 @@ def check_irradiance_consistency_qcrad(solar_zenith, ghi, dhi, dni,
341356
dhi_ratio = dhi / ghi
342357

343358
bounds = param['ghi_ratio']
344-
consistent_components = (
345-
_check_irrad_ratio(ratio=ghi_ratio, ghi=component_sum,
346-
sza=solar_zenith, bounds=bounds['high_zenith'])
347-
| _check_irrad_ratio(ratio=ghi_ratio, ghi=component_sum,
348-
sza=solar_zenith, bounds=bounds['low_zenith']))
359+
flag_lz, within_domain_lz = _check_irrad_ratio(
360+
ratio=ghi_ratio, ghi=component_sum, sza=solar_zenith,
361+
bounds=bounds['low_zenith'])
362+
flag_hz, within_domain_hz = _check_irrad_ratio(
363+
ratio=ghi_ratio, ghi=component_sum, sza=solar_zenith,
364+
bounds=bounds['high_zenith'])
365+
366+
consistent_components = ((flag_lz & within_domain_lz) |
367+
(flag_hz & within_domain_hz))
368+
consistent_components[~(within_domain_lz | within_domain_hz)] = \
369+
outside_domain
349370

350371
bounds = param['dhi_ratio']
351-
diffuse_ratio_limit = (
352-
_check_irrad_ratio(ratio=dhi_ratio, ghi=ghi, sza=solar_zenith,
353-
bounds=bounds['high_zenith'])
354-
| _check_irrad_ratio(ratio=dhi_ratio, ghi=ghi, sza=solar_zenith,
355-
bounds=bounds['low_zenith']))
372+
flag_lz, within_domain_lz = _check_irrad_ratio(
373+
ratio=dhi_ratio, ghi=ghi, sza=solar_zenith,
374+
bounds=bounds['low_zenith'])
375+
flag_hz, within_domain_hz = _check_irrad_ratio(
376+
ratio=dhi_ratio, ghi=ghi, sza=solar_zenith,
377+
bounds=bounds['high_zenith'])
378+
diffuse_ratio_limit = ((flag_lz & within_domain_lz) |
379+
(flag_hz & within_domain_hz))
380+
diffuse_ratio_limit[~(within_domain_lz | within_domain_hz)] = \
381+
outside_domain
356382

357383
return consistent_components, diffuse_ratio_limit
358384

pvanalytics/tests/quality/test_irradiance.py

+35-16
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,28 @@ def irradiance_qcrad():
4242
output = pd.DataFrame(
4343
columns=['ghi', 'dhi', 'dni', 'solar_zenith', 'dni_extra',
4444
'ghi_limit_flag', 'dhi_limit_flag', 'dni_limit_flag',
45-
'consistent_components', 'diffuse_ratio_limit'],
46-
data=np.array([[-100, 100, 100, 30, 1370, 0, 1, 1, 0, 0],
47-
[100, -100, 100, 30, 1370, 1, 0, 1, 0, 0],
48-
[100, 100, -100, 30, 1370, 1, 1, 0, 0, 1],
49-
[1000, 100, 900, 0, 1370, 1, 1, 1, 1, 1],
50-
[1000, 200, 800, 15, 1370, 1, 1, 1, 1, 1],
51-
[1000, 200, 800, 60, 1370, 0, 1, 1, 0, 1],
52-
[1000, 300, 850, 80, 1370, 0, 0, 1, 0, 1],
53-
[1000, 500, 800, 90, 1370, 0, 0, 1, 0, 1],
54-
[500, 100, 1100, 0, 1370, 1, 1, 1, 0, 1],
55-
[1000, 300, 1200, 0, 1370, 1, 1, 1, 0, 1],
56-
[500, 600, 100, 60, 1370, 1, 1, 1, 0, 0],
57-
[500, 600, 400, 80, 1370, 0, 0, 1, 0, 0],
58-
[500, 500, 300, 80, 1370, 0, 0, 1, 1, 1],
59-
[0, 0, 0, 93, 1370, 1, 1, 1, 0, 0]]))
45+
'consistent_components', 'diffuse_ratio_limit',
46+
'consistent_components_outside_domain',
47+
'diffuse_ratio_limit_outside_domain',
48+
],
49+
data=np.array([[-100, 100, 100, 30, 1370, 0, 1, 1, 0, 0, 0, 1],
50+
[100, -100, 100, 30, 1370, 1, 0, 1, 0, 0, 1, 0],
51+
[100, 100, -100, 30, 1370, 1, 1, 0, 0, 1, 1, 1],
52+
[1000, 100, 900, 0, 1370, 1, 1, 1, 1, 1, 1, 1],
53+
[1000, 200, 800, 15, 1370, 1, 1, 1, 1, 1, 1, 1],
54+
[1000, 200, 800, 60, 1370, 0, 1, 1, 0, 1, 0, 1],
55+
[1000, 300, 850, 80, 1370, 0, 0, 1, 0, 1, 0, 1],
56+
[1000, 500, 800, 90, 1370, 0, 0, 1, 0, 1, 0, 1],
57+
[500, 100, 1100, 0, 1370, 1, 1, 1, 0, 1, 0, 1],
58+
[1000, 300, 1200, 0, 1370, 1, 1, 1, 0, 1, 0, 1],
59+
[500, 600, 100, 60, 1370, 1, 1, 1, 0, 0, 0, 0],
60+
[500, 600, 400, 80, 1370, 0, 0, 1, 0, 0, 0, 0],
61+
[500, 500, 300, 80, 1370, 0, 0, 1, 1, 1, 1, 1],
62+
[0, 0, 0, 93, 1370, 1, 1, 1, 0, 0, 1, 1],
63+
[100, 100, 0, 95, 1370, 0, 0, 1, 0, 0, 1, 1],
64+
]))
6065
dtypes = ['float64', 'float64', 'float64', 'float64', 'float64',
61-
'bool', 'bool', 'bool', 'bool', 'bool']
66+
'bool', 'bool', 'bool', 'bool', 'bool', 'bool', 'bool']
6267
for (col, typ) in zip(output.columns, dtypes):
6368
output[col] = output[col].astype(typ)
6469
return output
@@ -179,6 +184,20 @@ def test_check_irradiance_consistency_qcrad(irradiance_qcrad):
179184
check_names=False)
180185

181186

187+
def test_check_irradiance_consistency_qcrad_outside_domain(irradiance_qcrad):
188+
expected = irradiance_qcrad
189+
cons_comp, diffuse = irradiance.check_irradiance_consistency_qcrad(
190+
expected['solar_zenith'], expected['ghi'],
191+
expected['dhi'], expected['dni'],
192+
outside_domain=True)
193+
assert_series_equal(cons_comp,
194+
expected['consistent_components_outside_domain'],
195+
check_names=False)
196+
assert_series_equal(diffuse,
197+
expected['diffuse_ratio_limit_outside_domain'],
198+
check_names=False)
199+
200+
182201
@pytest.fixture
183202
def times():
184203
"""One hour of times at 10 minute frequency.

0 commit comments

Comments
 (0)