Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/_lib/_util.py: 20%

347 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-14 06:37 +0000

1import re 

2from contextlib import contextmanager 

3import functools 

4import operator 

5import warnings 

6import numbers 

7from collections import namedtuple 

8import inspect 

9import math 

10from typing import ( 

11 Optional, 

12 Union, 

13 TYPE_CHECKING, 

14 TypeVar, 

15) 

16 

17import numpy as np 

18from scipy._lib._array_api import array_namespace 

19 

20 

21AxisError: type[Exception] 

22ComplexWarning: type[Warning] 

23VisibleDeprecationWarning: type[Warning] 

24 

25if np.lib.NumpyVersion(np.__version__) >= '1.25.0': 

26 from numpy.exceptions import ( 

27 AxisError, ComplexWarning, VisibleDeprecationWarning, 

28 DTypePromotionError 

29 ) 

30else: 

31 from numpy import ( 

32 AxisError, ComplexWarning, VisibleDeprecationWarning # noqa: F401 

33 ) 

34 DTypePromotionError = TypeError # type: ignore 

35 

36np_long: type 

37np_ulong: type 

38 

39if np.lib.NumpyVersion(np.__version__) >= "2.0.0.dev0": 

40 try: 

41 with warnings.catch_warnings(): 

42 warnings.filterwarnings( 

43 "ignore", 

44 r".*In the future `np\.long` will be defined as.*", 

45 FutureWarning, 

46 ) 

47 np_long = np.long # type: ignore[attr-defined] 

48 np_ulong = np.ulong # type: ignore[attr-defined] 

49 except AttributeError: 

50 np_long = np.int_ 

51 np_ulong = np.uint 

52else: 

53 np_long = np.int_ 

54 np_ulong = np.uint 

55 

56IntNumber = Union[int, np.integer] 

57DecimalNumber = Union[float, np.floating, np.integer] 

58 

59# Since Generator was introduced in numpy 1.17, the following condition is needed for 

60# backward compatibility 

61if TYPE_CHECKING: 

62 SeedType = Optional[Union[IntNumber, np.random.Generator, 

63 np.random.RandomState]] 

64 GeneratorType = TypeVar("GeneratorType", bound=Union[np.random.Generator, 

65 np.random.RandomState]) 

66 

67try: 

68 from numpy.random import Generator as Generator 

69except ImportError: 

70 class Generator: # type: ignore[no-redef] 

71 pass 

72 

73 

74def _lazywhere(cond, arrays, f, fillvalue=None, f2=None): 

75 """Return elements chosen from two possibilities depending on a condition 

76 

77 Equivalent to ``f(*arrays) if cond else fillvalue`` performed elementwise. 

78 

79 Parameters 

80 ---------- 

81 cond : array 

82 The condition (expressed as a boolean array). 

83 arrays : tuple of array 

84 Arguments to `f` (and `f2`). Must be broadcastable with `cond`. 

85 f : callable 

86 Where `cond` is True, output will be ``f(arr1[cond], arr2[cond], ...)`` 

87 fillvalue : object 

88 If provided, value with which to fill output array where `cond` is 

89 not True. 

90 f2 : callable 

91 If provided, output will be ``f2(arr1[cond], arr2[cond], ...)`` where 

92 `cond` is not True. 

93 

94 Returns 

95 ------- 

96 out : array 

97 An array with elements from the output of `f` where `cond` is True 

98 and `fillvalue` (or elements from the output of `f2`) elsewhere. The 

99 returned array has data type determined by Type Promotion Rules 

100 with the output of `f` and `fillvalue` (or the output of `f2`). 

101 

102 Notes 

103 ----- 

104 ``xp.where(cond, x, fillvalue)`` requires explicitly forming `x` even where 

105 `cond` is False. This function evaluates ``f(arr1[cond], arr2[cond], ...)`` 

106 onle where `cond` ``is True. 

107 

108 Examples 

109 -------- 

110 >>> import numpy as np 

111 >>> a, b = np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8]) 

112 >>> def f(a, b): 

113 ... return a*b 

114 >>> _lazywhere(a > 2, (a, b), f, np.nan) 

115 array([ nan, nan, 21., 32.]) 

116 

117 """ 

118 xp = array_namespace(cond, *arrays) 

119 

120 if (f2 is fillvalue is None) or (f2 is not None and fillvalue is not None): 

