Skip to content
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

ENH: vectorize scalar zero-search-functions #8357

Merged
merged 57 commits into from Jun 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c7c5cb8
fixes #8354 and addresses #7242
mikofski Feb 2, 2018
98672d0
remove extra whitespace
mikofski Feb 2, 2018
39581f4
convert all to ndarray using asarray()
mikofski Feb 3, 2018
f24a559
add test for halley's with arrays
mikofski Feb 3, 2018
70edc74
remove commented code for legacy halley's parabolic variant
mikofski Feb 3, 2018
5b90676
allow arrays in newton for secant
mikofski Feb 3, 2018
44bf2eb
use secant test case without runtime warning
mikofski Feb 4, 2018
fcca324
don't test sqrt(16) with x0=4
mikofski Feb 4, 2018
5f181cb
fix sqrt(15) and sqrt(16) values in assertion
mikofski Feb 4, 2018
cef5de3
fix bugs x0 might be seq, div by zero not where tol reached
mikofski Feb 4, 2018
c4d6c9f
Merge branch 'master' of github.com:scipy/scipy into vectorize_newton
mikofski Feb 25, 2018
fe3f1cd
import numpy as np
mikofski Feb 25, 2018
8b7d30e
avoid domain specific function names and variabl
mikofski Feb 25, 2018
2f2b3df
Merge branch 'master' into vectorize_newton
mikofski Mar 10, 2018
9831250
ENH: combine rtol and xtol, set TOL constant once
mikofski Mar 10, 2018
395b046
check if initial guess isscalar, only run vectorized when false
mikofski Mar 10, 2018
0b8fe21
update tests newton array to use x0 array
mikofski Mar 15, 2018
25f2f14
WIP: ENH: continue iterating, handle halting conditions
mikofski Mar 17, 2018
4f0bb08
Merge branch 'master' into vectorize_newton
mikofski Mar 21, 2018
397da36
TST: add benchmarks for array_newton and newton looped
mikofski Mar 21, 2018
0866363
TST: WIP: ENH: fix array newton benchmarks
mikofski Mar 23, 2018
ee034e1
Merge branch 'master' of github.com:scipy/scipy into vectorize_newton
mikofski Mar 23, 2018
3e5d3c9
incorporate changes by Jaime Fernandez
mikofski Mar 23, 2018
e0cdc03
apply Jaime's recommendations to Secant
mikofski Mar 24, 2018
5c87dcb
suppress RuntimeWarning for zero-der elements of array newton
mikofski Mar 24, 2018
ae9e627
DOC: ENH: WIP: update docstring
mikofski Mar 24, 2018
d219ded
ENH: do not suppress RuntimeWarning
mikofski Mar 24, 2018
1155126
WIP: ENC: do not use kwargs as function argument
mikofski Mar 24, 2018
5d3de87
WIP: ENH: x0 to float first, use inplace assign, stop div-by-zero warn
mikofski Mar 25, 2018
acbc75a
WIP: ENH: fix convert x0 to float even if complex
mikofski Mar 25, 2018
5873bb9
WIP: ENH: fix don't index failures, don't reset secant zero-der guess
mikofski Mar 25, 2018
8953401
WIP: ENH: return named tuple
mikofski Mar 25, 2018
5166a7f
ENH: use modern str.format(), add more tests
mikofski Mar 25, 2018
ffb15bd
ENC: replace ~zero_der with nz_der
mikofski Mar 25, 2018
4f5ac8a
ENH: fix test_complex_halley for array
mikofski Mar 25, 2018
cfd7571
Merge branch 'master' into vectorize_newton
mikofski Apr 9, 2018
4373615
BUG: fix secant active_zero_der
mikofski Apr 9, 2018
5fa936f
DOC: update docstring if flag is true, then output is namedtuple
mikofski Apr 9, 2018
cd415e7
ENH: make failures same size as roots and zero_der
mikofski Apr 9, 2018
95290fb
Merge branch 'master' into vectorize_newton
mikofski Apr 25, 2018
dc7250f
Merge upstream 'master' into vectorize_newton
mikofski Apr 25, 2018
043ba2e
Merge #8803 from master into vectorize_newton
mikofski May 17, 2018
2792162
Merge branch 'master' into vectorize_newton
mikofski May 30, 2018
86df16a
Merge upstream 'master' into vectorize_newton
mikofski Jun 1, 2018
51b870c
TST: test secant slope is zero conditions
mikofski Jun 3, 2018
2343ea8
add test for gh8904
mikofski Jun 5, 2018
faf14e1
Merge branch 'fix_gh8904_zeroder_at_root_newton_fails' into array_new…
mikofski Jun 5, 2018
9c242a3
test arrays duh
mikofski Jun 5, 2018
3746e28
fix #8904 change "failures" to "converged"
mikofski Jun 5, 2018
c144b11
Merge branch 'master' into array_newton_with_gh8904
mikofski Jun 17, 2018
658dc63
remove extra line between docstring in root results
mikofski Jun 17, 2018
a5af44a
Merge branch 'array_newton_with_gh8904' into vectorize_newton
mikofski Jun 17, 2018
6e083f9
TST: ENH: test fval before fder and terminate if all roots found
mikofski Jun 17, 2018
0cfcd3d
TST: fix check zero derivatives in array newton
mikofski Jun 17, 2018
45e7b6a
TST: fix gh8904 test for array neweton
mikofski Jun 17, 2018
1f2748c
ENH: initialize nz_der as True, if fval is zero set failures to False
mikofski Jun 17, 2018
c05a476
DOC: add example of good usage case for vectorized
mikofski Jun 24, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
62 changes: 62 additions & 0 deletions benchmarks/benchmarks/optimize_zeros.py
@@ -1,6 +1,7 @@
from __future__ import division, print_function, absolute_import

