1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

""" 

Spectral Algorithm for Nonlinear Equations 

""" 

from __future__ import division, absolute_import, print_function 

 

import collections 

 

import numpy as np 

from scipy.optimize import OptimizeResult 

from scipy.optimize.optimize import _check_unknown_options 

from .linesearch import _nonmonotone_line_search_cruz, _nonmonotone_line_search_cheng 

 

class _NoConvergence(Exception): 

pass 

 

 

def _root_df_sane(func, x0, args=(), ftol=1e-8, fatol=1e-300, maxfev=1000, 

fnorm=None, callback=None, disp=False, M=10, eta_strategy=None, 

sigma_eps=1e-10, sigma_0=1.0, line_search='cruz', **unknown_options): 

r""" 

Solve nonlinear equation with the DF-SANE method 

 

Options 

------- 

ftol : float, optional 

Relative norm tolerance. 

fatol : float, optional 

Absolute norm tolerance. 

Algorithm terminates when ``||func(x)|| < fatol + ftol ||func(x_0)||``. 

fnorm : callable, optional 

Norm to use in the convergence check. If None, 2-norm is used. 

maxfev : int, optional 

Maximum number of function evaluations. 

disp : bool, optional 

Whether to print convergence process to stdout. 

eta_strategy : callable, optional 

Choice of the ``eta_k`` parameter, which gives slack for growth 

of ``||F||**2``. Called as ``eta_k = eta_strategy(k, x, F)`` with 

`k` the iteration number, `x` the current iterate and `F` the current 

residual. Should satisfy ``eta_k > 0`` and ``sum(eta, k=0..inf) < inf``. 

Default: ``||F||**2 / (1 + k)**2``. 

sigma_eps : float, optional 

The spectral coefficient is constrained to ``sigma_eps < sigma < 1/sigma_eps``. 

Default: 1e-10 

sigma_0 : float, optional 

Initial spectral coefficient. 

Default: 1.0 

M : int, optional 

Number of iterates to include in the nonmonotonic line search. 

Default: 10 

line_search : {'cruz', 'cheng'} 

Type of line search to employ. 'cruz' is the original one defined in 

[Martinez & Raydan. Math. Comp. 75, 1429 (2006)], 'cheng' is 

a modified search defined in [Cheng & Li. IMA J. Numer. Anal. 29, 814 (2009)]. 

Default: 'cruz' 

 

References 

---------- 

.. [1] "Spectral residual method without gradient information for solving 

large-scale nonlinear systems of equations." W. La Cruz, 

J.M. Martinez, M. Raydan. Math. Comp. **75**, 1429 (2006). 

.. [2] W. La Cruz, Opt. Meth. Software, 29, 24 (2014). 

.. [3] W. Cheng, D.-H. Li. IMA J. Numer. Anal. **29**, 814 (2009). 

 

""" 

_check_unknown_options(unknown_options) 

 

if line_search not in ('cheng', 'cruz'): 

raise ValueError("Invalid value %r for 'line_search'" % (line_search,)) 

 

nexp = 2 

 

if eta_strategy is None: 

# Different choice from [1], as their eta is not invariant 

# vs. scaling of F. 

def eta_strategy(k, x, F): 

# Obtain squared 2-norm of the initial residual from the outer scope 

return f_0 / (1 + k)**2 

 

if fnorm is None: 

def fnorm(F): 

# Obtain squared 2-norm of the current residual from the outer scope 

return f_k**(1.0/nexp) 

 

def fmerit(F): 

return np.linalg.norm(F)**nexp 

 

nfev = [0] 

f, x_k, x_shape, f_k, F_k, is_complex = _wrap_func(func, x0, fmerit, nfev, maxfev, args) 

 

k = 0 

f_0 = f_k 

sigma_k = sigma_0 

 

F_0_norm = fnorm(F_k) 

 

# For the 'cruz' line search 

prev_fs = collections.deque([f_k], M) 

 

# For the 'cheng' line search 

Q = 1.0 

C = f_0 

 

converged = False 

message = "too many function evaluations required" 

 

while True: 

F_k_norm = fnorm(F_k) 

 

if disp: 

print("iter %d: ||F|| = %g, sigma = %g" % (k, F_k_norm, sigma_k)) 

 

if callback is not None: 

callback(x_k, F_k) 

 

