General concepts =============================================================================== Importing ----------------- The ``flint`` module exposes a set of distinctly-named types together with a small number of top-level functions and objects. Most functionality is provided as methods on the types. This means that there should be no namespace conflicts with most user code, with Python's builtin ``math`` and ``cmath`` modules, or with packages such as ``gmpy``, ``numpy``, ``sympy`` and ``mpmath``. For typical interactive use, it should therefore generally be safe to ``import *``: >>> from flint import * >>> fmpq(3) / 2 3/2 For non-interactive use, it is still good manners to use explicit imports or preserve the ``flint`` namespace prefix:: >>> import flint >>> flint.fmpq(3) / 2 3/2 Global context ----------------- Various settings are controlled by a global context object, ``flint.ctx``. Printing this object in the REPL shows the current settings, with a brief explanation of each parameter:: >>> from flint import ctx >>> ctx 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 user can mutate the properties directly, for example:: >>> ctx.pretty = False >>> fmpq(3,2) fmpq(3,2) >>> ctx.pretty = True 3/2 Calling ``ctx.default()`` restores the default settings. The special method ``ctx.cleanup()`` frees up internal caches used by MPFR, FLINT and Arb. The user does normally not have to worry about this. Types and methods ----------------- As a general rule, C functions associated with a type in FLINT or Arb are exposed as methods of the corresponding Python type. For example, there is both an :meth:`.fmpq.bernoulli` (which computes a Bernoulli number as an exact fraction) and :meth:`.arb.bernoulli` (which computes a Bernoulli number as an approximate real number). A function that transforms a single value to the same type is usually an ordinary method of that type, for instance :meth:`.arb.exp`. A function with a different signature can either provided as a static method that takes all inputs as function arguments, or as a method of the "primary" input, taking the other inputs as arguments to the method (for example :meth:`.arb.bessel_j`). When a method involves different types for inputs and outputs (or just among the inputs), it will typically be a method of the more "complex" type. For example, a matrix type is more "complex" than the underlying scalar type, so :meth:`.fmpz_mat.det` is a method of the matrix type, returning a scalar, and not vice versa. The method-based interface is intended to keep the code simple, not to be aesthetically pleasing to mathematicians. A functional top-level interface might be added in the future, allowing more idiomatic mathematical notation (for example, :func:`exp` and :func:`det` as regular functions). Mutability ---------- Objects have immutable semantics. For example, the second line in:: b = a a += c leaves *b* unchanged. However, mutation via direct element access is supported for matrices and polynomials. Some methods also allow explicitly performing the operation in-place. Civilized users will restrict their use of such methods to the point in the code where the object is first constructed:: def create_thing(): # ok a = thing() a.mutate() return a Crashing and burning --------------------------------------- Very little overflow checking is done ahead-of-time. Trying to compute an object far too large to hold in memory (for example, the exact factorial of `2^{64}-1`) will likely abort the process, instead of raising an :exc:`OverflowError` or :exc:`MemoryError` that can be caught at the Python level. Input that is obviously *invalid* (for example a negative number passed as a length) can also cause crashes or worse things to happen. Ideally, bad input should be caught at the Python level and result in appropriate exceptions being raised, but this is not yet done systematically. At this time, users should assume that invalid input leads to undefined behavior! Inexact numbers and numerical evaluation ----------------------------------------------------------------------- Real and complex numbers are represented by midpoint-radius intervals (balls). All operations on real and complex numbers output intervals representing rigorous error bounds. This also extends to polynomials and matrices of real and complex numbers. The working precision for real and complex arithmetic is controlled by the global context object attributes :func:`ctx.prec` (in bits) :func:`ctx.dps` (in decimal digits). Changing either attribute changes the other to match. Be careful about using Python float and complex literals as input. Doing ``arb(0.1)`` actually gives an interval containing the rational number .. math :: 3602879701896397 \times 2^{-55} = 0.1000000000000000055511151231257827021181583404541015625 which might not be what you want. Do ``arb("0.1")``, ``arb("1/10")`` or ``arb(fmpq(1,10))`` if you want the correct decimal fraction. Small integers and power-of-two denominators are still safe, for example ``arb(100.25)``. Pointwise boolean predicates (such as the usual comparison operators) involving inexact numbers return *True* only if the predicate certainly is true (i.e. it holds for all combinations of points that can be chosen from the set-valued inputs), and return *False* if the predicate either definitely is false or the truth cannot be determined. To determine that a predicate is definitely false, test both the predicate and the inverse predicate, e.g. if either ``x < y`` or ``y <= x`` returns *True*, then the other is definitely false; if both return *False*, then neither can be determined from the available data. The following convenience functions are provided for numerical evaluation with adaptive working precision. .. autofunction :: flint.good .. autofunction :: flint.showgood Power series ----------------------------------------------------------------------- Power series objects track the precision (the number of known terms) automatically. The upper precision for power series is controlled by ``flint.ctx.cap``, with the default value 10. >>> fmpq_series([0,1]).exp() 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + 1/5040*x^7 + 1/40320*x^8 + 1/362880*x^9 + O(x^10) >>> ctx.cap = 4 >>> fmpq_series([0,1]).exp() 1 + x + 1/2*x^2 + 1/6*x^3 + O(x^4) >>> ctx.cap = 10 >>> fmpq_series([0,1], prec=5).exp() 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + O(x^5) >>> ctx.cap = 3 >>> ctx.dps = 10 >>> arb_series([1,3,4]).exp() ([2.718281828 +/- 4.79e-10]) + ([8.154845485 +/- 4.36e-10])*x + ([23.10539554 +/- 2.25e-9])*x^2 + O(x^3) >>> ctx.default()