Tutorials

Keep and check exact algebra evidence

A coefficient array gives you an answer. A checked result also keeps the inputs and evidence needed to verify a stated relation. Use the _checked operations when a later calculation needs to validate the result independently.

array_parity_checked.mojo Download
"""Verify an exact division and a GCD from ascending coefficients."""
from apn_mojo import Rational
from poly import dense, exact
from std.testing import assert_equal, assert_true


def main() raises:
    var division = dense.divide_checked([1, 0, 1], [1, 2])
    assert_true(exact.check(division))
    var quotient = exact.quotient(division)
    assert_equal(quotient[0], Rational(-1, 4))
    assert_equal(quotient[1], Rational(1, 2))
    assert_equal(exact.remainder(division)[0], Rational(5, 4))
    var gcd = dense.gcd_checked([-1, 0, 1], [-1, 1])
    assert_true(exact.check(gcd))
    assert_equal(exact.value(gcd).to_list(), dense.asarray([-1, 1]).to_list())
    print("Exact division and GCD verified")

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

Output

Exact division and GCD verified

The first calculation establishes

\[ 1+x^2=(1+2x)\left(-\frac14+\frac12x\right)+\frac54. \]

exact.check verifies the identity and the dense remainder's degree bound. The GCD check additionally establishes maximality through Bézout or coprimality evidence. exact.value returns the ordinary array, so subsequent calculations use the same functional API.

Choose the evidence you need

Both namespaces offer checked GCD, resultant, division, ordered list division, factorization, and squarefree decomposition. Dense interpolation and Hermite interpolation can retain their nodes and all value or derivative constraints. coprimality(a, b) returns optional evidence for coprime operands.

A check proves the relation documented for that operation. Factor checks prove recomposition; for AA and QQbar, they also verify irreducibility, monicity, and exact multiplicities. Other factorization domains keep the product-only check. resultant_checked proves an ideal-membership identity. Choose resultant_certified when you need verification of the complete Sylvester determinant over any supported exact field. The checked-results reference lists every claim so you can decide whether it meets your application's needs.

Share one Budget across preparation, production, output extraction, and checking when the entire workflow needs a work limit. Result copies share storage, while extracted polynomial arrays own independent data.

Exact queries and algorithm choices

Use sign_on_interval(p, low, high) to establish a strict sign throughout a closed interval. It returns 1 or -1 when the interval contains no root, and None when a root occurs anywhere, including either endpoint. A repeated root also prevents a strict sign, even if the polynomial never changes sign. Equal endpoints are allowed; reversed endpoints raise DOMAIN. Inputs and endpoints can be integers, rationals, or real algebraic values. Sparse input needs a one-variable power shape. This query returns an exact answer without retaining a certificate.

array_exact_choices.mojo Download
"""Exact sign queries, determinant proofs, and sparse algorithm choices."""
from std.testing import assert_equal, assert_true
from apn_mojo import Rational
from poly import dense, sparse, exact


def main() raises:
    # x^2 - 2 stays positive throughout [2, 3].
    var sign = dense.sign_on_interval([-2, 0, 1], 2, 3)
    assert_equal(sign.value(), 1)
    print("Positive throughout [2, 3]")

    # The determinant gives Res(x^2 + 1, x^2 - 2) = 9.
    var resultant = dense.resultant_certified([1, 0, 1], [-2, 0, 1])
    assert_true(exact.check(resultant))
    assert_equal(exact.value(resultant)[0], Rational(9))
    print("Resultant 9 verified from its determinant")

    var h = sparse.asarray([((1, 0), 1), ((0, 1), 1)])  # x + y
    var a = sparse.multiply(h, [((1, 0), 1), ((0, 0), 1)])
    var b = sparse.multiply(h, [((0, 1), 1), ((0, 0), 1)])
    var common = sparse.gcd_checked(a, b, method="modular")
    assert_true(exact.check(common))
    assert_equal(exact.value(common).to_list(), h.to_list())
    print("Sparse modular GCD: x + y")

    # Dividing x by x + y^2 illustrates how monomial order affects reduction.
    var x = sparse.asarray([((1, 0), 1)])
    var divisor = sparse.asarray([((1, 0), 1), ((0, 2), 1)])
    var lex = sparse.divide_checked(x, divisor)
    var graded = sparse.divide_checked(x, divisor, order="grlex")
    assert_true(exact.check(lex) and exact.check(graded))
    assert_equal(exact.remainder(lex).to_list(), sparse.asarray([((0, 2), Rational(-1))]).to_list())
    assert_equal(exact.remainder(graded).to_list(), sparse.asarray([((1, 0), Rational(1))]).to_list())
    print("Division remainders: -y^2 under lex, x under grlex")

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

Output

Positive throughout [2, 3]
Resultant 9 verified from its determinant
Sparse modular GCD: x + y
Division remainders: -y^2 under lex, x under grlex

resultant_certified returns a coefficient array and a complete determinant transcript. Integer inputs promote to QQ; QQi, GF(p), AA, and QQbar retain their fields. Sparse inputs accept axis= to select the variable to eliminate, and the output retains the original power shape. As with other exact results, use exact.value, exact.check, and an immutable evidence() borrow. Keeping the elimination transcript can require substantial memory, so share a budget when working with larger degrees.

Choose method="modular" on sparse.gcd or sparse.gcd_checked to use sparse modular reconstruction over integers or rationals. The algorithm discovers and interpolates the occurring monomials, avoiding a dense box of parameter degrees. Both methods compute the same normalized GCD. The checked form also retains maximality evidence. method="auto" keeps the existing core dispatch; it does not automatically choose this dedicated sparse algorithm. Other method names raise DOMAIN; requesting modular reconstruction over QQi or a prime field raises UNSUPPORTED.

Choose the order for sparse division

divmod, divide_many, divide_checked, and divide_many_checked accept order="lex", "grlex", or "grevlex", plus an optional axes= permutation in descending priority. For example, axes=[1, 0] makes the second variable more significant. Every axis must appear exactly once; an empty list uses tuple order. The default remains lexicographic order with axis zero first.

In the example, lexicographic division reduces \(x\) by the leading \(x\) of \(x+y^2\), leaving \(-y^2\). Graded lexicographic order makes \(y^2\) the leading monomial, which cannot divide \(x\), so the quotient is zero and the remainder is \(x\). Reversing the axis priority under lexicographic order gives the same remainder in this example.

The chosen order affects the calculation, while output terms always return in canonical increasing lexicographic order. You can pass them straight into other array operations. Checked results retain the computational order in their evidence. Their checker establishes the recomposition identity; it does not independently certify reducedness or a unique remainder. Divisor list order also matters. Use a Gröbner basis when you need ideal normal forms.