1use std::collections::{HashMap, HashSet};
5use std::fmt;
6
7use thiserror::Error;
8
9use super::addr::{Direction, LaneAddr, LocationAddr, MoveType, SiteRef, WordRef, ZonedWordRef};
10use super::types::{ArchSpec, Bus, Word, Zone};
11use super::validate::ArchSpecError;
12
13#[derive(Debug, Error)]
15pub enum ArchSpecLoadError {
16 #[error("JSON parse error: {0}")]
17 Json(#[from] serde_json::Error),
18
19 #[error("validation errors: {0:?}")]
20 Validation(Vec<ArchSpecError>),
21}
22
23impl From<Vec<ArchSpecError>> for ArchSpecLoadError {
24 fn from(errors: Vec<ArchSpecError>) -> Self {
25 ArchSpecLoadError::Validation(errors)
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum LocationGroupError {
33 DuplicateAddress { address: u64 },
35 InvalidAddress {
37 zone_id: u32,
38 word_id: u32,
39 site_id: u32,
40 },
41}
42
43impl fmt::Display for LocationGroupError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 LocationGroupError::DuplicateAddress { address } => {
47 let addr = LocationAddr::decode(*address);
48 write!(
49 f,
50 "duplicate location address zone_id={}, word_id={}, site_id={}",
51 addr.zone_id, addr.word_id, addr.site_id
52 )
53 }
54 LocationGroupError::InvalidAddress {
55 zone_id,
56 word_id,
57 site_id,
58 } => {
59 write!(
60 f,
61 "invalid location zone_id={}, word_id={}, site_id={}",
62 zone_id, word_id, site_id
63 )
64 }
65 }
66 }
67}
68
69impl std::error::Error for LocationGroupError {}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum LaneGroupError {
73 DuplicateAddress { address: (u32, u32) },
75 InvalidLane { message: String },
77 Inconsistent { message: String },
79 WordNotInSiteBusList { zone_id: u32, word_id: u32 },
81 SiteNotInWordBusList { zone_id: u32, site_id: u32 },
83 AODConstraintViolation { message: String },
85}
86
87impl fmt::Display for LaneGroupError {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 match self {
90 LaneGroupError::DuplicateAddress { address } => {
91 let combined = (address.0 as u64) | ((address.1 as u64) << 32);
92 write!(f, "duplicate lane address 0x{:016x}", combined)
93 }
94 LaneGroupError::InvalidLane { message } => {
95 write!(f, "invalid lane: {}", message)
96 }
97 LaneGroupError::Inconsistent { message } => {
98 write!(f, "lane group inconsistent: {}", message)
99 }
100 LaneGroupError::WordNotInSiteBusList { zone_id, word_id } => {
101 write!(
102 f,
103 "zone {}: word_id {} not in words_with_site_buses",
104 zone_id, word_id
105 )
106 }
107 LaneGroupError::SiteNotInWordBusList { zone_id, site_id } => {
108 write!(
109 f,
110 "zone {}: site_id {} not in sites_with_word_buses",
111 zone_id, site_id
112 )
113 }
114 LaneGroupError::AODConstraintViolation { message } => {
115 write!(f, "AOD constraint violation: {}", message)
116 }
117 }
118 }
119}
120
121impl std::error::Error for LaneGroupError {}
122
123impl Bus<SiteRef> {
126 pub fn resolve_forward(&self, src: u16) -> Option<u16> {
128 self.src
129 .iter()
130 .position(|s| s.0 == src)
131 .and_then(|i| self.dst.get(i).map(|d| d.0))
132 }
133
134 pub fn resolve_backward(&self, dst: u16) -> Option<u16> {
136 self.dst
137 .iter()
138 .position(|d| d.0 == dst)
139 .and_then(|i| self.src.get(i).map(|s| s.0))
140 }
141}
142
143impl Bus<WordRef> {
144 pub fn resolve_forward(&self, src: u16) -> Option<u16> {
146 self.src
147 .iter()
148 .position(|s| s.0 == src)
149 .and_then(|i| self.dst.get(i).map(|d| d.0))
150 }
151
152 pub fn resolve_backward(&self, dst: u16) -> Option<u16> {
154 self.dst
155 .iter()
156 .position(|d| d.0 == dst)
157 .and_then(|i| self.src.get(i).map(|s| s.0))
158 }
159}
160
161impl Bus<ZonedWordRef> {
162 pub fn resolve_forward(&self, src: &ZonedWordRef) -> Option<&ZonedWordRef> {
164 self.src
165 .iter()
166 .position(|s| s == src)
167 .and_then(|i| self.dst.get(i))
168 }
169
170 pub fn resolve_backward(&self, dst: &ZonedWordRef) -> Option<&ZonedWordRef> {
172 self.dst
173 .iter()
174 .position(|d| d == dst)
175 .and_then(|i| self.src.get(i))
176 }
177}
178
179impl ArchSpec {
182 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
186 serde_json::from_str(json)
187 }
188
189 pub fn from_json_validated(json: &str) -> Result<Self, ArchSpecLoadError> {
191 let spec = Self::from_json(json)?;
192 spec.validate()?;
193 Ok(spec)
194 }
195
196 pub fn word_by_id(&self, id: u32) -> Option<&Word> {
200 self.words.get(id as usize)
201 }
202
203 pub fn zone_by_id(&self, id: u32) -> Option<&Zone> {
205 self.zones.get(id as usize)
206 }
207
208 pub fn word_partner_map(&self) -> HashMap<u32, u32> {
216 let mut map = HashMap::new();
217 for zone in &self.zones {
218 for &[w_a, w_b] in &zone.entangling_pairs {
219 map.insert(w_a, w_b);
220 map.insert(w_b, w_a);
221 }
222 }
223 map
224 }
225
226 pub fn word_zone_map(&self) -> HashMap<u32, u32> {
232 let mut map = HashMap::new();
233 for (zone_id, zone) in self.zones.iter().enumerate() {
234 let zid = zone_id as u32;
235 for &[w_a, w_b] in &zone.entangling_pairs {
236 map.entry(w_a).or_insert(zid);
237 map.entry(w_b).or_insert(zid);
238 }
239 for bus in &zone.word_buses {
240 for wref in &bus.src {
241 map.entry(wref.0 as u32).or_insert(zid);
242 }
243 for wref in &bus.dst {
244 map.entry(wref.0 as u32).or_insert(zid);
245 }
246 }
247 for &wid in &zone.words_with_site_buses {
248 map.entry(wid).or_insert(zid);
249 }
250 }
251 for wid in 0..self.words.len() as u32 {
252 map.entry(wid).or_insert(0);
253 }
254 map
255 }
256
257 pub fn left_cz_word_ids(&self) -> Vec<u32> {
260 let partner = self.word_partner_map();
261 let mut paired: HashSet<u32> = HashSet::new();
262 let mut home: HashSet<u32> = HashSet::new();
263 for (&w_a, &w_b) in &partner {
264 paired.insert(w_a);
265 paired.insert(w_b);
266 home.insert(w_a.min(w_b));
267 }
268 for wid in 0..self.words.len() as u32 {
269 if !paired.contains(&wid) {
270 home.insert(wid);
271 }
272 }
273 let mut result: Vec<u32> = home.into_iter().collect();
274 result.sort();
275 result
276 }
277
278 pub fn lane_for_endpoints(&self, src: &LocationAddr, dst: &LocationAddr) -> Option<LaneAddr> {
293 if let Some(lane) = self.try_lane_from_location(src, dst, Direction::Forward) {
295 return Some(lane);
296 }
297 self.try_lane_from_location(dst, src, Direction::Backward)
299 }
300
301 fn try_lane_from_location(
305 &self,
306 origin: &LocationAddr,
307 target: &LocationAddr,
308 direction: Direction,
309 ) -> Option<LaneAddr> {
310 let zone = self.zones.get(origin.zone_id as usize)?;
311
312 if zone.words_with_site_buses.contains(&origin.word_id) {
314 for bus_id in 0..zone.site_buses.len() {
315 if let Some(lane) = self.check_lane_candidate(
316 MoveType::SiteBus,
317 origin,
318 target,
319 bus_id as u32,
320 direction,
321 ) {
322 return Some(lane);
323 }
324 }
325 }
326
327 if zone.sites_with_word_buses.contains(&origin.site_id) {
329 for bus_id in 0..zone.word_buses.len() {
330 if let Some(lane) = self.check_lane_candidate(
331 MoveType::WordBus,
332 origin,
333 target,
334 bus_id as u32,
335 direction,
336 ) {
337 return Some(lane);
338 }
339 }
340 }
341
342 for bus_id in 0..self.zone_buses.len() {
344 if let Some(lane) = self.check_lane_candidate(
345 MoveType::ZoneBus,
346 origin,
347 target,
348 bus_id as u32,
349 direction,
350 ) {
351 return Some(lane);
352 }
353 }
354
355 None
356 }
357
358 pub fn zone_location_index(&self, loc: &LocationAddr, zone_id: u32) -> Option<usize> {
367 if loc.zone_id != zone_id {
368 return None;
369 }
370 let spw = self.sites_per_word();
371 let wid = loc.word_id as usize;
372 let sid = loc.site_id as usize;
373 if wid >= self.words.len() || sid >= spw {
374 return None;
375 }
376 Some(wid * spw + sid)
377 }
378
379 fn check_lane_candidate(
382 &self,
383 move_type: MoveType,
384 origin: &LocationAddr,
385 target: &LocationAddr,
386 bus_id: u32,
387 direction: Direction,
388 ) -> Option<LaneAddr> {
389 let lane = LaneAddr {
390 move_type,
391 zone_id: origin.zone_id,
392 word_id: origin.word_id,
393 site_id: origin.site_id,
394 bus_id,
395 direction,
396 };
397 let (s, d) = self.lane_endpoints(&lane)?;
398 let (expected_src, expected_dst) = match direction {
399 Direction::Forward => (s, d),
400 Direction::Backward => (d, s),
401 };
402 if expected_src == *origin && expected_dst == *target {
403 Some(lane)
404 } else {
405 None
406 }
407 }
408
409 pub fn location_position(&self, loc: &LocationAddr) -> Option<(f64, f64)> {
416 let zone = self.zones.get(loc.zone_id as usize)?;
417 let word = self.words.get(loc.word_id as usize)?;
418 let site = word.sites.get(loc.site_id as usize)?;
419 let x = zone.grid.x_position(site[0] as usize)?;
420 let y = zone.grid.y_position(site[1] as usize)?;
421 Some((x, y))
422 }
423
424 pub fn lane_endpoints(&self, lane: &LaneAddr) -> Option<(LocationAddr, LocationAddr)> {
429 if !self.check_lane(lane).is_empty() {
432 return None;
433 }
434
435 let zone = self.zone_by_id(lane.zone_id)?;
436
437 let fwd_src = LocationAddr {
441 zone_id: lane.zone_id,
442 word_id: lane.word_id,
443 site_id: lane.site_id,
444 };
445
446 let fwd_dst = match lane.move_type {
447 MoveType::SiteBus => {
448 let bus = zone.site_buses.get(lane.bus_id as usize)?;
449 let dst_site = bus.resolve_forward(lane.site_id as u16)?;
450 LocationAddr {
451 zone_id: lane.zone_id,
452 word_id: lane.word_id,
453 site_id: dst_site as u32,
454 }
455 }
456 MoveType::WordBus => {
457 let bus = zone.word_buses.get(lane.bus_id as usize)?;
458 let dst_word = bus.resolve_forward(lane.word_id as u16)?;
459 LocationAddr {
460 zone_id: lane.zone_id,
461 word_id: dst_word as u32,
462 site_id: lane.site_id,
463 }
464 }
465 MoveType::ZoneBus => {
466 let bus = self.zone_buses.get(lane.bus_id as usize)?;
467 let src_ref = ZonedWordRef {
468 zone_id: lane.zone_id as u8,
469 word_id: lane.word_id as u16,
470 };
471 let dst_ref = bus.resolve_forward(&src_ref)?;
472 LocationAddr {
473 zone_id: dst_ref.zone_id as u32,
474 word_id: dst_ref.word_id as u32,
475 site_id: lane.site_id,
476 }
477 }
478 };
479
480 match lane.direction {
481 Direction::Forward => Some((fwd_src, fwd_dst)),
482 Direction::Backward => Some((fwd_dst, fwd_src)),
483 }
484 }
485
486 pub fn is_home_position(&self, loc: &LocationAddr) -> bool {
491 self.left_cz_word_ids().contains(&loc.word_id)
492 }
493
494 pub fn get_cz_partner(&self, loc: &LocationAddr) -> Option<LocationAddr> {
501 let zone = self.zones.get(loc.zone_id as usize)?;
502 let partner_word = zone.entangling_pairs.iter().find_map(|pair| {
503 if pair[0] == loc.word_id {
504 Some(pair[1])
505 } else if pair[1] == loc.word_id {
506 Some(pair[0])
507 } else {
508 None
509 }
510 })?;
511 Some(LocationAddr {
512 zone_id: loc.zone_id,
513 word_id: partner_word,
514 site_id: loc.site_id,
515 })
516 }
517
518 pub fn location_at(&self, zone_id: u32, row: u32, col: u32) -> Option<LocationAddr> {
528 self.zones.get(zone_id as usize)?;
533 let word_zone = self.word_zone_map();
534 for (word_id, word) in self.words.iter().enumerate() {
535 let wid = word_id as u32;
536 if word_zone.get(&wid).copied().unwrap_or(0) != zone_id {
537 continue;
538 }
539 for (site_id, site) in word.sites.iter().enumerate() {
540 if site[0] == col && site[1] == row {
541 return Some(LocationAddr {
542 zone_id,
543 word_id: wid,
544 site_id: site_id as u32,
545 });
546 }
547 }
548 }
549 None
550 }
551
552 pub fn check_location(&self, loc: &LocationAddr) -> Option<String> {
556 let num_zones = self.zones.len() as u32;
557 let num_words = self.words.len() as u32;
558 let sites_per_word = self.sites_per_word() as u32;
559
560 if loc.zone_id >= num_zones {
561 return Some(format!(
562 "invalid location zone_id={} (num_zones={})",
563 loc.zone_id, num_zones
564 ));
565 }
566 if loc.word_id >= num_words {
567 return Some(format!(
568 "invalid location word_id={} (num_words={})",
569 loc.word_id, num_words
570 ));
571 }
572 if loc.site_id >= sites_per_word {
573 return Some(format!(
574 "invalid location site_id={} (sites_per_word={})",
575 loc.site_id, sites_per_word
576 ));
577 }
578 None
579 }
580
581 pub fn check_lane(&self, addr: &LaneAddr) -> Vec<String> {
588 let num_zones = self.zones.len() as u32;
589 let num_words = self.words.len() as u32;
590 let sites_per_word = self.sites_per_word() as u32;
591 let mut errors = Vec::new();
592
593 if addr.zone_id >= num_zones {
595 errors.push(format!(
596 "zone_id {} out of range (num_zones={})",
597 addr.zone_id, num_zones
598 ));
599 return errors;
600 }
601
602 let zone = &self.zones[addr.zone_id as usize];
603
604 match addr.move_type {
605 MoveType::SiteBus => {
606 if addr.word_id >= num_words {
607 errors.push(format!("word_id {} out of range", addr.word_id));
608 }
609 if addr.site_id >= sites_per_word {
610 errors.push(format!("site_id {} out of range", addr.site_id));
611 }
612 if let Some(bus) = zone.site_buses.get(addr.bus_id as usize) {
613 if addr.word_id < num_words
614 && !zone.words_with_site_buses.contains(&addr.word_id)
615 {
616 errors.push(format!(
617 "word_id {} not in zone {} words_with_site_buses",
618 addr.word_id, addr.zone_id
619 ));
620 }
621 if errors.is_empty() && bus.resolve_forward(addr.site_id as u16).is_none() {
622 errors.push(format!(
623 "site_id {} is not a valid source for zone {} site_bus {}",
624 addr.site_id, addr.zone_id, addr.bus_id
625 ));
626 }
627 } else {
628 errors.push(format!(
629 "unknown site_bus id {} in zone {}",
630 addr.bus_id, addr.zone_id
631 ));
632 }
633 }
634 MoveType::WordBus => {
635 if addr.word_id >= num_words {
636 errors.push(format!("word_id {} out of range", addr.word_id));
637 }
638 if addr.site_id >= sites_per_word {
639 errors.push(format!("site_id {} out of range", addr.site_id));
640 } else if !zone.sites_with_word_buses.contains(&addr.site_id) {
641 errors.push(format!(
642 "site_id {} not in zone {} sites_with_word_buses",
643 addr.site_id, addr.zone_id
644 ));
645 }
646 if let Some(bus) = zone.word_buses.get(addr.bus_id as usize) {
647 if errors.is_empty() && bus.resolve_forward(addr.word_id as u16).is_none() {
648 errors.push(format!(
649 "word_id {} is not a valid source for zone {} word_bus {}",
650 addr.word_id, addr.zone_id, addr.bus_id
651 ));
652 }
653 } else {
654 errors.push(format!(
655 "unknown word_bus id {} in zone {}",
656 addr.bus_id, addr.zone_id
657 ));
658 }
659 }
660 MoveType::ZoneBus => {
661 if addr.word_id >= num_words {
662 errors.push(format!("word_id {} out of range", addr.word_id));
663 }
664 if addr.site_id >= sites_per_word {
665 errors.push(format!("site_id {} out of range", addr.site_id));
666 }
667 if let Some(bus) = self.zone_buses.get(addr.bus_id as usize) {
668 let src_ref = ZonedWordRef {
669 zone_id: addr.zone_id as u8,
670 word_id: addr.word_id as u16,
671 };
672 if errors.is_empty() && bus.resolve_forward(&src_ref).is_none() {
673 errors.push(format!(
674 "zone_id={}, word_id={} is not a valid source for zone_bus {}",
675 addr.zone_id, addr.word_id, addr.bus_id
676 ));
677 }
678 } else {
679 errors.push(format!("unknown zone_bus id {}", addr.bus_id));
680 }
681 }
682 }
683 errors
684 }
685
686 pub fn check_zone(&self, zone: &super::addr::ZoneAddr) -> Option<String> {
688 if self.zone_by_id(zone.zone_id).is_none() {
689 Some(format!("invalid zone_id={}", zone.zone_id))
690 } else {
691 None
692 }
693 }
694
695 pub fn check_lane_group_consistency(&self, lanes: &[LaneAddr]) -> Vec<String> {
699 if lanes.is_empty() {
700 return vec![];
701 }
702 let first = &lanes[0];
703 let mut errors = Vec::new();
704
705 for lane in &lanes[1..] {
706 if lane.zone_id != first.zone_id {
707 errors.push(format!(
708 "zone_id mismatch: expected {}, got {}",
709 first.zone_id, lane.zone_id
710 ));
711 }
712 if lane.bus_id != first.bus_id {
713 errors.push(format!(
714 "bus_id mismatch: expected {}, got {}",
715 first.bus_id, lane.bus_id
716 ));
717 }
718 if lane.move_type != first.move_type {
719 errors.push(format!(
720 "move_type mismatch: expected {:?}, got {:?}",
721 first.move_type, lane.move_type
722 ));
723 }
724 if lane.direction != first.direction {
725 errors.push(format!(
726 "direction mismatch: expected {:?}, got {:?}",
727 first.direction, lane.direction
728 ));
729 }
730 }
731
732 errors
733 }
734
735 pub fn check_lane_group_membership(&self, lanes: &[LaneAddr]) -> (Vec<u32>, Vec<u32>) {
743 use std::collections::BTreeSet;
744
745 let mut bad_words = BTreeSet::new();
746 let mut bad_sites = BTreeSet::new();
747
748 for lane in lanes {
749 let zone = match self.zones.get(lane.zone_id as usize) {
750 Some(z) => z,
751 None => continue, };
753
754 match lane.move_type {
755 MoveType::SiteBus => {
756 if !zone.words_with_site_buses.contains(&lane.word_id) {
757 bad_words.insert(lane.word_id);
758 }
759 }
760 MoveType::WordBus => {
761 if !zone.sites_with_word_buses.contains(&lane.site_id) {
762 bad_sites.insert(lane.site_id);
763 }
764 }
765 MoveType::ZoneBus => {
766 }
768 }
769 }
770
771 (
772 bad_words.into_iter().collect(),
773 bad_sites.into_iter().collect(),
774 )
775 }
776
777 pub fn check_locations(&self, locations: &[LocationAddr]) -> Vec<LocationGroupError> {
780 let mut errors = Vec::new();
781
782 let mut checked = HashSet::new();
784 for loc in locations {
785 let bits = loc.encode();
786 if checked.insert(bits) && self.check_location(loc).is_some() {
787 errors.push(LocationGroupError::InvalidAddress {
788 zone_id: loc.zone_id,
789 word_id: loc.word_id,
790 site_id: loc.site_id,
791 });
792 }
793 }
794
795 let mut seen = HashSet::new();
797 let mut reported = HashSet::new();
798 for loc in locations {
799 let bits = loc.encode();
800 if !seen.insert(bits) && reported.insert(bits) {
801 errors.push(LocationGroupError::DuplicateAddress { address: bits });
802 }
803 }
804
805 errors
806 }
807
808 pub fn check_lanes(&self, lanes: &[LaneAddr]) -> Vec<LaneGroupError> {
812 let mut errors = Vec::new();
813
814 let mut checked = HashSet::new();
816 for lane in lanes {
817 let bits = lane.encode();
818 if checked.insert(bits) {
819 for msg in self.check_lane(lane) {
820 errors.push(LaneGroupError::InvalidLane { message: msg });
821 }
822 }
823 }
824
825 let mut seen = HashSet::new();
827 let mut reported = HashSet::new();
828 for lane in lanes {
829 let pair = lane.encode();
830 if !seen.insert(pair) && reported.insert(pair) {
831 errors.push(LaneGroupError::DuplicateAddress { address: pair });
832 }
833 }
834
835 if lanes.len() > 1 {
837 for msg in self.check_lane_group_consistency(lanes) {
838 errors.push(LaneGroupError::Inconsistent { message: msg });
839 }
840 let (bad_words, bad_sites) = self.check_lane_group_membership(lanes);
841 let zone_id = lanes[0].zone_id;
843 for word_id in bad_words {
844 errors.push(LaneGroupError::WordNotInSiteBusList { zone_id, word_id });
845 }
846 for site_id in bad_sites {
847 errors.push(LaneGroupError::SiteNotInWordBusList { zone_id, site_id });
848 }
849 for msg in self.check_lane_group_geometry(lanes) {
850 errors.push(LaneGroupError::AODConstraintViolation { message: msg });
851 }
852 }
853
854 errors
855 }
856
857 pub fn check_lane_group_geometry(&self, lanes: &[LaneAddr]) -> Vec<String> {
860 use std::collections::BTreeSet;
861
862 let positions: Vec<(f64, f64)> = lanes
863 .iter()
864 .filter_map(|lane| {
865 let loc = LocationAddr {
866 zone_id: lane.zone_id,
867 word_id: lane.word_id,
868 site_id: lane.site_id,
869 };
870 self.location_position(&loc)
871 })
872 .collect();
873
874 if positions.len() != lanes.len() {
875 return vec!["some lane positions could not be resolved".to_string()];
876 }
877
878 let unique_x: BTreeSet<u64> = positions.iter().map(|(x, _)| x.to_bits()).collect();
879 let unique_y: BTreeSet<u64> = positions.iter().map(|(_, y)| y.to_bits()).collect();
880
881 let expected: BTreeSet<(u64, u64)> = unique_x
882 .iter()
883 .flat_map(|x| unique_y.iter().map(move |y| (*x, *y)))
884 .collect();
885
886 let actual: BTreeSet<(u64, u64)> = positions
887 .iter()
888 .map(|(x, y)| (x.to_bits(), y.to_bits()))
889 .collect();
890
891 if actual != expected {
892 vec![format!(
893 "lanes do not form a complete grid: expected {} positions ({}x * {}y), got {} unique positions",
894 expected.len(),
895 unique_x.len(),
896 unique_y.len(),
897 actual.len()
898 )]
899 } else {
900 vec![]
901 }
902 }
903}
904
905#[cfg(test)]
906mod tests {
907 use super::*;
908 use crate::arch::addr::{
909 Direction, LaneAddr, LocationAddr, MoveType, SiteRef, WordRef, ZoneAddr, ZonedWordRef,
910 };
911 use crate::arch::types::{Grid, Mode};
912 use crate::version::Version;
913
914 fn make_valid_two_zone_spec() -> ArchSpec {
917 let grid0 = Grid::from_positions(&[0.0, 5.0, 10.0], &[0.0, 3.0]);
918 let grid1 = Grid::from_positions(&[20.0, 27.5, 35.0], &[0.0, 4.0]);
920
921 ArchSpec {
922 version: Version::new(2, 0),
923 words: vec![
924 Word {
925 sites: vec![[0, 0], [0, 1]],
926 },
927 Word {
928 sites: vec![[1, 0], [1, 1]],
929 },
930 ],
931 zones: vec![
932 Zone {
933 name: String::new(),
934 grid: grid0,
935 site_buses: vec![Bus {
936 src: vec![SiteRef(0)],
937 dst: vec![SiteRef(1)],
938 }],
939 word_buses: vec![Bus {
940 src: vec![WordRef(0)],
941 dst: vec![WordRef(1)],
942 }],
943 words_with_site_buses: vec![0, 1],
944 sites_with_word_buses: vec![0],
945 entangling_pairs: vec![[0, 1]],
946 },
947 Zone {
948 name: String::new(),
949 grid: grid1,
950 site_buses: vec![],
951 word_buses: vec![],
952 words_with_site_buses: vec![],
953 sites_with_word_buses: vec![],
954 entangling_pairs: vec![],
955 },
956 ],
957 zone_buses: vec![Bus {
958 src: vec![ZonedWordRef {
959 zone_id: 0,
960 word_id: 0,
961 }],
962 dst: vec![ZonedWordRef {
963 zone_id: 1,
964 word_id: 0,
965 }],
966 }],
967 modes: vec![Mode {
968 name: "full".to_string(),
969 zones: vec![0, 1],
970 bitstring_order: vec![],
971 }],
972 paths: None,
973 feed_forward: false,
974 atom_reloading: false,
975 blockade_radius: None,
976 }
977 }
978
979 #[test]
982 fn test_location_position_zone0() {
983 let spec = make_valid_two_zone_spec();
984 let pos = spec.location_position(&LocationAddr {
987 zone_id: 0,
988 word_id: 0,
989 site_id: 0,
990 });
991 assert_eq!(pos, Some((0.0, 0.0)));
992 }
993
994 #[test]
995 fn test_location_position_zone0_site1() {
996 let spec = make_valid_two_zone_spec();
997 let pos = spec.location_position(&LocationAddr {
999 zone_id: 0,
1000 word_id: 0,
1001 site_id: 1,
1002 });
1003 assert_eq!(pos, Some((0.0, 3.0)));
1004 }
1005
1006 #[test]
1007 fn test_location_position_zone1() {
1008 let spec = make_valid_two_zone_spec();
1009 let pos = spec.location_position(&LocationAddr {
1012 zone_id: 1,
1013 word_id: 1,
1014 site_id: 0,
1015 });
1016 assert_eq!(pos, Some((27.5, 0.0)));
1017 }
1018
1019 #[test]
1020 fn test_location_position_invalid_zone() {
1021 let spec = make_valid_two_zone_spec();
1022 let pos = spec.location_position(&LocationAddr {
1023 zone_id: 99,
1024 word_id: 0,
1025 site_id: 0,
1026 });
1027 assert!(pos.is_none());
1028 }
1029
1030 #[test]
1031 fn test_location_position_invalid_word() {
1032 let spec = make_valid_two_zone_spec();
1033 let pos = spec.location_position(&LocationAddr {
1034 zone_id: 0,
1035 word_id: 99,
1036 site_id: 0,
1037 });
1038 assert!(pos.is_none());
1039 }
1040
1041 #[test]
1042 fn test_location_position_invalid_site() {
1043 let spec = make_valid_two_zone_spec();
1044 let pos = spec.location_position(&LocationAddr {
1045 zone_id: 0,
1046 word_id: 0,
1047 site_id: 99,
1048 });
1049 assert!(pos.is_none());
1050 }
1051
1052 #[test]
1055 fn test_get_cz_partner() {
1056 let spec = make_valid_two_zone_spec();
1057 let partner = spec.get_cz_partner(&LocationAddr {
1059 zone_id: 0,
1060 word_id: 0,
1061 site_id: 0,
1062 });
1063 assert_eq!(
1064 partner,
1065 Some(LocationAddr {
1066 zone_id: 0, word_id: 1, site_id: 0,
1069 })
1070 );
1071 }
1072
1073 #[test]
1074 fn test_get_cz_partner_reverse() {
1075 let spec = make_valid_two_zone_spec();
1076 let partner = spec.get_cz_partner(&LocationAddr {
1078 zone_id: 0,
1079 word_id: 1,
1080 site_id: 1,
1081 });
1082 assert_eq!(
1083 partner,
1084 Some(LocationAddr {
1085 zone_id: 0,
1086 word_id: 0,
1087 site_id: 1,
1088 })
1089 );
1090 }
1091
1092 #[test]
1093 fn test_get_cz_partner_no_pair() {
1094 let spec = make_valid_two_zone_spec();
1095 let partner = spec.get_cz_partner(&LocationAddr {
1097 zone_id: 1,
1098 word_id: 0,
1099 site_id: 0,
1100 });
1101 assert!(partner.is_none());
1102 }
1103
1104 #[test]
1107 fn test_lane_endpoints_site_bus() {
1108 let spec = make_valid_two_zone_spec();
1109 let lane = LaneAddr {
1111 direction: Direction::Forward,
1112 move_type: MoveType::SiteBus,
1113 zone_id: 0,
1114 word_id: 0,
1115 site_id: 0,
1116 bus_id: 0,
1117 };
1118 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1119 assert_eq!(
1120 src,
1121 LocationAddr {
1122 zone_id: 0,
1123 word_id: 0,
1124 site_id: 0,
1125 }
1126 );
1127 assert_eq!(
1128 dst,
1129 LocationAddr {
1130 zone_id: 0,
1131 word_id: 0,
1132 site_id: 1,
1133 }
1134 );
1135 }
1136
1137 #[test]
1138 fn test_lane_endpoints_site_bus_backward() {
1139 let spec = make_valid_two_zone_spec();
1140 let lane = LaneAddr {
1141 direction: Direction::Backward,
1142 move_type: MoveType::SiteBus,
1143 zone_id: 0,
1144 word_id: 0,
1145 site_id: 0,
1146 bus_id: 0,
1147 };
1148 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1149 assert_eq!(
1151 src,
1152 LocationAddr {
1153 zone_id: 0,
1154 word_id: 0,
1155 site_id: 1,
1156 }
1157 );
1158 assert_eq!(
1159 dst,
1160 LocationAddr {
1161 zone_id: 0,
1162 word_id: 0,
1163 site_id: 0,
1164 }
1165 );
1166 }
1167
1168 #[test]
1169 fn test_lane_endpoints_word_bus() {
1170 let spec = make_valid_two_zone_spec();
1171 let lane = LaneAddr {
1173 direction: Direction::Forward,
1174 move_type: MoveType::WordBus,
1175 zone_id: 0,
1176 word_id: 0,
1177 site_id: 0,
1178 bus_id: 0,
1179 };
1180 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1181 assert_eq!(
1182 src,
1183 LocationAddr {
1184 zone_id: 0,
1185 word_id: 0,
1186 site_id: 0,
1187 }
1188 );
1189 assert_eq!(
1190 dst,
1191 LocationAddr {
1192 zone_id: 0,
1193 word_id: 1,
1194 site_id: 0,
1195 }
1196 );
1197 }
1198
1199 #[test]
1200 fn test_lane_endpoints_zone_bus() {
1201 let spec = make_valid_two_zone_spec();
1202 let lane = LaneAddr {
1204 direction: Direction::Forward,
1205 move_type: MoveType::ZoneBus,
1206 zone_id: 0,
1207 word_id: 0,
1208 site_id: 0,
1209 bus_id: 0,
1210 };
1211 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1212 assert_eq!(
1213 src,
1214 LocationAddr {
1215 zone_id: 0,
1216 word_id: 0,
1217 site_id: 0,
1218 }
1219 );
1220 assert_eq!(
1221 dst,
1222 LocationAddr {
1223 zone_id: 1,
1224 word_id: 0,
1225 site_id: 0,
1226 }
1227 );
1228 }
1229
1230 #[test]
1231 fn test_lane_endpoints_invalid_bus_returns_none() {
1232 let spec = make_valid_two_zone_spec();
1233 let lane = LaneAddr {
1234 direction: Direction::Forward,
1235 move_type: MoveType::SiteBus,
1236 zone_id: 0,
1237 word_id: 0,
1238 site_id: 0,
1239 bus_id: 99,
1240 };
1241 assert!(spec.lane_endpoints(&lane).is_none());
1242 }
1243
1244 #[test]
1247 fn test_json_round_trip() {
1248 let spec = make_valid_two_zone_spec();
1249 let json = serde_json::to_string_pretty(&spec).unwrap();
1250 let deserialized = ArchSpec::from_json(&json).unwrap();
1251 assert_eq!(spec, deserialized);
1252 }
1253
1254 #[test]
1255 fn test_from_json_validated() {
1256 let spec = make_valid_two_zone_spec();
1257 let json = serde_json::to_string(&spec).unwrap();
1258 let validated = ArchSpec::from_json_validated(&json).unwrap();
1259 assert_eq!(spec, validated);
1260 }
1261
1262 #[test]
1263 fn test_from_json_validated_invalid() {
1264 let json = r#"{"version": "1.0"}"#;
1265 let result = ArchSpec::from_json_validated(json);
1266 assert!(result.is_err());
1267 }
1268
1269 #[test]
1272 fn test_word_by_id_found() {
1273 let spec = make_valid_two_zone_spec();
1274 let word = spec.word_by_id(0).unwrap();
1275 assert_eq!(word.sites.len(), 2);
1276 }
1277
1278 #[test]
1279 fn test_word_by_id_not_found() {
1280 let spec = make_valid_two_zone_spec();
1281 assert!(spec.word_by_id(99).is_none());
1282 }
1283
1284 #[test]
1285 fn test_zone_by_id_found() {
1286 let spec = make_valid_two_zone_spec();
1287 let zone = spec.zone_by_id(0).unwrap();
1288 assert_eq!(zone.site_buses.len(), 1);
1289 }
1290
1291 #[test]
1292 fn test_zone_by_id_not_found() {
1293 let spec = make_valid_two_zone_spec();
1294 assert!(spec.zone_by_id(99).is_none());
1295 }
1296
1297 #[test]
1300 fn test_site_bus_resolve_forward() {
1301 let spec = make_valid_two_zone_spec();
1302 let bus = &spec.zones[0].site_buses[0];
1303 assert_eq!(bus.resolve_forward(0), Some(1));
1304 assert_eq!(bus.resolve_forward(99), None);
1305 }
1306
1307 #[test]
1308 fn test_site_bus_resolve_backward() {
1309 let spec = make_valid_two_zone_spec();
1310 let bus = &spec.zones[0].site_buses[0];
1311 assert_eq!(bus.resolve_backward(1), Some(0));
1312 assert_eq!(bus.resolve_backward(99), None);
1313 }
1314
1315 #[test]
1316 fn test_word_bus_resolve_forward() {
1317 let spec = make_valid_two_zone_spec();
1318 let bus = &spec.zones[0].word_buses[0];
1319 assert_eq!(bus.resolve_forward(0), Some(1));
1320 assert_eq!(bus.resolve_forward(99), None);
1321 }
1322
1323 #[test]
1324 fn test_word_bus_resolve_backward() {
1325 let spec = make_valid_two_zone_spec();
1326 let bus = &spec.zones[0].word_buses[0];
1327 assert_eq!(bus.resolve_backward(1), Some(0));
1328 assert_eq!(bus.resolve_backward(99), None);
1329 }
1330
1331 #[test]
1332 fn test_zone_bus_resolve_forward() {
1333 let spec = make_valid_two_zone_spec();
1334 let bus = &spec.zone_buses[0];
1335 let src = ZonedWordRef {
1336 zone_id: 0,
1337 word_id: 0,
1338 };
1339 let dst = bus.resolve_forward(&src).unwrap();
1340 assert_eq!(dst.zone_id, 1);
1341 assert_eq!(dst.word_id, 0);
1342 }
1343
1344 #[test]
1345 fn test_zone_bus_resolve_backward() {
1346 let spec = make_valid_two_zone_spec();
1347 let bus = &spec.zone_buses[0];
1348 let dst = ZonedWordRef {
1349 zone_id: 1,
1350 word_id: 0,
1351 };
1352 let src = bus.resolve_backward(&dst).unwrap();
1353 assert_eq!(src.zone_id, 0);
1354 assert_eq!(src.word_id, 0);
1355 }
1356
1357 #[test]
1360 fn test_check_location_valid() {
1361 let spec = make_valid_two_zone_spec();
1362 assert!(
1363 spec.check_location(&LocationAddr {
1364 zone_id: 0,
1365 word_id: 0,
1366 site_id: 0,
1367 })
1368 .is_none()
1369 );
1370 }
1371
1372 #[test]
1373 fn test_check_location_invalid_zone() {
1374 let spec = make_valid_two_zone_spec();
1375 let err = spec
1376 .check_location(&LocationAddr {
1377 zone_id: 99,
1378 word_id: 0,
1379 site_id: 0,
1380 })
1381 .unwrap();
1382 assert!(err.contains("zone_id"));
1383 }
1384
1385 #[test]
1388 fn test_check_lane_valid_site_bus() {
1389 let spec = make_valid_two_zone_spec();
1390 let lane = LaneAddr {
1391 direction: Direction::Forward,
1392 move_type: MoveType::SiteBus,
1393 zone_id: 0,
1394 word_id: 0,
1395 site_id: 0,
1396 bus_id: 0,
1397 };
1398 assert!(spec.check_lane(&lane).is_empty());
1399 }
1400
1401 #[test]
1402 fn test_check_lane_invalid_zone() {
1403 let spec = make_valid_two_zone_spec();
1404 let lane = LaneAddr {
1405 direction: Direction::Forward,
1406 move_type: MoveType::SiteBus,
1407 zone_id: 99,
1408 word_id: 0,
1409 site_id: 0,
1410 bus_id: 0,
1411 };
1412 let errors = spec.check_lane(&lane);
1413 assert!(!errors.is_empty());
1414 assert!(errors[0].contains("zone_id"));
1415 }
1416
1417 #[test]
1418 fn test_check_lane_invalid_bus() {
1419 let spec = make_valid_two_zone_spec();
1420 let lane = LaneAddr {
1421 direction: Direction::Forward,
1422 move_type: MoveType::SiteBus,
1423 zone_id: 0,
1424 word_id: 0,
1425 site_id: 0,
1426 bus_id: 99,
1427 };
1428 let errors = spec.check_lane(&lane);
1429 assert!(!errors.is_empty());
1430 }
1431
1432 #[test]
1433 fn test_check_lane_zone_bus_valid() {
1434 let spec = make_valid_two_zone_spec();
1435 let lane = LaneAddr {
1436 direction: Direction::Forward,
1437 move_type: MoveType::ZoneBus,
1438 zone_id: 0,
1439 word_id: 0,
1440 site_id: 0,
1441 bus_id: 0,
1442 };
1443 assert!(spec.check_lane(&lane).is_empty());
1444 }
1445
1446 #[test]
1447 fn test_check_lane_zone_bus_invalid_bus() {
1448 let spec = make_valid_two_zone_spec();
1449 let lane = LaneAddr {
1450 direction: Direction::Forward,
1451 move_type: MoveType::ZoneBus,
1452 zone_id: 0,
1453 word_id: 0,
1454 site_id: 0,
1455 bus_id: 99,
1456 };
1457 let errors = spec.check_lane(&lane);
1458 assert!(!errors.is_empty());
1459 assert!(errors[0].contains("zone_bus"));
1460 }
1461
1462 #[test]
1465 fn test_check_zone_valid() {
1466 let spec = make_valid_two_zone_spec();
1467 assert!(spec.check_zone(&ZoneAddr { zone_id: 0 }).is_none());
1468 }
1469
1470 #[test]
1471 fn test_check_zone_invalid() {
1472 let spec = make_valid_two_zone_spec();
1473 assert!(spec.check_zone(&ZoneAddr { zone_id: 99 }).is_some());
1474 }
1475
1476 #[test]
1479 fn test_check_lane_group_consistency_empty() {
1480 let spec = make_valid_two_zone_spec();
1481 assert!(spec.check_lane_group_consistency(&[]).is_empty());
1482 }
1483
1484 #[test]
1485 fn test_check_lane_group_consistency_zone_mismatch() {
1486 let spec = make_valid_two_zone_spec();
1487 let lanes = vec![
1488 LaneAddr {
1489 direction: Direction::Forward,
1490 move_type: MoveType::SiteBus,
1491 zone_id: 0,
1492 word_id: 0,
1493 site_id: 0,
1494 bus_id: 0,
1495 },
1496 LaneAddr {
1497 direction: Direction::Forward,
1498 move_type: MoveType::SiteBus,
1499 zone_id: 1,
1500 word_id: 0,
1501 site_id: 0,
1502 bus_id: 0,
1503 },
1504 ];
1505 let errors = spec.check_lane_group_consistency(&lanes);
1506 assert!(!errors.is_empty());
1507 assert!(errors[0].contains("zone_id mismatch"));
1508 }
1509
1510 #[test]
1513 fn test_check_locations_valid() {
1514 let spec = make_valid_two_zone_spec();
1515 let locs = vec![
1516 LocationAddr {
1517 zone_id: 0,
1518 word_id: 0,
1519 site_id: 0,
1520 },
1521 LocationAddr {
1522 zone_id: 0,
1523 word_id: 0,
1524 site_id: 1,
1525 },
1526 ];
1527 assert!(spec.check_locations(&locs).is_empty());
1528 }
1529
1530 #[test]
1531 fn test_check_locations_duplicate() {
1532 let spec = make_valid_two_zone_spec();
1533 let locs = vec![
1534 LocationAddr {
1535 zone_id: 0,
1536 word_id: 0,
1537 site_id: 0,
1538 },
1539 LocationAddr {
1540 zone_id: 0,
1541 word_id: 0,
1542 site_id: 0,
1543 },
1544 ];
1545 let errors = spec.check_locations(&locs);
1546 assert!(
1547 errors
1548 .iter()
1549 .any(|e| matches!(e, LocationGroupError::DuplicateAddress { .. }))
1550 );
1551 }
1552
1553 #[test]
1554 fn test_check_locations_invalid() {
1555 let spec = make_valid_two_zone_spec();
1556 let locs = vec![LocationAddr {
1557 zone_id: 99,
1558 word_id: 0,
1559 site_id: 0,
1560 }];
1561 let errors = spec.check_locations(&locs);
1562 assert!(
1563 errors
1564 .iter()
1565 .any(|e| matches!(e, LocationGroupError::InvalidAddress { .. }))
1566 );
1567 }
1568
1569 #[test]
1572 fn test_word_partner_map() {
1573 let spec = make_valid_two_zone_spec();
1574 let map = spec.word_partner_map();
1575 assert_eq!(map.get(&0), Some(&1));
1577 assert_eq!(map.get(&1), Some(&0));
1578 assert_eq!(map.len(), 2);
1579 }
1580
1581 #[test]
1582 fn test_word_zone_map() {
1583 let spec = make_valid_two_zone_spec();
1584 let map = spec.word_zone_map();
1585 assert_eq!(map.get(&0), Some(&0));
1587 assert_eq!(map.get(&1), Some(&0));
1588 assert_eq!(map.len(), 2); }
1590
1591 #[test]
1592 fn test_left_cz_word_ids() {
1593 let spec = make_valid_two_zone_spec();
1594 let home = spec.left_cz_word_ids();
1595 assert_eq!(home, vec![0]);
1598 }
1599
1600 #[test]
1601 fn test_is_home_position() {
1602 let spec = make_valid_two_zone_spec();
1603 let home = LocationAddr {
1605 zone_id: 0,
1606 word_id: 0,
1607 site_id: 0,
1608 };
1609 let staging = LocationAddr {
1610 zone_id: 0,
1611 word_id: 1,
1612 site_id: 0,
1613 };
1614 assert!(spec.is_home_position(&home));
1615 assert!(!spec.is_home_position(&staging));
1616 }
1617
1618 #[test]
1619 fn test_lane_for_endpoints_site_bus() {
1620 let spec = make_valid_two_zone_spec();
1621 let src = LocationAddr {
1624 zone_id: 0,
1625 word_id: 0,
1626 site_id: 0,
1627 };
1628 let dst = LocationAddr {
1629 zone_id: 0,
1630 word_id: 0,
1631 site_id: 1,
1632 };
1633 let lane = spec.lane_for_endpoints(&src, &dst);
1634 assert!(lane.is_some(), "should find a lane for (src, dst)");
1635 let l = lane.unwrap();
1636 assert_eq!(l.move_type, MoveType::SiteBus);
1637 assert_eq!(l.direction, Direction::Forward);
1638 }
1639
1640 #[test]
1641 fn test_lane_for_endpoints_word_bus() {
1642 let spec = make_valid_two_zone_spec();
1643 let src = LocationAddr {
1645 zone_id: 0,
1646 word_id: 0,
1647 site_id: 0,
1648 };
1649 let dst = LocationAddr {
1650 zone_id: 0,
1651 word_id: 1,
1652 site_id: 0,
1653 };
1654 let lane = spec.lane_for_endpoints(&src, &dst);
1655 assert!(lane.is_some(), "should find a word-bus lane");
1656 let l = lane.unwrap();
1657 assert_eq!(l.move_type, MoveType::WordBus);
1658 assert_eq!(l.direction, Direction::Forward);
1659 }
1660
1661 #[test]
1662 fn test_lane_for_endpoints_not_found() {
1663 let spec = make_valid_two_zone_spec();
1664 let src = LocationAddr {
1667 zone_id: 0,
1668 word_id: 0,
1669 site_id: 0,
1670 };
1671 let dst = LocationAddr {
1672 zone_id: 0,
1673 word_id: 1,
1674 site_id: 1,
1675 };
1676 assert!(spec.lane_for_endpoints(&src, &dst).is_none());
1677 }
1678}