diff --git a/.github/workflows/build_algorithm.yml b/.github/workflows/build_algorithm.yml index 7c3ce36c..55de9552 100644 --- a/.github/workflows/build_algorithm.yml +++ b/.github/workflows/build_algorithm.yml @@ -41,17 +41,17 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-wasi - # - name: Install CUDA - # if: env.SKIP_JOB != 'true' - # uses: Jimver/cuda-toolkit@v0.2.9 - # 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/cuda-toolkit@v0.2.9 + 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: > diff --git a/docs/challenges/knapsack.md b/docs/challenges/knapsack.md index 4ad742d1..cecb097d 100644 --- a/docs/challenges/knapsack.md +++ b/docs/challenges/knapsack.md @@ -1,10 +1,10 @@ # 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]. @@ -12,43 +12,39 @@ For our challenge, we use a version of the knapsack problem with configurable di 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). - -Gene Clustering - - -
Figure 2: Microarray clustering of differentially expressed genes in blood. Genes are clustered in rows, with red indicating high expression, yellow intermediate expression and blue low expression. The Knapsack problem is used to analyse gene expression clustering.
-
+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. diff --git a/docs/guides/innovating.md b/docs/guides/innovating.md index 6de57cf1..86da3161 100644 --- a/docs/guides/innovating.md +++ b/docs/guides/innovating.md @@ -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 ::solve_challenge(&challenge) { Ok(Some(solution)) => match challenge.verify_solution(&solution) { Ok(_) => println!("Valid solution"), @@ -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 ::cuda_solve_challenge(&challenge, &dev, algorithm_cuda_funcs) { Ok(Some(solution)) => match challenge.verify_solution(&solution) { diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index bdd68d28..e65e238f 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -54,6 +54,7 @@ pub struct Challenge { pub difficulty: Difficulty, pub weights: Vec, pub values: Vec, + pub interaction_values: Vec>, pub max_weight: u32, pub min_value: u32, } @@ -77,32 +78,56 @@ impl crate::ChallengeTrait for Challenge { fn generate_instance(seed: [u8; 32], difficulty: &Difficulty) -> Result { let mut rng = SmallRng::from_seed(StdRng::from_seed(seed).gen()); + // Generate weights w_i in the range [1, 50] let weights: Vec = (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 = (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![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::() / 2; - // Baseline greedy algorithm - let mut sorted_value_to_weight_ratio: Vec = (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::(); + 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 { @@ -110,6 +135,7 @@ impl crate::ChallengeTrait for Challenge { difficulty: difficulty.clone(), weights, values, + interaction_values, max_weight, min_value, }) @@ -120,17 +146,19 @@ impl crate::ChallengeTrait 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::, _>>()? + .iter() .sum::(); + if total_weight > self.max_weight { return Err(anyhow!( "Total weight ({}) exceeded max weight ({})", @@ -138,10 +166,9 @@ impl crate::ChallengeTrait for Challenge { self.max_weight )); } - let total_value = selected_items - .iter() - .map(|&item| self.values[item]) - .sum::(); + let selected_items_vec: Vec = 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 ({})", @@ -153,3 +180,33 @@ impl crate::ChallengeTrait for Challenge { } } } + +pub fn calculate_total_value( + indices: &Vec, + values: &Vec, + interaction_values: &Vec>, +) -> 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, + } +} diff --git a/tig-challenges/src/satisfiability.rs b/tig-challenges/src/satisfiability.rs index 608ce5d5..532afb53 100644 --- a/tig-challenges/src/satisfiability.rs +++ b/tig-challenges/src/satisfiability.rs @@ -71,13 +71,13 @@ pub const KERNEL: Option = None; impl crate::ChallengeTrait for Challenge { #[cfg(feature = "cuda")] fn cuda_generate_instance( - seeds: [u64; 4], + seed: [u8; 32], difficulty: &Difficulty, dev: &Arc, mut funcs: HashMap<&'static str, CudaFunction>, ) -> Result { // 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 { diff --git a/tig-challenges/src/vector_search.rs b/tig-challenges/src/vector_search.rs index ba81a2b2..05f06c38 100644 --- a/tig-challenges/src/vector_search.rs +++ b/tig-challenges/src/vector_search.rs @@ -73,13 +73,13 @@ pub const KERNEL: Option = None; impl ChallengeTrait for Challenge { #[cfg(feature = "cuda")] fn cuda_generate_instance( - seeds: [u8; 32], + seed: [u8; 32], difficulty: &Difficulty, dev: &Arc, mut funcs: HashMap<&'static str, CudaFunction>, ) -> Result { // 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 { diff --git a/tig-challenges/src/vehicle_routing.rs b/tig-challenges/src/vehicle_routing.rs index d2d57d07..b4781374 100644 --- a/tig-challenges/src/vehicle_routing.rs +++ b/tig-challenges/src/vehicle_routing.rs @@ -64,13 +64,13 @@ pub const KERNEL: Option = None; impl crate::ChallengeTrait for Challenge { #[cfg(feature = "cuda")] fn cuda_generate_instance( - seeds: [u8; 32], + seed: [u8; 32], difficulty: &Difficulty, dev: &Arc, mut funcs: HashMap<&'static str, CudaFunction>, ) -> Result { // 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 {