Coverage for /usr/lib/python3/dist-packages/sympy/functions/elementary/piecewise.py: 13%

794 statements  

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

1from sympy.core import S, Function, diff, Tuple, Dummy, Mul 

2from sympy.core.basic import Basic, as_Basic 

3from sympy.core.numbers import Rational, NumberSymbol, _illegal 

4from sympy.core.parameters import global_parameters 

5from sympy.core.relational import (Lt, Gt, Eq, Ne, Relational, 

6 _canonical, _canonical_coeff) 

7from sympy.core.sorting import ordered 

8from sympy.functions.elementary.miscellaneous import Max, Min 

9from sympy.logic.boolalg import (And, Boolean, distribute_and_over_or, Not, 

10 true, false, Or, ITE, simplify_logic, to_cnf, distribute_or_over_and) 

11from sympy.utilities.iterables import uniq, sift, common_prefix 

12from sympy.utilities.misc import filldedent, func_name 

13 

14from itertools import product 

15 

16Undefined = S.NaN # Piecewise() 

17 

18class ExprCondPair(Tuple): 

19 """Represents an expression, condition pair.""" 

20 

21 def __new__(cls, expr, cond): 

22 expr = as_Basic(expr) 

23 if cond == True: 

24 return Tuple.__new__(cls, expr, true) 

25 elif cond == False: 

26 return Tuple.__new__(cls, expr, false) 

27 elif isinstance(cond, Basic) and cond.has(Piecewise): 

28 cond = piecewise_fold(cond) 

29 if isinstance(cond, Piecewise): 

30 cond = cond.rewrite(ITE) 

31 

32 if not isinstance(cond, Boolean): 

33 raise TypeError(filldedent(''' 

34 Second argument must be a Boolean, 

35 not `%s`''' % func_name(cond))) 

36 return Tuple.__new__(cls, expr, cond) 

37 

38 @property 

39 def expr(self): 

40 """ 

41 Returns the expression of this pair. 

42 """ 

43 return self.args[0] 

44 

45 @property 

46 def cond(self): 

47 """ 

48 Returns the condition of this pair. 

49 """ 

50 return self.args[1] 

51 

52 @property 

53 def is_commutative(self): 

54 return self.expr.is_commutative 

55 

56 def __iter__(self): 

57 yield self.expr 

58 yield self.cond 

59 

60 def _eval_simplify(self, **kwargs): 

61 return self.func(*[a.simplify(**kwargs) for a in self.args]) 

62 

63 

64class Piecewise(Function): 

65 """ 

66 Represents a piecewise function. 

67 

68 Usage: 

69 

70 Piecewise( (expr,cond), (expr,cond), ... ) 

71 - Each argument is a 2-tuple defining an expression and condition 

72 - The conds are evaluated in turn returning the first that is True. 

73 If any of the evaluated conds are not explicitly False, 

74 e.g. ``x < 1``, the function is returned in symbolic form. 

75 - If the function is evaluated at a place where all conditions are False, 

76 nan will be returned. 

77 - Pairs where the cond is explicitly False, will be removed and no pair 

78 appearing after a True condition will ever be retained. If a single 

79 pair with a True condition remains, it will be returned, even when 

80 evaluation is False. 

81 

82 Examples 

83 ======== 

84 

85 >>> from sympy import Piecewise, log, piecewise_fold 

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

87 >>> f = x**2 

88 >>> g = log(x) 

89 >>> p = Piecewise((0, x < -1), (f, x <= 1), (g, True)) 

90 >>> p.subs(x,1) 

91 1 

92 >>> p.subs(x,5) 

93 log(5) 

94 

95 Booleans can contain Piecewise elements: 

96 

97 >>> cond = (x < y).subs(x, Piecewise((2, x < 0), (3, True))); cond 

98 Piecewise((2, x < 0), (3, True)) < y 

99 

100 The folded version of this results in a Piecewise whose 

101 expressions are Booleans: 

102 

103 >>> folded_cond = piecewise_fold(cond); folded_cond 

104 Piecewise((2 < y, x < 0), (3 < y, True)) 

105 

106 When a Boolean containing Piecewise (like cond) or a Piecewise 

107 with Boolean expressions (like folded_cond) is used as a condition, 

108 it is converted to an equivalent :class:`~.ITE` object: 

109 

110 >>> Piecewise((1, folded_cond)) 

111 Piecewise((1, ITE(x < 0, y > 2, y > 3))) 

112 

113 When a condition is an ``ITE``, it will be converted to a simplified 

114 Boolean expression: 

115 

116 >>> piecewise_fold(_) 

117 Piecewise((1, ((x >= 0) | (y > 2)) & ((y > 3) | (x < 0)))) 

118 

119 See Also 

120 ======== 

121 

122 piecewise_fold 

123 piecewise_exclusive 

124 ITE 

125 """ 

126 

127 nargs = None 

128 is_Piecewise = True 

129 

130 def __new__(cls, *args, **options): 

131 if len(args) == 0: 

132 raise TypeError("At least one (expr, cond) pair expected.") 

133 # (Try to) sympify args first 

134 newargs = [] 

135 for ec in args: 

136 # ec could be a ExprCondPair or a tuple 

137 pair = ExprCondPair(*getattr(ec, 'args', ec)) 

138 cond = pair.cond 

139 if cond is false: 

140 continue 

141 newargs.append(pair) 

142 if cond is true: 

143 break 

144 

145 eval = options.pop('evaluate', global_parameters.evaluate) 

146 if eval: 

147 r = cls.eval(*newargs) 

148 if r is not None: 

149 return r 

150 elif len(newargs) == 1 and newargs[0].cond == True: 

151 return newargs[0].expr 

152 

153 return Basic.__new__(cls, *newargs, **options) 

154 

155 @classmethod 

156 def eval(cls, *_args): 

157 """Either return a modified version of the args or, if no 

158 modifications were made, return None. 

159 

160 Modifications that are made here: 

161 

162 1. relationals are made canonical 

163 2. any False conditions are dropped 

164 3. any repeat of a previous condition is ignored 

165 4. any args past one with a true condition are dropped 

166 

167 If there are no args left, nan will be returned. 

168 If there is a single arg with a True condition, its 

169 corresponding expression will be returned. 

170 

171 EXAMPLES 

172 ======== 

173 

174 >>> from sympy import Piecewise 

175 >>> from sympy.abc import x 

176 >>> cond = -x < -1 

177 >>> args = [(1, cond), (4, cond), (3, False), (2, True), (5, x < 1)] 

178 >>> Piecewise(*args, evaluate=False) 

179 Piecewise((1, -x < -1), (4, -x < -1), (2, True)) 

180 >>> Piecewise(*args) 

181 Piecewise((1, x > 1), (2, True)) 

182 """ 

183 if not _args: 

184 return Undefined 

185 

186 if len(_args) == 1 and _args[0][-1] == True: 

187 return _args[0][0] 

188 

189 newargs = _piecewise_collapse_arguments(_args) 

190 

191 # some conditions may have been redundant 

192 missing = len(newargs) != len(_args) 

193 # some conditions may have changed 

194 same = all(a == b for a, b in zip(newargs, _args)) 

195 # if either change happened we return the expr with the 

196 # updated args 

197 if not newargs: 

