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

278 statements  

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

1"""Routines for numerical differentiation.""" 

2import functools 

3import numpy as np 

4from numpy.linalg import norm 

5 

6from scipy.sparse.linalg import LinearOperator 

7from ..sparse import issparse, csc_matrix, csr_matrix, coo_matrix, find 

8from ._group_columns import group_dense, group_sparse 

9 

10 

11def _adjust_scheme_to_bounds(x0, h, num_steps, scheme, lb, ub): 

12 """Adjust final difference scheme to the presence of bounds. 

13 

14 Parameters 

15 ---------- 

16 x0 : ndarray, shape (n,) 

17 Point at which we wish to estimate derivative. 

18 h : ndarray, shape (n,) 

19 Desired absolute finite difference steps. 

20 num_steps : int 

21 Number of `h` steps in one direction required to implement finite 

22 difference scheme. For example, 2 means that we need to evaluate 

23 f(x0 + 2 * h) or f(x0 - 2 * h) 

24 scheme : {'1-sided', '2-sided'} 

25 Whether steps in one or both directions are required. In other 

26 words '1-sided' applies to forward and backward schemes, '2-sided' 

27 applies to center schemes. 

28 lb : ndarray, shape (n,) 

29 Lower bounds on independent variables. 

30 ub : ndarray, shape (n,) 

31 Upper bounds on independent variables. 

32 

33 Returns 

34 ------- 

35 h_adjusted : ndarray, shape (n,) 

36 Adjusted absolute step sizes. Step size decreases only if a sign flip 

37 or switching to one-sided scheme doesn't allow to take a full step. 

38 use_one_sided : ndarray of bool, shape (n,) 

39 Whether to switch to one-sided scheme. Informative only for 

40 ``scheme='2-sided'``. 

41 """ 

42 if scheme == '1-sided': 

43 use_one_sided = np.ones_like(h, dtype=bool) 

44 elif scheme == '2-sided': 

45 h = np.abs(h) 

46 use_one_sided = np.zeros_like(h, dtype=bool) 

47 else: 

48 raise ValueError("`scheme` must be '1-sided' or '2-sided'.") 

49 

50 if np.all((lb == -np.inf) & (ub == np.inf)): 

51 return h, use_one_sided 

52 

53 h_total = h * num_steps 

54 h_adjusted = h.copy() 

55 

56 lower_dist = x0 - lb 

57 upper_dist = ub - x0 

58 

59 if scheme == '1-sided': 

60 x = x0 + h_total 

61 violated = (x < lb) | (x > ub) 

62 fitting = np.abs(h_total) <= np.maximum(lower_dist, upper_dist) 

63 h_adjusted[violated & fitting] *= -1 

64 

65 forward = (upper_dist >= lower_dist) & ~fitting 

66 h_adjusted[forward] = upper_dist[forward] / num_steps 

67 backward = (upper_dist < lower_dist) & ~fitting 

68 h_adjusted[backward] = -lower_dist[backward] / num_steps 

69 elif scheme == '2-sided': 

70 central = (lower_dist >= h_total) & (upper_dist >= h_total) 

71 

72 forward = (upper_dist >= lower_dist) & ~central 

73 h_adjusted[forward] = np.minimum( 

74 h[forward], 0.5 * upper_dist[forward] / num_steps) 

75 use_one_sided[forward] = True 

76 

77 backward = (upper_dist < lower_dist) & ~central 

78 h_adjusted[backward] = -np.minimum( 

79 h[backward], 0.5 * lower_dist[backward] / num_steps) 

80 use_one_sided[backward] = True 

81 

82 min_dist = np.minimum(upper_dist, lower_dist) / num_steps 

83 adjusted_central = (~central & (np.abs(h_adjusted) <= min_dist)) 

84 h_adjusted[adjusted_central] = min_dist[adjusted_central] 

85 use_one_sided[adjusted_central] = False 

86 

87 return h_adjusted, use_one_sided 

88 

89 

90@functools.lru_cache() 

