Skip to content

Release preparation for v4.0.0 - #45

Merged
fariedabuzaid merged 9 commits into
devfrom
release-v4-prep
Aug 13, 2026
Merged

Release preparation for v4.0.0#45
fariedabuzaid merged 9 commits into
devfrom
release-v4-prep

Conversation

@fariedabuzaid

@fariedabuzaid fariedabuzaid commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Release preparation for v4.0.0. The theme is that queries stop being
strings and structures start declaring their own vocabulary — and that the
package stops pretending some of its structures are more "built in" than
others.

The two proposals, checked first

The precompiled .autstr artifacts are gone. Load vs build from scratch was
0.017s/0.049s (Büchi ℕ), 0.023s/0.252s (Büchi ℤ), 0.022s/0.458s (MSO0), and the
built presentations are equivalent to the loaded ones relation by relation
with identical state counts. They bought 0.03–0.44s while being a second source
of truth that could silently drift.

The "arithmetic interface" was already gone — deleted a release earlier by
the symbolic rewrite. What looked like a live second interface was the README's
quick start, which still imported VariableETerm and had been broken code ever
since. The 85 lines that remained were the symbolic interface applied to ℤ.

Asking why it existed at all found the real asymmetry: nine structures
declared a default_signature; the three in buildin could not
, because they
were plain functions returning a bare presentation with nowhere to hang one. So
the signature moved onto the presentations and the module became redundant.

autstr.buildin is dissolved

Every structure the library ships is built in, so the name carved nothing:

was now
buildin.presentations autstr.arithmetic (Büchi ℕ/ℤ) + autstr.powerset (MSO0)
buildin.tree_presentations autstr.tree_arithmetic
buildin.automata autstr.utils.automata_tools (which already imported one from it)

All three presentations gained a codec, each verified against the automata
rather than assumed
: ℕ is LSB-first binary (checked against A(x,y,z)), and
MSO0 is a bitmask in canonical form — the universe rejects trailing zeros, so
{0} is 1 and ∅ is the empty word (checked against Subset over all subsets
of {0,1,2}). MSO0 thereby gains an interface it never had: union, intersection
and difference as +, *, -, with solutions returned as Python sets.

Three latent bugs, all found by writing examples

None of these had a failing test; each was exposed by a notebook cell.

  1. Enumeration was broken for every interpreted structure of dimension > 1,
    including the shipped Ordinal(2). iterate_language built each tape by
    string-concatenating letters, which holds only while a letter is one
    character; over a product alphabet a letter is a tuple, and str() flattened
    ('0','1') into text no codec could read. Membership worked throughout,
    which is why it went unseen.
  2. Such a presentation could not be reloaded after serialization. JSON has
    no tuples, so the alphabet came back as a set of lists (unhashable) and the
    padding symbol as a list.
  3. The v2 back-compat loader lost its only test when the artifacts went
    away: test_the_legacy_flat_payload_still_loads was left building a
    presentation and querying it, touching no legacy payload at all. It now
    constructs a v2 payload by hand.

Plus a groups.ipynb cell labelled "has an element of order 4" that asked for
order 2 — Z/2 alone satisfied it.

Serialization for the tree engine

The expensive constructions are the tree ones, so this was on the wrong side of
the line. A tree automaton's compiled form is a sorted table of child pairs with
one diagram root each, and STORE.export/import_nodes are generic over roots,
so the diagram half needed no new code. The collapsible Reach presentation is
22709 bytes, reloads in 0.01s against 0.5s to rebuild, and the reloaded relation
is still reflexive, contains E, and is transitive.

Notebooks and documentation

Six notebooks, not five. infinite_structures is new — the integer grid, the
regular tree, Turing configuration graphs, and level 2 collapsible pushdown
graphs, where the contrast is the point: both present computation as a graph and
both have a decidable first-order theory, but reachability is halting for one
and a relation of the other. composition gained interpretations, built around
the construction of ℤ from ℕ as a 2-dimensional quotient interpretation, with
the codec as the step that makes it legible.

The changelog moved to CHANGELOG.md (and a docs page), leaving the README with
what only it can say. References went the other way: four in the README, the
full annotated bibliography in the docs, cited at the point of use — which
exposed that the overview had no section on any v4 material. It now has two.

Checks

  • 855 passed, 5 skipped.
  • Docs build clean under -W --keep-going, every notebook executed, all
    cross-references resolving.
  • The history animation is regenerated through this release.

Every bibliography entry carries a DOI, checked against the publisher or the
journal rather than supplied from memory. One annotation was wrong and is
fixed: a publisher's abstract page claims rank-width is not defined in Oum &
Seymour 2006, which would have made it the wrong citation for RankWidthClass
— Oum's own survey settles it ("Rank-width was introduced by Oum and Seymour
[71]", where [71] is that paper). Kartzow's issue was also off, 9(1:12) rather
than 9(1).

Generated with Claude Code