if F_k_norm < ftol * F_0_norm + fatol: 

# Converged! 

message = "successful convergence" 

converged = True 

break 

 

# Control spectral parameter, from [2] 

if abs(sigma_k) > 1/sigma_eps: 

sigma_k = 1/sigma_eps * np.sign(sigma_k) 

elif abs(sigma_k) < sigma_eps: 

sigma_k = sigma_eps 

 

# Line search direction 

d = -sigma_k * F_k 

 

# Nonmonotone line search 

eta = eta_strategy(k, x_k, F_k) 

try: 

if line_search == 'cruz': 

alpha, xp, fp, Fp = _nonmonotone_line_search_cruz(f, x_k, d, prev_fs, eta=eta) 

elif line_search == 'cheng': 

alpha, xp, fp, Fp, C, Q = _nonmonotone_line_search_cheng(f, x_k, d, f_k, C, Q, eta=eta) 

except _NoConvergence: 

break 

 

# Update spectral parameter 

s_k = xp - x_k 

y_k = Fp - F_k 

sigma_k = np.vdot(s_k, s_k) / np.vdot(s_k, y_k) 

 

# Take step 

x_k = xp 

F_k = Fp 

f_k = fp 

 

# Store function value 

if line_search == 'cruz': 

prev_fs.append(fp) 

 

k += 1 

 

x = _wrap_result(x_k, is_complex, shape=x_shape) 

F = _wrap_result(F_k, is_complex) 

 

result = OptimizeResult(x=x, success=converged, 

message=message, 

fun=F, nfev=nfev[0], nit=k) 

 

return result 

 

 

def _wrap_func(func, x0, fmerit, nfev_list, maxfev, args=()): 

""" 

Wrap a function and an initial value so that (i) complex values 

are wrapped to reals, and (ii) value for a merit function 

fmerit(x, f) is computed at the same time, (iii) iteration count 

is maintained and an exception is raised if it is exceeded. 

 

Parameters 

---------- 

func : callable 

Function to wrap 

x0 : ndarray 

Initial value 

fmerit : callable 

Merit function fmerit(f) for computing merit value from residual. 

nfev_list : list 

List to store number of evaluations in. Should be [0] in the beginning. 

maxfev : int 

Maximum number of evaluations before _NoConvergence is raised. 

args : tuple 

Extra arguments to func 

 

Returns 

------- 

wrap_func : callable 

Wrapped function, to be called as 

``F, fp = wrap_func(x0)`` 

x0_wrap : ndarray of float 

Wrapped initial value; raveled to 1D and complex 

values mapped to reals. 

x0_shape : tuple 

Shape of the initial value array 

f : float 

Merit function at F 

F : ndarray of float 

Residual at x0_wrap 

is_complex : bool 

Whether complex values were mapped to reals 

 

""" 

x0 = np.asarray(x0) 

x0_shape = x0.shape 

F = np.asarray(func(x0, *args)).ravel() 

is_complex = np.iscomplexobj(x0) or np.iscomplexobj(F) 

x0 = x0.ravel() 

 

nfev_list[0] = 1 

 

if is_complex: 

def wrap_func(x): 

if nfev_list[0] >= maxfev: 

raise _NoConvergence() 

nfev_list[0] += 1 

z = _real2complex(x).reshape(x0_shape) 

v = np.asarray(func(z, *args)).ravel() 

F = _complex2real(v) 

f = fmerit(F) 

return f, F 

 

x0 = _complex2real(x0) 

F = _complex2real(F) 

else: 

def wrap_func(x): 

if nfev_list[0] >= maxfev: 

raise _NoConvergence() 

nfev_list[0] += 1 

x = x.reshape(x0_shape) 

F = np.asarray(func(x, *args)).ravel() 

f = fmerit(F) 

return f, F 

 

return wrap_func, x0, x0_shape, fmerit(F), F, is_complex 

 

 

def _wrap_result(result, is_complex, shape=None): 

""" 

Convert from real to complex and reshape result arrays. 

""" 

if is_complex: 

z = _real2complex(result) 

else: 

z = result 

if shape is not None: 

z = z.reshape(shape) 

return z 

 

 

def _real2complex(x): 

return np.ascontiguousarray(x, dtype=float).view(np.complex128) 

 

 

def _complex2real(z): 

return np.ascontiguousarray(z, dtype=complex).view(np.float64)