From 2cec01de89656aa721e130af7a43540433c76df5 Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Sun, 2 Aug 2026 09:22:54 +0800 Subject: [PATCH 1/4] Add plan for #1097: HamiltonianPath to HamiltonianCircuit --- ...2-hamiltonianpath-to-hamiltoniancircuit.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md diff --git a/docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md b/docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md new file mode 100644 index 000000000..d06304592 --- /dev/null +++ b/docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md @@ -0,0 +1,54 @@ +# HamiltonianPath to HamiltonianCircuit + +Implement issue #1097 as a witness-preserving reduction from +`HamiltonianPath` to `HamiltonianCircuit`, following +the `add-rule` workflow. + +## Batch 1: verify and implement the rule + +1. Run the full `verify-reduction` workflow before writing Rust code. Use a + standalone Typst proof and two independent Python implementations to verify + at least 5,000 cases each, including exhaustive undirected simple graphs + through five vertices, solution extraction from every feasible target + witness, the stated overhead bounds, and positive/negative examples. Treat + the verified construction and extraction as the implementation + specification. +2. Confirm the source and target both use `Or`, then add + `src/rules/hamiltonianpath_hamiltoniancircuit.rs` and register it in + `src/rules/mod.rs`. +3. For source graphs with at least two vertices, copy every source edge, add a + new vertex `x = n`, and add `{x, v}` for every old vertex. For empty and + singleton sources, produce a fixed triangle so the target remains feasible. + Store enough source-size state to extract a witness: return `[]` or `[0]` + for the two small cases; otherwise rotate the target circuit so `x` is + first, remove `x`, and preserve the remaining order. +4. Register the safe symbolic bounds `num_vertices = "num_vertices + 3"` and + `num_edges = "num_edges + num_vertices + 3"`. Keep the primitive edge + specific to the `SimpleGraph` variants. +5. Add focused tests at + `src/unit_tests/rules/hamiltonianpath_hamiltoniancircuit.rs`: a positive + closed loop, an infeasible graph, target structure and exact large-branch + sizes, both target cycle orientations/rotations, empty and singleton source + semantics, two isolated vertices, one edge, a disconnected graph, a star, + and inputs containing self-loops and parallel edges. Avoid snapshot fixtures + and redundant helper tests. +6. Add the issue's five-vertex worked example to the canonical rule example + database using the repository's current per-rule example-spec pattern, and + verify the target circuit and extracted source path end to end. + +## Batch 2: paper and generated integration + +1. With a fresh context, add a mandatory `reduction-rule` entry for + `HamiltonianPath` to `HamiltonianCircuit` in + `docs/paper/reductions.typ`. Cite Waggoner's course notes for the + universal-vertex mapping and retain Garey--Johnson only for the classical + problem definitions. Give a self-contained construction, both correctness + directions, witness extraction, small-instance branch, overhead, and a + tutorial-style worked example driven from canonical fixture data. +2. Add the Waggoner BibTeX record to `docs/paper/references.bib` if it is not + already present. Regenerate the reduction graph, schemas, and example + fixtures required by the paper, then run `make paper`. +3. Run formatting, tests, clippy, and coverage in accordance with repository + requirements. Inspect generated changes, keep only files required by this + rule, and ensure the final worktree is clean. + From f5cd69cceb6cc8996fd14b7e8f3ba204460960f3 Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Sun, 2 Aug 2026 09:34:55 +0800 Subject: [PATCH 2/4] Implement HamiltonianPath to HamiltonianCircuit reduction --- .../hamiltonianpath_hamiltoniancircuit.rs | 100 +++++++++++++ src/rules/mod.rs | 2 + .../hamiltonianpath_hamiltoniancircuit.rs | 134 ++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 src/rules/hamiltonianpath_hamiltoniancircuit.rs create mode 100644 src/unit_tests/rules/hamiltonianpath_hamiltoniancircuit.rs diff --git a/src/rules/hamiltonianpath_hamiltoniancircuit.rs b/src/rules/hamiltonianpath_hamiltoniancircuit.rs new file mode 100644 index 000000000..339b2e663 --- /dev/null +++ b/src/rules/hamiltonianpath_hamiltoniancircuit.rs @@ -0,0 +1,100 @@ +//! Reduction from HamiltonianPath to HamiltonianCircuit. +//! +//! For a graph with at least two vertices, the construction copies the source +//! graph and adds one universal vertex. Deleting that vertex from any target +//! Hamiltonian circuit leaves a Hamiltonian path in the source graph. Empty and +//! singleton sources reduce to a fixed triangle because both are feasible in +//! the HamiltonianPath model. + +use crate::models::graph::{HamiltonianCircuit, HamiltonianPath}; +use crate::reduction; +use crate::rules::traits::{ReduceTo, ReductionResult}; +use crate::topology::{Graph, SimpleGraph}; + +/// Result of reducing HamiltonianPath to HamiltonianCircuit. +#[derive(Debug, Clone)] +pub struct ReductionHamiltonianPathToHamiltonianCircuit { + target: HamiltonianCircuit, + num_original_vertices: usize, +} + +impl ReductionResult for ReductionHamiltonianPathToHamiltonianCircuit { + type Source = HamiltonianPath; + type Target = HamiltonianCircuit; + + fn target_problem(&self) -> &Self::Target { + &self.target + } + + fn extract_solution(&self, target_solution: &[usize]) -> Vec { + let n = self.num_original_vertices; + if n < 2 { + return (0..n).collect(); + } + + let universal = n; + let universal_position = target_solution + .iter() + .position(|&vertex| vertex == universal) + .expect("target Hamiltonian circuit must contain the universal vertex"); + + target_solution[universal_position + 1..] + .iter() + .chain(&target_solution[..universal_position]) + .copied() + .collect() + } +} + +#[reduction( + overhead = { + num_vertices = "num_vertices + 3", + num_edges = "num_edges + num_vertices + 3", + } +)] +impl ReduceTo> for HamiltonianPath { + type Result = ReductionHamiltonianPathToHamiltonianCircuit; + + fn reduce_to(&self) -> Self::Result { + let n = self.num_vertices(); + let target_graph = if n < 2 { + SimpleGraph::cycle(3) + } else { + let universal = n; + let mut edges = self.graph().edges(); + edges.extend((0..n).map(|vertex| (universal, vertex))); + SimpleGraph::new(n + 1, edges) + }; + + ReductionHamiltonianPathToHamiltonianCircuit { + target: HamiltonianCircuit::new(target_graph), + num_original_vertices: n, + } + } +} + +#[cfg(feature = "example-db")] +pub(crate) fn canonical_rule_example_specs() -> Vec { + use crate::export::SolutionPair; + + vec![crate::example_db::specs::RuleExampleSpec { + id: "hamiltonianpath_to_hamiltoniancircuit", + build: || { + let source = HamiltonianPath::new(SimpleGraph::new( + 5, + vec![(0, 1), (1, 2), (2, 3), (3, 4), (0, 2), (1, 3)], + )); + crate::example_db::specs::rule_example_with_witness::<_, HamiltonianCircuit>( + source, + SolutionPair { + source_config: vec![0, 1, 2, 3, 4], + target_config: vec![5, 0, 1, 2, 3, 4], + }, + ) + }, + }] +} + +#[cfg(test)] +#[path = "../unit_tests/rules/hamiltonianpath_hamiltoniancircuit.rs"] +mod tests; diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 95eb5f477..edf7b83cd 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -41,6 +41,7 @@ pub(crate) mod hamiltoniancircuit_stackercrane; pub(crate) mod hamiltoniancircuit_strongconnectivityaugmentation; pub(crate) mod hamiltoniancircuit_travelingsalesman; pub(crate) mod hamiltonianpath_degreeconstrainedspanningtree; +pub(crate) mod hamiltonianpath_hamiltoniancircuit; pub(crate) mod hamiltonianpath_isomorphicspanningtree; pub(crate) mod hamiltonianpathbetweentwovertices_longestpath; pub(crate) mod ilp_i32_ilp_bool; @@ -452,6 +453,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec HamiltonianPath { + HamiltonianPath::new(SimpleGraph::new( + 5, + vec![(0, 1), (1, 2), (2, 3), (3, 4), (0, 2), (1, 3)], + )) +} + +#[test] +fn test_hamiltonianpath_to_hamiltoniancircuit_closed_loop() { + let source = canonical_path_example(); + let reduction = ReduceTo::>::reduce_to(&source); + + assert_satisfaction_round_trip_from_satisfaction_target( + &source, + &reduction, + "HamiltonianPath -> HamiltonianCircuit", + ); + + let target = reduction.target_problem(); + assert_eq!(target.num_vertices(), 6); + assert_eq!(target.num_edges(), 11); + assert_eq!(target.graph().neighbors(5).len(), 5); + for vertex in 0..5 { + assert!(target.graph().has_edge(5, vertex)); + } + for edge in source.graph().edges() { + assert!(target.graph().has_edge(edge.0, edge.1)); + } +} + +#[test] +fn test_extracts_rotated_and_reversed_circuits() { + let source = canonical_path_example(); + let reduction = ReduceTo::>::reduce_to(&source); + + let rotated = vec![2, 3, 4, 5, 0, 1]; + assert!(reduction.target_problem().evaluate(&rotated).0); + assert_eq!(reduction.extract_solution(&rotated), vec![0, 1, 2, 3, 4]); + + let reversed_and_rotated = vec![2, 1, 0, 5, 4, 3]; + assert!(reduction.target_problem().evaluate(&reversed_and_rotated).0); + assert_eq!( + reduction.extract_solution(&reversed_and_rotated), + vec![4, 3, 2, 1, 0] + ); +} + +#[test] +fn test_empty_and_singleton_sources_use_feasible_triangle() { + let empty = HamiltonianPath::new(SimpleGraph::empty(0)); + let empty_reduction = ReduceTo::>::reduce_to(&empty); + assert_eq!( + empty_reduction.target_problem().graph(), + &SimpleGraph::cycle(3) + ); + assert_eq!( + empty_reduction.extract_solution(&[0, 1, 2]), + Vec::::new() + ); + + let singleton = HamiltonianPath::new(SimpleGraph::empty(1)); + let singleton_reduction = ReduceTo::>::reduce_to(&singleton); + assert_eq!( + singleton_reduction.target_problem().graph(), + &SimpleGraph::cycle(3) + ); + assert_eq!(singleton_reduction.extract_solution(&[2, 1, 0]), vec![0]); +} + +#[test] +fn test_two_vertex_boundary_cases() { + let isolated = HamiltonianPath::new(SimpleGraph::empty(2)); + let isolated_reduction = ReduceTo::>::reduce_to(&isolated); + assert_eq!(isolated_reduction.target_problem().num_vertices(), 3); + assert_eq!(isolated_reduction.target_problem().num_edges(), 2); + assert!(BruteForce::new() + .find_witness(isolated_reduction.target_problem()) + .is_none()); + + let one_edge = HamiltonianPath::new(SimpleGraph::new(2, vec![(0, 1)])); + let one_edge_reduction = ReduceTo::>::reduce_to(&one_edge); + let target_solution = BruteForce::new() + .find_witness(one_edge_reduction.target_problem()) + .expect("one source edge must extend to a target triangle"); + let extracted = one_edge_reduction.extract_solution(&target_solution); + assert!(one_edge.evaluate(&extracted).0); +} + +#[test] +fn test_star_and_disconnected_sources_remain_infeasible() { + let star = HamiltonianPath::new(SimpleGraph::star(5)); + let star_reduction = ReduceTo::>::reduce_to(&star); + assert!(BruteForce::new() + .find_witness(star_reduction.target_problem()) + .is_none()); + + let disconnected = HamiltonianPath::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (3, 4)])); + let disconnected_reduction = + ReduceTo::>::reduce_to(&disconnected); + assert!(BruteForce::new() + .find_witness(disconnected_reduction.target_problem()) + .is_none()); +} + +#[test] +fn test_self_loops_and_parallel_edges_are_copied() { + let source = HamiltonianPath::new(SimpleGraph::new(3, vec![(0, 0), (0, 1), (0, 1), (1, 2)])); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + assert_eq!(target.num_vertices(), 4); + assert_eq!(target.num_edges(), 7); + assert_eq!( + target + .graph() + .edges() + .into_iter() + .filter(|&(u, v)| (u == 0 && v == 1) || (u == 1 && v == 0)) + .count(), + 2 + ); + assert!(target.graph().has_edge(0, 0)); + + let target_solution = vec![3, 0, 1, 2]; + assert!(target.evaluate(&target_solution).0); + assert_eq!(reduction.extract_solution(&target_solution), vec![0, 1, 2]); +} From 7bdbd821851c599702ff3b9b09c7b4d19bbbfe73 Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Sun, 2 Aug 2026 09:51:19 +0800 Subject: [PATCH 3/4] Document HamiltonianPath to HamiltonianCircuit reduction --- docs/paper/reductions.typ | 43 ++++++++++++++++++++++++++++++++ docs/paper/references.bib | 9 ++++++- src/unit_tests/rules/analysis.rs | 5 ++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 88cd07556..194d583f8 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -17082,6 +17082,49 @@ The following table shows concrete variable overhead for example instances, take _Solution extraction._ For each source variable $x_i$, compute $x_i = sum_(j=0)^(K_i - 1) w_(i j) y_(i j)$ from the binary solution. ] +#let hp_hc = load-example("HamiltonianPath", "HamiltonianCircuit") +#let hp_hc_sol = hp_hc.solutions.at(0) +#let hp_hc_n = graph-num-vertices(hp_hc.source.instance) +#let hp_hc_m = graph-num-edges(hp_hc.source.instance) +#let hp_hc_source_edges = hp_hc.source.instance.graph.edges +#let hp_hc_target_n = graph-num-vertices(hp_hc.target.instance) +#let hp_hc_target_edges = hp_hc.target.instance.graph.edges +#let hp_hc_x = hp_hc_n +#reduction-rule("HamiltonianPath", "HamiltonianCircuit", + example: true, + example-caption: [Add universal vertex $x = #hp_hc_x$ to a #{hp_hc_n}-vertex Hamiltonian-path instance], + extra: [ + #pred-commands( + "pred create --example " + problem-spec(hp_hc.source) + " -o hp.json", + "pred reduce hp.json --to " + target-spec(hp_hc) + " -o bundle.json", + "pred solve bundle.json", + "pred evaluate hp.json --config " + hp_hc_sol.source_config.map(str).join(","), + ) + + *Step 1 -- Inspect the source.* The canonical fixture has $n = #hp_hc_n$ vertices and $m = #hp_hc_m$ edges: #hp_hc_source_edges.map(e => $(#e.at(0), #e.at(1))$).join(", "). Its stored Hamiltonian path is $[#hp_hc_sol.source_config.map(str).join(", ")]$. + + *Step 2 -- Add the universal vertex.* Set $x = n = #hp_hc_x$, copy the #hp_hc_m source edges, and add one edge from $x$ to each old vertex. The target therefore has $#hp_hc_target_n = #hp_hc_n + 1$ vertices and $#hp_hc_target_edges.len() = #hp_hc_m + #hp_hc_n$ edges: #hp_hc_target_edges.map(e => $(#e.at(0), #e.at(1))$).join(", "). + + *Step 3 -- Close and verify the circuit.* Prefixing the source witness by $x$ gives the stored target witness $[#hp_hc_sol.target_config.map(str).join(", ")]$. The two circuit edges incident to $x$ are present because $x$ is universal; every edge between consecutive old vertices belongs to the source path. + + *Step 4 -- Extract the path.* Rotate the target circuit to place $x$ first, then delete it. For the stored witness this returns $[#hp_hc_sol.target_config.slice(1).map(str).join(", ")]$, exactly the source Hamiltonian path. + + *Multiplicity:* The fixture stores one canonical witness. On this $n >= 2$ branch, each ordered source-path witness gives $n + 1 = #hp_hc_target_n$ target configurations, namely the cyclic rotations of $[x]$ followed by that path; rotating a target witness to $x$ and deleting $x$ reverses this correspondence. + ], +)[ + The standard universal-vertex mapping @waggoner2025npcomplete is an $O(n + m)$ reduction. Given an undirected graph $G = (V, E)$ with $n = |V|$ and $m = |E|$, it adds one vertex adjacent to every old vertex when $n >= 2$; the implementation uses a fixed triangle when $n < 2$ so that the target model, which requires at least three vertices for a circuit, preserves the feasible empty and singleton path instances. +][ + _Construction._ If $n >= 2$, introduce a fresh vertex $x = n$ and form $G' = (V', E')$ with + $V' = V union {x}$ and $E' = E union {{x, v} : v in V}$. + Hence the normal branch has exactly $|V'| = n + 1$ and $|E'| = m + n$. If $n < 2$, let $G'$ be the fixed triangle $K_3$. Across both branches, the registered safe bounds are $|V'| <= n + 3$ and $|E'| <= m + n + 3$. + + _Correctness._ First suppose $n >= 2$. ($arrow.r.double$) If $(v_0, v_1, dots, v_(n-1))$ is a Hamiltonian path in $G$, then $(x, v_0, v_1, dots, v_(n-1))$ is a Hamiltonian circuit in $G'$: all internal edges come from the path, and the two closing edges incident to $x$ exist by construction. ($arrow.l.double$) If $G'$ has a Hamiltonian circuit, rotate its cyclic order until $x$ is first. Deleting $x$ leaves an ordering of every old vertex exactly once. Every consecutive pair in that ordering is joined by an edge of $E'$ not incident to $x$, hence by an original edge of $E$, so the ordering is a Hamiltonian path in $G$. If $n = 0$ or $n = 1$, the source configuration $()$ or $(0)$ is a valid Hamiltonian path under the model's permutation semantics, while the fixed triangle has a Hamiltonian circuit; thus equivalence also holds on the small-instance branch. + + _Solution extraction._ For $n >= 2$, locate $x$ in the target permutation, cyclically rotate the circuit so that $x$ comes first, and delete $x$; preserve the remaining order as the source path. For $n = 0$ return $()$, and for $n = 1$ return $(0)$. + + _Loops and parallel edges._ The graph representation permits self-loops and repeated edges. The construction copies them verbatim, so the exact normal-branch count remains $m + n$. They do not affect the proof: a Hamiltonian witness is a permutation of distinct vertices, and feasibility only asks whether each required adjacency is present. +] + #let hc_hp = load-example("HamiltonianCircuit", "HamiltonianPath") #let hc_hp_sol = hc_hp.solutions.at(0) #let hc_hp_n = graph-num-vertices(hc_hp.source.instance) diff --git a/docs/paper/references.bib b/docs/paper/references.bib index 186f70de2..d4cef86cc 100644 --- a/docs/paper/references.bib +++ b/docs/paper/references.bib @@ -2114,6 +2114,14 @@ @misc{mit6854MinCostFlow url = {https://courses.csail.mit.edu/6.854/21/Scribe/s10-minCostFlowAlg/s10-minCostFlowAlg.html} } +@misc{waggoner2025npcomplete, + author = {Bowen Waggoner}, + title = {Standard 21: {P} and {NP} 2: {NP}-Completeness}, + year = {2025}, + howpublished = {CSCI 3104 course notes}, + url = {https://bowaggoner.com/courses/2025/csci3104/book/standards/21-pnp-complete.html} +} + @inproceedings{chandran_et_al:LIPIcs.IPEC.2016.11, author = {Chandran, Sunil and Issac, Davis and Karrenbauer, Andreas}, title = {{On the Parameterized Complexity of Biclique Cover and Partition}}, @@ -2142,4 +2150,3 @@ @article{berlekampMcElieceTilborg1978 year = {1978}, doi = {10.1109/TIT.1978.1055873} } - diff --git a/src/unit_tests/rules/analysis.rs b/src/unit_tests/rules/analysis.rs index 6088d9d38..e46454a4e 100644 --- a/src/unit_tests/rules/analysis.rs +++ b/src/unit_tests/rules/analysis.rs @@ -322,6 +322,11 @@ fn test_find_dominated_rules_returns_known_set() { "PartitionIntoPathsOfLength2 {graph: \"SimpleGraph\"}", "ILP {variable: \"bool\"}", ), + // HP → HC → RuralPostman → ILP is no worse than direct HP → ILP. + ( + "HamiltonianPath {graph: \"SimpleGraph\"}", + "ILP {variable: \"bool\"}", + ), ] .into_iter() .collect(); From 3345b5d6968204619610dc9eec4217c77a2ede0d Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Sun, 2 Aug 2026 09:51:19 +0800 Subject: [PATCH 4/4] chore: remove plan file after implementation --- ...2-hamiltonianpath-to-hamiltoniancircuit.md | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md diff --git a/docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md b/docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md deleted file mode 100644 index d06304592..000000000 --- a/docs/plans/2026-08-02-hamiltonianpath-to-hamiltoniancircuit.md +++ /dev/null @@ -1,54 +0,0 @@ -# HamiltonianPath to HamiltonianCircuit - -Implement issue #1097 as a witness-preserving reduction from -`HamiltonianPath` to `HamiltonianCircuit`, following -the `add-rule` workflow. - -## Batch 1: verify and implement the rule - -1. Run the full `verify-reduction` workflow before writing Rust code. Use a - standalone Typst proof and two independent Python implementations to verify - at least 5,000 cases each, including exhaustive undirected simple graphs - through five vertices, solution extraction from every feasible target - witness, the stated overhead bounds, and positive/negative examples. Treat - the verified construction and extraction as the implementation - specification. -2. Confirm the source and target both use `Or`, then add - `src/rules/hamiltonianpath_hamiltoniancircuit.rs` and register it in - `src/rules/mod.rs`. -3. For source graphs with at least two vertices, copy every source edge, add a - new vertex `x = n`, and add `{x, v}` for every old vertex. For empty and - singleton sources, produce a fixed triangle so the target remains feasible. - Store enough source-size state to extract a witness: return `[]` or `[0]` - for the two small cases; otherwise rotate the target circuit so `x` is - first, remove `x`, and preserve the remaining order. -4. Register the safe symbolic bounds `num_vertices = "num_vertices + 3"` and - `num_edges = "num_edges + num_vertices + 3"`. Keep the primitive edge - specific to the `SimpleGraph` variants. -5. Add focused tests at - `src/unit_tests/rules/hamiltonianpath_hamiltoniancircuit.rs`: a positive - closed loop, an infeasible graph, target structure and exact large-branch - sizes, both target cycle orientations/rotations, empty and singleton source - semantics, two isolated vertices, one edge, a disconnected graph, a star, - and inputs containing self-loops and parallel edges. Avoid snapshot fixtures - and redundant helper tests. -6. Add the issue's five-vertex worked example to the canonical rule example - database using the repository's current per-rule example-spec pattern, and - verify the target circuit and extracted source path end to end. - -## Batch 2: paper and generated integration - -1. With a fresh context, add a mandatory `reduction-rule` entry for - `HamiltonianPath` to `HamiltonianCircuit` in - `docs/paper/reductions.typ`. Cite Waggoner's course notes for the - universal-vertex mapping and retain Garey--Johnson only for the classical - problem definitions. Give a self-contained construction, both correctness - directions, witness extraction, small-instance branch, overhead, and a - tutorial-style worked example driven from canonical fixture data. -2. Add the Waggoner BibTeX record to `docs/paper/references.bib` if it is not - already present. Regenerate the reduction graph, schemas, and example - fixtures required by the paper, then run `make paper`. -3. Run formatting, tests, clippy, and coverage in accordance with repository - requirements. Inspect generated changes, keep only files required by this - rule, and ensure the final worktree is clean. -