Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/optimize/_nonlin.py: 22%

630 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1# Copyright (C) 2009, Pauli Virtanen <pav@iki.fi> 

2# Distributed under the same license as SciPy. 

3 

4import sys 

5import numpy as np 

6from scipy.linalg import norm, solve, inv, qr, svd, LinAlgError 

7from numpy import asarray, dot, vdot 

8import scipy.sparse.linalg 

9import scipy.sparse 

10from scipy.linalg import get_blas_funcs 

11import inspect 

12from scipy._lib._util import getfullargspec_no_self as _getfullargspec 

13from ._linesearch import scalar_search_wolfe1, scalar_search_armijo 

14 

15 

16__all__ = [ 

17 'broyden1', 'broyden2', 'anderson', 'linearmixing', 

18 'diagbroyden', 'excitingmixing', 'newton_krylov', 

19 'BroydenFirst', 'KrylovJacobian', 'InverseJacobian'] 

20 

21#------------------------------------------------------------------------------ 

22# Utility functions 

23#------------------------------------------------------------------------------ 

24 

25 

26class NoConvergence(Exception): 

27 pass 

28 

29 

30def maxnorm(x): 

31 return np.absolute(x).max() 

32 

33 

34def _as_inexact(x): 

35 """Return `x` as an array, of either floats or complex floats""" 

36 x = asarray(x) 

37 if not np.issubdtype(x.dtype, np.inexact): 

38 return asarray(x, dtype=np.float_) 

39 return x 

40 

41 

42def _array_like(x, x0): 

43 """Return ndarray `x` as same array subclass and shape as `x0`""" 

44 x = np.reshape(x, np.shape(x0)) 

45 wrap = getattr(x0, '__array_wrap__', x.__array_wrap__) 

46 return wrap(x) 

47 

48 

49def _safe_norm(v): 

50 if not np.isfinite(v).all(): 

51 return np.array(np.inf) 

52 return norm(v) 

53 

54#------------------------------------------------------------------------------ 

55# Generic nonlinear solver machinery 

56#------------------------------------------------------------------------------ 

57 

58 

59_doc_parts = dict( 

60 params_basic=""" 

61 F : function(x) -> f 

62 Function whose root to find; should take and return an array-like 

63 object. 

64 xin : array_like 

65 Initial guess for the solution 

66 """.strip(), 

67 params_extra=""" 

68 iter : int, optional 

69 Number of iterations to make. If omitted (default), make as many 

70 as required to meet tolerances. 

71 verbose : bool, optional 

72 Print status to stdout on every iteration. 

73 maxiter : int, optional 

74 Maximum number of iterations to make. If more are needed to 

75 meet convergence, `NoConvergence` is raised. 

76 f_tol : float, optional 

77 Absolute tolerance (in max-norm) for the residual. 

78 If omitted, default is 6e-6. 

79 f_rtol : float, optional 

80 Relative tolerance for the residual. If omitted, not used. 

81 x_tol : float, optional 

82 Absolute minimum step size, as determined from the Jacobian 

83 approximation. If the step size is smaller than this, optimization 

84 is terminated as successful. If omitted, not used. 

85 x_rtol : float, optional 

86 Relative minimum step size. If omitted, not used. 

87 tol_norm : function(vector) -> scalar, optional 

88 Norm to use in convergence check. Default is the maximum norm. 

89 line_search : {None, 'armijo' (default), 'wolfe'}, optional 

90 Which type of a line search to use to determine the step size in the 

91 direction given by the Jacobian approximation. Defaults to 'armijo'. 

92 callback : function, optional 

93 Optional callback function. It is called on every iteration as 

94 ``callback(x, f)`` where `x` is the current solution and `f` 

95 the corresponding residual. 

96 

97 Returns 

98 ------- 

99 sol : ndarray 

100 An array (of similar array type as `x0`) containing the final solution. 

101 

102 Raises 

103 ------ 

104 NoConvergence 

105 When a solution was not found. 

106 

107 """.strip() 

108) 

109 

110 

111def _set_doc(obj): 

112 if obj.__doc__: 

113 obj.__doc__ = obj.__doc__ % _doc_parts 

114 

115 

116def nonlin_solve(F, x0, jacobian='krylov', iter=None, verbose=False, 

117 maxiter=None, f_tol=None, f_rtol=None, x_tol=None, x_rtol=None, 

118 tol_norm=None, line_search='armijo', callback=None, 

119 full_output=False, raise_exception=True): 

120 """ 

121 Find a root of a function, in a way suitable for large-scale problems. 

122 

123 Parameters 

124 ---------- 

125 %(params_basic)s 

126 jacobian : Jacobian 

127 A Jacobian approximation: `Jacobian` object or something that 

128 `asjacobian` can transform to one. Alternatively, a string specifying 

129 which of the builtin Jacobian approximations to use: 

130 

131 krylov, broyden1, broyden2, anderson 

132 diagbroyden, linearmixing, excitingmixing 

133 

134 %(params_extra)s 

135 full_output : bool 

136 If true, returns a dictionary `info` containing convergence 

137 information. 

138 raise_exception : bool 

139 If True, a `NoConvergence` exception is raise if no solution is found. 

140 

141 See Also 

142 -------- 

143 asjacobian, Jacobian 

144 

145 Notes 

146 ----- 

147 This algorithm implements the inexact Newton method, with 

148 backtracking or full line searches. Several Jacobian 

149 approximations are available, including Krylov and Quasi-Newton 

150 methods. 

151 

152 References 

153 ---------- 

154 .. [KIM] C. T. Kelley, \"Iterative Methods for Linear and Nonlinear 

155 Equations\". Society for Industrial and Applied Mathematics. (1995) 

156 https://archive.siam.org/books/kelley/fr16/ 

157 

158 """ 

159 # Can't use default parameters because it's being explicitly passed as None 

160 # from the calling function, so we need to set it here. 

161 tol_norm = maxnorm if tol_norm is None else tol_norm 

162 condition = TerminationCondition(f_tol=f_tol, f_rtol=f_rtol, 

163 x_tol=x_tol, x_rtol=x_rtol, 

164 iter=iter, norm=tol_norm) 

165 

166 x0 = _as_inexact(x0) 

167 func = lambda z: _as_inexact(F(_array_like(z, x0))).flatten() 

168 x = x0.flatten() 

169 

170 dx = np.full_like(x, np.inf) 

171 Fx = func(x) 

172 Fx_norm = norm(Fx) 

173 

174 jacobian = asjacobian(jacobian) 

175 jacobian.setup(x.copy(), Fx, func) 

176 

177 if maxiter is None: 

178 if iter is not None: 

179 maxiter = iter + 1 

180 else: 

181 maxiter = 100*(x.size+1) 

182 

183 if line_search is True: 

184 line_search = 'armijo' 

185 elif line_search is False: 

186 line_search = None 

187 

188 if line_search not in (None, 'armijo', 'wolfe'): 

189 raise ValueError("Invalid line search") 

190 

191 # Solver tolerance selection 

192 gamma = 0.9 

193 eta_max = 0.9999 

194 eta_treshold = 0.1 

195 eta = 1e-3 

196 

197 for n in range(maxiter): 

198 status = condition.check(Fx, x, dx) 

199 if status: 

200 break 

201 

202 # The tolerance, as computed for scipy.sparse.linalg.* routines 

