Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
5863b08
feat(topology): Add GridGraph type for square and triangular lattices
GiggleLiu Jan 26, 2026
ccbcddc
feat(mapping): Add CopyLine structure for graph embedding
GiggleLiu Jan 26, 2026
e1c95c3
feat(mapping): Add gadget trait and basic gadgets for square lattice
GiggleLiu Jan 26, 2026
00194df
feat(mapping): Add MappingGrid for intermediate representation
GiggleLiu Jan 26, 2026
645e241
feat(mapping): Add map_graph function for graph to grid mapping
GiggleLiu Jan 26, 2026
2cdd913
refactor(mapping): Improve map_graph code quality
GiggleLiu Jan 26, 2026
d56fed9
feat(mapping): Add triangular lattice support
GiggleLiu Jan 26, 2026
e1b4f8d
test: Add integration tests for grid mapping
GiggleLiu Jan 26, 2026
d084460
docs: Add documentation and exports for grid mapping
GiggleLiu Jan 26, 2026
b963695
docs: Add implementation plan for grid graph reductions
GiggleLiu Jan 26, 2026
f26873b
test: Add standard graph and gadget tests from UnitDiskMapping.jl
GiggleLiu Jan 26, 2026
f18c68b
tests: Add MIS verification tests (ignored) from UnitDiskMapping.jl
GiggleLiu Jan 26, 2026
ae13cf9
refactor: Use ILPSolver for MIS verification tests
GiggleLiu Jan 26, 2026
ccc91a1
tests: Enable MIS verification tests with ilp feature
GiggleLiu Jan 26, 2026
c7d7014
docs: Add Unit Disk Mapping documentation from UnitDiskMapping.jl
GiggleLiu Jan 26, 2026
40d50bf
fix: Correct Unit Disk Mapping citation to Nguyen et al. PRX Quantum …
GiggleLiu Jan 26, 2026
40fba60
docs: Add triangular lattice citation and Petersen graph visualization
GiggleLiu Jan 26, 2026
818a91a
docs: Use cetz for graph visualization with unit disk edges
GiggleLiu Jan 26, 2026
5b0dc5b
fix: Use spacing-based radius for sparse grid graph visualization
GiggleLiu Jan 26, 2026
6ec2ad8
fix: Generate dense King's subgraph nodes from copy line data
GiggleLiu Jan 26, 2026
d0b3263
feat: Add dense_locations for proper King's subgraph visualization
GiggleLiu Jan 26, 2026
84b7d3d
feat: Complete map_config_back with copyline-based solution extraction
GiggleLiu Jan 27, 2026
98570ed
docs: Fix node count in Petersen graph King's subgraph example
GiggleLiu Jan 27, 2026
36ab181
feat: add WeightedGadget struct and Weightable trait
GiggleLiu Jan 28, 2026
1f342ce
feat: implement Weightable for TriTurn, TriBranch, and TriCross
GiggleLiu Jan 28, 2026
ac09acf
feat: add all triangular gadget definitions
GiggleLiu Jan 28, 2026
afa0b06
feat: implement Weightable for all triangular gadgets
GiggleLiu Jan 28, 2026
0b7273f
feat: add triangular_weighted_ruleset function
GiggleLiu Jan 28, 2026
ab62e43
feat: add trace_centers function for center location tracking
GiggleLiu Jan 28, 2026
a553257
feat: add map_weights function for weight mapping
GiggleLiu Jan 28, 2026
b8d36a2
feat: add weighted gadget MIS verification tests and interface tests
GiggleLiu Jan 28, 2026
7fdfceb
docs: Add weighted tests coverage implementation plan
GiggleLiu Jan 28, 2026
767456c
feat: add triangular copy line weighted MIS overhead test
GiggleLiu Jan 28, 2026
011ed68
test: add triangular map configurations back verification
GiggleLiu Jan 28, 2026
e934f08
test: add enhanced triangular weighted interface test
GiggleLiu Jan 28, 2026
81a198b
test: add configuration count preservation test
GiggleLiu Jan 28, 2026
6630527
docs: Add triangular crossing gadgets implementation plan
GiggleLiu Jan 28, 2026
3f0df71
feat: add source_matrix and mapped_matrix to TriangularGadget
GiggleLiu Jan 28, 2026
f77ec22
feat: add pattern_matches_triangular function
GiggleLiu Jan 28, 2026
32a5bd4
feat: add apply_triangular_gadget function
GiggleLiu Jan 28, 2026
a8e5e7c
feat: add TriangularTapeEntry and crossat_triangular
GiggleLiu Jan 28, 2026
8d51683
feat: add apply_triangular_crossing_gadgets function
GiggleLiu Jan 28, 2026
6d81cc3
feat: integrate apply_triangular_crossing_gadgets into mapping
GiggleLiu Jan 28, 2026
434cb32
feat: Implement triangular crossing gadget application
GiggleLiu Jan 28, 2026
7827c26
feat: Triangular mapping infrastructure improvements
GiggleLiu Jan 28, 2026
7f31abc
feat: Implement apply_triangular_crossing_gadgets
GiggleLiu Jan 28, 2026
b9b03ea
fix: Correct triangular copyline rightward segment length
GiggleLiu Jan 29, 2026
b33bd33
fix: Correct MIS overhead formula and alpha tensor verification
GiggleLiu Jan 29, 2026
356ea7d
fix: Address PR review comments from Copilot
GiggleLiu Jan 29, 2026
69f90a7
test: Add tests for existing features and refactor Weightable
GiggleLiu Jan 29, 2026
712805c
refactor: Use smallgraph from topology in standard_graphs tests
GiggleLiu Jan 29, 2026
d20cad5
test: Split mapping tests into modular structure
GiggleLiu Jan 29, 2026
75ded51
refactor: Remove legacy grid_mapping_tests.rs
GiggleLiu Jan 29, 2026
895c310
fix: Rewrite triangular gadgets to match Julia UnitDiskMapping
GiggleLiu Jan 29, 2026
e31010a
fix: Resolve Clippy warnings and add more test coverage
GiggleLiu Jan 29, 2026
d570e64
fix: Address remaining Clippy lints for CI
GiggleLiu Jan 29, 2026
554a39d
fix: Use saturating_sub in cross_at for release safety
GiggleLiu Jan 29, 2026
194775d
test: Add tests for grid and copyline to improve coverage
GiggleLiu Jan 29, 2026
6c229ea
docs: Update Petersen mapping visualizations with both lattice types
GiggleLiu Jan 29, 2026
7cfff4d
fix: Use pre-computed edges from JSON instead of recomputing
GiggleLiu Jan 29, 2026
58f9abe
fix: Correct triangular lattice basis to match Rust implementation
GiggleLiu Jan 29, 2026
8fa63cf
feat: add mapped_boundary_config function for config extraction
GiggleLiu Jan 29, 2026
8c409e2
feat: add map_config_back_pattern for single gadget config unapply
GiggleLiu Jan 29, 2026
6ee6248
feat: add map_config_copyback for extracting vertex configs from copy…
GiggleLiu Jan 29, 2026
6dfa6a7
feat: add SquarePattern enum for dynamic dispatch during config unapply
GiggleLiu Jan 29, 2026
8dfd5a2
feat: implement proper map_config_back following Julia's unapply algo…
GiggleLiu Jan 29, 2026
8b8862a
refactor: Rename mapping to unitdiskmapping and split gadgets.rs
GiggleLiu Jan 30, 2026
fb2e409
fix: Correct DanglingLeg pattern and rename copyline functions
GiggleLiu Jan 30, 2026
2a1b171
fix: Update export_petersen_mapping to use renamed function
GiggleLiu Jan 30, 2026
e9d8522
feat: Generate weighted and unweighted Petersen mappings
GiggleLiu Jan 30, 2026
15d1658
test: Add Julia comparison tests for square lattice mapping
GiggleLiu Jan 30, 2026
064e602
fix: Use 0-indexed coordinates for triangular mapping
GiggleLiu Jan 30, 2026
7d4b5c7
feat: Add triangular lattice visualization support
GiggleLiu Jan 30, 2026
afb0e98
fix: Use Julia's exact logic for crossing cell marking (strict matching)
GiggleLiu Jan 30, 2026
65063bb
fix: Correct connect() to convert Occupied cells to Connected
GiggleLiu Jan 30, 2026
a6e7588
fix: Correct square gadget names in export script
GiggleLiu Jan 30, 2026
2291244
feat: Add weight checking to triangular gadget matching and Makefile …
GiggleLiu Jan 30, 2026
13472d0
feat: Fix weighted mode square lattice mapping to match Julia exactly
GiggleLiu Jan 30, 2026
1124440
fix: Triangular weighted MIS tests now match Julia exactly
GiggleLiu Jan 30, 2026
ddb8f56
chore: Clean up debug files and update test data
GiggleLiu Jan 30, 2026
213a407
test: Add MIS overhead tests for cubical and tutte graphs
GiggleLiu Jan 30, 2026
dbaf491
test: Add full map_config_back verification for standard graphs (igno…
GiggleLiu Jan 30, 2026
c2088c6
test: Add weighted copyline MIS overhead verification
GiggleLiu Jan 30, 2026
3520bab
test: Add weighted mode map_config_back verification for standard graphs
GiggleLiu Jan 30, 2026
51ea247
test: Add triangular mode map_config_back verification
GiggleLiu Jan 30, 2026
932be66
docs: Add test plan for Julia test parity
GiggleLiu Jan 30, 2026
6caba26
update
GiggleLiu Jan 30, 2026
9b09bee
fix: Use native grid weights for weighted MIS tests
GiggleLiu Jan 30, 2026
3633034
fix: Extract doubled_cells before gadgets for correct map_config_back
GiggleLiu Jan 31, 2026
834f679
fix: Use source_weights=0.2 in triangular map_config_back tests
GiggleLiu Jan 31, 2026
fccfbae
chore: Run tests with --include-ignored in Makefile
GiggleLiu Jan 31, 2026
0727d00
save work
GiggleLiu Jan 31, 2026
6490f9c
chore: Clean up PR 13 - remove redundant files
GiggleLiu Jan 31, 2026
34e31d7
docs: Add KSG and Triangular lattice refactoring design
GiggleLiu Jan 31, 2026
d06c989
docs: Add KSG/Triangular refactoring implementation plan
GiggleLiu Jan 31, 2026
8021d18
feat: create ksg/ and triangular/ directory structure
GiggleLiu Jan 31, 2026
08c8d3f
feat: extract Pattern trait to shared traits module
GiggleLiu Jan 31, 2026
5cd392d
feat: create KSG unweighted gadgets with Ksg prefix
GiggleLiu Jan 31, 2026
8c32843
feat: create KSG weighted gadgets with WeightedKsg prefix
GiggleLiu Jan 31, 2026
bcfd40e
feat: create KSG mapping functions
GiggleLiu Jan 31, 2026
e2bc040
feat: update KSG module exports
GiggleLiu Jan 31, 2026
bf3ecc9
feat: create triangular weighted gadgets with WeightedTri prefix
GiggleLiu Jan 31, 2026
d0020dd
feat: create triangular mapping functions
GiggleLiu Jan 31, 2026
0499efd
feat: update triangular module exports
GiggleLiu Jan 31, 2026
2d35690
feat: update main mod.rs to export ksg and triangular modules
GiggleLiu Jan 31, 2026
1cde3dd
refactor: update KSG/triangular module structure and fix test imports
GiggleLiu Jan 31, 2026
a03e302
fix: use triangular trace_centers for triangular mapping tests
GiggleLiu Jan 31, 2026
b7cc58c
refactor: remove old backward-compat files and fix paper figures
GiggleLiu Jan 31, 2026
2bf6e22
fix: resolve all clippy warnings
GiggleLiu Jan 31, 2026
e3c961f
refactor: compact test data JSON and rename julia/ to data/
GiggleLiu Jan 31, 2026
f47a637
test: add KSG weighted gadget tests and fix CI timeout
GiggleLiu Jan 31, 2026
10fc3c6
improve test coverage
GiggleLiu Feb 1, 2026
4247454
docs: fix and improve doctest examples
GiggleLiu Feb 1, 2026
5f6a666
fix: resolve all clippy warnings
GiggleLiu Feb 1, 2026
5f93ca1
fix: use is_multiple_of for clippy lint on Rust 1.93+
GiggleLiu Feb 2, 2026
b8d6689
fix: revert is_multiple_of for i32 types
GiggleLiu Feb 2, 2026
f5e9740
fix: revert is_multiple_of in benchmarks due to type inference
GiggleLiu Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ keywords = ["np-hard", "optimization", "reduction", "sat", "graph"]
categories = ["algorithms", "science"]

[features]
default = []
default = ["ilp"]
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default feature is changed from [] to ["ilp"], which means users who don't want the ILP feature will now get it by default. This is a breaking change that should be noted in the PR description or reconsidered.

Suggested change
default = ["ilp"]
default = []

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabling the ilp feature by default may significantly increase compile times and binary size for users who don't need ILP solving capabilities. The ilp feature pulls in the good_lp dependency with HiGHS solver. Consider keeping default = [] and documenting that users should enable the ilp feature when needed. This follows Rust best practices of keeping default feature sets minimal.

Suggested change
default = ["ilp"]
default = []

Copilot uses AI. Check for mistakes.
ilp = ["good_lp"]

[dependencies]
Expand All @@ -22,9 +22,9 @@ num-traits = "0.2"
good_lp = { version = "1.8", default-features = false, features = ["highs"], optional = true }
inventory = "0.3"
ordered-float = "5.0"
rand = "0.8"
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rand is moved from dev-dependencies to dependencies. This adds a runtime dependency for all users. Verify that rand is actually needed in the main library code (e.g., in pathdecomposition.rs for random restarts) and not just in tests.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving rand from dev-dependencies to regular dependencies adds it as a runtime dependency. Since rand is only used in pathdecomposition.rs for random choices in the greedy algorithm, consider either: (1) making it optional behind a feature flag, or (2) using a simpler deterministic selection strategy as the default. Adding rand increases the dependency footprint for all users even if they never use the greedy path decomposition method.

Copilot uses AI. Check for mistakes.

[dev-dependencies]
rand = "0.8"
proptest = "1.0"
criterion = "0.5"

Expand Down
59 changes: 45 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
# Makefile for problemreductions

.PHONY: help build test fmt clippy doc mdbook paper clean coverage
.PHONY: help build test fmt clippy doc mdbook paper clean coverage rust-export compare

# Default target
help:
@echo "Available targets:"
@echo " build - Build the project"
@echo " test - Run all tests"
@echo " fmt - Format code with rustfmt"
@echo " fmt-check - Check code formatting"
@echo " clippy - Run clippy lints"
@echo " doc - Build mdBook documentation"
@echo " mdbook - Build and serve mdBook (with live reload)"
@echo " paper - Build Typst paper (requires typst)"
@echo " coverage - Generate coverage report (requires cargo-llvm-cov)"
@echo " clean - Clean build artifacts"
@echo " check - Quick check (fmt + clippy + test)"
@echo " build - Build the project"
@echo " test - Run all tests"
@echo " fmt - Format code with rustfmt"
@echo " fmt-check - Check code formatting"
@echo " clippy - Run clippy lints"
@echo " doc - Build mdBook documentation"
@echo " mdbook - Build and serve mdBook (with live reload)"
@echo " paper - Build Typst paper (requires typst)"
@echo " coverage - Generate coverage report (requires cargo-llvm-cov)"
@echo " clean - Clean build artifacts"
@echo " check - Quick check (fmt + clippy + test)"
@echo " rust-export - Generate Rust mapping JSON exports"
@echo " compare - Generate and compare Rust mapping exports"

# Build the project
build:
cargo build --all-features

# Run all tests
# Run all tests (including ignored tests)
test:
cargo test --all-features
cargo test --all-features -- --include-ignored

# Format code
fmt:
Expand Down Expand Up @@ -62,3 +64,32 @@ clean:
# Quick check before commit
check: fmt-check clippy test
@echo "✅ All checks passed!"

# Generate Rust mapping JSON exports for all graphs and modes
GRAPHS := diamond bull house petersen
MODES := unweighted weighted triangular
rust-export:
@for graph in $(GRAPHS); do \
for mode in $(MODES); do \
echo "Exporting $$graph ($$mode)..."; \
cargo run --example export_mapping_stages -- $$graph $$mode; \
done; \
done

# Generate Rust exports and show comparison
compare: rust-export
@echo ""
@echo "=== Julia vs Rust Comparison ==="
@for graph in $(GRAPHS); do \
echo ""; \
echo "=== $$graph ==="; \
echo "-- unweighted --"; \
echo "Julia: $$(jq '{nodes: .num_grid_nodes, overhead: .mis_overhead, tape: .num_tape_entries}' tests/julia/$${graph}_unweighted_trace.json)"; \
echo "Rust: $$(jq '{nodes: .stages[3].num_nodes, overhead: .total_overhead, tape: ((.crossing_tape | length) + (.simplifier_tape | length))}' tests/julia/$${graph}_rust_unweighted.json)"; \
echo "-- weighted --"; \
echo "Julia: $$(jq '{nodes: .num_grid_nodes, overhead: .mis_overhead, tape: .num_tape_entries}' tests/julia/$${graph}_weighted_trace.json)"; \
echo "Rust: $$(jq '{nodes: .stages[3].num_nodes, overhead: .total_overhead, tape: ((.crossing_tape | length) + (.simplifier_tape | length))}' tests/julia/$${graph}_rust_weighted.json)"; \
echo "-- triangular --"; \
echo "Julia: $$(jq '{nodes: .num_grid_nodes, overhead: .mis_overhead, tape: .num_tape_entries}' tests/julia/$${graph}_triangular_trace.json)"; \
echo "Rust: $$(jq '{nodes: .stages[3].num_nodes, overhead: .total_overhead, tape: ((.crossing_tape | length) + (.simplifier_tape | length))}' tests/julia/$${graph}_rust_triangular.json)"; \
done
1 change: 1 addition & 0 deletions benches/solver_benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ fn bench_satisfiability(c: &mut Criterion) {
}

/// Benchmark SpinGlass on varying sizes.
#[allow(clippy::manual_is_multiple_of)] // Type inference issues with is_multiple_of
fn bench_spin_glass(c: &mut Criterion) {
let mut group = c.benchmark_group("SpinGlass");

Expand Down
249 changes: 249 additions & 0 deletions docs/paper/compare_mapping.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
#import "@preview/cetz:0.3.2"

// Load JSON data for different modes
// Paths relative to this document (docs/paper/)
#let load_julia_unweighted(name) = json("../../tests/julia/" + name + "_unweighted_trace.json")
#let load_julia_weighted(name) = json("../../tests/julia/" + name + "_weighted_trace.json")
#let load_julia_triangular(name) = json("../../tests/julia/" + name + "_triangular_trace.json")
#let load_rust_square(name) = json("../../tests/julia/" + name + "_rust_unweighted.json")
#let load_rust_triangular(name) = json("../../tests/julia/" + name + "_rust_triangular.json")

// Color scheme
#let julia_color = rgb("#2196F3") // Blue
#let rust_color = rgb("#FF5722") // Orange
#let both_color = rgb("#4CAF50") // Green

// Create position key for comparison
#let pos_key(r, c) = str(r) + "," + str(c)

// Convert Julia 1-indexed nodes to 0-indexed (Rust is already 0-indexed)
// Preserves state field if present (O=Occupied, D=Doubled, C=Connected)
#let julia_to_0indexed(nodes) = nodes.map(n => (
row: n.row - 1,
col: n.col - 1,
weight: if "weight" in n { n.weight } else { 1 },
state: if "state" in n { n.state } else { "O" }
))

