Skip to content

Commit

Permalink
Trac #15114: disallow dangerous coercions to RIF
Browse files Browse the repository at this point in the history
Remove the following coercions because they make interval arithmetic
much less trustworthy.
 * `RealField` --> `RealIntervalField`
 * `float` --> `ComplexIntervalField`
 * `SR` --> `ComplexIntervalField`

For example, one could easily do the following by accident.
{{{
    sage: iv = 1 + 2^(-55) + 0. + RIF(1)
    sage: iv.lower(), iv.upper()
    (2.00000000000000, 2.00000000000000)
}}}

See also [https://groups.google.com/forum/#!topic/sage-devel/zfg0PhFR_qA
this topic on sage-devel]

Already removed in other tickets:
 * `ComplexField` --> `ComplexIntervalField`

URL: https://trac.sagemath.org/15114
Reported by: mstreng
Ticket author(s): Marc Mezzarobba
Reviewer(s): Vincent Delecroix
  • Loading branch information
Release Manager committed Feb 13, 2021
2 parents 10f5eb7 + 3c7644f commit d2cf1d0
Show file tree
Hide file tree
Showing 17 changed files with 281 additions and 88 deletions.
2 changes: 1 addition & 1 deletion src/sage/arith/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5599,7 +5599,7 @@ def _key_complex_for_display(a):
ai = a.imag()
if not ai:
return (0, ar)
epsilon = 1e-10
epsilon = ar.parent()(1e-10)
if ar.abs() < epsilon:
ar_truncated = 0
elif ar.prec() < 34:
Expand Down
12 changes: 4 additions & 8 deletions src/sage/categories/pushout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2542,14 +2542,10 @@ def merge(self, other):
We check that :trac:`12353` has been resolved::
sage: RealIntervalField(53)(-1) > RR(1)
False
sage: RealIntervalField(54)(-1) > RR(1)
False
sage: RealIntervalField(54)(1) > RR(-1)
True
sage: RealIntervalField(53)(1) > RR(-1)
True
sage: RIF(1) > RR(1)
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for >: 'Real Interval Field with 53 bits of precision' and 'Real Field with 53 bits of precision'
We check that various pushouts work::
Expand Down
20 changes: 10 additions & 10 deletions src/sage/doctest/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,17 +935,17 @@ def add_tolerance(self, wantval, want):
sage: want_tol = MarkedOutput().update(tol=0.0001)
sage: want_abs = MarkedOutput().update(abs_tol=0.0001)
sage: want_rel = MarkedOutput().update(rel_tol=0.0001)
sage: OC.add_tolerance(pi.n(64), want_tol).endpoints()
sage: OC.add_tolerance(RIF(pi.n(64)), want_tol).endpoints()
(3.14127849432443, 3.14190681285516)
sage: OC.add_tolerance(pi.n(64), want_abs).endpoints()
sage: OC.add_tolerance(RIF(pi.n(64)), want_abs).endpoints()
(3.14149265358979, 3.14169265358980)
sage: OC.add_tolerance(pi.n(64), want_rel).endpoints()
sage: OC.add_tolerance(RIF(pi.n(64)), want_rel).endpoints()
(3.14127849432443, 3.14190681285516)
sage: OC.add_tolerance(1e1000, want_tol)
sage: OC.add_tolerance(RIF(1e1000), want_tol)
1.000?e1000
sage: OC.add_tolerance(1e1000, want_abs)
sage: OC.add_tolerance(RIF(1e1000), want_abs)
1.000000000000000?e1000
sage: OC.add_tolerance(1e1000, want_rel)
sage: OC.add_tolerance(RIF(1e1000), want_rel)
1.000?e1000
sage: OC.add_tolerance(0, want_tol)
0.000?
Expand All @@ -956,13 +956,13 @@ def add_tolerance(self, wantval, want):
"""
if want.tol:
if wantval == 0:
return want.tol * RIFtol(-1,1)
return RIFtol(want.tol) * RIFtol(-1,1)
else:
return wantval * (1 + want.tol * RIFtol(-1,1))
return wantval * (1 + RIFtol(want.tol) * RIFtol(-1,1))
elif want.abs_tol:
return wantval + want.abs_tol * RIFtol(-1,1)
return wantval + RIFtol(want.abs_tol) * RIFtol(-1,1)
elif want.rel_tol:
return wantval * (1 + want.rel_tol * RIFtol(-1,1))
return wantval * (1 + RIFtol(want.rel_tol) * RIFtol(-1,1))
else:
return wantval

Expand Down
7 changes: 4 additions & 3 deletions src/sage/rings/complex_arb.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ from sage.rings.integer cimport Integer
from sage.rings.polynomial.polynomial_complex_arb cimport Polynomial_complex_arb
from sage.rings.real_arb cimport mpfi_to_arb, arb_to_mpfi
from sage.rings.real_arb import RealBallField
from sage.rings.real_mpfi cimport RealIntervalField_class
from sage.rings.real_mpfr cimport RealField_class, RealField, RealNumber
from sage.rings.ring import Field
from sage.structure.element cimport Element, ModuleElement
Expand All @@ -187,7 +188,7 @@ from sage.structure.unique_representation import UniqueRepresentation
from sage.arith.long cimport is_small_python_int

from sage.rings.complex_mpfr import ComplexField
from sage.rings.complex_interval_field import ComplexIntervalField
from sage.rings.complex_interval_field import ComplexIntervalField, ComplexIntervalField_class
from sage.rings.integer_ring import ZZ

cdef void ComplexIntervalFieldElement_to_acb(
Expand Down Expand Up @@ -829,9 +830,9 @@ class ComplexBallField(UniqueRepresentation, Field):
cdef bint real = False
if ring is None:
ring = self
elif isinstance(ring, ComplexBallField):
elif isinstance(ring, (ComplexBallField, ComplexIntervalField_class)):
pass
elif isinstance(ring, RealBallField):
elif isinstance(ring, (RealBallField, RealIntervalField_class)):
real = True
elif ring.has_coerce_map_from(self):
pass
Expand Down
4 changes: 2 additions & 2 deletions src/sage/rings/complex_interval.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement):
EXAMPLES::
sage: I = CIF.gen()
sage: b = 1.5 + 2.5*I
sage: b = 3/2 + 5/2*I
sage: TestSuite(b).run()
"""
def __cinit__(self, parent, *args):
Expand Down Expand Up @@ -1677,7 +1677,7 @@ cdef class ComplexIntervalFieldElement(sage.structure.element.FieldElement):
1.570796326794897?
sage: (-i).argument()
-1.570796326794897?
sage: (RR('-0.001') - i).argument()
sage: (-1/1000 - i).argument()
-1.571796326461564?
sage: CIF(2).argument()
0
Expand Down
29 changes: 19 additions & 10 deletions src/sage/rings/complex_interval_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,12 +477,13 @@ def _coerce_map_from_(self, S):
- anything that canonically coerces to the real interval field
with this precision
- some exact or lazy parents representing subsets of the complex
numbers, such as ``QQbar`` and ``CLF``.
EXAMPLES::
sage: CIF((2,1)) + 2 + I # indirect doctest
4 + 2*I
sage: CIF((2,1)) + RR.pi()
5.1415926535897932? + 1*I
sage: CIF((2,1)) + CC.pi()
Traceback (most recent call last):
...
Expand Down Expand Up @@ -512,21 +513,29 @@ def _coerce_map_from_(self, S):
Conversion via _complex_mpfi_ method map:
From: Universal Cyclotomic Field
To: Complex Interval Field with 53 bits of precision
TESTS::
sage: CIF.has_coerce_map_from(RR)
False
sage: CIF.has_coerce_map_from(RDF)
False
sage: CIF.has_coerce_map_from(float)
False
"""
# Direct and efficient conversions
if S is ZZ or S is QQ or S is float:
return True
if S is int:
if S is ZZ or S is QQ or S is int:
return True
if isinstance(S, (ComplexIntervalField_class,
RealIntervalField_class)):
return S.precision() >= self._prec

# Assume that a _complex_mpfi_ method always defines a
# coercion (as opposed to only a conversion).
f = self._convert_method_map(S)
if f is not None:
return f
# If coercion to CC is possible and there is a _complex_mpfi_
# method, assume that it defines a coercion to CIF
if self.middle_field().has_coerce_map_from(S):
f = self._convert_method_map(S)
if f is not None:
return f

return self._coerce_map_via( (self.real_field(),), S)

Expand Down
9 changes: 9 additions & 0 deletions src/sage/rings/complex_mpfr.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def late_import():
global AlgebraicNumber_base
global AlgebraicNumber
global AlgebraicReal
global UniversalCyclotomicField
global AA, QQbar, SR
global CLF, RLF, CDF
if NumberFieldElement_quadratic is None:
Expand All @@ -96,6 +97,7 @@ def late_import():
AlgebraicNumber_base = sage.rings.qqbar.AlgebraicNumber_base
AlgebraicNumber = sage.rings.qqbar.AlgebraicNumber
AlgebraicReal = sage.rings.qqbar.AlgebraicReal
from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField
AA = sage.rings.qqbar.AA
QQbar = sage.rings.qqbar.QQbar
import sage.symbolic.ring
Expand Down Expand Up @@ -558,6 +560,13 @@ class ComplexField_class(ring.Field):
return None
if S in [AA, QQbar, CLF, RLF]:
return self._generic_coerce_map(S)
# Needed to discover the correct coerce map. Without this, the maps
# (direct or via QQbar, with slightly different behavior wrt imaginary
# parts of real elements) that get picked for conversion from UCF both
# to CC and to other types of complex fields depend in which order the
# coercions are discovered.
if isinstance(S, UniversalCyclotomicField):
return self._generic_coerce_map(S)
return self._coerce_map_via([CLF], S)

def _repr_(self):
Expand Down
6 changes: 3 additions & 3 deletions src/sage/rings/polynomial/binary_form_reduce.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,13 @@ def covariant_z0(F, z0_cov=False, prec=53, emb=None, error_limit=0.000001):
w = z
v0 = v0 - NJinv*G.subs({u: v0[0], t: v0[1]})
z = v0[1].constant_coefficient() + v0[0].constant_coefficient()*CF.gen(0)
err = z.diameter() # precision
zz = (w - z).abs() # difference in w and z
err = z.diameter() # precision
zz = (w - z).abs().lower() # difference in w and z
else:
# despite there is no break, this happens
if err > error_limit or err.is_NaN():
raise ValueError("accuracy of Newton's root not within tolerance(%s > %s), increase precision" % (err, error_limit))
if z.imag() <= z.diameter():
if z.imag().upper() <= z.diameter():
raise ArithmeticError("Newton's method converged to z not in the upper half plane")
z = z.center()

Expand Down
4 changes: 2 additions & 2 deletions src/sage/rings/polynomial/complex_roots.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ def complex_roots(p, skip_squarefree=False, retval='interval', min_prec=0):
[(-14.61803398874990?..., 1), (-12.3819660112501...? + 0.?e-27*I, 1)]
sage: sorted((v[0][0].real(),v[1][0].real()))
[-14.61803398874989?, -12.3819660112501...?]
sage: v[0][0].imag() < 1e25
sage: v[0][0].imag().upper() < 1e25
True
sage: v[1][0].imag() < 1e25
sage: v[1][0].imag().upper() < 1e25
True
sage: K.<im> = QuadraticField(-1)
Expand Down
4 changes: 2 additions & 2 deletions src/sage/rings/qqbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6869,9 +6869,9 @@ def _real_refine_interval(self, interval, prec):
newton_lower = not newton_lower

if newton_lower:
interval = interval.intersection(l - pl/slope)
interval = interval.intersection(field(l) - pl/slope)
else:
interval = interval.intersection(u - pu/slope)
interval = interval.intersection(field(u) - pu/slope)
new_diam = interval.diameter()

if new_diam == 0:
Expand Down
Loading

0 comments on commit d2cf1d0

Please sign in to comment.