123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 |
- """Tools and arithmetics for monomials of distributed polynomials. """
- from itertools import combinations_with_replacement, product
- from textwrap import dedent
- from sympy.core import Mul, S, Tuple, sympify
- from sympy.polys.polyerrors import ExactQuotientFailed
- from sympy.polys.polyutils import PicklableWithSlots, dict_from_expr
- from sympy.utilities import public
- from sympy.utilities.iterables import is_sequence, iterable
- @public
- def itermonomials(variables, max_degrees, min_degrees=None):
- r"""
- ``max_degrees`` and ``min_degrees`` are either both integers or both lists.
- Unless otherwise specified, ``min_degrees`` is either ``0`` or
- ``[0, ..., 0]``.
- A generator of all monomials ``monom`` is returned, such that
- either
- ``min_degree <= total_degree(monom) <= max_degree``,
- or
- ``min_degrees[i] <= degree_list(monom)[i] <= max_degrees[i]``,
- for all ``i``.
- Case I. ``max_degrees`` and ``min_degrees`` are both integers
- =============================================================
- Given a set of variables $V$ and a min_degree $N$ and a max_degree $M$
- generate a set of monomials of degree less than or equal to $N$ and greater
- than or equal to $M$. The total number of monomials in commutative
- variables is huge and is given by the following formula if $M = 0$:
- .. math::
- \frac{(\#V + N)!}{\#V! N!}
- For example if we would like to generate a dense polynomial of
- a total degree $N = 50$ and $M = 0$, which is the worst case, in 5
- variables, assuming that exponents and all of coefficients are 32-bit long
- and stored in an array we would need almost 80 GiB of memory! Fortunately
- most polynomials, that we will encounter, are sparse.
- Consider monomials in commutative variables $x$ and $y$
- and non-commutative variables $a$ and $b$::
- >>> from sympy import symbols
- >>> from sympy.polys.monomials import itermonomials
- >>> from sympy.polys.orderings import monomial_key
- >>> from sympy.abc import x, y
- >>> sorted(itermonomials([x, y], 2), key=monomial_key('grlex', [y, x]))
- [1, x, y, x**2, x*y, y**2]
- >>> sorted(itermonomials([x, y], 3), key=monomial_key('grlex', [y, x]))
- [1, x, y, x**2, x*y, y**2, x**3, x**2*y, x*y**2, y**3]
- >>> a, b = symbols('a, b', commutative=False)
- >>> set(itermonomials([a, b, x], 2))
- {1, a, a**2, b, b**2, x, x**2, a*b, b*a, x*a, x*b}
- >>> sorted(itermonomials([x, y], 2, 1), key=monomial_key('grlex', [y, x]))
- [x, y, x**2, x*y, y**2]
- Case II. ``max_degrees`` and ``min_degrees`` are both lists
- ===========================================================
- If ``max_degrees = [d_1, ..., d_n]`` and
- ``min_degrees = [e_1, ..., e_n]``, the number of monomials generated
- is:
- .. math::
- (d_1 - e_1 + 1) (d_2 - e_2 + 1) \cdots (d_n - e_n + 1)
- Let us generate all monomials ``monom`` in variables $x$ and $y$
- such that ``[1, 2][i] <= degree_list(monom)[i] <= [2, 4][i]``,
- ``i = 0, 1`` ::
- >>> from sympy import symbols
- >>> from sympy.polys.monomials import itermonomials
- >>> from sympy.polys.orderings import monomial_key
- >>> from sympy.abc import x, y
- >>> sorted(itermonomials([x, y], [2, 4], [1, 2]), reverse=True, key=monomial_key('lex', [x, y]))
- [x**2*y**4, x**2*y**3, x**2*y**2, x*y**4, x*y**3, x*y**2]
- """
- n = len(variables)
- if is_sequence(max_degrees):
- if len(max_degrees) != n:
- raise ValueError('Argument sizes do not match')
- if min_degrees is None:
- min_degrees = [0]*n
- elif not is_sequence(min_degrees):
- raise ValueError('min_degrees is not a list')
- else:
- if len(min_degrees) != n:
- raise ValueError('Argument sizes do not match')
- if any(i < 0 for i in min_degrees):
- raise ValueError("min_degrees cannot contain negative numbers")
- total_degree = False
- else:
- max_degree = max_degrees
- if max_degree < 0:
- raise ValueError("max_degrees cannot be negative")
- if min_degrees is None:
- min_degree = 0
- else:
- if min_degrees < 0:
- raise ValueError("min_degrees cannot be negative")
- min_degree = min_degrees
- total_degree = True
- if total_degree:
- if min_degree > max_degree:
- return
- if not variables or max_degree == 0:
- yield S.One
- return
- # Force to list in case of passed tuple or other incompatible collection
- variables = list(variables) + [S.One]
- if all(variable.is_commutative for variable in variables):
- monomials_list_comm = []
- for item in combinations_with_replacement(variables, max_degree):
- powers = dict()
- for variable in variables:
- powers[variable] = 0
- for variable in item:
- if variable != 1:
- powers[variable] += 1
- if sum(powers.values()) >= min_degree:
- monomials_list_comm.append(Mul(*item))
- yield from set(monomials_list_comm)
- else:
- monomials_list_non_comm = []
- for item in product(variables, repeat=max_degree):
- powers = dict()
- for variable in variables:
- powers[variable] = 0
- for variable in item:
- if variable != 1:
- powers[variable] += 1
- if sum(powers.values()) >= min_degree:
- monomials_list_non_comm.append(Mul(*item))
- yield from set(monomials_list_non_comm)
- else:
- if any(min_degrees[i] > max_degrees[i] for i in range(n)):
- raise ValueError('min_degrees[i] must be <= max_degrees[i] for all i')
- power_lists = []
- for var, min_d, max_d in zip(variables, min_degrees, max_degrees):
- power_lists.append([var**i for i in range(min_d, max_d + 1)])
- for powers in product(*power_lists):
- yield Mul(*powers)
- def monomial_count(V, N):
- r"""
- Computes the number of monomials.
- The number of monomials is given by the following formula:
- .. math::
- \frac{(\#V + N)!}{\#V! N!}
- where `N` is a total degree and `V` is a set of variables.
- Examples
- ========
- >>> from sympy.polys.monomials import itermonomials, monomial_count
- >>> from sympy.polys.orderings import monomial_key
- >>> from sympy.abc import x, y
- >>> monomial_count(2, 2)
- 6
- >>> M = list(itermonomials([x, y], 2))
- >>> sorted(M, key=monomial_key('grlex', [y, x]))
- [1, x, y, x**2, x*y, y**2]
- >>> len(M)
- 6
- """
- from sympy.functions.combinatorial.factorials import factorial
- return factorial(V + N) / factorial(V) / factorial(N)
- def monomial_mul(A, B):
- """
- Multiplication of tuples representing monomials.
- Examples
- ========
- Lets multiply `x**3*y**4*z` with `x*y**2`::
- >>> from sympy.polys.monomials import monomial_mul
- >>> monomial_mul((3, 4, 1), (1, 2, 0))
- (4, 6, 1)
- which gives `x**4*y**5*z`.
- """
- return tuple([ a + b for a, b in zip(A, B) ])
- def monomial_div(A, B):
- """
- Division of tuples representing monomials.
- Examples
- ========
- Lets divide `x**3*y**4*z` by `x*y**2`::
- >>> from sympy.polys.monomials import monomial_div
- >>> monomial_div((3, 4, 1), (1, 2, 0))
- (2, 2, 1)
- which gives `x**2*y**2*z`. However::
- >>> monomial_div((3, 4, 1), (1, 2, 2)) is None
- True
- `x*y**2*z**2` does not divide `x**3*y**4*z`.
- """
- C = monomial_ldiv(A, B)
- if all(c >= 0 for c in C):
- return tuple(C)
- else:
- return None
- def monomial_ldiv(A, B):
- """
- Division of tuples representing monomials.
- Examples
- ========
- Lets divide `x**3*y**4*z` by `x*y**2`::
- >>> from sympy.polys.monomials import monomial_ldiv
- >>> monomial_ldiv((3, 4, 1), (1, 2, 0))
- (2, 2, 1)
- which gives `x**2*y**2*z`.
- >>> monomial_ldiv((3, 4, 1), (1, 2, 2))
- (2, 2, -1)
- which gives `x**2*y**2*z**-1`.
- """
- return tuple([ a - b for a, b in zip(A, B) ])
- def monomial_pow(A, n):
- """Return the n-th pow of the monomial. """
- return tuple([ a*n for a in A ])
- def monomial_gcd(A, B):
- """
- Greatest common divisor of tuples representing monomials.
- Examples
- ========
- Lets compute GCD of `x*y**4*z` and `x**3*y**2`::
- >>> from sympy.polys.monomials import monomial_gcd
- >>> monomial_gcd((1, 4, 1), (3, 2, 0))
- (1, 2, 0)
- which gives `x*y**2`.
- """
- return tuple([ min(a, b) for a, b in zip(A, B) ])
- def monomial_lcm(A, B):
- """
- Least common multiple of tuples representing monomials.
- Examples
- ========
- Lets compute LCM of `x*y**4*z` and `x**3*y**2`::
- >>> from sympy.polys.monomials import monomial_lcm
- >>> monomial_lcm((1, 4, 1), (3, 2, 0))
- (3, 4, 1)
- which gives `x**3*y**4*z`.
- """
- return tuple([ max(a, b) for a, b in zip(A, B) ])
- def monomial_divides(A, B):
- """
- Does there exist a monomial X such that XA == B?
- Examples
- ========
- >>> from sympy.polys.monomials import monomial_divides
- >>> monomial_divides((1, 2), (3, 4))
- True
- >>> monomial_divides((1, 2), (0, 2))
- False
- """
- return all(a <= b for a, b in zip(A, B))
- def monomial_max(*monoms):
- """
- Returns maximal degree for each variable in a set of monomials.
- Examples
- ========
- Consider monomials `x**3*y**4*z**5`, `y**5*z` and `x**6*y**3*z**9`.
- We wish to find out what is the maximal degree for each of `x`, `y`
- and `z` variables::
- >>> from sympy.polys.monomials import monomial_max
- >>> monomial_max((3,4,5), (0,5,1), (6,3,9))
- (6, 5, 9)
- """
- M = list(monoms[0])
- for N in monoms[1:]:
- for i, n in enumerate(N):
- M[i] = max(M[i], n)
- return tuple(M)
- def monomial_min(*monoms):
- """
- Returns minimal degree for each variable in a set of monomials.
- Examples
- ========
- Consider monomials `x**3*y**4*z**5`, `y**5*z` and `x**6*y**3*z**9`.
- We wish to find out what is the minimal degree for each of `x`, `y`
- and `z` variables::
- >>> from sympy.polys.monomials import monomial_min
- >>> monomial_min((3,4,5), (0,5,1), (6,3,9))
- (0, 3, 1)
- """
- M = list(monoms[0])
- for N in monoms[1:]:
- for i, n in enumerate(N):
- M[i] = min(M[i], n)
- return tuple(M)
- def monomial_deg(M):
- """
- Returns the total degree of a monomial.
- Examples
- ========
- The total degree of `xy^2` is 3:
- >>> from sympy.polys.monomials import monomial_deg
- >>> monomial_deg((1, 2))
- 3
- """
- return sum(M)
- def term_div(a, b, domain):
- """Division of two terms in over a ring/field. """
- a_lm, a_lc = a
- b_lm, b_lc = b
- monom = monomial_div(a_lm, b_lm)
- if domain.is_Field:
- if monom is not None:
- return monom, domain.quo(a_lc, b_lc)
- else:
- return None
- else:
- if not (monom is None or a_lc % b_lc):
- return monom, domain.quo(a_lc, b_lc)
- else:
- return None
- class MonomialOps:
- """Code generator of fast monomial arithmetic functions. """
- def __init__(self, ngens):
- self.ngens = ngens
- def _build(self, code, name):
- ns = {}
- exec(code, ns)
- return ns[name]
- def _vars(self, name):
- return [ "%s%s" % (name, i) for i in range(self.ngens) ]
- def mul(self):
- name = "monomial_mul"
- template = dedent("""\
- def %(name)s(A, B):
- (%(A)s,) = A
- (%(B)s,) = B
- return (%(AB)s,)
- """)
- A = self._vars("a")
- B = self._vars("b")
- AB = [ "%s + %s" % (a, b) for a, b in zip(A, B) ]
- code = template % dict(name=name, A=", ".join(A), B=", ".join(B), AB=", ".join(AB))
- return self._build(code, name)
- def pow(self):
- name = "monomial_pow"
- template = dedent("""\
- def %(name)s(A, k):
- (%(A)s,) = A
- return (%(Ak)s,)
- """)
- A = self._vars("a")
- Ak = [ "%s*k" % a for a in A ]
- code = template % dict(name=name, A=", ".join(A), Ak=", ".join(Ak))
- return self._build(code, name)
- def mulpow(self):
- name = "monomial_mulpow"
- template = dedent("""\
- def %(name)s(A, B, k):
- (%(A)s,) = A
- (%(B)s,) = B
- return (%(ABk)s,)
- """)
- A = self._vars("a")
- B = self._vars("b")
- ABk = [ "%s + %s*k" % (a, b) for a, b in zip(A, B) ]
- code = template % dict(name=name, A=", ".join(A), B=", ".join(B), ABk=", ".join(ABk))
- return self._build(code, name)
- def ldiv(self):
- name = "monomial_ldiv"
- template = dedent("""\
- def %(name)s(A, B):
- (%(A)s,) = A
- (%(B)s,) = B
- return (%(AB)s,)
- """)
- A = self._vars("a")
- B = self._vars("b")
- AB = [ "%s - %s" % (a, b) for a, b in zip(A, B) ]
- code = template % dict(name=name, A=", ".join(A), B=", ".join(B), AB=", ".join(AB))
- return self._build(code, name)
- def div(self):
- name = "monomial_div"
- template = dedent("""\
- def %(name)s(A, B):
- (%(A)s,) = A
- (%(B)s,) = B
- %(RAB)s
- return (%(R)s,)
- """)
- A = self._vars("a")
- B = self._vars("b")
- RAB = [ "r%(i)s = a%(i)s - b%(i)s\n if r%(i)s < 0: return None" % dict(i=i) for i in range(self.ngens) ]
- R = self._vars("r")
- code = template % dict(name=name, A=", ".join(A), B=", ".join(B), RAB="\n ".join(RAB), R=", ".join(R))
- return self._build(code, name)
- def lcm(self):
- name = "monomial_lcm"
- template = dedent("""\
- def %(name)s(A, B):
- (%(A)s,) = A
- (%(B)s,) = B
- return (%(AB)s,)
- """)
- A = self._vars("a")
- B = self._vars("b")
- AB = [ "%s if %s >= %s else %s" % (a, a, b, b) for a, b in zip(A, B) ]
- code = template % dict(name=name, A=", ".join(A), B=", ".join(B), AB=", ".join(AB))
- return self._build(code, name)
- def gcd(self):
- name = "monomial_gcd"
- template = dedent("""\
- def %(name)s(A, B):
- (%(A)s,) = A
- (%(B)s,) = B
- return (%(AB)s,)
- """)
- A = self._vars("a")
- B = self._vars("b")
- AB = [ "%s if %s <= %s else %s" % (a, a, b, b) for a, b in zip(A, B) ]
- code = template % dict(name=name, A=", ".join(A), B=", ".join(B), AB=", ".join(AB))
- return self._build(code, name)
- @public
- class Monomial(PicklableWithSlots):
- """Class representing a monomial, i.e. a product of powers. """
- __slots__ = ('exponents', 'gens')
- def __init__(self, monom, gens=None):
- if not iterable(monom):
- rep, gens = dict_from_expr(sympify(monom), gens=gens)
- if len(rep) == 1 and list(rep.values())[0] == 1:
- monom = list(rep.keys())[0]
- else:
- raise ValueError("Expected a monomial got {}".format(monom))
- self.exponents = tuple(map(int, monom))
- self.gens = gens
- def rebuild(self, exponents, gens=None):
- return self.__class__(exponents, gens or self.gens)
- def __len__(self):
- return len(self.exponents)
- def __iter__(self):
- return iter(self.exponents)
- def __getitem__(self, item):
- return self.exponents[item]
- def __hash__(self):
- return hash((self.__class__.__name__, self.exponents, self.gens))
- def __str__(self):
- if self.gens:
- return "*".join([ "%s**%s" % (gen, exp) for gen, exp in zip(self.gens, self.exponents) ])
- else:
- return "%s(%s)" % (self.__class__.__name__, self.exponents)
- def as_expr(self, *gens):
- """Convert a monomial instance to a SymPy expression. """
- gens = gens or self.gens
- if not gens:
- raise ValueError(
- "Cannot convert %s to an expression without generators" % self)
- return Mul(*[ gen**exp for gen, exp in zip(gens, self.exponents) ])
- def __eq__(self, other):
- if isinstance(other, Monomial):
- exponents = other.exponents
- elif isinstance(other, (tuple, Tuple)):
- exponents = other
- else:
- return False
- return self.exponents == exponents
- def __ne__(self, other):
- return not self == other
- def __mul__(self, other):
- if isinstance(other, Monomial):
- exponents = other.exponents
- elif isinstance(other, (tuple, Tuple)):
- exponents = other
- else:
- raise NotImplementedError
- return self.rebuild(monomial_mul(self.exponents, exponents))
- def __truediv__(self, other):
- if isinstance(other, Monomial):
- exponents = other.exponents
- elif isinstance(other, (tuple, Tuple)):
- exponents = other
- else:
- raise NotImplementedError
- result = monomial_div(self.exponents, exponents)
- if result is not None:
- return self.rebuild(result)
- else:
- raise ExactQuotientFailed(self, Monomial(other))
- __floordiv__ = __truediv__
- def __pow__(self, other):
- n = int(other)
- if not n:
- return self.rebuild([0]*len(self))
- elif n > 0:
- exponents = self.exponents
- for i in range(1, n):
- exponents = monomial_mul(exponents, self.exponents)
- return self.rebuild(exponents)
- else:
- raise ValueError("a non-negative integer expected, got %s" % other)
- def gcd(self, other):
- """Greatest common divisor of monomials. """
- if isinstance(other, Monomial):
- exponents = other.exponents
- elif isinstance(other, (tuple, Tuple)):
- exponents = other
- else:
- raise TypeError(
- "an instance of Monomial class expected, got %s" % other)
- return self.rebuild(monomial_gcd(self.exponents, exponents))
- def lcm(self, other):
- """Least common multiple of monomials. """
- if isinstance(other, Monomial):
- exponents = other.exponents
- elif isinstance(other, (tuple, Tuple)):
- exponents = other
- else:
- raise TypeError(
- "an instance of Monomial class expected, got %s" % other)
- return self.rebuild(monomial_lcm(self.exponents, exponents))
|