Tutorials

Calculate with algebraic coefficients

Use exact roots as ordinary polynomial coefficients. A polynomial containing \(\sqrt{2}\) stays exact through multiplication, division, interpolation, and Gröbner reduction. You don't need to choose a number field or a common primitive element before combining values.

Build a polynomial from roots

dense.root_between([-2, 0, 1], 1, 2) selects \(\sqrt{2}\). Put the returned algebraic.Real in a coefficient sequence to infer AA, the field of real algebraic numbers. Mixed scalar types use poly.coefficients((...)); sparse construction accepts mixed (power, value) tuples directly.

array_algebraic_coefficients.mojo Download
"""Build and calculate with exact real and complex algebraic coefficients."""
from poly import dense, sparse, algebraic, coefficients, exact
from std.testing import assert_true, assert_equal


def main() raises:
    var a = dense.root_between([-2, 0, 1], 1, 2)
    var left = dense.asarray(coefficients((algebraic.negative(a), 1)))
    var right = dense.asarray(coefficients((a.copy(), 1)))
    var product = dense.multiply(left, right)
    assert_true(product[0] == algebraic.asreal(-2))
    assert_true(product[1].is_zero())
    assert_true(product[2] == algebraic.asreal(1))
    print(product.domain(), ": (x - sqrt(2))(x + sqrt(2)) = x^2 - 2", sep="")

    var division = dense.divide_checked(product, left)
    assert_true(exact.check(division))
    assert_equal(exact.quotient(division).to_list(), right.to_list())

    var z = algebraic.ascomplex(a, 1)
    var complex_polynomial = dense.asarray(coefficients((algebraic.negative(z), 1)))
    assert_true(dense.evaluate(complex_polynomial, z).is_zero())
    print(complex_polynomial.domain(), ": x - (sqrt(2) + i) vanishes exactly at sqrt(2) + i", sep="")

    var multivariate = sparse.asarray((((1, 0), a.copy()), ((0, 1), 1)))
    var specialized = sparse.specialize(multivariate, [(0, a.copy())])
    assert_true(sparse.evaluate(specialized, (0, 3)) == algebraic.asreal(5))
    print("Sparse specialization: sqrt(2)*x + y becomes 2 + y")

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

Output

AA: (x - sqrt(2))(x + sqrt(2)) = x^2 - 2
QQbar: x - (sqrt(2) + i) vanishes exactly at sqrt(2) + i
Sparse specialization: sqrt(2)*x + y becomes 2 + y

The first multiplication produces \(x^2-2\) exactly. Its domain remains AA, even though its resulting coefficients happen to be integers. Domain inference uses input types; arithmetic does not silently narrow the result's scalar type. Empty arrays and zero polynomials also retain their domain.

Include exact complex values

algebraic.ascomplex(real, imag) combines two exact real values. Either part can be an integer, a rational, or a Real, including values from different algebraic extensions. The resulting algebraic.Complex infers QQbar, the field of complex algebraic numbers. This type is separate from APN's rounded Complex.

Use algebraic.add, subtract, multiply, divide, negative, reciprocal, and conjugate for scalar arithmetic. value.real() and value.imag() return exact Real components. Equality compares exact values: different root witnesses for the same number compare equal. Complex values have no ordering.

Inputs combined in a calculation Inferred domain
Real with integers or rationals AA
Real with APN ExactComplex QQbar
algebraic.Complex with any characteristic-zero exact scalar QQbar

Finite-field values have no implicit algebraic conversion. Approximation also stays explicit: pass a precision context= to dense.asarray or sparse.asarray to convert a whole array. Scalar bounds, refine, and approximate remain available when you need rational intervals or values. Certified evaluation retains the conversion witnesses.

Reuse the polynomial operations

