def variations(seq, n, repetition=False): """Returns a generator of the n-sized variations of ``seq`` (size N). ``repetition`` controls whether items in ``seq`` can appear more than once; Examples ======== variations(seq, n) will return N! / (N - n)! permutations without repetition of seq's elements: >>> from sympy.utilities.iterables import variations >>> list(variations([1, 2], 2)) [(1, 2), (2, 1)] variations(seq, n, True) will return the N**n permutations obtained by allowing repetition of elements: >>> list(variations([1, 2], 2, repetition=True)) [(1, 1), (1, 2), (2, 1), (2, 2)] If you ask for more items than are in the set you get the empty set unless you allow repetitions: >>> list(variations([0, 1], 3, repetition=False)) [] >>> list(variations([0, 1], 3, repetition=True))[:4] [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)] See Also ======== sympy.core.compatibility.permutations """ from sympy.core.compatibility import permutations if not repetition: seq = tuple(seq) if len(seq) < n: return for i in permutations(seq, n): yield i else: if n == 0: yield () else: for i in xrange(len(seq)): for cc in variations(seq, n - 1, True): yield (seq[i],) + cc
def prune(P, S, h): """ Prune the pair-set by applying the chain criterion [SCA, remark 2.5.11]. """ remove = set() for (a, b, c) in permutations(S, 3): A = sdm_LM(a) B = sdm_LM(b) C = sdm_LM(c) if len(set([A[0], B[0], C[0]])) != 1 or not h in [a, b, c]: continue if monomial_divides(B[1:], monomial_lcm(A[1:], C[1:])): remove.add((tuple(a), tuple(c))) return [(f, g) for (f, g) in P if (h not in [f, g]) or \ ((tuple(f), tuple(g)) not in remove and \ (tuple(g), tuple(f)) not in remove)]
def prune(P, S, h): """ Prune the pair-set by applying the chain criterion [SCA, remark 2.5.11]. """ remove = set() retain = set() for (a, b, c) in permutations(S, 3): A = sdm_LM(a) B = sdm_LM(b) C = sdm_LM(c) if len(set([A[0], B[0], C[0]])) != 1 or not h in [a, b, c] or \ any(tuple(x) in retain for x in [a, b, c]): continue if monomial_divides(B[1:], monomial_lcm(A[1:], C[1:])): remove.add((tuple(a), tuple(c))) retain.update([tuple(b), tuple(c), tuple(a)]) return [(f, g) for (f, g) in P if (h not in [f, g]) or \ ((tuple(f), tuple(g)) not in remove and \ (tuple(g), tuple(f)) not in remove)]
def test_Permutation(): # don't auto fill 0 raises(ValueError, lambda: Permutation([1])) p = Permutation([0, 1, 2, 3]) # call as bijective assert [p(i) for i in range(p.size)] == list(p) # call as operator assert p(range(p.size)) == list(p) # call as function assert list(p(1, 2)) == [0, 2, 1, 3] # conversion to list assert list(p) == range(4) # cycle form with size assert Permutation([[1, 2]], size=4) == Permutation([[1, 2], [0], [3]]) # random generation assert Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1])) p = Permutation([2, 5, 1, 6, 3, 0, 4]) q = Permutation([[1], [0, 3, 5, 6, 2, 4]]) assert len(set([p, p])) == 1 r = Permutation([1, 3, 2, 0, 4, 6, 5]) ans = Permutation(_af_rmuln(*[w.array_form for w in (p, q, r)])).array_form assert rmul(p, q, r).array_form == ans # make sure no other permutation of p, q, r could have given # that answer for a, b, c in permutations((p, q, r)): if (a, b, c) == (p, q, r): continue assert rmul(a, b, c).array_form != ans assert p.support() == range(7) assert q.support() == [0, 2, 3, 4, 5, 6] assert Permutation(p.cyclic_form).array_form == p.array_form assert p.cardinality == 5040 assert q.cardinality == 5040 assert q.cycles == 2 assert rmul(q, p) == Permutation([4, 6, 1, 2, 5, 3, 0]) assert rmul(p, q) == Permutation([6, 5, 3, 0, 2, 4, 1]) assert _af_rmul(p.array_form, q.array_form) == \ [6, 5, 3, 0, 2, 4, 1] assert rmul(Permutation([[1, 2, 3], [0, 4]]), Permutation([[1, 2, 4], [0], [3]])).cyclic_form == \ [[0, 4, 2], [1, 3]] assert q.array_form == [3, 1, 4, 5, 0, 6, 2] assert q.cyclic_form == [[0, 3, 5, 6, 2, 4]] assert q.full_cyclic_form == [[0, 3, 5, 6, 2, 4], [1]] assert p.cyclic_form == [[0, 2, 1, 5], [3, 6, 4]] t = p.transpositions() assert t == [(0, 5), (0, 1), (0, 2), (3, 4), (3, 6)] assert Permutation.rmul(*[Permutation(Cycle(*ti)) for ti in (t)]) assert Permutation([1, 0]).transpositions() == [(0, 1)] assert p**13 == p assert q**0 == Permutation(range(q.size)) assert q**-2 == ~q**2 assert q**2 == Permutation([5, 1, 0, 6, 3, 2, 4]) assert q**3 == q**2*q assert q**4 == q**2*q**2 a = Permutation(1, 3) b = Permutation(2, 0, 3) I = Permutation(3) assert ~a == a**-1 assert a*~a == I assert a*b**-1 == a*~b ans = Permutation(0, 5, 3, 1, 6)(2, 4) assert (p + q.rank()).rank() == ans.rank() assert (p + q.rank())._rank == ans.rank() assert (q + p.rank()).rank() == ans.rank() raises(TypeError, lambda: p + Permutation(range(10))) assert (p - q.rank()).rank() == Permutation(0, 6, 3, 1, 2, 5, 4).rank() assert p.rank() - q.rank() < 0 # for coverage: make sure mod is used assert (q - p.rank()).rank() == Permutation(1, 4, 6, 2)(3, 5).rank() assert p*q == Permutation(_af_rmuln(*[list(w) for w in (q, p)])) assert p*Permutation([]) == p assert Permutation([])*p == p assert p*Permutation([[0, 1]]) == Permutation([2, 5, 0, 6, 3, 1, 4]) assert Permutation([[0, 1]])*p == Permutation([5, 2, 1, 6, 3, 0, 4]) pq = p^q assert pq == Permutation([5, 6, 0, 4, 1, 2, 3]) assert pq == rmul(q, p, ~q) qp = q^p assert qp == Permutation([4, 3, 6, 2, 1, 5, 0]) assert qp == rmul(p, q, ~p) raises(ValueError, lambda: p^Permutation([])) assert p.commutator(q) == Permutation(0, 1, 3, 4, 6, 5, 2) assert q.commutator(p) == Permutation(0, 2, 5, 6, 4, 3, 1) assert p.commutator(q) == ~q.commutator(p) raises(ValueError, lambda: p.commutator(Permutation([]))) assert len(p.atoms()) == 7 assert q.atoms() == set([0, 1, 2, 3, 4, 5, 6]) assert p.inversion_vector() == [2, 4, 1, 3, 1, 0] assert q.inversion_vector() == [3, 1, 2, 2, 0, 1] assert Permutation.from_inversion_vector(p.inversion_vector()) == p assert Permutation.from_inversion_vector(q.inversion_vector()).array_form\ == q.array_form raises(ValueError, lambda: Permutation.from_inversion_vector([0, 2])) assert Permutation([i for i in range(500, -1, -1)]).inversions() == 125250 s = Permutation([0, 4, 1, 3, 2]) assert s.parity() == 0 _ = s.cyclic_form # needed to create a value for _cyclic_form assert len(s._cyclic_form) != s.size and s.parity() == 0 assert not s.is_odd assert s.is_even assert Permutation([0, 1, 4, 3, 2]).parity() == 1 assert _af_parity([0, 4, 1, 3, 2]) == 0 assert _af_parity([0, 1, 4, 3, 2]) == 1 s = Permutation([0]) assert s.is_Singleton assert Permutation([]).is_Empty r = Permutation([3, 2, 1, 0]) assert (r**2).is_Identity assert rmul(~p, p).is_Identity assert (~p)**13 == Permutation([5, 2, 0, 4, 6, 1, 3]) assert ~(r**2).is_Identity assert p.max() == 6 assert p.min() == 0 q = Permutation([[6], [5], [0, 1, 2, 3, 4]]) assert q.max() == 4 assert q.min() == 0 p = Permutation([1, 5, 2, 0, 3, 6, 4]) q = Permutation([[1, 2, 3, 5, 6], [0, 4]]) assert p.ascents() == [0, 3, 4] assert q.ascents() == [1, 2, 4] assert r.ascents() == [] assert p.descents() == [1, 2, 5] assert q.descents() == [0, 3, 5] assert Permutation(r.descents()).is_Identity assert p.inversions() == 7 # test the merge-sort with a longer permutation big = list(p) + list(range(p.max() + 1, p.max() + 130)) assert Permutation(big).inversions() == 7 assert p.signature() == -1 assert q.inversions() == 11 assert q.signature() == -1 assert rmul(p, ~p).inversions() == 0 assert rmul(p, ~p).signature() == 1 assert p.order() == 6 assert q.order() == 10 assert (p**(p.order())).is_Identity assert p.length() == 6 assert q.length() == 7 assert r.length() == 4 assert p.runs() == [[1, 5], [2], [0, 3, 6], [4]] assert q.runs() == [[4], [2, 3, 5], [0, 6], [1]] assert r.runs() == [[3], [2], [1], [0]] assert p.index() == 8 assert q.index() == 8 assert r.index() == 3 assert p.get_precedence_distance(q) == q.get_precedence_distance(p) assert p.get_adjacency_distance(q) == p.get_adjacency_distance(q) assert p.get_positional_distance(q) == p.get_positional_distance(q) p = Permutation([0, 1, 2, 3]) q = Permutation([3, 2, 1, 0]) assert p.get_precedence_distance(q) == 6 assert p.get_adjacency_distance(q) == 3 assert p.get_positional_distance(q) == 8 p = Permutation([0, 3, 1, 2, 4]) q = Permutation.josephus(4, 5, 2) assert p.get_adjacency_distance(q) == 3 raises(ValueError, lambda: p.get_adjacency_distance(Permutation([]))) raises(ValueError, lambda: p.get_positional_distance(Permutation([]))) raises(ValueError, lambda: p.get_precedence_distance(Permutation([]))) a = [Permutation.unrank_nonlex(4, i) for i in range(5)] iden = Permutation([0, 1, 2, 3]) for i in range(5): for j in range(i + 1, 5): assert a[i].commutes_with(a[j]) == \ (rmul(a[i], a[j]) == rmul(a[j], a[i])) if a[i].commutes_with(a[j]): assert a[i].commutator(a[j]) == iden assert a[j].commutator(a[i]) == iden a = Permutation(3) b = Permutation(0, 6, 3)(1, 2) assert a.cycle_structure == {1: 4} assert b.cycle_structure == {2: 1, 3: 1, 1: 2}
def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3): """ Compute indefinite integral using heuristic Risch algorithm. This is a heuristic approach to indefinite integration in finite terms using the extended heuristic (parallel) Risch algorithm, based on Manuel Bronstein's "Poor Man's Integrator". The algorithm supports various classes of functions including transcendental elementary or special functions like Airy, Bessel, Whittaker and Lambert. Note that this algorithm is not a decision procedure. If it isn't able to compute the antiderivative for a given function, then this is not a proof that such a functions does not exist. One should use recursive Risch algorithm in such case. It's an open question if this algorithm can be made a full decision procedure. This is an internal integrator procedure. You should use toplevel 'integrate' function in most cases, as this procedure needs some preprocessing steps and otherwise may fail. Specification ============= heurisch(f, x, rewrite=False, hints=None) where f : expression x : symbol rewrite -> force rewrite 'f' in terms of 'tan' and 'tanh' hints -> a list of functions that may appear in anti-derivate - hints = None --> no suggestions at all - hints = [ ] --> try to figure out - hints = [f1, ..., fn] --> we know better Examples ======== >>> from sympy import tan >>> from sympy.integrals.heurisch import heurisch >>> from sympy.abc import x, y >>> heurisch(y*tan(x), x) y*log(tan(x)**2 + 1)/2 See Manuel Bronstein's "Poor Man's Integrator": [1] http://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/index.html For more information on the implemented algorithm refer to: [2] K. Geddes, L. Stefanus, On the Risch-Norman Integration Method and its Implementation in Maple, Proceedings of ISSAC'89, ACM Press, 212-217. [3] J. H. Davenport, On the Parallel Risch Algorithm (I), Proceedings of EUROCAM'82, LNCS 144, Springer, 144-157. [4] J. H. Davenport, On the Parallel Risch Algorithm (III): Use of Tangents, SIGSAM Bulletin 16 (1982), 3-6. [5] J. H. Davenport, B. M. Trager, On the Parallel Risch Algorithm (II), ACM Transactions on Mathematical Software 11 (1985), 356-362. See Also ======== sympy.integrals.integrals.Integral.doit sympy.integrals.integrals.Integral components """ f = sympify(f) if not f.is_Add: indep, f = f.as_independent(x) else: indep = S.One if not f.has(x): return indep * f * x rewritables = { (sin, cos, cot): tan, (sinh, cosh, coth): tanh, } if rewrite: for candidates, rule in rewritables.iteritems(): f = f.rewrite(candidates, rule) else: for candidates in rewritables.iterkeys(): if f.has(*candidates): break else: rewrite = True terms = components(f, x) if hints is not None: if not hints: a = Wild('a', exclude=[x]) b = Wild('b', exclude=[x]) c = Wild('c', exclude=[x]) for g in set(terms): if g.is_Function: if g.func is exp: M = g.args[0].match(a * x**2) if M is not None: terms.add(erf(sqrt(-M[a]) * x)) M = g.args[0].match(a * x**2 + b * x + c) if M is not None: if M[a].is_positive: terms.add( sqrt(pi / 4 * (-M[a])) * exp(M[c] - M[b]**2 / (4 * M[a])) * erf(-sqrt(-M[a]) * x + M[b] / (2 * sqrt(-M[a])))) elif M[a].is_negative: terms.add( sqrt(pi / 4 * (-M[a])) * exp(M[c] - M[b]**2 / (4 * M[a])) * erf( sqrt(-M[a]) * x - M[b] / (2 * sqrt(-M[a])))) M = g.args[0].match(a * log(x)**2) if M is not None: if M[a].is_positive: terms.add(-I * erf(I * (sqrt(M[a]) * log(x) + 1 / (2 * sqrt(M[a]))))) if M[a].is_negative: terms.add( erf( sqrt(-M[a]) * log(x) - 1 / (2 * sqrt(-M[a])))) elif g.is_Pow: if g.exp.is_Rational and g.exp.q == 2: M = g.base.match(a * x**2 + b) if M is not None and M[b].is_positive: if M[a].is_positive: terms.add(asinh(sqrt(M[a] / M[b]) * x)) elif M[a].is_negative: terms.add(asin(sqrt(-M[a] / M[b]) * x)) M = g.base.match(a * x**2 - b) if M is not None and M[b].is_positive: if M[a].is_positive: terms.add(acosh(sqrt(M[a] / M[b]) * x)) elif M[a].is_negative: terms.add((-M[b] / 2 * sqrt(-M[a]) * atan( sqrt(-M[a]) * x / sqrt(M[a] * x**2 - M[b])) )) else: terms |= set(hints) for g in set(terms): terms |= components(cancel(g.diff(x)), x) # TODO: caching is significant factor for why permutations work at all. Change this. V = _symbols('x', len(terms)) mapping = dict(zip(terms, V)) rev_mapping = {} for k, v in mapping.iteritems(): rev_mapping[v] = k if mappings is None: # Pre-sort mapping in order of largest to smallest expressions (last is always x). def _sort_key(arg): return default_sort_key(arg[0].as_independent(x)[1]) mapping = sorted(mapping.items(), key=_sort_key, reverse=True) mappings = permutations(mapping) def _substitute(expr): return expr.subs(mapping) for mapping in mappings: # TODO: optimize this by not generating permutations where mapping[-1] != x. if mapping[-1][0] != x: continue mapping = list(mapping) diffs = [_substitute(cancel(g.diff(x))) for g in terms] denoms = [g.as_numer_denom()[1] for g in diffs] if all(h.is_polynomial(*V) for h in denoms) and _substitute(f).is_rational_function(*V): denom = reduce(lambda p, q: lcm(p, q, *V), denoms) break else: if not rewrite: result = heurisch(f, x, rewrite=True, hints=hints) if result is not None: return indep * result return None numers = [cancel(denom * g) for g in diffs] def _derivation(h): return Add(*[d * h.diff(v) for d, v in zip(numers, V)]) def _deflation(p): for y in V: if not p.has(y): continue if _derivation(p) is not S.Zero: c, q = p.as_poly(y).primitive() return _deflation(c) * gcd(q, q.diff(y)).as_expr() else: return p def _splitter(p): for y in V: if not p.has(y): continue if _derivation(y) is not S.Zero: c, q = p.as_poly(y).primitive() q = q.as_expr() h = gcd(q, _derivation(q), y) s = quo(h, gcd(q, q.diff(y), y), y) c_split = _splitter(c) if s.as_poly(y).degree() == 0: return (c_split[0], q * c_split[1]) q_split = _splitter(cancel(q / s)) return (c_split[0] * q_split[0] * s, c_split[1] * q_split[1]) else: return (S.One, p) special = {} for term in terms: if term.is_Function: if term.func is tan: special[1 + _substitute(term)**2] = False elif term.func is tanh: special[1 + _substitute(term)] = False special[1 - _substitute(term)] = False elif term.func is C.LambertW: special[_substitute(term)] = True F = _substitute(f) P, Q = F.as_numer_denom() u_split = _splitter(denom) v_split = _splitter(Q) polys = list(v_split) + [u_split[0]] + special.keys() s = u_split[0] * Mul(*[k for k, v in special.iteritems() if v]) polified = [p.as_poly(*V) for p in [s, P, Q]] if None in polified: return None a, b, c = [p.total_degree() for p in polified] poly_denom = (s * v_split[0] * _deflation(v_split[1])).as_expr() def _exponent(g): if g.is_Pow: if g.exp.is_Rational and g.exp.q != 1: if g.exp.p > 0: return g.exp.p + g.exp.q - 1 else: return abs(g.exp.p + g.exp.q) else: return 1 elif not g.is_Atom and g.args: return max([_exponent(h) for h in g.args]) else: return 1 A, B = _exponent(f), a + max(b, c) if A > 1 and B > 1: monoms = monomials(V, A + B - 1) else: monoms = monomials(V, A + B) poly_coeffs = _symbols('A', len(monoms)) poly_part = Add( *[poly_coeffs[i] * monomial for i, monomial in enumerate(monoms)]) reducibles = set() for poly in polys: if poly.has(*V): try: factorization = factor(poly, greedy=True) except PolynomialError: factorization = poly factorization = poly if factorization.is_Mul: reducibles |= set(factorization.args) else: reducibles.add(factorization) def _integrate(field=None): irreducibles = set() for poly in reducibles: for z in poly.atoms(Symbol): if z in V: break else: continue irreducibles |= set(root_factors(poly, z, filter=field)) log_coeffs, log_part = [], [] B = _symbols('B', len(irreducibles)) for i, poly in enumerate(irreducibles): if poly.has(*V): log_coeffs.append(B[i]) log_part.append(log_coeffs[-1] * log(poly)) coeffs = poly_coeffs + log_coeffs candidate = poly_part / poly_denom + Add(*log_part) h = F - _derivation(candidate) / denom numer = h.as_numer_denom()[0].expand(force=True) equations = defaultdict(lambda: S.Zero) for term in Add.make_args(numer): coeff, dependent = term.as_independent(*V) equations[dependent] += coeff solution = solve(equations.values(), *coeffs) return (solution, candidate, coeffs) if solution else None if not (F.atoms(Symbol) - set(V)): result = _integrate('Q') if result is None: result = _integrate() else: result = _integrate() if result is not None: (solution, candidate, coeffs) = result antideriv = candidate.subs(solution) for coeff in coeffs: if coeff not in solution: antideriv = antideriv.subs(coeff, S.Zero) antideriv = antideriv.subs(rev_mapping) antideriv = cancel(antideriv).expand(force=True) if antideriv.is_Add: antideriv = antideriv.as_independent(x)[1] return indep * antideriv else: if retries >= 0: result = heurisch(f, x, mappings=mappings, rewrite=rewrite, hints=hints, retries=retries - 1) if result is not None: return indep * result return None
def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3): """Compute indefinite integral using heuristic Risch algorithm. This is a heuristic approach to indefinite integration in finite terms using the extended heuristic (parallel) Risch algorithm, based on Manuel Bronstein's "Poor Man's Integrator". The algorithm supports various classes of functions including transcendental elementary or special functions like Airy, Bessel, Whittaker and Lambert. Note that this algorithm is not a decision procedure. If it isn't able to compute the antiderivative for a given function, then this is not a proof that such a functions does not exist. One should use recursive Risch algorithm in such case. It's an open question if this algorithm can be made a full decision procedure. This is an internal integrator procedure. You should use toplevel '_integrate' function in most cases, as this procedure needs some preprocessing steps and otherwise may fail. **Specification** heurisch(f, x, rewrite=False, hints=None) where f : expression x : symbol rewrite -> force rewrite 'f' in terms of 'tan' and 'tanh' hints -> a list of functions that may appear in anti-derivate - hints = None --> no suggestions at all - hints = [ ] --> try to figure out - hints = [f1, ..., fn] --> we know better **Examples** >>> from sympy import tan >>> from sympy.integrals.risch import heurisch >>> from sympy.abc import x, y >>> heurisch(y*tan(x), x) y*log(tan(x)**2 + 1)/2 See Manuel Bronstein's "Poor Man's Integrator": [1] http://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/index.html For more information on the implemented algorithm refer to: [2] K. Geddes, L. Stefanus, On the Risch-Norman Integration Method and its Implementation in Maple, Proceedings of ISSAC'89, ACM Press, 212-217. [3] J. H. Davenport, On the Parallel Risch Algorithm (I), Proceedings of EUROCAM'82, LNCS 144, Springer, 144-157. [4] J. H. Davenport, On the Parallel Risch Algorithm (III): Use of Tangents, SIGSAM Bulletin 16 (1982), 3-6. [5] J. H. Davenport, B. M. Trager, On the Parallel Risch Algorithm (II), ACM Transactions on Mathematical Software 11 (1985), 356-362. """ f = sympify(f) if not f.is_Add: indep, f = f.as_independent(x) else: indep = S.One if not f.has(x): return indep * f * x rewritables = { (sin, cos, cot) : tan, (sinh, cosh, coth) : tanh, } if rewrite: for candidates, rule in rewritables.iteritems(): f = f.rewrite(candidates, rule) else: for candidates in rewritables.iterkeys(): if f.has(*candidates): break else: rewrite = True terms = components(f, x) if hints is not None: if not hints: a = Wild('a', exclude=[x]) b = Wild('b', exclude=[x]) c = Wild('c', exclude=[x]) for g in set(terms): if g.is_Function: if g.func is exp: M = g.args[0].match(a*x**2) if M is not None: terms.add(erf(sqrt(-M[a])*x)) M = g.args[0].match(a*x**2 + b*x + c) if M is not None: if M[a].is_positive: terms.add(sqrt(pi/4*(-M[a]))*exp(M[c]-M[b]**2/(4*M[a]))* \ erf(-sqrt(-M[a])*x + M[b]/(2*sqrt(-M[a])))) elif M[a].is_negative: terms.add(sqrt(pi/4*(-M[a]))*exp(M[c]-M[b]**2/(4*M[a]))* \ erf(sqrt(-M[a])*x - M[b]/(2*sqrt(-M[a])))) M = g.args[0].match(a*log(x)**2) if M is not None: if M[a].is_positive: terms.add(-I*erf(I*(sqrt(M[a])*log(x)+1/(2*sqrt(M[a]))))) if M[a].is_negative: terms.add(erf(sqrt(-M[a])*log(x)-1/(2*sqrt(-M[a])))) elif g.is_Pow: if g.exp.is_Rational and g.exp.q == 2: M = g.base.match(a*x**2 + b) if M is not None and M[b].is_positive: if M[a].is_positive: terms.add(asinh(sqrt(M[a]/M[b])*x)) elif M[a].is_negative: terms.add(asin(sqrt(-M[a]/M[b])*x)) M = g.base.match(a*x**2 - b) if M is not None and M[b].is_positive: if M[a].is_positive: terms.add(acosh(sqrt(M[a]/M[b])*x)) elif M[a].is_negative: terms.add((-M[b]/2*sqrt(-M[a])*\ atan(sqrt(-M[a])*x/sqrt(M[a]*x**2-M[b])))) else: terms |= set(hints) for g in set(terms): terms |= components(cancel(g.diff(x)), x) # TODO: caching is significant factor for why permutations work at all. Change this. V = _symbols('x', len(terms)) mapping = dict(zip(terms, V)) rev_mapping = {} for k, v in mapping.iteritems(): rev_mapping[v] = k if mappings is None: # Pre-sort mapping in order of largest to smallest expressions (last is always x). def _sort_key(arg): return default_sort_key(arg[0].as_independent(x)[1]) mapping = sorted(mapping.items(), key=_sort_key, reverse=True) mappings = permutations(mapping) def _substitute(expr): return expr.subs(mapping) for mapping in mappings: # TODO: optimize this by not generating permutations where mapping[-1] != x. if mapping[-1][0] != x: continue mapping = list(mapping) diffs = [ _substitute(cancel(g.diff(x))) for g in terms ] denoms = [ g.as_numer_denom()[1] for g in diffs ] if all(h.is_polynomial(*V) for h in denoms) and _substitute(f).is_rational_function(*V): denom = reduce(lambda p, q: lcm(p, q, *V), denoms) break else: if not rewrite: result = heurisch(f, x, rewrite=True, hints=hints) if result is not None: return indep*result return None numers = [ cancel(denom*g) for g in diffs ] def _derivation(h): return Add(*[ d * h.diff(v) for d, v in zip(numers, V) ]) def _deflation(p): for y in V: if not p.has(y): continue if _derivation(p) is not S.Zero: c, q = p.as_poly(y).primitive() return _deflation(c)*gcd(q, q.diff(y)).as_expr() else: return p def _splitter(p): for y in V: if not p.has(y): continue if _derivation(y) is not S.Zero: c, q = p.as_poly(y).primitive() q = q.as_expr() h = gcd(q, _derivation(q), y) s = quo(h, gcd(q, q.diff(y), y), y) c_split = _splitter(c) if s.as_poly(y).degree() == 0: return (c_split[0], q * c_split[1]) q_split = _splitter(cancel(q / s)) return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) else: return (S.One, p) special = {} for term in terms: if term.is_Function: if term.func is tan: special[1 + _substitute(term)**2] = False elif term.func is tanh: special[1 + _substitute(term)] = False special[1 - _substitute(term)] = False elif term.func is C.LambertW: special[_substitute(term)] = True F = _substitute(f) P, Q = F.as_numer_denom() u_split = _splitter(denom) v_split = _splitter(Q) polys = list(v_split) + [ u_split[0] ] + special.keys() s = u_split[0] * Mul(*[ k for k, v in special.iteritems() if v ]) polified = [ p.as_poly(*V) for p in [s, P, Q] ] if None in polified: return None a, b, c = [ p.total_degree() for p in polified ] poly_denom = (s * v_split[0] * _deflation(v_split[1])).as_expr() def _exponent(g): if g.is_Pow: if g.exp.is_Rational and g.exp.q != 1: if g.exp.p > 0: return g.exp.p + g.exp.q - 1 else: return abs(g.exp.p + g.exp.q) else: return 1 elif not g.is_Atom and g.args: return max([ _exponent(h) for h in g.args ]) else: return 1 A, B = _exponent(f), a + max(b, c) if A > 1 and B > 1: monoms = monomials(V, A + B - 1) else: monoms = monomials(V, A + B) poly_coeffs = _symbols('A', len(monoms)) poly_part = Add(*[ poly_coeffs[i]*monomial for i, monomial in enumerate(monoms) ]) reducibles = set() for poly in polys: if poly.has(*V): try: factorization = factor(poly, greedy=True) except PolynomialError: factorization = poly factorization = poly if factorization.is_Mul: reducibles |= set(factorization.args) else: reducibles.add(factorization) def _integrate(field=None): irreducibles = set() for poly in reducibles: for z in poly.atoms(Symbol): if z in V: break else: continue irreducibles |= set(root_factors(poly, z, filter=field)) log_coeffs, log_part = [], [] B = _symbols('B', len(irreducibles)) for i, poly in enumerate(irreducibles): if poly.has(*V): log_coeffs.append(B[i]) log_part.append(log_coeffs[-1] * log(poly)) coeffs = poly_coeffs + log_coeffs candidate = poly_part/poly_denom + Add(*log_part) h = F - _derivation(candidate) / denom numer = h.as_numer_denom()[0].expand(force=True) equations = defaultdict(lambda: S.Zero) for term in Add.make_args(numer): coeff, dependent = term.as_independent(*V) equations[dependent] += coeff solution = solve(equations.values(), *coeffs) if solution is not None: return (solution, candidate, coeffs) else: return None if not (F.atoms(Symbol) - set(V)): result = _integrate('Q') if result is None: result = _integrate() else: result = _integrate() if result is not None: (solution, candidate, coeffs) = result antideriv = candidate.subs(solution) for coeff in coeffs: if coeff not in solution: antideriv = antideriv.subs(coeff, S.Zero) antideriv = antideriv.subs(rev_mapping) antideriv = cancel(antideriv).expand(force=True) if antideriv.is_Add: antideriv = antideriv.as_independent(x)[1] return indep * antideriv else: if retries >= 0: result = heurisch(f, x, mappings=mappings, rewrite=rewrite, hints=hints, retries=retries-1) if result is not None: return indep*result return None
def test_Permutation(): # don't auto fill 0 raises(ValueError, lambda: Permutation([1])) p = Permutation([0, 1, 2, 3]) # call as bijective assert [p(i) for i in range(p.size)] == list(p) # call as operator assert p(range(p.size)) == list(p) # call as function assert list(p(1, 2)) == [0, 2, 1, 3] # conversion to list assert list(p) == range(4) # cycle form with size assert Permutation([[1, 2]], size=4) == Permutation([[1, 2], [0], [3]]) # random generation assert Permutation.random(2) in (Permutation([1, 0]), Permutation([0, 1])) p = Permutation([2, 5, 1, 6, 3, 0, 4]) q = Permutation([[1], [0, 3, 5, 6, 2, 4]]) assert len(set([p, p])) == 1 r = Permutation([1, 3, 2, 0, 4, 6, 5]) ans = Permutation(_af_rmuln(*[w.array_form for w in (p, q, r)])).array_form assert rmul(p, q, r).array_form == ans # make sure no other permutation of p, q, r could have given # that answer for a, b, c in permutations((p, q, r)): if (a, b, c) == (p, q, r): continue assert rmul(a, b, c).array_form != ans assert p.support() == range(7) assert q.support() == [0, 2, 3, 4, 5, 6] assert Permutation(p.cyclic_form).array_form == p.array_form assert p.cardinality == 5040 assert q.cardinality == 5040 assert q.cycles == 2 assert rmul(q, p) == Permutation([4, 6, 1, 2, 5, 3, 0]) assert rmul(p, q) == Permutation([6, 5, 3, 0, 2, 4, 1]) assert _af_rmul(p.array_form, q.array_form) == \ [6, 5, 3, 0, 2, 4, 1] assert rmul(Permutation([[1, 2, 3], [0, 4]]), Permutation([[1, 2, 4], [0], [3]])).cyclic_form == \ [[0, 4, 2], [1, 3]] assert q.array_form == [3, 1, 4, 5, 0, 6, 2] assert q.cyclic_form == [[0, 3, 5, 6, 2, 4]] assert q.full_cyclic_form == [[0, 3, 5, 6, 2, 4], [1]] assert p.cyclic_form == [[0, 2, 1, 5], [3, 6, 4]] t = p.transpositions() assert t == [(0, 5), (0, 1), (0, 2), (3, 4), (3, 6)] assert Permutation.rmul(*[Permutation(Cycle(*ti)) for ti in (t)]) assert Permutation([1, 0]).transpositions() == [(0, 1)] assert p**13 == p assert q**0 == Permutation(range(q.size)) assert q**-2 == ~q**2 assert q**2 == Permutation([5, 1, 0, 6, 3, 2, 4]) assert q**3 == q**2 * q assert q**4 == q**2 * q**2 a = Permutation(1, 3) b = Permutation(2, 0, 3) I = Permutation(3) assert ~a == a**-1 assert a * ~a == I assert a * b**-1 == a * ~b ans = Permutation(0, 5, 3, 1, 6)(2, 4) assert (p + q.rank()).rank() == ans.rank() assert (p + q.rank())._rank == ans.rank() assert (q + p.rank()).rank() == ans.rank() raises(TypeError, lambda: p + Permutation(range(10))) assert (p - q.rank()).rank() == Permutation(0, 6, 3, 1, 2, 5, 4).rank() assert p.rank() - q.rank() < 0 # for coverage: make sure mod is used assert (q - p.rank()).rank() == Permutation(1, 4, 6, 2)(3, 5).rank() assert p * q == Permutation(_af_rmuln(*[list(w) for w in (q, p)])) assert p * Permutation([]) == p assert Permutation([]) * p == p assert p * Permutation([[0, 1]]) == Permutation([2, 5, 0, 6, 3, 1, 4]) assert Permutation([[0, 1]]) * p == Permutation([5, 2, 1, 6, 3, 0, 4]) pq = p ^ q assert pq == Permutation([5, 6, 0, 4, 1, 2, 3]) assert pq == rmul(q, p, ~q) qp = q ^ p assert qp == Permutation([4, 3, 6, 2, 1, 5, 0]) assert qp == rmul(p, q, ~p) raises(ValueError, lambda: p ^ Permutation([])) assert p.commutator(q) == Permutation(0, 1, 3, 4, 6, 5, 2) assert q.commutator(p) == Permutation(0, 2, 5, 6, 4, 3, 1) assert p.commutator(q) == ~q.commutator(p) raises(ValueError, lambda: p.commutator(Permutation([]))) assert len(p.atoms()) == 7 assert q.atoms() == set([0, 1, 2, 3, 4, 5, 6]) assert p.inversion_vector() == [2, 4, 1, 3, 1, 0] assert q.inversion_vector() == [3, 1, 2, 2, 0, 1] assert Permutation.from_inversion_vector(p.inversion_vector()) == p assert Permutation.from_inversion_vector(q.inversion_vector()).array_form\ == q.array_form raises(ValueError, lambda: Permutation.from_inversion_vector([0, 2])) assert Permutation([i for i in range(500, -1, -1)]).inversions() == 125250 s = Permutation([0, 4, 1, 3, 2]) assert s.parity() == 0 _ = s.cyclic_form # needed to create a value for _cyclic_form assert len(s._cyclic_form) != s.size and s.parity() == 0 assert not s.is_odd assert s.is_even assert Permutation([0, 1, 4, 3, 2]).parity() == 1 assert _af_parity([0, 4, 1, 3, 2]) == 0 assert _af_parity([0, 1, 4, 3, 2]) == 1 s = Permutation([0]) assert s.is_Singleton assert Permutation([]).is_Empty r = Permutation([3, 2, 1, 0]) assert (r**2).is_Identity assert rmul(~p, p).is_Identity assert (~p)**13 == Permutation([5, 2, 0, 4, 6, 1, 3]) assert ~(r**2).is_Identity assert p.max() == 6 assert p.min() == 0 q = Permutation([[6], [5], [0, 1, 2, 3, 4]]) assert q.max() == 4 assert q.min() == 0 p = Permutation([1, 5, 2, 0, 3, 6, 4]) q = Permutation([[1, 2, 3, 5, 6], [0, 4]]) assert p.ascents() == [0, 3, 4] assert q.ascents() == [1, 2, 4] assert r.ascents() == [] assert p.descents() == [1, 2, 5] assert q.descents() == [0, 3, 5] assert Permutation(r.descents()).is_Identity assert p.inversions() == 7 # test the merge-sort with a longer permutation big = list(p) + list(range(p.max() + 1, p.max() + 130)) assert Permutation(big).inversions() == 7 assert p.signature() == -1 assert q.inversions() == 11 assert q.signature() == -1 assert rmul(p, ~p).inversions() == 0 assert rmul(p, ~p).signature() == 1 assert p.order() == 6 assert q.order() == 10 assert (p**(p.order())).is_Identity assert p.length() == 6 assert q.length() == 7 assert r.length() == 4 assert p.runs() == [[1, 5], [2], [0, 3, 6], [4]] assert q.runs() == [[4], [2, 3, 5], [0, 6], [1]] assert r.runs() == [[3], [2], [1], [0]] assert p.index() == 8 assert q.index() == 8 assert r.index() == 3 assert p.get_precedence_distance(q) == q.get_precedence_distance(p) assert p.get_adjacency_distance(q) == p.get_adjacency_distance(q) assert p.get_positional_distance(q) == p.get_positional_distance(q) p = Permutation([0, 1, 2, 3]) q = Permutation([3, 2, 1, 0]) assert p.get_precedence_distance(q) == 6 assert p.get_adjacency_distance(q) == 3 assert p.get_positional_distance(q) == 8 p = Permutation([0, 3, 1, 2, 4]) q = Permutation.josephus(4, 5, 2) assert p.get_adjacency_distance(q) == 3 raises(ValueError, lambda: p.get_adjacency_distance(Permutation([]))) raises(ValueError, lambda: p.get_positional_distance(Permutation([]))) raises(ValueError, lambda: p.get_precedence_distance(Permutation([]))) a = [Permutation.unrank_nonlex(4, i) for i in range(5)] iden = Permutation([0, 1, 2, 3]) for i in range(5): for j in range(i + 1, 5): assert a[i].commutes_with(a[j]) == \ (rmul(a[i], a[j]) == rmul(a[j], a[i])) if a[i].commutes_with(a[j]): assert a[i].commutator(a[j]) == iden assert a[j].commutator(a[i]) == iden a = Permutation(3) b = Permutation(0, 6, 3)(1, 2) assert a.cycle_structure == {1: 4} assert b.cycle_structure == {2: 1, 3: 1, 1: 2}
def sdm_groebner(G, NF, O, K, extended=False): """ Compute a minimal standard basis of ``G`` with respect to order ``O``. The algorithm uses a normal form ``NF``, for example ``sdm_nf_mora``. The ground field is assumed to be ``K``, and monomials ordered according to ``O``. Let `N` denote the submodule generated by elements of `G`. A standard basis for `N` is a subset `S` of `N`, such that `in(S) = in(N)`, where for any subset `X` of `F`, `in(X)` denotes the submodule generated by the initial forms of elements of `X`. [SCA, defn 2.3.2] A standard basis is called minimal if no subset of it is a standard basis. One may show that standard bases are always generating sets. Minimal standard bases are not unique. This algorithm computes a deterministic result, depending on the particular order of `G`. If ``extended=True``, also compute the transition matrix from the initial generators to the groebner basis. That is, return a list of coefficient vectors, expressing the elements of the groebner basis in terms of the elements of ``G``. This functions implements the "sugar" strategy, see Giovini et al: "One sugar cube, please" OR Selection strategies in Buchberger algorithm. """ # The critical pair set. # A critical pair is stored as (i, j, s, t) where (i, j) defines the pair # (by indexing S), s is the sugar of the pair, and t is the lcm of their # leading monomials. P = [] # The eventual standard basis. S = [] Sugars = [] def Ssugar(i, j): """Compute the sugar of the S-poly corresponding to (i, j).""" LMi = sdm_LM(S[i]) LMj = sdm_LM(S[j]) return max(Sugars[i] - sdm_monomial_deg(LMi), Sugars[j] - sdm_monomial_deg(LMj)) \ + sdm_monomial_deg(sdm_monomial_lcm(LMi, LMj)) ourkey = lambda p: (p[2], O(p[3]), p[1]) def update(f, sugar, P): """Add f with sugar ``sugar`` to S, update P.""" if not f: return P k = len(S) S.append(f) Sugars.append(sugar) LMf = sdm_LM(f) def removethis(pair): i, j, s, t = pair if LMf[0] != t[0]: return False tik = sdm_monomial_lcm(LMf, sdm_LM(S[i])) tjk = sdm_monomial_lcm(LMf, sdm_LM(S[j])) return tik != t and tjk != t and sdm_monomial_divides(tik, t) and \ sdm_monomial_divides(tjk, t) # apply the chain criterion P = [p for p in P if not removethis(p)] # new-pair set N = [(i, k, Ssugar(i, k), sdm_monomial_lcm(LMf, sdm_LM(S[i]))) for i in range(k) if LMf[0] == sdm_LM(S[i])[0]] # TODO apply the product criterion? N.sort(key=ourkey) remove = set() for i, p in enumerate(N): for j in range(i + 1, len(N)): if sdm_monomial_divides(p[3], N[j][3]): remove.add(j) # TODO mergesort? P.extend(reversed([p for i, p in enumerate(N) if not i in remove])) P.sort(key=ourkey, reverse=True) # NOTE reverse-sort, because we want to pop from the end return P # Figure out the number of generators in the ground ring. try: # NOTE: we look for the first non-zero vector, take its first monomial # the number of generators in the ring is one less than the length # (since the zeroth entry is for the module generators) numgens = len(next(x[0] for x in G if x)[0]) - 1 except StopIteration: # No non-zero elements in G ... if extended: return [], [] return [] # This list will store expressions of the elements of S in terms of the # initial generators coefficients = [] # First add all the elements of G to S for i, f in enumerate(G): P = update(f, sdm_deg(f), P) if extended and f: coefficients.append(sdm_from_dict({(i,) + (0,)*numgens: K(1)}, O)) # Now carry out the buchberger algorithm. while P: i, j, s, t = P.pop() f, sf, g, sg = S[i], Sugars[i], S[j], Sugars[j] if extended: sp, coeff = sdm_spoly(f, g, O, K, phantom=(coefficients[i], coefficients[j])) h, hcoeff = NF(sp, S, O, K, phantom=(coeff, coefficients)) if h: coefficients.append(hcoeff) else: h = NF(sdm_spoly(f, g, O, K), S, O, K) P = update(h, Ssugar(i, j), P) # Finally interreduce the standard basis. # (TODO again, better data structures) S = set((tuple(f), i) for i, f in enumerate(S)) for (a, ai), (b, bi) in permutations(S, 2): A = sdm_LM(a) B = sdm_LM(b) if sdm_monomial_divides(A, B) and (b, bi) in S and (a, ai) in S: S.remove((b, bi)) L = sorted(((list(f), i) for f, i in S), key=lambda p: O(sdm_LM(p[0])), reverse=True) res = [x[0] for x in L] if extended: return res, [coefficients[i] for _, i in L] return res
def sdm_groebner(G, NF, O, K): """ Compute a minimal standard basis of ``G`` with respect to order ``O``. The algorithm uses a normal form ``NF``, for example ``sdm_nf_mora``. The ground field is assumed to be ``K``, and monomials ordered according to ``O``. Let `N` denote the submodule generated by elements of `G`. A standard basis for `N` is a subset `S` of `N`, such that `in(S) = in(N)`, where for any subset `X` of `F`, `in(X)` denotes the submodule generated by the initial forms of elements of `X`. [SCA, defn 2.3.2] A standard basis is called minimal if no subset of it is a standard basis. One may show that standard bases are always generating sets. Minimal standard bases are not unique. This algorithm computes a deterministic result, depending on the particular order of `G`. See [SCA, algorithm 2.3.8, and remark 1.6.3]. """ # First compute a standard basis S = [f for f in G if f] P = list(combinations(S, 2)) def prune(P, S, h): """ Prune the pair-set by applying the chain criterion [SCA, remark 2.5.11]. """ remove = set() retain = set() for (a, b, c) in permutations(S, 3): A = sdm_LM(a) B = sdm_LM(b) C = sdm_LM(c) if len(set([A[0], B[0], C[0]])) != 1 or not h in [a, b, c] or \ any(tuple(x) in retain for x in [a, b, c]): continue if monomial_divides(B[1:], monomial_lcm(A[1:], C[1:])): remove.add((tuple(a), tuple(c))) retain.update([tuple(b), tuple(c), tuple(a)]) return [(f, g) for (f, g) in P if (h not in [f, g]) or \ ((tuple(f), tuple(g)) not in remove and \ (tuple(g), tuple(f)) not in remove)] while P: # TODO better data structures!!! #print len(P), len(S) # Use the "normal selection strategy" lcms = [(i, sdm_LM(f)[:1] + monomial_lcm(sdm_LM(f)[1:], sdm_LM(g)[1:])) for \ i, (f, g) in enumerate(P)] i = min(lcms, key=lambda x: O(x[1]))[0] f, g = P.pop(i) h = NF(sdm_spoly(f, g, O, K), S, O, K) if h: S.append(h) P.extend((h, f) for f in S if sdm_LM(h)[0] == sdm_LM(f)[0]) P = prune(P, S, h) # Now interreduce it. (TODO again, better data structures) S = set(tuple(f) for f in S) for a, b in permutations(S, 2): A = sdm_LM(list(a)) B = sdm_LM(list(b)) if sdm_monomial_divides(A, B) and b in S and a in S: S.remove(b) return sorted((list(f) for f in S), key=lambda f: O(sdm_LM(f)), reverse=True)