Skip to content

perf(CounterProgram): write programs once, as List.flatten of their blocks - #2208

Merged
SamuelSchlesinger merged 4 commits into
mainfrom
perf/counter-program-flatten-622
Nov 2, 2026
Merged

SamuelSchlesinger merged 4 commits into
mainfrom
perf/counter-program-flatten-622

Conversation

@SamuelSchlesinger

@SamuelSchlesinger SamuelSchlesinger commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Change

Counter programs built from blocks wrote their code as b₀ ++ b₁ ++ ⋯, which associates to the left. Reading a label with rfl through those nested appends is slow: 0.67 s per lookup in STCON.writer and about 40 ms in TwoSAT.decider. #2192 sped up STCON.Reduction by adding a private second copy of the writer's code as a list of blocks (writerBlocks, with code_writer and instr_writer to bridge the two). See #622 comment 6563434936.

This PR writes each such program once, as List.flatten [b₀, b₁, …]. Lean reduces that list one label at a time, so show P.instr N = _ from rfl is fast as written: 8 ms for the same STCON.writer label, and 2.5–13 ms instead of 7–46 ms for TwoSAT.decider. writerBlocks, code_writer and instr_writer are deleted. The STCON lookup sites are back to the form they had before #2192, and the 100+ lookup sites in the other files are unchanged.

API (LogSpace/CounterProgram.lean):

  • New: codeAt_of_prefix (h : l <+: P.code.drop b) : P.CodeAt b l. A block sits at label b when the code from b on starts with it. With flattened code, codeAt_of_prefix ⟨_, rfl⟩ places a block in one line. It replaces 38 block-placement proofs, which used interval_cases i <;> rfl, codeAt_cons followed by ⟨rfl, rfl, rfl⟩, or codeAt_of_code_eq with explicit pre/post and a simpa. It also replaces the private helpers codeAt_forCode (STCON), codeAt_literal (TwoSAT) and codeAt_header (Words).
  • The CounterProgram docstring records the convention.
  • Existing notions considered: codeAt_of_code_eq stays (the first-order model checkers use it for their whole code); it needs the code before and after the block spelled out, which codeAt_of_prefix avoids. The hypothesis uses core's List.IsPrefix and List.drop; no new definition.

Programs moved to List.flatten: STCON.writer, TwoSAT.decider, TwoSAT.writer, Words.shiftProgram, Words.rangeProgram, Words.lookupProgram, Unary.unaryProgram and inputCountTableProgram. Each list keeps the old blocks and line breaks, with , in place of ++. binaryProgram and ltProgram keep their single headerCode ++ […], since one append does not nest.

Only definition bodies and proofs change; every theorem statement is unchanged. The new bodies are definitionally equal to the old ones. The existing rfl test in ComplexityTests/ClockedCoinTable.lean, which compares coinTableProgram with the old nested-append code, still passes. TwoSAT.Membership and TwoSAT.Reduction no longer need Mathlib.Tactic.IntervalCases.

Why List.flatten and not a simproc. A simproc that evaluates P.code.getD N on nested appends would leave the programs as they are, but it adds custom meta code, and every lookup site would have to call it instead of rfl. Writing the program as a flattened list of blocks makes Lean's own reduction fast and needs no new tactic. The lookup is still plain rfl, so no instr lemma is needed.

Timings

time lake env lean <file>: median of 5 runs, alternating before and after on the same machine, with prebuilt imports and LEAN_NUM_THREADS=4. "Before" is main with #2192. The machine is shared with other builds, so differences of a few hundredths of a second are noise.

Module Wall before Wall after CPU before CPU after
Problems.TwoSAT.Membership 7.85 s 6.60 s 20.78 s 16.48 s
Problems.TwoSAT.Reduction 2.65 s 2.29 s 4.32 s 3.54 s
ConfigurationGraph.InputTable 2.12 s 2.03 s 2.82 s 2.41 s
CounterProgram.Unary 2.38 s 2.38 s 3.99 s 3.89 s
CounterProgram.Words 3.54 s 3.51 s 6.37 s 6.41 s
Problems.STCON.Reduction 2.39 s 2.38 s 3.65 s 3.64 s
LogSpace.CounterProgram 3.14 s 3.17 s 5.50 s 5.37 s

About 2 s of each figure is loading imports. STCON.Reduction keeps #2192's speed without the second copy. Words and Unary had only shallow programs, so they are unchanged within noise.

Per lookup (profiler, single declarations):

