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 check_location(&self, loc: &LocationAddr) -> Option<String> {
522 let num_zones = self.zones.len() as u32;
523 let num_words = self.words.len() as u32;
524 let sites_per_word = self.sites_per_word() as u32;
525
526 if loc.zone_id >= num_zones {
527 return Some(format!(
528 "invalid location zone_id={} (num_zones={})",
529 loc.zone_id, num_zones
530 ));
531 }
532 if loc.word_id >= num_words {
533 return Some(format!(
534 "invalid location word_id={} (num_words={})",
535 loc.word_id, num_words
536 ));
537 }
538 if loc.site_id >= sites_per_word {
539 return Some(format!(
540 "invalid location site_id={} (sites_per_word={})",
541 loc.site_id, sites_per_word
542 ));
543 }
544 None
545 }
546
547 pub fn check_lane(&self, addr: &LaneAddr) -> Vec<String> {
554 let num_zones = self.zones.len() as u32;
555 let num_words = self.words.len() as u32;
556 let sites_per_word = self.sites_per_word() as u32;
557 let mut errors = Vec::new();
558
559 if addr.zone_id >= num_zones {
561 errors.push(format!(
562 "zone_id {} out of range (num_zones={})",
563 addr.zone_id, num_zones
564 ));
565 return errors;
566 }
567
568 let zone = &self.zones[addr.zone_id as usize];
569
570 match addr.move_type {
571 MoveType::SiteBus => {
572 if addr.word_id >= num_words {
573 errors.push(format!("word_id {} out of range", addr.word_id));
574 }
575 if addr.site_id >= sites_per_word {
576 errors.push(format!("site_id {} out of range", addr.site_id));
577 }
578 if let Some(bus) = zone.site_buses.get(addr.bus_id as usize) {
579 if addr.word_id < num_words
580 && !zone.words_with_site_buses.contains(&addr.word_id)
581 {
582 errors.push(format!(
583 "word_id {} not in zone {} words_with_site_buses",
584 addr.word_id, addr.zone_id
585 ));
586 }
587 if errors.is_empty() && bus.resolve_forward(addr.site_id as u16).is_none() {
588 errors.push(format!(
589 "site_id {} is not a valid source for zone {} site_bus {}",
590 addr.site_id, addr.zone_id, addr.bus_id
591 ));
592 }
593 } else {
594 errors.push(format!(
595 "unknown site_bus id {} in zone {}",
596 addr.bus_id, addr.zone_id
597 ));
598 }
599 }
600 MoveType::WordBus => {
601 if addr.word_id >= num_words {
602 errors.push(format!("word_id {} out of range", addr.word_id));
603 }
604 if addr.site_id >= sites_per_word {
605 errors.push(format!("site_id {} out of range", addr.site_id));
606 } else if !zone.sites_with_word_buses.contains(&addr.site_id) {
607 errors.push(format!(
608 "site_id {} not in zone {} sites_with_word_buses",
609 addr.site_id, addr.zone_id
610 ));
611 }
612 if let Some(bus) = zone.word_buses.get(addr.bus_id as usize) {
613 if errors.is_empty() && bus.resolve_forward(addr.word_id as u16).is_none() {
614 errors.push(format!(
615 "word_id {} is not a valid source for zone {} word_bus {}",
616 addr.word_id, addr.zone_id, addr.bus_id
617 ));
618 }
619 } else {
620 errors.push(format!(
621 "unknown word_bus id {} in zone {}",
622 addr.bus_id, addr.zone_id
623 ));
624 }
625 }
626 MoveType::ZoneBus => {
627 if addr.word_id >= num_words {
628 errors.push(format!("word_id {} out of range", addr.word_id));
629 }
630 if addr.site_id >= sites_per_word {
631 errors.push(format!("site_id {} out of range", addr.site_id));
632 }
633 if let Some(bus) = self.zone_buses.get(addr.bus_id as usize) {
634 let src_ref = ZonedWordRef {
635 zone_id: addr.zone_id as u8,
636 word_id: addr.word_id as u16,
637 };
638 if errors.is_empty() && bus.resolve_forward(&src_ref).is_none() {
639 errors.push(format!(
640 "zone_id={}, word_id={} is not a valid source for zone_bus {}",
641 addr.zone_id, addr.word_id, addr.bus_id
642 ));
643 }
644 } else {
645 errors.push(format!("unknown zone_bus id {}", addr.bus_id));
646 }
647 }
648 }
649 errors
650 }
651
652 pub fn check_zone(&self, zone: &super::addr::ZoneAddr) -> Option<String> {
654 if self.zone_by_id(zone.zone_id).is_none() {
655 Some(format!("invalid zone_id={}", zone.zone_id))
656 } else {
657 None
658 }
659 }
660
661 pub fn check_lane_group_consistency(&self, lanes: &[LaneAddr]) -> Vec<String> {
665 if lanes.is_empty() {
666 return vec![];
667 }
668 let first = &lanes[0];
669 let mut errors = Vec::new();
670
671 for lane in &lanes[1..] {
672 if lane.zone_id != first.zone_id {
673 errors.push(format!(
674 "zone_id mismatch: expected {}, got {}",
675 first.zone_id, lane.zone_id
676 ));
677 }
678 if lane.bus_id != first.bus_id {
679 errors.push(format!(
680 "bus_id mismatch: expected {}, got {}",
681 first.bus_id, lane.bus_id
682 ));
683 }
684 if lane.move_type != first.move_type {
685 errors.push(format!(
686 "move_type mismatch: expected {:?}, got {:?}",
687 first.move_type, lane.move_type
688 ));
689 }
690 if lane.direction != first.direction {
691 errors.push(format!(
692 "direction mismatch: expected {:?}, got {:?}",
693 first.direction, lane.direction
694 ));
695 }
696 }
697
698 errors
699 }
700
701 pub fn check_lane_group_membership(&self, lanes: &[LaneAddr]) -> (Vec<u32>, Vec<u32>) {
709 use std::collections::BTreeSet;
710
711 let mut bad_words = BTreeSet::new();
712 let mut bad_sites = BTreeSet::new();
713
714 for lane in lanes {
715 let zone = match self.zones.get(lane.zone_id as usize) {
716 Some(z) => z,
717 None => continue, };
719
720 match lane.move_type {
721 MoveType::SiteBus => {
722 if !zone.words_with_site_buses.contains(&lane.word_id) {
723 bad_words.insert(lane.word_id);
724 }
725 }
726 MoveType::WordBus => {
727 if !zone.sites_with_word_buses.contains(&lane.site_id) {
728 bad_sites.insert(lane.site_id);
729 }
730 }
731 MoveType::ZoneBus => {
732 }
734 }
735 }
736
737 (
738 bad_words.into_iter().collect(),
739 bad_sites.into_iter().collect(),
740 )
741 }
742
743 pub fn check_locations(&self, locations: &[LocationAddr]) -> Vec<LocationGroupError> {
746 let mut errors = Vec::new();
747
748 let mut checked = HashSet::new();
750 for loc in locations {
751 let bits = loc.encode();
752 if checked.insert(bits) && self.check_location(loc).is_some() {
753 errors.push(LocationGroupError::InvalidAddress {
754 zone_id: loc.zone_id,
755 word_id: loc.word_id,
756 site_id: loc.site_id,
757 });
758 }
759 }
760
761 let mut seen = HashSet::new();
763 let mut reported = HashSet::new();
764 for loc in locations {
765 let bits = loc.encode();
766 if !seen.insert(bits) && reported.insert(bits) {
767 errors.push(LocationGroupError::DuplicateAddress { address: bits });
768 }
769 }
770
771 errors
772 }
773
774 pub fn check_lanes(&self, lanes: &[LaneAddr]) -> Vec<LaneGroupError> {
778 let mut errors = Vec::new();
779
780 let mut checked = HashSet::new();
782 for lane in lanes {
783 let bits = lane.encode();
784 if checked.insert(bits) {
785 for msg in self.check_lane(lane) {
786 errors.push(LaneGroupError::InvalidLane { message: msg });
787 }
788 }
789 }
790
791 let mut seen = HashSet::new();
793 let mut reported = HashSet::new();
794 for lane in lanes {
795 let pair = lane.encode();
796 if !seen.insert(pair) && reported.insert(pair) {
797 errors.push(LaneGroupError::DuplicateAddress { address: pair });
798 }
799 }
800
801 if lanes.len() > 1 {
803 for msg in self.check_lane_group_consistency(lanes) {
804 errors.push(LaneGroupError::Inconsistent { message: msg });
805 }
806 let (bad_words, bad_sites) = self.check_lane_group_membership(lanes);
807 let zone_id = lanes[0].zone_id;
809 for word_id in bad_words {
810 errors.push(LaneGroupError::WordNotInSiteBusList { zone_id, word_id });
811 }
812 for site_id in bad_sites {
813 errors.push(LaneGroupError::SiteNotInWordBusList { zone_id, site_id });
814 }
815 for msg in self.check_lane_group_geometry(lanes) {
816 errors.push(LaneGroupError::AODConstraintViolation { message: msg });
817 }
818 }
819
820 errors
821 }
822
823 pub fn check_lane_group_geometry(&self, lanes: &[LaneAddr]) -> Vec<String> {
826 use std::collections::BTreeSet;
827
828 let positions: Vec<(f64, f64)> = lanes
829 .iter()
830 .filter_map(|lane| {
831 let loc = LocationAddr {
832 zone_id: lane.zone_id,
833 word_id: lane.word_id,
834 site_id: lane.site_id,
835 };
836 self.location_position(&loc)
837 })
838 .collect();
839
840 if positions.len() != lanes.len() {
841 return vec!["some lane positions could not be resolved".to_string()];
842 }
843
844 let unique_x: BTreeSet<u64> = positions.iter().map(|(x, _)| x.to_bits()).collect();
845 let unique_y: BTreeSet<u64> = positions.iter().map(|(_, y)| y.to_bits()).collect();
846
847 let expected: BTreeSet<(u64, u64)> = unique_x
848 .iter()
849 .flat_map(|x| unique_y.iter().map(move |y| (*x, *y)))
850 .collect();
851
852 let actual: BTreeSet<(u64, u64)> = positions
853 .iter()
854 .map(|(x, y)| (x.to_bits(), y.to_bits()))
855 .collect();
856
857 if actual != expected {
858 vec![format!(
859 "lanes do not form a complete grid: expected {} positions ({}x * {}y), got {} unique positions",
860 expected.len(),
861 unique_x.len(),
862 unique_y.len(),
863 actual.len()
864 )]
865 } else {
866 vec![]
867 }
868 }
869}
870
871#[cfg(test)]
872mod tests {
873 use super::*;
874 use crate::arch::addr::{
875 Direction, LaneAddr, LocationAddr, MoveType, SiteRef, WordRef, ZoneAddr, ZonedWordRef,
876 };
877 use crate::arch::types::{Grid, Mode};
878 use crate::version::Version;
879
880 fn make_valid_two_zone_spec() -> ArchSpec {
883 let grid0 = Grid::from_positions(&[0.0, 5.0, 10.0], &[0.0, 3.0]);
884 let grid1 = Grid::from_positions(&[20.0, 27.5, 35.0], &[0.0, 4.0]);
886
887 ArchSpec {
888 version: Version::new(2, 0),
889 words: vec![
890 Word {
891 sites: vec![[0, 0], [0, 1]],
892 },
893 Word {
894 sites: vec![[1, 0], [1, 1]],
895 },
896 ],
897 zones: vec![
898 Zone {
899 name: String::new(),
900 grid: grid0,
901 site_buses: vec![Bus {
902 src: vec![SiteRef(0)],
903 dst: vec![SiteRef(1)],
904 }],
905 word_buses: vec![Bus {
906 src: vec![WordRef(0)],
907 dst: vec![WordRef(1)],
908 }],
909 words_with_site_buses: vec![0, 1],
910 sites_with_word_buses: vec![0],
911 entangling_pairs: vec![[0, 1]],
912 },
913 Zone {
914 name: String::new(),
915 grid: grid1,
916 site_buses: vec![],
917 word_buses: vec![],
918 words_with_site_buses: vec![],
919 sites_with_word_buses: vec![],
920 entangling_pairs: vec![],
921 },
922 ],
923 zone_buses: vec![Bus {
924 src: vec![ZonedWordRef {
925 zone_id: 0,
926 word_id: 0,
927 }],
928 dst: vec![ZonedWordRef {
929 zone_id: 1,
930 word_id: 0,
931 }],
932 }],
933 modes: vec![Mode {
934 name: "full".to_string(),
935 zones: vec![0, 1],
936 bitstring_order: vec![],
937 }],
938 paths: None,
939 feed_forward: false,
940 atom_reloading: false,
941 blockade_radius: None,
942 }
943 }
944
945 #[test]
948 fn test_location_position_zone0() {
949 let spec = make_valid_two_zone_spec();
950 let pos = spec.location_position(&LocationAddr {
953 zone_id: 0,
954 word_id: 0,
955 site_id: 0,
956 });
957 assert_eq!(pos, Some((0.0, 0.0)));
958 }
959
960 #[test]
961 fn test_location_position_zone0_site1() {
962 let spec = make_valid_two_zone_spec();
963 let pos = spec.location_position(&LocationAddr {
965 zone_id: 0,
966 word_id: 0,
967 site_id: 1,
968 });
969 assert_eq!(pos, Some((0.0, 3.0)));
970 }
971
972 #[test]
973 fn test_location_position_zone1() {
974 let spec = make_valid_two_zone_spec();
975 let pos = spec.location_position(&LocationAddr {
978 zone_id: 1,
979 word_id: 1,
980 site_id: 0,
981 });
982 assert_eq!(pos, Some((27.5, 0.0)));
983 }
984
985 #[test]
986 fn test_location_position_invalid_zone() {
987 let spec = make_valid_two_zone_spec();
988 let pos = spec.location_position(&LocationAddr {
989 zone_id: 99,
990 word_id: 0,
991 site_id: 0,
992 });
993 assert!(pos.is_none());
994 }
995
996 #[test]
997 fn test_location_position_invalid_word() {
998 let spec = make_valid_two_zone_spec();
999 let pos = spec.location_position(&LocationAddr {
1000 zone_id: 0,
1001 word_id: 99,
1002 site_id: 0,
1003 });
1004 assert!(pos.is_none());
1005 }
1006
1007 #[test]
1008 fn test_location_position_invalid_site() {
1009 let spec = make_valid_two_zone_spec();
1010 let pos = spec.location_position(&LocationAddr {
1011 zone_id: 0,
1012 word_id: 0,
1013 site_id: 99,
1014 });
1015 assert!(pos.is_none());
1016 }
1017
1018 #[test]
1021 fn test_get_cz_partner() {
1022 let spec = make_valid_two_zone_spec();
1023 let partner = spec.get_cz_partner(&LocationAddr {
1025 zone_id: 0,
1026 word_id: 0,
1027 site_id: 0,
1028 });
1029 assert_eq!(
1030 partner,
1031 Some(LocationAddr {
1032 zone_id: 0, word_id: 1, site_id: 0,
1035 })
1036 );
1037 }
1038
1039 #[test]
1040 fn test_get_cz_partner_reverse() {
1041 let spec = make_valid_two_zone_spec();
1042 let partner = spec.get_cz_partner(&LocationAddr {
1044 zone_id: 0,
1045 word_id: 1,
1046 site_id: 1,
1047 });
1048 assert_eq!(
1049 partner,
1050 Some(LocationAddr {
1051 zone_id: 0,
1052 word_id: 0,
1053 site_id: 1,
1054 })
1055 );
1056 }
1057
1058 #[test]
1059 fn test_get_cz_partner_no_pair() {
1060 let spec = make_valid_two_zone_spec();
1061 let partner = spec.get_cz_partner(&LocationAddr {
1063 zone_id: 1,
1064 word_id: 0,
1065 site_id: 0,
1066 });
1067 assert!(partner.is_none());
1068 }
1069
1070 #[test]
1073 fn test_lane_endpoints_site_bus() {
1074 let spec = make_valid_two_zone_spec();
1075 let lane = LaneAddr {
1077 direction: Direction::Forward,
1078 move_type: MoveType::SiteBus,
1079 zone_id: 0,
1080 word_id: 0,
1081 site_id: 0,
1082 bus_id: 0,
1083 };
1084 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1085 assert_eq!(
1086 src,
1087 LocationAddr {
1088 zone_id: 0,
1089 word_id: 0,
1090 site_id: 0,
1091 }
1092 );
1093 assert_eq!(
1094 dst,
1095 LocationAddr {
1096 zone_id: 0,
1097 word_id: 0,
1098 site_id: 1,
1099 }
1100 );
1101 }
1102
1103 #[test]
1104 fn test_lane_endpoints_site_bus_backward() {
1105 let spec = make_valid_two_zone_spec();
1106 let lane = LaneAddr {
1107 direction: Direction::Backward,
1108 move_type: MoveType::SiteBus,
1109 zone_id: 0,
1110 word_id: 0,
1111 site_id: 0,
1112 bus_id: 0,
1113 };
1114 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1115 assert_eq!(
1117 src,
1118 LocationAddr {
1119 zone_id: 0,
1120 word_id: 0,
1121 site_id: 1,
1122 }
1123 );
1124 assert_eq!(
1125 dst,
1126 LocationAddr {
1127 zone_id: 0,
1128 word_id: 0,
1129 site_id: 0,
1130 }
1131 );
1132 }
1133
1134 #[test]
1135 fn test_lane_endpoints_word_bus() {
1136 let spec = make_valid_two_zone_spec();
1137 let lane = LaneAddr {
1139 direction: Direction::Forward,
1140 move_type: MoveType::WordBus,
1141 zone_id: 0,
1142 word_id: 0,
1143 site_id: 0,
1144 bus_id: 0,
1145 };
1146 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1147 assert_eq!(
1148 src,
1149 LocationAddr {
1150 zone_id: 0,
1151 word_id: 0,
1152 site_id: 0,
1153 }
1154 );
1155 assert_eq!(
1156 dst,
1157 LocationAddr {
1158 zone_id: 0,
1159 word_id: 1,
1160 site_id: 0,
1161 }
1162 );
1163 }
1164
1165 #[test]
1166 fn test_lane_endpoints_zone_bus() {
1167 let spec = make_valid_two_zone_spec();
1168 let lane = LaneAddr {
1170 direction: Direction::Forward,
1171 move_type: MoveType::ZoneBus,
1172 zone_id: 0,
1173 word_id: 0,
1174 site_id: 0,
1175 bus_id: 0,
1176 };
1177 let (src, dst) = spec.lane_endpoints(&lane).unwrap();
1178 assert_eq!(
1179 src,
1180 LocationAddr {
1181 zone_id: 0,
1182 word_id: 0,
1183 site_id: 0,
1184 }
1185 );
1186 assert_eq!(
1187 dst,
1188 LocationAddr {
1189 zone_id: 1,
1190 word_id: 0,
1191 site_id: 0,
1192 }
1193 );
1194 }
1195
1196 #[test]
1197 fn test_lane_endpoints_invalid_bus_returns_none() {
1198 let spec = make_valid_two_zone_spec();
1199 let lane = LaneAddr {
1200 direction: Direction::Forward,
1201 move_type: MoveType::SiteBus,
1202 zone_id: 0,
1203 word_id: 0,
1204 site_id: 0,
1205 bus_id: 99,
1206 };
1207 assert!(spec.lane_endpoints(&lane).is_none());
1208 }
1209
1210 #[test]
1213 fn test_json_round_trip() {
1214 let spec = make_valid_two_zone_spec();
1215 let json = serde_json::to_string_pretty(&spec).unwrap();
1216 let deserialized = ArchSpec::from_json(&json).unwrap();
1217 assert_eq!(spec, deserialized);
1218 }
1219
1220 #[test]
1221 fn test_from_json_validated() {
1222 let spec = make_valid_two_zone_spec();
1223 let json = serde_json::to_string(&spec).unwrap();
1224 let validated = ArchSpec::from_json_validated(&json).unwrap();
1225 assert_eq!(spec, validated);
1226 }
1227
1228 #[test]
1229 fn test_from_json_validated_invalid() {
1230 let json = r#"{"version": "1.0"}"#;
1231 let result = ArchSpec::from_json_validated(json);
1232 assert!(result.is_err());
1233 }
1234
1235 #[test]
1238 fn test_word_by_id_found() {
1239 let spec = make_valid_two_zone_spec();
1240 let word = spec.word_by_id(0).unwrap();
1241 assert_eq!(word.sites.len(), 2);
1242 }
1243
1244 #[test]
1245 fn test_word_by_id_not_found() {
1246 let spec = make_valid_two_zone_spec();
1247 assert!(spec.word_by_id(99).is_none());
1248 }
1249
1250 #[test]
1251 fn test_zone_by_id_found() {
1252 let spec = make_valid_two_zone_spec();
1253 let zone = spec.zone_by_id(0).unwrap();
1254 assert_eq!(zone.site_buses.len(), 1);
1255 }
1256
1257 #[test]
1258 fn test_zone_by_id_not_found() {
1259 let spec = make_valid_two_zone_spec();
1260 assert!(spec.zone_by_id(99).is_none());
1261 }
1262
1263 #[test]
1266 fn test_site_bus_resolve_forward() {
1267 let spec = make_valid_two_zone_spec();
1268 let bus = &spec.zones[0].site_buses[0];
1269 assert_eq!(bus.resolve_forward(0), Some(1));
1270 assert_eq!(bus.resolve_forward(99), None);
1271 }
1272
1273 #[test]
1274 fn test_site_bus_resolve_backward() {
1275 let spec = make_valid_two_zone_spec();
1276 let bus = &spec.zones[0].site_buses[0];
1277 assert_eq!(bus.resolve_backward(1), Some(0));
1278 assert_eq!(bus.resolve_backward(99), None);
1279 }
1280
1281 #[test]
1282 fn test_word_bus_resolve_forward() {
1283 let spec = make_valid_two_zone_spec();
1284 let bus = &spec.zones[0].word_buses[0];
1285 assert_eq!(bus.resolve_forward(0), Some(1));
1286 assert_eq!(bus.resolve_forward(99), None);
1287 }
1288
1289 #[test]
1290 fn test_word_bus_resolve_backward() {
1291 let spec = make_valid_two_zone_spec();
1292 let bus = &spec.zones[0].word_buses[0];
1293 assert_eq!(bus.resolve_backward(1), Some(0));
1294 assert_eq!(bus.resolve_backward(99), None);
1295 }
1296
1297 #[test]
1298 fn test_zone_bus_resolve_forward() {
1299 let spec = make_valid_two_zone_spec();
1300 let bus = &spec.zone_buses[0];
1301 let src = ZonedWordRef {
1302 zone_id: 0,
1303 word_id: 0,
1304 };
1305 let dst = bus.resolve_forward(&src).unwrap();
1306 assert_eq!(dst.zone_id, 1);
1307 assert_eq!(dst.word_id, 0);
1308 }
1309
1310 #[test]
1311 fn test_zone_bus_resolve_backward() {
1312 let spec = make_valid_two_zone_spec();
1313 let bus = &spec.zone_buses[0];
1314 let dst = ZonedWordRef {
1315 zone_id: 1,
1316 word_id: 0,
1317 };
1318 let src = bus.resolve_backward(&dst).unwrap();
1319 assert_eq!(src.zone_id, 0);
1320 assert_eq!(src.word_id, 0);
1321 }
1322
1323 #[test]
1326 fn test_check_location_valid() {
1327 let spec = make_valid_two_zone_spec();
1328 assert!(
1329 spec.check_location(&LocationAddr {
1330 zone_id: 0,
1331 word_id: 0,
1332 site_id: 0,
1333 })
1334 .is_none()
1335 );
1336 }
1337
1338 #[test]
1339 fn test_check_location_invalid_zone() {
1340 let spec = make_valid_two_zone_spec();
1341 let err = spec
1342 .check_location(&LocationAddr {
1343 zone_id: 99,
1344 word_id: 0,
1345 site_id: 0,
1346 })
1347 .unwrap();
1348 assert!(err.contains("zone_id"));
1349 }
1350
1351 #[test]
1354 fn test_check_lane_valid_site_bus() {
1355 let spec = make_valid_two_zone_spec();
1356 let lane = LaneAddr {
1357 direction: Direction::Forward,
1358 move_type: MoveType::SiteBus,
1359 zone_id: 0,
1360 word_id: 0,
1361 site_id: 0,
1362 bus_id: 0,
1363 };
1364 assert!(spec.check_lane(&lane).is_empty());
1365 }
1366
1367 #[test]
1368 fn test_check_lane_invalid_zone() {
1369 let spec = make_valid_two_zone_spec();
1370 let lane = LaneAddr {
1371 direction: Direction::Forward,
1372 move_type: MoveType::SiteBus,
1373 zone_id: 99,
1374 word_id: 0,
1375 site_id: 0,
1376 bus_id: 0,
1377 };
1378 let errors = spec.check_lane(&lane);
1379 assert!(!errors.is_empty());
1380 assert!(errors[0].contains("zone_id"));
1381 }
1382
1383 #[test]
1384 fn test_check_lane_invalid_bus() {
1385 let spec = make_valid_two_zone_spec();
1386 let lane = LaneAddr {
1387 direction: Direction::Forward,
1388 move_type: MoveType::SiteBus,
1389 zone_id: 0,
1390 word_id: 0,
1391 site_id: 0,
1392 bus_id: 99,
1393 };
1394 let errors = spec.check_lane(&lane);
1395 assert!(!errors.is_empty());
1396 }
1397
1398 #[test]
1399 fn test_check_lane_zone_bus_valid() {
1400 let spec = make_valid_two_zone_spec();
1401 let lane = LaneAddr {
1402 direction: Direction::Forward,
1403 move_type: MoveType::ZoneBus,
1404 zone_id: 0,
1405 word_id: 0,
1406 site_id: 0,
1407 bus_id: 0,
1408 };
1409 assert!(spec.check_lane(&lane).is_empty());
1410 }
1411
1412 #[test]
1413 fn test_check_lane_zone_bus_invalid_bus() {
1414 let spec = make_valid_two_zone_spec();
1415 let lane = LaneAddr {
1416 direction: Direction::Forward,
1417 move_type: MoveType::ZoneBus,
1418 zone_id: 0,
1419 word_id: 0,
1420 site_id: 0,
1421 bus_id: 99,
1422 };
1423 let errors = spec.check_lane(&lane);
1424 assert!(!errors.is_empty());
1425 assert!(errors[0].contains("zone_bus"));
1426 }
1427
1428 #[test]
1431 fn test_check_zone_valid() {
1432 let spec = make_valid_two_zone_spec();
1433 assert!(spec.check_zone(&ZoneAddr { zone_id: 0 }).is_none());
1434 }
1435
1436 #[test]
1437 fn test_check_zone_invalid() {
1438 let spec = make_valid_two_zone_spec();
1439 assert!(spec.check_zone(&ZoneAddr { zone_id: 99 }).is_some());
1440 }
1441
1442 #[test]
1445 fn test_check_lane_group_consistency_empty() {
1446 let spec = make_valid_two_zone_spec();
1447 assert!(spec.check_lane_group_consistency(&[]).is_empty());
1448 }
1449
1450 #[test]
1451 fn test_check_lane_group_consistency_zone_mismatch() {
1452 let spec = make_valid_two_zone_spec();
1453 let lanes = vec![
1454 LaneAddr {
1455 direction: Direction::Forward,
1456 move_type: MoveType::SiteBus,
1457 zone_id: 0,
1458 word_id: 0,
1459 site_id: 0,
1460 bus_id: 0,
1461 },
1462 LaneAddr {
1463 direction: Direction::Forward,
1464 move_type: MoveType::SiteBus,
1465 zone_id: 1,
1466 word_id: 0,
1467 site_id: 0,
1468 bus_id: 0,
1469 },
1470 ];
1471 let errors = spec.check_lane_group_consistency(&lanes);
1472 assert!(!errors.is_empty());
1473 assert!(errors[0].contains("zone_id mismatch"));
1474 }
1475
1476 #[test]
1479 fn test_check_locations_valid() {
1480 let spec = make_valid_two_zone_spec();
1481 let locs = vec![
1482 LocationAddr {
1483 zone_id: 0,
1484 word_id: 0,
1485 site_id: 0,
1486 },
1487 LocationAddr {
1488 zone_id: 0,
1489 word_id: 0,
1490 site_id: 1,
1491 },
1492 ];
1493 assert!(spec.check_locations(&locs).is_empty());
1494 }
1495
1496 #[test]
1497 fn test_check_locations_duplicate() {
1498 let spec = make_valid_two_zone_spec();
1499 let locs = vec![
1500 LocationAddr {
1501 zone_id: 0,
1502 word_id: 0,
1503 site_id: 0,
1504 },
1505 LocationAddr {
1506 zone_id: 0,
1507 word_id: 0,
1508 site_id: 0,
1509 },
1510 ];
1511 let errors = spec.check_locations(&locs);
1512 assert!(
1513 errors
1514 .iter()
1515 .any(|e| matches!(e, LocationGroupError::DuplicateAddress { .. }))
1516 );
1517 }
1518
1519 #[test]
1520 fn test_check_locations_invalid() {
1521 let spec = make_valid_two_zone_spec();
1522 let locs = vec![LocationAddr {
1523 zone_id: 99,
1524 word_id: 0,
1525 site_id: 0,
1526 }];
1527 let errors = spec.check_locations(&locs);
1528 assert!(
1529 errors
1530 .iter()
1531 .any(|e| matches!(e, LocationGroupError::InvalidAddress { .. }))
1532 );
1533 }
1534
1535 #[test]
1538 fn test_word_partner_map() {
1539 let spec = make_valid_two_zone_spec();
1540 let map = spec.word_partner_map();
1541 assert_eq!(map.get(&0), Some(&1));
1543 assert_eq!(map.get(&1), Some(&0));
1544 assert_eq!(map.len(), 2);
1545 }
1546
1547 #[test]
1548 fn test_word_zone_map() {
1549 let spec = make_valid_two_zone_spec();
1550 let map = spec.word_zone_map();
1551 assert_eq!(map.get(&0), Some(&0));
1553 assert_eq!(map.get(&1), Some(&0));
1554 assert_eq!(map.len(), 2); }
1556
1557 #[test]
1558 fn test_left_cz_word_ids() {
1559 let spec = make_valid_two_zone_spec();
1560 let home = spec.left_cz_word_ids();
1561 assert_eq!(home, vec![0]);
1564 }
1565
1566 #[test]
1567 fn test_is_home_position() {
1568 let spec = make_valid_two_zone_spec();
1569 let home = LocationAddr {
1571 zone_id: 0,
1572 word_id: 0,
1573 site_id: 0,
1574 };
1575 let staging = LocationAddr {
1576 zone_id: 0,
1577 word_id: 1,
1578 site_id: 0,
1579 };
1580 assert!(spec.is_home_position(&home));
1581 assert!(!spec.is_home_position(&staging));
1582 }
1583
1584 #[test]
1585 fn test_lane_for_endpoints_site_bus() {
1586 let spec = make_valid_two_zone_spec();
1587 let src = LocationAddr {
1590 zone_id: 0,
1591 word_id: 0,
1592 site_id: 0,
1593 };
1594 let dst = LocationAddr {
1595 zone_id: 0,
1596 word_id: 0,
1597 site_id: 1,
1598 };
1599 let lane = spec.lane_for_endpoints(&src, &dst);
1600 assert!(lane.is_some(), "should find a lane for (src, dst)");
1601 let l = lane.unwrap();
1602 assert_eq!(l.move_type, MoveType::SiteBus);
1603 assert_eq!(l.direction, Direction::Forward);
1604 }
1605
1606 #[test]
1607 fn test_lane_for_endpoints_word_bus() {
1608 let spec = make_valid_two_zone_spec();
1609 let src = LocationAddr {
1611 zone_id: 0,
1612 word_id: 0,
1613 site_id: 0,
1614 };
1615 let dst = LocationAddr {
1616 zone_id: 0,
1617 word_id: 1,
1618 site_id: 0,
1619 };
1620 let lane = spec.lane_for_endpoints(&src, &dst);
1621 assert!(lane.is_some(), "should find a word-bus lane");
1622 let l = lane.unwrap();
1623 assert_eq!(l.move_type, MoveType::WordBus);
1624 assert_eq!(l.direction, Direction::Forward);
1625 }
1626
1627 #[test]
1628 fn test_lane_for_endpoints_not_found() {
1629 let spec = make_valid_two_zone_spec();
1630 let src = LocationAddr {
1633 zone_id: 0,
1634 word_id: 0,
1635 site_id: 0,
1636 };
1637 let dst = LocationAddr {
1638 zone_id: 0,
1639 word_id: 1,
1640 site_id: 1,
1641 };
1642 assert!(spec.lane_for_endpoints(&src, &dst).is_none());
1643 }
1644}