Coverage for /usr/lib/python3/dist-packages/sympy/polys/rootoftools.py: 21%

636 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Implementation of RootOf class and related tools. """ 

2 

3 

4from sympy.core.basic import Basic 

5from sympy.core import (S, Expr, Integer, Float, I, oo, Add, Lambda, 

6 symbols, sympify, Rational, Dummy) 

7from sympy.core.cache import cacheit 

8from sympy.core.relational import is_le 

9from sympy.core.sorting import ordered 

10from sympy.polys.domains import QQ 

11from sympy.polys.polyerrors import ( 

12 MultivariatePolynomialError, 

13 GeneratorsNeeded, 

14 PolynomialError, 

15 DomainError) 

16from sympy.polys.polyfuncs import symmetrize, viete 

17from sympy.polys.polyroots import ( 

18 roots_linear, roots_quadratic, roots_binomial, 

19 preprocess_roots, roots) 

20from sympy.polys.polytools import Poly, PurePoly, factor 

21from sympy.polys.rationaltools import together 

22from sympy.polys.rootisolation import ( 

23 dup_isolate_complex_roots_sqf, 

24 dup_isolate_real_roots_sqf) 

25from sympy.utilities import lambdify, public, sift, numbered_symbols 

26 

27from mpmath import mpf, mpc, findroot, workprec 

28from mpmath.libmp.libmpf import dps_to_prec, prec_to_dps 

29from sympy.multipledispatch import dispatch 

30from itertools import chain 

31 

32 

33__all__ = ['CRootOf'] 

34 

35 

36 

37class _pure_key_dict: 

38 """A minimal dictionary that makes sure that the key is a 

39 univariate PurePoly instance. 

40 

41 Examples 

42 ======== 

43 

44 Only the following actions are guaranteed: 

45 

46 >>> from sympy.polys.rootoftools import _pure_key_dict 

47 >>> from sympy import PurePoly 

48 >>> from sympy.abc import x, y 

49 

50 1) creation 

51 

52 >>> P = _pure_key_dict() 

53 

54 2) assignment for a PurePoly or univariate polynomial 

55 

56 >>> P[x] = 1 

57 >>> P[PurePoly(x - y, x)] = 2 

58 

59 3) retrieval based on PurePoly key comparison (use this 

60 instead of the get method) 

61 

62 >>> P[y] 

63 1 

64 

65 4) KeyError when trying to retrieve a nonexisting key 

66 

67 >>> P[y + 1] 

68 Traceback (most recent call last): 

69 ... 

70 KeyError: PurePoly(y + 1, y, domain='ZZ') 

71 

72 5) ability to query with ``in`` 

73 

74 >>> x + 1 in P 

75 False 

76 

77 NOTE: this is a *not* a dictionary. It is a very basic object 

78 for internal use that makes sure to always address its cache 

79 via PurePoly instances. It does not, for example, implement 

80 ``get`` or ``setdefault``. 

81 """ 

82 def __init__(self): 

83 self._dict = {} 

84 

85 def __getitem__(self, k): 

86 if not isinstance(k, PurePoly): 

87 if not (isinstance(k, Expr) and len(k.free_symbols) == 1): 

88 raise KeyError 

89 k = PurePoly(k, expand=False) 

90 return self._dict[k] 

91 

92 def __setitem__(self, k, v): 

93 if not isinstance(k, PurePoly): 

94 if not (isinstance(k, Expr) and len(k.free_symbols) == 1): 

95 raise ValueError('expecting univariate expression') 

96 k = PurePoly(k, expand=False) 

97 self._dict[k] = v 

98 

99 def __contains__(self, k): 

100 try: 

101 self[k] 

102 return True 

103 except KeyError: 

104 return False 

105 

106_reals_cache = _pure_key_dict() 

107_complexes_cache = _pure_key_dict() 

108 

109 

110def _pure_factors(poly): 

111 _, factors = poly.factor_list() 

112 return [(PurePoly(f, expand=False), m) for f, m in factors] 

113 

114 

115def _imag_count_of_factor(f): 

116 """Return the number of imaginary roots for irreducible 

117 univariate polynomial ``f``. 

118 """ 

119 terms = [(i, j) for (i,), j in f.terms()] 

120 if any(i % 2 for i, j in terms): 

121 return 0 

122 # update signs 

123 even = [(i, I**i*j) for i, j in terms] 

124 even = Poly.from_dict(dict(even), Dummy('x')) 

125 return int(even.count_roots(-oo, oo)) 

126 

127 

128@public 

129def rootof(f, x, index=None, radicals=True, expand=True): 

130 """An indexed root of a univariate polynomial. 

131 

132 Returns either a :obj:`ComplexRootOf` object or an explicit 

133 expression involving radicals. 

134 

135 Parameters 

136 ========== 

137 

138 f : Expr 

139 Univariate polynomial. 

140 x : Symbol, optional 

141 Generator for ``f``. 

142 index : int or Integer 

143 radicals : bool 

144 Return a radical expression if possible. 

145 expand : bool 

146 Expand ``f``. 

147 """ 

148 return CRootOf(f, x, index=index, radicals=radicals, expand=expand) 

149 

150 

151@public 

152class RootOf(Expr): 

153 """Represents a root of a univariate polynomial. 

154 

155 Base class for roots of different kinds of polynomials. 

156 Only complex roots are currently supported. 

157 """ 

158 

159 __slots__ = ('poly',) 

160 

161 def __new__(cls, f, x, index=None, radicals=True, expand=True): 

162 """Construct a new ``CRootOf`` object for ``k``-th root of ``f``.""" 

163 return rootof(f, x, index=index, radicals=radicals, expand=expand) 

164 

165@public 

166class ComplexRootOf(RootOf): 

167 """Represents an indexed complex root of a polynomial. 