121 raise ValueError("Exactly one of `fillvalue` or `f2` must be given.") 

122 

123 args = xp.broadcast_arrays(cond, *arrays) 

124 bool_dtype = xp.asarray([True]).dtype # numpy 1.xx doesn't have `bool` 

125 cond, arrays = xp.astype(args[0], bool_dtype, copy=False), args[1:] 

126 

127 temp1 = xp.asarray(f(*(arr[cond] for arr in arrays))) 

128 

129 if f2 is None: 

130 fillvalue = xp.asarray(fillvalue) 

131 dtype = xp.result_type(temp1.dtype, fillvalue.dtype) 

132 out = xp.full(cond.shape, fill_value=fillvalue, dtype=dtype) 

133 else: 

134 ncond = ~cond 

135 temp2 = xp.asarray(f2(*(arr[ncond] for arr in arrays))) 

136 dtype = xp.result_type(temp1, temp2) 

137 out = xp.empty(cond.shape, dtype=dtype) 

138 out[ncond] = temp2 

139 

140 out[cond] = temp1 

141 

142 return out 

143 

144 

145def _lazyselect(condlist, choicelist, arrays, default=0): 

146 """ 

147 Mimic `np.select(condlist, choicelist)`. 

148 

149 Notice, it assumes that all `arrays` are of the same shape or can be 

150 broadcasted together. 

151 

152 All functions in `choicelist` must accept array arguments in the order 

153 given in `arrays` and must return an array of the same shape as broadcasted 

154 `arrays`. 

155 

156 Examples 

157 -------- 

158 >>> import numpy as np 

159 >>> x = np.arange(6) 

160 >>> np.select([x <3, x > 3], [x**2, x**3], default=0) 

161 array([ 0, 1, 4, 0, 64, 125]) 

162 

163 >>> _lazyselect([x < 3, x > 3], [lambda x: x**2, lambda x: x**3], (x,)) 

164 array([ 0., 1., 4., 0., 64., 125.]) 

165 

166 >>> a = -np.ones_like(x) 

167 >>> _lazyselect([x < 3, x > 3], 

168 ... [lambda x, a: x**2, lambda x, a: a * x**3], 

169 ... (x, a), default=np.nan) 

170 array([ 0., 1., 4., nan, -64., -125.]) 

171 

172 """ 

173 arrays = np.broadcast_arrays(*arrays) 

174 tcode = np.mintypecode([a.dtype.char for a in arrays]) 

175 out = np.full(np.shape(arrays[0]), fill_value=default, dtype=tcode) 

176 for func, cond in zip(choicelist, condlist): 

177 if np.all(cond is False): 

178 continue 

179 cond, _ = np.broadcast_arrays(cond, arrays[0]) 

180 temp = tuple(np.extract(cond, arr) for arr in arrays) 

181 np.place(out, cond, func(*temp)) 

182 return out 

183 

184 

185def _aligned_zeros(shape, dtype=float, order="C", align=None): 

186 """Allocate a new ndarray with aligned memory. 

187 

188 Primary use case for this currently is working around a f2py issue 

189 in NumPy 1.9.1, where dtype.alignment is such that np.zeros() does 

190 not necessarily create arrays aligned up to it. 

191 

192 """ 

193 dtype = np.dtype(dtype) 

194 if align is None: 

195 align = dtype.alignment 

196 if not hasattr(shape, '__len__'): 

197 shape = (shape,) 

198 size = functools.reduce(operator.mul, shape) * dtype.itemsize 

199 buf = np.empty(size + align + 1, np.uint8) 

200 offset = buf.__array_interface__['data'][0] % align 

201 if offset != 0: 

202 offset = align - offset 

203 # Note: slices producing 0-size arrays do not necessarily change 

204 # data pointer --- so we use and allocate size+1 

205 buf = buf[offset:offset+size+1][:-1] 

206 data = np.ndarray(shape, dtype, buf, order=order) 

207 data.fill(0) 

208 return data 

209 

210 

211def _prune_array(array): 

212 """Return an array equivalent to the input array. If the input 

213 array is a view of a much larger array, copy its contents to a 

214 newly allocated array. Otherwise, return the input unchanged. 

215 """ 

216 if array.base is not None and array.size < array.base.size // 2: 

217 return array.copy() 

218 return array 

219 

220 

221def float_factorial(n: int) -> float: 

