Core API

Core API

Applications use poly: its functions take coefficient sequences, infer domains, and return immutable arrays and checkable results. They are built on the core package, polynomials, which provides the mutable containers, coefficient rings, algorithms, and certificate checkers. Use the core API when you extend the library, add an algorithm, or integrate with a system that uses the core compatibility contract. Both packages share Budget, PolyError, and the option types such as NumericOptions.

Topic Reference
Domains, contexts, containers, orders, arithmetic, errors, and binary codec Representations and arithmetic
Division, gcds, resultants, fractions, and identity certificates Division and elimination
Squarefree, finite-field, integer, rational, and Gaussian factorization Univariate factorization
Bases, reduction, and ideal membership Gröbner bases
Sturm sequences, root counts, isolation, and refinement Exact real roots
Calculus, interpolation, bases, series, rational utilities, and text transport Polynomial toolkit
Point and ball arithmetic, enclosures, root covers, and incremental witnesses Numerical polynomials
Exact algebraic arithmetic and specialization Real algebraic numbers
Sparse modular gcds and multivariate factors Multivariate factorization
Projection, cells, complete decomposition, and selective coverings CAD

Exact operations are generic over a coefficient ring or field where supported. Numerical operations take explicit arithmetic contexts. Most computational calls accept a mutable Budget and raise PolyError; see errors and work limits. The exact public surface is described by the compatibility contract. Numerical operations also follow the precision and certificate guarantees.

Exchange exact arrays with core values

array_exact_core.mojo Download
"""Use core certificates and variable IDs without parsing an expression."""
import poly
from polynomials import Budget, factor_integer, check_factorization
from std.testing import assert_equal, assert_true


def main() raises:
    var work = Budget(1000000)
    var p = poly.dense.asarray([-1, 0, 1], work)
    var core = poly.dense.to_core(p, work, variable=7)
    var result = factor_integer(core, work)
    assert_true(check_factorization(core, result, work))
    print("Core factor product checked")
    var first = poly.dense.from_core(result.factors[0][0], work)
    print("First factor:", first.to_list(work))
    var surface = poly.sparse.asarray([((1, 0), 2), ((0, 1), 3)], work)
    var mapped = poly.sparse.to_core(surface, work, variables=[7, 2])
    var restored = poly.sparse.from_core[ndim=2](mapped, work, variables=[7, 2])
    assert_equal(restored.to_list(work), surface.to_list(work))
    print("Variable map round trip checked")

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

Output

Core factor product checked
First factor: [-1, 1]
Variable map round trip checked

to_core gives you independent mutable DensePoly or SparsePoly storage. from_core validates and copies it back into an immutable array. Changing either side afterwards cannot change the other. Dense import normalizes its single variable ID to zero. Sparse import accepts any valid core term order and returns the array convention of increasing lexicographic order.

Sparse axes and core variable IDs need not coincide. Supply the same variables= map in each direction, and set from_core[ndim=2] for two axes, including axes unused by the polynomial. The map is in array-axis order; unknown used IDs and duplicate map entries raise DOMAIN.

The example uses the core factorization result with its independent checker. That checker establishes the factor product, not irreducibility. Array users can retain and check the same evidence through the checked array operations, including factor_checked. Use the core bridge when you need mutable core values or an existing core algorithm interface. The example shares one allowance across construction, conversion, computation, and checking.

Exchange numerical arrays

to_core and from_core make independent copies while preserving every stored numerical format and ball bound. They need no arithmetic context. Dense export accepts variable=; import normalizes that ID to the array's axis zero.

array_access_interop.mojo Download
"""Keep numerical coefficients unchanged when crossing into core storage."""
from apn_mojo import ArithmeticContext, FloatFormat, Rational
from poly import sparse
from std.testing import assert_equal


