Tutorials

Select the CAD work your calculation needs

Use cad.project to compute projection polynomials, cad.cell_at to construct the cell containing an exact point, and cad.lazard_evaluate to prepare one lifting polynomial. Each returns retained evidence for cad.check and accepts ordinary sparse coefficient arrays.

array_parity_cad.mojo Download
"""Select a CAD cell and recover a nullified Lazard lifting polynomial."""
from poly import cad, algebraic, dense
from std.testing import assert_equal, assert_true


def main() raises:
    var inputs = [[((1, 0), 1), ((0, 0), -2)], [((0, 1), 1), ((0, 0), 3)]]
    var projection = cad.project(inputs, axes=[1, 0])
    assert_true(cad.check(projection))
    var cell = cad.cell_at(inputs, (2, -3), axes=[1, 0])
    assert_true(cad.check(cell))
    assert_equal(cad.signs(cell), [0, 0])
    var point = cad.sample(cell)
    assert_equal(algebraic.compare(point[0], 2), 0)
    assert_equal(algebraic.compare(point[1], -3), 0)
    var lifting = cad.lazard_evaluate([((2, 1), 1), ((2, 0), -1)], [(0, 0)], axis=1)
    assert_true(cad.check(lifting))
    assert_equal(cad.vanishing_orders(lifting), [(0, 2)])
    var roots = dense.real_roots(cad.lifting_coefficients(lifting))
    assert_equal(len(roots), 1)
    assert_equal(algebraic.compare(roots[0], 1), 0)
    print("Selected cell and Lazard lifting verified")

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

Output

Selected cell and Lazard lifting verified

Keep axes and coordinates distinct

axes=[1, 0] changes the base-to-top CAD traversal order. Input and output coordinates still use the polynomial's original axis order: (2, -3) assigns \(x=2\) and \(y=-3\). Lists accept homogeneous exact values; coordinate tuples can mix integers, rationals, and algebraic.Real values.

cad.signs(cell) reports the constant sign of each input polynomial on the selected cell. cad.sample(cell) returns the exact query point. The retained certificate establishes both sign invariance and query-point containment. Use cad.projection_polynomials(projection, axis=0) to copy the projection basis for an original axis, or omit axis to collect every level.

Fix coordinates before decomposing

Pass fixed=[(axis, value), ...] to study a slice of your problem. For example, fixing \(y=\sqrt{2}\) in \(x-y\) leaves three cells along the \(x\) direction: \(x<\sqrt{2}\), \(x=\sqrt{2}\), and \(x>\sqrt{2}\).

array_fixed_cad.mojo Download
"""Decompose, solve, stream, and save a CAD slice at exact coordinates."""
from poly import cad, dense, algebraic, io
from polynomials import Budget
from std.testing import assert_true, assert_equal


def main() raises:
    # x - y, with y fixed at sqrt(2).
    var p = [((1, 0), 1), ((0, 1), -1)]
    var a = dense.root_between([-2, 0, 1], 1, 2)
    var fixed = [(1, a.copy())]
    var result = cad.decompose([p.copy()], fixed=fixed)
    assert_true(cad.check(result))
    var cells = cad.cells(result)
    assert_equal(len(cells), 3)
    assert_equal(cells[1].signs, [0])
    assert_true(cells[1].sample[0] == a and cells[1].sample[1] == a)
    print("Cells on y = sqrt(2):", len(cells))

    var solved = cad.solve([cad.zero(p)], fixed=fixed)
    assert_true(solved.satisfiable() and cad.check(solved))
    var point = cad.sample(solved)
    assert_true(point.value()[0] == a)
    print("Exact satisfying point checked")

    var budget = Budget()
    var checker = cad.checker([p.copy()], budget, fixed=fixed)
    var receipt = cad.decompose_to([p.copy()], checker, budget, fixed=fixed)
    assert_true(checker.finish(receipt, budget))
    assert_equal(receipt.cells, 3)
    print("Restricted stream checked")

    var restored = io.loads[type_of(result)](io.dumps(result))
    assert_true(cad.check(restored))
    assert_equal(cad.fixed_coordinates(restored)[0][0], 1)
    assert_true(cad.fixed_coordinates(restored)[0][1] == a)
    print("Saved coordinate binding checked")

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

Output

Cells on y = sqrt(2): 3
Exact satisfying point checked
Restricted stream checked
Saved coordinate binding checked

The same fixed= argument works with decompose, solve, cover, decompose_to, and checker. Values can be integers, rationals, or exact real algebraic numbers. Use a list for one value type, or a tuple such as fixed=((0, 1), (2, a)) to mix types. Use each selected axis once; axis numbers must fit the polynomial's power shape. An empty iterable or fixed=() leaves every original coordinate free.

By default, CAD traverses the fixed axes in increasing order, then the free axes in increasing order. If you supply axes=, include every original axis and put the fixed ones first. For example, fixing axis 1 of a two-variable polynomial requires axes=[1, 0]. The order of pairs in fixed does not change this traversal. A streaming checker takes the same fixed= and axes= as its producer.

Samples retain all original coordinates, including the fixed values, in original axis order. Signs still correspond only to your input polynomials. Cell dimensions count the remaining free directions. Fixing every axis gives one point; solve and cover decide whether that point satisfies the constraints. A negative answer proves infeasibility on the requested slice. Use cad.fixed_coordinates(result) to copy the retained bindings.