168 

169 Roots of a univariate polynomial separated into disjoint 

170 real or complex intervals and indexed in a fixed order: 

171 

172 * real roots come first and are sorted in increasing order; 

173 * complex roots come next and are sorted primarily by increasing 

174 real part, secondarily by increasing imaginary part. 

175 

176 Currently only rational coefficients are allowed. 

177 Can be imported as ``CRootOf``. To avoid confusion, the 

178 generator must be a Symbol. 

179 

180 

181 Examples 

182 ======== 

183 

184 >>> from sympy import CRootOf, rootof 

185 >>> from sympy.abc import x 

186 

187 CRootOf is a way to reference a particular root of a 

188 polynomial. If there is a rational root, it will be returned: 

189 

190 >>> CRootOf.clear_cache() # for doctest reproducibility 

191 >>> CRootOf(x**2 - 4, 0) 

192 -2 

193 

194 Whether roots involving radicals are returned or not 

195 depends on whether the ``radicals`` flag is true (which is 

196 set to True with rootof): 

197 

198 >>> CRootOf(x**2 - 3, 0) 

199 CRootOf(x**2 - 3, 0) 

200 >>> CRootOf(x**2 - 3, 0, radicals=True) 

201 -sqrt(3) 

202 >>> rootof(x**2 - 3, 0) 

203 -sqrt(3) 

204 

205 The following cannot be expressed in terms of radicals: 

206 

207 >>> r = rootof(4*x**5 + 16*x**3 + 12*x**2 + 7, 0); r 

208 CRootOf(4*x**5 + 16*x**3 + 12*x**2 + 7, 0) 

209 

210 The root bounds can be seen, however, and they are used by the 

211 evaluation methods to get numerical approximations for the root. 

212 

213 >>> interval = r._get_interval(); interval 

214 (-1, 0) 

215 >>> r.evalf(2) 

216 -0.98 

217 

218 The evalf method refines the width of the root bounds until it 

219 guarantees that any decimal approximation within those bounds 

220 will satisfy the desired precision. It then stores the refined 

221 interval so subsequent requests at or below the requested 

222 precision will not have to recompute the root bounds and will 

223 return very quickly. 

224 

225 Before evaluation above, the interval was 

226 

227 >>> interval 

228 (-1, 0) 

229 

230 After evaluation it is now 

231 

232 >>> r._get_interval() # doctest: +SKIP 

233 (-165/169, -206/211) 

234 

235 To reset all intervals for a given polynomial, the :meth:`_reset` method 

236 can be called from any CRootOf instance of the polynomial: 

237 

238 >>> r._reset() 

239 >>> r._get_interval() 

240 (-1, 0) 

241 

242 The :meth:`eval_approx` method will also find the root to a given 

243 precision but the interval is not modified unless the search 

244 for the root fails to converge within the root bounds. And 

245 the secant method is used to find the root. (The ``evalf`` 

246 method uses bisection and will always update the interval.) 

247 

248 >>> r.eval_approx(2) 

249 -0.98 

250 

251 The interval needed to be slightly updated to find that root: 

252 

253 >>> r._get_interval() 

254 (-1, -1/2) 

255 

256 The ``evalf_rational`` will compute a rational approximation 

257 of the root to the desired accuracy or precision. 

258 

259 >>> r.eval_rational(n=2) 

260 -69629/71318 

261 

262 >>> t = CRootOf(x**3 + 10*x + 1, 1) 

263 >>> t.eval_rational(1e-1) 

264 15/256 - 805*I/256 

265 >>> t.eval_rational(1e-1, 1e-4) 

266 3275/65536 - 414645*I/131072 

267 >>> t.eval_rational(1e-4, 1e-4) 

268 6545/131072 - 414645*I/131072 

269 >>> t.eval_rational(n=2) 

270 104755/2097152 - 6634255*I/2097152 

271 

272 Notes 

273 ===== 

274 

275 Although a PurePoly can be constructed from a non-symbol generator 

276 RootOf instances of non-symbols are disallowed to avoid confusion 

277 over what root is being represented. 

278 

279 >>> from sympy import exp, PurePoly 

280 >>> PurePoly(x) == PurePoly(exp(x)) 

281 True 

282 >>> CRootOf(x - 1, 0) 

283 1 

284 >>> CRootOf(exp(x) - 1, 0) # would correspond to x == 0 

285 Traceback (most recent call last): 

286 ... 

287 sympy.polys.polyerrors.PolynomialError: generator must be a Symbol 

288 

289 See Also 

290 ======== 

291 

292 eval_approx 

293 eval_rational 

294 

295 """ 

296 

297 __slots__ = ('index',) 

298 is_complex = True 

299 is_number = True 

300 is_finite = True 

301 

302 def __new__(cls, f, x, index=None, radicals=False, expand=True): 

303 """ Construct an indexed complex root of a polynomial. 

304 

305 See ``rootof`` for the parameters. 

306 

307 The default value of ``radicals`` is ``False`` to satisfy 