91def _eps_for_method(x0_dtype, f0_dtype, method): 

92 """ 

93 Calculates relative EPS step to use for a given data type 

94 and numdiff step method. 

95 

96 Progressively smaller steps are used for larger floating point types. 

97 

98 Parameters 

99 ---------- 

100 f0_dtype: np.dtype 

101 dtype of function evaluation 

102 

103 x0_dtype: np.dtype 

104 dtype of parameter vector 

105 

106 method: {'2-point', '3-point', 'cs'} 

107 

108 Returns 

109 ------- 

110 EPS: float 

111 relative step size. May be np.float16, np.float32, np.float64 

112 

113 Notes 

114 ----- 

115 The default relative step will be np.float64. However, if x0 or f0 are 

116 smaller floating point types (np.float16, np.float32), then the smallest 

117 floating point type is chosen. 

118 """ 

119 # the default EPS value 

120 EPS = np.finfo(np.float64).eps 

121 

122 x0_is_fp = False 

123 if np.issubdtype(x0_dtype, np.inexact): 

124 # if you're a floating point type then over-ride the default EPS 

125 EPS = np.finfo(x0_dtype).eps 

126 x0_itemsize = np.dtype(x0_dtype).itemsize 

127 x0_is_fp = True 

128 

129 if np.issubdtype(f0_dtype, np.inexact): 

130 f0_itemsize = np.dtype(f0_dtype).itemsize 

131 # choose the smallest itemsize between x0 and f0 

132 if x0_is_fp and f0_itemsize < x0_itemsize: 

133 EPS = np.finfo(f0_dtype).eps 

134 

135 if method in ["2-point", "cs"]: 

136 return EPS**0.5 

137 elif method in ["3-point"]: 

138 return EPS**(1/3) 

139 else: 

140 raise RuntimeError("Unknown step method, should be one of " 

141 "{'2-point', '3-point', 'cs'}") 

142 

143 

144def _compute_absolute_step(rel_step, x0, f0, method): 

145 """ 

146 Computes an absolute step from a relative step for finite difference 

147 calculation. 

148 

149 Parameters 

150 ---------- 

151 rel_step: None or array-like 

152 Relative step for the finite difference calculation 

153 x0 : np.ndarray 

154 Parameter vector 

155 f0 : np.ndarray or scalar 

156 method : {'2-point', '3-point', 'cs'} 

157 

158 Returns 

159 ------- 

160 h : float 

161 The absolute step size 

162 

163 Notes 

164 ----- 

165 `h` will always be np.float64. However, if `x0` or `f0` are 

166 smaller floating point dtypes (e.g. np.float32), then the absolute 

167 step size will be calculated from the smallest floating point size. 

168 """ 

169 # this is used instead of np.sign(x0) because we need 

170 # sign_x0 to be 1 when x0 == 0. 

171 sign_x0 = (x0 >= 0).astype(float) * 2 - 1 

172 

173 rstep = _eps_for_method(x0.dtype, f0.dtype, method) 

174 

175 if rel_step is None: 

176 abs_step = rstep * sign_x0 * np.maximum(1.0, np.abs(x0)) 

177 else: 

178 # User has requested specific relative steps. 

179 # Don't multiply by max(1, abs(x0) because if x0 < 1 then their 

180 # requested step is not used. 

181 abs_step = rel_step * sign_x0 * np.abs(x0) 

182 

183 # however we don't want an abs_step of 0, which can happen if 

184 # rel_step is 0, or x0 is 0. Instead, substitute a realistic step 

185 dx = ((x0 + abs_step) - x0) 

186 abs_step = np.where(dx == 0, 

187 rstep * sign_x0 * np.maximum(1.0, np.abs(x0)), 

188 abs_step) 

189 

190 return abs_step 

191 

192 

193def _prepare_bounds(bounds, x0): 

