Tutorials

Work with floating-point and ball coefficients

Use the same ascending coefficients and sparse power tuples for numerical polynomials. Supply a precision context when you construct them, then pass the context to each arithmetic operation. Choose floating-point coefficients for rounded calculations or balls when you need an enclosure of every possible result.

Choose the arithmetic

array_numeric.mojo Download
"""Array numeric."""
from apn_mojo import ArithmeticContext, FloatFormat, BallContext, Rational
from poly import dense, sparse


def main() raises:
    var context = ArithmeticContext(format=FloatFormat(precision=80))
    var p = dense.asarray([1, 2, 3], context=context)
    print("Value:", dense.evaluate(p, 2, context=context).to_rational_exact())
    var balls = dense.asarray([Rational(1, 3), Rational(2)], context=BallContext(precision=80))
    var enclosure = dense.evaluate(balls, 1, context=BallContext(precision=80))
    print("Finite enclosure:", enclosure.is_finite())
    var q = sparse.asarray([((1, 0), 2), ((0, 1), 3)], context=context)
    print("Sparse value:", sparse.evaluate(q, (2, 1), context=context).to_rational_exact())

Run from the repository root pixi run mojo run -I build docs/examples/array_numeric.mojo

Output

Value: 17
Finite enclosure: True
Sparse value: 7

The first calculation evaluates \(1+2x+3x^2\) at \(x=2\), giving 17. The second starts from the exact rational \(1/3\) and encloses its conversion and evaluation in a ball. A ball's width includes rounding error and any uncertainty you put in the coefficients.

Context Coefficient values
ArithmeticContext(format=FloatFormat(precision=80)) APN Float
ComplexContext(real_context) APN Complex
BallContext(precision=80) APN Ball, or ComplexBall for complex input

Construct a context explicitly; a BallContext without a precision raises DOMAIN. Without context=, dense.asarray and sparse.asarray retain their exact integer, rational, Gaussian rational, algebraic, and prime-field behavior.

Numerical constructors accept exact integers, rationals, real and complex algebraic values, native floating-point values, and APN numerical scalars. A native float contributes its stored binary value: converting Float64(0.1) does not recover the decimal rational \(1/10\). Use Rational(1, 10) when that fraction is your intended input. Modular values and booleans do not convert implicitly.

For dense mixed scalar types, pass a tuple, for example dense.asarray((1, Rational(1, 3)), context=context). An explicitly complex context can also embed real coefficients. Ball construction encloses conversion error; converting a ball to a point type requires all enclosed values to round to the same point.

Choose the destination type

Use dense.astype[Float](values, context=...) or its sparse counterpart when you want an explicit coefficient type. The supported destinations are APN Float, Complex, Ball, and ComplexBall, paired with the contexts in the table above. Both ball types use BallContext.

array_access_dtype.mojo Download
"""Choose a numerical destination and re-round prepared coefficients."""
from apn_mojo import Float, ComplexBall, Rational, ArithmeticContext, FloatFormat, BallContext, contains
from poly import dense, sparse
from std.testing import assert_equal, assert_true


def main() raises:
    var high = ArithmeticContext(format=FloatFormat(precision=100))
    var low = ArithmeticContext(format=FloatFormat(precision=30))
    var p = dense.asarray([Rational(1, 3), Rational(2)], context=high)
    var rounded = dense.astype[Float](p, context=low)
    assert_equal(rounded[0].precision(), 30)
    assert_equal(p[0].precision(), 100)
    var enclosed = sparse.astype[ComplexBall]([(0, Rational(1, 3)), (2, Rational(1))], context=BallContext(precision=80))
    assert_true(contains(enclosed[0][1].real(), Rational(1, 3)))
    assert_true(contains(enclosed[0][1].imag(), 0))
    print("Rounded precision:", rounded[0].precision())
    print("Real coefficients enclosed in complex balls")

Run from the repository root pixi run mojo run -I build docs/examples/array_access_dtype.mojo

Output

Rounded precision: 30
Real coefficients enclosed in complex balls

The first conversion rounds the stored 100-bit coefficients to 30 bits and leaves the original array unchanged. The second requests complex balls even though all input coefficients are real. Raw homogeneous sequences, mixed-type tuples, and prepared arrays use the same calls. Sparse mixed input is a tuple of (power, coefficient) pairs with a common power shape.