def main() raises:
    var context = ArithmeticContext(format=FloatFormat(precision=80))
    var p = sparse.asarray([((2, 0), Rational(1, 3)), ((0, 1), Rational(2))], context=context)
    var variables: List[Int64] = [19, -4]
    var core = sparse.to_core(p, variables=variables)
    var restored = sparse.from_core[ndim=2](core, variables=variables)
    assert_equal(restored.to_list(), p.to_list())
    assert_equal(restored[1][1].to_json(), p[1][1].to_json())
    print("Numerical coefficients preserved across variable maps")

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

Output

Numerical coefficients preserved across variable maps

For sparse polynomials, variables= maps array axes to core variable IDs in both directions. Use from_core[ndim=N] to preserve the full power shape, including unused axes. Import accepts any valid core monomial order and returns increasing lexicographic array storage. Signed IDs and a different axis mapping are allowed; duplicate map entries or an unmapped used variable raise DOMAIN. Changing the exported core polynomial leaves the original array unchanged.

Exchange series, bases, and rational functions

Series and basis arrays can exchange core values with arbitrary variable IDs. Use to_core(..., variable=...) to choose an ID; from_core normalizes it to array axis zero. Fraction conversions take variables=, one core ID per array axis, for both numerator and denominator.

array_surface_interop.mojo Download
"""Exchange representations with core algorithms using explicit variable IDs."""
from poly import series, basis, rational
from apn_mojo import ArithmeticContext, FloatFormat, Rational
from std.testing import assert_equal


def main() raises:
    var context = ArithmeticContext(format=FloatFormat(precision=80))
    var s = series.asarray([Rational(1, 3), Rational(2)], order=5, context=context)
    var core_s = series.to_core(s, variable=-19)
    var restored_s = series.from_core(core_s, context=context)
    assert_equal(restored_s[0].to_json(), s[0].to_json())
    print("Restored series order:", restored_s.order())
    var b = basis.asarray([1, 2, 0], basis="bernstein", context=context)
    var core_b = basis.to_core(b, variable=42)
    assert_equal(len(basis.from_core(core_b, context=context)), 3)
    var f = rational.asarray([1], [1, 1], context=context)
    var ids: List[Int64] = [9]
    var core_f = rational.to_core(f, variables=ids)
    var restored_f = rational.from_core[1](core_f, variables=ids)
    assert_equal(rational.evaluate(restored_f, 1, context=context).to_rational_exact(), Rational(1, 2))
    print("Representation copies preserve stored coefficients")

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

Output

Restored series order: 5
Representation copies preserve stored coefficients

These conversions copy stored coefficients and Newton nodes without rounding them. Numerical series and basis imports still take context=: for a series, it supplies the format of implicit zero coefficients. It does not change stored nonzero coefficients. Formal order, basis kind, coefficient extent, and unused fraction axes survive the conversion. Mutating an exported core value cannot change the prepared input.

rational.to_core(f) returns a core RationalFunction for QQ. For QQi, GF(p), AA, and QQbar, it returns (numerator, denominator) as public SparsePoly values, since the core rational-function container is QQ-specific. Both forms own independent storage. Numerical fractions export the pair as numerical sparse polynomials. Import any of these forms with from_core[ndim=n](value); zero-axis constants are supported.

rational.to_core(f, variables=ids) assigns one core variable ID to each array axis, including unused axes. Restore with rational.from_core[ndim](core, variables=ids). IDs may be signed and need not increase; their positions define array-axis order. Duplicate IDs or unmapped variables raise DOMAIN. The same mapping works for exact and numerical fractions. Numerical copies preserve stored formats and coefficient bounds without a new context. Imports still validate both polynomials and require a denominator family that excludes the identically zero polynomial.

Algebraic coefficients in core containers

algebraic.Real and algebraic.Complex implement CoefficientField. dense.to_core therefore returns a DensePoly parameterized by the corresponding type, and sparse conversion works the same way. The mutable polynomial buffer is independent; coefficient copies share immutable witnesses.

algebraic.asreal(core_witness) validates and copies a mutable RealAlgebraic witness. It accepts valid nonminimal defining polynomials and normalizes the value for equality; later edits to the input witness cannot change the scalar. See the algebraic array reference for scalar helpers.