222 """Compute the factorial and return as a float 

223 

224 Returns infinity when result is too large for a double 

225 """ 

226 return float(math.factorial(n)) if n < 171 else np.inf 

227 

228 

229# copy-pasted from scikit-learn utils/validation.py 

230# change this to scipy.stats._qmc.check_random_state once numpy 1.16 is dropped 

231def check_random_state(seed): 

232 """Turn `seed` into a `np.random.RandomState` instance. 

233 

234 Parameters 

235 ---------- 

236 seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional 

237 If `seed` is None (or `np.random`), the `numpy.random.RandomState` 

238 singleton is used. 

239 If `seed` is an int, a new ``RandomState`` instance is used, 

240 seeded with `seed`. 

241 If `seed` is already a ``Generator`` or ``RandomState`` instance then 

242 that instance is used. 

243 

244 Returns 

245 ------- 

246 seed : {`numpy.random.Generator`, `numpy.random.RandomState`} 

247 Random number generator. 

248 

249 """ 

250 if seed is None or seed is np.random: 

251 return np.random.mtrand._rand 

252 if isinstance(seed, (numbers.Integral, np.integer)): 

253 return np.random.RandomState(seed) 

254 if isinstance(seed, (np.random.RandomState, np.random.Generator)): 

255 return seed 

256 

257 raise ValueError('%r cannot be used to seed a numpy.random.RandomState' 

258 ' instance' % seed) 

259 

260 

261def _asarray_validated(a, check_finite=True, 

262 sparse_ok=False, objects_ok=False, mask_ok=False, 

263 as_inexact=False): 

264 """ 

265 Helper function for SciPy argument validation. 

266 

267 Many SciPy linear algebra functions do support arbitrary array-like 

268 input arguments. Examples of commonly unsupported inputs include 

269 matrices containing inf/nan, sparse matrix representations, and 

270 matrices with complicated elements. 

271 

272 Parameters 

273 ---------- 

274 a : array_like 

275 The array-like input. 

276 check_finite : bool, optional 

277 Whether to check that the input matrices contain only finite numbers. 

278 Disabling may give a performance gain, but may result in problems 

279 (crashes, non-termination) if the inputs do contain infinities or NaNs. 

280 Default: True 

281 sparse_ok : bool, optional 

282 True if scipy sparse matrices are allowed. 

283 objects_ok : bool, optional 

284 True if arrays with dype('O') are allowed. 

285 mask_ok : bool, optional 

286 True if masked arrays are allowed. 

287 as_inexact : bool, optional 

288 True to convert the input array to a np.inexact dtype. 

289 

290 Returns 

291 ------- 

292 ret : ndarray 

293 The converted validated array. 

294 

295 """ 

296 if not sparse_ok: 

297 import scipy.sparse 

298 if scipy.sparse.issparse(a): 

299 msg = ('Sparse matrices are not supported by this function. ' 

300 'Perhaps one of the scipy.sparse.linalg functions ' 

301 'would work instead.') 

302 raise ValueError(msg) 

303 if not mask_ok: 

304 if np.ma.isMaskedArray(a): 

305 raise ValueError('masked arrays are not supported') 

306 toarray = np.asarray_chkfinite if check_finite else np.asarray 

307 a = toarray(a) 

308 if not objects_ok: 

309 if a.dtype is np.dtype('O'): 

310 raise ValueError('object arrays are not supported') 

311 if as_inexact: 

312 if not np.issubdtype(a.dtype, np.inexact): 

313 a = toarray(a, dtype=np.float64) 

314 return a 

315 

316 

317def _validate_int(k, name, minimum=None): 

318 """ 

319 Validate a scalar integer. 

320 

321 This function can be used to validate an argument to a function 

322 that expects the value to be an integer. It uses `operator.index` 

323 to validate the value (so, for example, k=2.0 results in a 

324 TypeError). 

325 

326 Parameters 

327 ---------- 

328 k : int 

329 The value to be validated. 

330 name : str 

331 The name of the parameter. 

332 minimum : int, optional 

333 An optional lower bound. 

334 """ 

335 try: 

336 k = operator.index(k) 

337 except TypeError: 

338 raise TypeError(f'{name} must be an integer.') from None 

339 if minimum is not None and k < minimum: 

340 raise ValueError(f'{name} must be an integer not less ' 

341 f'than {minimum}') from None 

342 return k 

343 

344 

