Skip to content

Commit b8f89b8

Browse files
authored
handle nan in tools.golden_sect_DataFrame (#1408)
* handle nan * docstring * stickler * handle all-nan case * stickler * coverage
1 parent c0c46b4 commit b8f89b8

File tree

2 files changed

+55
-10
lines changed

2 files changed

+55
-10
lines changed

pvlib/tests/test_tools.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,30 @@ def test__golden_sect_DataFrame_vector():
4545
v, x = tools._golden_sect_DataFrame(params, lower, upper,
4646
_obj_test_golden_sect)
4747
assert np.allclose(x, expected, atol=1e-8)
48+
49+
50+
def test__golden_sect_DataFrame_nans():
51+
# nan in bounds
52+
params = {'c': np.array([1., 2., 1.]), 'n': np.array([1., 1., 1.])}
53+
lower = np.array([0., 0.001, np.nan])
54+
upper = np.array([1.1, 1.2, 1.])
55+
expected = np.array([0.5, 0.25, np.nan])
56+
v, x = tools._golden_sect_DataFrame(params, lower, upper,
57+
_obj_test_golden_sect)
58+
assert np.allclose(x, expected, atol=1e-8, equal_nan=True)
59+
# nan in function values
60+
params = {'c': np.array([1., 2., np.nan]), 'n': np.array([1., 1., 1.])}
61+
lower = np.array([0., 0.001, 0.])
62+
upper = np.array([1.1, 1.2, 1.])
63+
expected = np.array([0.5, 0.25, np.nan])
64+
v, x = tools._golden_sect_DataFrame(params, lower, upper,
65+
_obj_test_golden_sect)
66+
assert np.allclose(x, expected, atol=1e-8, equal_nan=True)
67+
# all nan in bounds
68+
params = {'c': np.array([1., 2., 1.]), 'n': np.array([1., 1., 1.])}
69+
lower = np.array([np.nan, np.nan, np.nan])
70+
upper = np.array([1.1, 1.2, 1.])
71+
expected = np.array([np.nan, np.nan, np.nan])
72+
v, x = tools._golden_sect_DataFrame(params, lower, upper,
73+
_obj_test_golden_sect)
74+
assert np.allclose(x, expected, atol=1e-8, equal_nan=True)

pvlib/tools.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import numpy as np
77
import pandas as pd
88
import pytz
9+
import warnings
910

1011

1112
def cosd(angle):
@@ -286,14 +287,17 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8):
286287
287288
Parameters
288289
----------
289-
params : dict or Dataframe
290-
Parameters to be passed to `func`.
290+
params : dict of numeric
291+
Parameters to be passed to `func`. Each entry must be of the same
292+
length.
291293
292294
lower: numeric
293-
Lower bound for the optimization
295+
Lower bound for the optimization. Must be the same length as each
296+
entry of params.
294297
295298
upper: numeric
296-
Upper bound for the optimization
299+
Upper bound for the optimization. Must be the same length as each
300+
entry of params.
297301
298302
func: function
299303
Function to be optimized. Must be in the form
@@ -312,6 +316,7 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8):
312316
Notes
313317
-----
314318
This function will find the points where the function is maximized.
319+
Returns nan where lower or upper is nan, or where func evaluates to nan.
315320
316321
See also
317322
--------
@@ -326,10 +331,15 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8):
326331

327332
converged = False
328333
iterations = 0
329-
iterlimit = 1 + np.max(
330-
np.trunc(np.log(atol / (df['VH'] - df['VL'])) / np.log(phim1)))
331334

332-
while not converged and (iterations < iterlimit):
335+
# handle all NaN case gracefully
336+
with warnings.catch_warnings():
337+
warnings.filterwarnings(action='ignore',
338+
message='All-NaN slice encountered')
339+
iterlimit = 1 + np.nanmax(
340+
np.trunc(np.log(atol / (df['VH'] - df['VL'])) / np.log(phim1)))
341+
342+
while not converged and (iterations <= iterlimit):
333343

334344
phi = phim1 * (df['VH'] - df['VL'])
335345
df['V1'] = df['VL'] + phi
@@ -345,15 +355,23 @@ def _golden_sect_DataFrame(params, lower, upper, func, atol=1e-8):
345355
err = abs(df['V2'] - df['V1'])
346356

347357
# works with single value because err is np.float64
348-
converged = (err < atol).all()
358+
converged = (err[~np.isnan(err)] < atol).all()
349359
# err will be less than atol before iterations hit the limit
350360
# but just to be safe
351361
iterations += 1
352362

353363
if iterations > iterlimit:
354-
raise Exception("iterations exceeded maximum") # pragma: no cover
364+
raise Exception("Iterations exceeded maximum. Check that func",
365+
" is not NaN in (lower, upper)") # pragma: no cover
366+
367+
try:
368+
func_result = func(df, 'V1')
369+
x = np.where(np.isnan(func_result), np.nan, df['V1'])
370+
except KeyError:
371+
func_result = np.full_like(upper, np.nan)
372+
x = func_result.copy()
355373

356-
return func(df, 'V1'), df['V1']
374+
return func_result, x
357375

358376

359377
def _get_sample_intervals(times, win_length):

0 commit comments

Comments
 (0)