Skip to content

Commit 624c71f

Browse files
GiggleLiuisPANNclaude
authored
Fix #548: Add CosineProductIntegration model and Partition reduction (#684)
Add CosineProductIntegration (Garey & Johnson A7/AN14) as an independent model with a Partition → CosineProductIntegration reduction. The problem asks whether a balanced sign assignment exists for a sequence of integer frequencies, which is equivalent to Partition for positive integers but generalizes to arbitrary signed coefficients. Co-authored-by: Xiwei Pan <xiwei.pan@connect.hkust-gz.edu.cn> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e067f91 commit 624c71f

File tree

10 files changed

+476
-14
lines changed

10 files changed

+476
-14
lines changed

docs/paper/reductions.typ

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
"LongestCommonSubsequence": [Longest Common Subsequence],
145145
"ExactCoverBy3Sets": [Exact Cover by 3-Sets],
146146
"SubsetSum": [Subset Sum],
147+
"CosineProductIntegration": [Cosine Product Integration],
147148
"Partition": [Partition],
148149
"ThreePartition": [3-Partition],
149150
"PartialFeedbackEdgeSet": [Partial Feedback Edge Set],
@@ -4683,6 +4684,14 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
46834684
*Example.* Let $A = {3, 1, 1, 2, 2, 1}$ ($n = 6$, total sum $= 10$). Setting $A' = {3, 2}$ (indices 0, 3) gives sum $3 + 2 = 5 = 10 slash 2$, and $A without A' = {1, 1, 2, 1}$ also sums to 5. Hence a balanced partition exists.
46844685
]
46854686

4687+
#problem-def("CosineProductIntegration")[
4688+
Given a sequence of integers $(a_1, a_2, dots, a_n)$, determine whether there exists a sign assignment $epsilon in {-1, +1}^n$ such that $sum_(i=1)^n epsilon_i a_i = 0$.
4689+
][
4690+
Garey & Johnson problem A7/AN14. The original formulation asks whether $integral_0^(2 pi) product_(i=1)^n cos(a_i theta) d theta = 0$; by expanding each cosine as $(e^(i a_i theta) + e^(-i a_i theta)) slash 2$ via Euler's formula and integrating, the integral equals $(2 pi slash 2^n)$ times the number of sign assignments $epsilon$ with $sum epsilon_i a_i = 0$. Hence the integral is nonzero if and only if a balanced sign assignment exists, making this equivalent to a generalisation of Partition to signed integers. NP-complete by reduction from Partition @plaisted1976. Solvable in pseudo-polynomial time via dynamic programming on achievable partial sums.
4691+
4692+
*Example.* Let $(a_1, a_2, a_3) = (2, 3, 5)$. The sign assignment $(+1, +1, -1)$ gives $2 + 3 - 5 = 0$, so the integral is nonzero.
4693+
]
4694+
46864695
#{
46874696
let x = load-model-example("ShortestCommonSupersequence")
46884697
let alpha-size = x.instance.alphabet_size
@@ -7063,6 +7072,24 @@ where $P$ is a penalty weight large enough that any constraint violation costs m
70637072
_Solution extraction._ Discard slack variables: return $bold(x)' [0..n]$.
70647073
]
70657074