203 tol = min(eta, eta*Fx_norm) 

204 dx = -jacobian.solve(Fx, tol=tol) 

205 

206 if norm(dx) == 0: 

207 raise ValueError("Jacobian inversion yielded zero vector. " 

208 "This indicates a bug in the Jacobian " 

209 "approximation.") 

210 

211 # Line search, or Newton step 

212 if line_search: 

213 s, x, Fx, Fx_norm_new = _nonlin_line_search(func, x, Fx, dx, 

214 line_search) 

215 else: 

216 s = 1.0 

217 x = x + dx 

218 Fx = func(x) 

219 Fx_norm_new = norm(Fx) 

220 

221 jacobian.update(x.copy(), Fx) 

222 

223 if callback: 

224 callback(x, Fx) 

225 

226 # Adjust forcing parameters for inexact methods 

227 eta_A = gamma * Fx_norm_new**2 / Fx_norm**2 

228 if gamma * eta**2 < eta_treshold: 

229 eta = min(eta_max, eta_A) 

230 else: 

231 eta = min(eta_max, max(eta_A, gamma*eta**2)) 

232 

233 Fx_norm = Fx_norm_new 

234 

235 # Print status 

236 if verbose: 

237 sys.stdout.write("%d: |F(x)| = %g; step %g\n" % ( 

238 n, tol_norm(Fx), s)) 

239 sys.stdout.flush() 

240 else: 

241 if raise_exception: 

242 raise NoConvergence(_array_like(x, x0)) 

243 else: 

244 status = 2 

245 

246 if full_output: 

247 info = {'nit': condition.iteration, 

248 'fun': Fx, 

249 'status': status, 

250 'success': status == 1, 

251 'message': {1: 'A solution was found at the specified ' 

252 'tolerance.', 

253 2: 'The maximum number of iterations allowed ' 

254 'has been reached.' 

255 }[status] 

256 } 

257 return _array_like(x, x0), info 

258 else: 

259 return _array_like(x, x0) 

260 

261 

262_set_doc(nonlin_solve) 

263 

264 

265def _nonlin_line_search(func, x, Fx, dx, search_type='armijo', rdiff=1e-8, 

266 smin=1e-2): 

267 tmp_s = [0] 

268 tmp_Fx = [Fx] 

269 tmp_phi = [norm(Fx)**2] 

270 s_norm = norm(x) / norm(dx) 

271 

272 def phi(s, store=True): 

273 if s == tmp_s[0]: 

274 return tmp_phi[0] 

275 xt = x + s*dx 

276 v = func(xt) 

277 p = _safe_norm(v)**2 

278 if store: 

279 tmp_s[0] = s 

280 tmp_phi[0] = p 

281 tmp_Fx[0] = v 

282 return p 

283 

284 def derphi(s): 

285 ds = (abs(s) + s_norm + 1) * rdiff 

286 return (phi(s+ds, store=False) - phi(s)) / ds 

287 

288 if search_type == 'wolfe': 

289 s, phi1, phi0 = scalar_search_wolfe1(phi, derphi, tmp_phi[0], 

290 xtol=1e-2, amin=smin) 

291 elif search_type == 'armijo': 

292 s, phi1 = scalar_search_armijo(phi, tmp_phi[0], -tmp_phi[0], 

293 amin=smin) 

294 

295 if s is None: 

296 # XXX: No suitable step length found. Take the full Newton step, 

297 # and hope for the best. 

298 s = 1.0 

299 

300 x = x + s*dx 

301 if s == tmp_s[0]: 

302 Fx = tmp_Fx[0] 

303 else: 

304 Fx = func(x) 

305 Fx_norm = norm(Fx) 

306 

307 return s, x, Fx, Fx_norm 

308 

309 

310class TerminationCondition: 

311 """ 

312 Termination condition for an iteration. It is terminated if 

313 

314 - |F| < f_rtol*|F_0|, AND 

315 - |F| < f_tol 

316 

317 AND 

318 

319 - |dx| < x_rtol*|x|, AND 

320 - |dx| < x_tol 

321 

322 """ 

323 def __init__(self, f_tol=None, f_rtol=None, x_tol=None, x_rtol=None, 

324 iter=None, norm=maxnorm): 

325 

326 if f_tol is None: 

327 f_tol = np.finfo(np.float_).eps ** (1./3) 

328 if f_rtol is None: 

329 f_rtol = np.inf 

330 if x_tol is None: 

331 x_tol = np.inf 

332 if x_rtol is None: 

333 x_rtol = np.inf 

334 

335 self.x_tol = x_tol 

336 self.x_rtol = x_rtol 

337 self.f_tol = f_tol 

338 self.f_rtol = f_rtol 

339 

340 self.norm = norm 

341 

342 self.iter = iter 

343 

344 self.f0_norm = None 

345 self.iteration = 0 

346 

347 def check(self, f, x, dx): 

348 self.iteration += 1 

349 f_norm = self.norm(f) 

350 x_norm = self.norm(x) 

351 dx_norm = self.norm(dx) 

352 

353 if self.f0_norm is None: 

354 self.f0_norm = f_norm 

355 

356 if f_norm == 0: 

357 return 1 

358 

359 if self.iter is not None: 

360 # backwards compatibility with SciPy 0.6.0 

361 return 2 * (self.iteration > self.iter) 

362 

363 # NB: condition must succeed for rtol=inf even if norm == 0 

364 return int((f_norm <= self.f_tol 

365 and f_norm/self.f_rtol <= self.f0_norm) 

366 and (dx_norm <= self.x_tol 

367 and dx_norm/self.x_rtol <= x_norm)) 

368 

369 

370#------------------------------------------------------------------------------ 

371# Generic Jacobian approximation 

372#------------------------------------------------------------------------------ 

373 

374class Jacobian: 

375 """ 

376 Common interface for Jacobians or Jacobian approximations. 

377 

378 The optional methods come useful when implementing trust region 

379 etc., algorithms that often require evaluating transposes of the 

380 Jacobian. 

381 

382 Methods 

383 ------- 

384 solve 

385 Returns J^-1 * v 

386 update 

387 Updates Jacobian to point `x` (where the function has residual `Fx`) 

388 

389 matvec : optional 

390 Returns J * v 

391 rmatvec : optional 

392 Returns A^H * v 

393 rsolve : optional 

394 Returns A^-H * v 

395 matmat : optional 

396 Returns A * V, where V is a dense matrix with dimensions (N,K). 

397 todense : optional 

398 Form the dense Jacobian matrix. Necessary for dense trust region 

399 algorithms, and useful for testing. 

400 

401 Attributes 

402 ---------- 

403 shape 

404 Matrix dimensions (M, N) 

405 dtype 

406 Data type of the matrix. 

407 func : callable, optional 

408 Function the Jacobian corresponds to 

409 

410 """ 

411 

412 def __init__(self, **kw): 

413 names = ["solve", "update", "matvec", "rmatvec", "rsolve", 

414 "matmat", "todense", "shape", "dtype"] 

415 for name, value in kw.items(): 

416 if name not in names: 

417 raise ValueError("Unknown keyword argument %s" % name) 

418 if value is not None: 

419 setattr(self, name, kw[name]) 

420 

421 if hasattr(self, 'todense'): 

422 self.__array__ = lambda: self.todense() 

423 

424 def aspreconditioner(self): 

425 return InverseJacobian(self) 

