def test_common_prefix_suffix(): assert common_prefix([], [1]) == [] assert common_prefix(list(range(3))) == [0, 1, 2] assert common_prefix(list(range(3)), list(range(4))) == [0, 1, 2] assert common_prefix([1, 2, 3], [1, 2, 5]) == [1, 2] assert common_prefix([1, 2, 3], [1, 3, 5]) == [1] assert common_suffix([], [1]) == [] assert common_suffix(list(range(3))) == [0, 1, 2] assert common_suffix(list(range(3)), list(range(3))) == [0, 1, 2] assert common_suffix(list(range(3)), list(range(4))) == [] assert common_suffix([1, 2, 3], [9, 2, 3]) == [2, 3] assert common_suffix([1, 2, 3], [9, 7, 3]) == [3]
def test_common_prefix_suffix(): assert common_prefix([], [1]) == [] assert common_prefix(range(3)) == [0, 1, 2] assert common_prefix(range(3), range(4)) == [0, 1, 2] assert common_prefix([1, 2, 3], [1, 2, 5]) == [1, 2] assert common_prefix([1, 2, 3], [1, 3, 5]) == [1] assert common_suffix([], [1]) == [] assert common_suffix(range(3)) == [0, 1, 2] assert common_suffix(range(3), range(3)) == [0, 1, 2] assert common_suffix(range(3), range(4)) == [] assert common_suffix([1, 2, 3], [9, 2, 3]) == [2, 3] assert common_suffix([1, 2, 3], [9, 7, 3]) == [3]
def piecewise_fold(expr): """ Takes an expression containing a piecewise function and returns the expression in piecewise form. In addition, any ITE conditions are rewritten in negation normal form and simplified. Examples ======== >>> from sympy import Piecewise, piecewise_fold, sympify as S >>> from sympy.abc import x >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) >>> piecewise_fold(x*p) Piecewise((x**2, x < 1), (x, True)) See Also ======== Piecewise """ if not isinstance(expr, Basic) or not expr.has(Piecewise): return expr new_args = [] if isinstance(expr, (ExprCondPair, Piecewise)): for e, c in expr.args: if not isinstance(e, Piecewise): e = piecewise_fold(e) # we don't keep Piecewise in condition because # it has to be checked to see that it's complete # and we convert it to ITE at that time assert not c.has(Piecewise) # pragma: no cover if isinstance(c, ITE): c = c.to_nnf() c = simplify_logic(c, form='cnf') if isinstance(e, Piecewise): new_args.extend([(piecewise_fold(ei), And(ci, c)) for ei, ci in e.args]) else: new_args.append((e, c)) else: from sympy.utilities.iterables import cartes, sift, common_prefix # Given # P1 = Piecewise((e11, c1), (e12, c2), A) # P2 = Piecewise((e21, c1), (e22, c2), B) # ... # the folding of f(P1, P2) is trivially # Piecewise( # (f(e11, e21), c1), # (f(e12, e22), c2), # (f(Piecewise(A), Piecewise(B)), True)) # Certain objects end up rewriting themselves as thus, so # we do that grouping before the more generic folding. # The following applies this idea when f = Add or f = Mul # (and the expression is commutative). if expr.is_Add or expr.is_Mul and expr.is_commutative: p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) pc = sift(p, lambda x: tuple([c for e, c in x.args])) for c in list(ordered(pc)): if len(pc[c]) > 1: pargs = [list(i.args) for i in pc[c]] # the first one is the same; there may be more com = common_prefix(*[[i.cond for i in j] for j in pargs]) n = len(com) collected = [] for i in range(n): collected.append( (expr.func(*[ai[i].expr for ai in pargs]), com[i])) remains = [] for a in pargs: if n == len(a): # no more args continue if a[n].cond == True: # no longer Piecewise remains.append(a[n].expr) else: # restore the remaining Piecewise remains.append(Piecewise(*a[n:], evaluate=False)) if remains: collected.append((expr.func(*remains), True)) args.append(Piecewise(*collected, evaluate=False)) continue args.extend(pc[c]) else: args = expr.args # fold folded = list(map(piecewise_fold, args)) for ec in cartes(*[(i.args if isinstance(i, Piecewise) else [(i, true)]) for i in folded]): e, c = zip(*ec) new_args.append((expr.func(*e), And(*c))) return Piecewise(*new_args)
def factor_nc(expr): """Return the factored form of ``expr`` while handling non-commutative expressions. **examples** >>> from sympy.core.exprtools import factor_nc >>> from sympy import Symbol >>> from sympy.abc import x >>> A = Symbol('A', commutative=False) >>> B = Symbol('B', commutative=False) >>> factor_nc((x**2 + 2*A*x + A**2).expand()) (x + A)**2 >>> factor_nc(((x + A)*(x + B)).expand()) (x + A)*(x + B) """ from sympy.simplify.simplify import _mexpand from sympy.polys import gcd, factor expr = sympify(expr) if not isinstance(expr, Expr) or not expr.args: return expr if not expr.is_Add: return expr.func(*[factor_nc(a) for a in expr.args]) expr, rep, nc_symbols = _mask_nc(expr) if rep: return factor(expr).subs(rep) else: args = [a.args_cnc() for a in Add.make_args(expr)] c = g = l = r = S.One hit = False # find any commutative gcd term for i, a in enumerate(args): if i == 0: c = Mul._from_args(a[0]) elif a[0]: c = gcd(c, Mul._from_args(a[0])) else: c = S.One if c is not S.One: hit = True c, g = c.as_coeff_Mul() if g is not S.One: for i, (cc, _) in enumerate(args): cc = list(Mul.make_args(Mul._from_args(list(cc))/g)) args[i][0] = cc else: for i, (cc, _) in enumerate(args): cc[0] = cc[0]/c args[i][0] = cc # find any noncommutative common prefix for i, a in enumerate(args): if i == 0: n = a[1][:] else: n = common_prefix(n, a[1]) if not n: # is there a power that can be extracted? if not args[0][1]: break b, e = args[0][1][0].as_base_exp() ok = False if e.is_Integer: for t in args: if not t[1]: break bt, et = t[1][0].as_base_exp() if et.is_Integer and bt == b: e = min(e, et) else: break else: ok = hit = True l = b**e il = b**-e for i, a in enumerate(args): args[i][1][0] = il*args[i][1][0] break if not ok: break else: hit = True lenn = len(n) l = Mul(*n) for i, a in enumerate(args): args[i][1] = args[i][1][lenn:] # find any noncommutative common suffix for i, a in enumerate(args): if i == 0: n = a[1][:] else: n = common_suffix(n, a[1]) if not n: # is there a power that can be extracted? if not args[0][1]: break b, e = args[0][1][-1].as_base_exp() ok = False if e.is_Integer: for t in args: if not t[1]: break bt, et = t[1][-1].as_base_exp() if et.is_Integer and bt == b: e = min(e, et) else: break else: ok = hit = True r = b**e il = b**-e for i, a in enumerate(args): args[i][1][-1] = args[i][1][-1]*il break if not ok: break else: hit = True lenn = len(n) r = Mul(*n) for i, a in enumerate(args): args[i][1] = a[1][:len(a[1]) - lenn] if hit: mid = Add(*[Mul(*cc)*Mul(*nc) for cc, nc in args]) else: mid = expr # sort the symbols so the Dummys would appear in the same # order as the original symbols, otherwise you may introduce # a factor of -1, e.g. A**2 - B**2) -- {A:y, B:x} --> y**2 - x**2 # and the former factors into two terms, (A - B)*(A + B) while the # latter factors into 3 terms, (-1)*(x - y)*(x + y) rep1 = [(n, Dummy()) for n in sorted(nc_symbols, key=default_sort_key)] unrep1 = [(v, k) for k, v in rep1] unrep1.reverse() new_mid, r2, _ = _mask_nc(mid.subs(rep1)) new_mid = factor(new_mid) new_mid = new_mid.subs(r2).subs(unrep1) if new_mid.is_Pow: return _keep_coeff(c, g*l*new_mid*r) if new_mid.is_Mul: # XXX TODO there should be a way to inspect what order the terms # must be in and just select the plausible ordering without # checking permutations cfac = [] ncfac = [] for f in new_mid.args: if f.is_commutative: cfac.append(f) else: b, e = f.as_base_exp() assert e.is_Integer ncfac.extend([b]*e) pre_mid = g*Mul(*cfac)*l target = _mexpand(expr/c) for s in variations(ncfac, len(ncfac)): ok = pre_mid*Mul(*s)*r if _mexpand(ok) == target: return _keep_coeff(c, ok) # mid was an Add that didn't factor successfully return _keep_coeff(c, g*l*mid*r)
def piecewise_fold(expr): """ Takes an expression containing a piecewise function and returns the expression in piecewise form. In addition, any ITE conditions are rewritten in negation normal form and simplified. Examples ======== >>> from sympy import Piecewise, piecewise_fold, sympify as S >>> from sympy.abc import x >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) >>> piecewise_fold(x*p) Piecewise((x**2, x < 1), (x, True)) See Also ======== Piecewise """ if not isinstance(expr, Basic) or not expr.has(Piecewise): return expr new_args = [] if isinstance(expr, (ExprCondPair, Piecewise)): for e, c in expr.args: if not isinstance(e, Piecewise): e = piecewise_fold(e) # we don't keep Piecewise in condition because # it has to be checked to see that it's complete # and we convert it to ITE at that time assert not c.has(Piecewise) # pragma: no cover if isinstance(c, ITE): c = c.to_nnf() c = simplify_logic(c, form='cnf') if isinstance(e, Piecewise): new_args.extend([(piecewise_fold(ei), And(ci, c)) for ei, ci in e.args]) else: new_args.append((e, c)) else: from sympy.utilities.iterables import cartes, sift, common_prefix # Given # P1 = Piecewise((e11, c1), (e12, c2), A) # P2 = Piecewise((e21, c1), (e22, c2), B) # ... # the folding of f(P1, P2) is trivially # Piecewise( # (f(e11, e21), c1), # (f(e12, e22), c2), # (f(Piecewise(A), Piecewise(B)), True)) # Certain objects end up rewriting themselves as thus, so # we do that grouping before the more generic folding. # The following applies this idea when f = Add or f = Mul # (and the expression is commutative). if expr.is_Add or expr.is_Mul and expr.is_commutative: p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) pc = sift(p, lambda x: x.args[0].cond) for c in pc: if len(pc[c]) > 1: pargs = [list(i.args) for i in pc[c]] # the first one is the same; there may be more com = common_prefix(*[ [i.cond for i in j] for j in pargs]) n = len(com) collected = [] for i in range(n): collected.append(( expr.func(*[ai[i].expr for ai in pargs]), com[i])) remains = [] for a in pargs: if n == len(a): # no more args continue if a[n].cond == True: # no longer Piecewise remains.append(a[n].expr) else: # restore the remaining Piecewise remains.append( Piecewise(*a[n:], evaluate=False)) if remains: collected.append((expr.func(*remains), True)) args.append(Piecewise(*collected, evaluate=False)) continue args.extend(pc[c]) else: args = expr.args # fold folded = list(map(piecewise_fold, args)) for ec in cartes(*[ (i.args if isinstance(i, Piecewise) else [(i, true)]) for i in folded]): e, c = zip(*ec) new_args.append((expr.func(*e), And(*c))) return Piecewise(*new_args)