7075+
#let part_cpi = load-example("Partition", "CosineProductIntegration")
7076+
#let part_cpi_sol = part_cpi.solutions.at(0)
7077+
#let part_cpi_sizes = part_cpi.source.instance.sizes
7078+
#let part_cpi_n = part_cpi_sizes.len()
7079+
#let part_cpi_coeffs = part_cpi.target.instance.coefficients
7080+
#reduction-rule("Partition", "CosineProductIntegration",
7081+
example: true,
7082+
example-caption: [#part_cpi_n elements],
7083+
)[
7084+
This $O(n)$ identity reduction casts each positive integer size $s_i$ to the corresponding integer coefficient $a_i = s_i$. A balanced partition (two subsets of equal sum) exists if and only if a balanced sign assignment ($sum epsilon_i a_i = 0$) exists, because assigning element $i$ to subset $A'$ corresponds to $epsilon_i = -1$ and to $A without A'$ corresponds to $epsilon_i = +1$. Reference: Plaisted (1976) @plaisted1976.
7085+
][
7086+
_Construction._ Given Partition sizes $s_0, dots, s_(n-1) in ZZ^+$, set the CosineProductIntegration coefficients to $a_i = s_i$ for each $i in {0, dots, n-1}$.
7087+
7088+
_Correctness._ ($arrow.r.double$) If a balanced partition exists with subset $A'$ having $sum_(a in A') s(a) = S slash 2$, then the sign assignment $epsilon_i = -1$ for $i in A'$ and $epsilon_i = +1$ otherwise gives $sum epsilon_i a_i = S - 2 dot S slash 2 = 0$. ($arrow.l.double$) If a balanced sign assignment exists with $sum epsilon_i a_i = 0$, the elements with $epsilon_i = -1$ form a subset summing to $S slash 2$, which is a valid partition.
7089+
7090+
_Solution extraction._ Return the same binary vector: $x_i = 1$ (element in second subset) corresponds to $epsilon_i = -1$ (negative sign).
7091+
]
7092+
70667093
#let part_ks = load-example("Partition", "Knapsack")
70677094
#let part_ks_sol = part_ks.solutions.at(0)
70687095
#let part_ks_sizes = part_ks.source.instance.sizes

docs/paper/references.bib

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,3 +1446,11 @@ @article{edmondsjohnson1973
14461446
pages = {88--124},
14471447
year = {1973}
14481448
}
1449+
1450+
@techreport{plaisted1976,
1451+
author = {David A. Plaisted},
1452+
title = {Some Polynomial and Integer Divisibility Problems Are {NP}-Hard},
1453+
institution = {Stanford University, Department of Computer Science},
1454+
number = {STAN-CS-76-583},
1455+
year = {1976}
1456+
}