The ordinary dense and sparse functions accept these coefficients: arithmetic, calculus, evaluation, composition and substitution, division, GCDs, resultants, squarefree decomposition, and interpolation use the inferred field. Sparse Gröbner bases and membership use it too. Checked variants retain their usual certificates and claims; for example, exact.check(dense.divide_checked(...)) verifies the division identity. Formal series and alternative bases use the same field adapters.

sparse.specialize accepts (axis, value) assignments. With algebraic or complex coefficient arrays it returns another sparse array, retaining the original power shape. With integer or rational coefficients and real assignments, it preserves the existing algebraic.Specialization result and its group evaluation proofs. Pass that result to sparse.asarray to continue ordinary polynomial arithmetic, or use algebraic.coefficients to extract one remaining coefficient axis.

dense.real_roots accepts real algebraic coefficient arrays and retains its completeness certificate. sparse.roots_at retains its integer/rational-input contract. For algebraic coefficients, use algebraic.coefficients(sparse.specialize(...), axis=...) and pass that sequence to dense.real_roots. dense.complex_roots and sparse.complex_roots accept both AA and QQbar coefficients and return exact complex values with multiplicities. See the complex-root workflow for ordering, verification, and sparse axis selection. Complex root-cover APIs return certified numerical regions.

Factor over real or complex algebraic numbers

dense.factor(p) and sparse.factor(p) return the familiar (scalar, factors) pair. Each factor has a positive multiplicity, and their powered product times the scalar equals p. For a univariate polynomial, AA factors are linear or quadratic, and QQbar factors are linear. Sparse multivariate polynomials can have irreducible factors of higher degree in either field.

The input field determines the answer. Integer coefficients for \(x^2-2\) keep it irreducible over ZZ. Including algebraic.asreal(1) requests AA, where it splits into \((x-\sqrt2)(x+\sqrt2)\). To split \(x^2+1\), choose QQbar by including algebraic.ascomplex(1).

array_algebraic_factor.mojo Download
"""Choose the factorization field through ordinary coefficient values."""
from poly import dense, sparse, algebraic, coefficients, exact
from std.testing import assert_equal, assert_true


def main() raises:
    # x^2 - 2 splits once its coefficients belong to the real algebraic field.
    var p = coefficients((-2, 0, algebraic.asreal(1)))
    var real_result = dense.factor_checked(p)
    var real_factors = exact.factors(real_result)
    var sqrt_two = dense.root_between([-2, 0, 1], 1, 2)
    assert_true(real_factors[0][0][0] == algebraic.negative(sqrt_two))
    assert_true(real_factors[1][0][0] == sqrt_two)
    assert_true(exact.check(real_result))
    print("AA: x^2 - 2 = (x - sqrt(2))(x + sqrt(2))")

    # Over AA, x^2 + 1 stays quadratic. Over QQbar it has two linear factors.
    var real_pair = dense.factor(coefficients((1, 0, algebraic.asreal(1))))
    var complex_result = dense.factor_checked(coefficients((1, 0, algebraic.ascomplex(1))))
    assert_equal(len(real_pair[1]), 1)
    assert_equal(len(exact.factors(complex_result)), 2)
    assert_true(exact.check(complex_result))
    print("QQbar: x^2 + 1 = (x - i)(x + i)")

    # The active variable can be any axis; output terms keep the same shape.
    var terms = sparse.asarray((((0, 2), algebraic.asreal(1)), ((0, 0), -2)))
    var sparse_result = sparse.factor_checked(sparse.power(terms, 3))
    assert_true(exact.check(sparse_result))
    for factor, multiplicity in exact.factors(sparse_result):
        assert_equal(multiplicity, 3)
        assert_equal(factor.domain(), "AA")
    print("Sparse factors preserve axis 1 and multiplicity 3")

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

Output

AA: x^2 - 2 = (x - sqrt(2))(x + sqrt(2))
QQbar: x^2 + 1 = (x - i)(x + i)
Sparse factors preserve axis 1 and multiplicity 3