194 """ 

195 Prepares new-style bounds from a two-tuple specifying the lower and upper 

196 limits for values in x0. If a value is not bound then the lower/upper bound 

197 will be expected to be -np.inf/np.inf. 

198 

199 Examples 

200 -------- 

201 >>> _prepare_bounds([(0, 1, 2), (1, 2, np.inf)], [0.5, 1.5, 2.5]) 

202 (array([0., 1., 2.]), array([ 1., 2., inf])) 

203 """ 

204 lb, ub = [np.asarray(b, dtype=float) for b in bounds] 

205 if lb.ndim == 0: 

206 lb = np.resize(lb, x0.shape) 

207 

208 if ub.ndim == 0: 

209 ub = np.resize(ub, x0.shape) 

210 

211 return lb, ub 

212 

213 

214def group_columns(A, order=0): 

215 """Group columns of a 2-D matrix for sparse finite differencing [1]_. 

216 

217 Two columns are in the same group if in each row at least one of them 

218 has zero. A greedy sequential algorithm is used to construct groups. 

219 

220 Parameters 

221 ---------- 

222 A : array_like or sparse matrix, shape (m, n) 

223 Matrix of which to group columns. 

224 order : int, iterable of int with shape (n,) or None 

225 Permutation array which defines the order of columns enumeration. 

226 If int or None, a random permutation is used with `order` used as 

227 a random seed. Default is 0, that is use a random permutation but 

228 guarantee repeatability. 

229 

230 Returns 

231 ------- 

232 groups : ndarray of int, shape (n,) 

233 Contains values from 0 to n_groups-1, where n_groups is the number 

234 of found groups. Each value ``groups[i]`` is an index of a group to 

235 which ith column assigned. The procedure was helpful only if 

236 n_groups is significantly less than n. 

237 

238 References 

239 ---------- 

240 .. [1] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of 

241 sparse Jacobian matrices", Journal of the Institute of Mathematics 

242 and its Applications, 13 (1974), pp. 117-120. 

243 """ 

244 if issparse(A): 

245 A = csc_matrix(A) 

246 else: 

247 A = np.atleast_2d(A) 

248 A = (A != 0).astype(np.int32) 

249 

250 if A.ndim != 2: 

251 raise ValueError("`A` must be 2-dimensional.") 

252 

253 m, n = A.shape 

254 

255 if order is None or np.isscalar(order): 

256 rng = np.random.RandomState(order) 

257 order = rng.permutation(n) 

258 else: 

259 order = np.asarray(order) 

260 if order.shape != (n,): 

261 raise ValueError("`order` has incorrect shape.") 

262 

263 A = A[:, order] 

264 

265 if issparse(A): 

266 groups = group_sparse(m, n, A.indices, A.indptr) 

267 else: 

268 groups = group_dense(m, n, A) 

269 

270 groups[order] = groups.copy() 

271 

272 return groups 

273 

274 

275def approx_derivative(fun, x0, method='3-point', rel_step=None, abs_step=None, 

276 f0=None, bounds=(-np.inf, np.inf), sparsity=None, 

277 as_linear_operator=False, args=(), kwargs={}): 

