Premchan369 commited on
Commit
e22f4ef
·
verified ·
1 Parent(s): f815179

Upload qads/planner/graph.py

Browse files
Files changed (1) hide show
  1. qads/planner/graph.py +194 -0
qads/planner/graph.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """World State Graph Builder."""
2
+ import numpy as np
3
+ from typing import Dict, Any, List, Tuple, Optional
4
+ import networkx as nx
5
+
6
+
7
+ class WorldGraph:
8
+ """
9
+ Probabilistic graph representing the environment.
10
+ Each node stores: position, risk, traversal cost, energy cost, uncertainty, obstacle probability
11
+ """
12
+
13
+ def __init__(self, resolution: float = 0.5):
14
+ self.resolution = resolution
15
+ self.graph = nx.DiGraph()
16
+ self.node_positions: Dict[int, Tuple[float, ...]] = {}
17
+ self.node_metadata: Dict[int, Dict[str, Any]] = {}
18
+ self.next_id = 0
19
+
20
+ def add_node(self,
21
+ position: Tuple[float, ...],
22
+ risk: float = 0.0,
23
+ cost: float = 1.0,
24
+ energy: float = 1.0,
25
+ uncertainty: float = 0.0,
26
+ obstacle_prob: float = 0.0) -> int:
27
+ """Add a node to the graph."""
28
+ node_id = self.next_id
29
+ self.next_id += 1
30
+
31
+ self.graph.add_node(node_id)
32
+ self.node_positions[node_id] = position
33
+ self.node_metadata[node_id] = {
34
+ 'risk': float(risk),
35
+ 'cost': float(cost),
36
+ 'energy': float(energy),
37
+ 'uncertainty': float(uncertainty),
38
+ 'obstacle_prob': float(obstacle_prob),
39
+ 'traversal_prob': float(1.0 - obstacle_prob),
40
+ 'entropy': float(-uncertainty * np.log2(uncertainty + 1e-10) if uncertainty > 0 else 0.0)
41
+ }
42
+ return node_id
43
+
44
+ def add_edge(self,
45
+ node_a: int,
46
+ node_b: int,
47
+ weight: Optional[float] = None,
48
+ risk: float = 0.0):
49
+ """Add an edge between nodes."""
50
+ if weight is None:
51
+ pos_a = self.node_positions[node_a]
52
+ pos_b = self.node_positions[node_b]
53
+ weight = np.linalg.norm(np.array(pos_a) - np.array(pos_b))
54
+
55
+ meta_a = self.node_metadata[node_a]
56
+ meta_b = self.node_metadata[node_b]
57
+
58
+ # Composite edge cost
59
+ composite_cost = (
60
+ 0.3 * weight +
61
+ 0.2 * (meta_a['risk'] + meta_b['risk']) / 2 +
62
+ 0.2 * (meta_a['cost'] + meta_b['cost']) / 2 +
63
+ 0.15 * (meta_a['uncertainty'] + meta_b['uncertainty']) / 2 +
64
+ 0.15 * (meta_a['obstacle_prob'] + meta_b['obstacle_prob']) / 2
65
+ )
66
+
67
+ self.graph.add_edge(node_a, node_b,
68
+ weight=composite_cost,
69
+ distance=weight,
70
+ risk=risk)
71
+
72
+ def build_grid(self,
73
+ bounds: Tuple[Tuple[float, float], ...],
74
+ obstacle_map: Optional[np.ndarray] = None,
75
+ uncertainty_map: Optional[np.ndarray] = None):
76
+ """Build grid graph from bounds and maps."""
77
+ if len(bounds) == 2:
78
+ (x_min, x_max), (y_min, y_max) = bounds
79
+ nx_nodes = int((x_max - x_min) / self.resolution)
80
+ ny_nodes = int((y_max - y_min) / self.resolution)
81
+
82
+ # Create nodes
83
+ for i in range(nx_nodes):
84
+ for j in range(ny_nodes):
85
+ x = x_min + i * self.resolution
86
+ y = y_min + j * self.resolution
87
+
88
+ # Get map values
89
+ obs_prob = 0.0
90
+ unc = 0.0
91
+ if obstacle_map is not None:
92
+ mi = min(int(i * obstacle_map.shape[0] / nx_nodes), obstacle_map.shape[0]-1)
93
+ mj = min(int(j * obstacle_map.shape[1] / ny_nodes), obstacle_map.shape[1]-1)
94
+ obs_prob = obstacle_map[mi, mj]
95
+ if uncertainty_map is not None:
96
+ mi = min(int(i * uncertainty_map.shape[0] / nx_nodes), uncertainty_map.shape[0]-1)
97
+ mj = min(int(j * uncertainty_map.shape[1] / ny_nodes), uncertainty_map.shape[1]-1)
98
+ unc = uncertainty_map[mi, mj]
99
+
100
+ self.add_node(
101
+ position=(x, y),
102
+ risk=obs_prob * 0.5 + unc * 0.3,
103
+ cost=1.0 + obs_prob,
104
+ energy=1.0,
105
+ uncertainty=unc,
106
+ obstacle_prob=obs_prob
107
+ )
108
+
109
+ # Add edges (4-connectivity)
110
+ for i in range(nx_nodes):
111
+ for j in range(ny_nodes):
112
+ idx = i * ny_nodes + j
113
+ # Right
114
+ if i < nx_nodes - 1:
115
+ self.add_edge(idx, idx + ny_nodes)
116
+ self.add_edge(idx + ny_nodes, idx)
117
+ # Up
118
+ if j < ny_nodes - 1:
119
+ self.add_edge(idx, idx + 1)
120
+ self.add_edge(idx + 1, idx)
121
+
122
+ def get_entropy(self) -> float:
123
+ """Compute graph-level entropy."""
124
+ probs = []
125
+ for node_id, meta in self.node_metadata.items():
126
+ p = meta.get('traversal_prob', 1.0)
127
+ if p > 0:
128
+ probs.append(p)
129
+
130
+ if not probs:
131
+ return 0.0
132
+
133
+ probs = np.array(probs)
134
+ probs = probs / probs.sum()
135
+ entropy = -np.sum(probs * np.log2(probs + 1e-10))
136
+ return float(entropy)
137
+
138
+ def get_uncertainty(self) -> float:
139
+ """Compute average node uncertainty."""
140
+ uncertainties = [meta['uncertainty'] for meta in self.node_metadata.values()]
141
+ return float(np.mean(uncertainties)) if uncertainties else 0.0
142
+
143
+ def get_obstacle_density(self) -> float:
144
+ """Compute obstacle density."""
145
+ obs = [meta['obstacle_prob'] for meta in self.node_metadata.values()]
146
+ return float(np.mean(obs)) if obs else 0.0
147
+
148
+ def find_node_at(self, position: Tuple[float, ...], tolerance: float = None) -> Optional[int]:
149
+ """Find node closest to position."""
150
+ if tolerance is None:
151
+ tolerance = self.resolution * 2
152
+
153
+ pos = np.array(position)
154
+ best_id = None
155
+ best_dist = float('inf')
156
+
157
+ for node_id, node_pos in self.node_positions.items():
158
+ dist = np.linalg.norm(pos - np.array(node_pos))
159
+ if dist < tolerance and dist < best_dist:
160
+ best_id = node_id
161
+ best_dist = dist
162
+
163
+ return best_id
164
+
165
+ def to_cost_matrix(self) -> np.ndarray:
166
+ """Convert graph to cost matrix for QAOA."""
167
+ n = len(self.graph.nodes)
168
+ cost = np.zeros((n, n))
169
+
170
+ for u, v, data in self.graph.edges(data=True):
171
+ cost[u, v] = data.get('weight', 1.0)
172
+
173
+ # Add diagonal costs
174
+ for node_id, meta in self.node_metadata.items():
175
+ cost[node_id, node_id] = meta['cost']
176
+
177
+ return cost
178
+
179
+ def update_node(self, node_id: int, **kwargs):
180
+ """Update node metadata."""
181
+ if node_id in self.node_metadata:
182
+ self.node_metadata[node_id].update(kwargs)
183
+
184
+ def get_state_dict(self) -> Dict[str, Any]:
185
+ """Export graph state."""
186
+ return {
187
+ 'n_nodes': len(self.graph.nodes),
188
+ 'n_edges': len(self.graph.edges),
189
+ 'entropy': self.get_entropy(),
190
+ 'uncertainty': self.get_uncertainty(),
191
+ 'obstacle_density': self.get_obstacle_density(),
192
+ 'positions': self.node_positions.copy(),
193
+ 'metadata': self.node_metadata.copy()
194
+ }