// Extract copyline nodes from Julia copy_lines (convert to 0-indexed)
#let julia_copylines_to_nodes(copy_lines) = {
let nodes = ()
for cl in copy_lines {
for loc in cl.locations {
nodes.push((
row: loc.row - 1,
col: loc.col - 1,
weight: if "weight" in loc { loc.weight } else { 1 }
))
}
}
nodes
}

// Color for connected cells
#let connected_color = rgb("#E91E63") // Pink/Magenta for Connected

// Draw grid with nodes - all positions are 0-indexed
// triangular: if true, offset odd rows by 0.5 for triangular lattice
// Shows Connected cells (state="C") with a different color (ring)
#let draw_grid(nodes, grid_size, title, node_color: black, unit: 4pt, triangular: false) = {
let rows = grid_size.at(0)
let cols = grid_size.at(1)
let occupied = nodes.map(n => pos_key(n.row, n.col))

// Helper to compute x position with optional triangular offset
let get_x(r, c) = {
if triangular and calc.rem(r, 2) == 1 { c + 1.0 } // odd rows offset by 0.5
else { c + 0.5 }
}

cetz.canvas(length: unit, {
import cetz.draw: *
content((cols / 2, rows + 2), text(size: 7pt, weight: "bold", title))

// Draw empty grid sites as small dots
for r in range(0, rows) {
for c in range(0, cols) {
let key = pos_key(r, c)
if not occupied.contains(key) {
let y = rows - r - 0.5
let x = get_x(r, c)
circle((x, y), radius: 0.08, fill: luma(200), stroke: none)
}
}
}

// Draw filled nodes - Connected cells shown with different color
for node in nodes {
let r = node.row
let c = node.col
let w = if "weight" in node { node.weight } else { 1 }
let s = if "state" in node { node.state } else { "O" }
let y = rows - r - 0.5
let x = get_x(r, c)
let radius = if w == 1 { 0.25 } else if w == 2 { 0.35 } else { 0.45 }
// Connected cells shown with ring (stroke) instead of fill
if s == "C" {
circle((x, y), radius: radius, fill: none, stroke: 1.5pt + connected_color)
} else {
circle((x, y), radius: radius, fill: node_color, stroke: none)
}
}
})
}

