Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
"LongestCommonSubsequence": [Longest Common Subsequence],
"ExactCoverBy3Sets": [Exact Cover by 3-Sets],
"SubsetSum": [Subset Sum],
"CosineProductIntegration": [Cosine Product Integration],
"Partition": [Partition],
"ThreePartition": [3-Partition],
"PartialFeedbackEdgeSet": [Partial Feedback Edge Set],
Expand Down Expand Up @@ -4683,6 +4684,14 @@ A classical NP-complete problem from Garey and Johnson @garey1979[Ch.~3, p.~76],
*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.
]

#problem-def("CosineProductIntegration")[
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$.
][
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.

*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.
]

#{
let x = load-model-example("ShortestCommonSupersequence")
let alpha-size = x.instance.alphabet_size
Expand Down Expand Up @@ -7063,6 +7072,24 @@ where $P$ is a penalty weight large enough that any constraint violation costs m
_Solution extraction._ Discard slack variables: return $bold(x)' [0..n]$.
]

#let part_cpi = load-example("Partition", "CosineProductIntegration")
#let part_cpi_sol = part_cpi.solutions.at(0)
#let part_cpi_sizes = part_cpi.source.instance.sizes
#let part_cpi_n = part_cpi_sizes.len()
#let part_cpi_coeffs = part_cpi.target.instance.coefficients
#reduction-rule("Partition", "CosineProductIntegration",
example: true,
example-caption: [#part_cpi_n elements],
)[
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.
][
_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}$.

_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.

_Solution extraction._ Return the same binary vector: $x_i = 1$ (element in second subset) corresponds to $epsilon_i = -1$ (negative sign).
]

#let part_ks = load-example("Partition", "Knapsack")
#let part_ks_sol = part_ks.solutions.at(0)
#let part_ks_sizes = part_ks.source.instance.sizes
Expand Down
8 changes: 8 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -1446,3 +1446,11 @@ @article{edmondsjohnson1973
pages = {88--124},
year = {1973}
}