198 raise ValueError(filldedent(''' 

199 There are no conditions (or none that 

200 are not trivially false) to define an 

201 expression.''')) 

202 if missing or not same: 

203 return cls(*newargs) 

204 

205 def doit(self, **hints): 

206 """ 

207 Evaluate this piecewise function. 

208 """ 

209 newargs = [] 

210 for e, c in self.args: 

211 if hints.get('deep', True): 

212 if isinstance(e, Basic): 

213 newe = e.doit(**hints) 

214 if newe != self: 

215 e = newe 

216 if isinstance(c, Basic): 

217 c = c.doit(**hints) 

218 newargs.append((e, c)) 

219 return self.func(*newargs) 

220 

221 def _eval_simplify(self, **kwargs): 

222 return piecewise_simplify(self, **kwargs) 

223 

224 def _eval_as_leading_term(self, x, logx=None, cdir=0): 

225 for e, c in self.args: 

226 if c == True or c.subs(x, 0) == True: 

227 return e.as_leading_term(x) 

228 

229 def _eval_adjoint(self): 

230 return self.func(*[(e.adjoint(), c) for e, c in self.args]) 

231 

232 def _eval_conjugate(self): 

233 return self.func(*[(e.conjugate(), c) for e, c in self.args]) 

234 

235 def _eval_derivative(self, x): 

236 return self.func(*[(diff(e, x), c) for e, c in self.args]) 

237 

238 def _eval_evalf(self, prec): 

239 return self.func(*[(e._evalf(prec), c) for e, c in self.args]) 

240 

241 def _eval_is_meromorphic(self, x, a): 

242 # Conditions often implicitly assume that the argument is real. 

243 # Hence, there needs to be some check for as_set. 

244 if not a.is_real: 

245 return None 

246 

247 # Then, scan ExprCondPairs in the given order to find a piece that would contain a, 

248 # possibly as a boundary point. 

249 for e, c in self.args: 

250 cond = c.subs(x, a) 

251 

252 if cond.is_Relational: 

253 return None 

254 if a in c.as_set().boundary: 

255 return None 

256 # Apply expression if a is an interior point of the domain of e. 

257 if cond: 

258 return e._eval_is_meromorphic(x, a) 

259 

260 def piecewise_integrate(self, x, **kwargs): 

261 """Return the Piecewise with each expression being 

262 replaced with its antiderivative. To obtain a continuous 

263 antiderivative, use the :func:`~.integrate` function or method. 

264 

265 Examples 

266 ======== 

267 

268 >>> from sympy import Piecewise 

269 >>> from sympy.abc import x 

270 >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True)) 

271 >>> p.piecewise_integrate(x) 

272 Piecewise((0, x < 0), (x, x < 1), (2*x, True)) 

273 

274 Note that this does not give a continuous function, e.g. 

275 at x = 1 the 3rd condition applies and the antiderivative 

276 there is 2*x so the value of the antiderivative is 2: 

277 

278 >>> anti = _ 

279 >>> anti.subs(x, 1) 

280 2 

281 

282 The continuous derivative accounts for the integral *up to* 

283 the point of interest, however: 

284 

285 >>> p.integrate(x) 

286 Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True)) 

287 >>> _.subs(x, 1) 

288 1 

289 

290 See Also 

291 ======== 

292 Piecewise._eval_integral 

293 """ 

294 from sympy.integrals import integrate 

295 return self.func(*[(integrate(e, x, **kwargs), c) for e, c in self.args]) 

296 

297 def _handle_irel(self, x, handler): 

298 """Return either None (if the conditions of self depend only on x) else 

299 a Piecewise expression whose expressions (handled by the handler that 

300 was passed) are paired with the governing x-independent relationals, 

301 e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) -> 

302 Piecewise( 

303 (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)), 

304 (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)), 

305 (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)), 

306 (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True)) 

307 """ 

308 # identify governing relationals 

309 rel = self.atoms(Relational) 

310 irel = list(ordered([r for r in rel if x not in r.free_symbols 

311 and r not in (S.true, S.false)])) 

312 if irel: 

313 args = {} 

314 exprinorder = [] 

315 for truth in product((1, 0), repeat=len(irel)): 

316 reps = dict(zip(irel, truth)) 

317 # only store the true conditions since the false are implied 

318 # when they appear lower in the Piecewise args 

319 if 1 not in truth: 

320 cond = None # flag this one so it doesn't get combined 

321 else: 

322 andargs = Tuple(*[i for i in reps if reps[i]]) 

323 free = list(andargs.free_symbols) 

324 if len(free) == 1: 

325 from sympy.solvers.inequalities import ( 

326 reduce_inequalities, _solve_inequality) 

327 try: 

328 t = reduce_inequalities(andargs, free[0]) 

329 # ValueError when there are potentially 

330 # nonvanishing imaginary parts 

331 except (ValueError, NotImplementedError): 

332 # at least isolate free symbol on left 

333 t = And(*[_solve_inequality( 

334 a, free[0], linear=True) 

335 for a in andargs]) 

336 else: 

337 t = And(*andargs) 

338 if t is S.false: 

339 continue # an impossible combination 

340 cond = t 

341 expr = handler(self.xreplace(reps)) 

342 if isinstance(expr, self.func) and len(expr.args) == 1: 

343 expr, econd = expr.args[0] 

344 cond = And(econd, True if cond is None else cond) 

345 # the ec pairs are being collected since all possibilities 

346 # are being enumerated, but don't put the last one in since 

347 # its expr might match a previous expression and it 

348 # must appear last in the args 

349 if cond is not None: 

350 args.setdefault(expr, []).append(cond) 

351 # but since we only store the true conditions we must maintain 

352 # the order so that the expression with the most true values 

353 # comes first 

354 exprinorder.append(expr) 

355 # convert collected conditions as args of Or 

356 for k in args: 

357 args[k] = Or(*args[k]) 

358 # take them in the order obtained 

359 args = [(e, args[e]) for e in uniq(exprinorder)] 

360 # add in the last arg 

361 args.append((expr, True)) 

362 return Piecewise(*args) 

363 

364 def _eval_integral(self, x, _first=True, **kwargs): 

365 """Return the indefinite integral of the 

366 Piecewise such that subsequent substitution of x with a 

367 value will give the value of the integral (not including 

368 the constant of integration) up to that point. To only 

369 integrate the individual parts of Piecewise, use the 

370 ``piecewise_integrate`` method. 

371 

372 Examples 

373 ======== 

374 

375 >>> from sympy import Piecewise 

376 >>> from sympy.abc import x 

377 >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True)) 

378 >>> p.integrate(x) 

379 Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True)) 

380 >>> p.piecewise_integrate(x) 

381 Piecewise((0, x < 0), (x, x < 1), (2*x, True)) 

382 

383 See Also 

384 ======== 

385 Piecewise.piecewise_integrate 

386 """ 

387 from sympy.integrals.integrals import integrate 

388 

389 if _first: 

390 def handler(ipw): 

391 if isinstance(ipw, self.func): 

392 return ipw._eval_integral(x, _first=False, **kwargs) 

393 else: 

394 return ipw.integrate(x, **kwargs) 

395 irv = self._handle_irel(x, handler) 

396 if irv is not None: 

397 return irv 

398 

399 # handle a Piecewise from -oo to oo with and no x-independent relationals 

400 # ----------------------------------------------------------------------- 

