1use std::cmp::Ordering;
8use std::collections::{BinaryHeap, HashMap, HashSet};
9
10use crate::embedding::{Embedding, HardwareGraph, HardwareTopology};
11use crate::ising::{IsingError, IsingResult};
12
13#[derive(Debug, Clone)]
15pub struct LayoutConfig {
16 pub distance_weight: f64,
18 pub chain_length_weight: f64,
20 pub chain_degree_weight: f64,
22 pub max_chain_length: usize,
24 pub use_spectral_placement: bool,
26 pub refinement_iterations: usize,
28}
29
30impl Default for LayoutConfig {
31 fn default() -> Self {
32 Self {
33 distance_weight: 1.0,
34 chain_length_weight: 2.0,
35 chain_degree_weight: 0.5,
36 max_chain_length: 5,
37 use_spectral_placement: true,
38 refinement_iterations: 10,
39 }
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct LayoutStats {
46 pub avg_chain_length: f64,
48 pub max_chain_length: usize,
50 pub total_chain_length: usize,
52 pub long_chains: usize,
54 pub quality_score: f64,
56}
57
58pub struct LayoutAwareEmbedder {
60 config: LayoutConfig,
61 shortest_paths: HashMap<(usize, usize), Vec<usize>>,
63 qubit_coordinates: Option<HashMap<usize, Vec<f64>>>,
65}
66
67impl LayoutAwareEmbedder {
68 #[must_use]
70 pub fn new(config: LayoutConfig) -> Self {
71 Self {
72 config,
73 shortest_paths: HashMap::new(),
74 qubit_coordinates: None,
75 }
76 }
77
78 pub fn set_coordinates(&mut self, coordinates: HashMap<usize, Vec<f64>>) {
80 self.qubit_coordinates = Some(coordinates);
81 }
82
83 pub fn find_embedding(
85 &mut self,
86 logical_edges: &[(usize, usize)],
87 num_vars: usize,
88 hardware: &HardwareGraph,
89 ) -> IsingResult<(Embedding, LayoutStats)> {
90 if self.qubit_coordinates.is_none() && hardware.coordinates.is_some() {
92 self.qubit_coordinates = hardware.coordinates.as_ref().map(|coords| {
93 coords
94 .iter()
95 .map(|(q, coord)| (*q, coord.iter().map(|&x| x as f64).collect()))
96 .collect()
97 });
98 }
99
100 let initial_placement = if self.config.use_spectral_placement {
102 self.spectral_placement(logical_edges, num_vars, hardware)?
103 } else {
104 self.greedy_placement(logical_edges, num_vars, hardware)?
105 };
106
107 let mut embedding = self.build_chains(&initial_placement, logical_edges, hardware)?;
109
110 for _ in 0..self.config.refinement_iterations {
112 let improved = self.refine_embedding(&mut embedding, logical_edges, hardware)?;
113 if !improved {
114 break;
115 }
116 }
117
118 let stats = self.compute_stats(&embedding);
120
121 Ok((embedding, stats))
122 }
123
124 fn spectral_placement(
126 &self,
127 logical_edges: &[(usize, usize)],
128 num_vars: usize,
129 hardware: &HardwareGraph,
130 ) -> IsingResult<HashMap<usize, usize>> {
131 let mut placement = HashMap::new();
135 let mut available_qubits: Vec<_> = (0..hardware.num_qubits).collect();
136
137 let mut var_degrees: Vec<_> = (0..num_vars)
139 .map(|v| {
140 let degree = logical_edges
141 .iter()
142 .filter(|&&(u, w)| u == v || w == v)
143 .count();
144 (v, degree)
145 })
146 .collect();
147 var_degrees.sort_by_key(|&(_, d)| std::cmp::Reverse(d));
148
149 for (var, _) in var_degrees {
151 if let Some(best_qubit) =
152 self.find_best_qubit(var, &placement, logical_edges, &available_qubits, hardware)
153 {
154 placement.insert(var, best_qubit);
155 available_qubits.retain(|&q| q != best_qubit);
156 }
157 }
158
159 Ok(placement)
160 }
161
162 fn greedy_placement(
164 &self,
165 logical_edges: &[(usize, usize)],
166 num_vars: usize,
167 hardware: &HardwareGraph,
168 ) -> IsingResult<HashMap<usize, usize>> {
169 let mut placement = HashMap::new();
170 let mut available_qubits: HashSet<_> = (0..hardware.num_qubits).collect();
171
172 let mut var_order = self.order_variables_by_connectivity(logical_edges, num_vars);
174
175 for var in var_order {
176 let neighbors = self.get_logical_neighbors(var, logical_edges);
177 let placed_neighbors: Vec<_> = neighbors
178 .iter()
179 .filter_map(|&n| placement.get(&n).copied())
180 .collect();
181
182 let best_qubit = if placed_neighbors.is_empty() {
183 self.find_central_qubit(&available_qubits, hardware)
185 } else {
186 self.find_closest_qubit(&placed_neighbors, &available_qubits, hardware)?
188 };
189
190 placement.insert(var, best_qubit);
191 available_qubits.remove(&best_qubit);
192 }
193
194 Ok(placement)
195 }
196
197 fn build_chains(
199 &self,
200 placement: &HashMap<usize, usize>,
201 logical_edges: &[(usize, usize)],
202 hardware: &HardwareGraph,
203 ) -> IsingResult<Embedding> {
204 let mut embedding = Embedding::new();
205 let mut used_qubits = HashSet::new();
206
207 for (&var, &qubit) in placement {
209 embedding.add_chain(var, vec![qubit])?;
210 used_qubits.insert(qubit);
211 }
212
213 for &(u, v) in logical_edges {
215 if let (Some(chain_u), Some(chain_v)) =
216 (embedding.chains.get(&u), embedding.chains.get(&v))
217 {
218 if !self.chains_connected(chain_u, chain_v, hardware) {
219 let path =
221 self.find_connection_path(chain_u, chain_v, hardware, &used_qubits)?;
222
223 if chain_u.len() <= chain_v.len() {
225 self.extend_chain(&mut embedding, u, &path, &mut used_qubits)?;
226 } else {
227 self.extend_chain(&mut embedding, v, &path, &mut used_qubits)?;
228 }
229 }
230 }
231 }
232
233 Ok(embedding)
234 }
235
236 fn refine_embedding(
238 &self,
239 embedding: &mut Embedding,
240 logical_edges: &[(usize, usize)],
241 hardware: &HardwareGraph,
242 ) -> IsingResult<bool> {
243 let mut improved = false;
244
245 for (var, chain) in embedding.chains.clone() {
247 if chain.len() > self.config.max_chain_length {
248 if let Some(shorter_chain) =
249 self.find_shorter_chain(var, &chain, embedding, logical_edges, hardware)
250 {
251 embedding.chains.insert(var, shorter_chain);
252 improved = true;
253 }
254 }
255 }
256
257 for var in 0..embedding.chains.len() {
259 if self.try_local_swap(embedding, var, logical_edges, hardware)? {
260 improved = true;
261 }
262 }
263
264 Ok(improved)
265 }
266
267 fn find_best_qubit(
269 &self,
270 var: usize,
271 placement: &HashMap<usize, usize>,
272 logical_edges: &[(usize, usize)],
273 available: &[usize],
274 hardware: &HardwareGraph,
275 ) -> Option<usize> {
276 let neighbors = self.get_logical_neighbors(var, logical_edges);
277 let placed_neighbors: Vec<_> = neighbors
278 .iter()
279 .filter_map(|&n| placement.get(&n))
280 .collect();
281
282 if placed_neighbors.is_empty() {
283 available.first().copied()
285 } else {
286 available
288 .iter()
289 .min_by_key(|&&q| {
290 let total_dist: usize = placed_neighbors
291 .iter()
292 .map(|&&nq| self.qubit_distance(q, nq, hardware))
293 .sum();
294 total_dist
295 })
296 .copied()
297 }
298 }
299
300 fn find_central_qubit(&self, available: &HashSet<usize>, hardware: &HardwareGraph) -> usize {
302 available
304 .iter()
305 .max_by_key(|&&q| hardware.neighbors(q).len())
306 .copied()
307 .unwrap_or(0)
308 }
309
310 fn find_closest_qubit(
312 &self,
313 targets: &[usize],
314 available: &HashSet<usize>,
315 hardware: &HardwareGraph,
316 ) -> IsingResult<usize> {
317 available
318 .iter()
319 .min_by_key(|&&q| {
320 targets
321 .iter()
322 .map(|&t| self.qubit_distance(q, t, hardware))
323 .sum::<usize>()
324 })
325 .copied()
326 .ok_or_else(|| IsingError::HardwareConstraint("No available qubits".to_string()))
327 }
328
329 fn qubit_distance(&self, q1: usize, q2: usize, hardware: &HardwareGraph) -> usize {
331 if let Some(coords) = &self.qubit_coordinates {
332 if let (Some(c1), Some(c2)) = (coords.get(&q1), coords.get(&q2)) {
334 let dist_sq: f64 = c1
335 .iter()
336 .zip(c2.iter())
337 .map(|(x1, x2)| (x1 - x2).powi(2))
338 .sum();
339 return dist_sq.sqrt() as usize;
340 }
341 }
342
343 self.graph_distance(q1, q2, hardware)
345 }
346
347 fn graph_distance(&self, q1: usize, q2: usize, hardware: &HardwareGraph) -> usize {
349 if q1 == q2 {
350 return 0;
351 }
352
353 let mut visited = HashSet::new();
355 let mut queue = vec![(q1, 0)];
356 visited.insert(q1);
357
358 while let Some((current, dist)) = queue.pop() {
359 for neighbor in hardware.neighbors(current) {
360 if neighbor == q2 {
361 return dist + 1;
362 }
363 if visited.insert(neighbor) {
364 queue.push((neighbor, dist + 1));
365 }
366 }
367 }
368
369 usize::MAX }
371
372 fn chains_connected(
374 &self,
375 chain1: &[usize],
376 chain2: &[usize],
377 hardware: &HardwareGraph,
378 ) -> bool {
379 for &q1 in chain1 {
380 for &q2 in chain2 {
381 if hardware.are_connected(q1, q2) {
382 return true;
383 }
384 }
385 }
386 false
387 }
388
389 fn find_connection_path(
391 &self,
392 chain1: &[usize],
393 chain2: &[usize],
394 hardware: &HardwareGraph,
395 used_qubits: &HashSet<usize>,
396 ) -> IsingResult<Vec<usize>> {
397 let mut best_path = None;
399 let mut best_cost = usize::MAX;
400
401 for &start in chain1 {
402 for &goal in chain2 {
403 if let Some(path) = self.astar_path(start, goal, hardware, used_qubits) {
404 if path.len() < best_cost {
405 best_cost = path.len();
406 best_path = Some(path);
407 }
408 }
409 }
410 }
411
412 best_path.ok_or_else(|| IsingError::HardwareConstraint("Cannot connect chains".to_string()))
413 }
414
415 fn astar_path(
417 &self,
418 start: usize,
419 goal: usize,
420 hardware: &HardwareGraph,
421 used_qubits: &HashSet<usize>,
422 ) -> Option<Vec<usize>> {
423 #[derive(Eq, PartialEq)]
424 struct State {
425 cost: usize,
426 position: usize,
427 }
428
429 impl Ord for State {
430 fn cmp(&self, other: &Self) -> Ordering {
431 other.cost.cmp(&self.cost)
432 }
433 }
434
435 impl PartialOrd for State {
436 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
437 Some(self.cmp(other))
438 }
439 }
440
441 let mut heap = BinaryHeap::new();
442 let mut dist = HashMap::new();
443 let mut parent = HashMap::new();
444
445 heap.push(State {
446 cost: 0,
447 position: start,
448 });
449 dist.insert(start, 0);
450
451 while let Some(State { cost, position }) = heap.pop() {
452 if position == goal {
453 let mut path = Vec::new();
455 let mut current = goal;
456 while current != start {
457 if !used_qubits.contains(¤t) {
458 path.push(current);
459 }
460 current = parent[¤t];
461 }
462 path.reverse();
463 return Some(path);
464 }
465
466 if cost > dist[&position] {
467 continue;
468 }
469
470 for neighbor in hardware.neighbors(position) {
471 let next_cost = cost + 1;
472
473 if next_cost < *dist.get(&neighbor).unwrap_or(&usize::MAX) {
474 dist.insert(neighbor, next_cost);
475 parent.insert(neighbor, position);
476 let heuristic = self.qubit_distance(neighbor, goal, hardware);
477 heap.push(State {
478 cost: next_cost + heuristic,
479 position: neighbor,
480 });
481 }
482 }
483 }
484
485 None
486 }
487
488 fn extend_chain(
490 &self,
491 embedding: &mut Embedding,
492 var: usize,
493 extension: &[usize],
494 used_qubits: &mut HashSet<usize>,
495 ) -> IsingResult<()> {
496 if let Some(chain) = embedding.chains.get_mut(&var) {
497 for &qubit in extension {
498 if used_qubits.insert(qubit) {
499 chain.push(qubit);
500 embedding.qubit_to_variable.insert(qubit, var);
501 }
502 }
503 }
504 Ok(())
505 }
506
507 fn find_shorter_chain(
509 &self,
510 var: usize,
511 current_chain: &[usize],
512 embedding: &Embedding,
513 logical_edges: &[(usize, usize)],
514 hardware: &HardwareGraph,
515 ) -> Option<Vec<usize>> {
516 let neighbors = self.get_logical_neighbors(var, logical_edges);
518 let neighbor_chains: Vec<_> = neighbors
519 .iter()
520 .filter_map(|&n| embedding.chains.get(&n))
521 .collect();
522
523 None
526 }
527
528 const fn try_local_swap(
530 &self,
531 embedding: &Embedding,
532 var: usize,
533 logical_edges: &[(usize, usize)],
534 hardware: &HardwareGraph,
535 ) -> IsingResult<bool> {
536 Ok(false)
538 }
539
540 fn get_logical_neighbors(&self, var: usize, logical_edges: &[(usize, usize)]) -> Vec<usize> {
542 let mut neighbors = Vec::new();
543 for &(u, v) in logical_edges {
544 if u == var {
545 neighbors.push(v);
546 } else if v == var {
547 neighbors.push(u);
548 }
549 }
550 neighbors
551 }
552
553 fn order_variables_by_connectivity(
555 &self,
556 logical_edges: &[(usize, usize)],
557 num_vars: usize,
558 ) -> Vec<usize> {
559 let mut degrees: Vec<_> = (0..num_vars)
560 .map(|v| {
561 let degree = logical_edges
562 .iter()
563 .filter(|&&(u, w)| u == v || w == v)
564 .count();
565 (v, degree)
566 })
567 .collect();
568
569 degrees.sort_by_key(|&(_, d)| std::cmp::Reverse(d));
570 degrees.into_iter().map(|(v, _)| v).collect()
571 }
572
573 fn compute_stats(&self, embedding: &Embedding) -> LayoutStats {
575 let chain_lengths: Vec<_> = embedding.chains.values().map(std::vec::Vec::len).collect();
576
577 let total_length: usize = chain_lengths.iter().sum();
578 let max_length = chain_lengths.iter().copied().max().unwrap_or(0);
579 let avg_length = if chain_lengths.is_empty() {
580 0.0
581 } else {
582 total_length as f64 / chain_lengths.len() as f64
583 };
584
585 let long_chains = chain_lengths
586 .iter()
587 .filter(|&&len| len > self.config.max_chain_length)
588 .count();
589
590 let quality_score =
592 avg_length * self.config.chain_length_weight + long_chains as f64 * 10.0;
593
594 LayoutStats {
595 avg_chain_length: avg_length,
596 max_chain_length: max_length,
597 total_chain_length: total_length,
598 long_chains,
599 quality_score,
600 }
601 }
602}
603
604pub struct MultiLevelEmbedder {
606 base_embedder: LayoutAwareEmbedder,
608 num_levels: usize,
610 coarsening_ratio: f64,
612}
613
614impl MultiLevelEmbedder {
615 #[must_use]
617 pub fn new(config: LayoutConfig, num_levels: usize) -> Self {
618 Self {
619 base_embedder: LayoutAwareEmbedder::new(config),
620 num_levels,
621 coarsening_ratio: 0.5,
622 }
623 }
624
625 pub fn find_embedding(
627 &mut self,
628 logical_edges: &[(usize, usize)],
629 num_vars: usize,
630 hardware: &HardwareGraph,
631 ) -> IsingResult<(Embedding, LayoutStats)> {
632 let coarsened_graphs = self.coarsen_graph(logical_edges, num_vars);
634
635 let coarsest = coarsened_graphs
637 .last()
638 .expect("coarsen_graph should return at least the original graph");
639
640 let (mut embedding, _) =
642 self.base_embedder
643 .find_embedding(&coarsest.0, coarsest.1, hardware)?;
644
645 for level in (0..coarsened_graphs.len() - 1).rev() {
647 embedding = self.refine_level(
648 embedding,
649 &coarsened_graphs[level],
650 &coarsened_graphs[level + 1],
651 hardware,
652 )?;
653 }
654
655 let stats = self.base_embedder.compute_stats(&embedding);
656 Ok((embedding, stats))
657 }
658
659 fn coarsen_graph(
661 &self,
662 logical_edges: &[(usize, usize)],
663 num_vars: usize,
664 ) -> Vec<(Vec<(usize, usize)>, usize)> {
665 vec![(logical_edges.to_vec(), num_vars)]
667 }
668
669 const fn refine_level(
671 &self,
672 coarse_embedding: Embedding,
673 fine_graph: &(Vec<(usize, usize)>, usize),
674 coarse_graph: &(Vec<(usize, usize)>, usize),
675 hardware: &HardwareGraph,
676 ) -> IsingResult<Embedding> {
677 Ok(coarse_embedding)
679 }
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685
686 #[test]
687 fn test_layout_embedder_creation() {
688 let config = LayoutConfig::default();
689 let embedder = LayoutAwareEmbedder::new(config);
690 assert!(embedder.shortest_paths.is_empty());
691 }
692
693 #[test]
694 fn test_qubit_distance() {
695 let config = LayoutConfig::default();
696 let embedder = LayoutAwareEmbedder::new(config);
697
698 let hardware = HardwareGraph::new_chimera(2, 2, 2)
699 .expect("should create Chimera hardware graph for testing");
700
701 assert_eq!(embedder.qubit_distance(0, 0, &hardware), 0);
703
704 let dist = embedder.qubit_distance(0, 2, &hardware);
706 assert!(dist > 0);
707 }
708}