src/lib.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,16 @@ pub mod prelude {
7171
pub use crate::models::misc::{
7272
AdditionalKey, BinPacking, BoyceCoddNormalFormViolation, CapacityAssignment, CbqRelation,
7373
ConjunctiveBooleanQuery, ConjunctiveQueryFoldability, ConsistencyOfDatabaseFrequencyTables,
74-
EnsembleComputation, ExpectedRetrievalCost, Factoring, FlowShopScheduling,
75-
GroupingBySwapping, JobShopScheduling, Knapsack, LongestCommonSubsequence,
76-
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, Partition,
77-
ProductionPlanning, QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
78-
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
79-
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
80-
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals,
81-
ShortestCommonSupersequence, StackerCrane, StaffScheduling, StringToStringCorrection,
82-
SubsetSum, SumOfSquaresPartition, Term, ThreePartition, TimetableDesign,
74+
CosineProductIntegration, EnsembleComputation, ExpectedRetrievalCost, Factoring,
75+
FlowShopScheduling, GroupingBySwapping, JobShopScheduling, Knapsack,
76+
LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop,
77+
Partition, ProductionPlanning, QueryArg, RectilinearPictureCompression,
78+
ResourceConstrainedScheduling, SchedulingWithIndividualDeadlines,
79+
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
80+
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
81+
SequencingWithinIntervals, ShortestCommonSupersequence, StackerCrane, StaffScheduling,
82+
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, Term, ThreePartition,
83+
TimetableDesign,
8384
};
8485
pub use crate::models::set::{
8586
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//! Cosine Product Integration problem implementation.
2+
//!
3+
//! Given integer frequencies `a_1, ..., a_n`, determine whether a sign
4+
//! assignment `ε ∈ {-1, +1}^n` exists with `∑ εᵢ aᵢ = 0`.
5+
//!
6+
//! This is equivalent to asking whether
7+
//! `∫₀²π ∏ᵢ cos(aᵢ θ) dθ ≠ 0` (Garey & Johnson A7 AN14).
8+
//! The integral is nonzero exactly when such a balanced sign assignment
9+
//! exists, so the G&J question "does the integral equal zero?" is the
10+
//! complement of this satisfaction problem.
11+
12+
use crate::registry::{FieldInfo, ProblemSchemaEntry};
13+
use crate::traits::Problem;
14+
use serde::{Deserialize, Serialize};
15+
16+
inventory::submit! {
17+
ProblemSchemaEntry {
18+
name: "CosineProductIntegration",
19+
display_name: "Cosine Product Integration",
20+
aliases: &[],
21+
dimensions: &[],
22+
module_path: module_path!(),
23+
description: "Decide whether a balanced sign assignment exists for a sequence of integer frequencies",
24+
fields: &[
25+
FieldInfo {
26+
name: "coefficients",
27+
type_name: "Vec<i64>",
28+
description: "Integer cosine frequencies",
29+
},
30+
],
31+
}
32+
}
33+
34+
/// The Cosine Product Integration problem.
35+
///
36+
/// Given integer coefficients `a_1, ..., a_n`, determine whether there
37+
/// exists a sign assignment `ε ∈ {-1, +1}^n` with `∑ εᵢ aᵢ = 0`.
38+
///
39+
/// # Representation
40+
///
41+
/// Each variable chooses a sign: `0` means `+aᵢ`, `1` means `−aᵢ`.
42+
/// A configuration is satisfying when the resulting signed sum is zero.
43+
///
44+
/// # Example
45+
///
46+
/// ```
47+
/// use problemreductions::models::misc::CosineProductIntegration;
48+
/// use problemreductions::{Problem, Solver, BruteForce};
49+
///
50+
/// // coefficients [2, 3, 5]: sign assignment (+2, +3, -5) = 0
51+
/// let problem = CosineProductIntegration::new(vec![2, 3, 5]);
52+
/// let solver = BruteForce::new();
53+
/// let solution = solver.find_witness(&problem);
54+
/// assert!(solution.is_some());
55+
/// ```
56+
#[derive(Debug, Clone, Serialize, Deserialize)]
57+
pub struct CosineProductIntegration {
58+
coefficients: Vec<i64>,
59+
}
60+
61+
impl CosineProductIntegration {
62+
/// Create a new CosineProductIntegration instance.
63+
///
64+
/// # Panics
65+
///
66+
/// Panics if `coefficients` is empty.
67+
pub fn new(coefficients: Vec<i64>) -> Self {
68+
assert!(
69+
!coefficients.is_empty(),
70+
"CosineProductIntegration requires at least one coefficient"
71+
);
72+
Self { coefficients }
73+
}
74+
75+
/// Returns the cosine coefficients.
76+
pub fn coefficients(&self) -> &[i64] {
77+
&self.coefficients
78+
}
79+
80+
/// Returns the number of coefficients.
81+
pub fn num_coefficients(&self) -> usize {
82+
self.coefficients.len()
83+
}
84+
}
85+
86+
impl Problem for CosineProductIntegration {
87+
const NAME: &'static str = "CosineProductIntegration";
88+
type Value = crate::types::Or;
89+
90+
fn variant() -> Vec<(&'static str, &'static str)> {
91+
crate::variant_params![]
92+
}
93+
94+
fn dims(&self) -> Vec<usize> {
95+
vec![2; self.num_coefficients()]
96+
}
97+
98+
fn evaluate(&self, config: &[usize]) -> crate::types::Or {
99+
crate::types::Or({
100+
if config.len() != self.num_coefficients() {
101+
return crate::types::Or(false);
102+
}
103+
if config.iter().any(|&v| v >= 2) {
104+
return crate::types::Or(false);
105+
}
106+
let signed_sum: i128 = self
107+
.coefficients
108+
.iter()
109+
.zip(config.iter())
110+
.map(|(&a, &bit)| {
111+
let val = a as i128;
112+
if bit == 0 {
113+
val
114+
} else {
115+
-val
116+
}
117+
})
118+
.sum();
119+
signed_sum == 0
120+
})
121+
}
122+
}
123+
124+
crate::declare_variants! {
125+
default CosineProductIntegration => "2^(num_coefficients / 2)",
126+
}
127+
128+
#[cfg(feature = "example-db")]
129+
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
130+
vec![crate::example_db::specs::ModelExampleSpec {
131+
id: "cosine_product_integration",
132+
instance: Box::new(CosineProductIntegration::new(vec![2, 3, 5])),
133+
optimal_config: vec![0, 0, 1],
134+
optimal_value: serde_json::json!(true),
135+
}]
136+
}
137+
138+
#[cfg(test)]
139+
#[path = "../../unit_tests/models/misc/cosine_product_integration.rs"]
140+
mod tests;

src/models/misc/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//! - [`LongestCommonSubsequence`]: Longest Common Subsequence
1818
//! - [`MinimumTardinessSequencing`]: Minimize tardy tasks in single-machine scheduling
1919
//! - [`PaintShop`]: Minimize color switches in paint shop scheduling
20+
//! - [`CosineProductIntegration`]: Balanced sign assignment for integer frequencies
2021
//! - [`Partition`]: Partition a multiset into two equal-sum subsets
2122
//! - [`PartiallyOrderedKnapsack`]: Knapsack with precedence constraints
2223
//! - [`PrecedenceConstrainedScheduling`]: Schedule unit tasks on processors by deadline
@@ -68,6 +69,7 @@ mod capacity_assignment;
6869
pub(crate) mod conjunctive_boolean_query;
6970
pub(crate) mod conjunctive_query_foldability;
7071
mod consistency_of_database_frequency_tables;
72+
mod cosine_product_integration;
7173
mod ensemble_computation;
7274
pub(crate) mod expected_retrieval_cost;
7375
pub(crate) mod factoring;
@@ -109,6 +111,7 @@ pub use conjunctive_query_foldability::{ConjunctiveQueryFoldability, Term};
109111
pub use consistency_of_database_frequency_tables::{
110112
ConsistencyOfDatabaseFrequencyTables, FrequencyTable, KnownValue,
111113
};
114+
pub use cosine_product_integration::CosineProductIntegration;
112115
pub use ensemble_computation::EnsembleComputation;
113116
pub use expected_retrieval_cost::ExpectedRetrievalCost;
114117
pub use factoring::Factoring;
@@ -182,5 +185,6 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
182185
specs.extend(knapsack::canonical_model_example_specs());
183186
specs.extend(subset_sum::canonical_model_example_specs());
184187
specs.extend(three_partition::canonical_model_example_specs());
188+
specs.extend(cosine_product_integration::canonical_model_example_specs());
185189
specs
186190
}

