Skip to content
Merged
9 changes: 9 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"SpinGlass": [Spin Glass],
"QUBO": [QUBO],
"ILP": [Integer Linear Programming],
"Knapsack": [Knapsack],
"Satisfiability": [SAT],
"KSatisfiability": [$k$-SAT],
"CircuitSAT": [CircuitSAT],
Expand Down Expand Up @@ -886,6 +887,14 @@ Biclique Cover is equivalent to factoring the biadjacency matrix $M$ of the bipa
) <fig:binpacking-example>
]

#problem-def("Knapsack")[
Given $n$ items with weights $w_0, dots, w_(n-1) in NN$ and values $v_0, dots, v_(n-1) in NN$, and a capacity $C in NN$, find $S subset.eq {0, dots, n - 1}$ maximizing $sum_(i in S) v_i$ subject to $sum_(i in S) w_i lt.eq C$.
][
One of Karp's 21 NP-complete problems @karp1972. Knapsack is only _weakly_ NP-hard: a classical dynamic-programming algorithm runs in $O(n C)$ pseudo-polynomial time, and a fully polynomial-time approximation scheme (FPTAS) achieves $(1 - epsilon)$-optimal value in $O(n^2 slash epsilon)$ time @ibarra1975. The special case $v_i = w_i$ for all $i$ is the Subset Sum problem. Knapsack is also a special case of Integer Linear Programming with a single constraint. The best known exact algorithm is the $O^*(2^(n slash 2))$ meet-in-the-middle approach of Horowitz and Sahni @horowitz1974, which partitions items into two halves and combines sorted sublists.

*Example.* Let $n = 4$ items with weights $(2, 3, 4, 5)$, values $(3, 4, 5, 7)$, and capacity $C = 7$. Selecting $S = {1, 2}$ (items with weights 3 and 4) gives total weight $3 + 4 = 7 lt.eq C$ and total value $4 + 5 = 9$. Selecting $S = {0, 3}$ (weights 2 and 5) gives weight $2 + 5 = 7 lt.eq C$ and value $3 + 7 = 10$, which is optimal.
]