426 

427 def solve(self, v, tol=0): 

428 raise NotImplementedError 

429 

430 def update(self, x, F): 

431 pass 

432 

433 def setup(self, x, F, func): 

434 self.func = func 

435 self.shape = (F.size, x.size) 

436 self.dtype = F.dtype 

437 if self.__class__.setup is Jacobian.setup: 

438 # Call on the first point unless overridden 

439 self.update(x, F) 

440 

441 

442class InverseJacobian: 

443 def __init__(self, jacobian): 

444 self.jacobian = jacobian 

445 self.matvec = jacobian.solve 

446 self.update = jacobian.update 

447 if hasattr(jacobian, 'setup'): 

448 self.setup = jacobian.setup 

449 if hasattr(jacobian, 'rsolve'): 

450 self.rmatvec = jacobian.rsolve 

451 

452 @property 

453 def shape(self): 

454 return self.jacobian.shape 

455 

456 @property 

457 def dtype(self): 

458 return self.jacobian.dtype 

459 

460 

461def asjacobian(J): 

462 """ 

463 Convert given object to one suitable for use as a Jacobian. 

464 """ 

465 spsolve = scipy.sparse.linalg.spsolve 

466 if isinstance(J, Jacobian): 

467 return J 

468 elif inspect.isclass(J) and issubclass(J, Jacobian): 

469 return J() 

470 elif isinstance(J, np.ndarray): 

471 if J.ndim > 2: 

472 raise ValueError('array must have rank <= 2') 

473 J = np.atleast_2d(np.asarray(J)) 

474 if J.shape[0] != J.shape[1]: 

475 raise ValueError('array must be square') 

476 

477 return Jacobian(matvec=lambda v: dot(J, v), 

478 rmatvec=lambda v: dot(J.conj().T, v), 

479 solve=lambda v: solve(J, v), 

480 rsolve=lambda v: solve(J.conj().T, v), 

481 dtype=J.dtype, shape=J.shape) 

482 elif scipy.sparse.isspmatrix(J): 

483 if J.shape[0] != J.shape[1]: 

484 raise ValueError('matrix must be square') 

485 return Jacobian(matvec=lambda v: J*v, 

486 rmatvec=lambda v: J.conj().T * v, 

487 solve=lambda v: spsolve(J, v), 

488 rsolve=lambda v: spsolve(J.conj().T, v), 

489 dtype=J.dtype, shape=J.shape) 

490 elif hasattr(J, 'shape') and hasattr(J, 'dtype') and hasattr(J, 'solve'): 

491 return Jacobian(matvec=getattr(J, 'matvec'), 

492 rmatvec=getattr(J, 'rmatvec'), 

493 solve=J.solve, 

494 rsolve=getattr(J, 'rsolve'), 

495 update=getattr(J, 'update'), 

496 setup=getattr(J, 'setup'), 

497 dtype=J.dtype, 

498 shape=J.shape) 

499 elif callable(J): 

500 # Assume it's a function J(x) that returns the Jacobian 

501 class Jac(Jacobian): 

502 def update(self, x, F): 

503 self.x = x 

504 

505 def solve(self, v, tol=0): 

506 m = J(self.x) 

507 if isinstance(m, np.ndarray): 

508 return solve(m, v) 

509 elif scipy.sparse.isspmatrix(m): 

510 return spsolve(m, v) 

511 else: 

512 raise ValueError("Unknown matrix type") 

513 

514 def matvec(self, v): 

515 m = J(self.x) 

516 if isinstance(m, np.ndarray): 

517 return dot(m, v) 

518 elif scipy.sparse.isspmatrix(m): 

519 return m*v 

520 else: 

521 raise ValueError("Unknown matrix type") 

522 

523 def rsolve(self, v, tol=0): 

524 m = J(self.x) 

525 if isinstance(m, np.ndarray): 

526 return solve(m.conj().T, v) 

527 elif scipy.sparse.isspmatrix(m): 

528 return spsolve(m.conj().T, v) 

529 else: 

530 raise ValueError("Unknown matrix type") 

531 

532 def rmatvec(self, v): 

533 m = J(self.x) 

534 if isinstance(m, np.ndarray): 

535 return dot(m.conj().T, v) 

536 elif scipy.sparse.isspmatrix(m): 

537 return m.conj().T * v 

538 else: 

539 raise ValueError("Unknown matrix type") 

540 return Jac() 

541 elif isinstance(J, str): 

542 return dict(broyden1=BroydenFirst, 

543 broyden2=BroydenSecond, 

544 anderson=Anderson, 

545 diagbroyden=DiagBroyden, 

546 linearmixing=LinearMixing, 

547 excitingmixing=ExcitingMixing, 

548 krylov=KrylovJacobian)[J]() 

549 else: 

550 raise TypeError('Cannot convert object to a Jacobian') 

551 

552 

553#------------------------------------------------------------------------------ 

554# Broyden 

555#------------------------------------------------------------------------------ 

556 

557class GenericBroyden(Jacobian): 

558 def setup(self, x0, f0, func): 

559 Jacobian.setup(self, x0, f0, func) 

560 self.last_f = f0 

561 self.last_x = x0 

562 

563 if hasattr(self, 'alpha') and self.alpha is None: 

564 # Autoscale the initial Jacobian parameter 

565 # unless we have already guessed the solution. 

566 normf0 = norm(f0) 

567 if normf0: 

568 self.alpha = 0.5*max(norm(x0), 1) / normf0 

569 else: 

570 self.alpha = 1.0 

571 

572 def _update(self, x, f, dx, df, dx_norm, df_norm): 

573 raise NotImplementedError 

574 

575 def update(self, x, f): 

576 df = f - self.last_f 

577 dx = x - self.last_x 

578 self._update(x, f, dx, df, norm(dx), norm(df)) 

579 self.last_f = f 

580 self.last_x = x 

581 

582 

583class LowRankMatrix: 

584 r""" 

585 A matrix represented as 

586 

587 .. math:: \alpha I + \sum_{n=0}^{n=M} c_n d_n^\dagger 

588 

589 However, if the rank of the matrix reaches the dimension of the vectors, 

590 full matrix representation will be used thereon. 

591 

592 """ 

593 

594 def __init__(self, alpha, n, dtype): 

595 self.alpha = alpha 

596 self.cs = [] 

597 self.ds = [] 

598 self.n = n 

599 self.dtype = dtype 

600 self.collapsed = None 

601 

602 @staticmethod 

603 def _matvec(v, alpha, cs, ds): 

604 axpy, scal, dotc = get_blas_funcs(['axpy', 'scal', 'dotc'], 

605 cs[:1] + [v]) 

606 w = alpha * v 

607 for c, d in zip(cs, ds): 

608 a = dotc(d, v) 

609 w = axpy(c, w, w.size, a) 

610 return w 

611 

612 @staticmethod 

613 def _solve(v, alpha, cs, ds): 

614 """Evaluate w = M^-1 v""" 

615 if len(cs) == 0: 

616 return v/alpha 

617 

618 # (B + C D^H)^-1 = B^-1 - B^-1 C (I + D^H B^-1 C)^-1 D^H B^-1 

619 

620 axpy, dotc = get_blas_funcs(['axpy', 'dotc'], cs[:1] + [v]) 

621 

622 c0 = cs[0] 

623 A = alpha * np.identity(len(cs), dtype=c0.dtype) 

