Skip to content

Commit

Permalink
Merge branch 'blank_slate' into vehicle_routing/advanced_routing
Browse files Browse the repository at this point in the history
  • Loading branch information
FiveMovesAhead committed Oct 14, 2024
2 parents 84de83d + a668c34 commit 34bc8cc
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 72 deletions.
22 changes: 11 additions & 11 deletions .github/workflows/build_algorithm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasi
# - name: Install CUDA
# if: env.SKIP_JOB != 'true'
# uses: Jimver/[email protected]
# id: cuda-toolkit
# with:
# cuda: '12.1.0'
# method: network
# sub-packages: '["nvcc"]'
# - name: Cargo Build CUDA
# if: env.SKIP_JOB != 'true'
# run: cargo build -p tig-algorithms --release --features cuda
- name: Install CUDA
if: env.SKIP_JOB != 'true'
uses: Jimver/[email protected]
id: cuda-toolkit
with:
cuda: '12.1.0'
method: network
sub-packages: '["nvcc"]'
- name: Cargo Build CUDA
if: env.SKIP_JOB != 'true'
run: cargo build -p tig-algorithms --release --features cuda
- name: Cargo Build WASM
if: env.SKIP_JOB != 'true'
run: >
Expand Down
48 changes: 22 additions & 26 deletions docs/challenges/knapsack.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,50 @@
# Knapsack Problem