fariedabuzaid and others added 9 commits August 13, 2026 20:35
Two changes that turn out to be one. `.symbolic()` could be called with no
arguments on nine of the library's structures, because each declares a
`default_signature` naming its operators and the codec that turns Python values
into elements. It could not on Büchi arithmetic or MSO0: those were built by
plain functions returning a bare presentation, with nowhere to hang a
signature. `autstr.arithmetic` existed to fill that gap -- for ONE of the three,
which is what made it look like a second, rival interface.

So the signature moves onto the presentation, where the rest of the library
keeps it, and the module that worked around its absence goes away. All three
gain their codec, verified against the automata rather than assumed:

  - N: binary, least significant bit first (checked against A(x,y,z)),
  - Z: sign symbol then magnitude (as before),
  - MSO0: a set as a bitmask, position i set iff i is a member, in CANONICAL
    form -- the universe rejects trailing zeros, so {0} is `1` and the empty
    set is the empty word (checked against Subset over all subsets of {0,1,2}).

MSO0 therefore gains an interface it never had: union, intersection and
difference as `+`, `*`, `-`, and solutions that come back as Python sets.

`buildin` was the other half of the same confusion: every structure the library
ships is built in, so the name carved nothing. Its contents move to where their
subject already lives -- `arithmetic` (Buechi N and Z), `powerset` (MSO0),
`tree_arithmetic` (Skolem, matching algebra/tree_algebra), and the generic
automaton constructors to `utils.automata_tools`, which was already importing
`one` from across the package boundary. `CompiledPresentation` holds the part
the presentations share.

The precompiled `.autstr` artifacts go too. They saved 0.03s, 0.23s and 0.44s
against building from scratch, and the built presentations are identical to the
loaded ones relation by relation. Serialization stays fully supported; it just
stops shipping artifacts nobody needed.

BREAKING: `autstr.buildin.*` is gone, as are `autstr.arithmetic.integers` and
`signature`; encode/decode are now classmethods on the presentations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The abelian-groups cell was labelled "has an element of order 4" and asked for
an element of order 2: `A(x,x,x)` spells x = 0, so `A(x,x,z) and A(z,z,z)` says
2x = 0. Z/2 alone satisfied it. The corrected formula asks for y = 2x, z = 2y = 0
with x and y both nonzero, which separates the two groups of order 4 -- Z/4 has
such an element, Z/2 + Z/2 does not.

That cell is also where the symbolic interface earns its place: with `+` and
`.eq` the query reads as the mathematics, and a class has no constant symbols,
so the identity is named by what defines it (t + t = t).

MSO0 gains its codec's payoff: union, intersection and difference as `+`, `*`,
`-`, membership and subset as methods, and solutions that come back as Python
sets -- the eight ways to split {0,1,2} in two, enumerated from the automaton.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The composition notebook becomes "building new structures from old": the
integers constructed from the naturals as a 2-dimensional quotient
interpretation, which is small enough to read and real enough to be worth
saving. It carries the codec story -- the construction is correct but
illegible until a codec says that the pair (a, b) denotes a - b -- and closes
on the shipped example of the same idea, `autstr.ordinals`.

A new notebook, `infinite_structures`, covers what had none: the integer grid,
the regular tree, Turing configuration graphs and level 2 collapsible pushdown
graphs. The last two are the point. Both present computation as a graph and
both have a decidable first-order theory, but reachability is the halting
problem for one and a relation of the other -- so `Reach` is there to be
queried, and transitivity over all runs of all lengths is decided in under a
second.

Writing those two turned up three real defects, none of which any test saw:

  - ENUMERATION WAS BROKEN for every interpreted structure of dimension > 1,
    including the shipped `Ordinal(2)`. `iterate_language` built each tape by
    string-concatenating letters, which holds only while a letter is one
    character; over a product alphabet a letter is a tuple, and str() flattened
    ('0','1') into text no codec could read. Tapes are tuples of letters now.
    Membership worked throughout, which is why this went unseen.

  - SERIALIZING SUCH A PRESENTATION could not be reloaded: JSON has no tuples,
    so the alphabet came back as a set of lists (unhashable) and the padding
    symbol as a list. Both are restored on load.

  - The v2 back-compat loader lost its only test when the `.autstr` artifacts
    went away -- `test_the_legacy_flat_payload_still_loads` was left building a
    presentation and querying it, touching no legacy payload at all. It now
    constructs a v2 payload by hand and reads it.

Also: the abelian-groups notebook cell and `is_deterministic`'s docstring, which
still said reachability was not built.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The countable atomless Boolean algebra had no notebook coverage. It sits next to
the finite Boolean algebras, where the contrast is the point: finite ones are
powersets and full of atoms, and dropping finiteness leaves exactly one
countable algebra in which every nonzero element splits forever. Its elements
are trees, so it is the tree engine's turn.