401 ok, abei = self._intervals(x) 

402 if not ok: 

403 from sympy.integrals.integrals import Integral 

404 return Integral(self, x) # unevaluated 

405 

406 pieces = [(a, b) for a, b, _, _ in abei] 

407 oo = S.Infinity 

408 done = [(-oo, oo, -1)] 

409 for k, p in enumerate(pieces): 

410 if p == (-oo, oo): 

411 # all undone intervals will get this key 

412 for j, (a, b, i) in enumerate(done): 

413 if i == -1: 

414 done[j] = a, b, k 

415 break # nothing else to consider 

416 N = len(done) - 1 

417 for j, (a, b, i) in enumerate(reversed(done)): 

418 if i == -1: 

419 j = N - j 

420 done[j: j + 1] = _clip(p, (a, b), k) 

421 done = [(a, b, i) for a, b, i in done if a != b] 

422 

423 # append an arg if there is a hole so a reference to 

424 # argument -1 will give Undefined 

425 if any(i == -1 for (a, b, i) in done): 

426 abei.append((-oo, oo, Undefined, -1)) 

427 

428 # return the sum of the intervals 

429 args = [] 

430 sum = None 

431 for a, b, i in done: 

432 anti = integrate(abei[i][-2], x, **kwargs) 

433 if sum is None: 

434 sum = anti 

435 else: 

436 sum = sum.subs(x, a) 

437 e = anti._eval_interval(x, a, x) 

438 if sum.has(*_illegal) or e.has(*_illegal): 

439 sum = anti 

440 else: 

441 sum += e 

442 # see if we know whether b is contained in original 

443 # condition 

444 if b is S.Infinity: 

445 cond = True 

446 elif self.args[abei[i][-1]].cond.subs(x, b) == False: 

447 cond = (x < b) 

448 else: 

449 cond = (x <= b) 

450 args.append((sum, cond)) 

451 return Piecewise(*args) 

452 

453 def _eval_interval(self, sym, a, b, _first=True): 

454 """Evaluates the function along the sym in a given interval [a, b]""" 

455 # FIXME: Currently complex intervals are not supported. A possible 

456 # replacement algorithm, discussed in issue 5227, can be found in the 

457 # following papers; 

458 # http://portal.acm.org/citation.cfm?id=281649 

459 # http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.4127&rep=rep1&type=pdf 

460 

461 if a is None or b is None: 

462 # In this case, it is just simple substitution 

463 return super()._eval_interval(sym, a, b) 

464 else: 

465 x, lo, hi = map(as_Basic, (sym, a, b)) 

466 

467 if _first: # get only x-dependent relationals 

468 def handler(ipw): 

469 if isinstance(ipw, self.func): 

470 return ipw._eval_interval(x, lo, hi, _first=None) 

471 else: 

472 return ipw._eval_interval(x, lo, hi) 

473 irv = self._handle_irel(x, handler) 

474 if irv is not None: 

475 return irv 

476 

477 if (lo < hi) is S.false or ( 

478 lo is S.Infinity or hi is S.NegativeInfinity): 

479 rv = self._eval_interval(x, hi, lo, _first=False) 

480 if isinstance(rv, Piecewise): 

481 rv = Piecewise(*[(-e, c) for e, c in rv.args]) 

482 else: 

483 rv = -rv 

484 return rv 

485 

486 if (lo < hi) is S.true or ( 

487 hi is S.Infinity or lo is S.NegativeInfinity): 

488 pass 

489 else: 

490 _a = Dummy('lo') 

491 _b = Dummy('hi') 

492 a = lo if lo.is_comparable else _a 

493 b = hi if hi.is_comparable else _b 

494 pos = self._eval_interval(x, a, b, _first=False) 

495 if a == _a and b == _b: 

496 # it's purely symbolic so just swap lo and hi and 

497 # change the sign to get the value for when lo > hi 

498 neg, pos = (-pos.xreplace({_a: hi, _b: lo}), 

499 pos.xreplace({_a: lo, _b: hi})) 

500 else: 

501 # at least one of the bounds was comparable, so allow 

502 # _eval_interval to use that information when computing 

503 # the interval with lo and hi reversed 

504 neg, pos = (-self._eval_interval(x, hi, lo, _first=False), 

505 pos.xreplace({_a: lo, _b: hi})) 

506 

507 # allow simplification based on ordering of lo and hi 

508 p = Dummy('', positive=True) 

509 if lo.is_Symbol: 

510 pos = pos.xreplace({lo: hi - p}).xreplace({p: hi - lo}) 

511 neg = neg.xreplace({lo: hi + p}).xreplace({p: lo - hi}) 

512 elif hi.is_Symbol: 

513 pos = pos.xreplace({hi: lo + p}).xreplace({p: hi - lo}) 

514 neg = neg.xreplace({hi: lo - p}).xreplace({p: lo - hi}) 

515 # evaluate limits that may have unevaluate Min/Max 

516 touch = lambda _: _.replace( 

517 lambda x: isinstance(x, (Min, Max)), 

518 lambda x: x.func(*x.args)) 

519 neg = touch(neg) 

520 pos = touch(pos) 

521 # assemble return expression; make the first condition be Lt 

522 # b/c then the first expression will look the same whether 

523 # the lo or hi limit is symbolic 

524 if a == _a: # the lower limit was symbolic 

525 rv = Piecewise( 

526 (pos, 

527 lo < hi), 

528 (neg, 

529 True)) 

530 else: 

531 rv = Piecewise( 

532 (neg, 

533 hi < lo), 

534 (pos, 

535 True)) 

536 

537 if rv == Undefined: 

538 raise ValueError("Can't integrate across undefined region.") 

539 if any(isinstance(i, Piecewise) for i in (pos, neg)): 

540 rv = piecewise_fold(rv) 

541 return rv 

542 

543 # handle a Piecewise with lo <= hi and no x-independent relationals 

544 # ----------------------------------------------------------------- 

545 ok, abei = self._intervals(x) 

546 if not ok: 

547 from sympy.integrals.integrals import Integral 

548 # not being able to do the interval of f(x) can 

549 # be stated as not being able to do the integral 

550 # of f'(x) over the same range 

551 return Integral(self.diff(x), (x, lo, hi)) # unevaluated 

552 

553 pieces = [(a, b) for a, b, _, _ in abei] 

554 done = [(lo, hi, -1)] 

555 oo = S.Infinity 

556 for k, p in enumerate(pieces): 

557 if p[:2] == (-oo, oo): 

558 # all undone intervals will get this key 

559 for j, (a, b, i) in enumerate(done): 

560 if i == -1: 

561 done[j] = a, b, k 

562 break # nothing else to consider 

563 N = len(done) - 1 

564 for j, (a, b, i) in enumerate(reversed(done)): 

565 if i == -1: 

566 j = N - j 

567 done[j: j + 1] = _clip(p, (a, b), k) 

568 done = [(a, b, i) for a, b, i in done if a != b] 

569 

570 # return the sum of the intervals 

571 sum = S.Zero 

572 upto = None 

573 for a, b, i in done: 

574 if i == -1: 

575 if upto is None: 

576 return Undefined 

577 # TODO simplify hi <= upto 

578 return Piecewise((sum, hi <= upto), (Undefined, True)) 

579 sum += abei[i][-2]._eval_interval(x, a, b) 