[The area of Knapsack problems is one of the most active research areas of combinatorial optimization](https://en.wikipedia.org/wiki/Knapsack_problem). The problem is to maximise the value of items placed in a knapsack given the constraint that the total weight of items cannot exceed some limit.
The quadratic knapsack problem is one of the most popular variants of the single knapsack problem with applications in many optimization problems. The aim is to maximise the value of individual items placed in the knapsack while satisfying a weight constraint. However, pairs of items also have interaction values which may be negative or positive that are added to the total value within the knapsack.

# Example

For our challenge, we use a version of the knapsack problem with configurable difficulty, where the following two parameters can be adjusted in order to vary the difficulty of the challenge:
For our challenge, we use a version of the quadratic knapsack problem with configurable difficulty, where the following two parameters can be adjusted in order to vary the difficulty of the challenge:

- Parameter 1: $num\textunderscore{ }items$ is the number of items from which you need to select a subset to put in the knapsack.
- Parameter 2: $better\textunderscore{ }than\textunderscore{ }baseline \geq 1$ is the factor by which a solution must be better than the baseline value [link TIG challenges for explanation of baseline value].


The larger the $num\textunderscore{ }items$, the more number of possible $S_{knapsack}$, making the challenge more difficult. Also, the higher $better\textunderscore{ }than\textunderscore{ }baseline$, the less likely a given $S_{knapsack}$ will be a solution, making the challenge more difficult.

The weight $w_j$ of each of the $num\textunderscore{ }items$ is an integer, chosen independently, uniformly at random, and such that each of the item weights $1 <= w_j <= 50$, for $j=1,2,...,num\textunderscore{ }items$. The values of the items $v_j$ are similarly selected at random from the same distribution.
The weight $w_i$ of each of the $num\textunderscore{ }items$ is an integer, chosen independently, uniformly at random, and such that each of the item weights $1 <= w_i <= 50$, for $i=1,2,...,num\textunderscore{ }items$. The individual values of the items $v_i$ are selected by random from the range $50 <= v_i <= 100$, and the interaction values of pairs of items $V_{ij}$ are selected by random from the range $-50 <= V_{ij} <= 50$.

The total value of a knapsack is determined by summing up the individual values of items in the knapsack, as well as the interaction values of every pair of items $(i,j)$ where $i > j$ in the knapsack:

$$V_{knapsack} = \sum_{i \in knapsack}{v_i} + \sum_{(i,j)\in knapsack}{V_{ij}}$$

We impose a weight constraint $W(S_{knapsack}) <= 0.5 \cdot W(S_{all})$, where the knapsack can hold at most half the total weight of all items.


Consider an example of a challenge instance with `num_items=6` and `better_than_baseline = 1.09`. Let the baseline value be 100:
Consider an example of a challenge instance with `num_items=4` and `better_than_baseline = 1.10`. Let the baseline value be 150:

```
weights = [48, 20, 39, 13, 25, 16]
values = [24, 42, 27, 31, 44, 31]
max_weight = 80
min_value = baseline*better_than_baseline = 109
weights = [26, 20, 39, 13]
individual_values = [63, 87, 52, 97]
interaction_values = [ 0, 23, -18, -37
23, 0, 42, -28
-18, 42, 0, 32
-37, -28, 32, 0]
max_weight = 60
min_value = baseline*better_than_baseline = 165
```
The objective is to find a set of items where the total weight is at most 80 but has a total value of at least 109.
The objective is to find a set of items where the total weight is at most 60 but has a total value of at least 165.

Now consider the following selection:

```
selected_items = [1, 3, 4, 5]
selected_items = [0, 1, 3]
```

When evaluating this selection, we can confirm that the total weight is less than 80, and the total value is more than 109, thereby this selection of items is a solution:
When evaluating this selection, we can confirm that the total weight is less than 60, and the total value is more than 165, thereby this selection of items is a solution:

* Total weight = 20 + 13 + 25 + 16 = 74
* Total value = 42 + 31 + 44 + 31 = 148
* Total weight = 26 + 20 + 13 = 59
* Total value = 63 + 52 + 97 + 23 - 37 - 28 = 170

# Our Challenge
In TIG, the baseline value is determined by a greedy algorithm that simply iterates through items sorted by value to weight ratio, adding them if knapsack is still below the weight constraint.

# Applications

The Knapsack problems have a wide variety of practical applications. The [use of knapsack in integer programming](https://www.sciencedirect.com/science/article/pii/0012365X71900057) led to break thoughs in several disciplines, including [energy management](https://www.sciencedirect.com/science/article/abs/pii/S0301421513003418) and [cellular network frequency planning](https://www.slideshare.net/deepakecrbs/gsm-frequency-planning).

Although originally studied in the context of logistics, Knapsack problems appear regularly in diverse areas of science and technology. For example, in gene expression data, there are usually thousands of genes, but only a subset of them are informative for a specific problem. The Knapsack Problem can be used to select a subset of genes (items) that maximises the total information (value) without exceeding the limit of the number of genes that can be included in the analysis (weight limit).

<img src="../images/gene_clustering.jfif" alt="Gene Clustering" width="100%"/>


<figcaption>Figure 2: <a href="https://openres.ersjournals.com/content/4/4/00031-2018" target="_blank">Microarray clustering of differentially expressed genes in blood</a>. Genes are clustered in rows, with red indicating high expression, yellow intermediate expression and blue low expression. The Knapsack problem is <a href="https://www.sciencedirect.com/science/article/abs/pii/S0305054821003877" target="_blank">used to analyse</a> gene expression clustering.</figcaption>
<br/>
In TIG, the baseline value is determined by a greedy algorithm that simply iterates through items sorted by potential value to weight ratio, adding them if knapsack is still below the weight constraint.
8 changes: 4 additions & 4 deletions docs/guides/innovating.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ language governing permissions and limitations under the License.
// num_queries: 10,
// better_than_baseline: 350,
};
let seeds = [0; 8]; // change this to generate different instances
let challenge = Challenge::generate_instance(seeds, &difficulty).unwrap();
let seed = [0u8; 32]; // change this to generate different instances
let challenge = Challenge::generate_instance(seed, &difficulty).unwrap();
match <algorithm_name>::solve_challenge(&challenge) {
Ok(Some(solution)) => match challenge.verify_solution(&solution) {
Ok(_) => println!("Valid solution"),
Expand Down Expand Up @@ -215,9 +215,9 @@ mod cuda_tests {
// num_queries: 10,
// better_than_baseline: 350,
};
let seeds = [0; 8]; // change this to generate different instances
let seed = [0u8; 32]; // change this to generate different instances
let challenge =
Challenge::cuda_generate_instance(seeds, &difficulty, &dev, challenge_cuda_funcs)
Challenge::cuda_generate_instance(seed, &difficulty, &dev, challenge_cuda_funcs)
.unwrap();
match <algorithm_name>::cuda_solve_challenge(&challenge, &dev, algorithm_cuda_funcs) {
Ok(Some(solution)) => match challenge.verify_solution(&solution) {
Expand Down
107 changes: 82 additions & 25 deletions tig-challenges/src/knapsack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct Challenge {
pub difficulty: Difficulty,
pub weights: Vec<u32>,
pub values: Vec<u32>,
pub interaction_values: Vec<Vec<i32>>,
pub max_weight: u32,
pub min_value: u32,
}
Expand All @@ -77,39 +78,64 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
fn generate_instance(seed: [u8; 32], difficulty: &Difficulty) -> Result<Challenge> {
let mut rng = SmallRng::from_seed(StdRng::from_seed(seed).gen());

// Generate weights w_i in the range [1, 50]
let weights: Vec<u32> = (0..difficulty.num_items)
.map(|_| rng.gen_range(1..50))
.map(|_| rng.gen_range(1..=50))
.collect();
// Generate values v_i in the range [50, 100]
let values: Vec<u32> = (0..difficulty.num_items)
.map(|_| rng.gen_range(1..50))
.map(|_| rng.gen_range(50..=100))
.collect();

// Generate interactive values V_ij in the range [1, 50], with V_ij == V_ji and V_ij where i==j is 0.
let mut interaction_values: Vec<Vec<i32>> =
vec![vec![0; difficulty.num_items]; difficulty.num_items];
for i in 0..difficulty.num_items {
for j in (i + 1)..difficulty.num_items {
let value = rng.gen_range(-50..=50);
interaction_values[i][j] = value;
interaction_values[j][i] = value;
}
}

let max_weight: u32 = weights.iter().sum::<u32>() / 2;

// Baseline greedy algorithm
let mut sorted_value_to_weight_ratio: Vec<usize> = (0..difficulty.num_items).collect();
sorted_value_to_weight_ratio.sort_by(|&a, &b| {
let ratio_a = values[a] as f64 / weights[a] as f64;
let ratio_b = values[b] as f64 / weights[b] as f64;
// Precompute the ratio between the total value (value + sum of interactive values) and
// weight for each item. Pair the ratio with the item's weight and index
let mut value_weight_ratios: Vec<(usize, f32, u32)> = (0..difficulty.num_items)
.map(|i| {
let total_value = values[i] as i32 + interaction_values[i].iter().sum::<i32>();
let weight = weights[i];
let ratio = total_value as f32 / weight as f32;
(i, ratio, weight)
})
.collect();

// Sort the list of tuples by value-to-weight ratio in descending order
value_weight_ratios.sort_unstable_by(|&(_, ratio_a, _), &(_, ratio_b, _)| {
ratio_b.partial_cmp(&ratio_a).unwrap()
});

let mut total_weight = 0;
let mut min_value = 0;
for &item in &sorted_value_to_weight_ratio {
if total_weight + weights[item] > max_weight {
continue;
let mut selected_indices = Vec::new();
for &(i, _, weight) in &value_weight_ratios {
if total_weight + weight <= max_weight {
selected_indices.push(i);
total_weight += weight;
}
min_value += values[item];
total_weight += weights[item];
}
min_value = (min_value as f64 * (1.0 + difficulty.better_than_baseline as f64 / 1000.0))
selected_indices.sort_unstable();

let mut min_value = calculate_total_value(&selected_indices, &values, &interaction_values);
min_value = (min_value as f32 * (1.0 + difficulty.better_than_baseline as f32 / 1000.0))
.round() as u32;

Ok(Challenge {
seed,
difficulty: difficulty.clone(),
weights,
values,
interaction_values,
max_weight,
min_value,
})
Expand All @@ -120,28 +146,29 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
if selected_items.len() != solution.items.len() {
return Err(anyhow!("Duplicate items selected."));
}
if let Some(item) = selected_items
.iter()
.find(|&&item| item >= self.weights.len())
{
return Err(anyhow!("Item ({}) is out of bounds", item));
}

let total_weight = selected_items
.iter()
.map(|&item| self.weights[item])
.map(|&item| {
if item >= self.weights.len() {
return Err(anyhow!("Item ({}) is out of bounds", item));
}
Ok(self.weights[item])
})
.collect::<Result<Vec<_>, _>>()?
.iter()
.sum::<u32>();

if total_weight > self.max_weight {
return Err(anyhow!(
"Total weight ({}) exceeded max weight ({})",
total_weight,
self.max_weight
));
}
let total_value = selected_items
.iter()
.map(|&item| self.values[item])
.sum::<u32>();
let selected_items_vec: Vec<usize> = selected_items.into_iter().collect();
let total_value =
calculate_total_value(&selected_items_vec, &self.values, &self.interaction_values);
if total_value < self.min_value {
Err(anyhow!(
"Total value ({}) does not reach minimum value ({})",
Expand All @@ -153,3 +180,33 @@ impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
}
}
}

pub fn calculate_total_value(
indices: &Vec<usize>,
values: &Vec<u32>,
interaction_values: &Vec<Vec<i32>>,
) -> u32 {
let mut indices = indices.clone();
indices.sort_unstable();

let mut total_value = 0i32;

// Sum the individual values
for &i in &indices {
total_value += values[i] as i32;
}

// Sum the interactive values for pairs in indices
for i in 0..indices.len() {
for j in (i + 1)..indices.len() {
let idx_i = indices[i];
let idx_j = indices[j];
total_value += interaction_values[idx_i][idx_j];
}
}

match total_value {
v if v < 0 => 0u32,
v => v as u32,
}
}
4 changes: 2 additions & 2 deletions tig-challenges/src/satisfiability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ pub const KERNEL: Option<CudaKernel> = None;
impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
#[cfg(feature = "cuda")]
fn cuda_generate_instance(
seeds: [u64; 4],
seed: [u8; 32],
difficulty: &Difficulty,
dev: &Arc<CudaDevice>,
mut funcs: HashMap<&'static str, CudaFunction>,
) -> Result<Self> {
// TIG dev bounty available for a GPU optimisation for instance generation!
Self::generate_instance(seeds, difficulty)
Self::generate_instance(seed, difficulty)
}

fn generate_instance(seed: [u8; 32], difficulty: &Difficulty) -> Result<Self> {
Expand Down
4 changes: 2 additions & 2 deletions tig-challenges/src/vector_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ pub const KERNEL: Option<CudaKernel> = None;
impl ChallengeTrait<Solution, Difficulty, 2> for Challenge {
#[cfg(feature = "cuda")]
fn cuda_generate_instance(
seeds: [u8; 32],
seed: [u8; 32],
difficulty: &Difficulty,
dev: &Arc<CudaDevice>,
mut funcs: HashMap<&'static str, CudaFunction>,
) -> Result<Self> {
// TIG dev bounty available for a GPU optimisation for instance generation!
Self::generate_instance(seeds, difficulty)
Self::generate_instance(seed, difficulty)
}

fn generate_instance(seed: [u8; 32], difficulty: &Difficulty) -> Result<Self> {
Expand Down
4 changes: 2 additions & 2 deletions tig-challenges/src/vehicle_routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ pub const KERNEL: Option<CudaKernel> = None;
impl crate::ChallengeTrait<Solution, Difficulty, 2> for Challenge {
#[cfg(feature = "cuda")]
fn cuda_generate_instance(
seeds: [u8; 32],
seed: [u8; 32],
difficulty: &Difficulty,
dev: &Arc<CudaDevice>,
mut funcs: HashMap<&'static str, CudaFunction>,
) -> Result<Self> {
// TIG dev bounty available for a GPU optimisation for instance generation!
Self::generate_instance(seeds, difficulty)
Self::generate_instance(seed, difficulty)
}

fn generate_instance(seed: [u8; 32], difficulty: &Difficulty) -> Result<Challenge> {
Expand Down

0 comments on commit 34bc8cc

Please sign in to comment.