from math import sqrt, exp, cos, sin
import numpy as np

# Import testing parameters
try:
Expand Down Expand Up @@ -56,3 +57,64 @@ def setup(self, func, meth):

def time_newton(self, func, meth):
newton(self.f, self.x0, args=(), fprime=self.f_1, fprime2=self.f_2)


class NewtonArray(Benchmark):
params = [['loop', 'array'], ['newton', 'secant', 'halley']]
param_names = ['vectorization', 'solver']

def setup(self, vec, meth):
if vec == 'loop':
if meth == 'newton':
self.fvec = lambda f, x0, args, fprime, fprime2: [
newton(f, x, args=(a0, a1) + args[2:], fprime=fprime)
for (x, a0, a1) in zip(x0, args[0], args[1])
]
elif meth == 'halley':
self.fvec = lambda f, x0, args, fprime, fprime2: [
newton(
f, x, args=(a0, a1) + args[2:], fprime=fprime,
fprime2=fprime2
) for (x, a0, a1) in zip(x0, args[0], args[1])
]
else:
self.fvec = lambda f, x0, args, fprime, fprime2: [
newton(f, x, args=(a0, a1) + args[2:]) for (x, a0, a1)
in zip(x0, args[0], args[1])
]
else:
if meth == 'newton':
self.fvec = lambda f, x0, args, fprime, fprime2: newton(
f, x0, args=args, fprime=fprime
)
elif meth == 'halley':
self.fvec = newton
else:
self.fvec = lambda f, x0, args, fprime, fprime2: newton(
f, x0, args=args
)

def time_array_newton(self, vec, meth):

def f(x, *a):
b = a[0] + x * a[3]
return a[1] - a[2] * (np.exp(b / a[5]) - 1.0) - b / a[4] - x

def f_1(x, *a):
b = a[3] / a[5]
return -a[2] * np.exp(a[0] / a[5] + x * b) * b - a[3] / a[4] - 1

def f_2(x, *a):
b = a[3] / a[5]
return -a[2] * np.exp(a[0] / a[5] + x * b) * b ** 2

a0 = np.array([
5.32725221, 5.48673747, 5.49539973,
5.36387202, 4.80237316, 1.43764452,
5.23063958, 5.46094772, 5.50512718,
5.42046290
])
a1 = (np.sin(range(10)) + 1.0) * 7.0
args = (a0, a1, 1e-09, 0.004, 10, 0.27456)
x0 = [7.0] * 10
self.fvec(f, x0, args=args, fprime=f_1, fprime2=f_2)
164 changes: 156 additions & 8 deletions scipy/optimize/tests/test_zeros.py
Expand Up @@ -6,20 +6,22 @@
from numpy.testing import (assert_warns, assert_,
assert_allclose,
assert_equal)
from numpy import finfo
import numpy as np

from scipy.optimize import zeros as cc
from scipy.optimize import zeros

# Import testing parameters
from scipy.optimize._tstutils import functions, fstrings

