flintlib/arb

arb_pow could do much better for <a +- a> ^ <b +- c> with b >= c > 0 and a > 0

postmath opened this issue · 5 comments

Consider arb_pow(z, x, y, prec) with x = <a +- a> and y nonnegative. This works as I would expect for x=0, and also for exact half integers y, including y = 0. But if a > 0 and y is not an exact half integer, then arb_pow returns a non-finite answer (because it takes the logarithm of x, then multiplies by y, then takes the exponential; but we can't represent the half-infinite interval that the logarithm requires), whereas there is a finite answer. I propose to do roughly this, after all existing special cases have been done:

    if(arf_cmp_si(arb_midref(x), 0) > 0
        && arf_cmpabs_mag(arb_midref(x), arb_radref(x)) == 0
        && arb_is_nonnegative(y)) {
        /* x is an interval of the form <a +- a>, y is an interval of the form <b +- c> with b >=
          c. 

          (x, y) -> x^y is nondecreasing in x for y >= 0, and it is 0 for x=0 and y>0.  The lower
          bound of the interval for x is 0, and we have points with y>0 (because the case
          arb_is_zero(y) is excluded above). So the lowerbound of the result is 0, and the
          upperbound can be found somewhere at (2*a)^y.

          We compute this upperbound by computing z^y with z = < 3*a/2 +- a/2 >, which has the same
          upperbound (2*a) as x, then keeping the upper bound of this result and setting the lower
          bound to 0. This necessarily drops the precision to MAG_BITS, so we might as well do that
          from the start. */
        prec = (prec > MAG_BITS) ? MAG_BITS : prec;
        arf_mul_ui(arb_midref(z), arb_midref(x), 3, prec, ARF_RND_UP);
        arf_mul_2exp_si(arb_midref(z), arb_midref(z), -1);
        mag_mul_2exp_si(arb_radref(z), arb_radref(x), -1);

        arb_pow(z, z, y);

        /* Now we keep the upper bound of z (we may need to round up) and set the lower bound to
           zero. That is, if currently, z = < zc +/- zr >, we set it to < (zc + zr)/2 +/- (zc +
           zr)/2 >. */
        arb_get_ubound_arf(arb_midref(z), z, prec);
        arf_mul_2exp_si(arb_midref(z), arb_midref(z), -1);
        arf_get_mag(arb_radref(z), arb_midref(z));
        /* Note the above is inexact (rounding up), so need to update arb_midref(z) to match again */
        arf_set_mag(arb_midref(z), arb_radref(z));
    }

Agreed, but arb_pow ought to have such a special case for any x containing 0, not necessarily of the form [a +- a].

Hmm, but if there are negative numbers in x, and y is not an exact integer, the result isn't real, is it? So in that case I think the current answer of nan is correct.

Ah yes, you're right of course. So this really is a special special case.

The more general case would be relevant in acb_pow and acb_pow_arb.

Agreed, acb_pow and acb_pow_arb are a bit more complicated. I guess for acb_pow_arb you could reason that, with 0 \in x and y real:

x^y = exp(y * ln(x))
    = exp(y * ([-infinity, max(abs(x))] + i * acb_arg(x)))
    = [0, exp(max(y) * max(abs(x)))] * exp(i * y * acb_arg(x))

I suppose we could at least do a case distinction there between x being contained in any quadrants, vs containing 0 in its interior, to compute that second factor.

The acb_pow case would be another step messier. Would you be opposed to merging just this change to arb_pow for now? If not I can prepare a pull request next week.

Patching arb_pow is fine.