1use crate::{QvmError, Result, Topology};
4use crate::scheduler::{Job, Assignment, BinPacker, BinPackingAlgorithm};
5use crate::topology::{TileFinder, TilePreferences, TileManager};
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct SpatialMultiplexConfig {
12 pub enabled: bool,
14 pub max_parallel_jobs: usize,
16 pub min_job_separation: usize,
18 pub buffer_zone_size: usize,
20 pub isolation_strategy: SpatialIsolationStrategy,
22}
23
24impl Default for SpatialMultiplexConfig {
25 fn default() -> Self {
26 Self {
27 enabled: true,
28 max_parallel_jobs: 4,
29 min_job_separation: 2,
30 buffer_zone_size: 1,
31 isolation_strategy: SpatialIsolationStrategy::TileBased,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum SpatialIsolationStrategy {
39 TileBased,
41 DistanceBased,
43 BufferZoned,
45 ConnectivityBased,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct TemporalMultiplexConfig {
52 pub enabled: bool,
54 pub min_batch_size: usize,
56 pub max_batch_size: usize,
58 pub time_slice_duration: u64,
60 pub overlap_tolerance: u64,
62 pub scheduling_strategy: TemporalSchedulingStrategy,
64}
65
66impl Default for TemporalMultiplexConfig {
67 fn default() -> Self {
68 Self {
69 enabled: true,
70 min_batch_size: 2,
71 max_batch_size: 10,
72 time_slice_duration: 100_000, overlap_tolerance: 5_000, scheduling_strategy: TemporalSchedulingStrategy::RoundRobin,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
81pub enum TemporalSchedulingStrategy {
82 RoundRobin,
84 PriorityBased,
86 DeadlineAware,
88 LoadBalanced,
90}
91
92#[derive(Debug, Clone)]
94pub struct SpatialMultiplexer {
95 topology: Topology,
96 tile_manager: TileManager,
97 config: SpatialMultiplexConfig,
98}
99
100impl SpatialMultiplexer {
101 pub fn new(topology: Topology, config: SpatialMultiplexConfig) -> Self {
103 let tile_manager = TileManager::new();
104
105 Self {
106 topology,
107 tile_manager,
108 config,
109 }
110 }
111
112 pub async fn schedule_spatially(&mut self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
114 if !self.config.enabled {
115 return self.schedule_sequentially(jobs).await;
117 }
118
119 let parallel_groups = self.create_spatial_groups(jobs)?;
121 let mut all_assignments = Vec::new();
122
123 for group in parallel_groups {
124 let assignments = self.schedule_parallel_group(group).await?;
125 all_assignments.extend(assignments);
126 }
127
128 Ok(all_assignments)
129 }
130
131 fn create_spatial_groups(&self, jobs: Vec<Job>) -> Result<Vec<Vec<Job>>> {
133 let mut groups = Vec::new();
134 let mut remaining_jobs = jobs;
135
136 while !remaining_jobs.is_empty() {
137 let mut current_group = Vec::new();
138 let mut used_tiles = HashSet::new();
139 let mut i = 0;
140
141 while i < remaining_jobs.len() && current_group.len() < self.config.max_parallel_jobs {
143 let job = &remaining_jobs[i];
144
145 if let Ok(tile) = self.find_suitable_tile_for_job(job, &used_tiles) {
146 current_group.push(remaining_jobs.remove(i));
147 used_tiles.insert(tile.id);
148 } else {
149 i += 1;
150 }
151 }
152
153 if current_group.is_empty() && !remaining_jobs.is_empty() {
155 current_group.push(remaining_jobs.remove(0));
156 }
157
158 if !current_group.is_empty() {
159 groups.push(current_group);
160 }
161 }
162
163 Ok(groups)
164 }
165
166 async fn schedule_parallel_group(&mut self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
168 match self.config.isolation_strategy {
169 SpatialIsolationStrategy::TileBased => {
170 self.schedule_tile_based(jobs).await
171 }
172 SpatialIsolationStrategy::DistanceBased => {
173 self.schedule_distance_based(jobs).await
174 }
175 SpatialIsolationStrategy::BufferZoned => {
176 self.schedule_buffer_zoned(jobs).await
177 }
178 SpatialIsolationStrategy::ConnectivityBased => {
179 self.schedule_connectivity_based(jobs).await
180 }
181 }
182 }
183
184 async fn schedule_tile_based(&mut self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
186 let mut assignments = Vec::new();
187 let start_time = 0u64; for job in jobs {
190 let preferences = TilePreferences {
191 min_width: 2,
192 min_height: 2,
193 max_qubits: job.requirements.qubits_needed * 2,
194 buffer_size: self.config.buffer_zone_size,
195 prefer_center: false, min_connectivity: 0.5,
197 ..Default::default()
198 };
199
200 let tile_finder = TileFinder::new(&self.topology);
201 let tile = tile_finder
202 .find_best_tile(job.requirements.qubits_needed, &preferences)?
203 .ok_or_else(|| QvmError::scheduling_error("No suitable tile found for spatial multiplexing"))?;
204
205 let qubit_mapping: Vec<usize> = tile.qubits
206 .iter()
207 .take(job.requirements.qubits_needed)
208 .map(|q| q.index())
209 .collect();
210
211 let assignment = Assignment {
212 job_id: job.id,
213 tile_id: tile.id,
214 start_time,
215 duration: job.estimated_duration,
216 qubit_mapping,
217 classical_mapping: (0..job.circuit.num_classical).collect(),
218 resource_allocation: Default::default(),
219 };
220
221 assignments.push(assignment);
222 }
223
224 Ok(assignments)
225 }
226
227 async fn schedule_distance_based(&mut self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
229 let mut assignments = Vec::new();
230 let mut used_qubits = HashSet::new();
231 let start_time = 0u64;
232
233 for job in jobs {
234 let suitable_qubits = self.find_distant_qubits(&used_qubits, job.requirements.qubits_needed)?;
236
237 if suitable_qubits.len() < job.requirements.qubits_needed {
238 return Err(QvmError::scheduling_error("Not enough distant qubits available"));
239 }
240
241 let qubit_mapping: Vec<usize> = suitable_qubits
242 .into_iter()
243 .take(job.requirements.qubits_needed)
244 .collect();
245
246 for &qubit_idx in &qubit_mapping {
248 let buffer_qubits = self.topology.qubits_within_distance(
249 qubit_idx.into(),
250 self.config.min_job_separation as u32
251 );
252 for (buffer_qubit, _) in buffer_qubits {
253 used_qubits.insert(buffer_qubit.index());
254 }
255 }
256
257 let assignment = Assignment {
258 job_id: job.id,
259 tile_id: 0, start_time,
261 duration: job.estimated_duration,
262 qubit_mapping,
263 classical_mapping: (0..job.circuit.num_classical).collect(),
264 resource_allocation: Default::default(),
265 };
266
267 assignments.push(assignment);
268 }
269
270 Ok(assignments)
271 }
272
273 async fn schedule_buffer_zoned(&mut self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
275 let mut assignments = Vec::new();
277 let mut used_qubits = HashSet::new();
278 let start_time = 0u64;
279
280 for job in jobs {
281 let (job_qubits, buffer_qubits) = self.find_qubits_with_buffers(
283 &used_qubits,
284 job.requirements.qubits_needed
285 )?;
286
287 let assignment = Assignment {
288 job_id: job.id,
289 tile_id: 0,
290 start_time,
291 duration: job.estimated_duration,
292 qubit_mapping: job_qubits.clone(),
293 classical_mapping: (0..job.circuit.num_classical).collect(),
294 resource_allocation: crate::scheduler::ResourceAllocation {
295 buffer_qubits,
296 ..Default::default()
297 },
298 };
299
300 for &qubit in &assignment.qubit_mapping {
302 used_qubits.insert(qubit);
303 }
304 for &buffer in &assignment.resource_allocation.buffer_qubits {
305 used_qubits.insert(buffer);
306 }
307
308 assignments.push(assignment);
309 }
310
311 Ok(assignments)
312 }
313
314 async fn schedule_connectivity_based(&mut self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
316 let partitions = self.partition_topology(jobs.len())?;
318 let mut assignments = Vec::new();
319 let start_time = 0u64;
320
321 for (job, partition) in jobs.into_iter().zip(partitions) {
322 if partition.len() < job.requirements.qubits_needed {
323 return Err(QvmError::scheduling_error("Partition too small for job requirements"));
324 }
325
326 let qubit_mapping: Vec<usize> = partition
327 .into_iter()
328 .take(job.requirements.qubits_needed)
329 .collect();
330
331 let assignment = Assignment {
332 job_id: job.id,
333 tile_id: 0,
334 start_time,
335 duration: job.estimated_duration,
336 qubit_mapping,
337 classical_mapping: (0..job.circuit.num_classical).collect(),
338 resource_allocation: Default::default(),
339 };
340
341 assignments.push(assignment);
342 }
343
344 Ok(assignments)
345 }
346
347 async fn schedule_sequentially(&self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
349 let bin_packer = BinPacker::with_algorithm(BinPackingAlgorithm::FirstFitDecreasing);
350 let batch = crate::scheduler::batch::Batch {
351 id: 0,
352 jobs,
353 metadata: Default::default(),
354 };
355 bin_packer.pack_batch(batch, &self.topology).await
356 }
357
358 fn find_suitable_tile_for_job(&self, job: &Job, used_tiles: &HashSet<usize>) -> Result<crate::topology::Tile> {
360 Err(QvmError::scheduling_error("Tile finding not implemented"))
362 }
363
364 fn find_distant_qubits(&self, used_qubits: &HashSet<usize>, needed: usize) -> Result<Vec<usize>> {
365 let mut suitable = Vec::new();
366
367 for qubit in self.topology.qubits() {
368 let qubit_idx = qubit.index();
369 if used_qubits.contains(&qubit_idx) {
370 continue;
371 }
372
373 let is_distant = used_qubits.iter().all(|&used_idx| {
375 if let Some(path) = self.topology.shortest_path(qubit, used_idx.into()) {
376 path.len() > self.config.min_job_separation
377 } else {
378 true }
380 });
381
382 if is_distant {
383 suitable.push(qubit_idx);
384 if suitable.len() >= needed {
385 break;
386 }
387 }
388 }
389
390 Ok(suitable)
391 }
392
393 fn find_qubits_with_buffers(&self, used_qubits: &HashSet<usize>, needed: usize) -> Result<(Vec<usize>, Vec<usize>)> {
394 let mut job_qubits = Vec::new();
395 let mut buffer_qubits = Vec::new();
396
397 for qubit in self.topology.qubits() {
398 let qubit_idx = qubit.index();
399 if used_qubits.contains(&qubit_idx) {
400 continue;
401 }
402
403 let buffer_zone = self.topology.qubits_within_distance(
405 qubit,
406 self.config.buffer_zone_size as u32
407 );
408
409 let buffer_available = buffer_zone.iter().all(|(buffer_qubit, _)| {
411 !used_qubits.contains(&buffer_qubit.index())
412 });
413
414 if buffer_available {
415 job_qubits.push(qubit_idx);
416 buffer_qubits.extend(
417 buffer_zone.into_iter()
418 .filter(|(q, _)| q.index() != qubit_idx)
419 .map(|(q, _)| q.index())
420 );
421
422 if job_qubits.len() >= needed {
423 break;
424 }
425 }
426 }
427
428 Ok((job_qubits, buffer_qubits))
429 }
430
431 fn partition_topology(&self, num_partitions: usize) -> Result<Vec<Vec<usize>>> {
432 let qubits: Vec<_> = self.topology.qubits().into_iter().map(|q| q.index()).collect();
434 let partition_size = qubits.len() / num_partitions;
435
436 let mut partitions = Vec::new();
437 for i in 0..num_partitions {
438 let start = i * partition_size;
439 let end = if i == num_partitions - 1 { qubits.len() } else { (i + 1) * partition_size };
440 partitions.push(qubits[start..end].to_vec());
441 }
442
443 Ok(partitions)
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct TemporalMultiplexer {
450 config: TemporalMultiplexConfig,
451}
452
453impl TemporalMultiplexer {
454 pub fn new(config: TemporalMultiplexConfig) -> Self {
456 Self { config }
457 }
458
459 pub async fn schedule_temporally(&self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
461 if !self.config.enabled {
462 return Ok(vec![]); }
464
465 let batches = self.create_temporal_batches(jobs)?;
467 let mut all_assignments = Vec::new();
468 let mut current_time = 0u64;
469
470 for batch in batches {
471 let batch_assignments = self.schedule_temporal_batch(batch, current_time).await?;
472
473 if let Some(max_end_time) = batch_assignments.iter()
475 .map(|a| a.start_time + a.duration)
476 .max() {
477 current_time = max_end_time + self.config.overlap_tolerance;
478 }
479
480 all_assignments.extend(batch_assignments);
481 }
482
483 Ok(all_assignments)
484 }
485
486 fn create_temporal_batches(&self, mut jobs: Vec<Job>) -> Result<Vec<Vec<Job>>> {
488 match self.config.scheduling_strategy {
489 TemporalSchedulingStrategy::RoundRobin => {
490 self.create_round_robin_batches(jobs)
491 }
492 TemporalSchedulingStrategy::PriorityBased => {
493 jobs.sort_by_key(|job| std::cmp::Reverse(job.priority));
494 self.create_fixed_size_batches(jobs)
495 }
496 TemporalSchedulingStrategy::DeadlineAware => {
497 jobs.sort_by_key(|job| job.deadline.unwrap_or(u64::MAX));
498 self.create_fixed_size_batches(jobs)
499 }
500 TemporalSchedulingStrategy::LoadBalanced => {
501 self.create_load_balanced_batches(jobs)
502 }
503 }
504 }
505
506 fn create_round_robin_batches(&self, jobs: Vec<Job>) -> Result<Vec<Vec<Job>>> {
507 let mut batches = Vec::new();
508 let mut current_batch = Vec::new();
509
510 for job in jobs {
511 current_batch.push(job);
512
513 if current_batch.len() >= self.config.max_batch_size {
514 batches.push(current_batch);
515 current_batch = Vec::new();
516 }
517 }
518
519 if !current_batch.is_empty() {
520 batches.push(current_batch);
521 }
522
523 Ok(batches)
524 }
525
526 fn create_fixed_size_batches(&self, jobs: Vec<Job>) -> Result<Vec<Vec<Job>>> {
527 let mut batches = Vec::new();
528 let chunk_size = self.config.max_batch_size;
529
530 for chunk in jobs.chunks(chunk_size) {
531 batches.push(chunk.to_vec());
532 }
533
534 Ok(batches)
535 }
536
537 fn create_load_balanced_batches(&self, jobs: Vec<Job>) -> Result<Vec<Vec<Job>>> {
538 let mut batches = Vec::new();
540 let mut current_batch = Vec::new();
541 let mut current_batch_duration = 0u64;
542
543 for job in jobs {
544 if current_batch_duration + job.estimated_duration > self.config.time_slice_duration
545 && current_batch.len() >= self.config.min_batch_size {
546 batches.push(current_batch);
547 current_batch = Vec::new();
548 current_batch_duration = 0;
549 }
550
551 current_batch_duration += job.estimated_duration;
552 current_batch.push(job);
553
554 if current_batch.len() >= self.config.max_batch_size {
555 batches.push(current_batch);
556 current_batch = Vec::new();
557 current_batch_duration = 0;
558 }
559 }
560
561 if !current_batch.is_empty() {
562 batches.push(current_batch);
563 }
564
565 Ok(batches)
566 }
567
568 async fn schedule_temporal_batch(&self, jobs: Vec<Job>, start_time: u64) -> Result<Vec<Assignment>> {
569 let mut assignments = Vec::new();
572
573 for (i, job) in jobs.into_iter().enumerate() {
574 let assignment = Assignment {
575 job_id: job.id,
576 tile_id: 0,
577 start_time: start_time + (i as u64 * 1000), duration: job.estimated_duration,
579 qubit_mapping: (0..job.requirements.qubits_needed).collect(),
580 classical_mapping: (0..job.circuit.num_classical).collect(),
581 resource_allocation: Default::default(),
582 };
583 assignments.push(assignment);
584 }
585
586 Ok(assignments)
587 }
588}
589
590#[derive(Debug, Clone)]
592pub struct HybridMultiplexer {
593 spatial: SpatialMultiplexer,
594 temporal: TemporalMultiplexer,
595}
596
597impl HybridMultiplexer {
598 pub fn new(
600 topology: Topology,
601 spatial_config: SpatialMultiplexConfig,
602 temporal_config: TemporalMultiplexConfig,
603 ) -> Self {
604 Self {
605 spatial: SpatialMultiplexer::new(topology, spatial_config),
606 temporal: TemporalMultiplexer::new(temporal_config),
607 }
608 }
609
610 pub async fn schedule_hybrid(&mut self, jobs: Vec<Job>) -> Result<Vec<Assignment>> {
612 let temporal_batches = self.temporal.create_temporal_batches(jobs)?;
614 let mut all_assignments = Vec::new();
615 let mut current_time = 0u64;
616
617 for batch in temporal_batches {
618 let mut spatial_assignments = self.spatial.schedule_spatially(batch).await?;
620
621 for assignment in &mut spatial_assignments {
623 assignment.start_time += current_time;
624 }
625
626 if let Some(max_end_time) = spatial_assignments.iter()
628 .map(|a| a.start_time + a.duration)
629 .max() {
630 current_time = max_end_time + self.temporal.config.overlap_tolerance;
631 }
632
633 all_assignments.extend(spatial_assignments);
634 }
635
636 Ok(all_assignments)
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643 use crate::topology::TopologyBuilder;
644 use crate::circuit_ir::CircuitBuilder;
645
646 #[test]
647 fn test_spatial_multiplex_config() {
648 let config = SpatialMultiplexConfig::default();
649 assert!(config.enabled);
650 assert_eq!(config.max_parallel_jobs, 4);
651 assert_eq!(config.isolation_strategy, SpatialIsolationStrategy::TileBased);
652 }
653
654 #[test]
655 fn test_temporal_multiplex_config() {
656 let config = TemporalMultiplexConfig::default();
657 assert!(config.enabled);
658 assert_eq!(config.min_batch_size, 2);
659 assert_eq!(config.scheduling_strategy, TemporalSchedulingStrategy::RoundRobin);
660 }
661
662 #[tokio::test]
663 async fn test_temporal_multiplexer() {
664 let config = TemporalMultiplexConfig::default();
665 let multiplexer = TemporalMultiplexer::new(config);
666
667 let circuit = CircuitBuilder::new("test", 2, 2).h(0).unwrap().build();
668 let jobs = vec![
669 Job::new(0, circuit.clone()),
670 Job::new(1, circuit.clone()),
671 Job::new(2, circuit),
672 ];
673
674 let assignments = multiplexer.schedule_temporally(jobs).await.unwrap();
675 assert_eq!(assignments.len(), 3);
676
677 for i in 1..assignments.len() {
679 assert!(assignments[i].start_time >= assignments[i-1].start_time);
680 }
681 }
682}