"""Implementation of :class:`ComplexField` class. """ from sympy.external.gmpy import SYMPY_INTS from sympy.core.numbers import Float, I from sympy.polys.domains.characteristiczero import CharacteristicZero from sympy.polys.domains.field import Field from sympy.polys.domains.gaussiandomains import QQ_I from sympy.polys.domains.simpledomain import SimpleDomain from sympy.polys.polyerrors import DomainError, CoercionFailed from sympy.utilities import public from mpmath import MPContext @public class ComplexField(Field, CharacteristicZero, SimpleDomain): """Complex numbers up to the given precision. """ rep = 'CC' is_ComplexField = is_CC = True is_Exact = False is_Numerical = True has_assoc_Ring = False has_assoc_Field = True _default_precision = 53 @property def has_default_precision(self): return self.precision == self._default_precision @property def precision(self): return self._context.prec @property def dps(self): return self._context.dps @property def tolerance(self): return self._tolerance def __init__(self, prec=None, dps=None, tol=None): # XXX: The tolerance parameter is ignored but is kept for backward # compatibility for now. context = MPContext() if prec is None and dps is None: context.prec = self._default_precision elif dps is None: context.prec = prec elif prec is None: context.dps = dps else: raise TypeError("Cannot set both prec and dps") self._context = context self._dtype = context.mpc self.zero = self.dtype(0) self.one = self.dtype(1) # XXX: Neither of these is actually used anywhere. self._max_denom = max(2**context.prec // 200, 99) self._tolerance = self.one / self._max_denom @property def tp(self): # XXX: Domain treats tp as an alias of dtype. Here we need two separate # things: dtype is a callable to make/convert instances. We use tp with # isinstance to check if an object is an instance of the domain # already. return self._dtype def dtype(self, x, y=0): # XXX: This is needed because mpmath does not recognise fmpz. # It might be better to add conversion routines to mpmath and if that # happens then this can be removed. if isinstance(x, SYMPY_INTS): x = int(x) if isinstance(y, SYMPY_INTS): y = int(y) return self._dtype(x, y) def __eq__(self, other): return isinstance(other, ComplexField) and self.precision == other.precision def __hash__(self): return hash((self.__class__.__name__, self._dtype, self.precision)) def to_sympy(self, element): """Convert ``element`` to SymPy number. """ return Float(element.real, self.dps) + I*Float(element.imag, self.dps) def from_sympy(self, expr): """Convert SymPy's number to ``dtype``. """ number = expr.evalf(n=self.dps) real, imag = number.as_real_imag() if real.is_Number and imag.is_Number: return self.dtype(real, imag) else: raise CoercionFailed("expected complex number, got %s" % expr) def from_ZZ(self, element, base): return self.dtype(element) def from_ZZ_gmpy(self, element, base): return self.dtype(int(element)) def from_ZZ_python(self, element, base): return self.dtype(element) def from_QQ(self, element, base): return self.dtype(int(element.numerator)) / int(element.denominator) def from_QQ_python(self, element, base): return self.dtype(element.numerator) / element.denominator def from_QQ_gmpy(self, element, base): return self.dtype(int(element.numerator)) / int(element.denominator) def from_GaussianIntegerRing(self, element, base): return self.dtype(int(element.x), int(element.y)) def from_GaussianRationalField(self, element, base): x = element.x y = element.y return (self.dtype(int(x.numerator)) / int(x.denominator) + self.dtype(0, int(y.numerator)) / int(y.denominator)) def from_AlgebraicField(self, element, base): return self.from_sympy(base.to_sympy(element).evalf(self.dps)) def from_RealField(self, element, base): return self.dtype(element) def from_ComplexField(self, element, base): return self.dtype(element) def get_ring(self): """Returns a ring associated with ``self``. """ raise DomainError("there is no ring associated with %s" % self) def get_exact(self): """Returns an exact domain associated with ``self``. """ return QQ_I def is_negative(self, element): """Returns ``False`` for any ``ComplexElement``. """ return False def is_positive(self, element): """Returns ``False`` for any ``ComplexElement``. """ return False def is_nonnegative(self, element): """Returns ``False`` for any ``ComplexElement``. """ return False def is_nonpositive(self, element): """Returns ``False`` for any ``ComplexElement``. """ return False def gcd(self, a, b): """Returns GCD of ``a`` and ``b``. """ return self.one def lcm(self, a, b): """Returns LCM of ``a`` and ``b``. """ return a*b def almosteq(self, a, b, tolerance=None): """Check if ``a`` and ``b`` are almost equal. """ return self._context.almosteq(a, b, tolerance) def is_square(self, a): """Returns ``True``. Every complex number has a complex square root.""" return True def exsqrt(self, a): r"""Returns the principal complex square root of ``a``. Explanation =========== The argument of the principal square root is always within $(-\frac{\pi}{2}, \frac{\pi}{2}]$. The square root may be slightly inaccurate due to floating point rounding error. """ return a ** 0.5 CC = ComplexField()