624 for i, d in enumerate(ds): 

625 for j, c in enumerate(cs): 

626 A[i,j] += dotc(d, c) 

627 

628 q = np.zeros(len(cs), dtype=c0.dtype) 

629 for j, d in enumerate(ds): 

630 q[j] = dotc(d, v) 

631 q /= alpha 

632 q = solve(A, q) 

633 

634 w = v/alpha 

635 for c, qc in zip(cs, q): 

636 w = axpy(c, w, w.size, -qc) 

637 

638 return w 

639 

640 def matvec(self, v): 

641 """Evaluate w = M v""" 

642 if self.collapsed is not None: 

643 return np.dot(self.collapsed, v) 

644 return LowRankMatrix._matvec(v, self.alpha, self.cs, self.ds) 

645 

646 def rmatvec(self, v): 

647 """Evaluate w = M^H v""" 

648 if self.collapsed is not None: 

649 return np.dot(self.collapsed.T.conj(), v) 

650 return LowRankMatrix._matvec(v, np.conj(self.alpha), self.ds, self.cs) 

651 

652 def solve(self, v, tol=0): 

653 """Evaluate w = M^-1 v""" 

654 if self.collapsed is not None: 

655 return solve(self.collapsed, v) 

656 return LowRankMatrix._solve(v, self.alpha, self.cs, self.ds) 

657 

658 def rsolve(self, v, tol=0): 

659 """Evaluate w = M^-H v""" 

660 if self.collapsed is not None: 

661 return solve(self.collapsed.T.conj(), v) 

662 return LowRankMatrix._solve(v, np.conj(self.alpha), self.ds, self.cs) 

663 

664 def append(self, c, d): 

665 if self.collapsed is not None: 

666 self.collapsed += c[:,None] * d[None,:].conj() 

667 return 

668 

669 self.cs.append(c) 

670 self.ds.append(d) 

671 

672 if len(self.cs) > c.size: 

673 self.collapse() 

674 

675 def __array__(self): 

676 if self.collapsed is not None: 

677 return self.collapsed 

678 

679 Gm = self.alpha*np.identity(self.n, dtype=self.dtype) 

680 for c, d in zip(self.cs, self.ds): 

681 Gm += c[:,None]*d[None,:].conj() 

682 return Gm 

683 

684 def collapse(self): 

685 """Collapse the low-rank matrix to a full-rank one.""" 

686 self.collapsed = np.array(self) 

687 self.cs = None 

688 self.ds = None 

689 self.alpha = None 

690 

691 def restart_reduce(self, rank): 

692 """ 

693 Reduce the rank of the matrix by dropping all vectors. 

694 """ 

695 if self.collapsed is not None: 

696 return 

697 assert rank > 0 

698 if len(self.cs) > rank: 

699 del self.cs[:] 

700 del self.ds[:] 

701 

702 def simple_reduce(self, rank): 

703 """ 

704 Reduce the rank of the matrix by dropping oldest vectors. 

705 """ 

706 if self.collapsed is not None: 

707 return 

708 assert rank > 0 

709 while len(self.cs) > rank: 

710 del self.cs[0] 

711 del self.ds[0] 

712 

713 def svd_reduce(self, max_rank, to_retain=None): 

714 """ 

715 Reduce the rank of the matrix by retaining some SVD components. 

716 

717 This corresponds to the \"Broyden Rank Reduction Inverse\" 

718 algorithm described in [1]_. 

719 

720 Note that the SVD decomposition can be done by solving only a 

721 problem whose size is the effective rank of this matrix, which 

722 is viable even for large problems. 

723 

724 Parameters 

725 ---------- 

726 max_rank : int 

727 Maximum rank of this matrix after reduction. 

728 to_retain : int, optional 

729 Number of SVD components to retain when reduction is done 

730 (ie. rank > max_rank). Default is ``max_rank - 2``. 

731 

732 References 

733 ---------- 

734 .. [1] B.A. van der Rotten, PhD thesis, 

735 \"A limited memory Broyden method to solve high-dimensional 

736 systems of nonlinear equations\". Mathematisch Instituut, 

737 Universiteit Leiden, The Netherlands (2003). 

738 

739 https://web.archive.org/web/20161022015821/http://www.math.leidenuniv.nl/scripties/Rotten.pdf 

740 

741 """ 

742 if self.collapsed is not None: 

743 return 

744 

745 p = max_rank 

746 if to_retain is not None: 

747 q = to_retain 

748 else: 

749 q = p - 2 

750 

751 if self.cs: 

752 p = min(p, len(self.cs[0])) 

753 q = max(0, min(q, p-1)) 

754 

755 m = len(self.cs) 

756 if m < p: 

757 # nothing to do 

758 return 

759 

760 C = np.array(self.cs).T 

761 D = np.array(self.ds).T 

762 

763 D, R = qr(D, mode='economic') 

764 C = dot(C, R.T.conj()) 

765 

766 U, S, WH = svd(C, full_matrices=False) 

767 

768 C = dot(C, inv(WH)) 

769 D = dot(D, WH.T.conj()) 

770 

771 for k in range(q): 

772 self.cs[k] = C[:,k].copy() 

773 self.ds[k] = D[:,k].copy() 

774 

775 del self.cs[q:] 

776 del self.ds[q:] 

777 

778 

779_doc_parts['broyden_params'] = """ 

780 alpha : float, optional 

781 Initial guess for the Jacobian is ``(-1/alpha)``. 

782 reduction_method : str or tuple, optional 

783 Method used in ensuring that the rank of the Broyden matrix 

784 stays low. Can either be a string giving the name of the method, 

785 or a tuple of the form ``(method, param1, param2, ...)`` 

786 that gives the name of the method and values for additional parameters. 

787 

788 Methods available: 

789 

790 - ``restart``: drop all matrix columns. Has no extra parameters. 

791 - ``simple``: drop oldest matrix column. Has no extra parameters. 

792 - ``svd``: keep only the most significant SVD components. 

793 Takes an extra parameter, ``to_retain``, which determines the 

794 number of SVD components to retain when rank reduction is done. 

795 Default is ``max_rank - 2``. 

796 

797 max_rank : int, optional 

798 Maximum rank for the Broyden matrix. 

799 Default is infinity (i.e., no rank reduction). 

800 """.strip() 

801 

802 

803class BroydenFirst(GenericBroyden): 

804 r""" 

805 Find a root of a function, using Broyden's first Jacobian approximation. 

806 

807 This method is also known as \"Broyden's good method\". 

808 

809 Parameters 

810 ---------- 

811 %(params_basic)s 

812 %(broyden_params)s 

813 %(params_extra)s 

814 

815 See Also 

816 -------- 

817 root : Interface to root finding algorithms for multivariate 

818 functions. See ``method='broyden1'`` in particular. 

819 

820 Notes 

821 ----- 

822 This algorithm implements the inverse Jacobian Quasi-Newton update 

823 

824 .. math:: H_+ = H + (dx - H df) dx^\dagger H / ( dx^\dagger H df) 

825 

826 which corresponds to Broyden's first Jacobian update 

827 

828 .. math:: J_+ = J + (df - J dx) dx^\dagger / dx^\dagger dx 

829 

830 

831 References 

832 ---------- 

833 .. [1] B.A. van der Rotten, PhD thesis, 

834 \"A limited memory Broyden method to solve high-dimensional 

835 systems of nonlinear equations\". Mathematisch Instituut, 

836 Universiteit Leiden, The Netherlands (2003). 

837 

838 https://web.archive.org/web/20161022015821/http://www.math.leidenuniv.nl/scripties/Rotten.pdf 

839 

840 Examples 

841 -------- 

842 The following functions define a system of nonlinear equations 

843 

844 >>> def fun(x): 

845 ... return [x[0] + 0.5 * (x[0] - x[1])**3 - 1.0, 

846 ... 0.5 * (x[1] - x[0])**3 + x[1]] 

847 

848 A solution can be obtained as follows. 

849 

850 >>> from scipy import optimize 

851 >>> sol = optimize.broyden1(fun, [0, 0]) 

852 >>> sol 

853 array([0.84116396, 0.15883641]) 

854 

855 """ 