// Completeness check: warn about problem types in JSON but missing from paper
#{
let json-models = {
Expand Down
21 changes: 21 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,24 @@ @article{alber2004
doi = {10.1016/j.jalgor.2003.10.001}
}

@article{horowitz1974,
author = {Ellis Horowitz and Sartaj Sahni},
title = {Computing Partitions with Applications to the Knapsack Problem},
journal = {Journal of the ACM},
volume = {21},
number = {2},
pages = {277--292},
year = {1974},
doi = {10.1145/321812.321823}
}

@article{ibarra1975,
author = {Oscar H. Ibarra and Chul E. Kim},
title = {Fast Approximation Algorithms for the Knapsack and Sum of Subset Problems},
journal = {Journal of the ACM},
volume = {22},
number = {4},
pages = {463--468},
year = {1975},
doi = {10.1145/321906.321909}
}
21 changes: 21 additions & 0 deletions docs/src/reductions/problem_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,27 @@
}
]
},
{
"name": "Knapsack",
"description": "Select items to maximize total value subject to weight capacity constraint",
"fields": [
{
"name": "weights",
"type_name": "Vec<i64>",
"description": "Item weights w_i"
},
{
"name": "values",
"type_name": "Vec<i64>",
"description": "Item values v_i"
},
{
"name": "capacity",
"type_name": "i64",
"description": "Knapsack capacity C"
}
]
},
{
"name": "MaxCut",
"description": "Find maximum weight cut in a graph",
Expand Down
4 changes: 3 additions & 1 deletion problemreductions-cli/src/dispatch.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{bail, Context, Result};
use problemreductions::models::algebraic::{ClosestVectorProblem, ILP};
use problemreductions::models::misc::BinPacking;
use problemreductions::models::misc::{BinPacking, Knapsack};
use problemreductions::prelude::*;
use problemreductions::rules::{MinimizeSteps, ReductionGraph};
use problemreductions::solvers::{BruteForce, ILPSolver, Solver};
Expand Down Expand Up @@ -244,6 +244,7 @@ pub fn load_problem(
Some("f64") => deser_opt::<ClosestVectorProblem<f64>>(data),
_ => deser_opt::<ClosestVectorProblem<i32>>(data),
},
"Knapsack" => deser_opt::<Knapsack>(data),
_ => bail!("{}", crate::problem_name::unknown_problem_error(&canonical)),
}
}
Expand Down Expand Up @@ -303,6 +304,7 @@ pub fn serialize_any_problem(
Some("f64") => try_ser::<ClosestVectorProblem<f64>>(any),
_ => try_ser::<ClosestVectorProblem<i32>>(any),
},
"Knapsack" => try_ser::<Knapsack>(any),
_ => bail!("{}", crate::problem_name::unknown_problem_error(&canonical)),
}
}
Expand Down
4 changes: 2 additions & 2 deletions problemreductions-cli/src/problem_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub const ALIASES: &[(&str, &str)] = &[
("3SAT", "KSatisfiability"),
("KSAT", "KSatisfiability"),
("TSP", "TravelingSalesman"),
("BP", "BinPacking"),
("CVP", "ClosestVectorProblem"),
("MaxMatching", "MaximumMatching"),
];
Expand Down Expand Up @@ -50,8 +49,9 @@ pub fn resolve_alias(input: &str) -> String {
"paintshop" => "PaintShop".to_string(),
"bmf" => "BMF".to_string(),
"bicliquecover" => "BicliqueCover".to_string(),
"bp" | "binpacking" => "BinPacking".to_string(),
"binpacking" => "BinPacking".to_string(),
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BP alias for BinPacking is removed from both the ALIASES const and the resolve_alias() function. This is an unrelated breaking change: any existing CLI user who used pred create BP ... or pred evaluate BP ... will now get an "Unknown problem" error. If this removal was intentional, it should be mentioned in the PR description; otherwise, it should be reverted.

Copilot uses AI. Check for mistakes.
"cvp" | "closestvectorproblem" => "ClosestVectorProblem".to_string(),
"knapsack" => "Knapsack".to_string(),
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions "CLI dispatch + alias (KS)" but no KS alias was actually added. The ALIASES const does not include ("KS", "Knapsack") and resolve_alias() does not handle "ks". If you intended to add this alias (matching the pattern of TSP, MIS, CVP, etc.), it should be added to both ALIASES and resolve_alias().

Copilot uses AI. Check for mistakes.
_ => input.to_string(), // pass-through for exact names
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub mod prelude {
KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching,
MinimumDominatingSet, MinimumVertexCover, TravelingSalesman,
};
pub use crate::models::misc::{BinPacking, Factoring, PaintShop};
pub use crate::models::misc::{BinPacking, Factoring, Knapsack, PaintShop};
pub use crate::models::set::{MaximumSetPacking, MinimumSetCovering};

// Core traits
Expand Down
143 changes: 143 additions & 0 deletions src/models/misc/knapsack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//! Knapsack problem implementation.
//!
//! The 0-1 Knapsack problem asks for a subset of items that maximizes
//! total value while respecting a weight capacity constraint.

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

inventory::submit! {
ProblemSchemaEntry {
name: "Knapsack",
module_path: module_path!(),
description: "Select items to maximize total value subject to weight capacity constraint",
fields: &[
FieldInfo { name: "weights", type_name: "Vec<i64>", description: "Item weights w_i" },
FieldInfo { name: "values", type_name: "Vec<i64>", description: "Item values v_i" },
FieldInfo { name: "capacity", type_name: "i64", description: "Knapsack capacity C" },
],
}
}

/// The 0-1 Knapsack problem.
///
/// Given `n` items, each with weight `w_i` and value `v_i`, and a capacity `C`,
/// find a subset `S ⊆ {0, ..., n-1}` such that `∑_{i∈S} w_i ≤ C`,
/// maximizing `∑_{i∈S} v_i`.
///
/// # Representation
///
/// Each item has a binary variable: `x_i = 1` if item `i` is selected, `0` otherwise.
///
/// # Example
///
/// ```
/// use problemreductions::models::misc::Knapsack;
/// use problemreductions::{Problem, Solver, BruteForce};
///
/// let problem = Knapsack::new(vec![2, 3, 4, 5], vec![3, 4, 5, 7], 7);
/// let solver = BruteForce::new();
/// let solution = solver.find_best(&problem);
/// assert!(solution.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Knapsack {
weights: Vec<i64>,
values: Vec<i64>,
capacity: i64,
}

