gammalooprs/utils/linnet_ext.rs
1use linnet::permutation::Permutation;
2
3pub trait FromMappings: Sized {
4 fn from_mappings<I>(mappings: I, size: usize) -> Result<Self, String>
5 where
6 I: IntoIterator<Item = (usize, usize)>;
7}
8
9impl FromMappings for Permutation {
10 /// Creates a permutation from a set of mapping pairs, minimizing changes to other elements.
11 ///
12 /// Takes a collection of (from, to) pairs and constructs a valid permutation that satisfies
13 /// these mappings while keeping other elements as close to their identity positions as possible.
14 ///
15 /// # Arguments
16 ///
17 /// * `mappings` - An iterator of (from, to) pairs where each pair represents `from -> to`
18 /// * `size` - The size of the permutation to create
19 ///
20 /// # Returns
21 ///
22 /// A `Result` containing the permutation if successful, or an error string if the mappings
23 /// are inconsistent or invalid.
24 ///
25 /// # Examples
26 ///
27 /// ```
28 /// # use linnet::permutation::Permutation;
29 /// // Create a permutation where 5->0 and 0->1, others stay in place where possible
30 /// let mappings = vec![(5, 0), (0, 1)];
31 /// let p = Permutation::from_mappings(mappings.iter().copied(), 6).unwrap();
32 /// assert_eq!(p.map(), &[1, 5, 2, 3, 4, 0]);
33 ///
34 /// // Verify the specific mappings work
35 /// assert_eq!(p[5], 0); // 5 -> 0
36 /// assert_eq!(p[0], 1); // 0 -> 1
37 /// ```
38 fn from_mappings<I>(mappings: I, size: usize) -> Result<Self, String>
39 where
40 I: IntoIterator<Item = (usize, usize)>,
41 {
42 if size == 0 {
43 return Ok(Self::id(0));
44 }
45
46 // Start with identity
47 let mut map: Vec<usize> = (0..size).collect();
48
49 // owner_of_target[t] = Some(s) iff target t is already taken by source s (forced or assigned)
50 let mut owner_of_target: Vec<Option<usize>> = vec![None; size];
51
52 // forced_sources[s] = true iff s has a forced mapping in the input
53 let mut forced_sources = vec![false; size];
54
55 // 1) Apply and validate forced mappings in O(1) each
56 for (from, to) in mappings {
57 if from >= size || to >= size {
58 return Err(format!(
59 "Index out of bounds: mapping ({}, {}) for size {}",
60 from, to, size
61 ));
62 }
63
64 if let Some(prev_from) = owner_of_target[to]
65 && prev_from != from
66 {
67 return Err(format!(
68 "Target {} is mapped from multiple sources ({} and {})",
69 to, prev_from, from
70 ));
71 }
72 if forced_sources[from] && map[from] != to {
73 return Err(format!(
74 "Source {} has conflicting mappings ({} vs {})",
75 from, map[from], to
76 ));
77 }
78
79 // Record the forced mapping
80 map[from] = to;
81 owner_of_target[to] = Some(from);
82 forced_sources[from] = true;
83 }
84
85 // 2) Keep identities whenever possible (minimal changes)
86 // If a source s is not forced and its identity target s is free, keep s->s.
87 // Otherwise, it becomes "displaced" and will be assigned later.
88 let mut displaced_sources: Vec<usize> = Vec::new();
89 for s in 0..size {
90 if forced_sources[s] {
91 continue;
92 }
93 if owner_of_target[s].is_none() {
94 // take the identity slot
95 map[s] = s;
96 owner_of_target[s] = Some(s);
97 } else {
98 // identity target already used by some forced mapping -> displace s
99 displaced_sources.push(s);
100 }
101 }
102
103 // 3) Gather remaining free targets (those without an owner)
104 let mut free_targets: Vec<usize> = Vec::new();
105 for (t, owner) in owner_of_target.iter().enumerate().take(size) {
106 if owner.is_none() {
107 free_targets.push(t);
108 }
109 }
110
111 // Sanity: counts must match, otherwise constraints were inconsistent
112 if displaced_sources.len() != free_targets.len() {
113 return Err("Failed to create valid permutation (inconsistent counts)".to_string());
114 }
115
116 // 4) Assign displaced sources to remaining free targets
117 for (s, t) in displaced_sources.into_iter().zip(free_targets) {
118 // Note: t != s by construction (if t==s, owner_of_target[s] would have been None earlier,
119 // and we would have kept the identity)
120 map[s] = t;
121 owner_of_target[t] = Some(s);
122 }
123
124 // 5) Final uniqueness check (linear)
125 let mut seen = vec![false; size];
126 for &t in &map {
127 if t >= size || std::mem::replace(&mut seen[t], true) {
128 return Err("Failed to create valid permutation".to_string());
129 }
130 }
131
132 Ok(Self::from_map(map))
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use insta::assert_snapshot;
139 use linnet::permutation::Permutation;
140
141 #[test]
142 fn test_from_mappings_complex() {
143 // Test more complex displacement
144 let mappings = [(0, 3), (1, 0)];
145 let p = Permutation::from_mappings(mappings.iter().copied(), 4).unwrap();
146 assert_eq!(p[0], 3);
147 assert_eq!(p[1], 0);
148
149 assert_snapshot!( format!("{:?}",p.apply_slice([0,1,2,3])),@"[1, 3, 2, 0]");
150 }
151}