580 upto = b 

581 return sum 

582 

583 def _intervals(self, sym, err_on_Eq=False): 

584 r"""Return a bool and a message (when bool is False), else a 

585 list of unique tuples, (a, b, e, i), where a and b 

586 are the lower and upper bounds in which the expression e of 

587 argument i in self is defined and $a < b$ (when involving 

588 numbers) or $a \le b$ when involving symbols. 

589 

590 If there are any relationals not involving sym, or any 

591 relational cannot be solved for sym, the bool will be False 

592 a message be given as the second return value. The calling 

593 routine should have removed such relationals before calling 

594 this routine. 

595 

596 The evaluated conditions will be returned as ranges. 

597 Discontinuous ranges will be returned separately with 

598 identical expressions. The first condition that evaluates to 

599 True will be returned as the last tuple with a, b = -oo, oo. 

600 """ 

601 from sympy.solvers.inequalities import _solve_inequality 

602 

603 assert isinstance(self, Piecewise) 

604 

605 def nonsymfail(cond): 

606 return False, filldedent(''' 

607 A condition not involving 

608 %s appeared: %s''' % (sym, cond)) 

609 

610 def _solve_relational(r): 

611 if sym not in r.free_symbols: 

612 return nonsymfail(r) 

613 try: 

614 rv = _solve_inequality(r, sym) 

615 except NotImplementedError: 

616 return False, 'Unable to solve relational %s for %s.' % (r, sym) 

617 if isinstance(rv, Relational): 

618 free = rv.args[1].free_symbols 

619 if rv.args[0] != sym or sym in free: 

620 return False, 'Unable to solve relational %s for %s.' % (r, sym) 

621 if rv.rel_op == '==': 

622 # this equality has been affirmed to have the form 

623 # Eq(sym, rhs) where rhs is sym-free; it represents 

624 # a zero-width interval which will be ignored 

625 # whether it is an isolated condition or contained 

626 # within an And or an Or 

627 rv = S.false 

628 elif rv.rel_op == '!=': 

629 try: 

630 rv = Or(sym < rv.rhs, sym > rv.rhs) 

631 except TypeError: 

632 # e.g. x != I ==> all real x satisfy 

633 rv = S.true 

634 elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): 

635 rv = S.true 

636 return True, rv 

637 

638 args = list(self.args) 

639 # make self canonical wrt Relationals 

640 keys = self.atoms(Relational) 

641 reps = {} 

642 for r in keys: 

643 ok, s = _solve_relational(r) 

644 if ok != True: 

645 return False, ok 

646 reps[r] = s 

647 # process args individually so if any evaluate, their position 

648 # in the original Piecewise will be known 

649 args = [i.xreplace(reps) for i in self.args] 

650 

651 # precondition args 

652 expr_cond = [] 

653 default = idefault = None 

654 for i, (expr, cond) in enumerate(args): 

655 if cond is S.false: 

656 continue 

657 if cond is S.true: 

658 default = expr 

659 idefault = i 

660 break 

661 if isinstance(cond, Eq): 

662 # unanticipated condition, but it is here in case a 

663 # replacement caused an Eq to appear 

664 if err_on_Eq: 

665 return False, 'encountered Eq condition: %s' % cond 

666 continue # zero width interval 

667 

668 cond = to_cnf(cond) 

669 if isinstance(cond, And): 

670 cond = distribute_or_over_and(cond) 

671 

672 if isinstance(cond, Or): 

673 expr_cond.extend( 

674 [(i, expr, o) for o in cond.args 

675 if not isinstance(o, Eq)]) 

676 elif cond is not S.false: 

677 expr_cond.append((i, expr, cond)) 

678 elif cond is S.true: 

679 default = expr 

680 idefault = i 

681 break 

682 

683 # determine intervals represented by conditions 

684 int_expr = [] 

685 for iarg, expr, cond in expr_cond: 

686 if isinstance(cond, And): 

687 lower = S.NegativeInfinity 

688 upper = S.Infinity 

689 exclude = [] 

690 for cond2 in cond.args: 

691 if not isinstance(cond2, Relational): 

692 return False, 'expecting only Relationals' 

693 if isinstance(cond2, Eq): 

694 lower = upper # ignore 

695 if err_on_Eq: 

696 return False, 'encountered secondary Eq condition' 

697 break 

698 elif isinstance(cond2, Ne): 

699 l, r = cond2.args 

700 if l == sym: 

701 exclude.append(r) 

702 elif r == sym: 

703 exclude.append(l) 

704 else: 

705 return nonsymfail(cond2) 

706 continue 

707 elif cond2.lts == sym: 

708 upper = Min(cond2.gts, upper) 

709 elif cond2.gts == sym: 

710 lower = Max(cond2.lts, lower) 

711 else: 

712 return nonsymfail(cond2) # should never get here 

713 if exclude: 

714 exclude = list(ordered(exclude)) 

715 newcond = [] 

716 for i, e in enumerate(exclude): 

717 if e < lower == True or e > upper == True: 

718 continue 

719 if not newcond: 

720 newcond.append((None, lower)) # add a primer 

721 newcond.append((newcond[-1][1], e)) 

722 newcond.append((newcond[-1][1], upper)) 

723 newcond.pop(0) # remove the primer 

724 expr_cond.extend([(iarg, expr, And(i[0] < sym, sym < i[1])) for i in newcond]) 

725 continue 

726 elif isinstance(cond, Relational) and cond.rel_op != '!=': 

727 lower, upper = cond.lts, cond.gts # part 1: initialize with givens 

728 if cond.lts == sym: # part 1a: expand the side ... 

729 lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 

730 elif cond.gts == sym: # part 1a: ... that can be expanded 

731 upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 

732 else: 

733 return nonsymfail(cond) 

734 else: 

735 return False, 'unrecognized condition: %s' % cond 

736 

737 lower, upper = lower, Max(lower, upper) 

738 if err_on_Eq and lower == upper: 

739 return False, 'encountered Eq condition' 

740 if (lower >= upper) is not S.true: 

741 int_expr.append((lower, upper, expr, iarg)) 

742 

743 if default is not None: 

744 int_expr.append( 

745 (S.NegativeInfinity, S.Infinity, default, idefault)) 

746 

747 return True, list(uniq(int_expr)) 

748 

749 def _eval_nseries(self, x, n, logx, cdir=0): 

750 args = [(ec.expr._eval_nseries(x, n, logx), ec.cond) for ec in self.args] 

751 return self.func(*args) 

752 

753 def _eval_power(self, s): 

754 return self.func(*[(e**s, c) for e, c in self.args]) 

755 

756 def _eval_subs(self, old, new): 

757 # this is strictly not necessary, but we can keep track 

758 # of whether True or False conditions arise and be 

759 # somewhat more efficient by avoiding other substitutions 

760 # and avoiding invalid conditions that appear after a 

761 # True condition 

762 args = list(self.args) 

763 args_exist = False 

764 for i, (e, c) in enumerate(args): 

765 c = c._subs(old, new) 

766 if c != False: 

767 args_exist = True 

768 e = e._subs(old, new) 

769 args[i] = (e, c) 

770 if c == True: 

771 break 

772 if not args_exist: 

773 args = ((Undefined, True),) 

774 return self.func(*args) 

775 