856 

857 def __init__(self, alpha=None, reduction_method='restart', max_rank=None): 

858 GenericBroyden.__init__(self) 

859 self.alpha = alpha 

860 self.Gm = None 

861 

862 if max_rank is None: 

863 max_rank = np.inf 

864 self.max_rank = max_rank 

865 

866 if isinstance(reduction_method, str): 

867 reduce_params = () 

868 else: 

869 reduce_params = reduction_method[1:] 

870 reduction_method = reduction_method[0] 

871 reduce_params = (max_rank - 1,) + reduce_params 

872 

873 if reduction_method == 'svd': 

874 self._reduce = lambda: self.Gm.svd_reduce(*reduce_params) 

875 elif reduction_method == 'simple': 

876 self._reduce = lambda: self.Gm.simple_reduce(*reduce_params) 

877 elif reduction_method == 'restart': 

878 self._reduce = lambda: self.Gm.restart_reduce(*reduce_params) 

879 else: 

880 raise ValueError("Unknown rank reduction method '%s'" % 

881 reduction_method) 

882 

883 def setup(self, x, F, func): 

884 GenericBroyden.setup(self, x, F, func) 

885 self.Gm = LowRankMatrix(-self.alpha, self.shape[0], self.dtype) 

886 

887 def todense(self): 

888 return inv(self.Gm) 

889 

890 def solve(self, f, tol=0): 

891 r = self.Gm.matvec(f) 

892 if not np.isfinite(r).all(): 

893 # singular; reset the Jacobian approximation 

894 self.setup(self.last_x, self.last_f, self.func) 

895 return self.Gm.matvec(f) 

896 return r 

897 

898 def matvec(self, f): 

899 return self.Gm.solve(f) 

900 

901 def rsolve(self, f, tol=0): 

902 return self.Gm.rmatvec(f) 

903 

904 def rmatvec(self, f): 

905 return self.Gm.rsolve(f) 

906 

907 def _update(self, x, f, dx, df, dx_norm, df_norm): 

908 self._reduce() # reduce first to preserve secant condition 

909 

910 v = self.Gm.rmatvec(dx) 

911 c = dx - self.Gm.matvec(df) 

912 d = v / vdot(df, v) 

913 

914 self.Gm.append(c, d) 

915 

916 

917class BroydenSecond(BroydenFirst): 

918 """ 

919 Find a root of a function, using Broyden\'s second Jacobian approximation. 

920 

921 This method is also known as \"Broyden's bad method\". 

922 

923 Parameters 

924 ---------- 

925 %(params_basic)s 

926 %(broyden_params)s 

927 %(params_extra)s 

928 

929 See Also 

930 -------- 

931 root : Interface to root finding algorithms for multivariate 

932 functions. See ``method='broyden2'`` in particular. 

933 

934 Notes 

935 ----- 

936 This algorithm implements the inverse Jacobian Quasi-Newton update 

937 

938 .. math:: H_+ = H + (dx - H df) df^\\dagger / ( df^\\dagger df) 

939 

940 corresponding to Broyden's second method. 

941 

942 References 

943 ---------- 

944 .. [1] B.A. van der Rotten, PhD thesis, 

945 \"A limited memory Broyden method to solve high-dimensional 

946 systems of nonlinear equations\". Mathematisch Instituut, 

947 Universiteit Leiden, The Netherlands (2003). 

948 

949 https://web.archive.org/web/20161022015821/http://www.math.leidenuniv.nl/scripties/Rotten.pdf 

950 

951 Examples 

952 -------- 

953 The following functions define a system of nonlinear equations 

954 

955 >>> def fun(x): 

956 ... return [x[0] + 0.5 * (x[0] - x[1])**3 - 1.0, 

957 ... 0.5 * (x[1] - x[0])**3 + x[1]] 

958 

959 A solution can be obtained as follows. 

960 

961 >>> from scipy import optimize 

962 >>> sol = optimize.broyden2(fun, [0, 0]) 

963 >>> sol 

964 array([0.84116365, 0.15883529]) 

965 

966 """ 

967 

968 def _update(self, x, f, dx, df, dx_norm, df_norm): 

969 self._reduce() # reduce first to preserve secant condition 

970 

971 v = df 

972 c = dx - self.Gm.matvec(df) 

973 d = v / df_norm**2 

974 self.Gm.append(c, d) 

975 

976 

977#------------------------------------------------------------------------------ 

978# Broyden-like (restricted memory) 

979#------------------------------------------------------------------------------ 

980 

981class Anderson(GenericBroyden): 

982 """ 

983 Find a root of a function, using (extended) Anderson mixing. 

984 

985 The Jacobian is formed by for a 'best' solution in the space 

986 spanned by last `M` vectors. As a result, only a MxM matrix 

987 inversions and MxN multiplications are required. [Ey]_ 

988 

989 Parameters 

990 ---------- 

991 %(params_basic)s 

992 alpha : float, optional 

993 Initial guess for the Jacobian is (-1/alpha). 

994 M : float, optional 

995 Number of previous vectors to retain. Defaults to 5. 

996 w0 : float, optional 

997 Regularization parameter for numerical stability. 

998 Compared to unity, good values of the order of 0.01. 

999 %(params_extra)s 

1000 

1001 See Also 

1002 -------- 

1003 root : Interface to root finding algorithms for multivariate 

1004 functions. See ``method='anderson'`` in particular. 

1005 

1006 References 

1007 ---------- 

1008 .. [Ey] V. Eyert, J. Comp. Phys., 124, 271 (1996). 

1009 

1010 Examples 

1011 -------- 

1012 The following functions define a system of nonlinear equations 

1013 

1014 >>> def fun(x): 

1015 ... return [x[0] + 0.5 * (x[0] - x[1])**3 - 1.0, 

1016 ... 0.5 * (x[1] - x[0])**3 + x[1]] 

1017 

1018 A solution can be obtained as follows. 

1019 

1020 >>> from scipy import optimize 

1021 >>> sol = optimize.anderson(fun, [0, 0]) 

1022 >>> sol 

1023 array([0.84116588, 0.15883789]) 

1024 

1025 """ 

1026 

1027 # Note: 

1028 # 

1029 # Anderson method maintains a rank M approximation of the inverse Jacobian, 

1030 # 

1031 # J^-1 v ~ -v*alpha + (dX + alpha dF) A^-1 dF^H v 

1032 # A = W + dF^H dF 

1033 # W = w0^2 diag(dF^H dF) 

1034 # 