278 """Compute finite difference approximation of the derivatives of a 

279 vector-valued function. 

280 

281 If a function maps from R^n to R^m, its derivatives form m-by-n matrix 

282 called the Jacobian, where an element (i, j) is a partial derivative of 

283 f[i] with respect to x[j]. 

284 

285 Parameters 

286 ---------- 

287 fun : callable 

288 Function of which to estimate the derivatives. The argument x 

289 passed to this function is ndarray of shape (n,) (never a scalar 

290 even if n=1). It must return 1-D array_like of shape (m,) or a scalar. 

291 x0 : array_like of shape (n,) or float 

292 Point at which to estimate the derivatives. Float will be converted 

293 to a 1-D array. 

294 method : {'3-point', '2-point', 'cs'}, optional 

295 Finite difference method to use: 

296 - '2-point' - use the first order accuracy forward or backward 

297 difference. 

298 - '3-point' - use central difference in interior points and the 

299 second order accuracy forward or backward difference 

300 near the boundary. 

301 - 'cs' - use a complex-step finite difference scheme. This assumes 

302 that the user function is real-valued and can be 

303 analytically continued to the complex plane. Otherwise, 

304 produces bogus results. 

305 rel_step : None or array_like, optional 

306 Relative step size to use. If None (default) the absolute step size is 

307 computed as ``h = rel_step * sign(x0) * max(1, abs(x0))``, with 

308 `rel_step` being selected automatically, see Notes. Otherwise 

309 ``h = rel_step * sign(x0) * abs(x0)``. For ``method='3-point'`` the 

310 sign of `h` is ignored. The calculated step size is possibly adjusted 

311 to fit into the bounds. 

312 abs_step : array_like, optional 

313 Absolute step size to use, possibly adjusted to fit into the bounds. 

314 For ``method='3-point'`` the sign of `abs_step` is ignored. By default 

315 relative steps are used, only if ``abs_step is not None`` are absolute 

316 steps used. 

317 f0 : None or array_like, optional 

318 If not None it is assumed to be equal to ``fun(x0)``, in this case 

319 the ``fun(x0)`` is not called. Default is None. 

320 bounds : tuple of array_like, optional 

321 Lower and upper bounds on independent variables. Defaults to no bounds. 

322 Each bound must match the size of `x0` or be a scalar, in the latter 

323 case the bound will be the same for all variables. Use it to limit the 

324 range of function evaluation. Bounds checking is not implemented 

325 when `as_linear_operator` is True. 

326 sparsity : {None, array_like, sparse matrix, 2-tuple}, optional 

327 Defines a sparsity structure of the Jacobian matrix. If the Jacobian 

328 matrix is known to have only few non-zero elements in each row, then 

329 it's possible to estimate its several columns by a single function 

330 evaluation [3]_. To perform such economic computations two ingredients 

331 are required: 

332 

333 * structure : array_like or sparse matrix of shape (m, n). A zero 

334 element means that a corresponding element of the Jacobian 

335 identically equals to zero. 

336 * groups : array_like of shape (n,). A column grouping for a given 

337 sparsity structure, use `group_columns` to obtain it. 

338 

339 A single array or a sparse matrix is interpreted as a sparsity 

340 structure, and groups are computed inside the function. A tuple is 

341 interpreted as (structure, groups). If None (default), a standard 

342 dense differencing will be used. 

343 

344 Note, that sparse differencing makes sense only for large Jacobian 

345 matrices where each row contains few non-zero elements. 

346 as_linear_operator : bool, optional 

347 When True the function returns an `scipy.sparse.linalg.LinearOperator`. 

348 Otherwise it returns a dense array or a sparse matrix depending on 

349 `sparsity`. The linear operator provides an efficient way of computing 

350 ``J.dot(p)`` for any vector ``p`` of shape (n,), but does not allow 

351 direct access to individual elements of the matrix. By default 

352 `as_linear_operator` is False. 

353 args, kwargs : tuple and dict, optional 

354 Additional arguments passed to `fun`. Both empty by default. 

355 The calling signature is ``fun(x, *args, **kwargs)``. 

356 

357 Returns 

358 ------- 

359 J : {ndarray, sparse matrix, LinearOperator} 

360 Finite difference approximation of the Jacobian matrix. 

361 If `as_linear_operator` is True returns a LinearOperator 

362 with shape (m, n). Otherwise it returns a dense array or sparse 

363 matrix depending on how `sparsity` is defined. If `sparsity` 

364 is None then a ndarray with shape (m, n) is returned. If 

365 `sparsity` is not None returns a csr_matrix with shape (m, n). 

366 For sparse matrices and linear operators it is always returned as 

367 a 2-D structure, for ndarrays, if m=1 it is returned 

368 as a 1-D gradient array with shape (n,). 

369 

370 See Also 

371 -------- 

372 check_derivative : Check correctness of a function computing derivatives. 

373 

374 Notes 

375 ----- 

376 If `rel_step` is not provided, it assigned as ``EPS**(1/s)``, where EPS is 

377 determined from the smallest floating point dtype of `x0` or `fun(x0)`, 

378 ``np.finfo(x0.dtype).eps``, s=2 for '2-point' method and 

379 s=3 for '3-point' method. Such relative step approximately minimizes a sum 

380 of truncation and round-off errors, see [1]_. Relative steps are used by 

381 default. However, absolute steps are used when ``abs_step is not None``. 

382 If any of the absolute or relative steps produces an indistinguishable 

383 difference from the original `x0`, ``(x0 + dx) - x0 == 0``, then a 

384 automatic step size is substituted for that particular entry. 

385 

386 A finite difference scheme for '3-point' method is selected automatically. 

387 The well-known central difference scheme is used for points sufficiently 

388 far from the boundary, and 3-point forward or backward scheme is used for 

389 points near the boundary. Both schemes have the second-order accuracy in 

390 terms of Taylor expansion. Refer to [2]_ for the formulas of 3-point 

391 forward and backward difference schemes. 

392 

393 For dense differencing when m=1 Jacobian is returned with a shape (n,), 

394 on the other hand when n=1 Jacobian is returned with a shape (m, 1). 

395 Our motivation is the following: a) It handles a case of gradient 

396 computation (m=1) in a conventional way. b) It clearly separates these two 

397 different cases. b) In all cases np.atleast_2d can be called to get 2-D 

398 Jacobian with correct dimensions. 

399 

400 References 

401 ---------- 

402 .. [1] W. H. Press et. al. "Numerical Recipes. The Art of Scientific 

403 Computing. 3rd edition", sec. 5.7. 

404 

405 .. [2] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of 

406 sparse Jacobian matrices", Journal of the Institute of Mathematics 

407 and its Applications, 13 (1974), pp. 117-120. 

408 

409 .. [3] B. Fornberg, "Generation of Finite Difference Formulas on 

410 Arbitrarily Spaced Grids", Mathematics of Computation 51, 1988. 

411 

412 Examples 

413 -------- 

414 >>> import numpy as np 

415 >>> from scipy.optimize._numdiff import approx_derivative 

416 >>> 

417 >>> def f(x, c1, c2): 

418 ... return np.array([x[0] * np.sin(c1 * x[1]), 

419 ... x[0] * np.cos(c2 * x[1])]) 

420 ... 

421 >>> x0 = np.array([1.0, 0.5 * np.pi]) 

422 >>> approx_derivative(f, x0, args=(1, 2)) 

423 array([[ 1., 0.], 

424 [-1., 0.]]) 

425 

426 Bounds can be used to limit the region of function evaluation. 

427 In the example below we compute left and right derivative at point 1.0. 

428 

429 >>> def g(x): 

430 ... return x**2 if x >= 1 else x 

431 ... 

432 >>> x0 = 1.0 

433 >>> approx_derivative(g, x0, bounds=(-np.inf, 1.0)) 

434 array([ 1.]) 

435 >>> approx_derivative(g, x0, bounds=(1.0, np.inf)) 

436 array([ 2.]) 

437 """ 

