Skip to content

Development happens here#52

Open
msoos wants to merge 60 commits intomasterfrom
develop
Open

Development happens here#52
msoos wants to merge 60 commits intomasterfrom
develop

Conversation

@msoos
Copy link
Copy Markdown
Collaborator

@msoos msoos commented Apr 30, 2026

No description provided.

msoos and others added 30 commits April 30, 2026 20:42
The orchestrator was ~890 lines with seven captured-state lambdas. Pull
the per-pass mutable state into a Unate::RepPass struct and split the
function into a roster of private helpers (lazy true-lits, Tseitin
encoders, Y'-side def copy, indicators, f_solver build, base-assumps
build, aux-set build, per-test loop body, deferred materialize, summary
log). Each helper body is a near-verbatim move of the original block
(s/s/p.s, lambda calls become member calls); no logic changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pull the per-pass machinery out of Unate entirely. UnateDefRep lives in
its own header/cpp pair and owns the per-pass state as plain class
members instead of routing through a RepPass struct. setup_f_not_f
becomes a free helper in unate_def_common.{h,cpp} so synthesis_unate_def
and UnateDefRep can both call it without sharing a class. Unate's
synthesis_unate_def_rep is now a 2-line wrapper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Split the ~480-line process_test_var into a small orchestrator plus four
helpers: log_progress_periodic, try_commit_h (miter-UNSAT branch:
feasibility check + commit), process_cex (miter-SAT branch: F-solve +
pattern build + H refine), and log_per_var_summary. Per-var diagnostics
move into a private PerVarStats struct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UnateDefRep was already in ArjunInt; Unate sat at global scope, which
forced synthesis_unate_def_rep to live outside the file's ArjunInt block
as a bridge. Now both live in ArjunInt and the bridge folds into the
existing namespace block.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the anonymous-namespace translate_to_orig in unate_def_rep.cpp
and the two ad-hoc AIG::transform<aig_ptr> remap lambdas in manthan.cpp
(fill_var_to_formula_with, BVE rebuild) with a single static helper on
class AIG. The manthan call sites now use AIG::new_const(true) instead
of aig_mng.new_const(true); per arjun.h:587 the manager is "a
convenience, not a canonical source", so this is a no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces four hand-rolled `AIG::transform<Lit>` visitors that all emit
the same `(~h ∨ l), (~h ∨ r), (h ∨ ~l ∨ ~r)` clauses and only differ
in solver, true-lit factory, and leaf-var → SAT-Lit map:

  - unate_def.cpp:       aig_to_copy_visitor in synthesis_unate_def
  - unate_def_rep.cpp:   aig_to_copy_visitor in setup_yprime_backward_defs
  - unate_def_rep.cpp:   encode_h_in_miter
  - unate_def_rep.cpp:   encode_h_in_f

The new helper takes the solver, a lazy true-lit thunk (so callers can
keep deferring their TRUE-helper allocation), the leaf map, and optional
visit/and-emit counters to preserve `rep_stats` telemetry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
msoos and others added 30 commits May 1, 2026 19:38
standalone_unate_def_rep now constructs UnateDefRep directly instead of
going through Unate::synthesis_unate_def_rep, which was just a one-line
wrapper around UnateDefRep::run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pull the Y'-side backward-def loop, the indicator-clause loop, the
conditional-probe scratch setup, the per-test loop body, and the final
log block out of synthesis_unate_def into setup_y_prime_backward_defs,
build_indicators, build_cond_state, process_test_var, and
log_pass_summary respectively. The per-call transient state (s, cnf
pointer, true_lit, new_to_orig, assumps, etc.) moves to private
members so the helpers can share it without parameter plumbing, and
try_cond_unate_def now takes only `test`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The conditional unate-def candidate list ranked candidates by clause
first-seen order, which carries no signal. Rank by number of shared
clauses instead — inputs that co-occur with the to-define var more
often are more likely single-literal definers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the --unatedefcondrel option and the conf.unate_def_cond_relfirst
toggle; the related-inputs prefix is now always tried before the rest of
the input list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
try_cond_unate_def now considers non-input vars (to-define plus
already-tested non-backward-defined) as candidate L after the input
list is exhausted. The conditional-probe argument is independent of L
being shared between Y and Y' — non-input candidates have an indicator
pinning y_L = y_L', so a single Y-side assumption suffices.