1035 # so that for w0 = 0 the secant condition applies for last M iterates, i.e., 

1036 # 

1037 # J^-1 df_j = dx_j 

1038 # 

1039 # for all j = 0 ... M-1. 

1040 # 

1041 # Moreover, (from Sherman-Morrison-Woodbury formula) 

1042 # 

1043 # J v ~ [ b I - b^2 C (I + b dF^H A^-1 C)^-1 dF^H ] v 

1044 # C = (dX + alpha dF) A^-1 

1045 # b = -1/alpha 

1046 # 

1047 # and after simplification 

1048 # 

1049 # J v ~ -v/alpha + (dX/alpha + dF) (dF^H dX - alpha W)^-1 dF^H v 

1050 # 

1051 

1052 def __init__(self, alpha=None, w0=0.01, M=5): 

1053 GenericBroyden.__init__(self) 

1054 self.alpha = alpha 

1055 self.M = M 

1056 self.dx = [] 

1057 self.df = [] 

1058 self.gamma = None 

1059 self.w0 = w0 

1060 

1061 def solve(self, f, tol=0): 

1062 dx = -self.alpha*f 

1063 

1064 n = len(self.dx) 

1065 if n == 0: 

1066 return dx 

1067 

1068 df_f = np.empty(n, dtype=f.dtype) 

1069 for k in range(n): 

1070 df_f[k] = vdot(self.df[k], f) 

1071 

1072 try: 

1073 gamma = solve(self.a, df_f) 

1074 except LinAlgError: 

1075 # singular; reset the Jacobian approximation 

1076 del self.dx[:] 

1077 del self.df[:] 

1078 return dx 

1079 

1080 for m in range(n): 

1081 dx += gamma[m]*(self.dx[m] + self.alpha*self.df[m]) 

1082 return dx 

1083 

1084 def matvec(self, f): 

1085 dx = -f/self.alpha 

1086 

1087 n = len(self.dx) 

1088 if n == 0: 

1089 return dx 

1090 

1091 df_f = np.empty(n, dtype=f.dtype) 

1092 for k in range(n): 

1093 df_f[k] = vdot(self.df[k], f) 

1094 

1095 b = np.empty((n, n), dtype=f.dtype) 

1096 for i in range(n): 

1097 for j in range(n): 

1098 b[i,j] = vdot(self.df[i], self.dx[j]) 

1099 if i == j and self.w0 != 0: 

1100 b[i,j] -= vdot(self.df[i], self.df[i])*self.w0**2*self.alpha 

1101 gamma = solve(b, df_f) 

1102 

1103 for m in range(n): 

1104 dx += gamma[m]*(self.df[m] + self.dx[m]/self.alpha) 

1105 return dx 

1106 

1107 def _update(self, x, f, dx, df, dx_norm, df_norm): 

1108 if self.M == 0: 

1109 return 

1110 

1111 self.dx.append(dx) 

1112 self.df.append(df) 

1113 

1114 while len(self.dx) > self.M: 

1115 self.dx.pop(0) 

1116 self.df.pop(0) 

1117 

1118 n = len(self.dx) 

1119 a = np.zeros((n, n), dtype=f.dtype) 

1120 

1121 for i in range(n): 

1122 for j in range(i, n): 

1123 if i == j: 

1124 wd = self.w0**2 

1125 else: 

1126 wd = 0 

1127 a[i,j] = (1+wd)*vdot(self.df[i], self.df[j]) 

1128 

1129 a += np.triu(a, 1).T.conj() 

1130 self.a = a 

1131 

1132#------------------------------------------------------------------------------ 

1133# Simple iterations 

1134#------------------------------------------------------------------------------ 

1135 

1136 

1137class DiagBroyden(GenericBroyden): 

1138 """ 

1139 Find a root of a function, using diagonal Broyden Jacobian approximation. 

1140 

1141 The Jacobian approximation is derived from previous iterations, by 

1142 retaining only the diagonal of Broyden matrices. 

1143 

1144 .. warning:: 

1145 

1146 This algorithm may be useful for specific problems, but whether 

1147 it will work may depend strongly on the problem. 

1148 

1149 Parameters 

1150 ---------- 

1151 %(params_basic)s 

1152 alpha : float, optional 

1153 Initial guess for the Jacobian is (-1/alpha). 

1154 %(params_extra)s 

1155 

1156 See Also 

1157 -------- 

1158 root : Interface to root finding algorithms for multivariate 

1159 functions. See ``method='diagbroyden'`` in particular. 

1160 

1161 Examples 

1162 -------- 

1163 The following functions define a system of nonlinear equations 

1164 

1165 >>> def fun(x): 

1166 ... return [x[0] + 0.5 * (x[0] - x[1])**3 - 1.0, 

1167 ... 0.5 * (x[1] - x[0])**3 + x[1]] 

1168 

1169 A solution can be obtained as follows. 

1170 

1171 >>> from scipy import optimize 

1172 >>> sol = optimize.diagbroyden(fun, [0, 0]) 

1173 >>> sol 

1174 array([0.84116403, 0.15883384]) 

1175 

1176 """ 

1177 

1178 def __init__(self, alpha=None): 

1179 GenericBroyden.__init__(self) 

1180 self.alpha = alpha 

1181 

1182 def setup(self, x, F, func): 

1183 GenericBroyden.setup(self, x, F, func) 

1184 self.d = np.full((self.shape[0],), 1 / self.alpha, dtype=self.dtype) 

1185 

1186 def solve(self, f, tol=0): 

1187 return -f / self.d 

1188 

1189 def matvec(self, f): 

1190 return -f * self.d 

1191 

1192 def rsolve(self, f, tol=0): 

1193 return -f / self.d.conj() 

1194 

1195 def rmatvec(self, f): 

1196 return -f * self.d.conj() 

1197 

1198 def todense(self): 

1199 return np.diag(-self.d) 

1200 

1201 def _update(self, x, f, dx, df, dx_norm, df_norm): 

1202 self.d -= (df + self.d*dx)*dx/dx_norm**2 

1203 

1204 

1205class LinearMixing(GenericBroyden): 

1206 """ 

1207 Find a root of a function, using a scalar Jacobian approximation. 

1208 

1209 .. warning:: 

1210 

1211 This algorithm may be useful for specific problems, but whether 

1212 it will work may depend strongly on the problem. 

1213 

1214 Parameters 

1215 ---------- 

1216 %(params_basic)s 

1217 alpha : float, optional 

1218 The Jacobian approximation is (-1/alpha). 

1219 %(params_extra)s 

1220 

1221 See Also 

1222 -------- 

1223 root : Interface to root finding algorithms for multivariate 

1224 functions. See ``method='linearmixing'`` in particular. 

1225 

1226 """ 

1227 

1228 def __init__(self, alpha=None): 

1229 GenericBroyden.__init__(self) 

1230 self.alpha = alpha 

1231 

1232 def solve(self, f, tol=0): 

1233 return -f*self.alpha 

1234 

1235 def matvec(self, f): 

1236 return -f/self.alpha 

1237 

1238 def rsolve(self, f, tol=0): 

1239 return -f*np.conj(self.alpha) 

1240 

1241 def rmatvec(self, f): 

1242 return -f/np.conj(self.alpha) 

1243 

1244 def todense(self): 

1245 return np.diag(np.full(self.shape[0], -1/self.alpha)) 

1246 