// Compare two node sets
#let compare_nodes(julia_nodes, rust_nodes) = {
let julia_keys = julia_nodes.map(n => pos_key(n.row, n.col))
let rust_keys = rust_nodes.map(n => pos_key(n.row, n.col))
let both = ()
let julia_only = ()
let rust_only = ()

for n in julia_nodes {
let key = pos_key(n.row, n.col)
if rust_keys.contains(key) { both.push((n.row, n.col)) }
else { julia_only.push((n.row, n.col)) }
}
for n in rust_nodes {
let key = pos_key(n.row, n.col)
if not julia_keys.contains(key) { rust_only.push((n.row, n.col)) }
}
(both: both, julia_only: julia_only, rust_only: rust_only)
}

// Draw comparison grid
#let draw_comparison(julia_nodes, rust_nodes, grid_size, title, unit: 4pt, triangular: false) = {
let rows = grid_size.at(0)
let cols = grid_size.at(1)
let cmp = compare_nodes(julia_nodes, rust_nodes)
let all_occupied = cmp.both + cmp.julia_only + cmp.rust_only
let occupied_keys = all_occupied.map(((r, c)) => pos_key(r, c))

let get_x(r, c) = {
if triangular and calc.rem(r, 2) == 1 { c + 1.0 }
else { c + 0.5 }
}