438 if method not in ['2-point', '3-point', 'cs']: 

439 raise ValueError("Unknown method '%s'. " % method) 

440 

441 x0 = np.atleast_1d(x0) 

442 if x0.ndim > 1: 

443 raise ValueError("`x0` must have at most 1 dimension.") 

444 

445 lb, ub = _prepare_bounds(bounds, x0) 

446 

447 if lb.shape != x0.shape or ub.shape != x0.shape: 

448 raise ValueError("Inconsistent shapes between bounds and `x0`.") 

449 

450 if as_linear_operator and not (np.all(np.isinf(lb)) 

451 and np.all(np.isinf(ub))): 

452 raise ValueError("Bounds not supported when " 

453 "`as_linear_operator` is True.") 

454 

455 def fun_wrapped(x): 

456 f = np.atleast_1d(fun(x, *args, **kwargs)) 

457 if f.ndim > 1: 

458 raise RuntimeError("`fun` return value has " 

459 "more than 1 dimension.") 

460 return f 

461 

462 if f0 is None: 

463 f0 = fun_wrapped(x0) 

464 else: 

465 f0 = np.atleast_1d(f0) 

466 if f0.ndim > 1: 

467 raise ValueError("`f0` passed has more than 1 dimension.") 

468 

469 if np.any((x0 < lb) | (x0 > ub)): 

