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
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: [1.76.0, stable]
rust: [1.85.0, stable]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
Expand Down
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "rpid"
version = "0.1.0"
edition = "2021"
rust-version = "1.76"
version = "0.2.0"
edition = "2024"
rust-version = "1.85"
description = "Rust Programmable Interface for Domain-Independent Dynamic Programming (RPID)"
license = "MIT OR Apache-2.0"
authors = ["Ryo Kuroiwa <kuroiwa@nii.ac.jp>"]
Expand All @@ -13,6 +13,7 @@ rustc-hash = "2.1"
smallvec = "1.15"
num-traits = "0.2"
itertools = "0.14"
ordered-float = "5.0"

[dev-dependencies]
fixedbitset = "0.5"
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ struct TspState {
impl Dp for Tsp {
type State = TspState;
type CostType = i32;
type Label = usize;

fn get_target(&self) -> TspState {
fn get_target(&self) -> Self::State {
let mut unvisited = FixedBitSet::with_capacity(self.c.len());
unvisited.insert_range(1..);

Expand All @@ -36,7 +37,10 @@ impl Dp for Tsp {
}
}

fn get_successors(&self, state: &TspState) -> impl IntoIterator<Item = (TspState, i32, usize)> {
fn get_successors(
&self,
state: &Self::State,
) -> impl IntoIterator<Item = (Self::State, Self::CostType, Self::Label)> {
state.unvisited.ones().map(|next| {
let mut unvisited = state.unvisited.clone();
unvisited.remove(next);
Expand All @@ -51,7 +55,7 @@ impl Dp for Tsp {
})
}

fn get_base_cost(&self, state: &TspState) -> Option<i32> {
fn get_base_cost(&self, state: &Self::State) -> Option<Self::CostType> {
if state.unvisited.is_clear() {
Some(self.c[state.current][0])
} else {
Expand All @@ -64,7 +68,7 @@ impl Dominance for Tsp {
type State = TspState;
type Key = (FixedBitSet, usize);

fn get_key(&self, state: &TspState) -> Self::Key {
fn get_key(&self, state: &Self::State) -> Self::Key {
(state.unvisited.clone(), state.current)
}
}
Expand All @@ -73,7 +77,7 @@ impl Bound for Tsp {
type State = TspState;
type CostType = i32;

fn get_dual_bound(&self, _: &TspState) -> Option<i32> {
fn get_dual_bound(&self, _: &Self::State) -> Option<Self::CostType> {
Some(0)
}
}
Expand Down
145 changes: 34 additions & 111 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
//!
//! The algorithms in this module are useful for preprocessing and relaxation.

use num_traits::{One, Zero};
use num_traits::Zero;
use ordered_float::OrderedFloat;
use std::cmp::Ordering;
use std::iter::Sum;
use std::ops::{Add, Div, Rem, SubAssign};
use std::ops::{Add, SubAssign};

/// Transpose an m x n matrix.
///
Expand Down Expand Up @@ -834,9 +834,8 @@ where
.enumerate()
.map(|(i, (w, v))| (i, w, v))
.collect::<Vec<_>>();
sorted_weight_value_pairs.sort_by(|&a, &b| {
(f64::from(a.1) * f64::from(b.2)).total_cmp(&(f64::from(a.2) * f64::from(b.1)))
});
sorted_weight_value_pairs
.sort_by_key(|&(_, w, v)| OrderedFloat::from(f64::from(w) / f64::from(v)));

sorted_weight_value_pairs
}
Expand All @@ -854,20 +853,20 @@ where
/// # Examples
///
/// ```
/// use approx::assert_relative_eq;
/// use rpid::algorithms;
///
/// let weights = [1, 2, 4];
/// let values = [2, 3, 5];
///
/// let sorted_items = algorithms::sort_knapsack_items_by_efficiency(&weights, &values);
/// let sorted_weight_value_pairs = sorted_items.iter().map(|&(_, w, v)| (w, v));
/// let profit = algorithms::compute_fractional_knapsack_profit(5, sorted_weight_value_pairs, 1e-6);
/// assert_eq!(profit, 7.0);
/// let profit = algorithms::compute_fractional_knapsack_profit(5, sorted_weight_value_pairs);
/// assert_relative_eq!(profit, 7.5);
/// ```
pub fn compute_fractional_knapsack_profit<T>(
capacity: T,
sorted_weight_value_pairs: impl Iterator<Item = (T, T)>,
epsilon: f64,
) -> f64
where
T: PartialOrd + SubAssign + Copy,
Expand All @@ -886,134 +885,90 @@ where
}
}

f64::floor(profit + epsilon)
}

/// Computes the number of bins to pack weighted items, allowing fractions of items to be taken.
///
/// This bound is sometimes called LB1.
///
/// `epsilon` is the upper bound on the difference between two values to be considered equal.
/// If the cost of the fractional bin packing is `c`, the number of bins is `ceil(c - epsilon)`.
///
/// # Examples
///
/// ```
/// use rpid::algorithms;
///
/// let weights = [2, 2, 3, 4, 5];
///
/// let n_bins = algorithms::compute_fractional_bin_packing_cost(5, weights.iter().sum(), 0);
/// assert_eq!(n_bins, 4.0);
/// ```
pub fn compute_fractional_bin_packing_cost<T>(capacity: T, weight_sum: T, epsilon: T) -> f64
where
T: Sum<T> + Rem<Output = T> + Div<Output = T> + PartialOrd + Copy + One,
f64: From<T>,
{
if capacity <= epsilon {
return f64::INFINITY;
}

if weight_sum % capacity <= epsilon {
f64::from(weight_sum / capacity).trunc()
} else {
f64::from(weight_sum / capacity).trunc() + 1.0
}
profit
}

/// Computes the number of bins to pack items, whose weights are at least half of the capacity.
///
/// This bound is sometimes called LB2.
///
/// `epsilon` is the upper bound on the difference between two values to be considered equal.
///
/// # Examples
///
/// ```
/// use rpid::algorithms;
///
/// let weights = [4, 2, 3, 5, 4, 3, 3];
///
/// let n_bins = algorithms::compute_bin_packing_lb2(6, weights.iter().copied(), 0);
/// assert_eq!(n_bins, 5.0);
/// let n_bins = algorithms::compute_bin_packing_lb2(6, weights.iter().copied());
/// assert_eq!(n_bins, 5);
/// ```
pub fn compute_bin_packing_lb2<T>(capacity: T, weights: impl Iterator<Item = T>, epsilon: T) -> f64
pub fn compute_bin_packing_lb2<T>(capacity: T, weights: impl Iterator<Item = T>) -> usize
where
T: PartialOrd + Add<Output = T> + Copy,
{
if capacity <= epsilon {
return f64::INFINITY;
}

let mut n_more_than_half = 0;
let mut n_half = 0;

weights.for_each(|w| {
let twice = w + w;

if twice > capacity + epsilon {
if twice > capacity {
n_more_than_half += 1;
} else if twice + epsilon >= capacity {
}
if twice == capacity {
n_half += 1;
}
});

if n_half % 2 == 0 {
(n_more_than_half + n_half / 2) as f64
n_more_than_half + n_half / 2
} else {
(n_more_than_half + n_half / 2 + 1) as f64
n_more_than_half + n_half / 2 + 1
}
}

/// Computes the number of bins to pack items, whose weights are at least one third of the capacity.
///
/// This bound is sometimes called LB3.
///
/// `epsilon` is the upper bound on the difference between two values to be considered equal.
///
/// # Examples
///
/// ```
/// use rpid::algorithms;
///
/// let weights = [4, 2, 3, 5, 4, 3, 3];
///
/// let n_bins = algorithms::compute_bin_packing_lb3(6, weights.iter().copied(), 0);
/// assert_eq!(n_bins, 5.0);
/// let n_bins = algorithms::compute_bin_packing_lb3(6, weights.iter().copied());
/// assert_eq!(n_bins, 5);
/// ```
pub fn compute_bin_packing_lb3<T>(capacity: T, weights: impl Iterator<Item = T>, epsilon: T) -> f64
pub fn compute_bin_packing_lb3<T>(capacity: T, weights: impl Iterator<Item = T>) -> usize
where
T: PartialOrd + Add<Output = T> + Copy,
{
if capacity <= epsilon {
return f64::INFINITY;
}

let twice_capacity = capacity + capacity;

let weight_sum = weights
.map(|w| {
let thrice = w + w + w;

if thrice > twice_capacity + epsilon {
if thrice > twice_capacity {
6
} else if thrice + epsilon >= twice_capacity {
} else if thrice >= twice_capacity {
4
} else if thrice > capacity + epsilon {
} else if thrice > capacity {
3
} else if thrice + epsilon >= capacity {
} else if thrice >= capacity {
2
} else {
0
}
})
.sum::<i32>();
.sum::<usize>();

if weight_sum % 6 == 0 {
(weight_sum / 6) as f64
weight_sum / 6
} else {
(weight_sum / 6 + 1) as f64
weight_sum / 6 + 1
}
}

Expand Down Expand Up @@ -1359,73 +1314,41 @@ mod tests {
fn test_compute_fractional_knapsack_profit() {
let sorted_weight_value_pairs = [(1, 2), (2, 3), (4, 5), (1, 1)];
assert_relative_eq!(
compute_fractional_knapsack_profit(5, sorted_weight_value_pairs.into_iter(), 1e-6),
7.0
compute_fractional_knapsack_profit(5, sorted_weight_value_pairs.into_iter()),
7.5
);
}

#[test]
fn test_compute_fractional_knapsack_profit_empty() {
let sorted_weight_value_pairs: [(i32, i32); 0] = [];
assert_relative_eq!(
compute_fractional_knapsack_profit(0, sorted_weight_value_pairs.into_iter(), 1e-6),
compute_fractional_knapsack_profit(0, sorted_weight_value_pairs.into_iter()),
0.0
);
}

#[test]
fn test_compute_fractional_bin_packing_cost() {
let weights = [2, 2, 3, 4, 5];
assert_relative_eq!(
compute_fractional_bin_packing_cost(5, weights.iter().sum(), 0),
4.0
);
}

#[test]
fn test_compute_fractional_bin_packing_cost_empty() {
assert_relative_eq!(compute_fractional_bin_packing_cost(5, 0, 0), 0.0);
}

#[test]
fn test_compute_fractional_bin_packing_cost_infinity() {
let weights = [2, 2, 3, 4, 5];
assert!(compute_fractional_bin_packing_cost(0, weights.iter().sum(), 0).is_infinite());
}

#[test]
fn test_compute_bin_packing_lb2() {
let weights = [4, 2, 3, 5, 4, 3, 3];
assert_relative_eq!(compute_bin_packing_lb2(6, weights.into_iter(), 0), 5.0);
assert_eq!(compute_bin_packing_lb2(6, weights.into_iter()), 5);
}

#[test]
fn test_compute_bin_packing_lb2_empty() {
let weights: [i32; 0] = [];
assert_relative_eq!(compute_bin_packing_lb2(6, weights.into_iter(), 0), 0.0);
}

#[test]
fn test_compute_bin_packing_lb2_infinity() {
let weights = [4, 2, 3, 5, 4, 3, 3];
assert!(compute_bin_packing_lb2(0, weights.into_iter(), 0).is_infinite());
assert_eq!(compute_bin_packing_lb2(6, weights.into_iter()), 0);
}

#[test]
fn test_compute_bin_packing_lb3() {
let weights = [4, 2, 3, 5, 4, 3, 3];
assert_relative_eq!(compute_bin_packing_lb3(6, weights.into_iter(), 0), 5.0);
assert_eq!(compute_bin_packing_lb3(6, weights.into_iter()), 5);
}

#[test]
fn test_compute_bin_packing_lb3_empty() {
let weights: [i32; 0] = [];
assert_relative_eq!(compute_bin_packing_lb3(6, weights.into_iter(), 0), 0.0);
}

#[test]
fn test_compute_bin_packing_lb3_infinity() {
let weights = [4, 2, 3, 5, 4, 3, 3];
assert!(compute_bin_packing_lb3(0, weights.into_iter(), 0).is_infinite());
assert_eq!(compute_bin_packing_lb3(6, weights.into_iter()), 0);
}
}
Loading
Loading