···686686 for sel2 in all_sels[idx1 + 1:]:
687687 clauses.append([-sel1, -sel2])
688688689689+ # Constraint 2b: Symmetry breaking - gates of same type ordered by first input
690690+ # For consecutive gates of the same size, require first input index is non-decreasing
691691+ for gate_idx in range(n_gates - 1):
692692+ i = n_inputs + gate_idx
693693+ i_next = n_inputs + gate_idx + 1
694694+ size = gate_sizes[gate_idx]
695695+ size_next = gate_sizes[gate_idx + 1]
696696+697697+ if size != size_next:
698698+ continue # Only break symmetry between same-type gates
699699+700700+ if size == 2:
701701+ # For 2-input gates: if gate i has first input j and gate i+1 has first input j',
702702+ # require j <= j'
703703+ for j in range(i):
704704+ for k in range(j + 1, i):
705705+ for j_next in range(j): # j_next < j violates ordering
706706+ for k_next in range(j_next + 1, i_next):
707707+ if j_next in s2[i_next] and k_next in s2[i_next][j_next]:
708708+ clauses.append([-s2[i][j][k], -s2[i_next][j_next][k_next]])
709709+ elif size == 3:
710710+ for j in range(i):
711711+ for k in range(j + 1, i):
712712+ for l in range(k + 1, i):
713713+ for j_next in range(j):
714714+ for k_next in range(j_next + 1, i_next):
715715+ for l_next in range(k_next + 1, i_next):
716716+ if j_next in s3[i_next] and k_next in s3[i_next][j_next] and l_next in s3[i_next][j_next][k_next]:
717717+ clauses.append([-s3[i][j][k][l], -s3[i_next][j_next][k_next][l_next]])
718718+ else: # size == 4
719719+ for j in range(i):
720720+ for k in range(j + 1, i):
721721+ for l in range(k + 1, i):
722722+ for m in range(l + 1, i):
723723+ for j_next in range(j):
724724+ for k_next in range(j_next + 1, i_next):
725725+ for l_next in range(k_next + 1, i_next):
726726+ for m_next in range(l_next + 1, i_next):
727727+ if (j_next in s4[i_next] and k_next in s4[i_next][j_next] and
728728+ l_next in s4[i_next][j_next][k_next] and m_next in s4[i_next][j_next][k_next][l_next]):
729729+ clauses.append([-s4[i][j][k][l][m], -s4[i_next][j_next][k_next][l_next][m_next]])
730730+689731 # Constraint 3: Gate function consistency
690732 for gate_idx in range(n_gates):
691733 i = n_inputs + gate_idx
+19-6
search_with_4input.py
···237237 return f"{label}[{bar}] {current}/{total} ({pct:.0f}%)"
238238239239240240-def try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue):
240240+def try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue, stop_event=None):
241241 """Try a single (n2, n3, n4) configuration with stats reporting."""
242242 cost = n2 * 2 + n3 * 3 + n4 * 4
243243 n_gates = n2 + n3 + n4
···254254255255 start = time.time()
256256 try:
257257+ # Check early termination before building CNF
258258+ if stop_event and stop_event.is_set():
259259+ return None, cost, "Cancelled", (n2, n3, n4)
260260+257261 # Build the CNF without solving
258262 cnf = solver._build_general_cnf(n2, n3, n4, use_complements, restrict_functions)
259263 if cnf is None:
···284288 total_decisions = 0
285289286290 while True:
291291+ # Check early termination
292292+ if stop_event and stop_event.is_set():
293293+ elapsed = time.time() - start
294294+ return None, cost, f"Cancelled ({elapsed:.1f}s, {total_conflicts} conflicts)", (n2, n3, n4)
295295+287296 sat_solver.conf_budget(conflict_budget)
288297 status = sat_solver.solve_limited()
289298···326335327336def try_config(args):
328337 """Try a single (n2, n3, n4) configuration. Run in separate process."""
329329- n2, n3, n4, use_complements, restrict_functions, stats_queue = args
330330- return try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue)
338338+ n2, n3, n4, use_complements, restrict_functions, stats_queue, stop_event = args
339339+ return try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue, stop_event)
331340332341333342def worker_init():
···478487 configs_tested = 0
479488 total_configs = len(configs)
480489481481- # Create shared queue for stats
490490+ # Create shared queue for stats and stop event for early termination
482491 manager = Manager()
483492 stats_queue = manager.Queue()
493493+ stop_event = manager.Event()
484494 progress.stats_queue = stats_queue
485495486496 with ProcessPoolExecutor(max_workers=mp.cpu_count(), initializer=worker_init) as executor:
···506516 # Start async progress display
507517 progress.start(group_size, cost)
508518509509- # Add stats_queue to each config
510510- configs_with_queue = [(cfg[0], cfg[1], cfg[2], cfg[3], cfg[4], stats_queue) for cfg in group]
519519+ # Add stats_queue and stop_event to each config
520520+ stop_event.clear() # Reset for new cost group
521521+ configs_with_queue = [(cfg[0], cfg[1], cfg[2], cfg[3], cfg[4], stats_queue, stop_event) for cfg in group]
511522 futures = {executor.submit(try_config, cfg): cfg for cfg in configs_with_queue}
512523 found_solution = False
513524···536547 best_result = result
537548 best_cost = result_cost
538549 found_solution = True
550550+ # Signal other workers to stop
551551+ stop_event.set()
539552 for f in futures:
540553 f.cancel()
541554 break