Skip to content

Performance architecture: measure, then optimise - #19

Merged
DiogoRibeiro7 merged 4 commits into
mainfrom
feat/performance
Aug 11, 2026
Merged

Performance architecture: measure, then optimise#19
DiogoRibeiro7 merged 4 commits into
mainfrom
feat/performance

Conversation

@DiogoRibeiro7

@DiogoRibeiro7 DiogoRibeiro7 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Benchmarks each phase of the pipeline before changing any of it, optimises
only what the measurements showed dominated, and warns callers before the
exponential phase rather than after.

Measuring first

benchmarks/profile_phases.py times truth-table construction, prime
generation, chart construction and cover solving separately, across four
dimensions that do not cost the same: cases, conditions, sufficient share
and remainder share. Each sweep is independently seeded, so --max-width
cannot silently shift the tables below it.

Two phases dominate in two regimes: remainder-heavy problems (the
parsimonious case) by prime generation, dense on-sets by the cover search.
Truth-table construction barely moves with the case count — 25x the cases
costs under 2x the time.

The harness also exposed a measurement bug in itself: it was paying for
prime generation twice, because build_chart regenerated what had just
been generated. build_chart now accepts pre-computed primes.

Optimising what dominated

Prime generation was the bottleneck, and it was spending its time
allocating tuples of optional bits. Representing cubes as (mask, value)
integer pairs instead makes it 141x faster at eight conditions
(1.4135s to 0.0100s), and 83x end to end (2.9913s to 0.0360s).

The algorithm is unchanged — this is a representation change, not a
different method. Every exactness, chart, R-parity, property and
multi-value test passes unchanged, which is what makes it safe to keep.

Implicant.combine is no longer called internally as a result. It stays as
public API and gains a direct test rather than being quietly deleted.

Warning before the cliff

Exact minimisation is worst-case exponential and no heuristic is
substituted when a problem gets hard — a silently approximate answer is
worse than a slow one. What is negotiable is being told in advance.

Once the primes are known the chart shape is known, so minimize now
raises MinimizationComplexityWarning before entering the exponential
phase. The run still completes and the answer is still exact. Disable with
complexity_guard=False.

The trigger is the prime count, not the condition count, with thresholds
read off the measurements: 53 primes solve in 0.0024s, 78 in 0.60s. The
integration tests lower the threshold rather than building a genuinely
explosive chart, since such a chart is by construction slow to solve.

Documentation

The complexity table in the minimisation guide quoted estimates, including
~36s for a ten-condition parsimonious solution. It now quotes benchmark
output, and that figure is 0.45s.

Verification

580 tests pass, 100% coverage, mypy strict and ruff clean, docs build
--strict.


Summary by cubic

Measured each pipeline phase, made prime generation drastically faster, and added an early complexity warning so long runs don’t surprise users. Also avoided double work by letting build_chart accept precomputed primes and updated the docs with measured timings.

  • New Features

    • Early warning: minimize(..., complexity_guard=True) emits MinimizationComplexityWarning before the exponential cover search when prime counts are high; silence via warnings.simplefilter or disable with complexity_guard=False. Exported at setqca and setqca.minimize.
    • Benchmark harness: benchmarks/profile_phases.py measures truth-table, primes, chart, and cover across cases, conditions, sufficient share, and remainders (independently seeded).
    • Chart API: build_chart(..., primes=...) accepts precomputed primes to avoid regenerating them.
  • Refactors

    • Prime generation now uses (mask, value) integer bitmasks instead of tuples, making it 141x faster at 8 conditions (1.41s → 0.010s) and 83x end-to-end (2.99s → 0.036s). Algorithm unchanged; Implicant.combine remains public and is directly tested.
    • Docs updated with benchmark results; 10-condition parsimonious run now measures ~0.45s instead of ~36s.

Written for commit c1d2159. Summary will update on new commits.

Review in cubic

Times truth-table construction, prime generation, chart construction and
cover solving separately, across cases, conditions, sufficient share and
remainder share. Each sweep is independently seeded so --max-width cannot
silently change the tables below it.

build_chart accepts pre-computed primes so the harness does not count
prime generation twice.
Represents cubes as (mask, value) integer pairs rather than tuples of
optional bits. Same algorithm, 141x faster at eight conditions
(1.41s to 0.010s) and 83x end to end.

Implicant.combine is no longer used internally but remains public API,
so it gains a direct test.
minimize checks the chart shape once the primes are known but before the
exponential cover search begins, so a slow run says so rather than
appearing to hang. The run still completes and the answer is still exact.

The prime count is the trigger, with thresholds taken from measurement:
53 primes solve in 0.0024s, 78 in 0.60s. Disable with
complexity_guard=False.
Replaces the estimated complexity table with benchmark output. The
ten-condition parsimonious solution now takes 0.45s rather than the ~36s
the old table quoted.
@DiogoRibeiro7
DiogoRibeiro7 merged commit bb20316 into main Aug 11, 2026
17 checks passed
@DiogoRibeiro7
DiogoRibeiro7 deleted the feat/performance branch August 11, 2026 11:18
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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