Skip to content

feat(optimize): optimal mode segmentation (ISO/IEC 18004 Annex J)#148

Open
gerchowl wants to merge 3 commits into
sorairolake:mainfrom
gerchowl:feat/annex-j-optimal-segmentation
Open

feat(optimize): optimal mode segmentation (ISO/IEC 18004 Annex J)#148
gerchowl wants to merge 3 commits into
sorairolake:mainfrom
gerchowl:feat/annex-j-optimal-segmentation

Conversation

@gerchowl

Copy link
Copy Markdown

Description

Implements globally optimal mode segmentation (ISO/IEC 18004 Annex J) as a
dynamic program over parser run boundaries, replacing the greedy +
single-mode-clamp heuristic inside optimize_segments. dp[b] is the minimum
encoded bits for the first b parsed runs; each transition merges a contiguous
run span [a, b) into one segment using the lowest common mode, costed by the
exact Segment::encoded_len. Optimal segment boundaries are always a subset of
run boundaries (splitting a maximal same-class run only adds header overhead),
so the search is exact-optimal while reusing the existing per-segment bit
accounting. The public greedy Optimizer / Parser::optimize API is unchanged.

Runs in O(k^2) time / O(k) space, where k is the parsed run count.

Closes #146

Additional context

Note

Stacked on #147 (the minimal clamp fix). Until that merges, this PR's diff
also includes the clamp commit; I'll rebase once #147 lands — or this can be
merged standalone, in which case it supersedes #147 (close that one). Happy to
reshape into whichever you prefer.

If an O(n) optimizer is preferred over O(k^2), the alternative is a
per-character Nayuki-style DP (O(n * modes)); I chose the run-boundary DP
because it reuses the exact encoded_len cost and avoids re-deriving the
grouped (div_ceil) bit accounting. Happy to switch if you'd rather.

Validation:

  • Brute-force reference test enumerating every run-boundary segmentation,
    asserting the DP equals the exhaustive optimum across mixed-mode inputs and
    multiple versions, plus cases where the DP strictly beats both greedy and the
    single-mode encoding.
  • benches/optimize.rs scaling benchmark: confirms O(k^2) on adversarial
    alternating input and sub-microsecond cost on realistic few-run input.
  • End-to-end round trips with external decoders: 2000+ random 14-char
    alphanumeric IDs via rqrr (full QR) and 500+ via rxing (Micro QR M3), all
    byte-exact.

cargo +nightly fmt, clippy, and the library test suite all pass.

Checklist

gerchowl added 3 commits June 17, 2026 10:00
The greedy optimizer can split a mixed-mode payload into segments whose
combined mode/character-count header overhead exceeds the symbol capacity,
even though encoding the whole payload as a single segment fits. For
low-capacity Micro QR symbols this caused valid payloads to be falsely
rejected with DataTooLong (pinned version) or silently encoded into a
larger symbol than necessary (auto-version path).

Add optimize::optimize / optimize_segments, which run the existing greedy
merge and then return the single-mode baseline (one segment in the lowest
common mode) whenever it is smaller. The result is never worse than greedy
and never worse than the trivial single-mode encoding, so it cannot regress
any currently-working encoding. Route all encode paths (push_optimal_data,
encode_auto, encode_auto_micro, encode_auto_rect_micro) through it.

For example "9BA3935DM3TBE4" (14 alphanumeric characters) now encodes as a
Micro QR M3-L symbol (83 <= 84 data bits) instead of being rejected.

Add regression tests plus a data-driven capacity-boundary conformance test
across Micro, Normal, and rMQR versions.
…x J)

Replace the greedy + single-mode-clamp body of optimize_segments with a
dynamic program over parser run boundaries that finds the globally optimal
mode segmentation. dp[b] is the minimum encoded bits for the first b runs;
each transition merges a contiguous run span into one segment using the
lowest common mode, costed by the exact Segment::encoded_len. Optimal
segment boundaries are a subset of run boundaries, so this is exact-optimal
while reusing the existing per-segment bit accounting.

The public greedy Optimizer / Parser::optimize API is unchanged; only the
internal segmentation used by the encode paths is upgraded. The result is
never worse than greedy and never worse than the single-mode encoding, both
of which are candidate segmentations the DP dominates.

Add a brute-force reference test that enumerates all run-boundary
segmentations and asserts the DP matches the exhaustive optimum across
mixed-mode inputs and multiple versions, plus cases where the DP strictly
beats both greedy and single-mode.

Runs in O(k^2) time / O(k) space, k = parsed run count.
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.

Micro QR: spurious DataTooLong / needless upsizing for payloads that fit (suboptimal mode segmentation)

1 participant