345# Add a replacement for inspect.getfullargspec()/ 

346# The version below is borrowed from Django, 

347# https://github.com/django/django/pull/4846. 

348 

349# Note an inconsistency between inspect.getfullargspec(func) and 

350# inspect.signature(func). If `func` is a bound method, the latter does *not* 

351# list `self` as a first argument, while the former *does*. 

352# Hence, cook up a common ground replacement: `getfullargspec_no_self` which 

353# mimics `inspect.getfullargspec` but does not list `self`. 

354# 

355# This way, the caller code does not need to know whether it uses a legacy 

356# .getfullargspec or a bright and shiny .signature. 

357 

358FullArgSpec = namedtuple('FullArgSpec', 

359 ['args', 'varargs', 'varkw', 'defaults', 

360 'kwonlyargs', 'kwonlydefaults', 'annotations']) 

361 

362 

363def getfullargspec_no_self(func): 

364 """inspect.getfullargspec replacement using inspect.signature. 

365 

366 If func is a bound method, do not list the 'self' parameter. 

367 

368 Parameters 

369 ---------- 

370 func : callable 

371 A callable to inspect 

372 

373 Returns 

374 ------- 

375 fullargspec : FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, 

376 kwonlydefaults, annotations) 

377 

378 NOTE: if the first argument of `func` is self, it is *not*, I repeat 

379 *not*, included in fullargspec.args. 

380 This is done for consistency between inspect.getargspec() under 

381 Python 2.x, and inspect.signature() under Python 3.x. 

382 

383 """ 

384 sig = inspect.signature(func) 

385 args = [ 

386 p.name for p in sig.parameters.values() 

387 if p.kind in [inspect.Parameter.POSITIONAL_OR_KEYWORD, 

388 inspect.Parameter.POSITIONAL_ONLY] 

389 ] 

390 varargs = [ 

391 p.name for p in sig.parameters.values() 

392 if p.kind == inspect.Parameter.VAR_POSITIONAL 

393 ] 

394 varargs = varargs[0] if varargs else None 

395 varkw = [ 

396 p.name for p in sig.parameters.values() 

397 if p.kind == inspect.Parameter.VAR_KEYWORD 

398 ] 

399 varkw = varkw[0] if varkw else None 

400 defaults = tuple( 

401 p.default for p in sig.parameters.values() 

402 if (p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and 

403 p.default is not p.empty) 

404 ) or None 

405 kwonlyargs = [ 

406 p.name for p in sig.parameters.values() 

407 if p.kind == inspect.Parameter.KEYWORD_ONLY 

408 ] 

409 kwdefaults = {p.name: p.default for p in sig.parameters.values() 

410 if p.kind == inspect.Parameter.KEYWORD_ONLY and 

411 p.default is not p.empty} 

412 annotations = {p.name: p.annotation for p in sig.parameters.values() 

413 if p.annotation is not p.empty} 

414 return FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, 

415 kwdefaults or None, annotations) 

416 

417 

418class _FunctionWrapper: 

419 """ 

420 Object to wrap user's function, allowing picklability 

421 """ 

422 def __init__(self, f, args): 

423 self.f = f 

424 self.args = [] if args is None else args 

425 

426 def __call__(self, x): 

427 return self.f(x, *self.args) 

428 

429 

430class MapWrapper: 

431 """ 

432 Parallelisation wrapper for working with map-like callables, such as 

433 `multiprocessing.Pool.map`. 

434 

435 Parameters 

436 ---------- 

437 pool : int or map-like callable 

438 If `pool` is an integer, then it specifies the number of threads to 

439 use for parallelization. If ``int(pool) == 1``, then no parallel 

440 processing is used and the map builtin is used. 

441 If ``pool == -1``, then the pool will utilize all available CPUs. 

442 If `pool` is a map-like callable that follows the same 

443 calling sequence as the built-in map function, then this callable is 

444 used for parallelization. 

445 """ 

446 def __init__(self, pool=1): 

447 self.pool = None 

448 self._mapfunc = map 

449 self._own_pool = False 

450 

451 if callable(pool): 

452 self.pool = pool 

453 self._mapfunc = self.pool 

454 else: 

455 from multiprocessing import Pool 

456 # user supplies a number 

457 if int(pool) == -1: 

458 # use as many processors as possible 

459 self.pool = Pool() 

460 self._mapfunc = self.pool.map 

