autstr package#
Subpackages#
- autstr.symbolic package
- Submodules
- autstr.symbolic.backends module
BackendBackend.relation_symbols()Backend.arity()Backend.arity_of()Backend.evaluate()Backend.constant_automaton()Backend.longer_witness_automaton()Backend.accepts()Backend.iterate()Backend.is_finite()Backend.reserved_variable_names()Backend.check_member()Backend.evaluate_member()Backend.get_structure()Backend.describe()
StructureBackendStructureBackend.relation_symbols()StructureBackend.arity()StructureBackend.arity_of()StructureBackend.evaluate()StructureBackend.constant_automaton()StructureBackend.longer_witness_automaton()StructureBackend.accepts()StructureBackend.iterate()StructureBackend.is_finite()StructureBackend.describe()
TreeStructureBackendTreeStructureBackend.relation_symbols()TreeStructureBackend.arity()TreeStructureBackend.arity_of()TreeStructureBackend.evaluate()TreeStructureBackend.constant_automaton()TreeStructureBackend.longer_witness_automaton()TreeStructureBackend.accepts()TreeStructureBackend.iterate()TreeStructureBackend.is_finite()TreeStructureBackend.describe()
ClassBackendClassBackend.ADVICEClassBackend.relation_symbols()ClassBackend.arity()ClassBackend.arity_of()ClassBackend.evaluate()ClassBackend.reserved_variable_names()ClassBackend.constant_automaton()ClassBackend.longer_witness_automaton()ClassBackend.accepts()ClassBackend.iterate()ClassBackend.is_finite()ClassBackend.check_member()ClassBackend.evaluate_member()ClassBackend.get_structure()ClassBackend.describe()
- autstr.symbolic.compiler module
- autstr.symbolic.context module
SymbolicSymbolErrorRelationSymbolFunctionSymbolSymbolicContextSymbolicContext.var()SymbolicContext.vars()SymbolicContext.get_symbolic_vars()SymbolicContext.const()SymbolicContext.term()SymbolicContext.rel()SymbolicContext.get_symbolic_rel()SymbolicContext.func()SymbolicContext.get_symbolic_func()SymbolicContext.atom()SymbolicContext.relation()SymbolicContext.relation_arity()SymbolicContext.function()SymbolicContext.symbols()SymbolicContext.describe()SymbolicContext.fresh_name()SymbolicContext.compile()SymbolicContext.evaluate()SymbolicContext.check()SymbolicContext.check_member()SymbolicContext.evaluate_member()SymbolicContext.get_structure()SymbolicContext.materialize()
Relation
- autstr.symbolic.expr module
- autstr.symbolic.signature module
- Module contents
BackendBackend.relation_symbols()Backend.arity()Backend.arity_of()Backend.evaluate()Backend.constant_automaton()Backend.longer_witness_automaton()Backend.accepts()Backend.iterate()Backend.is_finite()Backend.reserved_variable_names()Backend.check_member()Backend.evaluate_member()Backend.get_structure()Backend.describe()
ClassBackendClassBackend.ADVICEClassBackend.relation_symbols()ClassBackend.arity()ClassBackend.arity_of()ClassBackend.evaluate()ClassBackend.reserved_variable_names()ClassBackend.constant_automaton()ClassBackend.longer_witness_automaton()ClassBackend.accepts()ClassBackend.iterate()ClassBackend.is_finite()ClassBackend.check_member()ClassBackend.evaluate_member()ClassBackend.get_structure()ClassBackend.describe()
CompileErrorElementCodecFormulaFunctionCodecFunctionSymbolRelationRelationSymbolSignatureStructureBackendStructureBackend.relation_symbols()StructureBackend.arity()StructureBackend.arity_of()StructureBackend.evaluate()StructureBackend.constant_automaton()StructureBackend.longer_witness_automaton()StructureBackend.accepts()StructureBackend.iterate()StructureBackend.is_finite()StructureBackend.describe()
SymbolicContextSymbolicContext.var()SymbolicContext.vars()SymbolicContext.get_symbolic_vars()SymbolicContext.const()SymbolicContext.term()SymbolicContext.rel()SymbolicContext.get_symbolic_rel()SymbolicContext.func()SymbolicContext.get_symbolic_func()SymbolicContext.atom()SymbolicContext.relation()SymbolicContext.relation_arity()SymbolicContext.function()SymbolicContext.symbols()SymbolicContext.describe()SymbolicContext.fresh_name()SymbolicContext.compile()SymbolicContext.evaluate()SymbolicContext.check()SymbolicContext.check_member()SymbolicContext.evaluate_member()SymbolicContext.get_structure()SymbolicContext.materialize()
SymbolicSymbolErrorTermVargraph_signature()operation_signature()order_signature()relational_signature()
- autstr.utils package
- Submodules
- autstr.utils.automata_tools module
- autstr.utils.logic module
- autstr.utils.misc module
- autstr.utils.tree_automata_tools module
- Module contents
Submodules#
autstr.algebra module#
Finite boolean algebras and the localizations Z[1/p] as automatic structures.
Finite boolean algebras (FiniteBooleanAlgebras): the algebra with n atoms is (up to isomorphism) the powerset algebra of [n]. The advice is simply the unary word 1^n; elements are subsets of [n] as bitvectors of length n, and all operations are positionwise. Signature:
Leq(x,y) x <= y Meet(x,y,z) z = x AND y Join(x,y,z) z = x OR y Compl(x,y) y = NOT x Atom(x) x is an atom
The localizations Z[1/p] (Z1pLocalization): a single automatic structure, not a class. An element is written with a sign and a radix-aligned pair of integer and fractional digits, and addition is recognized by a Buechi-style carry automaton.
The finite abelian groups live in autstr.groups beside the other group classes.
- class autstr.algebra.Z1pElement(num, exp)[source]#
Bases:
objectCanonical element of Z[1/p] represented as num / p**exp.
In canonical form, either num == 0 and exp == 0, or p does not divide num.
- class autstr.algebra.Z1pLocalization(p)[source]#
Bases:
objectFactory-backed arithmetic model for the fixed localization Z[1/p].
This class provides a canonical representation and exact arithmetic for a fixed prime p. It is designed as the API layer that a dedicated automatic presentation can be attached to.
- Parameters:
p (int)
- normalize(num, exp)[source]#
Return the canonical representative of num / p**exp.
- Return type:
- Parameters:
- from_fraction(num, den)[source]#
Create an element from a reduced or unreduced fraction num/den.
The denominator must be a positive power of p.
- Return type:
- Parameters:
- add(x, y)[source]#
Return x + y in canonical form.
- Return type:
- Parameters:
x (Z1pElement)
y (Z1pElement)
- neg(x)[source]#
Return -x in canonical form.
- Return type:
- Parameters:
x (Z1pElement)
- sub(x, y)[source]#
Return x - y in canonical form.
- Return type:
- Parameters:
x (Z1pElement)
y (Z1pElement)
- equals(x, y)[source]#
Semantic equality in Z[1/p] (canonical reps compare directly).
- Return type:
- Parameters:
x (Z1pElement)
y (Z1pElement)
- property presentation: AutomaticPresentation#
The automatic presentation of (Z[1/p], +), built on first use. Signature: A(x,y,z) [z = x + y], N0(x) [x >= 0], Z(x) [x = 0], Eq(x,y).
- autstr.algebra.z1p_localization(p)[source]#
Return a fixed-prime localization factory for Z[1/p].
- Return type:
- Parameters:
p (int)
- class autstr.algebra.FiniteBooleanAlgebras(eager_equality=False)[source]#
Bases:
SymbolicClassWrapperThe uniformly automatic class of all finite boolean algebras. The member with n atoms has advice 1^n; its elements are the subsets of {0, …, n-1}, encoded as bitvectors of length exactly n.
- Parameters:
eager_equality (bool)
- GRAPH = None#
meet and join are both there, so the vocabulary stays methods and only equality is bound
- Type:
no single binary operation
- EQUALITY = 'Leq(x,y) and Leq(y,x)'#
antisymmetry of the order is equality
- check(phi, n, **subsets)[source]#
Model check against the algebra with n atoms; free variables can be assigned subsets of {0, …, n-1}.
autstr.arithmetic module#
Büchi arithmetic, over the naturals and over the integers.
\((\mathbb{N}, +, <, \mid_2)\) and \((\mathbb{Z}, +, <, \mid_2)\)
presented in base 2, where x \mid_2 y says that y is a power of two
dividing x. Adding that predicate to Presburger arithmetic is what makes the
structure Büchi arithmetic: it can talk about the binary expansion of a
number, and it remains decidable.
>>> Z = BuechiArithmeticZ().symbolic()
>>> x, y, z = Z.vars("x y z")
>>> ((x + y).eq(z) & z.lt(100)).check()
True
Both presentations are compiled from a handful of small automata the first time they are constructed; the derived relations (order, equality, negation) are first-order definitions over those.
- class autstr.arithmetic.BuechiArithmetic[source]#
Bases:
CompiledPresentationBüchi arithmetic over the natural numbers: \((\mathbb{N}, +, <, \mid_2)\) in base 2.
>>> N = BuechiArithmetic() >>> x, y = N.symbolic().vars("x y") >>> ((x + y).eq(12) & x.lt(y)).check() True
B(x, y)holds iff y is a power of two dividing x; it is spelled.divided_by_powerrather than bound to|, which on formulas already means union.- Parameters:
automata – dictionary containing the automata that recognize the domain and the relations of the structure. ‘U’ is reserved key for the universe. All other keys are assumed to recognize relations over L(U)^k. They can be addressed by their keys in first-order queries.
- PADDING = '*'#
the magnitude, least significant bit first
- Type:
base-2 encoding
- class autstr.arithmetic.BuechiArithmeticZ[source]#
Bases:
CompiledPresentationBüchi arithmetic over the integers: \((\mathbb{Z}, +, <, \mid_2)\) in base 2.
>>> Z = BuechiArithmeticZ() >>> x, y, z = Z.symbolic().vars("x y z") >>> ((x + y).eq(z) & z.lt(100)).check() True >>> (3, 4, 7) in (x + y).eq(z) True
Integers are written directly wherever a term is expected –
x + 5,x.lt(100)– and solutions come back as Python integers. Anything the operators do not cover is reachable through SymbolicContext.rel.- Parameters:
automata – dictionary containing the automata that recognize the domain and the relations of the structure. ‘U’ is reserved key for the universe. All other keys are assumed to recognize relations over L(U)^k. They can be addressed by their keys in first-order queries.
- SIGN_POSITIVE = '0'#
a sign symbol, then the magnitude, least significant bit first
- Type:
base-2 encoding
- SIGN_NEGATIVE = '1'#
- PADDING = '*'#
autstr.chain_ring module#
Linear algebra over the finite chain ring R = Z/p^d.
This is the algebraic foundation for the chain-ring extension of the bounded-rank-width group classes: letting the center of a class-2 group have exponent p^d turns the commutator cocycle into an R-bilinear form over R = Z/p^d. R is a finite chain ring – a local principal ideal ring whose ideals are the chain
R ) pR ) p^2 R ) … ) p^d R = 0,
every element being u * p^s with u a unit and s = v(.) its valuation.
Over a field every module has a basis and every submodule is a direct summand;
over R neither holds, which is what separates the routines here from ordinary
linear algebra. A minimal generating set of a row module may consist of
non-unit rows – the row (2, 0) over Z/4 generates 2R, not a direct summand –
so a two-sided factorisation cannot be read off such a set directly. The
routines therefore work with a free basis of the saturation of a module
(saturate), which keeps the outer interfaces of factor_two_sided free
and confines the valuation to its middle factor Q.
At d = 1 the ring is the field F_p, a module is its own saturation, and every
routine reduces to the familiar field case, so field and ring callers share one
saturate / factor_two_sided interface.
This module is a leaf (nothing in autstr is imported here) so that the group
constructions can build on it without an import cycle; the small mod-p echelon
helper _rref_mod_p below mirrors groups._rref_mod for locating unit
r x r minors.
- autstr.chain_ring.valuation(x, p, d)[source]#
The p-adic valuation v(x) in {0, .., d} of x in R; v(0) = d.
- autstr.chain_ring.is_unit(x, p, d)[source]#
A unit of R is a valuation-0 element (x not divisible by p).
- autstr.chain_ring.to_digits(x, p, d)[source]#
An R-element as its d base-p digits, least significant first.
- autstr.chain_ring.from_digits(digits, p)[source]#
Reassemble an R-element from base-p digits, least significant first.
- autstr.chain_ring.inv_mod_pp(B, p, d)[source]#
Inverse of a square matrix that is invertible over R = Z/p^d.
Gaussian elimination with unit pivots; a unit pivot always exists because an R-invertible matrix is invertible mod p. Raises if B is singular over R.
- autstr.chain_ring.smith_normal_form(M, p, d)[source]#
Diagonalise M over R = Z/p^d.
Returns
(exps, Winv)whereexpsare the valuations of the nonzero invariant factors (each < d) in order, andWinvis a unimodular n x n matrix whose firstlen(exps)rows are a free basis of the saturation ofrowsp(M): concretelyrowsp(M) == rowsp(diag(p^exps) @ Winv[:t]).
- autstr.chain_ring.saturate(M, p, d)[source]#
A free basis of the saturation of
rowsp(M)over R = Z/p^d.Returns
(basis, exps):basis(rho x n) are the rows of a free direct summand equal to the saturation (pure closure) of the row module, andexpsthe valuations withrowsp(M) == rowsp(diag(p^exps) @ basis). The free rankrho = len(basis) = dim_{F_p}(M / pM)is the module cut-rank – the number of invariant factors – and equals the ordinary F_p rank when d = 1.
- autstr.chain_ring.module_cut_rank(M, p, d)[source]#
The free rank rho of the saturation of
rowsp(M)(module cut-rank).
- autstr.chain_ring.right_invertible(V, p, d)[source]#
True iff the rows of V are a free basis of a direct summand of R^n, equivalently V has full row rank mod p, equivalently some r x r minor is a unit. This is the “saturated interface” hypothesis required by
factor_two_sided.
- autstr.chain_ring.right_inverse(V, p, d)[source]#
A right inverse Y (n x r) with
V @ Y == I_rover R, for a right-invertible V (r x n). Raises if V is not right-invertible.
- autstr.chain_ring.solve_left(V, B, p, d)[source]#
The general ring solve
XwithX @ V == Bover R = Z/p^d.Vis (r x m) and may be rank-deficient (e.g. a padded basis with zero rows);Bis (s x m); the resultXis (s x r). Every row of B must lie inrowsp(V)– otherwise there is no solution and a ValueError is raised. Free coordinates of the solution are set to 0.Solves
A @ Y = CwithA = V^T(m x r) andC = B^Tby Smith-style diagonalisation of A: row operations are mirrored on C and column operations are accumulated inWcso the solution maps back asY = Wc @ Z. This is the ring generalisation of the field solverautstr.groups._solve_xa_eq_b(X A = B) used by the linear layout compiler, and drives the saturated streaming update over the chain ring.
- autstr.chain_ring.factor_two_sided(X, Vbar, Wbar, p, d)[source]#
The two-sided factorisation over R = Z/p^d.
Given
X(m x m’), a saturatedVbar(r x m’) and a saturatedWbar(r’ x m) such that every row of X lies inrowsp(Vbar)and every column of X lies incolsp(Wbar^T), returnsQ(r’ x r) withX == Wbar^T @ Q @ Vbar. The valuations are absorbed into Q, not the (free) interfaces. Raises if the containment hypotheses are violated.Solved as two ring linear systems (
Wbar^T Y = XthenQ Vbar = Y), which – unlike a right-inverse – tolerates zero-padded bases whose true rank (the module cut-rank) is below r, and reduces to the field two-step at d = 1. Correctness of the result is confirmed by reconstruction.
autstr.cocycle_groups module#
Distributed-center class-2 groups: cocycle tensors on site trees.
The tensor cut-rank generalisation of the bounded rank-width group classes. A site tree is a binary tree whose nodes are generators: ‘x’ sites and central ‘z’ sites. The commutator data is a tensor T[j, i, v] over the chain ring R = Z/p^d (i < j x-positions in post-order, v a z-position), presenting the central extension with the bilinear cocycle
(b, a)(b’, a’) = (b + b’ + C(a, a’), a + a’), C(a, a’)_v = sum_{i<j} T[j, i, v] a_j a’_i .
Both coordinate blocks range over R = Z/p^d (an exponent-p^d center forces
an exponent-p^d quotient); the default d = 1 is the field case R = F_p. The
width is the module cut-rank: the minimal number of generators of each
flattening’s module, which chain_ring computes via Smith normal form.
This module provides the reference group law, the six crossing flattenings whose module ranks measure, per subtree cut, the traffic a bottom-up automaton must carry – upward digit functionals (F_y, F_x), upward pair-sums (F_m), inward claims (F_g), and the mixed exports whose products flow back into inside checks (F_py, F_px); reshaping changes rank, so the width is their maximum – and CocycleRankWidthGroups, the uniformly tree-automatic presentation of these groups at any width r and depth d. The classes CutRankTreeGroups (all z-sites on a chain above the root) and TreeExtraspecialGroups (z-sites at the leaves, laminar targets, width 1) are corner cases.
- class autstr.cocycle_groups.CocycleSites(p, shape, d=1)[source]#
Bases:
objectA site tree over the chain ring R = Z/p^d with its cocycle tensors: reference law and cut-width measures.
Elements are (b, a) with b the center coordinates (one per z-site) and a the quotient coordinates (one per x-site), both over R = Z/p^d and in ascending post-order. With
d = 1(the default) R is the field F_p and this is exactly the original field construction;d > 1is the exponent-p^d case (center of exponent p^d), where the commutator cocycle is R-bilinear and the tensor coefficients live in R. The quotient shares the exponent p^d: a class-2 group whose commutator subgroup has exponent p^d cannot have an exponent-p quotient.The width measure
cut_widthis the module cut-rank: the free rank of the saturated interface (chain_ring.module_cut_rank), which coincides with the ordinary F_p flattening rank when d = 1 but, over the ring, correctly counts valuation-carrying (p-divisible) generators that vanish under a naive mod-p reduction.- multiply(T, g, h)[source]#
The reference group law of G(T) over R = Z/p^d.
Both the center coordinates b and the quotient coordinates a range over R; the cocycle C is R-bilinear, which is what makes the law associative over the ring. (Keeping a over F_p while C is R-valued would break associativity for d > 1: a carry a_j + a’_j >= p drops a term p*T*a’’ that is nonzero mod p^d. In a class-2 group with commutator subgroup of exponent p^d the quotient necessarily has exponent p^d as well, since [x,y]^p = [x^p,y] = 1 would otherwise force G’ to have exponent p.)
- Parameters:
T (Dict)
- autstr.cocycle_groups.fixed_k_sites(p, layout_shape, form, k, d=1)[source]#
Embed a CutRankTreeGroups instance: an all-x copy of the layout with a chain of k z-sites above the root. Positions of the x-layout are preserved; the z-chain occupies positions n+1 (innermost = center coordinate 0) through n+k. With
d > 1the sites and the form labels live over the chain ring R = Z/p^d.
- autstr.cocycle_groups.laminar_sites(p, shape, d=1)[source]#
Embed a TreeExtraspecialGroups instance: every inner node w of the shape becomes a chain x-site (for x_w) over x-site (for y_w), every leaf becomes a z-site, and [x_w, y_w] hits every leaf below w. Returns the sites, the tensor, and the address map {shape address: site positions}. With
d > 1the sites live over the chain ring R = Z/p^d (the tensor keeps unit coefficients; the center gains exponent p^d).
- autstr.cocycle_groups.point_target_sites(p, shape, d=1)[source]#
A width-1 family covered by neither corner class: as laminar_sites, but every commutator [x_w, y_w] hits only the leftmost leaf below w (point targets: the center grows with the tree, the law is not laminar). With
d > 1the sites live over the chain ring R = Z/p^d.
- autstr.cocycle_groups.scattered_sites(p, m, d=1)[source]#
The lower-bound family: m private z-sites on a chain at the bottom, m commuting x-pairs above, pair t targeting exactly z_t. The cut at the z-chain has an identity claim flattening: width m (the module cut-rank over R = Z/p^d equally, for
d > 1).
- class autstr.cocycle_groups.CocycleRankWidthGroups(p, r=1, d=1)[source]#
Bases:
SymbolicClassWrapperThe uniformly tree-automatic class of distributed-center class-2 groups of tensor cut-rank <= r over R = Z/p^d. The bottom-up automaton’s state is six R^r registers (plus three scratch slots):
wy, wx : upward digit functionals (column modules of F_y, F_x) qy, hx : mixed exports for inside targets (F_py, F_px) m : representative of the exports element of E_S = rowsp(F_m) g : representative of the residuals element of the claim module Gamma_S = colsp(F_g); a residual leaving the module is a rejection
The advice is a table-driven instruction stream: each site expands into its marker letter followed by a chain of operations, each either linear – a streamed matrix or injection column, the folds and read-off coefficients of the restriction calculus – or a streamed table keyed on register values and the residual digit: the sibling pairing tables and the claim extensions and joins. Over the ring these functions are well-defined and bilinear on the interface images but need not be matrices. Merges are one-step: the binary marker consumes both children’s raw registers and the stretch above it folds directly to the parent cut; no joint-interval interfaces exist.
advice(sites, T) compiles a tensor of module cut-width <= r; every linear coefficient and every table entry is derived through chain_ring solves, each guarded by an assertion that the required membership or factorisation actually holds, so a compilation that succeeds has verified its own structural hypotheses. Interfaces are minimal generating sets of the flattening modules (Smith normal form with the p-power factors kept); at d = 1 they are ordinary bases and every table is semantically a matrix, though the letter format is uniform in d.
The explicit presentation automata are beyond the enumeration builder by construction (the instruction phase is part of the state); simulate runs the exact transition function over the convolved trees, and check_implicit / evaluate_implicit decide first-order properties through the functional atoms.
- MARKERS = {'K': ('z', 0), 'L': ('x', 0), 'M': ('x', 2), 'U': ('x', 1), 'V': ('z', 1), 'W': ('z', 2)}#
(site kind, arity)
- Type:
marker letters
- advice(sites, T)[source]#
Compile a tensor of module cut-width <= r into the instruction stream; ValueError beyond the width, AssertionError if any structural check fails.
- Return type:
- Parameters:
sites (CocycleSites)
T (Dict)
- encode(element, sites, advice)[source]#
Element tree of the advice’s shape: each site’s digit repeats along its instruction stretch.
- Return type:
- Parameters:
sites (CocycleSites)
advice (Tree)
- decode(tree, advice)[source]#
Inverse of encode: the digit at each marker; z-sites to b and x-sites to a, in ascending post-order.
- multiply(sites, T, g, h)[source]#
- Parameters:
sites (CocycleSites)
T (Dict)
- simulate(advice, tx, ty, tz)[source]#
Run the transition function over the convolved trees, with an explicit stack (instruction stretches can be long).
- property cls#
- check(phi, sites, advice, **elements)[source]#
- Return type:
- Parameters:
sites (CocycleSites)
advice (Tree)
- property implicit_cls#
- check_implicit(phi, sites, advice, **elements)[source]#
First-order model checking over the functional atoms.
- Return type:
- Parameters:
sites (CocycleSites)
advice (Tree)
- evaluate_implicit(phi, sites, advice, **elements)[source]#
The satisfying set of phi, computed implicitly; yields assignments {var: (b, a)}.
- Parameters:
sites (CocycleSites)
advice (Tree)
autstr.collapsible module#
Level 2 collapsible pushdown systems and their configuration graphs.
A collapsible pushdown stack of level 2 is a stack of stacks in which every
letter additionally carries a collapse link to some part of the stack lying
below it — a record of what the stack looked like when the letter was pushed.
The collapse operation throws the stack back to that recorded point in one
step, which is what makes these systems strictly more expressive than ordinary
higher-order pushdown systems: they are the operational counterpart of
higher-order recursion schemes.
Why the tree engine. Kartzow (2010) proved that level 2 collapsible pushdown graphs are tree-automatic, and that is the only automatic route to them: their MSO theory is undecidable, so the infinite-tree machinery that serves ordinary pushdown graphs (Muller-Schupp, Caucal) has nothing to offer here, while a finite-tree presentation gives the whole first-order theory. The tradeoff is exactly the opposite of the one for ordinary pushdown graphs, whose point is decidable MSO. It is also tight: Broadbent showed that at level 3 even first-order model checking becomes undecidable.
The encoding. A stack w_1 : w_2 : … : w_n is a list of words, and
consecutive words share long prefixes, because that is the only way clone_2
can make new ones. So the words are laid into one tree: a block is a maximal
run of consecutive words sharing their first two letters, blocks of a blockline
hang off each other as right children (1-successors), and the blockline a
block induces — the same words with their shared first letter removed — hangs
below it as a left child (0-successor). Every initial left-closed path of
the tree is then one word of the stack. Collapse links are not stored: a
level 1 link always points to the preceding letter, and a level 2 link on a
node d points to the substack of width |{d' a right child : d' ≤ d}|,
which the position of d already determines. That is what makes the encoding
a bijection between configurations and a regular set of trees, and so the whole
structure tree-automatic without a quotient.
>>> system = Level2CPS( # Hague et al.'s example
... transitions=[('0', None, 'Cl', '1', 'clone'),
... ('1', None, 'A', '0', 'push a 2'),
... ('1', None, "A'", '2', 'push a 2'),
... ('2', 'a', 'P', '2', 'pop 1'),
... ('2', 'a', 'Co', '0', 'collapse')])
>>> graph = system.configuration_graph()
>>> graph.is_deterministic() # two rules fire in state 1
False
>>> graph.check('exists x.(not exists y.(E(x,y)))') # some are stuck
True
Reachability, and the contrast with Turing machines. For a level 2
collapsible pushdown graph the reachability relation is itself tree-automatic
(Kartzow, Prop. 5.1), and it is built here — Reach is a relation of the
graph like any other, so a first-order formula may ask about runs of any
length. That is the very question autstr.turing cannot answer, since for a
configuration graph of a Turing machine reachability is the halting problem. It
is also the reason these graphs belong on the tree engine at all:
>>> 'Reach' in graph.get_relation_symbols()
True
reach_along gives the sharper version: reachability along runs whose labels a
finite automaton accepts, which is Kartzow’s Reach_L and covers the
ε-contraction of the graph as the case of any number of silent labels followed
by one other. The construction is in autstr.collapsible_reach; it is
exponential in the number of control states, so Reach is declared but not
built until a query asks for it.
Configurations are all of them, not only those reachable from an initial one — the same reading Kartzow’s result takes.
References:
A. Kartzow, Collapsible Pushdown Graphs of Level 2 are Tree-Automatic, Logical Methods in Computer Science 9(1), 2013 (STACS 2010).
M. Hague, A. S. Murawski, C.-H. L. Ong, O. Serre, Collapsible Pushdown Automata and Recursion Schemes, LICS 2008.
C. Broadbent, The Limits of Decidability for First Order Logic on CPDA Graphs, STACS 2012.
- autstr.collapsible.PAD = '*'#
the padding symbol of the tree convolutions; it must sort before every other letter of the alphabet
- autstr.collapsible.SEP = '.'#
the label of a separator node — a node that splits one block from the next, written
εin the literature because it repeats no letter
- autstr.collapsible.BOTTOM = '⊥'#
the default bottom-of-stack symbol
- class autstr.collapsible.Letter(symbol, level=1, link=None)[source]#
Bases:
objectA stack letter: a symbol plus its collapse link.
- Parameters:
- class autstr.collapsible.Stack(words)[source]#
Bases:
objectA level 2 collapsible stack: a nonempty list of nonempty words.
The operations return
Nonewhere they are undefined — popping the last letter of a word, popping the last word, or collapsing on a link that points nowhere — rather than raising, so that a configuration with no successor is an ordinary answer rather than an error.- push(symbol, level=1)[source]#
push_{symbol,level}: write a letter onto the topmost word. A level 2 link points at the stack below the topmost word, whose width is recorded; a level 1 link needs no record.
- class autstr.collapsible.Configuration(state, stack)[source]#
Bases:
objectA configuration: a control state and a stack.
- class autstr.collapsible.Operation(kind, symbol=None, level=None)[source]#
Bases:
objectA stack operation, as it appears in a transition rule.
- Parameters:
- autstr.collapsible.initial_stack(bottom='⊥')[source]#
The initial stack
⊥_2: one word holding the bottom symbol.
- autstr.collapsible.encode_stack(stack)[source]#
The tree of a stack: blocks of a blockline hang off each other to the right, the blockline a block induces hangs below it to the left.
- autstr.collapsible.decode_stack(tree)[source]#
The stack a tree encodes.
Every word ends where the descent to the left ends, and every node that is a right child starts the next word. The collapse links are read off the positions: a level 2 link points to the substack whose width is the number of right children up to and including this node in the traversal.
- autstr.collapsible.encode_configuration(configuration)[source]#
The tree of a configuration: the state labels the root and the stack hangs below it to the left.
- Return type:
- Parameters:
configuration (Configuration)
- autstr.collapsible.decode_configuration(tree)[source]#
The configuration a tree encodes.
- Return type:
- Parameters:
tree (Tree)
- class autstr.collapsible.Rule(state, symbol, label, target, operation)[source]#
Bases:
objectOne transition: in state state, with symbol on top of the stack, read label, go to target and apply operation. A symbol of None matches any topmost symbol.
- applies_to(configuration)[source]#
Whether this rule’s state and stack guard match.
- Return type:
- Parameters:
configuration (Configuration)
- class autstr.collapsible.Level2CPS(transitions, bottom='⊥', initial_state=None, states=(), symbols=())[source]#
Bases:
objectA collapsible pushdown system of level 2.
- Parameters:
transitions (
Iterable[Sequence]) – the rules, as(state, symbol, label, target, operation)tuples. The symbol may be None or'-'to match any topmost symbol; the operation is a Operation or its spelling, one of'clone','push <symbol> [1|2]','pop 1','pop 2','collapse'.bottom (
str) – the bottom-of-stack symbol, which no push may write.initial_state (
Optional[str]) – the control state of the initial configuration; the first rule’s state when omitted.states (
Iterable[str]) – further control states, if some appear in no rule.
- RESERVED = ':<>'#
characters the encoding uses to build tree labels
- step(configuration)[source]#
Every successor of a configuration, as
(label, configuration)pairs — the Python oracle the automata are checked against, and the obvious way to run the system. A collapsible pushdown system is nondeterministic, so there may be several, or none.- Return type:
- Parameters:
configuration (Configuration)
- reachable(bound=6)[source]#
The configurations reachable from the initial one in at most bound steps.
The unbounded reachability relation is tree-automatic too (Kartzow, Prop. 5.1) but is not built here, so this is a search rather than a decision procedure — useful for seeing a system run, and for checking the graph against small examples.
- Return type:
- Parameters:
bound (int)
- configuration_graph(**kwargs)[source]#
The graph of all configurations under one step of the system.
- Return type:
- static encode(configuration)[source]#
The tree encoding a configuration.
- Return type:
- Parameters:
configuration (Configuration)
- class autstr.collapsible.Level2CPG(system, max_states=None)[source]#
Bases:
objectThe configuration graph of a Level2CPS, as a tree-automatic structure.
Vertices are all configurations — every control state over every level 2 collapsible stack — and there is a
γ-labelled edge wherever one rule of the system takes one configuration to another. The domain is regular because the encoding is a bijection onto a regular set of trees; the edges are regular because every stack operation is a bounded rewrite at the end of the tree’s last path.The presentation carries
E(a step under any label),Edge…(one per label of the system),Eq, and — built on first use — the stack operationsClone,Push…,Pop1,Pop2,Collapseas relations of their own, the control-state predicatesState…, and the predicatesTop…,Level1,Level2for the topmost letter.- Parameters:
- FIXED = ('U', 'Eq', 'E', 'Clone', 'Pop1', 'Pop2', 'Collapse', 'Level1', 'Level2')#
the relation names that do not depend on the system
- reach_along(name, labels)[source]#
Install
Reach_L: reachability along runs whose labels the given autstr.collapsible_reach.LabelAutomaton accepts.Plain reachability is the case where every label word is allowed, and an ε-contraction is the case of any number of silent labels followed by one other — so this one relation covers both.
autstr.collapsible_reach module#
Reachability in a level 2 collapsible pushdown graph.
Whether one configuration can reach another is decidable here, and that is the point of the whole encoding: over a Turing machine’s configuration graph the same question is halting. Kartzow proves the relation tree-automatic; this builds it.
Reach = A ; B ; C ; D. Every run splits into four stretches — words come
off the stack, then letters, then letters go back on, then words — and all four
relations are reflexive, so the composition excludes nothing. Reachability is
therefore a first-order formula over the four rather than an automaton of its
own, and Relations.reach writes it out. regular_reach gives Kartzow’s
sharper Reach_L, along runs whose labels a finite automaton accepts, by
building that automaton into a product system; an ε-contraction is the case of
any number of silent labels followed by one other.
Each of the four is checked on four tapes: the two configurations, the summary annotation of one of them, and a guessed control state per node. The annotation is what lets a bottom-up automaton consult a computation that runs top-down — a node carries the summary of the word read from the root down to it, which is a local check — and both scaffolding tapes are quantified away afterwards.
The summaries are what all of that rests on. Four kinds of run matter (Kartzow 2013, §4), all concerned with what a stack can do before it drops below where it started:
a return takes a stack to the one below it,
a loop takes a stack back to itself, and splits into a high loop, which never drops a letter, and a low loop, which drops exactly one and writes it back,
a 1-loop takes a stack to one with the same topmost word and more words underneath.
The pivotal fact is that which of these exist depends only on the stack’s topmost word — not on anything below it. So each word w has a summary: four relations on the control states, saying between which states a return, a high loop, a low loop and a 1-loop of w exist. Kartzow’s Prop. 4.20 then says the summary of wσ is determined by the summary of w together with σ, which makes the summaries the states of a finite automaton reading a word bottom to top.
How this computes them. The paper’s own effectiveness argument routes through µ-calculus model checking on collapsible pushdown graphs, which is a decision procedure of its own. It is not needed: the decomposition lemmas the paper proves are already a closed system of rules, each saying how one kind of run is built from shorter ones:
return = high loop, then a pop, or a drop into the word below
followed by a return of it, or a pushed level 2 letter
collapsed after a 1-loop
high loop = (push, loop of the longer word, pop) and (clone, return),
closed under composition
low loop = drop a level 1 letter, loop of the word below, write it back
1-loop = loop, clone, loop, clone, …
loop = high loop, or high loop then low loop then high loop
The least fixpoint of those rules is the summary. The rules refer to the summaries of longer words, which is why this is one simultaneous fixpoint over the whole table rather than an induction on word length.
Why all four together. A level 2 letter pushed at width d carries a link to width d−1, and a clone copies the letter with its link, so collapsing it from the copy drops two words at once — overshooting the copy’s own return. Such a run is no composition of two returns, and what covers it is exactly a 1-loop: push the letter, let the stack grow underneath while the topmost word comes back, then collapse. Returns therefore need 1-loops, which need loops, which need returns.
Reference: A. Kartzow, Collapsible Pushdown Graphs of Level 2 are Tree-Automatic, LMCS 9(1), 2013, §4.
- autstr.collapsible_reach.Letter#
a symbol and a link level. The link value is always zero here — a summary is asked of a word on its own, where a link into the stack below means nothing.
- Type:
a letter of a word, as the summaries see it
- class autstr.collapsible_reach.Summary(symbol=None, level=0, ret=frozenset({}), hloop=frozenset({}), lloop=frozenset({}), oneloop=frozenset({}))[source]#
Bases:
objectWhat the runs of one word are, between which control states.
- Parameters:
level (
int) – the link level of its topmost letter.ret (
FrozenSet[Tuple[str,str]]) – pairs (q, q’) with a return — a run to the stack below.hloop (
FrozenSet[Tuple[str,str]]) – pairs with a high loop — back to the same stack, never dropping the topmost letter.lloop (
FrozenSet[Tuple[str,str]]) – pairs with a low loop — dropping the topmost letter and writing it back.oneloop (
FrozenSet[Tuple[str,str]]) – pairs with a 1-loop — back to the same topmost word, with more words underneath.
- autstr.collapsible_reach.EMPTY = Summary(symbol=None, level=0, ret=frozenset(), hloop=frozenset(), lloop=frozenset(), oneloop=frozenset())#
nothing below the bottom letter, so no run of any kind, and no letter to drop onto
- Type:
the summary of the empty word
- class autstr.collapsible_reach.Summaries(system, depth=3, limit=7)[source]#
Bases:
objectThe summaries of the words a system can build.
The rules for a word’s runs refer to the runs of longer words — pushing a letter and dropping it again is how a run stays where it is — so this is one simultaneous least fixpoint rather than an induction on length. The fixpoint is taken over the words themselves, up to a length bound, with everything longer treated as having no runs at all.
That makes the result an under-approximation: every run it reports is real, and one it misses would need a word longer than the bound to be written down. The bound is raised until the summaries stop changing, which is where the fixpoint has been reached — for the automaton of Kartzow’s Prop. 4.20 the summaries of long words repeat, so this terminates on the systems it is meant for, and converged says whether it did.
- Parameters:
- stable: set#
the words whose summary stopped growing when the bound last rose — a value can only grow, so agreeing twice means it is the truth
- drops(letter)[source]#
The moves that remove the topmost letter: a pop of level 1, and a collapse when the link is of level 1 — a level 1 link always points at the preceding letter, so collapsing on one is popping it.
- class autstr.collapsible_reach.Annotation(encoding, summaries)[source]#
Bases:
objectThe summary of the word from the root of an encoding tree to each node, written on a tape of its own.
Kartzow’s automata ask, at a node d, which returns and loops the stack that node stands for has — and that stack’s topmost word is the word read from the root down to d. A bottom-up automaton cannot read downwards, so the answer is carried on a second tape, where the check becomes local: a node’s annotation is its parent’s extended by the node’s own letter, and a separator carries its parent’s along unchanged.
The tape is scaffolding — the construction pushes it through a projection and then drops it, which is what autstr.utils.tree_automata_tools.restrict_alphabet is for.
- Parameters:
encoding – the tree alphabet of the system, from autstr.collapsible.
summaries (
Summaries) – the summaries of that system’s words.
- START = '~start'#
the annotation of the root, where no letter has been read yet
- table#
annotation letter -> letter -> annotation letter
- extend(annotation, label)[source]#
The annotation a node carries, given its parent’s and its own label — or None where no encoding tree has that shape.
- of_tree(tree, annotation=None)[source]#
The annotation of a configuration tree, as a tree of its own — the oracle the automaton is checked against.
- automaton(alphabet=None)[source]#
Two tapes: a configuration tree, and its annotation.
- Parameters:
alphabet – the alphabet to build over, when a construction has letters of its own beside these — an automaton can only be combined with others that read the same one.
- Return type:
A node’s state is what a parent has to know about it — the annotation it carries and the label it carries — since the parent is where the two can be compared.
- class autstr.collapsible_reach.Relations(system, summaries=None, extra_states=())[source]#
Bases:
objectThe relations whose composition is reachability.
Kartzow’s Remark 4.4 splits every run into four stretches: A drops whole words, B drops letters from the topmost word, C pushes letters back on, and D grows the stack again. All four are reflexive, so reachability is their composition and needs no automaton of its own —
Reach(x,y) ≡ ∃d ∃e ∃f. A(x,d) ∧ B(d,e) ∧ C(e,f) ∧ D(f,y)
which is a formula the engine evaluates once the four are installed.
Each is checked on four tapes — the two configurations, the summary annotation of the first, and a guessed control state per node — and the last two are projected away afterwards.
- Parameters:
- NONE = '@-'#
a node no run visits, and so carries no guessed states
- BURIED = '@buried'#
a node one collapse takes away along with everything around it — told apart from an ordinary dropped letter on the tape, since the two inert regions would otherwise want the same entry
- drop_moves(symbol, level)[source]#
The transitions that take the topmost letter off: a pop of level 1, and a collapse when the link is of level 1.
- dropping(annotation, label, guess)[source]#
Whether a letter may be dropped as the guess says: a high loop of the word ending at it, then one transition that takes it off.
The word is the one read from the root down to this node, which is what the annotation names — so the check needs nothing but this node’s own four tapes.
- b()[source]#
B: the topmost word loses letters, and nothing goes below what is left.The two trees agree except along a tail of the first one’s last path, which is deleted; the second may gain a single separator where its own last word now ends. Climbing that tail is the run: at each letter a high loop and one drop, the state after one drop being the state before the next.
- Return type:
- without_scaffolding(checker, annotated=0)[source]#
A four-tape checker as a relation on two configurations.
The annotation is required to be the real one — that is what the annotation automaton says — and then both it and the guess are quantified away, leaving the alphabet to be narrowed back to the one the configurations are written in.
- Parameters:
annotated (
int) – which configuration the annotation belongs to. The words a run passes through are those of the longer of the two, which is the first tape where letters come off and the second where they go on.checker (SparseTreeAutomaton)
- Return type:
- push_moves(annotation, label)[source]#
The transitions that write label on a stack whose topmost word is the one annotation names.
A pop is guarded by the letter it takes off, so a node can check it alone; a push is guarded by the letter already on top, which is the one above the letter written — and which is not this node’s label when the letter goes below a separator. The summary knows it either way: it carries the topmost symbol of the word it names.
- c()[source]#
C: the topmost word gains letters, and the run never dips below where it started.B read backwards, and the same tree shape with the two configurations exchanged: the second one’s last path is longer by a tail, and the first carries the one separator the second loses. The run is a high loop and a push at each letter gained — the loop belonging to the node that names its word, the push to the node above, which is where the letter it writes is still on top.
- Return type:
- a()[source]#
A: the stack loses whole words.Kartzow’s Lemma 4.11 decomposes such a run into pieces that are returns (F1), a 1-loop then a level 2 collapse (F2), or a 1-loop then a pop that some later F2 closes off (F3). The words dropped are the encoding’s separators, and the run drops them from the last backwards — so within a subtree the chain runs through the right child’s separators, then the left child’s, then the node itself, which is reverse traversal order.
A collapse spans several words at once, which sounds like a link reaching across the tree. It is not: a level 2 link records the number of separators up to its letter, and the stack it points at is the tree’s prefix at that separator — which is exactly where the region being deleted begins. So an F3-F2 group is a chain climbing the top path inside that region, a 1-loop and a pop at each letter and a 1-loop and the collapse at the last, closed off by the region’s own root. Every check stays local, and the group then contributes a pair of states to the outer chain just as a return does.
- Return type:
- collapses_on_links()[source]#
Whether the system can collapse on a level 2 link at all — the case that makes a need its F2 and F3 pieces rather than returns alone.
- Return type:
- d()[source]#
D: the stack grows, and the run never dips below where it started.Kartzow’s Cor. 4.10 walks the milestones of the stack being built, consecutive ones joined by a single operation and a loop, and the encoding lays them out one per node in traversal order. Measuring that walk gives three moves and no others:
to a left child push that letter to a right child clone back up a level pop, one per level, each node popping its own
with the clone that starts an ascent happening at the deepest node reached, not where the two words part. A separator carries no letter, so it pops nothing on the way out.
That makes one invariant per subtree: the run arrives at its root’s milestone in one state and leaves the subtree in another, having cloned at its deepest node and popped back up to the word its parent names. The run ends inside exactly one subtree, and there it leaves by simply stopping — which is the difference between the two kinds of walk state below.
- Return type:
- reach()[source]#
Reachability: a run of any length, between any two configurations.
Kartzow’s Remark 4.4 splits every run into four stretches — words come off, then letters, then letters go back on, then words — and each is reflexive, so no run is excluded by having to pass through all four. The relation is therefore the composition, which is a first-order formula over the four and needs no automaton of its own:
Reach(x,y) ≡ ∃u ∃v ∃w. A(x,u) ∧ B(u,v) ∧ C(v,w) ∧ D(w,y)
The quantifiers range over configurations, so the domain of the scratch structure is the encoding trees themselves.
- Return type:
- class autstr.collapsible_reach.LabelAutomaton(transitions, initial, final)[source]#
Bases:
objectA finite automaton over a system’s edge labels.
It says which sequences of labels a run may read, which is what turns plain reachability into Kartzow’s regular reachability
Reach_L.- Parameters:
- static anything(labels)[source]#
Runs reading any sequence at all — plain reachability.
- Return type:
- static contracting(labels, silent)[source]#
Runs reading any number of silent labels and then one other — the step relation of an ε-contraction.
- Return type:
- property states#
- autstr.collapsible_reach.product(system, labels)[source]#
The system whose runs are those of system whose labels labels accepts, its control state carrying both.
Kartzow builds the label automaton into the reachability automaton directly; as a product system it is the same thing said once, and it costs nothing beyond the states multiplying.
- Return type:
- Parameters:
system (Level2CPS)
labels (LabelAutomaton)
- autstr.collapsible_reach.relabel_root(encoding, source, target)[source]#
Two configurations with the same stack, whose control states are the two given ones — the trees agree everywhere but at the root.
- Return type:
- Parameters:
- autstr.collapsible_reach.regular_reach(system, labels, summaries=None)[source]#
Reach_L: reachability along runs whose labels labels accepts.The label automaton goes into a product system, whose reachability is the ordinary one; what remains is to say that the two configurations are the plain ones underneath — same stack, control state tagged with the label automaton’s initial state at one end and an accepting one at the other:
Reach_L(x,y) ≡ ∃u ∃v. Tag_{p₀}(x,u) ∧ Reach(u,v) ∧ ⋁_f Tag_f(y,v)Both kinds of configuration live in the same structure for the length of that formula, which is why the encoding carries root letters for both, and the alphabet is narrowed again once the tags are quantified away.
- Return type:
- Parameters:
system (Level2CPS)
labels (LabelAutomaton)
summaries (Summaries | None)
autstr.composition module#
Composing automatic presentations.
Automatic structures over a common signature are closed under disjoint union and under direct products, and uniformly automatic classes under union and under the direct-product closure. Every one of these constructions is a statement about letters: re-express the factors over a common alphabet, then combine them with the Boolean operations the engine already has.
The letter work is done by autstr.sparse_automata.recode, which rewrites a
transition diagram for a new alphabet in one pass over its nodes. Widening an
alphabet therefore costs nothing that scales with the alphabet – and that is
what makes products affordable, because the pair alphabet of a direct product
has |A| * |B| letters but only bits_A + bits_B variables. Letters
multiply; bits add.
- autstr.composition.prefix(dfa, letters)[source]#
Accept exactly
letters . wfor the wordswthe automaton accepts.letters gives one letter per tape, so a k-ary relation is prefixed by one symbol of its convolution. A fresh start state reads that symbol and hands over; every other first symbol is rejected. This is how a disjoint union tags which side an element came from.
- autstr.composition.disjoint_union(left, right, tags=('<l>', '<r>'))[source]#
The disjoint union of two automatic structures over one signature.
An element of the left factor is encoded as
tags[0] . wand one of the right factor astags[1] . w, so the domains cannot collide and no tuple mixes the two sides – which is exactly right, since the relations of a disjoint union never cross it.Note that a disjoint union is a relational construction: the disjoint union of two groups is not a group, because the multiplication becomes partial.
- Return type:
- Parameters:
left (AutomaticPresentation)
right (AutomaticPresentation)
tags (Sequence)
- autstr.composition.direct_product(left, right, kind='sync')[source]#
The direct product of two automatic structures over one signature.
An element is a pair, encoded over the pair alphabet: position i carries one letter of each component, the shorter one padded. Both products are then Boolean combinations of the factors embedded into that alphabet:
sync R((a,b), (a',b')) iff R_A(a,a') and R_B(b,b') async R((a,b), (a',b')) iff (R_A(a,a') and b = b') or (R_B(b,b') and a = a')
The synchronous product moves both coordinates at once; the asynchronous one moves exactly one and holds the other fixed. For a k-ary relation the equality side asks that all k tapes agree on the untouched half.
- Parameters:
kind (
str) –'sync'or'async'.left (AutomaticPresentation)
right (AutomaticPresentation)
- Return type:
- autstr.composition.class_union(left, right, tags=('<l>', '<r>'), skip='<#>')[source]#
The union of two uniformly automatic classes over one signature.
A member of the result is a member of either class. What is tagged is the advice: an advice of the left class becomes
tags[0] . alphaand one of the right classtags[1] . alpha, so the two advice languages are disjoint and each member is instantiated by exactly one of the factors.Every tape of a convolution must be prefixed by one symbol, so the element tapes get the placeholder letter skip: an element
wbecomesskip . w. Use tagged_advice and tagged_element to build them.- Return type:
- Parameters:
left (UniformlyAutomaticClass)
right (UniformlyAutomaticClass)
tags (Sequence)
skip (str)
- autstr.composition.tagged_advice(advice, tag='<l>')[source]#
The advice of a factor, as an advice of the union.
- autstr.composition.tagged_element(element, skip='<#>')[source]#
An element of a factor, as an element of the union.
- autstr.composition.direct_product_closure(uniform, separator='<|>')[source]#
The class of all finite direct products of the class’s members.
The advice
alpha_1 # ... # alpha_npresents the product of the members thatalpha_1, ..., alpha_npresent, and an element of that product is the tuplew_1 # ... # w_nof its components. Because an element of a member is never longer than its advice, the blocks line up positionally across every tape of a convolution, and a relation of the product is just the original relation holding in every block.So each automaton – the domain and every relation alike – is the same block-reset wrapper of the original: read a block, and at a separator demand that the block was accepted and start the next one. The result has one more state than the original, where an encoding that interleaved the components would need one copy of the automaton per component.
This is the construction that takes the cyclic groups to the finite abelian groups (compare autstr.algebra.FiniteAbelianGroups, whose advice is a ‘#’-separated list of orders). Composed with class_union it mixes two families:
direct_product_closure(class_union(C, D))is the class of all finite products of members of either.Requires every element to be no longer than its advice, which holds exactly when the members are finite; the construction cannot check it.
- Return type:
- Parameters:
uniform (UniformlyAutomaticClass)
separator (str)
autstr.graphs module#
Graphs of bounded tree-depth and bounded pathwidth as uniformly automatic classes.
Both classes present graphs over sets of vertices (as in MSO0), so first-order logic over the presentation is monadic second-order logic over the graph. The shared signature is
Sing(x) x is a singleton Subset(x,y) x is a subset of y E(x,y) x = {u}, y = {v} and u,v are adjacent
Tree-depth <= d (TreeDepthClass): the advice spells out a DFS traversal of an elimination forest of height <= d, one letter per vertex encoding (depth, adjacency profile to its ancestors).
Pathwidth <= w (PathWidthClass): the advice spells out a linear layout, one letter per vertex encoding (register in {0..w}, adjacency profile to the registers of its earlier neighbors). Introducing a vertex at register r replaces the previous occupant of r; edges may only reach current occupants, which is exactly the interval structure of a path decomposition of width w.
TreeDepthGraph / PathWidthGraph encapsulate the string representations of single graphs and convert from/to networkx.
- class autstr.graphs.StringGraph(letters, nodes=None)[source]#
Bases:
objectBase class for graphs encoded as strings of per-vertex letters.
- class autstr.graphs.TreeDepthGraph(letters, nodes=None)[source]#
Bases:
StringGraphA graph of bounded tree-depth in its string representation: the DFS traversal of an elimination forest, one (depth, profile) letter per vertex. profile[t-1] == 1 means the vertex is adjacent to its unique ancestor at depth t.
- classmethod from_networkx(graph, forest=None, exact_below=13)[source]#
Build the string representation from a networkx graph.
- Parameters:
graph – undirected networkx graph
forest (
Optional[Dict]) – optional elimination forest as a dict node -> parent (roots map to None or are absent). Every edge of the graph must connect a vertex to one of its forest ancestors.exact_below (
int) – for graphs with fewer vertices, an optimal elimination forest is computed by exhaustive search; larger graphs fall back to a DFS forest (always valid, possibly deeper than the tree-depth).
- Return type:
- class autstr.graphs.PathWidthGraph(letters, nodes=None)[source]#
Bases:
StringGraphA graph of bounded pathwidth in its string representation: a linear layout, one (register, profile) letter per vertex. Introducing a vertex at register r replaces the previous occupant of r; profile lists the registers of the vertex’s earlier neighbors (their current occupants).
- classmethod from_networkx(graph, order=None, exact_below=13)[source]#
Build the string representation from a networkx graph.
- Parameters:
graph – undirected networkx graph
order (
Optional[Sequence]) – optional vertex ordering (linear layout). If omitted, a minimum vertex-separation ordering is computed exhaustively for graphs with fewer than exact_below vertices; larger graphs fall back to a BFS ordering (valid, possibly wider than the pathwidth).exact_below (int)
- Return type:
- class autstr.graphs.TreeDepthClass(d)[source]#
Bases:
_SetGraphClassThe uniformly automatic class of graphs of tree-depth <= d, presented over set-valued elements (MSO0 style).
- Parameters:
d (int)
autstr.groups module#
Uniformly automatic classes of finite groups.
Finite abelian groups (FiniteAbelianGroups): every finite abelian group is a direct sum of cyclic groups, so the advice is the ‘#’-separated list of their orders in LSB-first binary and addition is blockwise. This is the direct-product closure of the cyclic groups, made explicit – compare autstr.composition.direct_product_closure.
Groups with a cyclic subgroup of index <= 2 (IndexTwoCyclicGroups): every such group is <r, s | r^n, s^2 = r^w, s r s^-1 = r^u> with u^2 = 1 (mod n), and the classification yields six families over the cyclic part Z_n:
abelian Z_2 x Z_n u = 1 w = 0 cyclic C_2n u = 1 w = 1 dihedral D_n u = -1 w = 0 dicyclic Dic (Q_2n) u = -1 w = n/2 (n even) semidihedral SD_2n u = n/2 - 1 w = 0 (n = 2^k >= 4) modular M_2n u = n/2 + 1 w = 0 (n = 2^k >= 4)
The advice is one family symbol followed by the LSB-first binary digits of n; an element r^a s^e is encoded as the twist bit e followed by the digits of a. Multiplication obeys
(r^a s^e)(r^b s^f) = r^{a + u^e b + [e and f] w} s^{e xor f},
and every ingredient is regular: modular addition is the usual carry automaton, conjugation is the identity, negation, or +/-x + (x mod 2)*(n/2) (the n/2-shift is advice-recognizable), and w is an advice-definable constant. The relation M(x,y,z) is defined from these primitives by a first-order formula — the uniform analog of the Büchi-arithmetic bootstrap.
Extraspecial p-groups (ExtraspecialGroups(p), p a fixed prime): the Heisenberg-type group of order p^(1+2n) has elements (c, a, b) with c in Z_p, a, b in Z_p^n and multiplication
(c, a, b)(c’, a’, b’) = (c + c’ + <a, b’>, a + a’, b + b’).
For fixed p the bilinear correction <a, b’> pairs digits positionwise, so it is a running sum mod p in the automaton state — nilpotency class 2 with growing rank is uniformly automatic, in contrast to growing modulus (Heisenberg over Z_n interprets modular multiplication and is not).
Class-2 groups of bounded linear cut-rank (CutRankGroups(p, k, r)): the common generalization. A member is a central extension of Z_p^n by Z_p^k given by commutator labels [x_j, x_i] = y^B[j,i]; the advice spells out, cut by cut, a rank-<=r factorization of the crossing block of B, and the automaton carries r linear functionals of the digits read so far instead of the digits themselves. Bounded pathwidth, bounded vertex cover, the extraspecial matching and the complete graph (nothing commutes, pathwidth n-1, cut-rank 1!) are all special layouts.
- class autstr.groups.IndexTwoCyclicGroups[source]#
Bases:
SymbolicClassWrapperThe uniformly automatic class of finite groups with a cyclic subgroup of index <= 2, in one advice format. Signature: M(x,y,z) [z = x*y], T(x) [x is a twisted element r^a s], Eq(x,y), plus the primitives the bootstrap is built from (CAdd, Conj, IsW, family predicates, …).
- FAMILY_SYMBOLS = {'abelian': 'fA', 'cyclic': 'fC', 'dicyclic': 'fQ', 'dihedral': 'fD', 'modular': 'fM', 'semidihedral': 'fS'}#
- check(phi, advice, **elements)[source]#
Model check against one group; free variables can be assigned elements as (twist, exponent) pairs.
- check_implicit(phi, advice, **elements)[source]#
Like check, evaluated implicitly (no query automaton).
- class autstr.groups.ExtraspecialGroups(p)[source]#
Bases:
SymbolicClassWrapperFor a fixed prime p, the uniformly automatic class of Heisenberg-type groups of order p^(1+2n): elements (c, a, b) with c in Z_p and a, b in Z_p^n, multiplied as (c,a,b)(c’,a’,b’) = (c + c’ + <a,b’>, a + a’, b + b’). Advice: the unary word 1^(n+1). Signature: M(x,y,z), Cen(x) [x is central], Eq(x,y).
- Parameters:
p (int)
- check(phi, n, **elements)[source]#
Model check against the rank-n group; free variables can be assigned elements as (c, a_vector, b_vector) triples.
- class autstr.groups.FiniteAbelianGroups(eager_equality=False)[source]#
Bases:
SymbolicClassWrapperThe uniformly automatic class of all finite abelian groups, presented by their cyclic decompositions: the group Z_{n_1} ⊕ … ⊕ Z_{n_k} has advice bin(n_1)# … bin(n_k)# (LSB-first binary per block).
- Parameters:
eager_equality (bool)
- GRAPH = 'A'#
addition, not multiplication – these members are abelian by construction, and the class presents the operation as A(x, y, z).
- OPERATOR = '+'#
the Python operator it binds to
- EQUALITY = 'exists z0.(A(z0,z0,z0) and A(x,z0,y))'#
the identity is the unique idempotent, and x + 0 = y exactly when x = y. The witness may not be called e0: nltk reads e-names as event variables.
- check(phi, orders, **elements)[source]#
Model check against Z_{n_1} ⊕ … ⊕ Z_{n_k}; free variables can be assigned group elements (tuples with one value per factor, or a single int for a cyclic group).
- check_implicit(phi, orders, **elements)[source]#
Like check, evaluated implicitly (no query automaton).
- class autstr.groups.CutRankGroups(p, k=1, r=1, d=1, factored=None)[source]#
Bases:
SymbolicClassWrapperFor a fixed prime p, center dimension k, width r and ring depth d, the uniformly automatic class of class-2 groups over R = Z/p^d whose commutation form admits a linear layout of module cut-rank <= r (bounded linear rank-width over R). With d = 1 (the default) R is the field F_p and this is the original construction; d > 1 is the exponent-p^d (“Idea 2”) case, where the form is R-valued and the width is the module cut-rank of the crossing blocks (the streaming lifts unconditionally, carrying row-module generator interfaces).
A member is a form with labels B[j,i] in R^k for i < j, presenting x_j x_i = x_i x_j y^B[j,i]. Elements are (b, a) with b in R^k, a in R^n and
(b, a)(b’, a’) = (b + b’ + C(a, a’), a + a’), C(a, a’) = sum_{i<j} B[j,i] a_j a’_i,
a 2-cocycle because C is bilinear. The multiplication automaton never needs the digits it has read — only what the future consumes from them, which is the crossing block of B at the current cut. If every crossing block factors through rank r, the automaton can carry the r linear functionals w = V a’ instead of the digits. The advice letter at position t spells the factorization out — three matrices over Z_p:
T (r x r): basis change between cuts, w <- T w + v a’_t, v (r): coefficient of the new digit, R (k x r): read-off, sum_{i<t} B[t,i] a’_i = R w,
so the state is (deficit in Z_p^k, w in Z_p^r): p^(k+r) states however long the word. Every well-shaped advice presents some group in the class (the streamed cocycle is bilinear by construction), and advice compiles a form into letters, failing precisely when a cut exceeds rank r; linear_cut_rank measures the width a form needs.
Special layouts: a perfect matching is extraspecial (matching_form, cut-rank 1, compare ExtraspecialGroups); the complete graph — nothing commutes, pathwidth n-1 — also has cut-rank 1, its crossing blocks are all-ones (clique_form); a vertex cover of size c needs rank <= c. Signature: M(x,y,z), Eq(x,y); the center is first-order definable, Cen(x) := all y,z,w (M(x,y,z) and M(y,x,w) -> z = w).
factored=None (the default) enumerates one advice letter per (T, v, R) triple when that alphabet fits under 20000 letters (the original encoding, byte-identical) and otherwise switches to factored letters: each position becomes a marker ‘n’ followed by one letter per ring entry (T row-major, then v, then R row-major), with the element digit repeated along the stretch. The factored alphabet has q+1 advice letters however large r, k and d are – this is what makes width r >= 2 over the ring representable.
- MARKER = 'n'#
marker letter opening each position’s entry stretch in factored mode
- property cls: UniformlyAutomaticClass#
the multiplication automaton is a 4-tape product over the full advice alphabet, so its construction is O(sigma^4) and only feasible for a small ring alphabet (essentially Z/4, width 1). The reference law, advice compiler, width measure and simulate do not need it and stay cheap for any q.
- Type:
The uniformly automatic presentation, built lazily
- linear_cut_rank(n, form)[source]#
The width the given layout of the form needs: the maximal module cut-rank over R = Z/p^d of its crossing blocks (the free rank of the saturated interface; the ordinary F_p rank when d = 1).
- advice(n, form)[source]#
Compile a form — {(j, i): label in Z_p^k, i < j} — into advice. Maintains a row basis V of each crossing block’s row space and expresses the next basis and the current read-off row over it; raises if some cut exceeds rank r.
- clique_form(n, label=None)[source]#
Nothing commutes: [x_j, x_i] = y^label for every pair. Pathwidth n-1, but every crossing block is all-ones — cut-rank 1.
- matching_form(n)[source]#
Disjoint commutator pairs hitting the first center coordinate: the extraspecial layout, cut-rank 1.
- multiply(n, form, g, h)[source]#
Reference implementation of the group law given by the form, over R = Z/p^d (both quotient and center coordinates range over R).
- simulate(advice, gx, gy, gz)[source]#
Run the multiplication automaton directly over the tapes: True iff the advice accepts gx * gy = gz. Mirrors the M transition without building the 4-tape product DFA, so the streamed compile can be checked against the reference law for any ring alphabet (the full automaton is only buildable for small q). gx, gy, gz are (b, a) elements.
- encode(element, n)[source]#
Encode (b, a) with b in R^k, a in R^n, R = Z/p^d. In factored mode each position’s digit is repeated along its entry stretch.
- check(phi, advice, **elements)[source]#
Model check against the member presented by the advice; free variables can be assigned elements as (b, a) tuples.
- property implicit_cls#
The fully implicit presentation of this class (functional atoms only, nothing compiled): an autstr.implicit.ImplicitClass over raw element words. check_implicit/evaluate_implicit add the (b, a)-tuple encoding on top of it.
- check_implicit(phi, advice, **elements)[source]#
Like check, but evaluated implicitly (no query or base automaton) – the only viable model checker for the large-alphabet ring members whose cls cannot be built. See autstr.implicit.
- evaluate_implicit(phi, advice, **elements)[source]#
The satisfying set of phi on the member presented by the advice, computed implicitly: unassigned free variables stay open and are solved for. Yields assignments {var: (b, a)}; len is the exact solution count without enumeration. Works for members whose automata cannot be built.
- class autstr.groups.InfiniteExtraspecialGroup(p=3)[source]#
Bases:
objectThe infinite extraspecial p-group: finitely supported vectors over F_p with a one-digit centre.
An element is a triple (a, b, c) with a, b finitely supported sequences over F_p and c in F_p, multiplied by
(a, b, c) * (a’, b’, c’) = (a + a’, b + b’, c + c’ + <a, b’>),
the central extension of F_p^(w) + F_p^(w) by F_p along the standard symplectic form. It is non-abelian – the commutator of (a, b, c) and (a’, b’, c’) is <a, b’> - <a’, b> – and unlike ExtraspecialGroups it is a single infinite structure rather than a class of finite ones, so its elements have an advice-free encoding and can be written as constants.
Encoding: the first letter carries the centre, and letter i + 1 carries the pair (a_i, b_i); trailing (0, 0) pairs are trimmed, so encodings are unique and equality is the diagonal.
- Parameters:
p (int)
- PAD = '*'#
autstr.implicit module#
Implicit first-order evaluation, without compiling a query automaton.
The explicit path (presentations.AutomaticPresentation._build_automaton) turns a formula into a product automaton – intersection for AND, union for OR, complement for NOT, projection for EXISTS – and materialises it. That product blows up, and for the heavy group classes even the base multiplication automaton is infeasible to build (an O(sigma^4) product over the astronomical advice alphabet).
This module evaluates a formula implicitly instead: it keeps composite states and steps the base automata on the fly. Boolean connectives take the product of states; EXISTS is handled by an on-the-fly powerset (subset construction); NOT flips the acceptance test (exact, because every composite stays deterministic and total). The base automata are given by a small functional interface, so a class whose presentation cannot even be built is still checkable – this is the simulate methods of the group classes generalised to arbitrary first-order formulas.
Everything here works over a fixed input: an advice word/tree plus concrete assignments for some free variables; the remaining free variables are existentially closed and eliminated by the powerset construction. Because the advice is fixed input and quantifiers range over element tapes whose per-symbol alphabet is tiny, the subset construction never touches the huge advice alphabet; the cost is set by quantifier alternation, not alphabet size.
- Two shapes, one combinator pattern:
ImplicitDFA – string automata, run left-to-right.
ImplicitTA – bottom-up tree automata, run post-order.
Beyond the boolean check_* entry points, the module offers the satisfying-set primitive: StringSolutionSet/TreeSolutionSet compute, for a formula with open free variables over a fixed advice, the exact number of satisfying assignments (a count DP over the reachable composite states — no enumeration) and lazily enumerate them. ImplicitClass/ImplicitTreeClass package atoms + element alphabet as a first-class fully implicit presentation: a uniformly automatic class given purely functionally, offering model checking and satisfying-set evaluation and never compiling anything.
- class autstr.implicit.ImplicitDFA(tapes, initial, step, accepting)[source]#
Bases:
objectA deterministic, total automaton over a set of named tapes, given functionally. A symbol is a dict {tape: value}; the automaton reads only its own tapes from it.
- Parameters:
- autstr.implicit.dfa_atom(dfa, tapes)[source]#
Wrap an explicit SparseDFA as an implicit atom. tapes names the DFA’s tapes in the DFA’s own symbol order.
- Return type:
- Parameters:
- autstr.implicit.dfa_product(a, b, accept)[source]#
- Return type:
- Parameters:
a (ImplicitDFA)
b (ImplicitDFA)
- autstr.implicit.dfa_complement(a)[source]#
- Return type:
- Parameters:
a (ImplicitDFA)
- autstr.implicit.dfa_project(a, var, alphabet)[source]#
EXISTS var: subset construction. State is a frozenset of a-states; each step guesses var’s symbol over alphabet from every state in the set.
- Return type:
- Parameters:
a (ImplicitDFA)
var (str)
alphabet (Sequence)
- autstr.implicit.run_dfa(a, inputs, length)[source]#
Run over length synchronised positions; inputs gives a word for each of the automaton’s remaining tapes.
- class autstr.implicit.ImplicitTA(tapes, step, accepting)[source]#
Bases:
objectA deterministic bottom-up tree automaton over named tapes. step takes the node’s symbol dict and the child states (None for a missing child).
- Parameters:
- autstr.implicit.ta_atom(sta, tapes)[source]#
Wrap an explicit SparseTreeAutomaton as an implicit atom.
- Return type:
- Parameters:
- autstr.implicit.ta_product(a, b, accept)[source]#
- Return type:
- Parameters:
a (ImplicitTA)
b (ImplicitTA)
- autstr.implicit.ta_complement(a)[source]#
- Return type:
- Parameters:
a (ImplicitTA)
- autstr.implicit.ta_project(a, var, alphabet)[source]#
EXISTS var: bottom-up subset construction. State is a frozenset of a-states; a missing child contributes the singleton {None}.
- Return type:
- Parameters:
a (ImplicitTA)
var (str)
alphabet (Sequence)
- autstr.implicit.run_ta(a, inputs)[source]#
Run over labelled trees (one per tape, all of the same shape).
- Return type:
- Parameters:
a (ImplicitTA)
- class autstr.implicit.StringSolutionSet(a, inputs, length, solve_vars, alphabet_of)[source]#
Bases:
objectThe satisfying assignments for the open variables of an implicit string automaton over a fixed input.
One forward pass stores, per position, the reachable composite states with their outgoing edges (one per guessed symbol tuple); one backward pass counts the accepted suffixes per state. len is then the exact number of satisfying assignments without any enumeration, truthiness is non-emptiness, and iteration lazily yields {var: word} dicts (each word a list of element symbols, one per position). Deterministic composite states make the count exact: every assignment has exactly one run.
- class autstr.implicit.TreeSolutionSet(a, inputs, solve_vars, alphabet_of)[source]#
Bases:
objectThe satisfying assignments for the open variables of an implicit bottom-up tree automaton over a fixed input; assignments are labelled trees of the input’s exact shape. A bottom-up pass counts, per node and reachable state, the subtree labelings that reach it; len sums the accepting root states, iteration re-derives the labelings top-down.
- class autstr.implicit.MappedSolutions(base, mapper)[source]#
Bases:
objectA solution set with a mapper applied to every assignment value (e.g. decoding element words/trees back to group elements).
- autstr.implicit.check_string(phi, atoms, inputs, length, alphabet_of)[source]#
Implicitly decide a (relativized) formula over string automata.
- Parameters:
atoms (
Dict) – relation name -> an atom builderargs -> ImplicitDFA(or a SparseDFA, wrapped via dfa_atom with the formula’s argument order).inputs (
Dict[str,Sequence]) – a word for every tape that survives to the top (advice and assigned variables).alphabet_of – var name -> iterable of element symbols to guess for it.
length (int)
- Return type:
- autstr.implicit.check_tree(phi, atoms, inputs, alphabet_of)[source]#
Implicitly decide a (relativized) formula over tree automata.
- autstr.implicit.relativized_query(phi, assignments, relativize, variable_names, close_free=True)[source]#
Relativize to a fresh advice variable and add the Adv/Dom guards – the same query the explicit evaluate/check build, but returned as an expression for implicit evaluation. With close_free (the model-checking contract) unassigned free variables are existentially closed; without it (the satisfying-set contract) they stay open and are returned as the solve variables.
- Parameters:
relativize – UniformlyAutomaticClass._relativize (static).
variable_names – UniformlyAutomaticClass._variable_names (static).
- Returns:
(query expression, advice variable name, assigned variable names, solve variable names).
- autstr.implicit.check_class_string(phi, advice, assignments, atoms, element_alphabet, relativize, variable_names)[source]#
Implicit model check over string automata: relativize then run. atoms maps each wrapped relation (Dom, Adv, and the class relations) to a SparseDFA or a functional builder args -> ImplicitDFA.
- Return type:
- autstr.implicit.check_class_tree(phi, advice, assignments, atoms, element_alphabet, relativize, variable_names)[source]#
Implicit model check over tree automata: relativize then run bottom-up. atoms maps each wrapped relation to a SparseTreeAutomaton or a functional builder args -> ImplicitTA.
- Return type:
- autstr.implicit.evaluate_class_string(phi, advice, assignments, atoms, element_alphabet, relativize, variable_names)[source]#
The satisfying set of a formula over the member presented by the advice, computed implicitly: unassigned free variables stay open and are solved for over the fixed advice. Returns a StringSolutionSet of {var: word} assignments (exact len without enumeration).
- Return type:
- autstr.implicit.evaluate_class_tree(phi, advice, assignments, atoms, element_alphabet, relativize, variable_names)[source]#
Tree analog of evaluate_class_string: the satisfying assignments are labelled trees of the advice’s shape.
- Return type:
- class autstr.implicit.ImplicitClass(atoms, element_alphabet)[source]#
Bases:
objectA uniformly automatic class given purely functionally – the fully implicit presentation. atoms maps every relation name (including the wrapped ‘Dom’ and ‘Adv’) to a builder
args -> ImplicitDFA(or an explicit SparseDFA to wrap); element_alphabet lists the per-position element symbols quantifiers guess from. Nothing is ever compiled: the class offers implicit model checking and satisfying-set evaluation only, so it reaches members whose presentation automata cannot be built.- check(phi, advice, **assignments)[source]#
Model check against the member presented by the advice word; unassigned free variables are existentially closed.
- Return type:
- class autstr.implicit.ImplicitTreeClass(atoms, element_alphabet)[source]#
Bases:
objectTree analog of ImplicitClass: atoms are
args -> ImplicitTAbuilders (or explicit SparseTreeAutomaton objects), members are advice trees.- check(phi, advice, **assignments)[source]#
Model check against the member presented by the advice tree; unassigned free variables are existentially closed.
- Return type:
autstr.infinite_graphs module#
Infinite graphs as automatic structures.
A thin, engine-agnostic wrapper over an automatic or tree-automatic
presentation whose signature has a domain U and a binary edge relation. It
adds the graph vocabulary on top of the symbolic layer — x.adj(y) for
adjacency, plus .eq — so every infinite-graph factory (ordinals, integer
grids, the level-2 collapsible pushdown graphs, …) plugs into one surface,
exactly as InfiniteExtraspecialGroup shares a presentation.
The wrapper decides nothing itself: it forwards to the presentation, which is where FO(∃^∞) is decided by synchronous projection. It works over either the string or the tree engine, since both presentations expose the same relational interface (symbolic, check, evaluate, relation, get_relation_symbols).
- class autstr.infinite_graphs.InfiniteGraph(presentation, edge=None, directed=False, codec=None)[source]#
Bases:
objectA graph presented by automata: a domain and a binary edge relation.
- Parameters:
presentation – an AutomaticPresentation or TreeAutomaticPresentation carrying the domain
Uand the edge relation.edge (
Optional[str]) – the binary relation read as adjacency (default'E').directed (
bool) – whether edges are directed. Undirected is the default; the edge automaton is expected to be symmetric, which is_symmetric checks.codec – optional element codec, so vertices can be written as Python constants and solutions decoded.
- EDGE = 'E'#
the default name of the edge relation
- default_signature()[source]#
The signature symbolic() uses when none is given:
.adjbound to the edge relation, and.eqwhen the graph declares equality.
- symbolic(signature=None)[source]#
A symbolic interface to the graph. Build first-order formulas with
x.adj(y)and the usual connectives / quantifiers; see autstr.symbolic.
- is_symmetric()[source]#
Whether the edge relation is symmetric — decidable here, since it is a first-order question over an automatic structure. An undirected graph must satisfy it.
- Return type:
- class autstr.infinite_graphs.IntegerGrid(n=2)[source]#
Bases:
objectThe n-dimensional integer grid — the Cayley graph of ℤⁿ with the standard generators.
Vertices are points of ℤⁿ; two are adjacent iff they differ by ±1 in exactly one coordinate. That is precisely the asynchronous product of n copies of the two-way integer path (move one coordinate, hold the rest), so the grid is built by folding autstr.composition.direct_product over n integer paths rather than by authoring an automaton.
FO is decidable — it is an automatic structure. MSO is not: the grid interprets the halting problem, the tidiest illustration that FO and MSO are different questions.
- Parameters:
n (
int) – the dimension (n ≥ 1).IntegerGrid(1)is the two-way path.
- class autstr.infinite_graphs.RegularTree(k=2)[source]#
Bases:
objectThe infinite k-ary tree \(T_k\), with its successors and the prefix order.
Vertices are the words over
{0, …, k-1}: the root is the empty word, andx·iis the i-th child ofx. So the tree is its own encoding, and the relations are small automata read straight off the word operations — appending one letter, and being a prefix — rather than derived from another structure.The presentation carries
S0 … S{k-1}(Si(x, y)iffy = x·i), their unionChild,Prefix(the reflexive prefix order, i.e. ancestor-or-self),Eq, and the undirected child edgeE, under which the tree is a graph: the root has degree k and every other vertex degree k+1.\(T_k\) is the Cayley graph of the free monoid on k generators, and the 2k-regular version is the Cayley graph of the free group — the same object the pushdown graphs are unravelled from.
- Parameters:
k (
int) – the branching degree (k ≥ 1).RegularTree(1)is a ray.
- encode(vertex)[source]#
The encoding of a vertex, written as a sequence of child indices —
(0, 1, 1), or the string'011'— with the empty sequence for the root.- Return type:
autstr.interpretations module#
First-order interpretations of automatic structures.
Automatic structures are closed under first-order interpretations, and AutStr can compute the interpreted presentation: presentation.evaluate(φ) already returns the automaton of an FO formula’s satisfying assignments, so an interpretation is orchestration, not a new engine. It is the FO counterpart of the set / MSO interpretations that build the Caucal hierarchy.
An element of the interpreted structure is a k-tuple of source elements satisfying the domain formula; each relation is defined by an FO formula over the source’s signature, with its free variables grouped k-per-argument. The formula automata are folded — every k consecutive coordinate tapes become one element tape over the product alphabet — so the result is a standard presentation whose elements are k-tuples.
For dimension == 1 (the default) this is just a definable reduct/expansion
with a restricted domain: elements keep the source’s encoding, and the
interpreted automata are the canonical minimal DFAs of the formulas — bit-for-
bit what a hand-built presentation would produce. For k > 1 elements are
encoded as k-tape convolutions; measurement shows this adds no states (the
engine minimizes away a sparse domain’s redundancy) but a bounded ×k
symbol-width overhead, intrinsic to representing tuples.
Quotient interpretations (elements as classes of a definable equivalence) are
supported at every dimension and on either engine: pass quotient=ε, and the
universe is restricted to one representative per class.
Both engines. The source may be an AutomaticPresentation or a TreeAutomaticPresentation, and the result is a presentation of the same kind; the orchestration is identical because both engines encode a convolution letter the same way. Over trees an element of a k-dimensional interpretation is a k-tuple of trees, which is one tree over k-tuples — the same fold, since the tree convolution already overlays the shapes.
Only the choice of representative differs. Over words it is the shortlex-least
element of the class, and shortlex is a well-order, so that element exists.
Over trees no automatic order is well-founded — growing a tree at the position
where two differ makes it smaller — so a class need have no least element at
all, and the representative is instead the least description: a member that
reaches only so far past the positions the whole class shares. That is Kuske
and Weidner’s construction; _tree_representatives gives it in full, including
why the expensive half of their proof is not needed here. It is the one part of
this module that can blow up — provably so — and it is worth passing
max_states to the source presentation before asking for a large one.
- autstr.interpretations.RelationSpec#
a relation is a formula, or a
(formula, coordinate-order)pair; the coordinate order lists the free variables element-major (each element’s k coordinates together), and may be omitted when the sorted order already isalias of
str|Expression|Tuple[str|Expression,Sequence[str]]
- autstr.interpretations.interpret(source, domain, relations, dimension=1, quotient=None)[source]#
The first-order interpretation of a structure in source.
- Parameters:
source – the structure to interpret in — an AutomaticPresentation or a TreeAutomaticPresentation. The result is a presentation of the same kind.
domain (
Union[str,Expression,Tuple[Union[str,Expression],Sequence[str]]]) – the domain formula δ(x̄) withdimensionfree variables — the coordinates of one element; the new universe is the tuples satisfying it. A(formula, coordinate-order)pair fixes which free variable is which coordinate when the sorted order will not do.relations (
Dict[str,Union[str,Expression,Tuple[Union[str,Expression],Sequence[str]]]]) –{name: spec}wherespecis a formula or a(formula, coordinate-order)pair. A relation of arity r hasdimension · rfree variables; the coordinate order lists them element-major, so folding groups each argument’s coordinates.dimension (
int) – k — elements are k-tuples of source elements.quotient (
Union[str,Expression,Tuple[Union[str,Expression],Sequence[str]],None]) – an equivalence formula ε(x̄, ȳ) (a binary relation over elements, so2 · dimensionfree variables). When given, elements are its equivalence classes: the universe is restricted to one representative of each class, and the relations are read on those representatives. The caller must ensure ε really is an equivalence and that every relation is ε-invariant. Over words the representative is the shortlex-least member of the class; over trees it is the least description, which is the same idea made to work without a well-order, and which may cost exponentially many states (_tree_representatives).
- Returns:
a fresh presentation of the same kind as source. For k > 1 its alphabet is the source alphabet’s k-fold product.
autstr.mtbdd module#
A shared multi-terminal BDD store for transition symbols.
Sparse automata over convolution alphabets used to store transitions as flat
(left, right, symbol) -> target rows. That representation forces every
operation to enumerate symbols: cylindrification duplicates each row once
per letter of every new tape, padding blankets multiply out, and a set
quantifier over a wide convolution produces exception tables with millions of
rows that all say the same thing about the same digits. This module replaces
the symbol column by a decision diagram over the digits of the symbol, which
is the representation MONA uses for the same reason.
Encoding. A symbol of a k-tape convolution over a base alphabet of size m
is the integer sum_t digit_t * m**(k-1-t). Each letter is written in
bits = ceil(log2 m) binary variables, most significant first, and the
global variable order is tape-major: variable t*bits + j is bit j of
tape t. A tape therefore occupies a contiguous block of variables, so
existential projection quantifies a block, and cylindrification renames blocks.
Nodes. A node is either a terminal carrying an integer value (a state, or
a subset id, or a class id — the callers decide) or an internal node
(var, lo, hi) whose children test strictly larger variables. Nodes are
reduced (lo != hi) and hash-consed in one process-wide store, so
structurally equal transition functions are the same node: equality of
behavior is an integer comparison, and the memo tables of apply are shared
across pairs of states and across automata.
Invalid codes. When m is not a power of two some binary codes denote no
letter. Every node built here maps those codes to the reserved terminal
NONE, and every operation propagates it, so the reachable-target set of a
node never contains a state that only an invalid code would reach — the
counting arguments the flat representation needed (“does this pair except all
m preimages of the symbol?”) disappear.
- class autstr.mtbdd.ComputedTable(cap_log2=23, dict_limit=524288)[source]#
Bases:
objectA memo for apply that is exact while small and lossy once large.
A dict memo for apply2 grows without bound: a set quantifier fills it with tens of millions of entries at roughly 180 bytes each, outweighing the nodes it caches. But most apply calls in a normal query are tiny, and a dict beats any hand-rolled table at that size.
So: a plain dict until it exceeds dict_limit entries, then a direct-mapped array of (key, value) pairs at 16 bytes per slot that simply overwrites on collision. Memory is bounded by the table; a miss only recomputes a pure function.
Correctness rests on apply’s terminal operations being deterministic functions of their arguments: recomputing an entry allocates no new state id or subset, it re-derives the same one. Only the computed tables may be lossy — the unique table NodeStore._node_ids must stay exact, or hash-consing breaks and node equality stops meaning function equality.
- mask#
- shift#
- keys#
- vals#
- autstr.mtbdd.bits_of(mask)[source]#
The set bit positions of an integer bitset, ascending.
Subset constructions intern one set of states per union result — including every intermediate inside apply2 — so the sets are held as python ints: a bitset costs
num_states/8bytes and hashes in one pass, where a frozenset costs tens of bytes per member.
- class autstr.mtbdd.NodeStore[source]#
Bases:
objectHash-consed multi-terminal BDD nodes with memoized operations.
A set quantifier can create millions of nodes, so the per-node cost is part of the algorithm: var/lo/hi/term are array(‘q’) (8 bytes each, against 36 for a python list slot holding a boxed int) and the unique table is keyed by a single packed integer rather than a 3-tuple.
- letter(tape, children, m, bits)[source]#
The binary decision tree over tape tape’s variable block that selects
children[d]on digit d; codes d >= m lead to NONE.
- const(value, arity, m, bits)[source]#
The node mapping every valid symbol of an arity-tape convolution to value (and every invalid code to NONE).
- build_rows(symbols, targets, base, arity, m, bits)[source]#
The node of a transition function given as a base value plus a sorted list of
symbol -> targetdeviations.
- map_letters(node, arity, old_m, old_bits, new_m, new_bits, source, fill)[source]#
Re-express a diagram over a different base alphabet.
source[d] names the old letter that the new letter d should behave like, or -1 to send it to fill. The map runs from the new alphabet to the old one, so it need not be injective: several new letters may share an old one. That is what embeds a factor into a product’s pair alphabet, where every pair (a, b) behaves like its own component.
Per tape, take the old block’s old_m cofactors and reassemble them at the new digits. One memoized pass over the nodes – widening an alphabet never rebuilds a transition table, which is why a direct product is affordable: the pair alphabet has
|A| * |B|letters but its diagrams havebits_A + bits_Bvariables. Letters multiply; bits add.
- fold_tapes(node, arity, m, bits, k)[source]#
Group every k consecutive tapes into one tape over the product alphabet Sigma^k.
The result reads
arity // ktapes whose letters are k-tuples, ordered lexicographically (mixed-radix, first component most significant) – which is the order encode_symbol gives a set of tuples, so the encodings line up. New digit D at element-tape e decodes to old digit(D // m**(k-1-i)) % mat old tapee*k + i.Diagram surgery mirrors map_letters, but one new tape consumes k old tapes, so its cofactors range over all m**k combinations rather than m.
- recode_letters(node, arity, old_m, old_bits, new_m, new_bits, digit_map, fill)[source]#
Injective relabelling: old letter d becomes new letter
digit_map[d]; new letters outside the image go to fill.
- set_path(node, assignment, value)[source]#
The node that agrees with node everywhere except on the single full variable assignment assignment, where it takes value.
- mux(var, on_high, on_low)[source]#
The node that behaves like on_high where var is 1 and like on_low where it is 0 — var may sit below the arguments’ roots, in which case they are pushed down.
- rename(node, varmap, cache)[source]#
Substitute variable v by variable
varmap[v]. The map need not be monotone or injective: identifying two variables restricts the function to their diagonal (which is how a relation R(x, x) is built). Monotone maps cost one node per node.
- apply2(f, g, op, cache)[source]#
Pointwise combination of two nodes; op acts on terminal values.
cache is a memo keyed by the packed node pair: a dict, or a bounded ComputedTable when the pair space is large enough that an exact memo would outweigh the nodes it caches.
- quantify_letter(node, tape, m, bits, op, cache)[source]#
Combine the m cofactors of node on tape tape’s letter with op — the tape’s variables no longer occur in the result.
- reset()[source]#
Drop every node and memo. Only valid on a scratch store: node ids are indices, so any surviving holder of one is left dangling.
- Return type:
- collect(roots)[source]#
Mark-sweep this store down to the sub-DAG below roots. A subset construction abandons the set-valued diagram of every subset as soon as it has been relabelled, but hash-consing keeps it forever; on a scratch store those nodes can be reclaimed.
Node ids are indices, so a sweep renumbers everything. Returns the roots’ new ids together with the old -> new map of every surviving node, which the caller needs to translate its apply memos (they map ids to ids, and dropping them instead makes the sweep cost more in recomputation than it saves in memory).
- export(roots)[source]#
Extract the sub-DAG below roots as standalone arrays, renumbered so that children precede parents. Returns (var, lo, hi, term, roots).
autstr.ordinals module#
Ordinals as automatic structures, on both engines.
Delhommé’s theorem draws two lines. The word-automatic ordinals are precisely those below \(\omega^\omega\), and the tree-automatic ones precisely those below \(\omega^{\omega^\omega}\). Every ordinal below the first line lives in some \(\omega^n\) and every ordinal below the second in some \(\omega^{\omega^n}\), so two factories cover both classes exactly: Ordinal on the string engine and TreeOrdinal on the tree engine. Neither boundary ordinal is itself reachable — \(\omega^\omega\) is not word-automatic, \(\omega^{\omega^\omega}\) not tree-automatic — which is why both take an exponent rather than being a single structure.
Ordinal is derived, not authored. By Cantor normal form an ordinal \(\alpha < \omega^n\) is uniquely \(\sum_{i<n} \omega^i c_i\) with natural coefficients, so \(\omega^n\) is the set of n-tuples of naturals under reverse-lexicographic order — an n-dimensional first-order interpretation of Büchi arithmetic:
>>> W2 = Ordinal(2) # the ordinals below ω²
>>> S = W2.symbolic()
>>> a, b = S.vars("a b")
>>> a.lt(b).evaluate().contains(a=(0, 7), b=(1, 0)) # 7 < ω
True
Ordinals are written as Python tuples of Cantor coefficients in descending
exponent order, so (3, 2, 5) in Ordinal(3) is
\(\omega^2 \cdot 3 + \omega \cdot 2 + 5\) and tuples compare
lexicographically exactly as the ordinals do. A plain integer is the finite
ordinal of that value.
The signature carries the strict order .lt, equality .eq and the
successor .succ. Everything else is first-order over those and can be
defined on the spot: a limit ordinal is one that is neither zero nor a
successor, and both structures have a definable least element but no greatest.
TreeOrdinal reaches past the word barrier by nesting the same idea: an ordinal below \(\omega^{\omega^n}\) is a finite sequence of blocks indexed by the leading exponent coefficient, each block an ordinal below \(\omega^{\omega^{n-1}}\), bottoming out at a natural number. That nesting is a tree, and it is authored rather than interpreted — see TreeOrdinal.
- autstr.ordinals.OrdinalValue#
coefficients in descending exponent order, or an integer
- Type:
a Python ordinal
- class autstr.ordinals.Ordinal(n=1)[source]#
Bases:
objectThe ordinals below \(\omega^n\), under their natural order.
- Parameters:
n (
int) – the exponent (n ≥ 1).Ordinal(1)is \((\omega, <)\), i.e. the naturals;Ordinal(2)is \((\omega^2, <)\).
The presentation carries
Lt(the strict order),EqandSucc(the graph of the successor function,Succ(x, y)iffy = x + 1).- encode(value)[source]#
The encoding of an ordinal written as its Cantor coefficients in descending exponent order (or as a plain integer, for a finite ordinal).
- decode(word)[source]#
The ordinal encoded by a word, as a tuple of Cantor coefficients in descending exponent order.
- default_signature()[source]#
The signature symbolic() uses when none is given:
.lt,.eqand.succ, with ordinals written as coefficient tuples.
- symbolic(signature=None)[source]#
A symbolic interface to the order; write
a.lt(b)and ordinals as Python coefficient tuples.
- autstr.ordinals.CantorForm#
Cantor coefficients keyed by their exponent, itself an ordinal below omega^n written as a coefficient tuple
- Type:
a Python ordinal below omega^(omega^n)
- class autstr.ordinals.TreeOrdinal(n=1, max_states=None)[source]#
Bases:
TreeAutomaticPresentationThe ordinals below \(\omega^{\omega^n}\), under their natural order — the tree-automatic ordinals, which reach exactly this far.
- Parameters:
The encoding. By Cantor normal form an ordinal below \(\omega^{\omega^n}\) is a finite sum \(\sum \omega^{\beta_i} c_i\) with exponents \(\beta_i < \omega^n\) — and an exponent below \(\omega^n\) is an n-tuple of naturals, exactly what Ordinal orders. Splitting off the leading coefficient of the exponent turns that into a recursion: an ordinal below \(\omega^{\omega^n}\) is a sequence of blocks indexed densely by that leading coefficient, each block an ordinal below \(\omega^{\omega^{n-1}}\), and at n = 0 a natural number. So the encoding is a left spine of blocks whose payloads hang right and are themselves spines, bottoming out in binary chains — the shape the tree engine reads natively.
Indexing the blocks densely by position is what makes the order easy: two ordinals are aligned by construction, so the comparison is decided at the deepest position where they differ, and a single three-state automaton (
less,equal,greater) computes it bottom-up at every level of the nesting at once. Deeper is more significant everywhere — a deeper spine node is a higher exponent, a deeper bit is a higher power of two — and a position one ordinal has and the other lacks is a term the other is missing, so present beats absent.Authored, not interpreted. For n = 1 the trees coincide with Skolem arithmetic’s, since both encode a finitely supported map from \(\mathbb{N}\) to \(\mathbb{N}\). The order is not an interpretation of it: permuting the primes is an automorphism of \((\mathbb{N}_{>0}, \cdot)\), so no ordering of the exponent positions is definable there, while the encoding fixes one. The comparison automaton is small enough that authoring it is the honest route.
Ordinals are written as maps from exponent to coefficient:
>>> W = TreeOrdinal(1) # ordinals below ω^ω >>> a, b = W.symbolic().vars("a b") >>> a.lt(b).evaluate().contains(a={2: 5}, b={3: 1}) # ω²·5 < ω³ True
An exponent is written as Ordinal writes one — an integer, or a tuple of coefficients in descending order, left-padded to length n — and a plain integer ordinal is the finite ordinal of that value.
- SPINE = 's'#
spine node, tree root, the two bits, and padding
- ROOT = 'o'#
spine node, tree root, the two bits, and padding
- PAD = '*'#
spine node, tree root, the two bits, and padding
- LETTERS = frozenset({'*', '0', '1', 'o', 's'})#
- default_signature()[source]#
The strict order as
.lt, equality as.eq, the successor as.succ, and ordinals written as Cantor maps.
- is_total_order()[source]#
Whether
Ltis a strict total order — decidable, being first-order. Well-foundedness is not first-order, so it is not checkable here; it holds by construction.- Return type:
autstr.powerset module#
The finite subsets of the naturals – the structure MSO0.
Finite subsets of \(\mathbb{N}\) under \(\subseteq\), with singletons, the successor on singletons and their order. A finite set is a \(\{0,1\}\)-word, position i set iff i is a member, so by Büchi’s theorem first-order logic over this structure is exactly monadic second-order logic over \((\mathbb{N}, <)\): the definable sets of naturals are precisely the regular ones.
>>> M = MSO0().symbolic()
>>> x, y = M.vars("x y")
>>> ({0, 2}, {0, 1, 2}) in x.subset(y)
True
That correspondence is why the structure is called MSO0, and why quantifying over sets here costs no more than quantifying over elements elsewhere.
- class autstr.powerset.MSO0[source]#
Bases:
CompiledPresentationThe finite subsets of \(\mathbb{N}\) under \(\subseteq\), with singletons, successor and the order on singletons.
>>> M = MSO0() >>> x, y = M.symbolic().vars("x y") >>> ({0, 2} , {0, 1, 2}) in x.subset(y) True >>> (x + y).eq({0, 1}).check() # union True
By Büchi’s theorem, first-order logic over this structure is exactly monadic second-order logic over \((\mathbb{N}, <)\), so the definable sets are precisely the regular ones.
- Parameters:
automata – dictionary containing the automata that recognize the domain and the relations of the structure. ‘U’ is reserved key for the universe. All other keys are assumed to recognize relations over L(U)^k. They can be addressed by their keys in first-order queries.
- PADDING = '*'#
a set as a bitmask, position i set iff i is a member. The universe rejects trailing zeros, so the encoding is the CANONICAL one: {0} is 1, {0, 2} is 101, and the empty set is the empty word.
autstr.presentations module#
- class autstr.presentations.AutomaticPresentationSerializer[source]#
Bases:
object- MAGIC = b'APRS'#
- VERSION = 1#
- HEADER_FORMAT = '4sB3sII'#
- HEADER_SIZE = 16#
- class autstr.presentations.DeferredRelations[source]#
Bases:
objectRelations declared up front and built on first use.
Equality is definable in most presentations here – from
Leqin a lattice, fromSubseton set-valued elements, from the operation in a group – but defining it costs an automaton construction that most queries never need, and for the wider graph classes that construction is expensive. So such a relation is registered rather than built, and materializes when something asks for it.materialize() forces the construction, which is what you want before pickling or otherwise reusing a structure, and every constructor that registers deferred relations takes an
eagerflag for the same purpose.Subclasses say where their relations live (_relations) and how to install one (_install_relation). A definition is either a formula string over the existing signature or a callable returning an automaton.
- class autstr.presentations.AutomaticPresentation(automata, padding_symbol='*', enforce_consistency=True)[source]#
Bases:
DeferredRelationsA presentation of a possibly infinite structure by finite state machines.
- Parameters:
automata (
Dict[str,SparseDFA]) – dictionary containing the automata that recognize the domain and the relations of the structure. ‘U’ is reserved key for the universe. All other keys are assumed to recognize relations over L(U)^k. They can be addressed by their keys in first-order queries.padding_symbol (any | None)
enforce_consistency (bool)
- symbolic(signature=None)[source]#
A symbolic interface to this structure: variables, relation and function symbols that build first-order expressions with Python operators instead of formula strings.
- Parameters:
signature – declared functions, operators and element codec. Relation arities are read from the automata, so a structure with no functions needs no signature at all.
- Returns:
a autstr.symbolic.SymbolicContext.
- default_signature()[source]#
The signature symbolic() uses when none is given, or None for a structure that declares no operators and is addressed through its relation symbols. Structures that know their own vocabulary override this; see autstr.symbolic.operation_signature.
- check(phi)[source]#
Checks if a given first-order formula holds on the presented structure. Free variables are assumed be implicitly existentially quantified.
- evaluate(phi, updates=None, prepared_updates=None)[source]#
Evaluates a given first-order query on the presented structure. Returns a presentation of the set of all satisfying assignments.
- Parameters:
phi (
Union[str,Expression]) – the first order formula.updates (
Optional[Dict[str,Union[SparseDFA,str]]]) – Temporarily update the relations for the evaluationprepared_updates (
Optional[Dict[str,SparseDFA]]) – like updates, for automata already known to be restricted to the universe – results this presentation produced itself. They are only re-padded, skipping the domain intersection that _prepare_automaton would otherwise redo on every tape.
- Return type:
- Returns:
The truth value of the formula, if the formula where all free variables are existentially quantified.
- class autstr.presentations.CompiledPresentation[source]#
Bases:
AutomaticPresentationA presentation whose automata are compiled by a builder function.
Subclasses set _BUILD to that function and declare their vocabulary in default_signature, so the structure can be constructed with no arguments and addressed symbolically with no setup. The builder’s automata are adopted as they are rather than passed to AutomaticPresentation.__init__, which would restrict and pad relations the builder already restricted and padded.
- Parameters:
automata – dictionary containing the automata that recognize the domain and the relations of the structure. ‘U’ is reserved key for the universe. All other keys are assumed to recognize relations over L(U)^k. They can be addressed by their keys in first-order queries.
autstr.sparse_automata module#
- class autstr.sparse_automata.SparseDFASerializer[source]#
Bases:
object- VERSION = 3#
- HEADER_FORMAT = '4sB3sII'#
- HEADER_SIZE = 16#
- METADATA_FORMAT = 'IIIII'#
- METADATA_SIZE = 20#
- class autstr.sparse_automata.SparseDFA(num_states, default_states=(), exception_symbols=(), exception_states=(), is_accepting=(), start_state=0, symbol_arity=1, base_alphabet=None, nodes=None)[source]#
Bases:
objectDeterministic automaton over a convolution alphabet.
Each state carries one shared multi-terminal BDD over the binary digits of the symbol (see autstr.mtbdd) instead of a default target plus a row of
symbol -> targetexceptions. A transition that ignores a tape never tests that tape’s variables, so cylindrification is a variable renaming rather than a duplication of every row once per letter of every new tape — which is what the pipeline used to spend its memory on.The constructor still accepts the flat form, and default_states, exception_symbols and exception_states remain available as decoded views for inspection, rendering and serialization of narrow automata: the default of a decoded state is its most common target, so the view is the sparsest one (what sparsify used to compute).
- dense_next(max_entries=16777216)[source]#
The full
(num_states, num_symbols)next-state table. Only for automata narrow enough to enumerate; the pipeline never calls it.
- compute(word)[source]#
Final state after reading the word (encoded symbols).
A word is a sequential dependency chain, so this cannot be vectorized. Narrow automata run off the dense table (one list index per symbol); wide ones descend the diagram, memoizing each (state, symbol) step.
- accepts_batch(words)[source]#
Batched acceptance check for many equal-length words at once.
- Parameters:
words – either an already-encoded integer array of shape (batch, length), or a sequence of equal-length words of symbol tuples (encoded like accepts()).
- Return type:
- Returns:
boolean array of shape (batch,).
Uses JAX (jit + scan, GPU if available) when installed and the dense next-state table fits; otherwise a vectorized numpy fallback (over the table, or over the diagrams when the alphabet is too wide to enumerate).
- alphabet_projection(projection_map)[source]#
Relabel symbols by projection_map, which may merge them and thus make the automaton nondeterministic (inspection-scale: it decodes).
- intersect_subtapes(other, tapes)[source]#
{x in L(self) | x[tapes] in L(other)}.
Expressed as a product with other cylindrified onto tapes, so no symbol is enumerated.
- regular_right_quotient(other)[source]#
{u | uv in L(self) for some v in L(other)}: a state of self is accepting iff, paired with other’s start state, it can synchronously reach a pair of accepting states.
- sparsify()[source]#
No-op: the diagram representation is already the sparse one (its decoded view picks each state’s most common target as the default).
- Return type:
- minimize()[source]#
Moore partition refinement over the transition diagrams.
Relabelling a state’s diagram by the current partition yields the function
symbol -> class of target; hash-consing means two states induce the same function exactly when the relabelled diagrams are the same node, so a refinement round is one apply1 per state.- Return type:
- autstr.sparse_automata.recode(dfa, new_alphabet, letter_map=None)[source]#
Re-express an automaton over a different base alphabet.
letter_map sends each of the automaton’s letters to a letter of new_alphabet (injectively; the identity by default). Letters of the new alphabet outside the image are rejected: a fresh dead state absorbs them, which is what the closure constructions want – a factor of a disjoint union must reject the other side’s letters, and a factor of a direct product must reject the tag letters.
The rewrite happens on the diagrams, one pass over the nodes, so widening the alphabet does not rebuild any transition table.
- autstr.sparse_automata.reduce_set_nfa(store, nodes, subsets, is_accepting, start, arity, m, bits)[source]#
Shrink a set-valued NFA before determinizing it.
nodes[q] is a diagram from symbols to sets of successors (terminals are indices into subsets, whose entries are bitsets of states). Two reductions apply, both language-preserving and both cheap next to the subset construction that follows:
states that reach no accepting state, and states unreachable from start, are dropped: they enlarge every subset that contains them without ever affecting acceptance;
the remainder is quotiented by forward bisimulation — q and q’ merge when they agree on acceptance and, on every symbol, their successor sets have the same classes. Relabelling a state’s diagram so each terminal becomes the class bitset of its target set turns the state’s signature into a hash-consed node id, so a refinement round is one apply1 per state.
Determinizing the quotient explores subsets of classes, which are images of the subsets of states, so the state count can only shrink.
- class autstr.sparse_automata.SparseNFA(num_states, base_state=(), exception_symbols=(), exception_states=(), is_accepting=(), start_state=0, symbol_arity=1, base_alphabet=None, nodes=None, subsets=None)[source]#
Bases:
objectNondeterministic automaton whose states carry a diagram from symbols to sets of targets (terminals index self.subsets).
The flat constructor keeps the historical shape — a base target per state plus
symbol -> targetexception rows, where the exceptions of a symbol override the base rather than adding to it. That is the semantics determinize always implemented; running the NFA directly now follows the same reading.- determinize()[source]#
Subset construction on the diagrams: a subset’s transition is the union of its members’ diagrams, and the union of two set-valued diagrams is one apply. No symbol is enumerated.
The NFA is first pruned and quotiented by forward bisimulation (see reduce_set_nfa), which is cheap and shrinks the subset space.
- Return type:
autstr.sparse_tree_automata module#
Sparse bottom-up tree automata.
The tree analog of autstr.sparse_automata: deterministic bottom-up automata over binary trees (general trees embed via the first-child/next-sibling encoding), stored sparsely and processed with batched numpy throughout.
States. Real states are 0..num_states-1; the virtual absent state
BOT = num_states represents a missing child, so a single transition table
covers leaves (both children absent), unary and binary nodes:
state(node) = delta(state(left) or BOT, state(right) or BOT, label(node)).
Sparsity. Transitions are stored as a sorted table from the child pair
(left, right) to a shared multi-terminal BDD over the binary digits of
the symbol (see autstr.mtbdd); pairs absent from the table map every symbol
to the global default_state. Nothing in the pipeline ever enumerates the
convolution alphabet: a symbol is a variable assignment, so a transition that
ignores a tape simply does not test that tape’s variables. Boolean
combinations are pairwise apply on the diagrams, complementation relabels
acceptance and touches no diagram at all, and hash-consing makes two states
with the same transition function share one node.
- class autstr.sparse_tree_automata.Tree(label, left=None, right=None)[source]#
Bases:
objectAn immutable labelled binary tree (convenience representation).
- label#
- left#
- right#
- autstr.sparse_tree_automata.tree_to_arrays(tree, base_alphabet, arity=1)[source]#
Convert a Tree with tuple/symbol labels to the post-order array format (labels encoded as integers over base_alphabet^arity).
- autstr.sparse_tree_automata.convolve_trees(trees, base_alphabet, padding_symbol)[source]#
Overlay k trees into one tree over the tuple alphabet: the domain is the union of the domains, absent positions are padded.
- class autstr.sparse_tree_automata.SparseTreeAutomaton(num_states, default_state, exc_left=(), exc_right=(), exc_symbol=(), exc_target=(), is_accepting=(), symbol_arity=1, base_alphabet=None, pd_left=(), pd_right=(), pd_target=(), pair_keys=None, pair_nodes=None)[source]#
Bases:
objectDeterministic bottom-up tree automaton with MTBDD transitions.
The constructor takes the transition function in the flat form that is convenient to write down by hand — a global default, optional per-pair defaults, and
(left, right, symbol) -> targetexceptions — and compiles it into one decision diagram per child pair.- Parameters:
num_states (
int) – number of real states (0..num_states-1); the virtual absent-child state isBOT = num_states.default_state (
int) – target of every transition not listed below.exc_target (exc_left, exc_right, exc_symbol,) – parallel arrays of exception transitions delta(exc_left, exc_right, exc_symbol) = exc_target. Children may be BOT; targets are real states.
is_accepting – boolean array over the real states (acceptance is checked at the root).
pd_target (pd_left, pd_right,) – parallel arrays of pair defaults
delta(pd_left, pd_right, *) = pd_targetfor symbols without an exception. Pairs not listed fall back to the global default.pair_nodes (pair_keys,) – the compiled form (sorted packed pair keys and their diagram roots); passed by the pipeline instead of the flat arrays.
symbol_arity (int)
base_alphabet (Set | None)
- transitions(left, right, symbol)[source]#
Batched transition lookup: find each pair’s diagram, then descend it along the symbol’s digits.
- Return type:
- dense_delta(max_entries=10000000)[source]#
The full transition table
(BOT+1, BOT+1, num_symbols). For inspection and for reference oracles on small automata.
- exceptions(max_entries=10000000)[source]#
The transitions that differ from the global default, as flat
(left, right, symbol, target)arrays (inspection only).- Parameters:
max_entries (int)
- run(labels, lefts, rights)[source]#
State at the root of a post-order array tree.
Adaptive evaluation: children resolve before parents in post-order, so each vectorized round computes every node whose children are already known — one round per tree level, ideal for bushy trees. Long unary chains (e.g. string-like spines) are inherently sequential, so when a round stops being productive the remaining nodes are finished by a scalar post-order sweep instead of degenerating to O(n^2).
- Return type:
- accepts(*trees)[source]#
Does the automaton accept the convolution of the given trees? Accepts Tree objects (one per tape) or a single pre-encoded array tree given as the tuple (labels, lefts, rights).
- Return type:
- reachable_states()[source]#
Boolean mask of states reachable by some tree (bottom-up fixpoint). The targets of an available child pair are the terminals of its diagram; the global default joins as soon as some available pair is absent from the table.
- Return type:
- co_reachable_states(available=None)[source]#
Boolean mask of states that can occur in an accepting run: a state is co-reachable if it is accepting (as the root) or it is a child in some transition whose target is co-reachable and whose sibling subtree exists. The top-down companion to reachable_states.
- is_finite()[source]#
Whether the automaton accepts finitely many trees.
A state that can occur strictly below itself pumps: the context between the two occurrences can be repeated without bound. So the language is infinite exactly when the “child of” graph, restricted to states that are both reachable and co-reachable, has a cycle.
- Return type:
- class autstr.sparse_tree_automata.SparseTreeAutomatonSerializer[source]#
Bases:
objectBinary serialization for SparseTreeAutomaton.
The tree counterpart of autstr.sparse_automata.SparseDFASerializer, and the same payload idea: the compiled form is a sorted table of child pairs with one diagram root each, so storing it is storing the pair keys plus the sub-DAG below their roots.
- MAGIC = b'STAU'#
- VERSION = 1#
- HEADER_FORMAT = '4sB3sII'#
- HEADER_SIZE = 16#
- METADATA_FORMAT = 'IIIIII'#
num_states, num_nodes, num_pairs, default_state, symbol_arity, alphabet
- METADATA_SIZE = 24#
- classmethod serialize(automaton, filename)[source]#
- Return type:
- Parameters:
automaton (SparseTreeAutomaton)
filename (str)
- classmethod to_bytes(automaton)[source]#
- Return type:
- Parameters:
automaton (SparseTreeAutomaton)
autstr.tree_algebra module#
The countable atomless boolean algebra, as a tree-automatic structure.
There is exactly one countable atomless boolean algebra up to isomorphism —
Cantor’s back-and-forth argument again — and it is the infinite counterpart of
the autstr.algebra.FiniteBooleanAlgebras class, carrying the same signature
Leq, Meet, Join, Compl, Atom. The same formula runs against
both, and the one that separates them is the definition of the name: over a
finite algebra exists x. Atom(x) is true, and here it is false.
Elements are clopen subsets of Cantor space. A clopen subset of \(2^\omega\) is a finite union of cylinders, so it is decided by finitely many bits of a point — that is, by a finite binary decision tree with leaves labelled in or out. Making the tree reduced (no split whose two sides are the same constant) makes the encoding a bijection, which matters here: a non-canonical encoding would need a quotient, and quotients are exactly what the tree engine cannot yet supply (see autstr.interpretations).
One authored automaton. Reduction is what makes the order cheap. In a reduced tree a subtree is constant exactly when it is a leaf, so at any position the comparison is already decided unless both sides split there:
x is 0 here -> fine, nothing of x to contain
y is 1 here -> fine, y contains everything
x is 1, y is not -> y misses a point of x
y is 0, x is not -> x has a point y misses
both split -> recurse
Nothing below a leaf is ever consulted, so Leq is a two-state bottom-up
automaton. Everything else — Meet, Join, Compl, Atom, and
equality — is first-order over it and is defined rather than authored, built
the first time a query asks for it.
>>> B = AtomlessBooleanAlgebra()
>>> x, y = B.symbolic().vars("x y")
>>> (x * y).eq({'00'}).check() # some meet is the cylinder 00
True
>>> B.check('exists x.(Atom(x))') # atomless, by construction
False
A clopen set is written as the set of binary strings whose cylinders it
contains: {'0', '10'} is everything starting 0 or 10, set() is
empty and {''} is everything. Any covering set may be given — {'0','1'}
and {''} denote the same element — and decode returns the canonical one.
- autstr.tree_algebra.Clopen#
a clopen set, as the binary strings whose cylinders it contains
- class autstr.tree_algebra.AtomlessBooleanAlgebra(max_states=None)[source]#
Bases:
TreeAutomaticPresentationThe unique countable atomless boolean algebra.
- Parameters:
max_states (
Optional[int]) – optional cap on the subset determinizations inside projection.
Elements are the clopen subsets of Cantor space, encoded as reduced binary decision trees:
'0'and'1'label leaves that are out and in, and'n'labels a split on the next bit of a point.- SPLIT = 'n'#
split, out-leaf, in-leaf, padding
- OUT = '0'#
split, out-leaf, in-leaf, padding
- IN = '1'#
split, out-leaf, in-leaf, padding
- PAD = '*'#
split, out-leaf, in-leaf, padding
- LETTERS = frozenset({'*', '0', '1', 'n'})#
- default_signature()[source]#
Meet as
*, join as+, complement as unary-, the order as.leqand equality as.eq.The lattice operations are terms, not connectives:
&,|and~already mean conjunction, disjunction and negation of formulas, so binding them here would makex & yambiguous — the same reason Büchi arithmetic spells its divisibility relationdivided_by_power.
- is_atomless()[source]#
Whether every non-empty element strictly contains a non-empty one — decidable, being first-order, and the property that pins the algebra down to isomorphism.
- Return type:
autstr.tree_arithmetic module#
Skolem arithmetic: the multiplicative monoid of the naturals.
\((\mathbb{N}_{>0}, \cdot)\) is not string-automatic, but it is tree-automatic, and the prime factorization is why: multiplicatively the structure is the direct sum of countably many copies of \((\mathbb{N}, +)\), one per prime, and a tree bundles the finitely many nonzero summands. So multiplication becomes addition of exponents, carried out in parallel across the primes.
>>> S = skolem_arithmetic().symbolic()
>>> x, y = S.vars("x y")
>>> (6, 35, 210) in (x * y).eq(S.vars("z")[0])
True
This is the tree-engine counterpart of autstr.arithmetic, whose additive structures the string engine can present directly.
- class autstr.tree_arithmetic.SkolemArithmetic(max_states=None)[source]#
Bases:
TreeAutomaticPresentation(N_{>0}, ·, =) presented by tree automata.
Encoding: n = prod_i p_i^{e_i} is a left spine of ‘p’-labelled nodes, one per prime index up to the largest with e_i > 0 (so no trailing zero exponents); spine node i carries e_i as a right-hanging chain of bits with the least significant bit at the top and the most significant (always ‘1’) at the bottom; e_i = 0 is an absent chain. n = 1 is the single node ‘p’.
Relations: M(x, y, z) iff x·y = z, and equality E. Multiplication is positionwise addition of the exponent vectors, so its automaton runs a binary addition automaton independently on every exponent branch of the 3-tape convolution; a single further state rides down the spine and checks that every branch accepted.
- Parameters:
max_states (int | None)
- LETTERS = frozenset({'*', '0', '1', 'p'})#
- PAD = '*'#
autstr.tree_graphs module#
Graphs of bounded tree-width and bounded clique-width as uniformly tree-automatic classes.
The class presents graphs over sets of vertices (as in MSO0), so first-order logic over the presentation is monadic second-order logic over the graph — evaluating an MSO query once yields a tree automaton that decides it on every member graph in linear time (Courcelle’s theorem).
Practical envelope. Transitions are decision diagrams over the symbol’s digits (autstr.mtbdd), so the width of the convolution alphabet no longer drives the cost: a query with several free tapes is as cheap as the digits its transitions actually test. What remains is the subset explosion of determinizing an existential quantifier. Element (path) quantifiers project onto small subset automata and are cheap; set quantifiers (MSO proper) determinize over subsets of the intermediate automaton’s states, and each such subset carries its own diagram. Two-colourability compiles at w = 1; deeper set-quantifier nesting is bounded by memory, not by the alphabet. Deciding a compiled automaton on a graph is always linear and fast — compilation is the bottleneck.
The signature is shared with the string graph classes:
Sing(x) x is a singleton Subset(x,y) x is a subset of y E(x,y) x = {u}, y = {v} and u,v are adjacent
Tree-width <= w (TreeWidthClass): the advice is a binary tree with one node per vertex, labelled (register in {0..w}, adjacency profile), plus a structural letter ‘n’ that introduces no vertex (used to route branch points). Introducing a vertex at register r replaces the nearest occupant of r above; the profile lists registers of the vertex’s neighbors among the current occupants on its root path. The live registers along any path form bags of size <= w+1 — exactly a tree decomposition of width w — and every graph of tree-width <= w arises this way from a nice decomposition.
Clique-width <= k (CliqueWidthClass): the advice is a k-expression – leaves create a labelled vertex, u takes a disjoint union, r{i}{j} relabels, and e{i}{j} joins every label-i vertex to every label-j vertex. The vertices are the leaves. Adjacency is much cheaper to recognize than for tree-width: two vertices are joined exactly when some e{i}{j} node sees one holding label i and the other label j, so the automaton need only carry each marked vertex’s current label. The advice alphabet is correspondingly small, and MSO queries compile far faster – two-colourability is a 9-state automaton at k = 2, against a minute at tree-width 1.
Rank-width <= r (RankWidthClass): the advice is a rank decomposition (a binary tree whose leaves are the vertices) annotated with the GF(2) factorization data of its cuts – the graph analog of the bounded-rank-width group classes, compiled with the same chain_ring linear algebra at p = 2, d = 1. Each node carries a basis-change matrix per child and each binary node the bilinear form of its sibling block; adjacency of x and y is w_y^T Q w_x at the node where their subtrees meet, so the E automaton only carries the marked vertices’ r-bit interface vectors. Rank-width lower-bounds clique-width (rw <= cw <= 2^{rw+1} - 1) and is bounded on dense graphs where tree-width is not (cliques have rank-width 1).
Vertex sets are encoded synchronously over the advice: the element tree is the union of the root paths to the set’s members, labelled ‘1’ on members and ‘0’ on the way (the empty set is the single node ‘0’). For tree-width every node is a vertex; for clique-width and rank-width only the leaves are. TreeWidthGraph, CliqueWidthGraph and RankWidthGraph encapsulate single graphs and convert to networkx.
- class autstr.tree_graphs.TreeWidthGraph(letters, nodes=None)[source]#
Bases:
objectA graph in its tree representation: a binary tree with one (register, profile) letter per vertex (and ‘n’ for structural nodes). A vertex’s profile lists the registers of its already-introduced neighbors, resolved to the nearest writer above on the root path.
- encode_set(subset)[source]#
Encode a set of nodes as a marked tree over the advice shape.
- Return type:
- classmethod from_networkx(graph, decomposition=None)[source]#
Build the tree representation from a networkx graph.
- Parameters:
graph – undirected networkx graph (at least one vertex).
decomposition – optional tree decomposition as a networkx tree whose nodes are frozensets of vertices (bags). If omitted, the min-fill-in heuristic computes one (valid, possibly wider than the tree-width).
- Return type:
- class autstr.tree_graphs.TreeWidthClass(w, max_states=None)[source]#
Bases:
SymbolicClassWrapperThe uniformly tree-automatic class of graphs of tree-width <= w, presented over set-valued elements (MSO0 style).
- GRAPH = None#
elements are vertex sets and E is the edge relation, not equality
- EQUALITY = 'Subset(x,y) and Subset(y,x)'#
extensional equality of sets
- advice(graph)[source]#
The advice tree of a graph (its letters as alphabet symbols).
- Return type:
- Parameters:
graph (TreeWidthGraph | Tree)
- evaluate(phi)[source]#
Evaluate an MSO query over the class; see UniformlyTreeAutomaticClass.evaluate. Variables range over vertex sets.
- check(phi, graph, **sets)[source]#
Model check an MSO query against a single graph.
- Parameters:
phi – formula over Sing/Subset/E; free variables can be assigned via sets (name = set of nodes), unassigned ones are quantified existentially.
graph (
Union[TreeWidthGraph,Tree]) – a TreeWidthGraph (or a raw advice tree).sets – assignments for free variables, as sets of nodes.
- Return type:
- class autstr.tree_graphs.CliqueWidthGraph(expression, k)[source]#
Bases:
objectA graph given by a k-expression, held as a binary tree.
The expression’s leaves create vertices, so the vertices are the leaves, numbered left to right. Inner nodes are the three clique-width operations:
u disjoint union of the two children r{i}{j} relabel every label-i vertex to label j e{i}{j} join: add every edge between label i and label j
u is binary; r and e are unary (left child only).
- encode_set(subset)[source]#
The set as a tree of marks: ‘1’ at the chosen leaves, ‘0’ on the paths above them, absent elsewhere. The empty set is a single ‘0’.
- Return type:
- classmethod clique(n)[source]#
K_n, clique-width 2: absorb each new vertex into label 0.
- Return type:
- Parameters:
n (int)
- classmethod complete_bipartite(left, right)[source]#
K_{left,right}, clique-width 2: join the two colour classes once.
- Return type:
- Parameters:
- classmethod path(n)[source]#
P_n, clique-width 3: label 0 is the growing end, 1 the new vertex, 2 the settled interior.
- Return type:
- Parameters:
n (int)
- class autstr.tree_graphs.CliqueWidthClass(k, max_states=None)[source]#
Bases:
SymbolicClassWrapperThe uniformly tree-automatic class of graphs of clique-width <= k, presented over set-valued elements (MSO0 style).
The advice is a k-expression (see CliqueWidthGraph); the vertices are its leaves, and a vertex set is encoded synchronously as the union of the root paths to its members, ‘1’ on members and ‘0’ on the way.
Adjacency is far simpler here than for tree-width: two vertices are joined exactly when some e{i}{j} node sees one of them holding label i and the other label j. A bottom-up automaton therefore only has to remember the current label of each marked vertex, and whether the join has happened.
- GRAPH = None#
elements are vertex sets and E is the edge relation, not equality
- EQUALITY = 'Subset(x,y) and Subset(y,x)'#
extensional equality of sets
- advice(graph)[source]#
The advice tree of a graph (its k-expression).
- Return type:
- Parameters:
graph (CliqueWidthGraph | Tree)
- evaluate(phi)[source]#
Evaluate an MSO query over the class; variables range over vertex sets. See UniformlyTreeAutomaticClass.evaluate.
- check(phi, graph, **sets)[source]#
Model check an MSO query against a single graph.
- Return type:
- Parameters:
graph (CliqueWidthGraph | Tree)
- class autstr.tree_graphs.RankWidthGraph(shape, edges)[source]#
Bases:
objectA graph with a rank decomposition: a binary layout tree whose leaves are the vertices (numbered left to right), plus an edge set.
The width of the decomposition is the maximum, over all subtrees S, of the GF(2) rank of the bipartite adjacency matrix between the leaves inside S and the leaves outside (the cut-rank); the rank-width of the graph is the minimum over decompositions. This is the graph analog of the module cut-rank of the bounded-rank-width group classes, and the class compiler below reuses the same linear algebra (autstr.chain_ring at p = 2, d = 1). Unary layout nodes are allowed (they do not change the cuts).
- Parameters:
shape (Tree)
- cut_matrix(node)[source]#
The bipartite adjacency block of the node’s cut: rows the outside vertices, columns the inside leaves (left-to-right).
- property width: int#
the maximum GF(2) cut-rank over all proper subtrees.
- Type:
The rank-width of this decomposition
- encode_set(subset)[source]#
The set as a tree of marks: ‘1’ at the chosen leaves, ‘0’ on the paths above them, absent elsewhere. The empty set is a single ‘0’.
- Return type:
- encode_set_padded(subset, pad='*')[source]#
The set as a mark tree of the decomposition’s exact shape, pad outside the trimmed domain – what the implicit evaluator needs (it runs all tapes synchronously over the advice shape).
- static caterpillar(n)[source]#
The linear (caterpillar) decomposition: leaves 0..n-1 hang left to right off a left-deep spine.
- classmethod clique(n)[source]#
K_n: every crossing block is all ones – rank-width 1 on any decomposition.
- Return type:
- Parameters:
n (int)
- classmethod path(n)[source]#
P_n in path order: one edge crosses each caterpillar cut – rank-width 1.
- Return type:
- Parameters:
n (int)
- classmethod cycle(n)[source]#
C_n: two edges cross the middle caterpillar cuts – width 2 on this decomposition (and rank-width 2 for n >= 5).
- Return type:
- Parameters:
n (int)
- class autstr.tree_graphs.RankWidthClass(r, max_states=None)[source]#
Bases:
SymbolicClassWrapperThe uniformly tree-automatic class of graphs of rank-width <= r, presented over set-valued elements (MSO0 style: Sing, Subset, E).
The advice is a rank decomposition annotated with the GF(2) factorization data of its cuts, exactly as the bounded-rank-width group classes annotate theirs: each node carries a basis-change matrix per child (w <- T w) and each binary node the bilinear form Q of its sibling block, so that two vertices x in the left and y in the right subtree are adjacent iff w_y^T Q w_x over F_2, where w is the vertex’s interface vector (its column in the saturated basis of the cut, composed through the T maps). The letters are
leaf: ‘a’ + w (r bits) – the vertex’s interface vector unary: ‘b’ + T (r*r bits) binary: ‘d’ + TL + TR + Q (3 r*r bits)
and every well-shaped advice presents some graph of rank-width <= r. Vertex sets are encoded as union-of-root-path marks (see RankWidthGraph.encode_set); the E automaton carries each marked vertex’s interface vector – O(2^{2r}) states however large the graph.
advice(graph) compiles a RankWidthGraph whose decomposition has width <= r into the annotated advice (chain_ring.saturate / solve_left / factor_two_sided at p = 2, d = 1); check_implicit and evaluate_implicit run over the functional atoms without building any automaton. The flat letter alphabet caps r at 2: it grows as 2^{3r^2} binary letters, unlike the factored letters used by the group classes.
- GRAPH = None#
elements are vertex sets and E is the edge relation, not equality
- EQUALITY = 'Subset(x,y) and Subset(y,x)'#
extensional equality of sets
- property cls: UniformlyTreeAutomaticClass#
The presentation, built lazily (the r = 2 E automaton enumerates a few million transitions).
- advice(graph)[source]#
Compile a rank decomposition into the annotated advice; raises if some cut exceeds rank r. A pre-compiled advice tree passes through.
- Return type:
- Parameters:
graph (RankWidthGraph | Tree)
- evaluate(phi)[source]#
Evaluate an MSO query over the class; variables range over vertex sets. See UniformlyTreeAutomaticClass.evaluate.
- check(phi, graph, **sets)[source]#
Model check an MSO query against a single graph.
- Return type:
- Parameters:
graph (RankWidthGraph | Tree)
- property implicit_cls#
The fully implicit presentation (functional atoms only).
- check_implicit(phi, graph, **sets)[source]#
Like check, evaluated implicitly (no automaton is built). Set assignments are padded to the advice shape (the implicit evaluator runs all tapes synchronously).
- Return type:
- Parameters:
graph (RankWidthGraph | Tree)
- evaluate_implicit(phi, graph, **sets)[source]#
The satisfying set of phi on the graph, computed implicitly: unassigned free variables stay open. Yields assignments {var: vertex set} when a graph object is given (raw mark trees for a bare advice); len is the exact solution count.
- Parameters:
graph (RankWidthGraph | Tree)
autstr.tree_groups module#
Tree-indexed generalizations of the extraspecial p-groups as a uniformly tree-automatic class.
The advice is an arbitrary binary shape tree. It presents a class-2 group G_t with one generator pair x_w, y_w per inner node w, one central generator z_v per leaf v, exponent-p relations, and commutators
[x_w, y_w] = prod over the leaves v below w of z_v,
all other generator pairs commuting. The commutator supports form the laminar family of the tree: a spine advice (every inner node with a single child, one leaf at the bottom) makes every commutator hit the same z, which is exactly the extraspecial group p^(1+2n) of exponent p (p odd); general shapes interpolate between that and the direct sum of Heisenberg groups.
Concretely G_t = { (a, b, c) : a, b assign F_p to inner nodes, c assigns F_p to leaves } with the central-extension law
(a1,b1,c1)(a2,b2,c2) = (a1+a2, b1+b2, c), c(v) = c1(v) + c2(v) + sum over inner w < v of a1(w)*b2(w) (mod p).
Elements are encoded as trees of the advice’s exact shape: inner node w is labelled ‘i{a(w)}{b(w)}’, leaf v is labelled ‘l{c(v)}’. The multiplication automaton is the running-sum trick evaluated along all paths of the tree: a bottom-up state in F_p tracks the deficit c_z - c_x - c_y still owed by the ancestors, each inner node subtracts its commutator contribution a_x(w)*b_y(w), and siblings must agree on the owed amount when their branches merge — p + 1 states in total, independent of the shape.
Bounded rank-width (CutRankTreeGroups(p, k, r)): the tree analog of autstr.groups.CutRankGroups. A member is a class-2 central extension of Z_p^n by Z_p^k whose commutation form admits a tree layout in which every subtree’s crossing block has rank <= r over F_p; the advice spells out the factorizations node by node, and a spine layout is exactly the word class.
- class autstr.tree_groups.TreeExtraspecialGroups(p, max_states=None)[source]#
Bases:
SymbolicClassWrapperThe uniformly tree-automatic class of tree-indexed extraspecial p-groups (advice = shape tree, elements = coordinate labellings).
- static spine(n)[source]#
Shape with n inner nodes over a single leaf: presents the extraspecial group p^(1+2n).
- encode(shape, a=(), b=(), c=())[source]#
The element (a, b, c) of G_shape as a tree. Coordinates are given per node address (’’ = root, then ‘0’/’1’ for left/right children); omitted coordinates are 0.
- evaluate(phi)[source]#
Evaluate a first-order query over the class; see UniformlyTreeAutomaticClass.evaluate.
- check(phi, shape, **elements)[source]#
Model check a formula against the group G_shape. Free variables can be assigned element trees (see encode); unassigned ones are quantified existentially.
- check_implicit(phi, shape, **elements)[source]#
Like check, evaluated implicitly (no query tree automaton).
- class autstr.tree_groups.CutRankTreeGroups(p, k=1, r=1, d=1, factored=None)[source]#
Bases:
SymbolicClassWrapperFor a fixed prime p, center dimension k, width r and ring depth d, the uniformly tree-automatic class of class-2 groups over R = Z/p^d whose commutation form admits a tree layout of module cut-rank <= r (bounded rank-width over R) — the tree analog of autstr.groups.CutRankGroups, which is recovered exactly on spine layouts. With d = 1 (the default) R is the field F_p; d > 1 gives an exponent-p^d center. The tree merge is the step where the ring case genuinely differs from the field one: it needs the sibling block to factorise as V_R^T Q V_L, which requires the carried bases to be saturated free interfaces (chain_ring.factor_two_sided).
Generators are the post-order positions 1..n of a binary layout tree; the form is a dict {(j, i): label in Z_p^k, i < j} presenting x_j x_i = x_i x_j y^B[j,i], and the group law is the same bilinear cocycle as in the word class. Elements are (b, a) encoded as digit labellings of the advice’s shape; the k center digits live on a chain of ‘c’ nodes above the layout root.
Bottom-up, the state at a subtree S is (s, wx, wy): the correction accumulated by pairs inside S, and r linear functionals w = V·(digits of S) of each factor’s digits, where V is a row basis of S’s crossing block. Two vectors are needed because a crossing pair can be consumed in two ways: its larger endpoint is an ancestor of S (the read-off pairs y-functionals of S with that ancestor’s x-digit), or the pair is split between siblings (the merge pairs the right child’s x-functionals against the left child’s y-functionals — in post-order the left subtree lies entirely below the right one). The advice letter at a node holds the factorization data over R = Z/p^d (each entry as d base-p digits):
leaf: 'a' + v (r) unary: 'b' + T (r x r) + v (r) + R (k x r) binary: 'd' + T_L, T_R (r x r) + v (r) + R_L, R_R (k x r) + Q (k x (r x r))
with w <- T_L w_L + T_R w_R + v*digit (both factors), correction s <- s_L + s_R + x_t*(R_L wy_L + R_R wy_R) + wx_R^T Q_l wy_L, and the ‘c’ chain consuming s coordinate by coordinate: z_c - x_c - y_c = s. That is q^(k+2r) layout states however large the tree. advice compiles (shape, form) into letters — saturated free bases of the crossing blocks plus solving the consistency systems, including the two-sided factorization V_R^T Q V_L of the sibling block — and fails precisely when some subtree’s module cut-rank exceeds r; tree_cut_rank measures the width a layout needs. Every well-shaped advice presents some group in the class: the streamed cocycle is bilinear by construction.
The multiplication automaton is a product over the whole letter alphabet, so building it is only feasible for a small ring alphabet; cls is therefore lazy and simulate runs the transition directly over the tapes for larger q.
Signature: M(x,y,z), Eq(x,y); the center is first-order definable.
factored=None (the default) enumerates one advice letter per factorisation tuple when that alphabet fits under 20000 letters (the original encoding, byte-identical) and otherwise switches to factored letters: each layout node becomes a bare marker (‘a’ leaf / ‘b’ unary / ‘d’ binary) followed by a unary chain of one letter per ring entry, with the element digit repeated along the stretch. The factored alphabet has q+4 letters however large r, k and d are – this is what makes width r >= 2 over the ring representable.
- MARKERS = ('a', 'b', 'd')#
leaf, unary, binary layout node
- Type:
factored-mode node markers
- n_entries#
ring entries per stretch, by node kind. Over the ring (d > 1) the binary stretch carries, instead of the k bilinear forms Q, the k full pairing tables over the register space: q^{2r} entries each.
- property cls: UniformlyTreeAutomaticClass#
the tree multiplication automaton is a product over the whole letter alphabet, so its construction is only feasible for a small ring alphabet. The reference law, advice compiler, width measure and simulate never need it and stay cheap for any q.
- Type:
The uniformly tree-automatic presentation, built lazily
- tree_cut_rank(shape, form)[source]#
The width the given tree layout needs: the maximal module cut-rank over R = Z/p^d of the crossing blocks of its subtrees (the free rank of the saturated interface; the ordinary F_p rank when d = 1).
- advice(shape, form)[source]#
Compile a layout and a form into the advice tree; raises if some subtree’s crossing block exceeds rank r. In factored mode each node becomes its bare marker with the ring entries chained above it.
- clique_form(n, label=None)[source]#
Nothing commutes; every crossing block is all-ones — cut-rank 1 on every layout.
- matching_form(n)[source]#
Disjoint commutator pairs of post-order neighbours: the extraspecial layout.
- multiply(n, form, g, h)[source]#
Reference implementation of the group law over R = Z/p^d (identical to the word class: the group depends on the form, not the layout).
- simulate(advice, gx, gy, gz)[source]#
Run the multiplication automaton directly over the convolved trees: True iff the advice accepts gx * gy = gz. A bottom-up pass of the shared _m_step transition, without building the product tree automaton, so the saturated tree merge can be checked against the reference law for any ring alphabet. gx, gy, gz are (b, a) elements over the advice shape.
- encode(element, shape)[source]#
Encode (b, a) over a layout shape (an advice tree is accepted too — its center chain is stripped). a is indexed by post-order. In factored mode each node’s digit is repeated along its entry stretch (so the element tree has the advice’s exact shape).
- check(phi, advice, **elements)[source]#
Model check against the member presented by the advice; free variables can be assigned elements as (b, a) tuples.
- decode(tree, advice)[source]#
Inverse of encode over the member’s advice: an element tree back to its (b, a) tuple (a indexed by post-order layout positions).
- property implicit_cls#
The fully implicit presentation of this class (functional atoms only, nothing compiled): an autstr.implicit.ImplicitTreeClass over raw element trees. check_implicit/evaluate_implicit add the (b, a)-tuple encoding on top of it.
- check_implicit(phi, advice, **elements)[source]#
Like check, but evaluated implicitly (no query or base tree automaton) – the only viable model checker for the large-alphabet ring members whose cls cannot be built. See autstr.implicit.
- evaluate_implicit(phi, advice, **elements)[source]#
The satisfying set of phi on the member presented by the advice, computed implicitly: unassigned free variables stay open and are solved for. Yields assignments {var: (b, a)}; len is the exact solution count without enumeration. Works for members whose automata cannot be built.
- Parameters:
advice (Tree)
autstr.tree_presentations module#
Tree-automatic presentations: structures whose elements are finite trees and whose relations are recognized by sparse bottom-up tree automata reading tree convolutions.
Mirrors autstr.presentations.AutomaticPresentation with the tree pipeline underneath. The pipeline invariant also mirrors the string engine: stored relation automata are padding-saturated (accept their canonical convolutions with arbitrary all-padding regions attached below, via attach_padding), so expand may widen them to more tapes; intersections and unions preserve saturation; complements are re-intersected with the domain product; and project — which produces the canonical (trimmed) language — is followed by re-saturation, the tree analog of the string pipeline’s pad/unpad dance.
- autstr.tree_presentations.tree_one(symbol_arity=1, base_alphabet=None)[source]#
Automaton accepting every tree.
- Return type:
- Parameters:
symbol_arity (int)
- autstr.tree_presentations.tree_zero(symbol_arity=1, base_alphabet=None)[source]#
Automaton rejecting every tree.
- Return type:
- Parameters:
symbol_arity (int)
- class autstr.tree_presentations.TreeAutomaticPresentationSerializer[source]#
Bases:
objectBinary serialization for TreeAutomaticPresentation.
Mirrors autstr.presentations.AutomaticPresentationSerializer, but stores each automaton’s payload as raw bytes with a length prefix rather than as a JSON list of integers – which costs roughly four bytes of file per byte of data.
- MAGIC = b'TPRS'#
- VERSION = 1#
- HEADER_FORMAT = '4sB3sII'#
- HEADER_SIZE = 16#
- class autstr.tree_presentations.TreeAutomaticPresentation(automata, padding_symbol='*', enforce_consistency=True, max_states=None)[source]#
Bases:
DeferredRelationsA presentation of a structure by tree automata.
- Parameters:
automata (
Dict[str,SparseTreeAutomaton]) – ‘U’ is the domain automaton (symbol arity 1); every other key R presents a relation of arity k by an automaton of symbol arity k over convolutions of element trees.padding_symbol – base letter used to pad convolutions.
max_states (
Optional[int]) – optional cap for the subset determinizations inside projection (a clear error instead of an exponential blowup).enforce_consistency (bool)
- automatic_presentation_to_file(filename)[source]#
Write the presentation – domain, every built relation, alphabet – to a file. Deferred relations that have not been built are not stored; call materialize first to include them.
- classmethod automatic_presentation_from_file(filename)[source]#
Read a presentation written by automatic_presentation_to_file.
The signature is not stored – a codec is Python code, not data – so the result is a bare structure until symbolic is given one.
- Parameters:
filename (str)
- symbolic(signature=None)[source]#
A symbolic interface to this structure: variables, relation and function symbols that build first-order expressions with Python operators instead of formula strings.
Elements are trees, so a signature’s codec encodes Python values to Tree objects. Enumeration is shortlex by node count, which orders by encoding size rather than by any notion of value.
- Parameters:
signature – declared functions, operators and element codec.
- Returns:
a autstr.symbolic.SymbolicContext.
- default_signature()[source]#
The signature symbolic() uses when none is given, or None for a structure addressed through its relation symbols. See autstr.symbolic.operation_signature.
- update(**automata)[source]#
Install or replace relations. Values may be automata (saturated and domain-restricted like at construction time) or formula strings over the current signature.
- Return type:
- evaluate(phi, updates=None)[source]#
Automaton of all satisfying assignments (tapes = sorted free variables; padding-saturated form).
- Parameters:
phi – the first-order formula.
updates (
Optional[Dict[str,Union[SparseTreeAutomaton,str]]]) – relations to install for this evaluation only. Values may be automata or formula strings over the current signature, and are prepared exactly as at construction time – so a spliced automaton is padding-saturated and domain-restricted before any projection sees it.
- Return type:
autstr.tree_uniform module#
Uniformly tree-automatic classes of structures.
The tree analog of autstr.uniform: a single tuple of tree automata presents a whole class of structures. Every automaton carries one additional tape (tape 0) holding an advice tree that is read convolution-synchronously with the element encodings. Fixing an advice tree t instantiates one member structure S_t:
universe(S_t) = { x | t ⊗ x ∈ L(U) } R^{S_t} = { x̄ | t ⊗ x̄ ∈ L(R) }
First-order queries are evaluated once for the entire class; model checking a sentence against a member structure then reduces to running its advice tree through the query automaton. Because the advice may be a tree, the member universes can live on tree-shaped skeletons — the setting for bounded tree-width graph classes and tree-indexed algebraic families.
The formula layer (relativization of quantifiers to Dom(advice, ·), the advice as one more first-order variable, Adv as the projection of the domain onto the advice tape) is inherited unchanged from the string implementation; only the automaton operations differ.
- autstr.tree_uniform.sta_from_delta(sigma, states, arity, delta, finals, dead='dead', tapes=None)[source]#
Build a SparseTreeAutomaton from a bottom-up transition function.
- Parameters:
delta – delta(left_state, right_state, symbol_tuple) -> state, where an absent child is passed as None. dead names the sink (stored as the sparse global default); dead children are never enumerated, so delta need not handle them.
tapes – optional per-tape alphabets. A convolution tape usually ranges over a small part of the base alphabet – the advice tape reads advice letters, an element tape reads element letters – and every mixed tuple is dead. Enumerating
sigma^aritytherefore spends almost all of its time confirming that nonsense is dead: for clique-width at k = 4 only 207 of 17576 triples are meaningful. Naming the tapes’ alphabets restricts the enumeration to their product; every other symbol falls to the global default, which is the dead sink. Defaults to sigma on every tape.
- Return type:
Each child pair’s majority target becomes its pair default (ties prefer the dead sink), and only deviations are stored as exceptions — states that loop on most symbols stay cheap over large alphabets. With tapes given, the unenumerated symbols already outnumber the rest, so the dead sink is the majority everywhere and no pair defaults are emitted.
- class autstr.tree_uniform.UniformlyTreeAutomaticClass(automata, padding_symbol='*', max_states=None)[source]#
Bases:
UniformlyAutomaticClassA uniformly tree-automatic presentation of a class of structures.
- Parameters:
automata (
Dict[str,SparseTreeAutomaton]) – dictionary of SparseTreeAutomatons. ‘U’ is reserved for the domain automaton of symbol arity 2 (advice tape, element tape). Every other key R presents a relation of arity r with an automaton of symbol arity 1 + r (advice tape first, then the element tapes).padding_symbol – base letter used to pad the convolutions.
max_states (
Optional[int]) – optional cap for the subset determinizations inside projections (a clear error instead of an exponential blowup).
- check(phi, advice, **assignments)[source]#
Model check a formula against the member structure S_advice.
- check_implicit(phi, advice, **assignments)[source]#
Model check a formula against the member S_advice without compiling a query tree automaton: the formula is evaluated bottom-up on the fly over the base tree automata. Scales to classes whose query automaton is infeasible. See autstr.implicit.
Unlike check, the on-the-fly evaluator is synchronous: it walks the advice tree and every assigned element tree in lockstep and does not convolve or pad mismatched shapes, so the assigned trees must already be padded to the advice’s shape.
- evaluate_implicit(phi, advice, **assignments)[source]#
The satisfying set of phi on the member S_advice, computed implicitly (no query tree automaton): unassigned free variables stay open and are solved for over the fixed advice. Returns a TreeSolutionSet of {var: tree} assignments (trees of the advice’s shape) — its len is the exact solution count, iterating lazily yields the assignments. See autstr.implicit.
- Parameters:
advice (Tree)
- define(name, phi)[source]#
Define a new class relation by a first-order formula over the existing signature. The relation’s arguments are the free variables of phi in sorted order; the advice moves back to tape 0.
- Return type:
- Parameters:
autstr.turing module#
Turing machines and their configuration graphs.
The configuration graph of a Turing machine is the standard example of an automatic structure that is genuinely about computation: a configuration is a word, and one step of the machine rewrites that word locally, at the head, so the step relation is recognized by a small synchronous automaton — one whose size depends only on the transition table, not on the tape.
That makes the first-order theory of the graph decidable, and this module lets you ask it:
>>> machine = TuringMachine(
... transitions={('q', '1'): ('q', '1', 'R'),
... ('q', '_'): ('halt', '1', 'S')},
... blank='_')
>>> graph = machine.configuration_graph()
>>> graph.check('all x.(all y.(all z.((E(x,y) & E(x,z)) -> Eq(y,z))))')
True
The boundary lesson. Reachability — “does some sequence of steps lead from
this configuration to a halting one?” — is exactly the halting problem, so it
is undecidable, and therefore not first-order definable over this graph.
Nothing here provides it, and nothing can: the transitive closure of E is
outside FO. A decidable first-order theory is not a decidable graph. It is the
same lesson as the random graph in reverse — there, a decidable theory without
an automatic presentation; here, an automatic presentation whose decidable
theory still cannot express the one question you would most like to ask.
Encoding. The tape is one-way infinite, to the right. A configuration is
the word left · (state, symbol) · right: the tape contents, with the cell
under the head replaced by a symbol naming both the state and what is written
there. Trailing blanks are dropped, so each configuration has exactly one word.
A step changes the word only at the head and, at most, by one cell at the right
end, which is why a synchronous automaton can read it. A left move off cell 0
has no successor, as does a configuration whose (state, symbol) pair the
transition table does not list — those are the halting configurations, and
Halt is defined from E rather than authored.
- autstr.turing.DIRECTIONS = ('L', 'R', 'S')#
how a transition may move the head
- class autstr.turing.Configuration(state, tape, head=0)[source]#
Bases:
objectA machine configuration: the control state, the tape, and where the head is.
- Parameters:
- class autstr.turing.TuringMachine(transitions, blank='_', states=None, tape_alphabet=None)[source]#
Bases:
objectA deterministic single-tape Turing machine with a one-way infinite tape.
- Parameters:
transitions (
Dict[Tuple[str,str],Tuple[str,str,str]]) –{(state, read): (state, write, direction)}, with direction one of'L','R','S'. A pair the table omits is halting.blank (
str) – the blank symbol, which fills the tape beyond what has been written.states (
Optional[Sequence[str]]) – the control states. Read off the table when omitted.tape_alphabet (
Optional[Sequence[str]]) – the tape symbols. Read off the table when omitted; the blank always belongs.
- canonical(configuration)[source]#
The configuration with trailing blanks past the head dropped — the one word that stands for it.
- Return type:
- Parameters:
configuration (Configuration)
- step(configuration)[source]#
The successor configuration, or None if there is none — the transition table does not cover this
(state, symbol)pair, or the head would move off the left end of the tape.This is the Python oracle the automaton is checked against, and the obvious way to run the machine.
- Return type:
- Parameters:
configuration (Configuration)
- run(configuration, limit=1000)[source]#
The run from a configuration: it, then its successors, stopping when the machine halts or after limit steps.
- Parameters:
configuration (Configuration)
limit (int)
- configuration_graph()[source]#
The graph of configurations under one step of the machine.
- Return type:
- encode(configuration)[source]#
The word encoding a configuration.
- Return type:
- Parameters:
configuration (Configuration)
- class autstr.turing.ConfigurationGraph(machine)[source]#
Bases:
objectThe configuration graph of a TuringMachine: configurations as vertices, one machine step as a directed edge.
The presentation carries
E(one step),Eq, andHalt— the configurations with no successor, defined asnot exists y. E(x,y)rather than authored, since the engine can compute it.Reachability is deliberately absent; see the module docstring.
- Parameters:
machine (TuringMachine)
autstr.uniform module#
Uniformly automatic classes of structures.
A uniformly automatic presentation describes a whole class of structures with a single tuple of automata: every automaton carries one additional tape (tape 0) holding an advice string that is read synchronously with the element encodings. Fixing an advice string α instantiates one member structure S_α:
universe(S_α) = { x | α ⊗ x ∈ L(U) } R^{S_α} = { x̄ | α ⊗ x̄ ∈ L(R) }
First-order queries are evaluated once for the entire class; model checking a sentence against a member structure then reduces to running its advice string through the query automaton.
- autstr.uniform.dfa_from_delta(sigma, states, arity, delta, initial, finals, tapes=None, dead=None)[source]#
Build a SparseDFA from a transition function over the full symbol space sigma^arity. Convenience for constructing presentation automata.
- Parameters:
tapes – optional per-tape alphabets (the string analog of sta_from_delta’s parameter). A convolution tape usually ranges over a small part of the base alphabet — the advice tape reads advice letters, an element tape reads element letters — and every mixed tuple is dead. Naming the tapes’ alphabets restricts the enumeration to their product; every other symbol falls to dead, which must then be named and becomes every state’s sparse default.
dead – name of the sink state (required with tapes); delta must map it to itself on every enumerated symbol.
- Return type:
- class autstr.uniform.SymbolicClassWrapper[source]#
Bases:
objectMixin for the family wrappers that present a class through
.cls.The wrappers (the group and algebra families) hold their uniformly automatic class in
.clsand otherwise offer a domain-specific API. This forwards the symbolic interface to that class and supplies the family’s operator vocabulary, so ExtraspecialGroups(3).symbolic() works directly instead of reaching into.cls.Subclasses set GRAPH and OPERATOR; the default is a multiplicative group, since that is what most of these families are.
- GRAPH = 'M'#
the ternary relation R(x, y, z) meaning
x op y = z
- OPERATOR = '*'#
the Python operator it binds to
- EQUALITY = None#
how equality is defined when the class does not ship it, as a formula over the existing signature; None if the class already has one
- class autstr.uniform.UniformlyAutomaticClass(automata, padding_symbol='*')[source]#
Bases:
DeferredRelationsA uniformly automatic presentation of a class of structures.
- Parameters:
automata (
Dict[str,SparseDFA]) – dictionary of SparseDFAs. ‘U’ is reserved for the domain automaton of symbol arity 2 (advice tape, element tape). Every other key R presents a relation of arity r with an automaton of symbol arity 1 + r (advice tape first, then the element tapes).padding_symbol – symbol used to pad the shorter tapes of a convolution.
- symbolic(signature=None)[source]#
A symbolic interface to this class. Expressions are written over the class signature exactly as for a single structure; the advice tape is added and quantifiers relativized to the member domain during compilation, and results carry the advice under the tape name
'advice'.- Parameters:
signature – declared functions and operators. An element codec has no advice-free meaning here and is not used.
- Returns:
a autstr.symbolic.SymbolicContext.
- default_signature()[source]#
The signature symbolic() uses when none is given, or None for a class addressed through its relation symbols. See autstr.symbolic.operation_signature.
- evaluate(phi)[source]#
Evaluate a first-order query over the class.
- Parameters:
phi (
Union[str,Expression]) – formula over the class signature; quantifiers range over the elements of the member structures.- Return type:
- Returns:
(dfa, variables) where dfa presents all satisfying assignments — its tapes are the advice followed by the free variables of phi — and variables names the tapes in order.
- check(phi, advice, **assignments)[source]#
Model check a formula against the member structure S_advice.
- Parameters:
phi (
Union[str,Expression]) – a formula over the class signature. Free variables can be assigned concrete elements via assignments (name = encoded word of the same length as the advice); unassigned free variables are existentially quantified.advice (
List) – the advice string (sequence of base alphabet symbols).
- Return type:
- check_implicit(phi, advice, **assignments)[source]#
Model check a formula against the member S_advice without compiling a query automaton: the formula is evaluated on the fly over the base automata (implicit product / on-the-fly powerset / acceptance flip). Same contract as check; scales to classes whose query automaton is infeasible to build. See autstr.implicit.
- Return type:
- evaluate_implicit(phi, advice, **assignments)[source]#
The satisfying set of phi on the member S_advice, computed implicitly (no query automaton): unassigned free variables stay open and are solved for over the fixed advice. Returns a StringSolutionSet of {var: word} assignments — its len is the exact solution count (no enumeration), iterating lazily yields the assignments. See autstr.implicit.
- define(name, phi)[source]#
Define a new class relation by a first-order formula over the existing signature (the uniform analog of a Büchi-style bootstrap). The relation’s arguments are the free variables of phi in sorted order; the advice stays on tape 0.