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> {
235 let mut map = HashMap::new();
236 for (zone_id, zone) in self.zones.iter().enumerate() {
237 let zid = zone_id as u32;
238 for &[w_a, w_b] in &zone.entangling_pairs {
239 map.entry(w_a).or_insert(zid);
240 map.entry(w_b).or_insert(zid);
241 }
242 for bus in &zone.word_buses {
243 for wref in &bus.src {
244 map.entry(wref.0 as u32).or_insert(zid);
245 }
246 for wref in &bus.dst {
247 map.entry(wref.0 as u32).or_insert(zid);
248 }
249 }
250 for &wid in &zone.words_with_site_buses {
251 map.entry(wid).or_insert(zid);
252 }
253 }
254 for wid in 0..self.words.len() as u32 {
255 map.entry(wid).or_insert(0);
256 }
257 map
258 }
259
260 fn is_home_word_in_zone(zone: &Zone, word_id: u32) -> bool {
264 let mut paired = false;
265 for &[w_a, w_b] in &zone.entangling_pairs {
266 if w_a.min(w_b) == word_id {
267 return true;
268 }
269 paired |= w_a == word_id || w_b == word_id;
270 }
271 !paired
272 }
273
274 pub fn home_locations(&self) -> Vec<LocationAddr> {
278 let mut result = Vec::new();
279 for (zone_id, zone) in self.zones.iter().enumerate() {
280 for (word_id, word) in self.words.iter().enumerate() {
281 let word_id = word_id as u32;
282 if !Self::is_home_word_in_zone(zone, word_id) {
283 continue;
284 }
285 result.extend((0..word.sites.len() as u32).map(|site_id| LocationAddr {
286 zone_id: zone_id as u32,
287 word_id,
288 site_id,
289 }));
290 }
291 }
292 result
293 }
294
295 pub fn left_cz_word_ids(&self) -> Vec<u32> {
302 let partner = self.word_partner_map();
303 let mut paired: HashSet<u32> = HashSet::new();
304 let mut home: HashSet<u32> = HashSet::new();
305 for (&w_a, &w_b) in &partner {
306 paired.insert(w_a);
307 paired.insert(w_b);
308 home.insert(w_a.min(w_b));
309 }
310 for wid in 0..self.words.len() as u32 {
311 if !paired.contains(&wid) {
312 home.insert(wid);
313 }
314 }
315 let mut result: Vec<u32> = home.into_iter().collect();
316 result.sort();
317 result
318 }
319
320 pub fn lane_for_endpoints(&self, src: &LocationAddr, dst: &LocationAddr) -> Option<LaneAddr> {
335 if let Some(lane) = self.try_lane_from_location(src, dst, Direction::Forward) {
337 return Some(lane);
338 }
339 self.try_lane_from_location(dst, src, Direction::Backward)
341 }
342
343 fn try_lane_from_location(
347 &self,
348 origin: &LocationAddr,
349 target: &LocationAddr,
350 direction: Direction,
351 ) -> Option<LaneAddr> {
352 let zone = self.zones.get(origin.zone_id as usize)?;
353
354 if zone.words_with_site_buses.contains(&origin.word_id) {
356 for bus_id in 0..zone.site_buses.len() {
357 if let Some(lane) = self.check_lane_candidate(
358 MoveType::SiteBus,
359 origin,
360 target,
361 bus_id as u32,
362 direction,
363 ) {
364 return Some(lane);
365 }
366 }
367 }
368
369 if zone.sites_with_word_buses.contains(&origin.site_id) {
371 for bus_id in 0..zone.word_buses.len() {
372 if let Some(lane) = self.check_lane_candidate(
373 MoveType::WordBus,
374 origin,
375 target,
376 bus_id as u32,
377 direction,
378 ) {
379 return Some(lane);
380 }
381 }
382 }
383
384 for bus_id in 0..self.zone_buses.len() {
386 if let Some(lane) = self.check_lane_candidate(
387 MoveType::ZoneBus,
388 origin,
389 target,
390 bus_id as u32,
391 direction,
392 ) {
393 return Some(lane);
394 }
395 }
396
397 None
398 }
399
400 pub fn zone_location_index(&self, loc: &LocationAddr, zone_id: u32) -> Option<usize> {
409 if loc.zone_id != zone_id {
410 return None;
411 }
412 let spw = self.sites_per_word();
413 let wid = loc.word_id as usize;
414 let sid = loc.site_id as usize;
415 if wid >= self.words.len() || sid >= spw {
416 return None;
417 }
418 Some(wid * spw + sid)
419 }
420
421 fn check_lane_candidate(
424 &self,
425 move_type: MoveType,
426 origin: &LocationAddr,
427 target: &LocationAddr,
428 bus_id: u32,
429 direction: Direction,
430 ) -> Option<LaneAddr> {
431 let lane = LaneAddr {
432 move_type,
433 zone_id: origin.zone_id,
434 word_id: origin.word_id,
435 site_id: origin.site_id,
436 bus_id,
437 direction,
438 };
439 let (s, d) = self.lane_endpoints(&lane)?;
440 let (expected_src, expected_dst) = match direction {
441 Direction::Forward => (s, d),
442 Direction::Backward => (d, s),
443 };
444 if expected_src == *origin && expected_dst == *target {
445 Some(lane)
446 } else {
447 None
448 }
449 }
450
451 pub fn location_position(&self, loc: &LocationAddr) -> Option<(f64, f64)> {
458 let zone = self.zones.get(loc.zone_id as usize)?;
459 let word = self.words.get(loc.word_id as usize)?;
460 let site = word.sites.get(loc.site_id as usize)?;
461 let x = zone.grid.x_position(site[0] as usize)?;
462 let y = zone.grid.y_position(site[1] as usize)?;
463 Some((x, y))
464 }
465
466 pub fn lane_endpoints(&self, lane: &LaneAddr) -> Option<(LocationAddr, LocationAddr)> {
471 if !self.check_lane(lane).is_empty() {
474 return None;
475 }
476
477 let zone = self.zone_by_id(lane.zone_id)?;
478
479 let fwd_src = LocationAddr {
483 zone_id: lane.zone_id,
484 word_id: lane.word_id,
485 site_id: lane.site_id,
486 };
487
488 let fwd_dst = match lane.move_type {
489 MoveType::SiteBus => {
490 let bus = zone.site_buses.get(lane.bus_id as usize)?;
491 let dst_site = bus.resolve_forward(lane.site_id as u16)?;
492 LocationAddr {
493 zone_id: lane.zone_id,
494 word_id: lane.word_id,
495 site_id: dst_site as u32,
496 }
497 }
498 MoveType::WordBus => {
499 let bus = zone.word_buses.get(lane.bus_id as usize)?;
500 let dst_word = bus.resolve_forward(lane.word_id as u16)?;
501 LocationAddr {
502 zone_id: lane.zone_id,
503 word_id: dst_word as u32,
504 site_id: lane.site_id,
505 }
506 }
507 MoveType::ZoneBus => {
508 let bus = self.zone_buses.get(lane.bus_id as usize)?;
509 let src_ref = ZonedWordRef {
510 zone_id: lane.zone_id as u8,
511 word_id: lane.word_id as u16,
512 };
513 let dst_ref = bus.resolve_forward(&src_ref)?;
514 LocationAddr {
515 zone_id: dst_ref.zone_id as u32,
516 word_id: dst_ref.word_id as u32,
517 site_id: lane.site_id,
518 }
519 }
520 };
521
522 match lane.direction {
523 Direction::Forward => Some((fwd_src, fwd_dst)),
524 Direction::Backward => Some((fwd_dst, fwd_src)),
525 }
526 }
527
528 pub fn is_home_position(&self, loc: &LocationAddr) -> bool {
534 (loc.word_id as usize) < self.words.len()
535 && self
536 .zones
537 .get(loc.zone_id as usize)
538 .is_some_and(|zone| Self::is_home_word_in_zone(zone, loc.word_id))
539 }
540
541 pub fn get_cz_partner(&self, loc: &LocationAddr) -> Option<LocationAddr> {
548 let zone = self.zones.get(loc.zone_id as usize)?;
549 let partner_word = zone.entangling_pairs.iter().find_map(|pair| {
550 if pair[0] == loc.word_id {
551 Some(pair[1])
552 } else if pair[1] == loc.word_id {
553 Some(pair[0])
554 } else {
555 None
556 }
557 })?;
558 Some(LocationAddr {
559 zone_id: loc.zone_id,
560 word_id: partner_word,
561 site_id: loc.site_id,
562 })
563 }
564
565 pub fn location_at(&self, zone_id: u32, row: u32, col: u32) -> Option<LocationAddr> {
577 self.zones.get(zone_id as usize)?;
582 for (word_id, word) in self.words.iter().enumerate() {
583 let wid = word_id as u32;
584 for (site_id, site) in word.sites.iter().enumerate() {
585 if site[0] == col && site[1] == row {
586 return Some(LocationAddr {
587 zone_id,
588 word_id: wid,
589 site_id: site_id as u32,
590 });
591 }
592 }
593 }
594 None
595 }
596
597 pub fn check_location(&self, loc: &LocationAddr) -> Option<String> {
601 let num_zones = self.zones.len() as u32;
602 let num_words = self.words.len() as u32;
603 let sites_per_word = self.sites_per_word() as u32;
604
605 if loc.zone_id >= num_zones {
606 return Some(format!(
607 "invalid location zone_id={} (num_zones={})",
608 loc.zone_id, num_zones
609 ));
610 }
611 if loc.word_id >= num_words {
612 return Some(format!(
613 "invalid location word_id={} (num_words={})",
614 loc.word_id, num_words
615 ));
616 }
617 if loc.site_id >= sites_per_word {
618 return Some(format!(
619 "invalid location site_id={} (sites_per_word={})",
620 loc.site_id, sites_per_word
621 ));
622 }
623 None
624 }
625
626 pub fn check_lane(&self, addr: &LaneAddr) -> Vec<String> {
633 let num_zones = self.zones.len() as u32;
634 let num_words = self.words.len() as u32;
635 let sites_per_word = self.sites_per_word() as u32;
636 let mut errors = Vec::new();
637
638 if addr.zone_id >= num_zones {
640 errors.push(format!(
641 "zone_id {} out of range (num_zones={})",
642 addr.zone_id, num_zones
643 ));
644 return errors;
645 }
646
647 let zone = &self.zones[addr.zone_id as usize];
648
649 match addr.move_type {
650 MoveType::SiteBus => {
651 if addr.word_id >= num_words {
652 errors.push(format!("word_id {} out of range", addr.word_id));
653 }
654 if addr.site_id >= sites_per_word {
655 errors.push(format!("site_id {} out of range", addr.site_id));
656 }
657 if let Some(bus) = zone.site_buses.get(addr.bus_id as usize) {
658 if addr.word_id < num_words
659 && !zone.words_with_site_buses.contains(&addr.word_id)
660 {
661 errors.push(format!(
662 "word_id {} not in zone {} words_with_site_buses",
663 addr.word_id, addr.zone_id
664 ));
665 }
666 if errors.is_empty() && bus.resolve_forward(addr.site_id as u16).is_none() {
667 errors.push(format!(
668 "site_id {} is not a valid source for zone {} site_bus {}",
669 addr.site_id, addr.zone_id, addr.bus_id
670 ));
671 }
672 } else {
673 errors.push(format!(
674 "unknown site_bus id {} in zone {}",
675 addr.bus_id, addr.zone_id
676 ));
677 }
678 }
679 MoveType::WordBus => {
680 if addr.word_id >= num_words {
681 errors.push(format!("word_id {} out of range", addr.word_id));
682 }
683 if addr.site_id >= sites_per_word {
684 errors.push(format!("site_id {} out of range", addr.site_id));
685 } else if !zone.sites_with_word_buses.contains(&addr.site_id) {
686 errors.push(format!(
687 "site_id {} not in zone {} sites_with_word_buses",
688 addr.site_id, addr.zone_id
689 ));
690 }
691 if let Some(bus) = zone.word_buses.get(addr.bus_id as usize) {
692 if errors.is_empty() && bus.resolve_forward(addr.word_id as u16).is_none() {
693 errors.push(format!(
694 "word_id {} is not a valid source for zone {} word_bus {}",
695 addr.word_id, addr.zone_id, addr.bus_id
696 ));
697 }
698 } else {
699 errors.push(format!(
700 "unknown word_bus id {} in zone {}",
701 addr.bus_id, addr.zone_id
702 ));
703 }
704 }
705 MoveType::ZoneBus => {
706 if addr.word_id >= num_words {
707 errors.push(format!("word_id {} out of range", addr.word_id));
708 }
709 if addr.site_id >= sites_per_word {
710 errors.push(format!("site_id {} out of range", addr.site_id));
711 }
712 if let Some(bus) = self.zone_buses.get(addr.bus_id as usize) {
713 let src_ref = ZonedWordRef {
714 zone_id: addr.zone_id as u8,
715 word_id: addr.word_id as u16,
716 };
717 if errors.is_empty() && bus.resolve_forward(&src_ref).is_none() {
718 errors.push(format!(
719 "zone_id={}, word_id={} is not a valid source for zone_bus {}",
720 addr.zone_id, addr.word_id, addr.bus_id
721 ));
722 }
723 } else {
724 errors.push(format!("unknown zone_bus id {}", addr.bus_id));
725 }
726 }
727 }
728 errors
729 }
730
731 pub fn check_zone(&self, zone: &super::addr::ZoneAddr) -> Option<String> {
733 if self.zone_by_id(zone.zone_id).is_none() {
734 Some(format!("invalid zone_id={}", zone.zone_id))
735 } else {
736 None
737 }
738 }
739
740 pub fn check_lane_group_consistency(&self, lanes: &[LaneAddr]) -> Vec<String> {
744 if lanes.is_empty() {
745 return vec![];
746 }
747 let first = &lanes[0];
748 let mut errors = Vec::new();
749
750 for lane in &lanes[1..] {
751 if lane.zone_id != first.zone_id {
752 errors.push(format!(
753 "zone_id mismatch: expected {}, got {}",
754 first.zone_id, lane.zone_id
755 ));
756 }
757 if lane.bus_id != first.bus_id {
758 errors.push(format!(
759 "bus_id mismatch: expected {}, got {}",
760 first.bus_id, lane.bus_id
761 ));
762 }
763 if lane.move_type != first.move_type {
764 errors.push(format!(
765 "move_type mismatch: expected {:?}, got {:?}",
766 first.move_type, lane.move_type
767 ));
768 }
769 if lane.direction != first.direction {
770 errors.push(format!(
771 "direction mismatch: expected {:?}, got {:?}",
772 first.direction, lane.direction
773 ));
774 }
775 }
776
777 errors
778 }
779
780 pub fn check_lane_group_membership(&self, lanes: &[LaneAddr]) -> (Vec<u32>, Vec<u32>) {
788 use std::collections::BTreeSet;
789
790 let mut bad_words = BTreeSet::new();
791 let mut bad_sites = BTreeSet::new();
792
793 for lane in lanes {
794 let zone = match self.zones.get(lane.zone_id as usize) {
795 Some(z) => z,
796 None => continue, };
798
799 match lane.move_type {
800 MoveType::SiteBus => {
801 if !zone.words_with_site_buses.contains(&lane.word_id) {
802 bad_words.insert(lane.word_id);
803 }
804 }
805 MoveType::WordBus => {
806 if !zone.sites_with_word_buses.contains(&lane.site_id) {
807 bad_sites.insert(lane.site_id);
808 }
809 }
810 MoveType::ZoneBus => {
811 }
813 }
814 }
815
816 (
817 bad_words.into_iter().collect(),
818 bad_sites.into_iter().collect(),
819 )
820 }
821
822 pub fn check_locations(&self, locations: &[LocationAddr]) -> Vec<LocationGroupError> {
825 let mut errors = Vec::new();
826
827 let mut checked = HashSet::new();
829 for loc in locations {
830 let bits = loc.encode();
831 if checked.insert(bits) && self.check_location(loc).is_some() {
832 errors.push(LocationGroupError::InvalidAddress {
833 zone_id: loc.zone_id,
834 word_id: loc.word_id,
835 site_id: loc.site_id,
836 });
837 }
838 }
839
840 let mut seen = HashSet::new();
842 let mut reported = HashSet::new();
843 for loc in locations {
844 let bits = loc.encode();
845 if !seen.insert(bits) && reported.insert(bits) {
846 errors.push(LocationGroupError::DuplicateAddress { address: bits });
847 }
848 }
849
850 errors
851 }
852
853 pub fn check_lanes(&self, lanes: &[LaneAddr]) -> Vec<LaneGroupError> {
857 let mut errors = Vec::new();
858
859 let mut checked = HashSet::new();
861 for lane in lanes {
862 let bits = lane.encode();
863 if checked.insert(bits) {
864 for msg in self.check_lane(lane) {
865 errors.push(LaneGroupError::InvalidLane { message: msg });
866 }
867 }
868 }
869
870 let mut seen = HashSet::new();
872 let mut reported = HashSet::new();
873 for lane in lanes {
874 let pair = lane.encode();
875 if !seen.insert(pair) && reported.insert(pair) {
876 errors.push(LaneGroupError::DuplicateAddress { address: pair });
877 }
878 }
879
880 if lanes.len() > 1 {
882 for msg in self.check_lane_group_consistency(lanes) {
883 errors.push(LaneGroupError::Inconsistent { message: msg });
884 }
885 let (bad_words, bad_sites) = self.check_lane_group_membership(lanes);
886 let zone_id = lanes[0].zone_id;
888 for word_id in bad_words {
889 errors.push(LaneGroupError::WordNotInSiteBusList { zone_id, word_id });
890 }
891 for site_id in bad_sites {
892 errors.push(LaneGroupError::SiteNotInWordBusList { zone_id, site_id });
893 }
894 for msg in self.check_lane_group_geometry(lanes) {
895 errors.push(LaneGroupError::AODConstraintViolation { message: msg });
896 }
897 }
898
899 errors
900 }
901
902 pub fn check_lane_group_geometry(&self, lanes: &[LaneAddr]) -> Vec<String> {
905 use std::collections::BTreeSet;
906
907 let positions: Vec<(f64, f64)> = lanes
908 .iter()
909 .filter_map(|lane| {
910 let loc = LocationAddr {
911 zone_id: lane.zone_id,
912 word_id: lane.word_id,
913 site_id: lane.site_id,
914 };
915 self.location_position(&loc)
916 })
917 .collect();
918
919 if positions.len() != lanes.len() {
920 return vec!["some lane positions could not be resolved".to_string()];
921 }
922
923 let unique_x: BTreeSet<u64> = positions.iter().map(|(x, _)| x.to_bits()).collect();
924 let unique_y: BTreeSet<u64> = positions.iter().map(|(_, y)| y.to_bits()).collect();
925
926 let expected: BTreeSet<(u64, u64)> = unique_x
927 .iter()
928 .flat_map(|x| unique_y.iter().map(move |y| (*x, *y)))
929 .collect();
930
931 let actual: BTreeSet<(u64, u64)> = positions
932 .iter()
933 .map(|(x, y)| (x.to_bits(), y.to_bits()))
934 .collect();
935
936 if actual != expected {
937 vec![format!(
938 "lanes do not form a complete grid: expected {} positions ({}x * {}y), got {} unique positions",
939 expected.len(),
940 unique_x.len(),
941 unique_y.len(),
942 actual.len()
943 )]
944 } else {
945 vec![]
946 }
947 }
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
953 use crate::arch::addr::{
954 Direction, LaneAddr, LocationAddr, MoveType, SiteRef, WordRef, ZoneAddr, ZonedWordRef,
955 };
956 use crate::arch::types::{Grid, Mode};
957 use crate::version::Version;
958
959 fn make_valid_two_zone_spec() -> ArchSpec {
962 let grid0 = Grid::from_positions(&[0.0, 5.0, 10.0], &[0.0, 3.0]);
963 let grid1 = Grid::from_positions(&[20.0, 27.5, 35.0], &[0.0, 4.0]);
965
966 ArchSpec {
967 version: Version::new(2, 0),
968 words: vec![
969 Word {
970 sites: vec![[0, 0], [0, 1]],
971 },
972 Word {
973 sites: vec![[1, 0], [1, 1]],
974 },
975 ],
976 zones: vec![
977 Zone {
978 name: String::new(),
979 grid: grid0,
980 site_buses: vec![Bus {
981 src: vec![SiteRef(0)],
982 dst: vec![SiteRef(1)],
983 }],
984 word_buses: vec![Bus {
985 src: vec![WordRef(0)],
986 dst: vec![WordRef(1)],
987 }],
988 words_with_site_buses: vec![0, 1],
989 sites_with_word_buses: vec![0],
990 entangling_pairs: vec![[0, 1]],
991 },
992 Zone {
993 name: String::new(),
994 grid: grid1,
995 site_buses: vec![],
996 word_buses: vec![],
997 words_with_site_buses: vec![],
998 sites_with_word_buses: vec![],
999 entangling_pairs: vec![],
1000 },
1001 ],
1002 zone_buses: vec![Bus {
1003 src: vec![ZonedWordRef {
1004 zone_id: 0,
1005 word_id: 0,
1006 }],
1007 dst: vec![ZonedWordRef {
1008 zone_id: 1,
1009 word_id: 0,
1010 }],
1011 }],
1012 modes: vec![Mode {
1013 name: "full".to_string(),
1014 zones: vec![0, 1],
1015 bitstring_order: vec![],
1016 }],
1017 paths: None,
1018 feed_forward: false,
1019 atom_reloading: false,
1020 blockade_radius: None,
1021 }
1022 }
1023
1024 #[test]
1027 fn test_location_position_zone0() {
1028 let spec = make_valid_two_zone_spec();
1029 let pos = spec.location_position(&LocationAddr {
1032 zone_id: 0,
1033 word_id: 0,
1034 site_id: 0,
1035 });
1036 assert_eq!(pos, Some((0.0, 0.0)));
1037 }
1038
1039 #[test]
1040 fn test_location_position_zone0_site1() {
1041 let spec = make_valid_two_zone_spec();
1042 let pos = spec.location_position(&LocationAddr {
1044 zone_id: 0,
1045 word_id: 0,
1046 site_id: 1,
1047 });
1048 assert_eq!(pos, Some((0.0, 3.0)));
1049 }
1050
1051 #[test]
1052 fn test_location_position_zone1() {
1053 let spec = make_valid_two_zone_spec();
1054 let pos = spec.location_position(&LocationAddr {
1057 zone_id: 1,
1058 word_id: 1,
1059 site_id: 0,
1060 });
1061 assert_eq!(pos, Some((27.5, 0.0)));
1062 }
1063
1064 #[test]
1065 fn test_location_position_invalid_zone() {
1066 let spec = make_valid_two_zone_spec();
1067 let pos = spec.location_position(&LocationAddr {
1068 zone_id: 99,
1069 word_id: 0,
1070 site_id: 0,
1071 });
1072 assert!(pos.is_none());
1073 }
1074
1075 #[test]
1076 fn test_location_position_invalid_word() {
1077 let spec = make_valid_two_zone_spec();
1078 let pos = spec.location_position(&LocationAddr {
1079 zone_id: 0,
1080 word_id: 99,
1081 site_id: 0,
1082 });
1083 assert!(pos.is_none());
1084 }
1085
1086 #[test]
1087 fn test_location_position_invalid_site() {
1088 let spec = make_valid_two_zone_spec();
1089 let pos = spec.location_position(&LocationAddr {
1090 zone_id: 0,
1091 word_id: 0,
1092 site_id: 99,
1093 });
1094 assert!(pos.is_none());
1095 }
1096
1097 #[test]
1100 fn test_get_cz_partner() {
1101 let spec = make_valid_two_zone_spec();
1102 let partner = spec.get_cz_partner(&LocationAddr {
1104 zone_id: 0,
1105 word_id: 0,
1106 site_id: 0,
1107 });
1108 assert_eq!(
1109 partner,
1110 Some(LocationAddr {
1111 zone_id: 0, word_id: 1, site_id: 0,
1114 })
1115 );
1116 }
1117
1118 #[test]
1119 fn test_get_cz_partner_reverse() {
1120 let spec = make_valid_two_zone_spec();
1121 let partner = spec.get_cz_partner(&LocationAddr {
1123 zone_id: 0,
1124 word_id: 1,
1125 site_id: 1,
1126 });
1127 assert_eq!(
1128 partner,
1129 Some(LocationAddr {
1130 zone_id: 0,
1131 word_id: 0,
1132 site_id: 1,
1133 })
1134 );
1135 }
1136
1137 #[test]
1138 fn test_get_cz_partner_no_pair() {
1139 let spec = make_valid_two_zone_spec();
1140 let partner = spec.get_cz_partner(&LocationAddr {
1142 zone_id: 1,
1143 word_id: 0,
1144 site_id: 0,
1145 });
1146 assert!(partner.is_none());
1147 }
1148
1149 #[test]
1152 fn test_lane_endpoints_site_bus() {
1153 let spec = make_valid_two_zone_spec();
1154 let lane = LaneAddr {
1156 direction: Direction::Forward,
1157 move_type: MoveType::SiteBus,
1158 zone_id: 0,
1159 word_id: 0,
1160 site_id: 0,
1161 bus_id: 0,
1162 };
1163 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1164 assert_eq!(
1165 src,
1166 LocationAddr {
1167 zone_id: 0,
1168 word_id: 0,
1169 site_id: 0,
1170 }
1171 );
1172 assert_eq!(
1173 dst,
1174 LocationAddr {
1175 zone_id: 0,
1176 word_id: 0,
1177 site_id: 1,
1178 }
1179 );
1180 }
1181
1182 #[test]
1183 fn test_lane_endpoints_site_bus_backward() {
1184 let spec = make_valid_two_zone_spec();
1185 let lane = LaneAddr {
1186 direction: Direction::Backward,
1187 move_type: MoveType::SiteBus,
1188 zone_id: 0,
1189 word_id: 0,
1190 site_id: 0,
1191 bus_id: 0,
1192 };
1193 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1194 assert_eq!(
1196 src,
1197 LocationAddr {
1198 zone_id: 0,
1199 word_id: 0,
1200 site_id: 1,
1201 }
1202 );
1203 assert_eq!(
1204 dst,
1205 LocationAddr {
1206 zone_id: 0,
1207 word_id: 0,
1208 site_id: 0,
1209 }
1210 );
1211 }
1212
1213 #[test]
1214 fn test_lane_endpoints_word_bus() {
1215 let spec = make_valid_two_zone_spec();
1216 let lane = LaneAddr {
1218 direction: Direction::Forward,
1219 move_type: MoveType::WordBus,
1220 zone_id: 0,
1221 word_id: 0,
1222 site_id: 0,
1223 bus_id: 0,
1224 };
1225 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1226 assert_eq!(
1227 src,
1228 LocationAddr {
1229 zone_id: 0,
1230 word_id: 0,
1231 site_id: 0,
1232 }
1233 );
1234 assert_eq!(
1235 dst,
1236 LocationAddr {
1237 zone_id: 0,
1238 word_id: 1,
1239 site_id: 0,
1240 }
1241 );
1242 }
1243
1244 #[test]
1245 fn test_lane_endpoints_zone_bus() {
1246 let spec = make_valid_two_zone_spec();
1247 let lane = LaneAddr {
1249 direction: Direction::Forward,
1250 move_type: MoveType::ZoneBus,
1251 zone_id: 0,
1252 word_id: 0,
1253 site_id: 0,
1254 bus_id: 0,
1255 };
1256 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1257 assert_eq!(
1258 src,
1259 LocationAddr {
1260 zone_id: 0,
1261 word_id: 0,
1262 site_id: 0,
1263 }
1264 );
1265 assert_eq!(
1266 dst,
1267 LocationAddr {
1268 zone_id: 1,
1269 word_id: 0,
1270 site_id: 0,
1271 }
1272 );
1273 }
1274
1275 #[test]
1276 fn test_lane_endpoints_invalid_bus_returns_none() {
1277 let spec = make_valid_two_zone_spec();
1278 let lane = LaneAddr {
1279 direction: Direction::Forward,
1280 move_type: MoveType::SiteBus,
1281 zone_id: 0,
1282 word_id: 0,
1283 site_id: 0,
1284 bus_id: 99,
1285 };
1286 assert!(spec.lane_endpoints(&lane).is_none());
1287 }
1288
1289 #[test]
1292 fn test_json_round_trip() {
1293 let spec = make_valid_two_zone_spec();
1294 let json = serde_json::to_string_pretty(&spec).unwrap();
1295 let deserialized = ArchSpec::from_json(&json).unwrap();
1296 assert_eq!(spec, deserialized);
1297 }
1298
1299 #[test]
1300 fn test_from_json_validated() {
1301 let spec = make_valid_two_zone_spec();
1302 let json = serde_json::to_string(&spec).unwrap();
1303 let validated = ArchSpec::from_json_validated(&json).unwrap();
1304 assert_eq!(spec, validated);
1305 }
1306
1307 #[test]
1308 fn test_from_json_validated_invalid() {
1309 let json = r#"{"version": "1.0"}"#;
1310 let result = ArchSpec::from_json_validated(json);
1311 assert!(result.is_err());
1312 }
1313
1314 #[test]
1317 fn test_word_by_id_found() {
1318 let spec = make_valid_two_zone_spec();
1319 let word = spec.word_by_id(0).unwrap();
1320 assert_eq!(word.sites.len(), 2);
1321 }
1322
1323 #[test]
1324 fn test_word_by_id_not_found() {
1325 let spec = make_valid_two_zone_spec();
1326 assert!(spec.word_by_id(99).is_none());
1327 }
1328
1329 #[test]
1330 fn test_zone_by_id_found() {
1331 let spec = make_valid_two_zone_spec();
1332 let zone = spec.zone_by_id(0).unwrap();
1333 assert_eq!(zone.site_buses.len(), 1);
1334 }
1335
1336 #[test]
1337 fn test_zone_by_id_not_found() {
1338 let spec = make_valid_two_zone_spec();
1339 assert!(spec.zone_by_id(99).is_none());
1340 }
1341
1342 #[test]
1345 fn test_site_bus_resolve_forward() {
1346 let spec = make_valid_two_zone_spec();
1347 let bus = &spec.zones[0].site_buses[0];
1348 assert_eq!(bus.resolve_forward(0), Some(1));
1349 assert_eq!(bus.resolve_forward(99), None);
1350 }
1351
1352 #[test]
1353 fn test_site_bus_resolve_backward() {
1354 let spec = make_valid_two_zone_spec();
1355 let bus = &spec.zones[0].site_buses[0];
1356 assert_eq!(bus.resolve_backward(1), Some(0));
1357 assert_eq!(bus.resolve_backward(99), None);
1358 }
1359
1360 #[test]
1361 fn test_word_bus_resolve_forward() {
1362 let spec = make_valid_two_zone_spec();
1363 let bus = &spec.zones[0].word_buses[0];
1364 assert_eq!(bus.resolve_forward(0), Some(1));
1365 assert_eq!(bus.resolve_forward(99), None);
1366 }
1367
1368 #[test]
1369 fn test_word_bus_resolve_backward() {
1370 let spec = make_valid_two_zone_spec();
1371 let bus = &spec.zones[0].word_buses[0];
1372 assert_eq!(bus.resolve_backward(1), Some(0));
1373 assert_eq!(bus.resolve_backward(99), None);
1374 }
1375
1376 #[test]
1377 fn test_zone_bus_resolve_forward() {
1378 let spec = make_valid_two_zone_spec();
1379 let bus = &spec.zone_buses[0];
1380 let src = ZonedWordRef {
1381 zone_id: 0,
1382 word_id: 0,
1383 };
1384 let dst = bus.resolve_forward(&src).unwrap();
1385 assert_eq!(dst.zone_id, 1);
1386 assert_eq!(dst.word_id, 0);
1387 }
1388
1389 #[test]
1390 fn test_zone_bus_resolve_backward() {
1391 let spec = make_valid_two_zone_spec();
1392 let bus = &spec.zone_buses[0];
1393 let dst = ZonedWordRef {
1394 zone_id: 1,
1395 word_id: 0,
1396 };
1397 let src = bus.resolve_backward(&dst).unwrap();
1398 assert_eq!(src.zone_id, 0);
1399 assert_eq!(src.word_id, 0);
1400 }
1401
1402 #[test]
1405 fn test_check_location_valid() {
1406 let spec = make_valid_two_zone_spec();
1407 assert!(
1408 spec.check_location(&LocationAddr {
1409 zone_id: 0,
1410 word_id: 0,
1411 site_id: 0,
1412 })
1413 .is_none()
1414 );
1415 }
1416
1417 #[test]
1418 fn test_check_location_invalid_zone() {
1419 let spec = make_valid_two_zone_spec();
1420 let err = spec
1421 .check_location(&LocationAddr {
1422 zone_id: 99,
1423 word_id: 0,
1424 site_id: 0,
1425 })
1426 .unwrap();
1427 assert!(err.contains("zone_id"));
1428 }
1429
1430 #[test]
1433 fn test_check_lane_valid_site_bus() {
1434 let spec = make_valid_two_zone_spec();
1435 let lane = LaneAddr {
1436 direction: Direction::Forward,
1437 move_type: MoveType::SiteBus,
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_invalid_zone() {
1448 let spec = make_valid_two_zone_spec();
1449 let lane = LaneAddr {
1450 direction: Direction::Forward,
1451 move_type: MoveType::SiteBus,
1452 zone_id: 99,
1453 word_id: 0,
1454 site_id: 0,
1455 bus_id: 0,
1456 };
1457 let errors = spec.check_lane(&lane);
1458 assert!(!errors.is_empty());
1459 assert!(errors[0].contains("zone_id"));
1460 }
1461
1462 #[test]
1463 fn test_check_lane_invalid_bus() {
1464 let spec = make_valid_two_zone_spec();
1465 let lane = LaneAddr {
1466 direction: Direction::Forward,
1467 move_type: MoveType::SiteBus,
1468 zone_id: 0,
1469 word_id: 0,
1470 site_id: 0,
1471 bus_id: 99,
1472 };
1473 let errors = spec.check_lane(&lane);
1474 assert!(!errors.is_empty());
1475 }
1476
1477 #[test]
1478 fn test_check_lane_zone_bus_valid() {
1479 let spec = make_valid_two_zone_spec();
1480 let lane = LaneAddr {
1481 direction: Direction::Forward,
1482 move_type: MoveType::ZoneBus,
1483 zone_id: 0,
1484 word_id: 0,
1485 site_id: 0,
1486 bus_id: 0,
1487 };
1488 assert!(spec.check_lane(&lane).is_empty());
1489 }
1490
1491 #[test]
1492 fn test_check_lane_zone_bus_invalid_bus() {
1493 let spec = make_valid_two_zone_spec();
1494 let lane = LaneAddr {
1495 direction: Direction::Forward,
1496 move_type: MoveType::ZoneBus,
1497 zone_id: 0,
1498 word_id: 0,
1499 site_id: 0,
1500 bus_id: 99,
1501 };
1502 let errors = spec.check_lane(&lane);
1503 assert!(!errors.is_empty());
1504 assert!(errors[0].contains("zone_bus"));
1505 }
1506
1507 #[test]
1510 fn test_check_zone_valid() {
1511 let spec = make_valid_two_zone_spec();
1512 assert!(spec.check_zone(&ZoneAddr { zone_id: 0 }).is_none());
1513 }
1514
1515 #[test]
1516 fn test_check_zone_invalid() {
1517 let spec = make_valid_two_zone_spec();
1518 assert!(spec.check_zone(&ZoneAddr { zone_id: 99 }).is_some());
1519 }
1520
1521 #[test]
1524 fn test_check_lane_group_consistency_empty() {
1525 let spec = make_valid_two_zone_spec();
1526 assert!(spec.check_lane_group_consistency(&[]).is_empty());
1527 }
1528
1529 #[test]
1530 fn test_check_lane_group_consistency_zone_mismatch() {
1531 let spec = make_valid_two_zone_spec();
1532 let lanes = vec![
1533 LaneAddr {
1534 direction: Direction::Forward,
1535 move_type: MoveType::SiteBus,
1536 zone_id: 0,
1537 word_id: 0,
1538 site_id: 0,
1539 bus_id: 0,
1540 },
1541 LaneAddr {
1542 direction: Direction::Forward,
1543 move_type: MoveType::SiteBus,
1544 zone_id: 1,
1545 word_id: 0,
1546 site_id: 0,
1547 bus_id: 0,
1548 },
1549 ];
1550 let errors = spec.check_lane_group_consistency(&lanes);
1551 assert!(!errors.is_empty());
1552 assert!(errors[0].contains("zone_id mismatch"));
1553 }
1554
1555 #[test]
1558 fn test_check_locations_valid() {
1559 let spec = make_valid_two_zone_spec();
1560 let locs = vec![
1561 LocationAddr {
1562 zone_id: 0,
1563 word_id: 0,
1564 site_id: 0,
1565 },
1566 LocationAddr {
1567 zone_id: 0,
1568 word_id: 0,
1569 site_id: 1,
1570 },
1571 ];
1572 assert!(spec.check_locations(&locs).is_empty());
1573 }
1574
1575 #[test]
1576 fn test_check_locations_duplicate() {
1577 let spec = make_valid_two_zone_spec();
1578 let locs = vec![
1579 LocationAddr {
1580 zone_id: 0,
1581 word_id: 0,
1582 site_id: 0,
1583 },
1584 LocationAddr {
1585 zone_id: 0,
1586 word_id: 0,
1587 site_id: 0,
1588 },
1589 ];
1590 let errors = spec.check_locations(&locs);
1591 assert!(
1592 errors
1593 .iter()
1594 .any(|e| matches!(e, LocationGroupError::DuplicateAddress { .. }))
1595 );
1596 }
1597
1598 #[test]
1599 fn test_check_locations_invalid() {
1600 let spec = make_valid_two_zone_spec();
1601 let locs = vec![LocationAddr {
1602 zone_id: 99,
1603 word_id: 0,
1604 site_id: 0,
1605 }];
1606 let errors = spec.check_locations(&locs);
1607 assert!(
1608 errors
1609 .iter()
1610 .any(|e| matches!(e, LocationGroupError::InvalidAddress { .. }))
1611 );
1612 }
1613
1614 #[test]
1617 fn test_word_partner_map() {
1618 let spec = make_valid_two_zone_spec();
1619 let map = spec.word_partner_map();
1620 assert_eq!(map.get(&0), Some(&1));
1622 assert_eq!(map.get(&1), Some(&0));
1623 assert_eq!(map.len(), 2);
1624 }
1625
1626 #[test]
1627 fn test_word_zone_map() {
1628 let spec = make_valid_two_zone_spec();
1629 let map = spec.word_zone_map();
1630 assert_eq!(map.get(&0), Some(&0));
1632 assert_eq!(map.get(&1), Some(&0));
1633 assert_eq!(map.len(), 2); }
1635
1636 #[test]
1637 fn test_left_cz_word_ids() {
1638 let spec = make_valid_two_zone_spec();
1639 let home = spec.left_cz_word_ids();
1640 assert_eq!(home, vec![0]);
1643 }
1644
1645 #[test]
1646 fn test_is_home_position() {
1647 let spec = make_valid_two_zone_spec();
1648 let home = LocationAddr {
1650 zone_id: 0,
1651 word_id: 0,
1652 site_id: 0,
1653 };
1654 let staging = LocationAddr {
1655 zone_id: 0,
1656 word_id: 1,
1657 site_id: 0,
1658 };
1659 assert!(spec.is_home_position(&home));
1660 assert!(!spec.is_home_position(&staging));
1661 }
1662
1663 #[test]
1664 fn test_is_home_position_is_per_zone() {
1665 let spec = make_valid_two_zone_spec();
1666 let storage = LocationAddr {
1669 zone_id: 1,
1670 word_id: 1,
1671 site_id: 0,
1672 };
1673 assert!(spec.is_home_position(&storage));
1674 }
1675
1676 #[test]
1677 fn test_is_home_position_out_of_range() {
1678 let spec = make_valid_two_zone_spec();
1679 let at = |zone_id, word_id| LocationAddr {
1680 zone_id,
1681 word_id,
1682 site_id: 0,
1683 };
1684 assert!(!spec.is_home_position(&at(2, 0)));
1685 assert!(!spec.is_home_position(&at(0, 2)));
1686 }
1687
1688 #[test]
1689 fn test_home_locations_span_every_zone() {
1690 let spec = make_valid_two_zone_spec();
1691 let at = |zone_id, word_id, site_id| LocationAddr {
1692 zone_id,
1693 word_id,
1694 site_id,
1695 };
1696 assert_eq!(
1700 spec.home_locations(),
1701 vec![
1702 at(0, 0, 0),
1703 at(0, 0, 1),
1704 at(1, 0, 0),
1705 at(1, 0, 1),
1706 at(1, 1, 0),
1707 at(1, 1, 1),
1708 ]
1709 );
1710 for loc in spec.home_locations() {
1711 assert!(spec.is_home_position(&loc));
1712 }
1713 }
1714
1715 #[test]
1716 fn test_location_at_resolves_in_every_zone() {
1717 let spec = make_valid_two_zone_spec();
1718 for zone_id in 0..2 {
1721 assert_eq!(
1722 spec.location_at(zone_id, 0, 1),
1723 Some(LocationAddr {
1724 zone_id,
1725 word_id: 1,
1726 site_id: 0,
1727 })
1728 );
1729 }
1730 assert_eq!(spec.location_at(2, 0, 1), None);
1731 }
1732
1733 #[test]
1734 fn test_lane_for_endpoints_site_bus() {
1735 let spec = make_valid_two_zone_spec();
1736 let src = LocationAddr {
1739 zone_id: 0,
1740 word_id: 0,
1741 site_id: 0,
1742 };
1743 let dst = LocationAddr {
1744 zone_id: 0,
1745 word_id: 0,
1746 site_id: 1,
1747 };
1748 let lane = spec.lane_for_endpoints(&src, &dst);
1749 assert!(lane.is_some(), "should find a lane for (src, dst)");
1750 let l = lane.unwrap();
1751 assert_eq!(l.move_type, MoveType::SiteBus);
1752 assert_eq!(l.direction, Direction::Forward);
1753 }
1754
1755 #[test]
1756 fn test_lane_for_endpoints_word_bus() {
1757 let spec = make_valid_two_zone_spec();
1758 let src = LocationAddr {
1760 zone_id: 0,
1761 word_id: 0,
1762 site_id: 0,
1763 };
1764 let dst = LocationAddr {
1765 zone_id: 0,
1766 word_id: 1,
1767 site_id: 0,
1768 };
1769 let lane = spec.lane_for_endpoints(&src, &dst);
1770 assert!(lane.is_some(), "should find a word-bus lane");
1771 let l = lane.unwrap();
1772 assert_eq!(l.move_type, MoveType::WordBus);
1773 assert_eq!(l.direction, Direction::Forward);
1774 }
1775
1776 #[test]
1777 fn test_lane_for_endpoints_not_found() {
1778 let spec = make_valid_two_zone_spec();
1779 let src = LocationAddr {
1782 zone_id: 0,
1783 word_id: 0,
1784 site_id: 0,
1785 };
1786 let dst = LocationAddr {
1787 zone_id: 0,
1788 word_id: 1,
1789 site_id: 1,
1790 };
1791 assert!(spec.lane_for_endpoints(&src, &dst).is_none());
1792 }
1793}