461 self._own_pool = True 

462 elif int(pool) == 1: 

463 pass 

464 elif int(pool) > 1: 

465 # use the number of processors requested 

466 self.pool = Pool(processes=int(pool)) 

467 self._mapfunc = self.pool.map 

468 self._own_pool = True 

469 else: 

470 raise RuntimeError("Number of workers specified must be -1," 

471 " an int >= 1, or an object with a 'map' " 

472 "method") 

473 

474 def __enter__(self): 

475 return self 

476 

477 def terminate(self): 

478 if self._own_pool: 

479 self.pool.terminate() 

480 

481 def join(self): 

482 if self._own_pool: 

483 self.pool.join() 

484 

485 def close(self): 

486 if self._own_pool: 

487 self.pool.close() 

488 

489 def __exit__(self, exc_type, exc_value, traceback): 

490 if self._own_pool: 

491 self.pool.close() 

492 self.pool.terminate() 

493 

494 def __call__(self, func, iterable): 

495 # only accept one iterable because that's all Pool.map accepts 

496 try: 

497 return self._mapfunc(func, iterable) 

498 except TypeError as e: 

499 # wrong number of arguments 

500 raise TypeError("The map-like callable must be of the" 

501 " form f(func, iterable)") from e 

502 

503 

504def rng_integers(gen, low, high=None, size=None, dtype='int64', 

505 endpoint=False): 

506 """ 

507 Return random integers from low (inclusive) to high (exclusive), or if 

508 endpoint=True, low (inclusive) to high (inclusive). Replaces 

509 `RandomState.randint` (with endpoint=False) and 

510 `RandomState.random_integers` (with endpoint=True). 

511 

512 Return random integers from the "discrete uniform" distribution of the 

513 specified dtype. If high is None (the default), then results are from 

514 0 to low. 

515 

516 Parameters 

517 ---------- 

518 gen : {None, np.random.RandomState, np.random.Generator} 

519 Random number generator. If None, then the np.random.RandomState 

520 singleton is used. 

521 low : int or array-like of ints 

522 Lowest (signed) integers to be drawn from the distribution (unless 

523 high=None, in which case this parameter is 0 and this value is used 

524 for high). 

525 high : int or array-like of ints 

526 If provided, one above the largest (signed) integer to be drawn from 

527 the distribution (see above for behavior if high=None). If array-like, 

528 must contain integer values. 

529 size : array-like of ints, optional 

530 Output shape. If the given shape is, e.g., (m, n, k), then m * n * k 

531 samples are drawn. Default is None, in which case a single value is 

532 returned. 

533 dtype : {str, dtype}, optional 

534 Desired dtype of the result. All dtypes are determined by their name, 

535 i.e., 'int64', 'int', etc, so byteorder is not available and a specific 

536 precision may have different C types depending on the platform. 

537 The default value is 'int64'. 

538 endpoint : bool, optional 

539 If True, sample from the interval [low, high] instead of the default 

540 [low, high) Defaults to False. 

541 

542 Returns 

543 ------- 

544 out: int or ndarray of ints 

545 size-shaped array of random integers from the appropriate distribution, 

546 or a single such random int if size not provided. 

547 """ 

548 if isinstance(gen, Generator): 

549 return gen.integers(low, high=high, size=size, dtype=dtype, 

550 endpoint=endpoint) 

551 else: 

552 if gen is None: 

553 # default is RandomState singleton used by np.random. 

554 gen = np.random.mtrand._rand 

555 if endpoint: 

556 # inclusive of endpoint 

557 # remember that low and high can be arrays, so don't modify in 

558 # place 

559 if high is None: 

560 return gen.randint(low + 1, size=size, dtype=dtype) 

561 if high is not None: 

562 return gen.randint(low, high=high + 1, size=size, dtype=dtype) 

563 

564 # exclusive 

565 return gen.randint(low, high=high, size=size, dtype=dtype) 

566 

567 

568@contextmanager 

569def _fixed_default_rng(seed=1638083107694713882823079058616272161): 

570 """Context with a fixed np.random.default_rng seed.""" 

571 orig_fun = np.random.default_rng 

572 np.random.default_rng = lambda seed=seed: orig_fun(seed) 

573 try: 

574 yield 

575 finally: 

576 np.random.default_rng = orig_fun 

577 

578 

579def _rng_html_rewrite(func): 

