1use crate::quantum_ops::{content_hash, GrantRef, RearrangeReceipt};
27use ed25519_dalek::SigningKey;
28
29fn splitmix64(state: &mut u64) -> u64 {
30 *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
31 let mut z = *state;
32 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
33 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
34 z ^ (z >> 31)
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct Array {
44 pub w: u32,
45 pub h: u32,
46 pub occ: Vec<bool>,
48}
49
50impl Array {
51 pub fn empty(w: u32, h: u32) -> Array {
52 Array { w, h, occ: vec![false; (w * h) as usize] }
53 }
54 pub fn load(w: u32, h: u32, fill_permille: u32, seed: u64) -> Array {
56 let mut st = seed.wrapping_mul(0xA24B_AED4).wrapping_add(1);
57 let occ = (0..w * h)
58 .map(|_| (splitmix64(&mut st) % 1000) < fill_permille as u64)
59 .collect();
60 Array { w, h, occ }
61 }
62 pub fn atoms(&self) -> Vec<usize> {
63 (0..self.occ.len()).filter(|&i| self.occ[i]).collect()
64 }
65 pub fn count(&self) -> u32 {
66 self.occ.iter().filter(|&&o| o).count() as u32
67 }
68 fn pos(&self, idx: usize) -> (i64, i64) {
69 ((idx as u32 % self.w) as i64, (idx as u32 / self.w) as i64)
70 }
71 pub fn bytes(&self) -> Vec<u8> {
73 let mut b = Vec::with_capacity(self.occ.len() / 8 + 12);
74 b.extend_from_slice(b"wai:qc-array\x01");
75 b.extend_from_slice(&self.w.to_le_bytes());
76 b.extend_from_slice(&self.h.to_le_bytes());
77 for chunk in self.occ.chunks(8) {
78 let mut byte = 0u8;
79 for (i, &o) in chunk.iter().enumerate() {
80 if o {
81 byte |= 1 << i;
82 }
83 }
84 b.push(byte);
85 }
86 b
87 }
88}
89
90pub fn target_block(w: u32, h: u32, tw: u32, th: u32) -> Vec<usize> {
92 let x0 = (w - tw) / 2;
93 let y0 = (h - th) / 2;
94 let mut t = Vec::with_capacity((tw * th) as usize);
95 for y in y0..y0 + th {
96 for x in x0..x0 + tw {
97 t.push((y * w + x) as usize);
98 }
99 }
100 t
101}
102
103pub fn target_bytes(target: &[usize], w: u32, h: u32) -> Vec<u8> {
105 let mut b = Vec::with_capacity(target.len() * 4 + 16);
106 b.extend_from_slice(b"wai:qc-target\x01");
107 b.extend_from_slice(&w.to_le_bytes());
108 b.extend_from_slice(&h.to_le_bytes());
109 let mut sorted = target.to_vec();
110 sorted.sort_unstable();
111 for &s in &sorted {
112 b.extend_from_slice(&(s as u32).to_le_bytes());
113 }
114 b
115}
116
117fn hungarian(cost: &[Vec<i64>], n: usize, m: usize) -> Vec<usize> {
125 const INF: i64 = i64::MAX / 4;
126 let mut u = vec![0i64; n + 1];
127 let mut v = vec![0i64; m + 1];
128 let mut p = vec![0usize; m + 1]; let mut way = vec![0usize; m + 1];
130 for i in 1..=n {
131 p[0] = i;
132 let mut j0 = 0usize;
133 let mut minv = vec![INF; m + 1];
134 let mut used = vec![false; m + 1];
135 loop {
136 used[j0] = true;
137 let i0 = p[j0];
138 let mut delta = INF;
139 let mut j1 = 0usize;
140 for j in 1..=m {
141 if !used[j] {
142 let cur = cost[i0 - 1][j - 1] - u[i0] - v[j];
143 if cur < minv[j] {
144 minv[j] = cur;
145 way[j] = j0;
146 }
147 if minv[j] < delta {
148 delta = minv[j];
149 j1 = j;
150 }
151 }
152 }
153 for j in 0..=m {
154 if used[j] {
155 u[p[j]] += delta;
156 v[j] -= delta;
157 } else {
158 minv[j] -= delta;
159 }
160 }
161 j0 = j1;
162 if p[j0] == 0 {
163 break;
164 }
165 }
166 loop {
167 let j1 = way[j0];
168 p[j0] = p[j1];
169 j0 = j1;
170 if j0 == 0 {
171 break;
172 }
173 }
174 }
175 let mut assignment = vec![usize::MAX; n];
176 for j in 1..=m {
177 if p[j] != 0 {
178 assignment[p[j] - 1] = j - 1;
179 }
180 }
181 assignment
182}
183
184#[derive(Clone, Debug, PartialEq, Eq)]
190pub struct Plan {
191 pub moves: Vec<(usize, usize)>,
192 pub total_distance: u64,
193 pub atoms_moved: u32,
194 pub feasible: bool,
196}
197
198impl Plan {
199 pub fn bytes(&self) -> Vec<u8> {
200 let mut b = Vec::with_capacity(self.moves.len() * 8 + 8);
201 b.extend_from_slice(b"wai:qc-plan\x01");
202 for &(f, t) in &self.moves {
203 b.extend_from_slice(&(f as u32).to_le_bytes());
204 b.extend_from_slice(&(t as u32).to_le_bytes());
205 }
206 b
207 }
208 pub fn plan_hash(&self) -> [u8; 32] {
209 content_hash(&self.bytes())
210 }
211}
212
213pub fn solve(array: &Array, target: &[usize]) -> Plan {
216 let atoms = array.atoms();
217 let nt = target.len();
218 let na = atoms.len();
219 if na < nt {
220 return Plan { moves: Vec::new(), total_distance: 0, atoms_moved: 0, feasible: false };
221 }
222 let cost: Vec<Vec<i64>> = target
224 .iter()
225 .map(|&t| {
226 let (tx, ty) = array.pos(t);
227 atoms
228 .iter()
229 .map(|&a| {
230 let (ax, ay) = array.pos(a);
231 (tx - ax).abs() + (ty - ay).abs()
232 })
233 .collect()
234 })
235 .collect();
236 let assign = hungarian(&cost, nt, na);
237 let mut moves = Vec::new();
238 let mut total = 0u64;
239 let mut moved = 0u32;
240 for (i, &t) in target.iter().enumerate() {
241 let a = atoms[assign[i]];
242 let d = cost[i][assign[i]] as u64;
243 total += d;
244 if a != t {
245 moves.push((a, t));
246 moved += 1;
247 }
248 }
249 Plan { moves, total_distance: total, atoms_moved: moved, feasible: true }
250}
251
252pub fn verify(array: &Array, target: &[usize], plan: &Plan) -> bool {
256 if !plan.feasible {
257 return false;
258 }
259 let mut occ = array.occ.clone();
260 let mut froms = std::collections::HashSet::new();
261 let mut tos = std::collections::HashSet::new();
262 for &(f, t) in &plan.moves {
263 if f >= occ.len() || t >= occ.len() || !array.occ[f] {
264 return false; }
266 if !froms.insert(f) || !tos.insert(t) {
267 return false; }
269 }
270 for &(f, _) in &plan.moves {
272 occ[f] = false;
273 }
274 for &(_, t) in &plan.moves {
275 occ[t] = true;
276 }
277 target.iter().all(|&t| occ[t])
278}
279
280#[allow(clippy::too_many_arguments)]
283pub fn rearrange_and_seal(
284 signer: &SigningKey,
285 signer_id: &str,
286 array: &Array,
287 target: &[usize],
288 joules_micro: u64,
289 grant: GrantRef,
290) -> (Plan, bool, RearrangeReceipt) {
291 let plan = solve(array, target);
292 let success = verify(array, target, &plan);
293 let receipt = RearrangeReceipt::seal(
294 signer,
295 signer_id,
296 "hungarian-lsap",
297 content_hash(&array.bytes()),
298 content_hash(&target_bytes(target, array.w, array.h)),
299 plan.plan_hash(),
300 success,
301 plan.atoms_moved,
302 plan.total_distance,
303 joules_micro,
304 grant,
305 None,
306 );
307 (plan, success, receipt)
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 fn key(s: u8) -> SigningKey {
315 SigningKey::from_bytes(&[s; 32])
316 }
317
318 fn brute(cost: &[Vec<i64>], n: usize, m: usize) -> i64 {
320 let mut cols: Vec<usize> = (0..m).collect();
321 let mut best = i64::MAX;
322 fn rec(cost: &[Vec<i64>], n: usize, i: usize, used: &mut Vec<bool>, acc: i64, best: &mut i64) {
324 if i == n {
325 *best = (*best).min(acc);
326 return;
327 }
328 for j in 0..cost[i].len() {
329 if !used[j] {
330 used[j] = true;
331 rec(cost, n, i + 1, used, acc + cost[i][j], best);
332 used[j] = false;
333 }
334 }
335 }
336 let mut used = vec![false; m];
337 rec(cost, n, 0, &mut used, 0, &mut best);
338 let _ = &mut cols;
339 best
340 }
341
342 #[test]
343 fn hungarian_matches_brute_force() {
344 let mut st = 12345u64;
345 for _ in 0..30 {
346 let n = 3 + (splitmix64(&mut st) % 3) as usize;
347 let m = n + (splitmix64(&mut st) % 3) as usize;
348 let cost: Vec<Vec<i64>> = (0..n)
349 .map(|_| (0..m).map(|_| (splitmix64(&mut st) % 20) as i64).collect())
350 .collect();
351 let assign = hungarian(&cost, n, m);
352 let got: i64 = (0..n).map(|i| cost[i][assign[i]]).sum();
353 assert_eq!(got, brute(&cost, n, m), "Hungarian must be optimal");
354 let mut s = std::collections::HashSet::new();
356 assert!(assign.iter().all(|&c| s.insert(c)));
357 }
358 }
359
360 #[test]
361 fn rearrangement_makes_defect_free() {
362 let arr = Array::load(12, 12, 550, 7);
364 let target = target_block(12, 12, 6, 6);
365 assert!(arr.count() as usize >= target.len(), "enough atoms for the test");
366 let plan = solve(&arr, &target);
367 assert!(plan.feasible);
368 assert!(verify(&arr, &target, &plan), "target must be defect-free after the plan");
369 }
370
371 #[test]
372 fn already_placed_atoms_do_not_move() {
373 let mut arr = Array::empty(8, 8);
375 let target = target_block(8, 8, 4, 4);
376 for &t in &target {
377 arr.occ[t] = true;
378 }
379 let plan = solve(&arr, &target);
380 assert!(plan.feasible);
381 assert_eq!(plan.atoms_moved, 0);
382 assert_eq!(plan.total_distance, 0);
383 assert!(verify(&arr, &target, &plan));
384 }
385
386 #[test]
387 fn too_few_atoms_is_infeasible() {
388 let mut arr = Array::empty(8, 8);
389 arr.occ[0] = true; let target = target_block(8, 8, 3, 3); let plan = solve(&arr, &target);
392 assert!(!plan.feasible);
393 assert!(!verify(&arr, &target, &plan));
394 }
395
396 #[test]
397 fn tamper_breaks_verification() {
398 let arr = Array::load(10, 10, 600, 3);
399 let target = target_block(10, 10, 5, 5);
400 let mut plan = solve(&arr, &target);
401 assert!(verify(&arr, &target, &plan));
402 if let Some(mv) = plan.moves.first_mut() {
404 mv.1 = 0; }
406 if !target.contains(&0) {
408 assert!(!verify(&arr, &target, &plan), "a diverted move must fail verification");
409 }
410 }
411
412 #[test]
413 fn solve_is_deterministic() {
414 let arr = Array::load(14, 14, 520, 99);
415 let target = target_block(14, 14, 7, 7);
416 assert_eq!(solve(&arr, &target), solve(&arr, &target));
417 }
418
419 #[test]
420 fn seals_verifying_receipt() {
421 let arr = Array::load(12, 12, 560, 42);
422 let target = target_block(12, 12, 6, 6);
423 let (plan, success, rec) = rearrange_and_seal(
424 &key(1), "did:key:lab", &arr, &target, 400_000, GrantRef::unbounded("quantum.rearrange"),
425 );
426 assert!(success);
427 assert!(rec.verify());
428 assert!(rec.success);
429 assert!(rec.plan_matches(&plan.bytes()));
430 }
431}