TOL = 4*np.finfo(float).eps # tolerance


class TestBasic(object):
def run_check(self, method, name):
a = .5
b = sqrt(3)
xtol = 4*finfo(float).eps
rtol = 4*finfo(float).eps
xtol = rtol = TOL
for function, fname in zip(functions, fstrings):
zero, r = method(function, a, b, xtol=xtol, rtol=rtol,
full_output=True)
Expand Down Expand Up @@ -56,6 +58,72 @@ def test_newton(self):
x = zeros.newton(f, 3, fprime=f_1, fprime2=f_2, tol=1e-6)
assert_allclose(f(x), 0, atol=1e-6)

def test_array_newton(self):
"""test newton with array"""

def f1(x, *a):
b = a[0] + x * a[3]
return a[1] - a[2] * (np.exp(b / a[5]) - 1.0) - b / a[4] - x

def f1_1(x, *a):
b = a[3] / a[5]
return -a[2] * np.exp(a[0] / a[5] + x * b) * b - a[3] / a[4] - 1

def f1_2(x, *a):
b = a[3] / a[5]
return -a[2] * np.exp(a[0] / a[5] + x * b) * b ** 2

a0 = np.array([
5.32725221, 5.48673747, 5.49539973,
5.36387202, 4.80237316, 1.43764452,
5.23063958, 5.46094772, 5.50512718,
5.42046290
])
a1 = (np.sin(range(10)) + 1.0) * 7.0
args = (a0, a1, 1e-09, 0.004, 10, 0.27456)
x0 = [7.0] * 10
x = zeros.newton(f1, x0, f1_1, args)
x_expected = (
6.17264965, 11.7702805, 12.2219954,
7.11017681, 1.18151293, 0.143707955,
4.31928228, 10.5419107, 12.7552490,
8.91225749
)
assert_allclose(x, x_expected)
# test halley's
x = zeros.newton(f1, x0, f1_1, args, fprime2=f1_2)
assert_allclose(x, x_expected)
# test secant
x = zeros.newton(f1, x0, args=args)
assert_allclose(x, x_expected)

def test_array_secant_active_zero_der(self):
"""test secant doesn't continue to iterate zero derivatives"""
x = zeros.newton(lambda x, *a: x*x - a[0], x0=[4.123, 5],
args=[np.array([17, 25])])
assert_allclose(x, (4.123105625617661, 5.0))

def test_array_newton_integers(self):
# test secant with float
x = zeros.newton(lambda y, z: z - y ** 2, [4.0] * 2,
args=([15.0, 17.0],))
assert_allclose(x, (3.872983346207417, 4.123105625617661))
# test integer becomes float
x = zeros.newton(lambda y, z: z - y ** 2, [4] * 2, args=([15, 17],))
assert_allclose(x, (3.872983346207417, 4.123105625617661))

def test_array_newton_zero_der_failures(self):
# test derivative zero warning
assert_warns(RuntimeWarning, zeros.newton,
lambda y: y**2 - 2, [0., 0.], lambda y: 2 * y)
# test failures and zero_der
with pytest.warns(RuntimeWarning):
results = zeros.newton(lambda y: y**2 - 2, [0., 0.],
lambda y: 2*y, converged=True)
assert_allclose(results.root, 0)
assert results.zero_der.all()
assert not results.converged.any()

def test_newton_full_output(self):
# Test the full_output capability, both when converging and not.
# Use simple polynomials, to avoid hitting platform dependencies
Expand Down Expand Up @@ -109,8 +177,7 @@ def f(x):
return x - root