impl Knapsack {
/// Create a new Knapsack instance.
///
/// # Panics
/// Panics if `weights` and `values` have different lengths.
pub fn new(weights: Vec<i64>, values: Vec<i64>, capacity: i64) -> Self {
assert_eq!(
weights.len(),
values.len(),
"weights and values must have the same length"
);
Self {
weights,
values,
capacity,
}
}

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

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

/// Returns the knapsack capacity.
pub fn capacity(&self) -> i64 {
self.capacity
}

/// Returns the number of items.
pub fn num_items(&self) -> usize {
self.weights.len()
}
}

impl Problem for Knapsack {
const NAME: &'static str = "Knapsack";
type Metric = SolutionSize<i64>;

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

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

fn evaluate(&self, config: &[usize]) -> SolutionSize<i64> {
if config.len() != self.num_items() {
return SolutionSize::Invalid;
}
if config.iter().any(|&v| v >= 2) {
return SolutionSize::Invalid;
}
let total_weight: i64 = config
.iter()
.enumerate()
.filter(|(_, &x)| x == 1)
.map(|(i, _)| self.weights[i])
.sum();
if total_weight > self.capacity {
return SolutionSize::Invalid;
}
let total_value: i64 = config
.iter()
.enumerate()
.filter(|(_, &x)| x == 1)
.map(|(i, _)| self.values[i])
.sum();
SolutionSize::Valid(total_value)
}
}

impl OptimizationProblem for Knapsack {
type Value = i64;

fn direction(&self) -> Direction {
Direction::Maximize
}
}

crate::declare_variants! {
Knapsack => "2^(num_items / 2)",
}
Comment on lines +137 to +139
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reduction_graph.json is auto-generated (via cargo run --example export_graph or pred export-graph) but has not been regenerated to include the new Knapsack node. Other misc models like Factoring and PaintShop appear as nodes in this file even without reduction edges. Since the declare_variants! macro registers Knapsack in the variant registry, re-running the export should add it. Without this update, the paper's completeness check (which compares display-name keys against reduction_graph.json nodes) may produce a warning for the missing Knapsack entry.

Copilot uses AI. Check for mistakes.

#[cfg(test)]
#[path = "../../unit_tests/models/misc/knapsack.rs"]
mod tests;
3 changes: 3 additions & 0 deletions src/models/misc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
//! Problems with unique input structures that don't fit other categories:
//! - [`BinPacking`]: Bin Packing (minimize bins)
//! - [`Factoring`]: Integer factorization
//! - [`Knapsack`]: 0-1 Knapsack (maximize value subject to weight capacity)
//! - [`PaintShop`]: Minimize color switches in paint shop scheduling

mod bin_packing;
pub(crate) mod factoring;
mod knapsack;
pub(crate) mod paintshop;

pub use bin_packing::BinPacking;
pub use factoring::Factoring;
pub use knapsack::Knapsack;
pub use paintshop::PaintShop;
2 changes: 1 addition & 1 deletion src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ pub use graph::{
BicliqueCover, KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet,
MaximumMatching, MinimumDominatingSet, MinimumVertexCover, SpinGlass, TravelingSalesman,
};
pub use misc::{BinPacking, Factoring, PaintShop};
pub use misc::{BinPacking, Factoring, Knapsack, PaintShop};
Comment thread
zazabap marked this conversation as resolved.
pub use set::{MaximumSetPacking, MinimumSetCovering};
Loading