I thought I would just quickly list some features of Arb that are available via python-flint (pip install --pre python-flint
). All of these things come with the same correctness bounds that I mentioned above.
Firstly some background:
- Arb was created by Fredrik Johansson as a library that depended on FLINT where FLINT is about exact things like polynomials with integer coefficients and so on but Arb is about robust approximate numerics.
- Fredrik also created python-flint which wrapped FLINT and Arb for use in Python but then python-flint sort of fell by the wayside. I took over as maintainer of python-flint and fixed it up so that it has wheels, works on Windows etc.
- Fredrik then took over as maintainer of FLINT and merged Arb and some other things into FLINT.
So the situation now is that Arb does not exist as an independent library but is part of FLINT and python-flint is a wrapper around FLINT that exposes various things including Arb. Fredrik is maintainer of FLINT and I am maintainer of python-flint.
In general you can configure python-flint by assigning to the global (not thread-safe) context attributes:
In [25]: import flint
In [26]: flint.ctx
Out[26]:
pretty = True # pretty-print repr() output
unicode = False # use unicode characters in output
prec = 53 # real/complex precision (in bits)
dps = 15 # real/complex precision (in digits)
cap = 10 # power series precision
threads = 1 # max number of threads used internally
The main things are prec and dps which set the precision for Arb:
In [19]: flint.ctx.prec = 53
In [20]: flint.arb(2).sqrt()
Out[20]: [1.41421356237309 +/- 5.15e-15]
In [21]: flint.ctx.prec = 200
In [22]: flint.arb(2).sqrt()
Out[22]: [1.4142135623730950488016887242096980785696718753769480731767 +/- 2.17e-59]
You can use Arb to do real or complex calculations involving a large array of functions:
In [50]: a = flint.arb(1)
In [51]: a
Out[51]: 1.00000000000000
In [52]: a.exp()
Out[52]: [2.71828182845905 +/- 5.36e-15]
In [54]: ' '.join(n for n in dir(a) if not n.startswith('_'))
Out[54]: 'abs_lower abs_upper acos acosh agm airy airy_ai airy_ai_zero airy_bi airy_bi_zero asin asinh atan atan2 atanh backlund_s bell_number bernoulli bernoulli_poly bessel_i bessel_j bessel_k bessel_y beta_lower bin bin_uiui bits ceil chebyshev_t chebyshev_u chi ci const_catalan const_e const_euler const_glaisher const_khinchin const_log10 const_log2 const_sqrt_pi contains contains_integer contains_interior cos cos_pi cos_pi_fmpq cosh cot cot_pi coth coulomb coulomb_f coulomb_g csc csch digamma ei erf erfc erfcinv erfi erfinv exp expint expm1 fac fac_ui fib floor fmpq fmpz fresnel_c fresnel_s gamma gamma_fmpq gamma_lower gamma_upper gegenbauer_c gram_point hermite_h hypgeom hypgeom_0f1 hypgeom_1f1 hypgeom_2f1 hypgeom_u imag intersection is_exact is_finite is_integer is_nan is_zero jacobi_p laguerre_l lambertw legendre_p legendre_p_root legendre_q lgamma li log log1p log_base lower man_exp max mid mid_rad_10exp min nan neg neg_inf nonnegative_part overlaps partitions_p pi polylog pos_inf rad real rel_accuracy_bits rel_one_accuracy_bits repr rgamma rising rising2 rising_fmpq_ui root rsqrt sec sech sgn shi si sin sin_cos sin_cos_pi sin_cos_pi_fmpq sin_pi sin_pi_fmpq sinc sinc_pi sinh sinh_cosh sqrt str tan tan_pi tanh union unique_fmpz upper zeta zeta_nzeros'
I think there are more Arb functions in FLINT that are not yet exposed in python-flint. These functions are all available as methods on the arb which is an odd interface. It would be better to have contexts with methods like ctx.sin
and functions like sin
that use a global context. This needs a bit of careful design though because there are many more things in FLINT than just Arb and we probably want a general approach to contexts/domains rather than just something that is specific to Arb.
For complex calculations use acb
rather than arb
:
In [55]: flint.acb(-1).sqrt()
Out[55]: 1.00000000000000j
There are real and complex Arb matrices:
In [30]: M = flint.arb_mat([[1, 2], [3, 4]])
In [31]: M
Out[31]:
[1.00000000000000, 2.00000000000000]
[3.00000000000000, 4.00000000000000]
In [32]: M.eig()
Out[32]:
[[-0.37228132326901 +/- 7.54e-15] + [+/- 3.02e-15]j,
[5.3722813232690 +/- 2.07e-14] + [+/- 3.02e-15]j]
In [33]: M.det()
Out[33]: -2.00000000000000
In [34]: M.inv()
Out[34]:
[[-2.00000000000000 +/- 1.63e-15], [1.00000000000000 +/- 7.41e-16]]
[ [1.50000000000000 +/- 9.44e-16], [-0.500000000000000 +/- 4.17e-16]]
You can use Arb to compute series involving many functions:
In [35]: x = flint.arb_series([0, 1])
In [36]: x
Out[36]: 1.00000000000000*x + O(x^10)
In [37]: x.exp()
Out[37]: 1.00000000000000 + 1.00000000000000*x + 0.500000000000000*x^2 + ([0.166666666666667 +/- 3.71e-16])*x^3 + ([0.0416666666666667 +/- 4.26e-17])*x^4 + ([0.00833333333333333 +/- 4.61e-18])*x^5 + ([0.00138888888888889 +/- 2.23e-18])*x^6 + ([0.000198412698412698 +/- 4.64e-19])*x^7 + ([2.48015873015873e-5 +/- 1.84e-20])*x^8 + ([2.75573192239859e-6 +/- 3.91e-21])*x^9 + O(x^10)
You can compute the roots of any polynomial:
In [45]: x = flint.arb_poly([0, 1])
In [46]: p = x**3 + 2*x**2 + 1
In [47]: p
Out[47]: 1.00000000000000*x^3 + 2.00000000000000*x^2 + 1.00000000000000
In [48]: p.complex_roots()
Out[48]:
[[-2.20556943 +/- 1.12e-9] + [+/- 8.60e-10]j,
[0.10278472 +/- 6.14e-9] + [0.66545695 +/- 2.51e-9]j,
[0.10278472 +/- 6.14e-9] + [-0.66545695 +/- 2.51e-9]j]
It is usually better when computing roots to have an exact representation of the coefficients but compute approximate representations of the roots:
In [65]: x = flint.fmpz_poly([0, 1])
In [66]: p = (x**2 + 2)**2
In [67]: p
Out[67]: x^4 + 4*x^2 + 4
In [68]: p.complex_roots()
Out[68]: [([1.41421356237310 +/- 4.96e-15]j, 2), ([-1.41421356237310 +/- 4.96e-15]j, 2)]
The multiplicities of the roots are computed exactly but the values of the roots are computed approximately with hard error bounds. There is also much functionality available in python-flint for doing exact things with fmpz_poly etc.
You can compute integrals numerically with hard error bounds:
In [5]: flint.acb.integral(lambda e, _: e.sin(), 0, 1)
Out[5]: [0.459697694131860 +/- 6.21e-16]
The general property that Arb provides is that the true values are always bounded and that this property is preserved under composition of any Arb operations. You could e.g. compute numeric integrals to get the elements of a matrix and then compute the determinant of that matrix and so on and then at the end you still have total bounds on the correct result of the whole operation. You also have a guarantee that if you don’t mind burning more CPU time you can just crank up prec
and rerun everything to make the outputs as precise as you want.
The current python-flint interfaces can certainly be improved but a lot of the useful functionality of Arb is available and I think that using these things is really quite game changing in many applications. Even if you don’t generally want to use Arb in your library etc having the ability to use it sometimes to verify other calculations is valuable. Ideally it should be easy to switch between doing a calculation with e.g. numpy or doing it with Arb and then you would have an easy way of checking accuracy. In many applications it might be too slow for normal use but when you find yourself debugging accuracy problems being able to just flip a switch and have fully rigorous numerics is very useful.