776 def _eval_transpose(self): 

777 return self.func(*[(e.transpose(), c) for e, c in self.args]) 

778 

779 def _eval_template_is_attr(self, is_attr): 

780 b = None 

781 for expr, _ in self.args: 

782 a = getattr(expr, is_attr) 

783 if a is None: 

784 return 

785 if b is None: 

786 b = a 

787 elif b is not a: 

788 return 

789 return b 

790 

791 _eval_is_finite = lambda self: self._eval_template_is_attr( 

792 'is_finite') 

793 _eval_is_complex = lambda self: self._eval_template_is_attr('is_complex') 

794 _eval_is_even = lambda self: self._eval_template_is_attr('is_even') 

795 _eval_is_imaginary = lambda self: self._eval_template_is_attr( 

796 'is_imaginary') 

797 _eval_is_integer = lambda self: self._eval_template_is_attr('is_integer') 

798 _eval_is_irrational = lambda self: self._eval_template_is_attr( 

799 'is_irrational') 

800 _eval_is_negative = lambda self: self._eval_template_is_attr('is_negative') 

801 _eval_is_nonnegative = lambda self: self._eval_template_is_attr( 

802 'is_nonnegative') 

803 _eval_is_nonpositive = lambda self: self._eval_template_is_attr( 

804 'is_nonpositive') 

805 _eval_is_nonzero = lambda self: self._eval_template_is_attr( 

806 'is_nonzero') 

807 _eval_is_odd = lambda self: self._eval_template_is_attr('is_odd') 

808 _eval_is_polar = lambda self: self._eval_template_is_attr('is_polar') 

809 _eval_is_positive = lambda self: self._eval_template_is_attr('is_positive') 

810 _eval_is_extended_real = lambda self: self._eval_template_is_attr( 

811 'is_extended_real') 

812 _eval_is_extended_positive = lambda self: self._eval_template_is_attr( 

813 'is_extended_positive') 

814 _eval_is_extended_negative = lambda self: self._eval_template_is_attr( 

815 'is_extended_negative') 

816 _eval_is_extended_nonzero = lambda self: self._eval_template_is_attr( 

817 'is_extended_nonzero') 

818 _eval_is_extended_nonpositive = lambda self: self._eval_template_is_attr( 

819 'is_extended_nonpositive') 

820 _eval_is_extended_nonnegative = lambda self: self._eval_template_is_attr( 

821 'is_extended_nonnegative') 

822 _eval_is_real = lambda self: self._eval_template_is_attr('is_real') 

823 _eval_is_zero = lambda self: self._eval_template_is_attr( 

824 'is_zero') 

825 

826 @classmethod 

827 def __eval_cond(cls, cond): 

828 """Return the truth value of the condition.""" 

829 if cond == True: 

830 return True 

831 if isinstance(cond, Eq): 

832 try: 

833 diff = cond.lhs - cond.rhs 

834 if diff.is_commutative: 

835 return diff.is_zero 

836 except TypeError: 

837 pass 

838 

839 def as_expr_set_pairs(self, domain=None): 

840 """Return tuples for each argument of self that give 

841 the expression and the interval in which it is valid 

842 which is contained within the given domain. 

843 If a condition cannot be converted to a set, an error 

844 will be raised. The variable of the conditions is 

845 assumed to be real; sets of real values are returned. 

846 

847 Examples 

848 ======== 

849 

850 >>> from sympy import Piecewise, Interval 

851 >>> from sympy.abc import x 

852 >>> p = Piecewise( 

853 ... (1, x < 2), 

854 ... (2,(x > 0) & (x < 4)), 

855 ... (3, True)) 

856 >>> p.as_expr_set_pairs() 

857 [(1, Interval.open(-oo, 2)), 

858 (2, Interval.Ropen(2, 4)), 

859 (3, Interval(4, oo))] 

860 >>> p.as_expr_set_pairs(Interval(0, 3)) 

861 [(1, Interval.Ropen(0, 2)), 

862 (2, Interval(2, 3))] 

863 """ 

864 if domain is None: 

865 domain = S.Reals 

866 exp_sets = [] 

867 U = domain 

868 complex = not domain.is_subset(S.Reals) 

869 cond_free = set() 

870 for expr, cond in self.args: 

871 cond_free |= cond.free_symbols 

872 if len(cond_free) > 1: 

873 raise NotImplementedError(filldedent(''' 

874 multivariate conditions are not handled.''')) 

875 if complex: 

876 for i in cond.atoms(Relational): 

877 if not isinstance(i, (Eq, Ne)): 

878 raise ValueError(filldedent(''' 

879 Inequalities in the complex domain are 

880 not supported. Try the real domain by 

881 setting domain=S.Reals''')) 

882 cond_int = U.intersect(cond.as_set()) 

883 U = U - cond_int 

884 if cond_int != S.EmptySet: 

885 exp_sets.append((expr, cond_int)) 

886 return exp_sets 

887 

888 def _eval_rewrite_as_ITE(self, *args, **kwargs): 

889 byfree = {} 

890 args = list(args) 

891 default = any(c == True for b, c in args) 

892 for i, (b, c) in enumerate(args): 

893 if not isinstance(b, Boolean) and b != True: 

894 raise TypeError(filldedent(''' 

895 Expecting Boolean or bool but got `%s` 

896 ''' % func_name(b))) 

897 if c == True: 

898 break 

899 # loop over independent conditions for this b 

900 for c in c.args if isinstance(c, Or) else [c]: 

901 free = c.free_symbols 

902 x = free.pop() 

903 try: 

904 byfree[x] = byfree.setdefault( 

905 x, S.EmptySet).union(c.as_set()) 

906 except NotImplementedError: 

907 if not default: 

908 raise NotImplementedError(filldedent(''' 

909 A method to determine whether a multivariate 

910 conditional is consistent with a complete coverage 

911 of all variables has not been implemented so the 

912 rewrite is being stopped after encountering `%s`. 

913 This error would not occur if a default expression 

914 like `(foo, True)` were given. 

915 ''' % c)) 

916 if byfree[x] in (S.UniversalSet, S.Reals): 

917 # collapse the ith condition to True and break 

918 args[i] = list(args[i]) 

919 c = args[i][1] = True 

920 break 

921 if c == True: 

922 break 

923 if c != True: 

924 raise ValueError(filldedent(''' 

925 Conditions must cover all reals or a final default 

926 condition `(foo, True)` must be given. 

927 ''')) 

928 last, _ = args[i] # ignore all past ith arg 

929 for a, c in reversed(args[:i]): 

930 last = ITE(c, a, last) 

931 return _canonical(last) 

932 

933 def _eval_rewrite_as_KroneckerDelta(self, *args): 

934 from sympy.functions.special.tensor_functions import KroneckerDelta 

935 

936 rules = { 

937 And: [False, False], 

938 Or: [True, True], 

939 Not: [True, False], 

940 Eq: [None, None], 

941 Ne: [None, None] 

942 } 

943 

944 class UnrecognizedCondition(Exception): 

945 pass 

946 

947 def rewrite(cond): 

948 if isinstance(cond, Eq): 

949 return KroneckerDelta(*cond.args) 

950 if isinstance(cond, Ne): 

951 return 1 - KroneckerDelta(*cond.args) 

952 

953 cls, args = type(cond), cond.args 

