Skip to content

Latest commit

 

History

History
294 lines (249 loc) · 16.5 KB

python-api.md

File metadata and controls

294 lines (249 loc) · 16.5 KB

Native Python API

Jacobian exposes a small native mathematical API under jacobian.math. Native functions call the domain kernels directly and are independent of MCP.

from fractions import Fraction

import sympy

from jacobian.math import matrices, polynomials
from jacobian.math.number_theory import arithmetic

half = arithmetic.sum_rationals(Fraction(1, 3), Fraction(1, 6))
matrix = sympy.Matrix([[1, 2], [3, 4]])
determinant = matrices.determinant(matrix)

The polynomial expression normalizer is available natively as polynomials.normalize_polynomial_expression. It accepts the same typed PolynomialExpressionSource value used by the native API; the catalog's wire PolynomialExpressionNormalizeRequest is unwrapped in _tools.py before the same function runs. It never parses or evaluates caller-authored source strings.

Finite-dimensional Lie algebras expose exact operations from jacobian.math.lie_algebras. The adjoint representation, Killing form, derived and lower-central series, centralizers, and generated subalgebras and ideals use the algebra's ordered basis and exact structure constants. The upper central series returns subspaces on that basis, beginning with zero; it reaches the whole algebra exactly when the algebra is nilpotent and otherwise stops at its first stable proper term.

Each public jacobian.math.<domain> module declares its supported names in __all__; that is the authoritative native API. Functions accept domain values or a maintained backend type when it already carries the complete mathematical meaning. Private backend modules perform lazy conversions and calls to SymPy, NetworkX, FLINT, or Z3.

Public import paths follow mathematical ownership. Large owners expose recognizable subdivisions, for example jacobian.math.graphs.chip_firing or jacobian.math.polynomials.interpolation; backend and transport structure does not appear in the public path. When a pre-stable package is folded into its canonical owner, callers move to the owner path in the same release. The old path is removed rather than retained as a forwarding package, and operation IDs and wire schemas remain unchanged unless their mathematical contract changes independently.

Native values are mathematical values rather than wire envelopes. The architecture responsibility map distinguishes values, operation arguments, wire requests, and execution context. The native API can be broader than the published catalog: exporting a useful function does not require publishing another math tool. Native and MCP calls use the same domain-owned request admission, kernel path, canonical result construction, and typed outcome semantics. Most native functions simply normalize and validate their input, perform the computation, and return a canonical value. An operation uses a distinct execution plan only when admission derives information that its kernel or result construction needs to reuse. The MCP path adds only wire parsing and the final transport projection; native code must not inherit MCP byte/depth/echo policy. Intrinsic structural and computational limits still apply independently of the calling surface; sharing a numeric threshold does not make a transport limit mathematical.

This parity includes bounded execution. Deadlines, cancellation checkpoints, deterministic work accounting, worker containment, and backend-failure classification are transport-independent library behavior. A native call receives the canonical result or the same typed timeout, cancellation, resource, backend-availability, or backend-failure exception that a delivery adapter would translate. It does not receive ToolError, an MCP task handle, protocol progress metadata, or a CLI exit code.

The library execution context may carry an optional progress sink supplied by a caller. Progress reports only real completed units or phases and does not change the function's result type. MCP may connect that sink to protocol notifications; native applications may connect it to their own UI or leave it absent. Durable job identity, persistence, polling, authorization, and result retention belong to a serving runtime rather than the native mathematical API.

An MCP operation parses one wire request before calling the shared domain admission function. Native callers call that domain function directly rather than constructing a wire Pydantic model. Native and wire parity tests should assert equal exact results and typed outcomes, and should document any difference as an explicit transport-only constraint.

Every native callable that backs a public operation is itself an admission boundary. Python type annotations and Pydantic field declarations do not validate values supplied directly to that callable. The implementation must validate strict runtime types, shape, axes, and domain; enforce the same owner-defined work and intermediate limits; and enforce the same exact-output or materialization bounds before indexing, unpacking, backend work, or result allocation. Equivalent native and catalog invocations must use the same owner admission helper. Expected invalid native values, shapes, axes, and dimensions raise OperationDomainValidationError or the operation's declared subtype while preserving its stable owner code and domain/resource classification. Use OperationResourceAdmissionError when the operation contract distinguishes execution-envelope refusal from other domain rejections. Neither path may leak helper ValueError, IndexError, tuple-unpacking errors, or Pydantic validation exceptions.