308 ``eval(srepr(expr) == expr``. 

309 """ 

310 x = sympify(x) 

311 

312 if index is None and x.is_Integer: 

313 x, index = None, x 

314 else: 

315 index = sympify(index) 

316 

317 if index is not None and index.is_Integer: 

318 index = int(index) 

319 else: 

320 raise ValueError("expected an integer root index, got %s" % index) 

321 

322 poly = PurePoly(f, x, greedy=False, expand=expand) 

323 

324 if not poly.is_univariate: 

325 raise PolynomialError("only univariate polynomials are allowed") 

326 

327 if not poly.gen.is_Symbol: 

328 # PurePoly(sin(x) + 1) == PurePoly(x + 1) but the roots of 

329 # x for each are not the same: issue 8617 

330 raise PolynomialError("generator must be a Symbol") 

331 

332 degree = poly.degree() 

333 

334 if degree <= 0: 

335 raise PolynomialError("Cannot construct CRootOf object for %s" % f) 

336 

337 if index < -degree or index >= degree: 

338 raise IndexError("root index out of [%d, %d] range, got %d" % 

339 (-degree, degree - 1, index)) 

340 elif index < 0: 

341 index += degree 

342 

343 dom = poly.get_domain() 

344 

345 if not dom.is_Exact: 

346 poly = poly.to_exact() 

347 

348 roots = cls._roots_trivial(poly, radicals) 

349 

350 if roots is not None: 

351 return roots[index] 

352 

353 coeff, poly = preprocess_roots(poly) 

354 dom = poly.get_domain() 

355 

356 if not dom.is_ZZ: 

357 raise NotImplementedError("CRootOf is not supported over %s" % dom) 

358 

359 root = cls._indexed_root(poly, index, lazy=True) 

360 return coeff * cls._postprocess_root(root, radicals) 

361 

362 @classmethod 

363 def _new(cls, poly, index): 

364 """Construct new ``CRootOf`` object from raw data. """ 

365 obj = Expr.__new__(cls) 

366 

367 obj.poly = PurePoly(poly) 

368 obj.index = index 

369 

370 try: 

371 _reals_cache[obj.poly] = _reals_cache[poly] 

372 _complexes_cache[obj.poly] = _complexes_cache[poly] 

373 except KeyError: 

374 pass 

375 

376 return obj 

377 

378 def _hashable_content(self): 

379 return (self.poly, self.index) 

380 

381 @property 

382 def expr(self): 

383 return self.poly.as_expr() 

384 

385 @property 

386 def args(self): 

387 return (self.expr, Integer(self.index)) 

388 

389 @property 

390 def free_symbols(self): 

391 # CRootOf currently only works with univariate expressions 

392 # whose poly attribute should be a PurePoly with no free 

393 # symbols 

394 return set() 

395 

396 def _eval_is_real(self): 

397 """Return ``True`` if the root is real. """ 

398 self._ensure_reals_init() 

399 return self.index < len(_reals_cache[self.poly]) 

400 

401 def _eval_is_imaginary(self): 

402 """Return ``True`` if the root is imaginary. """ 

403 self._ensure_reals_init() 

404 if self.index >= len(_reals_cache[self.poly]): 

405 ivl = self._get_interval() 

406 return ivl.ax*ivl.bx <= 0 # all others are on one side or the other 

407 return False # XXX is this necessary? 

408 

409 @classmethod 

410 def real_roots(cls, poly, radicals=True): 

411 """Get real roots of a polynomial. """ 

412 return cls._get_roots("_real_roots", poly, radicals) 

413 

414 @classmethod 

415 def all_roots(cls, poly, radicals=True): 

416 """Get real and complex roots of a polynomial. """ 

417 return cls._get_roots("_all_roots", poly, radicals) 

418 

419 @classmethod 

420 def _get_reals_sqf(cls, currentfactor, use_cache=True): 

421 """Get real root isolating intervals for a square-free factor.""" 

422 if use_cache and currentfactor in _reals_cache: 

423 real_part = _reals_cache[currentfactor] 

424 else: 

425 _reals_cache[currentfactor] = real_part = \ 

426 dup_isolate_real_roots_sqf( 

427 currentfactor.rep.rep, currentfactor.rep.dom, blackbox=True) 

428 

429 return real_part 

430 

431 @classmethod 

432 def _get_complexes_sqf(cls, currentfactor, use_cache=True): 

433 """Get complex root isolating intervals for a square-free factor.""" 

434 if use_cache and currentfactor in _complexes_cache: 

435 complex_part = _complexes_cache[currentfactor] 

436 else: 

437 _complexes_cache[currentfactor] = complex_part = \ 

438 dup_isolate_complex_roots_sqf( 

439 currentfactor.rep.rep, currentfactor.rep.dom, blackbox=True) 

440 return complex_part 

441 

442 @classmethod 

443 def _get_reals(cls, factors, use_cache=True): 

444 """Compute real root isolating intervals for a list of factors. """ 

445 reals = [] 

446 

447 for currentfactor, k in factors: 

448 try: 

449 if not use_cache: 

450 raise KeyError 

451 r = _reals_cache[currentfactor] 

452 reals.extend([(i, currentfactor, k) for i in r]) 

453 except KeyError: 

454 real_part = cls._get_reals_sqf(currentfactor, use_cache) 

455 new = [(root, currentfactor, k) for root in real_part] 

456 reals.extend(new) 

457 

458 reals = cls._reals_sorted(reals) 

459 return reals 

460 

461 @classmethod 

462 def _get_complexes(cls, factors, use_cache=True): 

463 """Compute complex root isolating intervals for a list of factors. """ 

464 complexes = [] 

465 

466 for currentfactor, k in ordered(factors): 

467 try: 

468 if not use_cache: 

469 raise KeyError 

470 c = _complexes_cache[currentfactor] 

471 complexes.extend([(i, currentfactor, k) for i in c]) 

472 except KeyError: 

473 complex_part = cls._get_complexes_sqf(currentfactor, use_cache) 

474 new = [(root, currentfactor, k) for root in complex_part] 

475 complexes.extend(new) 

476 

477 complexes = cls._complexes_sorted(complexes) 

478 return complexes 

479 

480 @classmethod 

481 def _reals_sorted(cls, reals): 

482 """Make real isolating intervals disjoint and sort roots. """ 

483 cache = {} 

484 

485 for i, (u, f, k) in enumerate(reals): 

486 for j, (v, g, m) in enumerate(reals[i + 1:]): 

487 u, v = u.refine_disjoint(v) 

488 reals[i + j + 1] = (v, g, m) 

489 

490 reals[i] = (u, f, k) 

491 

492 reals = sorted(reals, key=lambda r: r[0].a) 

493 

494 for root, currentfactor, _ in reals: 

495 if currentfactor in cache: 

496 cache[currentfactor].append(root) 

497 else: 

498 cache[currentfactor] = [root] 

499 

500 for currentfactor, root in cache.items(): 

501 _reals_cache[currentfactor] = root 

502 

503 return reals 

504 

505 @classmethod 

506 def _refine_imaginary(cls, complexes): 

507 sifted = sift(complexes, lambda c: c[1]) 

508 complexes = [] 

509 for f in ordered(sifted): 

510 nimag = _imag_count_of_factor(f) 

511 if nimag == 0: 

512 # refine until xbounds are neg or pos 

513 for u, f, k in sifted[f]: 

514 while u.ax*u.bx <= 0: 

515 u = u._inner_refine() 

516 complexes.append((u, f, k)) 

517 else: 

518 # refine until all but nimag xbounds are neg or pos 

519 potential_imag = list(range(len(sifted[f]))) 

520 while True: 

521 assert len(potential_imag) > 1 

522 for i in list(potential_imag): 

523 u, f, k = sifted[f][i] 

524 if u.ax*u.bx > 0: 

525 potential_imag.remove(i) 

526 elif u.ax != u.bx: 

527 u = u._inner_refine() 

528 sifted[f][i] = u, f, k 

529 if len(potential_imag) == nimag: 

530 break 

531 complexes.extend(sifted[f]) 

532 return complexes 

533 

534 @classmethod 

535 def _refine_complexes(cls, complexes): 

536 """return complexes such that no bounding rectangles of non-conjugate 

537 roots would intersect. In addition, assure that neither ay nor by is 

538 0 to guarantee that non-real roots are distinct from real roots in 

539 terms of the y-bounds. 

540 """ 

541 # get the intervals pairwise-disjoint. 

542 # If rectangles were drawn around the coordinates of the bounding 

543 # rectangles, no rectangles would intersect after this procedure. 

544 for i, (u, f, k) in enumerate(complexes): 

545 for j, (v, g, m) in enumerate(complexes[i + 1:]): 

546 u, v = u.refine_disjoint(v) 

547 complexes[i + j + 1] = (v, g, m) 

548 

549 complexes[i] = (u, f, k) 

550 

551 # refine until the x-bounds are unambiguously positive or negative 

552 # for non-imaginary roots 

553 complexes = cls._refine_imaginary(complexes) 

554 

555 # make sure that all y bounds are off the real axis 

556 # and on the same side of the axis 

557 for i, (u, f, k) in enumerate(complexes): 

558 while u.ay*u.by <= 0: 

559 u = u.refine() 

560 complexes[i] = u, f, k 

561 return complexes 

562 

563 @classmethod 

564 def _complexes_sorted(cls, complexes): 

565 """Make complex isolating intervals disjoint and sort roots. """ 

566 complexes = cls._refine_complexes(complexes) 

567 # XXX don't sort until you are sure that it is compatible 

568 # with the indexing method but assert that the desired state 

569 # is not broken 

570 C, F = 0, 1 # location of ComplexInterval and factor 

571 fs = {i[F] for i in complexes} 

572 for i in range(1, len(complexes)): 

573 if complexes[i][F] != complexes[i - 1][F]: 

574 # if this fails the factors of a root were not 

575 # contiguous because a discontinuity should only 

576 # happen once 

577 fs.remove(complexes[i - 1][F]) 

578 for i, cmplx in enumerate(complexes): 

579 # negative im part (conj=True) comes before 

580 # positive im part (conj=False) 

581 assert cmplx[C].conj is (i % 2 == 0) 

582 

583 # update cache 

584 cache = {} 

585 # -- collate 

586 for root, currentfactor, _ in complexes: 

587 cache.setdefault(currentfactor, []).append(root) 

588 # -- store 

589 for currentfactor, root in cache.items(): 

590 _complexes_cache[currentfactor] = root 

591 

592 return complexes 

593 

594 @classmethod 

595 def _reals_index(cls, reals, index): 

596 """ 

597 Map initial real root index to an index in a factor where 

598 the root belongs. 

599 """ 

600 i = 0 

601 

602 for j, (_, currentfactor, k) in enumerate(reals): 

603 if index < i + k: 

604 poly, index = currentfactor, 0 

605 

606 for _, currentfactor, _ in reals[:j]: 

607 if currentfactor == poly: 

608 index += 1 

609 

610 return poly, index 

611 else: 

612 i += k 

613 

614 @classmethod 

615 def _complexes_index(cls, complexes, index): 

616 """ 

617 Map initial complex root index to an index in a factor where 

618 the root belongs. 

619 """ 

620 i = 0 

621 for j, (_, currentfactor, k) in enumerate(complexes): 

622 if index < i + k: 

623 poly, index = currentfactor, 0 

624 

625 for _, currentfactor, _ in complexes[:j]: 

626 if currentfactor == poly: 

627 index += 1 

628 

629 index += len(_reals_cache[poly]) 

630 

631 return poly, index 

632 else: 

633 i += k 

634 

635 @classmethod 

636 def _count_roots(cls, roots): 

637 """Count the number of real or complex roots with multiplicities.""" 

638 return sum([k for _, _, k in roots]) 

639 

640 @classmethod 

641 def _indexed_root(cls, poly, index, lazy=False): 

642 """Get a root of a composite polynomial by index. """ 

643 factors = _pure_factors(poly) 

644 

645 # If the given poly is already irreducible, then the index does not 

646 # need to be adjusted, and we can postpone the heavy lifting of 

647 # computing and refining isolating intervals until that is needed. 

648 if lazy and len(factors) == 1 and factors[0][1] == 1: 

649 return poly, index 

650 

651 reals = cls._get_reals(factors) 

652 reals_count = cls._count_roots(reals) 

653 

654 if index < reals_count: 

655 return cls._reals_index(reals, index) 

656 else: 

657 complexes = cls._get_complexes(factors) 

658 return cls._complexes_index(complexes, index - reals_count) 

659 

660 def _ensure_reals_init(self): 

661 """Ensure that our poly has entries in the reals cache. """ 

662 if self.poly not in _reals_cache: 

663 self._indexed_root(self.poly, self.index) 

664 

665 def _ensure_complexes_init(self): 

666 """Ensure that our poly has entries in the complexes cache. """ 

667 if self.poly not in _complexes_cache: 

668 self._indexed_root(self.poly, self.index) 

669 

670 @classmethod 

671 def _real_roots(cls, poly): 

672 """Get real roots of a composite polynomial. """ 

673 factors = _pure_factors(poly) 

674 

675 reals = cls._get_reals(factors) 

676 reals_count = cls._count_roots(reals) 

677 

678 roots = [] 

679 

680 for index in range(0, reals_count): 

681 roots.append(cls._reals_index(reals, index)) 

682 

683 return roots 

684 

685 def _reset(self): 

686 """ 

687 Reset all intervals 

688 """ 

689 self._all_roots(self.poly, use_cache=False) 

690 

691 @classmethod 

692 def _all_roots(cls, poly, use_cache=True): 

693 """Get real and complex roots of a composite polynomial. """ 

694 factors = _pure_factors(poly) 

695 

696 reals = cls._get_reals(factors, use_cache=use_cache) 

697 reals_count = cls._count_roots(reals) 

698 

699 roots = [] 

700 

701 for index in range(0, reals_count): 

702 roots.append(cls._reals_index(reals, index)) 

703 

704 complexes = cls._get_complexes(factors, use_cache=use_cache) 

705 complexes_count = cls._count_roots(complexes) 

706 

707 for index in range(0, complexes_count): 

708 roots.append(cls._complexes_index(complexes, index)) 

709 

710 return roots 

711 

712 @classmethod 

713 @cacheit 

714 def _roots_trivial(cls, poly, radicals): 

715 """Compute roots in linear, quadratic and binomial cases. """ 

716 if poly.degree() == 1: 

717 return roots_linear(poly) 

718 

719 if not radicals: 

720 return None 

721 

722 if poly.degree() == 2: 

723 return roots_quadratic(poly) 

724 elif poly.length() == 2 and poly.TC(): 

725 return roots_binomial(poly) 

726 else: 

727 return None 

728 

729 @classmethod 

730 def _preprocess_roots(cls, poly): 

731 """Take heroic measures to make ``poly`` compatible with ``CRootOf``.""" 

732 dom = poly.get_domain() 

733 

734 if not dom.is_Exact: 

735 poly = poly.to_exact() 

736 

737 coeff, poly = preprocess_roots(poly) 

738 dom = poly.get_domain() 

739 

740 if not dom.is_ZZ: 

741 raise NotImplementedError( 

742 "sorted roots not supported over %s" % dom) 

743 

744 return coeff, poly 

745 

746 @classmethod 

747 def _postprocess_root(cls, root, radicals): 

748 """Return the root if it is trivial or a ``CRootOf`` object. """ 

749 poly, index = root 

750 roots = cls._roots_trivial(poly, radicals) 

751 

752 if roots is not None: 

753 return roots[index] 

754 else: 

755 return cls._new(poly, index) 

756 

757 @classmethod 

758 def _get_roots(cls, method, poly, radicals): 

759 """Return postprocessed roots of specified kind. """ 

760 if not poly.is_univariate: 

761 raise PolynomialError("only univariate polynomials are allowed") 

762 # get rid of gen and it's free symbol 

763 d = Dummy() 

764 poly = poly.subs(poly.gen, d) 

765 x = symbols('x') 

766 # see what others are left and select x or a numbered x 

767 # that doesn't clash 

768 free_names = {str(i) for i in poly.free_symbols} 

769 for x in chain((symbols('x'),), numbered_symbols('x')): 

770 if x.name not in free_names: 

771 poly = poly.xreplace({d: x}) 

772 break 

773 coeff, poly = cls._preprocess_roots(poly) 

774 roots = [] 

775 

776 for root in getattr(cls, method)(poly): 

777 roots.append(coeff*cls._postprocess_root(root, radicals)) 

778 return roots 

779 

780 @classmethod 

781 def clear_cache(cls): 

782 """Reset cache for reals and complexes. 

783 

784 The intervals used to approximate a root instance are updated 

785 as needed. When a request is made to see the intervals, the 

786 most current values are shown. `clear_cache` will reset all 

787 CRootOf instances back to their original state. 

788 

789 See Also 

790 ======== 

791 

792 _reset 

793 """ 

794 global _reals_cache, _complexes_cache 

795 _reals_cache = _pure_key_dict() 

796 _complexes_cache = _pure_key_dict() 

797 

798 def _get_interval(self): 

799 """Internal function for retrieving isolation interval from cache. """ 

800 self._ensure_reals_init() 

801 if self.is_real: 

802 return _reals_cache[self.poly][self.index] 

803 else: 

804 reals_count = len(_reals_cache[self.poly]) 

805 self._ensure_complexes_init() 

806 return _complexes_cache[self.poly][self.index - reals_count] 

807 

808 def _set_interval(self, interval): 

809 """Internal function for updating isolation interval in cache. """ 

810 self._ensure_reals_init() 

811 if self.is_real: 

812 _reals_cache[self.poly][self.index] = interval 

813 else: 

814 reals_count = len(_reals_cache[self.poly]) 

815 self._ensure_complexes_init() 

816 _complexes_cache[self.poly][self.index - reals_count] = interval 

817 

818 def _eval_subs(self, old, new): 

819 # don't allow subs to change anything 

820 return self 

821 

822 def _eval_conjugate(self): 

823 if self.is_real: 

824 return self 

825 expr, i = self.args 

826 return self.func(expr, i + (1 if self._get_interval().conj else -1)) 

827 

828 def eval_approx(self, n, return_mpmath=False): 

829 """Evaluate this complex root to the given precision. 

830 

831 This uses secant method and root bounds are used to both 

832 generate an initial guess and to check that the root 

833 returned is valid. If ever the method converges outside the 

834 root bounds, the bounds will be made smaller and updated. 

835 """ 

836 prec = dps_to_prec(n) 

837 with workprec(prec): 

838 g = self.poly.gen 

839 if not g.is_Symbol: 

840 d = Dummy('x') 

841 if self.is_imaginary: 

842 d *= I 

843 func = lambdify(d, self.expr.subs(g, d)) 

844 else: 

845 expr = self.expr 

846 if self.is_imaginary: 

847 expr = self.expr.subs(g, I*g) 

848 func = lambdify(g, expr) 

849 

850 interval = self._get_interval() 

851 while True: 

852 if self.is_real: 

853 a = mpf(str(interval.a)) 

854 b = mpf(str(interval.b)) 

855 if a == b: 

856 root = a 

857 break 

858 x0 = mpf(str(interval.center)) 

859 x1 = x0 + mpf(str(interval.dx))/4 

860 elif self.is_imaginary: 

861 a = mpf(str(interval.ay)) 

862 b = mpf(str(interval.by)) 

863 if a == b: 

864 root = mpc(mpf('0'), a) 

865 break 

866 x0 = mpf(str(interval.center[1])) 

867 x1 = x0 + mpf(str(interval.dy))/4 

868 else: 

869 ax = mpf(str(interval.ax)) 

870 bx = mpf(str(interval.bx)) 

871 ay = mpf(str(interval.ay)) 

872 by = mpf(str(interval.by)) 

873 if ax == bx and ay == by: 

874 root = mpc(ax, ay) 

875 break 

876 x0 = mpc(*map(str, interval.center)) 

877 x1 = x0 + mpc(*map(str, (interval.dx, interval.dy)))/4 

878 try: 

879 # without a tolerance, this will return when (to within 

880 # the given precision) x_i == x_{i-1} 

881 root = findroot(func, (x0, x1)) 

882 # If the (real or complex) root is not in the 'interval', 

883 # then keep refining the interval. This happens if findroot 

884 # accidentally finds a different root outside of this 

885 # interval because our initial estimate 'x0' was not close 

886 # enough. It is also possible that the secant method will 

887 # get trapped by a max/min in the interval; the root 

888 # verification by findroot will raise a ValueError in this 

889 # case and the interval will then be tightened -- and 

890 # eventually the root will be found. 

891 # 

892 # It is also possible that findroot will not have any 

893 # successful iterations to process (in which case it 

894 # will fail to initialize a variable that is tested 

895 # after the iterations and raise an UnboundLocalError). 

896 if self.is_real or self.is_imaginary: 

897 if not bool(root.imag) == self.is_real and ( 

898 a <= root <= b): 

899 if self.is_imaginary: 

900 root = mpc(mpf('0'), root.real) 

901 break 

902 elif (ax <= root.real <= bx and ay <= root.imag <= by): 

903 break 

904 except (UnboundLocalError, ValueError): 

905 pass 

906 interval = interval.refine() 

907 

908 # update the interval so we at least (for this precision or 

909 # less) don't have much work to do to recompute the root 

910 self._set_interval(interval) 

911 if return_mpmath: 

912 return root 

913 return (Float._new(root.real._mpf_, prec) + 

914 I*Float._new(root.imag._mpf_, prec)) 

915 

916 def _eval_evalf(self, prec, **kwargs): 

917 """Evaluate this complex root to the given precision.""" 

918 # all kwargs are ignored 

919 return self.eval_rational(n=prec_to_dps(prec))._evalf(prec) 

920 

921 def eval_rational(self, dx=None, dy=None, n=15): 

922 """ 

923 Return a Rational approximation of ``self`` that has real 

924 and imaginary component approximations that are within ``dx`` 

925 and ``dy`` of the true values, respectively. Alternatively, 

926 ``n`` digits of precision can be specified. 

927 

928 The interval is refined with bisection and is sure to 

929 converge. The root bounds are updated when the refinement 

930 is complete so recalculation at the same or lesser precision 

931 will not have to repeat the refinement and should be much 

932 faster. 

933 

934 The following example first obtains Rational approximation to 

935 1e-8 accuracy for all roots of the 4-th order Legendre 

936 polynomial. Since the roots are all less than 1, this will 

937 ensure the decimal representation of the approximation will be 

938 correct (including rounding) to 6 digits: 

939 

940 >>> from sympy import legendre_poly, Symbol 

941 >>> x = Symbol("x") 

942 >>> p = legendre_poly(4, x, polys=True) 

943 >>> r = p.real_roots()[-1] 

944 >>> r.eval_rational(10**-8).n(6) 

945 0.861136 

946 

947 It is not necessary to a two-step calculation, however: the 

948 decimal representation can be computed directly: 

949 

950 >>> r.evalf(17) 

951 0.86113631159405258 

952 

953 """ 

954 dy = dy or dx 

955 if dx: 

956 rtol = None 

957 dx = dx if isinstance(dx, Rational) else Rational(str(dx)) 

958 dy = dy if isinstance(dy, Rational) else Rational(str(dy)) 

959 else: 

960 # 5 binary (or 2 decimal) digits are needed to ensure that 

961 # a given digit is correctly rounded 

962 # prec_to_dps(dps_to_prec(n) + 5) - n <= 2 (tested for 

963 # n in range(1000000) 

964 rtol = S(10)**-(n + 2) # +2 for guard digits 

965 interval = self._get_interval() 

966 while True: 

967 if self.is_real: 

968 if rtol: 

969 dx = abs(interval.center*rtol) 

970 interval = interval.refine_size(dx=dx) 

971 c = interval.center 

972 real = Rational(c) 

973 imag = S.Zero 

974 if not rtol or interval.dx < abs(c*rtol): 

975 break 

976 elif self.is_imaginary: 

977 if rtol: 

978 dy = abs(interval.center[1]*rtol) 

979 dx = 1 

980 interval = interval.refine_size(dx=dx, dy=dy) 

981 c = interval.center[1] 

982 imag = Rational(c) 

983 real = S.Zero 

984 if not rtol or interval.dy < abs(c*rtol): 

985 break 

986 else: 

987 if rtol: 

988 dx = abs(interval.center[0]*rtol) 

989 dy = abs(interval.center[1]*rtol) 

990 interval = interval.refine_size(dx, dy) 

991 c = interval.center 

992 real, imag = map(Rational, c) 

993 if not rtol or ( 

994 interval.dx < abs(c[0]*rtol) and 

995 interval.dy < abs(c[1]*rtol)): 

996 break 

997 

998 # update the interval so we at least (for this precision or 

999 # less) don't have much work to do to recompute the root 

1000 self._set_interval(interval) 

1001 return real + I*imag 

1002 

1003 

1004CRootOf = ComplexRootOf 

1005 

1006 

1007@dispatch(ComplexRootOf, ComplexRootOf) 

1008def _eval_is_eq(lhs, rhs): # noqa:F811 

1009 # if we use is_eq to check here, we get infinite recurion 

1010 return lhs == rhs 

1011 

1012 

1013@dispatch(ComplexRootOf, Basic) # type:ignore 

1014def _eval_is_eq(lhs, rhs): # noqa:F811 

1015 # CRootOf represents a Root, so if rhs is that root, it should set 

1016 # the expression to zero *and* it should be in the interval of the 

1017 # CRootOf instance. It must also be a number that agrees with the 

1018 # is_real value of the CRootOf instance. 

1019 if not rhs.is_number: 

1020 return None 

1021 if not rhs.is_finite: 

1022 return False 

1023 z = lhs.expr.subs(lhs.expr.free_symbols.pop(), rhs).is_zero 

1024 if z is False: # all roots will make z True but we don't know 

1025 # whether this is the right root if z is True 

1026 return False 

1027 o = rhs.is_real, rhs.is_imaginary 

1028 s = lhs.is_real, lhs.is_imaginary 

1029 assert None not in s # this is part of initial refinement 

1030 if o != s and None not in o: 

1031 return False 

1032 re, im = rhs.as_real_imag() 

1033 if lhs.is_real: 

1034 if im: 

1035 return False 

1036 i = lhs._get_interval() 

1037 a, b = [Rational(str(_)) for _ in (i.a, i.b)] 

1038 return sympify(a <= rhs and rhs <= b) 

1039 i = lhs._get_interval() 

1040 r1, r2, i1, i2 = [Rational(str(j)) for j in ( 

1041 i.ax, i.bx, i.ay, i.by)] 

1042 return is_le(r1, re) and is_le(re,r2) and is_le(i1,im) and is_le(im,i2) 

1043 

1044 

1045@public 

1046class RootSum(Expr): 

1047 """Represents a sum of all roots of a univariate polynomial. """ 

1048 

1049 __slots__ = ('poly', 'fun', 'auto') 

1050 

1051 def __new__(cls, expr, func=None, x=None, auto=True, quadratic=False): 

1052 """Construct a new ``RootSum`` instance of roots of a polynomial.""" 

1053 coeff, poly = cls._transform(expr, x) 

1054 

1055 if not poly.is_univariate: 

1056 raise MultivariatePolynomialError( 

1057 "only univariate polynomials are allowed") 

1058 

1059 if func is None: 

1060 func = Lambda(poly.gen, poly.gen) 

1061 else: 

1062 is_func = getattr(func, 'is_Function', False) 

1063 

1064 if is_func and 1 in func.nargs: 

1065 if not isinstance(func, Lambda): 

1066 func = Lambda(poly.gen, func(poly.gen)) 

1067 else: 

1068 raise ValueError( 

1069 "expected a univariate function, got %s" % func) 

1070 

1071 var, expr = func.variables[0], func.expr 

1072 

1073 if coeff is not S.One: 

1074 expr = expr.subs(var, coeff*var) 

1075 

1076 deg = poly.degree() 

1077 

1078 if not expr.has(var): 

1079 return deg*expr 

1080 

1081 if expr.is_Add: 

1082 add_const, expr = expr.as_independent(var) 

1083 else: 

1084 add_const = S.Zero 

1085 

1086 if expr.is_Mul: 

1087 mul_const, expr = expr.as_independent(var) 

1088 else: 

1089 mul_const = S.One 

1090 

1091 func = Lambda(var, expr) 

1092 

1093 rational = cls._is_func_rational(poly, func) 

1094 factors, terms = _pure_factors(poly), [] 

1095 

1096 for poly, k in factors: 

1097 if poly.is_linear: 

1098 term = func(roots_linear(poly)[0]) 

1099 elif quadratic and poly.is_quadratic: 

1100 term = sum(map(func, roots_quadratic(poly))) 

1101 else: 

1102 if not rational or not auto: 

1103 term = cls._new(poly, func, auto) 

1104 else: 

1105 term = cls._rational_case(poly, func) 

1106 

1107 terms.append(k*term) 

1108 

1109 return mul_const*Add(*terms) + deg*add_const 

1110 

1111 @classmethod 

1112 def _new(cls, poly, func, auto=True): 

1113 """Construct new raw ``RootSum`` instance. """ 

1114 obj = Expr.__new__(cls) 

1115 

1116 obj.poly = poly 

1117 obj.fun = func 

1118 obj.auto = auto 

1119 

1120 return obj 

1121 

1122 @classmethod 

1123 def new(cls, poly, func, auto=True): 

1124 """Construct new ``RootSum`` instance. """ 

1125 if not func.expr.has(*func.variables): 

1126 return func.expr 

1127 

1128 rational = cls._is_func_rational(poly, func) 

1129 

1130 if not rational or not auto: 

1131 return cls._new(poly, func, auto) 

1132 else: 

1133 return cls._rational_case(poly, func) 

1134 

1135 @classmethod 

1136 def _transform(cls, expr, x): 

1137 """Transform an expression to a polynomial. """ 

1138 poly = PurePoly(expr, x, greedy=False) 

1139 return preprocess_roots(poly) 

1140 

1141 @classmethod 

1142 def _is_func_rational(cls, poly, func): 

1143 """Check if a lambda is a rational function. """ 

1144 var, expr = func.variables[0], func.expr 

1145 return expr.is_rational_function(var) 

1146 

1147 @classmethod 

1148 def _rational_case(cls, poly, func): 

1149 """Handle the rational function case. """ 

1150 roots = symbols('r:%d' % poly.degree()) 

1151 var, expr = func.variables[0], func.expr 

1152 

1153 f = sum(expr.subs(var, r) for r in roots) 

1154 p, q = together(f).as_numer_denom() 

1155 

1156 domain = QQ[roots] 

1157 

1158 p = p.expand() 

1159 q = q.expand() 

1160 

1161 try: 

1162 p = Poly(p, domain=domain, expand=False) 

1163 except GeneratorsNeeded: 

1164 p, p_coeff = None, (p,) 

1165 else: 

1166 p_monom, p_coeff = zip(*p.terms()) 

1167 

1168 try: 

1169 q = Poly(q, domain=domain, expand=False) 

1170 except GeneratorsNeeded: 

1171 q, q_coeff = None, (q,) 

1172 else: 

1173 q_monom, q_coeff = zip(*q.terms()) 

1174 

1175 coeffs, mapping = symmetrize(p_coeff + q_coeff, formal=True) 

1176 formulas, values = viete(poly, roots), [] 

1177 

1178 for (sym, _), (_, val) in zip(mapping, formulas): 

1179 values.append((sym, val)) 

1180 

1181 for i, (coeff, _) in enumerate(coeffs): 

1182 coeffs[i] = coeff.subs(values) 

1183 

1184 n = len(p_coeff) 

1185 

1186 p_coeff = coeffs[:n] 

1187 q_coeff = coeffs[n:] 

1188 

1189 if p is not None: 

1190 p = Poly(dict(zip(p_monom, p_coeff)), *p.gens).as_expr() 

1191 else: 

1192 (p,) = p_coeff 

1193 

1194 if q is not None: 

1195 q = Poly(dict(zip(q_monom, q_coeff)), *q.gens).as_expr() 

1196 else: 

1197 (q,) = q_coeff 

1198 

1199 return factor(p/q) 

1200 

1201 def _hashable_content(self): 

1202 return (self.poly, self.fun) 

1203 

1204 @property 

1205 def expr(self): 

1206 return self.poly.as_expr() 

1207 

1208 @property 

1209 def args(self): 

1210 return (self.expr, self.fun, self.poly.gen) 

1211 

1212 @property 

1213 def free_symbols(self): 

1214 return self.poly.free_symbols | self.fun.free_symbols 

1215 

1216 @property 

1217 def is_commutative(self): 

1218 return True 

1219 

1220 def doit(self, **hints): 

1221 if not hints.get('roots', True): 

1222 return self 

1223 

1224 _roots = roots(self.poly, multiple=True) 

1225 

1226 if len(_roots) < self.poly.degree(): 

1227 return self 

1228 else: 

1229 return Add(*[self.fun(r) for r in _roots]) 

1230 

1231 def _eval_evalf(self, prec): 

1232 try: 

1233 _roots = self.poly.nroots(n=prec_to_dps(prec)) 

1234 except (DomainError, PolynomialError): 

1235 return self 

1236 else: 

1237 return Add(*[self.fun(r) for r in _roots]) 

1238 

1239 def _eval_derivative(self, x): 

1240 var, expr = self.fun.args 

1241 func = Lambda(var, expr.diff(x)) 

1242 return self.new(self.poly, func, self.auto)