Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5b9baa6
feat: add PaintShop -> QUBO reduction (#649)
zazabap Mar 28, 2026
4c6db4d
feat: add MinimumVertexCover -> MinimumHittingSet reduction (#200)
zazabap Mar 28, 2026
ff3e708
feat: add PartitionIntoPathsOfLength2 -> BoundedComponentSpanningFore…
zazabap Mar 28, 2026
51a1d29
feat: add HamiltonianCircuit -> LongestCircuit reduction (#358)
zazabap Mar 28, 2026
179136e
feat: add Partition -> SubsetSum reduction (#387)
zazabap Mar 28, 2026
97c073e
feat: add RootedTreeArrangement -> RootedTreeStorageAssignment reduct…
zazabap Mar 28, 2026
a3e8209
feat: add SubsetSum -> CapacityAssignment reduction (#426)
zazabap Mar 28, 2026
5377f67
feat: add LongestCommonSubsequence -> MaximumIndependentSet reduction…
zazabap Mar 28, 2026
2aa02f4
feat: add MinimumVertexCover -> EnsembleComputation reduction (#204)
zazabap Mar 28, 2026
7923931
feat: add KClique -> BalancedCompleteBipartiteSubgraph reduction (#231)
zazabap Mar 28, 2026
83075ae
feat: add KColoring(K3) -> TwoDimensionalConsecutiveSets reduction (#…
zazabap Mar 28, 2026
6d6a370
fix: correct LCS->MIS example_db target_config
zazabap Mar 28, 2026
94db88f
fix: address PR #804 review comments and merge main
zazabap Mar 29, 2026
0a16b9f
fix: escape #k-clique Typst variable reference in paper
zazabap Mar 29, 2026
4d7501a
fix: add missing Streif2021 bibliography entry for PaintShop→QUBO
zazabap Mar 29, 2026
bf95960
fix: resolve merge conflicts with main
zazabap Mar 30, 2026
bc76f7c
fix: replace deprecated 'sect' with 'inter' in Typst paper
isPANN Mar 30, 2026
7494013
fix: change EnsembleComputation to optimization (Min<usize>) and fix …
isPANN Mar 30, 2026
b322ef9
feat: add default budget for EnsembleComputation, make --budget optional
isPANN Mar 30, 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
365 changes: 363 additions & 2 deletions docs/paper/reductions.typ

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,18 @@ @techreport{plaisted1976
year = {1976}
}

@article{Streif2021,
title = {Beating classical heuristics for the binary paint shop problem
with the quantum approximate optimization algorithm},
author = {Streif, Michael and Yarkoni, Sheir and Skolik, Andrea
and Neukart, Florian and Leib, Martin},
journal = {Physical Review A},
volume = {104},
pages = {012403},
year = {2021},
doi = {10.1103/PhysRevA.104.012403}
}

@techreport{storer1977,
author = {James A. Storer},
title = {NP-Completeness Results Concerning Data Compression},
Expand Down
33 changes: 15 additions & 18 deletions problemreductions-cli/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ use problemreductions::models::misc::{
LongestCommonSubsequence, MinimumExternalMacroDataCompression, MinimumTardinessSequencing,
MultiprocessorScheduling, PaintShop, PartiallyOrderedKnapsack, ProductionPlanning, QueryArg,
RectilinearPictureCompression, RegisterSufficiency, ResourceConstrainedScheduling,
SchedulingToMinimizeWeightedCompletionTime,
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, ThreePartition, TimetableDesign,
SchedulingToMinimizeWeightedCompletionTime, SchedulingWithIndividualDeadlines,
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
SequencingWithinIntervals, ShortestCommonSupersequence, StringToStringCorrection, SubsetSum,
SumOfSquaresPartition, ThreePartition, TimetableDesign,
};
use problemreductions::models::BiconnectivityAugmentation;
use problemreductions::prelude::*;
Expand Down Expand Up @@ -661,7 +661,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str {
"SpinGlass" => "--graph 0-1,1-2 --couplings 1,1",
"KColoring" => "--graph 0-1,1-2,2-0 --k 3",
"HamiltonianCircuit" => "--graph 0-1,1-2,2-3,3-0",
"EnsembleComputation" => "--universe 4 --sets \"0,1,2;0,1,3\" --budget 4",
"EnsembleComputation" => "--universe 4 --sets \"0,1,2;0,1,3\"",
"RootedTreeStorageAssignment" => "--universe 5 --sets \"0,2;1,3;0,4;2,4\" --bound 1",
"MinMaxMulticenter" => {
"--graph 0-1,1-2,2-3 --weights 1,1,1,1 --edge-weights 1,1,1 --k 2"
Expand Down Expand Up @@ -2622,26 +2622,23 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> {
// EnsembleComputation
"EnsembleComputation" => {
let usage =
"Usage: pred create EnsembleComputation --universe 4 --sets \"0,1,2;0,1,3\" --budget 4";
"Usage: pred create EnsembleComputation --universe 4 --sets \"0,1,2;0,1,3\" [--budget 4]";
let universe_size = args.universe.ok_or_else(|| {
anyhow::anyhow!("EnsembleComputation requires --universe\n\n{usage}")
})?;
let subsets = parse_sets(args)?;
let budget = args
.budget
.as_deref()
.ok_or_else(|| anyhow::anyhow!("EnsembleComputation requires --budget\n\n{usage}"))?
.parse::<usize>()
.map_err(|e| {
let instance = if let Some(budget_str) = args.budget.as_deref() {
let budget = budget_str.parse::<usize>().map_err(|e| {
anyhow::anyhow!(
"Invalid --budget value for EnsembleComputation: {e}\n\n{usage}"
)
})?;
(
ser(EnsembleComputation::try_new(universe_size, subsets, budget)
.map_err(anyhow::Error::msg)?)?,
resolved_variant.clone(),
)
EnsembleComputation::try_new(universe_size, subsets, budget)
.map_err(anyhow::Error::msg)?
} else {
EnsembleComputation::with_default_budget(universe_size, subsets)
};
(ser(instance)?, resolved_variant.clone())
}

// ComparativeContainment
Expand Down
5 changes: 3 additions & 2 deletions src/models/graph/minimum_vertex_cover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
use crate::topology::{Graph, SimpleGraph};
use crate::traits::Problem;
use crate::types::{Min, WeightElement};
use crate::types::{Min, One, WeightElement};
use num_traits::Zero;
use serde::{Deserialize, Serialize};

Expand All @@ -17,7 +17,7 @@ inventory::submit! {
aliases: &["MVC"],
dimensions: &[
VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
VariantDimension::new("weight", "i32", &["i32"]),
VariantDimension::new("weight", "i32", &["i32", "One"]),
],
module_path: module_path!(),
description: "Find minimum weight vertex cover in a graph",
Expand Down Expand Up @@ -152,6 +152,7 @@ fn is_vertex_cover_config<G: Graph>(graph: &G, config: &[usize]) -> bool {

crate::declare_variants! {
default MinimumVertexCover<SimpleGraph, i32> => "1.1996^num_vertices",
MinimumVertexCover<SimpleGraph, One> => "1.1996^num_vertices",
}

#[cfg(feature = "example-db")]
Expand Down
84 changes: 50 additions & 34 deletions src/models/misc/ensemble_computation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::registry::{FieldInfo, ProblemSchemaEntry};
use crate::traits::Problem;
use crate::types::Min;
use serde::{Deserialize, Serialize};

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

/// Create with an automatically derived search-space bound.
///
/// The default budget is the sum of all subset sizes (worst-case without
/// intermediate-set reuse). This is always sufficient for the optimal
/// solution to fit within the search space.
pub fn with_default_budget(universe_size: usize, subsets: Vec<Vec<usize>>) -> Self {
let budget = Self::default_budget(&subsets);
Self::new(universe_size, subsets, budget)
}

/// Compute a default search-space bound from the subsets.
///
/// Returns the sum of all subset sizes, clamped to at least 1.
pub fn default_budget(subsets: &[Vec<usize>]) -> usize {
subsets.iter().map(|s| s.len()).sum::<usize>().max(1)
}

pub fn try_new(
universe_size: usize,
subsets: Vec<Vec<usize>>,
Expand Down Expand Up @@ -146,49 +164,47 @@ impl EnsembleComputation {

impl Problem for EnsembleComputation {
const NAME: &'static str = "EnsembleComputation";
type Value = crate::types::Or;
type Value = Min<usize>;

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

fn evaluate(&self, config: &[usize]) -> crate::types::Or {
crate::types::Or({
if config.len() != 2 * self.budget {
return crate::types::Or(false);
}
fn evaluate(&self, config: &[usize]) -> Min<usize> {
if config.len() != 2 * self.budget {
return Min(None);
}

let Some(required_subsets) = self.required_subsets() else {
return crate::types::Or(false);
let Some(required_subsets) = self.required_subsets() else {
return Min(None);
};
if required_subsets.is_empty() {
return Min(Some(0));
}

let mut computed = Vec::with_capacity(self.budget);
for step in 0..self.budget {
let left_operand = config[2 * step];
let right_operand = config[2 * step + 1];

let Some(left) = self.decode_operand(left_operand, &computed) else {
return Min(None);
};
let Some(right) = self.decode_operand(right_operand, &computed) else {
return Min(None);
};
if required_subsets.is_empty() {
return crate::types::Or(true);

if !Self::are_disjoint(&left, &right) {
return Min(None);
}

let mut computed = Vec::with_capacity(self.budget);
for step in 0..self.budget {
let left_operand = config[2 * step];
let right_operand = config[2 * step + 1];

let Some(left) = self.decode_operand(left_operand, &computed) else {
return crate::types::Or(false);
};
let Some(right) = self.decode_operand(right_operand, &computed) else {
return crate::types::Or(false);
};

if !Self::are_disjoint(&left, &right) {
return crate::types::Or(false);
}

computed.push(Self::union_disjoint(&left, &right));
if Self::all_required_subsets_present(&required_subsets, &computed) {
return crate::types::Or(true);
}
computed.push(Self::union_disjoint(&left, &right));
if Self::all_required_subsets_present(&required_subsets, &computed) {
return Min(Some(step + 1));
}
}

false
})
Min(None)
}

fn variant() -> Vec<(&'static str, &'static str)> {
Expand Down Expand Up @@ -227,7 +243,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
2,
)),
optimal_config: vec![0, 1, 3, 2],
optimal_value: serde_json::json!(true),
optimal_value: serde_json::json!(2),
}]
}

Expand Down
19 changes: 19 additions & 0 deletions src/models/misc/longest_common_subsequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,25 @@ impl LongestCommonSubsequence {
pub fn num_transitions(&self) -> usize {
self.max_length.saturating_sub(1)
}

/// Returns the cross-frequency product: the sum over each alphabet symbol
/// of the product of that symbol's frequency across all input strings.
///
/// Formally: Σ_{c ∈ 0..alphabet_size} Π_{i=1..k} count(c, strings\[i\])
/// where count(c, s) is the number of occurrences of symbol c in string s.
///
/// This equals the exact number of match-node vertices in the LCS → MaxIS
/// reduction graph.
pub fn cross_frequency_product(&self) -> usize {
(0..self.alphabet_size)
.map(|c| {
self.strings
.iter()
.map(|s| s.iter().filter(|&&sym| sym == c).count())
.product::<usize>()
})
.sum()
}
}

/// Check whether `candidate` is a subsequence of `target` using greedy
Expand Down
10 changes: 10 additions & 0 deletions src/models/misc/paintshop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ impl PaintShop {
&self.car_labels
}

/// Get the sequence as car indices.
pub fn sequence_indices(&self) -> &[usize] {
&self.sequence_indices
}

/// Get whether each position is the first occurrence of its car.
pub fn is_first(&self) -> &[bool] {
&self.is_first
}

/// Get the coloring of the sequence from a configuration.
///
/// Config assigns a color (0 or 1) to each car for its first occurrence.
Expand Down
11 changes: 5 additions & 6 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ pub use misc::{
MultiprocessorScheduling, PaintShop, Partition, PrecedenceConstrainedScheduling,
ProductionPlanning, QueryArg, RectilinearPictureCompression, RegisterSufficiency,
ResourceConstrainedScheduling, SchedulingToMinimizeWeightedCompletionTime,
SchedulingWithIndividualDeadlines,
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
SequencingWithinIntervals, ShortestCommonSupersequence, StackerCrane, StaffScheduling,
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, Term, ThreePartition,
TimetableDesign,
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
StackerCrane, StaffScheduling, StringToStringCorrection, SubsetSum, SumOfSquaresPartition,
Term, ThreePartition, TimetableDesign,
};
pub use set::{
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, IntegerKnapsack, MaximumSetPacking,
Expand Down
69 changes: 69 additions & 0 deletions src/rules/hamiltoniancircuit_longestcircuit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Reduction from HamiltonianCircuit to LongestCircuit.
//!
//! Given an HC instance G = (V, E), construct an LC instance on the same graph
//! with unit edge weights. A Hamiltonian circuit exists iff the optimal circuit
//! length equals |V|.

use crate::models::graph::{HamiltonianCircuit, LongestCircuit};
use crate::reduction;
use crate::rules::traits::{ReduceTo, ReductionResult};
use crate::topology::{Graph, SimpleGraph};

/// Result of reducing HamiltonianCircuit to LongestCircuit.
#[derive(Debug, Clone)]
pub struct ReductionHamiltonianCircuitToLongestCircuit {
target: LongestCircuit<SimpleGraph, i32>,
}

impl ReductionResult for ReductionHamiltonianCircuitToLongestCircuit {
type Source = HamiltonianCircuit<SimpleGraph>;
type Target = LongestCircuit<SimpleGraph, i32>;

fn target_problem(&self) -> &Self::Target {
&self.target
}

fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
crate::rules::graph_helpers::edges_to_cycle_order(self.target.graph(), target_solution)
}
}

#[reduction(
overhead = {
num_vertices = "num_vertices",
num_edges = "num_edges",
}
)]
impl ReduceTo<LongestCircuit<SimpleGraph, i32>> for HamiltonianCircuit<SimpleGraph> {
type Result = ReductionHamiltonianCircuitToLongestCircuit;

fn reduce_to(&self) -> Self::Result {
let n = self.num_vertices();
let edges = self.graph().edges();
let target = LongestCircuit::new(SimpleGraph::new(n, edges), vec![1i32; self.num_edges()]);
ReductionHamiltonianCircuitToLongestCircuit { target }
}
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
use crate::export::SolutionPair;

vec![crate::example_db::specs::RuleExampleSpec {
id: "hamiltoniancircuit_to_longestcircuit",
build: || {
let source = HamiltonianCircuit::new(SimpleGraph::cycle(4));
crate::example_db::specs::rule_example_with_witness::<_, LongestCircuit<SimpleGraph, i32>>(
source,
SolutionPair {
source_config: vec![0, 1, 2, 3],
target_config: vec![1, 1, 1, 1],
},
)
},
}]
}

#[cfg(test)]
#[path = "../unit_tests/rules/hamiltoniancircuit_longestcircuit.rs"]
mod tests;
Loading
Loading