sagemath / sage

Main repository of SageMath
https://www.sagemath.org
Other
1.32k stars 451 forks source link

`q.is_zero()` for some symbolic `q` produce erroneous results #38586

Open maxale opened 2 weeks ago

maxale commented 2 weeks ago

Steps To Reproduce

The code:

q = -1/8224*2^(1/4)*(2^(3/4)*(64*(-1)^(3/4) - 7) + 17*I*2^(1/4)*((-1)^(3/4) - 4) - (-1)^(3/4)*sqrt(193*sqrt(2) + 240*I) + 16*I*2^(3/4) + 60*2^(1/4))
print( QQbar(q).minpoly() )
print( imag(QQbar(q)).is_zero() )
print( imag(q).is_zero() )

prints:

x - 1/514 True False

Expected Behavior

Conversion to QQbar shows that the value of q is simply 1/514 and thus its imaginary part equals 0.

Actual Behavior

The call imag(q).is_zero() returns False, while the correct result should be True. Hence, either imag() or is_zero() produces a wrong result here.

Additional Information

No response

Environment

- **OS**: Ubuntu 22.04.4 LTS
- **Sage Version**: 10.5.beta2

Checklist

maxale commented 2 weeks ago

It is worth to note that full_simplify() does simplify q into 1/514:

sage: q.full_simplify()
1/514

while .is_real() also produces a wrong result:

sage: q.is_real()
False
user202729 commented 2 weeks ago

It's is_zero() returning false here but…

    def __bool__(self):
        """
        Return ``True`` unless this symbolic expression can be shown by Sage
        to be zero.  Note that deciding if an expression is zero is
        undecidable in general.

        EXAMPLES::

            sage: x = var('x')
            sage: forget()
            sage: bool(SR(0))
            False
            sage: bool(SR(1))
            True
            sage: assert(abs(x))
            sage: assert(not x/x - 1)

        This is called by :meth:`is_zero`::

            sage: k = var('k')
            sage: pol = 1/(k-1) - 1/k - 1/k/(k-1)
            sage: pol.is_zero()
            True

            sage: f = sin(x)^2 + cos(x)^2 - 1
            sage: f.is_zero()
            True

Not sure if anything Sage can do here about it.

Reading: https://math.stackexchange.com/questions/617529/richardsons-theorem-for-constants/1379799#1379799

maxale commented 2 weeks ago

Not sure if anything Sage can do here about it.

Well, if is_zero is unable to determine the answer for a given expression it should raise an exception to make the caller aware about the issue, but it should not return a wrong/uncertain answer.

user202729 commented 2 weeks ago

I imagine that would be quite hard to implement while making Sage still reasonably useful in practice.

  1. If the expression is a constant, it should not be too difficult to try to numerically evaluate it to a certain precision to prove it's nonzero.
  2. However, if the expression depends on some variables, to prove it's nonzero we need to evaluate it at random arguments.
  3. But evaluating at random argument might as well throw error e.g.
sage: y=(x+abs(x)+I)^2
sage: ((y^2-1)/(y+1)-y+1).is_zero()
True
sage: ((y^2-1)/(y+1)-y+1)(x=-3)
ValueError: power::eval(): division by zero
  1. Something that throws error while evaluating at random argument might actually be zero (in the current implementation).

That said, the proposal that is_zero() tries a bit harder for constant might be reasonable.

sage: q = -1/8224*2^(1/4)*(2^(3/4)*(64*(-1)^(3/4) - 7) + 17*I*2^(1/4)*((-1)^(3/4) - 4) - (-1)^(3/4)*
....: sqrt(193*sqrt(2) + 240*I) + 16*I*2^(3/4) + 60*2^(1/4))
sage: a = imag(q)
sage: a.variables()
()

The question is how much harder, however. N is probably reasonably fast, but simplify() can be arbitrarily slow.

maxale commented 2 weeks ago

The original example shows that q.imag().is_zero() and q.is_real() both give incorrect result. This may make an impression that the two are being somewhat equivalent, but it turns out that each of them may produce an incorrect result independently of the other.

In the following example r.imag().is_zero() gives correct True while r.is_real() gives incorrect False:

r = -1/2056*sqrt(2)*(-231043*sqrt(2) + 1682065/4)^(1/4)*cos(1/2*arctan(1/2*sqrt(-67260300684*sqrt(2) + 97845815881)/(38051*sqrt(2) + 20432)))
print( r.n() )
print( r.imag().is_zero() )
print( r.is_real() )

Output:

-0.0118652163874385 True False

maxale commented 2 weeks ago

I've separated the issue with .is_real() to #38600. Let's devote this one to .is_zero().