470 raise ValueError("`x0` violates bound constraints.") 

471 

472 if as_linear_operator: 

473 if rel_step is None: 

474 rel_step = _eps_for_method(x0.dtype, f0.dtype, method) 

475 

476 return _linear_operator_difference(fun_wrapped, x0, 

477 f0, rel_step, method) 

478 else: 

479 # by default we use rel_step 

480 if abs_step is None: 

481 h = _compute_absolute_step(rel_step, x0, f0, method) 

482 else: 

483 # user specifies an absolute step 

484 sign_x0 = (x0 >= 0).astype(float) * 2 - 1 

485 h = abs_step 

486 

487 # cannot have a zero step. This might happen if x0 is very large 

488 # or small. In which case fall back to relative step. 

489 dx = ((x0 + h) - x0) 

490 h = np.where(dx == 0, 

491 _eps_for_method(x0.dtype, f0.dtype, method) * 

492 sign_x0 * np.maximum(1.0, np.abs(x0)), 

493 h) 

494 

495 if method == '2-point': 

496 h, use_one_sided = _adjust_scheme_to_bounds( 

497 x0, h, 1, '1-sided', lb, ub) 

498 elif method == '3-point': 

499 h, use_one_sided = _adjust_scheme_to_bounds( 

500 x0, h, 1, '2-sided', lb, ub) 

501 elif method == 'cs': 

502 use_one_sided = False 

503 

504 if sparsity is None: 

505 return _dense_difference(fun_wrapped, x0, f0, h, 

506 use_one_sided, method) 

507 else: 

508 if not issparse(sparsity) and len(sparsity) == 2: 

509 structure, groups = sparsity 

510 else: 

511 structure = sparsity 

512 groups = group_columns(sparsity) 

513 

514 if issparse(structure): 

515 structure = csc_matrix(structure) 

516 else: 

517 structure = np.atleast_2d(structure) 

518 

519 groups = np.atleast_1d(groups) 

520 return _sparse_difference(fun_wrapped, x0, f0, h, 

521 use_one_sided, structure, 

522 groups, method) 

523 

524 

525def _linear_operator_difference(fun, x0, f0, h, method): 

526 m = f0.size 

527 n = x0.size 

528 

529 if method == '2-point': 

530 def matvec(p): 

531 if np.array_equal(p, np.zeros_like(p)): 

532 return np.zeros(m) 

533 dx = h / norm(p) 

534 x = x0 + dx*p 

535 df = fun(x) - f0 

536 return df / dx 

537 

538 elif method == '3-point': 

539 def matvec(p): 

540 if np.array_equal(p, np.zeros_like(p)): 

541 return np.zeros(m) 

542 dx = 2*h / norm(p) 

543 x1 = x0 - (dx/2)*p 

544 x2 = x0 + (dx/2)*p 

545 f1 = fun(x1) 

546 f2 = fun(x2) 

547 df = f2 - f1 

548 return df / dx 

549 

550 elif method == 'cs': 

551 def matvec(p): 

552 if np.array_equal(p, np.zeros_like(p)): 

553 return np.zeros(m) 

554 dx = h / norm(p) 

555 x = x0 + dx*p*1.j 

556 f1 = fun(x) 

557 df = f1.imag 

558 return df / dx 

559 

560 else: 

561 raise RuntimeError("Never be here.") 

562 

563 return LinearOperator((m, n), matvec) 

564 

565 

566def _dense_difference(fun, x0, f0, h, use_one_sided, method): 

567 m = f0.size 

568 n = x0.size 

569 J_transposed = np.empty((n, m)) 

570 h_vecs = np.diag(h) 

571 

