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