Legacy native-only helpers that have not yet been audited may violate this boundary; each such violation is migration debt. Audit and repair them before publishing them as public-operation implementations.

Pydantic request models are transport contracts, not containers for hidden execution state. They may enforce typed shape, canonical representation, and cheap intrinsic cross-field consistency. They must not store admission plans in PrivateAttr fields or perform backend calls, candidate enumeration, algorithm selection, or result-work planning in validators. Those steps belong to the native function after wire-to-domain conversion.

Canonical native values

The interoperability contract distinguishes shared values, meaningful refinements, representation transforms, and changes of parent. It also defines when conversions belong only in the native API and what serialization does and does not establish.

Reuse the owner-defined public type for the same mathematical value and representation, normally from the domain's values.py. Producers return that type and consumers accept it directly. Distinct storage or basis variants need explicit semantics and conversions, not operation-specific copies of the same fields. Request and result models may contain a canonical value alongside genuine operation parameters, but must not reproduce it as a parallel set of fields.

Finite probability uses this same rule. FiniteJointTable, MutualInformationResult, and the asymmetric local-lemma witness and result values are the same canonical types in native calls and JSON transport. Their rational fields use CanonicalRational; use from_fraction(...) and as_fraction() for explicit scalar conversion. Mutual-information results retain both ordered label axes, including rows and columns of zero mass. Joint-table normalization and local-lemma result-growth admission run when the operation executes, while parsing retains structure and authored claims. all_terminal_reliability(graph, probability) still accepts a native Fraction probability and returns the same AllTerminalReliabilityResult as MCP, with canonical rational probability fields.

For example, the finite-field API follows this ownership chain:

finite_field(...) -> FiniteFieldPresentation
FiniteDimensionalSubspace(...) -> FiniteDimensionalSubspace
linear_map_rank(subspace, direction) accepts that FiniteDimensionalSubspace

The same rule applies after serialization: a producer's canonical value must pass through the consumer's typed boundary without the caller reconstructing its field presentation, axes, ambient dimension, normalization, or other mathematical context. Empty and degenerate values retain that context too.

Integer encoding does not authorize flattening that value. A dynamics request continues to accept RationalPolynomial or FinitePolynomialMap, rather than separate coefficient and field parameters; only their integer leaves serialize as decimal strings. This preserves direct producer-to-consumer composition and keeps parent, variable, and domain/codomain data available to admission.

For example, a QQ matrix with row_count=0, column_count=3, and entries=[] is a map from a three-dimensional space to the zero space. Its dimensions survive dense/sparse conversion, backend conversion, and JSON round trips. An empty quadratic matrix additionally supplies its radicand; an embedded number-field matrix retains its embedding. Never infer a parent from an entry when there may be no entries.

Likewise, FiniteDimensionalSubspace supplies presentation, row_axis, and column_axis independently of its basis. An empty basis and empty basis_axis.labels represent the zero subspace in that ambient matrix space; scalar restriction retains the target axis even when its matrix has zero columns. Nonempty basis matrices must match the declared parent and axes. Consumers establish the basis's independence at admission, not during parsing.

Integer values and JSON

Migrated integer fields hold Python int values, not decimal text. The same model owns both native computation and JSON encoding:

from jacobian.math.matrices.values import IntegerMatrix

matrix = IntegerMatrix(entries=((2**53 + 1,),))
assert matrix.entries[0][0] == 2**53 + 1
assert matrix.model_dump()["entries"] == ((2**53 + 1,),)
assert matrix.model_dump(mode="json")["entries"] == [["9007199254740993"]]
assert IntegerMatrix.model_validate_json(matrix.model_dump_json()) == matrix

Use native integers in mathematical tests. Test malformed decimal strings and serialization separately through model_validate_json(). Passing a decoded JSON dictionary to model_validate() selects the native path and does not decode its strings. This encoding choice does not increase an operation's work or output limits. See the migration requirements for the remaining legacy-field and compound-rational scope.

Finite abelian groups

