17

I looked around for some documentation of how numpy/scipy functions behave in terms of numerical stability, e.g. are any means taken to improve numerical stability or are there alternative stable implementations.

I am specifically interested in addition (+ operator) of floating point arrays, numpy.sum(), numpy.cumsum() and numpy.dot(). In all cases I am essentially summing a very large quantity of floating points numbers and I am concerned about the accuracy of such calculations.

Does anyone know of any reference to such issues in the numpy/scipy documentation or some other source?

Bitwise
  • 7,577
  • 6
  • 33
  • 50
  • 9
    There simply are no stable summation algorithms in numpy at this time... (dot is normally handled by lapack anyway) in other words, the stability of usual addition applies (possibly with extended precision registers, but that depends on the hardware and operation) – seberg May 31 '13 at 19:25
  • @seberg thanks, do you know if lapack does anything special to ensure dot product stability? – Bitwise May 31 '13 at 20:12
  • 1
    If you look at, for instance, some of the ATLAS code for doing a dot product, basically the only tricky that they do for accuracy, when doing a sum of single precision numbers, is to actually accumulate the sum in double. e.g. https://github.com/vtjnash/atlas-3.10.0/blob/master/src/blas/level1/ATL_sdsdot.c. – Robert T. McGibbon Jun 05 '13 at 06:16
  • @RobertMcGibbon Thanks, all my variables are 64 bit anyway - exactly for this reason. – Bitwise Jun 06 '13 at 16:07
  • 2
    `sum` and `dot` are BLASs not from the LAPACK. – dastrobu Aug 22 '13 at 22:12
  • 1
    related: [Long (>20million element) array summation in python numpy](http://stackoverflow.com/q/8599333/4279) – jfs Sep 10 '13 at 05:41

1 Answers1

2

The phrase "stability" refers to an algorithm. If your algorithm is unstable to start with then increasing precision or reducing rounding error of the component steps is not going to gain much.

The more complex numpy routines like "solve" are wrappers for the ATLAS/BLAS/LAPACK routines. You can refer to documentation there, for example "dgesv" solves a system of real valued linear equations using an LU decomposition with partial pivoting and row interchanges : underlying Fortran code docs for LAPACK can be seen here http://www.netlib.org/lapack/explore-html/ but http://docs.scipy.org/doc/numpy/user/install.html points out that many different versions of the standard routine implementations are available - speed optimisation and precision will vary between them.

Your examples don't introduce much rounding, "+" has no unnecessary rounding, the precision depends purely on rounding implicit in the floating point datatype when the smaller number has low-order bits that cannot be represented in an answer. Sum and dot depend only on the order of evaluation. Cumsum cannot be easily re-ordered as it outputs an array.

For the cumulative rounding during a "cumsum" or "dot" function you do have choices:

On Linux 64bit numpy provides access to a high precision "long double" type float128 which you could use to reduce loss of precision in intermediate calculations at the cost of performance and memory.

However on my Win64 install "numpy.longdouble" maps to "numpy.float64" a normal C double type so your code is not cross-platform, check "finfo". (Neither float96 or float128 with genuinely higher precision exist on Canopy Express Win64)

log2(finfo(float64).resolution)
> -49.828921423310433
actually 53-bits of mantissa internally ~ 16 significant decimal figures

log2(finfo(float32).resolution)
> -19.931568                          # ~ only 7 meaningful digits

Since sum() and dot() reduce the array to a single value, maximising precision is easy with built-ins:

x = arange(1, 1000000, dtype = float32)
y = map(lambda z : float32(1.0/z), arange(1, 1000000))
sum(x)                     #  4.9994036e+11
sum(x, dtype = float64)    #  499999500000.0
sum(y)                     #  14.357357
sum(y, dtype = float64)    #  14.392725788474309
dot(x,y)                             # 999999.0
einsum('i,i', x, y)                  # * dot product = 999999.0
einsum('i,i', x, y, dtype = float64) # 999999.00003965141
  • note the single precision roundings within "dot" cancel in this case as each almost-integer is rounded to an exact integer

Optimising rounding depends on the kind of thing you are adding up - adding many small numbers first can help delay rounding but would not avoid problems where big numbers exist but cancel each other out as intermediate calculations still cause a loss of precision

example showing evaluation order dependence ...

x = array([ 1., 2e-15, 8e-15 , -0.7, -0.3], dtype=float32)
# evaluates to
# array([  1.00000000e+00,   2.00000001e-15,   8.00000003e-15,
#   -6.99999988e-01,  -3.00000012e-01], dtype=float32)
sum(x) # 0
sum(x,dtype=float64) # 9.9920072216264089e-15
sum(random.permutation(x)) # gives 9.9999998e-15 / 2e-15 / 0.0
jayprich
  • 228
  • 2
  • 6
  • 9
    I disagree with your claim that a naive cumsum implementation will give you the highest precision result that you can hope for. Computing a summation of a large number of floating point values is an *excellent* example of a situation where it is important to use an algorithm that has been designed to minimise precision loss. See, for example, the [Kahan summation algorithm](http://en.wikipedia.org/wiki/Kahan_summation_algorithm): note that it scans linearly over the data (ie, it does not rely on reordering), and so it should be relatively easy to adjust it for use in a cumsum implementation. – John Bartholomew Jul 31 '14 at 18:15
  • Could have been nice to also give a definition for stability while describing what it is not – matanster Jul 19 '23 at 15:15
  • @matanster - there are many definitions for different contexts ( computations , linear algebra , dynamical systems , differential equations ) but I think the documentation of LAPACK is a good resource. My working definition would be that some problems are fundamentally hard to solve numerically - example : https://en.m.wikipedia.org/wiki/Stiff_equation – jayprich Jul 21 '23 at 06:56