methods = [cc.bisect, cc.ridder]
xtol = 4*finfo(float).eps
rtol = 4*finfo(float).eps
xtol = rtol = TOL
for method in methods:
res = method(f, -1e8, 1e7, xtol=xtol, rtol=rtol)
assert_allclose(root, res, atol=xtol, rtol=rtol,
Expand All @@ -134,7 +201,7 @@ def f(x):
return x - 0.6

atol = 0.51
rtol = 4*finfo(float).eps
rtol = TOL
methods = [cc.brentq, cc.brenth]
for method in methods:
res = method(f, 0, 1, xtol=atol, rtol=rtol)
Expand Down Expand Up @@ -162,13 +229,78 @@ def f_1(x, *a):
return 2 * a[0] * x + a[1]

def f_2(x, *a):
return 2 * a[0]
retval = 2 * a[0]
try:
size = len(x)
except TypeError:
return retval
else:
return [retval] * size

z = complex(1.0, 2.0)
coeffs = (2.0, 3.0, 4.0)
y = zeros.newton(f, z, args=coeffs, fprime=f_1, fprime2=f_2, tol=1e-6)
# (-0.75000000000000078+1.1989578808281789j)
assert_allclose(f(y, *coeffs), 0, atol=1e-6)
z = [z] * 10
coeffs = (2.0, 3.0, 4.0)
y = zeros.newton(f, z, args=coeffs, fprime=f_1, fprime2=f_2, tol=1e-6)
assert_allclose(f(y, *coeffs), 0, atol=1e-6)


def test_zero_der_nz_dp():
"""Test secant method with a non-zero dp, but an infinite newton step"""
# pick a symmetrical functions and choose a point on the side that with dx
# makes a secant that is a flat line with zero slope, EG: f = (x - 100)**2,
# which has a root at x = 100 and is symmetrical around the line x = 100
# we have to pick a really big number so that it is consistently true
# now find a point on each side so that the secant has a zero slope
dx = np.finfo(float).eps ** 0.33
# 100 - p0 = p1 - 100 = p0 * (1 + dx) + dx - 100
# -> 200 = p0 * (2 + dx) + dx
p0 = (200.0 - dx) / (2.0 + dx)
x = zeros.newton(lambda y: (y - 100.0)**2, x0=[p0] * 10)
assert_allclose(x, [100] * 10)
# test scalar cases too
p0 = (2.0 - 1e-4) / (2.0 + 1e-4)
x = zeros.newton(lambda y: (y - 1.0) ** 2, x0=p0)
assert_allclose(x, 1)
p0 = (-2.0 + 1e-4) / (2.0 + 1e-4)
x = zeros.newton(lambda y: (y + 1.0) ** 2, x0=p0)
assert_allclose(x, -1)


def test_array_newton_failures():
"""Test that array newton fails as expected"""
# p = 0.68 # [MPa]
# dp = -0.068 * 1e6 # [Pa]
# T = 323 # [K]
diameter = 0.10 # [m]
# L = 100 # [m]
roughness = 0.00015 # [m]
rho = 988.1 # [kg/m**3]
mu = 5.4790e-04 # [Pa*s]
u = 2.488 # [m/s]
reynolds_number = rho * u * diameter / mu # Reynolds number

def colebrook_eqn(darcy_friction, re, dia):
return (1 / np.sqrt(darcy_friction) +
2 * np.log10(roughness / 3.7 / dia +
2.51 / re / np.sqrt(darcy_friction)))

# only some failures
with pytest.warns(RuntimeWarning):
result = zeros.newton(
colebrook_eqn, x0=[0.01, 0.2, 0.02223, 0.3], maxiter=2,
args=[reynolds_number, diameter], converged=True
)
assert not result.converged.all()
# they all fail
with pytest.raises(RuntimeError):
result = zeros.newton(
colebrook_eqn, x0=[0.01] * 2, maxiter=2,
args=[reynolds_number, diameter], converged=True
)


# this test should **not** raise a RuntimeWarning
Expand All @@ -182,16 +314,29 @@ def f_zeroder_root(x):
# should work with secant
r = zeros.newton(f_zeroder_root, x0=0)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# test again with array
r = zeros.newton(f_zeroder_root, x0=[0]*10)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)

# 1st derivative
def fder(x):
return 3 * x**2 - 2 * x

# 2nd derivative
def fder2(x):
return 6*x - 2

# should work with newton and halley
r = zeros.newton(f_zeroder_root, x0=0, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
r = zeros.newton(f_zeroder_root, x0=0, fprime=fder,
fprime2=lambda x: 6*x - 2)
fprime2=fder2)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# test again with array
r = zeros.newton(f_zeroder_root, x0=[0]*10, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
r = zeros.newton(f_zeroder_root, x0=[0]*10, fprime=fder,
fprime2=fder2)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)

# also test that if a root is found we do not raise RuntimeWarning even if
Expand All @@ -201,4 +346,7 @@ def fder(x):
# a zero derivative, so it should return the root w/o RuntimeWarning
r = zeros.newton(f_zeroder_root, x0=0.5, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# test again with array
r = zeros.newton(f_zeroder_root, x0=[0.5]*10, fprime=fder)
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol)
# doesn't apply to halley