astype converts every coefficient, including within the same domain. A real destination requires exactly zero imaginary parts; an uncertain imaginary interval is rejected even when it contains zero. A ball-to-point conversion succeeds only when the entire enclosure rounds to one value. Ball destinations round bounds outward, preserving uncertainty. Choose an exact rational input when you want conversion from that exact value instead of from an already rounded approximation.

Convert algebraic coefficients

Pass an AA or QQbar array to asarray with a context to obtain numerical coefficients. The exact input stays unchanged. Real and complex floating-point conversion refines exact root bounds until every enclosed value rounds to the same result with the same rounding status. It honors the context's rounding mode and traps, and raises DOMAIN if max_precision cannot establish a finite rounded result. Work exhaustion separately raises LIMIT.

array_algebraic_numeric.mojo Download
from poly import dense, algebraic, evaluation
from apn_mojo import ArithmeticContext, FloatFormat, BallContext
from polynomials import NumericOptions


def main() raises:
    var root = dense.root_between([-2, 0, 1], 1, 2)
    var exact = dense.asarray([root.copy(), algebraic.asreal(1)])
    var rounded = dense.asarray(exact, context=ArithmeticContext(format=FloatFormat(precision=80)))
    print("precision:", rounded[0].precision())
    var checked = dense.enclose(exact, 2, context=BallContext(precision=80))
    print("checked:", evaluation.check(checked))
    var adaptive = dense.adaptive_evaluate(exact, 2, options=NumericOptions(initial_precision=8, max_precision=128, absolute_bits=60))
    print("accurate:", adaptive.accurate(), evaluation.check(adaptive))

Run from the repository root pixi run mojo run -I build docs/examples/array_algebraic_numeric.mojo

Output

precision: 80
checked: True
accurate: True True

With BallContext, conversion refines the real components at a scale relative to their magnitude, then rounds their bounds outward. QQbar selects ComplexBall; an explicitly real context rejects a nonzero imaginary part. Complex contexts can choose separate real and imaginary precisions.

A numerical array contains rounded coefficients or coefficient enclosures. Use enclose or adaptive_evaluate on the exact array when you also need an independently checkable connection to the original algebraic coefficients. Those results retain root witnesses alongside their arithmetic traces; see certified evaluation.

Calculate with prepared arrays

Numerical arrays support arithmetic, calculus, evaluation, interpolation, and transformations. Dense and univariate sparse arrays also support composition and division. Every call takes context=. Sparse calculus uses axis=, and a multivariate evaluation takes a coordinate tuple in axis order.

Construction from raw values applies the context. Calling asarray on an already prepared numerical array of the selected domain shares its stored coefficients; it does not round them again. Arithmetic applies the context you pass to that operation. Use astype to convert every stored coefficient into another type or precision context.

Arrays support indexing, iteration, and to_list(). poly.to_dense and poly.to_sparse convert numerical storage with an explicit context. Dense conversion allocates gap zeros, so use a work limit when sparse exponents can be large.

Use add(p, scalar, context=...), subtract, multiply, or divide for scalar arithmetic. Sparse scalar division keeps sparse storage, even for very large powers; division by a polynomial continues to use the quotient/remainder operations described below.

Distinguish a degree bound from a known degree

Only certain zero coefficients are removed. If the leading ball contains zero and nonzero values, degree_bound(p) returns its power, while certified_degree(p) returns None. A zero polynomial has degree -1. This distinction prevents uncertain leading coefficients from disappearing during construction or arithmetic.

Numerical root covers use dense.real_root_cover or dense.complex_root_cover. Inspect complete(), accurate(), isolated(), and outcome() before using a cover. roots.check verifies its claims, and roots.refine returns a new cover under new NumericOptions. Ball coefficients describe a family of polynomials; a method can leave cells unresolved when one uniform root claim does not hold across that family.

All computations accept max_steps= or a shared Budget, including input conversion and output allocation. See root workflows, saving arrays, and the numerical array reference.

Interpolate, transform, and divide

Numerical interpolate and hermite_interpolate accept ordinary sequences of nodes and samples with context=. evaluate_many returns every sample, including zeros at the end. jet_evaluate(p, x, order, context=...) returns order Taylor coefficients at x; these are derivatives divided by their factorials.

array_extension_numerical.mojo Download
"""Recover numerical coefficients and inspect checked division."""
from apn_mojo import ArithmeticContext, FloatFormat, Rational
from poly import dense, sparse
from std.testing import assert_equal, assert_true


