# Use method "ExactAlgebraics" of PossibleZeroQ for Series zero test

I am trying to compute a series expansion around infinity with very large numerical, but entirely algebraic coefficients, and I keep running into zero test errors, which look exactly like the ones from PossibleZeroQ.

Using the example from the PossibleZeroQ documentation,

a = Sqrt[2] + Sqrt[3] - RootReduce[Sqrt[2] + Sqrt[3]] + 10^-10000;

PossibleZeroQ[a]
PossibleZeroQ::ztest1: Unable to decide whether numeric quantity
1/(10000<<9658>>000000)+Sqrt[2]+Sqrt[3]-Root[1-10 Slot[<<1>>]^2+#1^4&,4,0] is equal to zero.
Assuming it is.

True (* Wrong *)

an example series would be

Series[a+1/x,{x,Infinity,1}]
Series::ztest1: Unable to decide whether numeric quantity
1/(10000<<9658>>000000)+Sqrt[2]+Sqrt[3]-Root[1-10 Slot[<<1>>]^2+#1^4&,4,0] is equal to zero.
Assuming it is.

1/x + O[1/x^2] (* Also wrong *)

PossibleZeroQ has the option Method->"ExactAlgebraics", which gives a provably correct answer for algebraic expressions, and it tends to be very efficient for even large expressions.

PossibleZeroQ[a,Method->"ExactAlgebraics"]
False

Is there any way to set up Mathematica, such that the same method is also used for zero tests in Series?

Edit

I tried the method suggested by Michael, and it does work for the example I used above. However, this does not seem to work reliably. Using an example from my code,

longExpr = CloudGet[CloudObject["https://www.wolframcloud.com/obj/78fc84e0-083f-424f-9707-86ba0cd83ce3"]];

ClearSystemCache[];

With[{opts = Options@PossibleZeroQ},
InternalWithLocalSettings[
SetOptions[PossibleZeroQ, Method -> "ExactAlgebraics"],
Series[longExpr + 1/x, {x, Infinity, 1}],
SetOptions[PossibleZeroQ, opts]]
]
Series::ztest1: Unable to decide whether numeric quantity **longExpr** is equal to zero. Assuming it is.

1/x + O[1/x^2]

which happens to be correct in this case. Calling PossibleZeroQ directly however does not throw the error

ClearSystemCache[];

PossibleZeroQ[longExpr,Method->"ExactAlgebraics"]
True

How could this be explained?

• Would this approach be helpful?: a0 = Limit[longExpr + 1/x, x -> Infinity]; FullSimplify[a0] + Series[longExpr + 1/x - a0, {x, Infinity, 1}] Jul 17, 2020 at 16:20
• I only used the function longExpr + 1/x as an example, unfortunately I have to deal with much more complicated functions in my code. Also FullSimplify could probably take a very long time if the expression is not actually zero (I am actually quite surprised how fast it is for longExpr) Jul 19, 2020 at 11:45
• If they're algebraic numbers, you might try RootReduce. For instance, longExpr + 1/x // RootReduce is much faster than FullSimplify. Perhaps reducing the numbers are they are being built up might be possible. Jul 19, 2020 at 12:33

SetOptions is a way:

ClearSystemCache[]; (* needed if you already did Series[a +...] *)
With[{opts = Options@PossibleZeroQ},
InternalWithLocalSettings[
SetOptions[PossibleZeroQ, Method -> "ExactAlgebraics"],
Series[a + 1/x, {x, Infinity, 1}],
SetOptions[PossibleZeroQ, opts]
]
]
(*
1/(1000 <<9993>> 0000) + Sqrt[2] + Sqrt[3] - Root[1 - 10 #1^2 + #1^4 &, 4]) +
1/x +O[1/x]^2
*)
• Perfect, that's exactly what I was looking for. This method to set options locally is definitely something that will come in handy in the future. Thank you! Jul 16, 2020 at 9:36
• Unfortunately this method does not always seem to work. I posted an example as an edit in my question. Jul 16, 2020 at 14:26
• @Hausdorff I get an error retrieving longExpr: CloudGet::notperm: Unable to perform the requested operation. Permission denied. Jul 16, 2020 at 14:36
• Sorry, I have never used Wolfram Cloud objects before. I think I now set it to be public. Jul 16, 2020 at 14:42
• @Hausdorff I can't figure it out. Series does not appear in a Trace to call PossibleZeroQ directly, although it evaluates its Options. Possibly the call is hidden from Trace, but PossibleZeroQ works on longExpr; so I think there is an internal routine that might have limitations built in, such as on the size of the expression or the time to try to simplify, but I have not discovered such a routine. Jul 16, 2020 at 18:10

I have been trying to understand better how Mathematica evaluates Series, and here I just collect some notes about what I found. I suppose this is mostly for my own reference, but perhaps somebody else may find this useful, should they stumble over a similar problem.

It is all a little speculative, as the Series function is mostly opaque. If anybody has any more insights, I would be really grateful. Also please let me know if any of the things I wrote below is wrong.

First off, to address the issues at hand regarding the use of the option "ExactAlgebraics" for zero tests in Series:

## TL;DR

I don't think it is currently possible to make Mathematica use the "ExactAlgebraics" consistently for all zero tests of series coefficients, as PossibleZeroQ is never actually used. However it seems there are special cases, such as coefficients containing Root objects, in which Series adheres to the Method option of PossibleZeroQ.

## How does Mathematica compute series expansions?

To look at the inner workings of Series, I found the tools traceView and Spelunk to be really helpful. The PrintDefinitions function of the built-in package GeneralUtilities is also very nice. In traceView I used the option TraceInternal->True to see as much of the internal evaluations as possible.

When calling Series, the function and arguments are quickly passed on to the function SystemPrivateInternalSeries, which then does the heavy lifting. This function has some explicit definitions for special functions, which can see be seen with PrintDefinition after evaluating Series once and calling ClearAttributes[SystemPrivateInternalSeries,ReadProtected]. However, for the generic case it is a kernel function, making it mostly opaque. TraceInternal->True still reveals some hints about its behavior.

## When does Mathematica check for coefficients to be zero?

Here we only need to look at the handling of sums during the series expansion.

When InternalSeries is called on a sum, Mathematica maps InternalSeries over the separate summands. If the expansion is around infinity, it then picks out the highest power in the expansion variable of all the summand expansions, collects every coefficient of that order, and then checks whether this coefficient is zero. If it is not zero, Series does not perform any further checks, and returns the series. Should the coefficient of the highest power be zero, it moves on to the next highest, repeating the process until it finds the highest power with non-zero coefficient. Notably, it does not check, whether any of the lower powers are zero.

For completeness, let's look at an example. Consider the expansion of a + b + c/x around $$x=\infty$$ up to order $$\mathcal{O}\left(\tfrac{1}{x^2}\right)$$. Mathematica first computes the expansion of every summand, i.e. a + O[1/x^2], b + O[1/x^2] and c/x O[1/x^2]. It determines the highest power, here $$\tfrac{1}{x^0}$$, and performs its zero test on the coefficient, which in this case is a + b. If a + b is found to be non-zero, Series returns a + b + c/x + O[1/x^2], even if c is actually zero.

## How does Mathematica check, whether a coefficient is zero?

Series uses a multistep process for the zero test. Looking at the traces the behavior is very closely related to that of PossibleZeroQ. One can also see in the trace of Series that in fact the default assumptions of PossibleZeroQ are being used, as there is a call

Assumptions /. Options[PossibleZeroQ]

so there definitely appears to be a connection. However PossibleZeroQ itself never seems to be called explicitly.

To see whether a coefficient is zero, it is first evaluated numerically at $MachinePrecision. Should the result be inconclusive, the coefficient is reevaluated using the precision of$MaxExtraPrecision. If the result is still inconclusive, InternalSeries generically stops here and assumes the coefficient to be zero, returning the warning Series::ztest1.

However, InternalSeries does appear to scan the expression of the coefficient for certain objects, and can enter a different branch for the zero test. One example of an object that is treated specially is Root. In this case one can see that Series does not only read in the assumptions of PossibleZeroQ, but also the specified method, since the trace then also shows

Method /. Option[PossibleZeroQ]

Now, after the numeric tests fail, and if OptionValue[PossibleZeroQ,Method] has been set to "ExactAlgebraics", InternalSeries performs additional steps, such as using Factor, in line with the behavior of PossibleZeroQ[#,Method->"ExactAlgebraics"].

This is the reason why the solution of Michael to set the default option Method of PossibleZeroQ worked for the example from the PossibleZeroQ documentation. However, in the case of my expression longExpr, which contains only square roots and rationals, this branch of InternalSeries is not used. Setting the Method option does nothing in this case, as it is never even checked.

I have not investigated further, which objects other than Root receive this special treatment. Anything that is guaranteed to return an algebraic number would be an obvious initial guess.

## Work-arounds

As far as I can see there are two options to get around this problem:

If in an expansion around infinity the highest order is known, one could add a non-trivial zero involving Root at that order. My example involving longExpr could be modified by adding the zero Sqrt[2] + Sqrt[3] - RootReduce[Sqrt[2] + Sqrt[3]], so that

ClearSystemCache[];
With[{opts=Options@PossibleZeroQ},
Internal`WithLocalSettings[
SetOptions[PossibleZeroQ,Method->"ExactAlgebraics"],
Timing@Series[Sqrt[2] + Sqrt[3] - RootReduce[Sqrt[2] + Sqrt[3]] + longExpr+1/x,{x,Infinity,1}],
SetOptions[PossibleZeroQ,opts]]
]
{69.5388, 1/x + O[1/x^2]} (* no Series::ztest1 message *)

The runtime is comparable to that of PossibleZeroQ,

ClearSystemCache[];
Timing@PossibleZeroQ[Sqrt[2] + Sqrt[3] - RootReduce[Sqrt[2] + Sqrt[3]] + longExpr,Method->"ExactAlgebraics"]
{66.1458, True}

but it is significantly slower than it would be without Root,

ClearSystemCache[];
Timing@PossibleZeroQ[longExpr,Method->"ExactAlgebraics"]
{2.40949, True}

The other work-around would be to imitate InternalSeries, by mapping Series over the sum, extracting the highest coefficient, then explicitly using PossibleZeroQ to check for zeroes, and rebuilding the final SeriesData object from those of the summands. This option is also not ideal, as it essentially only works if the original expression is already in the form of a Taylor/Laurent series. However, this method could be used, when defining a custom series expansion which is based on Mathematica's Series and SeriesData.

## Conclusion

Since PossibleZeroQ does not seem to be explicitly called in Series, I would guess InternalSeries never uses the function PossibleZeroQ, and it rather calls the associated internal kernel functions directly. It does seem a bit weird that Series respects the options set for PossibleZeroQ, but only when it decides that this might be useful. This could just be an oversight in the design of Series, as I am considering a bit of an edge case here, or there may be examples where using "ExactAlgebraics" generally might be a bad idea (though I would not know what they are).

In any case, I think letting the user decide via a ZeroTest option would be a really nice functionality.