@techreport{plaisted1976,
author = {David A. Plaisted},
title = {Some Polynomial and Integer Divisibility Problems Are {NP}-Hard},
institution = {Stanford University, Department of Computer Science},
number = {STAN-CS-76-583},
year = {1976}
}
19 changes: 10 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,16 @@ pub mod prelude {
pub use crate::models::misc::{
AdditionalKey, BinPacking, BoyceCoddNormalFormViolation, CapacityAssignment, CbqRelation,
ConjunctiveBooleanQuery, ConjunctiveQueryFoldability, ConsistencyOfDatabaseFrequencyTables,
EnsembleComputation, ExpectedRetrievalCost, Factoring, FlowShopScheduling,
GroupingBySwapping, JobShopScheduling, Knapsack, LongestCommonSubsequence,
MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop, Partition,
ProductionPlanning, QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals,
ShortestCommonSupersequence, StackerCrane, StaffScheduling, StringToStringCorrection,
SubsetSum, SumOfSquaresPartition, Term, ThreePartition, TimetableDesign,
CosineProductIntegration, EnsembleComputation, ExpectedRetrievalCost, Factoring,
FlowShopScheduling, GroupingBySwapping, JobShopScheduling, Knapsack,
LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling, PaintShop,
Partition, ProductionPlanning, QueryArg, RectilinearPictureCompression,
ResourceConstrainedScheduling, SchedulingWithIndividualDeadlines,
SequencingToMinimizeMaximumCumulativeCost, SequencingToMinimizeWeightedCompletionTime,
SequencingToMinimizeWeightedTardiness, SequencingWithReleaseTimesAndDeadlines,
SequencingWithinIntervals, ShortestCommonSupersequence, StackerCrane, StaffScheduling,
StringToStringCorrection, SubsetSum, SumOfSquaresPartition, Term, ThreePartition,
TimetableDesign,
};
pub use crate::models::set::{
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
Expand Down
140 changes: 140 additions & 0 deletions src/models/misc/cosine_product_integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//! Cosine Product Integration problem implementation.
//!
//! Given integer frequencies `a_1, ..., a_n`, determine whether a sign
//! assignment `ε ∈ {-1, +1}^n` exists with `∑ εᵢ aᵢ = 0`.
//!
//! This is equivalent to asking whether
//! `∫₀²π ∏ᵢ cos(aᵢ θ) dθ ≠ 0` (Garey & Johnson A7 AN14).
//! The integral is nonzero exactly when such a balanced sign assignment
//! exists, so the G&J question "does the integral equal zero?" is the
//! complement of this satisfaction problem.

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

inventory::submit! {
ProblemSchemaEntry {
name: "CosineProductIntegration",
display_name: "Cosine Product Integration",
aliases: &[],
dimensions: &[],
module_path: module_path!(),
description: "Decide whether a balanced sign assignment exists for a sequence of integer frequencies",
fields: &[
FieldInfo {
name: "coefficients",
type_name: "Vec<i64>",
description: "Integer cosine frequencies",
},
],
}
}

/// The Cosine Product Integration problem.
///
/// Given integer coefficients `a_1, ..., a_n`, determine whether there
/// exists a sign assignment `ε ∈ {-1, +1}^n` with `∑ εᵢ aᵢ = 0`.
///
/// # Representation
///
/// Each variable chooses a sign: `0` means `+aᵢ`, `1` means `−aᵢ`.
/// A configuration is satisfying when the resulting signed sum is zero.
///
/// # Example
///
/// ```
/// use problemreductions::models::misc::CosineProductIntegration;
/// use problemreductions::{Problem, Solver, BruteForce};
///
/// // coefficients [2, 3, 5]: sign assignment (+2, +3, -5) = 0
/// let problem = CosineProductIntegration::new(vec![2, 3, 5]);
/// let solver = BruteForce::new();
/// let solution = solver.find_witness(&problem);
/// assert!(solution.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CosineProductIntegration {
coefficients: Vec<i64>,
}

impl CosineProductIntegration {
/// Create a new CosineProductIntegration instance.
///
/// # Panics
///
/// Panics if `coefficients` is empty.
pub fn new(coefficients: Vec<i64>) -> Self {
assert!(
!coefficients.is_empty(),
"CosineProductIntegration requires at least one coefficient"
);
Self { coefficients }
}

/// Returns the cosine coefficients.
pub fn coefficients(&self) -> &[i64] {
&self.coefficients
}

/// Returns the number of coefficients.
pub fn num_coefficients(&self) -> usize {
self.coefficients.len()
}
}

impl Problem for CosineProductIntegration {
const NAME: &'static str = "CosineProductIntegration";
type Value = crate::types::Or;

fn variant() -> Vec<(&'static str, &'static str)> {
crate::variant_params![]
}

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

fn evaluate(&self, config: &[usize]) -> crate::types::Or {
crate::types::Or({
if config.len() != self.num_coefficients() {
return crate::types::Or(false);
}
if config.iter().any(|&v| v >= 2) {
return crate::types::Or(false);
}
let signed_sum: i128 = self
.coefficients
.iter()
.zip(config.iter())
.map(|(&a, &bit)| {
let val = a as i128;
if bit == 0 {
val
} else {
-val
}
})
.sum();
signed_sum == 0
})
}
}

crate::declare_variants! {
default CosineProductIntegration => "2^(num_coefficients / 2)",
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
vec![crate::example_db::specs::ModelExampleSpec {
id: "cosine_product_integration",
instance: Box::new(CosineProductIntegration::new(vec![2, 3, 5])),
optimal_config: vec![0, 0, 1],
optimal_value: serde_json::json!(true),
}]
}

#[cfg(test)]
#[path = "../../unit_tests/models/misc/cosine_product_integration.rs"]
mod tests;
4 changes: 4 additions & 0 deletions src/models/misc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//! - [`LongestCommonSubsequence`]: Longest Common Subsequence
//! - [`MinimumTardinessSequencing`]: Minimize tardy tasks in single-machine scheduling
//! - [`PaintShop`]: Minimize color switches in paint shop scheduling
//! - [`CosineProductIntegration`]: Balanced sign assignment for integer frequencies
//! - [`Partition`]: Partition a multiset into two equal-sum subsets
//! - [`PartiallyOrderedKnapsack`]: Knapsack with precedence constraints
//! - [`PrecedenceConstrainedScheduling`]: Schedule unit tasks on processors by deadline
Expand Down Expand Up @@ -68,6 +69,7 @@ mod capacity_assignment;
pub(crate) mod conjunctive_boolean_query;
pub(crate) mod conjunctive_query_foldability;
mod consistency_of_database_frequency_tables;
mod cosine_product_integration;
mod ensemble_computation;
pub(crate) mod expected_retrieval_cost;
pub(crate) mod factoring;
Expand Down Expand Up @@ -109,6 +111,7 @@ pub use conjunctive_query_foldability::{ConjunctiveQueryFoldability, Term};
pub use consistency_of_database_frequency_tables::{
ConsistencyOfDatabaseFrequencyTables, FrequencyTable, KnownValue,
};
pub use cosine_product_integration::CosineProductIntegration;
pub use ensemble_computation::EnsembleComputation;
pub use expected_retrieval_cost::ExpectedRetrievalCost;
pub use factoring::Factoring;
Expand Down Expand Up @@ -182,5 +185,6 @@ pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::M
specs.extend(knapsack::canonical_model_example_specs());
specs.extend(subset_sum::canonical_model_example_specs());
specs.extend(three_partition::canonical_model_example_specs());
specs.extend(cosine_product_integration::canonical_model_example_specs());
specs
}
10 changes: 5 additions & 5 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ pub use graph::{
pub use misc::PartiallyOrderedKnapsack;
pub use misc::{
AdditionalKey, BinPacking, CapacityAssignment, CbqRelation, ConjunctiveBooleanQuery,
ConjunctiveQueryFoldability, ConsistencyOfDatabaseFrequencyTables, EnsembleComputation,
ExpectedRetrievalCost, Factoring, FlowShopScheduling, GroupingBySwapping, JobShopScheduling,
Knapsack, LongestCommonSubsequence, MinimumTardinessSequencing, MultiprocessorScheduling,
PaintShop, Partition, PrecedenceConstrainedScheduling, ProductionPlanning, QueryArg,
RectilinearPictureCompression, ResourceConstrainedScheduling,
ConjunctiveQueryFoldability, ConsistencyOfDatabaseFrequencyTables, CosineProductIntegration,
EnsembleComputation, ExpectedRetrievalCost, Factoring, FlowShopScheduling, GroupingBySwapping,
JobShopScheduling, Knapsack, LongestCommonSubsequence, MinimumTardinessSequencing,
MultiprocessorScheduling, PaintShop, Partition, PrecedenceConstrainedScheduling,
ProductionPlanning, QueryArg, RectilinearPictureCompression, ResourceConstrainedScheduling,
SchedulingWithIndividualDeadlines, SequencingToMinimizeMaximumCumulativeCost,
SequencingToMinimizeWeightedCompletionTime, SequencingToMinimizeWeightedTardiness,
SequencingWithReleaseTimesAndDeadlines, SequencingWithinIntervals, ShortestCommonSupersequence,
Expand Down
2 changes: 2 additions & 0 deletions src/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub(crate) mod minimumvertexcover_maximumindependentset;
pub(crate) mod minimumvertexcover_minimumfeedbackarcset;
pub(crate) mod minimumvertexcover_minimumfeedbackvertexset;
pub(crate) mod minimumvertexcover_minimumsetcovering;
pub(crate) mod partition_cosineproductintegration;
pub(crate) mod partition_knapsack;
pub(crate) mod partition_multiprocessorscheduling;
pub(crate) mod partition_sequencingwithinintervals;
Expand Down Expand Up @@ -283,6 +284,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::Ru
specs.extend(maximummatching_maximumsetpacking::canonical_rule_example_specs());
specs.extend(maximumsetpacking_qubo::canonical_rule_example_specs());
specs.extend(minimummultiwaycut_qubo::canonical_rule_example_specs());
specs.extend(partition_cosineproductintegration::canonical_rule_example_specs());
specs.extend(partition_knapsack::canonical_rule_example_specs());
specs.extend(partition_multiprocessorscheduling::canonical_rule_example_specs());
specs.extend(partition_sequencingwithinintervals::canonical_rule_example_specs());
Expand Down
76 changes: 76 additions & 0 deletions src/rules/partition_cosineproductintegration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Reduction from Partition to CosineProductIntegration.
//!
//! Given a Partition instance with sizes `[s_1, ..., s_n]`, construct a
//! CosineProductIntegration instance with coefficients `[s_1, ..., s_n]`
//! (cast from `u64` to `i64`).
//!
//! A balanced partition exists iff a balanced sign assignment exists:
//! subset A has sum = total/2 iff the sign vector `ε_i = +1` for `i ∈ A`,
//! `ε_i = -1` for `i ∉ A` satisfies `∑ ε_i s_i = 0`.
//!
//! Solution extraction is the identity mapping.

use crate::models::misc::{CosineProductIntegration, Partition};
use crate::reduction;
use crate::rules::traits::{ReduceTo, ReductionResult};

/// Result of reducing Partition to CosineProductIntegration.
#[derive(Debug, Clone)]
pub struct ReductionPartitionToCPI {
target: CosineProductIntegration,
}

impl ReductionResult for ReductionPartitionToCPI {
type Source = Partition;
type Target = CosineProductIntegration;

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

fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
target_solution.to_vec()
}
}

#[reduction(overhead = {
num_coefficients = "num_elements",
})]
impl ReduceTo<CosineProductIntegration> for Partition {
type Result = ReductionPartitionToCPI;

fn reduce_to(&self) -> Self::Result {
let coefficients: Vec<i64> = self.sizes().iter().map(|&s| s as i64).collect();
ReductionPartitionToCPI {
target: CosineProductIntegration::new(coefficients),
}
}
}

#[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: "partition_to_cosineproductintegration",
build: || {
// sizes [3, 1, 1, 2, 2, 1]: partition {3,2,1}={6} and {1,2,1}={4}? No...
// Actually [3,1,1,2,2,1] sum=10, need sum=5 each.
// config [1,0,0,1,0,0] → selected={3,2}=5, rest={1,1,2,1}=5 ✓
// 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!
// Wait: config [1,0,0,1,0,0] means elements 0,3 in subset 1.
// For CPI: bit 1 means −a_i. So −3+1+1−2+2+1 = 0. Yes!
crate::example_db::specs::rule_example_with_witness::<_, CosineProductIntegration>(
Partition::new(vec![3, 1, 1, 2, 2, 1]),
SolutionPair {
source_config: vec![1, 0, 0, 1, 0, 0],
target_config: vec![1, 0, 0, 1, 0, 0],
},
)
},
}]
}

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