954 if cls not in rules: 

955 raise UnrecognizedCondition(cls) 

956 

957 b1, b2 = rules[cls] 

958 k = Mul(*[1 - rewrite(c) for c in args]) if b1 else Mul(*[rewrite(c) for c in args]) 

959 

960 if b2: 

961 return 1 - k 

962 return k 

963 

964 conditions = [] 

965 true_value = None 

966 for value, cond in args: 

967 if type(cond) in rules: 

968 conditions.append((value, cond)) 

969 elif cond is S.true: 

970 if true_value is None: 

971 true_value = value 

972 else: 

973 return 

974 

975 if true_value is not None: 

976 result = true_value 

977 

978 for value, cond in conditions[::-1]: 

979 try: 

980 k = rewrite(cond) 

981 result = k * value + (1 - k) * result 

982 except UnrecognizedCondition: 

983 return 

984 

985 return result 

986 

987 

988def piecewise_fold(expr, evaluate=True): 

989 """ 

990 Takes an expression containing a piecewise function and returns the 

991 expression in piecewise form. In addition, any ITE conditions are 

992 rewritten in negation normal form and simplified. 

993 

994 The final Piecewise is evaluated (default) but if the raw form 

995 is desired, send ``evaluate=False``; if trivial evaluation is 

996 desired, send ``evaluate=None`` and duplicate conditions and 

997 processing of True and False will be handled. 

998 

999 Examples 

1000 ======== 

1001 

1002 >>> from sympy import Piecewise, piecewise_fold, S 

1003 >>> from sympy.abc import x 

1004 >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) 

1005 >>> piecewise_fold(x*p) 

1006 Piecewise((x**2, x < 1), (x, True)) 

1007 

1008 See Also 

1009 ======== 

1010 

1011 Piecewise 

1012 piecewise_exclusive 

1013 """ 

1014 if not isinstance(expr, Basic) or not expr.has(Piecewise): 

1015 return expr 

1016 

1017 new_args = [] 

1018 if isinstance(expr, (ExprCondPair, Piecewise)): 

1019 for e, c in expr.args: 

1020 if not isinstance(e, Piecewise): 

1021 e = piecewise_fold(e) 

1022 # we don't keep Piecewise in condition because 

1023 # it has to be checked to see that it's complete 

1024 # and we convert it to ITE at that time 

1025 assert not c.has(Piecewise) # pragma: no cover 

1026 if isinstance(c, ITE): 

1027 c = c.to_nnf() 

1028 c = simplify_logic(c, form='cnf') 

1029 if isinstance(e, Piecewise): 

1030 new_args.extend([(piecewise_fold(ei), And(ci, c)) 

1031 for ei, ci in e.args]) 

1032 else: 

1033 new_args.append((e, c)) 

1034 else: 

1035 # Given 

1036 # P1 = Piecewise((e11, c1), (e12, c2), A) 

1037 # P2 = Piecewise((e21, c1), (e22, c2), B) 

1038 # ... 

1039 # the folding of f(P1, P2) is trivially 

1040 # Piecewise( 

1041 # (f(e11, e21), c1), 

1042 # (f(e12, e22), c2), 

1043 # (f(Piecewise(A), Piecewise(B)), True)) 

1044 # Certain objects end up rewriting themselves as thus, so 

1045 # we do that grouping before the more generic folding. 

1046 # The following applies this idea when f = Add or f = Mul 

1047 # (and the expression is commutative). 

1048 if expr.is_Add or expr.is_Mul and expr.is_commutative: 

1049 p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) 

1050 pc = sift(p, lambda x: tuple([c for e,c in x.args])) 

1051 for c in list(ordered(pc)): 

1052 if len(pc[c]) > 1: 

1053 pargs = [list(i.args) for i in pc[c]] 

1054 # the first one is the same; there may be more 

1055 com = common_prefix(*[ 

1056 [i.cond for i in j] for j in pargs]) 

1057 n = len(com) 

1058 collected = [] 

1059 for i in range(n): 

1060 collected.append(( 

1061 expr.func(*[ai[i].expr for ai in pargs]), 

1062 com[i])) 

1063 remains = [] 

1064 for a in pargs: 

1065 if n == len(a): # no more args 

1066 continue 

1067 if a[n].cond == True: # no longer Piecewise 

1068 remains.append(a[n].expr) 

1069 else: # restore the remaining Piecewise 

1070 remains.append( 

1071 Piecewise(*a[n:], evaluate=False)) 

1072 if remains: 

1073 collected.append((expr.func(*remains), True)) 

1074 args.append(Piecewise(*collected, evaluate=False)) 

1075 continue 

1076 args.extend(pc[c]) 

1077 else: 

1078 args = expr.args 

1079 # fold 

1080 folded = list(map(piecewise_fold, args)) 

1081 for ec in product(*[ 

1082 (i.args if isinstance(i, Piecewise) else 

1083 [(i, true)]) for i in folded]): 

1084 e, c = zip(*ec) 

1085 new_args.append((expr.func(*e), And(*c))) 

1086 

1087 if evaluate is None: 

1088 # don't return duplicate conditions, otherwise don't evaluate 

1089 new_args = list(reversed([(e, c) for c, e in { 

1090 c: e for e, c in reversed(new_args)}.items()])) 

1091 rv = Piecewise(*new_args, evaluate=evaluate) 

1092 if evaluate is None and len(rv.args) == 1 and rv.args[0].cond == True: 

1093 return rv.args[0].expr 

1094 if any(s.expr.has(Piecewise) for p in rv.atoms(Piecewise) for s in p.args): 

1095 return piecewise_fold(rv) 

1096 return rv 

1097 

1098 

1099def _clip(A, B, k): 

1100 """Return interval B as intervals that are covered by A (keyed 

1101 to k) and all other intervals of B not covered by A keyed to -1. 

1102 

1103 The reference point of each interval is the rhs; if the lhs is 

1104 greater than the rhs then an interval of zero width interval will 

1105 result, e.g. (4, 1) is treated like (1, 1). 

1106 

1107 Examples 

1108 ======== 

1109 

1110 >>> from sympy.functions.elementary.piecewise import _clip 

1111 >>> from sympy import Tuple 

1112 >>> A = Tuple(1, 3) 

1113 >>> B = Tuple(2, 4) 

1114 >>> _clip(A, B, 0) 

1115 [(2, 3, 0), (3, 4, -1)] 

1116 

1117 Interpretation: interval portion (2, 3) of interval (2, 4) is 

1118 covered by interval (1, 3) and is keyed to 0 as requested; 

1119 interval (3, 4) was not covered by (1, 3) and is keyed to -1. 

1120 """ 

1121 a, b = B 

1122 c, d = A 

1123 c, d = Min(Max(c, a), b), Min(Max(d, a), b) 

1124 a, b = Min(a, b), b 

1125 p = [] 

1126 if a != c: 

1127 p.append((a, c, -1)) 

1128 else: 

1129 pass 

1130 if c != d: 

1131 p.append((c, d, k)) 

1132 else: 

1133 pass 

1134 if b != d: 

1135 if d == c and p and p[-1][-1] == -1: 

1136 p[-1] = p[-1][0], b, -1 

1137 else: 

1138 p.append((d, b, -1)) 

1139 else: 

1140 pass 

1141 

1142 return p 

1143 

1144 

