1use std::collections::{HashMap, HashSet};
9use std::hash::{Hash, Hasher};
10
11use thiserror::Error;
12
13use crate::arch::addr::{LaneAddr, LocationAddr, ZoneAddr};
14use crate::arch::query::LaneGroupError;
15use crate::arch::types::ArchSpec;
16
17#[derive(Debug, Clone, PartialEq, Eq, Error)]
24pub enum MoveValidationError {
25 #[error("lane {lane:?} cannot be resolved to endpoints")]
27 UnresolvableLane {
28 lane: LaneAddr,
30 },
31
32 #[error("{0}")]
34 LaneGroup(LaneGroupError),
35
36 #[error(
44 "lane {lane:?} targets {dst:?}, which is occupied by qubit {occupant} \
45 that does not move in this group"
46 )]
47 DestinationOccupiedByStationaryAtom {
48 lane: LaneAddr,
50 dst: LocationAddr,
52 occupant: u32,
54 },
55
56 #[error(
60 "stale ValidatedMoves token: lane {lane:?} expected qubit {expected} \
61 at {src:?}, which no longer holds it"
62 )]
63 StaleMoverSource {
64 lane: LaneAddr,
66 src: LocationAddr,
68 expected: u32,
70 },
71
72 #[error("lanes {first:?} and {second:?} share destination {dst:?}")]
78 ContestedDestination {
79 dst: LocationAddr,
81 first: LaneAddr,
83 second: LaneAddr,
85 },
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct ValidatedMoves {
99 movers: Vec<(u32, LocationAddr, LocationAddr, LaneAddr)>,
102}
103
104impl ValidatedMoves {
105 pub fn movers(&self) -> &[(u32, LocationAddr, LocationAddr, LaneAddr)] {
107 &self.movers
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct AtomStateData {
122 pub locations_to_qubit: HashMap<LocationAddr, u32>,
124 pub qubit_to_locations: HashMap<u32, LocationAddr>,
126 pub collision: HashMap<u32, u32>,
132 pub prev_lanes: HashMap<u32, LaneAddr>,
135 pub move_count: HashMap<u32, u32>,
138}
139
140impl Hash for AtomStateData {
141 fn hash<H: Hasher>(&self, state: &mut H) {
142 fn hash_sorted_map<H: Hasher, K: Ord + Hash, V: Hash>(
145 state: &mut H,
146 tag: u8,
147 entries: &mut [(K, V)],
148 ) {
149 tag.hash(state);
150 entries.len().hash(state);
151 entries.sort_by(|a, b| a.0.cmp(&b.0));
152 for (k, v) in entries.iter() {
153 k.hash(state);
154 v.hash(state);
155 }
156 }
157
158 let mut loc_entries: Vec<_> = self
159 .locations_to_qubit
160 .iter()
161 .map(|(k, v)| (k.encode(), *v))
162 .collect();
163 hash_sorted_map(state, 0, &mut loc_entries);
164
165 let mut qubit_entries: Vec<_> = self
166 .qubit_to_locations
167 .iter()
168 .map(|(k, v)| (*k, v.encode()))
169 .collect();
170 hash_sorted_map(state, 1, &mut qubit_entries);
171
172 let mut collision_entries: Vec<_> = self.collision.iter().map(|(k, v)| (*k, *v)).collect();
173 hash_sorted_map(state, 2, &mut collision_entries);
174
175 let mut lane_entries: Vec<_> = self
176 .prev_lanes
177 .iter()
178 .map(|(k, v)| (*k, v.encode_u64()))
179 .collect();
180 hash_sorted_map(state, 3, &mut lane_entries);
181
182 let mut count_entries: Vec<_> = self.move_count.iter().map(|(k, v)| (*k, *v)).collect();
183 hash_sorted_map(state, 4, &mut count_entries);
184 }
185}
186
187impl AtomStateData {
188 pub fn new() -> Self {
190 Self {
191 locations_to_qubit: HashMap::new(),
192 qubit_to_locations: HashMap::new(),
193 collision: HashMap::new(),
194 prev_lanes: HashMap::new(),
195 move_count: HashMap::new(),
196 }
197 }
198
199 pub fn from_locations(locations: &[(u32, LocationAddr)]) -> Self {
204 let mut locations_to_qubit = HashMap::new();
205 let mut qubit_to_locations = HashMap::new();
206
207 for &(qubit, loc) in locations {
208 qubit_to_locations.insert(qubit, loc);
209 locations_to_qubit.insert(loc, qubit);
210 }
211
212 Self {
213 locations_to_qubit,
214 qubit_to_locations,
215 collision: HashMap::new(),
216 prev_lanes: HashMap::new(),
217 move_count: HashMap::new(),
218 }
219 }
220
221 pub fn add_atoms(&self, locations: &[(u32, LocationAddr)]) -> Result<Self, &'static str> {
230 let mut qubit_to_locations = self.qubit_to_locations.clone();
231 let mut locations_to_qubit = self.locations_to_qubit.clone();
232
233 for &(qubit, loc) in locations {
234 if qubit_to_locations.contains_key(&qubit) {
235 return Err("Attempted to add atom that already exists");
236 }
237 if locations_to_qubit.contains_key(&loc) {
238 return Err("Attempted to add atom to occupied location");
239 }
240 qubit_to_locations.insert(qubit, loc);
241 locations_to_qubit.insert(loc, qubit);
242 }
243
244 Ok(Self {
245 locations_to_qubit,
246 qubit_to_locations,
247 collision: HashMap::new(),
248 prev_lanes: HashMap::new(),
249 move_count: HashMap::new(),
250 })
251 }
252
253 fn resolve_movers(
260 &self,
261 lanes: &[LaneAddr],
262 arch_spec: &ArchSpec,
263 ) -> Option<Vec<(u32, LocationAddr, LocationAddr, LaneAddr)>> {
264 let mut movers = Vec::with_capacity(lanes.len());
265 let mut seen_srcs: HashSet<LocationAddr> = HashSet::new();
266 for lane in lanes {
267 let (src, dst) = arch_spec.lane_endpoints(lane)?;
268 if !seen_srcs.insert(src) {
269 continue;
270 }
271 if let Some(&qubit) = self.locations_to_qubit.get(&src) {
272 movers.push((qubit, src, dst, *lane));
273 }
274 }
275 Some(movers)
276 }
277
278 pub fn apply_moves(&self, lanes: &[LaneAddr], arch_spec: &ArchSpec) -> Option<Self> {
300 let mut movers = self.resolve_movers(lanes, arch_spec)?;
301 movers.sort_unstable_by_key(|&(qubit, ..)| qubit);
305
306 let mut qubit_to_locations = self.qubit_to_locations.clone();
307 let mut locations_to_qubit = self.locations_to_qubit.clone();
308 let mut collisions = self.collision.clone();
309 let mut move_count = self.move_count.clone();
310 let mut prev_lanes: HashMap<u32, LaneAddr> = HashMap::new();
311
312 for (qubit, src, _, _) in &movers {
314 locations_to_qubit.remove(src);
315 qubit_to_locations.remove(qubit);
316 }
317
318 let mover_srcs: HashSet<LocationAddr> = movers.iter().map(|&(_, src, ..)| src).collect();
320 for (qubit, _, dst, lane) in &movers {
321 *move_count.entry(*qubit).or_insert(0) += 1;
322 prev_lanes.insert(*qubit, *lane);
323
324 if let Some(&stationary) = self.locations_to_qubit.get(dst)
327 && !mover_srcs.contains(dst)
328 {
329 locations_to_qubit.remove(dst);
330 qubit_to_locations.remove(&stationary);
331 collisions.insert(*qubit, stationary);
332 continue;
333 }
334
335 if let Some(&other) = locations_to_qubit.get(dst) {
338 locations_to_qubit.remove(dst);
339 qubit_to_locations.remove(&other);
340 collisions.insert(*qubit, other);
341 continue;
342 }
343
344 qubit_to_locations.insert(*qubit, *dst);
345 locations_to_qubit.insert(*dst, *qubit);
346 }
347
348 Some(Self {
349 locations_to_qubit,
350 qubit_to_locations,
351 prev_lanes,
352 collision: collisions,
353 move_count,
354 })
355 }
356
357 pub fn validate_moves(
380 &self,
381 lanes: &[LaneAddr],
382 arch_spec: &ArchSpec,
383 ) -> Result<ValidatedMoves, Vec<MoveValidationError>> {
384 let mut errors: Vec<MoveValidationError> = arch_spec
385 .check_lanes(lanes)
386 .into_iter()
387 .map(MoveValidationError::LaneGroup)
388 .collect();
389
390 let invalid_lane_reported = errors.iter().any(|e| {
394 matches!(
395 e,
396 MoveValidationError::LaneGroup(LaneGroupError::InvalidLane { .. })
397 )
398 });
399
400 let mut seen_lanes: HashSet<u64> = HashSet::new();
404 let mut resolved: Vec<(LaneAddr, LocationAddr, LocationAddr)> =
405 Vec::with_capacity(lanes.len());
406 for lane in lanes {
407 if !seen_lanes.insert(lane.encode_u64()) {
408 continue;
409 }
410 match arch_spec.lane_endpoints(lane) {
411 Some((src, dst)) => resolved.push((*lane, src, dst)),
412 None if !invalid_lane_reported => {
413 errors.push(MoveValidationError::UnresolvableLane { lane: *lane })
414 }
415 None => {}
416 }
417 }
418
419 let mover_srcs: HashSet<LocationAddr> = resolved
420 .iter()
421 .filter(|(_, src, _)| self.locations_to_qubit.contains_key(src))
422 .map(|&(_, src, _)| src)
423 .collect();
424
425 let mut claimed_dsts: HashMap<LocationAddr, LaneAddr> = HashMap::new();
426 for &(lane, _, dst) in &resolved {
427 if let Some(&occupant) = self.locations_to_qubit.get(&dst)
428 && !mover_srcs.contains(&dst)
429 {
430 errors.push(MoveValidationError::DestinationOccupiedByStationaryAtom {
431 lane,
432 dst,
433 occupant,
434 });
435 }
436 if let Some(&first) = claimed_dsts.get(&dst) {
437 errors.push(MoveValidationError::ContestedDestination {
438 dst,
439 first,
440 second: lane,
441 });
442 } else {
443 claimed_dsts.insert(dst, lane);
444 }
445 }
446
447 if !errors.is_empty() {
448 return Err(errors);
449 }
450
451 let movers = self
452 .resolve_movers(lanes, arch_spec)
453 .expect("all lanes resolved above");
454 Ok(ValidatedMoves { movers })
455 }
456
457 pub fn apply_validated(
474 &self,
475 moves: &ValidatedMoves,
476 ) -> Result<Self, Vec<MoveValidationError>> {
477 let mover_srcs: HashSet<LocationAddr> =
478 moves.movers.iter().map(|&(_, src, _, _)| src).collect();
479 let mut errors: Vec<MoveValidationError> = Vec::new();
480 for &(qubit, src, dst, lane) in &moves.movers {
481 if self.locations_to_qubit.get(&src) != Some(&qubit) {
482 errors.push(MoveValidationError::StaleMoverSource {
483 lane,
484 src,
485 expected: qubit,
486 });
487 continue;
488 }
489 if let Some(&occupant) = self.locations_to_qubit.get(&dst)
490 && !mover_srcs.contains(&dst)
491 {
492 errors.push(MoveValidationError::DestinationOccupiedByStationaryAtom {
493 lane,
494 dst,
495 occupant,
496 });
497 }
498 }
499 if !errors.is_empty() {
500 return Err(errors);
501 }
502
503 let mut qubit_to_locations = self.qubit_to_locations.clone();
504 let mut locations_to_qubit = self.locations_to_qubit.clone();
505 let mut move_count = self.move_count.clone();
506 let mut prev_lanes: HashMap<u32, LaneAddr> = HashMap::new();
507
508 for (qubit, src, _, _) in &moves.movers {
509 locations_to_qubit.remove(src);
510 qubit_to_locations.remove(qubit);
511 }
512
513 for (qubit, _, dst, lane) in &moves.movers {
514 *move_count.entry(*qubit).or_insert(0) += 1;
515 prev_lanes.insert(*qubit, *lane);
516 qubit_to_locations.insert(*qubit, *dst);
517 locations_to_qubit.insert(*dst, *qubit);
518 }
519
520 Ok(Self {
521 locations_to_qubit,
522 qubit_to_locations,
523 prev_lanes,
524 collision: self.collision.clone(),
525 move_count,
526 })
527 }
528
529 pub fn get_qubit(&self, location: &LocationAddr) -> Option<u32> {
531 self.locations_to_qubit.get(location).copied()
532 }
533
534 pub fn get_qubit_pairing(
546 &self,
547 zone: &ZoneAddr,
548 arch_spec: &ArchSpec,
549 ) -> Option<(Vec<u32>, Vec<u32>, Vec<u32>)> {
550 let _zone_data = arch_spec.zone_by_id(zone.zone_id)?;
553 let zone_id = zone.zone_id;
554
555 let mut controls = Vec::new();
556 let mut targets = Vec::new();
557 let mut unpaired = Vec::new();
558 let mut visited = std::collections::HashSet::new();
559
560 let mut sorted_qubits: Vec<_> = self.qubit_to_locations.iter().collect();
562 sorted_qubits.sort_by_key(|(qubit, _)| **qubit);
563
564 for (qubit, loc) in &sorted_qubits {
565 let qubit = **qubit;
566 let loc = **loc;
567 if visited.contains(&qubit) {
568 continue;
569 }
570 visited.insert(qubit);
571
572 if loc.zone_id != zone_id {
573 continue;
574 }
575
576 let blockaded = match arch_spec.get_cz_partner(&loc) {
577 Some(b) => b,
578 None => {
579 unpaired.push(qubit);
580 continue;
581 }
582 };
583
584 let target_qubit = match self.get_qubit(&blockaded) {
585 Some(t) => t,
586 None => {
587 unpaired.push(qubit);
588 continue;
589 }
590 };
591
592 controls.push(qubit);
593 targets.push(target_qubit);
594 visited.insert(target_qubit);
595 }
596
597 Some((controls, targets, unpaired))
598 }
599}
600
601impl Default for AtomStateData {
602 fn default() -> Self {
603 Self::new()
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610 use crate::arch::addr::{SiteRef, WordRef, ZonedWordRef};
611 use crate::arch::types::{Bus, Grid, Mode, Word, Zone};
612 use crate::version::Version;
613
614 fn make_test_spec() -> crate::arch::ArchSpec {
618 let grid0 = Grid::from_positions(&[0.0, 5.0, 10.0], &[0.0, 3.0]);
619 let grid1 = Grid::from_positions(&[0.0, 7.5, 15.0], &[0.0, 4.0]);
620
621 crate::arch::ArchSpec {
622 version: Version::new(2, 0),
623 words: vec![
624 Word {
625 sites: vec![[0, 0], [0, 1]],
626 },
627 Word {
628 sites: vec![[1, 0], [1, 1]],
629 },
630 ],
631 zones: vec![
632 Zone {
633 name: String::new(),
634 grid: grid0,
635 site_buses: vec![Bus {
636 src: vec![SiteRef(0)],
637 dst: vec![SiteRef(1)],
638 }],
639 word_buses: vec![Bus {
640 src: vec![WordRef(0)],
641 dst: vec![WordRef(1)],
642 }],
643 words_with_site_buses: vec![0, 1],
644 sites_with_word_buses: vec![0],
645 entangling_pairs: vec![[0, 1]],
646 },
647 Zone {
648 name: String::new(),
649 grid: grid1,
650 site_buses: vec![],
651 word_buses: vec![],
652 words_with_site_buses: vec![],
653 sites_with_word_buses: vec![],
654 entangling_pairs: vec![],
655 },
656 ],
657 zone_buses: vec![Bus {
658 src: vec![ZonedWordRef {
659 zone_id: 0,
660 word_id: 0,
661 }],
662 dst: vec![ZonedWordRef {
663 zone_id: 1,
664 word_id: 0,
665 }],
666 }],
667 modes: vec![Mode {
668 name: "full".to_string(),
669 zones: vec![0, 1],
670 bitstring_order: vec![],
671 }],
672 paths: None,
673 feed_forward: false,
674 atom_reloading: false,
675 blockade_radius: None,
676 }
677 }
678
679 #[test]
680 fn new_state_is_empty() {
681 let state = AtomStateData::new();
682 assert!(state.locations_to_qubit.is_empty());
683 assert!(state.qubit_to_locations.is_empty());
684 assert!(state.collision.is_empty());
685 assert!(state.prev_lanes.is_empty());
686 assert!(state.move_count.is_empty());
687 }
688
689 #[test]
690 fn from_locations_creates_bidirectional_map() {
691 let locs = vec![
692 (
693 0,
694 LocationAddr {
695 zone_id: 0,
696 word_id: 0,
697 site_id: 0,
698 },
699 ),
700 (
701 1,
702 LocationAddr {
703 zone_id: 0,
704 word_id: 1,
705 site_id: 0,
706 },
707 ),
708 ];
709 let state = AtomStateData::from_locations(&locs);
710 assert_eq!(
711 state.get_qubit(&LocationAddr {
712 zone_id: 0,
713 word_id: 0,
714 site_id: 0
715 }),
716 Some(0)
717 );
718 assert_eq!(
719 state.get_qubit(&LocationAddr {
720 zone_id: 0,
721 word_id: 1,
722 site_id: 0
723 }),
724 Some(1)
725 );
726 }
727
728 #[test]
729 fn add_atoms_succeeds_and_fields_match() {
730 let state = AtomStateData::new();
731 let loc0 = LocationAddr {
732 zone_id: 0,
733 word_id: 0,
734 site_id: 0,
735 };
736 let loc1 = LocationAddr {
737 zone_id: 0,
738 word_id: 1,
739 site_id: 0,
740 };
741 let new_state = state.add_atoms(&[(0, loc0), (1, loc1)]).unwrap();
742
743 assert_eq!(new_state.qubit_to_locations.len(), 2);
744 assert_eq!(new_state.qubit_to_locations[&0], loc0);
745 assert_eq!(new_state.qubit_to_locations[&1], loc1);
746 assert_eq!(new_state.locations_to_qubit[&loc0], 0);
747 assert_eq!(new_state.locations_to_qubit[&loc1], 1);
748 assert!(new_state.collision.is_empty());
749 assert!(new_state.prev_lanes.is_empty());
750 assert!(new_state.move_count.is_empty());
751 }
752
753 #[test]
754 fn add_atoms_duplicate_qubit_fails() {
755 let state = AtomStateData::from_locations(&[(
756 0,
757 LocationAddr {
758 zone_id: 0,
759 word_id: 0,
760 site_id: 0,
761 },
762 )]);
763 let result = state.add_atoms(&[(
764 0,
765 LocationAddr {
766 zone_id: 0,
767 word_id: 1,
768 site_id: 0,
769 },
770 )]);
771 assert!(result.is_err());
772 }
773
774 #[test]
775 fn add_atoms_occupied_location_fails() {
776 let state = AtomStateData::from_locations(&[(
777 0,
778 LocationAddr {
779 zone_id: 0,
780 word_id: 0,
781 site_id: 0,
782 },
783 )]);
784 let result = state.add_atoms(&[(
785 1,
786 LocationAddr {
787 zone_id: 0,
788 word_id: 0,
789 site_id: 0,
790 },
791 )]);
792 assert!(result.is_err());
793 }
794
795 #[test]
796 fn apply_moves_basic() {
797 let spec = make_test_spec();
798 let state = AtomStateData::from_locations(&[
800 (
801 0,
802 LocationAddr {
803 zone_id: 0,
804 word_id: 0,
805 site_id: 0,
806 },
807 ),
808 (
809 1,
810 LocationAddr {
811 zone_id: 0,
812 word_id: 1,
813 site_id: 0,
814 },
815 ),
816 ]);
817
818 let lane = LaneAddr {
820 direction: crate::arch::addr::Direction::Forward,
821 move_type: crate::arch::addr::MoveType::SiteBus,
822 zone_id: 0,
823 word_id: 0,
824 site_id: 0,
825 bus_id: 0,
826 };
827
828 let new_state = state.apply_moves(&[lane], &spec).unwrap();
829 assert_eq!(
830 new_state.get_qubit(&LocationAddr {
831 zone_id: 0,
832 word_id: 0,
833 site_id: 1
834 }),
835 Some(0)
836 );
837 assert_eq!(
838 new_state.get_qubit(&LocationAddr {
839 zone_id: 0,
840 word_id: 0,
841 site_id: 0
842 }),
843 None
844 );
845 assert_eq!(*new_state.move_count.get(&0).unwrap(), 1);
846 }
847
848 #[test]
849 fn apply_moves_collision() {
850 let spec = make_test_spec();
851 let state = AtomStateData::from_locations(&[
853 (
854 0,
855 LocationAddr {
856 zone_id: 0,
857 word_id: 0,
858 site_id: 0,
859 },
860 ),
861 (
862 1,
863 LocationAddr {
864 zone_id: 0,
865 word_id: 0,
866 site_id: 1,
867 },
868 ),
869 ]);
870
871 let lane = LaneAddr {
872 direction: crate::arch::addr::Direction::Forward,
873 move_type: crate::arch::addr::MoveType::SiteBus,
874 zone_id: 0,
875 word_id: 0,
876 site_id: 0,
877 bus_id: 0,
878 };
879
880 let new_state = state.apply_moves(&[lane], &spec).unwrap();
881 assert!(new_state.collision.contains_key(&0));
882 assert_eq!(*new_state.collision.get(&0).unwrap(), 1);
883 assert!(new_state.qubit_to_locations.is_empty());
884 }
885
886 #[test]
887 fn apply_moves_verifies_all_fields() {
888 let spec = make_test_spec();
889 let loc_0_0 = LocationAddr {
890 zone_id: 0,
891 word_id: 0,
892 site_id: 0,
893 };
894 let loc_0_1 = LocationAddr {
895 zone_id: 0,
896 word_id: 0,
897 site_id: 1,
898 };
899 let loc_1_0 = LocationAddr {
900 zone_id: 0,
901 word_id: 1,
902 site_id: 0,
903 };
904 let state = AtomStateData::from_locations(&[(0, loc_0_0), (1, loc_1_0)]);
905
906 let lane = LaneAddr {
907 direction: crate::arch::addr::Direction::Forward,
908 move_type: crate::arch::addr::MoveType::SiteBus,
909 zone_id: 0,
910 word_id: 0,
911 site_id: 0,
912 bus_id: 0,
913 };
914
915 let new_state = state.apply_moves(&[lane], &spec).unwrap();
916
917 assert_eq!(new_state.qubit_to_locations[&0], loc_0_1);
919 assert_eq!(new_state.locations_to_qubit[&loc_0_1], 0);
920 assert_eq!(new_state.qubit_to_locations[&1], loc_1_0);
922 assert_eq!(new_state.locations_to_qubit[&loc_1_0], 1);
923 assert!(!new_state.locations_to_qubit.contains_key(&loc_0_0));
925 assert_eq!(new_state.prev_lanes.len(), 1);
927 assert_eq!(new_state.prev_lanes[&0], lane);
928 assert_eq!(new_state.move_count[&0], 1);
930 assert!(new_state.collision.is_empty());
932 }
933
934 #[test]
935 fn apply_moves_collision_verifies_all_fields() {
936 let spec = make_test_spec();
937 let state = AtomStateData::from_locations(&[
938 (
939 0,
940 LocationAddr {
941 zone_id: 0,
942 word_id: 0,
943 site_id: 0,
944 },
945 ),
946 (
947 1,
948 LocationAddr {
949 zone_id: 0,
950 word_id: 0,
951 site_id: 1,
952 },
953 ),
954 ]);
955
956 let lane = LaneAddr {
957 direction: crate::arch::addr::Direction::Forward,
958 move_type: crate::arch::addr::MoveType::SiteBus,
959 zone_id: 0,
960 word_id: 0,
961 site_id: 0,
962 bus_id: 0,
963 };
964
965 let new_state = state.apply_moves(&[lane], &spec).unwrap();
966
967 assert!(new_state.qubit_to_locations.is_empty());
969 assert!(new_state.locations_to_qubit.is_empty());
970 assert_eq!(new_state.collision[&0], 1);
972 assert_eq!(new_state.prev_lanes[&0], lane);
974 assert_eq!(new_state.move_count[&0], 1);
976 }
977
978 #[test]
979 fn apply_moves_skips_empty_source() {
980 let spec = make_test_spec();
981 let state = AtomStateData::from_locations(&[(
983 1,
984 LocationAddr {
985 zone_id: 0,
986 word_id: 1,
987 site_id: 0,
988 },
989 )]);
990
991 let lane = LaneAddr {
992 direction: crate::arch::addr::Direction::Forward,
993 move_type: crate::arch::addr::MoveType::SiteBus,
994 zone_id: 0,
995 word_id: 0,
996 site_id: 0,
997 bus_id: 0,
998 };
999
1000 let new_state = state.apply_moves(&[lane], &spec).unwrap();
1001 assert_eq!(new_state.qubit_to_locations.len(), 1);
1003 assert!(new_state.prev_lanes.is_empty());
1004 assert!(new_state.move_count.is_empty());
1005 }
1006
1007 #[test]
1008 fn apply_moves_invalid_lane_returns_none() {
1009 let spec = make_test_spec();
1010 let state = AtomStateData::from_locations(&[(
1011 0,
1012 LocationAddr {
1013 zone_id: 0,
1014 word_id: 0,
1015 site_id: 0,
1016 },
1017 )]);
1018
1019 let bad_lane = LaneAddr {
1020 direction: crate::arch::addr::Direction::Forward,
1021 move_type: crate::arch::addr::MoveType::SiteBus,
1022 zone_id: 0,
1023 word_id: 0,
1024 site_id: 0,
1025 bus_id: 99, };
1027
1028 assert!(state.apply_moves(&[bad_lane], &spec).is_none());
1029 }
1030
1031 #[test]
1032 fn apply_moves_accumulates_move_count() {
1033 let spec = make_test_spec();
1034 let state = AtomStateData::from_locations(&[(
1035 0,
1036 LocationAddr {
1037 zone_id: 0,
1038 word_id: 0,
1039 site_id: 0,
1040 },
1041 )]);
1042
1043 let lane_fwd = LaneAddr {
1045 direction: crate::arch::addr::Direction::Forward,
1046 move_type: crate::arch::addr::MoveType::SiteBus,
1047 zone_id: 0,
1048 word_id: 0,
1049 site_id: 0,
1050 bus_id: 0,
1051 };
1052 let state2 = state.apply_moves(&[lane_fwd], &spec).unwrap();
1053 assert_eq!(state2.move_count[&0], 1);
1054
1055 let lane_bwd = LaneAddr {
1058 direction: crate::arch::addr::Direction::Backward,
1059 move_type: crate::arch::addr::MoveType::SiteBus,
1060 zone_id: 0,
1061 word_id: 0,
1062 site_id: 0,
1063 bus_id: 0,
1064 };
1065 let state3 = state2.apply_moves(&[lane_bwd], &spec).unwrap();
1066 assert_eq!(state3.move_count[&0], 2);
1067 }
1068
1069 #[test]
1070 fn get_qubit_empty_location() {
1071 let state = AtomStateData::from_locations(&[(
1072 0,
1073 LocationAddr {
1074 zone_id: 0,
1075 word_id: 0,
1076 site_id: 0,
1077 },
1078 )]);
1079 assert_eq!(
1080 state.get_qubit(&LocationAddr {
1081 zone_id: 0,
1082 word_id: 1,
1083 site_id: 0
1084 }),
1085 None
1086 );
1087 }
1088
1089 #[test]
1090 fn get_qubit_pairing_all_unpaired() {
1091 let spec = make_test_spec();
1092 let state = AtomStateData::from_locations(&[
1095 (
1096 0,
1097 LocationAddr {
1098 zone_id: 0,
1099 word_id: 0,
1100 site_id: 0,
1101 },
1102 ),
1103 (
1104 1,
1105 LocationAddr {
1106 zone_id: 0,
1107 word_id: 0,
1108 site_id: 1,
1109 },
1110 ),
1111 ]);
1112
1113 let zone = ZoneAddr { zone_id: 0 };
1114 let (controls, targets, unpaired) = state.get_qubit_pairing(&zone, &spec).unwrap();
1115
1116 assert!(controls.is_empty());
1117 assert!(targets.is_empty());
1118 assert_eq!(unpaired.len(), 2);
1119 }
1120
1121 #[test]
1122 fn get_qubit_pairing_with_pairs() {
1123 let spec = make_test_spec();
1124 let state = AtomStateData::from_locations(&[
1130 (
1131 0,
1132 LocationAddr {
1133 zone_id: 0,
1134 word_id: 0,
1135 site_id: 0,
1136 },
1137 ),
1138 (
1139 1,
1140 LocationAddr {
1141 zone_id: 0,
1142 word_id: 1,
1143 site_id: 0,
1144 },
1145 ),
1146 (
1147 2,
1148 LocationAddr {
1149 zone_id: 0,
1150 word_id: 0,
1151 site_id: 1,
1152 },
1153 ),
1154 ]);
1155
1156 let zone = ZoneAddr { zone_id: 0 };
1157 let (controls, targets, unpaired) = state.get_qubit_pairing(&zone, &spec).unwrap();
1158
1159 assert_eq!(controls.len(), 1);
1161 assert_eq!(targets.len(), 1);
1162 use std::collections::HashSet;
1163 let control_set: HashSet<u32> = controls.iter().copied().collect();
1164 let target_set: HashSet<u32> = targets.iter().copied().collect();
1165 assert_eq!(control_set, HashSet::from([0]));
1166 assert_eq!(target_set, HashSet::from([1]));
1167 assert_eq!(unpaired, vec![2]);
1169 }
1170
1171 #[test]
1172 fn get_qubit_pairing_invalid_zone() {
1173 let spec = make_test_spec();
1174 let state = AtomStateData::new();
1175 let zone = ZoneAddr { zone_id: 99 };
1176 assert!(state.get_qubit_pairing(&zone, &spec).is_none());
1177 }
1178
1179 #[test]
1180 fn get_qubit_pairing_skips_qubits_outside_zone() {
1181 let spec = make_test_spec();
1182 let state = AtomStateData::from_locations(&[(
1185 0,
1186 LocationAddr {
1187 zone_id: 0,
1188 word_id: 0,
1189 site_id: 0,
1190 },
1191 )]);
1192
1193 let zone = ZoneAddr { zone_id: 0 };
1195 let (controls, targets, unpaired) = state.get_qubit_pairing(&zone, &spec).unwrap();
1196
1197 assert!(controls.is_empty());
1198 assert!(targets.is_empty());
1199 assert_eq!(unpaired, vec![0]);
1200 }
1201
1202 #[test]
1203 fn default_is_empty() {
1204 let state = AtomStateData::default();
1205 assert!(state.locations_to_qubit.is_empty());
1206 assert!(state.qubit_to_locations.is_empty());
1207 }
1208
1209 #[test]
1210 fn clone_produces_equal_state() {
1211 let state = AtomStateData::from_locations(&[
1212 (
1213 0,
1214 LocationAddr {
1215 zone_id: 0,
1216 word_id: 0,
1217 site_id: 0,
1218 },
1219 ),
1220 (
1221 1,
1222 LocationAddr {
1223 zone_id: 0,
1224 word_id: 1,
1225 site_id: 0,
1226 },
1227 ),
1228 ]);
1229 let cloned = state.clone();
1230 assert_eq!(state, cloned);
1231 }
1232
1233 #[test]
1234 fn hash_is_deterministic() {
1235 use std::collections::hash_map::DefaultHasher;
1236
1237 let state1 = AtomStateData::from_locations(&[
1238 (
1239 0,
1240 LocationAddr {
1241 zone_id: 0,
1242 word_id: 0,
1243 site_id: 0,
1244 },
1245 ),
1246 (
1247 1,
1248 LocationAddr {
1249 zone_id: 0,
1250 word_id: 1,
1251 site_id: 0,
1252 },
1253 ),
1254 ]);
1255 let state2 = AtomStateData::from_locations(&[
1256 (
1257 1,
1258 LocationAddr {
1259 zone_id: 0,
1260 word_id: 1,
1261 site_id: 0,
1262 },
1263 ),
1264 (
1265 0,
1266 LocationAddr {
1267 zone_id: 0,
1268 word_id: 0,
1269 site_id: 0,
1270 },
1271 ),
1272 ]);
1273
1274 let mut h1 = DefaultHasher::new();
1275 let mut h2 = DefaultHasher::new();
1276 state1.hash(&mut h1);
1277 state2.hash(&mut h2);
1278 assert_eq!(h1.finish(), h2.finish());
1279 }
1280
1281 fn make_chain_spec() -> crate::arch::ArchSpec {
1286 let grid0 = Grid::from_positions(&[0.0, 5.0, 10.0, 15.0], &[0.0, 3.0]);
1287 let grid1 = Grid::from_positions(&[30.0, 35.0, 40.0, 45.0], &[0.0, 4.0]);
1288
1289 let spec = crate::arch::ArchSpec {
1290 version: Version::new(2, 0),
1291 words: (0..4u32)
1292 .map(|w| Word {
1293 sites: vec![[w, 0], [w, 1]],
1294 })
1295 .collect(),
1296 zones: vec![
1297 Zone {
1298 name: String::new(),
1299 grid: grid0,
1300 site_buses: vec![],
1301 word_buses: vec![Bus {
1302 src: vec![WordRef(0), WordRef(1), WordRef(2)],
1303 dst: vec![WordRef(1), WordRef(2), WordRef(3)],
1304 }],
1305 words_with_site_buses: vec![],
1306 sites_with_word_buses: vec![0],
1307 entangling_pairs: vec![[0, 1]],
1308 },
1309 Zone {
1310 name: String::new(),
1311 grid: grid1,
1312 site_buses: vec![],
1313 word_buses: vec![],
1314 words_with_site_buses: vec![],
1315 sites_with_word_buses: vec![],
1316 entangling_pairs: vec![],
1317 },
1318 ],
1319 zone_buses: vec![],
1320 modes: vec![Mode {
1321 name: "full".to_string(),
1322 zones: vec![0, 1],
1323 bitstring_order: vec![],
1324 }],
1325 paths: None,
1326 feed_forward: false,
1327 atom_reloading: false,
1328 blockade_radius: None,
1329 };
1330 assert!(
1331 spec.validate().is_ok(),
1332 "overlapping acyclic buses must be legal: {:?}",
1333 spec.validate()
1334 );
1335 spec
1336 }
1337
1338 fn chain_lane(word_id: u32) -> LaneAddr {
1340 LaneAddr {
1341 direction: crate::arch::addr::Direction::Forward,
1342 move_type: crate::arch::addr::MoveType::WordBus,
1343 zone_id: 0,
1344 word_id,
1345 site_id: 0,
1346 bus_id: 0,
1347 }
1348 }
1349
1350 fn word_loc(word_id: u32) -> LocationAddr {
1351 LocationAddr {
1352 zone_id: 0,
1353 word_id,
1354 site_id: 0,
1355 }
1356 }
1357
1358 #[test]
1359 fn apply_moves_chain_succeeds_in_any_lane_order() {
1360 let spec = make_chain_spec();
1361 let state = AtomStateData::from_locations(&[(0, word_loc(0)), (1, word_loc(1))]);
1363 let lanes = [chain_lane(0), chain_lane(1)];
1364
1365 for order in [[0usize, 1], [1, 0]] {
1366 let slice = [lanes[order[0]], lanes[order[1]]];
1367 let result = state.apply_moves(&slice, &spec).unwrap();
1368 assert!(
1369 result.collision.is_empty(),
1370 "chain must not collide (order {order:?})"
1371 );
1372 assert_eq!(result.qubit_to_locations[&0], word_loc(1));
1373 assert_eq!(result.qubit_to_locations[&1], word_loc(2));
1374 assert_eq!(result.locations_to_qubit.len(), 2);
1375 assert_eq!(result.move_count[&0], 1);
1376 assert_eq!(result.move_count[&1], 1);
1377 }
1378 }
1379
1380 #[test]
1381 fn apply_moves_is_invariant_under_lane_permutation() {
1382 let spec = make_chain_spec();
1383 let state =
1385 AtomStateData::from_locations(&[(0, word_loc(0)), (1, word_loc(1)), (2, word_loc(2))]);
1386 let lanes = [chain_lane(0), chain_lane(1), chain_lane(2)];
1387
1388 let reference = state.apply_moves(&lanes, &spec).unwrap();
1389 assert!(reference.collision.is_empty());
1390 assert_eq!(reference.qubit_to_locations[&2], word_loc(3));
1391
1392 for perm in [
1393 [0usize, 1, 2],
1394 [0, 2, 1],
1395 [1, 0, 2],
1396 [1, 2, 0],
1397 [2, 0, 1],
1398 [2, 1, 0],
1399 ] {
1400 let slice = [lanes[perm[0]], lanes[perm[1]], lanes[perm[2]]];
1401 let permuted = state.apply_moves(&slice, &spec).unwrap();
1402 assert_eq!(
1403 permuted, reference,
1404 "lane order {perm:?} changed the result"
1405 );
1406 }
1407 }
1408
1409 #[test]
1410 fn apply_moves_stationary_collision_is_order_independent() {
1411 let spec = make_chain_spec();
1412 let state =
1415 AtomStateData::from_locations(&[(0, word_loc(1)), (1, word_loc(2)), (2, word_loc(3))]);
1416 let lanes = [chain_lane(1), chain_lane(2)];
1417
1418 let reference = state.apply_moves(&lanes, &spec).unwrap();
1419 assert_eq!(reference.collision, HashMap::from([(1, 2)]));
1420 assert_eq!(reference.qubit_to_locations[&0], word_loc(2));
1422 assert_eq!(reference.qubit_to_locations.len(), 1);
1423
1424 let reversed = state.apply_moves(&[lanes[1], lanes[0]], &spec).unwrap();
1425 assert_eq!(reversed, reference);
1426 }
1427
1428 #[test]
1429 fn validate_moves_accepts_chain_and_apply_validated_matches() {
1430 let spec = make_chain_spec();
1431 let state = AtomStateData::from_locations(&[(0, word_loc(0)), (1, word_loc(1))]);
1432 let lanes = [chain_lane(0), chain_lane(1)];
1433
1434 let validated = state.validate_moves(&lanes, &spec).expect("chain is valid");
1435 let via_token = state.apply_validated(&validated).expect("token is fresh");
1436 let via_legacy = state.apply_moves(&lanes, &spec).unwrap();
1437 assert_eq!(via_token, via_legacy);
1438 assert!(via_token.collision.is_empty());
1439 }
1440
1441 #[test]
1442 fn validate_moves_rejects_mover_onto_stationary_atom() {
1443 let spec = make_chain_spec();
1444 let state = AtomStateData::from_locations(&[(0, word_loc(2)), (1, word_loc(3))]);
1446 let lanes = [chain_lane(2)];
1447
1448 let errors = state.validate_moves(&lanes, &spec).unwrap_err();
1449 assert!(errors.iter().any(|e| matches!(
1450 e,
1451 MoveValidationError::DestinationOccupiedByStationaryAtom { occupant: 1, .. }
1452 )));
1453 }
1454
1455 #[test]
1456 fn validate_moves_rejects_filler_onto_stationary_atom() {
1457 let spec = make_chain_spec();
1458 let state = AtomStateData::from_locations(&[(0, word_loc(2))]);
1461 let lanes = [chain_lane(1)];
1462
1463 let errors = state.validate_moves(&lanes, &spec).unwrap_err();
1464 assert!(errors.iter().any(|e| matches!(
1465 e,
1466 MoveValidationError::DestinationOccupiedByStationaryAtom { occupant: 0, .. }
1467 )));
1468 let legacy = state.apply_moves(&lanes, &spec).unwrap();
1471 assert_eq!(legacy.qubit_to_locations[&0], word_loc(2));
1472 assert!(legacy.collision.is_empty());
1473 }
1474
1475 #[test]
1476 fn validate_moves_duplicate_lane_reports_duplicate_only() {
1477 let spec = make_chain_spec();
1478 let state = AtomStateData::from_locations(&[(0, word_loc(0))]);
1479 let lanes = [chain_lane(0), chain_lane(0)];
1480
1481 let errors = state.validate_moves(&lanes, &spec).unwrap_err();
1482 assert!(errors.iter().any(|e| matches!(
1483 e,
1484 MoveValidationError::LaneGroup(LaneGroupError::DuplicateAddress { .. })
1485 )));
1486 assert!(
1489 !errors
1490 .iter()
1491 .any(|e| matches!(e, MoveValidationError::ContestedDestination { .. }))
1492 );
1493 assert!(
1494 !errors
1495 .iter()
1496 .any(|e| matches!(e, MoveValidationError::UnresolvableLane { .. }))
1497 );
1498 }
1499
1500 #[test]
1501 fn validate_moves_reports_contested_destination() {
1502 let mut spec = make_chain_spec();
1506 spec.zones[0].word_buses[0] = Bus {
1507 src: vec![WordRef(0), WordRef(2)],
1508 dst: vec![WordRef(1), WordRef(1)],
1509 };
1510 let state = AtomStateData::from_locations(&[(0, word_loc(0)), (1, word_loc(2))]);
1511 let lanes = [chain_lane(0), chain_lane(2)];
1512
1513 let errors = state.validate_moves(&lanes, &spec).unwrap_err();
1514 assert!(errors.iter().any(|e| matches!(
1515 e,
1516 MoveValidationError::ContestedDestination { first, second, .. }
1517 if first != second
1518 )));
1519 }
1520
1521 #[test]
1522 fn validate_moves_invalid_lane_reports_single_error() {
1523 let spec = make_chain_spec();
1524 let state = AtomStateData::from_locations(&[(0, word_loc(0))]);
1525 let mut bad = chain_lane(0);
1528 bad.bus_id = 7;
1529
1530 let errors = state.validate_moves(&[bad], &spec).unwrap_err();
1531 assert_eq!(errors.len(), 1, "expected exactly one error: {errors:?}");
1532 assert!(matches!(
1533 errors[0],
1534 MoveValidationError::LaneGroup(LaneGroupError::InvalidLane { .. })
1535 ));
1536 }
1537
1538 #[test]
1539 fn apply_validated_rejects_token_whose_destination_became_occupied() {
1540 let spec = make_chain_spec();
1541 let state = AtomStateData::from_locations(&[(0, word_loc(0))]);
1542 let validated = state
1543 .validate_moves(&[chain_lane(0)], &spec)
1544 .expect("valid against the original state");
1545
1546 let later = state.add_atoms(&[(5, word_loc(1))]).unwrap();
1550 let errors = later.apply_validated(&validated).unwrap_err();
1551 assert!(errors.iter().any(|e| matches!(
1552 e,
1553 MoveValidationError::DestinationOccupiedByStationaryAtom { occupant: 5, .. }
1554 )));
1555 }
1556
1557 #[test]
1558 fn apply_validated_rejects_token_whose_source_moved_on() {
1559 let spec = make_chain_spec();
1560 let state = AtomStateData::from_locations(&[(0, word_loc(0))]);
1561 let validated = state
1562 .validate_moves(&[chain_lane(0)], &spec)
1563 .expect("valid against the original state");
1564
1565 let after = state.apply_validated(&validated).expect("token is fresh");
1568 let errors = after.apply_validated(&validated).unwrap_err();
1569 assert!(
1570 errors
1571 .iter()
1572 .any(|e| matches!(e, MoveValidationError::StaleMoverSource { expected: 0, .. }))
1573 );
1574 assert_eq!(after.qubit_to_locations[&0], word_loc(1));
1576 }
1577
1578 #[test]
1579 fn validate_moves_accepts_filler_onto_vacated_site() {
1580 let spec = make_chain_spec();
1581 let state = AtomStateData::from_locations(&[(0, word_loc(2))]);
1584 let lanes = [chain_lane(1), chain_lane(2)];
1585
1586 let validated = state
1587 .validate_moves(&lanes, &spec)
1588 .expect("filler is valid");
1589 let result = state.apply_validated(&validated).expect("token is fresh");
1590 assert_eq!(result.qubit_to_locations[&0], word_loc(3));
1591 assert_eq!(result.locations_to_qubit.len(), 1);
1592 }
1593}