1use super::super::copyline::{create_copylines, mis_overhead_copyline, CopyLine};
7use super::super::grid::MappingGrid;
8use super::super::pathdecomposition::{
9 pathwidth, vertex_order_from_layout, PathDecompositionMethod,
10};
11use super::gadgets::{
12 apply_crossing_gadgets, apply_simplifier_gadgets, tape_entry_mis_overhead, KsgPattern,
13 KsgTapeEntry,
14};
15use super::gadgets_weighted::{
16 apply_weighted_crossing_gadgets, apply_weighted_simplifier_gadgets,
17 weighted_tape_entry_mis_overhead, WeightedKsgPattern, WeightedKsgTapeEntry,
18};
19use super::{PADDING, SPACING};
20use crate::rules::unitdiskmapping::{mapping_integer_overflow, mapping_invalid};
21use crate::rules::ReductionError;
22use crate::topology::{Graph, KingsSubgraph, TriangularSubgraph};
23use serde::{Deserialize, Serialize};
24use std::collections::{HashMap, HashSet};
25use std::fmt;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29pub enum GridKind {
30 Kings,
32 Triangular,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct MappingResult<T = KsgTapeEntry> {
39 pub positions: Vec<(i64, i64)>,
41 pub node_weights: Vec<i64>,
43 pub grid_dimensions: (usize, usize),
45 pub kind: GridKind,
47 pub lines: Vec<CopyLine>,
49 pub padding: usize,
51 pub spacing: usize,
53 pub mis_overhead: i64,
55 pub tape: Vec<T>,
57 #[serde(default)]
59 pub doubled_cells: HashSet<(usize, usize)>,
60}
61
62impl<T> MappingResult<T> {
63 pub fn num_original_vertices(&self) -> usize {
65 self.lines.len()
66 }
67
68 pub fn edges(&self) -> Vec<(usize, usize)> {
70 match self.kind {
71 GridKind::Kings => self.to_kings_subgraph().edges(),
72 GridKind::Triangular => self.to_triangular_subgraph().edges(),
73 }
74 }
75
76 pub fn num_edges(&self) -> usize {
78 match self.kind {
79 GridKind::Kings => self.to_kings_subgraph().num_edges(),
80 GridKind::Triangular => self.to_triangular_subgraph().num_edges(),
81 }
82 }
83
84 pub fn print_config(&self, config: &[Vec<usize>]) {
91 print!("{}", self.format_config(config));
92 }
93
94 pub fn format_config(&self, config: &[Vec<usize>]) -> String {
96 let (rows, cols) = self.grid_dimensions;
97
98 let mut pos_to_node: HashMap<(i64, i64), usize> = HashMap::new();
100 for (idx, &(r, c)) in self.positions.iter().enumerate() {
101 pos_to_node.insert((r, c), idx);
102 }
103
104 let mut lines = Vec::new();
105
106 for r in 0..rows {
107 let row = i64::try_from(r).expect("mapping grid rows are validated against i64");
108 let mut line = String::new();
109 for c in 0..cols {
110 let is_selected = config
111 .get(r)
112 .and_then(|row| row.get(c))
113 .copied()
114 .unwrap_or(0)
115 > 0;
116 let column =
117 i64::try_from(c).expect("mapping grid columns are validated against i64");
118 let has_node = pos_to_node.contains_key(&(row, column));
119
120 let s = if has_node {
121 if is_selected {
122 "*"
123 } else {
124 "o"
125 }
126 } else {
127 "."
128 };
129 line.push_str(s);
130 line.push(' ');
131 }
132 line.pop();
134 lines.push(line);
135 }
136
137 lines.join("\n")
138 }
139
140 pub fn print_config_flat(&self, config: &[usize]) {
142 print!("{}", self.format_config_flat(config));
143 }
144
145 pub fn format_config_flat(&self, config: &[usize]) -> String {
147 self.format_grid_with_config(Some(config))
148 }
149
150 pub fn to_kings_subgraph(&self) -> KingsSubgraph {
153 KingsSubgraph::new(self.positions.clone())
154 }
155
156 pub fn to_triangular_subgraph(&self) -> TriangularSubgraph {
159 TriangularSubgraph::new(self.positions.clone())
160 }
161
162 fn format_grid_with_config(&self, config: Option<&[usize]>) -> String {
168 if self.positions.is_empty() {
169 return String::from("(empty grid graph)");
170 }
171
172 let (rows, cols) = self.grid_dimensions;
173
174 let mut pos_to_idx: HashMap<(i64, i64), usize> = HashMap::new();
175 for (idx, &(r, c)) in self.positions.iter().enumerate() {
176 pos_to_idx.insert((r, c), idx);
177 }
178
179 let mut lines = Vec::new();
180
181 for r in 0..rows {
182 let r = i64::try_from(r).expect("mapping grid rows are validated against i64");
183 let mut line = String::new();
184 for c in 0..cols {
185 let c = i64::try_from(c).expect("mapping grid columns are validated against i64");
186 let s = if let Some(&idx) = pos_to_idx.get(&(r, c)) {
187 if let Some(cfg) = config {
188 if cfg.get(idx).copied().unwrap_or(0) > 0 {
189 "●".to_string()
190 } else {
191 "○".to_string()
192 }
193 } else {
194 let w = self.node_weights[idx];
195 let ws = format!("{}", w);
196 if ws.len() == 1 {
197 ws
198 } else {
199 "●".to_string()
200 }
201 }
202 } else {
203 "⋅".to_string()
204 };
205 line.push_str(&s);
206 line.push(' ');
207 }
208 line.pop();
209 lines.push(line);
210 }
211
212 lines.join("\n")
213 }
214}
215
216impl MappingResult<KsgTapeEntry> {
217 pub fn map_config_back(
230 &self,
231 grid_config: &[usize],
232 ) -> crate::rules::ExtractionResult<Vec<usize>> {
233 self.map_config_back_internal(grid_config)
234 .map_err(|error| crate::rules::ExtractionError::invalid(error.to_string()))
235 }
236
237 fn map_config_back_internal(
238 &self,
239 grid_config: &[usize],
240 ) -> Result<Vec<usize>, ReductionError> {
241 if grid_config.len() != self.positions.len() {
242 return Err(mapping_invalid(
243 "grid configuration length must match the mapped vertex count",
244 ));
245 }
246 let (rows, cols) = self.grid_dimensions;
248 let mut config_2d = vec![vec![0usize; cols]; rows];
249
250 for (idx, &(row, col)) in self.positions.iter().enumerate() {
251 let row = usize::try_from(row)
252 .map_err(|_| mapping_invalid("mapping result contains a negative grid row"))?;
253 let col = usize::try_from(col)
254 .map_err(|_| mapping_invalid("mapping result contains a negative grid column"))?;
255 if row >= rows || col >= cols {
256 return Err(mapping_invalid(
257 "mapping result contains a position outside its grid dimensions",
258 ));
259 }
260 config_2d[row][col] = grid_config[idx];
261 }
262
263 unapply_gadgets(&self.tape, &mut config_2d)?;
265
266 map_config_copyback(
268 &self.lines,
269 self.padding,
270 self.spacing,
271 &config_2d,
272 &self.doubled_cells,
273 )
274 }
275}
276
277impl MappingResult<WeightedKsgTapeEntry> {
278 pub fn map_config_back(
280 &self,
281 grid_config: &[usize],
282 ) -> crate::rules::ExtractionResult<Vec<usize>> {
283 self.map_config_back_internal(grid_config)
284 .map_err(|error| crate::rules::ExtractionError::invalid(error.to_string()))
285 }
286
287 fn map_config_back_internal(
288 &self,
289 grid_config: &[usize],
290 ) -> Result<Vec<usize>, ReductionError> {
291 if grid_config.len() != self.positions.len() {
292 return Err(mapping_invalid(
293 "grid configuration length must match the mapped vertex count",
294 ));
295 }
296 let (rows, cols) = self.grid_dimensions;
298 let mut config_2d = vec![vec![0usize; cols]; rows];
299
300 for (idx, &(row, col)) in self.positions.iter().enumerate() {
301 let row = usize::try_from(row)
302 .map_err(|_| mapping_invalid("mapping result contains a negative grid row"))?;
303 let col = usize::try_from(col)
304 .map_err(|_| mapping_invalid("mapping result contains a negative grid column"))?;
305 if row >= rows || col >= cols {
306 return Err(mapping_invalid(
307 "mapping result contains a position outside its grid dimensions",
308 ));
309 }
310 config_2d[row][col] = grid_config[idx];
311 }
312
313 unapply_weighted_gadgets(&self.tape, &mut config_2d)?;
315
316 map_config_copyback(
318 &self.lines,
319 self.padding,
320 self.spacing,
321 &config_2d,
322 &self.doubled_cells,
323 )
324 }
325}
326
327impl<T> fmt::Display for MappingResult<T> {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 write!(f, "{}", self.format_grid_with_config(None))
330 }
331}
332
333pub(crate) fn map_config_copyback(
340 lines: &[CopyLine],
341 padding: usize,
342 spacing: usize,
343 config: &[Vec<usize>],
344 doubled_cells: &HashSet<(usize, usize)>,
345) -> Result<Vec<usize>, ReductionError> {
346 let mut result = vec![0usize; lines.len()];
347
348 for line in lines {
349 let locs = line.copyline_locations(padding, spacing);
350 let n = locs.len();
351 let mut count = 0i64;
352
353 for (iloc, &(row, col, weight)) in locs.iter().enumerate() {
354 let ci = config
355 .get(row)
356 .and_then(|r| r.get(col))
357 .copied()
358 .ok_or(mapping_invalid(
359 "copy line lies outside the configuration grid",
360 ))?;
361
362 if doubled_cells.contains(&(row, col)) {
364 if ci == 2 {
366 count = count
367 .checked_add(1)
368 .ok_or(mapping_integer_overflow("summing copy-back values"))?;
369 } else if ci == 1 {
370 let prev_zero =
372 if iloc > 0 {
373 let (pr, pc, _) = locs[iloc - 1];
374 config.get(pr).and_then(|r| r.get(pc)).copied().ok_or(
375 mapping_invalid(
376 "copy-line neighbor lies outside the configuration grid",
377 ),
378 )? == 0
379 } else {
380 true
381 };
382 let next_zero =
383 if iloc + 1 < n {
384 let (nr, nc, _) = locs[iloc + 1];
385 config.get(nr).and_then(|r| r.get(nc)).copied().ok_or(
386 mapping_invalid(
387 "copy-line neighbor lies outside the configuration grid",
388 ),
389 )? == 0
390 } else {
391 true
392 };
393 if prev_zero && next_zero {
394 count = count
395 .checked_add(1)
396 .ok_or(mapping_integer_overflow("summing copy-back values"))?;
397 }
398 }
399 } else if weight >= 1 {
401 let value = i64::try_from(ci)
403 .map_err(|_| mapping_integer_overflow("converting a copy-back value to i64"))?;
404 count = count
405 .checked_add(value)
406 .ok_or(mapping_integer_overflow("summing copy-back values"))?;
407 }
408 }
410
411 let overhead = i64::try_from(n / 2)
413 .map_err(|_| mapping_integer_overflow("converting copy-back overhead to i64"))?;
414 let adjusted = count
416 .checked_sub(overhead)
417 .ok_or(mapping_integer_overflow("subtracting copy-back overhead"))?;
418 let adjusted = adjusted.max(0);
419 result[line.vertex] = usize::try_from(adjusted)
420 .map_err(|_| mapping_integer_overflow("converting a copy-back result to usize"))?;
421 }
422
423 Ok(result)
424}
425
426pub(crate) fn unapply_gadgets(
428 tape: &[KsgTapeEntry],
429 config: &mut [Vec<usize>],
430) -> Result<(), ReductionError> {
431 for entry in tape.iter().rev() {
433 let pattern = KsgPattern::from_tape_idx(entry.pattern_idx).ok_or(mapping_invalid(
434 "tape contains an unknown unweighted KSG gadget index",
435 ))?;
436 pattern.map_config_back(entry.row, entry.col, config)?;
437 }
438 Ok(())
439}
440
441pub(crate) fn unapply_weighted_gadgets(
443 tape: &[WeightedKsgTapeEntry],
444 config: &mut [Vec<usize>],
445) -> Result<(), ReductionError> {
446 for entry in tape.iter().rev() {
448 let pattern = WeightedKsgPattern::from_tape_idx(entry.pattern_idx).ok_or(
449 mapping_invalid("tape contains an unknown weighted KSG gadget index"),
450 )?;
451 pattern.map_config_back(entry.row, entry.col, config)?;
452 }
453 Ok(())
454}
455
456fn embed_graph_internal(
458 num_vertices: usize,
459 edges: &[(usize, usize)],
460 vertex_order: &[usize],
461) -> Result<(MappingGrid, Vec<CopyLine>), ReductionError> {
462 if num_vertices == 0 {
463 return Err(mapping_invalid("num_vertices must be positive"));
464 }
465
466 let copylines = create_copylines(num_vertices, edges, vertex_order)?;
467
468 let max_hslot = copylines.iter().map(|l| l.hslot).max().unwrap_or(1);
470
471 let padding_twice = PADDING
472 .checked_mul(2)
473 .ok_or(mapping_integer_overflow("computing grid padding"))?;
474 let extent = |slots: usize| {
475 slots
476 .checked_mul(SPACING)
477 .and_then(|value| value.checked_add(2))
478 .and_then(|value| value.checked_add(padding_twice))
479 .ok_or(mapping_integer_overflow("computing grid dimensions"))
480 };
481 let rows = extent(max_hslot)?;
482 let cols = extent(num_vertices - 1)?;
483
484 let mut grid = MappingGrid::with_padding(rows, cols, SPACING, PADDING);
485
486 for line in ©lines {
488 for (row, col, weight) in line.copyline_locations(PADDING, SPACING) {
489 let weight = i64::try_from(weight)
490 .map_err(|_| mapping_integer_overflow("converting a grid weight to i64"))?;
491 grid.add_node(row, col, weight);
492 }
493 }
494
495 for &(u, v) in edges {
497 let u_line = ©lines[u];
498 let v_line = ©lines[v];
499
500 let (smaller_line, larger_line) = if u_line.vslot < v_line.vslot {
501 (u_line, v_line)
502 } else {
503 (v_line, u_line)
504 };
505 let (row, col) = grid.cross_at(smaller_line.vslot, larger_line.vslot, smaller_line.hslot);
506
507 if col > 0 {
509 grid.connect(row, col - 1);
510 }
511 if row > 0 && grid.is_occupied(row - 1, col) {
512 grid.connect(row - 1, col);
513 } else if row + 1 < grid.size().0 && grid.is_occupied(row + 1, col) {
514 grid.connect(row + 1, col);
515 }
516 }
517
518 Ok((grid, copylines))
519}
520
521#[cfg(test)]
527pub(crate) fn embed_graph(
528 num_vertices: usize,
529 edges: &[(usize, usize)],
530 vertex_order: &[usize],
531) -> Result<MappingGrid, ReductionError> {
532 embed_graph_internal(num_vertices, edges, vertex_order).map(|(grid, _)| grid)
533}
534
535pub fn map_unweighted(
543 num_vertices: usize,
544 edges: &[(usize, usize)],
545) -> Result<MappingResult<KsgTapeEntry>, ReductionError> {
546 map_unweighted_with_method(num_vertices, edges, PathDecompositionMethod::Auto)
547}
548
549pub fn map_unweighted_with_method(
556 num_vertices: usize,
557 edges: &[(usize, usize)],
558 method: PathDecompositionMethod,
559) -> Result<MappingResult<KsgTapeEntry>, ReductionError> {
560 let layout = pathwidth(num_vertices, edges, method);
561 let vertex_order = vertex_order_from_layout(&layout);
562 map_unweighted_with_order(num_vertices, edges, &vertex_order)
563}
564
565pub fn map_unweighted_with_order(
571 num_vertices: usize,
572 edges: &[(usize, usize)],
573 vertex_order: &[usize],
574) -> Result<MappingResult<KsgTapeEntry>, ReductionError> {
575 let (mut grid, copylines) = embed_graph_internal(num_vertices, edges, vertex_order)?;
576
577 let doubled_cells = grid.doubled_cells();
579
580 let crossing_tape = apply_crossing_gadgets(&mut grid, ©lines);
582
583 let simplifier_tape = apply_simplifier_gadgets(&mut grid, 2);
585
586 let mut tape = crossing_tape;
588 tape.extend(simplifier_tape);
589
590 let copyline_overhead = copylines.iter().try_fold(0_i64, |total, line| {
592 total
593 .checked_add(mis_overhead_copyline(line, SPACING, PADDING)?)
594 .ok_or(mapping_integer_overflow("summing copy-line MIS overhead"))
595 })?;
596
597 let gadget_overhead = tape.iter().try_fold(0_i64, |total, entry| {
599 total
600 .checked_add(tape_entry_mis_overhead(entry)?)
601 .ok_or(mapping_integer_overflow("summing gadget MIS overhead"))
602 })?;
603 let mis_overhead = copyline_overhead
604 .checked_add(gadget_overhead)
605 .ok_or(mapping_integer_overflow("computing total MIS overhead"))?;
606
607 if grid.has_unresolved_cells() {
608 return Err(mapping_invalid(
609 "mapping left doubled or connected cells unresolved",
610 ));
611 }
612
613 let positions: Vec<(i64, i64)> = grid
617 .occupied_coords()
618 .into_iter()
619 .filter_map(|(row, col)| {
620 grid.get(row, col)
621 .filter(|cell| cell.weight() > 0)
622 .map(|_| {
623 Ok((
624 i64::try_from(row).map_err(|_| {
625 mapping_integer_overflow("converting a grid row to i64")
626 })?,
627 i64::try_from(col).map_err(|_| {
628 mapping_integer_overflow("converting a grid column to i64")
629 })?,
630 ))
631 })
632 })
633 .collect::<Result<_, ReductionError>>()?;
634 let node_weights = vec![1i64; positions.len()];
635
636 Ok(MappingResult {
637 positions,
638 node_weights,
639 grid_dimensions: grid.size(),
640 kind: GridKind::Kings,
641 lines: copylines,
642 padding: PADDING,
643 spacing: SPACING,
644 mis_overhead,
645 tape,
646 doubled_cells,
647 })
648}
649
650pub fn map_weighted(
659 num_vertices: usize,
660 edges: &[(usize, usize)],
661) -> Result<MappingResult<WeightedKsgTapeEntry>, ReductionError> {
662 map_weighted_with_method(num_vertices, edges, PathDecompositionMethod::Auto)
663}
664
665pub fn map_weighted_with_method(
672 num_vertices: usize,
673 edges: &[(usize, usize)],
674 method: PathDecompositionMethod,
675) -> Result<MappingResult<WeightedKsgTapeEntry>, ReductionError> {
676 let layout = pathwidth(num_vertices, edges, method);
677 let vertex_order = vertex_order_from_layout(&layout);
678 map_weighted_with_order(num_vertices, edges, &vertex_order)
679}
680
681pub fn map_weighted_with_order(
687 num_vertices: usize,
688 edges: &[(usize, usize)],
689 vertex_order: &[usize],
690) -> Result<MappingResult<WeightedKsgTapeEntry>, ReductionError> {
691 let (mut grid, copylines) = embed_graph_internal(num_vertices, edges, vertex_order)?;
692
693 let doubled_cells = grid.doubled_cells();
695
696 let crossing_tape = apply_weighted_crossing_gadgets(&mut grid, ©lines);
698
699 let simplifier_tape = apply_weighted_simplifier_gadgets(&mut grid, 2);
701
702 let mut tape = crossing_tape;
704 tape.extend(simplifier_tape);
705
706 let copyline_overhead = copylines.iter().try_fold(0_i64, |total, line| {
708 let overhead = mis_overhead_copyline(line, SPACING, PADDING)?;
709 let overhead = overhead.checked_mul(2).ok_or(mapping_integer_overflow(
710 "doubling weighted copy-line MIS overhead",
711 ))?;
712 total.checked_add(overhead).ok_or(mapping_integer_overflow(
713 "summing weighted copy-line MIS overhead",
714 ))
715 })?;
716
717 let gadget_overhead = tape.iter().try_fold(0_i64, |total, entry| {
719 total
720 .checked_add(weighted_tape_entry_mis_overhead(entry)?)
721 .ok_or(mapping_integer_overflow(
722 "summing weighted gadget MIS overhead",
723 ))
724 })?;
725 let mis_overhead =
726 copyline_overhead
727 .checked_add(gadget_overhead)
728 .ok_or(mapping_integer_overflow(
729 "computing total weighted MIS overhead",
730 ))?;
731
732 if grid.has_unresolved_cells() {
733 return Err(mapping_invalid(
734 "weighted mapping left doubled or connected cells unresolved",
735 ));
736 }
737
738 let positions_and_weights = grid
740 .occupied_coords()
741 .into_iter()
742 .filter_map(|(row, col)| {
743 grid.get(row, col)
744 .filter(|cell| cell.weight() > 0)
745 .map(|cell| {
746 Ok((
747 (
748 i64::try_from(row).map_err(|_| {
749 mapping_integer_overflow("converting a grid row to i64")
750 })?,
751 i64::try_from(col).map_err(|_| {
752 mapping_integer_overflow("converting a grid column to i64")
753 })?,
754 ),
755 cell.weight(),
756 ))
757 })
758 })
759 .collect::<Result<Vec<_>, ReductionError>>()?;
760 let (positions, node_weights): (Vec<_>, Vec<_>) = positions_and_weights.into_iter().unzip();
761
762 Ok(MappingResult {
763 positions,
764 node_weights,
765 grid_dimensions: grid.size(),
766 kind: GridKind::Kings,
767 lines: copylines,
768 padding: PADDING,
769 spacing: SPACING,
770 mis_overhead,
771 tape,
772 doubled_cells,
773 })
774}
775
776#[cfg(test)]
777#[path = "../../../unit_tests/rules/unitdiskmapping/ksg/mapping.rs"]
778mod tests;