Skip to content

Commit 18ab529

Browse files
committed
add lambertw_pvlib
1 parent 3ad5a9e commit 18ab529

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

pvlib/tools.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,3 +586,59 @@ def _file_context_manager(filename_or_object, mode='r', encoding=None):
586586
# otherwise, assume a filename or path
587587
context = open(str(filename_or_object), mode=mode, encoding=encoding)
588588
return context
589+
590+
591+
def _log_lambertw(logx):
592+
r'''Computes W(x) starting from log(x).
593+
594+
Parameters
595+
----------
596+
logx : numeric
597+
Log(x) of
598+
599+
Returns
600+
-------
601+
numeric
602+
Lambert's W(x)
603+
604+
'''
605+
# handles overflow cases, but results in nan for x <= 1
606+
w = logx - np.log(logx) # initial guess, w = log(x) - log(log(x))
607+
608+
for _ in range(0, 3):
609+
# Newton's. Halley's is not substantially faster or more accurate
610+
# because f''(w) = -1 / (w**2) is small for large w
611+
w = w * (1. - np.log(w) + logx) / (1. + w)
612+
return w
613+
614+
615+
def lambertw_pvlib(x):
616+
r'''Lambert's W function.
617+
618+
Parameters
619+
----------
620+
x : numeric
621+
Must be real numbers.
622+
623+
Returns
624+
-------
625+
numeric
626+
Lambert's W(x). Principal branch only.
627+
628+
'''
629+
w = np.full_like(x, np.nan)
630+
small = x <= 10
631+
# for large x, solve 0 = f(w) = w + log(w) - log(x) using Newton's
632+
w[~small] = _log_lambertw(np.log(x[~small]))
633+
634+
# w will contain nan for these numbers due to log(w) = log(log(x))
635+
# for small x, solve 0 = g(w) = w * exp(w) - x using Halley's method
636+
if any(small):
637+
z = x[small]
638+
g = np.log(x[small] + 1) - np.log(np.log(x[small] + 1) + 1)
639+
for _ in range(0, 3):
640+
expg = np.exp(g)
641+
g = g - (g*expg - z) * (g + 1) / (expg * (g + 1)**2 - 0.5*(g + 2)*(expg*g - z))
642+
w[small] = g
643+
644+
return w

tests/test_tools.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,28 @@ def test__file_context_manager():
283283
buffer = StringIO("test content")
284284
with tools._file_context_manager(buffer) as obj:
285285
assert obj.read() == "test content"
286+
287+
288+
def test_lambertw_pvlib():
289+
test_exp = np.arange(-10., 300, step=10)
290+
test_x = 10.**test_exp
291+
# known solution from scipy.special.lambertw
292+
expected = np.array([
293+
9.9999999989999997e-11, 5.6714329040978384e-01,
294+
2.0028685413304952e+01, 4.2306755091738395e+01,
295+
6.4904633770046118e+01, 8.7630277151947183e+01,
296+
1.1042491882731335e+02, 1.3326278259180333e+02,
297+
1.5613026581351718e+02, 1.7901931374150624e+02,
298+
2.0192476320084489e+02, 2.2484310644511851e+02,
299+
2.4777185185877809e+02, 2.7070916610249782e+02,
300+
2.9365366103997610e+02, 3.1660426041503479e+02,
301+
3.3956011295458728e+02, 3.6252053376149752e+02,
302+
3.8548496362161768e+02, 4.0845294003314166e+02,
303+
4.3142407612718210e+02, 4.5439804503371403e+02,
304+
4.7737456808796901e+02, 5.0035340579834485e+02,
305+
5.2333435083468805e+02, 5.4631722251791496e+02,
306+
5.6930186244110166e+02, 5.9228813095427859e+02,
307+
6.1527590431628334e+02, 6.3826507236734335e+02,
308+
6.6125553661218726e+02])
309+
result = tools.lambertw_pvlib(test_x)
310+
assert np.allclose(result, expected, rtol=1e-14)

0 commit comments

Comments
 (0)