580 """Rewrite the HTML rendering of ``np.random.default_rng``. 

581 

582 This is intended to decorate 

583 ``numpydoc.docscrape_sphinx.SphinxDocString._str_examples``. 

584 

585 Examples are only run by Sphinx when there are plot involved. Even so, 

586 it does not change the result values getting printed. 

587 """ 

588 # hexadecimal or number seed, case-insensitive 

589 pattern = re.compile(r'np.random.default_rng\((0x[0-9A-F]+|\d+)\)', re.I) 

590 

591 def _wrapped(*args, **kwargs): 

592 res = func(*args, **kwargs) 

593 lines = [ 

594 re.sub(pattern, 'np.random.default_rng()', line) 

595 for line in res 

596 ] 

597 return lines 

598 

599 return _wrapped 

600 

601 

602def _argmin(a, keepdims=False, axis=None): 

603 """ 

604 argmin with a `keepdims` parameter. 

605 

606 See https://github.com/numpy/numpy/issues/8710 

607 

608 If axis is not None, a.shape[axis] must be greater than 0. 

609 """ 

610 res = np.argmin(a, axis=axis) 

611 if keepdims and axis is not None: 

612 res = np.expand_dims(res, axis=axis) 

613 return res 

614 

615 

616def _first_nonnan(a, axis): 

617 """ 

618 Return the first non-nan value along the given axis. 

619 

620 If a slice is all nan, nan is returned for that slice. 

621 

622 The shape of the return value corresponds to ``keepdims=True``. 

623 

624 Examples 

625 -------- 

626 >>> import numpy as np 

627 >>> nan = np.nan 

628 >>> a = np.array([[ 3., 3., nan, 3.], 

629 [ 1., nan, 2., 4.], 

630 [nan, nan, 9., -1.], 

631 [nan, 5., 4., 3.], 

632 [ 2., 2., 2., 2.], 

633 [nan, nan, nan, nan]]) 

634 >>> _first_nonnan(a, axis=0) 

635 array([[3., 3., 2., 3.]]) 

636 >>> _first_nonnan(a, axis=1) 

637 array([[ 3.], 

638 [ 1.], 

639 [ 9.], 

640 [ 5.], 

641 [ 2.], 

642 [nan]]) 

643 """ 

644 k = _argmin(np.isnan(a), axis=axis, keepdims=True) 

645 return np.take_along_axis(a, k, axis=axis) 

646 

647 

648def _nan_allsame(a, axis, keepdims=False): 

649 """ 

650 Determine if the values along an axis are all the same. 

651 

652 nan values are ignored. 

653 

654 `a` must be a numpy array. 

655 

656 `axis` is assumed to be normalized; that is, 0 <= axis < a.ndim. 

657 

658 For an axis of length 0, the result is True. That is, we adopt the 

659 convention that ``allsame([])`` is True. (There are no values in the 

660 input that are different.) 

661 

662 `True` is returned for slices that are all nan--not because all the 

663 values are the same, but because this is equivalent to ``allsame([])``. 

664 

665 Examples 

666 -------- 

667 >>> from numpy import nan, array 

668 >>> a = array([[ 3., 3., nan, 3.], 

669 ... [ 1., nan, 2., 4.], 

670 ... [nan, nan, 9., -1.], 

671 ... [nan, 5., 4., 3.], 

672 ... [ 2., 2., 2., 2.], 

673 ... [nan, nan, nan, nan]]) 

674 >>> _nan_allsame(a, axis=1, keepdims=True) 

675 array([[ True], 

676 [False], 

677 [False], 

678 [False], 

679 [ True], 

680 [ True]]) 

681 """ 

682 if axis is None: 

683 if a.size == 0: 

684 return True 

685 a = a.ravel() 

686 axis = 0 

687 else: 

688 shp = a.shape 

689 if shp[axis] == 0: 

690 shp = shp[:axis] + (1,)*keepdims + shp[axis + 1:] 

691 return np.full(shp, fill_value=True, dtype=bool) 

692 a0 = _first_nonnan(a, axis=axis) 

693 return ((a0 == a) | np.isnan(a)).all(axis=axis, keepdims=keepdims) 

694 

695 

696def _contains_nan(a, nan_policy='propagate', use_summation=True, 

697 policies=None): 

698 if not isinstance(a, np.ndarray): 

699 use_summation = False # some array_likes ignore nans (e.g. pandas) 