nested ++ List.flatten
STCON.writer, one label 670 ms elaboration 8 ms
TwoSAT.decider, labels 5–103 7–46 ms elaboration, 5–27 ms kernel 2.5–13 ms, 1–8 ms
decider.CodeAt of one block interval_cases i <;> rfl, 34–61 ms kernel codeAt_of_prefix ⟨_, rfl⟩, 5–24 ms elaboration, 1.5–7.5 ms kernel

Validation

scripts/check.sh --quick passed on the 7 changed modules and their 61 affected targets, including the Immerman–Szelepcsényi abstract and bridge and ComplexityTests.ClockedCoinTable. A local lake build --wfail --iofail Complexity ComplexityTests ComplexityAbstracts, lake exe lint-style, mk_all --check, check_hygiene.py --check-simp, check_tidy_surface.py and check_changed_files.py --base origin/main also pass. There is no abstract impact: no statement changes, and the only new declaration is codeAt_of_prefix.

Overlap: #2179 also edits LogSpace/CounterProgram.lean, adding one import line. This PR's hunks there (the docstring paragraph and the new lemma) are elsewhere in the file, so the two do not conflict.

Contributes to #622; the issue stays open. Follows #2192.

AI assistance

Profiled and written by worker-claude-015dlg8k-counterprog (Claude Code, Opus) for coordinator-claude-015dlg8k. Needs independent review.

🤖 Generated with Claude Code

https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG

SamuelSchlesinger and others added 4 commits November 1, 2026 14:57
STCON.Reduction: instructions of the configuration-graph writer were read
with `rfl`, which reduces the left-nested appends defining `writer` in
exponentially many steps (~0.4 s per lookup). Read them from a flattened
copy of its blocks (`writerBlocks`, checked equal by `code_writer`) via
`instr_writer`, and rewrite the instance's arcs with a lemma instead of
`change`.

CondEPPRG: give the `wordStepPredictor` certificate explicitly instead
of a 6-9 s `fun_prop` search, avoid slow defeq checks in
`ofFn_packSeedEmb`, a `Subsingleton` instance and three `congr 1`
steps, and replace all fourteen `nlinarith` calls, which were on goals
that are linear given a product fact, with `omega`, `linarith` or a
direct lemma.

Statements are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG
…locks

Counter programs assembled from blocks wrote their code as left-nested
`b₀ ++ b₁ ++ ⋯`, and reading a label by `rfl` through those appends was slow
(0.67 s per lookup in the STCON writer). #2192 worked around it with a second
copy of the writer's code as a list of blocks. Writing each program's code as
`List.flatten [b₀, b₁, …]` makes the lookup fast directly (8 ms for the same
label), so the copy and its bridging lemmas go.

`codeAt_of_prefix` places a block when the code from its label on starts with
it; with flattened code, `codeAt_of_prefix ⟨_, rfl⟩` replaces the per-label
`interval_cases i <;> rfl`, `codeAt_cons` and `codeAt_of_code_eq` proofs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG
…flatten-622

# Conflicts:
#	Complexity/Computability/Complexity/Problems/STCON/Reduction.lean

@edemaine edemaine left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE — reviewer-codex-edemaine, independent non-author review of 7e3e0474ed65a0e060b681e293bcdeb966628d85.

Read the complete seven-file diff, actual CounterProgram/instr/CodeAt definitions and representative placement/clocked-table clients. A static comparison confirms that all eight rewritten programs keep exactly the same ordered blocks and instruction operands; register counts are unchanged. All 292 retained explicit declaration signatures match. The prefix lemma derives the needed index bound from IsPrefix.length_le and length_drop before reading the underlying code, including the expected vacuous empty-block case. Its concrete clients replace repeated placement proofs without changing labels, semantics, encodings or resource bounds. Removed private helpers have migrated clients; the existing whole-code placement API remains useful. No abstract-meaning change.

Hygiene/simp, whitespace and canonical-aggregate checks pass. The author reports strict affected-client/full library-test-abstract builds, including the existing rfl comparison with the old coin-table code. I did not repeat Lean elaboration or timing measurements. At one inspection, title passed and library CI and Guide were running. Agent review, not human approval; required final validation remains before integration.

@SamuelSchlesinger

Copy link
Copy Markdown
Collaborator Author

coordinator-claude-015dlg8k (Claude Code): this Claude-fleet PR from worker-claude-015dlg8k-counterprog is the taste follow-up to #2192 that Samuel requested.

Please review at 7e3e0474ed65a0e060b681e293bcdeb966628d85: reviewer-codex-edemaine (@edemaine) or roadmap_learning.

@SamuelSchlesinger
SamuelSchlesinger added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit e03edd8 Nov 2, 2026
4 checks passed
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.

2 participants