Case Studies

Small worked examples with measured numbers: how many expressions were processed, the size before and after, the verification status, and the time and memory it took. Every figure below was produced by the commands shown — nothing is estimated.

Note

Measured on Windows 11 x64, .NET SDK 10.0.301, Release build, single-threaded, after a warm-up call. Literal counts are machine-independent; time and memory are not — treat them as an order of magnitude, not a specification. Memory is bytes allocated on the calling thread for the call (OptimizationMetrics.AllocatedBytes). Peak working set is recorded by the comparison harness (peakWorkingSetBytes in doc/comparison/our-results.json) at process level: it is a monotone per-process high-water mark, so it cannot be attributed to a single call and deliberately stays out of the per-call OptimizationMetrics — see doc/COMPARISON_METHODOLOGY.md for the attribution caveat.

1. A generated entitlement condition

A code generator expands a plan/feature model into a guard. The condition is correct but verbose, and it is evaluated on every request.

Input (7 variables, 14 literals):

(!sso | pro) & (!audit | pro) & (pro | free) & !(pro & free) &
(pro & seats | pro & unlimited) & (!trial | free)

Result:

pro & !free & !trial & (seats | unlimited)
Metric Value
Variables 7
Literals 14 → 5 (−64%)
AST nodes 27 → 9
Verification equivalent to the input, proven
Minimality MinimalProven
Time 2.1 ms
Allocated 749 KiB

The interesting part is not the shrink — it is that MinimalProven says no smaller two-level cover exists, and the equivalence proof rules out the classic failure mode of hand-simplifying a generated guard: silently dropping a case (!trial, here).

2. A duplicated templated guard

Templating engines emit the same sub-condition once per branch. Five variables, four near-identical terms.

Input (12 literals) → result (5 literals):

region_eu & tier_gold & !suspended | region_eu & tier_silver & !suspended |
region_us & tier_gold & !suspended | region_us & tier_silver & !suspended
-->
!suspended & (region_eu | region_us) & (tier_gold | tier_silver)
Metric Value
Literals 12 → 5 (−58%)
AST nodes 21 → 9
Verification proven equivalent
Minimality MinimalProven
Time 1.7 ms · 564 KiB

3. A wide rule set, past the exact gate

Fourteen variables is beyond the exact-minimization gate, so the honest outcome is a smaller expression without a minimality claim — which is exactly what is reported.

Input (18 literals) → result (16 literals):

a1&a2 | a3&a4 | a5&a6 | a7&a8 | a9&a10 | a11&a12 | a13&a14 | a1&a4 | a5&a8
-->
a10 & a9 | a11 & a12 | a13 & a14 | a3 & a4 | a7 & a8 | a1 & (a2 | a4) | a5 & (a6 | a8)
Metric Value
Variables 14
Literals 18 → 16
Verification proven equivalent (SAT miter — no 2ⁿ table)
Minimality Heuristicnot claimed as optimal
Time 12.9 ms · 5.8 MiB

This is the case that distinguishes the library from a tool that just returns something smaller: the modest 18 → 16 gain is reported as heuristic, and the trace shows which engine ran and why no proof was available.

4. A business-rule regression check

A refactor of an access rule drops the business-hours guard for owners. The check is the regression test.

var check = EquivalenceChecker.Check("admin | (owner & businessHours)", "admin | owner");
// AreEquivalent = false
// Counterexample: admin=0, businessHours=0, owner=1
Metric Value
Verdict not equivalent — behaviour changed
Counterexample admin=0, businessHours=0, owner=1
Time 0.3 ms · 11 KiB

The counterexample is the whole value: it names the exact input where the refactor grants access it should not, and drops straight into a unit test. The same check runs without writing code via the CLI's check verb — the verdict lands in the exit code (0 equivalent, 3 not equivalent), so it slots directly into a CI step.

5. Bulk run over the committed corpus

Seventeen functions from 2 to 24 variables, one command, showing how status and cost track the zone model.

dotnet run -c Release --project LogicalOptimizer.Benchmarks -- compare
Zone Function Vars Input literals Result literals Status Time (ms)
small maj3 3 6 5 MinimalProven 0.7
small consensus3 3 6 4 MinimalProven 1.0
small xor2 2 4 4 MinimalProven 0.2
small xor3 3 12 10 MinimalProven 2.3
small maj4 4 12 8 MinimalProven 4.9
small mux2 3 4 4 MinimalProven 0.2
small pos6 6 6 6 MinimalProven 2.3
small eq5 5 10 10 MinimalProven 1.9
small pairs8 8 8 8 MinimalProven 2.7
small pairs10 10 10 10 MinimalProven 9.4
mid pairs12 12 12 12 BudgetExceeded 20.4
mid collapse14 14 28 7 Heuristic 1.7
mid pairs16 16 16 16 Heuristic 5.7
mid collapse18 18 36 9 Heuristic 2.9
mid pairs20 20 20 20 Heuristic 8.9
mid chain22 22 60 40 Heuristic 66.1
mid pairs24 24 24 24 Heuristic 12.1

What to read from it. Every function in the guarantee zone comes back MinimalProven. pairs12 is the honest edge case: 12 variables is inside the exact range but outside the unbounded guarantee, the cover search hit its budget, and the status says BudgetExceeded rather than pretending to a proof. Past the gate the results stay verified but heuristic — collapse14 (28 → 7) and collapse18 (36 → 9) show the heuristic path still finds large structural wins, while pairs16/pairs20/pairs24 are already minimal and correctly left alone. Nothing in the run exceeds ~66 ms.

Why LogicalOptimizer for these

All five cases share one requirement: the answer must be trustworthy inside a .NET process.

  • Every result is equivalence-verified before return, and optimality is either proven or explicitly not claimed — so the output can be shipped into a generated guard without a human re-deriving the truth table.
  • No native dependency, no JVM, no Python: the same code runs in a service, a build task, or a Native-AOT single-file binary.
  • Counterexamples turn "these rules differ" into an actionable failing test.

For arithmetic constraints (Z3), competition-scale SAT (Kissat/CaDiCaL) or industrial synthesis (ABC), pick those instead — see Choosing a Tool.

Reproduce

// Cases 1-3: size, status, time and memory
var options = new OptimizationOptions { IncludeMetrics = true };
var result = new BooleanExpressionOptimizer().OptimizeExpression(expression, options);
Console.WriteLine($"{result.MinimizationStatus} {result.IsEquivalent()} " +
                  $"{result.Metrics!.ElapsedTime.TotalMilliseconds:F2}ms " +
                  $"{result.Metrics.AllocatedBytes / 1024.0:F1}KiB");
# Case 5: the whole corpus
dotnet run -c Release --project LogicalOptimizer.Benchmarks -- compare