Order is strictly [related_inputs, related_noninputs, all_inputs,
all_noninputs]; inputs always run first. Non-input L is only committed
when L is currently defined and deps_recursive(L_orig) is a subset of
orig_sampl_vars (and excludes test_orig), so the resulting BW chain
stays well-ordered for Manthan's recompute pass.

Gated by --unatedefcondnoninp (default 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port Manthan's `minimize_conflict` to the unate_def_rep CEX path. After
extracting the initial input/aux pattern from the F-only conflict,
iteratively try dropping each pattern lit and re-solving with the
remaining lits + force_wrong assumed. UNSAT-with-~force_wrong-still-
in-conflict ⇒ replace with the smaller conflict; otherwise pin and
move on.

Aux lits sort to the front so we drop aux first — input-only patterns
make smaller H AIGs at commit and avoid the Y-side encode entirely.

New knobs --unatedefrepminim (default 1) and --unatedefrepminbud
(default 32). New stats: minim_attempts/succeeded/solver_calls/
lits_in/lits_out plus per-var minim trace.

amba3b5y: avg_hit_aig 16.7 → 12.3, total 21.85s → 20.74s.
nxquery: iters 346 → 305, total 15.94 → 16.18 (≈noise).
bob: less aggressive — fewer hits but still faster overall (13.09 →
11.90).

Fuzzers updated.
Manthan's "input-only conflict first" trick. Before assuming aux pins
on the F-only solver, try with X assumed alone + force_wrong. UNSAT-
with-~force_wrong-in-conflict ⇒ keep the input-only pattern: it lives
purely over inputs, gives a smaller H (no aux leaves), and avoids the
Y-side encode at commit (h_aux_leaf_count==0 reuses h_enc_lit).

Falls back to the standard input+aux call on SAT or budget. Off when
aux mode is 0 since the aux set is empty.

Knob --unatedefrepinpfirst (default 2 = only when aux_vars non-empty;
1 = always; 0 = off). Stats: inpfirst_attempts/unsat/sat/undef.

amba3b5y: 15/187 input-only UNSATs, 21.85s baseline → 19.19s (vs
20.74s after commit 1). bob/nxq roughly neutral.

Fuzzers updated.
Manthan's "drop y-vars from conflict" — one SAT call dropping every
aux lit from the (post-minim) pattern. UNSAT-with-~force_wrong-in-
conflict ⇒ replace pattern with the input-only conflict. Saves the
greedy O(n) loop when aux is jointly redundant.

Cheap: skipped when pattern already input-only or empty after minim.
On the three benchmarks the trigger fires 19–116 times per run but
finds no joint aux redundancies (greedy already removed them); kept
on as a fallback for benchmarks where greedy hits its budget.

Knob --unatedefrepdropaux (default 1). Stats: dropaux_attempts/
succeeded/lits_dropped.

Fuzzers updated.
After miter SAT, collect up to K-1 additional CEX models by adding
fresh-activation-gated blocking clauses on (input ∪ aux) projections,
then re-solving the miter under base + act + ~act_block_*. Activation
lits are pinned TRUE before returning so blocking is permanently
inert for subsequent iters.

Per iter: score each model via a single input-only F-only probe and
pick the model with the smallest input-only conflict (or first if no
model gives input-only UNSAT). Refines H using the chosen model.

Default K=1 (off) — the simple "smallest input-only conflict" heuristic
didn't pick up gains on amba/bob/nxq (the first model already gave the
smallest input-only conflict in 100% of cases on these benchmarks),
and the per-iter overhead (K-1 miter solves, K F-only probes) cost
20–80% extra unate_def_rep time. Kept as a knob the fuzzer
exercises (1, 2, 3, 5, 8) since it may help harder instances.

Knob --unatedefrepmulticex (default 1). Stats: multicex_attempts /
models_collected / extra_solves / picked_nonfirst / time_multicex_solve.
Add a manthan-style per-iter trace line that shows, for every guess+
refine iter: var, iter index, miter outcome, CEX models collected,
pattern size, minim attempt/dropped count, H AIG nodes before vs
after, and the action taken (BREAK / CONT / REFINE).

