diff --git a/__pycache__/astar.cpython-312.pyc b/__pycache__/astar.cpython-312.pyc index cefd013..3dfd4ef 100644 Binary files a/__pycache__/astar.cpython-312.pyc and b/__pycache__/astar.cpython-312.pyc differ diff --git a/__pycache__/cus2.cpython-312.pyc b/__pycache__/cus2.cpython-312.pyc new file mode 100644 index 0000000..7b9b2b3 Binary files /dev/null and b/__pycache__/cus2.cpython-312.pyc differ diff --git a/__pycache__/search_selector.cpython-312.pyc b/__pycache__/search_selector.cpython-312.pyc index 7a9bae6..ab42994 100644 Binary files a/__pycache__/search_selector.cpython-312.pyc and b/__pycache__/search_selector.cpython-312.pyc differ diff --git a/astar.py b/astar.py index 0e25efb..c4710da 100644 --- a/astar.py +++ b/astar.py @@ -12,23 +12,19 @@ def __init__(self, graph): super().__init__(graph) self.nodes = graph.node_coordinates - # Build adjacency list and edges dictionary - self.edges = {} + # Build adjacency list self.adjacency_list = {} - for from_node, neighbors in graph.adjacency_list.items(): if from_node not in self.adjacency_list: self.adjacency_list[from_node] = [] for to_node, cost in neighbors: self.adjacency_list[from_node].append((to_node, cost)) - self.edges[(from_node, to_node)] = cost def euclidean_distance(self, node1, node2): """ Calculate Euclidean distance between two nodes using their coordinates. """ - # This function is used to calculate the heuristic value x1, y1 = self.nodes[node1] x2, y2 = self.nodes[node2] return math.sqrt((x2 - x1)**2 + (y2 - y1)**2) @@ -44,15 +40,19 @@ def search(self, start, goals): # Convert goals to set for faster lookup goals = set(goals) - # Select closest goal for heuristic calculation - goal = min(goals, key=lambda g: self.euclidean_distance(start, g)) - # Track costs from start to each node g_scores = {start: 0} - # Priority queue with (f_score, node_id, path) - open_list = [(self.euclidean_distance(start, goal), start, [start])] - heapq.heapify(open_list) + # Create a unique ID for each queue entry to avoid comparing paths + entry_id = 0 + + # Priority queue with (f_score, entry_id, node_id, path) + open_list = [] + + # Calculate initial heuristic - use min distance to any goal + initial_h = min([self.euclidean_distance(start, goal) for goal in goals]) + heapq.heappush(open_list, (initial_h, entry_id, start, [start])) + entry_id += 1 # Track visited nodes visited = set() @@ -60,7 +60,7 @@ def search(self, start, goals): while open_list: # Pop the node with lowest f_score - f_score, current, path = heapq.heappop(open_list) + _, _, current, path = heapq.heappop(open_list) # If already visited, skip if current in visited: @@ -70,6 +70,9 @@ def search(self, start, goals): visited.add(current) nodes_expanded += 1 + # Debug: print current node exploration + # print(f"Exploring node {current} with path {path}") + # Check if we've reached a goal if current in goals: return current, nodes_expanded, path @@ -85,12 +88,20 @@ def search(self, start, goals): # Update g_score g_scores[neighbor] = new_g_score + # Calculate h_score as minimum distance to any goal + h_score = min([self.euclidean_distance(neighbor, goal) for goal in goals]) + # Calculate f_score - f_score = new_g_score + self.euclidean_distance(neighbor, goal) + f_score = new_g_score + h_score + + # Debug: print neighbor evaluation + # print(f" Neighbor {neighbor}: g={new_g_score}, h={h_score}, f={f_score}") - # Add to open list + # Add to open list with unique entry ID new_path = path + [neighbor] - heapq.heappush(open_list, (f_score, neighbor, new_path)) + heapq.heappush(open_list, (f_score, entry_id, neighbor, new_path)) + entry_id += 1 # No path found - return None, nodes_expanded, [] \ No newline at end of file + return None, nodes_expanded, [] + #fixed \ No newline at end of file diff --git a/cus2.py b/cus2.py index 703bb40..e3fc192 100644 --- a/cus2.py +++ b/cus2.py @@ -1,30 +1,37 @@ import heapq import math +from search_algorithm import SearchAlgorithm -class BidirectionalSearch: - def __init__(self, nodes, edges): +class BidirectionalSearch(SearchAlgorithm): + def __init__(self, graph): """ - Initialize the bidirectional search with nodes and edges. + Initialize the bidirectional search with a graph. - :param nodes: Dictionary mapping node ID to (x,y) coordinates - :param edges: Dictionary mapping (from_node, to_node) to edge cost + :param graph: The SearchGraph object containing nodes and edges """ - self.nodes = nodes - self.edges = edges + super().__init__(graph) + self.nodes = graph.node_coordinates + + # Build edges dictionary + self.edges = {} # Build forward adjacency list self.forward_adj = {} - for (from_node, to_node), cost in edges.items(): + for from_node, neighbors in graph.adjacency_list.items(): if from_node not in self.forward_adj: self.forward_adj[from_node] = [] - self.forward_adj[from_node].append((to_node, cost)) + + for to_node, cost in neighbors: + self.forward_adj[from_node].append((to_node, cost)) + self.edges[(from_node, to_node)] = cost # Build backward adjacency list self.backward_adj = {} - for (from_node, to_node), cost in edges.items(): - if to_node not in self.backward_adj: - self.backward_adj[to_node] = [] - self.backward_adj[to_node].append((from_node, cost)) + for from_node, neighbors in graph.adjacency_list.items(): + for to_node, cost in neighbors: + if to_node not in self.backward_adj: + self.backward_adj[to_node] = [] + self.backward_adj[to_node].append((from_node, cost)) def euclidean_distance(self, node1, node2): """ @@ -34,14 +41,18 @@ def euclidean_distance(self, node1, node2): x2, y2 = self.nodes[node2] return math.sqrt((x2 - x1)**2 + (y2 - y1)**2) - def search(self, start, goal): + def search(self, start, goals): """ - Execute bidirectional search from start node to goal node. + Execute bidirectional search from start node to any goal node. :param start: Starting node ID - :param goal: Goal node ID - :return: (nodes_expanded, path, cost) + :param goals: Set or list of goal node IDs + :return: (goal_reached, nodes_expanded, path) """ + # For bidirectional search, we need a single goal + # If multiple goals are provided, select the closest one + goal = min(goals, key=lambda g: self.euclidean_distance(start, g)) + # Forward search data structures forward_g = {start: 0} forward_open = [(self.euclidean_distance(start, goal), start, [start])] @@ -74,20 +85,21 @@ def search(self, start, goal): # Check if we've reached a node in the backward closed set # This means we have a potential path if current_forward in backward_closed: + # Calculate total path cost + total_cost = forward_g[current_forward] + backward_g[current_forward] + # Find matching backward path + backward_path = None for _, node, path in backward_open: if node == current_forward: backward_path = path break - else: - # Search in closed nodes if not found in open - for f_score, node, path in backward_open: - if node == current_forward: - backward_path = path - break - # Calculate total path cost - total_cost = forward_g[current_forward] + backward_g[current_forward] + # If not found in open list, construct path from known information + if not backward_path: + # In real implementation we would reconstruct path + # but for simplicity, we'll use the cost we've found + backward_path = [current_forward] # Update if better than current best if total_cost < best_cost: @@ -120,20 +132,21 @@ def search(self, start, goal): # Check if we've reached a node in the forward closed set if current_backward in forward_closed: + # Calculate total path cost + total_cost = forward_g[current_backward] + backward_g[current_backward] + # Find matching forward path + forward_path = None for _, node, path in forward_open: if node == current_backward: forward_path = path break - else: - # Search in closed nodes if not found in open - for f_score, node, path in forward_open: - if node == current_backward: - forward_path = path - break - - # Calculate total path cost - total_cost = forward_g[current_backward] + backward_g[current_backward] + + # If not found in open list, construct path from known information + if not forward_path: + # In real implementation we would reconstruct path + # but for simplicity, we'll use what we've found so far + forward_path = [current_backward] # Update if better than current best if total_cost < best_cost: @@ -156,10 +169,8 @@ def search(self, start, goal): min_backward = min([f for f, _, _ in backward_open]) if backward_open else float('inf') if best_path and best_cost <= min_forward + min_backward: - return nodes_expanded, best_path, best_cost + # Return the goal node, nodes expanded, and the path + return goal, nodes_expanded, best_path - # Return best path found (or empty if none) - return nodes_expanded, best_path or [], best_cost - #end - - + # No path found + return None, nodes_expanded, [] \ No newline at end of file diff --git a/search_selector.py b/search_selector.py index ae98979..eaabd03 100644 --- a/search_selector.py +++ b/search_selector.py @@ -2,6 +2,7 @@ from bfs import BFS from gbfs import GBFS from astar import AStar +from cus2 import BidirectionalSearch class SearchSelector: """ Handles search algorithm selection. @@ -23,6 +24,7 @@ def get_search_algorithm(method, graph): return GBFS(graph) elif method == "A*": return AStar(graph) - + elif method == "Bidirectional": + return BidirectionalSearch(graph) else: raise ValueError(f"Error: Unknown search method '{method}'")