Fixed coordinates also work with algebraic coefficients. Lifting visits only the requested coefficient values and coordinate values, while projection and local root isolation still process the expanded rational problem. Shared budgets include preparation, traversal, and checking. Stream completion requires an accepted receipt; a partial stream after an error does not prove coverage.

Checked archives preserve the exact bindings and their root witnesses through io.dumps/loads and incremental io.dump/load. Restoring a result checks both the geometry and its coordinate scope. Equivalent exact values can use different defining witnesses in a stream producer and checker.

Lift through a vanishing specialization

The polynomial \(x^2(y-1)\) becomes zero when you substitute \(x=0\). Lazard evaluation removes the vanishing factor before lifting in \(y\). It records the least nonvanishing derivative order, two in this example, and produces \(2(y-1)\). The positive factorial factor preserves the roots and signs used by lifting.

Assignments are ordered (axis, exact_value) pairs. Leave the lifting axis unassigned and assign every other occurring axis. cad.vanishing_orders keeps that assignment order. cad.lifting_coefficients returns ascending exact-real coefficients, which dense.real_roots can consume directly.

Choose a feasibility workflow

cad.solve uses selective cylindrical covering to search a conjunction of sign constraints. cad.cover builds a full CAD decomposition and retains either a satisfying cell or a covering of conflicts. Use the latter when you need that complete decomposition evidence; cad.cells(result) returns all terminal cells with their dimensions, samples, and signs. This can require substantially more work and memory. Both use the same constraints, satisfiable(), cad.sample, and cad.check conventions. cad.decompose remains the direct operation for all sign-invariant cells.

These operations accept integer, rational, and real algebraic coefficients. Query points and assignments can also contain exact algebraic values. Complex and modular coefficients are rejected. Lazard projection is their default; lazard=False selects McCallum projection. All preparation and checking share a supplied Budget, or use max_steps= for a single call. See the CAD reference for signatures.

Put algebraic values in coefficients

Use a Real value wherever a coefficient belongs. For example, the same constraint helpers can express \(x=\sqrt{2}\) and \(x>0\):

array_algebraic_cad.mojo Download
from poly import cad, dense, algebraic


def main() raises:
    var a = dense.root_between([-2, 0, 1], 1, 2)
    var p = [(0, algebraic.negative(a)), (1, algebraic.asreal(1))]
    var solution = cad.solve([cad.zero(p), cad.positive([(1, 1)])])
    print("satisfiable:", solution.satisfiable())
    print("checked:", cad.check(solution))
    print("exact sample:", cad.sample(solution).value()[0] == a)
    var decomposition = cad.decompose([p.copy()])
    print("cells on the line:", len(cad.cells(decomposition)))
    var checker = cad.checker([p.copy()])
    var receipt = cad.decompose_to([p.copy()], checker)
    from polynomials import Budget
    var budget = Budget()
    print("stream checked:", checker.finish(receipt, budget))
    print("stream stacks and cells:", receipt.stacks, receipt.cells)

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

Output

satisfiable: True
checked: True
exact sample: True
cells on the line: 3
stream checked: True
stream stacks and cells: 2 3

Integer, rational, and algebraic constraints can share one list. A homogeneous sparse polynomial uses one coefficient type; sparse.asarray(((power, value), ...)) prepares a tuple with mixed coefficient types. All existing workflows accept AA, including projection, cell queries, Lazard evaluation, selective solving, full covering, and streaming. projection_polynomials returns AA arrays when its input coefficients are AA.

Samples and signs keep their original meaning. Samples contain only your polynomial axes, in their original order; signs contain one entry per input polynomial. cells returns only the cells for the specified coefficient values. An algebraic coefficient's other conjugates do not introduce extra user cells. vanishing_orders likewise lists only the assignments you supplied.

Decomposition follows only the coefficient values you supplied. For example, \(x-\sqrt{2}\) gives three cells on the real line: below, at, and above \(\sqrt{2}\). Lifting does not explore the coefficient's negative conjugate or intervals between coefficient roots. This applies to decompose, cover, solve, and decompose_to automatically.

The implementation still builds a rational projection problem with auxiliary variables for distinct irrational coefficients. Rational values need no extra axis; equal or opposite irrational values share one. Projection and exact root arithmetic can remain expensive, especially with many distinct coefficients. Use a work budget, and choose solve, project, or cell_at when a full decomposition would give you more than you need.

Check or consume algebraic CAD evidence

Use cad.check(result) to check the geometric certificate and its exact connection to your coefficients and fixed coordinates. .evidence() borrows the underlying rational certificate, including auxiliary variables and isolating constraints. Its tree covers the selected coefficient and coordinate values, then every remaining free polynomial axis. The ordinary core check_cad requires full-space coverage; use the facade checker for this restricted claim.

decompose_to streams the restricted traversal without retaining its full cell history. Prepare its checker with the same polynomials, fixed=, and axes=, then finish with the returned receipt. For AA inputs, the checker also enforces the auxiliary-first axis order and selected coefficient sections.

Custom sinks receive original input signs first, followed by triples for each coefficient's defining polynomial and two strict isolating bounds. Those triples have signs (0, 1, 1). Any user-fixed axes add one defining-polynomial sign each, in traversal order; these signs are zero. Auxiliary variable IDs begin at the original axis count, and the projection header places them before the original axes.

A stack event contains every local root and its proof, but only the selected section has descendants on a fixed axis. Receipt cell counts equal the number of terminal cells returned by cad.cells; stack counts include both coefficient and user-fixed prefixes. For \(x-\sqrt{2}\) with its original axis left free, the receipt records two stacks and three cells.