572 for i in range(h.size): 

573 if method == '2-point': 

574 x = x0 + h_vecs[i] 

575 dx = x[i] - x0[i] # Recompute dx as exactly representable number. 

576 df = fun(x) - f0 

577 elif method == '3-point' and use_one_sided[i]: 

578 x1 = x0 + h_vecs[i] 

579 x2 = x0 + 2 * h_vecs[i] 

580 dx = x2[i] - x0[i] 

581 f1 = fun(x1) 

582 f2 = fun(x2) 

583 df = -3.0 * f0 + 4 * f1 - f2 

584 elif method == '3-point' and not use_one_sided[i]: 

585 x1 = x0 - h_vecs[i] 

586 x2 = x0 + h_vecs[i] 

587 dx = x2[i] - x1[i] 

588 f1 = fun(x1) 

589 f2 = fun(x2) 

590 df = f2 - f1 

591 elif method == 'cs': 

592 f1 = fun(x0 + h_vecs[i]*1.j) 

593 df = f1.imag 

594 dx = h_vecs[i, i] 

595 else: 

596 raise RuntimeError("Never be here.") 

597 

598 J_transposed[i] = df / dx 

599 

600 if m == 1: 

601 J_transposed = np.ravel(J_transposed) 

602 

603 return J_transposed.T 

604 

605 

606def _sparse_difference(fun, x0, f0, h, use_one_sided, 

607 structure, groups, method): 

608 m = f0.size 

609 n = x0.size 

610 row_indices = [] 

611 col_indices = [] 

612 fractions = [] 

613 

614 n_groups = np.max(groups) + 1 

615 for group in range(n_groups): 

616 # Perturb variables which are in the same group simultaneously. 

617 e = np.equal(group, groups) 

618 h_vec = h * e 

619 if method == '2-point': 

620 x = x0 + h_vec 

621 dx = x - x0 

622 df = fun(x) - f0 

623 # The result is written to columns which correspond to perturbed 

624 # variables. 

625 cols, = np.nonzero(e) 

626 # Find all non-zero elements in selected columns of Jacobian. 

627 i, j, _ = find(structure[:, cols]) 

628 # Restore column indices in the full array. 

629 j = cols[j] 

630 elif method == '3-point': 

631 # Here we do conceptually the same but separate one-sided 

632 # and two-sided schemes. 

633 x1 = x0.copy() 

634 x2 = x0.copy() 

635 

636 mask_1 = use_one_sided & e 

637 x1[mask_1] += h_vec[mask_1] 

638 x2[mask_1] += 2 * h_vec[mask_1] 

639 

640 mask_2 = ~use_one_sided & e 

641 x1[mask_2] -= h_vec[mask_2] 

642 x2[mask_2] += h_vec[mask_2] 

643 

644 dx = np.zeros(n) 

645 dx[mask_1] = x2[mask_1] - x0[mask_1] 

646 dx[mask_2] = x2[mask_2] - x1[mask_2] 

647 

648 f1 = fun(x1) 

649 f2 = fun(x2) 

650 

651 cols, = np.nonzero(e) 

652 i, j, _ = find(structure[:, cols]) 

653 j = cols[j] 

654 

655 mask = use_one_sided[j] 

656 df = np.empty(m) 

657 

658 rows = i[mask] 

659 df[rows] = -3 * f0[rows] + 4 * f1[rows] - f2[rows] 

660 

661 rows = i[~mask] 

662 df[rows] = f2[rows] - f1[rows] 

663 elif method == 'cs': 

664 f1 = fun(x0 + h_vec*1.j) 

665 df = f1.imag 

666 dx = h_vec 

667 cols, = np.nonzero(e) 

668 i, j, _ = find(structure[:, cols]) 

669 j = cols[j] 

670 else: 

671 raise ValueError("Never be here.") 

672 

673 # All that's left is to compute the fraction. We store i, j and 

674 # fractions as separate arrays and later construct coo_matrix. 

675 row_indices.append(i) 

676 col_indices.append(j) 