1247 def _update(self, x, f, dx, df, dx_norm, df_norm): 

1248 pass 

1249 

1250 

1251class ExcitingMixing(GenericBroyden): 

1252 """ 

1253 Find a root of a function, using a tuned diagonal Jacobian approximation. 

1254 

1255 The Jacobian matrix is diagonal and is tuned on each iteration. 

1256 

1257 .. warning:: 

1258 

1259 This algorithm may be useful for specific problems, but whether 

1260 it will work may depend strongly on the problem. 

1261 

1262 See Also 

1263 -------- 

1264 root : Interface to root finding algorithms for multivariate 

1265 functions. See ``method='excitingmixing'`` in particular. 

1266 

1267 Parameters 

1268 ---------- 

1269 %(params_basic)s 

1270 alpha : float, optional 

1271 Initial Jacobian approximation is (-1/alpha). 

1272 alphamax : float, optional 

1273 The entries of the diagonal Jacobian are kept in the range 

1274 ``[alpha, alphamax]``. 

1275 %(params_extra)s 

1276 """ 

1277 

1278 def __init__(self, alpha=None, alphamax=1.0): 

1279 GenericBroyden.__init__(self) 

1280 self.alpha = alpha 

1281 self.alphamax = alphamax 

1282 self.beta = None 

1283 

1284 def setup(self, x, F, func): 

1285 GenericBroyden.setup(self, x, F, func) 

1286 self.beta = np.full((self.shape[0],), self.alpha, dtype=self.dtype) 

1287 

1288 def solve(self, f, tol=0): 

1289 return -f*self.beta 

1290 

1291 def matvec(self, f): 

1292 return -f/self.beta 

1293 

1294 def rsolve(self, f, tol=0): 

1295 return -f*self.beta.conj() 

1296 

1297 def rmatvec(self, f): 

1298 return -f/self.beta.conj() 

1299 

1300 def todense(self): 

1301 return np.diag(-1/self.beta) 

1302 

1303 def _update(self, x, f, dx, df, dx_norm, df_norm): 

1304 incr = f*self.last_f > 0 

1305 self.beta[incr] += self.alpha 

1306 self.beta[~incr] = self.alpha 

1307 np.clip(self.beta, 0, self.alphamax, out=self.beta) 

1308 

1309 

1310#------------------------------------------------------------------------------ 

1311# Iterative/Krylov approximated Jacobians 

1312#------------------------------------------------------------------------------ 

1313 

1314class KrylovJacobian(Jacobian): 

1315 r""" 

1316 Find a root of a function, using Krylov approximation for inverse Jacobian. 

1317 

1318 This method is suitable for solving large-scale problems. 

1319 

1320 Parameters 

1321 ---------- 

1322 %(params_basic)s 

1323 rdiff : float, optional 

1324 Relative step size to use in numerical differentiation. 

1325 method : str or callable, optional 

1326 Krylov method to use to approximate the Jacobian. Can be a string, 

1327 or a function implementing the same interface as the iterative 

1328 solvers in `scipy.sparse.linalg`. If a string, needs to be one of: 

1329 ``'lgmres'``, ``'gmres'``, ``'bicgstab'``, ``'cgs'``, ``'minres'``, 

1330 ``'tfqmr'``. 

1331 

1332 The default is `scipy.sparse.linalg.lgmres`. 

1333 inner_maxiter : int, optional 

1334 Parameter to pass to the "inner" Krylov solver: maximum number of 

1335 iterations. Iteration will stop after maxiter steps even if the 

1336 specified tolerance has not been achieved. 

1337 inner_M : LinearOperator or InverseJacobian 

1338 Preconditioner for the inner Krylov iteration. 

1339 Note that you can use also inverse Jacobians as (adaptive) 

1340 preconditioners. For example, 

1341 

1342 >>> from scipy.optimize import BroydenFirst, KrylovJacobian 

1343 >>> from scipy.optimize import InverseJacobian 

1344 >>> jac = BroydenFirst() 

1345 >>> kjac = KrylovJacobian(inner_M=InverseJacobian(jac)) 

1346 

1347 If the preconditioner has a method named 'update', it will be called 

1348 as ``update(x, f)`` after each nonlinear step, with ``x`` giving 

1349 the current point, and ``f`` the current function value. 

1350 outer_k : int, optional 

1351 Size of the subspace kept across LGMRES nonlinear iterations. 

1352 See `scipy.sparse.linalg.lgmres` for details. 

1353 inner_kwargs : kwargs 

1354 Keyword parameters for the "inner" Krylov solver 

1355 (defined with `method`). Parameter names must start with 

1356 the `inner_` prefix which will be stripped before passing on 

1357 the inner method. See, e.g., `scipy.sparse.linalg.gmres` for details. 

1358 %(params_extra)s 

1359 

1360 See Also 

1361 -------- 

1362 root : Interface to root finding algorithms for multivariate 

1363 functions. See ``method='krylov'`` in particular. 

1364 scipy.sparse.linalg.gmres 

1365 scipy.sparse.linalg.lgmres 

1366 

1367 Notes 

1368 ----- 

1369 This function implements a Newton-Krylov solver. The basic idea is 

1370 to compute the inverse of the Jacobian with an iterative Krylov 

1371 method. These methods require only evaluating the Jacobian-vector 

1372 products, which are conveniently approximated by a finite difference: 

1373 

1374 .. math:: J v \approx (f(x + \omega*v/|v|) - f(x)) / \omega 

1375 

1376 Due to the use of iterative matrix inverses, these methods can 

1377 deal with large nonlinear problems. 

1378 

1379 SciPy's `scipy.sparse.linalg` module offers a selection of Krylov 

1380 solvers to choose from. The default here is `lgmres`, which is a 

1381 variant of restarted GMRES iteration that reuses some of the 

1382 information obtained in the previous Newton steps to invert 

1383 Jacobians in subsequent steps. 

1384 

1385 For a review on Newton-Krylov methods, see for example [1]_, 

1386 and for the LGMRES sparse inverse method, see [2]_. 

1387 

1388 References 

1389 ---------- 

1390 .. [1] C. T. Kelley, Solving Nonlinear Equations with Newton's Method, 

1391 SIAM, pp.57-83, 2003. 

1392 :doi:`10.1137/1.9780898718898.ch3` 

1393 .. [2] D.A. Knoll and D.E. Keyes, J. Comp. Phys. 193, 357 (2004). 

1394 :doi:`10.1016/j.jcp.2003.08.010` 

1395 .. [3] A.H. Baker and E.R. Jessup and T. Manteuffel, 

1396 SIAM J. Matrix Anal. Appl. 26, 962 (2005). 

1397 :doi:`10.1137/S0895479803422014` 

1398 

1399 Examples 

1400 -------- 

1401 The following functions define a system of nonlinear equations 

1402 

1403 >>> def fun(x): 

1404 ... return [x[0] + 0.5 * x[1] - 1.0, 

1405 ... 0.5 * (x[1] - x[0]) ** 2] 

1406 

1407 A solution can be obtained as follows. 

1408 

1409 >>> from scipy import optimize 

1410 >>> sol = optimize.newton_krylov(fun, [0, 0]) 

1411 >>> sol 

1412 array([0.66731771, 0.66536458]) 

1413 

1414 """ 

1415 

1416 def __init__(self, rdiff=None, method='lgmres', inner_maxiter=20, 

1417 inner_M=None, outer_k=10, **kw): 

