1522 lines
47 KiB
Python
1522 lines
47 KiB
Python
|
from functools import reduce
|
||
|
from itertools import product
|
||
|
|
||
|
from sympy.core.basic import Basic
|
||
|
from sympy.core.containers import Tuple
|
||
|
from sympy.core.expr import Expr
|
||
|
from sympy.core.function import Lambda
|
||
|
from sympy.core.logic import fuzzy_not, fuzzy_or, fuzzy_and
|
||
|
from sympy.core.mod import Mod
|
||
|
from sympy.core.numbers import oo, igcd, Rational
|
||
|
from sympy.core.relational import Eq, is_eq
|
||
|
from sympy.core.kind import NumberKind
|
||
|
from sympy.core.singleton import Singleton, S
|
||
|
from sympy.core.symbol import Dummy, symbols, Symbol
|
||
|
from sympy.core.sympify import _sympify, sympify, _sympy_converter
|
||
|
from sympy.functions.elementary.integers import ceiling, floor
|
||
|
from sympy.functions.elementary.trigonometric import sin, cos
|
||
|
from sympy.logic.boolalg import And, Or
|
||
|
from .sets import Set, Interval, Union, FiniteSet, ProductSet, SetKind
|
||
|
from sympy.utilities.misc import filldedent
|
||
|
|
||
|
|
||
|
class Rationals(Set, metaclass=Singleton):
|
||
|
"""
|
||
|
Represents the rational numbers. This set is also available as
|
||
|
the singleton ``S.Rationals``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import S
|
||
|
>>> S.Half in S.Rationals
|
||
|
True
|
||
|
>>> iterable = iter(S.Rationals)
|
||
|
>>> [next(iterable) for i in range(12)]
|
||
|
[0, 1, -1, 1/2, 2, -1/2, -2, 1/3, 3, -1/3, -3, 2/3]
|
||
|
"""
|
||
|
|
||
|
is_iterable = True
|
||
|
_inf = S.NegativeInfinity
|
||
|
_sup = S.Infinity
|
||
|
is_empty = False
|
||
|
is_finite_set = False
|
||
|
|
||
|
def _contains(self, other):
|
||
|
if not isinstance(other, Expr):
|
||
|
return False
|
||
|
return other.is_rational
|
||
|
|
||
|
def __iter__(self):
|
||
|
yield S.Zero
|
||
|
yield S.One
|
||
|
yield S.NegativeOne
|
||
|
d = 2
|
||
|
while True:
|
||
|
for n in range(d):
|
||
|
if igcd(n, d) == 1:
|
||
|
yield Rational(n, d)
|
||
|
yield Rational(d, n)
|
||
|
yield Rational(-n, d)
|
||
|
yield Rational(-d, n)
|
||
|
d += 1
|
||
|
|
||
|
@property
|
||
|
def _boundary(self):
|
||
|
return S.Reals
|
||
|
|
||
|
def _kind(self):
|
||
|
return SetKind(NumberKind)
|
||
|
|
||
|
|
||
|
class Naturals(Set, metaclass=Singleton):
|
||
|
"""
|
||
|
Represents the natural numbers (or counting numbers) which are all
|
||
|
positive integers starting from 1. This set is also available as
|
||
|
the singleton ``S.Naturals``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import S, Interval, pprint
|
||
|
>>> 5 in S.Naturals
|
||
|
True
|
||
|
>>> iterable = iter(S.Naturals)
|
||
|
>>> next(iterable)
|
||
|
1
|
||
|
>>> next(iterable)
|
||
|
2
|
||
|
>>> next(iterable)
|
||
|
3
|
||
|
>>> pprint(S.Naturals.intersect(Interval(0, 10)))
|
||
|
{1, 2, ..., 10}
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
Naturals0 : non-negative integers (i.e. includes 0, too)
|
||
|
Integers : also includes negative integers
|
||
|
"""
|
||
|
|
||
|
is_iterable = True
|
||
|
_inf = S.One
|
||
|
_sup = S.Infinity
|
||
|
is_empty = False
|
||
|
is_finite_set = False
|
||
|
|
||
|
def _contains(self, other):
|
||
|
if not isinstance(other, Expr):
|
||
|
return False
|
||
|
elif other.is_positive and other.is_integer:
|
||
|
return True
|
||
|
elif other.is_integer is False or other.is_positive is False:
|
||
|
return False
|
||
|
|
||
|
def _eval_is_subset(self, other):
|
||
|
return Range(1, oo).is_subset(other)
|
||
|
|
||
|
def _eval_is_superset(self, other):
|
||
|
return Range(1, oo).is_superset(other)
|
||
|
|
||
|
def __iter__(self):
|
||
|
i = self._inf
|
||
|
while True:
|
||
|
yield i
|
||
|
i = i + 1
|
||
|
|
||
|
@property
|
||
|
def _boundary(self):
|
||
|
return self
|
||
|
|
||
|
def as_relational(self, x):
|
||
|
return And(Eq(floor(x), x), x >= self.inf, x < oo)
|
||
|
|
||
|
def _kind(self):
|
||
|
return SetKind(NumberKind)
|
||
|
|
||
|
|
||
|
class Naturals0(Naturals):
|
||
|
"""Represents the whole numbers which are all the non-negative integers,
|
||
|
inclusive of zero.
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
Naturals : positive integers; does not include 0
|
||
|
Integers : also includes the negative integers
|
||
|
"""
|
||
|
_inf = S.Zero
|
||
|
|
||
|
def _contains(self, other):
|
||
|
if not isinstance(other, Expr):
|
||
|
return S.false
|
||
|
elif other.is_integer and other.is_nonnegative:
|
||
|
return S.true
|
||
|
elif other.is_integer is False or other.is_nonnegative is False:
|
||
|
return S.false
|
||
|
|
||
|
def _eval_is_subset(self, other):
|
||
|
return Range(oo).is_subset(other)
|
||
|
|
||
|
def _eval_is_superset(self, other):
|
||
|
return Range(oo).is_superset(other)
|
||
|
|
||
|
|
||
|
class Integers(Set, metaclass=Singleton):
|
||
|
"""
|
||
|
Represents all integers: positive, negative and zero. This set is also
|
||
|
available as the singleton ``S.Integers``.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import S, Interval, pprint
|
||
|
>>> 5 in S.Naturals
|
||
|
True
|
||
|
>>> iterable = iter(S.Integers)
|
||
|
>>> next(iterable)
|
||
|
0
|
||
|
>>> next(iterable)
|
||
|
1
|
||
|
>>> next(iterable)
|
||
|
-1
|
||
|
>>> next(iterable)
|
||
|
2
|
||
|
|
||
|
>>> pprint(S.Integers.intersect(Interval(-4, 4)))
|
||
|
{-4, -3, ..., 4}
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
Naturals0 : non-negative integers
|
||
|
Integers : positive and negative integers and zero
|
||
|
"""
|
||
|
|
||
|
is_iterable = True
|
||
|
is_empty = False
|
||
|
is_finite_set = False
|
||
|
|
||
|
def _contains(self, other):
|
||
|
if not isinstance(other, Expr):
|
||
|
return S.false
|
||
|
return other.is_integer
|
||
|
|
||
|
def __iter__(self):
|
||
|
yield S.Zero
|
||
|
i = S.One
|
||
|
while True:
|
||
|
yield i
|
||
|
yield -i
|
||
|
i = i + 1
|
||
|
|
||
|
@property
|
||
|
def _inf(self):
|
||
|
return S.NegativeInfinity
|
||
|
|
||
|
@property
|
||
|
def _sup(self):
|
||
|
return S.Infinity
|
||
|
|
||
|
@property
|
||
|
def _boundary(self):
|
||
|
return self
|
||
|
|
||
|
def _kind(self):
|
||
|
return SetKind(NumberKind)
|
||
|
|
||
|
def as_relational(self, x):
|
||
|
return And(Eq(floor(x), x), -oo < x, x < oo)
|
||
|
|
||
|
def _eval_is_subset(self, other):
|
||
|
return Range(-oo, oo).is_subset(other)
|
||
|
|
||
|
def _eval_is_superset(self, other):
|
||
|
return Range(-oo, oo).is_superset(other)
|
||
|
|
||
|
|
||
|
class Reals(Interval, metaclass=Singleton):
|
||
|
"""
|
||
|
Represents all real numbers
|
||
|
from negative infinity to positive infinity,
|
||
|
including all integer, rational and irrational numbers.
|
||
|
This set is also available as the singleton ``S.Reals``.
|
||
|
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import S, Rational, pi, I
|
||
|
>>> 5 in S.Reals
|
||
|
True
|
||
|
>>> Rational(-1, 2) in S.Reals
|
||
|
True
|
||
|
>>> pi in S.Reals
|
||
|
True
|
||
|
>>> 3*I in S.Reals
|
||
|
False
|
||
|
>>> S.Reals.contains(pi)
|
||
|
True
|
||
|
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
ComplexRegion
|
||
|
"""
|
||
|
@property
|
||
|
def start(self):
|
||
|
return S.NegativeInfinity
|
||
|
|
||
|
@property
|
||
|
def end(self):
|
||
|
return S.Infinity
|
||
|
|
||
|
@property
|
||
|
def left_open(self):
|
||
|
return True
|
||
|
|
||
|
@property
|
||
|
def right_open(self):
|
||
|
return True
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
return other == Interval(S.NegativeInfinity, S.Infinity)
|
||
|
|
||
|
def __hash__(self):
|
||
|
return hash(Interval(S.NegativeInfinity, S.Infinity))
|
||
|
|
||
|
|
||
|
class ImageSet(Set):
|
||
|
"""
|
||
|
Image of a set under a mathematical function. The transformation
|
||
|
must be given as a Lambda function which has as many arguments
|
||
|
as the elements of the set upon which it operates, e.g. 1 argument
|
||
|
when acting on the set of integers or 2 arguments when acting on
|
||
|
a complex region.
|
||
|
|
||
|
This function is not normally called directly, but is called
|
||
|
from ``imageset``.
|
||
|
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Symbol, S, pi, Dummy, Lambda
|
||
|
>>> from sympy import FiniteSet, ImageSet, Interval
|
||
|
|
||
|
>>> x = Symbol('x')
|
||
|
>>> N = S.Naturals
|
||
|
>>> squares = ImageSet(Lambda(x, x**2), N) # {x**2 for x in N}
|
||
|
>>> 4 in squares
|
||
|
True
|
||
|
>>> 5 in squares
|
||
|
False
|
||
|
|
||
|
>>> FiniteSet(0, 1, 2, 3, 4, 5, 6, 7, 9, 10).intersect(squares)
|
||
|
{1, 4, 9}
|
||
|
|
||
|
>>> square_iterable = iter(squares)
|
||
|
>>> for i in range(4):
|
||
|
... next(square_iterable)
|
||
|
1
|
||
|
4
|
||
|
9
|
||
|
16
|
||
|
|
||
|
If you want to get value for `x` = 2, 1/2 etc. (Please check whether the
|
||
|
`x` value is in ``base_set`` or not before passing it as args)
|
||
|
|
||
|
>>> squares.lamda(2)
|
||
|
4
|
||
|
>>> squares.lamda(S(1)/2)
|
||
|
1/4
|
||
|
|
||
|
>>> n = Dummy('n')
|
||
|
>>> solutions = ImageSet(Lambda(n, n*pi), S.Integers) # solutions of sin(x) = 0
|
||
|
>>> dom = Interval(-1, 1)
|
||
|
>>> dom.intersect(solutions)
|
||
|
{0}
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
sympy.sets.sets.imageset
|
||
|
"""
|
||
|
def __new__(cls, flambda, *sets):
|
||
|
if not isinstance(flambda, Lambda):
|
||
|
raise ValueError('First argument must be a Lambda')
|
||
|
|
||
|
signature = flambda.signature
|
||
|
|
||
|
if len(signature) != len(sets):
|
||
|
raise ValueError('Incompatible signature')
|
||
|
|
||
|
sets = [_sympify(s) for s in sets]
|
||
|
|
||
|
if not all(isinstance(s, Set) for s in sets):
|
||
|
raise TypeError("Set arguments to ImageSet should of type Set")
|
||
|
|
||
|
if not all(cls._check_sig(sg, st) for sg, st in zip(signature, sets)):
|
||
|
raise ValueError("Signature %s does not match sets %s" % (signature, sets))
|
||
|
|
||
|
if flambda is S.IdentityFunction and len(sets) == 1:
|
||
|
return sets[0]
|
||
|
|
||
|
if not set(flambda.variables) & flambda.expr.free_symbols:
|
||
|
is_empty = fuzzy_or(s.is_empty for s in sets)
|
||
|
if is_empty == True:
|
||
|
return S.EmptySet
|
||
|
elif is_empty == False:
|
||
|
return FiniteSet(flambda.expr)
|
||
|
|
||
|
return Basic.__new__(cls, flambda, *sets)
|
||
|
|
||
|
lamda = property(lambda self: self.args[0])
|
||
|
base_sets = property(lambda self: self.args[1:])
|
||
|
|
||
|
@property
|
||
|
def base_set(self):
|
||
|
# XXX: Maybe deprecate this? It is poorly defined in handling
|
||
|
# the multivariate case...
|
||
|
sets = self.base_sets
|
||
|
if len(sets) == 1:
|
||
|
return sets[0]
|
||
|
else:
|
||
|
return ProductSet(*sets).flatten()
|
||
|
|
||
|
@property
|
||
|
def base_pset(self):
|
||
|
return ProductSet(*self.base_sets)
|
||
|
|
||
|
@classmethod
|
||
|
def _check_sig(cls, sig_i, set_i):
|
||
|
if sig_i.is_symbol:
|
||
|
return True
|
||
|
elif isinstance(set_i, ProductSet):
|
||
|
sets = set_i.sets
|
||
|
if len(sig_i) != len(sets):
|
||
|
return False
|
||
|
# Recurse through the signature for nested tuples:
|
||
|
return all(cls._check_sig(ts, ps) for ts, ps in zip(sig_i, sets))
|
||
|
else:
|
||
|
# XXX: Need a better way of checking whether a set is a set of
|
||
|
# Tuples or not. For example a FiniteSet can contain Tuples
|
||
|
# but so can an ImageSet or a ConditionSet. Others like
|
||
|
# Integers, Reals etc can not contain Tuples. We could just
|
||
|
# list the possibilities here... Current code for e.g.
|
||
|
# _contains probably only works for ProductSet.
|
||
|
return True # Give the benefit of the doubt
|
||
|
|
||
|
def __iter__(self):
|
||
|
already_seen = set()
|
||
|
for i in self.base_pset:
|
||
|
val = self.lamda(*i)
|
||
|
if val in already_seen:
|
||
|
continue
|
||
|
else:
|
||
|
already_seen.add(val)
|
||
|
yield val
|
||
|
|
||
|
def _is_multivariate(self):
|
||
|
return len(self.lamda.variables) > 1
|
||
|
|
||
|
def _contains(self, other):
|
||
|
from sympy.solvers.solveset import _solveset_multi
|
||
|
|
||
|
def get_symsetmap(signature, base_sets):
|
||
|
'''Attempt to get a map of symbols to base_sets'''
|
||
|
queue = list(zip(signature, base_sets))
|
||
|
symsetmap = {}
|
||
|
for sig, base_set in queue:
|
||
|
if sig.is_symbol:
|
||
|
symsetmap[sig] = base_set
|
||
|
elif base_set.is_ProductSet:
|
||
|
sets = base_set.sets
|
||
|
if len(sig) != len(sets):
|
||
|
raise ValueError("Incompatible signature")
|
||
|
# Recurse
|
||
|
queue.extend(zip(sig, sets))
|
||
|
else:
|
||
|
# If we get here then we have something like sig = (x, y) and
|
||
|
# base_set = {(1, 2), (3, 4)}. For now we give up.
|
||
|
return None
|
||
|
|
||
|
return symsetmap
|
||
|
|
||
|
def get_equations(expr, candidate):
|
||
|
'''Find the equations relating symbols in expr and candidate.'''
|
||
|
queue = [(expr, candidate)]
|
||
|
for e, c in queue:
|
||
|
if not isinstance(e, Tuple):
|
||
|
yield Eq(e, c)
|
||
|
elif not isinstance(c, Tuple) or len(e) != len(c):
|
||
|
yield False
|
||
|
return
|
||
|
else:
|
||
|
queue.extend(zip(e, c))
|
||
|
|
||
|
# Get the basic objects together:
|
||
|
other = _sympify(other)
|
||
|
expr = self.lamda.expr
|
||
|
sig = self.lamda.signature
|
||
|
variables = self.lamda.variables
|
||
|
base_sets = self.base_sets
|
||
|
|
||
|
# Use dummy symbols for ImageSet parameters so they don't match
|
||
|
# anything in other
|
||
|
rep = {v: Dummy(v.name) for v in variables}
|
||
|
variables = [v.subs(rep) for v in variables]
|
||
|
sig = sig.subs(rep)
|
||
|
expr = expr.subs(rep)
|
||
|
|
||
|
# Map the parts of other to those in the Lambda expr
|
||
|
equations = []
|
||
|
for eq in get_equations(expr, other):
|
||
|
# Unsatisfiable equation?
|
||
|
if eq is False:
|
||
|
return False
|
||
|
equations.append(eq)
|
||
|
|
||
|
# Map the symbols in the signature to the corresponding domains
|
||
|
symsetmap = get_symsetmap(sig, base_sets)
|
||
|
if symsetmap is None:
|
||
|
# Can't factor the base sets to a ProductSet
|
||
|
return None
|
||
|
|
||
|
# Which of the variables in the Lambda signature need to be solved for?
|
||
|
symss = (eq.free_symbols for eq in equations)
|
||
|
variables = set(variables) & reduce(set.union, symss, set())
|
||
|
|
||
|
# Use internal multivariate solveset
|
||
|
variables = tuple(variables)
|
||
|
base_sets = [symsetmap[v] for v in variables]
|
||
|
solnset = _solveset_multi(equations, variables, base_sets)
|
||
|
if solnset is None:
|
||
|
return None
|
||
|
return fuzzy_not(solnset.is_empty)
|
||
|
|
||
|
@property
|
||
|
def is_iterable(self):
|
||
|
return all(s.is_iterable for s in self.base_sets)
|
||
|
|
||
|
def doit(self, **hints):
|
||
|
from sympy.sets.setexpr import SetExpr
|
||
|
f = self.lamda
|
||
|
sig = f.signature
|
||
|
if len(sig) == 1 and sig[0].is_symbol and isinstance(f.expr, Expr):
|
||
|
base_set = self.base_sets[0]
|
||
|
return SetExpr(base_set)._eval_func(f).set
|
||
|
if all(s.is_FiniteSet for s in self.base_sets):
|
||
|
return FiniteSet(*(f(*a) for a in product(*self.base_sets)))
|
||
|
return self
|
||
|
|
||
|
def _kind(self):
|
||
|
return SetKind(self.lamda.expr.kind)
|
||
|
|
||
|
|
||
|
class Range(Set):
|
||
|
"""
|
||
|
Represents a range of integers. Can be called as ``Range(stop)``,
|
||
|
``Range(start, stop)``, or ``Range(start, stop, step)``; when ``step`` is
|
||
|
not given it defaults to 1.
|
||
|
|
||
|
``Range(stop)`` is the same as ``Range(0, stop, 1)`` and the stop value
|
||
|
(just as for Python ranges) is not included in the Range values.
|
||
|
|
||
|
>>> from sympy import Range
|
||
|
>>> list(Range(3))
|
||
|
[0, 1, 2]
|
||
|
|
||
|
The step can also be negative:
|
||
|
|
||
|
>>> list(Range(10, 0, -2))
|
||
|
[10, 8, 6, 4, 2]
|
||
|
|
||
|
The stop value is made canonical so equivalent ranges always
|
||
|
have the same args:
|
||
|
|
||
|
>>> Range(0, 10, 3)
|
||
|
Range(0, 12, 3)
|
||
|
|
||
|
Infinite ranges are allowed. ``oo`` and ``-oo`` are never included in the
|
||
|
set (``Range`` is always a subset of ``Integers``). If the starting point
|
||
|
is infinite, then the final value is ``stop - step``. To iterate such a
|
||
|
range, it needs to be reversed:
|
||
|
|
||
|
>>> from sympy import oo
|
||
|
>>> r = Range(-oo, 1)
|
||
|
>>> r[-1]
|
||
|
0
|
||
|
>>> next(iter(r))
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
TypeError: Cannot iterate over Range with infinite start
|
||
|
>>> next(iter(r.reversed))
|
||
|
0
|
||
|
|
||
|
Although ``Range`` is a :class:`Set` (and supports the normal set
|
||
|
operations) it maintains the order of the elements and can
|
||
|
be used in contexts where ``range`` would be used.
|
||
|
|
||
|
>>> from sympy import Interval
|
||
|
>>> Range(0, 10, 2).intersect(Interval(3, 7))
|
||
|
Range(4, 8, 2)
|
||
|
>>> list(_)
|
||
|
[4, 6]
|
||
|
|
||
|
Although slicing of a Range will always return a Range -- possibly
|
||
|
empty -- an empty set will be returned from any intersection that
|
||
|
is empty:
|
||
|
|
||
|
>>> Range(3)[:0]
|
||
|
Range(0, 0, 1)
|
||
|
>>> Range(3).intersect(Interval(4, oo))
|
||
|
EmptySet
|
||
|
>>> Range(3).intersect(Range(4, oo))
|
||
|
EmptySet
|
||
|
|
||
|
Range will accept symbolic arguments but has very limited support
|
||
|
for doing anything other than displaying the Range:
|
||
|
|
||
|
>>> from sympy import Symbol, pprint
|
||
|
>>> from sympy.abc import i, j, k
|
||
|
>>> Range(i, j, k).start
|
||
|
i
|
||
|
>>> Range(i, j, k).inf
|
||
|
Traceback (most recent call last):
|
||
|
...
|
||
|
ValueError: invalid method for symbolic range
|
||
|
|
||
|
Better success will be had when using integer symbols:
|
||
|
|
||
|
>>> n = Symbol('n', integer=True)
|
||
|
>>> r = Range(n, n + 20, 3)
|
||
|
>>> r.inf
|
||
|
n
|
||
|
>>> pprint(r)
|
||
|
{n, n + 3, ..., n + 18}
|
||
|
"""
|
||
|
|
||
|
def __new__(cls, *args):
|
||
|
if len(args) == 1:
|
||
|
if isinstance(args[0], range):
|
||
|
raise TypeError(
|
||
|
'use sympify(%s) to convert range to Range' % args[0])
|
||
|
|
||
|
# expand range
|
||
|
slc = slice(*args)
|
||
|
|
||
|
if slc.step == 0:
|
||
|
raise ValueError("step cannot be 0")
|
||
|
|
||
|
start, stop, step = slc.start or 0, slc.stop, slc.step or 1
|
||
|
try:
|
||
|
ok = []
|
||
|
for w in (start, stop, step):
|
||
|
w = sympify(w)
|
||
|
if w in [S.NegativeInfinity, S.Infinity] or (
|
||
|
w.has(Symbol) and w.is_integer != False):
|
||
|
ok.append(w)
|
||
|
elif not w.is_Integer:
|
||
|
if w.is_infinite:
|
||
|
raise ValueError('infinite symbols not allowed')
|
||
|
raise ValueError
|
||
|
else:
|
||
|
ok.append(w)
|
||
|
except ValueError:
|
||
|
raise ValueError(filldedent('''
|
||
|
Finite arguments to Range must be integers; `imageset` can define
|
||
|
other cases, e.g. use `imageset(i, i/10, Range(3))` to give
|
||
|
[0, 1/10, 1/5].'''))
|
||
|
start, stop, step = ok
|
||
|
|
||
|
null = False
|
||
|
if any(i.has(Symbol) for i in (start, stop, step)):
|
||
|
dif = stop - start
|
||
|
n = dif/step
|
||
|
if n.is_Rational:
|
||
|
if dif == 0:
|
||
|
null = True
|
||
|
else: # (x, x + 5, 2) or (x, 3*x, x)
|
||
|
n = floor(n)
|
||
|
end = start + n*step
|
||
|
if dif.is_Rational: # (x, x + 5, 2)
|
||
|
if (end - stop).is_negative:
|
||
|
end += step
|
||
|
else: # (x, 3*x, x)
|
||
|
if (end/stop - 1).is_negative:
|
||
|
end += step
|
||
|
elif n.is_extended_negative:
|
||
|
null = True
|
||
|
else:
|
||
|
end = stop # other methods like sup and reversed must fail
|
||
|
elif start.is_infinite:
|
||
|
span = step*(stop - start)
|
||
|
if span is S.NaN or span <= 0:
|
||
|
null = True
|
||
|
elif step.is_Integer and stop.is_infinite and abs(step) != 1:
|
||
|
raise ValueError(filldedent('''
|
||
|
Step size must be %s in this case.''' % (1 if step > 0 else -1)))
|
||
|
else:
|
||
|
end = stop
|
||
|
else:
|
||
|
oostep = step.is_infinite
|
||
|
if oostep:
|
||
|
step = S.One if step > 0 else S.NegativeOne
|
||
|
n = ceiling((stop - start)/step)
|
||
|
if n <= 0:
|
||
|
null = True
|
||
|
elif oostep:
|
||
|
step = S.One # make it canonical
|
||
|
end = start + step
|
||
|
else:
|
||
|
end = start + n*step
|
||
|
if null:
|
||
|
start = end = S.Zero
|
||
|
step = S.One
|
||
|
return Basic.__new__(cls, start, end, step)
|
||
|
|
||
|
start = property(lambda self: self.args[0])
|
||
|
stop = property(lambda self: self.args[1])
|
||
|
step = property(lambda self: self.args[2])
|
||
|
|
||
|
@property
|
||
|
def reversed(self):
|
||
|
"""Return an equivalent Range in the opposite order.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Range
|
||
|
>>> Range(10).reversed
|
||
|
Range(9, -1, -1)
|
||
|
"""
|
||
|
if self.has(Symbol):
|
||
|
n = (self.stop - self.start)/self.step
|
||
|
if not n.is_extended_positive or not all(
|
||
|
i.is_integer or i.is_infinite for i in self.args):
|
||
|
raise ValueError('invalid method for symbolic range')
|
||
|
if self.start == self.stop:
|
||
|
return self
|
||
|
return self.func(
|
||
|
self.stop - self.step, self.start - self.step, -self.step)
|
||
|
|
||
|
def _kind(self):
|
||
|
return SetKind(NumberKind)
|
||
|
|
||
|
def _contains(self, other):
|
||
|
if self.start == self.stop:
|
||
|
return S.false
|
||
|
if other.is_infinite:
|
||
|
return S.false
|
||
|
if not other.is_integer:
|
||
|
return other.is_integer
|
||
|
if self.has(Symbol):
|
||
|
n = (self.stop - self.start)/self.step
|
||
|
if not n.is_extended_positive or not all(
|
||
|
i.is_integer or i.is_infinite for i in self.args):
|
||
|
return
|
||
|
else:
|
||
|
n = self.size
|
||
|
if self.start.is_finite:
|
||
|
ref = self.start
|
||
|
elif self.stop.is_finite:
|
||
|
ref = self.stop
|
||
|
else: # both infinite; step is +/- 1 (enforced by __new__)
|
||
|
return S.true
|
||
|
if n == 1:
|
||
|
return Eq(other, self[0])
|
||
|
res = (ref - other) % self.step
|
||
|
if res == S.Zero:
|
||
|
if self.has(Symbol):
|
||
|
d = Dummy('i')
|
||
|
return self.as_relational(d).subs(d, other)
|
||
|
return And(other >= self.inf, other <= self.sup)
|
||
|
elif res.is_Integer: # off sequence
|
||
|
return S.false
|
||
|
else: # symbolic/unsimplified residue modulo step
|
||
|
return None
|
||
|
|
||
|
def __iter__(self):
|
||
|
n = self.size # validate
|
||
|
if not (n.has(S.Infinity) or n.has(S.NegativeInfinity) or n.is_Integer):
|
||
|
raise TypeError("Cannot iterate over symbolic Range")
|
||
|
if self.start in [S.NegativeInfinity, S.Infinity]:
|
||
|
raise TypeError("Cannot iterate over Range with infinite start")
|
||
|
elif self.start != self.stop:
|
||
|
i = self.start
|
||
|
if n.is_infinite:
|
||
|
while True:
|
||
|
yield i
|
||
|
i += self.step
|
||
|
else:
|
||
|
for _ in range(n):
|
||
|
yield i
|
||
|
i += self.step
|
||
|
|
||
|
@property
|
||
|
def is_iterable(self):
|
||
|
# Check that size can be determined, used by __iter__
|
||
|
dif = self.stop - self.start
|
||
|
n = dif/self.step
|
||
|
if not (n.has(S.Infinity) or n.has(S.NegativeInfinity) or n.is_Integer):
|
||
|
return False
|
||
|
if self.start in [S.NegativeInfinity, S.Infinity]:
|
||
|
return False
|
||
|
if not (n.is_extended_nonnegative and all(i.is_integer for i in self.args)):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
def __len__(self):
|
||
|
rv = self.size
|
||
|
if rv is S.Infinity:
|
||
|
raise ValueError('Use .size to get the length of an infinite Range')
|
||
|
return int(rv)
|
||
|
|
||
|
@property
|
||
|
def size(self):
|
||
|
if self.start == self.stop:
|
||
|
return S.Zero
|
||
|
dif = self.stop - self.start
|
||
|
n = dif/self.step
|
||
|
if n.is_infinite:
|
||
|
return S.Infinity
|
||
|
if n.is_extended_nonnegative and all(i.is_integer for i in self.args):
|
||
|
return abs(floor(n))
|
||
|
raise ValueError('Invalid method for symbolic Range')
|
||
|
|
||
|
@property
|
||
|
def is_finite_set(self):
|
||
|
if self.start.is_integer and self.stop.is_integer:
|
||
|
return True
|
||
|
return self.size.is_finite
|
||
|
|
||
|
@property
|
||
|
def is_empty(self):
|
||
|
try:
|
||
|
return self.size.is_zero
|
||
|
except ValueError:
|
||
|
return None
|
||
|
|
||
|
def __bool__(self):
|
||
|
# this only distinguishes between definite null range
|
||
|
# and non-null/unknown null; getting True doesn't mean
|
||
|
# that it actually is not null
|
||
|
b = is_eq(self.start, self.stop)
|
||
|
if b is None:
|
||
|
raise ValueError('cannot tell if Range is null or not')
|
||
|
return not bool(b)
|
||
|
|
||
|
def __getitem__(self, i):
|
||
|
ooslice = "cannot slice from the end with an infinite value"
|
||
|
zerostep = "slice step cannot be zero"
|
||
|
infinite = "slicing not possible on range with infinite start"
|
||
|
# if we had to take every other element in the following
|
||
|
# oo, ..., 6, 4, 2, 0
|
||
|
# we might get oo, ..., 4, 0 or oo, ..., 6, 2
|
||
|
ambiguous = "cannot unambiguously re-stride from the end " + \
|
||
|
"with an infinite value"
|
||
|
if isinstance(i, slice):
|
||
|
if self.size.is_finite: # validates, too
|
||
|
if self.start == self.stop:
|
||
|
return Range(0)
|
||
|
start, stop, step = i.indices(self.size)
|
||
|
n = ceiling((stop - start)/step)
|
||
|
if n <= 0:
|
||
|
return Range(0)
|
||
|
canonical_stop = start + n*step
|
||
|
end = canonical_stop - step
|
||
|
ss = step*self.step
|
||
|
return Range(self[start], self[end] + ss, ss)
|
||
|
else: # infinite Range
|
||
|
start = i.start
|
||
|
stop = i.stop
|
||
|
if i.step == 0:
|
||
|
raise ValueError(zerostep)
|
||
|
step = i.step or 1
|
||
|
ss = step*self.step
|
||
|
#---------------------
|
||
|
# handle infinite Range
|
||
|
# i.e. Range(-oo, oo) or Range(oo, -oo, -1)
|
||
|
# --------------------
|
||
|
if self.start.is_infinite and self.stop.is_infinite:
|
||
|
raise ValueError(infinite)
|
||
|
#---------------------
|
||
|
# handle infinite on right
|
||
|
# e.g. Range(0, oo) or Range(0, -oo, -1)
|
||
|
# --------------------
|
||
|
if self.stop.is_infinite:
|
||
|
# start and stop are not interdependent --
|
||
|
# they only depend on step --so we use the
|
||
|
# equivalent reversed values
|
||
|
return self.reversed[
|
||
|
stop if stop is None else -stop + 1:
|
||
|
start if start is None else -start:
|
||
|
step].reversed
|
||
|
#---------------------
|
||
|
# handle infinite on the left
|
||
|
# e.g. Range(oo, 0, -1) or Range(-oo, 0)
|
||
|
# --------------------
|
||
|
# consider combinations of
|
||
|
# start/stop {== None, < 0, == 0, > 0} and
|
||
|
# step {< 0, > 0}
|
||
|
if start is None:
|
||
|
if stop is None:
|
||
|
if step < 0:
|
||
|
return Range(self[-1], self.start, ss)
|
||
|
elif step > 1:
|
||
|
raise ValueError(ambiguous)
|
||
|
else: # == 1
|
||
|
return self
|
||
|
elif stop < 0:
|
||
|
if step < 0:
|
||
|
return Range(self[-1], self[stop], ss)
|
||
|
else: # > 0
|
||
|
return Range(self.start, self[stop], ss)
|
||
|
elif stop == 0:
|
||
|
if step > 0:
|
||
|
return Range(0)
|
||
|
else: # < 0
|
||
|
raise ValueError(ooslice)
|
||
|
elif stop == 1:
|
||
|
if step > 0:
|
||
|
raise ValueError(ooslice) # infinite singleton
|
||
|
else: # < 0
|
||
|
raise ValueError(ooslice)
|
||
|
else: # > 1
|
||
|
raise ValueError(ooslice)
|
||
|
elif start < 0:
|
||
|
if stop is None:
|
||
|
if step < 0:
|
||
|
return Range(self[start], self.start, ss)
|
||
|
else: # > 0
|
||
|
return Range(self[start], self.stop, ss)
|
||
|
elif stop < 0:
|
||
|
return Range(self[start], self[stop], ss)
|
||
|
elif stop == 0:
|
||
|
if step < 0:
|
||
|
raise ValueError(ooslice)
|
||
|
else: # > 0
|
||
|
return Range(0)
|
||
|
elif stop > 0:
|
||
|
raise ValueError(ooslice)
|
||
|
elif start == 0:
|
||
|
if stop is None:
|
||
|
if step < 0:
|
||
|
raise ValueError(ooslice) # infinite singleton
|
||
|
elif step > 1:
|
||
|
raise ValueError(ambiguous)
|
||
|
else: # == 1
|
||
|
return self
|
||
|
elif stop < 0:
|
||
|
if step > 1:
|
||
|
raise ValueError(ambiguous)
|
||
|
elif step == 1:
|
||
|
return Range(self.start, self[stop], ss)
|
||
|
else: # < 0
|
||
|
return Range(0)
|
||
|
else: # >= 0
|
||
|
raise ValueError(ooslice)
|
||
|
elif start > 0:
|
||
|
raise ValueError(ooslice)
|
||
|
else:
|
||
|
if self.start == self.stop:
|
||
|
raise IndexError('Range index out of range')
|
||
|
if not (all(i.is_integer or i.is_infinite
|
||
|
for i in self.args) and ((self.stop - self.start)/
|
||
|
self.step).is_extended_positive):
|
||
|
raise ValueError('Invalid method for symbolic Range')
|
||
|
if i == 0:
|
||
|
if self.start.is_infinite:
|
||
|
raise ValueError(ooslice)
|
||
|
return self.start
|
||
|
if i == -1:
|
||
|
if self.stop.is_infinite:
|
||
|
raise ValueError(ooslice)
|
||
|
return self.stop - self.step
|
||
|
n = self.size # must be known for any other index
|
||
|
rv = (self.stop if i < 0 else self.start) + i*self.step
|
||
|
if rv.is_infinite:
|
||
|
raise ValueError(ooslice)
|
||
|
val = (rv - self.start)/self.step
|
||
|
rel = fuzzy_or([val.is_infinite,
|
||
|
fuzzy_and([val.is_nonnegative, (n-val).is_nonnegative])])
|
||
|
if rel:
|
||
|
return rv
|
||
|
if rel is None:
|
||
|
raise ValueError('Invalid method for symbolic Range')
|
||
|
raise IndexError("Range index out of range")
|
||
|
|
||
|
@property
|
||
|
def _inf(self):
|
||
|
if not self:
|
||
|
return S.EmptySet.inf
|
||
|
if self.has(Symbol):
|
||
|
if all(i.is_integer or i.is_infinite for i in self.args):
|
||
|
dif = self.stop - self.start
|
||
|
if self.step.is_positive and dif.is_positive:
|
||
|
return self.start
|
||
|
elif self.step.is_negative and dif.is_negative:
|
||
|
return self.stop - self.step
|
||
|
raise ValueError('invalid method for symbolic range')
|
||
|
if self.step > 0:
|
||
|
return self.start
|
||
|
else:
|
||
|
return self.stop - self.step
|
||
|
|
||
|
@property
|
||
|
def _sup(self):
|
||
|
if not self:
|
||
|
return S.EmptySet.sup
|
||
|
if self.has(Symbol):
|
||
|
if all(i.is_integer or i.is_infinite for i in self.args):
|
||
|
dif = self.stop - self.start
|
||
|
if self.step.is_positive and dif.is_positive:
|
||
|
return self.stop - self.step
|
||
|
elif self.step.is_negative and dif.is_negative:
|
||
|
return self.start
|
||
|
raise ValueError('invalid method for symbolic range')
|
||
|
if self.step > 0:
|
||
|
return self.stop - self.step
|
||
|
else:
|
||
|
return self.start
|
||
|
|
||
|
@property
|
||
|
def _boundary(self):
|
||
|
return self
|
||
|
|
||
|
def as_relational(self, x):
|
||
|
"""Rewrite a Range in terms of equalities and logic operators. """
|
||
|
if self.start.is_infinite:
|
||
|
assert not self.stop.is_infinite # by instantiation
|
||
|
a = self.reversed.start
|
||
|
else:
|
||
|
a = self.start
|
||
|
step = self.step
|
||
|
in_seq = Eq(Mod(x - a, step), 0)
|
||
|
ints = And(Eq(Mod(a, 1), 0), Eq(Mod(step, 1), 0))
|
||
|
n = (self.stop - self.start)/self.step
|
||
|
if n == 0:
|
||
|
return S.EmptySet.as_relational(x)
|
||
|
if n == 1:
|
||
|
return And(Eq(x, a), ints)
|
||
|
try:
|
||
|
a, b = self.inf, self.sup
|
||
|
except ValueError:
|
||
|
a = None
|
||
|
if a is not None:
|
||
|
range_cond = And(
|
||
|
x > a if a.is_infinite else x >= a,
|
||
|
x < b if b.is_infinite else x <= b)
|
||
|
else:
|
||
|
a, b = self.start, self.stop - self.step
|
||
|
range_cond = Or(
|
||
|
And(self.step >= 1, x > a if a.is_infinite else x >= a,
|
||
|
x < b if b.is_infinite else x <= b),
|
||
|
And(self.step <= -1, x < a if a.is_infinite else x <= a,
|
||
|
x > b if b.is_infinite else x >= b))
|
||
|
return And(in_seq, ints, range_cond)
|
||
|
|
||
|
|
||
|
_sympy_converter[range] = lambda r: Range(r.start, r.stop, r.step)
|
||
|
|
||
|
def normalize_theta_set(theta):
|
||
|
r"""
|
||
|
Normalize a Real Set `theta` in the interval `[0, 2\pi)`. It returns
|
||
|
a normalized value of theta in the Set. For Interval, a maximum of
|
||
|
one cycle $[0, 2\pi]$, is returned i.e. for theta equal to $[0, 10\pi]$,
|
||
|
returned normalized value would be $[0, 2\pi)$. As of now intervals
|
||
|
with end points as non-multiples of ``pi`` is not supported.
|
||
|
|
||
|
Raises
|
||
|
======
|
||
|
|
||
|
NotImplementedError
|
||
|
The algorithms for Normalizing theta Set are not yet
|
||
|
implemented.
|
||
|
ValueError
|
||
|
The input is not valid, i.e. the input is not a real set.
|
||
|
RuntimeError
|
||
|
It is a bug, please report to the github issue tracker.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy.sets.fancysets import normalize_theta_set
|
||
|
>>> from sympy import Interval, FiniteSet, pi
|
||
|
>>> normalize_theta_set(Interval(9*pi/2, 5*pi))
|
||
|
Interval(pi/2, pi)
|
||
|
>>> normalize_theta_set(Interval(-3*pi/2, pi/2))
|
||
|
Interval.Ropen(0, 2*pi)
|
||
|
>>> normalize_theta_set(Interval(-pi/2, pi/2))
|
||
|
Union(Interval(0, pi/2), Interval.Ropen(3*pi/2, 2*pi))
|
||
|
>>> normalize_theta_set(Interval(-4*pi, 3*pi))
|
||
|
Interval.Ropen(0, 2*pi)
|
||
|
>>> normalize_theta_set(Interval(-3*pi/2, -pi/2))
|
||
|
Interval(pi/2, 3*pi/2)
|
||
|
>>> normalize_theta_set(FiniteSet(0, pi, 3*pi))
|
||
|
{0, pi}
|
||
|
|
||
|
"""
|
||
|
from sympy.functions.elementary.trigonometric import _pi_coeff
|
||
|
|
||
|
if theta.is_Interval:
|
||
|
interval_len = theta.measure
|
||
|
# one complete circle
|
||
|
if interval_len >= 2*S.Pi:
|
||
|
if interval_len == 2*S.Pi and theta.left_open and theta.right_open:
|
||
|
k = _pi_coeff(theta.start)
|
||
|
return Union(Interval(0, k*S.Pi, False, True),
|
||
|
Interval(k*S.Pi, 2*S.Pi, True, True))
|
||
|
return Interval(0, 2*S.Pi, False, True)
|
||
|
|
||
|
k_start, k_end = _pi_coeff(theta.start), _pi_coeff(theta.end)
|
||
|
|
||
|
if k_start is None or k_end is None:
|
||
|
raise NotImplementedError("Normalizing theta without pi as coefficient is "
|
||
|
"not yet implemented")
|
||
|
new_start = k_start*S.Pi
|
||
|
new_end = k_end*S.Pi
|
||
|
|
||
|
if new_start > new_end:
|
||
|
return Union(Interval(S.Zero, new_end, False, theta.right_open),
|
||
|
Interval(new_start, 2*S.Pi, theta.left_open, True))
|
||
|
else:
|
||
|
return Interval(new_start, new_end, theta.left_open, theta.right_open)
|
||
|
|
||
|
elif theta.is_FiniteSet:
|
||
|
new_theta = []
|
||
|
for element in theta:
|
||
|
k = _pi_coeff(element)
|
||
|
if k is None:
|
||
|
raise NotImplementedError('Normalizing theta without pi as '
|
||
|
'coefficient, is not Implemented.')
|
||
|
else:
|
||
|
new_theta.append(k*S.Pi)
|
||
|
return FiniteSet(*new_theta)
|
||
|
|
||
|
elif theta.is_Union:
|
||
|
return Union(*[normalize_theta_set(interval) for interval in theta.args])
|
||
|
|
||
|
elif theta.is_subset(S.Reals):
|
||
|
raise NotImplementedError("Normalizing theta when, it is of type %s is not "
|
||
|
"implemented" % type(theta))
|
||
|
else:
|
||
|
raise ValueError(" %s is not a real set" % (theta))
|
||
|
|
||
|
|
||
|
class ComplexRegion(Set):
|
||
|
r"""
|
||
|
Represents the Set of all Complex Numbers. It can represent a
|
||
|
region of Complex Plane in both the standard forms Polar and
|
||
|
Rectangular coordinates.
|
||
|
|
||
|
* Polar Form
|
||
|
Input is in the form of the ProductSet or Union of ProductSets
|
||
|
of the intervals of ``r`` and ``theta``, and use the flag ``polar=True``.
|
||
|
|
||
|
.. math:: Z = \{z \in \mathbb{C} \mid z = r\times (\cos(\theta) + I\sin(\theta)), r \in [\texttt{r}], \theta \in [\texttt{theta}]\}
|
||
|
|
||
|
* Rectangular Form
|
||
|
Input is in the form of the ProductSet or Union of ProductSets
|
||
|
of interval of x and y, the real and imaginary parts of the Complex numbers in a plane.
|
||
|
Default input type is in rectangular form.
|
||
|
|
||
|
.. math:: Z = \{z \in \mathbb{C} \mid z = x + Iy, x \in [\operatorname{re}(z)], y \in [\operatorname{im}(z)]\}
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import ComplexRegion, Interval, S, I, Union
|
||
|
>>> a = Interval(2, 3)
|
||
|
>>> b = Interval(4, 6)
|
||
|
>>> c1 = ComplexRegion(a*b) # Rectangular Form
|
||
|
>>> c1
|
||
|
CartesianComplexRegion(ProductSet(Interval(2, 3), Interval(4, 6)))
|
||
|
|
||
|
* c1 represents the rectangular region in complex plane
|
||
|
surrounded by the coordinates (2, 4), (3, 4), (3, 6) and
|
||
|
(2, 6), of the four vertices.
|
||
|
|
||
|
>>> c = Interval(1, 8)
|
||
|
>>> c2 = ComplexRegion(Union(a*b, b*c))
|
||
|
>>> c2
|
||
|
CartesianComplexRegion(Union(ProductSet(Interval(2, 3), Interval(4, 6)), ProductSet(Interval(4, 6), Interval(1, 8))))
|
||
|
|
||
|
* c2 represents the Union of two rectangular regions in complex
|
||
|
plane. One of them surrounded by the coordinates of c1 and
|
||
|
other surrounded by the coordinates (4, 1), (6, 1), (6, 8) and
|
||
|
(4, 8).
|
||
|
|
||
|
>>> 2.5 + 4.5*I in c1
|
||
|
True
|
||
|
>>> 2.5 + 6.5*I in c1
|
||
|
False
|
||
|
|
||
|
>>> r = Interval(0, 1)
|
||
|
>>> theta = Interval(0, 2*S.Pi)
|
||
|
>>> c2 = ComplexRegion(r*theta, polar=True) # Polar Form
|
||
|
>>> c2 # unit Disk
|
||
|
PolarComplexRegion(ProductSet(Interval(0, 1), Interval.Ropen(0, 2*pi)))
|
||
|
|
||
|
* c2 represents the region in complex plane inside the
|
||
|
Unit Disk centered at the origin.
|
||
|
|
||
|
>>> 0.5 + 0.5*I in c2
|
||
|
True
|
||
|
>>> 1 + 2*I in c2
|
||
|
False
|
||
|
|
||
|
>>> unit_disk = ComplexRegion(Interval(0, 1)*Interval(0, 2*S.Pi), polar=True)
|
||
|
>>> upper_half_unit_disk = ComplexRegion(Interval(0, 1)*Interval(0, S.Pi), polar=True)
|
||
|
>>> intersection = unit_disk.intersect(upper_half_unit_disk)
|
||
|
>>> intersection
|
||
|
PolarComplexRegion(ProductSet(Interval(0, 1), Interval(0, pi)))
|
||
|
>>> intersection == upper_half_unit_disk
|
||
|
True
|
||
|
|
||
|
See Also
|
||
|
========
|
||
|
|
||
|
CartesianComplexRegion
|
||
|
PolarComplexRegion
|
||
|
Complexes
|
||
|
|
||
|
"""
|
||
|
is_ComplexRegion = True
|
||
|
|
||
|
def __new__(cls, sets, polar=False):
|
||
|
if polar is False:
|
||
|
return CartesianComplexRegion(sets)
|
||
|
elif polar is True:
|
||
|
return PolarComplexRegion(sets)
|
||
|
else:
|
||
|
raise ValueError("polar should be either True or False")
|
||
|
|
||
|
@property
|
||
|
def sets(self):
|
||
|
"""
|
||
|
Return raw input sets to the self.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Interval, ComplexRegion, Union
|
||
|
>>> a = Interval(2, 3)
|
||
|
>>> b = Interval(4, 5)
|
||
|
>>> c = Interval(1, 7)
|
||
|
>>> C1 = ComplexRegion(a*b)
|
||
|
>>> C1.sets
|
||
|
ProductSet(Interval(2, 3), Interval(4, 5))
|
||
|
>>> C2 = ComplexRegion(Union(a*b, b*c))
|
||
|
>>> C2.sets
|
||
|
Union(ProductSet(Interval(2, 3), Interval(4, 5)), ProductSet(Interval(4, 5), Interval(1, 7)))
|
||
|
|
||
|
"""
|
||
|
return self.args[0]
|
||
|
|
||
|
@property
|
||
|
def psets(self):
|
||
|
"""
|
||
|
Return a tuple of sets (ProductSets) input of the self.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Interval, ComplexRegion, Union
|
||
|
>>> a = Interval(2, 3)
|
||
|
>>> b = Interval(4, 5)
|
||
|
>>> c = Interval(1, 7)
|
||
|
>>> C1 = ComplexRegion(a*b)
|
||
|
>>> C1.psets
|
||
|
(ProductSet(Interval(2, 3), Interval(4, 5)),)
|
||
|
>>> C2 = ComplexRegion(Union(a*b, b*c))
|
||
|
>>> C2.psets
|
||
|
(ProductSet(Interval(2, 3), Interval(4, 5)), ProductSet(Interval(4, 5), Interval(1, 7)))
|
||
|
|
||
|
"""
|
||
|
if self.sets.is_ProductSet:
|
||
|
psets = ()
|
||
|
psets = psets + (self.sets, )
|
||
|
else:
|
||
|
psets = self.sets.args
|
||
|
return psets
|
||
|
|
||
|
@property
|
||
|
def a_interval(self):
|
||
|
"""
|
||
|
Return the union of intervals of `x` when, self is in
|
||
|
rectangular form, or the union of intervals of `r` when
|
||
|
self is in polar form.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Interval, ComplexRegion, Union
|
||
|
>>> a = Interval(2, 3)
|
||
|
>>> b = Interval(4, 5)
|
||
|
>>> c = Interval(1, 7)
|
||
|
>>> C1 = ComplexRegion(a*b)
|
||
|
>>> C1.a_interval
|
||
|
Interval(2, 3)
|
||
|
>>> C2 = ComplexRegion(Union(a*b, b*c))
|
||
|
>>> C2.a_interval
|
||
|
Union(Interval(2, 3), Interval(4, 5))
|
||
|
|
||
|
"""
|
||
|
a_interval = []
|
||
|
for element in self.psets:
|
||
|
a_interval.append(element.args[0])
|
||
|
|
||
|
a_interval = Union(*a_interval)
|
||
|
return a_interval
|
||
|
|
||
|
@property
|
||
|
def b_interval(self):
|
||
|
"""
|
||
|
Return the union of intervals of `y` when, self is in
|
||
|
rectangular form, or the union of intervals of `theta`
|
||
|
when self is in polar form.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Interval, ComplexRegion, Union
|
||
|
>>> a = Interval(2, 3)
|
||
|
>>> b = Interval(4, 5)
|
||
|
>>> c = Interval(1, 7)
|
||
|
>>> C1 = ComplexRegion(a*b)
|
||
|
>>> C1.b_interval
|
||
|
Interval(4, 5)
|
||
|
>>> C2 = ComplexRegion(Union(a*b, b*c))
|
||
|
>>> C2.b_interval
|
||
|
Interval(1, 7)
|
||
|
|
||
|
"""
|
||
|
b_interval = []
|
||
|
for element in self.psets:
|
||
|
b_interval.append(element.args[1])
|
||
|
|
||
|
b_interval = Union(*b_interval)
|
||
|
return b_interval
|
||
|
|
||
|
@property
|
||
|
def _measure(self):
|
||
|
"""
|
||
|
The measure of self.sets.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Interval, ComplexRegion, S
|
||
|
>>> a, b = Interval(2, 5), Interval(4, 8)
|
||
|
>>> c = Interval(0, 2*S.Pi)
|
||
|
>>> c1 = ComplexRegion(a*b)
|
||
|
>>> c1.measure
|
||
|
12
|
||
|
>>> c2 = ComplexRegion(a*c, polar=True)
|
||
|
>>> c2.measure
|
||
|
6*pi
|
||
|
|
||
|
"""
|
||
|
return self.sets._measure
|
||
|
|
||
|
def _kind(self):
|
||
|
return self.args[0].kind
|
||
|
|
||
|
@classmethod
|
||
|
def from_real(cls, sets):
|
||
|
"""
|
||
|
Converts given subset of real numbers to a complex region.
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import Interval, ComplexRegion
|
||
|
>>> unit = Interval(0,1)
|
||
|
>>> ComplexRegion.from_real(unit)
|
||
|
CartesianComplexRegion(ProductSet(Interval(0, 1), {0}))
|
||
|
|
||
|
"""
|
||
|
if not sets.is_subset(S.Reals):
|
||
|
raise ValueError("sets must be a subset of the real line")
|
||
|
|
||
|
return CartesianComplexRegion(sets * FiniteSet(0))
|
||
|
|
||
|
def _contains(self, other):
|
||
|
from sympy.functions import arg, Abs
|
||
|
other = sympify(other)
|
||
|
isTuple = isinstance(other, Tuple)
|
||
|
if isTuple and len(other) != 2:
|
||
|
raise ValueError('expecting Tuple of length 2')
|
||
|
|
||
|
# If the other is not an Expression, and neither a Tuple
|
||
|
if not isinstance(other, (Expr, Tuple)):
|
||
|
return S.false
|
||
|
# self in rectangular form
|
||
|
if not self.polar:
|
||
|
re, im = other if isTuple else other.as_real_imag()
|
||
|
return fuzzy_or(fuzzy_and([
|
||
|
pset.args[0]._contains(re),
|
||
|
pset.args[1]._contains(im)])
|
||
|
for pset in self.psets)
|
||
|
|
||
|
# self in polar form
|
||
|
elif self.polar:
|
||
|
if other.is_zero:
|
||
|
# ignore undefined complex argument
|
||
|
return fuzzy_or(pset.args[0]._contains(S.Zero)
|
||
|
for pset in self.psets)
|
||
|
if isTuple:
|
||
|
r, theta = other
|
||
|
else:
|
||
|
r, theta = Abs(other), arg(other)
|
||
|
if theta.is_real and theta.is_number:
|
||
|
# angles in psets are normalized to [0, 2pi)
|
||
|
theta %= 2*S.Pi
|
||
|
return fuzzy_or(fuzzy_and([
|
||
|
pset.args[0]._contains(r),
|
||
|
pset.args[1]._contains(theta)])
|
||
|
for pset in self.psets)
|
||
|
|
||
|
|
||
|
class CartesianComplexRegion(ComplexRegion):
|
||
|
r"""
|
||
|
Set representing a square region of the complex plane.
|
||
|
|
||
|
.. math:: Z = \{z \in \mathbb{C} \mid z = x + Iy, x \in [\operatorname{re}(z)], y \in [\operatorname{im}(z)]\}
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import ComplexRegion, I, Interval
|
||
|
>>> region = ComplexRegion(Interval(1, 3) * Interval(4, 6))
|
||
|
>>> 2 + 5*I in region
|
||
|
True
|
||
|
>>> 5*I in region
|
||
|
False
|
||
|
|
||
|
See also
|
||
|
========
|
||
|
|
||
|
ComplexRegion
|
||
|
PolarComplexRegion
|
||
|
Complexes
|
||
|
"""
|
||
|
|
||
|
polar = False
|
||
|
variables = symbols('x, y', cls=Dummy)
|
||
|
|
||
|
def __new__(cls, sets):
|
||
|
|
||
|
if sets == S.Reals*S.Reals:
|
||
|
return S.Complexes
|
||
|
|
||
|
if all(_a.is_FiniteSet for _a in sets.args) and (len(sets.args) == 2):
|
||
|
|
||
|
# ** ProductSet of FiniteSets in the Complex Plane. **
|
||
|
# For Cases like ComplexRegion({2, 4}*{3}), It
|
||
|
# would return {2 + 3*I, 4 + 3*I}
|
||
|
|
||
|
# FIXME: This should probably be handled with something like:
|
||
|
# return ImageSet(Lambda((x, y), x+I*y), sets).rewrite(FiniteSet)
|
||
|
complex_num = []
|
||
|
for x in sets.args[0]:
|
||
|
for y in sets.args[1]:
|
||
|
complex_num.append(x + S.ImaginaryUnit*y)
|
||
|
return FiniteSet(*complex_num)
|
||
|
else:
|
||
|
return Set.__new__(cls, sets)
|
||
|
|
||
|
@property
|
||
|
def expr(self):
|
||
|
x, y = self.variables
|
||
|
return x + S.ImaginaryUnit*y
|
||
|
|
||
|
|
||
|
class PolarComplexRegion(ComplexRegion):
|
||
|
r"""
|
||
|
Set representing a polar region of the complex plane.
|
||
|
|
||
|
.. math:: Z = \{z \in \mathbb{C} \mid z = r\times (\cos(\theta) + I\sin(\theta)), r \in [\texttt{r}], \theta \in [\texttt{theta}]\}
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import ComplexRegion, Interval, oo, pi, I
|
||
|
>>> rset = Interval(0, oo)
|
||
|
>>> thetaset = Interval(0, pi)
|
||
|
>>> upper_half_plane = ComplexRegion(rset * thetaset, polar=True)
|
||
|
>>> 1 + I in upper_half_plane
|
||
|
True
|
||
|
>>> 1 - I in upper_half_plane
|
||
|
False
|
||
|
|
||
|
See also
|
||
|
========
|
||
|
|
||
|
ComplexRegion
|
||
|
CartesianComplexRegion
|
||
|
Complexes
|
||
|
|
||
|
"""
|
||
|
|
||
|
polar = True
|
||
|
variables = symbols('r, theta', cls=Dummy)
|
||
|
|
||
|
def __new__(cls, sets):
|
||
|
|
||
|
new_sets = []
|
||
|
# sets is Union of ProductSets
|
||
|
if not sets.is_ProductSet:
|
||
|
for k in sets.args:
|
||
|
new_sets.append(k)
|
||
|
# sets is ProductSets
|
||
|
else:
|
||
|
new_sets.append(sets)
|
||
|
# Normalize input theta
|
||
|
for k, v in enumerate(new_sets):
|
||
|
new_sets[k] = ProductSet(v.args[0],
|
||
|
normalize_theta_set(v.args[1]))
|
||
|
sets = Union(*new_sets)
|
||
|
return Set.__new__(cls, sets)
|
||
|
|
||
|
@property
|
||
|
def expr(self):
|
||
|
r, theta = self.variables
|
||
|
return r*(cos(theta) + S.ImaginaryUnit*sin(theta))
|
||
|
|
||
|
|
||
|
class Complexes(CartesianComplexRegion, metaclass=Singleton):
|
||
|
"""
|
||
|
The :class:`Set` of all complex numbers
|
||
|
|
||
|
Examples
|
||
|
========
|
||
|
|
||
|
>>> from sympy import S, I
|
||
|
>>> S.Complexes
|
||
|
Complexes
|
||
|
>>> 1 + I in S.Complexes
|
||
|
True
|
||
|
|
||
|
See also
|
||
|
========
|
||
|
|
||
|
Reals
|
||
|
ComplexRegion
|
||
|
|
||
|
"""
|
||
|
|
||
|
is_empty = False
|
||
|
is_finite_set = False
|
||
|
|
||
|
# Override property from superclass since Complexes has no args
|
||
|
@property
|
||
|
def sets(self):
|
||
|
return ProductSet(S.Reals, S.Reals)
|
||
|
|
||
|
def __new__(cls):
|
||
|
return Set.__new__(cls)
|