cetz.canvas(length: unit, {
import cetz.draw: *
content((cols / 2, rows + 3), text(size: 7pt, weight: "bold", title))
content((cols / 2, rows + 1.5), text(size: 5pt)[
#box(fill: both_color, width: 4pt, height: 4pt) #cmp.both.len()
#box(fill: julia_color, width: 4pt, height: 4pt) #cmp.julia_only.len()
#box(fill: rust_color, width: 4pt, height: 4pt) #cmp.rust_only.len()
])

for r in range(0, rows) {
for c in range(0, cols) {
let key = pos_key(r, c)
if not occupied_keys.contains(key) {
let y = rows - r - 0.5
let x = get_x(r, c)
circle((x, y), radius: 0.08, fill: luma(200), stroke: none)
}
}
}

for (r, c) in cmp.both {
circle((get_x(r, c), rows - r - 0.5), radius: 0.35, fill: both_color, stroke: none)
}
for (r, c) in cmp.julia_only {
circle((get_x(r, c), rows - r - 0.5), radius: 0.35, fill: julia_color, stroke: none)
}
for (r, c) in cmp.rust_only {
circle((get_x(r, c), rows - r - 0.5), radius: 0.35, fill: rust_color, stroke: none)
}
})
}

// Compare a single mode
// triangular: if true, use triangular lattice visualization (offset odd rows)
#let compare_mode(name, mode, julia, rust, triangular: false) = {
let grid_size = julia.grid_size
// Use grid_nodes_copylines_only if available (has state: O, D, C), else fall back to copy_lines
let julia_copylines = if "grid_nodes_copylines_only" in julia {
julia_to_0indexed(julia.grid_nodes_copylines_only)
} else {
julia_copylines_to_nodes(julia.copy_lines)
}
let julia_before_simp = julia_to_0indexed(julia.at("grid_nodes_before_simplifiers", default: julia.grid_nodes))
let julia_final = julia_to_0indexed(julia.grid_nodes)
// Rust stages: 0=copylines only, 1=with connections, 2=after crossing, 3=after simplifiers
let rust_stage1 = rust.stages.at(1).grid_nodes // with connections (matches Julia copylines_only)
let rust_stage2 = rust.stages.at(2).grid_nodes // after crossing gadgets
let rust_stage3 = rust.stages.at(3).grid_nodes // after simplifiers

