optimizing a gate level bcm to the end of the earth and back

feat: add circuit breaking to ignore consecutive gates

dunkirk.sh 18a8ce74 9a1e9bfc

verified
+61 -6
+42
bcd_optimization/solver.py
··· 686 686 for sel2 in all_sels[idx1 + 1:]: 687 687 clauses.append([-sel1, -sel2]) 688 688 689 + # Constraint 2b: Symmetry breaking - gates of same type ordered by first input 690 + # For consecutive gates of the same size, require first input index is non-decreasing 691 + for gate_idx in range(n_gates - 1): 692 + i = n_inputs + gate_idx 693 + i_next = n_inputs + gate_idx + 1 694 + size = gate_sizes[gate_idx] 695 + size_next = gate_sizes[gate_idx + 1] 696 + 697 + if size != size_next: 698 + continue # Only break symmetry between same-type gates 699 + 700 + if size == 2: 701 + # For 2-input gates: if gate i has first input j and gate i+1 has first input j', 702 + # require j <= j' 703 + for j in range(i): 704 + for k in range(j + 1, i): 705 + for j_next in range(j): # j_next < j violates ordering 706 + for k_next in range(j_next + 1, i_next): 707 + if j_next in s2[i_next] and k_next in s2[i_next][j_next]: 708 + clauses.append([-s2[i][j][k], -s2[i_next][j_next][k_next]]) 709 + elif size == 3: 710 + for j in range(i): 711 + for k in range(j + 1, i): 712 + for l in range(k + 1, i): 713 + for j_next in range(j): 714 + for k_next in range(j_next + 1, i_next): 715 + for l_next in range(k_next + 1, i_next): 716 + 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]: 717 + clauses.append([-s3[i][j][k][l], -s3[i_next][j_next][k_next][l_next]]) 718 + else: # size == 4 719 + for j in range(i): 720 + for k in range(j + 1, i): 721 + for l in range(k + 1, i): 722 + for m in range(l + 1, i): 723 + for j_next in range(j): 724 + for k_next in range(j_next + 1, i_next): 725 + for l_next in range(k_next + 1, i_next): 726 + for m_next in range(l_next + 1, i_next): 727 + if (j_next in s4[i_next] and k_next in s4[i_next][j_next] and 728 + l_next in s4[i_next][j_next][k_next] and m_next in s4[i_next][j_next][k_next][l_next]): 729 + clauses.append([-s4[i][j][k][l][m], -s4[i_next][j_next][k_next][l_next][m_next]]) 730 + 689 731 # Constraint 3: Gate function consistency 690 732 for gate_idx in range(n_gates): 691 733 i = n_inputs + gate_idx
+19 -6
search_with_4input.py
··· 237 237 return f"{label}[{bar}] {current}/{total} ({pct:.0f}%)" 238 238 239 239 240 - def try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue): 240 + def try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue, stop_event=None): 241 241 """Try a single (n2, n3, n4) configuration with stats reporting.""" 242 242 cost = n2 * 2 + n3 * 3 + n4 * 4 243 243 n_gates = n2 + n3 + n4 ··· 254 254 255 255 start = time.time() 256 256 try: 257 + # Check early termination before building CNF 258 + if stop_event and stop_event.is_set(): 259 + return None, cost, "Cancelled", (n2, n3, n4) 260 + 257 261 # Build the CNF without solving 258 262 cnf = solver._build_general_cnf(n2, n3, n4, use_complements, restrict_functions) 259 263 if cnf is None: ··· 284 288 total_decisions = 0 285 289 286 290 while True: 291 + # Check early termination 292 + if stop_event and stop_event.is_set(): 293 + elapsed = time.time() - start 294 + return None, cost, f"Cancelled ({elapsed:.1f}s, {total_conflicts} conflicts)", (n2, n3, n4) 295 + 287 296 sat_solver.conf_budget(conflict_budget) 288 297 status = sat_solver.solve_limited() 289 298 ··· 326 335 327 336 def try_config(args): 328 337 """Try a single (n2, n3, n4) configuration. Run in separate process.""" 329 - n2, n3, n4, use_complements, restrict_functions, stats_queue = args 330 - return try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue) 338 + n2, n3, n4, use_complements, restrict_functions, stats_queue, stop_event = args 339 + return try_config_with_stats(n2, n3, n4, use_complements, restrict_functions, stats_queue, stop_event) 331 340 332 341 333 342 def worker_init(): ··· 478 487 configs_tested = 0 479 488 total_configs = len(configs) 480 489 481 - # Create shared queue for stats 490 + # Create shared queue for stats and stop event for early termination 482 491 manager = Manager() 483 492 stats_queue = manager.Queue() 493 + stop_event = manager.Event() 484 494 progress.stats_queue = stats_queue 485 495 486 496 with ProcessPoolExecutor(max_workers=mp.cpu_count(), initializer=worker_init) as executor: ··· 506 516 # Start async progress display 507 517 progress.start(group_size, cost) 508 518 509 - # Add stats_queue to each config 510 - configs_with_queue = [(cfg[0], cfg[1], cfg[2], cfg[3], cfg[4], stats_queue) for cfg in group] 519 + # Add stats_queue and stop_event to each config 520 + stop_event.clear() # Reset for new cost group 521 + configs_with_queue = [(cfg[0], cfg[1], cfg[2], cfg[3], cfg[4], stats_queue, stop_event) for cfg in group] 511 522 futures = {executor.submit(try_config, cfg): cfg for cfg in configs_with_queue} 512 523 found_solution = False 513 524 ··· 536 547 best_result = result 537 548 best_cost = result_cost 538 549 found_solution = True 550 + # Signal other workers to stop 551 + stop_event.set() 539 552 for f in futures: 540 553 f.cancel() 541 554 break