CLI Usage
Install the CLI as a global .NET tool (the command is logical-optimizer):
dotnet tool install -g LogicalOptimizer.Cli
The examples below use logical-optimizer "<expr>". Running from a source checkout is
equivalent — substitute dotnet run --project LogicalOptimizer.Cli -c Release -- "<expr>".
All outputs shown are verified against the built CLI.
Default optimize
logical-optimizer "a & b | a & c"
Original: a & b | a & c
Optimized: a & (b | c)
Equivalent: proven
Minimality: proven
Cost: 4 -> 3 literals
CNF: a & (b | c)
DNF: a & b | a & c
Variables: [a, b, c]
A truth table is appended for expressions with ≤ 6 variables; an Advanced: line appears
only when a pattern (XOR / implication / equivalence) is recognized.
Main flags
| Flag | Effect |
|---|---|
--cnf |
Output only the Conjunctive Normal Form |
--dnf |
Output only the Disjunctive Normal Form |
--anf |
Output only the Algebraic Normal Form (Zhegalkin / Reed–Muller polynomial) |
--advanced |
Include advanced logical forms (XOR / → / ↔) |
--truth-table |
Output only the truth table |
--format=json (alias --json) |
Machine-readable JSON report on stdout (stable schemaVersion); diagnostics stay on stderr |
--trace |
Append the diagnostic trace: engine chosen and why, budgets, candidate costs, proof paths, fallbacks |
--cnf-mode=tseitin |
Equisatisfiable linear-size CNF (Tseitin) instead of the distributive CNF |
--cnf-mode=equivalent |
Distributive (logically equivalent) CNF — the default |
--outputs=Name1,Name2 <csv> |
Multi-output CSV minimization with shared cubes |
--csv "<csv>" |
Parse a CSV truth table (also auto-detected for .csv files) |
--verbose |
Detailed output: metrics, iterations, elapsed time, Minimality: status |
--demo |
Features demonstration |
--benchmark |
Performance testing |
--stress |
Extreme stress testing for large expressions |
--csv-example |
Print the expected CSV truth-table format |
--help, -h |
Usage and supported operators |
The complete flag set is locked by DocExamplesTests.Cli_RecognizesEveryDocumentedFlag, and the
standard-format verbs below by
DocExamplesTests.Cli_RecognizesEveryDocumentedStandardFormatVerb.
--cnf
logical-optimizer --cnf "a & b | c"
# (a | c) & (b | c)
--dnf
logical-optimizer --dnf "(a | b) & c"
# a & c | b & c
--anf
Emits the canonical XOR-of-AND-monomials (Zhegalkin / Reed–Muller) form:
logical-optimizer --anf "a & !b | !a & b"
# a XOR b
logical-optimizer --anf "a | b"
# (a XOR b) XOR (a & b)
--advanced
logical-optimizer --advanced "a & !b | !a & b"
# a XOR b
Implication and equivalence patterns render as a → b and a ↔ b respectively.
--cnf-mode=tseitin
Produces a linear-size equisatisfiable CNF (auxiliary variables) instead of the distributive CNF — the right choice when handing the formula to a SAT solver, since it avoids the exponential blow-up distribution can cause. The polarity-based Plaisted–Greenbaum style cuts clause count up to ~2×.
--outputs (multi-output CSV)
The CSV can be passed inline (using \n for row breaks) or as a file. Shared don't-cares
and PLA-style cube sharing are exploited across the output columns:
logical-optimizer --outputs=Sum,Carry "a,b,Sum,Carry\n0,0,0,0\n0,1,1,0\n1,0,1,0\n1,1,0,1"
Sum = a & !b | b & !a
Carry = a & b
--verbose
Adds a metrics block including the explicit minimality status, for example
Minimality: MinimalProven (see Operation contracts & statuses).
--format=json
Emits a stable, versioned report to stdout for CI and tooling (human diagnostics stay on
stderr). --json is an alias, and the spaced form --format json also works.
logical-optimizer --format=json "a & b | a & c"
{
"schemaVersion": 1,
"input": "a & b | a & c",
"sourceFormat": "expression",
"optimized": "a & (b | c)",
"equivalent": true,
"minimality": "MinimalProven",
"cost": { "originalLiterals": 4, "optimizedLiterals": 3 },
"cnf": { "expression": "a & (b | c)", "status": "Computed", "minimality": "MinimalProven" },
"dnf": { "expression": "a & b | a & c", "status": "Computed" },
"variables": ["a", "b", "c"]
}
advanced appears only when an XOR/→/↔ pattern is detected. On an invalid expression the
document carries an error object instead of the result fields. Fields are only added within
a schemaVersion, never renamed or removed.
JSON with a CSV truth table
--format=json accepts a CSV input — inline or a *.csv file — as one single-output report. The
document names the input the CLI received, and reports the expression derived from the table
separately, so an archived report stays traceable to the table it came from:
logical-optimizer --format=json --csv "a,b,Result\n0,0,0\n0,1,1\n1,0,1\n1,1,1"
{
"schemaVersion": 1,
"input": "a,b,Result\\n0,0,0\\n0,1,1\\n1,0,1\\n1,1,1",
"sourceFormat": "csv",
"analyzedExpression": "(!a & b) | (a & !b) | (a & b)",
"optimized": "a | b",
"equivalent": true,
"minimality": "MinimalProven"
}
Every verdict in the report — optimized, equivalent, minimality, cost, the normal forms —
is about analyzedExpression. For a *.csv file, input is the path as passed. With a plain
expression sourceFormat is "expression" and analyzedExpression is omitted, because input
already is the analyzed expression.
--format=json is not available with --outputs: that mode emits one expression per output
column, which a single-expression report cannot carry. The combination is rejected as a usage
error (exit code 1).
The report is a published contract, not just a convention:
- JSON Schema (Draft 2020-12):
cli-report-v1.schema.json— validate a report with, for example,check-jsonschema --schemafile cli-report-v1.schema.json report.json; - golden examples for every outcome a consumer must handle — success,
BudgetExceededminimality, aTooLargenormal form, a CSV source, a structured parse error, and a bare processing error:schema/examples/; - what may change and what may not, in
schema/README.md.
The schema is closed, and CI validates both the committed examples and freshly generated output against it, so a field cannot appear, disappear or change type without a reviewed schema diff.
--trace
Explains how the result was reached — which engine ran and on what threshold, the budgets in force, every candidate's cost, which one was adopted or rejected, how equivalence and minimality were discharged, and any fallback. Works with both output formats:
logical-optimizer --trace "a & b | a & c" # under a "Trace:" heading
logical-optimizer --format=json --trace "a & b | a & c" # as a "trace" array
The trace is diagnostic: unlike the JSON report's fields, its wording and ordering are not a stability contract. See Diagnostic Trace.
Equivalence check (check)
check takes two expressions and proves them equivalent or returns a concrete
counterexample — the same EquivalenceChecker engine the library exposes (truth table in the
exhaustive range, SAT miter beyond it), so a determined verdict is always proven:
logical-optimizer check "a & b | a & c" "a & (b | c)"
Left: a & b | a & c
Right: a & (b | c)
Equivalent: proven
When the two differ, the counterexample names an exact input where they disagree — here a refactor that dropped the business-hours guard for owners:
logical-optimizer check "admin | (owner & businessHours)" "admin | owner"
Left: admin | (owner & businessHours)
Right: admin | owner
Equivalent: no
Counterexample: admin=0, businessHours=0, owner=1
The exit code carries the verdict, so a CI step needs no output parsing: 0 equivalent,
3 not equivalent, 4 unknown (conflict budget exhausted on a very large instance), with the
usual 1 for a usage error and 2 for an invalid expression.
--format=json emits a versioned report — its own document type with its own schema,
cli-check-report-v1.schema.json,
alongside golden examples under
schema/examples/
(check-*.json):
logical-optimizer check --format=json "admin | (owner & businessHours)" "admin | owner"
{
"schemaVersion": 1,
"left": "admin | (owner & businessHours)",
"right": "admin | owner",
"verdict": "not_equivalent",
"equivalent": false,
"counterexample": {
"admin": false,
"businessHours": false,
"owner": true
}
}
verdict is equivalent, not_equivalent, or unknown; the boolean equivalent field is
present only for a determined verdict, and counterexample exactly when the verdict is
not_equivalent. On a malformed expression the document carries an error object whose side
says which of the two expressions failed to parse (exit code 2).
Standard-format problem files
Everything above operates on a Boolean expression. The CLI also takes four verbs that read a
problem file in a standard competition format through
LogicalOptimizer.Formats and dispatch it to the in-house SAT,
MaxSAT, pseudo-Boolean or d-DNNF engine. The verb is the first argument, followed by exactly one
file path:
| Verb | Input format | Engine | Prints |
|---|---|---|---|
solve <file> |
DIMACS CNF | SatSolver (CDCL) |
s SATISFIABLE + a v model line, s UNSATISFIABLE, or s UNKNOWN |
maxsat <file> |
WCNF (weighted partial MaxSAT) | MaxSatSolver |
s OPTIMUM FOUND + o <cost> + a v model line |
solve-pb <file> |
OPB (pseudo-Boolean) | PseudoBooleanEncoder → SatSolver |
as solve |
count <file> --engine dnnf |
DIMACS CNF | KnowledgeCompilation (d-DNNF) |
the exact model count, one line |
The s / o / v line convention is the usual competition output, so existing tooling can
consume it unchanged.
solve (DIMACS CNF)
p cnf 3 2
1 -3 0
2 3 -1 0
logical-optimizer solve problem.cnf
s SATISFIABLE
v -1 -2 -3 0
The v line lists one signed literal per variable declared in the header — negative for false,
positive for true — terminated by 0.
maxsat (WCNF)
p wcnf 2 3 10
10 1 2 0
1 -1 0
1 -2 0
logical-optimizer maxsat problem.wcnf
s OPTIMUM FOUND
o 1
v -1 2 0
o is the total weight of the falsified soft clauses in the optimal assignment. Unsatisfiable
hard clauses print s UNSATISFIABLE; a budget that runs out prints s UNKNOWN (with the best
o found so far, when there is one) rather than passing a non-optimal answer off as optimal.
solve-pb (OPB)
* #variable= 2 #constraint= 1
+1 x1 +1 x2 >= 1;
logical-optimizer solve-pb problem.opb
s SATISFIABLE
v -1 2 0
The constraints are encoded to CNF and handed to the same CDCL solver. Only the problem's own
variables appear in the v line; the auxiliary variables the encoding introduces are not
reported.
count (exact #SAT)
logical-optimizer count problem.cnf --engine dnnf
5
The formula is compiled to a d-DNNF circuit and counted exactly — the result is a BigInteger,
so it does not overflow on large formulas. Variables declared in the header but absent from every
clause are free and are accounted for. --engine currently accepts one value, dnnf; the spaced
(--engine dnnf) and joined (--engine=dnnf) forms both work.
Errors
A missing file, a malformed problem, an unknown option, an unsupported --engine value or an
exhausted budget is reported on stderr and exits with code 1. A solved problem exits 0 —
including s UNSATISFIABLE, which is an answer, not a failure.
Exit codes
| Code | Meaning |
|---|---|
0 |
Success (for check: proven equivalent) |
1 |
Usage error (invalid arguments) |
2 |
Processing error (e.g. an invalid expression) |
3 |
check only: proven not equivalent (a counterexample was found) |
4 |
check only: unknown — the conflict budget ran out before a proof |
The standard-format verbs use 0 and 1 only: every parse, file and budget failure is a 1.
3 and 4 are additive codes specific to the check verb; the meaning of 0/1/2 is
unchanged.
Operators
| Operator | Meaning | Precedence |
|---|---|---|
! |
NOT | 1 (highest) |
& |
AND | 2 |
\| |
OR | 3 (lowest) |
() |
grouping | — |
0, 1 |
constants | — |