Univariate factors are monic; the scalar is the input's leading coefficient. They sort by increasing degree, then by coefficients read from leading to constant. Complex coefficients use real part first, then imaginary part for this deterministic order. Zero and constants have no factors, with their value returned as the scalar. Neither factors nor zero results narrow their coefficient domain.

factor_checked retains the original polynomial and factorization. Over these two fields, exact.check verifies the entire factorization: coefficient witnesses, monicity, distinct sorted factors, positive multiplicities, and the product. For univariate inputs, it checks irreducibility directly: a linear factor is irreducible, and an AA quadratic must have strictly negative discriminant. Checking does not solve for the polynomial's roots again.

Factor several variables

Use tuple powers for multivariate input. The same functions infer the field, retain unused axes, and return sparse factors. For example, \(x^2+y^2\) stays irreducible over AA but splits into \((x-iy)(x+iy)\) over QQbar:

array_algebraic_multivariate_factor.mojo Download
from poly import sparse, algebraic, exact


def main() raises:
    # Powers (a, b) represent x**a * y**b. Choose the coefficient field
    # with one algebraic value; integer entries promote automatically.
    var real = sparse.asarray((((2, 0), algebraic.asreal(1)), ((0, 2), 1)))
    var complex = sparse.asarray((((2, 0), algebraic.ascomplex(1)), ((0, 2), 1)))
    var real_result = sparse.factor_checked(real)
    var complex_result = sparse.factor_checked(complex)
    print(real.domain(), "factors:", len(exact.factors(real_result)))
    print(complex.domain(), "factors:", len(exact.factors(complex_result)))
    print("verified:", exact.check(real_result), exact.check(complex_result))
    var line = sparse.asarray((((1, 0), algebraic.asreal(1)), ((0, 1), 1)))
    var repeated = sparse.factor_checked(sparse.power(line, 3))
    print("multiplicity:", exact.factors(repeated)[0][1])

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

Output

AA factors: 1
QQbar factors: 2
verified: True True
multiplicity: 3

Multivariate factors sort by total degree, then their stored monomials and coefficients. Each factor's greatest stored coefficient is one; the returned scalar accounts for that normalization. Use the returned scalar when reconstructing the input. A complex coefficient field does not force every multivariate factor to be linear: \(x^2+y\) is irreducible over QQbar.

The multivariate checker uses retained univariate image factorizations to verify irreducibility. It checks those certificates and rules out every proper divisor combination without rerunning the factorization producer. Keep the complete checked result for exact.check: result.evidence() exposes the core product certificate, whose own checker establishes reconstruction only.

Set max_steps for large inputs. The algorithm removes coordinate factors and repeated factors first, but general recombination builds a dense image whose storage grows with the product of the per-variable degree ranges. The search and its independent check can take exponential work. Budget exhaustion raises without returning partial factors. Root isolation and algebraic arithmetic can also dominate work for small inputs.

Costs, evidence, and boundaries

Every computation accepts a shared Budget or max_steps=. Coefficient-field arithmetic also verifies each scalar certificate before using its result in polynomial operations or checkers. A small coefficient array can still require substantial work: algebraic arithmetic constructs and factors annihilating polynomials, then selects the intended root. Degrees and coefficient sizes can grow when combining unrelated values. Budget exhaustion raises LIMIT and leaves borrowed inputs unchanged.

algebraic.check verifies a real witness and its retained arithmetic or evaluation step; for a complex value it checks both components. It does not certify an entire history of scalar calls. Polynomial checked results retain their separate, operation-specific evidence.

Rational functions accept AA and QQbar coefficients. CAD workflows accept AA coefficients directly and keep user coordinates and signs in their original order. To save exact algebraic arrays and their witnesses, see serialization. See the algebraic reference for scalar helpers.

For ratios of these polynomials, use poly.rational. Construction, arithmetic, substitution, evaluation, partial fractions, Laurent expansions, and residues retain the inferred exact field. Algebraic evaluation points and expansion centers can also promote an existing QQ function.