# Solver Notes

## Formula Notes

- The training split is software-verification CNF with many bit-blasted
  Tseitin/AIG-like encodings. Local data has 215 train files; the provided
  Kissat baseline file has 222 records, 174 of which match local filenames.
- Repeated visible clusters include `string_compare_safety_cbmc_unwinding#`
  (34), `linked_list_swap_contents_safety_unwind#` (24), `hsat_vc#` (16),
  `dspam_dump_vc#` (15), `hard-#-U-#` (14), and `itox_vc#` (9). Filenames are
  analysis hints only; solver behavior uses CNF structure.
- Unit-heavy 2/3-CNF vc-style formulas (`hsat`, `dspam`, `itox`, `xinetd`) are
  very sensitive to branch order. Low-index branching solves the local samples
  much faster than activity branching and also finds SAT models for the itox
  cases after checking every clause.
- The linked-list formulas have a high clause/variable ratio, large clauses,
  and no large unit prefix. Low-index branching proves local samples UNSAT
  quickly; unwind41 solved in about 6s with forced low branching, and unwind80
  solved in about 30s.
- The string-compare formulas have lower clause/variable ratio, very wide
  clauses, and mostly binary prefixes. Activity, low, high, and a root-frontier
  branching experiment did not solve unwind600 within 120s, so the current
  solver treats that structure as unsupported.

## Techniques Used

- C++20 from-scratch CDCL with two-watched literals, first-UIP analysis,
  restarts, phase saving, and a variable activity heap.
- A gated stochastic local-search model finder runs only on small/medium CNFs.
  It is SAT-only and still uses the full clause checker before any model is
  emitted; unsuccessful searches fall back to CDCL/UNKNOWN.
- VeriPB proof output uses `rup` lines for every learned clause and a final
  empty RUP contradiction. Local checks were run with `veripb --cnf`.
- SAT output is always independently checked against every parsed clause before
  exit code 10. Unassigned variables are filled only after the formula is known
  satisfiable under the constructed assignment.
- Structural branch choices:
  - tiny AIG-to-CNF formulas with exactly two asserted unit outputs and
    max-width 5 use high-index branching; this solves the local
    `smt-modmul8` and `smt-modmul10` instances with checkable RUP proofs;
  - unit-heavy max-3-CNF formulas use low-index branching;
  - high clause/variable-ratio large formulas use low-index branching for the
    linked-list shape;
  - large low-ratio very-wide formulas with few units are returned UNKNOWN to
    avoid wasting the full evaluator timeout on the string-compare shape;
  - other formulas use activity branching with a bounded internal time budget.

## Remaining Things To Try

- Reverse-engineer the string-compare tail constraints. The final clauses look
  like asserted Tseitin outputs over minterms, but tail-only constraints remain
  satisfiable; a proof likely needs a higher-level equality/string argument.
  A capped root-clause reduction experiment on unwinding 600 produced no
  contradiction and wrote a large discarded proof fragment, so it is now gated
  behind `LYMPHOSAT_ROOT_REDUCE=1` for analysis only.
- Add a proof-efficient structural equivalence checker for AIG/Tseitin circuits.
  This could attack AWS/CoreMQTT/CoreJSON and string-compare formulas without
  relying on generic CDCL.
- Investigate better SAT model construction for `gcdmax`, `sqrt`, `newton`,
  and SV-COMP witness formulas. Greedy clause satisfaction and the current
  stochastic local-search pass did not find models for the tested gcd/sqrt/dist
  cases.
- Reduce VeriPB checker cost for very large linked-list proofs if broker
  feedback shows prover timeouts or OOMs. The proof is compact, but VeriPB still
  must load the full CNF.

## Known Limitations

- This is not competitive on the string-compare family yet.
- The linked-list proofs are small, but local VeriPB checking on unwind80 timed
  out at 120s after using about 23GB RSS; smaller unwind41 verified in about
  61s and 7.5GB RSS. Broker verifier memory/time will determine how many count.
- The solver is deliberately conservative: unsupported structures return
  UNKNOWN, and no UNSAT is printed unless a RUP proof has been written.

## Changelog

- Added `tools/profile_stream.py` to stream CNF statistics from xz-compressed
  training files.
- Added `tools/run_local.py` for local decompression and timed solver runs.
- Replaced the scaffold with a C++ watched-literal CDCL solver that logs
  VeriPB RUP proofs and checks every SAT model.
- Added branch-order structural detection for unit-heavy max-3-CNF and
  high-ratio linked-list-like formulas.
- Added bounded search budgets and a conservative UNKNOWN detector for the
  observed unsupported string-compare-like structure.
- Fixed SAT model output so only the final `v` line is terminated by `0`;
  broker feedback showed large SAT models were invalid when every chunk ended
  with `0`.
- Added a conservative SAT output cap: complete models above 2,000,000
  variables are returned UNKNOWN because broker feedback still marked the
  giant zfcp models invalid, likely due model capture size.
- Added a gated stochastic local-search SAT pass for small/medium CNFs. It did
  not help the main missed SAT clusters locally, but it allowed `smt-modmul8`
  to be solved in a local 60s run.
- Added a narrow small-AIG detector that switches to high-index branching for
  the local `smt-modmul` shape. Local results: `smt-modmul8` UNSAT in about
  0.9s with VeriPB verification in about 1.6s; `smt-modmul10` UNSAT in about
  76s with VeriPB verification in about 113s; `smt-modmul12` remains UNKNOWN
  at the 120s internal cap.
- Tightened the small-AIG detector to require the `+2` and positive top-var
  unit pattern. The broader first version regressed `llbmc-array-avg-2`, whose
  unit pattern uses `-2`; the tightened detector restores that local UNSAT.
- Added, then disabled by default, a narrow longer-budget detector for
  AWS/Core-like CNFs with clause/variable ratio around 4-5, many root units,
  bounded maximum width, and clause-count bands matching locally solved
  samples. It recovered `coreJSON:skipNumber` and `aws_hash_iter_next` in the
  broker, but increased concurrent VeriPB memory pressure and moved more
  linked-list proofs to prover OOM, reducing total counted solves. It remains
  available for analysis with `LYMPHOSAT_LONG_AWS=1`.
- Added a large-SAT partial-model output path. For formulas above the complete
  model cap it emits a checked subset of assigned literals that covers every
  original clause, capped at two million literals; if no compact cover exists
  the solver returns UNKNOWN.
- Added environment-gated analysis hooks for root failed-literal probing
  (`LYMPHOSAT_FAILED_PROBE=1`), root unit clause reduction
  (`LYMPHOSAT_ROOT_REDUCE=1`), long AWS/Core budget
  (`LYMPHOSAT_LONG_AWS=1`), and local time-budget override
  (`LYMPHOSAT_TIME_BUDGET=<seconds>`). The first two are disabled for
  submission because local probes either did not solve new instances or
  consumed too much proof I/O; the long AWS/Core path is disabled because of
  broker verifier-memory tradeoffs.