1418 self.preconditioner = inner_M 

1419 self.rdiff = rdiff 

1420 # Note that this retrieves one of the named functions, or otherwise 

1421 # uses `method` as is (i.e., for a user-provided callable). 

1422 self.method = dict( 

1423 bicgstab=scipy.sparse.linalg.bicgstab, 

1424 gmres=scipy.sparse.linalg.gmres, 

1425 lgmres=scipy.sparse.linalg.lgmres, 

1426 cgs=scipy.sparse.linalg.cgs, 

1427 minres=scipy.sparse.linalg.minres, 

1428 tfqmr=scipy.sparse.linalg.tfqmr, 

1429 ).get(method, method) 

1430 

1431 self.method_kw = dict(maxiter=inner_maxiter, M=self.preconditioner) 

1432 

1433 if self.method is scipy.sparse.linalg.gmres: 

1434 # Replace GMRES's outer iteration with Newton steps 

1435 self.method_kw['restart'] = inner_maxiter 

1436 self.method_kw['maxiter'] = 1 

1437 self.method_kw.setdefault('atol', 0) 

1438 elif self.method in (scipy.sparse.linalg.gcrotmk, 

1439 scipy.sparse.linalg.bicgstab, 

1440 scipy.sparse.linalg.cgs): 

1441 self.method_kw.setdefault('atol', 0) 

1442 elif self.method is scipy.sparse.linalg.lgmres: 

1443 self.method_kw['outer_k'] = outer_k 

1444 # Replace LGMRES's outer iteration with Newton steps 

1445 self.method_kw['maxiter'] = 1 

1446 # Carry LGMRES's `outer_v` vectors across nonlinear iterations 

1447 self.method_kw.setdefault('outer_v', []) 

1448 self.method_kw.setdefault('prepend_outer_v', True) 

1449 # But don't carry the corresponding Jacobian*v products, in case 

1450 # the Jacobian changes a lot in the nonlinear step 

1451 # 

1452 # XXX: some trust-region inspired ideas might be more efficient... 

1453 # See e.g., Brown & Saad. But needs to be implemented separately 

1454 # since it's not an inexact Newton method. 

1455 self.method_kw.setdefault('store_outer_Av', False) 

1456 self.method_kw.setdefault('atol', 0) 

1457 

1458 for key, value in kw.items(): 

1459 if not key.startswith('inner_'): 

1460 raise ValueError("Unknown parameter %s" % key) 

1461 self.method_kw[key[6:]] = value 

1462 

1463 def _update_diff_step(self): 

1464 mx = abs(self.x0).max() 

1465 mf = abs(self.f0).max() 

1466 self.omega = self.rdiff * max(1, mx) / max(1, mf) 

1467 

1468 def matvec(self, v): 

1469 nv = norm(v) 

1470 if nv == 0: 

1471 return 0*v 

1472 sc = self.omega / nv 

1473 r = (self.func(self.x0 + sc*v) - self.f0) / sc 

1474 if not np.all(np.isfinite(r)) and np.all(np.isfinite(v)): 

1475 raise ValueError('Function returned non-finite results') 

1476 return r 

1477 

1478 def solve(self, rhs, tol=0): 

1479 if 'tol' in self.method_kw: 

1480 sol, info = self.method(self.op, rhs, **self.method_kw) 

1481 else: 

1482 sol, info = self.method(self.op, rhs, tol=tol, **self.method_kw) 

1483 return sol 

1484 

1485 def update(self, x, f): 

1486 self.x0 = x 

1487 self.f0 = f 

1488 self._update_diff_step() 

1489 

1490 # Update also the preconditioner, if possible 

1491 if self.preconditioner is not None: 

1492 if hasattr(self.preconditioner, 'update'): 

1493 self.preconditioner.update(x, f) 

1494 

1495 def setup(self, x, f, func): 

1496 Jacobian.setup(self, x, f, func) 

1497 self.x0 = x 

1498 self.f0 = f 

1499 self.op = scipy.sparse.linalg.aslinearoperator(self) 

1500 

1501 if self.rdiff is None: 

1502 self.rdiff = np.finfo(x.dtype).eps ** (1./2) 

1503 

1504 self._update_diff_step() 

1505 

1506 # Setup also the preconditioner, if possible 

1507 if self.preconditioner is not None: 

1508 if hasattr(self.preconditioner, 'setup'): 

1509 self.preconditioner.setup(x, f, func) 

1510 

1511 

1512#------------------------------------------------------------------------------ 

1513# Wrapper functions 

1514#------------------------------------------------------------------------------ 

1515 

1516def _nonlin_wrapper(name, jac): 

1517 """ 

1518 Construct a solver wrapper with given name and Jacobian approx. 

1519 

1520 It inspects the keyword arguments of ``jac.__init__``, and allows to 

1521 use the same arguments in the wrapper function, in addition to the 

1522 keyword arguments of `nonlin_solve` 

1523 

1524 """ 

1525 signature = _getfullargspec(jac.__init__) 

1526 args, varargs, varkw, defaults, kwonlyargs, kwdefaults, _ = signature 

1527 kwargs = list(zip(args[-len(defaults):], defaults)) 

1528 kw_str = ", ".join(["%s=%r" % (k, v) for k, v in kwargs]) 

1529 if kw_str: 

1530 kw_str = ", " + kw_str 

1531 kwkw_str = ", ".join(["%s=%s" % (k, k) for k, v in kwargs]) 

1532 if kwkw_str: 

1533 kwkw_str = kwkw_str + ", " 

1534 if kwonlyargs: 

1535 raise ValueError('Unexpected signature %s' % signature) 

1536 

1537 # Construct the wrapper function so that its keyword arguments 

1538 # are visible in pydoc.help etc. 

1539 wrapper = """ 

1540def %(name)s(F, xin, iter=None %(kw)s, verbose=False, maxiter=None, 

1541 f_tol=None, f_rtol=None, x_tol=None, x_rtol=None, 

1542 tol_norm=None, line_search='armijo', callback=None, **kw): 

1543 jac = %(jac)s(%(kwkw)s **kw) 

1544 return nonlin_solve(F, xin, jac, iter, verbose, maxiter, 

1545 f_tol, f_rtol, x_tol, x_rtol, tol_norm, line_search, 

1546 callback) 

1547""" 

1548 

1549 wrapper = wrapper % dict(name=name, kw=kw_str, jac=jac.__name__, 

1550 kwkw=kwkw_str) 

1551 ns = {} 

1552 ns.update(globals()) 

1553 exec(wrapper, ns) 

1554 func = ns[name] 

1555 func.__doc__ = jac.__doc__ 

1556 _set_doc(func) 

1557 return func 

1558 

1559 

1560broyden1 = _nonlin_wrapper('broyden1', BroydenFirst) 

1561broyden2 = _nonlin_wrapper('broyden2', BroydenSecond) 

1562anderson = _nonlin_wrapper('anderson', Anderson) 

1563linearmixing = _nonlin_wrapper('linearmixing', LinearMixing) 

1564diagbroyden = _nonlin_wrapper('diagbroyden', DiagBroyden) 

1565excitingmixing = _nonlin_wrapper('excitingmixing', ExcitingMixing) 

1566newton_krylov = _nonlin_wrapper('newton_krylov', KrylovJacobian)