The abelian operations use AbelianPresentation(invariant_factors=(2, 12)). Its invariant factors and all element coordinates are exact Python integers. model_dump() preserves them; model_dump(mode="json") and model_dump_json() encode them as canonical decimal strings. model_validate_json() validates and decodes that encoding back into the same value type. Native construction does not accept numeric strings, and JSON decoding does not accept numbers for these fields. Finite based chain complexes and filtered chain data use Python int and fractions.Fraction coefficients. Their JSON representation uses canonical integer strings and reduced rational strings such as "-3/7"; read those wire values with model_validate_json(). A ChainMapValue binds its degreewise matrices to exact source and target ChainComplexValue endpoints; optional basis labels bind named axes. Its shape and coefficient grammar are structural, while consumers that rely on the chain-map equation replay that relation at their own admitted boundary. Differential matrices, chain maps, mapping cones, tensor products, filtered subspaces, and producer results retain the same native coefficient types in Python.

reduce_element(group, coordinates) returns an ElementReduceResult retaining the source coordinates and an AbelianElement with canonical coordinates. element_order and elements_equal retain their source group and canonical elements in their result values. generated_subgroup and quotient_group similarly return parent-bound AbelianSubgroup values. Different presentations are not implicitly identified merely because they present isomorphic groups.

normalize_presentation(source) retains the source cyclic-factor presentation and returns an invariant-factor AbelianPresentation; quotient computation returns an abstract AbelianQuotient. Neither result supplies a coordinate isomorphism or quotient projection. Group order and exponent are mathematical result fields, not duplicated serialized claims. Work bounds are checked at native operation admission. Reduction accepts exact Python integers natively and canonical decimal-string coordinates through JSON, with a 32,768-digit encoding guard in both paths.

The abelian Smith operations still retain a 4,096 group-order admission cap; do not interpret canonical integer encoding as evidence that this separate scale limitation has been repaired. Other finite-group and character operations retain their own bounded coordinate contracts. See exact integer migrations and large-group admission for the required repairs.

A carrier's structural size limit is not an operation's admission limit. The QQ matrix carrier supports axes through 4,096 so it can represent the eventual hitting operation's state space. Each consumer separately admits its work, intermediate growth, and result size; this does not make arbitrary dense 4,096-by-4,096 computations admissible.

Deserialization checks structure and context, not mathematical claims such as primality, lattice rank, an order relation, or a functional graph's cycles. Operations check the properties they rely on during admission. A parsed result is still a claim from its source; serialization does not turn it into an independently verified certificate. Do not add certificates unless a consumer needs a concrete mathematical witness or checking relation.

The native surface also retains useful deterministic helpers intentionally excluded from math.find, including classical combinatorial numbers, basic formal-series transformations, Young-diagram projections, graph transforms and decomposition projections, DFA complement, and finite-metric balls. Their absence from the public operation catalog is deliberate: native availability does not create a distinct agent discovery intent.

Optional native runtimes

Python package dependencies install with Jacobian. Singular and QEPCAD are optional system runtimes for a small declared subset of operations; ordinary Python calls and imports do not require them. Use from jacobian.backends import check_backend and check_backend("singular") (or "qepcad") to inspect the current environment. Calls that need an unavailable runtime raise jacobian.backends.BackendUnavailableError, with backend, required_version, detail, and installation attributes. No runtime is automatically installed. See backend requirements for versions, operation coverage, and deployment instructions.

Numerical-semigroup relation encoding

jacobian.math.number_theory.numerical_semigroups.relation_binomials is a native-only conversion of supplied homogeneous factorization relations to signed monomial pairs. Its RelationBinomialsResult retains the ordered minimal-generator axis. Pass that canonical, strictly increasing axis explicitly; the helper does not reorder generators or reinterpret relation coordinates. The axis has at most 20 generators, each at most 500, bounding the check that the supplied axis is minimal. Empty, redundant, and incomplete relation families are valid inputs. Their conversion establishes neither generation of the toric kernel nor minimality of a presentation.

For example, given a result from number_theory.numerical_semigroup.minimal_presentation.compute, pass its minimal_generators and relations to relation_binomials. Native callers can construct individual pairs with the exported MinimalPresentationRelation; the class name does not certify that a supplied family is a presentation.

The pre-stable public operation number_theory.numerical_semigroup.presentation_binomials.compute and its request/result names were removed. It only projected relation coordinates to fixed-coefficient monomials and did not validate a presentation. There is no replacement catalog alias: use the native conversion for this projection, or retain the existing minimal-presentation operation when the presentation itself is required.