677 fractions.append(df[i] / dx[j]) 

678 

679 row_indices = np.hstack(row_indices) 

680 col_indices = np.hstack(col_indices) 

681 fractions = np.hstack(fractions) 

682 J = coo_matrix((fractions, (row_indices, col_indices)), shape=(m, n)) 

683 return csr_matrix(J) 

684 

685 

686def check_derivative(fun, jac, x0, bounds=(-np.inf, np.inf), args=(), 

687 kwargs={}): 

688 """Check correctness of a function computing derivatives (Jacobian or 

689 gradient) by comparison with a finite difference approximation. 

690 

691 Parameters 

692 ---------- 

693 fun : callable 

694 Function of which to estimate the derivatives. The argument x 

695 passed to this function is ndarray of shape (n,) (never a scalar 

696 even if n=1). It must return 1-D array_like of shape (m,) or a scalar. 

697 jac : callable 

698 Function which computes Jacobian matrix of `fun`. It must work with 

699 argument x the same way as `fun`. The return value must be array_like 

700 or sparse matrix with an appropriate shape. 

701 x0 : array_like of shape (n,) or float 

702 Point at which to estimate the derivatives. Float will be converted 

703 to 1-D array. 

704 bounds : 2-tuple of array_like, optional 

705 Lower and upper bounds on independent variables. Defaults to no bounds. 

706 Each bound must match the size of `x0` or be a scalar, in the latter 

707 case the bound will be the same for all variables. Use it to limit the 

708 range of function evaluation. 

709 args, kwargs : tuple and dict, optional 

710 Additional arguments passed to `fun` and `jac`. Both empty by default. 

711 The calling signature is ``fun(x, *args, **kwargs)`` and the same 

712 for `jac`. 

713 

714 Returns 

715 ------- 

716 accuracy : float 

717 The maximum among all relative errors for elements with absolute values 

718 higher than 1 and absolute errors for elements with absolute values 

719 less or equal than 1. If `accuracy` is on the order of 1e-6 or lower, 

720 then it is likely that your `jac` implementation is correct. 

721 

722 See Also 

723 -------- 

724 approx_derivative : Compute finite difference approximation of derivative. 

725 

726 Examples 

727 -------- 

728 >>> import numpy as np 

729 >>> from scipy.optimize._numdiff import check_derivative 

730 >>> 

731 >>> 

732 >>> def f(x, c1, c2): 

733 ... return np.array([x[0] * np.sin(c1 * x[1]), 

734 ... x[0] * np.cos(c2 * x[1])]) 

735 ... 

736 >>> def jac(x, c1, c2): 

737 ... return np.array([ 

738 ... [np.sin(c1 * x[1]), c1 * x[0] * np.cos(c1 * x[1])], 

739 ... [np.cos(c2 * x[1]), -c2 * x[0] * np.sin(c2 * x[1])] 

740 ... ]) 

741 ... 

742 >>> 

743 >>> x0 = np.array([1.0, 0.5 * np.pi]) 

744 >>> check_derivative(f, jac, x0, args=(1, 2)) 

745 2.4492935982947064e-16 

746 """ 

747 J_to_test = jac(x0, *args, **kwargs) 

748 if issparse(J_to_test): 

749 J_diff = approx_derivative(fun, x0, bounds=bounds, sparsity=J_to_test, 

750 args=args, kwargs=kwargs) 

751 J_to_test = csr_matrix(J_to_test) 

752 abs_err = J_to_test - J_diff 

753 i, j, abs_err_data = find(abs_err) 

754 J_diff_data = np.asarray(J_diff[i, j]).ravel() 

755 return np.max(np.abs(abs_err_data) / 

756 np.maximum(1, np.abs(J_diff_data))) 

757 else: 

758 J_diff = approx_derivative(fun, x0, bounds=bounds, 

759 args=args, kwargs=kwargs) 

760 abs_err = np.abs(J_to_test - J_diff) 

761 return np.max(abs_err / np.maximum(1, np.abs(J_diff)))