700 if policies is None: 

701 policies = ['propagate', 'raise', 'omit'] 

702 if nan_policy not in policies: 

703 raise ValueError("nan_policy must be one of {%s}" % 

704 ', '.join("'%s'" % s for s in policies)) 

705 

706 if np.issubdtype(a.dtype, np.inexact): 

707 # The summation method avoids creating a (potentially huge) array. 

708 if use_summation: 

709 with np.errstate(invalid='ignore', over='ignore'): 

710 contains_nan = np.isnan(np.sum(a)) 

711 else: 

712 contains_nan = np.isnan(a).any() 

713 elif np.issubdtype(a.dtype, object): 

714 contains_nan = False 

715 for el in a.ravel(): 

716 # isnan doesn't work on non-numeric elements 

717 if np.issubdtype(type(el), np.number) and np.isnan(el): 

718 contains_nan = True 

719 break 

720 else: 

721 # Only `object` and `inexact` arrays can have NaNs 

722 contains_nan = False 

723 

724 if contains_nan and nan_policy == 'raise': 

725 raise ValueError("The input contains nan values") 

726 

727 return contains_nan, nan_policy 

728 

729 

730def _rename_parameter(old_name, new_name, dep_version=None): 

731 """ 

732 Generate decorator for backward-compatible keyword renaming. 

733 

734 Apply the decorator generated by `_rename_parameter` to functions with a 

735 recently renamed parameter to maintain backward-compatibility. 

736 

737 After decoration, the function behaves as follows: 

738 If only the new parameter is passed into the function, behave as usual. 

739 If only the old parameter is passed into the function (as a keyword), raise 

740 a DeprecationWarning if `dep_version` is provided, and behave as usual 

741 otherwise. 

742 If both old and new parameters are passed into the function, raise a 

743 DeprecationWarning if `dep_version` is provided, and raise the appropriate 

744 TypeError (function got multiple values for argument). 

745 

746 Parameters 

747 ---------- 

748 old_name : str 

749 Old name of parameter 

750 new_name : str 

751 New name of parameter 

752 dep_version : str, optional 

753 Version of SciPy in which old parameter was deprecated in the format 

754 'X.Y.Z'. If supplied, the deprecation message will indicate that 

755 support for the old parameter will be removed in version 'X.Y+2.Z' 

756 

757 Notes 

758 ----- 

759 Untested with functions that accept *args. Probably won't work as written. 

760 

761 """ 

762 def decorator(fun): 

763 @functools.wraps(fun) 

764 def wrapper(*args, **kwargs): 

765 if old_name in kwargs: 

766 if dep_version: 

767 end_version = dep_version.split('.') 

768 end_version[1] = str(int(end_version[1]) + 2) 

769 end_version = '.'.join(end_version) 

770 message = (f"Use of keyword argument `{old_name}` is " 

771 f"deprecated and replaced by `{new_name}`. " 

772 f"Support for `{old_name}` will be removed " 

773 f"in SciPy {end_version}.") 

774 warnings.warn(message, DeprecationWarning, stacklevel=2) 

775 if new_name in kwargs: 

776 message = (f"{fun.__name__}() got multiple values for " 

777 f"argument now known as `{new_name}`") 

778 raise TypeError(message) 

779 kwargs[new_name] = kwargs.pop(old_name) 

780 return fun(*args, **kwargs) 

781 return wrapper 

782 return decorator 

783 

784 

785def _rng_spawn(rng, n_children): 

786 # spawns independent RNGs from a parent RNG 

787 bg = rng._bit_generator 

788 ss = bg._seed_seq 

789 child_rngs = [np.random.Generator(type(bg)(child_ss)) 

790 for child_ss in ss.spawn(n_children)] 

791 return child_rngs 

792 

793 

794def _get_nan(*data): 

795 # Get NaN of appropriate dtype for data 

796 data = [np.asarray(item) for item in data] 

797 try: 

798 dtype = np.result_type(*data, np.half) # must be a float16 at least 

799 except DTypePromotionError: 

800 # fallback to float64 

801 return np.array(np.nan, dtype=np.float64)[()] 

802 return np.array(np.nan, dtype=dtype)[()] 

803 

804 

805def normalize_axis_index(axis, ndim): 

806 # Check if `axis` is in the correct range and normalize it 

807 if axis < -ndim or axis >= ndim: 