Gated by --unatedefrepiterverb (default 4) so it only fires under
--verb >=4. Fuzzer randomizes the threshold across {0, 1, 4, 99} to
exercise both fired and silent paths.

Useful for debugging refinement loops where H grows fast or stalls.
Track per-NEW-var conflict-pattern frequency (manthan's
`var_conflict_freq`) — bumped each time a var appears in an
extracted (post-minim, post-drop_aux) pattern. Stored across iters
in member `pattern_freq`, sized once at run() entry.

When --unatedefrepfreqsort=1, sort minim drop order by ascending
pattern frequency within the aux/input bucket. Less-frequent vars
are CEX-specific and more likely removable, so dropping them first
maximises the budget's expected yield.

Default 0 (off): on bob the freq sort produces aux-heavy H AIGs
that Manthan struggles to repair downstream (bob 13s → 19s with
freq_sort=1, even though unate_def_rep itself finds 5 hits vs 1).
Fuzzer randomises {0, 1} so the path is exercised.
Add invariant checks and tracing for the new code paths:

- minimize_pattern: SLOW_DEBUG release_assert that all entry/exit lits
  live in input ∪ aux and that the post-minim pattern never grows.
- drop_aux_oneshot: SLOW_DEBUG release_assert post-drop pattern is
  strictly input-only.
- collect_cex_models: SLOW_DEBUG check that every pair of collected
  models differs on at least one (input ∪ aux) value (the blocking
  clauses must guarantee this; a duplicate would mean the activation
  gating broke).
- VERBOSE_DEBUG entry/exit prints in minim, drop_aux, multi-cex,
  inpfirst paths (gated by `verbose_debug_enabled`).

Also fix a pre-existing typo in the SLOW_DEBUG block of
try_commit_h: stray ':' instead of ';' after release_assert(bad == 0)
plus unqualified `cout`/`endl`. The block was unreachable in default
builds but blocked any --define SLOW_DEBUG build of unate_def_rep.cpp.
Verified by enabling SLOW_DEBUG locally and running 50 iters of
fuzz_unate_def_rep — all checks pass.

No runtime cost in release builds (SLOW_DEBUG_DO / VERBOSE_DEBUG_DO
expand to no-ops).
Manthan's "extra minim passes for hot variables" — after the main
greedy loop converges, reverse the lit order, clear the pin set, and
sweep again. The greedy result depends on iteration order, so a
different sweep can find additional removable lits even when the
first pass hit a fixed point.

Each extra pass shares the same minim_budget; bail when budget hits 0.

Knob --unatedefrepminextra (default 0). Off by default because on amba
extra passes shrink patterns harder, but the resulting H grows more
across iters before miter UNSAT (avg_hit_aig 16.5 → 50.5, total
19.30 → 22.61) — net loss on this benchmark. Kept as a knob the
fuzzer randomises across {0, 1, 3, 10}; may help different inputs.

Stats: minim_extra_pass_calls.
When --unatedefrepmultipat=1 and multi-cex collected K>=2 models, run
process_cex on every non-chosen model after the chosen-model call.
Each yields its own (X, aux) pattern; H grows by all of them per
miter SAT instead of just one. Manthan's batched-repair analogue —
trades extra F-only minim work for faster H growth and earlier
miter UNSAT.

On amba+--multicex 3 --multipat 1: hits 4 → 9 (more!) but
avg_hit_aig 16.5 → 95 (much bigger H), total 19.30 → 27.15 (slower
overall). Off by default — the bigger H confuses Manthan downstream
on these benchmarks. Fuzzer randomises {0, 1}.

Stats: multipat_refine_calls.
Parse the [unate_def_rep] Done. and time-breakdown lines from each
arjun run, accumulate per-knob stat totals across iterations, and
print a coverage summary at the end. Lets the user see at a glance
that the random knob choices actually exercise all the new code
paths (minim, inpfirst, dropaux, multicex), and how often each
fires / succeeds.

Sample 300-iter run reports:
  total iters: 154 (rep tests: 50, hits: 8)
  minim: in=278 out=276 (-2, 0.7%)
  inpfirst: att=164 UNSAT=160 (97.6%)
  multicex: att=126 models=652 (avg 5.2 models/iter)

If e.g. minim_sz_in == minim_sz_out for every run, the minim path
is broken or never fires; this gives the user a quick sanity signal.
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