src/models/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ pub use graph::{
3737
pub use misc::PartiallyOrderedKnapsack;
3838
pub use misc::{
3939
AdditionalKey, BinPacking, CapacityAssignment, CbqRelation, ConjunctiveBooleanQuery,
40-
ConjunctiveQueryFoldability, ConsistencyOfDatabaseFrequencyTables, EnsembleComputation,
41-
ExpectedRetrievalCost, Factoring, FlowShopScheduling, GroupingBySwapping, JobShopScheduling,
42-
Knapsack, LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling,
43-
PaintShop, Partition, PrecedenceConstrainedScheduling, ProductionPlanning, QueryArg,
44-
RectilinearPictureCompression, ResourceConstrainedScheduling,
40+
ConjunctiveQueryFoldability, ConsistencyOfDatabaseFrequencyTables, CosineProductIntegration,
41+
EnsembleComputation, ExpectedRetrievalCost, Factoring, FlowShopScheduling, GroupingBySwapping,
42+
JobShopScheduling, Knapsack, LongestCommonSubsequence, MinimumTardinessSequencing,
43+
MultiprocessorScheduling, PaintShop, Partition, PrecedenceConstrainedScheduling,
44+
ProductionPlanning, QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
4545
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
4646
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
4747
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,

src/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub(crate) mod minimumvertexcover_maximumindependentset;
4949
pub(crate) mod minimumvertexcover_minimumfeedbackarcset;
5050
pub(crate) mod minimumvertexcover_minimumfeedbackvertexset;
5151
pub(crate) mod minimumvertexcover_minimumsetcovering;
52+
pub(crate) mod partition_cosineproductintegration;
5253
pub(crate) mod partition_knapsack;
5354
pub(crate) mod partition_multiprocessorscheduling;
5455
pub(crate) mod partition_sequencingwithinintervals;
@@ -283,6 +284,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::Ru
283284
specs.extend(maximummatching_maximumsetpacking::canonical_rule_example_specs());
284285
specs.extend(maximumsetpacking_qubo::canonical_rule_example_specs());
285286
specs.extend(minimummultiwaycut_qubo::canonical_rule_example_specs());
287+
specs.extend(partition_cosineproductintegration::canonical_rule_example_specs());
286288
specs.extend(partition_knapsack::canonical_rule_example_specs());
287289
specs.extend(partition_multiprocessorscheduling::canonical_rule_example_specs());
288290
specs.extend(partition_sequencingwithinintervals::canonical_rule_example_specs());
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! Reduction from Partition to CosineProductIntegration.
2+
//!
3+
//! Given a Partition instance with sizes `[s_1, ..., s_n]`, construct a
4+
//! CosineProductIntegration instance with coefficients `[s_1, ..., s_n]`
5+
//! (cast from `u64` to `i64`).
6+
//!
7+
//! A balanced partition exists iff a balanced sign assignment exists:
8+
//! subset A has sum = total/2 iff the sign vector `ε_i = +1` for `i ∈ A`,
9+
//! `ε_i = -1` for `i ∉ A` satisfies `∑ ε_i s_i = 0`.
10+
//!
11+
//! Solution extraction is the identity mapping.
12+
13+
use crate::models::misc::{CosineProductIntegration, Partition};
14+
use crate::reduction;
15+
use crate::rules::traits::{ReduceTo, ReductionResult};
16+
17+
/// Result of reducing Partition to CosineProductIntegration.
18+
#[derive(Debug, Clone)]
19+
pub struct ReductionPartitionToCPI {
20+
target: CosineProductIntegration,
21+
}
22+
23+
impl ReductionResult for ReductionPartitionToCPI {
24+
type Source = Partition;
25+
type Target = CosineProductIntegration;
26+
27+
fn target_problem(&self) -> &Self::Target {
28+
&self.target
29+
}
30+
31+
fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
32+
target_solution.to_vec()
33+
}
34+
}
35+
36+
#[reduction(overhead = {
37+
num_coefficients = "num_elements",
38+
})]
39+
impl ReduceTo<CosineProductIntegration> for Partition {
40+
type Result = ReductionPartitionToCPI;
41+
42+
fn reduce_to(&self) -> Self::Result {
43+
let coefficients: Vec<i64> = self.sizes().iter().map(|&s| s as i64).collect();
44+
ReductionPartitionToCPI {
45+
target: CosineProductIntegration::new(coefficients),
46+
}
47+
}
48+
}
49+
50+
#[cfg(feature = "example-db")]
51+
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
52+
use crate::export::SolutionPair;
53+
54+
vec![crate::example_db::specs::RuleExampleSpec {
55+
id: "partition_to_cosineproductintegration",
56+
build: || {
57+
// sizes [3, 1, 1, 2, 2, 1]: partition {3,2,1}={6} and {1,2,1}={4}? No...
58+
// Actually [3,1,1,2,2,1] sum=10, need sum=5 each.
59+
// config [1,0,0,1,0,0] → selected={3,2}=5, rest={1,1,2,1}=5 ✓
60+
// sign assignment: bit=1→−, bit=0→+ : (+3,−1,−1,+2,−2,−1) = 3-1-1+2-2-1=0? No, 3-1-1+2-2-1=0. Yes!
61+
// Wait: config [1,0,0,1,0,0] means elements 0,3 in subset 1.
62+
// For CPI: bit 1 means −a_i. So −3+1+1−2+2+1 = 0. Yes!
63+
crate::example_db::specs::rule_example_with_witness::<_, CosineProductIntegration>(
64+
Partition::new(vec![3, 1, 1, 2, 2, 1]),
65+
SolutionPair {
66+
source_config: vec![1, 0, 0, 1, 0, 0],
67+
target_config: vec![1, 0, 0, 1, 0, 0],
68+
},
69+
)
70+
},
71+
}]
72+
}
73+
74+
#[cfg(test)]
75+
#[path = "../unit_tests/rules/partition_cosineproductintegration.rs"]
76+
mod tests;

0 commit comments

Comments
 (0)