1145def piecewise_simplify_arguments(expr, **kwargs): 

1146 from sympy.simplify.simplify import simplify 

1147 

1148 # simplify conditions 

1149 f1 = expr.args[0].cond.free_symbols 

1150 args = None 

1151 if len(f1) == 1 and not expr.atoms(Eq): 

1152 x = f1.pop() 

1153 # this won't return intervals involving Eq 

1154 # and it won't handle symbols treated as 

1155 # booleans 

1156 ok, abe_ = expr._intervals(x, err_on_Eq=True) 

1157 def include(c, x, a): 

1158 "return True if c.subs(x, a) is True, else False" 

1159 try: 

1160 return c.subs(x, a) == True 

1161 except TypeError: 

1162 return False 

1163 if ok: 

1164 args = [] 

1165 covered = S.EmptySet 

1166 from sympy.sets.sets import Interval 

1167 for a, b, e, i in abe_: 

1168 c = expr.args[i].cond 

1169 incl_a = include(c, x, a) 

1170 incl_b = include(c, x, b) 

1171 iv = Interval(a, b, not incl_a, not incl_b) 

1172 cset = iv - covered 

1173 if not cset: 

1174 continue 

1175 if incl_a and incl_b: 

1176 if a.is_infinite and b.is_infinite: 

1177 c = S.true 

1178 elif b.is_infinite: 

1179 c = (x >= a) 

1180 elif a in covered or a.is_infinite: 

1181 c = (x <= b) 

1182 else: 

1183 c = And(a <= x, x <= b) 

1184 elif incl_a: 

1185 if a in covered or a.is_infinite: 

1186 c = (x < b) 

1187 else: 

1188 c = And(a <= x, x < b) 

1189 elif incl_b: 

1190 if b.is_infinite: 

1191 c = (x > a) 

1192 else: 

1193 c = (x <= b) 

1194 else: 

1195 if a in covered: 

1196 c = (x < b) 

1197 else: 

1198 c = And(a < x, x < b) 

1199 covered |= iv 

1200 if a is S.NegativeInfinity and incl_a: 

1201 covered |= {S.NegativeInfinity} 

1202 if b is S.Infinity and incl_b: 

1203 covered |= {S.Infinity} 

1204 args.append((e, c)) 

1205 if not S.Reals.is_subset(covered): 

1206 args.append((Undefined, True)) 

1207 if args is None: 

1208 args = list(expr.args) 

1209 for i in range(len(args)): 

1210 e, c = args[i] 

1211 if isinstance(c, Basic): 

1212 c = simplify(c, **kwargs) 

1213 args[i] = (e, c) 

1214 

1215 # simplify expressions 

1216 doit = kwargs.pop('doit', None) 

1217 for i in range(len(args)): 

1218 e, c = args[i] 

1219 if isinstance(e, Basic): 

1220 # Skip doit to avoid growth at every call for some integrals 

1221 # and sums, see sympy/sympy#17165 

1222 newe = simplify(e, doit=False, **kwargs) 

1223 if newe != e: 

1224 e = newe 

1225 args[i] = (e, c) 

1226 

1227 # restore kwargs flag 

1228 if doit is not None: 

1229 kwargs['doit'] = doit 

1230 

1231 return Piecewise(*args) 

1232 

1233 

1234def _piecewise_collapse_arguments(_args): 

1235 newargs = [] # the unevaluated conditions 

1236 current_cond = set() # the conditions up to a given e, c pair 

1237 for expr, cond in _args: 

1238 cond = cond.replace( 

1239 lambda _: _.is_Relational, _canonical_coeff) 

1240 # Check here if expr is a Piecewise and collapse if one of 

1241 # the conds in expr matches cond. This allows the collapsing 

1242 # of Piecewise((Piecewise((x,x<0)),x<0)) to Piecewise((x,x<0)). 

1243 # This is important when using piecewise_fold to simplify 

1244 # multiple Piecewise instances having the same conds. 

1245 # Eventually, this code should be able to collapse Piecewise's 

1246 # having different intervals, but this will probably require 

1247 # using the new assumptions. 

1248 if isinstance(expr, Piecewise): 

1249 unmatching = [] 

1250 for i, (e, c) in enumerate(expr.args): 

1251 if c in current_cond: 

1252 # this would already have triggered 

1253 continue 

1254 if c == cond: 

1255 if c != True: 

1256 # nothing past this condition will ever 

1257 # trigger and only those args before this 

1258 # that didn't match a previous condition 

1259 # could possibly trigger 

1260 if unmatching: 

1261 expr = Piecewise(*( 

1262 unmatching + [(e, c)])) 

1263 else: 

1264 expr = e 

1265 break 

1266 else: 

1267 unmatching.append((e, c)) 

1268 

1269 # check for condition repeats 

1270 got = False 

1271 # -- if an And contains a condition that was 

1272 # already encountered, then the And will be 

1273 # False: if the previous condition was False 

1274 # then the And will be False and if the previous 

1275 # condition is True then then we wouldn't get to 

1276 # this point. In either case, we can skip this condition. 

1277 for i in ([cond] + 

1278 (list(cond.args) if isinstance(cond, And) else 

1279 [])): 

1280 if i in current_cond: 

1281 got = True 

1282 break 

1283 if got: 

1284 continue 

1285 

1286 # -- if not(c) is already in current_cond then c is 

1287 # a redundant condition in an And. This does not 

1288 # apply to Or, however: (e1, c), (e2, Or(~c, d)) 

1289 # is not (e1, c), (e2, d) because if c and d are 

1290 # both False this would give no results when the 

1291 # true answer should be (e2, True) 

1292 if isinstance(cond, And): 

1293 nonredundant = [] 

1294 for c in cond.args: 

1295 if isinstance(c, Relational): 

1296 if c.negated.canonical in current_cond: 

1297 continue 

1298 # if a strict inequality appears after 

1299 # a non-strict one, then the condition is 

1300 # redundant 

1301 if isinstance(c, (Lt, Gt)) and ( 

1302 c.weak in current_cond): 

1303 cond = False 

1304 break 

1305 nonredundant.append(c) 

1306 else: 

1307 cond = cond.func(*nonredundant) 

1308 elif isinstance(cond, Relational): 

1309 if cond.negated.canonical in current_cond: 

1310 cond = S.true 

1311 

1312 current_cond.add(cond) 

1313 

1314 # collect successive e,c pairs when exprs or cond match 

1315 if newargs: 

1316 if newargs[-1].expr == expr: 

1317 orcond = Or(cond, newargs[-1].cond) 

1318 if isinstance(orcond, (And, Or)): 

1319 orcond = distribute_and_over_or(orcond) 

1320 newargs[-1] = ExprCondPair(expr, orcond) 

1321 continue 

1322 elif newargs[-1].cond == cond: 

1323 continue 

1324 newargs.append(ExprCondPair(expr, cond)) 

1325 return newargs 

1326 

1327 

1328_blessed = lambda e: getattr(e.lhs, '_diff_wrt', False) and ( 

1329 getattr(e.rhs, '_diff_wrt', None) or 

1330 isinstance(e.rhs, (Rational, NumberSymbol))) 

1331 

1332 

1333def piecewise_simplify(expr, **kwargs): 

1334 expr = piecewise_simplify_arguments(expr, **kwargs) 