Bipartiteness in the graphs notebook now appears twice: as the formula string,
and built from `symbolic()` with `.implies`, `.all` and `.drop`. A set variable
is just a variable, which is what makes the query monadic second-order, and the
symbolic form makes that legible instead of nesting six parentheses. Checked to
agree with the string form on all three of that cell's graphs.

`infinite_structures` joins the documentation's notebook toctree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README carried the whole release history, which made it long and made the
history shallow -- there was never room to say what a release actually changed.
CHANGELOG.md takes it over, with v4.0.0 written out properly: what was added,
what moved, what was fixed, and an upgrade path from 3.x. The docs build copies
it in, so it has a page on the site too.

What stays in the README is what only the README can say: the AI-assisted
algorithm engineering narrative, the gource animation, the highlights of the
newest version, and a link to the rest.

The quick start now opens on the symbolic interface -- which is the honest
entry point, and was also the one broken example in the file, since it still
imported a class that the arithmetic rewrite deleted a release ago.

Version 4.0.0 rather than 3.2: `autstr.buildin` is gone, `autstr.arithmetic`
holds something else, and the old term-algebra front end has no replacement of
the same shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gource over the full commit history, now reaching the symbolic layer, the
interpretations, the infinite graphs and the collapsible pushdown work. The
sieve automaton is unchanged, byte for byte -- the example it renders did not
move.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The string side had it and the tree side did not, which put the expensive
constructions on the wrong side of the line: a tree relation is where the cost
lives, and `Reach` for a collapsible pushdown graph is exponential in the
system's control states.

It turned out to be a short walk. A tree automaton's compiled form is a sorted
table of child pairs with one decision-diagram root each, so the payload is the
pair keys plus the shared sub-DAG below those roots -- and `STORE.export` /
`import_nodes` are generic over roots, so the diagram half needed no new code.
A relation over a convolution alphabet too wide to enumerate still writes out
in the size of its diagrams, exactly as on the string side.

Two deliberate differences from the string presentation serializer: each
automaton's payload is stored as raw bytes with a length prefix rather than as
a JSON list of integers (which costs about four bytes of file per byte of
data), and the JSON-has-no-tuples fix moved to `utils.misc` instead of being
copied, since both engines need it now.

Measured on the case it exists for: the collapsible `Reach` presentation is
22709 bytes, reloads in 0.01s against 0.5s to rebuild, and the reloaded
relation is still reflexive, still contains the edge relation, and is still
transitive. Skolem arithmetic round-trips and answers identical queries; a
flipped payload byte is caught by the checksum.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README carried eleven references and the documentation none, which is
backwards -- a reader of the docs meets the constructions and has nowhere to
follow them. `references.md` now holds the bibliography, grouped and annotated
with what each work IS in this library, and the README keeps the four it leans
on hardest plus a link.

Citing where it was due exposed a gap: the overview had no section on any of
the v4 material. It gains two -- interpretations (with the tree quotient's
least description, and the ordinals as the payoff) and infinite graphs (the
grid, the tree, Turing configuration graphs, and the collapsible pushdown
graphs where reachability comes back). Each names its source at the point of
use: Kuske & Weidner and Colcombet & Loeding on tree quotients, Delhomme on why
`Ordinal` takes an exponent, Kartzow on the encoding and the run decomposition,
Courcelle on the linear-time claim, Abu Zaid/Graedel/Reinhardt on advice.

Three entries had no bibliographic details anywhere in the repository and could
not be verified offline (Delhomme 2004, Oum & Seymour 2006, Colcombet & Loeding
2007); they carry author, title and venue but no DOI, pending a check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
All four now carry a DOI, and one annotation was wrong.

  Delhomme 2004  -> 10.1016/j.crma.2004.03.035  (CR Math. 339(1), 5-10)
  Oum & Seymour  -> 10.1016/j.jctb.2005.10.006  (JCTB 96(4), 514-528)
  Colcombet &    -> 10.2168/LMCS-3(2:4)2007     (LMCS 3(2:4), 1-36)
    Loeding
  Kuske &        -> 10.1007/978-3-642-22993-0_39 (LNCS 6907, 424-435)
    Weidner
  Kartzow        -> 10.2168/LMCS-9(1:12)2013    (issue was 9(1:12), not 9(1))

Oum & Seymour is the one that needed care. A publisher's abstract page claims
rank-width is not defined there, which would have made it the wrong citation
for `RankWidthClass`; Oum's own later survey settles it -- "Rank-width was
introduced by Oum and Seymour [71]", where [71] is exactly this paper. The
entry now says so, as the branch-width of the cut-rank function.

Colcombet and Loeding's role is stated more carefully too: the existence of
injective presentations for tree-automatic structures, which is what makes a
quotient interpretation well posed over trees, with Kuske and Weidner making it
effective.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fariedabuzaid
fariedabuzaid merged commit 055109f into dev Aug 13, 2026
4 checks passed
@fariedabuzaid
fariedabuzaid deleted the release-v4-prep branch August 13, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant