Skip to main content

qvm_scheduler/composer/
mapping.rs

1//! Qubit and classical bit mapping management
2
3use crate::{QvmError, Result, Qubit, ClassicalBit, Topology};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Resource mapping for a circuit
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ResourceMapping {
10    /// Logical to physical qubit mapping
11    pub qubit_mapping: Vec<usize>,
12    /// Logical to physical classical bit mapping
13    pub classical_mapping: Vec<usize>,
14    /// Mapping metadata
15    pub metadata: MappingMetadata,
16}
17
18/// Mapping metadata
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20pub struct MappingMetadata {
21    /// Mapping quality score (0.0 to 1.0)
22    pub quality_score: f64,
23    /// Number of SWAP operations needed
24    pub swap_count: usize,
25    /// Routing overhead
26    pub routing_overhead: f64,
27    /// Custom mapping properties
28    pub properties: HashMap<String, String>,
29}
30
31/// Qubit mapper for creating optimal qubit assignments
32#[derive(Debug, Clone)]
33pub struct QubitMapper {
34    topology: Topology,
35    config: MapperConfig,
36}
37
38/// Mapper configuration
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct MapperConfig {
41    /// Mapping strategy
42    pub strategy: MappingStrategy,
43    /// Enable SWAP insertion
44    pub enable_swaps: bool,
45    /// Maximum SWAP overhead allowed
46    pub max_swap_overhead: f64,
47    /// Prioritize connectivity
48    pub prioritize_connectivity: bool,
49}
50
51impl Default for MapperConfig {
52    fn default() -> Self {
53        Self {
54            strategy: MappingStrategy::ConnectivityAware,
55            enable_swaps: true,
56            max_swap_overhead: 0.5,
57            prioritize_connectivity: true,
58        }
59    }
60}
61
62/// Mapping strategies
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum MappingStrategy {
65    /// Simple linear mapping
66    Linear,
67    /// Random mapping
68    Random,
69    /// Connectivity-aware mapping
70    ConnectivityAware,
71    /// Distance-minimizing mapping
72    DistanceMinimizing,
73    /// Adaptive mapping
74    Adaptive,
75}
76
77impl QubitMapper {
78    /// Create a new qubit mapper
79    pub fn new(topology: &Topology) -> Self {
80        Self {
81            topology: topology.clone(),
82            config: MapperConfig::default(),
83        }
84    }
85
86    /// Create a classical bit allocator
87    pub fn create_classical_allocator(&self) -> ClassicalAllocator {
88        ClassicalAllocator::new(self.topology.qubit_count()) // Assume same number of classical bits as qubits
89    }
90
91    /// Create mapper with custom configuration
92    pub fn with_config(topology: &Topology, config: MapperConfig) -> Self {
93        Self {
94            topology: topology.clone(),
95            config,
96        }
97    }
98
99    /// Create a resource mapping
100    pub fn create_mapping(
101        &self,
102        qubit_assignments: &[usize],
103        classical_assignments: &[usize],
104    ) -> Result<ResourceMapping> {
105        let quality_score = self.calculate_mapping_quality(qubit_assignments);
106        
107        let metadata = MappingMetadata {
108            quality_score,
109            swap_count: 0, // Would be calculated during routing
110            routing_overhead: 0.0,
111            properties: HashMap::new(),
112        };
113
114        Ok(ResourceMapping {
115            qubit_mapping: qubit_assignments.to_vec(),
116            classical_mapping: classical_assignments.to_vec(),
117            metadata,
118        })
119    }
120
121    /// Find optimal qubit mapping for a circuit
122    pub fn find_optimal_mapping(
123        &self,
124        logical_qubits: usize,
125        connectivity_requirements: &[(usize, usize)],
126    ) -> Result<Vec<usize>> {
127        match self.config.strategy {
128            MappingStrategy::Linear => self.linear_mapping(logical_qubits),
129            MappingStrategy::Random => self.random_mapping(logical_qubits),
130            MappingStrategy::ConnectivityAware => {
131                self.connectivity_aware_mapping(logical_qubits, connectivity_requirements)
132            }
133            MappingStrategy::DistanceMinimizing => {
134                self.distance_minimizing_mapping(logical_qubits, connectivity_requirements)
135            }
136            MappingStrategy::Adaptive => {
137                self.adaptive_mapping(logical_qubits, connectivity_requirements)
138            }
139        }
140    }
141
142    /// Linear mapping strategy
143    fn linear_mapping(&self, logical_qubits: usize) -> Result<Vec<usize>> {
144        if logical_qubits > self.topology.qubit_count() {
145            return Err(QvmError::allocation_error(
146                "Not enough physical qubits available".to_string()
147            ));
148        }
149
150        Ok((0..logical_qubits).collect())
151    }
152
153    /// Random mapping strategy
154    fn random_mapping(&self, logical_qubits: usize) -> Result<Vec<usize>> {
155        use std::collections::HashSet;
156
157        if logical_qubits > self.topology.qubit_count() {
158            return Err(QvmError::allocation_error(
159                "Not enough physical qubits available".to_string()
160            ));
161        }
162
163        let mut mapping = Vec::new();
164        let mut used_qubits = HashSet::new();
165        
166        // Simple pseudo-random assignment (not cryptographically secure)
167        let mut seed = 12345u64;
168        for _ in 0..logical_qubits {
169            loop {
170                seed = (seed.wrapping_mul(1103515245).wrapping_add(12345)) & 0x7fffffff;
171                let physical_qubit = (seed as usize) % self.topology.qubit_count();
172                
173                if !used_qubits.contains(&physical_qubit) {
174                    mapping.push(physical_qubit);
175                    used_qubits.insert(physical_qubit);
176                    break;
177                }
178            }
179        }
180
181        Ok(mapping)
182    }
183
184    /// Connectivity-aware mapping strategy
185    fn connectivity_aware_mapping(
186        &self,
187        logical_qubits: usize,
188        connectivity_requirements: &[(usize, usize)],
189    ) -> Result<Vec<usize>> {
190        if logical_qubits > self.topology.qubit_count() {
191            return Err(QvmError::allocation_error(
192                "Not enough physical qubits available".to_string()
193            ));
194        }
195
196        // Start with linear mapping
197        let mut mapping = (0..logical_qubits).collect::<Vec<_>>();
198
199        // Try to improve mapping based on connectivity requirements
200        for &(logical1, logical2) in connectivity_requirements {
201            if logical1 >= logical_qubits || logical2 >= logical_qubits {
202                continue;
203            }
204
205            let physical1 = mapping[logical1];
206            let physical2 = mapping[logical2];
207
208            // Check if these physical qubits are connected
209            if !self.topology.are_connected(Qubit(physical1), Qubit(physical2)) {
210                // Try to find better mapping
211                if let Some(better_mapping) = self.find_connected_pair(logical1, logical2, &mapping) {
212                    mapping = better_mapping;
213                }
214            }
215        }
216
217        Ok(mapping)
218    }
219
220    /// Distance-minimizing mapping strategy
221    fn distance_minimizing_mapping(
222        &self,
223        logical_qubits: usize,
224        connectivity_requirements: &[(usize, usize)],
225    ) -> Result<Vec<usize>> {
226        if logical_qubits > self.topology.qubit_count() {
227            return Err(QvmError::allocation_error(
228                "Not enough physical qubits available".to_string()
229            ));
230        }
231
232        // Simple greedy approach: place the most connected qubits on central qubits
233        let mut mapping = vec![0; logical_qubits];
234        let mut used_physical = std::collections::HashSet::new();
235
236        // Count logical qubit connectivity
237        let mut connectivity_count = vec![0; logical_qubits];
238        for &(q1, q2) in connectivity_requirements {
239            if q1 < logical_qubits { connectivity_count[q1] += 1; }
240            if q2 < logical_qubits { connectivity_count[q2] += 1; }
241        }
242
243        // Sort by connectivity (most connected first)
244        let mut qubit_order: Vec<_> = (0..logical_qubits).collect();
245        qubit_order.sort_by_key(|&q| std::cmp::Reverse(connectivity_count[q]));
246
247        // Assign most connected qubits to most central physical qubits
248        let physical_centrality = self.calculate_centrality();
249        let mut physical_order: Vec<_> = (0..self.topology.qubit_count()).collect();
250        physical_order.sort_by(|&a, &b| {
251            physical_centrality[b].partial_cmp(&physical_centrality[a]).unwrap_or(std::cmp::Ordering::Equal)
252        });
253
254        for (i, &logical_qubit) in qubit_order.iter().enumerate() {
255            if i < physical_order.len() {
256                mapping[logical_qubit] = physical_order[i];
257                used_physical.insert(physical_order[i]);
258            }
259        }
260
261        Ok(mapping)
262    }
263
264    /// Adaptive mapping strategy
265    fn adaptive_mapping(
266        &self,
267        logical_qubits: usize,
268        connectivity_requirements: &[(usize, usize)],
269    ) -> Result<Vec<usize>> {
270        // Choose strategy based on circuit characteristics
271        let connectivity_density = connectivity_requirements.len() as f64 / (logical_qubits * logical_qubits) as f64;
272        
273        if connectivity_density > 0.5 {
274            self.distance_minimizing_mapping(logical_qubits, connectivity_requirements)
275        } else if connectivity_density > 0.1 {
276            self.connectivity_aware_mapping(logical_qubits, connectivity_requirements)
277        } else {
278            self.linear_mapping(logical_qubits)
279        }
280    }
281
282    /// Calculate centrality of each physical qubit
283    fn calculate_centrality(&self) -> Vec<f64> {
284        let qubit_count = self.topology.qubit_count();
285        let mut centrality = vec![0.0; qubit_count];
286
287        for i in 0..qubit_count {
288            // Simple centrality: number of neighbors
289            centrality[i] = self.topology.neighbors(Qubit(i)).len() as f64;
290        }
291
292        centrality
293    }
294
295    /// Find a connected pair for two logical qubits
296    fn find_connected_pair(
297        &self,
298        logical1: usize,
299        logical2: usize,
300        current_mapping: &[usize],
301    ) -> Option<Vec<usize>> {
302        let mut used_physical: std::collections::HashSet<_> = current_mapping.iter().collect();
303        
304        // Try different physical qubit pairs
305        for physical1 in 0..self.topology.qubit_count() {
306            if used_physical.contains(&physical1) && current_mapping[logical1] != physical1 {
307                continue;
308            }
309
310            for neighbor in self.topology.neighbors(Qubit(physical1)) {
311                let physical2 = neighbor.index();
312                
313                if used_physical.contains(&physical2) && current_mapping[logical2] != physical2 {
314                    continue;
315                }
316
317                // Create new mapping
318                let mut new_mapping = current_mapping.to_vec();
319                new_mapping[logical1] = physical1;
320                new_mapping[logical2] = physical2;
321                
322                return Some(new_mapping);
323            }
324        }
325
326        None
327    }
328
329    /// Calculate mapping quality score
330    fn calculate_mapping_quality(&self, mapping: &[usize]) -> f64 {
331        if mapping.is_empty() {
332            return 1.0;
333        }
334
335        let mut total_distance = 0.0;
336        let mut pair_count = 0;
337
338        // Calculate average distance between consecutive qubits
339        for i in 0..(mapping.len() - 1) {
340            if let Some(path) = self.topology.shortest_path(Qubit(mapping[i]), Qubit(mapping[i + 1])) {
341                total_distance += (path.len() - 1) as f64;
342                pair_count += 1;
343            }
344        }
345
346        if pair_count == 0 {
347            return 1.0;
348        }
349
350        let avg_distance = total_distance / pair_count as f64;
351        
352        // Quality decreases with distance (1.0 for distance 1, 0.5 for distance 2, etc.)
353        1.0 / (1.0 + avg_distance - 1.0)
354    }
355
356    /// Route a two-qubit gate with SWAP insertion if needed
357    pub fn route_two_qubit_gate(
358        &self,
359        control: usize,
360        target: usize,
361        mapping: &mut [usize],
362    ) -> Result<Vec<SwapOperation>> {
363        let physical_control = mapping[control];
364        let physical_target = mapping[target];
365
366        if self.topology.are_connected(Qubit(physical_control), Qubit(physical_target)) {
367            // Already connected, no SWAPs needed
368            return Ok(vec![]);
369        }
370
371        if !self.config.enable_swaps {
372            return Err(QvmError::allocation_error(
373                "Qubits not connected and SWAP insertion disabled".to_string()
374            ));
375        }
376
377        // Find shortest path and insert SWAPs
378        if let Some(path) = self.topology.shortest_path(Qubit(physical_control), Qubit(physical_target)) {
379            let mut swaps = Vec::new();
380            
381            // Insert SWAPs to bring qubits together
382            for i in 0..(path.len() - 2) {
383                let swap_op = SwapOperation {
384                    qubit1: path[i].index(),
385                    qubit2: path[i + 1].index(),
386                };
387                swaps.push(swap_op);
388                
389                // Update mapping
390                // This is simplified - in practice you'd need more sophisticated tracking
391            }
392
393            // Check swap overhead
394            let overhead = swaps.len() as f64 / mapping.len() as f64;
395            if overhead > self.config.max_swap_overhead {
396                return Err(QvmError::allocation_error(
397                    "SWAP overhead too high".to_string()
398                ));
399            }
400
401            Ok(swaps)
402        } else {
403            Err(QvmError::allocation_error(
404                "No path found between qubits".to_string()
405            ))
406        }
407    }
408}
409
410/// SWAP operation for routing
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct SwapOperation {
413    pub qubit1: usize,
414    pub qubit2: usize,
415}
416
417/// Classical bit allocator for managing classical bit assignments
418#[derive(Debug, Clone)]
419pub struct ClassicalAllocator {
420    /// Total classical bits available
421    total_bits: usize,
422    /// Currently allocated bits
423    allocated_bits: std::collections::HashSet<usize>,
424    /// Allocation strategy
425    strategy: ClassicalAllocationStrategy,
426}
427
428/// Classical bit allocation strategies
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
430pub enum ClassicalAllocationStrategy {
431    /// Sequential allocation
432    Sequential,
433    /// Random allocation
434    Random,
435    /// Minimize conflicts with measurement patterns
436    ConflictMinimizing,
437}
438
439impl ClassicalAllocator {
440    /// Create a new classical allocator
441    pub fn new(total_bits: usize) -> Self {
442        Self {
443            total_bits,
444            allocated_bits: std::collections::HashSet::new(),
445            strategy: ClassicalAllocationStrategy::Sequential,
446        }
447    }
448
449    /// Allocate classical bits for a circuit
450    pub fn allocate_bits(&mut self, required_bits: usize) -> Result<Vec<usize>> {
451        if required_bits > self.available_bits() {
452            return Err(QvmError::allocation_error(
453                format!("Not enough classical bits: need {}, have {}", 
454                       required_bits, self.available_bits())
455            ));
456        }
457
458        match self.strategy {
459            ClassicalAllocationStrategy::Sequential => self.allocate_sequential(required_bits),
460            ClassicalAllocationStrategy::Random => self.allocate_random(required_bits),
461            ClassicalAllocationStrategy::ConflictMinimizing => self.allocate_conflict_minimizing(required_bits),
462        }
463    }
464
465    /// Sequential allocation
466    fn allocate_sequential(&mut self, required_bits: usize) -> Result<Vec<usize>> {
467        let mut allocation = Vec::new();
468        
469        for bit in 0..self.total_bits {
470            if !self.allocated_bits.contains(&bit) {
471                allocation.push(bit);
472                self.allocated_bits.insert(bit);
473                
474                if allocation.len() >= required_bits {
475                    break;
476                }
477            }
478        }
479        
480        Ok(allocation)
481    }
482
483    /// Random allocation
484    fn allocate_random(&mut self, required_bits: usize) -> Result<Vec<usize>> {
485        let mut allocation = Vec::new();
486        let available: Vec<_> = (0..self.total_bits)
487            .filter(|bit| !self.allocated_bits.contains(bit))
488            .collect();
489        
490        // Simple pseudo-random selection
491        let mut seed = 54321u64;
492        for _ in 0..required_bits {
493            if available.is_empty() {
494                break;
495            }
496            
497            seed = (seed.wrapping_mul(1103515245).wrapping_add(12345)) & 0x7fffffff;
498            let idx = (seed as usize) % available.len();
499            let bit = available[idx];
500            
501            allocation.push(bit);
502            self.allocated_bits.insert(bit);
503        }
504        
505        Ok(allocation)
506    }
507
508    /// Conflict-minimizing allocation
509    fn allocate_conflict_minimizing(&mut self, required_bits: usize) -> Result<Vec<usize>> {
510        // For now, use sequential allocation
511        // In a real implementation, this would analyze measurement patterns
512        self.allocate_sequential(required_bits)
513    }
514
515    /// Release allocated bits
516    pub fn release_bits(&mut self, bits: &[usize]) {
517        for &bit in bits {
518            self.allocated_bits.remove(&bit);
519        }
520    }
521
522    /// Get number of available bits
523    pub fn available_bits(&self) -> usize {
524        self.total_bits - self.allocated_bits.len()
525    }
526
527    /// Reset all allocations
528    pub fn reset(&mut self) {
529        self.allocated_bits.clear();
530    }
531
532    /// Check if a bit is allocated
533    pub fn is_allocated(&self, bit: usize) -> bool {
534        self.allocated_bits.contains(&bit)
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541    use crate::topology::TopologyBuilder;
542
543    #[test]
544    fn test_qubit_mapper_creation() {
545        let topology = TopologyBuilder::grid(3, 3);
546        let mapper = QubitMapper::new(&topology);
547        assert_eq!(mapper.config.strategy, MappingStrategy::ConnectivityAware);
548    }
549
550    #[test]
551    fn test_linear_mapping() {
552        let topology = TopologyBuilder::grid(3, 3);
553        let mapper = QubitMapper::new(&topology);
554        
555        let mapping = mapper.linear_mapping(4).unwrap();
556        assert_eq!(mapping, vec![0, 1, 2, 3]);
557    }
558
559    #[test]
560    fn test_mapping_quality() {
561        let topology = TopologyBuilder::linear(5);
562        let mapper = QubitMapper::new(&topology);
563        
564        let good_mapping = vec![0, 1, 2]; // Sequential
565        let bad_mapping = vec![0, 2, 4];  // Gaps
566        
567        let quality_good = mapper.calculate_mapping_quality(&good_mapping);
568        let quality_bad = mapper.calculate_mapping_quality(&bad_mapping);
569        
570        assert!(quality_good > quality_bad);
571    }
572
573    #[test]
574    fn test_resource_mapping_creation() {
575        let topology = TopologyBuilder::grid(2, 2);
576        let mapper = QubitMapper::new(&topology);
577        
578        let qubit_assignments = vec![0, 1];
579        let classical_assignments = vec![0, 1];
580        
581        let mapping = mapper.create_mapping(&qubit_assignments, &classical_assignments).unwrap();
582        assert_eq!(mapping.qubit_mapping, vec![0, 1]);
583        assert_eq!(mapping.classical_mapping, vec![0, 1]);
584        assert!(mapping.metadata.quality_score > 0.0);
585    }
586
587    #[test]
588    fn test_connectivity_aware_mapping() {
589        let topology = TopologyBuilder::grid(3, 3);
590        let mapper = QubitMapper::new(&topology);
591        
592        let connectivity_requirements = vec![(0, 1), (1, 2)];
593        let mapping = mapper.connectivity_aware_mapping(3, &connectivity_requirements).unwrap();
594        
595        assert_eq!(mapping.len(), 3);
596        // Should try to keep connected qubits close
597    }
598}