From eba46d496c4c6d8c78727b8c4bfbd1d4a2db8c92 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 20 Mar 2026 20:55:33 +0800 Subject: [PATCH 1/3] Add plan for #207: MinimumVertexCover to MinimumFeedbackVertexSet --- ...vertexcover-to-minimumfeedbackvertexset.md | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md diff --git a/docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md b/docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md new file mode 100644 index 000000000..95e6d2867 --- /dev/null +++ b/docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md @@ -0,0 +1,347 @@ +# MinimumVertexCover to MinimumFeedbackVertexSet Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add the `MinimumVertexCover -> MinimumFeedbackVertexSet` reduction, its closed-loop coverage, a canonical example fixture, and the paper entry required for the reduction catalog. + +**Architecture:** Follow the existing graph-to-graph reduction pattern used by [`src/rules/minimumvertexcover_maximumindependentset.rs`](/Users/jinguomini/rcode/problem-reductions/.worktrees/issue-207/src/rules/minimumvertexcover_maximumindependentset.rs), but target a directed graph model. The reduction keeps the same vertex set and weights, replaces each undirected edge `{u,v}` with directed arcs `(u,v)` and `(v,u)`, and uses identity solution extraction because both source and target configurations mark the selected vertices with `1`. The implementation batch should stop once the code, tests, and canonical rule example are in place; the paper/export batch runs afterward because it depends on generated fixture data. + +**Tech Stack:** Rust, cargo test, the reduction registry macro `#[reduction]`, BruteForce solver, Typst paper docs, example-db fixture generation. + +--- + +## Batch 1: Code, tests, and canonical example + +### Task 1: Write the failing reduction tests + +**Files:** +- Create: `src/unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs` +- Reference: `src/rules/test_helpers.rs` +- Reference: `src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs` + +**Step 1: Write the failing test** + +Add three tests: + +```rust +#[test] +fn test_minimumvertexcover_to_minimumfeedbackvertexset_closed_loop() { + let source = MinimumVertexCover::new( + SimpleGraph::new(7, vec![ + (0, 1), (0, 2), (0, 3), (1, 2), + (1, 3), (3, 4), (4, 5), (5, 6), + ]), + vec![1i32; 7], + ); + let reduction = ReduceTo::>::reduce_to(&source); + assert_optimization_round_trip_from_optimization_target( + &source, + &reduction, + "MVC -> MFVS closed loop", + ); +} + +#[test] +fn test_minimumvertexcover_to_minimumfeedbackvertexset_structure() { + let source = MinimumVertexCover::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + vec![10, 20, 30], + ); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + assert_eq!(target.num_vertices(), 3); + assert_eq!(target.num_arcs(), 4); + assert_eq!(target.weights(), &[10, 20, 30]); + assert_eq!( + target.graph().arcs(), + vec![(0, 1), (1, 0), (1, 2), (2, 1)], + ); +} + +#[test] +fn test_minimumvertexcover_to_minimumfeedbackvertexset_identity_extraction() { + let source = MinimumVertexCover::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1, 1]); + let reduction = ReduceTo::>::reduce_to(&source); + assert_eq!(reduction.extract_solution(&[1, 0]), vec![1, 0]); +} +``` + +**Step 2: Run test to verify it fails** + +Run: `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_closed_loop -- --exact` +Expected: FAIL because the rule module is not implemented or not registered yet. + +**Step 3: Commit** + +Do not commit yet. This task stays uncommitted until the implementation turns the tests green. + +### Task 2: Implement the reduction and register it + +**Files:** +- Create: `src/rules/minimumvertexcover_minimumfeedbackvertexset.rs` +- Modify: `src/rules/mod.rs` +- Test: `src/unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs` + +**Step 1: Write minimal implementation** + +Create the rule module with this structure: + +```rust +#[derive(Debug, Clone)] +pub struct ReductionVCToFVS { + target: MinimumFeedbackVertexSet, +} + +impl ReductionResult for ReductionVCToFVS +where + W: WeightElement + crate::variant::VariantParam, +{ + type Source = MinimumVertexCover; + type Target = MinimumFeedbackVertexSet; + + fn target_problem(&self) -> &Self::Target { &self.target } + + fn extract_solution(&self, target_solution: &[usize]) -> Vec { + target_solution.to_vec() + } +} + +#[reduction(overhead = { + num_vertices = "num_vertices", + num_arcs = "2 * num_edges", +})] +impl ReduceTo> for MinimumVertexCover { + type Result = ReductionVCToFVS; + + fn reduce_to(&self) -> Self::Result { + let arcs = self.graph().edges().iter().flat_map(|&(u, v)| [(u, v), (v, u)]).collect(); + let target = MinimumFeedbackVertexSet::new( + DirectedGraph::new(self.graph().num_vertices(), arcs), + self.weights().to_vec(), + ); + ReductionVCToFVS { target } + } +} +``` + +Register the module in `src/rules/mod.rs` next to the other `minimumvertexcover_*` rules. + +**Step 2: Run test to verify it passes** + +Run: +- `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_closed_loop -- --exact` +- `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_structure -- --exact` +- `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_identity_extraction -- --exact` + +Expected: PASS. + +**Step 3: Refactor** + +Keep the arc construction readable and deterministic. If needed, extract a small helper inside the rule file, but do not add any abstraction beyond this rule. + +**Step 4: Commit** + +```bash +git add src/rules/minimumvertexcover_minimumfeedbackvertexset.rs src/rules/mod.rs src/unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs +git commit -m "Add MinimumVertexCover to MinimumFeedbackVertexSet reduction" +``` + +### Task 3: Add the canonical rule example + +**Files:** +- Modify: `src/rules/minimumvertexcover_minimumfeedbackvertexset.rs` +- Reference: `src/rules/minimumvertexcover_maximumindependentset.rs` + +**Step 1: Write the failing example-db expectation** + +Add a `#[cfg(feature = "example-db")] pub(crate) fn canonical_rule_example_specs() -> Vec` block to the new rule file. Use the issue’s 7-vertex example and one verified witness pair: + +```rust +SolutionPair { + source_config: vec![1, 1, 0, 1, 0, 1, 0], + target_config: vec![1, 1, 0, 1, 0, 1, 0], +} +``` + +The source instance should be: + +```rust +MinimumVertexCover::new( + SimpleGraph::new( + 7, + vec![ + (0, 1), (0, 2), (0, 3), (1, 2), + (1, 3), (3, 4), (4, 5), (5, 6), + ], + ), + vec![1i32; 7], +) +``` + +**Step 2: Run test to verify it fails** + +Run: `cargo test example_db -- --nocapture` +Expected: FAIL if the new rule spec is not wired into the aggregated registry. + +**Step 3: Write minimal implementation** + +Add the new rule’s spec to its module and extend `src/rules/mod.rs` inside `canonical_rule_example_specs()` with: + +```rust +specs.extend(minimumvertexcover_minimumfeedbackvertexset::canonical_rule_example_specs()); +``` + +**Step 4: Run test to verify it passes** + +Run: `cargo test example_db -- --nocapture` +Expected: PASS. + +**Step 5: Commit** + +```bash +git add src/rules/minimumvertexcover_minimumfeedbackvertexset.rs src/rules/mod.rs +git commit -m "Add canonical example for MinimumVertexCover to MinimumFeedbackVertexSet" +``` + +### Task 4: Run focused verification for the implementation batch + +**Files:** +- No code changes expected + +**Step 1: Run the relevant tests** + +Run: +- `cargo test minimumvertexcover_minimumfeedbackvertexset` +- `cargo test minimum_feedback_vertex_set` +- `cargo test minimum_vertex_cover` + +Expected: PASS. + +**Step 2: Run the library quality checks for touched code** + +Run: +- `cargo fmt --check` +- `cargo clippy --all-targets --all-features -- -D warnings` + +Expected: PASS. + +**Step 3: Commit** + +Do not create a new commit unless one of the verification commands requires a small follow-up fix. + +## Batch 2: Paper entry, exports, fixtures, and final verification + +### Task 5: Add the paper reduction entry and worked example + +**Files:** +- Modify: `docs/paper/reductions.typ` +- Reference: `docs/paper/reductions.typ` near `#reduction-rule("MinimumVertexCover", "MaximumIndependentSet", ...)` +- Reference: `docs/paper/reductions.typ` near `#problem-def("MinimumFeedbackVertexSet")` + +**Step 1: Write the failing paper entry** + +Insert a new block near the other `MinimumVertexCover` reductions: + +```typst +#let mvc_fvs = load-example("MinimumVertexCover", "MinimumFeedbackVertexSet") +#let mvc_fvs_sol = mvc_fvs.solutions.at(0) +#reduction-rule("MinimumVertexCover", "MinimumFeedbackVertexSet", + example: true, + example-caption: [7-vertex graph: each undirected edge becomes a directed 2-cycle], + extra: [ + ... + ], +)[ + ... +][ + _Construction._ ... + _Correctness._ ... + _Solution extraction._ ... +] +``` + +The narrative must explicitly fix the earlier issue wording mistake: longer directed cycles can exist, but hitting every bidirected edge-pair already hits every longer cycle because each cycle uses source edges whose endpoints are covered. + +**Step 2: Run test to verify it fails** + +Run: `make paper` +Expected: FAIL before the reduction is documented or before fixture data exists. + +**Step 3: Write minimal implementation** + +Fill in: +- theorem body: bidirected-edge construction, same weights, same budget, overhead `n` vertices and `2m` arcs +- proof body: forward/backward correctness and identity extraction +- worked example driven from `mvc_fvs` fixture data, not hardcoded duplicate numbers + +**Step 4: Run test to verify it passes** + +Run: `make paper` +Expected: PASS once the example fixture data and exports are in place. + +**Step 5: Commit** + +```bash +git add docs/paper/reductions.typ +git commit -m "Document MinimumVertexCover to MinimumFeedbackVertexSet reduction" +``` + +### Task 6: Regenerate exports and run final verification + +**Files:** +- Generated/updated: `docs/src/reductions/reduction_graph.json` +- Generated/updated: `docs/src/reductions/problem_schemas.json` +- Generated/updated: `src/example_db/fixtures/examples.json` + +**Step 1: Regenerate derived artifacts** + +Run: +- `cargo run --example export_graph` +- `cargo run --example export_schemas` +- `make regenerate-fixtures` + +Expected: generated graph/schema/example fixture data include the new rule. + +**Step 2: Run final verification** + +Run: +- `make test` +- `make clippy` +- `make paper` + +Expected: PASS. + +**Step 3: Commit** + +```bash +git add docs/src/reductions/reduction_graph.json docs/src/reductions/problem_schemas.json src/example_db/fixtures/examples.json +git commit -m "Regenerate exports for MinimumVertexCover to MinimumFeedbackVertexSet" +``` + +### Task 7: Prepare the branch for review-pipeline + +**Files:** +- Delete: `docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md` + +**Step 1: Verify branch state** + +Run: `git status --short` +Expected: only the plan file remains staged for deletion or the tree is otherwise clean. + +**Step 2: Remove the plan file** + +Run: + +```bash +git rm docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md +git commit -m "chore: remove plan file after implementation" +``` + +**Step 3: Push and summarize** + +Run: +- `git push` +- post the PR implementation summary comment describing: + - the new rule file, tests, and canonical example + - the paper entry and regenerated fixture/export files + - any deviations from this plan From 1cca4b488bd639fa3eb7da613174d59a4c39c3ca Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 20 Mar 2026 21:08:02 +0800 Subject: [PATCH 2/3] Implement #207: add MinimumVertexCover to MinimumFeedbackVertexSet reduction --- docs/paper/reductions.typ | 24 +++- ...mumvertexcover_minimumfeedbackvertexset.rs | 97 +++++++++++++ src/rules/mod.rs | 2 + ...mumvertexcover_minimumfeedbackvertexset.rs | 130 ++++++++++++++++++ 4 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/rules/minimumvertexcover_minimumfeedbackvertexset.rs create mode 100644 src/unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 3a1f13b6a..ce5e24959 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -3723,7 +3723,7 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76], let weights = x.instance.weights let precs = x.instance.precedences let ntasks = lengths.len() - let sol = x.optimal.at(0) + let sol = (config: x.optimal_config, metric: x.optimal_value) let opt = sol.metric.Valid let lehmer = sol.config let schedule = { @@ -4178,6 +4178,28 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead _Solution extraction._ For IS solution $S$, return $C = V backslash S$, i.e.\ flip each variable: $c_v = 1 - s_v$. ] +#let mvc_fvs = load-example("MinimumVertexCover", "MinimumFeedbackVertexSet") +#let mvc_fvs_sol = mvc_fvs.solutions.at(0) +#let mvc_fvs_cover = mvc_fvs_sol.source_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => i) +#let mvc_fvs_fvs = mvc_fvs_sol.target_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => i) +#reduction-rule("MinimumVertexCover", "MinimumFeedbackVertexSet", + example: true, + example-caption: [7-vertex graph: each source edge becomes a directed 2-cycle], + extra: [ + Source VC: $C = {#mvc_fvs_cover.map(str).join(", ")}$ (size #mvc_fvs_cover.len()) on a graph with $n = #graph-num-vertices(mvc_fvs.source.instance)$ vertices and $|E| = #graph-num-edges(mvc_fvs.source.instance)$ edges \ + Target FVS: $F = {#mvc_fvs_fvs.map(str).join(", ")}$ (size #mvc_fvs_fvs.len()) on a digraph with the same $n = #graph-num-vertices(mvc_fvs.target.instance)$ vertices and $|A| = #mvc_fvs.target.instance.graph.arcs.len() = 2 |E|$ arcs \ + Canonical witness is preserved exactly: $C = F$ #sym.checkmark + ], +)[ + Each undirected edge $\{u, v\}$ can be viewed as the directed 2-cycle $u -> v -> u$. Replacing every source edge this way turns the task "hit every edge with a chosen endpoint" into "hit every directed cycle with a chosen vertex." The vertex set, weights, and budget are preserved, so the reduction is size-preserving up to doubling the edge count into arcs. +][ + _Construction._ Given a Minimum Vertex Cover instance $(G = (V, E), bold(w))$, build the directed graph $D = (V, A)$ on the same vertex set, where for every undirected edge $\{u, v\} in E$ we add both arcs $(u, v)$ and $(v, u)$ to $A$. Keep the vertex weights unchanged and reuse the same decision variables $x_v in {0,1}$. + + _Correctness._ ($arrow.r.double$) If $C subset.eq V$ is a vertex cover of $G$, then every source edge $\{u, v\}$ has an endpoint in $C$, so the corresponding 2-cycle $u -> v -> u$ in $D$ is hit by $C$. Any longer directed cycle in $D$ is also made from source edges, so one of its vertices lies in $C$ as well. Therefore removing $C$ destroys all directed cycles, and $C$ is a feedback vertex set of $D$. ($arrow.l.double$) If $F subset.eq V$ is a feedback vertex set of $D$, then for every source edge $\{u, v\}$ the digraph contains the 2-cycle $u -> v -> u$, which must be hit by $F$. Hence at least one of $u, v$ lies in $F$, so $F$ covers every edge of $G$ and is a vertex cover. + + _Solution extraction._ Return the target solution vector unchanged: a selected vertex in the feedback vertex set is selected in the vertex cover, and vice versa. +] + #reduction-rule("MaximumIndependentSet", "MinimumVertexCover")[ The exact reverse of VC $arrow.r$ IS: complementing an independent set yields a vertex cover. The graph and weights are preserved unchanged, and $|"IS"| + |"VC"| = |V|$ ensures optimality carries over. ][ diff --git a/src/rules/minimumvertexcover_minimumfeedbackvertexset.rs b/src/rules/minimumvertexcover_minimumfeedbackvertexset.rs new file mode 100644 index 000000000..7f984aa67 --- /dev/null +++ b/src/rules/minimumvertexcover_minimumfeedbackvertexset.rs @@ -0,0 +1,97 @@ +//! Reduction from MinimumVertexCover to MinimumFeedbackVertexSet. +//! +//! Each undirected edge becomes a directed 2-cycle, so a vertex cover is +//! exactly a feedback vertex set in the constructed digraph. + +use crate::models::graph::{MinimumFeedbackVertexSet, MinimumVertexCover}; +use crate::reduction; +use crate::rules::traits::{ReduceTo, ReductionResult}; +use crate::topology::{DirectedGraph, Graph, SimpleGraph}; +use crate::types::WeightElement; + +/// Result of reducing MinimumVertexCover to MinimumFeedbackVertexSet. +#[derive(Debug, Clone)] +pub struct ReductionVCToFVS { + target: MinimumFeedbackVertexSet, +} + +impl ReductionResult for ReductionVCToFVS +where + W: WeightElement + crate::variant::VariantParam, +{ + type Source = MinimumVertexCover; + type Target = MinimumFeedbackVertexSet; + + fn target_problem(&self) -> &Self::Target { + &self.target + } + + fn extract_solution(&self, target_solution: &[usize]) -> Vec { + target_solution.to_vec() + } +} + +#[reduction( + overhead = { + num_vertices = "num_vertices", + num_arcs = "2 * num_edges", + } +)] +impl ReduceTo> for MinimumVertexCover { + type Result = ReductionVCToFVS; + + fn reduce_to(&self) -> Self::Result { + let arcs = self + .graph() + .edges() + .into_iter() + .flat_map(|(u, v)| [(u, v), (v, u)]) + .collect(); + + let target = MinimumFeedbackVertexSet::new( + DirectedGraph::new(self.graph().num_vertices(), arcs), + self.weights().to_vec(), + ); + + ReductionVCToFVS { target } + } +} + +#[cfg(feature = "example-db")] +pub(crate) fn canonical_rule_example_specs() -> Vec { + use crate::export::SolutionPair; + + vec![crate::example_db::specs::RuleExampleSpec { + id: "minimumvertexcover_to_minimumfeedbackvertexset", + build: || { + let source = MinimumVertexCover::new( + SimpleGraph::new( + 7, + vec![ + (0, 1), + (0, 2), + (0, 3), + (1, 2), + (1, 3), + (3, 4), + (4, 5), + (5, 6), + ], + ), + vec![1i32; 7], + ); + + crate::example_db::specs::rule_example_with_witness::<_, MinimumFeedbackVertexSet>( + source, + SolutionPair { + source_config: vec![1, 1, 0, 1, 0, 1, 0], + target_config: vec![1, 1, 0, 1, 0, 1, 0], + }, + ) + }, + }] +} + +#[cfg(test)] +#[path = "../unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs"] +mod tests; diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 68b87be76..41af1613e 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -26,6 +26,7 @@ mod maximumsetpacking_casts; pub(crate) mod maximumsetpacking_qubo; pub(crate) mod minimummultiwaycut_qubo; pub(crate) mod minimumvertexcover_maximumindependentset; +pub(crate) mod minimumvertexcover_minimumfeedbackvertexset; pub(crate) mod minimumvertexcover_minimumsetcovering; pub(crate) mod sat_circuitsat; pub(crate) mod sat_coloring; @@ -101,6 +102,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec MinimumVertexCover { + MinimumVertexCover::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 0), (2, 3), (3, 4)]), + vec![4, 1, 3, 2, 5], + ) +} + +#[test] +fn test_minimumvertexcover_to_minimumfeedbackvertexset_closed_loop() { + let source = weighted_cycle_cover_source(); + let reduction: ReductionVCToFVS = + ReduceTo::>::reduce_to(&source); + + assert_optimization_round_trip_from_optimization_target( + &source, + &reduction, + "MVC -> FVS closed loop", + ); +} + +#[test] +fn test_reduction_structure() { + let source = weighted_cycle_cover_source(); + let reduction: ReductionVCToFVS = + ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + assert_eq!(target.graph().num_vertices(), source.graph().num_vertices()); + assert_eq!(target.num_arcs(), 2 * source.num_edges()); + + let mut arcs = target.graph().arcs(); + arcs.sort_unstable(); + + assert_eq!( + arcs, + vec![ + (0, 1), + (0, 2), + (1, 0), + (1, 2), + (2, 0), + (2, 1), + (2, 3), + (3, 2), + (3, 4), + (4, 3), + ] + ); +} + +#[test] +fn test_weight_preservation() { + let source = weighted_cycle_cover_source(); + let reduction: ReductionVCToFVS = + ReduceTo::>::reduce_to(&source); + + assert_eq!(reduction.target_problem().weights(), source.weights()); +} + +#[test] +fn test_identity_solution_extraction() { + let source = weighted_cycle_cover_source(); + let reduction: ReductionVCToFVS = + ReduceTo::>::reduce_to(&source); + + assert_eq!( + reduction.extract_solution(&[1, 0, 1, 0, 1]), + vec![1, 0, 1, 0, 1] + ); +} + +#[cfg(feature = "example-db")] +#[test] +fn test_canonical_rule_example_spec_builds() { + let example = (canonical_rule_example_specs() + .into_iter() + .find(|spec| spec.id == "minimumvertexcover_to_minimumfeedbackvertexset") + .expect("example spec should be registered") + .build)(); + + assert_eq!(example.source.problem, "MinimumVertexCover"); + assert_eq!(example.target.problem, "MinimumFeedbackVertexSet"); + assert_eq!(example.solutions.len(), 1); + assert_eq!( + example.solutions[0].source_config, + example.solutions[0].target_config + ); + + let source: MinimumVertexCover = + serde_json::from_value(example.source.instance.clone()) + .expect("source example deserializes"); + let target: MinimumFeedbackVertexSet = + serde_json::from_value(example.target.instance.clone()) + .expect("target example deserializes"); + let solution = &example.solutions[0]; + + let source_metric = source.evaluate(&solution.source_config); + let target_metric = target.evaluate(&solution.target_config); + assert!( + source_metric.is_valid(), + "source witness should be feasible" + ); + assert!( + target_metric.is_valid(), + "target witness should be feasible" + ); + + let best_source = BruteForce::new() + .find_best(&source) + .expect("source example should have an optimum"); + let best_target = BruteForce::new() + .find_best(&target) + .expect("target example should have an optimum"); + + assert_eq!(source_metric, source.evaluate(&best_source)); + assert_eq!(target_metric, target.evaluate(&best_target)); +} From 99eb46ee81eb0f4fc7ae9257bc191ed74816a937 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 20 Mar 2026 21:08:15 +0800 Subject: [PATCH 3/3] chore: remove plan file after implementation --- ...vertexcover-to-minimumfeedbackvertexset.md | 347 ------------------ 1 file changed, 347 deletions(-) delete mode 100644 docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md diff --git a/docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md b/docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md deleted file mode 100644 index 95e6d2867..000000000 --- a/docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md +++ /dev/null @@ -1,347 +0,0 @@ -# MinimumVertexCover to MinimumFeedbackVertexSet Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Add the `MinimumVertexCover -> MinimumFeedbackVertexSet` reduction, its closed-loop coverage, a canonical example fixture, and the paper entry required for the reduction catalog. - -**Architecture:** Follow the existing graph-to-graph reduction pattern used by [`src/rules/minimumvertexcover_maximumindependentset.rs`](/Users/jinguomini/rcode/problem-reductions/.worktrees/issue-207/src/rules/minimumvertexcover_maximumindependentset.rs), but target a directed graph model. The reduction keeps the same vertex set and weights, replaces each undirected edge `{u,v}` with directed arcs `(u,v)` and `(v,u)`, and uses identity solution extraction because both source and target configurations mark the selected vertices with `1`. The implementation batch should stop once the code, tests, and canonical rule example are in place; the paper/export batch runs afterward because it depends on generated fixture data. - -**Tech Stack:** Rust, cargo test, the reduction registry macro `#[reduction]`, BruteForce solver, Typst paper docs, example-db fixture generation. - ---- - -## Batch 1: Code, tests, and canonical example - -### Task 1: Write the failing reduction tests - -**Files:** -- Create: `src/unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs` -- Reference: `src/rules/test_helpers.rs` -- Reference: `src/unit_tests/rules/minimumvertexcover_maximumindependentset.rs` - -**Step 1: Write the failing test** - -Add three tests: - -```rust -#[test] -fn test_minimumvertexcover_to_minimumfeedbackvertexset_closed_loop() { - let source = MinimumVertexCover::new( - SimpleGraph::new(7, vec![ - (0, 1), (0, 2), (0, 3), (1, 2), - (1, 3), (3, 4), (4, 5), (5, 6), - ]), - vec![1i32; 7], - ); - let reduction = ReduceTo::>::reduce_to(&source); - assert_optimization_round_trip_from_optimization_target( - &source, - &reduction, - "MVC -> MFVS closed loop", - ); -} - -#[test] -fn test_minimumvertexcover_to_minimumfeedbackvertexset_structure() { - let source = MinimumVertexCover::new( - SimpleGraph::new(3, vec![(0, 1), (1, 2)]), - vec![10, 20, 30], - ); - let reduction = ReduceTo::>::reduce_to(&source); - let target = reduction.target_problem(); - assert_eq!(target.num_vertices(), 3); - assert_eq!(target.num_arcs(), 4); - assert_eq!(target.weights(), &[10, 20, 30]); - assert_eq!( - target.graph().arcs(), - vec![(0, 1), (1, 0), (1, 2), (2, 1)], - ); -} - -#[test] -fn test_minimumvertexcover_to_minimumfeedbackvertexset_identity_extraction() { - let source = MinimumVertexCover::new(SimpleGraph::new(2, vec![(0, 1)]), vec![1, 1]); - let reduction = ReduceTo::>::reduce_to(&source); - assert_eq!(reduction.extract_solution(&[1, 0]), vec![1, 0]); -} -``` - -**Step 2: Run test to verify it fails** - -Run: `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_closed_loop -- --exact` -Expected: FAIL because the rule module is not implemented or not registered yet. - -**Step 3: Commit** - -Do not commit yet. This task stays uncommitted until the implementation turns the tests green. - -### Task 2: Implement the reduction and register it - -**Files:** -- Create: `src/rules/minimumvertexcover_minimumfeedbackvertexset.rs` -- Modify: `src/rules/mod.rs` -- Test: `src/unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs` - -**Step 1: Write minimal implementation** - -Create the rule module with this structure: - -```rust -#[derive(Debug, Clone)] -pub struct ReductionVCToFVS { - target: MinimumFeedbackVertexSet, -} - -impl ReductionResult for ReductionVCToFVS -where - W: WeightElement + crate::variant::VariantParam, -{ - type Source = MinimumVertexCover; - type Target = MinimumFeedbackVertexSet; - - fn target_problem(&self) -> &Self::Target { &self.target } - - fn extract_solution(&self, target_solution: &[usize]) -> Vec { - target_solution.to_vec() - } -} - -#[reduction(overhead = { - num_vertices = "num_vertices", - num_arcs = "2 * num_edges", -})] -impl ReduceTo> for MinimumVertexCover { - type Result = ReductionVCToFVS; - - fn reduce_to(&self) -> Self::Result { - let arcs = self.graph().edges().iter().flat_map(|&(u, v)| [(u, v), (v, u)]).collect(); - let target = MinimumFeedbackVertexSet::new( - DirectedGraph::new(self.graph().num_vertices(), arcs), - self.weights().to_vec(), - ); - ReductionVCToFVS { target } - } -} -``` - -Register the module in `src/rules/mod.rs` next to the other `minimumvertexcover_*` rules. - -**Step 2: Run test to verify it passes** - -Run: -- `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_closed_loop -- --exact` -- `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_structure -- --exact` -- `cargo test test_minimumvertexcover_to_minimumfeedbackvertexset_identity_extraction -- --exact` - -Expected: PASS. - -**Step 3: Refactor** - -Keep the arc construction readable and deterministic. If needed, extract a small helper inside the rule file, but do not add any abstraction beyond this rule. - -**Step 4: Commit** - -```bash -git add src/rules/minimumvertexcover_minimumfeedbackvertexset.rs src/rules/mod.rs src/unit_tests/rules/minimumvertexcover_minimumfeedbackvertexset.rs -git commit -m "Add MinimumVertexCover to MinimumFeedbackVertexSet reduction" -``` - -### Task 3: Add the canonical rule example - -**Files:** -- Modify: `src/rules/minimumvertexcover_minimumfeedbackvertexset.rs` -- Reference: `src/rules/minimumvertexcover_maximumindependentset.rs` - -**Step 1: Write the failing example-db expectation** - -Add a `#[cfg(feature = "example-db")] pub(crate) fn canonical_rule_example_specs() -> Vec` block to the new rule file. Use the issue’s 7-vertex example and one verified witness pair: - -```rust -SolutionPair { - source_config: vec![1, 1, 0, 1, 0, 1, 0], - target_config: vec![1, 1, 0, 1, 0, 1, 0], -} -``` - -The source instance should be: - -```rust -MinimumVertexCover::new( - SimpleGraph::new( - 7, - vec![ - (0, 1), (0, 2), (0, 3), (1, 2), - (1, 3), (3, 4), (4, 5), (5, 6), - ], - ), - vec![1i32; 7], -) -``` - -**Step 2: Run test to verify it fails** - -Run: `cargo test example_db -- --nocapture` -Expected: FAIL if the new rule spec is not wired into the aggregated registry. - -**Step 3: Write minimal implementation** - -Add the new rule’s spec to its module and extend `src/rules/mod.rs` inside `canonical_rule_example_specs()` with: - -```rust -specs.extend(minimumvertexcover_minimumfeedbackvertexset::canonical_rule_example_specs()); -``` - -**Step 4: Run test to verify it passes** - -Run: `cargo test example_db -- --nocapture` -Expected: PASS. - -**Step 5: Commit** - -```bash -git add src/rules/minimumvertexcover_minimumfeedbackvertexset.rs src/rules/mod.rs -git commit -m "Add canonical example for MinimumVertexCover to MinimumFeedbackVertexSet" -``` - -### Task 4: Run focused verification for the implementation batch - -**Files:** -- No code changes expected - -**Step 1: Run the relevant tests** - -Run: -- `cargo test minimumvertexcover_minimumfeedbackvertexset` -- `cargo test minimum_feedback_vertex_set` -- `cargo test minimum_vertex_cover` - -Expected: PASS. - -**Step 2: Run the library quality checks for touched code** - -Run: -- `cargo fmt --check` -- `cargo clippy --all-targets --all-features -- -D warnings` - -Expected: PASS. - -**Step 3: Commit** - -Do not create a new commit unless one of the verification commands requires a small follow-up fix. - -## Batch 2: Paper entry, exports, fixtures, and final verification - -### Task 5: Add the paper reduction entry and worked example - -**Files:** -- Modify: `docs/paper/reductions.typ` -- Reference: `docs/paper/reductions.typ` near `#reduction-rule("MinimumVertexCover", "MaximumIndependentSet", ...)` -- Reference: `docs/paper/reductions.typ` near `#problem-def("MinimumFeedbackVertexSet")` - -**Step 1: Write the failing paper entry** - -Insert a new block near the other `MinimumVertexCover` reductions: - -```typst -#let mvc_fvs = load-example("MinimumVertexCover", "MinimumFeedbackVertexSet") -#let mvc_fvs_sol = mvc_fvs.solutions.at(0) -#reduction-rule("MinimumVertexCover", "MinimumFeedbackVertexSet", - example: true, - example-caption: [7-vertex graph: each undirected edge becomes a directed 2-cycle], - extra: [ - ... - ], -)[ - ... -][ - _Construction._ ... - _Correctness._ ... - _Solution extraction._ ... -] -``` - -The narrative must explicitly fix the earlier issue wording mistake: longer directed cycles can exist, but hitting every bidirected edge-pair already hits every longer cycle because each cycle uses source edges whose endpoints are covered. - -**Step 2: Run test to verify it fails** - -Run: `make paper` -Expected: FAIL before the reduction is documented or before fixture data exists. - -**Step 3: Write minimal implementation** - -Fill in: -- theorem body: bidirected-edge construction, same weights, same budget, overhead `n` vertices and `2m` arcs -- proof body: forward/backward correctness and identity extraction -- worked example driven from `mvc_fvs` fixture data, not hardcoded duplicate numbers - -**Step 4: Run test to verify it passes** - -Run: `make paper` -Expected: PASS once the example fixture data and exports are in place. - -**Step 5: Commit** - -```bash -git add docs/paper/reductions.typ -git commit -m "Document MinimumVertexCover to MinimumFeedbackVertexSet reduction" -``` - -### Task 6: Regenerate exports and run final verification - -**Files:** -- Generated/updated: `docs/src/reductions/reduction_graph.json` -- Generated/updated: `docs/src/reductions/problem_schemas.json` -- Generated/updated: `src/example_db/fixtures/examples.json` - -**Step 1: Regenerate derived artifacts** - -Run: -- `cargo run --example export_graph` -- `cargo run --example export_schemas` -- `make regenerate-fixtures` - -Expected: generated graph/schema/example fixture data include the new rule. - -**Step 2: Run final verification** - -Run: -- `make test` -- `make clippy` -- `make paper` - -Expected: PASS. - -**Step 3: Commit** - -```bash -git add docs/src/reductions/reduction_graph.json docs/src/reductions/problem_schemas.json src/example_db/fixtures/examples.json -git commit -m "Regenerate exports for MinimumVertexCover to MinimumFeedbackVertexSet" -``` - -### Task 7: Prepare the branch for review-pipeline - -**Files:** -- Delete: `docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md` - -**Step 1: Verify branch state** - -Run: `git status --short` -Expected: only the plan file remains staged for deletion or the tree is otherwise clean. - -**Step 2: Remove the plan file** - -Run: - -```bash -git rm docs/plans/2026-03-20-minimumvertexcover-to-minimumfeedbackvertexset.md -git commit -m "chore: remove plan file after implementation" -``` - -**Step 3: Push and summarize** - -Run: -- `git push` -- post the PR implementation summary comment describing: - - the new rule file, tests, and canonical example - - the paper entry and regenerated fixture/export files - - any deviations from this plan