def main() raises:
    var context = ArithmeticContext(format=FloatFormat(precision=96))
    var p = dense.interpolate([-1, 0, 1], [0, 1, 4], context=context)
    assert_equal(p[0].to_rational_exact(), Rational(1))
    assert_equal(p[1].to_rational_exact(), Rational(2))
    assert_equal(p[2].to_rational_exact(), Rational(1))
    var samples = dense.evaluate_many(p, [-1, 0, 1, -1], context=context)
    assert_equal(len(samples), 4)
    assert_equal(samples[3].to_rational_exact(), Rational(0))
    var divisor = dense.asarray([1, 1], context=context)
    var division = dense.divide_checked(p, divisor, context=context)
    assert_true(dense.check_division(division))
    assert_equal(division.quotient()[1].to_rational_exact(), Rational(1))
    assert_equal(len(division.remainder()), 0)
    var surface = sparse.asarray([((1, 1), 2)], context=context)
    var integrated = sparse.definite_integral(surface, 0, 2, axis=0, context=context)
    assert_equal(sparse.evaluate(integrated, (0, 3), context=context).to_rational_exact(), Rational(12))
    print("Recovered (x + 1)^2 and retained four samples")
    print("Numerical division checked; integrated surface is 4*y")

Run from the repository root pixi run mojo run -I build docs/examples/array_extension_numerical.mojo

Output

Recovered (x + 1)^2 and retained four samples
Numerical division checked; integrated surface is 4*y

The samples recover \((x+1)^2\). Dividing by \(x+1\) produces the expected quotient and zero remainder. For general floating-point data, a rounded quotient and remainder can leave a residual. divide_checked retains bounds on that residual, while check_division verifies them independently. For ball inputs, the evidence also establishes a division enclosure for every represented input pair. The leading divisor coefficient must exclude zero.

Use divmod when you only need the quotient/remainder pair. affine_transform returns \(p(ax+b)\), taylor_shift returns \(p(x+b)\), and definite_integral computes an endpoint difference. Sparse transforms and definite integration select an axis= and retain the original power shape. sparse.substitute takes (axis, replacement) pairs and replaces them simultaneously. Univariate sparse divmod, compose, and jet_evaluate use a dense conversion and charge its full extent before allocation.

For interpolation with balls, node enclosures must be separated so their differences exclude zero. hermite_interpolate treats each row as a value followed by consecutive derivatives; pass derivatives=False for Taylor coefficients.

Numerical series and alternative bases

array_extension_representations.mojo Download
"""Enclose formal coefficients and work directly in a Bernstein basis."""
from apn_mojo import BallContext, Rational, contains
from poly import series, basis
from std.testing import assert_equal, assert_true


def main() raises:
    var context = BallContext(precision=96)
    var x = series.asarray([0, 1], order=5, context=context)
    var exponential = series.exp(x, context=context)
    assert_equal(exponential.order(), 5)
    assert_true(contains(exponential[4], Rational(1, 24)))
    var curve = basis.from_polynomial([0, 0, 1], basis="bernstein", degree=3, context=context)
    assert_equal(len(curve), 4)
    assert_true(contains(basis.evaluate(curve, Rational(1, 3), context=context), Rational(1, 9)))
    var halves = basis.subdivide(curve, Rational(1, 2), context=context)
    assert_true(contains(basis.evaluate(halves[0], 1, context=context), Rational(1, 4)))
    print("exp(x): five known coefficients with enclosing balls")
    print("Bernstein evaluation and subdivision enclose the expected values")

Run from the repository root pixi run mojo run -I build docs/examples/array_extension_representations.mojo

Output

exp(x): five known coefficients with enclosing balls
Bernstein evaluation and subdivision enclose the expected values

The series has five known coefficients, through \(x^4\), with an unknown formal tail \(O(x^5)\). Its balls enclose coefficient values; they do not bound the analytic remainder of \(e^x\) at a chosen point. Numerical series provide the same arithmetic, calculus, composition, logarithm, exponential, and reversion operations as exact series. Construct with series.asarray(..., order=..., context=...), then pass a context to each computation. Different formal orders require an explicit common lower order=; increasing a prepared series' known order raises DOMAIN.

basis.asarray interprets supplied coefficients in a named basis; basis.from_polynomial converts monomial coefficients. Both accept context= for Newton, Chebyshev, and Bernstein representations. Coefficients retain their representation extent, including zeros. Bernstein subdivision preserves that degree. Newton arithmetic keeps the left operand's nodes and raises if they cannot represent the resulting degree.

See formal series, alternative bases, and certified evaluation for the mathematical contracts.