Skip to content

Commit 0f51be6

Browse files
zazabapclaudeisPANN
authored
feat: add 11 Tier 1a + 1b medium-confidence reduction rules (#770) (#804)
* feat: add PaintShop -> QUBO reduction (#649) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add MinimumVertexCover -> MinimumHittingSet reduction (#200) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add PartitionIntoPathsOfLength2 -> BoundedComponentSpanningForest reduction (#241) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add HamiltonianCircuit -> LongestCircuit reduction (#358) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add Partition -> SubsetSum reduction (#387) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add RootedTreeArrangement -> RootedTreeStorageAssignment reduction (#424) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add SubsetSum -> CapacityAssignment reduction (#426) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add LongestCommonSubsequence -> MaximumIndependentSet reduction (#109) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add MinimumVertexCover -> EnsembleComputation reduction (#204) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add KClique -> BalancedCompleteBipartiteSubgraph reduction (#231) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add KColoring(K3) -> TwoDimensionalConsecutiveSets reduction (#437) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: correct LCS->MIS example_db target_config * fix: escape #k-clique Typst variable reference in paper Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add missing Streif2021 bibliography entry for PaintShop→QUBO Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace deprecated 'sect' with 'inter' in Typst paper Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: change EnsembleComputation to optimization (Min<usize>) and fix MVC→EC reduction - EnsembleComputation: Value changed from Or (feasibility) to Min<usize> (minimize sequence length). The budget parameter remains as a search-space bound, but evaluate() now returns the number of steps used rather than just true/false. - MVC→EC reduction: source changed from MinimumVertexCover<SimpleGraph, i32> to MinimumVertexCover<SimpleGraph, One>. The weighted variant was unsound because EC has no weight field. With both sides as Min, the optimal value relationship J* = K* + |E| is tight — no trivial upper bound needed. - Updated paper entries, model tests, and rule tests accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add default budget for EnsembleComputation, make --budget optional - Add `with_default_budget()` and `default_budget()` methods. Default is sum of subset sizes (worst-case without reuse), clamped to at least 1. - CLI: --budget is now optional; omitting it uses the default. - Paper: remove J from problem definition (it's a search-space bound, not a mathematical parameter). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Xiwei Pan <xiwei.pan@connect.hkust-gz.edu.cn>
1 parent a70b132 commit 0f51be6

33 files changed

Lines changed: 3144 additions & 69 deletions

docs/paper/reductions.typ

Lines changed: 363 additions & 2 deletions
Large diffs are not rendered by default.

docs/paper/references.bib

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,18 @@ @techreport{plaisted1976
15211521
year = {1976}
15221522
}
15231523

1524+
@article{Streif2021,
1525+
title = {Beating classical heuristics for the binary paint shop problem
1526+
with the quantum approximate optimization algorithm},
1527+
author = {Streif, Michael and Yarkoni, Sheir and Skolik, Andrea
1528+
and Neukart, Florian and Leib, Martin},
1529+
journal = {Physical Review A},
1530+
volume = {104},
1531+
pages = {012403},
1532+
year = {2021},
1533+
doi = {10.1103/PhysRevA.104.012403}
1534+
}
1535+
15241536
@techreport{storer1977,
15251537
author = {James A. Storer},
15261538
title = {NP-Completeness Results Concerning Data Compression},

problemreductions-cli/src/commands/create.rs

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ use problemreductions::models::misc::{
2828
LongestCommonSubsequence, MinimumExternalMacroDataCompression, MinimumTardinessSequencing,
2929
MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack, ProductionPlanning, QueryArg,
3030
RectilinearPictureCompression, RegisterSufficiency, ResourceConstrainedScheduling,
31-
SchedulingToMinimizeWeightedCompletionTime,
32-
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
33-
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
34-
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
35-
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, ThreePartition, TimetableDesign,
31+
SchedulingToMinimizeWeightedCompletionTime, SchedulingWithIndividualDeadlines,
32+
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
33+
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
34+
SequencingWithinIntervals, ShortestCommonSupersequence, StringToStringCorrection, SubsetSum,
35+
SumOfSquaresPartition, ThreePartition, TimetableDesign,
3636
};
3737
use problemreductions::models::BiconnectivityAugmentation;
3838
use problemreductions::prelude::*;
@@ -661,7 +661,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
661661
"SpinGlass" => "--graph 0-1,1-2 --couplings 1,1",
662662
"KColoring" => "--graph 0-1,1-2,2-0 --k 3",
663663
"HamiltonianCircuit" => "--graph 0-1,1-2,2-3,3-0",
664-
"EnsembleComputation" => "--universe 4 --sets \"0,1,2;0,1,3\" --budget 4",
664+
"EnsembleComputation" => "--universe 4 --sets \"0,1,2;0,1,3\"",
665665
"RootedTreeStorageAssignment" => "--universe 5 --sets \"0,2;1,3;0,4;2,4\" --bound 1",
666666
"MinMaxMulticenter" => {
667667
"--graph 0-1,1-2,2-3 --weights 1,1,1,1 --edge-weights 1,1,1 --k 2"
@@ -2622,26 +2622,23 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
26222622
// EnsembleComputation
26232623
"EnsembleComputation" => {
26242624
let usage =
2625-
"Usage: pred create EnsembleComputation --universe 4 --sets \"0,1,2;0,1,3\" --budget 4";
2625+
"Usage: pred create EnsembleComputation --universe 4 --sets \"0,1,2;0,1,3\" [--budget 4]";
26262626
let universe_size = args.universe.ok_or_else(|| {
26272627
anyhow::anyhow!("EnsembleComputation requires --universe\n\n{usage}")
26282628
})?;
26292629
let subsets = parse_sets(args)?;
2630-
let budget = args
2631-
.budget
2632-
.as_deref()
2633-
.ok_or_else(|| anyhow::anyhow!("EnsembleComputation requires --budget\n\n{usage}"))?
2634-
.parse::<usize>()
2635-
.map_err(|e| {
2630+
let instance = if let Some(budget_str) = args.budget.as_deref() {
2631+
let budget = budget_str.parse::<usize>().map_err(|e| {
26362632
anyhow::anyhow!(
26372633
"Invalid --budget value for EnsembleComputation: {e}\n\n{usage}"
26382634
)
26392635
})?;
2640-
(
2641-
ser(EnsembleComputation::try_new(universe_size, subsets, budget)
2642-
.map_err(anyhow::Error::msg)?)?,
2643-
resolved_variant.clone(),
2644-
)
2636+
EnsembleComputation::try_new(universe_size, subsets, budget)
2637+
.map_err(anyhow::Error::msg)?
2638+
} else {
2639+
EnsembleComputation::with_default_budget(universe_size, subsets)
2640+
};
2641+
(ser(instance)?, resolved_variant.clone())
26452642
}
26462643

26472644
// ComparativeContainment

src/models/graph/minimum_vertex_cover.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
77
use crate::topology::{Graph, SimpleGraph};
88
use crate::traits::Problem;
9-
use crate::types::{Min, WeightElement};
9+
use crate::types::{Min, One, WeightElement};
1010
use num_traits::Zero;
1111
use serde::{Deserialize, Serialize};
1212

@@ -17,7 +17,7 @@ inventory::submit! {
1717
aliases: &["MVC"],
1818
dimensions: &[
1919
VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20-
VariantDimension::new("weight", "i32", &["i32"]),
20+
VariantDimension::new("weight", "i32", &["i32", "One"]),
2121
],
2222
module_path: module_path!(),
2323
description: "Find minimum weight vertex cover in a graph",
@@ -152,6 +152,7 @@ fn is_vertex_cover_config<G: Graph>(graph: &G, config: &[usize]) -> bool {
152152

153153
crate::declare_variants! {
154154
default MinimumVertexCover<SimpleGraph, i32> => "1.1996^num_vertices",
155+
MinimumVertexCover<SimpleGraph, One> => "1.1996^num_vertices",
155156
}
156157

157158
#[cfg(feature = "example-db")]

src/models/misc/ensemble_computation.rs

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use crate::registry::{FieldInfo, ProblemSchemaEntry};
44
use crate::traits::Problem;
5+
use crate::types::Min;
56
use serde::{Deserialize, Serialize};
67

78
inventory::submit! {
@@ -11,7 +12,7 @@ inventory::submit! {
1112
aliases: &[],
1213
dimensions: &[],
1314
module_path: module_path!(),
14-
description: "Determine whether required subsets can be built by a bounded sequence of disjoint unions",
15+
description: "Find the minimum-length sequence of disjoint unions that builds all required subsets",
1516
fields: &[
1617
FieldInfo { name: "universe_size", type_name: "usize", description: "Number of elements in the universe A" },
1718
FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Required subsets that must appear among the computed z_i values" },
@@ -33,6 +34,23 @@ impl EnsembleComputation {
3334
Self::try_new(universe_size, subsets, budget).unwrap_or_else(|err| panic!("{err}"))
3435
}
3536

37+
/// Create with an automatically derived search-space bound.
38+
///
39+
/// The default budget is the sum of all subset sizes (worst-case without
40+
/// intermediate-set reuse). This is always sufficient for the optimal
41+
/// solution to fit within the search space.
42+
pub fn with_default_budget(universe_size: usize, subsets: Vec<Vec<usize>>) -> Self {
43+
let budget = Self::default_budget(&subsets);
44+
Self::new(universe_size, subsets, budget)
45+
}
46+
47+
/// Compute a default search-space bound from the subsets.
48+
///
49+
/// Returns the sum of all subset sizes, clamped to at least 1.
50+
pub fn default_budget(subsets: &[Vec<usize>]) -> usize {
51+
subsets.iter().map(|s| s.len()).sum::<usize>().max(1)
52+
}
53+
3654
pub fn try_new(
3755
universe_size: usize,
3856
subsets: Vec<Vec<usize>>,
@@ -146,49 +164,47 @@ impl EnsembleComputation {
146164

147165
impl Problem for EnsembleComputation {
148166
const NAME: &'static str = "EnsembleComputation";
149-
type Value = crate::types::Or;
167+
type Value = Min<usize>;
150168

151169
fn dims(&self) -> Vec<usize> {
152170
vec![self.universe_size + self.budget; 2 * self.budget]
153171
}
154172

155-
fn evaluate(&self, config: &[usize]) -> crate::types::Or {
156-
crate::types::Or({
157-
if config.len() != 2 * self.budget {
158-
return crate::types::Or(false);
159-
}
173+
fn evaluate(&self, config: &[usize]) -> Min<usize> {
174+
if config.len() != 2 * self.budget {
175+
return Min(None);
176+
}
160177

161-
let Some(required_subsets) = self.required_subsets() else {
162-
return crate::types::Or(false);
178+
let Some(required_subsets) = self.required_subsets() else {
179+
return Min(None);
180+
};
181+
if required_subsets.is_empty() {
182+
return Min(Some(0));
183+
}
184+
185+
let mut computed = Vec::with_capacity(self.budget);
186+
for step in 0..self.budget {
187+
let left_operand = config[2 * step];
188+
let right_operand = config[2 * step + 1];
189+
190+
let Some(left) = self.decode_operand(left_operand, &computed) else {
191+
return Min(None);
192+
};
193+
let Some(right) = self.decode_operand(right_operand, &computed) else {
194+
return Min(None);
163195
};
164-
if required_subsets.is_empty() {
165-
return crate::types::Or(true);
196+
197+
if !Self::are_disjoint(&left, &right) {
198+
return Min(None);
166199
}
167200

168-
let mut computed = Vec::with_capacity(self.budget);
169-
for step in 0..self.budget {
170-
let left_operand = config[2 * step];
171-
let right_operand = config[2 * step + 1];
172-
173-
let Some(left) = self.decode_operand(left_operand, &computed) else {
174-
return crate::types::Or(false);
175-
};
176-
let Some(right) = self.decode_operand(right_operand, &computed) else {
177-
return crate::types::Or(false);
178-
};
179-
180-
if !Self::are_disjoint(&left, &right) {
181-
return crate::types::Or(false);
182-
}
183-
184-
computed.push(Self::union_disjoint(&left, &right));
185-
if Self::all_required_subsets_present(&required_subsets, &computed) {
186-
return crate::types::Or(true);
187-
}
201+
computed.push(Self::union_disjoint(&left, &right));
202+
if Self::all_required_subsets_present(&required_subsets, &computed) {
203+
return Min(Some(step + 1));
188204
}
205+
}
189206

190-
false
191-
})
207+
Min(None)
192208
}
193209

194210
fn variant() -> Vec<(&'static str, &'static str)> {
@@ -227,7 +243,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
227243
2,
228244
)),
229245
optimal_config: vec![0, 1, 3, 2],
230-
optimal_value: serde_json::json!(true),
246+
optimal_value: serde_json::json!(2),
231247
}]
232248
}
233249

src/models/misc/longest_common_subsequence.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,25 @@ impl LongestCommonSubsequence {
118118
pub fn num_transitions(&self) -> usize {
119119
self.max_length.saturating_sub(1)
120120
}
121+
122+
/// Returns the cross-frequency product: the sum over each alphabet symbol
123+
/// of the product of that symbol's frequency across all input strings.
124+
///
125+
/// Formally: Σ_{c ∈ 0..alphabet_size} Π_{i=1..k} count(c, strings\[i\])
126+
/// where count(c, s) is the number of occurrences of symbol c in string s.
127+
///
128+
/// This equals the exact number of match-node vertices in the LCS → MaxIS
129+
/// reduction graph.
130+
pub fn cross_frequency_product(&self) -> usize {
131+
(0..self.alphabet_size)
132+
.map(|c| {
133+
self.strings
134+
.iter()
135+
.map(|s| s.iter().filter(|&&sym| sym == c).count())
136+
.product::<usize>()
137+
})
138+
.sum()
139+
}
121140
}
122141

123142
/// Check whether `candidate` is a subsequence of `target` using greedy

src/models/misc/paintshop.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ impl PaintShop {
134134
&self.car_labels
135135
}
136136

137+
/// Get the sequence as car indices.
138+
pub fn sequence_indices(&self) -> &[usize] {
139+
&self.sequence_indices
140+
}
141+
142+
/// Get whether each position is the first occurrence of its car.
143+
pub fn is_first(&self) -> &[bool] {
144+
&self.is_first
145+
}
146+
137147
/// Get the coloring of the sequence from a configuration.
138148
///
139149
/// Config assigns a color (0 or 1) to each car for its first occurrence.

src/models/mod.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,11 @@ pub use misc::{
4545
MultiprocessorScheduling, PaintShop, Partition, PrecedenceConstrainedScheduling,
4646
ProductionPlanning, QueryArg, RectilinearPictureCompression, RegisterSufficiency,
4747
ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime,
48-
SchedulingWithIndividualDeadlines,
49-
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
50-
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
51-
SequencingWithinIntervals, ShortestCommonSupersequence, StackerCrane, StaffScheduling,
52-
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, Term, ThreePartition,
53-
TimetableDesign,
48+
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
49+
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
50+
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
51+
StackerCrane, StaffScheduling, StringToStringCorrection, SubsetSum, SumOfSquaresPartition,
52+
Term, ThreePartition, TimetableDesign,
5453
};
5554
pub use set::{
5655
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, IntegerKnapsack, MaximumSetPacking,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//! Reduction from HamiltonianCircuit to LongestCircuit.
2+
//!
3+
//! Given an HC instance G = (V, E), construct an LC instance on the same graph
4+
//! with unit edge weights. A Hamiltonian circuit exists iff the optimal circuit
5+
//! length equals |V|.
6+
7+
use crate::models::graph::{HamiltonianCircuit, LongestCircuit};
8+
use crate::reduction;
9+
use crate::rules::traits::{ReduceTo, ReductionResult};
10+
use crate::topology::{Graph, SimpleGraph};
11+
12+
/// Result of reducing HamiltonianCircuit to LongestCircuit.
13+
#[derive(Debug, Clone)]
14+
pub struct ReductionHamiltonianCircuitToLongestCircuit {
15+
target: LongestCircuit<SimpleGraph, i32>,
16+
}
17+
18+
impl ReductionResult for ReductionHamiltonianCircuitToLongestCircuit {
19+
type Source = HamiltonianCircuit<SimpleGraph>;
20+
type Target = LongestCircuit<SimpleGraph, i32>;
21+
22+
fn target_problem(&self) -> &Self::Target {
23+
&self.target
24+
}
25+
26+
fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
27+
crate::rules::graph_helpers::edges_to_cycle_order(self.target.graph(), target_solution)
28+
}
29+
}
30+
31+
#[reduction(
32+
overhead = {
33+
num_vertices = "num_vertices",
34+
num_edges = "num_edges",
35+
}
36+
)]
37+
impl ReduceTo<LongestCircuit<SimpleGraph, i32>> for HamiltonianCircuit<SimpleGraph> {
38+
type Result = ReductionHamiltonianCircuitToLongestCircuit;
39+
40+
fn reduce_to(&self) -> Self::Result {
41+
let n = self.num_vertices();
42+
let edges = self.graph().edges();
43+
let target = LongestCircuit::new(SimpleGraph::new(n, edges), vec![1i32; self.num_edges()]);
44+
ReductionHamiltonianCircuitToLongestCircuit { target }
45+
}
46+
}
47+
48+
#[cfg(feature = "example-db")]
49+
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
50+
use crate::export::SolutionPair;
51+
52+
vec![crate::example_db::specs::RuleExampleSpec {
53+
id: "hamiltoniancircuit_to_longestcircuit",
54+
build: || {
55+
let source = HamiltonianCircuit::new(SimpleGraph::cycle(4));
56+
crate::example_db::specs::rule_example_with_witness::<_, LongestCircuit<SimpleGraph, i32>>(
57+
source,
58+
SolutionPair {
59+
source_config: vec![0, 1, 2, 3],
60+
target_config: vec![1, 1, 1, 1],
61+
},
62+
)
63+
},
64+
}]
65+
}
66+
67+
#[cfg(test)]
68+
#[path = "../unit_tests/rules/hamiltoniancircuit_longestcircuit.rs"]
69+
mod tests;

0 commit comments

Comments
 (0)