1335 if not isinstance(expr, Piecewise): 

1336 return expr 

1337 args = list(expr.args) 

1338 

1339 args = _piecewise_simplify_eq_and(args) 

1340 args = _piecewise_simplify_equal_to_next_segment(args) 

1341 return Piecewise(*args) 

1342 

1343 

1344def _piecewise_simplify_equal_to_next_segment(args): 

1345 """ 

1346 See if expressions valid for an Equal expression happens to evaluate 

1347 to the same function as in the next piecewise segment, see: 

1348 https://github.com/sympy/sympy/issues/8458 

1349 """ 

1350 prevexpr = None 

1351 for i, (expr, cond) in reversed(list(enumerate(args))): 

1352 if prevexpr is not None: 

1353 if isinstance(cond, And): 

1354 eqs, other = sift(cond.args, 

1355 lambda i: isinstance(i, Eq), binary=True) 

1356 elif isinstance(cond, Eq): 

1357 eqs, other = [cond], [] 

1358 else: 

1359 eqs = other = [] 

1360 _prevexpr = prevexpr 

1361 _expr = expr 

1362 if eqs and not other: 

1363 eqs = list(ordered(eqs)) 

1364 for e in eqs: 

1365 # allow 2 args to collapse into 1 for any e 

1366 # otherwise limit simplification to only simple-arg 

1367 # Eq instances 

1368 if len(args) == 2 or _blessed(e): 

1369 _prevexpr = _prevexpr.subs(*e.args) 

1370 _expr = _expr.subs(*e.args) 

1371 # Did it evaluate to the same? 

1372 if _prevexpr == _expr: 

1373 # Set the expression for the Not equal section to the same 

1374 # as the next. These will be merged when creating the new 

1375 # Piecewise 

1376 args[i] = args[i].func(args[i + 1][0], cond) 

1377 else: 

1378 # Update the expression that we compare against 

1379 prevexpr = expr 

1380 else: 

1381 prevexpr = expr 

1382 return args 

1383 

1384 

1385def _piecewise_simplify_eq_and(args): 

1386 """ 

1387 Try to simplify conditions and the expression for 

1388 equalities that are part of the condition, e.g. 

1389 Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) 

1390 -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) 

1391 """ 

1392 for i, (expr, cond) in enumerate(args): 

1393 if isinstance(cond, And): 

1394 eqs, other = sift(cond.args, 

1395 lambda i: isinstance(i, Eq), binary=True) 

1396 elif isinstance(cond, Eq): 

1397 eqs, other = [cond], [] 

1398 else: 

1399 eqs = other = [] 

1400 if eqs: 

1401 eqs = list(ordered(eqs)) 

1402 for j, e in enumerate(eqs): 

1403 # these blessed lhs objects behave like Symbols 

1404 # and the rhs are simple replacements for the "symbols" 

1405 if _blessed(e): 

1406 expr = expr.subs(*e.args) 

1407 eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] 

1408 other = [ei.subs(*e.args) for ei in other] 

1409 cond = And(*(eqs + other)) 

1410 args[i] = args[i].func(expr, cond) 

1411 return args 

1412 

1413 

1414def piecewise_exclusive(expr, *, skip_nan=False, deep=True): 

1415 """ 

1416 Rewrite :class:`Piecewise` with mutually exclusive conditions. 

1417 

1418 Explanation 

1419 =========== 

1420 

1421 SymPy represents the conditions of a :class:`Piecewise` in an 

1422 "if-elif"-fashion, allowing more than one condition to be simultaneously 

1423 True. The interpretation is that the first condition that is True is the 

1424 case that holds. While this is a useful representation computationally it 

1425 is not how a piecewise formula is typically shown in a mathematical text. 

1426 The :func:`piecewise_exclusive` function can be used to rewrite any 

1427 :class:`Piecewise` with more typical mutually exclusive conditions. 

1428 

1429 Note that further manipulation of the resulting :class:`Piecewise`, e.g. 

1430 simplifying it, will most likely make it non-exclusive. Hence, this is 

1431 primarily a function to be used in conjunction with printing the Piecewise 

1432 or if one would like to reorder the expression-condition pairs. 

1433 

1434 If it is not possible to determine that all possibilities are covered by 

1435 the different cases of the :class:`Piecewise` then a final 

1436 :class:`~sympy.core.numbers.NaN` case will be included explicitly. This 

1437 can be prevented by passing ``skip_nan=True``. 

1438 

1439 Examples 

1440 ======== 

1441 

1442 >>> from sympy import piecewise_exclusive, Symbol, Piecewise, S 

1443 >>> x = Symbol('x', real=True) 

1444 >>> p = Piecewise((0, x < 0), (S.Half, x <= 0), (1, True)) 

1445 >>> piecewise_exclusive(p) 

1446 Piecewise((0, x < 0), (1/2, Eq(x, 0)), (1, x > 0)) 

1447 >>> piecewise_exclusive(Piecewise((2, x > 1))) 

1448 Piecewise((2, x > 1), (nan, x <= 1)) 

1449 >>> piecewise_exclusive(Piecewise((2, x > 1)), skip_nan=True) 

1450 Piecewise((2, x > 1)) 

1451 

1452 Parameters 

1453 ========== 

1454 

1455 expr: a SymPy expression. 

1456 Any :class:`Piecewise` in the expression will be rewritten. 

1457 skip_nan: ``bool`` (default ``False``) 

1458 If ``skip_nan`` is set to ``True`` then a final 

1459 :class:`~sympy.core.numbers.NaN` case will not be included. 

1460 deep: ``bool`` (default ``True``) 

1461 If ``deep`` is ``True`` then :func:`piecewise_exclusive` will rewrite 

1462 any :class:`Piecewise` subexpressions in ``expr`` rather than just 

1463 rewriting ``expr`` itself. 

1464 

1465 Returns 

1466 ======= 

1467 

1468 An expression equivalent to ``expr`` but where all :class:`Piecewise` have 

1469 been rewritten with mutually exclusive conditions. 

1470 

1471 See Also 

1472 ======== 

1473 

1474 Piecewise 

1475 piecewise_fold 

1476 """ 

1477 

1478 def make_exclusive(*pwargs): 

1479 

1480 cumcond = false 

1481 newargs = [] 

1482 

1483 # Handle the first n-1 cases 

1484 for expr_i, cond_i in pwargs[:-1]: 

1485 cancond = And(cond_i, Not(cumcond)).simplify() 

1486 cumcond = Or(cond_i, cumcond).simplify() 

1487 newargs.append((expr_i, cancond)) 

1488 

1489 # For the nth case defer simplification of cumcond 

1490 expr_n, cond_n = pwargs[-1] 

1491 cancond_n = And(cond_n, Not(cumcond)).simplify() 

1492 newargs.append((expr_n, cancond_n)) 

1493 

1494 if not skip_nan: 

1495 cumcond = Or(cond_n, cumcond).simplify() 

1496 if cumcond is not true: 

1497 newargs.append((Undefined, Not(cumcond).simplify())) 

1498 

1499 return Piecewise(*newargs, evaluate=False) 

1500 

1501 if deep: 

1502 return expr.replace(Piecewise, make_exclusive) 

1503 elif isinstance(expr, Piecewise): 

1504 return make_exclusive(*expr.args) 

1505 else: 

1506 return expr