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."""
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.
"""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.
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.
"""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¶
"""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.