[
= #name - #mode

== Overview
#table(
columns: 3,
stroke: 0.5pt,
[*Metric*], [*Julia*], [*Rust*],
[Vertices], [#julia.num_vertices], [#rust.num_vertices],
[Grid], [#grid_size.at(0)×#grid_size.at(1)], [#rust.stages.at(0).grid_size.at(0)×#rust.stages.at(0).grid_size.at(1)],
[Final Nodes], [#julia.num_grid_nodes], [#rust.stages.at(3).num_nodes],
[Before Simpl], [#julia.num_grid_nodes_before_simplifiers], [#rust.stages.at(2).num_nodes],
[Overhead], [#julia.mis_overhead], [#rust.total_overhead],
[Tape], [#julia.tape.len()], [#(rust.crossing_tape.len() + rust.simplifier_tape.len())],
)

== Copylines with Connections
#grid(
columns: 3,
gutter: 0.3em,
draw_grid(julia_copylines, grid_size, "Julia", node_color: julia_color, triangular: triangular),
draw_grid(rust_stage1, grid_size, "Rust", node_color: rust_color, triangular: triangular),
draw_comparison(julia_copylines, rust_stage1, grid_size, "Diff", triangular: triangular),
)

== After Crossing Gadgets
#grid(
columns: 3,
gutter: 0.3em,
draw_grid(julia_before_simp, grid_size, "Julia", node_color: julia_color, triangular: triangular),
draw_grid(rust_stage2, grid_size, "Rust", node_color: rust_color, triangular: triangular),
draw_comparison(julia_before_simp, rust_stage2, grid_size, "Diff", triangular: triangular),
)

== Final
#grid(
columns: 3,
gutter: 0.3em,
draw_grid(julia_final, grid_size, "Julia", node_color: julia_color, triangular: triangular),
draw_grid(rust_stage3, grid_size, "Rust", node_color: rust_color, triangular: triangular),
draw_comparison(julia_final, rust_stage3, grid_size, "Diff", triangular: triangular),
)

#pagebreak()
]
}

// Compare all 3 modes for a graph
#let compare_graph(name) = {
let julia_uw = load_julia_unweighted(name)
let julia_w = load_julia_weighted(name)
let julia_tri = load_julia_triangular(name)
let rust_sq = load_rust_square(name)
let rust_tri = load_rust_triangular(name)

[
#compare_mode(name, "UnWeighted (Square)", julia_uw, rust_sq)
#compare_mode(name, "Weighted (Square)", julia_w, rust_sq)
#compare_mode(name, "Triangular Weighted", julia_tri, rust_tri, triangular: true)
]
}

// Document setup
#set page(margin: 0.6cm, paper: "a4")
#set text(size: 6pt)

#align(center, text(size: 12pt, weight: "bold")[Julia vs Rust Mapping Comparison])
#v(0.3em)

#compare_graph("diamond")
#compare_graph("bull")
#compare_graph("house")
#compare_graph("petersen")
Loading