| |
| from dimod import BinaryQuadraticModel |
|
|
| |
| from neal import SimulatedAnnealingSampler |
| from .base_solver import BaseSolver |
|
|
|
|
| class DWaveSolver(BaseSolver): |
| def __init__( |
| self, normalize_scale=0, num_reads=10, verbose_level=2, seed=None, **kwargs |
| ): |
| """ |
| Args: |
| seed: Random seed forwarded to neal.SimulatedAnnealingSampler.sample(). |
| None (default) leaves annealing non-deterministic run-to-run; |
| set for reproducible sweeps/benchmarks. |
| """ |
| super().__init__( |
| solver="dwave", |
| normalize_scale=normalize_scale, |
| num_reads=num_reads, |
| verbose_level=verbose_level, |
| seed=seed, |
| **kwargs, |
| ) |
| self.seed = seed |
|
|
| def solve(self, builder, optimization=False, preprocess=True): |
| """ |
| Solve QUBO using simulated annealing. |
| |
| Args: |
| builder: QUBOBuilder instance |
| optimization: Accepted for interface compatibility; unused by DWave/SA. |
| preprocess: When True (default), applies BFS variable reduction, |
| diagonal pruning, correction loop, and window stats tracking. |
| When False, runs a simple loop with no preprocessing. |
| |
| Returns: |
| Dictionary containing solution, energy, and raw response |
| """ |
| best_sample = [] |
| best_energy = [] |
| window_stats = [] |
| forced_collisions = [] |
| response = None |
| correction_count = 0 |
| import time as timing |
|
|
| if not preprocess: |
| |
| while (builder.total_t) > (builder.current_T): |
| Q = builder.Q |
| if self.norm_scale != 0: |
| Q = self.normalize_qubo(builder.Q, self.norm_scale) |
| self.logger.standard( |
| "Start position:", builder.problem.start, "Iteration:", builder.iter |
| ) |
| bqm = BinaryQuadraticModel.from_qubo(Q) |
| sampler = SimulatedAnnealingSampler() |
| response = sampler.sample(bqm, num_reads=self.num_reads, seed=self.seed) |
| first = response.first |
| best_sample.append(first.sample) |
| best_energy.append(response.first.energy) |
| last_pos = self.decode_path(first.sample, builder.problem)[-1] |
| builder.update_problem(last_pos[:2]) |
|
|
| return { |
| "solution": best_sample, |
| "energy": best_energy, |
| "raw_response": response, |
| } |
|
|
| |
| while (builder.total_t) > (builder.current_T): |
| active_robots = [r for r in builder.problem.robots.values() if r.active] |
| if not active_robots: |
| self.logger.standard( |
| "✅ All robots reached goal or inactive. Stopping solver." |
| ) |
| break |
|
|
| window_start = timing.time() |
| fixed_vars, window_stat, is_preprocessed, window_forced_collisions = ( |
| self._prepare_window(builder) |
| ) |
| window_stats.append(window_stat) |
| forced_collisions.extend(window_forced_collisions) |
|
|
| if is_preprocessed: |
| self.logger.standard( |
| f"⚡ Window {builder.iter} fully pre-processed, skipping solver" |
| ) |
| t_fast = timing.time() |
| full_sol, invalid_moves = self._handle_iteration_result( |
| {}, fixed_vars, builder |
| ) |
| self.logger.debug( |
| f"⏱️ _handle_iteration_result: {(timing.time() - t_fast) * 1000:.1f}ms, " |
| f"total window: {(timing.time() - window_start) * 1000:.1f}ms" |
| ) |
| best_sample.append(full_sol) |
| best_energy.append(0.0) |
| continue |
|
|
| if self.norm_scale != 0: |
| builder.Q = self.normalize_qubo(builder.Q, self.norm_scale) |
|
|
| self.logger.standard("Num wires", builder.get_num_wires()) |
| for _, robot_id in enumerate(builder.problem.robots): |
| start_pos = builder.problem.robots[robot_id].current_position |
| self.logger.standard( |
| "Start position:", start_pos, "Iteration:", builder.iter |
| ) |
|
|
| bqm = BinaryQuadraticModel.from_qubo(builder.Q) |
| sampler = SimulatedAnnealingSampler() |
| response = sampler.sample(bqm, num_reads=self.num_reads, seed=self.seed) |
|
|
| first = response.first |
| full_sol, invalid_moves = self._handle_iteration_result( |
| first.sample, fixed_vars, builder |
| ) |
| best_sample.append(full_sol) |
| best_energy.append(response.first.energy) |
|
|
| if invalid_moves: |
| correction_count += 1 |
| self.logger.standard( |
| f"🔄 Correction attempt {correction_count}/{self.max_corrections} for current window" |
| ) |
|
|
| if correction_count >= self.max_corrections: |
| self.logger.minimal( |
| f"⚠️ Max corrections ({self.max_corrections}) exceeded at t={builder.current_T}. " |
| f"Keeping last result (invalid moves for robots {list(invalid_moves.keys())})." |
| ) |
| path = self.decode_path( |
| full_sol, builder.problem, t_offset=builder.current_T |
| ) |
| robot_paths = self.get_robot_paths(path) |
| robot_paths = self._resolve_duplicate_timesteps( |
| robot_paths, builder.problem |
| ) |
| builder.update_problem(robot_paths) |
| correction_count = 0 |
| |
| else: |
| correction_count = 0 |
|
|
| final_solution = self.build_solution_from_robot_paths(builder.problem) |
|
|
| return { |
| "solution": final_solution, |
| "energy": best_energy, |
| "raw_response": response, |
| "metadata": { |
| "window_stats": window_stats, |
| "forced_collisions": forced_collisions, |
| "num_robots": builder.problem.num_robots, |
| "total_variables": builder.initial_num_vars, |
| "fixed_variables": len(fixed_vars) if "fixed_vars" in dir() else 0, |
| "solver_config": self.to_dict(), |
| "penalties": builder.penalties, |
| }, |
| } |
|
|