808 msg = f"axis {axis} is out of bounds for array of dimension {ndim}" 

809 raise AxisError(msg) 

810 

811 if axis < 0: 

812 axis = axis + ndim 

813 return axis 

814 

815 

816def _call_callback_maybe_halt(callback, res): 

817 """Call wrapped callback; return True if algorithm should stop. 

818 

819 Parameters 

820 ---------- 

821 callback : callable or None 

822 A user-provided callback wrapped with `_wrap_callback` 

823 res : OptimizeResult 

824 Information about the current iterate 

825 

826 Returns 

827 ------- 

828 halt : bool 

829 True if minimization should stop 

830 

831 """ 

832 if callback is None: 

833 return False 

834 try: 

835 callback(res) 

836 return False 

837 except StopIteration: 

838 callback.stop_iteration = True 

839 return True 

840 

841 

842class _RichResult(dict): 

843 """ Container for multiple outputs with pretty-printing """ 

844 def __getattr__(self, name): 

845 try: 

846 return self[name] 

847 except KeyError as e: 

848 raise AttributeError(name) from e 

849 

850 __setattr__ = dict.__setitem__ 

851 __delattr__ = dict.__delitem__ 

852 

853 def __repr__(self): 

854 order_keys = ['message', 'success', 'status', 'fun', 'funl', 'x', 'xl', 

855 'col_ind', 'nit', 'lower', 'upper', 'eqlin', 'ineqlin', 

856 'converged', 'flag', 'function_calls', 'iterations', 

857 'root'] 

858 order_keys = getattr(self, '_order_keys', order_keys) 

859 # 'slack', 'con' are redundant with residuals 

860 # 'crossover_nit' is probably not interesting to most users 

861 omit_keys = {'slack', 'con', 'crossover_nit', '_order_keys'} 

862 

863 def key(item): 

864 try: 

865 return order_keys.index(item[0].lower()) 

866 except ValueError: # item not in list 

867 return np.inf 

868 

869 def omit_redundant(items): 

870 for item in items: 

871 if item[0] in omit_keys: 

872 continue 

873 yield item 

874 

875 def item_sorter(d): 

876 return sorted(omit_redundant(d.items()), key=key) 

877 

878 if self.keys(): 

879 return _dict_formatter(self, sorter=item_sorter) 

880 else: 

881 return self.__class__.__name__ + "()" 

882 

883 def __dir__(self): 

884 return list(self.keys()) 

885 

886 

887def _indenter(s, n=0): 

888 """ 

889 Ensures that lines after the first are indented by the specified amount 

890 """ 

891 split = s.split("\n") 

892 indent = " "*n 

893 return ("\n" + indent).join(split) 

894 

895 

896def _float_formatter_10(x): 

897 """ 

898 Returns a string representation of a float with exactly ten characters 

899 """ 

900 if np.isposinf(x): 

901 return " inf" 

902 elif np.isneginf(x): 

903 return " -inf" 

904 elif np.isnan(x): 

905 return " nan" 

906 return np.format_float_scientific(x, precision=3, pad_left=2, unique=False) 

907 

908 

909def _dict_formatter(d, n=0, mplus=1, sorter=None): 

910 """ 

911 Pretty printer for dictionaries 

912 

913 `n` keeps track of the starting indentation; 

914 lines are indented by this much after a line break. 

915 `mplus` is additional left padding applied to keys 

916 """ 

917 if isinstance(d, dict): 

918 m = max(map(len, list(d.keys()))) + mplus # width to print keys 

919 s = '\n'.join([k.rjust(m) + ': ' + # right justified, width m 

920 _indenter(_dict_formatter(v, m+n+2, 0, sorter), m+2) 

921 for k, v in sorter(d)]) # +2 for ': ' 

922 else: 

923 # By default, NumPy arrays print with linewidth=76. `n` is 

924 # the indent at which a line begins printing, so it is subtracted 

925 # from the default to avoid exceeding 76 characters total. 

926 # `edgeitems` is the number of elements to include before and after 

927 # ellipses when arrays are not shown in full. 

928 # `threshold` is the maximum number of elements for which an 

929 # array is shown in full. 

930 # These values tend to work well for use with OptimizeResult. 

931 with np.printoptions(linewidth=76-n, edgeitems=2, threshold=12, 

932 formatter={'float_kind': _float_formatter_10}): 

933 s = str(d) 

934 return s