From eeec85ae5bb7b57646cb878f4c4e59ac5fb66ccf Mon Sep 17 00:00:00 2001 From: Fatigue24 Date: Sun, 3 Nov 2024 02:29:14 +0200 Subject: [PATCH 01/49] Improving the baseline algorithm for QKP --- tig-challenges/src/knapsack.rs | 126 +++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 15 deletions(-) diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index e65e238..a354c7e 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -102,31 +102,127 @@ impl crate::ChallengeTrait for Challenge { // 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) + let mut item_values: Vec<(usize, f32)> = (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) + let ratio = total_value as f32 / weights[i] as f32; + (i, ratio) }) .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() - }); + // Sort the list of ratios in descending order + item_values.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + // Step 1: Initial solution obtained by greedily selecting items based on value-weight ratio + let mut selected_items = Vec::with_capacity(difficulty.num_items); + let mut unselected_items = Vec::with_capacity(difficulty.num_items); let mut total_weight = 0; - 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; + let mut total_value = 0; + + for &(item, _) in &item_values { + if total_weight + weights[item] <= max_weight { + total_weight += weights[item]; + total_value += values[item] as i32; + + for &prev_item in &selected_items { + total_value += interaction_values[item][prev_item]; + } + selected_items.push(item); + } else { + unselected_items.push(item); } } - selected_indices.sort_unstable(); + + // Step 2: Improvement of solution with Local Search and Tabu-List + // Precompute sum of interaction values with each selected item for all items(if i==j, then interaction_values[i][j] == 0) + let mut interaction_sum_list = vec![0; difficulty.num_items]; + for x in 0..difficulty.num_items { + interaction_sum_list[x] = values[x] as i32; + for &item in &selected_items { + interaction_sum_list[x] += interaction_values[x][item]; + } + } + + // Optimized local search with tabu list + let max_iterations = 100; + let mut tabu_list = vec![0; difficulty.num_items]; - let mut min_value = calculate_total_value(&selected_indices, &values, &interaction_values); + for _ in 0..max_iterations { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 0..unselected_items.len() { + let new_item = unselected_items[i]; + if tabu_list[new_item] > 0 { + continue; + } + + let new_item_values_sum = interaction_sum_list[new_item]; + // Greedy assumption that remove_item_values_sum + interaction_values[new_item][remove_item] is higher or equal to zero + // It's not only a huge optimization, but it also produces better solutions most of the time + if new_item_values_sum < best_improvement { + continue; + } + + // Compute minimal weight of remove_item required to put new_item + let min_weight = weights[new_item] as i32 - (max_weight as i32 - total_weight as i32); + for j in 0..selected_items.len() { + let remove_item = selected_items[j]; + if tabu_list[remove_item] > 0 { + continue; + } + + // Don't check the weight if there is enough remaining capacity + if min_weight < 0 + { + // Skip a remove_item if the remaining capacity after removal is insufficient to push a new_item + let removed_item_weight = weights[remove_item] as i32; + if removed_item_weight < min_weight { + continue; + } + } + + let remove_item_values_sum = interaction_sum_list[remove_item]; + let value_diff = new_item_values_sum - remove_item_values_sum - interaction_values[new_item][remove_item]; + + if value_diff > best_improvement { + best_improvement = value_diff; + best_swap = Some((i, j)); + } + } + } + + if let Some((unselected_index, selected_index)) = best_swap { + let new_item = unselected_items[unselected_index]; + let remove_item = selected_items[selected_index]; + + selected_items.swap_remove(selected_index); + unselected_items.swap_remove(unselected_index); + selected_items.push(new_item); + unselected_items.push(remove_item); + + total_value += best_improvement; + total_weight = total_weight + weights[new_item] - weights[remove_item]; + + // Update sum of interaction values after swapping items + for x in 0..difficulty.num_items { + interaction_sum_list[x] += interaction_values[x][new_item] - interaction_values[x][remove_item]; + } + + // Update tabu list + tabu_list[new_item] = 3; + tabu_list[remove_item] = 3; + } else { + break; // No improvement found, terminate local search + } + + // Decrease tabu counters + for t in tabu_list.iter_mut() { + *t = if *t > 0 { *t - 1 } else { 0 }; + } + } + + let mut min_value = calculate_total_value(&selected_items, &values, &interaction_values); min_value = (min_value as f32 * (1.0 + difficulty.better_than_baseline as f32 / 1000.0)) .round() as u32; From cd12b6f72d3c01af67cf01be666393fc6c764f82 Mon Sep 17 00:00:00 2001 From: Fatigue24 Date: Sun, 3 Nov 2024 02:58:48 +0200 Subject: [PATCH 02/49] fixed condition for skipping weight check --- tig-challenges/src/knapsack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index a354c7e..d4e833a 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -173,7 +173,7 @@ impl crate::ChallengeTrait for Challenge { } // Don't check the weight if there is enough remaining capacity - if min_weight < 0 + if min_weight >= 0 { // Skip a remove_item if the remaining capacity after removal is insufficient to push a new_item let removed_item_weight = weights[remove_item] as i32; From fea23bf2219c50d6e1f705f028809e46e38b0599 Mon Sep 17 00:00:00 2001 From: Fatigue24 Date: Wed, 6 Nov 2024 18:23:50 +0200 Subject: [PATCH 03/49] Align knapsack instance generation with academic benchmarks --- tig-challenges/src/knapsack.rs | 84 +++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/tig-challenges/src/knapsack.rs b/tig-challenges/src/knapsack.rs index d4e833a..06a5af3 100644 --- a/tig-challenges/src/knapsack.rs +++ b/tig-challenges/src/knapsack.rs @@ -77,29 +77,48 @@ 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()); - + + // Set constant density for value generation + let density = 0.25; + // Generate weights w_i in the range [1, 50] let weights: Vec = (0..difficulty.num_items) .map(|_| rng.gen_range(1..=50)) .collect(); - // Generate values v_i in the range [50, 100] + + // Generate values v_i in the range [1, 100] with density probability, 0 otherwise let values: Vec = (0..difficulty.num_items) - .map(|_| rng.gen_range(50..=100)) + .map(|_| { + if rng.gen_bool(density) { + rng.gen_range(1..=100) + } else { + 0 + } + }) .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]; + + // Generate interaction values V_ij with the following properties: + // - V_ij == V_ji (symmetric matrix) + // - V_ii == 0 (diagonal is zero) + // - Values are in range [1, 100] with density probability, 0 otherwise + 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); + let value = if rng.gen_bool(density) { + rng.gen_range(1..=100) + } else { + 0 + }; + + // Set both V_ij and V_ji due to symmetry interaction_values[i][j] = value; interaction_values[j][i] = value; } } - + let max_weight: u32 = weights.iter().sum::() / 2; - + // 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 item_values: Vec<(usize, f32)> = (0..difficulty.num_items) @@ -109,15 +128,16 @@ impl crate::ChallengeTrait for Challenge { (i, ratio) }) .collect(); - + // Sort the list of ratios in descending order item_values.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); - + // Step 1: Initial solution obtained by greedily selecting items based on value-weight ratio let mut selected_items = Vec::with_capacity(difficulty.num_items); let mut unselected_items = Vec::with_capacity(difficulty.num_items); let mut total_weight = 0; let mut total_value = 0; + let mut is_selected = vec![false; difficulty.num_items]; for &(item, _) in &item_values { if total_weight + weights[item] <= max_weight { @@ -128,13 +148,14 @@ impl crate::ChallengeTrait for Challenge { total_value += interaction_values[item][prev_item]; } selected_items.push(item); + is_selected[item] = true; } else { unselected_items.push(item); } } // Step 2: Improvement of solution with Local Search and Tabu-List - // Precompute sum of interaction values with each selected item for all items(if i==j, then interaction_values[i][j] == 0) + // Precompute sum of interaction values with each selected item for all items let mut interaction_sum_list = vec![0; difficulty.num_items]; for x in 0..difficulty.num_items { interaction_sum_list[x] = values[x] as i32; @@ -143,10 +164,17 @@ impl crate::ChallengeTrait for Challenge { } } + let mut min_selected_item_values = i32::MAX; + for x in 0..difficulty.num_items { + if is_selected[x] { + min_selected_item_values = min_selected_item_values.min(interaction_sum_list[x]); + } + } + // Optimized local search with tabu list let max_iterations = 100; let mut tabu_list = vec![0; difficulty.num_items]; - + for _ in 0..max_iterations { let mut best_improvement = 0; let mut best_swap = None; @@ -158,30 +186,27 @@ impl crate::ChallengeTrait for Challenge { } let new_item_values_sum = interaction_sum_list[new_item]; - // Greedy assumption that remove_item_values_sum + interaction_values[new_item][remove_item] is higher or equal to zero - // It's not only a huge optimization, but it also produces better solutions most of the time - if new_item_values_sum < best_improvement { + if new_item_values_sum < best_improvement + min_selected_item_values { continue; } - + // Compute minimal weight of remove_item required to put new_item - let min_weight = weights[new_item] as i32 - (max_weight as i32 - total_weight as i32); + let min_weight = weights[new_item] as i32 - (max_weight as i32 - total_weight as i32); for j in 0..selected_items.len() { let remove_item = selected_items[j]; if tabu_list[remove_item] > 0 { continue; } - + // Don't check the weight if there is enough remaining capacity - if min_weight >= 0 - { + if min_weight > 0 { // Skip a remove_item if the remaining capacity after removal is insufficient to push a new_item let removed_item_weight = weights[remove_item] as i32; if removed_item_weight < min_weight { continue; } } - + let remove_item_values_sum = interaction_sum_list[remove_item]; let value_diff = new_item_values_sum - remove_item_values_sum - interaction_values[new_item][remove_item]; @@ -201,12 +226,19 @@ impl crate::ChallengeTrait for Challenge { selected_items.push(new_item); unselected_items.push(remove_item); + is_selected[new_item] = true; + is_selected[remove_item] = false; + total_value += best_improvement; total_weight = total_weight + weights[new_item] - weights[remove_item]; // Update sum of interaction values after swapping items + min_selected_item_values = i32::MAX; for x in 0..difficulty.num_items { interaction_sum_list[x] += interaction_values[x][new_item] - interaction_values[x][remove_item]; + if is_selected[x] { + min_selected_item_values = min_selected_item_values.min(interaction_sum_list[x]); + } } // Update tabu list @@ -215,17 +247,17 @@ impl crate::ChallengeTrait for Challenge { } else { break; // No improvement found, terminate local search } - + // Decrease tabu counters for t in tabu_list.iter_mut() { *t = if *t > 0 { *t - 1 } else { 0 }; } } - + let mut min_value = calculate_total_value(&selected_items, &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(), From 74c31623b90850dc1352d6644eb1ce6c3e4a43cf Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sat, 28 Dec 2024 22:43:30 +0800 Subject: [PATCH 04/49] Add jekyll config to ignore symbolic links. --- _config.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 _config.yml diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..48336a4 --- /dev/null +++ b/_config.yml @@ -0,0 +1,2 @@ +exclude: + - tig-benchmarker/ \ No newline at end of file From 236437717404edbf4b307a6767c7eac3383b40a0 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sun, 29 Dec 2024 03:09:49 +0800 Subject: [PATCH 05/49] Add script to calc apy. --- tig-benchmarker/calc_apy.py | 112 ++++++++++++++++++ .../{calc_rewards.py => calc_reward_share.py} | 66 +---------- tig-benchmarker/common/calcs.py | 102 ++++++++++++++++ tig-benchmarker/common/structs.py | 5 +- 4 files changed, 221 insertions(+), 64 deletions(-) create mode 100644 tig-benchmarker/calc_apy.py rename tig-benchmarker/{calc_rewards.py => calc_reward_share.py} (58%) create mode 100644 tig-benchmarker/common/calcs.py diff --git a/tig-benchmarker/calc_apy.py b/tig-benchmarker/calc_apy.py new file mode 100644 index 0000000..a1cf1a8 --- /dev/null +++ b/tig-benchmarker/calc_apy.py @@ -0,0 +1,112 @@ +import numpy as np +import json +import requests +from common.structs import * +from common.calcs import * + +API_URL = "https://mainnet-api.tig.foundation" + +deposit = input("Enter deposit in TIG (leave blank to fetch deposit from your player_id): ") +if deposit != "": + lock_period = input("Enter number of weeks to lock (longer lock will have higher APY): ") + deposit = float(deposit) + lock_period = float(lock_period) + player_id = None +else: + player_id = input("Enter player_id: ").lower() + +print("Fetching data...") +block = Block.from_dict(requests.get(f"{API_URL}/get-block?include_data").json()["block"]) +opow_data = { + x["player_id"]: OPoW.from_dict(x) + for x in requests.get(f"{API_URL}/get-opow?block_id={block.id}").json()["opow"] +} + +factors = { + benchmarker: { + **{ + f: opow_data[benchmarker].block_data.num_qualifiers_by_challenge.get(f, 0) + for f in block.data.active_ids["challenge"] + }, + "weighted_deposit": opow_data[benchmarker].block_data.delegated_weighted_deposit.to_float() + } + for benchmarker in opow_data +} + +if player_id is None: + blocks_till_round_ends = block.config["rounds"]["blocks_per_round"] - (block.details.height % block.config["rounds"]["blocks_per_round"]) + seconds_till_round_ends = blocks_till_round_ends * block.config["rounds"]["seconds_between_blocks"] + weighted_deposit = calc_weighted_deposit(deposit, seconds_till_round_ends, lock_period * 604800) +else: + player_data = Player.from_dict(requests.get(f"{API_URL}/get-player-data?player_id={player_id}&block_id={block.id}").json()["player"]) + deposit = sum(x.to_float() for x in player_data.block_data.deposit_by_locked_period) + weighted_deposit = player_data.block_data.weighted_deposit.to_float() + for delegatee, fraction in player_data.block_data.delegatees.items(): + factors[delegatee]["weighted_deposit"] -= fraction * weighted_deposit + +total_factors = { + f: sum(factors[benchmarker][f] for benchmarker in opow_data) + for f in list(block.data.active_ids["challenge"]) + ["weighted_deposit"] +} +reward_shares = { + benchmarker: opow_data[benchmarker].block_data.reward_share + for benchmarker in opow_data +} + +print("Optimising delegation by splitting into 100 chunks...") +chunk = weighted_deposit / 100 +delegate = {} +for i in range(100): + print(f"Chunk {i + 1}: simulating delegation...") + total_factors["weighted_deposit"] += chunk + if len(delegate) == 10: + potential_delegatees = list(delegate) + else: + potential_delegatees = [benchmarker for benchmarker in opow_data if opow_data[benchmarker].block_data.self_deposit.to_float() >= 10000] + highest_apy_benchmarker = max( + potential_delegatees, + key=lambda delegatee: ( + calc_influence({ + benchmarker: { + f: (factors[benchmarker][f] + chunk * (benchmarker == delegatee and f == "weighted_deposit")) / total_factors[f] + for f in total_factors + } + for benchmarker in opow_data + }, block.config["opow"])[delegatee] * + reward_shares[delegatee] * chunk / (factors[delegatee]["weighted_deposit"] + chunk) + ) + ) + print(f"Chunk {i + 1}: best delegatee is {highest_apy_benchmarker}") + if highest_apy_benchmarker not in delegate: + delegate[highest_apy_benchmarker] = 0 + delegate[highest_apy_benchmarker] += 1 + factors[highest_apy_benchmarker]["weighted_deposit"] += chunk + +influences = calc_influence({ + benchmarker: { + f: factors[benchmarker][f] / total_factors[f] + for f in total_factors + } + for benchmarker in opow_data +}, block.config["opow"]) +print("") + + +print("Optimised delegation split:") +reward_pool = block.config["rewards"]["distribution"]["opow"] * next(x["block_reward"] for x in reversed(block.config["rewards"]["schedule"]) if x["round_start"] <= block.details.round) +deposit_chunk = deposit / 100 +total_reward = 0 +for delegatee, num_chunks in delegate.items(): + share_fraction = influences[delegatee] * reward_shares[delegatee] * (num_chunks * chunk) / factors[delegatee]["weighted_deposit"] + reward = share_fraction * reward_pool + total_reward += reward + apy = reward * block.config["rounds"]["blocks_per_round"] * 52 / (num_chunks * deposit_chunk) + print(f"{delegatee}: %delegated = {num_chunks}%, apy = {apy * 100:.2f}%") + +print(f"average_apy = {total_reward * 10080 * 52 / deposit * 100:.2f}% on your deposit of {deposit} TIG") + +print("") +print("To set this delegation split, run the following command:") +req = {"delegatees": {k: v / 100 for k, v in delegate.items()}} +print("API_KEY=") +print(f"curl -H \"X-Api-Key: $API_KEY\" -X POST -d '{json.dumps(req)}' {API_URL}/set-delegatees") \ No newline at end of file diff --git a/tig-benchmarker/calc_rewards.py b/tig-benchmarker/calc_reward_share.py similarity index 58% rename from tig-benchmarker/calc_rewards.py rename to tig-benchmarker/calc_reward_share.py index 1a28989..853053a 100644 --- a/tig-benchmarker/calc_rewards.py +++ b/tig-benchmarker/calc_reward_share.py @@ -1,68 +1,10 @@ import numpy as np -import json import requests -from typing import List, Dict from common.structs import * +from common.calcs import * from copy import deepcopy -def calc_influence(fractions, opow_config) -> Dict[str, float]: - benchmarkers = list(fractions) - factors = list(next(iter(fractions.values()))) - num_challenges = len(factors) - 1 - avg_qualifier_fractions = { - benchmarker: sum( - fractions[benchmarker][f] - for f in factors - if f != "weighted_deposit" - ) / num_challenges - for benchmarker in benchmarkers - } - deposit_fraction_cap = { - benchmarker: avg_qualifier_fractions[benchmarker] * opow_config["max_deposit_to_qualifier_ratio"] - for benchmarker in benchmarkers - } - capped_fractions = { - benchmarker: { - **fractions[benchmarker], - "weighted_deposit": min( - fractions[benchmarker]["weighted_deposit"], - deposit_fraction_cap[benchmarker] - ) - } - for benchmarker in benchmarkers - } - avg_fraction = { - benchmarker: np.mean(list(capped_fractions[benchmarker].values())) - for benchmarker in benchmarkers - } - var_fraction = { - benchmarker: np.var(list(capped_fractions[benchmarker].values())) - for benchmarker in benchmarkers - } - imbalance = { - benchmarker: (var_fraction[benchmarker] / np.square(avg_fraction[benchmarker]) / num_challenges) if avg_fraction[benchmarker] > 0 else 0 - for benchmarker in benchmarkers - } - imbalance_penalty = { - benchmarker: 1.0 - np.exp(-opow_config["imbalance_multiplier"] * imbalance[benchmarker]) - for benchmarker in benchmarkers - } - weighted_avg_fraction = { - benchmarker: ((avg_qualifier_fractions[benchmarker] * num_challenges) + capped_fractions[benchmarker]["weighted_deposit"] * opow_config["deposit_multiplier"]) / (num_challenges + opow_config["deposit_multiplier"]) - for benchmarker in benchmarkers - } - unormalised_influence = { - benchmarker: weighted_avg_fraction[benchmarker] * (1.0 - imbalance_penalty[benchmarker]) - for benchmarker in benchmarkers - } - total = sum(unormalised_influence.values()) - influence = { - benchmarker: unormalised_influence[benchmarker] / total - for benchmarker in benchmarkers - } - return influence - API_URL = "https://mainnet-api.tig.foundation" player_id = input("Enter player_id: ").lower() @@ -140,12 +82,12 @@ print(f"reward = {reward['only_self_deposit']:.4f} TIG per block") print("") print("Scenario 2 (current self + delegated deposit)") print(f"%weighted_deposit = {fractions[player_id]['weighted_deposit'] * 100:.2f}%") -print(f"reward = {reward['current']:.4f} TIG per block ({reward['current'] / reward['only_self_deposit'] * 100 - 100:.2f}% difference*)") +print(f"reward = {reward['current']:.4f} TIG per block (recommended max reward_share = {100 - reward['only_self_deposit'] / reward['current'] * 100:.2f}%*)") print("") print(f"Scenario 3 (self + delegated deposit at parity)") print(f"%weighted_deposit = average %qualifiers = {average_fraction_of_qualifiers * 100:.2f}%") -print(f"reward = {reward['at_parity']:.4f} TIG per block ({reward['at_parity'] / reward['only_self_deposit'] * 100 - 100:.2f}% difference*)") +print(f"reward = {reward['at_parity']:.4f} TIG per block (recommended max reward_share = {100 - reward['only_self_deposit'] / reward['at_parity'] * 100:.2f}%*)") print("") -print("*These are percentage differences in reward compared with relying only on self-deposit (Scenario 1).") +print("*Recommend not setting reward_share above the max. You will not benefit from delegation (earn the same as Scenario 1 with zero delegation).") print("") print("Note: the imbalance penalty is such that your reward increases at a high rate when moving up to parity") \ No newline at end of file diff --git a/tig-benchmarker/common/calcs.py b/tig-benchmarker/common/calcs.py new file mode 100644 index 0000000..3f47230 --- /dev/null +++ b/tig-benchmarker/common/calcs.py @@ -0,0 +1,102 @@ +import numpy as np +from typing import List, Dict + + +def calc_influence(fractions: Dict[str, Dict[str, float]], opow_config: dict) -> Dict[str, float]: + """ + Calculate the influence of each benchmarker based on their fractions and the OPoW configuration. + + Args: + fractions: A dictionary of dictionaries, mapping benchmarker_ids to their fraction of each factor (challenges & weighted_deposit). + opow_config: A dictionary containing configuration parameters for the calculation. + + Returns: + Dict[str, float]: A dictionary mapping each benchmarker_id to their calculated influence. + """ + benchmarkers = list(fractions) + factors = list(next(iter(fractions.values()))) + num_challenges = len(factors) - 1 + avg_qualifier_fractions = { + benchmarker: sum( + fractions[benchmarker][f] + for f in factors + if f != "weighted_deposit" + ) / num_challenges + for benchmarker in benchmarkers + } + deposit_fraction_cap = { + benchmarker: avg_qualifier_fractions[benchmarker] * opow_config["max_deposit_to_qualifier_ratio"] + for benchmarker in benchmarkers + } + capped_fractions = { + benchmarker: { + **fractions[benchmarker], + "weighted_deposit": min( + fractions[benchmarker]["weighted_deposit"], + deposit_fraction_cap[benchmarker] + ) + } + for benchmarker in benchmarkers + } + avg_fraction = { + benchmarker: np.mean(list(capped_fractions[benchmarker].values())) + for benchmarker in benchmarkers + } + var_fraction = { + benchmarker: np.var(list(capped_fractions[benchmarker].values())) + for benchmarker in benchmarkers + } + imbalance = { + benchmarker: (var_fraction[benchmarker] / np.square(avg_fraction[benchmarker]) / num_challenges) if avg_fraction[benchmarker] > 0 else 0 + for benchmarker in benchmarkers + } + imbalance_penalty = { + benchmarker: 1.0 - np.exp(-opow_config["imbalance_multiplier"] * imbalance[benchmarker]) + for benchmarker in benchmarkers + } + weighted_avg_fraction = { + benchmarker: ((avg_qualifier_fractions[benchmarker] * num_challenges) + capped_fractions[benchmarker]["weighted_deposit"] * opow_config["deposit_multiplier"]) / (num_challenges + opow_config["deposit_multiplier"]) + for benchmarker in benchmarkers + } + unormalised_influence = { + benchmarker: weighted_avg_fraction[benchmarker] * (1.0 - imbalance_penalty[benchmarker]) + for benchmarker in benchmarkers + } + total = sum(unormalised_influence.values()) + influence = { + benchmarker: unormalised_influence[benchmarker] / total + for benchmarker in benchmarkers + } + return influence + + +def calc_weighted_deposit(deposit: float, seconds_till_round_end: int, lock_seconds: int) -> float: + """ + Calculate weighted deposit + + Args: + deposit: Amount to deposit + seconds_till_round_end: Seconds remaining in current round + lock_seconds: Total lock duration in seconds + + Returns: + Weighted deposit + """ + weighted_deposit = 0 + + if lock_seconds <= 0: + return weighted_deposit + + # Calculate first chunk (partial week) + weighted_deposit += deposit * min(seconds_till_round_end, lock_seconds) // lock_seconds + + remaining_seconds = lock_seconds - min(seconds_till_round_end, lock_seconds) + weight = 2 + while remaining_seconds > 0: + chunk_seconds = min(remaining_seconds, 604800) + chunk = deposit * chunk_seconds // lock_seconds + weighted_deposit += chunk * weight + remaining_seconds -= chunk_seconds + weight = min(weight + 1, 26) + + return weighted_deposit \ No newline at end of file diff --git a/tig-benchmarker/common/structs.py b/tig-benchmarker/common/structs.py index c2d29f1..4a69547 100644 --- a/tig-benchmarker/common/structs.py +++ b/tig-benchmarker/common/structs.py @@ -201,6 +201,7 @@ class OPoWBlockData(FromDict): num_qualifiers_by_challenge: Dict[str, int] cutoff: int delegated_weighted_deposit: PreciseNumber + self_deposit: PreciseNumber delegators: Set[str] reward_share: float imbalance: PreciseNumber @@ -221,13 +222,13 @@ class PlayerDetails(FromDict): class PlayerState(FromDict): total_fees_paid: PreciseNumber available_fee_balance: PreciseNumber - delegatee: Optional[dict] + delegatees: Optional[dict] votes: dict reward_share: Optional[dict] @dataclass class PlayerBlockData(FromDict): - delegatee: Optional[str] + delegatees: Dict[str, float] reward_by_type: Dict[str, PreciseNumber] deposit_by_locked_period: List[PreciseNumber] weighted_deposit: PreciseNumber From 48d8dcd88889739d2c847a98197d6a6f1d18ffba Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Tue, 31 Dec 2024 22:20:04 +0800 Subject: [PATCH 06/49] Move envvars to .env file. --- .gitignore | 1 - tig-benchmarker/.env | 7 +++++++ tig-benchmarker/README.md | 20 ++++++++++++-------- 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 tig-benchmarker/.env diff --git a/.gitignore b/.gitignore index 0e380f9..ce4f5b6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,4 @@ Cargo.lock __pycache__/ # Ignore these. -.env db_data/ \ No newline at end of file diff --git a/tig-benchmarker/.env b/tig-benchmarker/.env new file mode 100644 index 0000000..31520d1 --- /dev/null +++ b/tig-benchmarker/.env @@ -0,0 +1,7 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=mysecretpassword +POSTGRES_DB=postgres +UI_PORT=80 +DB_PORT=5432 +MASTER_PORT=5115 +VERBOSE= \ No newline at end of file diff --git a/tig-benchmarker/README.md b/tig-benchmarker/README.md index 4285f64..b5be57e 100644 --- a/tig-benchmarker/README.md +++ b/tig-benchmarker/README.md @@ -7,17 +7,21 @@ Benchmarker for TIG. Expected setup is a single master and multiple slaves on di Simply run: ``` -POSTGRES_USER=postgres \ -POSTGRES_PASSWORD=mysecretpassword \ -POSTGRES_DB=postgres \ -UI_PORT=80 \ -DB_PORT=5432 \ -MASTER_PORT=5115 \ -# set VERBOSE=1 for debug master logs -VERBOSE= \ docker-compose up --build ``` +This uses the `.env` file: + +``` +POSTGRES_USER=postgres +POSTGRES_PASSWORD=mysecretpassword +POSTGRES_DB=postgres +UI_PORT=80 +DB_PORT=5432 +MASTER_PORT=5115 +VERBOSE= +``` + See last section on how to find your player_id & api_key. **Notes:** From fb4eba57569f00d9aeca817d7f98982da1a05245 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Tue, 31 Dec 2024 22:20:57 +0800 Subject: [PATCH 07/49] Hardcode master port to 5115. --- tig-benchmarker/master/master/slave_manager.py | 4 ++-- tig-benchmarker/postgres/init.sql | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tig-benchmarker/master/master/slave_manager.py b/tig-benchmarker/master/master/slave_manager.py index 100f2ff..aede5d1 100644 --- a/tig-benchmarker/master/master/slave_manager.py +++ b/tig-benchmarker/master/master/slave_manager.py @@ -276,8 +276,8 @@ class SlaveManager: return {"status": "OK"} config = CONFIG["slave_manager_config"] - thread = Thread(target=lambda: uvicorn.run(app, host="0.0.0.0", port=config["port"])) + thread = Thread(target=lambda: uvicorn.run(app, host="0.0.0.0", port=5115)) thread.daemon = True thread.start() - logger.info(f"webserver started on 0.0.0.0:{config['port']}") + logger.info(f"webserver started on 0.0.0.0:5115") diff --git a/tig-benchmarker/postgres/init.sql b/tig-benchmarker/postgres/init.sql index a822447..fa413be 100644 --- a/tig-benchmarker/postgres/init.sql +++ b/tig-benchmarker/postgres/init.sql @@ -160,7 +160,6 @@ SELECT ' } }, "slave_manager_config": { - "port": 5115, "time_before_batch_retry": 60000, "slaves": [ { From 587ea86692d4392ae1517eff1792ca5e36614480 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Tue, 31 Dec 2024 22:21:48 +0800 Subject: [PATCH 08/49] Refresh db connection. --- .../master/master/client_manager.py | 10 ++++---- tig-benchmarker/master/master/job_manager.py | 24 +++++++++---------- .../master/master/precommit_manager.py | 4 ++-- .../master/master/slave_manager.py | 10 ++++---- tig-benchmarker/master/master/sql.py | 20 +++++++++------- .../master/master/submissions_manager.py | 10 ++++---- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/tig-benchmarker/master/master/client_manager.py b/tig-benchmarker/master/master/client_manager.py index f46667e..d6faf07 100644 --- a/tig-benchmarker/master/master/client_manager.py +++ b/tig-benchmarker/master/master/client_manager.py @@ -5,7 +5,7 @@ import threading import uvicorn import json from datetime import datetime -from master.sql import db_conn +from master.sql import get_db_conn from fastapi import FastAPI, Query, Request, HTTPException, Depends, Header from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware @@ -21,7 +21,7 @@ class ClientManager: logger.info("ClientManager initialized and connected to the database.") # Fetch initial config from database - result = db_conn.fetch_one( + result = get_db_conn().fetch_one( """ SELECT config FROM config LIMIT 1 @@ -54,7 +54,7 @@ class ClientManager: @self.app.get('/stop/{benchmark_id}') async def stop_benchmark(benchmark_id: str): try: - db_conn.execute( + get_db_conn().execute( """ UPDATE job SET stopped = true @@ -76,7 +76,7 @@ class ClientManager: new_config["api_url"] = new_config["api_url"].rstrip('/') # Update config in database - db_conn.execute( + get_db_conn().execute( """ DELETE FROM config; INSERT INTO config (config) @@ -95,7 +95,7 @@ class ClientManager: @self.app.get("/get-jobs") async def get_jobs(): - result = db_conn.fetch_all( + result = get_db_conn().fetch_all( """ WITH recent_jobs AS ( SELECT benchmark_id diff --git a/tig-benchmarker/master/master/job_manager.py b/tig-benchmarker/master/master/job_manager.py index 82bbc0a..c956235 100644 --- a/tig-benchmarker/master/master/job_manager.py +++ b/tig-benchmarker/master/master/job_manager.py @@ -5,7 +5,7 @@ from common.merkle_tree import MerkleHash, MerkleBranch, MerkleTree from common.structs import * from common.utils import * from typing import Dict, List, Optional, Set -from master.sql import db_conn +from master.sql import get_db_conn from master.client_manager import CONFIG import math @@ -39,7 +39,7 @@ class JobManager: for benchmark_id, x in precommits.items(): if ( benchmark_id in proofs or - db_conn.fetch_one( # check if job is already created + get_db_conn().fetch_one( # check if job is already created """ SELECT 1 FROM job @@ -122,14 +122,14 @@ class JobManager: ) ] - db_conn.execute_many(*atomic_inserts) + get_db_conn().execute_many(*atomic_inserts) # update jobs from confirmed benchmarks for benchmark_id, x in benchmarks.items(): if ( benchmark_id in proofs or - (result := db_conn.fetch_one( + (result := get_db_conn().fetch_one( """ SELECT num_batches, batch_size FROM job @@ -171,11 +171,11 @@ class JobManager: ) ] - db_conn.execute_many(*atomic_update) + get_db_conn().execute_many(*atomic_update) # update jobs from confirmed proofs if len(proofs) > 0: - db_conn.execute( + get_db_conn().execute( """ UPDATE job SET end_time = (EXTRACT(EPOCH FROM NOW()) * 1000)::BIGINT @@ -186,7 +186,7 @@ class JobManager: ) # stop any expired jobs - db_conn.execute( + get_db_conn().execute( """ UPDATE job SET stopped = true @@ -202,7 +202,7 @@ class JobManager: now = int(time.time() * 1000) # Find jobs where all root_batchs are ready - rows = db_conn.fetch_all( + rows = get_db_conn().fetch_all( """ WITH ready AS ( SELECT A.benchmark_id @@ -241,7 +241,7 @@ class JobManager: merkle_root = tree.calc_merkle_root() # Update the database with calculated merkle root - db_conn.execute_many(*[ + get_db_conn().execute_many(*[ ( """ UPDATE job_data @@ -266,7 +266,7 @@ class JobManager: ]) # Find jobs where all proofs_batchs are ready - rows = db_conn.fetch_all( + rows = get_db_conn().fetch_all( """ WITH ready AS ( SELECT A.benchmark_id @@ -306,7 +306,7 @@ class JobManager: for x in y ] - batch_merkle_roots = db_conn.fetch_one( + batch_merkle_roots = get_db_conn().fetch_one( """ SELECT JSONB_AGG(merkle_root ORDER BY batch_idx) as batch_merkle_roots FROM batch_data @@ -341,7 +341,7 @@ class JobManager: ) # Update database with calculated merkle proofs - db_conn.execute_many(*[ + get_db_conn().execute_many(*[ ( """ UPDATE job_data diff --git a/tig-benchmarker/master/master/precommit_manager.py b/tig-benchmarker/master/master/precommit_manager.py index 1e3a58e..bbba4f5 100644 --- a/tig-benchmarker/master/master/precommit_manager.py +++ b/tig-benchmarker/master/master/precommit_manager.py @@ -6,7 +6,7 @@ from master.submissions_manager import SubmitPrecommitRequest from common.structs import * from common.utils import FromDict from typing import Dict, List, Optional, Set -from master.sql import db_conn +from master.sql import get_db_conn from master.client_manager import CONFIG logger = logging.getLogger(os.path.splitext(os.path.basename(__file__))[0]) @@ -93,7 +93,7 @@ class PrecommitManager: logger.info(f"global qualifier difficulty stats for {challenges[c_id].details.name}: (#nonces: {x['nonces']}, #solutions: {x['solutions']}, avg_nonces_per_solution: {avg_nonces_per_solution})") def run(self, difficulty_samples: Dict[str, List[int]]) -> SubmitPrecommitRequest: - num_pending_jobs = db_conn.fetch_one( + num_pending_jobs = get_db_conn().fetch_one( """ SELECT COUNT(*) FROM job diff --git a/tig-benchmarker/master/master/slave_manager.py b/tig-benchmarker/master/master/slave_manager.py index aede5d1..94df085 100644 --- a/tig-benchmarker/master/master/slave_manager.py +++ b/tig-benchmarker/master/master/slave_manager.py @@ -13,7 +13,7 @@ import uvicorn from common.structs import * from common.utils import * from typing import Dict, List, Optional, Set -from master.sql import db_conn +from master.sql import get_db_conn from master.client_manager import CONFIG @@ -27,7 +27,7 @@ class SlaveManager: def run(self): with self.lock: - self.batches = db_conn.fetch_all( + self.batches = get_db_conn().fetch_all( """ SELECT * FROM ( SELECT @@ -145,7 +145,7 @@ class SlaveManager: if len(concurrent) == 0: logger.debug(f"no batches available for {slave_name}") if len(updates) > 0: - db_conn.execute_many(*updates) + get_db_conn().execute_many(*updates) return JSONResponse(content=jsonable_encoder(concurrent)) @app.post('/submit-batch-root/{batch_id}') @@ -182,7 +182,7 @@ class SlaveManager: # Update roots table with merkle root and solution nonces benchmark_id, batch_idx = batch_id.split("_") batch_idx = int(batch_idx) - db_conn.execute_many(*[ + get_db_conn().execute_many(*[ ( """ UPDATE root_batch @@ -247,7 +247,7 @@ class SlaveManager: # Update proofs table with merkle proofs benchmark_id, batch_idx = batch_id.split("_") batch_idx = int(batch_idx) - db_conn.execute_many(*[ + get_db_conn().execute_many(*[ ( """ UPDATE proofs_batch diff --git a/tig-benchmarker/master/master/sql.py b/tig-benchmarker/master/master/sql.py index 0c4d46f..288e444 100644 --- a/tig-benchmarker/master/master/sql.py +++ b/tig-benchmarker/master/master/sql.py @@ -100,11 +100,15 @@ class PostgresDB: db_conn = None -if db_conn is None: - db_conn = PostgresDB( - host=os.environ["POSTGRES_HOST"], - port=5432, - dbname=os.environ["POSTGRES_DB"], - user=os.environ["POSTGRES_USER"], - password=os.environ["POSTGRES_PASSWORD"] - ) \ No newline at end of file + +def get_db_conn(): + global db_conn + if db_conn is None or db_conn.closed: + db_conn = PostgresDB( + host=os.environ["POSTGRES_HOST"], + port=5432, + dbname=os.environ["POSTGRES_DB"], + user=os.environ["POSTGRES_USER"], + password=os.environ["POSTGRES_PASSWORD"] + ) + return db_conn \ No newline at end of file diff --git a/tig-benchmarker/master/master/submissions_manager.py b/tig-benchmarker/master/master/submissions_manager.py index e941e56..16cf85f 100644 --- a/tig-benchmarker/master/master/submissions_manager.py +++ b/tig-benchmarker/master/master/submissions_manager.py @@ -7,7 +7,7 @@ import os from common.structs import * from common.utils import * from typing import Union, Set, List, Dict -from master.sql import db_conn +from master.sql import get_db_conn from master.client_manager import CONFIG logger = logging.getLogger(os.path.splitext(os.path.basename(__file__))[0]) @@ -72,7 +72,7 @@ class SubmissionsManager: **kwargs ): if len(benchmarks) > 0: - db_conn.execute( + get_db_conn().execute( """ UPDATE job SET benchmark_submitted = true @@ -82,7 +82,7 @@ class SubmissionsManager: ) if len(proofs) > 0: - db_conn.execute( + get_db_conn().execute( """ UPDATE job SET proof_submitted = true @@ -100,7 +100,7 @@ class SubmissionsManager: else: self._post_thread("precommit", submit_precommit_req) - benchmark_to_submit = db_conn.fetch_one( + benchmark_to_submit = get_db_conn().fetch_one( """ WITH updated AS ( UPDATE job @@ -144,7 +144,7 @@ class SubmissionsManager: else: logger.debug("no benchmark to submit") - proof_to_submit = db_conn.fetch_one( + proof_to_submit = get_db_conn().fetch_one( """ WITH updated AS ( UPDATE job From 614de4fe302f9a280f7f3bcd4631963cb664b59c Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Tue, 31 Dec 2024 22:31:20 +0800 Subject: [PATCH 09/49] Address warning there is already a transaction in progress. --- tig-benchmarker/master/master/sql.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tig-benchmarker/master/master/sql.py b/tig-benchmarker/master/master/sql.py index 288e444..311a622 100644 --- a/tig-benchmarker/master/master/sql.py +++ b/tig-benchmarker/master/master/sql.py @@ -17,10 +17,15 @@ class PostgresDB: } self._conn = None + @property + def closed(self) -> bool: + return self._conn is None or self._conn.closed + def connect(self) -> None: """Establish connection to PostgreSQL database""" try: self._conn = psycopg2.connect(**self.conn_params) + self._conn.autocommit = False logger.info(f"Connected to PostgreSQL database at {self.conn_params['host']}:{self.conn_params['port']}") except Exception as e: logger.error(f"Error connecting to PostgreSQL: {str(e)}") @@ -40,7 +45,6 @@ class PostgresDB: try: with self._conn.cursor() as cur: - cur.execute("BEGIN") for query in args: cur.execute(*query) self._conn.commit() From 3138f31c0007b5506f42c1fdb9a9860fb5957007 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Tue, 31 Dec 2024 22:54:05 +0800 Subject: [PATCH 10/49] Slave kills tig-worker if master stops batch. --- tig-benchmarker/slave.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tig-benchmarker/slave.py b/tig-benchmarker/slave.py index 13762c8..cfcaeab 100644 --- a/tig-benchmarker/slave.py +++ b/tig-benchmarker/slave.py @@ -53,18 +53,30 @@ def run_tig_worker(tig_worker_path, batch, wasm_path, num_workers, output_path): process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) - stdout, stderr = process.communicate() - if process.returncode != 0: - PROCESSING_BATCH_IDS.remove(batch["id"]) - raise Exception(f"tig-worker failed: {stderr.decode()}") - result = json.loads(stdout.decode()) - logger.info(f"computing batch took {now() - start}ms") - logger.debug(f"batch result: {result}") - with open(f"{output_path}/{batch['id']}/result.json", "w") as f: - json.dump(result, f) - - PROCESSING_BATCH_IDS.remove(batch["id"]) - READY_BATCH_IDS.add(batch["id"]) + while True: + ret = process.poll() + if ret is not None: + if ret != 0: + PROCESSING_BATCH_IDS.remove(batch["id"]) + raise Exception(f"tig-worker failed with return code {ret}") + + stdout, stderr = process.communicate() + result = json.loads(stdout.decode()) + logger.info(f"computing batch {batch['id']} took {now() - start}ms") + logger.debug(f"batch {batch['id']} result: {result}") + with open(f"{output_path}/{batch['id']}/result.json", "w") as f: + json.dump(result, f) + + PROCESSING_BATCH_IDS.remove(batch["id"]) + READY_BATCH_IDS.add(batch["id"]) + break + + elif batch["id"] not in PROCESSING_BATCH_IDS: + process.kill() + logger.info(f"batch {batch['id']} stopped") + break + + time.sleep(0.1) def purge_folders(output_path, ttl): @@ -214,6 +226,9 @@ def poll_batches(session, master_ip, master_port, output_path): json.dump(batch, f) PENDING_BATCH_IDS.clear() PENDING_BATCH_IDS.update(root_batch_ids + proofs_batch_ids) + for batch_id in PROCESSING_BATCH_IDS - set(root_batch_ids + proofs_batch_ids): + logger.info(f"stopping batch {batch_id}") + PROCESSING_BATCH_IDS.remove(batch_id) time.sleep(5) else: From 715da6cd6904d4fb357b04304cb338e136ceaed2 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sun, 5 Jan 2025 11:13:24 +0800 Subject: [PATCH 11/49] Only process votes for players who have active deposits. --- tig-protocol/src/contracts/algorithms.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tig-protocol/src/contracts/algorithms.rs b/tig-protocol/src/contracts/algorithms.rs index 1cd7787..3cb824f 100644 --- a/tig-protocol/src/contracts/algorithms.rs +++ b/tig-protocol/src/contracts/algorithms.rs @@ -148,6 +148,7 @@ pub(crate) async fn update(cache: &mut AddBlockCache) { let active_algorithm_ids = &block_data.active_ids[&ActiveType::Algorithm]; let active_breakthrough_ids = &block_data.active_ids[&ActiveType::Breakthrough]; let active_challenge_ids = &block_data.active_ids[&ActiveType::Challenge]; + let active_player_ids = &block_data.active_ids[&ActiveType::Player]; // update votes for breakthrough_state in voting_breakthroughs_state.values_mut() { @@ -156,7 +157,8 @@ pub(crate) async fn update(cache: &mut AddBlockCache) { (false, PreciseNumber::from(0)), ]); } - for (player_id, player_state) in active_players_state.iter() { + for player_id in active_player_ids.iter() { + let player_state = &active_players_state[player_id]; let player_data = &active_players_block_data[player_id]; for (breakthrough_id, vote) in player_state.votes.iter() { let yes = vote.value; From 14373ff80c4d2df5ffb4d313a7ee48c446fb30da Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 9 Jan 2025 11:25:43 +0800 Subject: [PATCH 12/49] Add delay before voting begins. --- tig-protocol/src/contracts/players.rs | 4 ++-- tig-structs/src/config.rs | 1 + tig-structs/src/core.rs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tig-protocol/src/contracts/players.rs b/tig-protocol/src/contracts/players.rs index 652d1e3..7991a8c 100644 --- a/tig-protocol/src/contracts/players.rs +++ b/tig-protocol/src/contracts/players.rs @@ -212,8 +212,8 @@ pub async fn set_vote( .get_breakthrough_state(&breakthrough_id) .await .ok_or_else(|| anyhow!("Invalid breakthrough '{}'", breakthrough_id))?; - if breakthrough_state.round_pushed > latest_block_details.round - && latest_block_details.round >= breakthrough_state.round_votes_tallied + if latest_block_details.round < breakthrough_state.round_voting_starts + || latest_block_details.round >= breakthrough_state.round_votes_tallied { return Err(anyhow!("Cannot vote on breakthrough '{}'", breakthrough_id)); } diff --git a/tig-structs/src/config.rs b/tig-structs/src/config.rs index 4da83da..2c82563 100644 --- a/tig-structs/src/config.rs +++ b/tig-structs/src/config.rs @@ -23,6 +23,7 @@ serializable_struct_with_getters! { BreakthroughsConfig { bootstrap_address: String, min_percent_yes_votes: f64, + vote_start_delay: u32, vote_period: u32, min_lock_period_to_vote: u32, submission_fee: PreciseNumber, diff --git a/tig-structs/src/core.rs b/tig-structs/src/core.rs index 6b88564..553972c 100644 --- a/tig-structs/src/core.rs +++ b/tig-structs/src/core.rs @@ -272,6 +272,7 @@ serializable_struct_with_getters! { block_confirmed: u32, round_submitted: u32, round_pushed: u32, + round_voting_starts: u32, round_votes_tallied: u32, votes_tally: HashMap, round_active: Option, From 30a0f59f30f0637dd8b40e1c29c5e03cf83db0eb Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 9 Jan 2025 15:46:37 +0800 Subject: [PATCH 13/49] Update description for knapsack. --- docs/challenges/knapsack.md | 48 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/docs/challenges/knapsack.md b/docs/challenges/knapsack.md index cecb097..be7e71d 100644 --- a/docs/challenges/knapsack.md +++ b/docs/challenges/knapsack.md @@ -1,50 +1,54 @@ # Knapsack Problem -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. +The quadratic knapsack problem is one of the most popular variants of the single knapsack problem, with applications in many optimization contexts. The aim is to maximize the value of individual items placed in the knapsack while satisfying a weight constraint. However, pairs of items also have positive interaction values, contributing to the total value within the knapsack. -# Example + +## Challenge Overview 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]. - +- Parameter 2: $better\textunderscore{ }than\textunderscore{ }baseline \geq 1$ is the factor by which a solution must be better than the 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_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 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 values of the items are nonzero with a density of 25%, meaning they have a 25% probability of being nonzero. The nonzero individual values of the item, $v_i$, and the nonzero interaction values of pairs of items, $V_{ij}$, are selected at random from the range $[1,100]$. -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: +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}}$$ +$$ +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=4` and `better_than_baseline = 1.10`. Let the baseline value be 150: +# Example + +Consider an example of a challenge instance with `num_items=4` and `better_than_baseline = 1.50`. Let the baseline value be 46: ``` -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 +weights = [39, 29, 15, 43] +individual_values = [0, 14, 0, 75] +interaction_values = [ 0, 0, 0, 0 + 0, 0, 32, 0 + 0, 32, 0, 0 + 0, 0, 0, 0] +max_weight = 63 +min_value = baseline*better_than_baseline = 69 ``` -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. +The objective is to find a set of items where the total weight is at most 63 but has a total value of at least 69. Now consider the following selection: ``` -selected_items = [0, 1, 3] +selected_items = [2, 3] ``` -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: +When evaluating this selection, we can confirm that the total weight is less than 63, and the total value is more than 69, thereby this selection of items is a solution: -* Total weight = 26 + 20 + 13 = 59 -* Total value = 63 + 52 + 97 + 23 - 37 - 28 = 170 +* Total weight = 15 + 43 = 58 +* Total value = 0 + 75 + 0 = 75 # Our Challenge -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. +In TIG, the baseline value is determined by a two-stage approach. First, items are selected based on their value-to-weight ratio, including interaction values, until the capacity is reached. Then, a tabu-based local search refines the solution by swapping items to improve value while avoiding reversals, with early termination for unpromising swaps. From 6e445c35c75d53ce315257644448eea721ba1743 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 9 Jan 2025 19:25:29 +0800 Subject: [PATCH 14/49] Lock rust-toolchain to v1.82.0 --- .github/workflows/build_algorithm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_algorithm.yml b/.github/workflows/build_algorithm.yml index 7c3ce36..487126f 100644 --- a/.github/workflows/build_algorithm.yml +++ b/.github/workflows/build_algorithm.yml @@ -38,7 +38,7 @@ jobs: echo "CHALLENGE=$CHALLENGE" >> $GITHUB_ENV echo "ALGORITHM=$ALGORITHM" >> $GITHUB_ENV echo "WASM_PATH=$ALGORITHM" >> $GITHUB_ENV - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@1.82.0 with: targets: wasm32-wasi # - name: Install CUDA From f7910611429b5e9f250838ad39ae354642ed99bc Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sat, 11 Jan 2025 01:44:27 +0800 Subject: [PATCH 15/49] Increase default max fuel to 10 billion. --- scripts/test_algorithm.sh | 8 +++++++- tig-worker/src/main.rs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/test_algorithm.sh b/scripts/test_algorithm.sh index 14b04b2..fb6ebf2 100644 --- a/scripts/test_algorithm.sh +++ b/scripts/test_algorithm.sh @@ -76,6 +76,12 @@ if ! is_positive_integer "$num_workers"; then echo "Error: Number of workers must be a positive integer." exit 1 fi +read -p "Enter max fuel (default is 10000000000): " max_fuel +max_fuel=${max_fuel:-1} +if ! is_positive_integer "$max_fuel"; then + echo "Error: Max fuel must be a positive integer." + exit 1 +fi read -p "Enable debug mode? (leave blank to disable) " enable_debug if [[ -n $enable_debug ]]; then debug_mode=true @@ -117,7 +123,7 @@ while [ $remaining_nonces -gt 0 ]; do start_time=$(date +%s%3N) stdout=$(mktemp) stderr=$(mktemp) - ./target/release/tig-worker compute_batch "$SETTINGS" "random_string" $current_nonce $nonces_to_compute $power_of_2_nonces $REPO_DIR/tig-algorithms/wasm/$CHALLENGE/$ALGORITHM.wasm --workers $nonces_to_compute >"$stdout" 2>"$stderr" + ./target/release/tig-worker compute_batch "$SETTINGS" "random_string" $current_nonce $nonces_to_compute $power_of_2_nonces $REPO_DIR/tig-algorithms/wasm/$CHALLENGE/$ALGORITHM.wasm --workers $nonces_to_compute --fuel $max_fuel >"$stdout" 2>"$stderr" exit_code=$? output_stdout=$(cat "$stdout") output_stderr=$(cat "$stderr") diff --git a/tig-worker/src/main.rs b/tig-worker/src/main.rs index 260ad47..e2bea8b 100644 --- a/tig-worker/src/main.rs +++ b/tig-worker/src/main.rs @@ -28,7 +28,7 @@ fn cli() -> Command { .arg(arg!( "Path to a wasm file").value_parser(clap::value_parser!(PathBuf))) .arg( arg!(--fuel [FUEL] "Optional maximum fuel parameter for WASM VM") - .default_value("2000000000") + .default_value("10000000000") .value_parser(clap::value_parser!(u64)), ) .arg( From 07737efe621eb277e4b38f7b8b4ac930eba25eb8 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sun, 12 Jan 2025 15:45:31 +0000 Subject: [PATCH 16/49] Change breakthrough submission flow. --- docs/agreements/invention_assignment.doc | Bin 73728 -> 74240 bytes tig-breakthroughs/README.md | 24 +++++++++---- .../knapsack/{template.md => evidence.md} | 34 +++++++++++++++--- .../evidence.md} | 34 +++++++++++++++--- .../template.md => vector_search/evidence.md} | 34 +++++++++++++++--- .../{template.md => evidence.md} | 34 +++++++++++++++--- 6 files changed, 133 insertions(+), 27 deletions(-) rename tig-breakthroughs/knapsack/{template.md => evidence.md} (87%) rename tig-breakthroughs/{vector_search/template.md => satisfiability/evidence.md} (87%) rename tig-breakthroughs/{satisfiability/template.md => vector_search/evidence.md} (87%) rename tig-breakthroughs/vehicle_routing/{template.md => evidence.md} (87%) diff --git a/docs/agreements/invention_assignment.doc b/docs/agreements/invention_assignment.doc index b74d27ff61119add9753235e39a34ff64ba00b25..deef5578bf755cd0def450bd24fff77ee8037fb1 100644 GIT binary patch delta 8810 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 delta 7942 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 T-A|dG`$ykXz`)": + * **Evidence form**: copy & fill in `evidence.md` from the relevant challenges folder (e.g. [`knapsack/evidence.md`](./knapsack/evidence.md)). Of particular importance is Section 1 which describes your breakthrough -3. Copy [invention_assignment.doc](../docs/agreements/invention_assignment.doc), fill in the details, and sign + * **Invention assignment**: copy & replace [invention_assignment.doc](../docs/agreements/invention_assignment.doc) the highlighted parts. Inventor and witness must sign. -4. Email your invention assignment to contact@tig.foundation with subject "Invention Assignment" + * **Address Signature**: use [etherscan](https://etherscan.io/verifiedSignatures#) to sign a message `I am signing this message to confirm my submission of breakthrough `. Use your player_id that is making the submission. Send the verified etherscan link with message and signature. -5. Submit your evidence via https://play.tig.foundation/innovator - * 250 TIG will be deducted from your Available Fee Balance to make a submission - * You can topup via the [Benchmarker page](https://play.tig.foundation/benchmarker) + * (Optional) **Code implementation**: attach code implementing your breakthrough. Do not submit this code to TIG separately. This will be done for you + +**Notes**: +* The time of submission will be taken as the timestamp of the auto-reply to your email attaching the required documents. + +* Iterations are permitted for errors highlighted by the Foundation. This will not change the timestamp of your submission + +* 250 TIG will be deducted from your Available Fee Balance to make a breakthrough submission + +* An additional 10 TIG will be deducted from your Available Fee Balance to make an algorithm submission (if one is attached) + +* You can topup via the [Benchmarker page](https://play.tig.foundation/benchmarker) ## Method Submission Flow 1. New submissions get their branch pushed to a private version of this repository 2. A new submission made during round `X` will have its branch pushed to the public version of this repository at the start of round `X + 2` -3. From the start of round `X + 2` till the start of round `X + 4`, token holders can vote on whether they consider the method to be a breakthrough based off the submitted evidence +3. From the start of round `X + 3` till the start of round `X + 4`, token holders can vote on whether they consider the method to be a breakthrough based off the submitted evidence 4. At the start of round `X + 4`, if the submission has at least 50% yes votes, it becomes active 5. Every block, a method's adoption is the sum of all algorithm adoption, where the algorithm is attributed to that method. Methods with at least 50% adoption earn rewards and a merge point 6. At the end of a round, a method from each challenge with the most merge points, meeting the minimum threshold of 5040, gets merged to the `main` branch diff --git a/tig-breakthroughs/knapsack/template.md b/tig-breakthroughs/knapsack/evidence.md similarity index 87% rename from tig-breakthroughs/knapsack/template.md rename to tig-breakthroughs/knapsack/evidence.md index 43c8fa0..033a260 100644 --- a/tig-breakthroughs/knapsack/template.md +++ b/tig-breakthroughs/knapsack/evidence.md @@ -23,19 +23,35 @@ WHEN PROVIDING EVIDENCE, YOU MAY CITE LINKS TO EXTERNAL DATA SOURCES. ## UNIQUE ALGORITHM IDENTIFIER (UAI) -> UAI PLACEHOLDER - DO NOT EDIT +> UAI PLACEHOLDER - ASSIGNED BY TIG PROTOCOL -## DESCRIPTION OF ALGORITHMIC METHOD +## SECTION 1 + +IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DESCRIBE THE METHOD BECAUSE THIS WILL DEFINE THE METHOD THAT IS THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### DESCRIPTION OF ALGORITHMIC METHOD PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. -> knapsack - DO NOT EDIT +> knapsack PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. > YOUR RESPONSE HERE -## ATTRIBUTION +## SECTION 2 + +THE COPYRIGHT IN THE IMPLEMENTATION WILL BE THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### IMPLEMENTATION OF ALGORITHMIC METHOD + +TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOULD IDENTIFY THE CODE AND SUBMIT IT WITH THIS + +> YOUR RESPONSE HERE + +## SECTION 3 + +### ATTRIBUTION PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) @@ -45,7 +61,9 @@ PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. > YOUR RESPONSE HERE -## NOVELTY AND INVENTIVENESS +## SECTION 4 + +### NOVELTY AND INVENTIVENESS To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. @@ -114,18 +132,24 @@ To support your claim that an algorithmic method is novel and inventive, you sho > YOUR RESPONSE HERE +## SECTION 5 + ### EVIDENCE TO SUPPORT PATENTABILITY - **Development Records:** Please provide documentation of the invention process, including notes, sketches, and software versions, to establish a timeline of your innovation. > YOUR RESPONSE HERE +## SECTION 6 + ### SUGGESTED APPLICATIONS - Please provide suggestions for any real world applications of your abstract algorithmic method that occur to you. > YOUR RESPONSE HERE +## SECTION 7 + ### ANY OTHER INFORMATION - Please provide any other evidence or argument that you think might help support you request for eligibility for Breakthrough Rewards. diff --git a/tig-breakthroughs/vector_search/template.md b/tig-breakthroughs/satisfiability/evidence.md similarity index 87% rename from tig-breakthroughs/vector_search/template.md rename to tig-breakthroughs/satisfiability/evidence.md index 846f3b8..adc25ce 100644 --- a/tig-breakthroughs/vector_search/template.md +++ b/tig-breakthroughs/satisfiability/evidence.md @@ -23,19 +23,35 @@ WHEN PROVIDING EVIDENCE, YOU MAY CITE LINKS TO EXTERNAL DATA SOURCES. ## UNIQUE ALGORITHM IDENTIFIER (UAI) -> UAI PLACEHOLDER - DO NOT EDIT +> UAI PLACEHOLDER - ASSIGNED BY TIG PROTOCOL -## DESCRIPTION OF ALGORITHMIC METHOD +## SECTION 1 + +IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DESCRIBE THE METHOD BECAUSE THIS WILL DEFINE THE METHOD THAT IS THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### DESCRIPTION OF ALGORITHMIC METHOD PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. -> vector_search - DO NOT EDIT +> satisfiability PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. > YOUR RESPONSE HERE -## ATTRIBUTION +## SECTION 2 + +THE COPYRIGHT IN THE IMPLEMENTATION WILL BE THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### IMPLEMENTATION OF ALGORITHMIC METHOD + +TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOULD IDENTIFY THE CODE AND SUBMIT IT WITH THIS + +> YOUR RESPONSE HERE + +## SECTION 3 + +### ATTRIBUTION PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) @@ -45,7 +61,9 @@ PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. > YOUR RESPONSE HERE -## NOVELTY AND INVENTIVENESS +## SECTION 4 + +### NOVELTY AND INVENTIVENESS To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. @@ -114,18 +132,24 @@ To support your claim that an algorithmic method is novel and inventive, you sho > YOUR RESPONSE HERE +## SECTION 5 + ### EVIDENCE TO SUPPORT PATENTABILITY - **Development Records:** Please provide documentation of the invention process, including notes, sketches, and software versions, to establish a timeline of your innovation. > YOUR RESPONSE HERE +## SECTION 6 + ### SUGGESTED APPLICATIONS - Please provide suggestions for any real world applications of your abstract algorithmic method that occur to you. > YOUR RESPONSE HERE +## SECTION 7 + ### ANY OTHER INFORMATION - Please provide any other evidence or argument that you think might help support you request for eligibility for Breakthrough Rewards. diff --git a/tig-breakthroughs/satisfiability/template.md b/tig-breakthroughs/vector_search/evidence.md similarity index 87% rename from tig-breakthroughs/satisfiability/template.md rename to tig-breakthroughs/vector_search/evidence.md index a6c86a0..16965b1 100644 --- a/tig-breakthroughs/satisfiability/template.md +++ b/tig-breakthroughs/vector_search/evidence.md @@ -23,19 +23,35 @@ WHEN PROVIDING EVIDENCE, YOU MAY CITE LINKS TO EXTERNAL DATA SOURCES. ## UNIQUE ALGORITHM IDENTIFIER (UAI) -> UAI PLACEHOLDER - DO NOT EDIT +> UAI PLACEHOLDER - ASSIGNED BY TIG PROTOCOL -## DESCRIPTION OF ALGORITHMIC METHOD +## SECTION 1 + +IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DESCRIBE THE METHOD BECAUSE THIS WILL DEFINE THE METHOD THAT IS THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### DESCRIPTION OF ALGORITHMIC METHOD PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. -> satisfiability - DO NOT EDIT +> vector_search PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. > YOUR RESPONSE HERE -## ATTRIBUTION +## SECTION 2 + +THE COPYRIGHT IN THE IMPLEMENTATION WILL BE THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### IMPLEMENTATION OF ALGORITHMIC METHOD + +TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOULD IDENTIFY THE CODE AND SUBMIT IT WITH THIS + +> YOUR RESPONSE HERE + +## SECTION 3 + +### ATTRIBUTION PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) @@ -45,7 +61,9 @@ PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. > YOUR RESPONSE HERE -## NOVELTY AND INVENTIVENESS +## SECTION 4 + +### NOVELTY AND INVENTIVENESS To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. @@ -114,18 +132,24 @@ To support your claim that an algorithmic method is novel and inventive, you sho > YOUR RESPONSE HERE +## SECTION 5 + ### EVIDENCE TO SUPPORT PATENTABILITY - **Development Records:** Please provide documentation of the invention process, including notes, sketches, and software versions, to establish a timeline of your innovation. > YOUR RESPONSE HERE +## SECTION 6 + ### SUGGESTED APPLICATIONS - Please provide suggestions for any real world applications of your abstract algorithmic method that occur to you. > YOUR RESPONSE HERE +## SECTION 7 + ### ANY OTHER INFORMATION - Please provide any other evidence or argument that you think might help support you request for eligibility for Breakthrough Rewards. diff --git a/tig-breakthroughs/vehicle_routing/template.md b/tig-breakthroughs/vehicle_routing/evidence.md similarity index 87% rename from tig-breakthroughs/vehicle_routing/template.md rename to tig-breakthroughs/vehicle_routing/evidence.md index 8837425..6577f8a 100644 --- a/tig-breakthroughs/vehicle_routing/template.md +++ b/tig-breakthroughs/vehicle_routing/evidence.md @@ -23,19 +23,35 @@ WHEN PROVIDING EVIDENCE, YOU MAY CITE LINKS TO EXTERNAL DATA SOURCES. ## UNIQUE ALGORITHM IDENTIFIER (UAI) -> UAI PLACEHOLDER - DO NOT EDIT +> UAI PLACEHOLDER - ASSIGNED BY TIG PROTOCOL -## DESCRIPTION OF ALGORITHMIC METHOD +## SECTION 1 + +IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DESCRIBE THE METHOD BECAUSE THIS WILL DEFINE THE METHOD THAT IS THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### DESCRIPTION OF ALGORITHMIC METHOD PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. -> vehicle_routing - DO NOT EDIT +> vehicle_routing PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. > YOUR RESPONSE HERE -## ATTRIBUTION +## SECTION 2 + +THE COPYRIGHT IN THE IMPLEMENTATION WILL BE THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. + +### IMPLEMENTATION OF ALGORITHMIC METHOD + +TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOULD IDENTIFY THE CODE AND SUBMIT IT WITH THIS + +> YOUR RESPONSE HERE + +## SECTION 3 + +### ATTRIBUTION PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) @@ -45,7 +61,9 @@ PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. > YOUR RESPONSE HERE -## NOVELTY AND INVENTIVENESS +## SECTION 4 + +### NOVELTY AND INVENTIVENESS To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. @@ -114,18 +132,24 @@ To support your claim that an algorithmic method is novel and inventive, you sho > YOUR RESPONSE HERE +## SECTION 5 + ### EVIDENCE TO SUPPORT PATENTABILITY - **Development Records:** Please provide documentation of the invention process, including notes, sketches, and software versions, to establish a timeline of your innovation. > YOUR RESPONSE HERE +## SECTION 6 + ### SUGGESTED APPLICATIONS - Please provide suggestions for any real world applications of your abstract algorithmic method that occur to you. > YOUR RESPONSE HERE +## SECTION 7 + ### ANY OTHER INFORMATION - Please provide any other evidence or argument that you think might help support you request for eligibility for Breakthrough Rewards. From 70907ed1ddb5f3f0b576b5f5ef306a86f967b864 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Wed, 15 Jan 2025 00:32:47 +0000 Subject: [PATCH 17/49] Remove session from slave to fix connection issue. --- tig-benchmarker/slave.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tig-benchmarker/slave.py b/tig-benchmarker/slave.py index cfcaeab..4f1594b 100644 --- a/tig-benchmarker/slave.py +++ b/tig-benchmarker/slave.py @@ -21,11 +21,11 @@ FINISHED_BATCH_IDS = {} def now(): return int(time.time() * 1000) -def download_wasm(session, download_url, wasm_path): +def download_wasm(download_url, wasm_path): if not os.path.exists(wasm_path): start = now() logger.info(f"downloading WASM from {download_url}") - resp = session.get(download_url) + resp = requests.get(download_url) if resp.status_code != 200: raise Exception(f"status {resp.status_code} when downloading WASM: {resp.text}") with open(wasm_path, 'wb') as f: @@ -93,11 +93,11 @@ def purge_folders(output_path, ttl): for batch_id in purge_batch_ids: if os.path.exists(f"{output_path}/{batch_id}"): logger.info(f"purging batch {batch_id}") - shutil.rmtree(f"{output_path}/{batch_id}") + shutil.rmtree(f"{output_path}/{batch_id}", ignore_errors=True) FINISHED_BATCH_IDS.pop(batch_id) -def send_results(session, master_ip, master_port, tig_worker_path, download_wasms_folder, num_workers, output_path): +def send_results(headers, master_ip, master_port, tig_worker_path, download_wasms_folder, num_workers, output_path): try: batch_id = READY_BATCH_IDS.pop() except KeyError: @@ -130,7 +130,7 @@ def send_results(session, master_ip, master_port, tig_worker_path, download_wasm submit_url = f"http://{master_ip}:{master_port}/submit-batch-root/{batch_id}" logger.info(f"posting root to {submit_url}") - resp = session.post(submit_url, json=result) + resp = requests.post(submit_url, headers=headers, json=result) if resp.status_code == 200: FINISHED_BATCH_IDS[batch_id] = now() logger.info(f"successfully posted root for batch {batch_id}") @@ -163,7 +163,7 @@ def send_results(session, master_ip, master_port, tig_worker_path, download_wasm submit_url = f"http://{master_ip}:{master_port}/submit-batch-proofs/{batch_id}" logger.info(f"posting proofs to {submit_url}") - resp = session.post(submit_url, json={"merkle_proofs": proofs_to_submit}) + resp = requests.post(submit_url, headers=headers, json={"merkle_proofs": proofs_to_submit}) if resp.status_code == 200: FINISHED_BATCH_IDS[batch_id] = now() logger.info(f"successfully posted proofs for batch {batch_id}") @@ -176,7 +176,7 @@ def send_results(session, master_ip, master_port, tig_worker_path, download_wasm time.sleep(2) -def process_batch(session, tig_worker_path, download_wasms_folder, num_workers, output_path): +def process_batch(tig_worker_path, download_wasms_folder, num_workers, output_path): try: batch_id = PENDING_BATCH_IDS.pop() except KeyError: @@ -200,7 +200,7 @@ def process_batch(session, tig_worker_path, download_wasms_folder, num_workers, batch = json.load(f) wasm_path = os.path.join(download_wasms_folder, f"{batch['settings']['algorithm_id']}.wasm") - download_wasm(session, batch['download_url'], wasm_path) + download_wasm(batch['download_url'], wasm_path) Thread( target=run_tig_worker, @@ -208,10 +208,10 @@ def process_batch(session, tig_worker_path, download_wasms_folder, num_workers, ).start() -def poll_batches(session, master_ip, master_port, output_path): +def poll_batches(headers, master_ip, master_port, output_path): get_batches_url = f"http://{master_ip}:{master_port}/get-batches" logger.info(f"fetching batches from {get_batches_url}") - resp = session.get(get_batches_url) + resp = requests.get(get_batches_url, headers=headers) if resp.status_code == 200: batches = resp.json() @@ -262,19 +262,18 @@ def main( raise FileNotFoundError(f"tig-worker not found at path: {tig_worker_path}") os.makedirs(download_wasms_folder, exist_ok=True) - session = requests.Session() - session.headers.update({ + headers = { "User-Agent": slave_name - }) + } Thread( target=wrap_thread, - args=(process_batch, session, tig_worker_path, download_wasms_folder, num_workers, output_path) + args=(process_batch, tig_worker_path, download_wasms_folder, num_workers, output_path) ).start() Thread( target=wrap_thread, - args=(send_results, session, master_ip, master_port, tig_worker_path, download_wasms_folder, num_workers, output_path) + args=(send_results, headers, master_ip, master_port, tig_worker_path, download_wasms_folder, num_workers, output_path) ).start() Thread( @@ -282,7 +281,7 @@ def main( args=(purge_folders, output_path, ttl) ).start() - wrap_thread(poll_batches, session, master_ip, master_port, output_path) + wrap_thread(poll_batches, headers, master_ip, master_port, output_path) if __name__ == "__main__": From 81c01e5bcfe3d16bdc56d2a843604d220e7104a5 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 17:45:54 +0000 Subject: [PATCH 18/49] Allow difficulty_ranges to be disabled. --- .../master/master/difficulty_sampler.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tig-benchmarker/master/master/difficulty_sampler.py b/tig-benchmarker/master/master/difficulty_sampler.py index 784c4bd..a79d789 100644 --- a/tig-benchmarker/master/master/difficulty_sampler.py +++ b/tig-benchmarker/master/master/difficulty_sampler.py @@ -117,6 +117,7 @@ class DifficultySampler: self.challenges = {} def on_new_block(self, challenges: Dict[str, Challenge], **kwargs): + config = CONFIG["difficulty_sampler_config"] for c in challenges.values(): if c.block_data is None: continue @@ -126,7 +127,10 @@ class DifficultySampler: else: upper_frontier, lower_frontier = c.block_data.base_frontier, c.block_data.scaled_frontier self.valid_difficulties[c.details.name] = calc_valid_difficulties(list(upper_frontier), list(lower_frontier)) - self.frontiers[c.details.name] = calc_all_frontiers(self.valid_difficulties[c.details.name]) + if config["difficulty_ranges"] is None: + self.frontiers[c.details.name] = [] + else: + self.frontiers[c.details.name] = calc_all_frontiers(self.valid_difficulties[c.details.name]) self.challenges = [c.details.name for c in challenges.values()] @@ -153,12 +157,16 @@ class DifficultySampler: logger.debug(f"No valid difficulties found for {c_name} - skipping selected difficulties") if not found_valid: - frontiers = self.frontiers[c_name] - difficulty_range = config["difficulty_ranges"][c_name] - idx1 = math.floor(difficulty_range[0] * (len(frontiers) - 1)) - idx2 = math.ceil(difficulty_range[1] * (len(frontiers) - 1)) - difficulties = [p for frontier in frontiers[idx1:idx2 + 1] for p in frontier] - difficulty = random.choice(difficulties) + if len(self.frontiers[c_name]) == 0 or config["difficulty_ranges"] is None: + valid_difficulties = self.valid_difficulties[c_name] + difficulty = random.choice(valid_difficulties) + else: + frontiers = self.frontiers[c_name] + difficulty_range = config["difficulty_ranges"][c_name] + idx1 = math.floor(difficulty_range[0] * (len(frontiers) - 1)) + idx2 = math.ceil(difficulty_range[1] * (len(frontiers) - 1)) + difficulties = [p for frontier in frontiers[idx1:idx2 + 1] for p in frontier] + difficulty = random.choice(difficulties) samples[c_name] = difficulty logger.debug(f"Sampled difficulty {difficulty} for challenge {c_name}") From 387d1d9f0df3f9ddb437f4231ccffe97868e2097 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:03:29 +0000 Subject: [PATCH 19/49] Compiled knapsack/classic_quadkp --- .../classic_quadkp/benchmarker_outbound.rs | 167 ++++++++++++++++++ .../src/knapsack/classic_quadkp/commercial.rs | 167 ++++++++++++++++++ .../src/knapsack/classic_quadkp/inbound.rs | 167 ++++++++++++++++++ .../classic_quadkp/innovator_outbound.rs | 167 ++++++++++++++++++ .../src/knapsack/classic_quadkp/mod.rs | 4 + .../src/knapsack/classic_quadkp/open_data.rs | 167 ++++++++++++++++++ tig-algorithms/src/knapsack/mod.rs | 3 +- tig-algorithms/src/knapsack/template.rs | 26 +-- .../wasm/knapsack/classic_quadkp.wasm | Bin 0 -> 162976 bytes 9 files changed, 843 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/knapsack/classic_quadkp/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/knapsack/classic_quadkp/commercial.rs create mode 100644 tig-algorithms/src/knapsack/classic_quadkp/inbound.rs create mode 100644 tig-algorithms/src/knapsack/classic_quadkp/innovator_outbound.rs create mode 100644 tig-algorithms/src/knapsack/classic_quadkp/mod.rs create mode 100644 tig-algorithms/src/knapsack/classic_quadkp/open_data.rs create mode 100644 tig-algorithms/wasm/knapsack/classic_quadkp.wasm diff --git a/tig-algorithms/src/knapsack/classic_quadkp/benchmarker_outbound.rs b/tig-algorithms/src/knapsack/classic_quadkp/benchmarker_outbound.rs new file mode 100644 index 0000000..4f65bef --- /dev/null +++ b/tig-algorithms/src/knapsack/classic_quadkp/benchmarker_outbound.rs @@ -0,0 +1,167 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Result; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut edge_costs: Vec<(usize, f32)> = (0..vertex_count) + .map(|flow_index| { + let total_flow = challenge.values[flow_index] as i32 + + challenge.interaction_values[flow_index].iter().sum::(); + let cost = total_flow as f32 / challenge.weights[flow_index] as f32; + (flow_index, cost) + }) + .collect(); + + edge_costs.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut coloring = Vec::with_capacity(vertex_count); + let mut uncolored = Vec::with_capacity(vertex_count); + let mut current_entropy = 0; + let mut current_temperature = 0; + + for &(flow_index, _) in &edge_costs { + if current_entropy + challenge.weights[flow_index] <= challenge.max_weight { + current_entropy += challenge.weights[flow_index]; + current_temperature += challenge.values[flow_index] as i32; + + for &colored in &coloring { + current_temperature += challenge.interaction_values[flow_index][colored]; + } + coloring.push(flow_index); + } else { + uncolored.push(flow_index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for flow_index in 0..vertex_count { + mutation_rates[flow_index] = challenge.values[flow_index] as i32; + for &colored in &coloring { + mutation_rates[flow_index] += challenge.interaction_values[flow_index][colored]; + } + } + + let max_generations = 100; + let mut cooling_schedule = vec![0; vertex_count]; + + for _ in 0..max_generations { + let mut best_mutation = 0; + let mut best_crossover = None; + + for uncolored_index in 0..uncolored.len() { + let mutant = uncolored[uncolored_index]; + if cooling_schedule[mutant] > 0 { + continue; + } + + unsafe { + let mutant_fitness = *mutation_rates.get_unchecked(mutant); + let min_entropy_reduction = *challenge.weights.get_unchecked(mutant) as i32 - (challenge.max_weight as i32 - current_entropy as i32); + + if mutant_fitness < 0 { + continue; + } + + for colored_index in 0..coloring.len() { + let gene_to_remove = *coloring.get_unchecked(colored_index); + if *cooling_schedule.get_unchecked(gene_to_remove) > 0 { + continue; + } + + if min_entropy_reduction > 0 { + let removed_entropy = *challenge.weights.get_unchecked(gene_to_remove) as i32; + if removed_entropy < min_entropy_reduction { + continue; + } + } + + let fitness_change = mutant_fitness - *mutation_rates.get_unchecked(gene_to_remove) + - *challenge.interaction_values.get_unchecked(mutant).get_unchecked(gene_to_remove); + + if fitness_change > best_mutation { + best_mutation = fitness_change; + best_crossover = Some((uncolored_index, colored_index)); + } + } + } + } + + if let Some((uncolored_index, colored_index)) = best_crossover { + let gene_to_add = uncolored[uncolored_index]; + let gene_to_remove = coloring[colored_index]; + + coloring.swap_remove(colored_index); + uncolored.swap_remove(uncolored_index); + coloring.push(gene_to_add); + uncolored.push(gene_to_remove); + + current_temperature += best_mutation; + current_entropy = current_entropy + challenge.weights[gene_to_add] - challenge.weights[gene_to_remove]; + + unsafe { + for flow_index in 0..vertex_count { + *mutation_rates.get_unchecked_mut(flow_index) += + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_add) - + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_remove); + } + } + + cooling_schedule[gene_to_add] = 3; + cooling_schedule[gene_to_remove] = 3; + } else { + break; + } + + if current_temperature as u32 >= challenge.min_value { + return Ok(Some(Solution { items: coloring })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + } + + if current_temperature as u32 >= challenge.min_value { + Ok(Some(Solution { items: coloring })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/classic_quadkp/commercial.rs b/tig-algorithms/src/knapsack/classic_quadkp/commercial.rs new file mode 100644 index 0000000..c8ff1e3 --- /dev/null +++ b/tig-algorithms/src/knapsack/classic_quadkp/commercial.rs @@ -0,0 +1,167 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Result; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut edge_costs: Vec<(usize, f32)> = (0..vertex_count) + .map(|flow_index| { + let total_flow = challenge.values[flow_index] as i32 + + challenge.interaction_values[flow_index].iter().sum::(); + let cost = total_flow as f32 / challenge.weights[flow_index] as f32; + (flow_index, cost) + }) + .collect(); + + edge_costs.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut coloring = Vec::with_capacity(vertex_count); + let mut uncolored = Vec::with_capacity(vertex_count); + let mut current_entropy = 0; + let mut current_temperature = 0; + + for &(flow_index, _) in &edge_costs { + if current_entropy + challenge.weights[flow_index] <= challenge.max_weight { + current_entropy += challenge.weights[flow_index]; + current_temperature += challenge.values[flow_index] as i32; + + for &colored in &coloring { + current_temperature += challenge.interaction_values[flow_index][colored]; + } + coloring.push(flow_index); + } else { + uncolored.push(flow_index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for flow_index in 0..vertex_count { + mutation_rates[flow_index] = challenge.values[flow_index] as i32; + for &colored in &coloring { + mutation_rates[flow_index] += challenge.interaction_values[flow_index][colored]; + } + } + + let max_generations = 100; + let mut cooling_schedule = vec![0; vertex_count]; + + for _ in 0..max_generations { + let mut best_mutation = 0; + let mut best_crossover = None; + + for uncolored_index in 0..uncolored.len() { + let mutant = uncolored[uncolored_index]; + if cooling_schedule[mutant] > 0 { + continue; + } + + unsafe { + let mutant_fitness = *mutation_rates.get_unchecked(mutant); + let min_entropy_reduction = *challenge.weights.get_unchecked(mutant) as i32 - (challenge.max_weight as i32 - current_entropy as i32); + + if mutant_fitness < 0 { + continue; + } + + for colored_index in 0..coloring.len() { + let gene_to_remove = *coloring.get_unchecked(colored_index); + if *cooling_schedule.get_unchecked(gene_to_remove) > 0 { + continue; + } + + if min_entropy_reduction > 0 { + let removed_entropy = *challenge.weights.get_unchecked(gene_to_remove) as i32; + if removed_entropy < min_entropy_reduction { + continue; + } + } + + let fitness_change = mutant_fitness - *mutation_rates.get_unchecked(gene_to_remove) + - *challenge.interaction_values.get_unchecked(mutant).get_unchecked(gene_to_remove); + + if fitness_change > best_mutation { + best_mutation = fitness_change; + best_crossover = Some((uncolored_index, colored_index)); + } + } + } + } + + if let Some((uncolored_index, colored_index)) = best_crossover { + let gene_to_add = uncolored[uncolored_index]; + let gene_to_remove = coloring[colored_index]; + + coloring.swap_remove(colored_index); + uncolored.swap_remove(uncolored_index); + coloring.push(gene_to_add); + uncolored.push(gene_to_remove); + + current_temperature += best_mutation; + current_entropy = current_entropy + challenge.weights[gene_to_add] - challenge.weights[gene_to_remove]; + + unsafe { + for flow_index in 0..vertex_count { + *mutation_rates.get_unchecked_mut(flow_index) += + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_add) - + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_remove); + } + } + + cooling_schedule[gene_to_add] = 3; + cooling_schedule[gene_to_remove] = 3; + } else { + break; + } + + if current_temperature as u32 >= challenge.min_value { + return Ok(Some(Solution { items: coloring })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + } + + if current_temperature as u32 >= challenge.min_value { + Ok(Some(Solution { items: coloring })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/classic_quadkp/inbound.rs b/tig-algorithms/src/knapsack/classic_quadkp/inbound.rs new file mode 100644 index 0000000..314acfb --- /dev/null +++ b/tig-algorithms/src/knapsack/classic_quadkp/inbound.rs @@ -0,0 +1,167 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Result; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut edge_costs: Vec<(usize, f32)> = (0..vertex_count) + .map(|flow_index| { + let total_flow = challenge.values[flow_index] as i32 + + challenge.interaction_values[flow_index].iter().sum::(); + let cost = total_flow as f32 / challenge.weights[flow_index] as f32; + (flow_index, cost) + }) + .collect(); + + edge_costs.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut coloring = Vec::with_capacity(vertex_count); + let mut uncolored = Vec::with_capacity(vertex_count); + let mut current_entropy = 0; + let mut current_temperature = 0; + + for &(flow_index, _) in &edge_costs { + if current_entropy + challenge.weights[flow_index] <= challenge.max_weight { + current_entropy += challenge.weights[flow_index]; + current_temperature += challenge.values[flow_index] as i32; + + for &colored in &coloring { + current_temperature += challenge.interaction_values[flow_index][colored]; + } + coloring.push(flow_index); + } else { + uncolored.push(flow_index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for flow_index in 0..vertex_count { + mutation_rates[flow_index] = challenge.values[flow_index] as i32; + for &colored in &coloring { + mutation_rates[flow_index] += challenge.interaction_values[flow_index][colored]; + } + } + + let max_generations = 100; + let mut cooling_schedule = vec![0; vertex_count]; + + for _ in 0..max_generations { + let mut best_mutation = 0; + let mut best_crossover = None; + + for uncolored_index in 0..uncolored.len() { + let mutant = uncolored[uncolored_index]; + if cooling_schedule[mutant] > 0 { + continue; + } + + unsafe { + let mutant_fitness = *mutation_rates.get_unchecked(mutant); + let min_entropy_reduction = *challenge.weights.get_unchecked(mutant) as i32 - (challenge.max_weight as i32 - current_entropy as i32); + + if mutant_fitness < 0 { + continue; + } + + for colored_index in 0..coloring.len() { + let gene_to_remove = *coloring.get_unchecked(colored_index); + if *cooling_schedule.get_unchecked(gene_to_remove) > 0 { + continue; + } + + if min_entropy_reduction > 0 { + let removed_entropy = *challenge.weights.get_unchecked(gene_to_remove) as i32; + if removed_entropy < min_entropy_reduction { + continue; + } + } + + let fitness_change = mutant_fitness - *mutation_rates.get_unchecked(gene_to_remove) + - *challenge.interaction_values.get_unchecked(mutant).get_unchecked(gene_to_remove); + + if fitness_change > best_mutation { + best_mutation = fitness_change; + best_crossover = Some((uncolored_index, colored_index)); + } + } + } + } + + if let Some((uncolored_index, colored_index)) = best_crossover { + let gene_to_add = uncolored[uncolored_index]; + let gene_to_remove = coloring[colored_index]; + + coloring.swap_remove(colored_index); + uncolored.swap_remove(uncolored_index); + coloring.push(gene_to_add); + uncolored.push(gene_to_remove); + + current_temperature += best_mutation; + current_entropy = current_entropy + challenge.weights[gene_to_add] - challenge.weights[gene_to_remove]; + + unsafe { + for flow_index in 0..vertex_count { + *mutation_rates.get_unchecked_mut(flow_index) += + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_add) - + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_remove); + } + } + + cooling_schedule[gene_to_add] = 3; + cooling_schedule[gene_to_remove] = 3; + } else { + break; + } + + if current_temperature as u32 >= challenge.min_value { + return Ok(Some(Solution { items: coloring })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + } + + if current_temperature as u32 >= challenge.min_value { + Ok(Some(Solution { items: coloring })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/classic_quadkp/innovator_outbound.rs b/tig-algorithms/src/knapsack/classic_quadkp/innovator_outbound.rs new file mode 100644 index 0000000..3c9de50 --- /dev/null +++ b/tig-algorithms/src/knapsack/classic_quadkp/innovator_outbound.rs @@ -0,0 +1,167 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Result; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut edge_costs: Vec<(usize, f32)> = (0..vertex_count) + .map(|flow_index| { + let total_flow = challenge.values[flow_index] as i32 + + challenge.interaction_values[flow_index].iter().sum::(); + let cost = total_flow as f32 / challenge.weights[flow_index] as f32; + (flow_index, cost) + }) + .collect(); + + edge_costs.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut coloring = Vec::with_capacity(vertex_count); + let mut uncolored = Vec::with_capacity(vertex_count); + let mut current_entropy = 0; + let mut current_temperature = 0; + + for &(flow_index, _) in &edge_costs { + if current_entropy + challenge.weights[flow_index] <= challenge.max_weight { + current_entropy += challenge.weights[flow_index]; + current_temperature += challenge.values[flow_index] as i32; + + for &colored in &coloring { + current_temperature += challenge.interaction_values[flow_index][colored]; + } + coloring.push(flow_index); + } else { + uncolored.push(flow_index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for flow_index in 0..vertex_count { + mutation_rates[flow_index] = challenge.values[flow_index] as i32; + for &colored in &coloring { + mutation_rates[flow_index] += challenge.interaction_values[flow_index][colored]; + } + } + + let max_generations = 100; + let mut cooling_schedule = vec![0; vertex_count]; + + for _ in 0..max_generations { + let mut best_mutation = 0; + let mut best_crossover = None; + + for uncolored_index in 0..uncolored.len() { + let mutant = uncolored[uncolored_index]; + if cooling_schedule[mutant] > 0 { + continue; + } + + unsafe { + let mutant_fitness = *mutation_rates.get_unchecked(mutant); + let min_entropy_reduction = *challenge.weights.get_unchecked(mutant) as i32 - (challenge.max_weight as i32 - current_entropy as i32); + + if mutant_fitness < 0 { + continue; + } + + for colored_index in 0..coloring.len() { + let gene_to_remove = *coloring.get_unchecked(colored_index); + if *cooling_schedule.get_unchecked(gene_to_remove) > 0 { + continue; + } + + if min_entropy_reduction > 0 { + let removed_entropy = *challenge.weights.get_unchecked(gene_to_remove) as i32; + if removed_entropy < min_entropy_reduction { + continue; + } + } + + let fitness_change = mutant_fitness - *mutation_rates.get_unchecked(gene_to_remove) + - *challenge.interaction_values.get_unchecked(mutant).get_unchecked(gene_to_remove); + + if fitness_change > best_mutation { + best_mutation = fitness_change; + best_crossover = Some((uncolored_index, colored_index)); + } + } + } + } + + if let Some((uncolored_index, colored_index)) = best_crossover { + let gene_to_add = uncolored[uncolored_index]; + let gene_to_remove = coloring[colored_index]; + + coloring.swap_remove(colored_index); + uncolored.swap_remove(uncolored_index); + coloring.push(gene_to_add); + uncolored.push(gene_to_remove); + + current_temperature += best_mutation; + current_entropy = current_entropy + challenge.weights[gene_to_add] - challenge.weights[gene_to_remove]; + + unsafe { + for flow_index in 0..vertex_count { + *mutation_rates.get_unchecked_mut(flow_index) += + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_add) - + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_remove); + } + } + + cooling_schedule[gene_to_add] = 3; + cooling_schedule[gene_to_remove] = 3; + } else { + break; + } + + if current_temperature as u32 >= challenge.min_value { + return Ok(Some(Solution { items: coloring })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + } + + if current_temperature as u32 >= challenge.min_value { + Ok(Some(Solution { items: coloring })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/classic_quadkp/mod.rs b/tig-algorithms/src/knapsack/classic_quadkp/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/knapsack/classic_quadkp/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/classic_quadkp/open_data.rs b/tig-algorithms/src/knapsack/classic_quadkp/open_data.rs new file mode 100644 index 0000000..5c7bc34 --- /dev/null +++ b/tig-algorithms/src/knapsack/classic_quadkp/open_data.rs @@ -0,0 +1,167 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Result; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut edge_costs: Vec<(usize, f32)> = (0..vertex_count) + .map(|flow_index| { + let total_flow = challenge.values[flow_index] as i32 + + challenge.interaction_values[flow_index].iter().sum::(); + let cost = total_flow as f32 / challenge.weights[flow_index] as f32; + (flow_index, cost) + }) + .collect(); + + edge_costs.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut coloring = Vec::with_capacity(vertex_count); + let mut uncolored = Vec::with_capacity(vertex_count); + let mut current_entropy = 0; + let mut current_temperature = 0; + + for &(flow_index, _) in &edge_costs { + if current_entropy + challenge.weights[flow_index] <= challenge.max_weight { + current_entropy += challenge.weights[flow_index]; + current_temperature += challenge.values[flow_index] as i32; + + for &colored in &coloring { + current_temperature += challenge.interaction_values[flow_index][colored]; + } + coloring.push(flow_index); + } else { + uncolored.push(flow_index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for flow_index in 0..vertex_count { + mutation_rates[flow_index] = challenge.values[flow_index] as i32; + for &colored in &coloring { + mutation_rates[flow_index] += challenge.interaction_values[flow_index][colored]; + } + } + + let max_generations = 100; + let mut cooling_schedule = vec![0; vertex_count]; + + for _ in 0..max_generations { + let mut best_mutation = 0; + let mut best_crossover = None; + + for uncolored_index in 0..uncolored.len() { + let mutant = uncolored[uncolored_index]; + if cooling_schedule[mutant] > 0 { + continue; + } + + unsafe { + let mutant_fitness = *mutation_rates.get_unchecked(mutant); + let min_entropy_reduction = *challenge.weights.get_unchecked(mutant) as i32 - (challenge.max_weight as i32 - current_entropy as i32); + + if mutant_fitness < 0 { + continue; + } + + for colored_index in 0..coloring.len() { + let gene_to_remove = *coloring.get_unchecked(colored_index); + if *cooling_schedule.get_unchecked(gene_to_remove) > 0 { + continue; + } + + if min_entropy_reduction > 0 { + let removed_entropy = *challenge.weights.get_unchecked(gene_to_remove) as i32; + if removed_entropy < min_entropy_reduction { + continue; + } + } + + let fitness_change = mutant_fitness - *mutation_rates.get_unchecked(gene_to_remove) + - *challenge.interaction_values.get_unchecked(mutant).get_unchecked(gene_to_remove); + + if fitness_change > best_mutation { + best_mutation = fitness_change; + best_crossover = Some((uncolored_index, colored_index)); + } + } + } + } + + if let Some((uncolored_index, colored_index)) = best_crossover { + let gene_to_add = uncolored[uncolored_index]; + let gene_to_remove = coloring[colored_index]; + + coloring.swap_remove(colored_index); + uncolored.swap_remove(uncolored_index); + coloring.push(gene_to_add); + uncolored.push(gene_to_remove); + + current_temperature += best_mutation; + current_entropy = current_entropy + challenge.weights[gene_to_add] - challenge.weights[gene_to_remove]; + + unsafe { + for flow_index in 0..vertex_count { + *mutation_rates.get_unchecked_mut(flow_index) += + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_add) - + challenge.interaction_values.get_unchecked(flow_index).get_unchecked(gene_to_remove); + } + } + + cooling_schedule[gene_to_add] = 3; + cooling_schedule[gene_to_remove] = 3; + } else { + break; + } + + if current_temperature as u32 >= challenge.min_value { + return Ok(Some(Solution { items: coloring })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + } + + if current_temperature as u32 >= challenge.min_value { + Ok(Some(Solution { items: coloring })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/mod.rs b/tig-algorithms/src/knapsack/mod.rs index 33ddf59..1f8cd19 100644 --- a/tig-algorithms/src/knapsack/mod.rs +++ b/tig-algorithms/src/knapsack/mod.rs @@ -98,7 +98,8 @@ // c003_a050 -// c003_a051 +pub mod classic_quadkp; +pub use classic_quadkp as c003_a051; // c003_a052 diff --git a/tig-algorithms/src/knapsack/template.rs b/tig-algorithms/src/knapsack/template.rs index 6386cbf..dab7384 100644 --- a/tig-algorithms/src/knapsack/template.rs +++ b/tig-algorithms/src/knapsack/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::knapsack::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/knapsack/classic_quadkp.wasm b/tig-algorithms/wasm/knapsack/classic_quadkp.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8fcd958c63ed49e2f81fc3a6f808707b670438a6 GIT binary patch literal 162976 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ν=u9|JJOLEV6z8K)| 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 SwJE&78r Date: Thu, 16 Jan 2025 21:03:33 +0000 Subject: [PATCH 20/49] Compiled knapsack/dynamic --- .../knapsack/dynamic/benchmarker_outbound.rs | 95 ++++++++++++++++++ .../src/knapsack/dynamic/commercial.rs | 95 ++++++++++++++++++ .../src/knapsack/dynamic/inbound.rs | 95 ++++++++++++++++++ .../knapsack/dynamic/innovator_outbound.rs | 95 ++++++++++++++++++ tig-algorithms/src/knapsack/dynamic/mod.rs | 4 + .../src/knapsack/dynamic/open_data.rs | 95 ++++++++++++++++++ tig-algorithms/src/knapsack/mod.rs | 3 +- tig-algorithms/src/knapsack/template.rs | 26 +---- tig-algorithms/wasm/knapsack/dynamic.wasm | Bin 0 -> 155901 bytes 9 files changed, 483 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/knapsack/dynamic/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/knapsack/dynamic/commercial.rs create mode 100644 tig-algorithms/src/knapsack/dynamic/inbound.rs create mode 100644 tig-algorithms/src/knapsack/dynamic/innovator_outbound.rs create mode 100644 tig-algorithms/src/knapsack/dynamic/mod.rs create mode 100644 tig-algorithms/src/knapsack/dynamic/open_data.rs create mode 100644 tig-algorithms/wasm/knapsack/dynamic.wasm diff --git a/tig-algorithms/src/knapsack/dynamic/benchmarker_outbound.rs b/tig-algorithms/src/knapsack/dynamic/benchmarker_outbound.rs new file mode 100644 index 0000000..f6b1570 --- /dev/null +++ b/tig-algorithms/src/knapsack/dynamic/benchmarker_outbound.rs @@ -0,0 +1,95 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight; + let min_value = challenge.min_value; + let num_items = challenge.difficulty.num_items; + + // Sort items by value-to-weight ratio in descending order + let mut sorted_items: Vec = (0..num_items).collect(); + sorted_items.sort_by(|&a, &b| { + let ratio_a = challenge.values[a] as f64 / challenge.weights[a] as f64; + let ratio_b = challenge.values[b] as f64 / challenge.weights[b] as f64; + ratio_b.partial_cmp(&ratio_a).unwrap() + }); + + // Initialize combinations with a single empty combo + let mut combinations: Vec<(Vec, u32, u32)> = vec![(vec![false; num_items], 0, 0)]; + + let mut items = Vec::new(); + for &item in &sorted_items { + // Create new combos with the current item + let mut new_combinations: Vec<(Vec, u32, u32)> = combinations + .iter() + .map(|(combo, value, weight)| { + let mut new_combo = combo.clone(); + new_combo[item] = true; + ( + new_combo, + value + challenge.values[item], + weight + challenge.weights[item], + ) + }) + .filter(|&(_, _, weight)| weight <= max_weight) // Keep only combos within weight limit + .collect(); + + // Check if any new combination meets the minimum value requirement + if let Some((combo, _, _)) = new_combinations + .iter() + .find(|&&(_, value, _)| value >= min_value) + { + items = combo + .iter() + .enumerate() + .filter_map(|(i, &included)| if included { Some(i) } else { None }) + .collect(); + break; + } + + // Merge new_combinations with existing combinations + combinations.append(&mut new_combinations); + + // Deduplicate combinations by keeping the highest value for each weight + combinations.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1))); // Sort by weight, then by value + combinations.dedup_by(|a, b| a.2 == b.2 && a.1 <= b.1); // Deduplicate by weight, keeping highest value + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/dynamic/commercial.rs b/tig-algorithms/src/knapsack/dynamic/commercial.rs new file mode 100644 index 0000000..5814371 --- /dev/null +++ b/tig-algorithms/src/knapsack/dynamic/commercial.rs @@ -0,0 +1,95 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight; + let min_value = challenge.min_value; + let num_items = challenge.difficulty.num_items; + + // Sort items by value-to-weight ratio in descending order + let mut sorted_items: Vec = (0..num_items).collect(); + sorted_items.sort_by(|&a, &b| { + let ratio_a = challenge.values[a] as f64 / challenge.weights[a] as f64; + let ratio_b = challenge.values[b] as f64 / challenge.weights[b] as f64; + ratio_b.partial_cmp(&ratio_a).unwrap() + }); + + // Initialize combinations with a single empty combo + let mut combinations: Vec<(Vec, u32, u32)> = vec![(vec![false; num_items], 0, 0)]; + + let mut items = Vec::new(); + for &item in &sorted_items { + // Create new combos with the current item + let mut new_combinations: Vec<(Vec, u32, u32)> = combinations + .iter() + .map(|(combo, value, weight)| { + let mut new_combo = combo.clone(); + new_combo[item] = true; + ( + new_combo, + value + challenge.values[item], + weight + challenge.weights[item], + ) + }) + .filter(|&(_, _, weight)| weight <= max_weight) // Keep only combos within weight limit + .collect(); + + // Check if any new combination meets the minimum value requirement + if let Some((combo, _, _)) = new_combinations + .iter() + .find(|&&(_, value, _)| value >= min_value) + { + items = combo + .iter() + .enumerate() + .filter_map(|(i, &included)| if included { Some(i) } else { None }) + .collect(); + break; + } + + // Merge new_combinations with existing combinations + combinations.append(&mut new_combinations); + + // Deduplicate combinations by keeping the highest value for each weight + combinations.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1))); // Sort by weight, then by value + combinations.dedup_by(|a, b| a.2 == b.2 && a.1 <= b.1); // Deduplicate by weight, keeping highest value + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/dynamic/inbound.rs b/tig-algorithms/src/knapsack/dynamic/inbound.rs new file mode 100644 index 0000000..216e1c9 --- /dev/null +++ b/tig-algorithms/src/knapsack/dynamic/inbound.rs @@ -0,0 +1,95 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight; + let min_value = challenge.min_value; + let num_items = challenge.difficulty.num_items; + + // Sort items by value-to-weight ratio in descending order + let mut sorted_items: Vec = (0..num_items).collect(); + sorted_items.sort_by(|&a, &b| { + let ratio_a = challenge.values[a] as f64 / challenge.weights[a] as f64; + let ratio_b = challenge.values[b] as f64 / challenge.weights[b] as f64; + ratio_b.partial_cmp(&ratio_a).unwrap() + }); + + // Initialize combinations with a single empty combo + let mut combinations: Vec<(Vec, u32, u32)> = vec![(vec![false; num_items], 0, 0)]; + + let mut items = Vec::new(); + for &item in &sorted_items { + // Create new combos with the current item + let mut new_combinations: Vec<(Vec, u32, u32)> = combinations + .iter() + .map(|(combo, value, weight)| { + let mut new_combo = combo.clone(); + new_combo[item] = true; + ( + new_combo, + value + challenge.values[item], + weight + challenge.weights[item], + ) + }) + .filter(|&(_, _, weight)| weight <= max_weight) // Keep only combos within weight limit + .collect(); + + // Check if any new combination meets the minimum value requirement + if let Some((combo, _, _)) = new_combinations + .iter() + .find(|&&(_, value, _)| value >= min_value) + { + items = combo + .iter() + .enumerate() + .filter_map(|(i, &included)| if included { Some(i) } else { None }) + .collect(); + break; + } + + // Merge new_combinations with existing combinations + combinations.append(&mut new_combinations); + + // Deduplicate combinations by keeping the highest value for each weight + combinations.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1))); // Sort by weight, then by value + combinations.dedup_by(|a, b| a.2 == b.2 && a.1 <= b.1); // Deduplicate by weight, keeping highest value + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/dynamic/innovator_outbound.rs b/tig-algorithms/src/knapsack/dynamic/innovator_outbound.rs new file mode 100644 index 0000000..9889e0a --- /dev/null +++ b/tig-algorithms/src/knapsack/dynamic/innovator_outbound.rs @@ -0,0 +1,95 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight; + let min_value = challenge.min_value; + let num_items = challenge.difficulty.num_items; + + // Sort items by value-to-weight ratio in descending order + let mut sorted_items: Vec = (0..num_items).collect(); + sorted_items.sort_by(|&a, &b| { + let ratio_a = challenge.values[a] as f64 / challenge.weights[a] as f64; + let ratio_b = challenge.values[b] as f64 / challenge.weights[b] as f64; + ratio_b.partial_cmp(&ratio_a).unwrap() + }); + + // Initialize combinations with a single empty combo + let mut combinations: Vec<(Vec, u32, u32)> = vec![(vec![false; num_items], 0, 0)]; + + let mut items = Vec::new(); + for &item in &sorted_items { + // Create new combos with the current item + let mut new_combinations: Vec<(Vec, u32, u32)> = combinations + .iter() + .map(|(combo, value, weight)| { + let mut new_combo = combo.clone(); + new_combo[item] = true; + ( + new_combo, + value + challenge.values[item], + weight + challenge.weights[item], + ) + }) + .filter(|&(_, _, weight)| weight <= max_weight) // Keep only combos within weight limit + .collect(); + + // Check if any new combination meets the minimum value requirement + if let Some((combo, _, _)) = new_combinations + .iter() + .find(|&&(_, value, _)| value >= min_value) + { + items = combo + .iter() + .enumerate() + .filter_map(|(i, &included)| if included { Some(i) } else { None }) + .collect(); + break; + } + + // Merge new_combinations with existing combinations + combinations.append(&mut new_combinations); + + // Deduplicate combinations by keeping the highest value for each weight + combinations.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1))); // Sort by weight, then by value + combinations.dedup_by(|a, b| a.2 == b.2 && a.1 <= b.1); // Deduplicate by weight, keeping highest value + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/dynamic/mod.rs b/tig-algorithms/src/knapsack/dynamic/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/knapsack/dynamic/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/dynamic/open_data.rs b/tig-algorithms/src/knapsack/dynamic/open_data.rs new file mode 100644 index 0000000..9098047 --- /dev/null +++ b/tig-algorithms/src/knapsack/dynamic/open_data.rs @@ -0,0 +1,95 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight; + let min_value = challenge.min_value; + let num_items = challenge.difficulty.num_items; + + // Sort items by value-to-weight ratio in descending order + let mut sorted_items: Vec = (0..num_items).collect(); + sorted_items.sort_by(|&a, &b| { + let ratio_a = challenge.values[a] as f64 / challenge.weights[a] as f64; + let ratio_b = challenge.values[b] as f64 / challenge.weights[b] as f64; + ratio_b.partial_cmp(&ratio_a).unwrap() + }); + + // Initialize combinations with a single empty combo + let mut combinations: Vec<(Vec, u32, u32)> = vec![(vec![false; num_items], 0, 0)]; + + let mut items = Vec::new(); + for &item in &sorted_items { + // Create new combos with the current item + let mut new_combinations: Vec<(Vec, u32, u32)> = combinations + .iter() + .map(|(combo, value, weight)| { + let mut new_combo = combo.clone(); + new_combo[item] = true; + ( + new_combo, + value + challenge.values[item], + weight + challenge.weights[item], + ) + }) + .filter(|&(_, _, weight)| weight <= max_weight) // Keep only combos within weight limit + .collect(); + + // Check if any new combination meets the minimum value requirement + if let Some((combo, _, _)) = new_combinations + .iter() + .find(|&&(_, value, _)| value >= min_value) + { + items = combo + .iter() + .enumerate() + .filter_map(|(i, &included)| if included { Some(i) } else { None }) + .collect(); + break; + } + + // Merge new_combinations with existing combinations + combinations.append(&mut new_combinations); + + // Deduplicate combinations by keeping the highest value for each weight + combinations.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| b.1.cmp(&a.1))); // Sort by weight, then by value + combinations.dedup_by(|a, b| a.2 == b.2 && a.1 <= b.1); // Deduplicate by weight, keeping highest value + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/mod.rs b/tig-algorithms/src/knapsack/mod.rs index 33ddf59..384121e 100644 --- a/tig-algorithms/src/knapsack/mod.rs +++ b/tig-algorithms/src/knapsack/mod.rs @@ -1,4 +1,5 @@ -// c003_a001 +pub mod dynamic; +pub use dynamic as c003_a001; // c003_a002 diff --git a/tig-algorithms/src/knapsack/template.rs b/tig-algorithms/src/knapsack/template.rs index 6386cbf..dab7384 100644 --- a/tig-algorithms/src/knapsack/template.rs +++ b/tig-algorithms/src/knapsack/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::knapsack::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/knapsack/dynamic.wasm b/tig-algorithms/wasm/knapsack/dynamic.wasm new file mode 100644 index 0000000000000000000000000000000000000000..f40eead5177b5c142e9939efc4b6a7e317fde8bd GIT binary patch literal 155901 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 literal 0 HcmV?d00001 From e7f98cd403399d8d07ec8a35cdd3a0a777af57d6 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:04:03 +0000 Subject: [PATCH 21/49] Compiled knapsack/knapheudp --- .../knapheudp/benchmarker_outbound.rs | 110 ++++++++++++++++++ .../src/knapsack/knapheudp/commercial.rs | 110 ++++++++++++++++++ .../src/knapsack/knapheudp/inbound.rs | 110 ++++++++++++++++++ .../knapsack/knapheudp/innovator_outbound.rs | 110 ++++++++++++++++++ tig-algorithms/src/knapsack/knapheudp/mod.rs | 4 + .../src/knapsack/knapheudp/open_data.rs | 110 ++++++++++++++++++ tig-algorithms/src/knapsack/mod.rs | 3 +- tig-algorithms/src/knapsack/template.rs | 26 +---- tig-algorithms/wasm/knapsack/knapheudp.wasm | Bin 0 -> 156587 bytes 9 files changed, 558 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/knapsack/knapheudp/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/knapsack/knapheudp/commercial.rs create mode 100644 tig-algorithms/src/knapsack/knapheudp/inbound.rs create mode 100644 tig-algorithms/src/knapsack/knapheudp/innovator_outbound.rs create mode 100644 tig-algorithms/src/knapsack/knapheudp/mod.rs create mode 100644 tig-algorithms/src/knapsack/knapheudp/open_data.rs create mode 100644 tig-algorithms/wasm/knapsack/knapheudp.wasm diff --git a/tig-algorithms/src/knapsack/knapheudp/benchmarker_outbound.rs b/tig-algorithms/src/knapsack/knapheudp/benchmarker_outbound.rs new file mode 100644 index 0000000..863a240 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapheudp/benchmarker_outbound.rs @@ -0,0 +1,110 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let weights: Vec = challenge.weights.iter().map(|&w| w as usize).collect(); + let values: Vec = challenge.values.iter().map(|&v| v as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let mut dp = vec![0; max_weight + 1]; + let mut selected = vec![vec![false; max_weight + 1]; num_items]; + + for (i, &(item_index, _)) in sorted_items.iter().enumerate() { + let weight = weights[item_index]; + let value = values[item_index]; + + for w in (weight..=max_weight).rev() { + let new_value = dp[w - weight] + value; + if new_value > dp[w] { + dp[w] = new_value; + selected[i][w] = true; + } + } + + if dp[max_weight] >= min_value { + break; + } + } + + if dp[max_weight] < min_value { + return Ok(None); + } + + let mut items = Vec::new(); + let mut w = max_weight; + for i in (0..num_items).rev() { + if selected[i][w] { + let item_index = sorted_items[i].0; + items.push(item_index); + w -= weights[item_index]; + } + if w == 0 { + break; + } + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapheudp/commercial.rs b/tig-algorithms/src/knapsack/knapheudp/commercial.rs new file mode 100644 index 0000000..49af535 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapheudp/commercial.rs @@ -0,0 +1,110 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let weights: Vec = challenge.weights.iter().map(|&w| w as usize).collect(); + let values: Vec = challenge.values.iter().map(|&v| v as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let mut dp = vec![0; max_weight + 1]; + let mut selected = vec![vec![false; max_weight + 1]; num_items]; + + for (i, &(item_index, _)) in sorted_items.iter().enumerate() { + let weight = weights[item_index]; + let value = values[item_index]; + + for w in (weight..=max_weight).rev() { + let new_value = dp[w - weight] + value; + if new_value > dp[w] { + dp[w] = new_value; + selected[i][w] = true; + } + } + + if dp[max_weight] >= min_value { + break; + } + } + + if dp[max_weight] < min_value { + return Ok(None); + } + + let mut items = Vec::new(); + let mut w = max_weight; + for i in (0..num_items).rev() { + if selected[i][w] { + let item_index = sorted_items[i].0; + items.push(item_index); + w -= weights[item_index]; + } + if w == 0 { + break; + } + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapheudp/inbound.rs b/tig-algorithms/src/knapsack/knapheudp/inbound.rs new file mode 100644 index 0000000..7a516c2 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapheudp/inbound.rs @@ -0,0 +1,110 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let weights: Vec = challenge.weights.iter().map(|&w| w as usize).collect(); + let values: Vec = challenge.values.iter().map(|&v| v as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let mut dp = vec![0; max_weight + 1]; + let mut selected = vec![vec![false; max_weight + 1]; num_items]; + + for (i, &(item_index, _)) in sorted_items.iter().enumerate() { + let weight = weights[item_index]; + let value = values[item_index]; + + for w in (weight..=max_weight).rev() { + let new_value = dp[w - weight] + value; + if new_value > dp[w] { + dp[w] = new_value; + selected[i][w] = true; + } + } + + if dp[max_weight] >= min_value { + break; + } + } + + if dp[max_weight] < min_value { + return Ok(None); + } + + let mut items = Vec::new(); + let mut w = max_weight; + for i in (0..num_items).rev() { + if selected[i][w] { + let item_index = sorted_items[i].0; + items.push(item_index); + w -= weights[item_index]; + } + if w == 0 { + break; + } + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapheudp/innovator_outbound.rs b/tig-algorithms/src/knapsack/knapheudp/innovator_outbound.rs new file mode 100644 index 0000000..927bd2b --- /dev/null +++ b/tig-algorithms/src/knapsack/knapheudp/innovator_outbound.rs @@ -0,0 +1,110 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let weights: Vec = challenge.weights.iter().map(|&w| w as usize).collect(); + let values: Vec = challenge.values.iter().map(|&v| v as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let mut dp = vec![0; max_weight + 1]; + let mut selected = vec![vec![false; max_weight + 1]; num_items]; + + for (i, &(item_index, _)) in sorted_items.iter().enumerate() { + let weight = weights[item_index]; + let value = values[item_index]; + + for w in (weight..=max_weight).rev() { + let new_value = dp[w - weight] + value; + if new_value > dp[w] { + dp[w] = new_value; + selected[i][w] = true; + } + } + + if dp[max_weight] >= min_value { + break; + } + } + + if dp[max_weight] < min_value { + return Ok(None); + } + + let mut items = Vec::new(); + let mut w = max_weight; + for i in (0..num_items).rev() { + if selected[i][w] { + let item_index = sorted_items[i].0; + items.push(item_index); + w -= weights[item_index]; + } + if w == 0 { + break; + } + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapheudp/mod.rs b/tig-algorithms/src/knapsack/knapheudp/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapheudp/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/knapheudp/open_data.rs b/tig-algorithms/src/knapsack/knapheudp/open_data.rs new file mode 100644 index 0000000..f9a8ce3 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapheudp/open_data.rs @@ -0,0 +1,110 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let weights: Vec = challenge.weights.iter().map(|&w| w as usize).collect(); + let values: Vec = challenge.values.iter().map(|&v| v as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let mut dp = vec![0; max_weight + 1]; + let mut selected = vec![vec![false; max_weight + 1]; num_items]; + + for (i, &(item_index, _)) in sorted_items.iter().enumerate() { + let weight = weights[item_index]; + let value = values[item_index]; + + for w in (weight..=max_weight).rev() { + let new_value = dp[w - weight] + value; + if new_value > dp[w] { + dp[w] = new_value; + selected[i][w] = true; + } + } + + if dp[max_weight] >= min_value { + break; + } + } + + if dp[max_weight] < min_value { + return Ok(None); + } + + let mut items = Vec::new(); + let mut w = max_weight; + for i in (0..num_items).rev() { + if selected[i][w] { + let item_index = sorted_items[i].0; + items.push(item_index); + w -= weights[item_index]; + } + if w == 0 { + break; + } + } + + Ok(Some(Solution { items })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/mod.rs b/tig-algorithms/src/knapsack/mod.rs index 33ddf59..83e4ad1 100644 --- a/tig-algorithms/src/knapsack/mod.rs +++ b/tig-algorithms/src/knapsack/mod.rs @@ -34,7 +34,8 @@ // c003_a018 -// c003_a019 +pub mod knapheudp; +pub use knapheudp as c003_a019; // c003_a020 diff --git a/tig-algorithms/src/knapsack/template.rs b/tig-algorithms/src/knapsack/template.rs index 6386cbf..dab7384 100644 --- a/tig-algorithms/src/knapsack/template.rs +++ b/tig-algorithms/src/knapsack/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::knapsack::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/knapsack/knapheudp.wasm b/tig-algorithms/wasm/knapsack/knapheudp.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d918cce9e26ea0dad4dc9c82eba31dace5e6d5ae GIT binary patch literal 156587 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

vD@(J>cGtU!n;%N3IB&FPU5N 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

c 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 Date: Thu, 16 Jan 2025 21:04:06 +0000 Subject: [PATCH 22/49] Compiled knapsack/knapmaxxing --- .../knapmaxxing/benchmarker_outbound.rs | 115 ++++++++++++++++++ .../src/knapsack/knapmaxxing/commercial.rs | 115 ++++++++++++++++++ .../src/knapsack/knapmaxxing/inbound.rs | 115 ++++++++++++++++++ .../knapmaxxing/innovator_outbound.rs | 115 ++++++++++++++++++ .../src/knapsack/knapmaxxing/mod.rs | 4 + .../src/knapsack/knapmaxxing/open_data.rs | 115 ++++++++++++++++++ tig-algorithms/src/knapsack/mod.rs | 3 +- tig-algorithms/src/knapsack/template.rs | 26 +--- tig-algorithms/wasm/knapsack/knapmaxxing.wasm | Bin 0 -> 156011 bytes 9 files changed, 583 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/knapsack/knapmaxxing/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/knapsack/knapmaxxing/commercial.rs create mode 100644 tig-algorithms/src/knapsack/knapmaxxing/inbound.rs create mode 100644 tig-algorithms/src/knapsack/knapmaxxing/innovator_outbound.rs create mode 100644 tig-algorithms/src/knapsack/knapmaxxing/mod.rs create mode 100644 tig-algorithms/src/knapsack/knapmaxxing/open_data.rs create mode 100644 tig-algorithms/wasm/knapsack/knapmaxxing.wasm diff --git a/tig-algorithms/src/knapsack/knapmaxxing/benchmarker_outbound.rs b/tig-algorithms/src/knapsack/knapmaxxing/benchmarker_outbound.rs new file mode 100644 index 0000000..86f9f7f --- /dev/null +++ b/tig-algorithms/src/knapsack/knapmaxxing/benchmarker_outbound.rs @@ -0,0 +1,115 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let max_weight_plus_one = max_weight + 1; + + let weights: Vec = challenge.weights.iter().map(|weight| *weight as usize).collect(); + let values: Vec = challenge.values.iter().map(|value| *value as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let num_states = (num_items + 1) * (max_weight_plus_one); + let mut dp = vec![0; num_states]; + + for i in 1..=num_items { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one; + let i_times_max_weight_plus_one = i * max_weight_plus_one; + for w in (item_weight..=max_weight).rev() { + let prev_state = i_minus_one_times_max_weight_plus_one + w; + let curr_state = i_times_max_weight_plus_one + w; + dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value); + } + } + + let mut items = Vec::with_capacity(num_items); + let mut i = num_items; + let mut w = max_weight; + let mut total_value = 0; + while i > 0 && total_value < min_value { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let prev_state = (i - 1) * (max_weight_plus_one) + w; + let curr_state = i * (max_weight_plus_one) + w; + if dp[curr_state] != dp[prev_state] { + items.push(item_index); + w -= item_weight; + total_value += item_value; + } + i -= 1; + } + + if total_value >= min_value { + Ok(Some(Solution { items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapmaxxing/commercial.rs b/tig-algorithms/src/knapsack/knapmaxxing/commercial.rs new file mode 100644 index 0000000..9b4962b --- /dev/null +++ b/tig-algorithms/src/knapsack/knapmaxxing/commercial.rs @@ -0,0 +1,115 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let max_weight_plus_one = max_weight + 1; + + let weights: Vec = challenge.weights.iter().map(|weight| *weight as usize).collect(); + let values: Vec = challenge.values.iter().map(|value| *value as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let num_states = (num_items + 1) * (max_weight_plus_one); + let mut dp = vec![0; num_states]; + + for i in 1..=num_items { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one; + let i_times_max_weight_plus_one = i * max_weight_plus_one; + for w in (item_weight..=max_weight).rev() { + let prev_state = i_minus_one_times_max_weight_plus_one + w; + let curr_state = i_times_max_weight_plus_one + w; + dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value); + } + } + + let mut items = Vec::with_capacity(num_items); + let mut i = num_items; + let mut w = max_weight; + let mut total_value = 0; + while i > 0 && total_value < min_value { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let prev_state = (i - 1) * (max_weight_plus_one) + w; + let curr_state = i * (max_weight_plus_one) + w; + if dp[curr_state] != dp[prev_state] { + items.push(item_index); + w -= item_weight; + total_value += item_value; + } + i -= 1; + } + + if total_value >= min_value { + Ok(Some(Solution { items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapmaxxing/inbound.rs b/tig-algorithms/src/knapsack/knapmaxxing/inbound.rs new file mode 100644 index 0000000..7fd304a --- /dev/null +++ b/tig-algorithms/src/knapsack/knapmaxxing/inbound.rs @@ -0,0 +1,115 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let max_weight_plus_one = max_weight + 1; + + let weights: Vec = challenge.weights.iter().map(|weight| *weight as usize).collect(); + let values: Vec = challenge.values.iter().map(|value| *value as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let num_states = (num_items + 1) * (max_weight_plus_one); + let mut dp = vec![0; num_states]; + + for i in 1..=num_items { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one; + let i_times_max_weight_plus_one = i * max_weight_plus_one; + for w in (item_weight..=max_weight).rev() { + let prev_state = i_minus_one_times_max_weight_plus_one + w; + let curr_state = i_times_max_weight_plus_one + w; + dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value); + } + } + + let mut items = Vec::with_capacity(num_items); + let mut i = num_items; + let mut w = max_weight; + let mut total_value = 0; + while i > 0 && total_value < min_value { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let prev_state = (i - 1) * (max_weight_plus_one) + w; + let curr_state = i * (max_weight_plus_one) + w; + if dp[curr_state] != dp[prev_state] { + items.push(item_index); + w -= item_weight; + total_value += item_value; + } + i -= 1; + } + + if total_value >= min_value { + Ok(Some(Solution { items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapmaxxing/innovator_outbound.rs b/tig-algorithms/src/knapsack/knapmaxxing/innovator_outbound.rs new file mode 100644 index 0000000..a730bb6 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapmaxxing/innovator_outbound.rs @@ -0,0 +1,115 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let max_weight_plus_one = max_weight + 1; + + let weights: Vec = challenge.weights.iter().map(|weight| *weight as usize).collect(); + let values: Vec = challenge.values.iter().map(|value| *value as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let num_states = (num_items + 1) * (max_weight_plus_one); + let mut dp = vec![0; num_states]; + + for i in 1..=num_items { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one; + let i_times_max_weight_plus_one = i * max_weight_plus_one; + for w in (item_weight..=max_weight).rev() { + let prev_state = i_minus_one_times_max_weight_plus_one + w; + let curr_state = i_times_max_weight_plus_one + w; + dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value); + } + } + + let mut items = Vec::with_capacity(num_items); + let mut i = num_items; + let mut w = max_weight; + let mut total_value = 0; + while i > 0 && total_value < min_value { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let prev_state = (i - 1) * (max_weight_plus_one) + w; + let curr_state = i * (max_weight_plus_one) + w; + if dp[curr_state] != dp[prev_state] { + items.push(item_index); + w -= item_weight; + total_value += item_value; + } + i -= 1; + } + + if total_value >= min_value { + Ok(Some(Solution { items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/knapmaxxing/mod.rs b/tig-algorithms/src/knapsack/knapmaxxing/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapmaxxing/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/knapmaxxing/open_data.rs b/tig-algorithms/src/knapsack/knapmaxxing/open_data.rs new file mode 100644 index 0000000..e2849d9 --- /dev/null +++ b/tig-algorithms/src/knapsack/knapmaxxing/open_data.rs @@ -0,0 +1,115 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::knapsack::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_weight = challenge.max_weight as usize; + let min_value = challenge.min_value as usize; + let num_items = challenge.difficulty.num_items; + + let max_weight_plus_one = max_weight + 1; + + let weights: Vec = challenge.weights.iter().map(|weight| *weight as usize).collect(); + let values: Vec = challenge.values.iter().map(|value| *value as usize).collect(); + + let mut sorted_items: Vec<(usize, f64)> = (0..num_items) + .map(|i| (i, values[i] as f64 / weights[i] as f64)) + .collect(); + sorted_items.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut upper_bound = 0; + let mut remaining_weight = max_weight; + for &(item_index, ratio) in &sorted_items { + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + if item_weight <= remaining_weight { + upper_bound += item_value; + remaining_weight -= item_weight; + } else { + upper_bound += (ratio * remaining_weight as f64).floor() as usize; + break; + } + } + + if upper_bound < min_value { + return Ok(None); + } + + let num_states = (num_items + 1) * (max_weight_plus_one); + let mut dp = vec![0; num_states]; + + for i in 1..=num_items { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let i_minus_one_times_max_weight_plus_one = (i - 1) * max_weight_plus_one; + let i_times_max_weight_plus_one = i * max_weight_plus_one; + for w in (item_weight..=max_weight).rev() { + let prev_state = i_minus_one_times_max_weight_plus_one + w; + let curr_state = i_times_max_weight_plus_one + w; + dp[curr_state] = dp[prev_state].max(dp[prev_state - item_weight] + item_value); + } + } + + let mut items = Vec::with_capacity(num_items); + let mut i = num_items; + let mut w = max_weight; + let mut total_value = 0; + while i > 0 && total_value < min_value { + let (item_index, _) = sorted_items[i - 1]; + let item_weight = weights[item_index]; + let item_value = values[item_index]; + + let prev_state = (i - 1) * (max_weight_plus_one) + w; + let curr_state = i * (max_weight_plus_one) + w; + if dp[curr_state] != dp[prev_state] { + items.push(item_index); + w -= item_weight; + total_value += item_value; + } + i -= 1; + } + + if total_value >= min_value { + Ok(Some(Solution { items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/knapsack/mod.rs b/tig-algorithms/src/knapsack/mod.rs index 33ddf59..7a46bee 100644 --- a/tig-algorithms/src/knapsack/mod.rs +++ b/tig-algorithms/src/knapsack/mod.rs @@ -10,7 +10,8 @@ // c003_a006 -// c003_a007 +pub mod knapmaxxing; +pub use knapmaxxing as c003_a007; // c003_a008 diff --git a/tig-algorithms/src/knapsack/template.rs b/tig-algorithms/src/knapsack/template.rs index 6386cbf..dab7384 100644 --- a/tig-algorithms/src/knapsack/template.rs +++ b/tig-algorithms/src/knapsack/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::knapsack::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/knapsack/knapmaxxing.wasm b/tig-algorithms/wasm/knapsack/knapmaxxing.wasm new file mode 100644 index 0000000000000000000000000000000000000000..b638fd0fd51e2ff422d3150c0ce8047cdc929282 GIT binary patch literal 156011 zcmeFa3!GoaRp)tM|M&mj{g>3TWw-3EdnMZ~%ZgeQ+8s&h`&jox+>b8@I9succX>Og3~+J6WF+U8|TsX&l#TwXEF?g>f2DT+7mgDm)ASHE5Mi zshp)*x`C@Wt^-^e@dFa0`o>LlI*s2M-+p@y05;THAj~f>pN=|>JgtBK_k909+W&(| zlzrcw-*+JTXx#apI}hyt!S8(MKl;8q4@AElx0d3_$DQwNUNYG@9=|94*2VY6n~VSU zvbXL32a6jf+M71twD+z5-S>RoP2YL@tG{*fhvVjL_r>pvPsH^LUj41#b@6rIeelAK z(?4+0jtl1gtN+^nllb*-{`On{hqr(4JLc~?@Q;7+-An$S_<|pN_dku7e7+yFG0jX&#Q3wnhHQWT8m6N6DVSzaxXI zVc8xR%_e=uxxDa(Ukt1iv~VhPySpZ%Q=3ov`%VD9!x5NiQRLJKpxFp4MX#p&$R`Uf^A8=l!zJ^f-ijJAjna2(?(CT_UwK;|xpv-iQ7`HM z@DQqw>&(6Ghl|!du61Y}#O~iEh@DQd$3;6q+XlDcZ6IyKXMpZ(^1M|CaU01vxrL~t2rW3-1WMB6k?yWOY*Z`DU$t`7mM3qbA-AqHN3 z-v1kc*X@SU7B-Z!+j#gUs07r}H$i21vtw`0=JkM}Lsmyv$7|-Cnn%?8y;I_AF^ZqaFEk?1WNUra-L45r{Zdjj!w_Y8-lpsNWR6}cF8n)9?_#Y3tANy2cK`87FYnBz~gpOjQ$AlSsww?U$2 z)_|gH0lMfXCB8ge+PJ}(4gW?EQKHRTr>Wm{UGyk~mDjsz2fdNkA)mVBVnC}siN(4V zO0P4D5bhZU1Z$)g`8^9o-8FB3)FS_ls6dJ_BSCcsGjgRt!^*LA(yPA!3?hX3fl{>023e$&APO_o0!E&9{ZT}9k~ zOdWiZhUn5or{<2_vs88aD5ZnG9<;tr$HTrJjYD6LSy|Or+2V7d#Q^_P)=(?leBK&* zJ~Y%8&`;4&2PmEi4YgfIK!=8Qq@Ry-8`^_$&d7G8FT^?1?vpo(Wc};j)XTut5tp30 z7ub*9a(yrB!s8t`d5C(Eo4jQ)hjdj2BI!H!XP(RM@Ee8tbl*b2dg7md`gCMtlPCxE z?Eo$0OfN4~54)}`e~xm{97bVqy=$qMplCS^qURlxk=rUYu;bAFURWYfTZ?Z zTW?S3r*m6&WCVRhmqHUGR;5#vm4pOZq^*NAmF>fn5YGVEJWdpCwRi(eQq7YOYguX^etQ0cixhpim2U2_45 z1*>{{^oW|7_T0%H>x>Y2szXw{Dy6 z6eyX+Oa!f-SbL$%nVB1VwNXyNIQu`$JffA82%+mD*?q}=w7>C_ ztV}R?Q!jN%7ptr_cp076Pnm-F^>kOQRU8)3ZcLU_@r4r4V#cx^|35urD--b}yw_Ay zv8n@gRfqNlOq|dy@;vH)gaOR^R{&VIF~a1Tf9z>B=pnNOGv7=LXJU6C@WAWS*H8Oo zApjG4!^$4%t&1U-$z+Pm{*fl3F%ap$8oeIG#lH0h1}EMLm`VK1K|xh7#fNd#_{z(w zB>O{^fG`^;v*Uk}-m}p`*3HH)>ZNR|+c1HpoD#;eY93xQFjAaGedMD)ej|^%wqY-D;3JcLD8*?^5}QsM@~F>?}eMbm%&Q~0QKS-)N;534{y$c$pC z?=45sL#6;e*yrblUfiV>4MfA(b5n0ZEXtZERA}7Tn>1EL)mP)hDcI0B8wTyjI2&t> zQ!Ts3V5k@jnjML;);xw<-9H^IOk5bZ^k0Mj>vq*@)%eX?sj4!0j3n_7-hDdi?}k1< z#Rc@nqo$u+^q(vTjpL)~vxCEX^Fe^Q1#iO@4(qEyWWMUxMM72cx(GLjfBKKl+?~qF zNcDMo@sEf8>K*^Na8>L0)8Q)X_+JfI_yay2uCORSX;+M{0Kpx+w5KCK>yJ~~zlwR$ zEDNkyNLD`*PJ^i~DI_Z`Y@W_{0!?>tE5(>^0V56ns2T(#pGo5y#+i$3Ro$1HGtLv6 zH{X;%jP&SpU%qm=;P+P_eRA?J4o+5JUoht;KktOsrSI^=^-j**`_PXtDarqlHBD1d+*mUteFKYp=s z(00o>2-f4;ggJZ$RJt7pr-SoRzV-f<01)?UQRl-%OC+OrqyDxdy*!ez%g+`ytNUjw>s}Xi^-LH^ zJXUs3KhhZ*27lsyi+N3UMcq7A6HyVXW9Ee5}& zR-Y~EN~3(>1gwl28vv~JnLYUrzs68TpU7Fp5&H;Kwqz}OeoEB-MJ2mwnGBB|dMvA=}AuK642iZvzD0H2TW0e1*2 z|8d~yS3nN#K9MBZk@zqO+=CFR@W=33c!H^2QIj>FkeU^lP|1LWN}h2$<$qX%{3rZz zHI^)t)~5BKB-9>yn2Ijh5S#D8ESmDo9}nmmudFU|7B}Gdx@cM&en&EOjZ7K;+y_oW zg0G}3_0v%ARCwCFCfTl9QQn|{sKm7@$MCs2xk6XFB6nT%8m=1GnEjOcE-&u}QkhI! zK{l{yh>+TDe_sHK>sOYnc3p(BD?Rg?`#lRrb-(ggpt^5KMybxo^shj5{k^DTSlh_X zjFEE^-MPD3ljvSvPcMV!el1CAf##6Iwe69=%;ZBFhGifTD@9xbgJAufq|2}#!$`oMH%R7H zKGJ8I z>5GAm)!7LAg>b^hEa}o=>56)K>F+gtb5V*EUbjfpZmDGimdm@L9mx>4E*Nx-oybU% zJy05h!Xz;8@WKFGAF&|r^6{j!<_fT-L|oUJcTM{BQ7~rIXx=pC17yI0#YMAEZ%!u%Qm-GR6a1Z8(OKE#h-d9ij zRSbk0C$TZDDz!8z=W)C>!-Q+@RfWzw5>h^-8{kvv2Dn9L13Et}AkBJ8{Wl(#Xl}Z6 zE|K+=2D=IK9~PTGh5a-uN$gTxIxbUIo|>$%cJ03^>il6mYS2I`4BRX!>Sfa5Q~pab zQgDV1lnbnol*Wlm$1Ij6fm_u^-}j{4lUhbR%0oSzYSCgtdY3 zHdY<;0;4^~($JJQMG@A|do=GnT+#GaM51J|3>g@)G-PT6L#D>VV93~0Gi0b0NrJnU z3>jlzGi10zn8=W+xwZ@$y=;bz{b~^XH-iurmayBsv;{Vfpw`n76Nl^4#L4kagIBH8 zks4a5*D5Qg1B$^dmh#5VA2kAt;@+|~hY|1|{na$k0AemqQkmv@3FlPFxW6Uy-BubU z=5zE)#kedkMwS{d?y;{>J1jJO4PIq=FA;5F|H2lD`xr?oaa}GMe=`atrsKcyRPfW} z%n*HE7j0r}zby#V=B9c46#X1*;ViQz`|m>F-KRbqR_Gg}@_}x(Sdo^gmNsF{8jRz= zF*#8HDCsGzl%@z2>vsz)L4_^A<*taQEX0D4`C=H$P{>>tZPhdR0u8Gd=||>q7$fUb z##}Dfwt{-zWEr|*On6&j&=Mpc;t>F-3YKqVF#`@RQtl3r*n-XGcd%e%uFH9)j-sHN z{Ls&o|FWPRUAk66`vPD_XaloA55QLQ6a|?3)x2Ok4 zv4M7N$;~tLbs*%zbeq+CTEnp{po)}B9rTih^Ob;%M8j*HV??;Rh9hF7@=`|x)a#B2 zmSAqCe7T+;PMk4|d4tjU+alG93?XLOzw(9eIYpe zMbD%SB{EEX6fC1C2eP~^d6CKw+?mfOc$V31#%<9=iXgzxl1(X{{BW!gV9OkR{5&1u z@Z&wXWsB@aRD(nzc}1E;Ib-vh+NRegwkqa(^hVfL_7xpxYB4y{=_U5BTf{?}`I$vz zZRWpI{-id!Rj#!^XBmKl%VN@wx}d!<#ft4Zp_x3;&*-+HL z4i&Wg$iW=4-6{ujO%^y=cB*B5S$=NpnOhy%Kkzb>Jpf@7=L3QG6`8Vb(UOgri~vI) zxT^8oIL#(@aS%p=t!UZ^a6SuK!r%5XNlJ1UXe4_TB~}L1GErPEu>y6oob8=v(pW;g z^s2y0`ocod27t6h9-l%l|C8y{!sooLqOpa1{^@@4pzMIicGh{Gs!tRt?5zc*ArIL!oE<=&$TKY zjF*A|o(DPMLpYDgLudJF3%ZS}R z8DS-s*XW-Lu1El!3(SEP^Gx9sl*P{$BZiuJIuo#*2+OEoVL3K@vFyiZ31Gr9Zy2n~ zFdkw#9>H>bh)H2NHB^2nE>ZcP!>~YV7}dtWx`8@0b)C))M0|twn<}EIDYe{E2)9(9 z;$6%w&5UE0MPNxe{9`rXM7W*KL4p#4ITw9cBNIs!2Xm@v)|dFvI1@W8BfJ2+8p&vq zYz|jM7?8k&2!rOP3@!}&{S|hF;5{0nIQfmm*MQx}*Huznie%ycRgy*Hq9$?a6%hQK1_-rqR$$(Zj?8~ zERlspA96MU3_m_n*5gP$Bzt(u^1)Kbslt*|t%fTM=#FY+rg}SP*xn?QmE~)!2@sc| zT%dF_Qp*I545Fp<0Q!SqUpKKKM25 z9w@=@@AWCr3*HP7z)SUUsC~2MBk=cf!Dgf@!M?8{FQPVJ;YF0|U?A&^;`glmci>dm z5GQF?t2dghHd}lqCi@$Li=kQ;D}<}{rm)qns=WJ580(*x{e3l?SyBfqQaQ(d6 zliwhH6%N>dk2MER+KHJiQ0vIxmp`^HK9`eo;gg+5!e^3QV@;Mz{0G->^8Rx+i50DB z^A9j$-_~`x2Jb3wLr+f7{P>f0`Q)GJiE&x=4z4+S=b!7z#As`B#jCZ)^#nyS><2%Y zJ$XV;@PQAXunw~)zpW?m>+lIe%bt8uPsSiXirSMu&=WRh4Bx4|5?|Ak-sqERb^qsj zG6uC|kBC}AM}I~E4{m4+8pWM&lv!#hqEZfaCB;_$vxa1W&}Gs?kLHPw@`&G53vS7L zdX(ac`m-_>O`z%zQbj-%WgIBgYo%1asRx@@IxOlx981~I8s-4ae3(8EMW!}lNq5Ge zJA6~sOTMd@Fc7HkVk0l4P0(dsS*Vgeu?c*|y_NUjX~S~wy2}5!JC0*%0i4Miee56q zA%!k*Z1_*xl@dDw1QG(W;Xi*&HINw!ZfxhV+7EK&ggES4E9#D9i|n?N-AEco%Fmdf2QB-s=T*wI(4E?vFsc@kS zC5gZ9eE}s&KuI$9)O$}aJD~z86HsIFk#uHi2Sg+?1QKCmb~TX5D3|O=y4TQo2nwMh z!*mMN&Pn2z0D-@Szk8?UH8TIZXMq8I$wuUd6wQjbi2fcZS|S$KGv=eyWZ==T0ffxS z*moDuhiLVK+8a@Z$65-HRqX_N1B)0XtFdV2?e)Eetu2+~jnNgbnc|tkT#$nvO%7IP zBiUdwhh40df*}Hcu$|?8Cp&?}#ejN+R`G$8G5^b4NiG}wK zJ=sPfU#Bz~uzSKhGX&_q{7Y`&kQZQ_yP4W|soU@tjT@DAM5FBCfp^G}>HM))#xPB= zU5C`iBp9s*mlM;)5NkeN#A{IV8R1-pzE#WOnqOKh*k+nCaH`Qub0Pfp%ik^bo6hr} zX*U;M%SaCht5O=n*>*sA_sS!yTEhLN*=2vBHj}ZWXjpZ?>hcD(fn}}0Om;mt{k=;5 zNipwo2_5ARB665{ON9%S2UE{t<>46Pu!n5RWLjc0ODN&WGJ}-D)9*PA())X;5AKk?qvk^ek@`qxC25K7IwqXH2gk-14Nnc;!Vc~SXHozJyK1EHh1UU?lP zuB`jO)_38tW!>SMVXu0#GMmg=lAEL?FX^_YkZB%aJJ2~}QJ6InzBX17gl^7|>9Z10 zA$kf2viUmBWBOO@l#mW8h^ip_M%e2gYeo<{+NO&*7>D@B z!rL8%*SKl`Z>xltD0~U8#pBn6wd^8jwBT5u-wdQo1*w>RaB05^7+gocrL;S7#EZA~ux2eVTI{ksQ zV0Xox2hyYwY#KC1&CIOrA#rHSyrTEvU&^mAe}#l>l-e~;;2_LyKxy-A7T++Fp-Wd} z3pcY?O-LP*Bqpz0rRYbsAtwUATS-ZFykee70r=;)2Le4MENOw17^_`a5MWae#qOZUj(>i&})=!H&;J5ye9G z$L~e_Ur`F*Zye=j7dO1tB?+jqFN6t#nAkd=Nmb^x6 z0VO(DXL>C5(qel#Pr_D7hR;r0G_5wL5ssUX(k1(g6%bF99?L^c22f0n#HeO9+ceg6 zO7XHD@(qTvd`gAbpz@%+JMY01DKV|eL7*w>7aOJFVs?tSj@vNL7WSHj2MGbj5KUta zZMq$jFl(I*D4FUFHz}JQgFp^}hLGeeX5}_oB!(GW0L=<-745yfCaxx>qZO^a7;`Px zozK}#>hS3EvhdNf@JH$ZaDF1KwL>h7;G<}>Eq7+iO$sz33ycOrK$7}*dmKL{tVKGV z_b9}eKK|fooLW{+H0DdcdODhCIWU*r%gG#@{;DH#o5n`O_PoJ}$R#<~h|*>ot`ERp z4?3{_HMVk48e)+$C|ODlyNX|pg`cjE{JXSfv@614Sa=I30DJ;J zU41XofsHDVBV|qP5yEB-*(%g#*D7Z9_+~6~lCsW`!_Rsk?`@i;2DNQ7R+F8)+Kd%L zy`2#+U*hnL$;BjYfzqTg%*B_;5^X!3sG{tPToG;p>av{cAx_SUWs=rlXAuGBX>fG#rHz|4=Ce8GpQ zyIg7C2mno$Nwp;87YJi;zeJ9NL{lsc;pyB2X0MbjtlT7BHiSuQ&JzcP+9X^Q&(|Hu zKp2Z7-+jZhUq065Dm~y|ID59Sc+jQt)pEj+f-?TYpT#oY3Ey9dVGi;6`(Qa4<_K#E zTE}3N^whAl+RjAef9hwcwbR*-qL-Kf`OpU?kpDUvkYCJ_`eASYJj5Q^RYw9CM8VB# z3elc;DI;z7FqCCN6C2R6BcbT}4Dk%1wdR7G;9J-rB1V-Y-Y=hj4C#L}$ZLV3Q}?>` zm;@F(UzMLmcRSME5)pedwy{w2s?nNw!(m&Y_S(f$_Z}-$Mg)o8JSW z)OfH|WK`QiHNpg3*_a~w?0}Ki-~pv~+{nJ66pn;YHhYKK8l^zM8W04Gj~~mQ1%~n@ zB8B$b^YUvn?kVbYLkBhBV`bk1gj7JL0+P4kt<`YV?txMTJH23p+yj%#VS=_yh%gD2 zU`OMt2l%rv#u{VL)Fn(E>4{;kl5(zqsGKehrkbvVV^3_x@VE?S3}a(|F5?Ugw8CUD z@_C!DK|RfvrijS~Tb)-0Zx=!^+Rk)I8h}|bc$c3)W&J9mjplIE0=R_B{aUq>kozai z5Nmio3ML#4>m}qGZ%C?sk8lBjEW-9PilX(d=PP#jbv&&d0fbc$rfiEfno5nfj^Bz~POm8qc@oyil- zBN3{Ze~AGtQ&UfG<_AQMq=TfwtqspP%n(&FWx=$hs6RCVv&q?@Az)5|0-|I$SVQE} zF#Du1EkGh_w`g4vY456z7$E0hApmcdXE2|NWVAvusKCiZs(?8b^!na4g~M5Uj`$hjUg)1TV%h0=a61BpI!k9C2k0A;kc&+Z!0E1#FyZ;%2nd z3Rt077L}%hEGl@*f9F2AT8JaLXg|xZFo802u1>alyXPfKry_-p>?yr!=~RMN=~Nc! ztq&8ZsBQ^VR((G`hXg7tO`{ANrG-JZ5tqYkBh^*55k`y=?yP2(G0C#8c~tc)`FZXa zME<*xI<;#OCDzvPPM$J%=ktn2yu*qrU#}J2Zbemq<$mn#^etAjz48dF=oTw-BOc+K ztmN~NggV{qCjCoH=2E~ymjRw!s{-oL4^=>=YXTLz8v`nIR{^E%itBZUS1$B&d@yu4 ze~qDsagOy8Iv#a%hkmOV+l%!JpZKR4SpUCa%$co<#D!uq<6}P%Q?|)KVoI9>%x*{} zoRG{C2DblQB1~Hdwt!6Nl*JyHT&JfwW$G3M-K#S)`Jp@E7NU&7(VZrctrgZG9U> z1cnAh7rBexrhPiG+v+a*ZuZ;Ak~#iw&e-ahuIN|GOS(|LCqMqt-{s*qnMP%AdUaE_ z3P9-G#z<$Zxyg}}_A1&cwhom>t^O1MX~+n0G6|wQr$5e^inNGmtDwNPC3Hc8m@D*? zx?Ddo4UUXn3Lesxnosh`kjZnM9|2;d#sUxu@fT{I?rNbsz%IL!51}wv3Ochs)6i!e z`otdPtQ(K|^D#8RYAfB72tf^DyAiOh)7bu6*e*ps+f;>Z{0tR>QR1ty-58*?oLZah zFbUZJ+st1;HWo=3y&)k7n;NhXnyI|Nva*4$&_waHg>j;*q98n$sY@q!W{MRztXybFjp-D-taOlqvT ztjKEcDji_J2y8N7pdRpnDM|FswaHONaW(~2a}B86y1=y81;!@i9Ke**Q3A7Bvw)2& z%3cSQ^#KW3G2$O$#i&Tg04o@4C02|ftY$%}VWnQtg;B`{TVx6&3M_s8UzcJ5(1th2 zCmb-CDn2>LQ48rE7$eNw0r@5@P8j>EgxW5r)5e2*&SX#c2VH5J5p^rLO69|%N`_Gv z-xdwGpdF)lTCTC;SKiP?`BsUxSc&2}UUp*`Em>EzBat-T{Kf9#x4Db-0urleOcyh5 z=5321%FSNJHNj)qH;@CR#7o`9Zsxm-^C89-aWvL+Ol0000kz9|K7fgh$xsC8B&fkx zTteGh-7IP-&^kmcjvt4%KY$h?2onJ9hLJYPW{o+@_e*H&0opCBJ^Kc&a&z;E5oqiA zX19Uq9EW&AzIg!c#7IwNvxGhZE3OIchG|n#;I;|LHIe;|X?OlKvzKvT;iT(NyDi!z z+mc^G8sy@)%Wj@G#XCQ_Cnim7V=QwqgC^QZqMPUXF8^s91wt}tz zUg9bMNeMRn`coh}9Bp=dW}5?C;plR9glmNlyo+q6gQFmvn(we99kNG0;tY>W`4^Uv zh}6{#E#wZ7~nMIIw9ZlTA}B0`83i&oiYE40*bJvE)-HDvOe zc&zY_%YBCWEfbv)Gq&lNLbYuoOnML~v$F3aWxu8hnw6{*eDWJqDxn#=WFE3Vw5Y@{|c3y@&IqV_Rt zIpB$g8{{g0S(LR&L9=EG(+mzFFH;8rVzRBMP}dA9($G1miG!Bqc8T1w24+(5OEox& z_NPSLjsAm3WC%_uab`h9NX3EV%(n{s3M&=hV5MGRMPd0IYydo8m1PY>AeUAxOJ3mmG|}H%!g-6iv(V)`KKz| z5Etd(W*|J>q#2UvCqfR&_jc#yk#rYQ*UWBo)^k-0rAiEw~aI0F5q0aG^(GJady$tlBaw5|h_8p90v1_?Q2+BGNHe`9|tY zs!lPTPZ)K^ZbHkK``zB+eEHsKfow~rp}Ch6;SoZ6_ZNM&&FfSo-rJidEnp*kvM!j` z-A1_F`mP-;HZJ5-@@DfsBX=7$icUeiRdpt$@oK=5xsFsmqb0dPVMAjFGfvvZ3ndd^ zg9)p$^5}4A@`Td>2PmXJ09Aie)*ql^2!!)0l|ySdpih;Th7b&(eg{+NLcb+u8`iI^ zt*NnYtpV-V9dykA?A%K@QgLmNLJ-2D#ICk03QnhWw(j zu&Scr12E$&0d566^agks&I671Ird6nex}31cC3*Q#LR=dI)b1vfWVp`LJ-;)4e)vi zflX5h0j;hF0d1C)U<^wi!d5wx!A6jkQ%>a?U5LjRWN&2`B*T||j&uQq$>x`*l357@ zv{{kXyeMeR3vG$(rWcUJWOd+f-K1IK$3Im$STAsEYQ7)`OnI-eXI@P6(YBKXK__)( z@6%PvnOCwKOkj%9?Xiq8sDuHe{i{VD(&R)N`d2&lEW3Kn1mw^dr1mD1O{w5H7yz}F z`Ogb{LQF&H8FLUUVw+Ytvht^l>G=c+Y1Z!^Yy(k#B1S<7Zd-83B8G}d6Ex}zi3&Vn zRenIRoh^h5Z6Aod%2j~u8HEc8>`_#O!z^65+ls>e2L%^H)bHQ!N@kDL6va8ssI#zu z5AMi>c_8P_-=n;I&aa^Ku)qI(w(FB6^a-v+4mF0LOSxD^I z(Q7)@C$iVXBkw47TCE-F?VbE`Ufe$Km!COfrj6Vl?s6Vm)9&)QIOJJ{l!45mqB2e~ zGaapnh+p`F@>O=pXfwhVZ5-ulidA)$;OLR$M0>U0hR|?0g5IBrQDWLmNy_EJaj}s} z-{`jbS6VRHs{>E~jNXB@H-g7wAJK!F+RO_<`?2Je$#|q&*8sVHj zm@UwVy>Gb5cep7(#fc(=IfBTJU=zd#{5L{?#6G)}Y34NnpmCTxChVGMx7@L5H0_`n zj2F%3>>!-$3)m z3`#msTHI!k3AKdH?G4>VjLU8rx$U1ovS@i`PY@S`Y5M;xra6jHz0-CSjOVK%KH+GO z3*80uimSBa$V!&EXeUjMG>c(MKIx!AT9MdNlN9U7lm<<*myOv0_nd9)-mLor zI^Y@SEeOMTyGbO@2w*`CBt+5ps?0WQHmV|*H`~gBv_Ehu3~^!RTOj345Prj6OYw~I zM$u7C2%Y)E?b|M^^Vu4`1>$uig{Bs~PVg#x9XxHUaYz{S$9_M^dHk*YaI*^<2X1Wp z2v0q4(21Z>JLn$uFR!eMN$54;m`ZvxQd6TkLd*~nL2@;Y`la!yn=PbB8Y1i^s^5$R z(8exm@;^e1Y#a2J2Z?IqhUxzMW2j++tWSb5h@k;@z+&*{fx^5GYw;GVuadVC<1I3k zVqPJTbulQNDPz@otF@H1qCbdfB z1jrxbY(E!!vh;`mQ>x@5Ya(9RM9!?z3Z!T?7IK}FU;Sh1CX}kgk46PFdY|M@z{lm! zdPqRbFcyjpsKQBJT&R!6q^`dYR@vw{jfnb*<55&>;-+Ybwn$>o=kzvF@j}um+$I!- zI^&c;QIpiuhhu%WOkx!-cmw(kd7+=C+lJ+WL?gMJb~3}3c#Y3Nd zD@-3ps`Otb6Br~aMQ(KZ#%WqG_K>?riwI?W(RmNb?Z{ci z!4LL10J>px0bmAGRlTzNX-bDcGjv+ujf{wWkKvu(`i!tzJtcQV!b$bDP ziP7GIYflrsiQZUrrUMHj7im^PhyjpqBaji2{-T2%(5N9PWyQeGq-Wk46g_I+e}v2s zQ#{K1c}J;#9uUVXVd^E-ex$0+q0au}m9T&s!i|JQ9q@WeKo*oZ>KAOsNXuay%ki+u z&6qLboMXsFeUcw=AX`Md(gV-;Bv3Gdq~o7e4_8DT|8*|>75)Tvk$sFn5`;R=7*W%*g4YqD>cN_By#fuUm?n2Q=8fc)jHz*0`t~ooBZyWcHNSV^^=!$Dd+)`R> zi{&faE*{kTKf`_(f5E|1b9evHvE%ojIK3Pn&aXhrK#g?cn2^n!KWw?WJKYuc6qoC} zYL^ddA38d>T)leaXmR;1wtuMWF8?1-varxz+Rcklo-twSw#d4WJ1fN!l}%(H&XkRa z>JCS08Jw7}9eRlmxy#ii@|W3_&9%jU=?|F;WhCu%JLlqi$GWE?LQIrl zq+|8+v(JQ=8JcZ$i9-@*T{gtf3Il|PtNo@zUtPj}6VrhGRl(lB)|R^ZBe5k!<}=b8 zTM%RXOyy?kZO1H4_F(zqu(+gJUbyYWBo<~dB{`}zSf*9x6@^)Go;y#)7lA!yrf~fK1_7>ENsB4oEvh)8+W2yNSR53w%4AkgxAC*QpI!? z~{77@B}r#LfAx=&SQ!9z+xdsaR+Qt6-Bf87>tJyQbS@>rzI8dN*C;E$|}$ za>-!nyy}JQX**_7uKdw{j1PliZ(xDD#=>2^A+iCZLTt(aHPxtu3l0`N`AQ3hfFe9^ zao*WIs1Ofpgn&yTu~vXBy{GL~BnR7u5VX8rrd(3l%31S5oGOo1F5|_@lj|b&PC2tp zZgEvkfu(orCtw%dYy%cwf4yS;i2y6D2v&4@fK~UJ;t3!ee^eIG*=(dwb<0FW2#dMT z(-J}UTaH7Mgj8_|J|{-3tmWcT1`ZSHlRmItPj(>t98!0wXq#>9z}Rv-gF=Dc z>0^Z6JS^Mex|FF}}X*Z$M~H$!hp|CRbXLdB*Z?3nBY z=4nQNPOVbI_|jx1)Z9#-GLIpUBed0Cu2!}fOs|?r^m1)PFE^!^(Ub_TLb5jKWqh*~ zZvs*FRqIkSv!ET<%<}mu!D679r3J+pGT@~OP0A>_`f}=_FmIRZYZ@09*ft%(c_VsA zZj&5nOsG{5SvJaAoY<|mF@#igCs%Ue=~L2WZH|;+tt71`rsi8tBTu&5OIPrc(@TVg z1}|M9vl{fKoJPeX0>|Z}%=0;8_&m7j{0VNF?h5nS`A4}yf{0kmQFlMSR3Qqpvg0uD zcBJ={GR{NrhOd}ICK&Je3@4;J^W=P>4NhabVJscs2n|l#3%hBWRq-B>ZyrcVTp6VO zhwP|iZGL&S5{ElS=|8_5yEH)tnjRmTVYNl#*O&8UYUzT@gY6`Vz&cWhX3s2b<`OW9 z@b$GFr-0;iaxb481uLVS7?Tx!bJar+s_&*u_+tyhWwk}6ybOymUaFa?09$3dHghyC36$UKU+RIBKVMH}0$ zAh=E8J2c$sgEceSW+I{kn?qU?^-wTMG^^-q7458~m~u!Qd%A&TF5Z+2*%uA}gs>CqCPyOx_85S182+d-r(&I?`=R;m`^K_)B55N-^=(? zxybo&GJ0mbV10G0eX}@qb@DgFCmcSmc58&!Q%43BEF;)xFGuT%-^7k) zo#i5}E!eQy)s<~v6V+zn#h4d~i^4__cM-{{n#hRKKgVr0cBoZ^e6!DzCLoBX_C^LN z>pC~Z9t+H_)0U$0upB;M3Ml3`Q3os_^@qb6*h9A~+R*=z7$9SVi67&z*gtvBSGqvp z>`*JhhJ3k+S3?w6^r6BAXRbE_L+lwI3X@5)doKj|LF zp;Ah}z^O>%#RM28Jwq` zl^ye2b)LThr&R0kc|%p#$xR(Py-h_MB1%cKCyEfI+Jt1jVv zsVcLECkx!R3#tLzqA9-^OQAub?dhaYYZ%VSlF80DgzLax!&+piq=@Qi#YImms$=7} zx(7m%l7JP8`0}@Z9y^GIYRi@$@C+_;dAk}1F@Nd@_j{J~O$bX2zs`efy$3kg)@UlT z^Q3UJNKa!e1kz(&B-a@bi7YX@5Bjrrf1|M|O4skaPG-Nu_t_94Y;f!}?5s22zX3VQ zpM^S*nhKy}zEq%td&I81I4c}=F zp0@^9?ZH*Yt~y{X)7kgsrLDRIO^%`43s#)>(J#j}%%{ z&pOVGc-wudHTA3{w~WSAs@+kAA3l0z>+Qi74uWC*qHVxS&_OWZ9?6!+SSRTL1P}1= zl?Qpy7Da!=qs-)hJY>FT+Da5Sn?!6@M!h|dlfH;-{?!x{NmurPAK=2qv>y1;qLS!T zO$>%IqxC6$;0#h>j~fPTT=tLRuDa()j@3}aVSY;}VCf+g$R*J^i#MH#3CLlKZp@0w z+%q7KZnlIcvs~I4o#3BMAN*fW_UIqw7FGz{ngDAChKL0hT?Pi;_=(tr0M#&K$PL>a zEK6HfnW0L_u%Ricu5O3GhT5@+ptZxn7uJq;60@5`#}MgIX1%P)(rVOV5DK-ClM~0W z=&Mu-Xw8%u3s#)r4#6^LLK8EBFOekMJNOfIn5C4Jl{k0so!3<#%jwLKrUKiFiS>uW zxd)%VPWrF4BH1oHq8|tW;t!v`#*AU1LtbX*t7$m*8eeeuY?GY67_+e%E=IJvW$!#9 z1mjKGMUp}W-Ez#Q2q)~Wd(c0lJ8T}Fkw;NI=G+z2Pa|(kuBGE!sU8#*A9h+7$4whYlUXycPyg3{k3 zIN=Mr0dJ?na1&8LJTkLcIkOqjo^obka+_J2)KZ8CVRBiQsy`V8^7b$ixH=jfN7;E* z1TV-^kSUJc>FGz^Ba%>Es2!*upJCLI{t_eTk$qRV+}#eXnD7f}Q6}eDn<=x@0ap<f&{hZD&@`Er4uYY==w zvB(_~dgV9KAlDP-?n>la{rXRzw)5E}N-BlT$59iLMY73dSf_fubR^WuEGlXE()*ln zzME?tcASvZd@Yxl@GK%+Ru#{=*`83t96CClVc(-iDx=7=rghosWajKlm{i zbQhC_w~z@Z0#bE5=raWCLo5ZGc2R+S7O;zyyWKzY$Gf!+komn za^LC5C;d05$c3Vht0)Uar=w8zxXK!#Y&i~P&!}voEDQTt{ewS3*)(Oi??$Kcp<2>~ zaga49v<@{wKcurAA}W{$v!+*^E)`g0b;bYNeg>vdxAI$4**lgzH7VAxNhJ` zAcd3XtTTh(C?^R6@&e%qJ`6>M2+K_5*l;Fz>ZkHPgrKeKxGN~eK1}w|R_tfZ8Y6s? zLg!0?$@+(vib)a2q-IfBQ;@t&Q@T*519xO$h;u~wuEsNhb0-VFFoR`4z>S$v21~n~ z85A2Cnj(IChzjkH)=UgA7bj>!O1@LdfZ;-}bHhcVK6}iuaKmB7AnLoLR{ zdKK9`Avb{mD@DhPAWHaP87h3#fSOMO(MNNL4^%D1)rMT$yW{w1i2K*UN9j0{dkM^w zy@V@Ett*0R_{hKqvcJTK5*~v*KucEm(8B>f0MPIO6hRIN9}%7u$mhecvI9@Vc835* zeAlXKyQl`rhwrTWnN(!4 zyw`rX9eabHRBL^R7ayCjr$j8RH#J7l2(!bKsD(tbk`&~y7O;b`z`S>|tRo+$SO*z0 zX6YGPRJSs7U~wCJP+Hc?WlB{`nOf-wNP!-)cq7L{GBLFRrrXOD*JOqub=ODDAp)i__Nb8h~es>Qp7UcU_q06X%4taC_Thk%G(Ii3;S$i{sg{mBAP8?``TcUKho zSK&D$t)q`poerITdd0)Lp-u zqj*Kq^Jn06Ai5dafSS*|fnTO;-nFbB0hDTQY9#!L)CL!#-KT%1Gz@g}>FS2&w3}3A zPgG^h)~jO?CPa(DD7${xTIar$$x5&+L@^rkZY60V-d-I?K{xO)JtJb)X?To0te*6_ zUSx$*RH+@kIwluhmPObWA_xf0(KRcT1i8vMtW7pxl$EuHQ04#iA(2DKl@F$gee>9a zwDxGgL~Nb|b{6+JO31Nb7yFa%W5}Z?d3tRDI_SdT98~ve%0Fp6n7egQf#C^YniUL0 z&S;AUt9NW7!p+p6DPSp^8bX5p0TP|DVB^}(kHT_-Hd7G7&@HTM?FpNl#vt{%zaNm4 zWav+D;b;8m^8S$|oZDC~3~T}h%K@wxBlILJ2clCxcEj~R0x<(iSP$??8dw+~hWf88!##%kafGktIqzETzh!kL3r^+V2z zewx}5VB&*0MS6i3u~qO_g?T0u7AX&5vtYHb5B39gw>jojAC$-i=G!{%f|UTgSVYnj z-0&`&9$*srO?1NtLWO#ysl;gH-}}I$PeylHIAnoWkK9FQV!bEC+X$PyYY`^WrZoft z9uO_)=%94`ISX2&#Ni<hygJ4DvPF+s4D;jIb+*wRYe2DI8BA#uun=z#cG(E!I>9!2cvbFcm_~5LwEK4M$%E| zO+>H*iI|1U%7y+#Bn1%$10t<}K(UPj5i|eSMU0&ec_{BoaMe7AKLO6$(|JebHL3i~ z@|M^OK}aG`aIE_$KNvJ#s=a6Un$5|Yvt$$9Pkf27#B9z#Pcb3~`+UySy@r)IVND>gAQOK>mooW!5DnT$t^+hy;wj z2&B8uQvVCTac1yHMhE#VayOUW8f292 z2i_a}T=qm5Q4h29fPN1CIPo}v5PtMsLT)ysk)?Rfz3@Zt|IAN*?O*=d|8j(L{4Ggs z?%#dt)Ia~Nd%pB%UyW2W4hQl;*kTgp^E0UB))j3f;X_YZ2P!fWVGyzZz{8BM*4F{X zv42E2^6b2~6Qp%;xJl%^*gqFf%+}-bza%4)Qr(RHwsg~g=VJS2wy51Nv%Nx)@oAR2 zK3Ed@WmGyeKNnpSOPD|X9GMxBzP+;g(X;kw&9|PmM;wZ^`mImeBb_k4s;_5I_K}%c zep!2Vq()Z4^98G;6KQ>iuNHdz06r(JFfTXQOU@}d8BvFM>L0=+H1~by04FtpaRe9) z!L}{=hmpRLIocRjXwR>R5`Q-|A+3v*L~4a3fF7&>BESwcOZLh7wX09A*d6=l+wQ zI%BO|7qO{@Qi7{vrHC3zarKXtvb3PIzoHZan9`{grC7e4IrqJuv-ptUzCJIh|K|n` z@Gba13rV{yA*$yPsfkEGA^%o9-ZdOV`dVBkjhfc}f|`@iiqej*jH6dmh=#q+)C+02 zZQ1LILKtX6>AIYMPyI4>Qdow6nInIzyVV5uxDi|;?Gk&Z{F4~}LBaDct4FLqwTvQWL!nba=k&bNuFFgW}JQuYjO zkWs}0>15S0S={;Yn3X)nN{Cw0%!sjanZ2mI+kO!%poPAcCqljWyOaJ~=#lLK3Jl1G zWqF6S=2Wv}4oY@K6R2mshGy6$sj^_(v1NW3gU|^t1br^@a9QUklHipHFj;qbgmGBe z0Znr8Xc7YsO5mb9AF=^NNfvG8*TSs)F0%%elPMa+Q57S_1{(>pa_;#WVOC@yTR2N| zX7IMci&no1FEUqS%_}C${<4E*xV5TY1!M=kR|T8{+UdVWt!T!aO&sZOX2L=cvfSjE z>3lkiTjfO6wnvE=M9WRh1=!G}V%M|gmr;oksU^bkOc9Z z4m|OhXE6T$FGPE%#WJ+c;+d=~j~&hRvy?0PD+&>VvxdTQAgGFyf;F3c{)ln#JsYzZ z@H84)ahgjC6_N`RX%t7wgUO*cR%}MHBr{3phO{Ovp(HYxb?~8e5GV`6hWF)9RL__n zVNSK!kvuUh>J4J0|IlV1NVrtE7*LU@dBXj#o~c(n&6)wvMjW3 z(IXIKa~bs{ougUYEUAacf@0Q24^_xoVgsR2%_fdio_8%TMu=yXiy421WG~pmJI-{K z|6FZ!x)S7_8czI~C@AtW5hdNj?4Urkh&)YSV*jQFrbQAZ`>HjxPTm@%uSmG+6%e5} zlztRTVw*t-x=h@Z>kB4;Pw->B!OD^g@QyWqK+ZWkajPcDS~k) zzcf>*#ZCK81o(f7@4ybmRf9HTq*CtTfEz+As@C@HcZR29&+t~MiD#ob*M6>Ohyk?D zb9?~?_MM?^4d1$E!bZjp&xo#S6fs>vKgbwnp&y8#)_Hz^deM;!564TIX%6IE6zl7( zs#mE%2W2#oA+ZF0H=jZ&Jjsz_%4IRff0%LiD)*}ILJ_>`GliQ|QWgI-juyq7(gygi zdCWQdymLCdfd3|sa{J2TC;eFky*a^nEEpYQQ9kwAc=c(fgA~N1Q?=n7{oc!;ve)Lf=`&$7{x^TX zKk)0$hcUbqS6?Mxx zeDf}`7pwt}N&_i4mV4~TkW5ds82O}iQ1%)8w$`Wmkm-HSe>NZf04&ow!#=d=rfD~E zM-lnce-TMs&S;M0lPo%ZGaqF0kq!)7UOvp3C(Fyp-sv-ZB;mvnc+f47(iA_b76s^+^aFnChqQ1!#A?*mk(s&M(x* zP42cGUUE5Y2f{DhVO}HSz-=!}c(pieB~^1vMv=`W71AW1u0~Tt{wZ~fT%AWUV0oM> zihGMo*e_jdJ6hyo5$5>n)D9w zP{k9Y;ZbW}(Vj*q`KQH2m-u5?qvlHg*pG6*)jx}e-R>os8~((P)XSy-Uw1Tyt2xnW zHrNUaXxTGGd=ulU2a{XY-$qb$h9Gp^P2z%pofh&w8nhk=JQb?gOv7h`0PW5~nSj?t zazG+O?~qr3<3+R}M0hLj0=q<+=t`FXoBZ6FQ&BqGKcpbd8?i{NPUP?4dx})mDIc1m zeEVFSJNk2O`|(>8dQ8eWVy%o7L})_&7&IjPniH_-j0EoN~pzE?;tyU0gy%nR09e`-ldiMf&JlKC(7}V0rik zRc4{E!mRYf&Ac{wNU1NZ#zvX>>g3b@$p1Vak7ZcE<_l#vCH}Np=X;Q6C_%I_CE?3Ss>=gB23nx?_YG=;+)!eJe5OrufJxu7trH>r zk6m_GCr{|nBkW(`1DrV+v?Tk;k}l{8lOv)r@(t?pUDGPmn2T~(Hea6? z2#}gw;+Pim)e%2+RZ3}(WfE>QOC3PK+$2>S?zCX%E1UdLj2d!y0u(R;mE(9j zb{|4&mX!oCDF|DSk(i{Tz&JSP3@z~2soag?hV2DH62?Hl%}j}#hT%q3KR^mr9~*9k ze7vXm%nJNSe;nic4Q{!}M{(m|qQi$)JfxaRbM(!6_(D=IF_w|rCZUYVY$WGSc2m3QmC5Jdk$$S<3$mH7KU%Qx#aG_5mh>>0% zX?R){L^_a`9}$9JvZKd%BGMplK@~X8l{>401a=&u)ZhRfP7!54GvbO9WBAk9x8FpV z#b1=XNmpdi;9n@rv!R3*sF4B`T>wJI#i(mDN5yD1zlb5ll&ozCNsc1GJ~EY0suB#+{p4$QreEFkHd__JVIlBx@8~bmdLKC5D3wimR?BI zTBc2y7m=Lf+Y|6cAwiCqm83=9TO~u9HLXOs1kXol?!8ChI7CAf>T+h$8%gOem!ul9^@I3Ny|7MQj z0Vl|Ik;idzx{XMN)q&(u*~mO1x9?_-^&M8nNM575sRADPlz% zi^?Q%I%=H%VTY#VE~~zE*$KEu*ohWjl%0eC*y#deC2!gTv2N!v0VJQ@4f2eiMNZCX zR`QJ3OrFvFs5~PJ9f%c;8HhAHe{NKy$@DUjX4NFs`jTqBfmDlKx^G>n#*RsLg32Z8 zHld8dwn&p|8tG7~MeYKP+xl1`)Z{@ys3lzvf8=QGePEMMoXAIOhSvt=1yNvmL6Y5K z%I+o;fPIlN=~b9Q&74?I2b{#5fWZGuO(&>C)@*)ME{2~wb@h$1qargDcj7n)=b4=rs6Jm zNBt1z<&(9BeU4w>pED;NccK2T_<|t7y2wXbybR<^?1239S0KN`$!~^y<%o_Tf0Rvh zqG74_Y31(%`NkC$o16pr=dZ^m1LT8tkdL~lNT1XG=7oG6gU$aS9~md1WWp^vw~jxT zP+Fzqhtv<-g$dBMyO9Y%(};ul5%tFv_0!T{bpm8qyr`T~hVnXc>ZMbEWzei6s|J$* z`k4eJ90XO$*@>uDCSh_+bWt3Zy_Gd2y9T7ZR4WLJ|s>v7$ppzn7v{KVr^}ESjB7G z!z-T*?cwn!>)FFtk`opBG{s|cw*nfhr?t&h%^j@-gG-WT0+oTVm%XY3Q8_CWCc`0s z!-6ku=zk=fEqCF@b=3rJ2C2-nDGEBnLTmmPnPRT9x}}-8Q0qA5e0c|o^aV15||bYiL9z+_=S|~vRQy3 ze2DNZ{cEi^5tX-Rtl@WaLXNVpNfo(@Dd0FXTRe(0@2@yBQ8IR)0HV1jlmV3ZV0AIn zklY`ZJ?>(j@oLDPwwxBQfdk(RH>EqO-4nxhWvb~M&+Zxd4iiOfRHthPm1DE^Z;`Ry zrDJ~Vgt&3{I#`eOQ2r|O;s4&yFVE{*2EQEldx$Ko+nmmHz!<$cd4Sc7Ho)Gm-;1tJ z?$hsiS0~3UeOo46|Gzh|o}5sGSoxZS$T^C%nMcHKPPc8t;y?f#8I9spr6bXtnUQGD zwn3oem|SzC(VUqmj#CsT--Pp& z6H7gj9nk3&(HVPXT89Y}pXFiIl5_NaFMq~jI+xBRb1W66X{~H|804wp_v)ex3GkTP zf?2JN1bAi?;7JAno@5$#rUiIrEWpE!4mMSQ2S)*{RRws`awGlK$s^h<5CS|WbV+>X zAxP5#Jc^(`)S8#(Wz&6GHw1Wwb;~;F+e?MDOhY_W@U`R@lw;R@b-b+Pm#$(eU3aP5 zwN$*qh9ANnI!l`9{wwf(xmN%@!M1>#GO(gk3V1%FzzUAA;qxl6qAiV9fxya&3RMqD zsF1CyPzF|J6j-r$iPn8bLFhADI76DBo#M`k*%Ytkp{j*!KhaT!*_ zW6b4nF9p)q9aZf@wP&pMjJx#B98p1JiFQ{7Y-+=R%^(Uh10@lixgvyWimOcl6tj+k zYnI$BPvr_yWan@Q>Rh{&Up@@k@OlW@R1J=VY;umM^>7}9JB-*=@tW;SU499;*rx7@ z1zlD}fmBrXxh=H4&CMPzs>0C@t^_@^iVLWbe4D#WKYZ+B81tzygq(cjdo9*feDmSr zmGsGvsuovigjKMq$PX7gRQgRzG!!79ZXRIRQI;$f-(qAFf>L~)qPWb0N@*2TN<&ad zo3loPO56>CO1%|9rDg~!QKbqh_0-59sDv?>`e9Hht%6ED3d^7phx@lZoQ*TO=uOKD zM4DsCMnN90Vk7LTGSGB?6=-_oeW$f2>sfy6;OIH#6eC@G(PR$fXaB$ zrMxr`azz2vqnxht27D-RXBh2hKH zB}BzB25XgI4!slLN@dVIDlDhOaDSn>s#9_VKkxa1BT^osHjmz-H5qICINk-2>F zi99&S1hv?x;!=5z2Hzk*t+mNWXB32jz%2+>m?l+R3UX6$0*B`|Re)Ckub6p@+$)X& z>^O87mUK(`PRFxDZu_l8jnra_4iymmZDBvx`kaV7GC%{#IWNCdMh_X2A1+1)JLKzJUB9} zgkbB8se|d3a`D*Zw}RP%*WhYEUdp%gtT+UKg_~u^8*vdXZp*7SEfYm}l$)D)B1%2P z6G;ie%0D5RR6LP$z2^DYL7uwpZin2-8u}Nkv>KPS*fyv-`P^F$!$ZP4_`CGA zY>_}5_@p!kOk3d+LWTN>vMM&|xaU(mgUFU$V_1ffF zJ${b#pOMG+Wn;HAa-7;~>{0dw5!Zahx^c}K2@>G5dnN=nbqG9)Jw(QcW6}yoJh~`c zKAVI~oE$tju3iaiR`Je_A>OGbDBl>uoki5&H)upxp$!XyS`$o46$B+U?bC6trm;}? zOFJh*43y7$fkqsO!+(ym(MIlg3&(dx(26aY1a?>evz`bQICIKYEa4=cyy>Gn(G&R| z27VcRb`!^#^7R1`ax^GL{l|R75J!ljBnc}Q4l~xG%$&y-c8m%P;wm1dT;G-)Fy=YN z&O$_TZW(`4;$dPzi-&3J7G1I9RQ^aymU4abN6J-Lkf0`1D3MBC^Hbtai#f%^c*)vE z^@n&^TE@fRZRW_l03s{lQ~obP5~Gn1JpMwfQ1@u9AZ3<+ItKn6>&XCA07xK02)4RR zZ)CtV-e<3bm`GcE%OPkzX|Lh3WCdAsn{?!BC(Mh!sEkKs#p*v*G)ywfqG9%R0xkY$ zlz5j`!7vOq$RxK1=^n>?l))jCPzY5k7RLNk#qi+Z39+zsp0M^`^9h02O`UHh@)rBw^Os@Io5_aM|g*>5LwkZ(KCdzu7H*@W)k+@Qp?ci+;!by>o5_6!6voA!F$jW0d3155R_`RSy2BMvi)e{oC zJSromCXe8O|Bt=5kGAwG3q0TVJ?Gwg?mhS8-1<(clDy}nz{-$7tpSpVX{}wOKnzHn zmF~r?}ELbhn~z;lyDI}2HKcN1?nEU*)HW*;j;k#L(~ z96E0H6Do4`_%_YAr1A~lqQc-?62y#Q(&)^#n*Ge@<6M#LYhYeRM(EdL<^=>MyldmG z=k}~?7dEVKTVm%RvnF;i+2$>|w`UqQ?KAVX-+#>AY&Iw3ZE=p70b5@9%&3GhGj47Y zr~y4DZ*Jn1oVRy8{f<4Qoeu1hiV^(iPc!RuNS>+X>o6T~@QEz(+|x0=wYmLtTo}`F zfzyEn5IL6HOKuo){BxGAP5uG-*jMJj(>Wo}vVP%^mplM@cQ&UXKf7sLxrIDDPssBl z$YWc%p(I}i$ZwG1Sh3Hs5T^Gvw}(71XCWLA@`}Df9<~DH`Ybn zFNhkEISXk#Y=VA&)3#;{{d15ZD)@Rs|GDr!YaRLUu)ZMYlA?h1lirW7h^h-*e6q(#CVFj|~oHjWQ+&r4+Li=BKU7 zQkLpN1VknBvhD#Lf=gesB>ON*t^5XJCPU$0R)-j%tBIfE93ohdjxqZG_UceDkX9Ya zmnV%m%GaU}g_zPPb_{Q<4z&{-5)JAQSBdc7u2vCJ2RRp8u{&RbBE)2HUFGYM>q8|9 z$4_)4j#w`43pcY=7%+L!7(ve?b+%fNNBTDio$ZIOqs)m`g65(WhDwi9TnJT2U{(6I9n@bkwvw9TH0#q8%k%)L8zZpvq}BjLfPQw)ti>~xCb&jtvo0h zk2mEv=xWni9GRMIIr|jKcSY!|2C>v{d*%7?d#rk@ zaBM*E@t3b7hX1{3RV%obGA}FBy6B3eD#Hyh#SSxm8=;X7GASRZsdGQ%|F4*09~lTc z4MUn52NT!zmU2urr6jY)k<*tn&>oN&v=-aFIOaBDpNg=XqJP zmRzt9q>x7NlK*7SAsTYhNNxufp< zAdkiW6yN79szIdRDa!t1<%YBISTwVZlLMT)(y#pHS;C2Vn6o*U-GmYM2zcdbSUsL8 z(B%iQvmy-XJn}o|@)se7>eFm*b6cb}6cNMT1G16oT^71WNHk@NINMroNy8X4@c_5Y zs+&1j!VcU;KjL|OKs?80+?D?0XCB!22_ct`G>6gAYq`!L;K;Jm zmK(-YTYA4Eo|_smvmGPld{NDS8FvnY=YZ4Cm|HXQfq}P8(SOF1$LFYd$5Q9LvpJ2& zeBh$Y1GMpg^Lrd06_4R8ZHzDP&Lf}tBC*dMjsm;V&%M}5+BG#vlg`ov5v2kDq`T8o z1Rwi{nWFbL2zaTW59fs$M5NZoAditfLJRN%gG2=fyjqD_qhRyxXv>K6JPm zjboAx3@1T2s_I}({g)3JmUkv{8y$F0Y`oXPd5qemN@`Xpzc@u1I>1{*_!F;Z()d=@v4w@S(o|6=udR;^BaU2?* zj771>;#Xh<!bcH5(`lgkK{%xH&boK{f8$wEq*| zx%e>7w%t|{!OrvkH5Y;!SJ1YuBWfL{Cj~Ww3KExiRSV^6z0)5v&DWfI#_)h58V5Hz zbcj<6`RpGqLV+t+9pfxC-ew#JSC$<075Td9ZE?{r&0Ix53Uc67?w6R!>&VQJ1vqAk zt7pwcWmv`6INa(LCL``tKq_&c(aKSKn|%eg#%P2qN9z8K_{Q#vw}qS1`ZMM68@<1Q zW0LFZUC$L070h8?#mFM5ULwV@LN(agih~sP!+X0|6>%7Odgf0Xj>|EK{PK^Z4A8;7 z!7w{Z^-f?GHRryePy!$VQ_*`5{!Mj}?uXQrlWqi-9A7cOL_l^PaB@kM!`%Q$Zj*g} zFuHHRasB|EXXx_9FTjk`h=3li7hR)}#S#USBc%Ce%&SH_x-hL%W^QeCQRGOrPv^$^S2owbvY}qIqqWg~!gE%lRkz;X)Ec!L zFsc3rsq=MS`h#GzOp`N0r>*9vcn=tXw@e60B}caN3xid?*SiyO`E1BO&c& z@|G}KbCzgFeZbIiJL>&<-YnL`W21}HZ7keNsCTix@Yv{J`+5idlo8>Aao>OoOcdN- zzr=z&)DUrD_1w{7eR2BXriO#<6NR~)9R(7Yr`a{xZah}u^tcEn)Gv*DJ((^ssKZsJ zxDwWTiN@3MS6XVHJ=g(KNSazCF&Rue=?teQKGW#gbKGkC3@3SYUWqSX^ot_-Og%cs z7q!jN3jNdu0J%t~|F1DoS;{XG6c1(*96ZLVRN{}Mm0&Z#!hQAd$ZeYU~g{QBPJHF08&8QN2K^>W~jgjDETprd-!-lzJ7 zde7i`3`%7zuFY-OU(%)k=&RuC*s!PKdB0K%lTeIsxb2ABVVXlIHYKFhK#{}2+d~m8 zNGQf_DqEq**9=9mxPjsfAQOrR4`V2%GL}_@BQE}S9_1L+*KOuUH zd?G)W<;CBtiTD7Ut2=A3?mS?0wU`n>Va{jVN@XEHD&EL~0iWaf8t}D~zD&dwZlwe~ zg{6B`T*ui2TH~B%R!6^T$EeZRVoJm41?qVNz*_3_J<5pA+BD_sq$d;@g1qve!OA0lI2loO(VX-wvmKGe4WK&*#6gwr|WgS zG7(QX00Pb9GG`=02*iRcyP1pN#=6C@^!SIMpwy|`=+&?f8Z$~FU1(%Z1~H2W%U*mZ z?z(EF(hdR(0o#!13?o256)V}rBc*p@n$KuF5nOxqlI2B`2(D(g=s@c_lZB%Ny6knW z8zm$V2Sh=RxJX%FNs!=JqBPPnxW3-sc9Q5m0ctCX_0wWN&W-JQ8pRPu1+>tqt zD-wPMYX&5eE|)lKQ`w6Rw<*+0%y2l#PLVL93(5+bLZFd_&XeA6rgkGXwap60%cODE ztQ5>~Ku1eshC!~DeAy|fV^fII6!L8M6m{H?L;t=>j<{k^BbRywctAD5)MnE?zjwBX z=p)?+Jg2~^^K|DINJ56N%`59}w15-eS#WEbb}}jJ$h?Rlkk$0eXDOuQ+)a0J*@8O* zcU9^1pEVqZupTKyB;|wv(u4+WdW|7YzUJ34h1&Frvpw+;CD!hX*rW7J`_{~3kT^NG zq`Y2zF6c&2Tf6nLC+-s$LXBm~bB=p4uL zZ{~SYx!~r=lxUe=GSbldZu-y9vaH}CH9=G4VMsJ@fN2njs;n!SmyBh=y5fhZ@Xh7~ljx%oJga6-kHj);mHg^Z{KvkMvf9x+g9 zRLMDB#6Va=x^wbnKELp#p}G#Jl@}r04P;_524pr+h&*4Kk)Y%a)Hko&KqyZIZk^gF z(##xFeYTnQrY)Rl$7sM5DDJqsk*6}x*v7ei19_A<>DrV?vT5xVmhaYp z_E3cd6FZzwSnLYRjR>p__v#Xd-%7PfBX37HrQ3%9A@ZE!u-g9cVL;~p7vc}-PwD~w~o%&gvFB} z-r>Xb82>u($)%8E(5yf`w}}ur7U+!934TxZ>)1$B(!OKIFj)}r2);W0K9?V%!S!Cg ztJUhVGyWc?H44-e!)sSDe4KxM9xiZmJ9SU|ol8G>y$2r3o5;V0En*)APdMz)J-e65 zkIU9@R)DM`Ce6jD(Np6~b5#?{|7C@wyG^bzFIj|SJiAQJp#P?lbK8v9X;5j5#d{U^ zNxMcQf{(UDP!TkYZ!UB&C^kneQ$OtJgl$pO>#RsTP2(u8K zE_af=nwl`E!VIz*0<%itVh?YCNSk_fZ|=I-OUlHZZNjX5aMK3bN4~k&nkH1>;FvN5 zJzz|nq28dt5RAD|U=B|yFjs0f@TVajCq12ju|5$HBZ#bU)cemM7G>0CT^RRP$Fh!eV!Q2ZmKoc zNiR;(i>7MyD)NumR04roE5wEz@Y|^zSwbX570H+dKj;&+|GK_;Tj)EB2BqJ=%1HQL+V$ zAz^wX{kQEam)*SOGPybTnB0R$FxI@i@jo@@LW4DXwJ80_@Z>#&hL|3H9~%j1f$9GD zah;L2_D}!CzsSblSueaIyEfVIodekH;J>CgP|qDD`xWdk!USZGL;;tm$GxaleVu=h z!wyxiUwadGK>2haGw;KBqu^46f!GuC@;i5>mRQ;3LJ|R|duo8ZseMdIF@yqRyN`kT zEfkftP-}wuZqP*3RhC*^g;|+9u1QQutueFpM*W24V>HMOEwb1kPS^-J_6h*uA}F|S zR$P;2Z%Rkf`bp61SzNdHvd(3r`w7u|mOZ<5?)Xj@y>-Y24P5-DdgcZ#oBju5`2h_v zAew#V02H^Wkl0f#z&07JA$D@B-L>vo2D(>Jr@4ssfHK;qaqk;l$opk&8e4g!DZemC7IjXS0W!Gga$F%zr-C`6 zu%CUO24Fhi2@9lrv+X-Z=j~bid*Xq-BALt14Y``EL8dCd4$vMo7AxD;b4rWw+qQ*^z`tlgOYoPDWTu7SX+!+>#=;pFdlvSw&2_MZsP>#*s$U1qHI zb&kHW8jSspZ>o9UYZpV+Bx}5=0&J2jH>#59(LaHHyds@$=nL#|A&rL`TWRhKzm`sd zS=woLwF{dzAnm=ltc^a!h}}OSN{Z4(y{r`>Mm$f}Ijn`v7&#!f%@})mjMS%H5r8?y zPK?o3o1r>n7CfR5`{WVw_hh}8SJ30@-689%S&CO8p#e;`uvjgF@StVPwL6nTWPn{UnSj}Js zz-H)UB%OguxHO`LIlUYL=q&a;WOkB5Sjjs=nfSk6P0+>2Ei;0mSF?6eMOc82O;M6Y z@`k5;#U4ym-9ppd3Tk2@f)t-m_kj)FL)@!Z@a}Lh$!kCd?SbqlQ-PJHge7 zmdUGgv>F;|rSwe8#wLV9z~*8JcA~HeiU8GU4Mhw7sQ~Ix!1h0!sL9Yx zUw9v@O3{ZVv~Vfcf{9*pg4qXz%*G$P?!=JmP|ynaPUsd6kggocW3@DT3tJ|!e%OZr zbKJ~{OUk&}#C@3A+$aSV6r$bP*vkoX(;;ePV^`^v!6Vl4K8t+H=^C;!(CCUJN})SF zaTaMo0YMq*X*X=@;FU$WGYzz~{TFw1R>7 zt-O?}9zU<|m$mTu0wv|2=6;}rPJdbPQx`P;pO+QykH^Z?|05ndKK^7pQuk%W`{I#W zE-UVgNA5YetoYyKan8q|h(`uppk$^RM}ghGN%W zygNVNU{9&K>=I5XDd0&41S)b3#&v*NLet!NLpB3+^^Fuy2;QinQBx(zPFruZL zt-{vThcU3P0^TmA(J_)Bijl@fN-R;uqYLYWwRkDgPtPCo&Qy2Y@0>iInLM5zKbmhH zWKHWtFkDXe6EFaEo7Fj$mE%sfPh<@~aKJB3I4x8}&cCr(lbW2~M{_3R#n1Y?bFRSTHB9qf< zp=jtsxuFkj10);DK$=hPJ<4-{(mX?;(*Y%@_$>0P^94kaLT9pSG7s5C5{j9bkMMs2 zTs>#g%*@BkwEY9z){`4$bKIFhyTgey_qQ{i63AM>(htYc(H?MW zXJRtty#NZ=KE$M92l8T=CHLn*OJNiTKe*b+>J^sM$b9ud<-jXz(V;vfN^bW6Hzw3e9X09mj z^8NIgyUu#Qdhd$zPTtc!R56u@*-jtZnsndRq=to-Q!LcyR@_rO7>qhM-JpcY#WIzv zMm;p!w>UJU1V$!h6h#SCSDYzA*X6bF>N^t9W;KF`DH{1_k!R<|OEU1N0bR0#*PQ4K zouhV5VZ@hrizg(qu+!uxq{hXuaC;8?&XfmF$Y%X*#qU^CD+)QhRlA9rlc+8Y_X{<+vdpCivY6s`LC*W~!eeYNj$XL1psVkA7|?HY<87ZSBLD*nq0r z5Suq*1%PMkqdM+3a%D?&W`gK6?TXJ;+L^d)VS*L0Ig_!%$;`6Z zgV#9iOz@Qfu;oQ>7S`(SP3WE4l2DuM%^bbK9`ho5qi(jPH#F0%o`4N*n6mldCn#}INp6OSPke=Z(FR{m@}h6MdP@fdRTZ^vUu+YiMf>eyvygY+>3 z^FN5h5Z8a-N1J-&B2AAFbv8azmjl(y24tO^0Gvx;kpBTVH`b^OIqv{TIyGDxqb0DF zaN&PM6f})=yk^3MMN4P$2K2yDv(PxOp_vPLtv-- ziNZS<43U1$;5M^eKM`is??;PrwNSd%ga;d`EyLd?Eih~YRUT7nLXxThyBl{m59x6F zqVM|2HhJa|aN9hdv6gq8-NOl3cuNODLRrPYwC`Pff@@?FTcM{mO}!~Qee2EQ=2`gJdQFm6(CQ1*+(9@`2nqD&1c zw-d+$PB(ZhC;t0Elto2DM*;8i{||m9&oG1H?HRB<=KD0s>1FBTes#C?1asRt z#dAAIa&8GxnMB@o!HWKw~9Sl#K8_|iB`1rKg)%YO8Y(kSf|VWz6mMV z%dR_tnFx?K9eZV|?7?k_Z?E^BqUWL)^oI&g4Dlq!$TBdqh=jPJqj$ZeV`|jah*0Pn z;XSBS#%VBZ)omLth>4fw9L5s;2bR<6&qIf(7x$u7iOvc*o_iB+GSMckFH4|Zy8E-_ zm0sDa|sK z-S;PLGtR_c%F9xE~ju1TD8C!UT}iRM|PkmQ&!JW7AjHi z3?v%YkWr1v474iQxPU3Rr>qW$^EK#$1P>m~;7wHTy#mBXTI3BbdLlH%tC{D>qsvIJ zLc|SYlI|tXmB;TrEiSXV02=ZsT9F&WtLdJ0$O@ZJ%LX(nQc-@pE@+$ws z$N(cCBKky-l&%Jg_J`%`H}h0SShd*V{fBL|+5n8bA3lx}P@T|dfee}V`awT;x|)3$ z^}0rPK@*{_P+SzfIxNg7WhHpN;+WDFVB0AFag$m=V3yDY-0&6~vD*-~EsT`WVm&## zN;eU1)-M>%MrUa~cefEDb@zjLh2!Ov-i5OV+Q0_!CAvSBV`=ZJk*Xnz9=+OT0F89O z5x0iJ!@kbUc)onL|MM4?OZv8)xpZwbPs%*_g}L@rR*L7-yEw4hzHT6)W0IOT8RpZO zFp90uhx*Jbd=NsMUkilnvt9044TCvVl z*k`nD<({xLRnVT>0GMq~q_4~!hj(iyKnpWMQ1fvoZ3L)T{&SZ7+Mf88iSF^h;xUx03@RxGcX1S(&;on zK=v035CD#?w6|Q%&%oc5kxqT8;crXz64>USZkS0CpH!A{*CXscfDKEt=eQ9ohYQRa zqBA#5PpATbj-5I9i9B)%Uc$J<;ALJY886qMI3R^63vrCaS<`J9zbGs`QPSDY-dk-c zy|3eJ|8g9BqACi+F#vL=r`rKgIz8CF_~nc3i(j5-UfTXndOH_7K3!t@n)B4B#}H{d zJvA5@rMqe|M7-a|gRE!iWB{%i4_2Ed8kw*|mBG3n>L5TQ1V5)&%p{|h^xjqBs;oE-361sE&63H`UP_LZT(=dIpQ5$kQ?`_`@6q!p+RUYYrp@JnCfStBf)H?nl_ff{gMLizE%LgoC zt3edO83Og4EOQN>(0sF`&5an9y-(^z4Z;HD6_-*dt$W%7s(sMhBt0V zA}O}q!BTynVwSvjJg_tb7hxGJVwrx~t9n@vXQ)ck1;I@@wy)F4U_vV_ijxueQ1Lds zcZ_39_MEpeq0~8^4E2ol41H=~fLTX81bb{j_3XF9+EhA$y;iaECU^%2WwO(r%k}Pd z)S0ajM-ZS*@zpfc2k+dI8s349h)K1nj!i8CB%r8^YDZwg<#5Hv!P_ zU^ytDlu8+fit%;V@^7#CU5sC%v~uh~n;S>_H5Jq3tc)A!6qipi4_LnSc(h3525#;Q z>A0z(bM9!84Ut|&}fF^d^MbAHn1Q@G{cBo-JpmYa1#JUGqi3)c|ke4 zF+31L>JG6wbe=5o#5qoMVEUN{32rz@av}}YE0o3>5<+xW{lb+alybM2So-e;XTVxr zAQ$eYS~JEJV&Kj}MuthxKa9^zxGuASKQ6F5Hp288{fi4k1u)xs7l;9EFh!zg+2#nAMa^FW;02cc~ zDr4Od{3QSZpn}=uox5(j>NwDhv&LjTfpvsIb#B6k-D$ZA+rSMs@YFkw&WeXHkKGfw zD6-~jX>~TO;G2uBI6bA4?g@6dz>W0EO zS_xZ-nrH7sPFdx7AC%*U8#GIwNbmXS`=6nz<>rB={XmPq{1*?uuj7aO5G{kfT7YKK zqFv?0>iXGVm@!X&uq$bo+9%@hcvyoxtR6>%=!M<4*$eGHf1|+|;xGS249TCVVg3Tn z)Qpp#@j!8DkAcplT}?Oc`lF^B@etjheY-(#n!Nj(yuZ-qjfcpq!YoD&SSH~J*LE_) zhJMDwbXNm5-D%i3E3Dd?bijs|(<8Yd;+RhwVy^Affu>W-j4OKM0DZ_q1g~OqiZzt( zZWq8@=IAb3$O_HmX^&QAH<16(CF2;(D5!BTPz9jRi;jw*z^ z(2_1iJtks|>+e+4;yw)CE>!-+PDBpcHrUnwi82TG%aP-#XewA51P5bAYQn-j`dbe` zC8ol{755&+e!TwprkHS$jR>;#m4Ed>!IM$p;0-unEov3Pn6fLqx3h;s6Bt?OCh z7OD*scTn!7L1jf04-zTBmk5^W<*$WM#;7ImB$bmS41bYSO_Dg7A4#1_QnyL!PLgJt zq?t()PTo;)mLw1a(bGN!kMG23Ure~tp5P9%=@%cn@2{&HiMn*Ex5h|L-U!kJ-`Mzt zh`=`mKE<-)3UdWN3!&U*XSxs|e5V#!$<1vfe0Ox~X zsE}sNMqReVa9rg%`oNS@A+qLpKKqw~YPF!-nw>Ic=ZDE3tb*3`r$~V=B@qM>o%rq3uK+Q|*DiCsD6g{H%yr$p|(Nq&dFModnKWD%^Jx!y) zmHFqen3E2|3%<40uU_nL^aDUG?<>lx)18@}>&-9p7nhb-2CF-E?%KU)?>_8|{+HoF z&d&a_VntUL#jnALEu7^d+<#|qGfyO3&(G7JvA=JrtE1Hw$hLO$i$xQR@hKM`>oJ$h zi*db68uWm)9s_zSzt{{MHBV0#tVNgfq3X>nMSi|(@F7H(&eDNJ!l1eKn%)Td4xk@g z#kGDpN+^9aH8S}r&)w4bi|1LAMjrkZ7WKZtKVLeJaNRlS36u7dv~O{5lk}`f=aaN| zaZi)z`<<9~fCHlHO&K1y?-QA4xyiCM_>wc8H{VOtKS89`28%-!RDm zky^5rH)YF{wXC_@rQqSGH$n`_NM$$o& z<`k(S=_4lD)mdGEBI(ymvR`3Xh@^*2IzUoGTma#dCOJcy>;YA$J63UT@Xyh5rG_l; znU?H}ftl=!f%tD{3~Zx;K2?EiZwA?v`LXZ5(aDCZ74D_B~@H*u5=EYLuwb zHLAzkixHuRI zX)FbKSr8i85J^232ME4taa3u&lqJDY(>?O7D{}GlxG(pE&Vwt8{*U#_o~G!#;49K4 zt4l>%4^ySFhf`6_HHRhyD(*j>^F=mPlXg!5CpB>Df7)H2F)r$o%QSB5yaBiWtZA-%JYA-ZU0t zwKR+`mH2>j#;G71feC34$y@_}MYtSzZDd{>q9F{+PBV^3tK+!-Xy&X+zO`~Lb`41y zu2Y7sppzRSU(3gWWsSL$eTF-c*^O3EFRil!M1MMCcs1>9eTO+c{vmd{WLFim6spVG zMk3IH%1$BV9ec^LERWqIlVO5yBKnI%hxJ`rs8vu2gm}5?PW`V#fa#5ez~zmFo}$pq z3}t2!8sgiG6{g=h`Rxz^R(vOY_i=8+Wt-47+eshbL!2;}OBZ>t;&@tAnwd+t9#7xT zsS>grVOP`D98cLgm{xM$*k&O8JMyya;N_P;97f-aIZp4P0oD)-T#P^8^36ae{SqJo zJdS7qiJ6TZe*;G`*Jm^I>Be{_5R00EZOtJ&O=^Av?Xox9l!SbQ)KE6%0FP03PrLlt zBfJ5(gI@Xwa%6fL?PG3EPyRkH@Bf3mh+?KdM~C8c6%!N^zU=&xoToFwKqxhl66g~o zg!OZ$m`j`Q^7kwi=jM-guc*$LWTOw+-Z#R0#|wbc-Kam^^UhB}0S#I(_+V+YZN^km z4rq2+M8mRXrAUNWocX8)Zv`3d>V?!jcm=DBP7S(6|EPh+gTX^Sg3GUIyo0sY)!<)5 zl8&B1gFDzMNM)Jw9cV$GP-^?XQW5|Fy2A184rHY?6Q}2?2I^AS2n(i_!=Mr@CqQ0T zh{3EbV8sDGo+7Zh*GGicE>jvR2`hl}67e#&pffo#V3nAZMPCOx7V?|@Drk`H zXEGsLF55h6Co7aU*-7bcQ~P--bwXsGD&a2whCJgc#)TrsjuY%6qmEl`$_~0tO@<-L z-UQe|p+gtb)wzYYdIW||tSi{3JRAd<`3IO4zKI{tmJzrPVdw7#@TQUtW+Kow@U@EP zh3_nY4YLZHQd96HRjM9%C;MW0@vw65xe<%hyu)sdf`ecM;Rka8S zO$&si$_i%6hm4cpwCzdpj0?|5LTnGwQnCH^Dof@}c~^F+-%4GAHB2`PH{4iEQg+O^ z)Dx=X^;T*55;JtgV3G1X__-2S5+u!@lTAU8Y;dfr?bEP00|(Yw6P|Ev-=N` z7L>6QdU*dD=8aCmvPN|EjXcsXJC4j$3=8_1J7zyeo!nw{j08JYM8~3g{qKa5MTZ%X zB=&2s@o&+!as>5^bkG^l4cl?&2z-Lc(|eN9Vs7TYTa%&r>6Dm3oTw^2^4SNx;voaa z&4ENbU$<-y+4{g`fPdo(e8JmtPAV5>rBWSnU!f^xY9B(MAXp?4WSVSFdCG1 zeqbi?m*vc~B&-*ij^SbrmZ+37+xRXGwM&G`oiVGPi4edDv-EkuhySPCc;$qXyI6x* ztl8``Mt_<}tkZ#zB;PhAH1y;5V5H0GEs1;%ek?^Re-ll zcl(0U>xCZES6)yvb1VH-VMt%3@)U8s8?ywWRi)G|I50Z|yBc@pUz=NUF&Yfglewr1 zI$hk}cd#i`Sfjksqgj8u($*U<#4mgJ?z2qs1nFi+% zPe?VoN_8Y4GJ^VKm{`G<%NK?&6Yv$NvOu!ooQXE?-#DfKP1D#02xPE`o-9d*&7Xsl zyE8y~SBrO2yY2!2(Nune#Xw1Lr>2vL@$BkoNR0bU=x)B>M8+__-Gt|+6AHxfrMcW> z@?{9&Y*4G{KU9@7N-7$Z;9Ef{f^&HQmRtetDiPVb%wPh4w!bq*<|}FqnaE;TWg3Ke z7L9}xg2$=s@C~&4C!ywY8axQU#x=2Pa5ahwP&@&Ey-<_B(E!$qJ&(01(s1(8d#{?EWZ{>RPZ6iQ;~ za}Xo{)Ic|rPl)qsS@i$5Q_YQOJ6Jm5_||}3Qq)uO1#8hhP(jL=yCd=UR{xTy3||B# zZb-vA@-2n@^x=}g{_k%T@e+NR?eeH65ZxR|xy&L{5Y$E7WipVHM9EQLW*0S_dbr`J zsi#9d*jE9K{DXsfC31$CQ3gN3nLbz|BJcY8%~{o9`c>sI_#qS59RxCvJ)c2_Rj}l; zw1Hjl?ox$BQ?Xq_t{6&fFJXOPQ&NnkP68j)^TD(_|6@GXr@7Kuw1k6Y2xniM5a{b( zFesdJLMF?siN65?S_A?asvyBxC}<)&p4_uoEeh6RoY3s1-}lR-%%-VOi2|f)x`VcF zuVAY!`Plq{A-6lQ$rW{!j0(=1+Fwl(_o_VBk|f{Hgp$ zq-ATG@&(YD>{4VyNII2ONH!>ZN($y_-6#K0HkV9aqB?7;FrTo_Q7bOTzDCRutZ16a zW(|~b3%(e$Ix=2f-OPjb;dtdQW`M!12HJgdjhowd4Nih}IMBYP_+R-!tb7YU!0UVY zK@|FKN<8y&oOtbA`v+~3D74qX3{trLZSt~Q!G@0!=;v5rc`BgF8vHv zi%Sa5H0$^HQ_TxK3D-x+K(jTYs(n1XAvY$qRyVQRhg_R*Qx#z@oyA zt*X2+tr%O$SXU*k>j_UDdYzug7?VzBH5H+EMvj#XirooD6^m15n(l%iK{={ezfHC9 z6_;;Ms>t~T7io9!Gvx>xRnI#G>9fV)=OMmrjZJzTGHd$n=A+$^08v@idJk!0&#Vx_Q0mQnYOv>WQXX5xhNTitmn-nZ++fOc(arM!8NXH z1p8exd9JWDR8O{9nJe;BM5?dC@o^b!Mk4*m<%M?CsYSYL4?0cI)kIn z-1fdt{`i0R>}P)M_5)x!oVTdSOndx-qDkHbCp?Fvdiz#s_$Xo4-uatu%`D_^;eH&bPzzuBa85T1= z3Wu$gqvb85!L^7%rV@j+`8ZnT49)6yjyn4(QLo~K)Dh$98CCS!4mh=iaz4R7WH5{T zJbJ&J1!Ji;h5-o>vyY)JXk$qAV5aDgd%UIWdiWb`EPI-?DC?lJd4>^dj%uJVRBsNp zGsU5gxZq-wf`}h;>D1?(2+}ka2p{uvALiOJ{9K6&X~6X}=hLn@4#^zPr8{YS4F2mf z_^$;~?xDWmr%EHT+u8I`yX6!T$vSgP;-O||>Ty{r+?*s>>6GLG&nFGOp4l~`s=S&_ z*_P$Qm1X&ds#l3fmlcN@B-|DkzHF6&xQi#P+kp5I;_n=yndTxAl=J3dCB%Ag_yGcT zj&#a_|6x>Na{dwwkSff@QwTBij`N*wuIDhyjir^mJ*c!YmL1+%wu5q@${Fy`cV-wc zf-FQ#jVc70+zc6mt8X(tuxq>Fm5!$_w;vu9>F;+S$Z}u%K%i6G%+qrZ1WNkYct(7R zO}<)3N#RSR-EgDK^4y)D7a^S!Rvn18o!E%Pm~rUMQ#eFrFS={;Ru|ILvI~b#1Z1U_ zc5(3>%W2f~+(l-oP>0NgRMS?{C6kVyDr#Bh7h(G$5w>D;WGJ$P9az&vGb;MZz>VA- zH0~@iv?A_T;1vOq1{UJnDI}d2n|m{;&wk0EB$b@&_hYMmMVgxC zM|h`YYN1P632t=ZFwE24!09hTZ8xa8j-b(*p=`V@DgxjZ!CpUo23XTRPB?XZ1$K(2 zeuSHV)sZl67upiY7!DMpnJYTRPj=heREQ6TXFzfZkiv@6)<%9L9#m$thNK(0qs4nFlgko zO@k&tpp&t6(A#t=chivD zZmQX$uW_5}903DN0J6e$?q!`~gkSvkjF4+AOe`nADz&-mHJUL4exBch4pr5N(f^bh zL+)F^fYC)#Qv`#>=OHSWi6zeDVlA^m#eX(xo2qQJo@-vn zMF2K1$6fs9s3LpCI%F??BM>F#0rM(Np*9X`j977@+Ta^=`d=fOU5ex|e)!m*BcH!K zJ7r#BGgmbR zD5$MW_+^4asmi9*L&t`NV5ke*h)RtwYptp=ctDX4$snFarM*EBYpM^N@u1+iIYt^Y zrGG6$&&3ro=Y$CRj6!6)@qef)7f()5q(u*#8&VzO;RsUT ziax{~ZCReK`T1)64)7=Z1UO+7$f7B%2~!2}Mapcjq|pqQrm5Fb z(#LZRYKZ{(UGop^5g}b9)8wm$l{~4elAsd1Fhv<9iCFF#KYSHqB08(`%k7hP;6bkX zY=I8t1;@X(?$`|P4d<-8m+&9oZ9ubLI($7Q37U%SCc?^1TAZ;(1V9d82chBgh_;hX zZ~;z14Sv#f&XLv%k$_|vax>7%EFEr`7{7CaUngBT`3_C(K&!{!0ZfNl+IOi(Eg|ZX ziD4otWY=mTdulS`2>cJZ%Dk4hxSFpcQxWsna9)2rwqQs2>=tz=n9(}og^nc8KsRV` zw5sL^{0)4_&#ob6a-;1cbOyseG}4q^iGOo=%>rpACp$)}uWMxvuNKQ-s`>aOkxp@i2?l6wn%;g1)Glevo@Xbj*M z+vwo|CJsxVCIC*@fDf*9RwiRQdh(8=8lNJ-+R598#J@qhn~BpmgKKGdc343ay7CZk zi4tUy=#BVL4>lE7W!Jn!l)rkwb+Vi%x#~FNs-9VsRK^K6Ku@{y2kU{Yv0^ccO3px@ z?4v@552Ct z$tnCP02B0u=;DK)0x{jlBd8D}O&=DwpbK1(ip4m`ILm=GQr8AQUDgXjhSTRe=f9DP zyTZZ<6v_`gVUkfThG;pg$}5iMvv4#E5|cb!rj$0EtAa90Ctk&++&%2MJ1w;jf%p+yQ8u`C}k=^W9*#>~I)R*HjwJk3(|P$wo9 z?%-++)QA$WH$0g5%Qq1u{Ar+EyHc-pVhTL4J|(ijMJg<@5Sd?}vy&JPi&V#I6PUCp zDUklq(W!&)`ElF7+3js+U) z6Mc$ahWDNUg_+OF`Q*T?75{_LM*9|wmtJ0pjFoZqRpT^JncGwgu#|a+c|*W}6gF6b zS>&Y&u&|^8s7tEGd={NO&|(7|C5jY*(bhi z5_2O-VTW$;avlx3{-&JNO}~eTRGQ2{hV%p@>kt*@)vR2KUDCM{$njHy3;YbAuN={G ze9Spvf{tGK_EV$Y9k}rf-og9)Eu+~xMsr7Zzk|=fLNng>4_*5f64?u8M_4>^>vM+vE|W zPlkF_vUGSjpa6Z7KHoPVUkm#niClx|w|&xN*liBf%IXVKucA>$5>F1E{6R#bw$89* z-IkKeQu#ot2~sOQM`nu47()O3H^wHP5HIQo(`-d~D~wY>`&6Fb(wLp&=j#2rtqzzf z6M+G*$?8473Ai7|y!a-RtbC$-5G&T(v|a0YRjqHxp}G>!4* z#m(}BrrA2vVQlvf59~~pu$ojf1Q;jGn*dSleP9y`^T3nGs2iOoZaEvX;#DoQ5f+;Ujlav`EtdY4|sdG-Rm5)sF|=2sa`P ziD=(;E|8|nzCRK%uYofNBSE|)Xx#Y4&p6;ZlUacka}+;;7`~Rwa$rub#90pXMCMes z%lc>kSF7x>g&y4)6n3TG`7-(^NKan?7`_SASDn*M>(u{EZfH|Fl-;uG;^k$*=vX+9 zP*JA29y(!L|Nm{@5%ki1&*V4AQzxCywP;Jh3N^4rz0zc$W%|+ysrP(JN|qJIGrSx<34czfeOg~X z1r^dRjjBXKX!3-TNRN7G7_}CeAl=D_Y&xzxVmhRcSs8%*gJ`!_R)YNLTVS zMW!YMtAehjFL|AP`Y5HS4BT?3kxROt;>3GF-1Yqsw9bi9bt0S-x<@F(HZVtg z@D^M$I95q%I?H+t2w734OE_CZ)lkn{>&_2&g+!+IFbiZ5$!?qlMAxG8pICJ^vB^jTBR-QK6%Dul+v!~V66<_LkP&tav=F^?yP<`fB^Xe zSfWViZ<67Ht%;$*4h0LQ*dE%78Zohm{b`J3a4e;N4iIQ!Vej91?I+HGKG_$blRnnK zCc7!TMgH>Gk1#b(h#Z=((RI{d$azP|p+*eC*v}AlMK--Ivh5~kMv4KlxYn_QO~JCJ zwf&ImGUS(#N%kQ*(i`{$W=|l%ldkx#G8(@6d_)W88Jn_Wh?AZi=WI-JZZufHUMvB6 zjiYgfyMxS8qPdMTA|2>516N?1jqH*>Hl`i6?%BN4*Uo)zJ`TIXwRO zp&(~*RYFg!@%N(F?!ReRv|w&v57#BeCon`^nN_T#JkX^MSiPJ(&jOZLB$pyxZ~~v@ zV+`9F?7VhAS!Vas61;@En}g-R^@iLN!qC*c@hYh2k7uZs8!$nCj+o^RD`$)pcI;}Z z)Z|j+na1~aKiwn`p~O9tqYxbZ!xtyS3LKOmUhR?ux(b|N@f^Acm!N5dM40Gw3-abhtPLyzXDTUI?RnQ#ceAvsB&CLxX`fVdMup1>zj5FOzjk%A3y z5DsT%MY~awK1{|yEKexl z8`%b8vIwgRirHdWE6ntiO+^mxx5qx&UJ{hHdSmFQ!Ev$NVpb2|BF*Z6dPgy9%QwVaEEo`5Ga6yzta zRR>6TY4-!@@rS{tuH47v*MI?9LS16CKaA2og6{c;%2f|Z4pk8)D6T3HS}n*7ej0Mf z;~AWF3AQI+0<#-AWGwQb^G+t5Ec6L@JJ&EDLCIrEeffl@*b7c&Z63%j5_8D}TB@QY#H=hr0(_?rLm!PcA`J*Z+BsWR z@`%X{K+_6G=%ZyQP%~S#hU2MOV&*_!A|3$x?8jRj0s1lP!7b>ctGn@uM6aX6N+n}N zsg6jH168CA8DCPNu=BQ5$f=#m3;IFI-hjC;OQp&E<(~RtrY*w6!PR9!YD@kXm8C)dm7@P5QjE&?v2(b^OjC!*V zZOozEh}|mw$R@iSgtz{#p`a7bYsGLfHO zUE;}@FmMZ;F$-)uOTBrG(1m9C%K2Kx-61jK6)T#^ zb(mh*tf-E(M6j+@MgoUaLb5ieN=Mhj5N9*;jhIt;dn3%`f}smUK?RVBFop$hLqSQ_*}7&2^0nAGQDYh9 zTpA?qYo!fl!eEI}A}dhtS%9O_KGns@Vk4boz|qM0jD$9L$+#4a@|m41w}uV9uOi(Y zG5{BKUh3m{oFE0rkr%oM-e{kmdYE45pd$-CjzDJJ6WKG?Q>f&OoP|!+E^W}wh0duu z-#|T49?)GhRA*`zx_T1359mon){~$*p(okhD0?9L*=bT}Rne)1&f2RB6zXQ72MS9B z09F+p3-7vpRMqsxzKOftSm?CILN6LUD=+jiSP2Khnx#%WG1_pc`;&Dh;N2{BKnvc8 z9dO#bRv>9jgO1yFZIJ4Wa$x)5w5hNXU}IKFv>;;pFw5xd$VHJLm=?)&GZzVZA|U}? zdi}9hfJhIYZfSy)LXH-yO3VAD34vspSkSVxz9ln_su6PEw47Zjb~gA9!`OiDGEtaG zi=ai#8MI7n=QE~{xCYc+>SW&FeRH=9?Q*%jX|^&iIE2JkCh+=YF9#)Udr1!SYAxo0XFWousk;`Wp(*jau7?r z?3`q{L|&Sr7iR+OjJ>c=B4l+=RtH}}YUqwv0RCOH;R?Xo)-pRBp~9+=nVnk%ATo8+ zz*K|M0oxs~fB=T*oO_eeIZBu{(3#l|Ej4JjMwjY{=AErbC;tGtScE{p7U}vM$Se0e zLtgPTfPRQ_Id7mGd%Xdd7Uf{%;W=U+P);lxqn!BHqFj8YOAX2~pVKJE7`CEZuwYhj zD^ZP64y0*N?wn>n5wC2Gekh5mE0W}~D6_%r#}G982}d9U%@zIVF+?JGyz4z18ANnP zDX7`}N9K$TiO(&vAt=D}N;8}mnZk#KWrYh5*PG1n9XwD2K>AXy1_qfBYKUl!b|edp zVNo5aj!fhYwNFnwxLzqFQDnUuA$m$efN=$jXNF_6@rUVPa_6#y0DX)BwkBXzSIX=| zsg2GMV#1byx@|N7k5X+Bke*-N>M?Tbd0pg?JT-@dSKYAyxJgFF~Jk=EjI$Z><;@ zXXX+?C9qe6v!ZIoIYx3WoZ}N><%`2P%7}BJNHsVYEleA)1L7`7KpOZLrpgKaQJ}%U zFjan4_}3yFYpxmn1kC31)^H8@myjW)$(|@qS^*Fz7^HB>G?F0 zG=xhmVq#k*;~Z)!nhsgEsHIt2ol;9_GZRU zo2Rwbz6v;rDld58o0=~P-3xH)xO|x(Z9>FU!-55oWe^v05zkwpo;H9LaoY8G+E8mF!CN(0o5`)6!SKeb)TWg;rYLDt-IC5+ zHKPOqZTGc?ZEA>So9^cxqv_Zu8>JY0t1%!67`(EYt=&BG$O}S_#|zKH^y*&6CV?cX zb7cz~>;;wb#SBv<1aP_3k%*r-S511Os|IuD6#-&#T7W<{keG*AL~&_KfLI(e&8$BG zDQQ51^|(0$jhLZkFOrHj?mUI0INpm4F}16l03^@_#Yl<-L8ljr43z=}ci+^_Ze-3F z!}DviRpqK&49-Va0FgeK!ELS1U^p8^g9wYiN&0OG4Pp}e(aeu(7qT6q!GfYecZ&?n zffAerqr3EblML*r@|dEp6VV`x$o;HzHB&y{B0GM58%1DodwZNJ>wdE&q7WA|kMV?A z={M^l7WgRdlv0yR_?C2Yj`DyWc6>ji>E`2slFsEd`*r>=<7i`DYDg&h?_4AxV$Grm z>6Hm)opk|e2(dsmgjm;}c#T9{9q~U3uwhlSVo2AuDr3lr)jgl)LK)bqYG@DI` zH<4V)q|i{i{p*0p{Uwp6C2SbDWVv=G%%z36WY zgzmgR(IV8ft)fL9HPwYd1YtLn%?zBFJ{d)b7BbiNbDmBnv>9>M+goxQ(q-6A1>f8bp!@Nz&5B&FBAh+n_83;z>SJM2KDoat$|x1w!TQ*dM>e*-Nenf)rzew zwry~0yPi)YNh7w3MNDj~g{Vt6MphhqTJD7YSCU1sHLjP9bZA-@TSM|-&=^^vZi=m0 z2%`rsMLUA@7Gf(@Y;0L7u`Fd@lq%a;A$!@z$^=Bwv1Mt(W2XFGI`j#@s8(nVRNf%8 zBCyCsSR~5O@nc+f*gShFG=_<_$|hFOkVU#hX7x4+vV_cP=U|EFkXdb<7AEaEYMZ4! zs)kQzz_4u%BZn>7gk+tIr zuN@!7bapGR*)PL})gfJZ>r<{MWDATlN_gPCJC4doBsbz*Bi@f zxo}-k#h^n~&aan3e0oqc^;>m>Dp{n4l)alXTx&9FIHZO+g(o$PILR<9`}IkAAzm(@#Yc)OecTU^17(K?wWbMli< zm(+myD+}~b%;v(!y6`(gtI%o~2@+@c%fj(>L7T}a$(x#gj@HR?Z9Es^iTK9|i;d;f zA6i$pA-gIaH2~{v%UBNw9X+zd(>@MGQcoQgSbrRa=MA7=fk#!oq-d%tjoZBX!`v`t zmmmosusG^j?)dKL{a!WSUr`j_1PCz3=I<^qzl)zij+!}aR=Ckb5P@n{=Un^9%cH9! zBvYwzjY`UcBkb>p$o6UW*d-;qIc$t(rDwop)oE}785^>gxYoW*%cuyQw0!JP3C-Zm zt*RZz6x^*~=GY|#6_A)K;GQz;rvesKTD5|us9@>Xp~4Eh`>)FfbV68HGl53!h*Lz= z(u~Ubit_T@&r6D#D~f326~)pw5n(s-t-PjKp{`I~)i3%i3mQM3kDEBq0RxqbPY-!6 z_ZWA?++!{=o8kI2dW_-+o1@3viOJ(i)8nP+@lL9@Dm)J)ElfmRI&@};DGyTN50p^{ z42dlKq$n7Gn;gef> zfmlIi90$TXHWS zH#W9a6~PAn=orA~T>_O9l8$_<_vlkhtQgoQCyq0Hd1mu(J>n z>rlurUjb^oS;if2^Nv?%l#itsPqD7z31KdV#pO@0f!s#g>}v|eXj&xf8zxza9Irv^ zM5A_ci~id?5ygmZ4CO&FJzwb;@^FR&`tTSRRPYmY0c8QvLZl9}YJ3cXyxfU}++U#xKGLCqxSX<-KqKV|&(9HHp?7_@MQbFV@p9d8Nwst%Bt~gQuCf+C zqDim$Of3Cz#`A0@a(K3x{JZi`2nV4Gvt)9uZ0?Sq_Klwov{f;^-Q#KB4I_vS$Y8w$ zi+-C&Q*VX)xb8zF!E)_E>iE&l#GBSQeuNv?<9Y&na1+efQ}jPuHYb2^+!&(IC2&XH5M*7#Aw}^9 zWefBznaBSBTy}32RgU7E^6bj(`Vh5`-CLpP6&p2bXty%ge&emY^_EeGklUQq7C^{# z_3S@kv!|{&v{iSnGyHZ|4Hf25SVl}+(uP=$O{_5Prv2(y>F*v$A?k( z^r<0V>h9^xGbF*JpB|P+(b$5=<_0UPeHC{Tgu{f}N9WT(kuS#r@)2p*R%C9?;rj^B z(EqD{+_~`N+^yv;9E5({g^VWzkd@M~C^GTK;(Z-&*jcWvj=bRi-29sUPpiY0|2Rp* zqOFd;?QU>9#GfbC2_~;DeCOF0Ol#rjcC8`%xBJUdIHbT{BMxMJ@ zkoa*MUe3VOJx__5sS-0UsKo442|dW4zYT)=j(gQa5P6infJC6CO67%+I z>zw|5rqqfj;=#gHy9*68Ts3^^PkrjQpFmeWElzz}Y(Jqp@o8!5(^C6M96eGSt)6V#G!PKV<+E4jR{Do7WE^I&Tj#3v* zeY&Xqv?o5nzQ(v;<5TX)5Ok$=h#OZgy@KG#MKP*#l)~$l_VN|MK_XSxS9Iil=1N73aT6p5z*~30;Nbp=|U4gfj%t0Vw;>V+cxLRx5;_Q zwmC1`Cg(S7oAc6ba=v8SoG;rZ=f&IR{O`8OIovkqCEMiWnCdoAIeGgs- zmle3SM6^!u6Whr)IWO8KCtCeBIWOEMCnrR<$$7yxIkCEIlk>nfIkEO^lXL$zIT7r( z$$9=ZIk9`{W?6O=i zsi!kIsLexzANj8v&B>Y%RoCjR+0_FDBT&6XltbVoP`)Lb_800BR}Um8B2b|&g*rS^ z33hY@3e(eI*~;M4o54aYT$puGliLMk+czM9OilQF#;c^Wk8!e1VR+kk7RK8Dbf)JN zTtHIUx)F5_72V;UaFrm&`YQi+GPGIh!93zA@W0bP{Y^{XI7VmoK|WZHb}C4-oOp9r zgfa`1S=dnKHJi$;T4uE-8gAZ6nRSN}L;X+}mF982u0QN81^aO$iZC5ET@0tLALlc;DHNjlbYIgl%@wGJ#C8i%L>@hH7%1-1 zsF6DD?|IYGMfEQ5Ws#ZSzx2#wx!<^3&8H`u7a}m05sc6|Cm3OBeVSG4qc!%Dwi1<+jC z%l~d>O%i3JA`7_NBQ7F}-h#-pKnG4Ya2I*3zEbbFo^X!~{w$L*uZ5WgU%89q*l3{!{Rjcc;qTF+&ypg-JkI&75o(TFwGFzi zqiz?_V)LTKkO+zO;x&4Ef=7_CdiI(TS_-j*W^1o8@j9UrjJmVMC_0?(Xt`CHp3XrWbIKLaoH)Wn5*}5kK!NL57vf zq4NAB;uRjl*Y}moXhEhP#Xu5SZN`4g?hYve{iW zMgY3glm^PC_wvI*`#QZWpr*b?EF1|FYlm#Ha^m`&e!VkN2boEK`)C)|o-q<6?reYG zjH{F8d(+8}51TbRH=TOhZ3`w7Jea#gTA}U9_4Wd4HZVMPh4ZGiAeM-@1o<`bAc;W! zDt^$TuH@UUW2W;t9XOV?ru=s4IQ>--2_vd7iUVBb3~k}nOqBx9_`8p<(~dWKJQ@!l zCG{+CaFKlP1Tme4=u?yKuCKRF)5t;ab~*uHtC}A>eu%fIejUXP1Zp}L7EX8~8mr^F zG@Z#1fL5iucr^CyFx{D-LXOFVkJBPB1d0&Qc?l|;?kuMph2TP>sKe^=6>QdKEZsZH z(J(r939?>)NLSL+fT4**EVQb+{^si1Xtb`m6DM-pySMO(vTtsOLOjgx9>aionvlg; zRM%Sv#>Q{1u0vM(U^!X_f0p6$=`obl%kmEVa(a}P@lGKYzSAY;5XnyQn-STIJu0O` zdXi{Ilv@+>h$V{w2>usv2*|i8t#Aq;qXx)I$Km6-srn2+jg|YyVjMZVZVUe4*YL z6O|bWRJ$*JH^y7NC#P))xOx|9&I3p`L%39(HeXXb(k7y*u+eB7RXRvlO6D~}qt+(e zQ{FKe8ohMr+nwbtqv0K+k&#D7CfrqyF822f9}Y&i)X^QImze)0l(^WSqzCU9T}&xH z=5VRJzr^w{sq1=lN8~bQYMt}kGNOWut>9ul)e$bWt`D{!FTR6jD10zJM!9;_ex&3< zOCF4JH65+%rs-Qo7vEvI+Hk3UuxGSSxKxww;F+ARN*?d{FbD!JyajH=&-cQH__+zx ztwe3?i02?x2szHc2|7M^`Y5|%5q7-19Y{U< zU;pmE{HMSA^WS>5I2BaR<@KTp04i4q)T0R~o`uY4xTqt3`wM9p(U+0!&JHv^#g|_8 zu#mR(T=OIjJ_?W89YU;0L&KD@2X&#~=~y27p8I=!=xbWfw-1rJP+th6(1CPqbP+%t zMG$b}U9VYMFf9UT0;3+P4zts=bbK_A^wZ-I0bQ65(>|&R&5odsCKa2pfZmABng>ZU zt~O=ED`r!v*x2gTHL=+{Cn#jT;@GA1viJ^Wu4hE}sc`U-Z3uaxA+|aBb0Z~0n+{1% z5CaTy*-7V}$bNh0*^M=_AuhYI3Md$IjF4KO2I{4AmV>BI~g_fh|pBxdKA@^uKL1xqc9&qBgck?q`!g>zM;O z)0$%#QTV4#a@M}P;Ry9bu&I~g%8`0Uy%)s+DI`k;j<|a@r?>Vy*M*V)nH!gyXLxwt zFK&~{Ao2oiBLA0THVK=hoC*+E2x>USc4p-N!pQ&K`g|yWy)0npgwfL}0z5g!K)6xu zUc-@l6~%DALJ*f*$BOM|?hK0TjRcH+;%wb_(l=cLP}lt-z7!$k_rOc>t?r22u?C2E z&4TI;YkJ90_`nL@SOq8=u)nZHHo6Bi&RYw-C+0qC)!a0`Uu1y2tvEYuFrAhG*A$i; z6Ym@)qT$**2I|}C<6J!)<2xm$@ZH;H&VlcQ;J-)=2(;YmIM-+nCzU``-ZM^vDCddW zVos&3i@sDw`#U^T-vu|2YM_~j3j0}e66O7Ap1{$8Fm+rY-iO|BqKRNnZN%&IA!Y|X zm=gCH6y9gR@F!TTn0e6qGOP4>;e=M{34Ylr(h|sdJ@hrW$n|-Skvkm9Zua(Dl6&iL z1hHb%=}`4ngrC)z>6xHi>ZwFv>qvA)L%3!TY$kYI_y=#F23pK?b%Y6;Yv;PdMFq!& zkduv=BI`kGh?vhs4wRq5k8~M8#+3)i3f!J)G`eQqs1^DO1nka2`eM6$>F`@d0NS-t zQu>Lh58vA~5jRTJeBKjt!r2TEv`nY6fTdqLH><$IhGcoEn$VcAZ#?L;JV?HyE z>5vMUGe?;!gcOkw2ggvR452bpicl#P6%EKxDjGB>l}JSs(x9Yhk_MGB{I0!m-+k`; z^L#$f^SXb}>-)#Ewr%hCUTa-z?Q37_TGzGKI(vgBfUeh^FuqXNqX@2o1%ubcrnhnC z>1dS}h)(*@a65qi@``jCjfP-L{mhFnn-3UJ4gMV5KAcq|m?ZxGvUG&X76B(0B6ESE zFf)gmnNS_{cL>-yJPIR@43|#nkO-_-4uC`LZ^e$r+mxZ=5 z$ceT&GQ3&?I^91mO*T8Id930j^Kq!n15Rfw%3Cm<;Nc> zp)16oHelC?Lpt<;zQi3}yMoxmumH#jM5EeT5TgJ3s&sT}7l*y89SHhi-U>PtkG{5j zHwYEU949>C%Ai&*I2tf!jg3;kV4E_&4FQ^14Ih9J?}xr;b!s3Hmabs#0!Yvt#L>L} z-W8lVG+?YPccS3%edxUmU|11-Z~MfTP*ArV9cV;-cJv4yJ$gX>ee?*6U@(#xbFel4 zxJgIo|M%OP5XA?WSao6}qQBiH2p#IPc%%9YdpDK@RRI%3Ka6qcvjBG!9X0@O6CE}H zqzec1}+^-iaIG=LF`lulNgX=cy5l+2H=o{vK|^|#KZwLbm3|m+Hz2fV6r8{48M6H z)Ca586n#vipf9-x*`H7`s7Aq0e)b5{IjBa#qk|w*c~EVExnAJ!w84M}<{$$ji_l8K zH7!ifU?Kt;!QY}9{$V|U$%%UUFd_>I9n`Iu}KB1_e%n zu_3+R6%_##6%5it9IENjqcf_7fSc$k*uQXCKB1%_0d8U-?2jr;K8=6*n#7;w|L?0x zbedqY_z5*RQ9jInVO7G^BxpmbBu1LcuSI+KNsR@>G4~#*!%z+KyRL%g zkNOP1=_*v6LrtC0f6FIS6Z%Y`s(-B%(1#WabA@Q_WzgmbM>RNttSfLQXhI}>xE`EQ z9Ca2-5z2@6umgV~4VN2VincR#TpnD-=B!{TG-^m^Y=lV(^fP`_Basu&-kgofHuQgg zTnAksw#s}g{W13c`SP`ye^!T0RRx8Z>IKR6yslQC|H%_k(rzU_im^92Ag=8$kcV z+#VR~*jJ8;I08ZfDdbutjS;}GYJ?AV6R_C=bsWWf+Cp?H0+a$MA#`0MoK3vH4{cU3 zK=k|9hH(?B2yH|_4>6Yp*+XYrkV!&t5x(t;T|o!N+8VL1nkis%1&#wcRf0~2z(~0O z{4@@}%7xNFD>ja#S`e3t!W0q%N)^P4!JD7ORMwxLVl%JCE^z<&Hk)}Fc0uyb3(zb< zB0v*Qd^~c(Ktcy;r!HV3F}d__LM@@1O%)gh0^(?n{m>r-s0;+)H#MvRb`abK$jozi z&X~+#Z4Y<`B8Clvp?RhnBwU@;c^L5t(gYYu#6%vQzy3et_S75}(EYQR$!-E(NsL~t zoaiRttO&s-$bpCGV>O&SnqWutA0(-n)FEdlGX0@Qh^XA4k_0*zmcOE&ojC$OPyn?+ zMn_peOZ+h+4R2OP2jX!PQiDzxWEL~`H+VbHz+prYnsr4z7%R}m(GJOc_r}XG zIRgJs%jG{L>7Ptu4t~d^83eAAmie(wkbfo+(^_TTiJtevze!rq4xzXV^eT*XV#Q%e zeoQ7o4W$WFLl(6uFO?Wkw!u5XwhUo$W)gs&MF6dK^Gso~3u2nf@R;0FXnMn%MFMn?E* z28H_ZV>Nsucv1XFji4}feSd8OKOG$(AD*_JmX@}^W+XqtkMFfAGAvYGTSH4jTU!sy z2bVP>B5^on92K_`2SW{H9F7jhDXc-k;b#Aa6#$z7_E7Zj@bK~Q_Xzcf@Q8v>XMv&; zzD+?O3REI4D2g8fRB@`Xi@;8Veb!&F+T^w8P&C-$I1Sk0r$N}c798Q;qJ5*dmVvzB zV18%-pBobt70A`+@`L#yz@|tU1gr;0hX$w>R)#hlH6x-UqkJ{B^t5#i42|>*W*cbp z`C3|rx;z74EhFvOM!MR5Iy^&PT?0J>A0H#l;2@s}9?HfjzX?``X@-RPVO+sELm4(e z^C+-I=^xOBTL|;TsITSwPLi(%+IB#5CsZD#9Td8j7aZirh5ylfW3C#P9~;j1jRIi+ z9^$DuaY!Q#N8#VnqKpBi;0*r~Zxd!SL2X%-zv28)kRXtp$x9YtVZnd71U!!T%Oz)C zz|<8hK1fLr)NLK#JT%TBEXpZ5JUlD{YUTu&?1DmqqMZ0^qWPh|e0yNMHE%874yzXq zS1m`yxj}s!VV8p4H!LC|Iy{Q&$M+8gQRTuj=JP`SOg9K=X2Pxm`_wZ<#ZNONG)mJ~ za~V`5ab_C#t%1qG;wgoTDh@WaC}5FHg19EmX-75548CQxy6fe!q#5XXjH zc@l%$hlzJom>0%&u0Jm*7&sml%ALArtO2N9!oFal*r+hz%v8b9@TWu{o$3379@rcI2mAn4!H?36;YEhb)=`H;5a^9y zJ};85=@T6k?1wQ7ja4@^($d!E>CfhA`}2%+eDpNKqN6mSokhfXg@=Kf!uV)Nq~g{> z`C@1tU^j$)s(l;5Im%Cz$G*I9o^Mc895-w&Kf*saEC#N1LS7mir`p?}Z6bmf;|1*k zO85$CWFQSHDrC zqj=o8rd+M5TLH1=V*(c%9ir(M6z2uE%$wlY6`-d8bkLSA2>TRvn8Z+J9252?*xLl4 z_k;*DQ35V0O%7C{HiBz-CydL}0|s6R`@C@)q{88nKUSu@n}?_m5aA0!S->I&BEPEM z?}K6Jhy2p6ufy}+5Ee^<@rXvvK<4h$5Zu#E7&T;xYxYJ>dq32ApYnuwt*iR>1*yc@ zC4?FBZyfNj7-TD?P7^>jXzX_1bpKMsfk&O2G+FdU$7id{8M--r95_bjeALZi|{t4&kbDP2lJT$De zOr~E07}c&*@oNFQ{)SWklAaIvcY2Eeqw>%_pJPgB<-ES~>(&@R+|9IzQ~ecSc+zpy3d3{eHQwuf(3G}%)DFXSx9@XiT0OmoC){OI}eaWo3U zXOqUu%c^Q?g~m2uIMa4RFv-hj)AX@43_EFE(e)m`&^&J}3&YE1)NRX3ci6RJEDysM z6I8;LVurh;#tJcfp0I4?dbqt)x^kE3Ii7;Y^n%h?fbb?(|&4Te{< z#Or?yQnqxDUBK{ZZEgOt>D9M~$F5{kUv(N|sy7oZ(pznu+I4dlDSyfY(A^%zRMNE(3aF6*FoNUUC)J~i;7Qa_^ zjm5C6=e5r`4-7BwlP*yUvfnO>_+j|%)^LvB0Ma!R3C6H(%*VZZ4uu@FK%y|5zB;~U zeXQVT4`c&|Vg|EEWd z>dqTTA%Y|t!=-k1YtO&Kf5kAT(CqZb?21e> zVFbf!X|5&IkMeEs^CQqdU{h;vd-LirwO|gsE&#>*L<)<46;S(@5ttM4U+4Hd;wo2$ z6J#))-dehDbj?{qJ3Yf={%+Mo^>|KO{UVPFdx*JWn7GN)xS^5bS=i_Y-h9Dj1SQ z8xfqDg*Yy0*KQo{#SjU1bMKs~vSvfq`+M2JfYGw2;$H$rd+90s5wPuV_|_!;kGz=l zB;JcsacJL-N*7w!zuzC+p1YPObag&6wPxa`(qEiR4<4KPEC-%LP^iDwzs;)wo`A9n zYLtZVV&NR}P0>h%dZIm=hK8{(>Pq=>y%&e07LBoSbZATjFI-v08#Q=&TyKZ4P`>ws z12Vx+$cEb`Hem@(9m9E{LB6Yj3@?g{#-jn{y(#RN8_EUCksIdE^$Cj(^@}v-Mg{V@ zP$h0qB$w+0Q7E2B2v=~pA3z@tI7x9T=6b^EAGYpXI5#!@^NziP`TkK{ZbVQ(V3hZy zMJB`r>i$v2T%n2U#$0%AK#K}_SHe*Oj?r*LpY6ZqM6*ohg}A9ayC?IOP3D=(i_%5! z6Acd_4%mtX5%^DpeID#)u$#lqMZ@yg0VYJU?HuwFI6Vni+2SR9T; zrQ)O^ZxHNgowfd9__xJP$fG|mIFb+UH9u{#%tT<$3@CF7X8`5^j^Lq=cVrYV0=1c8 zh=Duxa3^B?!y>qm!9l)!^m)TGMxD_nph<_kU2s(Vg9bm;Z<2xtZQ|KX#ZTm)ppKOk ziAokGDUb;RI5o(F`fV0)L}eNI9}YP4gNLD0o{j&H0cHpOCBV0*iO`=vN3&j(bl1?( z@brN}oKUWbDGdDhah1^%6>dx*G!~RAsHf?T=h2~b`KnMYCYB#Neo^l^ zebeUb{X|hQ8eK(O*LeN{8(W9PeteJ9XQX8WC_=(a34J5uvZI}k1PyYxl?e#Vnd={v zvz->^RX^}?h0oWKaVO`!1sa+&m6z=+Dn4|$w5+P`!Zot62us#@-uxvcrMGSu2}p4i zr_Y_&_x|Jf^+qCh+VmO9vki@{ZS5SLmboln;jz-&m+!wiGB!SO%i;3l)tB3j9S;q= zv}2{>dJ=)CPVgrnni@$dvIK2$8KRP)9BCHGiYTUDm@MU(qxzDm$kgMmvq*ClkZ^SZhlo!$lcyPzkmNSil-Eh$RqTjDM0_gE%FZ;Y z!Gw$umyx7(@yTLpL_ZN1p`>GmvZ884K>@rNIcaZdCy_=FCBzWD$Rb2UT!d%{H>XTd zbVzmZ~bfGjd4k3_8WHMfWOc4;I3CRdciEu=zVj|*1DuGUC2(l3g zq9h_k;0Q<~GI)75moSTBmpoRndR@+%8|eK;LfjWLk2iSW*Q^UNBjjAVoolnnYcq009vo3($ld z#pwde1PDYKAwdF#AOLTKArM4}B!Vy*5o3`Y1f&IA1@L4E5l5mWK^^oHjVvx=Oq8AO z#SI~@nx51|N~tDr$SI=)HvzVwq#!DBtH?rR4%tm$7HNTy8WDtppe?LMTTTl#Px} z;s`igDv^uZM)JmaGKz3?39h^d*IWLhTG1?3Ev{Nv$#XS)nYX&!FK-Rp7*~J)xVQc& z@kpki*AcU7ijM3~w3M3K2a}Ye<2>MPU9>f=s z!xLbZ4loH4AWS?PdR9mcv?+)HK?pAg>xK}R2^2(>fjm$pkU+2n@WL2Xv`XkCGm2&K zMo_=0Qe+Wpgoprfgn}$V@B$(f9|SKbEMS9|26_l$AcjDRBw<8J5b-A>WT+Hg3Qr_Z ziSW`_JQ)#3fcFGhyc}$1ctn7L;DrSdXjDivUJ+SKAmRlPGT|{W0ctCNQp8ipLU=?= zUYn=|n1m<`ioov@ApyYuw85AjD4T2Wj8y-Isu>{WJ;U%FIL=%|}RPlHc+_x&8f()Q+ zLqJ%mU=jnB9%Ls82Mbq7L}l>rj{tof-jT3CSev*W(HB>N`xGK*15E+Ml%PaHD05+2 zTvre}6~v2xvJ#kr>_Z3?7RFfwVIyJ!1kzOsdNUFzSK(Qq=@9%os2drM()eW*^vWuf z9}vqYz(XbB1QGlYJb91^Bp1pf!n#LgGWMj%c!CBn947z_L6)+iDnMl&843WNgS4SV zAvk!tx+D^MhLFW^plNW(JmM0#j?=(PfRRohk|-3sfE+Oorr(J=6hsVRlMr#BO2a6U z{E#BZV@d=L2!sgWypzxgAPA`aiQ2jrD1b19E#Vx!7Are3XJ`fI^I=zqT?=-YJ@JhQ z!n|AXhQVO<SC-xT*Ok7>EuFffSLE=m@#$VE> z0Y+Q!R6L4*rM!)9A-&9R^uz$8mdq4AalpUxw|$Zxl}5*bB~Jz~l_)|G#fyPzoXV32c_e|x z-=8t+7lQ?3Z@%2idf9>nUQTZI7K`niymbD^yX!CI?E@_TTYdTf|NeaTf)YVTC8x?p zG3uR9;YNu3U0xml7K3;+p!x@H2TTzhwn70d2OSAZgnb;{A;CB#7>5Mou!Lb85{yHF zaY!%@38u;z1DdD?MLQU@mnA{RBLID=AZnYyCJdVhY@(B!81yCRCO)}Qk%^6l{Od+X z{&iy@|GF`ef8AKff8N;8+y19b0(#*8v`HfWd6Po^*~UTs*(QzrvrPv1Cz~uXB@bj6 zdXk2pXSYBv_kso{1{VH0-1aZSYySpdBX0lTAL)NK{LwmufEyanI$&`YgUMvE*bpQj zNKOW+$-wzbkp4@M`7c5Czl5o$Lx=jHW7J@oQ3N3fO$On=1d;y|ME_yHfdNk>qJtu2 zvH%3iWI*eLhN*f@9sg22;r~)Sk^fdbv40rE|6u@!;(s*2Plf(R11f>2Vwlnke^mkh zt?B>M=8ekT|71XA{{Q7LrH^DlAEE06r|>ka_xKY#P3q6TTT^}-;k*c5NTh~;aCBs# zR|G$V2gW;0yqLoJu0QDcLw?i(`g{HmUX*X37dpKObFcO`4qo==&X(3*cJm!HVCEtW zW~!&^)DARIE9-wn!w**TqC~WRyN{0lojknA$)#*Cdt|r5%gV;m*~`gh#r)ssbWYNl zs>?gT=p54T7_HkB=7P2MyM-+F7i6800srnn#6x>=}jze1z)dUor(*D^3XYQ1=y#s37n&rHUh6icmkHrT~+ShJ^vD+{ZL!kk`h_)UhXt3kBnKXAKT~(H<5Bkp>9@wE2X#Mudgis$FYZO+DC>Mv zdjGeghX!?`Qwb#xmjY~J-?!Ga+%iJy_yx8}e0 z>MN?Sym!|#g6_Fdbe)N*{}J8Fd#4vSr^=q#-;w8=FSo>Vn#FdJ;zEfli{hT9bSHGh zFD?)hs(DEdtaSIeyW3B7*Q=EhRIZVn(_l|>>GRVAR!dIO%}%LH9-XoH&QkmB%U6|V zm+fhAWtbmMs6Mmaab|gEeAiIhXE|L*{$;P`lBMSvEyu2zHAv=`yxn8>;GI~rxXEYz zy>}Q%LbR(@PH`v9rMw+4eXEuaphV#_pQv96Sa_;rY~>26r9~=wVbw~y8xJ(R8(U<} zd@MDP``Jx4cYk8wW`4U7)k~$iM)vA2yL_F}vWL^YNu8{7^E{!q%e2y6zAom?HKwx9 zi};xjx;M)%luy& zO`My@k7Z|WvDinT%@^E$qU8INleWFF<@)lKhpU)BP8T&Vnhyc;51&b{zn zrlxeqioTQGaGN0KI;3~!JR{_#v1~;JB2191RGrnSxpvE99|@Pux?%X|7tSwZe7P1X zQcK@l_Tfa^tsC1v%AI{+yKX>Mpw2bhOz8gO&x>|Y!+w2|AUAcd!96V7Fcc=V` z%wOucD*qAH$93yD!eib$jn9rdsr5sg{`rS*v|2k{Snl5Sw1mHn#a*2m{OJA~->RM$ z67q89wA-`;RmvM>4p#?)nd>(_EUSgC7eExqIB_(YZ;8(*2krtQDj zbk~1q*1ogU6FM!kU8NKKLd=(5yAba!`ZfEYM>p%%=`8_|gGO(@Hoe;E5T8iv-VzgW zU0k!GLz9#Zt5Kzs9&UFhMt;ty%vv>9ww}-Sx3aKlQy#kSV!CX%?f6^Tp(28Xy?Mqt z*PZ9TP<1L~-n_;KcHB1N#l?&?n7MysJ1@m^17@CTz#G4@4gaKzK1zh`OArSxq|mzP*s zv_3+Ao0rgx-Q@QN45YeyrFOahFq3+iwtzI4sK&H=yRhPTBkl#&fOI!&&ot`7r}O69 z9S*qDr}VI9Va`1%@`+pf+TSeglt1(I>Vb$drr}JXMe_$9$4RLr1W7e)q`Gw;r5$|f zrINEiOFTz^4QCzWd}*@Gsk$SpzjSu4oXOHWy7T?sm&!g%Hs@45U-X_T5&iM`cSEs# z{bF{<_f|5b<5b-^JJdU?og&(&_qZHoX)If4Eq*C(>ysk z>nGE>#m_=#b~Pd~=*6n7Jq?XBwII8}4XJc{{WSO5EczjWk(UJV+nvgWLnE9AblKEG zb-T)-l@S*UWNunpT1XVW+|s_;S#oH!hfNU(Dxf!#DlUpUZynHF{Yj)V>bfkvIcO|m zZO5f7#Vg?pRPRazo}#?#)fVsMzDDKETh*t@ zdGUHJaVCEAD-YGDIvMg`n0@bN?MuH?bf$WsTDd5(f!f*K%?qt$u1waaa;OSyAi*T)_2 zx(kMkx)%xf=p7mPR?~x2KBK#R@r&y$%$Sq+X_J9ILtE~}Q0k2Zwjm==R*)OFS5|$qCEMT1nY}92G8vP#J`=a%Wm?&JN!*f+ zmz%X0-M@b0Sjs-9X2$KBL$@v7(3T38NZf5oFOxrE0b@Hk4~Y`2;5ekUnMQQH=z zhV`A~Efw&q*MeCpL=h}ddG^EL)Vif3se06ory^zBEVQ!S&Z?f-Vf0aou}Fw3hTl5( z#qhun3tl}_+=P-ex4@L&cVqd_0?r<4ubL>%??+SU5s~cN;oNM_+i8dQycxcqdFId> zGhWwR)_EDn7j=0@O@>px?%}Y;sk3;e3a-^f-^jkpz3O>hTY57`g}rL$wR^&1eSs%@ zuCR*N*U>U!+8&3^mESZoC4g42-BZS_K1uDh@9w$t+B&6eqI+#--|0Bagl}W z2cC|hIqf+&OIMPX+zP)(b80^Ia*?Z))uYi+hpN6*nWM(vQUv1`5?UUZ=h0P%1;jS#u6wn2=stB3XSS?jo{?~HuEvrcRm(04%R{2}=#7UL zN;12Wm$kmQPpzhJ5`Uzqv8Fq>aM>%oQ}S!sU=ilx%oTQB>}RsAWqaRAY&mrF$iel8 zj>RUNIKu5wWE>?OJ-Ev2Ui{4b+Jk%b_scoo<)&mLKF*h~{O-Woo*)@hnskTkzah?U zT4U$asNIZD!bq=BxOz_2>O(VkIj@jk(T;OG=%yrc>0_;YER`k2n!oo^GE+8u?DO}N z!;h%Um{seU1(p(*$J`s0HP(w#KU;49aE zf3u`7^({N|)drqLavoBbhTryI>R7ON;ESqDe zcXYO;$OTVR@!FYgLW{26BL9`Mf3(uDY;1s%C;i>+;YpR_C-N`u*tmPT0F|w_K4z$H zdUj?0ll=_2;1U|+`f|ltg7NF>)yK{@$C=8kT~+1qv1pWc%eBS3I4_@lec+<78)vLf zkRjM-|CCNQJ~1-i-P5<^Nt#&a@n3Icnzm}KK5i?MUfnjA@x@0`;z7rZ3!g(@w$12j ziEijmrfTZ25kHVIZhTKH zkP)I1XSPnwTXJ{T?ro{PBJxTKd9#~bPAy>cFvRp_J0wrrTdEYr<`_Pyd2)E0Z8u}f z@zrJiGZR;MEAWa_%3I_Q)_*+rE?tLZUt?cWa6U}RVNVus^WE%n)7-<~dVJiNA}`uG z#igACHy@pzwK!!Vb$)@;%7TcQ3AArpx=#>9I7Ay+Nt>bcG0`7$q}+qrnQcY3j0c|k zTMiFvGB<3hpxPs}rB2Ph-NqiT&kYM#a@GWNMTr;7NNHBf{rVZ^!n(55TJ=#rSTE<0OxydHS+ZK)k4hG$9Q2&`hTSzS)%1j0;){0&?>L;$ai)71 z8EV!xv;@Z8jqi0xbCP|j?p^Px*Y1>8>gO0iSSulu`I>)1HTLM*_oqv;Td&c7T6Kig zmd_CX#K`Q3KPe#Rv#PsZRdMOOnU-Ode#&G?hm6EM84i#6@`ax_J8}&fwxYL`qinB- z@prrxubF*WZq8xBP2Y-FxuuCKMt*D9Bf0CeK=6;gfhzq+HACCiR;}s^X8uYEw;zq_v4=qG9TsM^MaQjo6@Vhrpovem0DD6pLJa8L949fWV&SX~%s?xQaG zfNR>+B|*17dcVWJ6{fz%g4edUY0F0KG73XcQS!LMszCc~UFD{W z&7$P`YvMl7&2T#0bu7bg9?ny8lP6DkJow3*W6MJvZ|#X^9Nrly%0K3Pw0EmDXPt+p ze5%z4(=(S&)upZ}I>xL1EUCY5i?*81Hx2zOuP^nv4KW6JF)0k$i@lo*I`Zdz8<1b& zpr&^;{$^_N@$PP&ADL3DBQD>$W5tDipHJE~42LnxW$DAGw54s&Dd&V2&Xs|z)Ei5xHr)zCpRJ3TLnVRHJT5O~Ct6Zh;AI@7J3V7~z zRYslUe#T5zEwlN4t-Sp)yo3^QgV*hNm#_!ovtEa12GYqPjk~Rz2NOP=q?tOpGcUD|x1*{9Vh|)3)JiyY8+QyT))mTux`Om1vxWgZ*@gBKht&17c6CQoESJ zUz{wu?WB?v>N_{=!}nNDQ?U$8G-b|C)>$_iALyVj17@_2HudZs$K0LGX0{g_&n;3} z(l6C0Tv`(IU^Yej-i@$LUFVpq1>egxu2m`8Dt5pA-myGt_v1>xL#`G$8;P^yvI(0w zQZJ{yt?fo`7ty`N>yJ#m}HzmMnn`5qc)pE|MGdhrICB~J?M9cA9= zbSq~+AU|qmi^bcliG0b;Imzu@OJdHUZ_p4=NhcK~yuI!AX^zt@*_)S+Ao~rDw+wVF z{Hpn6nMCpKG~GqR%LVHzgRf?>E9f}$?oC&4!Uz>-{t-K2!dNSIB84;0&VVSqnLN&X zKX#VEz?mPz&Q`$>s4t>t1oW5K=C`raHaf&V#ZJ>YByi>*uv7kRS)BO@dP?Ma6+wYn zo6+GGO=GpEv*Z&2uX{qwo@Wcs8BkKbe!8M)1! zOkdQzRH>Sh@o}sC1U=%VQ|wSuZS7gc1WvE=E^J<)__73D34_C#z3z-9Z0LMj(1y-U zz$kjordcWWHzIfz6X|;*gHHEw<64L5So)-{HiDmYkgMfc2`qhXhEIyziDI_u{X6LI zlW;YvzZCstZ419{O2Ou`c|+$a>JNKd{iNcHu0$#FFmi7B{#uG-@Y54b-*jrB%B5QG zqUYCQhtSnMbwztlslSYElKj?w5?241MG?(%N474CZ*^<@2&;u0w~-uc*00&`VL5na zHKad0MD{E05^bFA{Qd}82A8zaE$!LivL^SUno~2=XgF?v*mU-tdvy^_#YNh$SUBaL zmSY9Un}TwTZ&DIgO5@x@=Gh}Bz83HCOSmp3O*1=gol|7~bdAvE(Wh(g@mSQyoKuwT z&SIik@jICd>o{+l2E*n*{;Euw8~QXjxQ43q>wOEGt}}bf?Pg}irWec)M*RteyT3M`Kz&^vvm936?6TV+nWaNGY=%p4si9} z_sKq9>h!v|l&%FUapoasiJ`?$R7GuhWodXx9zNzbDJko6bl?21iF%NpH)05Pk3vyY9%-oE!JKY?1-9$M)wX?90sLQne(QyWG1@rP~o2c`WfIrcNE z8bgM5*QfWKnNLf)`T2R+a>HhwM9rCxr{}Y{@tTh^LpQD;U-@fr$;T0n?y-vj5+vO! zFX?^yJ6O-CSEQ=UJ;}+t4ScMKKlZX%I^L4ozb-6JxV~-~=``ZbkvCcV_{S}!u=Byo zuF_t_QfCTJqZ{A`Y~S8c)pe1|XAa)3Xtzo_`m{Gr?@C4Y1*y~Cv&gdsHw$S7Us>KF z%%=7lp1z?MtajV@#wU$S+0sne8;6W6H^n3g8Xc<(iBOd4Ju5@Nc8l|0Yj9GT8@{5J;~5rZ}*5_M4vPfc@Bu0ND1nG^gZXJ7vksiI2W=VMvNd3v8&|ksR7Mc_<>4IN52TyPn7O-2W_YTaU z&{BTR7Je3ZwBzw;UgJV7Th_E;TsG_Kj@wI3jnBGWDCC?jxE%YzZNp_JeF(e6QG8TNT=IS2-LT>251A(#hE(QK8FGER z#+De9Hv^utBWvuLyI*<#JacJh=Al)3xLgIE)R^4x55C6wP`m3ExUWIyawlI(kuy+?Z*^_&t9oX-KXwO^68S{OIHn%8N-edDI?1*<0ZC=Ki&dJ<>^n4V?(8hmq0vL$AB_B! z%e}q+vH5xHM3zfNX{MubZQ$hN<4lis6!w37dG(m5dZj;A*F0;7=(V1APog-r zhOSA>zmEkI= zeklClv-%e+F7(EwgiBT}`q3h?s9kgKns=U(hqC1CG}n*!4ZQNi%{*oN@s$Ka{+HW$ z@hY$A%Y$BmtGk~{^u=i<+Ri_-eenI6miev^Wwq=QavU;Gy0gD7J&@^ggl^L9Ia^WV zz-Q?bGdF2eI!Fk+>Rm1$UhvUp-zQ~bS4G(X7lpGz9$61P@%e6PYxU?zrHa33{PL?sV{@d8cXnG$4=wZH;@J3k7xkKG!zZq>+}qVNecL^9 z)GUqJG0*fKe_AvgG9s*d>ihjBX`u>4C8qWKwfIfAwsmQqwA(C;FV0ur>Ukc;rR{lD z!!j$NYpF)@Q)}+yUX9BsN>hr3SMCjzzvr07{pjU1M77^p zqITfkt5GwHq^7tS2VEZ zc-ZHdQ(8-z^QAVe+iktdp#VOQ{QQEy+b;P$JFgHt%{-u1PelLu_lt}=`7;#DWwa}b zmN}N4(N&UVeZ4n#lrLNJjuGkXaKBToncBLtaLdCPk?(JOrKT-p{CbeG*7C!a!JNKf zZ5Cfj(s;`>=e)A2_mRBVgcXNCst%Ajp-umhpW#IboghOjE8rGT683ZT>HI zeT^tf>UNKITe=nLReYVy_gv*F&CBZY2k+ZXJD&ZOrdJ!Wdydv{$(9V+_bX1WVW;@y zB-m~^<5=}psq~Yan9R0)QaQ(}B9>OtV<@ioGicw4&viI%;sLH5c{kN4?^v_RyX(C* z@0+QM8jl*>vt@GI7v; zR&86gF5n8MdGp|{@>gxowj6V)tbBf!I;T=Ci6x)DOMQh;M~7q|)3(Fld6)l;k+Xxw zw?(_Tob1<%x2$oy_eHi_6$NHGQ+0}FjjzCsrBOv(nk`17->{U)q9DTM*PB3&y*n-8Oo$FQo!dAB68JW$~g(Y8ZzQfTo71mF{8NcL6F8rkUd+NM?K+&^u&d&6*VT`v9J>z;MjOHZmlxjx)z_C{A?V^P7Q(@W%y zoD9zX$eo6lb+$sR={rsl@)RQrj9nDyK|7JI_Lw4~(YwVK%@s%4SAQnw-rCtTU*Pr2 z?dRFfGIb_Px(;~m%e)qLXO3QW4Xr=(RhvL<+Ob;UhAj;z6xc5ZU1 zS1xV8Y_@c0mFwX2Jpm)#OKoYzsgN57S0G9| zDCBPRJWwl=Gb{eWKwb~ESVgyASKwu9#M;8us>aeRAzgDteDmW}@w`-WEyahk@p9m@ zV$YzHn@#9SYv#vOO9vau1#1cNl`ZU$+q(*xcPk@Vb_cJ`$WQQSO1f}WieMMGHSCpf ztX!*6eA_Y(mEhG7-m>3@{n{d<(CQtRdF|V0y9Dx+f&)(FS6MSuq}JAGK9{B3PubL; zr&|yfDc{^CTS>)wcEDQ zy3=y6WLYLP_IqmI_jIg#rmMqP$>%ImC?4-AeY{nQ-7qF`dhGeWpw0DWV{EUy?Jv=51~cmAH6TT}fMHPx!%d?Vs<~+sdxp zQFwZPsn^CMLS7ppw>r?B&szFz-1fcC;r3ehHmx3ZP>J(sU-VG%!6fGh;>Eo(NWb$~ zb&$~!ep}QH#naDec*VD-ve!0=B@&m$RlHozPAT`>s#L3TZ-07;>n@p((%asy?9`{N zuuNq%eS5klnx=nbZFbsDEvBF3x~OG$?y*uGm?vM=nMlR&-+lLO@!uuh%uj ze8(GfPKeN4p2|lOBF>z@JEuM}@#$h#<%7P(X%0tJ8-%4!+ zc42-Ab%tEXO4sR2?ykB_iE$WfWmfYk`clOeMO#S zZL<+~W8dpjtzual5 zF8dctO`oB2v5@J*NQinr?S1+ouVcp(-e28gEV*x6pISgb*7rT``}=gwY~=9gsT+ze zpD{KYZlEeOeV|``!z^g3=G)|+`Qof%d{kn#QrDHAGWG6JN3_aMW$chGa#21f6XAL; zFkq!^xZVc3b#O%AA(&6HK)!k0vXGWoA+Y#`-ND_(JCbKdB{F(scKh8G3FnVnjc65> zY1)a>FI^;2Z_SSgA?1%Ln$a^>x_wue|C%Gbc~9KK6Q+WlUHQg(?^>x2@5Vc9 z<+ciE-!HDlpmh`Y5vw(sohqbhac->%jc1YPcJh3bwYSG_2#>e?47L( z&m3M!Ubp{FI`iV|3XgSHrC!;8+a$TbqEt#h{9T+HN9*|DuygC`%@nGoo&WAL+>x$e zL)EK|6+%qs?!<&w%I9erjb9_rx`;}tcE~@I`=Y{D{qZT+ztT~D-Rt|dUktkJIdk$k zmsf9RNIlPx9lY-N;<=!yLNz%?QeN_%QtNCf`?=c#c zVRV*vY_NMlDLhgTjmt3UmB`*HT6jQ8n2>|tfh%A1R5mU?;|gK$HE+)gcQf0?<@Aoi zw;7Bnxedz2Eo#F(xsTfcK8 z0Ds}7a_+R7ubS9Vy&C6#A%qH<*-@1y^6O=q9Y&o^U%Ytx=|I!F^e?w)>kpcgXSwa= z58SBp5707Wb6Q#+eEf(Te-jsEmTb}{J-BkbcW9^gJe~LEO>NVCXqEc&Rz{vTY@Hjh z^rzMFI+iQ3RnPG)IaLhlGi1A~NT2p8R)|>IOFQbF`|?BQFxBt8V7XX;r_24=C=Zpz zhW}q}=K>qqRo?OIwGGQAAuf4{1PGJ&EV~|i#^dpL2~ozL@$Pi(8OJl;N1E(#~*$Ce-?lD%H?-`wSE5i zb1!p$dSLczKe=zfdDBI652lVT{`Ap&weXX_x%8H=-@821_njZTe(?66yzi!OU-7xS zuUz=(HDCE;{r=-OKlA3{fgcTBeeU*sFRHz@_t`%<@qwEzeg1WO|9Irg1utCdtHo1a zI()?sp8WXhK2v)9&|Uri^yvA2e9tSNT$!CY_S_o>J4^Q;X}?Pshg-!-;qzkr+uN z6QhY#Vl0tPjE@YD#77b%BO}R?(UH{1*hm@<+2Le7nMjT#lgZI!Dmj)+C&x#joE=S! zj*KQpM@LhmW25QO@ziiCo=T)fQpwb4DwP^brBmZ$!(;KW1iZ9jpiPa9jitxN)5Ga_ zI*}epC)1V4AJBt;aJJ z<7{`<;X(EtBqd&8-xV7+G@e?ncp+eAn<42s%u!1KD|W?iWOa==L>yc(Y@SWG-D%dv zV&$*u;v?$$P7etraV>G>CEdD{&5MM;IbB&S{pY2D}gZ3B+-*9&WnmeJ*kQ*i#k zI~(Cs^p=XT5~=Yq_zu_W5s!&>hg;&(CG1Xj47II&xX8kRh3@%&sK2PsJGNhXefJW} zSZOwvjd)9J*DP4#V%A<$lh#FzE<$Z>{w;5iA&s&gd|n7cZ|ya?r3Q4fjhZ1Z@)}z7 z_KsMv26c!}?TsvpQfymcc~5@Y-S7xr7hMX_yJpk!IhBKFkYCsxj88v{&@jEW(|{Mr zbc0AZwq;fXz_AoP1+@|+Y$~yRLxXU!?X5wW#s}^d(JMz<9Tq*G60I?Dt-zJ#6$m`1 zt2NzcsX40!0+;Pgt!~3JEKra#bpx|_y+fN|ae7TW%Wy(3vEa+aJ zgP));^xV!K!Jp)$({yPLRHZ1vR@;m}Cp?0xU$l<|^sq~Atv1wvM%r6;zCgU{d!jI$ zAn`{@{9&!FHNCvR(Xd-Bw-#iG%&r7$I{u2ljt;Y&-wNhHDRjNe z`8+rRECq?B1aGfEQ(g5}i~AJOG@nyu9X$0-Q-=Qn{!*8T6-%^AHzDezm2Ak zIuYTr=)X)aS$CUjs~)cb-FMZk(!1)ASgS2Hsrc5GD-go1G+2QWVxF(N?X!*M>F}jc zBo#Hs8QU51kE481@^UN+Jy%kf#0f0~su>#OT(Qp$d%rXt*+ z*cbp?4IaW9uFp0)wQ6J$7WLB}!C4iKhNX6+xdF+8m>PUnaAFF=kVPuO!A8Bd0rPXs zZEa8)YwE%+LRU_?ku|rv+F1+TV+qT;Jiw`DqV4v+s2e*Ki?lY@=ysLn8QmI#K!8o{DUx)M1EHZMDopwYL8%}$bv^6%KZEAcG!%V#qluZRHjSKMyoYg zb(&paH@-*oGhHCK_r!n|QFBb`iFGexPpq|%X4eid|4MX|vR(S-= z8^3+TQ;vLcIS4PPR*#IwbmmT71RkdvO{l!99!W>Bb(-2a4Co3FP0+*W={k)>8!gXI zWUpDAP8V~&4VkyJ6*~%U?cb}G^1s`+y(&4of+9ufEib!G!e77S5mM-NAsh)#7Waif zuQTly0TsGmlX1SSAz!0C!{q;`dKkt((PARq+-6PNJV<~+jZe$2swq3p3h8KUvgsvJ zv-H|_xl^kxRqE;$zOXps($*_WhQ- z&7<3Elw-&{sjEr^dicc#yevVyu{~b>GZN4+z0QwBgn>zvgBPBmL_kU2M$zf zXDb`6lV(Vd?%f|TCn`Vb4hpN^?&SeTixcI@C4DC4g5>5;J3XS%Zbimm_x@C=Un+$~8oG07D7Z7cS5N-3_ zJyi;xWD8yC`pg-vBJ!cCyOkbFtThGQI_#!Sk8#BqY_*b z8ex+^}i4wP!HL|2d*cpV!H1e|LXG#Io3p2jxm_~9a3FJn;D=p@`(qcZCnB^MFJV|kR z#)ycb_}b>f!hr_D&-RKFoNpZ&G~%sSYT6KEb*Q0n4g%s8kEnJM|0$r6*C>%qGv1)@ zLQ=Zwk=k0n8T?6m(@&#=a1x*vR~u_5*>l5&{kb_&BqTB3o9wEFo<o1~rmN(yK^P<*#s4%~ z@Ux(2VZgZrTnk3P6gUQ4&;oaXw}bbBN5JR7*TA>H55X%d1I|&f4t@{Z2kr;&2Y&^= z2)+fL13hQ~mw{J+G?)TMKn=VJ+zlQ89|3;{^gMbFm8Hr?(1$>B+w#zTJ(J2s&u5Ri zZ|gwiq35^dt$0t6WcfoSx)DDIvMI7WZ%3MYE`%U{{^NZpf`U!udQY^?j+ei>Q!ZM`0y zkaeHHt+r|15d@OT)VeRmt&iTq?(0HscHi6N{|Iif8~8tgTXrvPMpUUuS8cDXd7BWO zu2koytbg^>F(+(&S4Cr1#;H;f3V((tL@aDy@T@C6F;(8fJT$5!9o9=5T(j2;uY z8d?ek^_^bmZr;V1DJ%_b+FcC|{QXV^DW!;qR z?)3qqfwGsa=|{h!v;ywDHt9*;4uWfeb(eZPJ=eNbZ)%q!O_S2GhK9EOhC z>G))(FqxaqWhW2ir?ZpALLoDOf83nT<)(Z0I6E_ypU%xp=L?6)n+QN=h%kSsI9tffMUZa+Ad)xw*`QQYe>-imgn^^3VNa`J(PlO&5=nv)r6YX*M@EGn=2ynKaB_ zP!+~iK8}w};b3mAI8`diXK8No5al|ZE9Yl&+2VY;e5^cCI)2PNN_jFfYf^9crh zW>xlCj73VGJxC@u8EAP&z~mKE?vmy*nyGHYtfIQv#u4jIL74x_z!uZ&plkgKoOPz= z6+2@Ht}a*BR+*>kW?&bISrr>YKoDZTY*Af{O2`rx>rPq%!x@&hb*#|JM{ELP zzQh35eMD<}ig-_Cs)Q`HP-eYpzcnBFjI7nzOa+Y})5_|SObx!_$(J2y-564}CBZWG zB3U_=lG{8(<~8H+EsEy({fNEm#%87IO-yq{amI!$pfO0wQNNd0nJPmK1cgmCmS+!@ zQz+)fGIdh={^<~h4D81FMwr|h8Jg0M5Rz@8WLC!2;*m$`1Z zR(&(DD6;F`tUB-=F32EJHAr&JD?eF@J#(y@muF;)cvkbE@#_wqafK!<+hpK0&6UKB7GkIX)pCbzLX$}4@J{Wui8S}Yki zZ|j1gDX1K;2u%LH5ixc|dzyAzn4W`oZ?eEsyfgz@!)-BB(Txr*@!3&peJ{dy7?+$EhwH;U{ z`QV}bfic`Q0{ecf?4Hxclg5_Z+K3q&N!Z*@>utO2&cdclkxBN>T;z>mdzF5|mMs{c z*$ReXD2A=O-tgubsF+62(>&77E2Z~o4x)STdzqu=lDcl^kMgg3a`yt=vrsr)w{dGO zYtK>ZOgQ_Y{|ObH!!-Dm`6S-gR!%mp@%o zczewr?JODlb z9s(Z)p8%f*{{S8bUj|SPJ${}1?%7pcmuc_+z0*yyazl0J_J4v{u+D&dr(FC=W`*+Rc=&>%kt%?G|tptb%jk9`IrCB={k?sLdNA5C>Us6x<0Q zhjQ)#4}s5v=Rr>g|KJJ`1vh~raKY=qz2JS|!{F24aquh{ID>5rD2_PCNl*f>1#bp_ z4ju(x0N(<=XFb1{bG#8ufjdA4+ztK$JPE!F`p&VDFUWyaa2`Ai{vJF5o&nE+3pQxa z;ML$(Z~`blb&lu3+rWch$SHa1wzH#Z&4Z`SD(9v}b#?HN;_2)# zD^rL~(<|f2pQdpv6oEMUXuBDz?|vRZxDMM6cTS)HEmib0&$-w&_ea z+8vd??rn`JfnvY;a-b2LjC5VLiSIO-H(khoqM$EqPGifo%CYXy#A$P9^LwU0(F))5 zr(vpxoo7-`DVTNyQx1Dgx$0i*j+XXooY0gu)F{GN%_~W-)q9`6IOa8C@Lh^CgJQk9 z!iP2f=1Nre&s21s%wunP%(>v5Z0S>Q{-x*N`~H?~&F<;{&aTV*zVE!FmyLV+KhpEt zJ;mOkzN?(C^grOdr1z@c$9n#?cenFUztdCQ^}QaqKe_8}w)GqAo$mWa|9f{m-}mzV z-Tfcib-4esUFV#+T{rc9ulJkIXM11Y_XX!ey&L`4?W*^z^o#2mG6ryAeHZf) hh$QLYsaj)^4MO)_u-K`c9`pp(zTGW$;2LBa^M7#Oh0y>2 literal 0 HcmV?d00001 From 76ff526390b7aa14aa8e6c6dc3be9b3ca3796965 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:04:26 +0000 Subject: [PATCH 23/49] Compiled knapsack/quadkp_improved --- tig-algorithms/src/knapsack/mod.rs | 3 +- .../quadkp_improved/benchmarker_outbound.rs | 188 ++++++++++++++++++ .../knapsack/quadkp_improved/commercial.rs | 188 ++++++++++++++++++ .../src/knapsack/quadkp_improved/inbound.rs | 188 ++++++++++++++++++ .../quadkp_improved/innovator_outbound.rs | 188 ++++++++++++++++++ .../src/knapsack/quadkp_improved/mod.rs | 4 + .../src/knapsack/quadkp_improved/open_data.rs | 188 ++++++++++++++++++ tig-algorithms/src/knapsack/template.rs | 26 +-- .../wasm/knapsack/quadkp_improved.wasm | Bin 0 -> 167148 bytes 9 files changed, 948 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/knapsack/quadkp_improved/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/knapsack/quadkp_improved/commercial.rs create mode 100644 tig-algorithms/src/knapsack/quadkp_improved/inbound.rs create mode 100644 tig-algorithms/src/knapsack/quadkp_improved/innovator_outbound.rs create mode 100644 tig-algorithms/src/knapsack/quadkp_improved/mod.rs create mode 100644 tig-algorithms/src/knapsack/quadkp_improved/open_data.rs create mode 100644 tig-algorithms/wasm/knapsack/quadkp_improved.wasm diff --git a/tig-algorithms/src/knapsack/mod.rs b/tig-algorithms/src/knapsack/mod.rs index 33ddf59..7ac16f2 100644 --- a/tig-algorithms/src/knapsack/mod.rs +++ b/tig-algorithms/src/knapsack/mod.rs @@ -104,7 +104,8 @@ // c003_a053 -// c003_a054 +pub mod quadkp_improved; +pub use quadkp_improved as c003_a054; // c003_a055 diff --git a/tig-algorithms/src/knapsack/quadkp_improved/benchmarker_outbound.rs b/tig-algorithms/src/knapsack/quadkp_improved/benchmarker_outbound.rs new file mode 100644 index 0000000..f3b2d5c --- /dev/null +++ b/tig-algorithms/src/knapsack/quadkp_improved/benchmarker_outbound.rs @@ -0,0 +1,188 @@ +/*! +Copyright 2024 Rootz + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{SeedableRng, Rng, rngs::StdRng}; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut item_scores: Vec<(usize, f32)> = (0..vertex_count) + .map(|index| { + let interaction_sum: i32 = challenge.interaction_values[index].iter().sum(); + let secondary_score = challenge.values[index] as f32 / challenge.weights[index] as f32; + let combined_score = (challenge.values[index] as f32 * 0.75 + interaction_sum as f32 * 0.15 + secondary_score * 0.1) + / challenge.weights[index] as f32; + (index, combined_score) + }) + .collect(); + + item_scores.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut selected_items = Vec::with_capacity(vertex_count); + let mut unselected_items = Vec::with_capacity(vertex_count); + let mut current_weight = 0; + let mut current_value = 0; + + for &(index, _) in &item_scores { + if current_weight + challenge.weights[index] <= challenge.max_weight { + current_weight += challenge.weights[index]; + current_value += challenge.values[index] as i32; + + for &selected in &selected_items { + current_value += challenge.interaction_values[index][selected]; + } + selected_items.push(index); + } else { + unselected_items.push(index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for index in 0..vertex_count { + mutation_rates[index] = challenge.values[index] as i32; + for &selected in &selected_items { + mutation_rates[index] += challenge.interaction_values[index][selected]; + } + } + + let max_generations = 150; + let mut cooling_schedule = vec![0; vertex_count]; + let mut rng = StdRng::seed_from_u64(challenge.seed[0] as u64); + + for generation in 0..max_generations { + let mut best_gain = 0; + let mut best_swap = None; + + for (u_index, &mutant) in unselected_items.iter().enumerate() { + if cooling_schedule[mutant] > 0 { + continue; + } + + let mutant_fitness = mutation_rates[mutant]; + let extra_weight = challenge.weights[mutant] as i32 - (challenge.max_weight as i32 - current_weight as i32); + + if mutant_fitness < 0 { + continue; + } + + for (c_index, &selected) in selected_items.iter().enumerate() { + if cooling_schedule[selected] > 0 { + continue; + } + + if extra_weight > 0 && (challenge.weights[selected] as i32) < extra_weight { + continue; + } + + let interaction_penalty = (challenge.interaction_values[mutant][selected] as f32 * 0.3) as i32; + let fitness_gain = mutant_fitness - mutation_rates[selected] - interaction_penalty; + + if fitness_gain > best_gain { + best_gain = fitness_gain; + best_swap = Some((u_index, c_index)); + } + } + } + + if let Some((u_index, c_index)) = best_swap { + let added_item = unselected_items[u_index]; + let removed_item = selected_items[c_index]; + + selected_items.swap_remove(c_index); + unselected_items.swap_remove(u_index); + selected_items.push(added_item); + unselected_items.push(removed_item); + + current_value += best_gain; + current_weight = current_weight + challenge.weights[added_item] - challenge.weights[removed_item]; + + if current_weight > challenge.max_weight { + continue; + } + + for index in 0..vertex_count { + mutation_rates[index] += challenge.interaction_values[index][added_item] + - challenge.interaction_values[index][removed_item]; + } + + cooling_schedule[added_item] = 3; + cooling_schedule[removed_item] = 3; + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + + if current_value as u32 > (challenge.min_value * 9 / 10) { + let high_potential_items: Vec = unselected_items + .iter() + .filter(|&&i| challenge.values[i] as i32 > (challenge.min_value as i32 / 4)) + .copied() + .collect(); + + for &item in high_potential_items.iter().take(2) { + if current_weight + challenge.weights[item] <= challenge.max_weight { + selected_items.push(item); + unselected_items.retain(|&x| x != item); + current_weight += challenge.weights[item]; + current_value += challenge.values[item] as i32; + + for &selected in &selected_items { + if selected != item { + current_value += challenge.interaction_values[item][selected]; + } + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + } + } + } + } + + if current_value as u32 >= challenge.min_value && current_weight <= challenge.max_weight { + Ok(Some(Solution { items: selected_items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = None; + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/quadkp_improved/commercial.rs b/tig-algorithms/src/knapsack/quadkp_improved/commercial.rs new file mode 100644 index 0000000..6d7cbab --- /dev/null +++ b/tig-algorithms/src/knapsack/quadkp_improved/commercial.rs @@ -0,0 +1,188 @@ +/*! +Copyright 2024 Rootz + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{SeedableRng, Rng, rngs::StdRng}; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut item_scores: Vec<(usize, f32)> = (0..vertex_count) + .map(|index| { + let interaction_sum: i32 = challenge.interaction_values[index].iter().sum(); + let secondary_score = challenge.values[index] as f32 / challenge.weights[index] as f32; + let combined_score = (challenge.values[index] as f32 * 0.75 + interaction_sum as f32 * 0.15 + secondary_score * 0.1) + / challenge.weights[index] as f32; + (index, combined_score) + }) + .collect(); + + item_scores.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut selected_items = Vec::with_capacity(vertex_count); + let mut unselected_items = Vec::with_capacity(vertex_count); + let mut current_weight = 0; + let mut current_value = 0; + + for &(index, _) in &item_scores { + if current_weight + challenge.weights[index] <= challenge.max_weight { + current_weight += challenge.weights[index]; + current_value += challenge.values[index] as i32; + + for &selected in &selected_items { + current_value += challenge.interaction_values[index][selected]; + } + selected_items.push(index); + } else { + unselected_items.push(index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for index in 0..vertex_count { + mutation_rates[index] = challenge.values[index] as i32; + for &selected in &selected_items { + mutation_rates[index] += challenge.interaction_values[index][selected]; + } + } + + let max_generations = 150; + let mut cooling_schedule = vec![0; vertex_count]; + let mut rng = StdRng::seed_from_u64(challenge.seed[0] as u64); + + for generation in 0..max_generations { + let mut best_gain = 0; + let mut best_swap = None; + + for (u_index, &mutant) in unselected_items.iter().enumerate() { + if cooling_schedule[mutant] > 0 { + continue; + } + + let mutant_fitness = mutation_rates[mutant]; + let extra_weight = challenge.weights[mutant] as i32 - (challenge.max_weight as i32 - current_weight as i32); + + if mutant_fitness < 0 { + continue; + } + + for (c_index, &selected) in selected_items.iter().enumerate() { + if cooling_schedule[selected] > 0 { + continue; + } + + if extra_weight > 0 && (challenge.weights[selected] as i32) < extra_weight { + continue; + } + + let interaction_penalty = (challenge.interaction_values[mutant][selected] as f32 * 0.3) as i32; + let fitness_gain = mutant_fitness - mutation_rates[selected] - interaction_penalty; + + if fitness_gain > best_gain { + best_gain = fitness_gain; + best_swap = Some((u_index, c_index)); + } + } + } + + if let Some((u_index, c_index)) = best_swap { + let added_item = unselected_items[u_index]; + let removed_item = selected_items[c_index]; + + selected_items.swap_remove(c_index); + unselected_items.swap_remove(u_index); + selected_items.push(added_item); + unselected_items.push(removed_item); + + current_value += best_gain; + current_weight = current_weight + challenge.weights[added_item] - challenge.weights[removed_item]; + + if current_weight > challenge.max_weight { + continue; + } + + for index in 0..vertex_count { + mutation_rates[index] += challenge.interaction_values[index][added_item] + - challenge.interaction_values[index][removed_item]; + } + + cooling_schedule[added_item] = 3; + cooling_schedule[removed_item] = 3; + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + + if current_value as u32 > (challenge.min_value * 9 / 10) { + let high_potential_items: Vec = unselected_items + .iter() + .filter(|&&i| challenge.values[i] as i32 > (challenge.min_value as i32 / 4)) + .copied() + .collect(); + + for &item in high_potential_items.iter().take(2) { + if current_weight + challenge.weights[item] <= challenge.max_weight { + selected_items.push(item); + unselected_items.retain(|&x| x != item); + current_weight += challenge.weights[item]; + current_value += challenge.values[item] as i32; + + for &selected in &selected_items { + if selected != item { + current_value += challenge.interaction_values[item][selected]; + } + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + } + } + } + } + + if current_value as u32 >= challenge.min_value && current_weight <= challenge.max_weight { + Ok(Some(Solution { items: selected_items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = None; + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/quadkp_improved/inbound.rs b/tig-algorithms/src/knapsack/quadkp_improved/inbound.rs new file mode 100644 index 0000000..d82bd71 --- /dev/null +++ b/tig-algorithms/src/knapsack/quadkp_improved/inbound.rs @@ -0,0 +1,188 @@ +/*! +Copyright 2024 Rootz + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{SeedableRng, Rng, rngs::StdRng}; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut item_scores: Vec<(usize, f32)> = (0..vertex_count) + .map(|index| { + let interaction_sum: i32 = challenge.interaction_values[index].iter().sum(); + let secondary_score = challenge.values[index] as f32 / challenge.weights[index] as f32; + let combined_score = (challenge.values[index] as f32 * 0.75 + interaction_sum as f32 * 0.15 + secondary_score * 0.1) + / challenge.weights[index] as f32; + (index, combined_score) + }) + .collect(); + + item_scores.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut selected_items = Vec::with_capacity(vertex_count); + let mut unselected_items = Vec::with_capacity(vertex_count); + let mut current_weight = 0; + let mut current_value = 0; + + for &(index, _) in &item_scores { + if current_weight + challenge.weights[index] <= challenge.max_weight { + current_weight += challenge.weights[index]; + current_value += challenge.values[index] as i32; + + for &selected in &selected_items { + current_value += challenge.interaction_values[index][selected]; + } + selected_items.push(index); + } else { + unselected_items.push(index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for index in 0..vertex_count { + mutation_rates[index] = challenge.values[index] as i32; + for &selected in &selected_items { + mutation_rates[index] += challenge.interaction_values[index][selected]; + } + } + + let max_generations = 150; + let mut cooling_schedule = vec![0; vertex_count]; + let mut rng = StdRng::seed_from_u64(challenge.seed[0] as u64); + + for generation in 0..max_generations { + let mut best_gain = 0; + let mut best_swap = None; + + for (u_index, &mutant) in unselected_items.iter().enumerate() { + if cooling_schedule[mutant] > 0 { + continue; + } + + let mutant_fitness = mutation_rates[mutant]; + let extra_weight = challenge.weights[mutant] as i32 - (challenge.max_weight as i32 - current_weight as i32); + + if mutant_fitness < 0 { + continue; + } + + for (c_index, &selected) in selected_items.iter().enumerate() { + if cooling_schedule[selected] > 0 { + continue; + } + + if extra_weight > 0 && (challenge.weights[selected] as i32) < extra_weight { + continue; + } + + let interaction_penalty = (challenge.interaction_values[mutant][selected] as f32 * 0.3) as i32; + let fitness_gain = mutant_fitness - mutation_rates[selected] - interaction_penalty; + + if fitness_gain > best_gain { + best_gain = fitness_gain; + best_swap = Some((u_index, c_index)); + } + } + } + + if let Some((u_index, c_index)) = best_swap { + let added_item = unselected_items[u_index]; + let removed_item = selected_items[c_index]; + + selected_items.swap_remove(c_index); + unselected_items.swap_remove(u_index); + selected_items.push(added_item); + unselected_items.push(removed_item); + + current_value += best_gain; + current_weight = current_weight + challenge.weights[added_item] - challenge.weights[removed_item]; + + if current_weight > challenge.max_weight { + continue; + } + + for index in 0..vertex_count { + mutation_rates[index] += challenge.interaction_values[index][added_item] + - challenge.interaction_values[index][removed_item]; + } + + cooling_schedule[added_item] = 3; + cooling_schedule[removed_item] = 3; + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + + if current_value as u32 > (challenge.min_value * 9 / 10) { + let high_potential_items: Vec = unselected_items + .iter() + .filter(|&&i| challenge.values[i] as i32 > (challenge.min_value as i32 / 4)) + .copied() + .collect(); + + for &item in high_potential_items.iter().take(2) { + if current_weight + challenge.weights[item] <= challenge.max_weight { + selected_items.push(item); + unselected_items.retain(|&x| x != item); + current_weight += challenge.weights[item]; + current_value += challenge.values[item] as i32; + + for &selected in &selected_items { + if selected != item { + current_value += challenge.interaction_values[item][selected]; + } + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + } + } + } + } + + if current_value as u32 >= challenge.min_value && current_weight <= challenge.max_weight { + Ok(Some(Solution { items: selected_items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = None; + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/quadkp_improved/innovator_outbound.rs b/tig-algorithms/src/knapsack/quadkp_improved/innovator_outbound.rs new file mode 100644 index 0000000..8b43c0e --- /dev/null +++ b/tig-algorithms/src/knapsack/quadkp_improved/innovator_outbound.rs @@ -0,0 +1,188 @@ +/*! +Copyright 2024 Rootz + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{SeedableRng, Rng, rngs::StdRng}; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut item_scores: Vec<(usize, f32)> = (0..vertex_count) + .map(|index| { + let interaction_sum: i32 = challenge.interaction_values[index].iter().sum(); + let secondary_score = challenge.values[index] as f32 / challenge.weights[index] as f32; + let combined_score = (challenge.values[index] as f32 * 0.75 + interaction_sum as f32 * 0.15 + secondary_score * 0.1) + / challenge.weights[index] as f32; + (index, combined_score) + }) + .collect(); + + item_scores.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut selected_items = Vec::with_capacity(vertex_count); + let mut unselected_items = Vec::with_capacity(vertex_count); + let mut current_weight = 0; + let mut current_value = 0; + + for &(index, _) in &item_scores { + if current_weight + challenge.weights[index] <= challenge.max_weight { + current_weight += challenge.weights[index]; + current_value += challenge.values[index] as i32; + + for &selected in &selected_items { + current_value += challenge.interaction_values[index][selected]; + } + selected_items.push(index); + } else { + unselected_items.push(index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for index in 0..vertex_count { + mutation_rates[index] = challenge.values[index] as i32; + for &selected in &selected_items { + mutation_rates[index] += challenge.interaction_values[index][selected]; + } + } + + let max_generations = 150; + let mut cooling_schedule = vec![0; vertex_count]; + let mut rng = StdRng::seed_from_u64(challenge.seed[0] as u64); + + for generation in 0..max_generations { + let mut best_gain = 0; + let mut best_swap = None; + + for (u_index, &mutant) in unselected_items.iter().enumerate() { + if cooling_schedule[mutant] > 0 { + continue; + } + + let mutant_fitness = mutation_rates[mutant]; + let extra_weight = challenge.weights[mutant] as i32 - (challenge.max_weight as i32 - current_weight as i32); + + if mutant_fitness < 0 { + continue; + } + + for (c_index, &selected) in selected_items.iter().enumerate() { + if cooling_schedule[selected] > 0 { + continue; + } + + if extra_weight > 0 && (challenge.weights[selected] as i32) < extra_weight { + continue; + } + + let interaction_penalty = (challenge.interaction_values[mutant][selected] as f32 * 0.3) as i32; + let fitness_gain = mutant_fitness - mutation_rates[selected] - interaction_penalty; + + if fitness_gain > best_gain { + best_gain = fitness_gain; + best_swap = Some((u_index, c_index)); + } + } + } + + if let Some((u_index, c_index)) = best_swap { + let added_item = unselected_items[u_index]; + let removed_item = selected_items[c_index]; + + selected_items.swap_remove(c_index); + unselected_items.swap_remove(u_index); + selected_items.push(added_item); + unselected_items.push(removed_item); + + current_value += best_gain; + current_weight = current_weight + challenge.weights[added_item] - challenge.weights[removed_item]; + + if current_weight > challenge.max_weight { + continue; + } + + for index in 0..vertex_count { + mutation_rates[index] += challenge.interaction_values[index][added_item] + - challenge.interaction_values[index][removed_item]; + } + + cooling_schedule[added_item] = 3; + cooling_schedule[removed_item] = 3; + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + + if current_value as u32 > (challenge.min_value * 9 / 10) { + let high_potential_items: Vec = unselected_items + .iter() + .filter(|&&i| challenge.values[i] as i32 > (challenge.min_value as i32 / 4)) + .copied() + .collect(); + + for &item in high_potential_items.iter().take(2) { + if current_weight + challenge.weights[item] <= challenge.max_weight { + selected_items.push(item); + unselected_items.retain(|&x| x != item); + current_weight += challenge.weights[item]; + current_value += challenge.values[item] as i32; + + for &selected in &selected_items { + if selected != item { + current_value += challenge.interaction_values[item][selected]; + } + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + } + } + } + } + + if current_value as u32 >= challenge.min_value && current_weight <= challenge.max_weight { + Ok(Some(Solution { items: selected_items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = None; + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/quadkp_improved/mod.rs b/tig-algorithms/src/knapsack/quadkp_improved/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/knapsack/quadkp_improved/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/quadkp_improved/open_data.rs b/tig-algorithms/src/knapsack/quadkp_improved/open_data.rs new file mode 100644 index 0000000..560b774 --- /dev/null +++ b/tig-algorithms/src/knapsack/quadkp_improved/open_data.rs @@ -0,0 +1,188 @@ +/*! +Copyright 2024 Rootz + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{SeedableRng, Rng, rngs::StdRng}; +use tig_challenges::knapsack::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vertex_count = challenge.weights.len(); + + let mut item_scores: Vec<(usize, f32)> = (0..vertex_count) + .map(|index| { + let interaction_sum: i32 = challenge.interaction_values[index].iter().sum(); + let secondary_score = challenge.values[index] as f32 / challenge.weights[index] as f32; + let combined_score = (challenge.values[index] as f32 * 0.75 + interaction_sum as f32 * 0.15 + secondary_score * 0.1) + / challenge.weights[index] as f32; + (index, combined_score) + }) + .collect(); + + item_scores.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + + let mut selected_items = Vec::with_capacity(vertex_count); + let mut unselected_items = Vec::with_capacity(vertex_count); + let mut current_weight = 0; + let mut current_value = 0; + + for &(index, _) in &item_scores { + if current_weight + challenge.weights[index] <= challenge.max_weight { + current_weight += challenge.weights[index]; + current_value += challenge.values[index] as i32; + + for &selected in &selected_items { + current_value += challenge.interaction_values[index][selected]; + } + selected_items.push(index); + } else { + unselected_items.push(index); + } + } + + let mut mutation_rates = vec![0; vertex_count]; + for index in 0..vertex_count { + mutation_rates[index] = challenge.values[index] as i32; + for &selected in &selected_items { + mutation_rates[index] += challenge.interaction_values[index][selected]; + } + } + + let max_generations = 150; + let mut cooling_schedule = vec![0; vertex_count]; + let mut rng = StdRng::seed_from_u64(challenge.seed[0] as u64); + + for generation in 0..max_generations { + let mut best_gain = 0; + let mut best_swap = None; + + for (u_index, &mutant) in unselected_items.iter().enumerate() { + if cooling_schedule[mutant] > 0 { + continue; + } + + let mutant_fitness = mutation_rates[mutant]; + let extra_weight = challenge.weights[mutant] as i32 - (challenge.max_weight as i32 - current_weight as i32); + + if mutant_fitness < 0 { + continue; + } + + for (c_index, &selected) in selected_items.iter().enumerate() { + if cooling_schedule[selected] > 0 { + continue; + } + + if extra_weight > 0 && (challenge.weights[selected] as i32) < extra_weight { + continue; + } + + let interaction_penalty = (challenge.interaction_values[mutant][selected] as f32 * 0.3) as i32; + let fitness_gain = mutant_fitness - mutation_rates[selected] - interaction_penalty; + + if fitness_gain > best_gain { + best_gain = fitness_gain; + best_swap = Some((u_index, c_index)); + } + } + } + + if let Some((u_index, c_index)) = best_swap { + let added_item = unselected_items[u_index]; + let removed_item = selected_items[c_index]; + + selected_items.swap_remove(c_index); + unselected_items.swap_remove(u_index); + selected_items.push(added_item); + unselected_items.push(removed_item); + + current_value += best_gain; + current_weight = current_weight + challenge.weights[added_item] - challenge.weights[removed_item]; + + if current_weight > challenge.max_weight { + continue; + } + + for index in 0..vertex_count { + mutation_rates[index] += challenge.interaction_values[index][added_item] + - challenge.interaction_values[index][removed_item]; + } + + cooling_schedule[added_item] = 3; + cooling_schedule[removed_item] = 3; + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + + for cooling_rate in cooling_schedule.iter_mut() { + *cooling_rate = if *cooling_rate > 0 { *cooling_rate - 1 } else { 0 }; + } + + if current_value as u32 > (challenge.min_value * 9 / 10) { + let high_potential_items: Vec = unselected_items + .iter() + .filter(|&&i| challenge.values[i] as i32 > (challenge.min_value as i32 / 4)) + .copied() + .collect(); + + for &item in high_potential_items.iter().take(2) { + if current_weight + challenge.weights[item] <= challenge.max_weight { + selected_items.push(item); + unselected_items.retain(|&x| x != item); + current_weight += challenge.weights[item]; + current_value += challenge.values[item] as i32; + + for &selected in &selected_items { + if selected != item { + current_value += challenge.interaction_values[item][selected]; + } + } + + if current_value as u32 >= challenge.min_value { + return Ok(Some(Solution { items: selected_items })); + } + } + } + } + } + + if current_value as u32 >= challenge.min_value && current_weight <= challenge.max_weight { + Ok(Some(Solution { items: selected_items })) + } else { + Ok(None) + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + pub const KERNEL: Option = None; + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/knapsack/template.rs b/tig-algorithms/src/knapsack/template.rs index 6386cbf..dab7384 100644 --- a/tig-algorithms/src/knapsack/template.rs +++ b/tig-algorithms/src/knapsack/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::knapsack::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/knapsack/quadkp_improved.wasm b/tig-algorithms/wasm/knapsack/quadkp_improved.wasm new file mode 100644 index 0000000000000000000000000000000000000000..d3790ec4f808e0d6d3db327b5cf5da5cd98c8f49 GIT binary patch literal 167148 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 OcX2PLHx035(f<#KT-{&* literal 0 HcmV?d00001 From 3e85764aee65efc658d0178bb6314cb7d7ff52c1 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:04:52 +0000 Subject: [PATCH 24/49] Compiled satisfiability/fast_walk_sat --- .../fast_walk_sat/benchmarker_outbound.rs | 250 ++++++++++++++++++ .../fast_walk_sat/commercial.rs | 250 ++++++++++++++++++ .../satisfiability/fast_walk_sat/inbound.rs | 250 ++++++++++++++++++ .../fast_walk_sat/innovator_outbound.rs | 250 ++++++++++++++++++ .../src/satisfiability/fast_walk_sat/mod.rs | 4 + .../satisfiability/fast_walk_sat/open_data.rs | 250 ++++++++++++++++++ tig-algorithms/src/satisfiability/mod.rs | 3 +- tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/fast_walk_sat.wasm | Bin 0 -> 161544 bytes 9 files changed, 1258 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/fast_walk_sat/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/fast_walk_sat/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/fast_walk_sat/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/fast_walk_sat/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/fast_walk_sat/mod.rs create mode 100644 tig-algorithms/src/satisfiability/fast_walk_sat/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/fast_walk_sat.wasm diff --git a/tig-algorithms/src/satisfiability/fast_walk_sat/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/fast_walk_sat/benchmarker_outbound.rs new file mode 100644 index 0000000..dceb0e8 --- /dev/null +++ b/tig-algorithms/src/satisfiability/fast_walk_sat/benchmarker_outbound.rs @@ -0,0 +1,250 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashSet; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual = HashSet::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual.insert(i); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if let Some(&i) = residual.iter().next() { + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + for &c in &n_clauses[v] { + if variables[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } else { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } + } + for &c in &p_clauses[v] { + if variables[v] { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } else { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/fast_walk_sat/commercial.rs b/tig-algorithms/src/satisfiability/fast_walk_sat/commercial.rs new file mode 100644 index 0000000..a71cfb8 --- /dev/null +++ b/tig-algorithms/src/satisfiability/fast_walk_sat/commercial.rs @@ -0,0 +1,250 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashSet; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual = HashSet::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual.insert(i); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if let Some(&i) = residual.iter().next() { + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + for &c in &n_clauses[v] { + if variables[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } else { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } + } + for &c in &p_clauses[v] { + if variables[v] { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } else { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/fast_walk_sat/inbound.rs b/tig-algorithms/src/satisfiability/fast_walk_sat/inbound.rs new file mode 100644 index 0000000..f8bed23 --- /dev/null +++ b/tig-algorithms/src/satisfiability/fast_walk_sat/inbound.rs @@ -0,0 +1,250 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashSet; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual = HashSet::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual.insert(i); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if let Some(&i) = residual.iter().next() { + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + for &c in &n_clauses[v] { + if variables[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } else { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } + } + for &c in &p_clauses[v] { + if variables[v] { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } else { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/fast_walk_sat/innovator_outbound.rs b/tig-algorithms/src/satisfiability/fast_walk_sat/innovator_outbound.rs new file mode 100644 index 0000000..560c2eb --- /dev/null +++ b/tig-algorithms/src/satisfiability/fast_walk_sat/innovator_outbound.rs @@ -0,0 +1,250 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashSet; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual = HashSet::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual.insert(i); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if let Some(&i) = residual.iter().next() { + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + for &c in &n_clauses[v] { + if variables[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } else { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } + } + for &c in &p_clauses[v] { + if variables[v] { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } else { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/fast_walk_sat/mod.rs b/tig-algorithms/src/satisfiability/fast_walk_sat/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/fast_walk_sat/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/fast_walk_sat/open_data.rs b/tig-algorithms/src/satisfiability/fast_walk_sat/open_data.rs new file mode 100644 index 0000000..c68797e --- /dev/null +++ b/tig-algorithms/src/satisfiability/fast_walk_sat/open_data.rs @@ -0,0 +1,250 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashSet; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual = HashSet::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual.insert(i); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if let Some(&i) = residual.iter().next() { + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + for &c in &n_clauses[v] { + if variables[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } else { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } + } + for &c in &p_clauses[v] { + if variables[v] { + if num_good_so_far[c] == 1 { + residual.insert(c); + } + num_good_so_far[c] -= 1; + } else { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + residual.remove(&c); + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..fe1adc4 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -18,7 +18,8 @@ // c001_a010 -// c001_a011 +pub mod fast_walk_sat; +pub use fast_walk_sat as c001_a011; // c001_a012 diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/fast_walk_sat.wasm b/tig-algorithms/wasm/satisfiability/fast_walk_sat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..55b1a251013359ac629c6f9c60c4c40e86b146f7 GIT binary patch literal 161544 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

Uaumx0L_e@oJ6?G{pzS%}qSc7WA1n6?$ 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 I9?O{j0qe Date: Thu, 16 Jan 2025 21:05:13 +0000 Subject: [PATCH 25/49] Compiled satisfiability/inbound --- .../inbound/benchmarker_outbound.rs | 267 ++++++++++++++++++ .../src/satisfiability/inbound/commercial.rs | 267 ++++++++++++++++++ .../src/satisfiability/inbound/inbound.rs | 267 ++++++++++++++++++ .../inbound/innovator_outbound.rs | 267 ++++++++++++++++++ .../src/satisfiability/inbound/mod.rs | 4 + .../src/satisfiability/inbound/open_data.rs | 267 ++++++++++++++++++ tig-algorithms/src/satisfiability/mod.rs | 3 +- tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/inbound.wasm | Bin 0 -> 162266 bytes 9 files changed, 1343 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/inbound/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/inbound/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/inbound/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/inbound/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/inbound/mod.rs create mode 100644 tig-algorithms/src/satisfiability/inbound/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/inbound.wasm diff --git a/tig-algorithms/src/satisfiability/inbound/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/inbound/benchmarker_outbound.rs new file mode 100644 index 0000000..b08ce0d --- /dev/null +++ b/tig-algorithms/src/satisfiability/inbound/benchmarker_outbound.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Clifford Algueraz + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/inbound/commercial.rs b/tig-algorithms/src/satisfiability/inbound/commercial.rs new file mode 100644 index 0000000..b8c9432 --- /dev/null +++ b/tig-algorithms/src/satisfiability/inbound/commercial.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Clifford Algueraz + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/inbound/inbound.rs b/tig-algorithms/src/satisfiability/inbound/inbound.rs new file mode 100644 index 0000000..72ae4d8 --- /dev/null +++ b/tig-algorithms/src/satisfiability/inbound/inbound.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Clifford Algueraz + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/inbound/innovator_outbound.rs b/tig-algorithms/src/satisfiability/inbound/innovator_outbound.rs new file mode 100644 index 0000000..ca5c0a3 --- /dev/null +++ b/tig-algorithms/src/satisfiability/inbound/innovator_outbound.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Clifford Algueraz + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/inbound/mod.rs b/tig-algorithms/src/satisfiability/inbound/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/inbound/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/inbound/open_data.rs b/tig-algorithms/src/satisfiability/inbound/open_data.rs new file mode 100644 index 0000000..75d7647 --- /dev/null +++ b/tig-algorithms/src/satisfiability/inbound/open_data.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Clifford Algueraz + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..86b7334 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -32,7 +32,8 @@ // c001_a017 -// c001_a018 +pub mod inbound; +pub use inbound as c001_a018; // c001_a019 diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/inbound.wasm b/tig-algorithms/wasm/satisfiability/inbound.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ca5eb20141e1239be44870f450ae5daf6e3a2f27 GIT binary patch literal 162266 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

pAN{S>UHAR||mLdbsY?35d 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 Z!LI$<7rQK#37o*{*T#X9(-N?Z`5(P#uLb}B literal 0 HcmV?d00001 From 862d0d04921f394e7684bf1b3b6df6f3957c157b Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:05:28 +0000 Subject: [PATCH 26/49] Compiled satisfiability/sat_allocd --- tig-algorithms/src/satisfiability/mod.rs | 3 +- .../sat_allocd/benchmarker_outbound.rs | 284 ++++++++++++++++++ .../satisfiability/sat_allocd/commercial.rs | 284 ++++++++++++++++++ .../src/satisfiability/sat_allocd/inbound.rs | 284 ++++++++++++++++++ .../sat_allocd/innovator_outbound.rs | 284 ++++++++++++++++++ .../src/satisfiability/sat_allocd/mod.rs | 4 + .../satisfiability/sat_allocd/open_data.rs | 284 ++++++++++++++++++ tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/sat_allocd.wasm | Bin 0 -> 162728 bytes 9 files changed, 1428 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/sat_allocd/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_allocd/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/sat_allocd/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_allocd/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_allocd/mod.rs create mode 100644 tig-algorithms/src/satisfiability/sat_allocd/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/sat_allocd.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..3f695fd 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -42,7 +42,8 @@ // c001_a022 -// c001_a023 +pub mod sat_allocd; +pub use sat_allocd as c001_a023; // c001_a024 diff --git a/tig-algorithms/src/satisfiability/sat_allocd/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/sat_allocd/benchmarker_outbound.rs new file mode 100644 index 0000000..2e6758f --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_allocd/benchmarker_outbound.rs @@ -0,0 +1,284 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_allocd/commercial.rs b/tig-algorithms/src/satisfiability/sat_allocd/commercial.rs new file mode 100644 index 0000000..53644aa --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_allocd/commercial.rs @@ -0,0 +1,284 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_allocd/inbound.rs b/tig-algorithms/src/satisfiability/sat_allocd/inbound.rs new file mode 100644 index 0000000..8cfe0c9 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_allocd/inbound.rs @@ -0,0 +1,284 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_allocd/innovator_outbound.rs b/tig-algorithms/src/satisfiability/sat_allocd/innovator_outbound.rs new file mode 100644 index 0000000..6452120 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_allocd/innovator_outbound.rs @@ -0,0 +1,284 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_allocd/mod.rs b/tig-algorithms/src/satisfiability/sat_allocd/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_allocd/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/sat_allocd/open_data.rs b/tig-algorithms/src/satisfiability/sat_allocd/open_data.rs new file mode 100644 index 0000000..5dc5c06 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_allocd/open_data.rs @@ -0,0 +1,284 @@ +/*! +Copyright 2024 AllFather + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/sat_allocd.wasm b/tig-algorithms/wasm/satisfiability/sat_allocd.wasm new file mode 100644 index 0000000000000000000000000000000000000000..38f99848b5dad7ab85fe73df71e34a44535b992d GIT binary patch literal 162728 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 Date: Thu, 16 Jan 2025 21:05:31 +0000 Subject: [PATCH 27/49] Compiled satisfiability/sat_global --- tig-algorithms/src/satisfiability/mod.rs | 3 +- .../sat_global/benchmarker_outbound.rs | 297 ++++++++++++++++++ .../satisfiability/sat_global/commercial.rs | 297 ++++++++++++++++++ .../src/satisfiability/sat_global/inbound.rs | 297 ++++++++++++++++++ .../sat_global/innovator_outbound.rs | 297 ++++++++++++++++++ .../src/satisfiability/sat_global/mod.rs | 4 + .../satisfiability/sat_global/open_data.rs | 297 ++++++++++++++++++ tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/sat_global.wasm | Bin 0 -> 162197 bytes 9 files changed, 1493 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/sat_global/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global/mod.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/sat_global.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..947c0be 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -64,7 +64,8 @@ // c001_a033 -// c001_a034 +pub mod sat_global; +pub use sat_global as c001_a034; // c001_a035 diff --git a/tig-algorithms/src/satisfiability/sat_global/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/sat_global/benchmarker_outbound.rs new file mode 100644 index 0000000..03d219f --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global/benchmarker_outbound.rs @@ -0,0 +1,297 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + + + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let vad = num_p as f32 / (num_p + num_n).max(1) as f32; + + if vad >= 1.8 { + variables[v] = true; + } else if vad <= 0.56 { + variables[v] = false; + } else { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 900.0 + 1.8 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global/commercial.rs b/tig-algorithms/src/satisfiability/sat_global/commercial.rs new file mode 100644 index 0000000..d026725 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global/commercial.rs @@ -0,0 +1,297 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + + + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let vad = num_p as f32 / (num_p + num_n).max(1) as f32; + + if vad >= 1.8 { + variables[v] = true; + } else if vad <= 0.56 { + variables[v] = false; + } else { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 900.0 + 1.8 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global/inbound.rs b/tig-algorithms/src/satisfiability/sat_global/inbound.rs new file mode 100644 index 0000000..fd50847 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global/inbound.rs @@ -0,0 +1,297 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + + + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let vad = num_p as f32 / (num_p + num_n).max(1) as f32; + + if vad >= 1.8 { + variables[v] = true; + } else if vad <= 0.56 { + variables[v] = false; + } else { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 900.0 + 1.8 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global/innovator_outbound.rs b/tig-algorithms/src/satisfiability/sat_global/innovator_outbound.rs new file mode 100644 index 0000000..80b6edc --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global/innovator_outbound.rs @@ -0,0 +1,297 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + + + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let vad = num_p as f32 / (num_p + num_n).max(1) as f32; + + if vad >= 1.8 { + variables[v] = true; + } else if vad <= 0.56 { + variables[v] = false; + } else { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 900.0 + 1.8 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global/mod.rs b/tig-algorithms/src/satisfiability/sat_global/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/sat_global/open_data.rs b/tig-algorithms/src/satisfiability/sat_global/open_data.rs new file mode 100644 index 0000000..f1cbf28 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global/open_data.rs @@ -0,0 +1,297 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + + + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let vad = num_p as f32 / (num_p + num_n).max(1) as f32; + + if vad >= 1.8 { + variables[v] = true; + } else if vad <= 0.56 { + variables[v] = false; + } else { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 900.0 + 1.8 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/sat_global.wasm b/tig-algorithms/wasm/satisfiability/sat_global.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a36133b86c4ef6c6d25d57831654192501462273 GIT binary patch literal 162197 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

p{xSH$z*Fg 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 F{{VQto0b3o literal 0 HcmV?d00001 From 23d811caa501412fa1f1ff62e7e5befdfc1390e7 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:05:33 +0000 Subject: [PATCH 28/49] Compiled satisfiability/sat_global_opt --- tig-algorithms/src/satisfiability/mod.rs | 3 +- .../sat_global_opt/benchmarker_outbound.rs | 292 ++++++++++++++++++ .../sat_global_opt/commercial.rs | 292 ++++++++++++++++++ .../satisfiability/sat_global_opt/inbound.rs | 292 ++++++++++++++++++ .../sat_global_opt/innovator_outbound.rs | 292 ++++++++++++++++++ .../src/satisfiability/sat_global_opt/mod.rs | 4 + .../sat_global_opt/open_data.rs | 292 ++++++++++++++++++ tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/sat_global_opt.wasm | Bin 0 -> 155171 bytes 9 files changed, 1468 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/sat_global_opt/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global_opt/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global_opt/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global_opt/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global_opt/mod.rs create mode 100644 tig-algorithms/src/satisfiability/sat_global_opt/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/sat_global_opt.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..1cbb8b0 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -78,7 +78,8 @@ // c001_a040 -// c001_a041 +pub mod sat_global_opt; +pub use sat_global_opt as c001_a041; // c001_a042 diff --git a/tig-algorithms/src/satisfiability/sat_global_opt/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/sat_global_opt/benchmarker_outbound.rs new file mode 100644 index 0000000..bf1e166 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global_opt/benchmarker_outbound.rs @@ -0,0 +1,292 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global_opt/commercial.rs b/tig-algorithms/src/satisfiability/sat_global_opt/commercial.rs new file mode 100644 index 0000000..1a41da5 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global_opt/commercial.rs @@ -0,0 +1,292 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global_opt/inbound.rs b/tig-algorithms/src/satisfiability/sat_global_opt/inbound.rs new file mode 100644 index 0000000..b39f496 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global_opt/inbound.rs @@ -0,0 +1,292 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global_opt/innovator_outbound.rs b/tig-algorithms/src/satisfiability/sat_global_opt/innovator_outbound.rs new file mode 100644 index 0000000..18bbd68 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global_opt/innovator_outbound.rs @@ -0,0 +1,292 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_global_opt/mod.rs b/tig-algorithms/src/satisfiability/sat_global_opt/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global_opt/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/sat_global_opt/open_data.rs b/tig-algorithms/src/satisfiability/sat_global_opt/open_data.rs new file mode 100644 index 0000000..eb4a1c0 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_global_opt/open_data.rs @@ -0,0 +1,292 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(0.5) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/sat_global_opt.wasm b/tig-algorithms/wasm/satisfiability/sat_global_opt.wasm new file mode 100644 index 0000000000000000000000000000000000000000..94b46b5734d7a5e43a416b2933a9996dc8374365 GIT binary patch literal 155171 zcmeFa3zS{gS?75k_gVK=)h(%P%PQI0=cpW)A}LnDksRA3+STKSA{(3FaffuzU>IoN zmIx_XBLXR@%8_i8#A!Uiz1nFE5g|k-v4R5$;|7P65>I0q6U=HLX7GTMxEU{Cfd?|( zW;#Uk`+xhK`>2Oy10mgO##*j>&e{9y$G5-t_w9Y6@4E8`<0y*a`{Kzv(#6Hac=3+J z{>F=ut|EIH-BF>8o2q{Jja9K`H{4kv4?-yy5$_(;yT`1pq9O&&@XE1c$D%vx;aV+K zj)jVqMK07O<>@cQpNP{+rJW{m+>A#nV=@0laXbRFYOb=6S7> zq-jd6N;R^&l?E@Rm3X|N+e%#N6s_nXC21U2tJO-gW=+Rw#M5deO{iS{SEE+SAk=W2 zPE^!+tdhSSz7zS@BP6aI`kt+ zRQZ9o{lMYq7vk3UzU}a#ANkI=|IiP-?Qrx+-29O^@^S0Cb{;);Rd=zoar}ZyZr%0P zx&OX9x}jOW;Gg_=-+tTw_}%~HE#I-{s*QKWo#XMl;-8Ld7w)<0pH02)pI4!F7 zdf~Uc;f>c`chk+^`knv79pCjo9sI$!T{wT|;cFM(@gqO_&iOlkJpPyQyW_j#d*UC9 zPsZzc2o0OYu*!^zC@Z$S?mw{S)!?(`kLNaV%cg7Wt==xjdbYl6}2@i@mG< zvwgnmqM4+_G?$j%ac7>m#8+>b^6}hQHBPE&C2F~NCW&(W(N!al=6rf{WuJ>(W}a^?&iFpZIcZPZYJsLL(Vfc16*3dU>$9O4(d>u({Akp7?)zYB`#T zA{t_Fm9GCZ_cU`g_v*8%KFco7GsI$GUwQ8LKBj@$`!?qGXfecU0Fr>ha?XtQ%!y&)M-DuBI@WUOGX@%r&>gu5$Go;Kh`-#%Jx zH(OfNMrH_K$#|LGa;>vH&y(vJp-?QQKdYJWhVHw0O=!)tiq`25g?h}aO8u(eebDco z%c@`kEp|XD8f2ZS*NrAD?d~oesWB)^U9I28U=KTm+cZtP?WhH9)dpU!^#@qf0J%4S z*zjsy|9@zB?RJ=LZc{0`jr(r`N`^Z4CZG&&w(QNBtQG*22tfR4&l?9C1A>`jjO7`j@w)&PHy7+M+-(UwL}N^ubofF~v{3;_TL9C(p``}7YSJvTm| z$Je=$Tjx!1_+-BunT_6jh=Gih45V?wjktIUzMI?wgB+QSk7wh&`>Dp9fAWV_68q>z zMv?jlpx~4DoCSjZ?hk$YK|gtftHm4UAW#Us?kk63KHJkM7XwL7R6LDr!dmzr3+4l6sIAhg)a*Ht5zwwsQ z3b0yq$(ef?`*9X$w9wE_4>KTxv^Vpw#zH-oS5bV|n?TY8m^Bg#bY1qKtv5=4PXerEM)QxEtbr;V8{onq`zmEic zahg@opAnb^{Jdk}9}PWk>I$aex}nZ?+wiQfO`$KRC$8wKfc(=8jOGepbJH$wu2Lh< zmXidOH?q_`ddeR0QhN~YR|T*)0@S}BSl0c8w~Qt}a?R6oz^J-(+Q0K4Qb!>K|b=x*{D#2Vh?%!BKL(}*PMOh$WqNRMqjoR>XQ zjm(bd?J@ndZm%p3Kre6e(AbDoY2`&F0f822>j+I%4xmX0X~w?okVXX?XJHuHK8D7N zO-9C#2FBxWJ-WgN>#)Z+EfNV$OG=jY=2>*kSoHU|yTy3HbH2nB!= z&CrWYWbAeZC(cQ;>d%ePu$8o}ZE07ntV!o|+&+ykD^O*yr`G9WHahS{;yB`Y-0BU&$$oY|8wuV#BQ0u?783m!_R#sl(KBI=YIVcPF-e&;`HZ!;?JH* z?XIaiY1`_zp__-lRI!^nH=jB2Ep}bw`h)MQ*|nzq7 z-aggJ5i*gVxY2|eIE~!#o&9JCGM;>OKcbwKxnZ9$svTl z(UX)+Ab8_w>XJ4}S+n;tGOd%c1o4|7kJ40eC_uZ>SUwhS7Izjg7VY?tKWm1G=n>j$ zsHs@h8Fg8Q_IgAta}jnPbw0`jy8bl`tX&@<@```t88zrZvpLh=ObS;-?z=ss<^A-g zDWA-R!GzvWvIlx=qsLXGF-4~SNRZGNfb?IE-UQ%c-@J**iFN{H62J9`MpY}=hf&qA z;bm2lL!n9ln9Y;b@jp-RmBC5YOvTP?1#ha^G#N{=B+O;iI>f94+NFAy;TIJnL(MT` zk6woCJgRHVX)$KKxz2<&)0dub(d|(Ckf7QxCk!w6yNyh&xUX?Xa|aj=Ob|Vxu+ZR# zW|x^@HoLg<^YP|TNBXybGIAPtd^ys)pxIn+rByIsEC)z|CuTkmPsi*^KO*&GxCsbq zXrUzoAg6&&Sc*fSk}T%v+wJ{O_t8T4wFp^S_PNZ zne0G~Fj-~i2lPkwbyzz{shkm`wY7tC0)6Awh znUe{$0EOY=H0mQCb#NPbiEA_8q8w8Xtr}|Q{{!gatV!c$>Ew%df|ps6fJP|y$i<3Q zEOnTZTlkS-wkMg?6Mi?*J4%>ZN&RHELk*_F8uEhO2({ zS$WsSC5Y%vb;x5gtZevtP4{N(77f_o$Hh}n4lAw>AIlql>EGgq(q;WR=`~n-j4Nl^ zFe&#}n4er2Fb-EWO6|=7mMf0TbA*%#F>$p}qgKqaSEHsH_%4`ea9|8fw6?}X1sPZC z%@w(%H4w9EcIEaeRR%%7{D(&TOf)yP8IK45#r&VxRlA+?iy2a@xBRc%wJg-)>alQD zYx!RbSJjqZ4p)_y|7^IzCVxC!!LAy9eBQtCXaE-L*6@`h{riC6%*$8h#1!n;m>KI_ zzd90#8&^lTNc=N@diJhV4oIqZr93`r{V|#R2H}X-R*lAGqvAWg1upQSQYg zwS~hdMl*}GJ}~`fab9;`1u-tszMt_A@zc4SVs5|m-##s{B zXG6>|QqgC>aK%#2@2@@d)c7%cn;2i8H@_x7Z->ey>u|sfv0!K&zi7qd5IqPs^UnJI z(Ad}J5VrD%;t*Pc@f*9RG1W-dtYME|sVbM?QO&w1tpRoc56Y1F16|&eH-45nDxTkrGaiFo@>>{7uV7e?$ zp(ul2Fnz9a*m0C&ihwwk30&x6%XZs2rRA`{QrM=WsC6*|oJl7A=YH`l?tEQuf}>AR zvW1JaRK6Y!Yyrl_N@zKng>GCtNRP?IPueYCPBK^Yk;i!4#1}%GCjnw76 z!ZL;1^K$g9OrJL5dHjJe)~>LChGYN6c?MARTj!A`RsWq@<`KM|u6f96CS^^0EAy^N z$sw!%b|EyQ^=%%g$f-${FX8lEOO?*IY4)zM zE2{XV7<*2aoO#WU7|a0vDBk6ua~2tlf*R;$4<+49lapec*5W?*)@r781BfNvzMW_ zMXv=~VxchG;C_Ayvx>{qBiH~>W&aeCq?^@Ve$~3es+`M5tbcQQ7z|kU#|c4+uQKME zs~^BQVc0VSrkbRC4+6$Zaj6SkfJME;MkEXdu;5j};~ue00kk)PV09P=Lc~{iBFf4Np(VaX}yZXSfNd5F<=Gs0Xm2mt7lQ1uKm! zQJ`x5Z%I;JjE@DDT7}PCo{27^G9DOZ4Vs6WRDBZ#T2h4-vErq?5yF8HBD2SDkX6+xz`eZAoE;?>Q{sVA!rQ}kh09D=`xsACn1O))PXAo2~(v@AROUGr3dM{xW41~^=QR~@CTqlZPY+uN> zC2ROy#%_%d`yH@^$5~jReZ$9u7^bA!qj$hASy-5iNCCPQ#G@oNMe?g;p!l2qM_ySnE&faZ;GW$#fK^*q?#JutKjjHC=QJWt~gV-vXkN@x&sV!F@3Te`r%uwmIL>48?O|e82K?I`Bz(0O7 z3QvO0`TKtD}vu_ZYcCz6UFun`6~$@=!BR5ILxKr?A6hh0KiR50B~NSA3O>+9sU5*v@@YO zGeAJko2afU=7eErB&%7vKept>vAYLpi<=Je`_F>CF^V9Rxq2CQ`|*M%0!MbW4j&c!Q+uLv_U3=2ZTFKazm zN<{wJD?%CcY7?#G#-^>_^O_DEus7vbG@ZW)z_2B$oo7Z0w)AF%Xn(mgV$|!-2>i2! z`I|Q+)~oR9AE&M5Pn?v(jXrxE3}_AXo8+0#&&h zKvk}b0ZY%ck^&a}62t z<=u=FiuGHNFlL#XfY|LH3WEaGt}IM{v2|&ZfnZWnT?C`n7vn(+3|uHO|3FWs-YcrY z!0%14WJHpw@YP|N`maZ$Om3D{=Ih1wZwLWMwMB^hnjDg{dW2M}_Z#I6xm z-1_7&VFjJX(Okh0_Xv+R}5*0_!?r-YQg`Uu$;mbO>exX0MWx@ z>~=334WiVjRXPKu7BtCqL6aGwUEnHDHY&6{+0~LPCjl|ArFn5Ik#HQfHiU=44ynlH z*Semtkro3mSnNXVO)qA=h*qoRL2MyXd5Kjv&mj-wIgHhSi>#R=csG-M;a^x(H8Pe& zad1>LOeN#Jppps+@KcW3=m>tPLqu@VAxtN@Dq)H}SEU?TiS;}hzLvZu$Yl}aU+)01MC#{mFaC`=CfR@c8{elE-foM0SO+GG<>iX5IR1qaPI z6V>@J@3O3Q)9G(l1}gGTrd3vm!Fn*G$r{`|N9(_er5FE;N@A>rAd zyzg&*@t42&{@=M|F9y{)68j79ulck}ZmA(DBLZn*XrkZ-3oS(Ar;$q{f?*6h-Rz%u z&u4{`l9|cP%%c*NE?hX+JUj$bZk_@VPv;Ey7h}S;2XdMa?-z&Ztb98ohkT^lKYiymD-ENg<8f{6N1eIx_idUaq9`LDG?M zt}p}X6m+8;1=fg+5BiV}%3%2MkvzBq^}vEqs_?1dmH|VIjLHaEblH|_R7_sA?qw6j zqU|^!)*z+0N0`aJ_5TGPfLS~dCuxNsU86ZdPW#w+XCfHMrC}$JkSw5QNe+__BT?W& zMqR?3Lf~RKHiwWN&7fjzlBk?C@z0Y^w{uN2~AwKwtRkJR`H7}?=S-$D(@rDI>tT}l$Uj)hwZiE7!kFIXHgy&LnK6sL9 zFL)-Ez2=!HjMS&rZ}PtLHi?C=W%Ez6C>}>#PeisA&Q!_MG(TK2Qj~m2B}BLS?;ua@ zoxf1Y*kFmANowt@DzU99g_Kv`l9ilL6w0itvIM=@N`6cw&|JSC1wYi?B7X`lX*N`^)a@u@Yjb4wW*9C!<6j{jXoS={<;b5K2ApZ=14 zC3_beE15gY*2CQ8u4?_J|6j}M-(HN2b zJtAc;B|G>tl?_>wmj7b!eMMy#4!;LW00slZvdS-ZK4Q7xyZ|6+rB5tO*#IB~XV}qG zf&j8!b2*irVqA2OhH3M=5|lww4VEw$0hY=r|uN}SMxay=@lNK`;Y`{8l+V!6!e%) zgBH6daSfH(69L7G?m`k?>XRE7D9A;vH>1!y#0#NQT(ADr-2^h_Z<99g#BpwNZm`AL z8TW<0Dp=sR_+Xq%1xE~+4nH2USTA%}XI0RHqkLSs1)v{0UdEN3Amuo3yHVDQ)LKywv2RO)Ka;=tx)kY)(FeCh!)Wl+4KtXs{L%j0G3VaA*p3( z^8TIKh*zqQGSW&7R7=#!H%Pw4O352_6Wy%CeB{Vc7#^gl&qnfN!2*?WNRUV>RNdjk znp6-^8Yk{8Aptvsx^4qM9mPSuhjk$k*2vrYN68Pih+W>;KiU9&THTC&0&a|?5D-i#R2`DTytWw6j(xaEKU&M+A_MmD=bu(j)0#zsroS=b!@82dSjl?A43-(nY#1^7wu_sgFy zZW>_ENh=}tuYUMng33+0)|mzA$q=me>5SdP+HW)vL(?NG3@9a3E}HX{7P^Dkq9`VS!xK#AdP_e@neB)#!&1i zmVaTSyPbTp`rUbrZs?%O_(+KuWeBMTnQD-{4b`q5SM_dTDnq0fMBm+b14iTxU@36k z2$Voa^Q(pNSHc{t@~yCRNy0Ana;=hLtr$_UT$)U|TnYOZZN+e)_Ero__E09300=b0 zVlne>SL@ZQr}fejv4lXY^`{A`8zG?&myTpy`A;0Zg6js~99QzbRG}CG5LI8%;?{6s z!g#8FVLoT0H{1}_NF6oi!f&Ve-DJOMm;V4Kwoe4q?}zd5O-r-??DpBG-26THsEN;FFLxft}9q~!wL zPS}|quoOfTB|XFqC|btL0aGjTd#1FEHcdwnI+fHKmN#twkp@V|G{nvZXe*@`;swgA zWH?=GC~wTk+o$xfX3;23kTE?M#?H&C4+}(M4pvBt3TO(Uf)cz2E2JwcFr1+ZwKP{?W@)ZQE46-xE7dK-)v6!L zQ(PC1lZ}fdV=F_WEJ-g5l*@h=sOl;UG!s^Vr8E36Avp9-SO!~8uH@%|j|k;=!_}*M zlWVC_^_vPm;|p?PeZM{G3_iNU9+iH%`_Q7(x7efUqSh%ry44=J;!%euH`lAs>6+sTJr0d4^jMDTC42N_B&nB2p{+1`2!4&(hp7(r z5;|@V%q#R;dJ|u&U+BZ%dm4Un3)8Ncxnsn~vKaaL9(S@_L2)PL_(AnBMe!t=cih=t zA#pBc0&W7HM!N#$YE3A-EOtVrGT-otpVGV~N>}C^4kQ~Q3D(aytV8CqmtLBoeiC(k zB||;692n|VaeS;V4)Cr;IE_UjICJMtFv zms>$TR-@Xbh_3Ur9Q6+Yi8VPX18nj0bbly((6iaBX4~ zv7$_spo}HjhU>q^c|YjA(#BF*+oCcd)UxR%spqf+k+P_fQxH@`m$1d5TKne8N>}yj z>>jQ%(`(1|s%#pr(&<%sHj*mGLX;|>@@pjXd%eO<)*9|wz4V(Uw2G2IST@#lgC5s! zQWL{8Mpc)DSKhWDTp?BS+`0IG1sO9{kkG8pSxSmBfBchywZ%bKG#+4z;^(#{+ZU_K`CgV5P)^&v#m_?dct@YzHq+ zcm1h9KC31T!`u#vt!cMoHa4$-t(TTv=(!ZjE=nD(09*e+`6?Vwj>ZjI1g<8tjlJc8 zls2HncyRzTRsAWn*dki~0Xhrg)pHu@_IG1cCad=K5g1@E96fcgD20DVol1?7G)D!hlZOh_&5Ly zJ)96qkbABZPsX^gUAo%H!w~st>rdFFtGf(Gp}R;JIO+g{wkK(qt|s+8+PMOt*sWyP z3@xB+jtM%-IRPrrtDG=(WJpsG=MPDaV2HhJfXOmmn}inumZboffO~F?ymI8=(!2j3 zTteLGf}{p%UR3~*_`4cnVHEIRMJKNG+^O;;Bz|`x9`kx(w#judrq_AtY_z}gKy29* zf>i*;QlnP_q!S^-Z>}s0Xb9o`Y`7ZDHkF?gFl}3mh32T*&RARZ?;>IDbvN z+duUbmh1))cz4$?>-yf`Ia_$T&|v--t?PfFlT-D9C^9G%~8rV+) zrO5yR}p)sZ0cDn}XU<|aX)xW@9;a)>gwex;V#^C?C_{f>r zyWVx;m*QjDF6xe^;)hK)ZO)#ZZr9!U4xO;GqhImp@!6&Ft;ORMM{lm|V~88v z7PsR^IWFf2E;Dz5{K9f?aZa$dau%I$7H>BO4?P)Bvx`LRn=%e%5<8yI&om2@ zh*`x2!M|Ere{Gn=BK9-GkdG$sxQ@8!g+PuRuKGc48!+olFbAi-w`6@b-6s97LkoOe z(~3=>L0EJ04LA50@rl&mhM4_6F?^=QIXJkR(T6zf#%C z@DyOSblgcL9R2b)(v}7p4nzP{M#xjbAvD6au-LV5G{YP&rc+KiV=C;MaZLs%&t4`5 z3`uNt4FClS4I)jp1DL>mGQ@*GTZ&=SvUYk?ZB^{11&vo9g zjtfj>j3aJmKzq2D4v8~IjqSP}(bNZs#j0hXoV=;d`HNX_dFIVlnd4>DXx)=|BI*Ht z2(!)lor!{qBt0sz{s|XIRxwLSCIt{K27!b=7kl_2j8J-1M{0D#T}X3bv6b?tR4ZF9 zIb13f*0pVhy@K++pc5)e#m_veS}{CJ*&z&tv*c95mzPGy6=v%v`)uJjOLXpQfBRql z^I!h)XU;_@LVQE3_ex%f#Zdu{^x+Yv>Bbqqz-728Zuzo0R`hKj^D^n#au;$88tcB7 zkS2`@!5j}QPI{rWZ#cJE@&bevxf&Ow-yCV**o8W`q^%3q>ZscaCj-?GPEPzSma7W& zS<7T#8bCTic=H4&E0m9~LOmh>(WW1`w{bKEjR9!3)Rd?xbbd zi>TjLlgVkBaU+9_B-2KY;4zMBs&V9p?g2xAjI7K!gs-Ufh~9${xR?vof_Myb@x7JL z(SzggoE62rRz6Rzj?_E#5ySY=%ID<7V;tyt>H=ilsDcAp|51NG8^DaQ6AsZ>rDZc# z)POKn-D`OpjBvQ9c)%b;MNx!ofJHKy#C4~x zD!1H48p8N0@i`){?_g^eWr^@8*$3Guz+Imw6bBCGuSleh0{p&6g-ZJ(wFYmKKqYEj zid0vXP#s$#R7s#%EmQ}Y63jR@C#li>V0YORuSRcHRD4Ge!(!9g`A?^uGl1WW3>DI; znc|eV9;%@6y+HDVofy96Q(d8rLJQP)<60)Jw1V~duE?_YsNjB*s5sLOLSu_$k>bV3mt zJ&ZxakF<-r8%5^U!8jQtm@LqvCo&sec1QIi?bi5Vh>d6O)w)F*thn36InQSu2>@q;-+%u{z) z@`E9RZ9odDvokJX2v(Vvc)&LD-UEC#sg7vvJ?QAc3j+@rwi&ub4P0pk|4ot~jKkK3 z2m{wOf1)7IBJN|BIt8PBg^c=>4#Y|1Qrw{iiiuo`4a}Scrrh=u?0_H#JHAvU&7v0D z67)98vD(?m)fyAYNqA6P4$E|CMyRzs-^EL-z_C+q2rcMJ&|!3g3yCan(9oYU#Ml*X z9^z&~t_mS2mi&GA^hJXR*_K21-_DcZNaDoJCVlI*ON0-JfXZu^Ao1L2?}oX^z6lLp zYA@`jX-Gf;n!7=~cbMiVQRHQP$}%M7U;a5u`PcFCvUh;?t+~d+#Z43rr~JRFbAO0> zpczR+pwg9vqfHi2=U>?)ikMd@YGG+(I8D0C%)HH1)V66-r9hAjTP^y3GRFCP*JoEUCOb*U9X+61JmJqZkI|vD-2wh0`P>7rbH)#t!5 zGT!t?+Y+4|LSDj_9|<^I^_&aBxtXjO?t4Z25R}ok+Qw_0UsUmTAj#RF##u4kyuG-= zJV!lD=Q!>l1DONYZ3LN&N}?EJg|;@^=myRKL$pd$D9lePNdeOo zVoH^{;CWM~GSisUq;ydvS1ajVt0KsEg54OS_oQ9L@Np(f;K|P{-&d{dUV3uFFJPka zqz)j|4}LCmFvZL&TcKF(peC7AVnb$2ihEN_ zqcn#Oy7F73CgvxW|OPL9${ zT?^1De`>^k)$-SXpXf3z`b+ym2ln`V&2Q8ZKOH{(=EJRkx8 zghKK>k$9rO1=>TI*^;siM#x*Nye`5D>MAUou2MrTd}~Bh<{5Q`rQOVeQ&b6f$5pj= zRqI{V%NKFI_upys%A38bk=|9yt~eWB;RNaJnhJ}Ji0&b%#5D+Ncv=J$nCsePM%P-L zEnvl3Ya`6Y;i{a5Htxc#4z!5;7zcp;ISk-}vl0=a%(4l{fjL6&{MbnyBoq|mVF-5c zh}gU_yvYy%k{PXSNYD=nE>xP(`A!i_9T>rC_5c&dQ^@pfk#$}NYF~S!lFPD%kt7)%lx#1fJCcHtpA)NN$FL|`fVxX=KuwEf&doLu33LIc5? zAV>?6gfQH^un28bIzj|~k&-P1H7|A!I#Cw`)Cx`082D(1dg`HqdqOGOM2kp5kFg&3 zVP05UgsMWwkJcx3q7EY7R}W`!o}&LZ_PcMu-dBwxW)AdRjKY+~aumU(j(-a;V<&|6 zlot`Bu$!v@z;UxFl+1A1HpQED3Bt9Yb&ZUQLMr5*EwSvw?jT02VLzzD-up`}S1M2g|1R)#@~{vgoG zLsCV_&CxvMOt3{GSYuFe20J)YuL&*8z^cjr-E9>-{=)Gh$HsHeKpi^ts zS0plRj>au@&TP=#MTQ~(sR%su5oC{f05%ZyV>Phhm6VBfrkGG28(ybp>#*ONgfoZz zN}VkZ`_+kQtU1iV5_o`{g%MFMr0pZGWW7toVZFZ;dxU(2fQ-x-4KLVXzuZcFv#7S# z1TV4qs-z7J07$1o7!a4k+!LV#j6rA@Ku|1m1?N<;%wcnkHHj)fyTwZ5L{ooKF~q=F zaY&r+u@sq`t=C5ohV%|)t)azw`cd~m5F*5Y2X;%;k~9=msK`#jEb@wA^~MQ9a)~iL zMW=~DJWUD{SH$$}D)A4$m%O6csP!93dFGl@HES#&3X8~(^3o8U zmA`HkRLiHmx!8fx#~eE;>MMX-mKc1C!XA9rD(r!HbzxmlM)toFD3`4aj0`{*E%gEI z>Ht|3Y7-{KZi7A7@s`7Qu^|-424dt z$JNec2pu9@ZOWQFMp;bu(N^qdOf@53nnFYifs{JO7V>c+$GBFJB3e_2>H=Y^J3_6Z zoSIt4-GS0KewzJ`puYB5u`(gz9Pr<~ayn-NFN53^RNp^^e$4=(1T9EGcgj>SSSUTx zV37n^NK@DR$24+-2QD1$2lF*BVK%+a@O5g;E)yCKOpR8B#0kI+X71b?SI2OuxnrKYJAV~iohLS#6?>aZkg zA)!?y!D28BY&Qas_f8JR$cH5kU!1X#$PG%jCS?LOJAB_XX02SV9NLtnm3#md=n-u; zauh0yOo@Q0zg&lSujUU8g=~fZmJC9VY4kwp{|b5FFA4WLqkpg_FhfxW(Q-R0+GZr9 zB|wR7B?&0S?VRAE1T~ah07qK4jn>6S4hc#;7qo-K%$2eWO%xbUq(zl^DX7vY;oyK` z0E+{o{Lcy7egb0&e`&07J9FpfsB65%Hqn^+cl0(Xf#eYJn7?nelcjMr-;UE*8nQOt zr23QuwRktz>$j6U5(*r%gEjGSF;F>c8VhN%rx01EZL~ba=$Ioo1xVVvqR77?XEPBtaWDgZqlNb8hX37F-*I7yWC^y{>rsYYBx?#!8!8PJAca+6HC6+s;ns;aMn` zf$4J|VCPvr#c%Sx9-^d)sUe6EsMGb*u^Rz8P4+tFDQoay?vnR+y=8Juz*?DRA}i^;rm? z9ZBN)(KWGh(uu0bT2mCrB}8)R8nYvTNn+Vpo8+Vym1Pj9^1B~gW?IZ1?K$Wfc4C-u zTr~wn)UZxy%%|{fXj&8s*q}r~j-KQMHq)}#g~M;C?$wk(X+4-HwpW4aX}~nAF{o$4 z7P*p>?HxmtaKrhu{jn5H^+$p*VI=yzi_L4AAK4ZfZAI}312cKS=6nYrd6m#a=- z2QU6HRQr!rh;Tt=u=r8&d?mH>ql4@CX=JT*pHNq83M&FKM)b9UvDix2eO;WDYaQQ) zyb}duu@w;_lmnD%IU+K>4+_fJf)V5G1eba5?^pII+(Fwka*}4*I9Pn+Z^eMbb_WKe zsW1@6g&)NxW_O|xW?P*D%`^GnBV&CKXgM)Oh*q});^Gk#9^2XT;z^0XR?$Yr9#{Yc zO(G_7d8U~Tnh<1lhN?OAz{^OPx>jvmaOQ&&^3TI_AN=4lq~e;vAHlQWvUk8y!xZT> z6qiHYC`Kw`Lvsv{I!jX05sWN76DXRYyV@bslcLrwM3@7Tn8Iq&2SXEuU~Ld-ISAyG zoFe@}#H_e6V>+ZzQH9`n(cz^~-!*G&1T(b(RWq*BGQT3q~>RN@#xu=>BI z9@RkrK6}7^NP*rj@$n9+5u65G(>X(tUqZsL0PLKI0kVJQdBXJJThXhEp0lDg-+IQ1 zIA39PgO6H~w&<^FkS`0;m-)t)l6h@|SY7AyR)>f{>|2NVepr+xJ|j|)$!#cNh0I9K zB{>iQao9h}o)hyEw{SZO`Jg81koS%^O4^bLfRC1t(XTB7P#~Xi!4)7O0ENgA=$&?` zO0T0yYtK?x`~^qo{tN6kHWw|`&=24m#w%2ao+4U8iKeY5%5mUdh~``Pdf-qnhQ&L( zA|Bcok$iywDG$F;y?Khk$jY>cb`s{rnrXEDsZTK{5MG%td|3p$iifbV@R=Db2zliB zG^lBv`28#iFqN!^tbrR}ryBTRl@5~EQ$l}3Pr-Gb;zu5OimJ-f&WfifU_6~%@f6LA zt^eQW6I5Ubr`BbN#h1UO)BFG11_oeF{Hq+q3<}ej{3Y>l*J4uX_P9o_D6Q#J(Bt3- z9^lFqadZt2Vd_^KebTue9d;ms+9V zkDlS?LmR`*7r(&Gc)0%1PjNjKu8+<8pMC)$CC~F6^Zwc2Dz1O!FGl7(RBLC&^X=~ZS`1gODpHm56(B=0`(s{EoG0ZFc|4r-| z(C6^Of^>q0MHD}uupd?gNu)yA?qJ8)ICG_OBx`fczI2%y;U8p)N-RqQUkBN>IoAyg z3@P8arEC%mV;_K1HvLGBm4SvL{TWM0bW4F2o9HyI3>s4g!eHIRviZaXzyjKoOGR8s z^1W$>tJt3@E3V;x7gVrq((*9aO)hFM^`1B3CydU=W(OK`V;-(*LtQ45#Ii%1_H{VI zYfa;o%n7-!&yY9Rd2hA^NgQ$*KCaHBO)w;VoLk#81hP9BD&ch-obKma*J6~YS2~ko zhp0Uudcr5}pG2GY7TYM#QI1T!tB$eW*8AgL{F-PO&EP4rcXRA%U2MwP=hVq##5t{+ zQLA1!S3;k!(ot{r@haeoae+eVRI?xsp6udtQQ|g$ggc+gOBb$}2YT7CXSZFjhuLkK^)J91F6S!!13*8?~vM3(E7QW-xc8t?JII#rw1~zB-&Ul`>X(r!NuR+AiuJLXitiJa(ZQkSd%gD}f4{TmTF=rEGu}po+G&|9Zi(8LX_-tkXN_9F)L?ALn1stHkk3La_#fe%b15gGV*A1Cz zSZ%uI5PP#7bq)2%Vna)Ea;ngu%mN+P*HAE|DG!&XP6=ZI3&5Cv8YsaYiL~@MD1p2I z>RcT;znd2^i*19oPKiagWOD$U=%tUuwkYKR{XExLOI0M`!Y_Nfp1s~hO*?_NZf{e8>#PN8LT?+kPZ&W0<+H|?O6?s&g3T7o^owG>Dswp~H(TpC?!BzL8?0`3X z*3$7q^uA-j$XLE`Zk$V6$18E8z@8NBA^d6hP)0vRSgcREnuI_pZ(F{F0FO<@S;q&( zdx0K~R+g*y=cs4V(xEbZC2E2X zbFN)3$GQz@opS6@8}{4EPkcq8c@b29?$C_cvOHqBf_(51DLzy-7E*1!a45ZGar3cw zK`Xrl(IL{`kw)3;^}zNA!$V>*d^TRfYn~c24qTF^*4(|`z5crq1h4;e?)VzK{Z~Jp z=OL7f#&*IvJdLwDae)8Ek8^wF$1Rn`sCz#4E1b^J@ z8&96r%_T6Mx^WJLw^PpWO~l1g2?z!5KZ)${@1w0f$s@YNt@iLJz0gPoiu;F0yTaan z-TPM5E$XltH?bG20k(t#l*B(a+I5rS>51%+N3DaR&)_w*KGlb`?(_b$_3(!wndaG? z4|Co)<;D);pjrM~`=skutr3>=k-X(E=w{;x+G9R)lXGOsFD)gvOr6a(_*3lNWG_6q zrUCMu+k^sSD9pibw{qGgyUBP^BExc2ls8oFW;^#dH(0*36c-gLp+aL-g|devRH&?~ z5c;6;q*oMuM~?rhK6jR*FAn-gP80X|eCKVxa+ukk{BaCHG6(X^aD5y^XH2y-_b_O8 zGT(MApXL|r<0f<4jxD&1wgckl?ijBTymr&Y6JE`a*^{!l1;fb3f*#T&9~VGUME*W? z3t#QR8PHqK{qN2!srS{2 zrWn5NXpHZRB-&k#t}us`J)6fjN>~S#TiV|SKy;RPW;j3+a+=HZvAbG8@Km6}!>-gu z$A^)XVRm(d6T!hl55gpPuOkx>=B*IEg%Rl&kpMLLd0R$B$!!0-8tgZrkyxF`ALKAa zxFss$oR%n?o{cj{f6h&xv|Zu7MIyX{8iZ$D9ocY5A<1S(Ko2cfF(k)>%z;1KV2A`Z z;OC{^6z-3tu{Sx*Rgi{r0RT0i6RdtA2QBD=VZiqhrS!1$bIAp?EzEgxB% zj9{t%232N2vE0=3#BF^;@}SZvFvkX|c~A0~&Pa(w&Tzqq1G7%4+K1&8VegRQJ={Lt z)a`@iExPBj9j&q_d3MA#&wP&8_LY6INx#3uRd2!Uo} zzeQW8LMoYUE_+f%A11SYg!nZOwDj-D0{HY~E>SEjj%bqOlvZh1=U~`{zEN4(HydTH zXrAwkGhlE{{#bO2-oZRIRZ3|O=N4`=3q!+U?l~dmC|X_0S%Ap#?{)mZPXGM=pItcS z957l{X`qyzap26+8#!fA3W%%+d|wlI$tOvJ3S<}UT9-wKbSI0Qvsn3~UA~~hlYoF3 z=sBLh$KDHF$BwQjq?(@6+q&>b<Lp3=bVn2WC2RQ9d z(-pEQ5s;Uw!W|-1vHA>a6Dt0w%np(DlkVH z0RW2j1R|QC#Uc*Kgu9u2`GZqwAf*X*=<*leupx?~L(ooSe&q{1(V4Yt zcl8?}A4_SaU;0;IT;;F;1{*GJz`hilwu3LrPmWWB$b776XXlG&msrRM5tx>$M$lBI zU|1KSoYQIg^)h8m`Au3eD+#LwF~YVX!`vAP7Hd>K4q`o&5Se22)+#|^U$aCiM&^JB zscT{v0H!W83HLoxr<=9Sk$*+}Km*4a2zfsTBeyxAMc&9Vug(8BZgD@=PiPn_9L!`I zdQA)3Yoo}G0|m>`4@{Sl72}jM-8f9kIoLLUB+(~`JfRYTgWNdQ0Fu$@!5R!>Er~Sk zkp#AV7DU@{V}7Zqu#IGwT;~aALx3<%spQa9a63Q85fZlQevnz801loa_CVjrktDzb z-Y)bwNq+E(>Wt(eDajO(Cw3!8cJ!;GC9hH4L;*#7`t!z&?}Lg3WO4BtVqzva(aw-| zGc=s>sUqW-TMV~3?~!|u7C2Btd>6BCU2A zZA2J_4SUAbG}As;i`>PUw+&?lQq( zvc1t5pnZpkR2@{IMn;^c1x#W$rTD4Fz^*DjZm4T*3K+`Q{X?ZZ|9^*6Gz0=~|K;(ve-j}uc8=g*?^%lPv# zrByN>fEU;g*#+#^ey)K9K+=eU`4RTVCHB+O-*o|`SiB^kBH?X4?d#=Zf2q){!>f9W zpySxC8?{e?nJDG>G~@}3Fh0b(cpU8BQX7(915jSB7KFy}U!4|2g2Ffk?eqV#Flc*O z#fr%_{DArbH}@~9no5+uT`EVz)rJ}f+W}so$^-5J)t9C7;B7Z0T+$SchCvm>!Zshi zd72=)=M#jArCSo*E4wchj`9~n+7yqmV5yABgTP6QFO3PJ7t?yB@eg4_uOvu&DTvjz z^6;0F&vKn;c#ZfK#gBl4o57CZJ-t69^oT4rUGNV%WSv2203;fE$Cnfd4_d*(_<{J z1$HReE1Zz7W66DTld;Sx*}mE|5Cwj1u5fW#F-$nC`cym0jw#Z8i)JYxen^$V!PdNm zu)LLF!|!%A%lsux7S?S}rwyQt?n&;)n$e!h`}BLsp5$KrUbrVYY1zY4 z;X1e5u$~%Ih?tC7{46ntFE+A>;LS2$JE;)sW0CFO;$Rr3d^!-$**Xx;+187coRDj7 zFr2fs7tX0p<*!mWN70ocuwvnyoW?^qXPbQ0xk3f`K#d}tlZSAQQy3@Pi1&0C;Yva~ zg1KDA>DXT)Jm$7wRjUIL zo~?@TB)tevGKD+SB0O6y!o$rLJl^=Phz$eoUW6wtc4+TOKFki3BEoY@mjq}Y1T`(f zqY&zY&8{RbySak#WJTNGDc`Ed3XU*E;|kankri!^vkF93R#Yf^NJ53m zstQG9Wve19_AcSN@5qULMssIL>mz+dd4RdAp2@e~lW%idPv&lK`c%ykv(S;I<@A_y zG2K@&(l;Jg?Oe6DTJ5dwRd42u7D7w3yDDN+?MG~SVVJF865*MhAy$*`F#(XzI3Da> za5I$36{OJ4u@Kd{Y9V`dKW4-0A!buHI1sbRI9$$y3LtJjWK#xg2nZa_E&~?Z)IGtV z%gZp39u<9V3vF+6Gsp7s;a~?>0G=7e1=L8k&0Ve^j-T%beX2|$b15Myi$Z<#vHVK< zMe^Z$r+uGPRR>| z@1!83z>imv5qeb-X?o%Vh4%E!dzQ7o=fU@ed)k1}J}6p!iWpJFzgX8_=#(ImdU%Oa zi&~|=J-mmkOLJW4jXWYh+CIV-w*!EIY`S1jaL9L;wN!moCh z9nZF*3|33V9DY;MZTVWcc3U-Zeh|S*(ByMcJ>ctl;F5zl=oRH5Fu8Ssh|;Uy3eV+q zBBknE5?Z|sOl29m+l2hI)+RIEsvr~yZb7KrBxw}Wn60!n#)TNc@yV0kRK@N<-DXKz7E z$ldhq{0yKS&Airq)53zgasi~@x`v?=oT{3*sE^?vN^9o)eLzdo2D#3$^dgW!=@|ZB3wu$hc+!0MR1gxn}8xhJp>dJ zludTC2qzU#lq$0d-uX}4YOWKDe#8Uh2)k3y*n+2x?rR%S)tTN%XBY-Tpfa+{;> zS%k|p$6RyV%Fvmk@JfRLUrdA3YZu&Mx$186Q%WyQ~edavvUai$u>~?~G&ugey zCcrUs+Xc0V|A}5MfIZu9gL`jhvFUF6(YKCbP-L^oZLs^y?Ve4K-p*A(MA;f!GpA;% z*U{^a+brOc%}!z)06-~HT0t7!aU1Bn1B9Z{9W)x#rfRWqO?9%_w;Y3n1a!dnsyA?W z1>C?Vg*ITq3Xc#H)JHrkLz9ksK0(k6Od1DSRxLQmv&#H_oS0lMP-%}(kT33by&-u{ z#m|%eGf;dl-=-h9rIC}$F6Z|lcA*f~e9gLH%_<2Jz@dG%I5d>KSIG4c7$b~HE1dr4 zqHy_K5-t@ztoW1am8fPJ?%WWQw**#!?xAW<4jcb;>iK!h$uOueAadx0 z2w<_^2En{KmYrTT7QblYC85APbtO%>2!yUxZ2&9tXQj`!1Tc-rmf6WrT4qGZh zVOR$tgHue6&j%n@`Q*4C^r297z|S+}+Ns(}58zfgwBEQZxKeZl#|kG!z_N&D$vr?1 zat~{EpuTnmG{> zcY%coBr*asmsR_F z2rq5Q9}r$Zpf9_IgPv=PF1{RuR!ZO?h$e!VM0w+^(HewJdCj^_>3`O)7QD$MTdcD( zV9U#28DSkOqu8eHFlHST+icjUN`SwPH^H4!E(dN&sS*4rou;Pc5PPPTZ@_XO!TY+z zOE1S@WOME1xYU;85-kS~K!nPOBAe{?tP$Dde;{vRWi~vm3wg-;H656mHiErNUy=de|h{}fZ^JLYcE z_!Xj^+?919U+QHqiyKi{kT>4E0{X2LQOy$i>#!kO`bIy&2Wf}@uBN7rYT8*#x;%-JmW;vc1W&%zPx--`4Ro=TX{w<`Eyu8&W#Uf^VSCiB*E- zA}tJ)o}4&Ss?fktlFafHgVMM5jATk$q4p-Wvz2NVRb-a;Q!@wb&cZr2^OI&)n4haS z8+ui8%jks)fz$?#2WiH=nEa5fHfY5StItCv@=={syib|!gC$_wK+}cW6a8TkMp-Xa zNec!^=M(!39x`Ci8jNw;n`ZeB#5mzG2S!J&D*3U*II9-pl-{jSW(y=6gRI#(r@}Ey zg&*@|mLZ@Lzgu3oV7$Vs0C8Qpn>c9w1LaI!v564(jjEmt0%O%Y5W%LB2cnfLVFzX@ zt7`mw_zlBb$Exa;d;>B3AIz#6x6kMSjsaVAT&ya<4KP`NnY0bFNIQX*cjVIK19mTM z*Z0g%d5DI1M6cw(c@W|s=?Hdm3^6Nida%<6{xWj)_^y|%QCWQhmLG0rnNoc+9isE0 z8|Y+0M@5K89uko#+d~dl6oNXmB-;2PY=}CcpK6&XYS`GIxe+JBZoo^WaD?|p%4PH&R^HUBa)`9jC~iFy5ABV|qZxdhY~Y;q|MAzBInGL( zIg5?i#qreI0-k6$tP-Aip~4SpXTph=6X@^k%WuOB8L491d<{=6M0Pg1HptpjePlt| z5n`GWisahry-_*F;E8+{mCYootZ_V|ZN7Hh+aS)CVk`RE=gtlnq2w}VS~s~SkuJ81sr z9|J3G1AKw7ttZ!@GpjJ^zoG?X8(wdYCC))z!a1esFh7wTyWn~_2mT4|)*`opkcL*0 z<{`oJA|BusVEih?8=|Yf+MY};#yX~UAMq2Cg<_7MN~Rs{@5z{!+}Q5@aI0bYu?|r#QsMn zuX6>kE!bzSL&wR-VbkjzyDu~gynp<3_>?fO?#Xq7`?m~`4%Txa@Hjt47MRcU1=tv-6j>JS6HZ4r&(-Dkl$^FPjG4-QL z4R?vs-4H3rd>`4CKf+2_Mg&So{xLkbw#!CmGRgG)pj}avb25@ZcUPhnKDB5JztJ39tgUJE3{(!uG&P=IOra?k#J%W*?^L*vJIQ;! z*zQIS`=uA-`8##uKbW?(RMDnkPWTG_7mwT+PZaurB@ni!67z1DU)fqZt?m}Q@Er+q`Or8xoaEj4uQ{TW48UDbi?*x{p+p@+DvJF;{ zPZOJ$sC6vJ10{Wqhyerv1kCC;(#=>0rD!ZGUqD@|Du&_%@ z(;Sfn9*r16+8caY>LL%>se66K+)$77HhlH^>cZ9oQlB73;Fw=5xx=Z;DztfT3=6<$ z!9CDv?usbl>)QAVySmz}%C2S~``M@&9Tr;1I;)TbNgj$tna5p|gOfxvRI$YXL!brq z*zxar7nWK?8A_XQjo+5x9LE&7OY@e}Gl$y-8HU@|>2LjZr6lJUaIj+{EULH*idL<4 z9e(7#E^YEjaPq{(B!HHn<2I8R$~b-6qRc$!|7g~=od=|Az{^Y+a>|T9}I=?t%ZHQX^V||()3pdcT&@g;GO^C^t z546bI2G6J4Qeh!n)7D6jK|Jc)sW(ErfOdAwRYlK%9R{rMDbRlSyc8o9!st_UGz@;1 zOovmuW&Vid5}gcS*_`y8`X@C~F&yhW+~yi9?jY^V=Xs5wzE7_gX}|HnJeC0baenGh zUYQb$7Kg`kjgmMiVzCe1Az5aUrxz-a#}dS_o+=X)V>mY`5O2WOSB z<(6JXkfAz8%(@d_bCX(JjegQ?;r&TwWKB?~2PuGSkeXNrG9!Y_#j7DhTKq6%*qPCX zjJ6s82i{u&8M)R2WEdFZ=0ihBz8*+y<|gL_vU1{SIqDV5Sxmgi#M73-i0tMAeV+lf z{1yJ*KLEqnKEY#zIvNGgHGTXx&{Yee`&&4@KnG@XV3nB^AV3DbW^hTt5$qt5^{yA~ zh_TfFHK&E>Q-3;9pV9^`xOu;kGb@G1FPRCA*=Ym2cTUD-P7G~GQ2ais z@cOh&BhG)_o=i(mQ@#szc1o5=VT1RS8kmq&8knh9x8^#t0RQtyp=GJ3o5Uo`e`!zc z!pV7Y*QMXtvT>o%ZG(3(0(N|j86al{LZ9Wp9uH+sV z*XMy)8P=OoX(aSnrg#K&fG^|YUMcLs3DzH3wp9zNd5b6sj4^rgjzCaA1GKI!&oJM zD^n8_+F*)VQGv~EY=JKy+i>+BXD;zRlWXu*^1!*sun(L?y%gLU))IIU{FLu@$=?BT z*vCA_{e!=?On9fRY&;#fHclP?pm>Hn%47cF-=??~u=y(RFtkKk*D~B=kY{O`{F2!K z(mwrH{~a$%`>Od5n&uPc%n9tsLe=ySm%Y^dDRz;RcdMXtF+=w3zjm>KMAAqCsy{!D zZs_w|@C;(c$bxlB(Ki+aj}``Mb7#wraUkvF{jEMJE0TmJp%>_waEXg274TvgR z0wC81uY@@I00lZT719EzCNWn4Rp&4Cf!ct715?h49JM+2L8>jnSf_;~09qnv`Dc4z zzG<{7#s_!x&j?ph(;)ZBa3d8CuI-=fmo&nSN{U8euNA@_s5UG70DRmsxEFL~QVO}& zLkpttrN}gWW-U=#3dcc-iRc1!O)aP2&2~4s3l_e4ZX92a2O~D}|0e$5%>R~Y#IG!V z9U*lQ$arlxzf^|_RvbS(&bNah)rMa&&`@3vmm84+DsN&Dw$YS&<>qhb=9>^| zDzB3gqK$FA@@lx;jGKLm@+nj+WYUDjXU2ci|6k|Qx6^JtT-4oExR`=TTM7h*kYyL4rFC<&$3E#_j7kI97D*^_IB;H2PAA zfno(5nEZ;FGu;()-OTiuod8Os6a3^8^aweee}1c{)thmcU)5bCb8I8oNL{xXFiyBj zkr6j@7QjScXopFI?!V_+N38O z*{+lnpFNpyJch^i9_yRcdWFYu2|aGv#IA`GcPZ1=B;~*`weMO!yeje#_1*iw7j!9I z#jH2cf;sdGG5`%kYtR)+=OJ!%s2E=A&cC1{oIyu3>9?Y7Td)YzdO-CG2mHTI0vxki z?@F=5E=2)Zs#ti_o()sW45%8afG+m(8MI)x%)|>!!4A>JgJP z&{F5gpTqzG7pqsVS*|rs+tV`dv3gOdSj1T|^&nMHV8l{EgVI_cZ8Df@qo9^8NChLN zA!u}92qP`!$T(>SH4ZqPb|TF0_w94;y|3OYlF;^aQ{>(AXP>>l{q67H{`R-OUEH{O z3kQaHwI>na!DI|vixLcvHg06+H25sR3Cb3e1*WC^m!SZ1V0p|(3?vGikqSl5G#Vmn zgHjMZd-!NiLn}@CfTBD+axy~|{g-RCa{QP%5}jlBwB@Nazqy*ZtvXb#9do3Jr{K*R zhso_8vB{0mSR)g3b;X;Qpgu`Z^KE74Ry-AfOhnb29UBrfOBL)&@KA%fs;dtf2VS&> zp~$IaFUqixI2K^Yq?xuUgN2+5&SMHiAso$awb{uDUlr423bBr6A&?bKV24#ZoA2l& z)0bq4&Q&*f5CTD;@x*?jGva83wxfws^zXbriem}(7CoR=W)*XU^FyTrJhP_v%ps0BsbkN{j7Wq2odp z|K0P6+Fpk*Z%x%iPC3{)-3~C}XKYe8z^n$4x@#Vf8zC~~yc^be%9+o4%NNQmjM|av zOZby#$j#2Il@2p=WH;V;usX;9RG_d#w}D)c>8r4IfE6M)Av5G|*@qtlL~S9zh#TEp zU;O;J@KMNx`Y7h;;kYxp(Bx^>WM-tD5q9$SF)u=WE=$BY33Z={NMZL2nLYj0IT3x* zJwChY>4MP<_N8pni*D)#^-+?~$*5#fVS8b#b!WVVQ&GsQPDQ58V`V?_$oaMQu$LGL zj=2Hd2)Y)79|ng>iY^~f2KGL9)2)Ly*fGclO^3P13M?mm*)z(Qu8#otfayJ5EPHeW zN7aHS=JAu<@z&PJ1-G5e@tj#r2$dSbX}B%kfJ8J@v|bgjZ=eoB-ZGTYYCOL$o%e{jx~QYWg! z>&mcMC5tE_c0y}~zC$QM6%uK8f~yX400K)b%(}K1#3JWLmvI@P=90mHT!|Unjj+D0 zL8t;lui#GXWmqg7T1J(`(8pv%i&mzas=C2u9k&G|AlNYfWHq^KJdPLKOhHZe$Hl-v z@YyI%AOxTRT+Po;;NoX{6M{b5$iEnUmrB{~b}UM&(*AVDhd>Jh$Kg|U0S#U6BvC-!ey~oU;o=^P0=n)&;?a0Q*k=8XZUkf4a_aZEo#^to zr*#**0a|BFkwN4EW?+j9bU}okM>ig5#O8zuwc*$TL3@2kxaJ#vr>bP%%Qrlkgg%Q# z>qIq7Q-$AmVG~9S>MfB~JCAZ&R>=P}Zu9Ezc^ZHPIOVkMmO8Z}@@zm+a5^pxK;NWv z*V2PYX&}0IX^QSlMQe^wb0*4PkB|v=9MUnWVB)6lQx)`rS*1Syn+dCE>LV3rh*0hQ z2zA8fB5WgkArZ2%#vRhV%oV-|`GHcdIN{>F{7RRcnT)p)rFw8<=?PP7ss z5wIGqT^YUtEk}3YH2+xAqd>d*%zNP~jT>sgX%Yr0EXQF0Eb<+LAd|B}&>a{`?V}S| zX(`n!COr1&b;UlL4`UYlX*H~ZunVLdciwv%5HpZDQ)$nnM)^=vAljXYljp-@v5y+( zHGrb?n4{d!U}qB4o;n(!D5A8Mt9!HubfM_Pxvwz}OpP2j;arJ=XPbf$YnmXTR*;8P zN&HLiM+?q*wF6g9j?RIBXfbATyx23|N+3ZK`UECMc+NXs?SaDSO>7_qtvuP87X8GI zI7nycfQ9&QCk{}aBUH5NSiO61M?Za_0!HC;_#GV>Seen%$4L*8`N7vk_zvR*mI;vo zE72c)KX?J#{Nyh;4C~u51`g9fCMb&Uujo(GY;v(MZp4h1(NoQv03+S4)FD?oEIgtHKT(-RY>A0ls1KE4yl3EUA_r&Flmp>Jk#9SZ9eljk>!GY{O zaXIJZyW?`+%b$o#aCIR2@wn`J`D1ap;N_{fT=eosBe0yA8>gC(wa;KLk;&PXl$K&!cFYk)W-Cn*m zF85TId-ImdclSmMWbuJEx&0XLu)Ly+AN9IO*T3m?D_#E^uiNSRH@s#N;PDJb)+UeBfLk9a+wu0QOx$InpaSG?}0>tFVIAzeS<^tFDCXS)7ZUhhiR zAN2aNbp7*QYu^LU2fVJ)`~J&$h|{Wi2=_%+oh!m6Lw0?ZR(ODIJ6hopQZb(4mJbe< z!Jfl{P26PF&(5uQG$CYO-7~p7HMu-FzO>vL#9FQ4tRuvLJ-Qq`$(f!Axt4+sIUCkY zyXABiD^ueswO;$#<-;%_IF?jD5ffK!8CriqepXOO&oL-L!yZ9KeuTq3(G_A%`{f6eN8hHS~c^kYqy%Nd!#OkV*$E%_g7&O9=59 z3|PktjABxE>bVIT8B-{kb}3F{^O2d2k!j#FhGaZ4tu!*YM~SaW@V3UIl0qMzW>gFs zOKQeQFGr;z0o2R-QAt>zP)vfW8AJm9PR)3Lp}-_iV(lvk1k-KeKWkMVr+^fKukNFYsWM7|2R1%ByYW%2Lh~sV z>UCr9X&nqkt=kCL!SbuQ27?#_S%aKleN8Y5X`E_9Ek0kg~glu7(eBoFz88_BbQ zzJsThw7oOhYoyKv^bHk-)ZsUz4!@1mS<<}9S#g%W^KnC5Or%aLq|VbznJG9W)Ffp( zA!QQp2b>#)4e=A3!7vs!kZMTe^}>c#eRdx+QQ-9$Ken5MPA7y;-LCXpRNJcAFtTc@ zh&6;n6%J;y%_e`TX+j|k=d+;@XVO7x8!04wGg623+aTyl% z@5W`=*$>BMSl{R3GHmicjLWdp|G-Ndc=QV0j>sGqw}``C#FbrkCz$SHJn8NO7ZDt( zfC4)TsNuqx0>ZV`=2VX4c%711Z6y{{ql9x1CRo42|QVn{0||EsW8+? zW8(G-OrPAbA4}M2c<~!YZZzTTghOeggvFY1hCk}N+I%r_3mP_vV4B_JSuLt3p4sX# zCd1Xi>f|SCe|Zw@%m*iBjQLf0ox6mEWt44SXV>H(0De4H#gg3W>tAH>x9Kh)fZ#f0 znz=kX&3!j}G%217aH@e)tFU?mFTg7}MJ**7n8s;nt(xx|oXU3_aT+;q9g@HU`$u&+ zS(H;h^aI+Hl#4;zILvBZ_A1sE@5aDgek&CAY72TKFkByX zs9nxKjs(ti8?wYCn5~PyL?N?S-Ti4K5j|q5bIUEnY%Xch1~hExsoNy@V*$de?)xNV zwdF1W^m)H(@IkmDI{?r%ARqBFd(OB6523sN_Y`zagJSi2xWy2!1vnkx<}X9gu$Ir6 zZ$wH2J_5N^R^y|)4j+)3-tB@VR3PJg5g|zUAkNyQ5Go}wsxt5bB(%hzSWf$Uz)-^S zbgin*sd`#Rg>Ml<|D!DF1tl|D)mP|v+<@Roygr?%?rGkL(dwyB)#X5Db@ylL8;;^5 z77;{572WFo|0UsstKau(iy~(RSltv*&N~?M4u+<2%RcOE2!l-lhtJT@9n7i%HOGlq z+u{ddxJp8(gUL&lnkS=HcKH@jK}d{v8`qGijqwc1%2~faGq`xHrTk`>{k7vED?)Nf zFvHpM+KehxVyB(nVg4G~N*N*v^bis)!8$x;F6H8e!)H!P&ge!EhLswts&ufrzd^Ak z=Y8q|J%m)0ZuAh&RWNn+hy}TYpJwnkRB8G(HYb5QxHdw*+u^*3DVnTXu~7r;4cM+z ztV717_Z|lH;;8-$EXlN}53MkBR{`_XM9uscGZ<_J!~mr53YWnEciogFj4q8bZ7$HZ z-U{Rxqrkun8;4ooiuKq{q}v8z6KtG)jjdVQ`)1bc#cXRN;|w$AO*&Ak`C>97>s6;Z?(UrF{mbX%3&y3Kx^{Im z$CEke3lG=vG&P>9?k0?WV0Er^OKuMVZE1Og8|U`o5Ce#Oen z3~;S~$RaqD-aL%;lbLjYGJ+^{rGf3$TDW5*qZkyL5N~4$0#&hslm>t{MzLfqJ!mQm z93PRB0D_1uEqp>Ic!Wcu7n7AvdLBhl(Z~wSxQ0-kb^igo>De?6j$imRkZ^MRw8p~S z>A^-cG$^1qW=2&PCxAB~B#|B?Fh&T5?=(Un_GgL^5RS*RyI9P1P-$SdI;(s)7Js2! zfZF`i4reLmhEwLkEguUYHvG-xnL&=ToWQ7IKeL@@A{9V%EYKlq)sYJXM$j)Qc##{v zjGLP=0f=(Z(3*)2v=oEO=Tjh0Jm0VS*Ze!K2~^Xey? z-uUL^^qYI?n+Dzqw6kHB0upUn$Jyqy3D8rWK~YA#c2NkMPDZSMAg=BYzMJSLQ`F7+ zGbz8mS9~TAk2gTrk%y;m9&2voL!?=n5thA}6M~bT2G7Zdl4dRyy{8M}YPG2mp}}v4 zyID zDq)?#VXLyz2*1mC6GFb~8~6~xK2Q-WmLcTa$zp6+1pV@a=F?cSXH0KW9fVT$bqkA> z$D5&+eK4-@snpG}0);ljA(kem43VeSavQECsMHXs7$pz=jR6e5vA7Hsu?Sp3RW2Hi zi&PN=K3IiG*$D+`qZ!f7LO2gnwV1xu!!<|goDa$3U|YcUaa$ zhL$tm<*X@&lqrw4Bf5hP7%4hWc73erby90&@d9a6dbJulS-AM1Mi`(yVo=ir4fGjX z%G`v;;!7M1Y!+dnwMHxzi@aF&7`{){>JvOFqRaq8=i003w_E@APnCs5(r?;vA&+8N;ZV@G3#f1^~@Oz08*5wuT<-J_Iu0J2?9>Ieac0?#{Z$kTpDMa z%O*9=q)`NZj}ij6sPFpYc2(-Qn4$tgI4DRrshi0Rd_1h4Yy=55lGLmx87WcL0wY6i zU?tF*#(Z;rj!}0mMWi#GGj_ivpqwE+WVRS4KI&z-1h`9p&F0HwV=a=WnLZt7LKz7J zSD9`PoK@j;Lly9XO^ys|*?%=0r<;pJhLn*P#E%6d$P}Fw-Rh~635~{IzyKLW0e3hV zit$iX0ao~@01H8`)5>H-Rt8JO=rKZ~6(Upub?R{L6mgf9KAm(V=5qlRxq4qM?}Ee# ztmF(Ka)70J#^)YoZ426{zU?rLew*gG52*Na znNRpjt*IiWdQlNkTznK1o%J^#x7) zf)+pgXOFy>Etre-MYN2=<|tIxsrrJt#6V&);Y;y7=Kgi!S*y)y)qPDNR558F!R;^%`T_YcNiKt_CA60u0(044|pY?ySpx zs3{v4QPxSaLok3%A_h(zPjCaAfuw0r12^qy+&C(5ZKv8}LyOg;sUb24Pa0yZ1!_+X z)FS-~XzZXDsf$o%_z~ct>am6aW|J0|X%QUr>DBqM=geZj;-vAufmR~|%DcspBg%EfJF|GCzS;;&&M}NAT50?h=4=BMMW6rHRz{3GTPRmxzk~^59 z)$?`ZC#&93tZfDR=Bgc1UBb*$Nd$6qT1CR6oR8~Aco*YQF?qy(=y=qcJZjgE+LK3} z`cY@{h&b~2aE3<^B4(|ygh|orL4T-mW}2vewa`rUufKTquZtt(U^(7hr3c53K%AkU z*1sXw=aIv=R)z)Kei%jGa;A>tSDK4rT^xX%MfGBg^6ZKVM9t{-8>7H=FBF_b)H;MW zg#Jba;F4;y@DBiy-Dfq#Uc*^mQXI%Inx15bs~c&$~Y?13)W&a4`KCz)0#i z)XGaFr)U2!pa2t#JF~oKwL3Gj-MRT*e_?THux0DE?K>{HbSF;A^e=UpvSh>~c^OU9 z1>AG)hAlYvjF_&$o4De^KjUZZA>sYrvRE#zFWwk{K9bd$)Si%O5hLll#K>!Ho`H{& zy=rgZ1+gV|nXB^|%qGXOw?bxN|JC3$CQys4*nP4xGjgUE3Wm1jBr0r~=-+5LweUni z3$LXGO~+BI`s^P(7~X%wJbn1&A2#;^hF#he6jDL=$=gs=`&MccFqEd{YY(n}`NC!W zU4Rsr@o|*$T_TE?FYF?pFHh;-{dBL|Ibh{uXP>Q|@$4a=?e^KF{Y&a+U-H>yKD(s9 zqki@ueYVSIJNjnIx4s|$2+#UH+wL*o@$6@Pw#{eTJVrd8{qp$P*8Z0I*+V{CwA>aC zv5suLhU-j8{2+O?N<>T2`eYV|ab8hyIXK(*Cp1sg#-N+&$4t~>T*ZOQ$n=s1#pFX?N zXKs{B&wlvVc^3VRbEu-+dwmwTss)#0FbtnTu&K}~c?kyK8O-DQg$gP(Q(3@EGd8q}pURDeWt+dlUHb@}i zYm{)66H$)BAzFn{ZW}~FxHYLD8!Kux3a<{`OrN(JnPvrL>{K6h&gnC-R>CD#G15^q zdAJ^0sUgZWsw=eASRn~B3W>rl-lB7>H5QbBr!~e`-QLD~^wFuGXVFXzRUliD!|)%< zUxzGfkBEExB#UY}7NdNO-n)e)&jERB<=V^%OQ$-IU|uZRQ~zs`Eq47!P?Fu9eDnk# zbvk@AV@SO3I#yVH{MdKFw(w)C`rgBwZ25}BO|9zZcoF*+W~(dQu5q~P-vYvBt2+-@ z|AM_Ld~oQ-#Ab07%r#prrLu&Rxt4qAHHn#8gbt<`2bs#o7*{_^0}M8E!{~oHv3DI@ zB1DA8{unUPSr7QD*uuFslduoBk4FNpXZ>MQbC^k!9=}SyU2E50#+(X6A+Ns!J(`Xu zRaz`L-byG#w>tNE82cL9$9r8p`up4*f3)UuM~aW_io=ynNJ#v$mdxWcoRI-Ry5&ib z58u$o>xz4%T-v+F>fv{O0?kNJQG4)n zxv6ut1^Etawqc@SUNRR|Hoz%qrvrEL`{`~N^n>TYWdJp3XT4>!28Y5A!v(FssxJ_& zhdjBpRNWap8e)(TRhmh$+hR-k1H=Amp0|`M?Bs4CR&on*wieYOT{0U1ejH0yqEmqh zJ|!%L%}eR3OEeUlXO2`?95okwgaR|9G24Ly*t-EVLcoMUalx;N3+b-4w5shZgC9^! zcLAIq<};{d+l{*+y%P`gPYc9dB(ag@i544K06XRXEmFKV$z>wLM&UTI=vb|h5=j`> zQmh6IvYJgHtjR^2XGRN$a;GrQUA$^uFI26tlBd3Kg@0X{aTVi_qQustEJ`!ex7sQu zs*b}bnXk+vC&;c*dW?rt$R_$nYxp+&*BZsTQOQuo2|8HfB3pZ0WV2M!AiJ9+@kV45 zCRHQbTAmi!9k9&us>r6UF|xT;#0OETQQhQ*25=TLjZ(AP27=C|z8AfrF#~|!F@_Y!z4L+c&dipa_5!4mg`s>fA3(9p6 z3Ygk%XW>TWCuT&RHA9AIDHzNKJy)3vK2yc`Hr^^*vdU(8OG{whikoY)+z5h5>HXeh z)-Lb1Dq@-qG@*upeYayDAz?94z1Zn6twoSJu)8styij6u|6_jFY0+< zkf+#VFt!vhq$A~RnE3^NgY2k=^uJbLw)VHWWZHiFog3#uOmYUn4+$HJ898;Fw9-5Td976V6d9wHSc$K)|v9EWqo%4zU&h z(;?;Ue|n8iGQUcxkr6r&9T+Zf!=gg44TF$EU?YnT6?awuDCE*bTvg{EdQhiIVw#rg zg=)ITc{=DR>|&{=+HKr=x<13{+`ZAv@;CCkmfNpGMT`EXH;mD%`Ft8bGGCg&OS|FI zXObNFsky)*L)pY%7@d)-aAYJUhXaX`CroN6SI6`_QCZpP{dvAVm3(3V$O?{4krOnX zMDIG8YbU1##8_ZHoy=8o1_w-ky+OPLztM6de!n#V-5dO%Jr9m5^V&Ng(#laqaAhpFt~tw8$un5eLcIG9<7iSv%ZO<1D9b9Bhix_}Z=dfcT?4Pc^@3ZwKMVjpEd(r@nL|X}d69>R_Tk!O71j6(o0b)t5(vCK&yBXvvLa^(~ zuwM(OHWC`Xp0Ekz>q0sD9!o2RM}zIv>GY|BvU+ZFLO_4W$WuV`JA0)*NY6Z2m#RG& z6EB~KAk=bgd$sY50GSjlJWZ^(mrG)H-ne#0o-~Y$m{t- zw1(y%f-Srj-GHLv<77uCz{;E*p!()-uWPWj`reSo`o__j3#HTg?zrSk>|ih0qh_xZ zx)TO_*$o9lEKitONwxSlAr{s?|4Ha-i!<~7DBF@{)M;=fZ~odhkHNq`g)B43vIc@V zynv=Gp>%oU3wkKG}r3?#dbNO705m7U@HJ@(*7n$4y#F$a;UZtC&C5@&JWXXoC)pu*)hk;9kkFf`0={pb}n=Z$1Q)E>yv&0PyA~gqSp`J3|p~-Ggb2LPc@a` zEKZditbJMM_1gZEsP~5Y(WAPzs3x_LntFU=Q~QcH4)OON$ljpa=GC?q)+g9B(4jG6 zLYfs23dotZK07h!8ZkmzLK%$0mX>xk9cWivi0GUY&9CAa3{!f2k`#zhy- z=)cEq1$fV{TUdiJsxBQRS_zMKkX-b0UvVuAK`-|q#g0`>sYA_72;*k%HAjef??QmN z0T;!g>LjF+Y?JY=YSyKLY^DeXjLp2b1)Lvut3P6KZ-2(#ZKJS1SKna5QnW~`ND=>O z%Dl8x)OJBALPFO+YFu(JXW4v2c1DOb!PL@tj<9(KIt2la+DED-(@B}Y%V36zs3qX8 zj@NYWbeY~^gwJS%SEe^^g*qkNs~>53s&nu9gf568#1Nu}X*3D8kvs@tB|RH9G&|aZ z_v7MRU~{-cRedrW+zVATHP+9|cD(%E`lVp##GtY?3xndiwaLd67YAk3oq`WA#0oq; z6#YlYGOmP#;DH2ga+1t22m<65bxJYW(#!Vrg|G{szL1ee3U}rq3LnNnd1td z;0!o37;3Ryv|1UngqiC9VeSn5-j}~T=$vm-S)-uC>EsAh2Va3B$;2u%AvGmGvlar8 zZbZ0xt)=1JCYO|QmIX`1WMrF1{Psr$@+Pp~+27i^{N!8T`?2@@NO!^ggxu~Gh0HLjoBr5L`G@U->QJq5gtqoGhm*0X_F0QT_J$IrW=rPQ0eNI|e*p3h_A4 zbazzcpp*5+FmL$wY{XQo|Mncu;#<2N4kSL)6WRcw=?C&;9)M79hyv?o>7<$)V*UIs zBY`G}v&!Qor9EwBk+Ob`9+fZBd>~{~)#kfLX&Mr$-a*UI6octi7$j*(RI>;~G^~eO zy|%=dgNokwuo%D`(AfGO-gU^fI%RLIQHWLHBby#Gy%P`0FPP-94NJ~{L&+*toZ3-C%>vS?^zsW@z zR$gPkM}aKa`cWZa6!=`Fx!Qsc;dKscUi1EapA6(6M6iPpeAN78h#Z7ZLRCl~#m-Ya z=QU@`##QiA^(fyDmV=@%K>K~4etNV|&ld2&&_!G}(5HWfC%Efoki7PkLPeA!z07*2i^kV{h{ZS_(Nqlw)~|Q{@K#-kO(Ng$>2##h z4Xh?l!$4YW7X!vHwBm}{t+5%8;P~NcA24uor=181$rO;s5d<;nT;DRG18a8i6eh)Y zjK-iEXKYQ=R+H8{{1Ig9$OFI$6(H->!2Pjuri~V4ZAn|GKg+6jucHI%Hd{C3(3c!O zI`+obll_ZeX!}MC>6Y1d0fx#=F~sa%zNs;^v&IlWUVtGIZ)zZH#Ly<4Fyyc|l2#{n zPomX{X(X*GOH#`^qIXfd4zkZahS?XVQq+{!`^dIpkh~s`X0e8wRM*A+R^bcdj1~PW zN8G+yb#5X2gNOq#+6J>U8!xe#j0eit=}uAYCt$z;(0(KaA*A7% z1lZ(`E)IFo9huL(a?v3e1|xWWiZG-IBHOWVF(`1XVEHOC@0zrOpujHD+E6@jml1Z< z)E#?vyOC`R1DW949p-_WsSdtz#FX|%t^=0-q{R?H>^6urCM{-3rolxH!G>J-R|Y@E z{Dmnfp7$UaER(vUaw_Ext&Z>En^twh(%idNR*JGVh3F<~uinE# zkB?7!%yO(fW~-h(c5#-Vs!Fpsme5XrDOQ!_o|b&Z@_BHh88%(Ny{CEaf*{>5M}ehN zJdTi?*g~YEfi4>9%MmCoNCMY;QQe-dnStN#QI<2DkuhQ;HU@d*cjO2Wjw>P$zyzPJ zemaY5C#(g+BWI*T8FuBq(E{=dQ;g(qOh1TKVs{ugSw41R*$HW+nL2hCXH6{(*%cK# z5w;p$*h%XlX8MLS_<|xKZFo^`z2NiF*4M_Algy#zRu0c2w*DoD5$Q}i^<2lM8Aa39 z<@|@rt+wXuYBU=kfu$>lm{RO;Pr(b28S9qfemg&;pNw1%JDrA#;uvT=;=RF7MSL(S=OB*lD$}Q{iB8-K`9MGFH$ihk1o>A*nd|&P(Qd{B#`+n(VfK zbn=ht_4pioBZEydY@dQ2l@^Sv(t_Zm4$^{#cwLUPLb=$(OVR)k)OGve)tGMbaQkrJ zyPZ*J5Mx+WFVqhABlv}@3wox?3=ebDs`h`h+H`NW?i&?H-)hUAdP1xWekz2^A*V8W zCGha*e;^sSOYVNeY#kv}4BnH+ZN%X4yzSxRg8-dibWdYgz|7;*ghbdc-YxlVDQ!ru zN4jK>p7|W+9W^`<^Rz(96}J9_Kf;&u$?FwThI6+H*dFR!gr&FwCh4*892t@#dfiFL z(d7XrI8mYyAc?-wfNLXt1{IT-!9R@cWJ4QFA0e{Xs@IKJia0JI{D>jeI>@wUq#5Tx zwhq$<*(TFihioOCE$g<>Uf`M{>j%0scz0T^<)y@S;ed?sY!_n|7KlWK)k5%D%U46% zE-GBx$YrDiVhtY?18Be*KdgU)o|6zme1Gnfaf2XY`Occ@%bNG&J@907Q1a1)+U$Q2(B z0h|2gox+DaBs7WOsk+RM5FV!JK$BP-8(~%k6oHKy8h&sW!L8vr@=oekgrQ=Rfjtw} zrEaaB#;h{RKYb|9J}*LO9mo^eB6$XDA|@nMdJ+5KX1PmrD9^W5s#6ZESVKel6U=M0 zQAD$uS`tlJs7Xl!W5q9l?h~r>`^pVa^60nbJw@s-1AbLU8?V6g5nm6E2XS|_d=uG5iw zzuh85aNA`ft+qAuD4Ca|7d+U*9_D&+=D4^#`B6_wFzJ%Ga_JE=2k0FaG$) zzWS4oeDT-6?4zAL;+uW`EdR-u-}BY~^($Za=vREh!3aG1n@@e_uFv29N5B46AElTl zh^RGqF72Ay{1$$pt9JGK#D3;JrXxcj!SOd_=)N5a;IsX$8z2dON@Nm01HH1Te=+9W z<$dtx+a3CuHkvha>FJ29$X zvh;W&zMAtZGP+S<%R6{Y4PD-(3TR^13Zhmj@cm5la$S@4$KB@T{YS zGV1WY3BVbi)pR&0LZ8W^yO(E_4bWj}{5F4UanSPH_2R&J9YT$=NgT-4@MeM~nyMG# zl?cF?;`?%asA)r*t$TzsRUn;V&?yN9y9XS_L`ETapBYfF`-MO$Y?L@t;hjRH8PAUp z$t@Az`ExlTH6pK#7!#2Z?^O)*7JVBV5c#GCM6Lm$QL~*P zRBL{LYHii3l?DP-T$`4wLCrK_(kq5<{L0xGvkJ8>&==Y;y^l2*9i;XgiwhfzUi{aI z?~fPxTul%#j07>$J~Y9%c*mS^9;MmJzz#;_!%AjrJ;|Mzt-LNr4jUJHK+<|^hKJkn zuJE%n3bmd&)kQX(y45+%@O8u8edbg*8MksanXN!mo2|KH?roT@T#$PYz6pzpS)|co zv9%u}pb(u*uB~6_7duj>`$tJ%rZ&1PIb|fEn-6~E+O%wUVPvsK5Y&Lm}$i_#F6m$NtRW^v3IYH~i5GrO@>MxI&9lAPH#*j(8rZCC=u9gu8@ae*`~ z5e!PRRiw(S0gT=p)wv?qSuOik$3(f=oR2jz(3Ef*$=tzlQaJZ_>Cq3z!Yeic;nvia1Pm_O#RZtjh#TVZvj6IkYBwTus20>;}id7%(!4 zTCBeA9RDzR5iX%N(j_=@-Et`mMTtFn?fgL*j!osyRwhC-@n!2sd@(ZH#n7AwZZVC? zi{%E+hMB*(6V;ISa0a+r8!gl%r$`A1O;SS3*22!X`mSz=#0tI}O@P=!t#lr%8|rmN zxSf%Kyl!@vlLu|H`C_RmeOQ%fWDWCHl~vi`y|Mh@XT1@}Egq%EXW)xFnM|=de023p zx!c`}-Y7 zq8EAq#wGlQzFLl1?S^u|?b$Fw}b33R&}c(yP; zH7wDByho?bdj?;*Q_Tshbb=7W)>2jX}lCUD;3K}H&gTU~An+KC)gI;65jxGMia zPEEVSHtZl2kadkQJy=po2*JLNVzyO}7P_YIzck-k8?`729>?hzX%xVuA^1MZ=A~oi*Vp&~om)k^GkGGAo8gI_F)=($Lo&omkXk*gkf%|6k~;j@mf-+mh8-&6yJjk?E(dJj zlmPK#%8GRh%+ypnv>sVeq*1B@J*z3mX3U9W4iqSYpb}gy7xYxnYjWsGXbWsT z^7L*sXVeB}Y6coTO@*YvvBJgOy12#__N|ayq6)&Ouy2JmC)S(^k#eMcM#~vklR%B^MXfEVJ zsL)SjR0uqee9Va_Ek5DN`3PQwNU__BlIc)+$)-HXVWbz~N#AVF zlfHQxo@7N~iYFtKmf3+_K!>r2^~`t@JBPVr@FYFe2n|7#(bG=p7Rnx02gkT#%J~A_ zq1}S{K-(JX<08ga_A)g$n+LuGw|aw=LpmbG@cbVgEvJ_)KC5T>a4TfX8fO9lxnBb@T2#=H@C){Wh#izw ziP^#Nei2e0c}6>SX~Vx$b`YkgR?R$QeIu<62r@ku4H4&xnzC-tLE1C7uJWrmjdrl9 z*0mE&s9EyX?b)A>-vM?izIxy>VI1FRFpic(z)hSPCmCDIYpqqB)PqjbxfQQaT7vA$v% zoS*=E1}a!Gvb=L6e?5<2;-cX?(FW5?Jt;H0K`$S#mb0YL!n}SB4sYyT(9FsLb0q#t zALuCOgKT$cu^V^eUheh2(8}kNSCECJMD_uD4QyG|0J^oH5$tA%4^XM0>ce<^L8$ZO zbEEiEUCrmNBd5<gpV z&0QxRH;!6Ngakm!<`)t*Q*~>%JqU;!HQCInCN{MGl|Z4$`W{(8s1*F%1T6-FZG`(< zq9QL1&sgCCXED-*w~`1~OW!0G{ekzN3zK+V96Qf6iGQs~8&J!Xkq9ZVz54h6H}Po^ zVphke;Ycrxkll1s?Fava(y&T(RoX;bX3x(vpd>B96I3J>Mw&r7`N6+w?u|rz>_4PC zFv*b)sTLQ2*_*BF9!_-iT;jR=Z=3H-gmJ$`UBxE*KwXqnfy0RO{LjB}H-bC!-q~lq zUHscXM_%TGc|Awj3-WBpIwHYeP&<<7nQjetf0TZe*c#3m73kV9UY@?bO*e;cQXQ!M z|6_aj$Dg0~>Z~vGS7z0h*@qxw&g$3y;JeaIL>NB6&GIp#;;Q?+T3b8LqKvWAgb?#U zduxS!&DCS3=p+gf#ao@_MTFi|Cz~ra;(2+VKbH8S`yQH`C%B2F%jzpWK4yVamOlO0 zOeZr&D6dZbFd4P@eTovStnxT$@@sLMLTRhDC{y2@sy{gHubY6O?|OZ-x~G13cf6}B ztG|19yh{tIT9{bEg%(5gI1phG{>c--)UF;E(vR?HU(AxL$Gku9yM)Xr?#(0L`0cE^ zfvfOxC*RieUs(1lInIIV5o(|^gz$i8+n6u64s+4th-h{`9UT=%fu+T<6h)F$Wr0ik z2%?USfJK!MfgVU0vLX}>`qwoGt41~6S+?Hn3C~h2#*6cFR7V7AsGgZ9FfXt={*eDk z1PRNjNEZeVx ztavk-fos@W%A8n;57^xJmf*2__EsB9Jw|yuVo*RU0a&_X-z97=HOua&^9fiIDjfp_ zP8M26nW5-Snj}FpepJW&MeRo(cECCdbGw( z+8p-=ByjcA-B6(2W_a6x2Iv|%o=?M2x`^Lw_un4HOmRaq1WX4GWZWhZ9!wbz~6R%N*hsd^{Os0J~|ZDFJDuVP@=h114oS zuhZZajDZ9n)7og+^hen`a)`HMa$xZK#+6TvGX1E>H$u6m z$brU3Q*;yKEU`#nP>JOk_lewh0f?<=|XYCoREQ)4ZD#G?TVP@FKCiJgMz5 z=6`2!fwhoFjGiOd>P?t%!kA!2p=N}AJLGc=Vx(U;5S`H$lp+a^(15W^CWR@8L;!t%wH7luQ)l)x`lt@hQ=gQ({@I80S;h^)BC8PN=R5MGdECMdHO~fhDnU$gd>VQ1QQe_{Z-lE7bFU{XJG+zP=*$QU%{{-w#+<32`ibY zCL>EH@>LcbS%72uX2WYsL& zp@Md+W62{KCsCy5l<%za>M~dBcx1ICiA0z{Co^e#L1s!;ki>x@y<`t*6aikZ)6Pr#u zui+QV+yh`CGFpe|$` zh2xWF#wDU*bfJP-oFp(~$&L^R&6Y5*$lQ|DwZn&(fJSR$25o~!?xHPg|0KDk8m1NY zmqg1*pxOc`jcuPAr4oWgXxDr$+ZTtxevG=RLKx-llrth{MhCzutFdG#4k?g>RFn;g zUd-7_?$czpbVDOc^HbA?!N?@aaFiy~lb zZ6_zI_DS__h-yFk0zt@303A2oc+Qiq@%x+7>6jWRbtS?+h%%hF#x$xf7{l({P441fL(G#~E#~rsM7!~3Y)e58jh#kJRY{0_Ct_mKd@0^Z zhyhqM%x1m@8exkWHMSRQ61G>KY%eednWRJ(MsJ1*Ms_Ck`Q=BoY_y^%1;U9$VelgI zDE1l99yJH&xULw@*=up&ItHihUX5Bf40mc)04-T3 zg`H(dP(WqjvALgV2vTS;s&}DG<^`Cg2*hRrk%cY|LlAPMxI98A1*4-Olu36ULS*e` z@zXPxW<(=(U|=!903c*&k`NneoJ?5hBCC`YtK77~H!K~>`{rLz=E(jKNwg2#K^#n? zEfcBelTlAkcm(8$cAD4hR9s>9f(3xkXJCfG_|V0;k7bj>RdB+(7Lz=Sw#TGghwP|l}Q!x6Zr?7RMA#2c2-ZS z5@Y9~I!Lfqv$pG76os|@NfRXSU9&c*g>I4rwRM`9G)OpOv@3H^9d3(+8=Uk3jr1gJ zd|^>h)C;dZss`&SJ_!LyP<$jEgD3Y zgaa)@td1+#f?CpiAnd$pySk8#YIwT|9Zn&pfP4}PpJtLdGdgllGGSdh|fiq>bO z^;9}&JxjEn=HOHnFe1z?1Fbuiiw;WbPS$s*`VS;G99dWGD*rLXw(XcH4{OCw*&=0DG~8SRU3K) zE1^B+079W@Lk@7YwN`GdbWABXH&8;s)J;264Jrq0cgz8S49Ph`lgK%~uxOAoqaCx- zkllJ%ibKxEHxiw$Hn1DZt}_kM_11}3_PHWn3Fkz9NOGxckQ^}^avwFP{SGMZ*tlyM zNKPsnlbrO|kX*b5q?+Uy&uNmQ4;x7ii5I41V?m8c4x*_^?xGGp2`_KldrS(Qo+QWT zW)42Opo335f|+pe1%eO~q0{B}x%z&Wkkj$-(rbByd9WgI}wiTIt18t`-g0`_erR~(oKuJy8l$kOe^sUgt z32oP}^<$vPQvV1O7ufVK4R~!@D)>7ZaN}uWXre|PmAH}u3*+vC>)OnG<-$y$DJ-gZ z7+2t@u|uQcpjjz36>dC&8!LtjlRk;O%Q&MUQ>imqG&PygA<2xNXH8~&BblMjtwS$QR4;l)|rrzMkhsTdSfBvTco#!z+kJox6pk}S;;puAuZb5GrEwp#3?NFtVpY~ zIccT-Y$PqaJr$^qA(ldex#W^o@&NYORbL~lhDVgi{Q-$9D!3kax4@Be8E&xf#o7T(y66T zY9^bsp+3gAOZpfIwN4+q9}#Mmb?EA(K|3xQY|up^4WXnme9BOO73Za)ITDU~Y57ur z7r=xIv?7|oaD`s)dkPckA{$)yP-k3LW@p$pcIT*dubQLGO`_!aD3|$B*NjHTbE%aI^=jNI&S2+9xDph=_Cf zignu5^C$~JS}49o#~B9AEF$E$;wI@t`ydt~X93>rLKC9H5P@aG!1N~alcHNYweTkU z@6jPfje-}#4ArjE(FmHd5&C1jm}W(Z-_u-A;XMxmVo*rMv`&08@0v0fn3(yEg%#lR zoavnuNTCqmPJ$8Og$4sh!D21=upfv1KFdKHMyk;dBxy2#> z@qsSQhFP>1nS+Jj!zmpWAfGr<=LdSju)C1ZCdMd5Gzzj*_Cli|>Pb>njoXi%Pm&5l z15DBsMFw0oE7W5nl(?NqLB?s?2rZv#mEnose147y%GlEi0dm85$L@bBAtHF_aebL~*QKlkhGi zFglx2zD80+6;ZB6z8<3Au$nJ&x*&WeL6EV zXerFlXGu%n%nU6y(o$oFZca;^K|W0sQ|gL=oXjG|W++iNjq1V3&MBf98XIckd5M-t z#-0k3P8+hX#n~91Kp^W0NMnW~rN+*n9A{996)BQi6tR}vq6|QMI(7z4bjVa(N(-0> zT1x93$TE2%+@g&UnkAbMrqJw*ACC&nu|a7nyrM<&iiXPFXoGstF-5}$6*yGhMQl(T zr-iUVQ$wjejPF|WCd34oz>-YR_@Kd0uXnOdwhu&r$p_+SaScdy>YlKHF)(8g;^@bU zkTOFVSe8eJ9vrr+LoCk`hk_A=4I0ZN1Fc5u@@%^LS62Yv$Rr_WMjlx z2SzV-TjK?mXlQt=;&n1d+bPOzg{@4cy(3>M<}s1Q5iLYXfJ!E5YK~W!t<2|*$5VE= zWH)x@;jcqLXzYI)iysZGQ zdWSa}09Z?~ZaUdr>A^1yM0F4%p+4z1R8*_?+bzx$(MG&_D0qZ-#UZL><}%8U&^c-0Kt|JyuFvcK@;y&SoB;%u zm8P~0B^H7}57R_rNWX{ae9W?K`8ZsS^a7S=USWBV@XX4$Qh5gCnUyE_8z>_vJR}g% zn!2AbT+i%Ym)yVh2)G_xf0OLq%|3vJV;fHDI+jyR+w3t3n(u|OyLDezoAwJ4_?JUoF-w8E1 zFu(%-kCP`Uz!I1lTQTKP7r zbEN$oW{b)oYU->R0B!1^M#;L{MJgGyM+bY8lEsTMMNTj<^IzwgG`v|!kS(ncW@zh- zk|p_xqm5~`ES{PNJ}JAfi0|oI!8C)OgssUn8Xs~mJBcK@(^;DiA47E4!3A-}5q%hk zVIY)Yd(ygd7m7PYqXuxj={MHHVd$n5<7(&i&O`_|2B4Nd_QK5o)smSDtT4_Fo~lZJ zw1xR$Z0NHr$O8nEdc^$|@!sD1zNp{7Aj@713h;QP_p+DorOyyYkc4Acb9iKx>5$xG zT>GfQ>*5f2qEa*7=X?*1up}cP3)9TX{+tCER*cf5W?-J}!BG@FVzJr^p;hFO;Bs+g zUyjV+j>#o2^gmlcXJtQ!r4&3&73{Qvt`)$7)fOvQhzb@~_GMN;8Wk$ovVw^oalSEt z$dpofWG!{CtS`&2OK|SbI@f2>#tX8A7qhP-$}PPpTcWO*oI0lNj->I!I6LZ&4(O;_ ze7eiC3F6pyqonQ%@lGN3K;Ugcyc7^G)etWP#M`Les@O59{m(#@xm{-f81fJm{?G^Z z=7f+%pJdr}@t&boK=KpsV}yB7k1(T?-t!ahYYEhFsp94Ra}$P+#1!yI1Zb4B4}+;w z3oWXM?HMzWIORsU?;1yO$h%+}s06#AR1bixa8^w{!X`sfv@BhZ2bB|!5t1S?KBYrN zL=sFiKwWquEG*nei70IMoa#>sPvnJ#8$l6;5gr#54v`VE00 zr&p9|9_~rMYKcg{kVsIRA@m&`914r*u&jyHPYK#)s=k$Bq^05EXC^+>`GuJlRp8Kx zz2{b;Ad(@PDlyT#k4;|j-YHq#Sjbb-a)e-0A2_K3)J}lf5JYNGcN>;myTP(BA|0o4 zJV(ES57zb3Z(MZpYb-d_M^lAbhAual3wq-|&PZ4Cfz*0P!_E{TL0<{K2v*`RYy8fl zGXUCn024onTd1h+XiZIGBIJ3fRQuei_JG`VW#cO>emXfi zPwA6+yHgKVl-pxuSFY+%LWTxGpM({e5N)z1=$W(Qlb*xkiqzpc$|DCA)r`BHj1X-B zkReqRF<6MTG2l&EzY6Y*?}PQ?D)=g{Fy^9L2QoAVF&us|^P)^Mnyc9tWw^%_a@>Zj zlZ-%dw3zkY){6Z7fH71D#qd1U%aVQybH-<(u|i}4ZMtQ|CE<7(CWqPyI;&q3h%_!Z zJ1(fOW+!UYS5$NHyrPoTpifLnVDwN%ZNoPv-!g-bfkgU%h#K|l@Z{v_YkKZj#*bvu ztzVN3e_U{v%|r=vk5Ybn`U&G8roymcs+Gmr@zu`p)t;s*hPQpV+Ii~;p#w2kZ|h7t zs^?C$k9;2{37%*ZoyL!TCGK4C;YVQr^~g_950QL{da~Xp^DrPGA;Z7YY(|1{@}WJI zY2~eZv5+TGz40MJZsWtmuFOPxwCzR_elR?9_+%oNYe)0N4DKkKs20!?*V&mQ)PQHT z_m6XzVHoCf5q9aUL>dQCs~mrO=53`!!o{e4^7xQ9W&32Z1C@_FKRL{o*)tLfF!J=5 z1<33D)4Y8rv{pnGz@@vKf;N}!J5dw`>p4#R?u3MV$6bEs9itWpU6P_TRYZ*CKW0s+ zEQkgw+pqVm$&vymkKb{JO}sGAk420_EFU=#?(y~~_wY4i;fNb8n&h}~;(YI?ez0}r zvDrKGJ1~oItCM1N4chQTjzj(-*B<}p{0E_SR~L7_Aj=ERf0&u=&dvAw3yVvG zEnBy3-*L&MljG0RnH@wo94Kd^n@D~RQ#HvZ+Oq$GO-f#~Y02krQgZ*MB@b*;@~TZs zUb{)j=WSZ@noUYRchizrZ&Gr&Y03SYl)PfolKVC(dHJR#uiB&}ImkA_{N7DU?%K5E z^EN4Y>82&G+@$1=O-nv^lakvuEjikxBnclk>CteLlH`5dq$CSc%>hGW`XqDECMB=f zq$I&Qo0NRcCMC(xwMog#Hz~4A*cVShTzX!w~S*C$10hgYLTxW_iSfh%S-%nolYnPT#HOSSX1 zTd&LhA)*6)iyfqq)7hVuqpQ+cK>w{$)Fa}Q&gE6tx6bAWaZc(P5Qa9W9)8O?tm-xy z9bKK%kIBREm=CJ2-Q~M(Iaub*+3ge=oQXP5=W!sfpCIkivj==uS_buxrP-YJokc5z zM&nrMH*15_GBhUWdI3<025{$TNt<7{LDdo3&aZHYUa1|ehr-%2v@1XOMl~TQUZJp6 zJ1sq1#h4WNZt5@_zNuF@HHiACT-OEo;f|B_*3)lz9KrW~bI?=7If}=3)dsq|UFn`v z)jFYkobsGbMPeuiq!i-4JGlt6Xkp@2-HRy~3gp$bt5xj6*i1K302l5J!+9n`*}ma( zrHTF7wWGb)w{Axd@0}iP`tlJ*yL#xI4xj_?2!7PxQ|~T6|@eNU3$88+9eYuV~?x42cBUkf4TQwQh+UZdCTtMmF0qi#91f( zDdsD$tom;pWwp!R?9V{EM%xa?9sFF$NqzKfM{P&3k_WS`c`9e_21Cf!_4K6`Ot3IMO2O|x=K^-h31u6){b>$CKI1I+lI+mh@ zf$TboG!+SnF7-0?onFHwpiF_&-c+s@*#V7$&m_8 z%l6vKnBb{hS_(={#ZnDOmoPB!*|l1%kvfy5^?H6vlKqh<>IIoTki(oc6+>1Kl{TTw zOtK|H3*7PzWDo?2si=cpV!GbSIS7Qve4ty)hvpU}7Zaf$+}~ba8URVNgBL^*?B4QH zD9H60pf(N{WPuKGWq(L}y)wEqNQ(1iS4RtYgRE+FW%X7qr5hK9ee_@)VpidoiSs%( zLLC6>x+7ItOe*Ul@>OH*CV&#I*I0o<=%%4?GS7`O5U%LDd`{XtX9;6(i9o%4fru_q zgC+@x8vIQ+)8E;En}Gf=`gbz`!GjU859HOuZ$HO&kA$J>tT&IJBw5Wt2;{1Jhv+~f zOs?jTigL@D<%4fKUe2Fc9`Lh&x%;+q-|^$iXTJWqFMsyL-`)3EcIWbsJo@&x{>GpF z`9pu4{SbQXVsG{?A2^8!*nRTEz5KlRx#vY-6r)T6mQp zTooXU`p(Mv55E6;EV(EEo|;Y3+;z4BsDO{1W;cbns0;Bbr8Fq#D3CP;xTp(wwbmtq zc=>Ce{y%=?L!Y_#qkowV_b&g^&-{nSe(LQXI`vOO8J;QUoCqGLg6>fpJF~nm?gsUp zxX*AkpYgzu;oh< z9MJFYPL#&5lvj4p9b}*y;mI8SB5U8*{6(@ok<-Q9{5RhUq!IK+N3-F)=o%2chIAb6 z0`H6EV0d|P$WrCyDSwI@In{ca+QNJ=EzdogV5Th8-5(ylg$5^+dARgO&s2=Bn&fSSVYiSrDs2$sw>uzwlr- z*oXMx{f!lDS&sZ*w0-x&jQyZV)WuTR&y1X~kTVXN@dUVnRAGdnhIU1Z)$U?CATi0k z;iH8p)PT0gnF`f;TO-(8;Y3M*v295UVzUfFi@z@)?&+0#%58#}@(t0teFT_YAM^`N)x|A71~sY0PiNf@9P07rD_J`~xmH zjB9*HrtVTSO(}XFxpM~gNyc179)3QTXK_i zTCxtS`$tNM_6TBZjQLhVHwiz@65;Z93^*T|-lOb=0FBJ<|3d0wkD|m?OUrFjLRiM# z62T$N=HMR5t9ta`!3zHkbcQT;lHlSViAAV`x!0tn*3-7M_32uA;)0gK)4Eih^Re7d zv(@?LhE8iZwzMD3z#+4+1P!y)ClxLrxtYgo4Z{`$Gq|%2lCj?tl4af+$+9n$J3(^3 z4#_o5Uw~vr#{B^f&?{k&gc~VZSRZI$<)i9!bJJjL-KPkNeDHi{njwvNr@~V%q%;KF zIKwyMVBEo39w8s5`-!}2RQ47H4;1Z*t?-9#;w?29Las zmu2<;{(0G4Kk?GE4qb5_r!f$#jj@pYE{bC=W){4!Wt&iFt1MtOZO=$_PWGwqOvR z&YgOpvlNZUADETV>L)XT;gms~hmPeHraco!jH#{GP0YQt!2y2K#$4z&B@s+QHS7#Q z7O`;X6~ah*z-n4D^rw+aJdyAbN|ASSESVGqNMlt1Kb*w?my#;pRlhCjZ5}Mq?)fj) zd>wCH8>!!3$jxr_RxS6dCz?;dmU)94NG=M;1(4{10}@vvqb+R={Zc+;{crq`qkqaS zkb-4K;zD@RqA(v-=Nfptd9=CF8bPQ#1s=`T0w|sSy52hVY=x1#cF44#j79{6K5qt2 z(H0H_R)U#IrYgt;G|y$^*Nc(c8Y4$L!&YKlsAf(yhmjcmgS0NP&VLZ%)O6#u#jD^+ zvZhsq3C!*tX$2uR(_V`k^b(_r`LQqDg9-gt*-0X=$+b|ZCWu$f1LVM+5c$@N@e z4nCFlB6-w4GU_H|^GYEfNGI}j#JJT$pRPF!JcwbSt8IlnqT4vlE3~(E1%-?SWuSDp z0F9Lkk_g9AF@Z@;>p<|}GNY`gLLdT1*#zF0TBuAcc%v_zp^UoWUt1_0Y!us` z2I7~~$!UHn=(SM;%Zpt%9@ z)!HA8-@p$uQTkNFT`XWgBiTZWJn_#A<2$p3w zr3lemGfyjYwT(3JeG9k-p+>X0T5}OxbDVo%xhAJz+ZZUZ+32Q%MtMMah`ym*amEE||olGpl znp!010{M7g zp8d+f6WSXI1M_zHnWCpxj`8E{z8py#LoqoK6c@X~x%L(v9$4oUB2U{t;-FfHS0){U ziG#Dst4H43X$p*S%;WPzpMti@QY}y4`qp_L8;mr zEX$U>Mu8AnCr~de?yMeQ{|!72p3iZ`LFU7&s9%pn;&=}7IW!h)`9!_JMaq3i38-mlWbDFpC{0Ojwq>StH-!u^)`j6vuO7wzC-uEk-9_+ zF-&hD-pqA_1i*L(>Uh{k``(j{cig&n^_U8XJefC)n=`{;+&NFp&G z;_N7D8ib|j6xo;zkTPB?B}#&biVh3-vC5YY(yaH%WK}E{!s8(MHvg>fC;KjrtiWZ{ zX0O{kAmR6$t9}s}Z^A@Ca&^j#G|c?vo#D2fa)3r8T(j!pYz7`ul<5VWV*=p5$^k_% z*gyh5yC z(5`c_Wc7GXwVu`R2P8!Fob&8=srXt+=!T%0$NglArq7F2YJN0+m)9M69~j@e{>*!9 z$sni4;&=J0pZbGO-+T8Le&uV~iR)Vrr)Pih$3OPfpM2ztzy4*Po#UC)PUvQC@LaCH zfur{J`dK(nkw84(-vW~S+l$PFtPw-GpXyGqUdZ9}S9y0fzhiXOiP1g}mWS!~Q}bxQ zzYp-@>gDnscZ~L(7+queYxrWnXU<=JVzi&Hc)9$IoHOsazsB#cDfgE9PDCk=qaW=} zWuk)pROKkFSoG@m;yEc}de(`{mW~T?wFj%ev`1M*C0r-QMLb zbcGi}M*DYoNi|$kQpV%*#J$7I9lh5=as0e$80+WX11}Ya;EqlqPkolmJ{-|Wh8>dw zX9e#(va=mA8;tMns^y_Bvu|YTz}cJlmpFn~TM)K%`on)Wdzj8_S}rl&F=wQ5xDIsK zx=ntc#ZA2W{q7=wS5XoyJ`hk%Mv_Zh*8B$H`>pa4Y(6Tv-D~PvadYE5Pq9m5VijkR zb*leJc`WjYLSXSYt(tKR3kQ-x5T!yJe!GN5ap};NOuY>2c=i@kH5L(9S(F+e>Xwh4 zSYCU}TaTYSb^6@evAc$Q!3qITa8xUF=ib-y8HhsOxtPbh&i+;x(w8|$ykH#GS25cA z+L*X(EBF3S_kuZRZuD$d96^QCPQQ>xZg#2}XZZ(5JIXFSb<58*B%`Znx#~vCWS8)S zI-8`yMJ(F+&A*6-k=8#{?xib~yefe(&q>2 z1Fm#-T8pUY9_G|Q@0CStP5^(1nqB_7fe68a>|pIB+Q-$U3`!@8@zZ4>T1O?HO9*4< zoTydZ&I{`ALFoahX|PwZHq6!#!_|fa=Xp7MxXND1DPOt`vE}p{^FxADex$S3;i0Do zGAbSgHczjhe8yw;Q}nCefJYLMc2a%nuvjf_m%9fa!sOVU>UYR@@kWA`VOugMt-3#8 zpuN9loK3LoT)B;5fYjz!(@D4;eI0F%dh=`1G(~h-xo~*Yg{aI}dLija^qZ0%mHFL9 zVy$VO*ora1QErOh+7~A{=qoR~t4FBWEg^bk98EgsFqdR#IywqdVOJu?1&A^3DOVFC zT+xN`T|r&+I|PA2l2Hog3CcZ?<;`ngy}%N(^_ zaOS2n2k|PuxDd3ivCd$B)*D=Hp#mEgsfOvCt+0Sj?i7CK`MqId@7w5`8)XhtruT^s{W4mwXhIGq8Y6u5oAVQEu zt$~fJrW-?4J0oQCmQrE5vEg<;&A!{MsCx;Z$lrEX2ywL+>vs`u72B0W?{;OPlRl*) z4>lvy(RMwQ?t8R+d6^@}m1Ar+PK>niqgpnqanpt#qA2$j->YGTnEYOB9d5tJVLr^N z;&oicoc2be-)!D+dg~0*M8Qp*M8Qsp66L>Z}1nsK(023OCLmlE3ghX zuSdfAEj+0V230PIPF(}K#vlK^lobI%4+M8g^|1h3Zs(j91emltzV1FlBOya<6cFd%zTrnBo0kyT&9h{XUNJ))k&&mAz5G@ zS^(D8fqaL%FKCqn_{Kn5z>VMTUzaX?Ih2d@B+@t_X?AT0nCu=&0W z#4q3sVBx@87+U}~m;lETKu1CT3#sI0cnF^B)xaq<@11~W*k}C#h#Ut^DhQI#-XzkU zVSg*Ap%MIJGAoV%0<$h)-2J0&V7?a^x$qb3;M#-yWnlgU?XL}vvjz>4Cx`^Y8CW95 z76u{+(83-NQ}97H&@_OIEf-q~bDj#S7Q`Y8*1$*&CcI4xQ?H^BxAnZc1#V}wi75?! z+&It-^YP%Ofsu9qkOQRx_W8h8jp?4>@y=fWEPkHl76>ph4K^Eq-Ujz|1fhOqI!lLq z4g`n|LlkUZegSSJL@dDU1rOLbc%6h9CIt*0K$`+WnT81E-xDmDWpn_*YOYf~wpR6e8_v}q60iPbj%{bpA3bRrQ7e?+t* z5$r!At|Aele?$Q9BH#}otmg7SSYqx?&tH9A60{fK4}I_-s%JT~6$07|7y}Zb5y7O0 zz72u904!5I&oanGSecn~Dk-2_;lULdnL+Fyas;d(hjX5N85HJnpqHTjkJ@_PDG##! zqm((h3IA7j5PN%esvewO1%e7ZEYO1RL)D)<8X-sc2ar6R6CypciRNw;{0l3XYv853 zU!ZY(-CD_1|~;x1d}c1*+NAtYrWW=@|1!wAR?Xdj54#LxdF4ZRR<{HyT0 zeWkj_oo z&CSir&DSm1EzB(fd^-+EF#c2m1TF#d5w)2U767M(D*$9CN&xIwuq*uqFPw`T0M!J> z4eb0zse&Co2cL66ftCr6@K)n;y$sa7eK;Bhx&~VMTE05E98Fy@;k*NaH**35eAwVOl55CTW^Xfn6T#^SBwDGr+D6c5utcvjy`Is6VlDR7luHwTJ*emGDTfpn&l3fRNzue~BX_ z0s_O47KPauep;PqB5FCv4}7r+MFaah2Iefn#+u*=^Ao(oY#kBe0V)jIjqS?`2n5X> z63m`&2!w}2yf%RL|CJ9NqycOIr$Zjme@ON#9TlVz1-mxb=gZdt=gVeGjR*nlH(xI3 zIR1cFKw3I3~30>uWU4|W5vdviiL z-T@IY?2ygeFyFwCC=i!}C89{+IN!Ja;QugAln3YopoB_5BMxX_p1`3@U+e(V;1C*? z2JjR6drIFRxL(5`72KNf=-HY9FT%_ms1tt`2CxYayS~762*~Dsi+j)zvgeWdw^dP00jJBmzlpX!QA@AN(P&wD-%zk~f%@ zqF}6oFUdiR_o?z8?88jyQ-#$AA{M+K?|49XioMZWediJ*Uh4!#PV$W%@JNlp)G0jB zHq`^J+aFvBJNme1yBd|;Z2x>?Xs~sMaL>L+!JE#=Rr@wq$>>s9>mHaH39Xs&z8da$ zn?5?h{iJZo@mtH&gr^^ZzuX;t>haA1_v?B6sd;<@BQ$-0zytjZ$Opd|HJ>n!AJSX@ z-qb#-L7dJpxaaNIR;|8P6b;~(H0es1#^0uK%5%6YsAU|5pQ4D&GcgZcW}&)d@gqHZ-GYN1buRy_Ur5Wy>-Ub-?_6E*Yo=Tig= zbZs;p-R*RRG&z7^qsjQ3D#^}W%9HOAoP5YpL&0s;gJqLn5M0-GqAI&`(_zl!B!b&B zMixX#7rfm%iJgJV3v!4y*==?E)c#2U1Uuh7=N>~gN~)QpAXv}or>$--v8jDh9KkCO zSn7wo-JkP%QU<{-hDoyRsS(|%DP;tI$!pBJ(~(vvHl>Z=!Ad3i0Y2?fohc&(XPwbe z`st^C#(v5I!HbqO-|u_anzC`q2Eh$?9x&xCp508HT7%&IZL$~d9^JUNaLNtADJ5R9 zGJ%y(8m4>@{I%CZaIJ5}@kdjE2$uVJ{rTSS{hvptA`pB&VWzySs;-W2dK-c>thWW? zJiNBcPNyK)LH(Mx=gg(HCDWM*c9d(_otbL4f8BI0f-lD@hRQ@u^hHb;AUON{;Q6DM z`%Yv}7b7?~HX^;ldZdWn{{&W}YHg=oTvZQ{DC{ z@0kGvzhjd2_z(s!#m&4&@a=T_5Z>FDG7rvtL9jBH(Z7~nfA8$f)pJo z`Uvl%P@!&bx8Bu^%XUCCA{5F$;Y~%c{{2Tm$Or^hIZGd+%gL`invOC;a+m*kk@YR%N=KCixv$T1Y^3AcioMQBOxL9IcqA7X7Xn_#)$_=Ivp@Y*4<6UqU$ zdjugL1i#%C%Jdn5dgY-&1Y1Xq9y)kD=$IK4f#B4Qv9(*Hc|W;9+YlTj`YfhsU`=fl zl!D-Vu)f=$n7796H)J?zC8A^4=&tJ*zJ7)Qo276^VE)z=W* zxc{9n)&{}W%~ZJmv$r!8*Y_HUSux?tdQ&q`V@1 zRRh93xIv*2F$#*mrFja{z_31)y&T7(ZXH@Q zUzQdadVeoF5Ma3M`RlI$hDYgnJPNS&Z}|2c{fEj4RaS<*T3H%xII^O zcQDj>$*aqw=IJlb(F21_WuYC%E+E)f{a>b41^S_aBCM1IaiYOFNH?DctQEl{nyRXy zH>^kcusv6X!WxaCVPtSr7$;Og(G%8lIc!h6kYKLotg$iMo*)^@PH0vWnm;0zBpYal z>=0kJS4d>APq-mF!k^0q#j*p!*=#Rx3C6R8AO?lX1$lr2sumn!vkwl~xgFNcjg9}f zE6+f#Zv>ki7U1U};W?*u@v%Yfz7d9CZWM_(WTQ}TL0&|V_6Io9!I30PM4sQDQ^HB+ z(gK?KG}?d`PHQojW#kO`l?5(|HJSv3!CLQUrt~+HzF*Oi$X~dXUp9M zm|G5%JCFAR%mg?RD7Y%2VIi9Xd;o8FhXjQJqZ`;yY~RRWZ$$I;3E_sbgF_;~#jr47 z{shLb1A;;Wxk11@0BjQx5&;bV5a2!t^8sbqfIRjG|b1z7Q0F)a@1KW*Z;0X62=)W9L zruU11GN6V2w*m8)fQ18C*;xXTUKVv#RaJHO0=}VOwvjP>t84}MM3Eik4>~a1DzN?Q ziP}ddB6i+!aD;ik6dd99^H@7854>W4c|Z4XKqrU!g9DqYVY5O3xK9?bVN)v{5QPKY zVzU+CB;lYW@X$O_Js>~WZ}AEoVZP)QAtF8i5;(#(^?W&S4u6t1v&ny@Hv#nUGn%LW zJMTq=&3kV`0WI7IaiEhcgX7<5BZC8AH7udR($MARq z`<_sE2*N;EG>(S{O(5{1`EdMbAuJIhp~*NA5(T27>1c+aIF3b-gk+!%*p2AZm>TpY zbO-t#`k}xh-Y#@Ex)*wZdx;*x4x>ldW7zNLADEwzfc&Dxb}O@w967o*WAFZBXByIK z@I1V_#*3XkcizKNM0NF?oa4(+oxZH|g1mi4))B0r5Q(g)p>4QynU%HON*}Jm{viFceJQ_zv*Li)JoN-ulC|^}gUcvEjVbSs8 z(z2?COE>WR0#ugalBKIol-|Bm$irYt%Pw9r^nP^aW;2$(KvqscOW)Aa+Q#0&(P`~E zxAmUhT;Gl1(XsJ6i_1?}U+p|~IymIY-u2R3aTu%$#uo#rsU{?`FdD+*SQ%ak+(MiM zR!AwK94~{F!7326`R&XT^>}G~1kpuH4KUsWUUeEy3L}PtO!ToUaB5gS9$p?3wmepV zR~KW5WAb1Hcp+qfVh=muz5)w0aU^n6fF}ys) zJ)X;XBa9Nh@`V#Zsmr!d1SkT|yo`kHiOVsmX2R4Idr3S#;n70eVksz81;fOm6HO#Z zhBzp(Qz_|n!Z$@5EFTt~OtP>sPG~a1Ls&D<+wOJefa^}*N#z);_ACprvicef=yxG#x`^56V z8Tj~7G0@PIGXq0ndkL;$3(a z;+F9#V*ztuH29UVOguj(p%PqD)3C)P6cdav!WbhSeZoRq(hNzIphi-`NC`^`CuCxi z@)-Qo^jw@8ZV_NLQQm|r(h&j)UCcxxE}@VA(-DjwZ=xF|;XEOs9mglS2*ZchCoCff z;3N1YFzc``ya~yo;(Rn-8*IW(d_}PU9jj4NE5S@CyJhJsPpK|AqP<0+i)2*vZ9-h=9!- z*dOT=0IOqcuNd|gZdeFPiAh9!q9_asm5gPhcH=xz?jnUKGMz0c!1k0JRW4kpq|R0j zIq^anUFNAG@zYZkHOp#iLrZ{lM-ccX z2`{7ovIn_haDcIS(ELcQaFyWMY8Z>74M6?oOJPBl5EcTNLj-6Qgys<-ctL1hejY2d z7|0KT^n@T#B90%D;e~v$5FS(t%|K(p;wA9Ue9(AE7y@mNVWB0!W`c%z2oRc|7XqCM zibP98n=x24FNDWD0gV7^%LC_#CgAzdkh-J>Rvlm*q`)fx7Eu5Kj6S#s5@HMqX!HRL z_~;`KTp$MBY=VNWOQA5kAWt?5AAm+-A+Ywt9u4F#s5=9VgYwW!vLGZ!VDPJA)WN-> z(ejWvXn8dFWIh2>gS0@dXfzJow-TBFjlj(Y0cIrv)fnW`5ADODFyJn+3c#;W0`f

*vHLzPDU13FVpL`e%kS7mhjFG`XgvDTfU7HsSDv$>TZc5N7&|wHepdu{`K{QB+ z2ZOs#fNzElw<>s6Fdc+`2kM3gM=`V`0gl-Kw+Fb&#ej#3L-9iBaq#2;M?g8CJS+tM z!yq1cQg}2*6*M@C2Q&z@ng*%@a@m3h1%NgO+y)l~p}^DC#^K;I2rrBRk_H7W!L9=F zC{;8aDCywi!2|-DM*^D*rtPts1V{*?;UHmtZHC|1BKOx8wxBX;NbRI1m+pI zY>R;K&_GVi{Hz#ofCD=#jEhWW&CZ#If$6jW5CfEGp8&+}0vhzcp@&UXlo6O$W5IR! z$J4*aY@H@D^{b_g%w+^|VXGC~+uYpU3SbOtg8z7T8CVfDg0KWM|9@2SmVgkXjx;Qo?wYqpfJX?F#Q}9%19o^+`sZZ^ z1q26d@d$|q;|C~Jf3DCuPEv#CV&KMAXA@C=pgcBc6!1#X|j!sV8dLIt-^+(;(YoFkRJB3&Esl-Vas|R4+5<88x98# z4EEB^UoQq2wz%f;Nq~_VuCVP?K)4N@fZ5-0IU3V9mc=smkqGqZ`XlD z`4fH4zrZ%X(KmzZ@NDmV`5ys>wTOAl159h+sQiTq0RK+^4bcC7|FCEC_v;dXUi`Q8 zB!FRUW~K0* z>c)e82HpX|45#68@`!^j{7z2f~73UM~Dq0Q{$}|4+L&%y<9I0Q3C+&tYC3DFAr{uLYdPYRKsE z2lkkgpMTemd{n{I87C-Q)i*FQ+}|UN8^i&+IG9W^25ZHBXl2l#i5NCjv& zziWtMf5FZG!}9y@G^p?NTKLz-A4SFY_N9H%@H1Z69)qkU(0T@pI|Ju@-U3rQak|=Q% zA~Np^&)rIaeICQ}xv=&Lp-k(a0PWzO68q4>KLrN$)bf{GD1ZDsm%%6QGtUSd`g!u> zeOoW${)fE&(t*)iN|epT-}UPuJBtk zal?XLSA|xe*zRn3`qH6K-OTir-M%HJ=a2RoWUBXP#ZY-pkR?|^DzDGvcF7I%tCEU& zSG6blpX#b_xZM4?Z=2X#!_s5gA3i?!*yj`TGJcABu{Cx0>zI5zgY;G<@n9b5yI_~e zm0AIAQ{rYT|0Y@Sy_=qE9molN3*|2?O3P27nO}=jd~xElR6f;d=*VddSx-kp_)d3f zz`{#Adh*WP7oW&kx1_sIPin{hhs&0Y3(N0e6_F0{*|s?_YU|d?H^oE zC$bGB9LD++OJAHDu~>D6Y;smb^rYO%yQ^*YtldzWRd%q+S;VwBuDX7!y?l93Z0~sI zCkbtP?p2Sr6RR(Zw4b_R(j=O5;_X43hwp^igpEGw9=a=%z(=}X*c&c*EZ~561)9cqURu?MjgjCCDryXs2H@(7=@`N#x^T~ykb0pq>2e-?B=%H9$ z%ewy4CQq}p?9qa+j57@`?ln65jVoOx8=~IapeT5~jFn$sk>_M(^K;Mk!$iTu{%t*N z6`#h0wtTNI*~eT=Z!p|abjfvHn5xR&6bowm(x0lW%v+~VWo7I%JB%SM<=swsY-m>J9Pg6?r-KVTS2(QH=q3s696h&EB(KKPZ)vJ% zQiga*QRDY8;YxYtg$;UDP!~2i6K8f(($c*n^2F+wvC4f_pY6k7wHo4eWP`;^v6Y;Gm2TBCnQMSDccn% zSMO^(p0$RbnJOw&*?GUAt*o3Uxo308sly_#Dwz`NpY}d0xp&q1OtqKf%AJ-gR#aQY ztuj4SnRQi+@wn*0qem`5?Y(ziI+bYAC|eVsMv=BU8})Y&RlIE?5tTYTU6R&+YSxuI zQCW^X$GBLMu-spCeB;ek@0=VD@22ceLE%5dC@r{GD&6-iHiGEHqka5k_OpA50{1lb zPdYG{27Vo8h7Xi#8<&2um=dNQar*AiufTF}&s*)jA@4EK%X!xY%oEN#)lc^Oh>hdS z;ibhb9hP>N*1GmSJHg#eWp7LleEeXOcUAvOx}=0D=?>{=l|q{MQA;f5vyXSa82=N| zD-Ij02l0n>vRQAav}4re?ZP`gzujoPJ8XbmLDIXlDca`Ad5dM`Vktq;QgILOYt3EM z77WhO3UlSqVH)GjosCPM>+9-8@dQktqLZ(g(v}_bzUH)cZJ1DA%_s4@eiio;#i70! zk?~FBECqVWdq>l|H0^lK)0}HUJ!$0N(g1WJ?Yv9$F!f`TlVblkx{9wRi zQoZ=Mv)`UjF5TP-rBg<#BKB0@belL$ROWr|f8xV7t2J*ELUI+(GG!hPIGlIBWk_^9 z)VP`LMor$#GWU#GpkD`g@?}*Yc$)noWn=t7dmm@_7} zrME2f_SAH?k*(VUzWLle#ZL-eYrnEqSYnaU$W8m645QMIG2u&c1Vr1~KI-+x@ZL)O z($b`9Vl48+>`mj>rm~YE4FX5%eOo17m;S00Ml%hZLlbvH4$h+Hg96hGTgvhj0I&w6>P+R1(I z54}?GTD2p)>cxupM0(`ti*Ncuhlho1P9Lfi5sOiBVeVDwsdfnKlI?doNmX@RZYg{v zX4lhe&+8MHt|4u^$ln{cKbx9ckeAs(Xb{U&>!Qq%PDE|4?&}H*Ql-UYJ~y8!T{W@$ z^O@-J%U4KQRa5t;mxwoszpq~>?d>Dpv(v{+Q>z-Hk9kn*>dt}AnVg^1G&k`A(@q2pkh-Qus-Z^zW6Omb2ECutDRjt)`*Tz_0tGE0r})+ zT*YPKHM>UCHhvW7iMYwaZIqr4+uVI6Q~FxyGNpTT>?ON)g`4D=E<3+n8!rlq6i&oO zM=V;^E)&~Vq1YzKTdz?yX&Ub>%9q%lEA_Cu@TR=XE$!P$B2jmv*K}NH z^a~LPGMB2-lbqP3@~kBHwxmnw&JJ7W3DK4>mt$8w>x$px7Q3m zHND@;Vm2eJp00?7 z#LMyImSxsKlTX*-n|GVZi!v8t@JEB4)#;~?+$+srEt2#6o>6^w-P3U zrRql0EX(?sb+1y&E{dX7rCn{)Sn=Ry%c-Qp4s9ZLYLDMBdqY~ydxCziHMLB#I7fUj zTIxvz&-p$R!y^t1dqL}VsFwN-=gIKI=75FBe0zI(A_>byDR%v>3ZC%+oAVK4sB;&t#D>oJe~@+s~SocKWX?i zi8p3B=4GC2$e4x$|rU$X)QC@VA8+4Rz$ zvpUKSy`%3uezN4)*5jw5<7!IS{n8>QaVL*$@VFlG4nUBrCt! zQTN1&MwKSq#rtlHv02dE^DN?k$VYx?fG<=fyK3Wc`Tc9wNv`Wc*&lO}5x6p1CmBto zGN?-rJx-*sLZ?4{J2UZ^NQv68m6C5xzdG&Otf0D8kod`b&xaehPq%L=^9jW+Jk4k- z5W3Q-=gJpyxoFcZ=}gMqSBu-jCoBn>dA0J7#6pSHM}4B-y9)JI%B~S`oUmbDjkot4 zJH2@8<@%FdL=GQS%hx2~b7T9yJmrz+;mk+G)uDHMSNm3uu3cX3b0KGh`NXC8yin=_ zx#fYM&;2-&LA5${Zf{R#l0@JFMUQ={Mx@B%&5_IeF3jm6UJ>3Q+h=65Va?=HS9k9dPg8_?PXBx>-nvVD<7sR0)auT~BA>ma z=?}Z*E`17q)hXB89@#XUNKVTvZhlKYTHCF-Y{%i!0@lV*d)#QBsM4|#Y!N-Jn!e-n zaSiQ@9u1mUaGcDA#b&Wd)fM=Mrr;B%^Moq(#U3_0~c2K z-xf0xKAJvbcwfk0BuFvFWQ($==z-n?yORe5BxR&>wOXCdE)(e&5z=LKi=MMJS1gRq z)_+?2w0O65pUBSB8_Rs< zw%StZaYA*K?9~-&YM)6Y?Nx&1S=vKX-aF(oaqm`&-4NU~ve;d+B8X2|L377r>fz#D z8C*K!k~2Yot?+OQDc5it+33?3Wf#F0w;x~lCT6~G|C7lt*T-HMpACK4;z7fZ$h16n z-F3|Nak)f;*Cgz)_?XP3Q%=rvX?~sTuG8g^Tk2yUhS^LP&v?zPQHnmf`Te;Q zSsgdXKPdNJWKZ;~@$DZMl@Y>MVs3g65iM)A8rH=w$)Gj^#V7lECu4KWd9rkQ} z5o^KQ3K7;fL%4h23fF2~l~`2FyZvj?2A33J>F}>j2SxXv;|cseG*YGexORNE&M6Tm zXMq==m&94gj5kOpOUFtst?aV-I^*;3P0qey|6z5;y&ciU-)EA|N5U;WII!g@5k-6R znr|0$e9Xpt6t^pjx;0(#e)5A2eHU77**bj4BMYT=pbz`_qk&b|0sn_PJwo8IQBtdGyuj zv(lBM8pHlAt$c(s^BMiuYA4Q!eNi3$X>iQIAz)^&#OD+Rn)|2sYCmgRBDc>7pBcwA zkW}9;f4y7kvGkIe_;b<^+{8bv!Ka!gZmHwW%c>w{H0UN`DBwVtGXkRy*A z{rnN~*EMuWdAGZm%FoJRe3j3?u>R+ZfJ+hfBqMs)mi1}O!?VjrD z{FXPk>9M}oowUoWir!n%f_r09u5D2nHokw0?nDhy*<{A)Jk+`~O{0t;A}uX?+HQlt z?e5-kz_fn89&%nksB@#3<%iAqKzURn3RT9{-w0sEt?ndTAxfWH zmmiZSTO_QeUVpZC5bMa2& ziPVw3d6OnC^h!u;H|5Q}`#In2^^Qc-_{EoG_ik>8h!ku#Q5O9{if+~z%#r!_;iBbn zzZWjo#Z_>w^(HLkjJ5}LlD4PNbQ$b6k2|qWArFNYz7EasC*y;f4_LO1#eFzKGPWZ} zu#R3n@&aFU^?-9z=~S^$4*m756}I;iP|IWT-`}z?c}zZL5o5`^i|Wa`S!Iz(P?SjO zR0*Q@Xo)Pj=Y06k1Gebh6EZR}%f6OzODnCZn{7p6iwz62j4wxolx&Ggcj}k$vETQ4 zv4)R(X8q5bcygU+{fY8-?Yqudhbr&Cw^8VZh;wl{S%fA-VlE#WCesTg``(NQJ+(;g zr38Lv`udC#@)11%{a;;@-|iBq*Ppf+}k@Y9~U_+WZk+_0v*vi-9FO2 z{EOOCM|#nL6zvrgYk3bvu08Ne0*VA*U(bbd+fca>~8KLYdaWr&zW}At>+$ zaz4D-5|BJZ&X46X0nID;9A`3Epbm&WBOpiO#w8lcG&voZh_v;@oH0J0N9zJ=KS!m$0c#giX zZM94_A$@e06m9w;9TwUFHky)HtV0QA0BY7pB>XNK}P5kdOI;bVgb(P=jjN2PP$i;L`@M*>A_v(9k5!Z zpVGc8>w+)alaTpe&iI9j#$vbY9~Hgf)fWluOVOTd3ws(LtZMn?hnFlhc;jg{IFo@m-yIw)sAXN_@gROW3nUNKid? zA7yz1^KI)`$kHcY6bOrhp9Kci5@mkAZ>NzpwRYZVqoi+tN%>$f99OV^y79fqBxA!H z22)#RRfm+s%(uN|#`gUh_C(VlS@ZJ2>GAVq+kth9eJFccM;=g)#%cLEdmjF18_PJi z|(0Ocd(NoDYnR}L6zBeqiwD{aKFz`m?x!O0Ub){CV>!{VAS?dQxyMETb zS0}k`sbLm`mPE27xvYy|cxo>=q9xz-MQOWvn_^!Mo zV~TXt`{+PLYX;eWY15{1k8NFPMlH{}8dT{`U7AAsqQo-G_zm`D2K*2=Xzo+EthC$N zs?_}1w?uJrYdt+YFfy`AYDie~zshon&gCy6i{CX;*oO9oF4TeNMc_ zs4{iOCmzuAvcP^nM5StaithQcyeRJG7DwDU$dxH+wDQUK+cF^+10AoEUPcq;`4^D& zP$SlFTa>h&7Y9+@#Fk8Im z0{6`^k+3nV6ZD3N5X}yJntgb96{E0{^XXLPDT&9|-^6%nJP;n>k9Gb) zR}tyZ_7S}})gtoD&aGcZMwud_W~kzZM6RzeF67e~|=utHnhc^k-k zp4_WzwOuuck+m_X%kZa1tzP!Zu&Cs@h3e7m$4;5Cc7Eo+dm}}j|J}Chxw2JO>TK>F0sva__$Pe`}+ryB3M z%z8IjBrOOb8lBGDwOC}KPkSiIERxd6=Fgu} z7wXY5p1w`pvxk9wa|bfMOtMS9ZEPZz|1w1M9z;F5sx>Gb@_+pR}aU3pv#5d zqS$<{6iBvddfMMNe0WrNiXklee&}AvMB9gqnx=8Z#Y7Q_Ax?99l+l|JcdhVRTgrh! z&mZ+y_GKL3po7Yh;xMKqCcbl3w+7qXG(&v}xFG3qHvi(xq`=LU7Znrq`yYtt6(*H0 zF|t3mKuNo>xwlo+!g9gBl`960S8AJDMw5O@EELqeOgbI>`rTCpm3MF5L?UyNmAlsQ zy^#AD_Ps<_TXb(kaec-|fdipc*_(ycOeLPSHn09hBY61UW|Rk99wv)yjh3$yzx6eK zNg?IN`(yO(gHD_Jk8k_pv9gshUYfy@V|S$my}t83UxTGSVHrhMFUuGvctYRMaWdQV z#He(mJ*TAk&tI!eKCI%3^Xe7nidBu{MJDXSk|x)h#}dd(yhP8oI_nJz9bcrDTd+&k~}4F~H2(#MX$=?nb8Ep5y`H|&O?jhG*e zD-*hVD#NDim9%z>Sme$HD;3*JY`fL0?(QqpB$0#O9}EAP!@jfiiRnelc&bx+X@FM3>_k zGyUdzegnY=cgHyo(Ht!$`+{~RU9}sWVV>S^ud1_4#F6|GwF zyD)YbeSpI;1PLs%!7Ah-!uA9jC#EF z()vAP@9W!_IzM8m+r(wtWt?%PeOY}p!>NR9)aR}xt$Os6SdIL4)k-@$zq8KO@`+`m zUWY#_7&=R{{G6oD^SNa{a!2R6q-@q9LzRlYg5jgq<4uzxvJacU8(p=HohvI2pZWZ= zU3EvexMx;pR6i->*vjbGC6{$tNfR|UsIFZq8QxuP*~;dIw5aDgPd=`g2%6;AKKt!K zs~BGeq!`t4@kZ=+ROglychViI+2=La-|D!ZM5P=YtfiXdlhu_XxXHB-P=hlP(qe=n z{`H6aCGXp(utz-{#);NDvc|8PmEyT2!*BJZDn0EGhLgTK1Z1AQI7tMDID2!I^&|<&K-)iG;7Q zd(b1%M(kK$u=9~z`1_VG#FXVCKOZJ-Hvh15EPH4|gUV%y8tz=MCbz8WeK;pNZrv40 z2_N14wQf7!hYuU^XMgW{K%TmO@#Wcyp&V&}yJybbPNz@RD)kobm(p37o#-(zvw{C@? zt1HP-1m_3oq_5Z)noJjAKj-e;TgrrYR4x30Mo+Z|CMtr4Q+l_!VlOa_Q3X@UmPVEq z(_Tj;Nj*1C?ZZqJuUoP1(dD11epI2&%&itib{fyCt*f^9U1PTG7`t6Q*!g_tDZ9$b z7w3tKDwPwclBxSu)_HYziw;q&yY*i5`pQk7A2Ylo*vDpOy_UXhi8^pNyvw4{KVuD1 zvvA?eI@ELuQNXFy>{I|{ar)OQn-YvqP_j2}dB8|v*eTVjX1%!G@i+|cV#ywq3_Ya& zY^}fxsaBq1Jr(Uy5y5NQC?UdYl^S?=rRqcPMD0K3KeiSbsa3EUJCSxxR`UL4s8#4T zWz#`5CxtHuqD0ajLR-i)E_dQDZq(F>cN8tNN)qjktG?<{j*p*uzCnUFcvZ-jbF@4xQfa1l=ekI$`=G;q*6|2<~`nzhCPvLx?4i^-J^&o^|E zA_we)db^?uu?Fu}nzdD&q+S1!m~(qy>r$TAul8J|tr2f9TGe~h{cy&Okh_a?vT8}g z8H1fXbt$Ln_?vb%)kx7^jY!X&66&k9q~k|-Cmb<`3Iu5uoeJ?J;tx7wOiq=Y)w zb|5G)BtG$H-cSM6N+*GDayotaiaySdSShTSom5Vm`_Vdq?1izHMsoX!MT*+P+B~m1 z!ZsIdR5BE!@@bn&quZV&3+E=|>j+-Vw5$G(MeYG-b{LUmHZ6@MmX0-*^VVS`E8A&7 zclH-h?p20UZI0cL%ZqbsO}KQOfwA%56*6cTEzw~R+v&(8Vmz8c+mBe$UYn&CSiECX zZhZY@6Ni7Af7GG;I#o`QvAI_51&iWLqbzl0105W)0~X>Y8ks$fSj= zv-5N9=O-O0rW$H{L>KKJQT{+}a-?i@**(>8PGXQnHLA5eSoglad&_feO_B9n<_f8z znf}rzyBM^lY5KY87l#9OG(OqA`s303uxXaY(MoiGowH*8gRkaAJdNb(X6NK}$E!Xk zrhm!Z(H2a5O3i$y?fV`cXTBR4t0Au{?Y-z5w1=O2f+!~uwBA{E)x8Z@2~l>_9h7P=LANTn zBsF#H7Nvg*!^~Q8(^Q$eJ=~#d+NLVbjJPRVcXia_0Q=#L#f57n94K~LH{_^U$v*w6 zcC90Wehg`o$k0QZil+`WaJFpdyCzAsv`Ry@q&+XB#AY5__*jH_=t=3-Xa!oEOMzO( z<_^)Kg!}i#_MdTG6q@`~WerBsCuc#j(~*@7Svk$i1r#rlxQO=)-lrb-ICVPi{q=)} zqK9`6Df{_lemm%TWJue@N&Tv@XY}3^vm0kGe(CDA3lp42OSqz&>C6|pWj3%i?%B^?#Cbjf6 zlYhs-m`63nygj{nhC1&$h<5L0x~(O4@n_vL-hqCNp-R7=+4YPYn)Pw%u7$~c7R*PZ zjnU;x@Dt}&=>M$Y-$=amZj`pKV|jh?di<6ncT*{sUst$oxy~50{kmOrnOP}AH}qYM zGE@EZ*uCIol&pQb+l)5FKOMF(O zDSiK#<6CJjx#jf(>(6?{TgQMHRs?c;yw>8*G&xV=DVV&Zy*3bnlxX*Fj-^=22vathVO00D2 znR4&6{I-=1kCTE|8IqM)jr%73(3f5*TBMN}F|ZWV8{n_6T1 z^5xr)M_b>ee!flGdd#Ri)8!C%q@}^vPu+yZY;S)!I*OWk6BA&PXw)e-wti+{e4pnM z&G)9Qow8n}O5G*v!!PQ0EcRRd!{T%U)fwBNWB(SPECdbd(_9tB&UqC{g{>YSo%GCk z^`U2i=yQ>`T*%Me=|Oaao8n4+s12V(7tDC zFAEuLmGC2h|Yho-pHa3RoTBUCkmw;5S}6Y(~?#w{ZNF7qbf36Uo38DG=E-#-98DZcLjP~_c-mp6!7Jkc~LmeuZXvD#aTFN zpk_`J*FG6^7n#h4EYaMPUuS4Ji z-DoJDPouZ0eV-lmonA9&mspD+5$b2=W9uGc0@kXAxQXFOuJMVmTQ{EoM~@QPH2lf6 zR-?DoJQjKIW{+g(8~pMipDnd+@Alp(rxxu+t%0r z)rv)0^3W3&aKv-isU?UXxD zn?)cvP38Ad`8%GaIVtE9SHm-{i)Pep)R%hOd}?4iVTsQUL?u^-lbm!a#m?sf?jl$K z=}`EMxJ#f6ED?t#-|ntMAzk-Bnf7^^8I)`0Yj8#p69gt|DCd+clFuHC=u>0}h+T%k zdKJ!JxBhD{J!C%Pt@Epg3!mX4F5_2UXgHK{y=ZKySKTu~~wY*h> z6~r{4+iFD`qSEGGw0dnu)=IClh4*1@3i_*XPZ&d ztZum~3|`|M6O0@))5mnBVyUHPi1j4eSbV9t)m-b}H^a0qigmZnC0JJW{<32j-B=sd zBA+%5CI@b-M>At4D8zo^UT(D^$F6$|SFy9n-8l)&k~zbyVfLCOE!yh%i`$W4Zah-; z_8*oed+(gxu!Q;j`Rd)0bL(u4=)Kidx6K?&tawxl-Upx_MZ*GNR{}kxv?TjgreGVX z(3OCYY+(7b6oikp6dOJ}5m6!w-F9Qg$R0yS&*-jeI6KaH>N!O{w!Wimp7bzwy9yE9 zrWW>YaZMK%Y zrK-b1d${Hiis%8Y_K6Js+Gb>he_UkEJ$8^f5nNhHS#-M0cqV7tjkFMQjDVvNj(mum z+kpOXdG{*z0<{_XOh#Qb0tx#{3l@@~+}J^HOt3mISiKHp%)mD!fALSKV-C6Ag z7XsOpHKJ=K5M3ota)kpBTqh7*i~X}Z3ZC2wh4j;BJgG%ZewbU&Ahhz$#f~cn5g9A0 z{Bp8Ls%ays77$b!PeSF4fVxRyX<93&@@1_Q%6IziAdnO}(E*oObBlfagz5I-n^uV@6IAZDiR^ZUiT(RkQXCB%AQo4Wj;}1*%uo98g7)h$oczC z1y@?FImwEWFDrOZw37=&g;FRa>kEn*2@1E9HIkx3-g!jB90H@`H%b{a?=rq%m_sh8 z3Zz0_r>uCgV6iU?7K1HluC>aQ6j$es04R!QY%#p_GeG#Wb7KkjerwluwV?wfHbz@o zu{gYA%_CTyA-(1vDufXfOKr1qutViYgLRLL)uzpmPWvtWX-yE80UOzRYio%!Ds0}L z*@AVq!3M@_hOpWI1kIx=+?v4)osW!Af?XGWmC;D{Qq;ys}49X-J#k;iDAVXuRwcQ*t!jJIZ7;FuHLT>OuGPjNV?&aC?l7?a7w++E9g$vW zowy?*{~de$ug8CNDE>G0_+O1*FHzd^U$w{I#vi`l9sE@{sCYvqg3ybBP9ED;fb1IXc%gh#v<;8`CVyTij zp2-(7b0y->OlPvQV7`2Oda-K)C-QUInPQ=knI=7coTc0>Mf%Qiu97AD z+{tXNTv0Wp+>P1deD=o0VkLJvU#`q%7UwFnwCF^!Tv;S@hBC9I`TSftcVoV=P%0iT z(dK-iLQ_v==JLgSS+(TL*?eiPSX`K&&CliL=kkS$|dg_h()|BGPi^#b=ZV^h6iiZ^chg=vH2>R3dWv| znv5O3In1|Zp)FvtmGR#mPobLJ_E{P)P06=&n(Oxq_QH)NjndqhbX#$>hU~I2NWoDX zRyTOK25k>?IrUhcvr^VjD9mF$`>ZU~XW=^#QpEc0^7l2tCB*WhMQ>cj%2G#6BWj)^ zuKISFU1O+tzLn6$*u)VCt1^?Wv^bx^GaP!C)xYK5BRu))_9uq= zRQ5so5IaLXqGzKqwxQh?kBQ8T4Hx4#*_=*;{n&5ltUV-U_XMv{Y$v$MitFEKwJ2m@ z*SzF8T=?F~!W2po3oIU{QN{)z!>ECUTP0(X)G>?ZvpnDkxveDaa z=0>-AjF5AX`f`7n0sZwztz&WWjGQsgRAe4%^ly(bIrP1|uBRV-4s#*~V?Fei?(cUD zD+bz6znv^0;U^k)wAu-*2L-`J`wL@uY6MRmVioti4wy6+?#@ok*mlAX0T{hqm(x%< zd?_-+8JR|#*FCQ{mc-M?kfG)>cjHexF_INnu~zKt=_Bj zij#j3D9%FZ6>iIx4sPAD%P61H+Az6QpXyLP>sCI++4Ny|ILxNANNoQACHm&MY zxY{natzYRBru;U{{wlrl%ddLwX21C7;4i_)z^A}JgNMMw;M?F4@ZaDkVEAP`_XSS_ z5pV>If@9zXU>Y0;b6^4743hpp61)by z4!jZE27Vjd0p1PL^qX`E>9g8H%l0DOXcS22kgZB%?@A!sN*J=)NDs5=?Kriz80A%Z zl_|)+r98SKfEW{ayu@xn)CSdI?R{$3v%#J8+k@ayaB+v{RUigVf|rB0gAagv zz^B32z@y+`m*>wQ2Ijy9xE;I)d;r`79spkg-v+}y4lDw3kO3#b5@>?AfDeJc0xILv z{5}L80Z%#0fkj{j+yWZl_2Bn`(*7C0p8($gKLJlW$G8F~z{|mH;Jx6(;OpSx^BiCY z=0O$gfY*VyfxEyb!56`I!B2tec+w7eKoVrZs8jZ~+s?jQD<|u1?aadN_)D~w^|rY5 zJ1mTnaC)MFZ0vS-oC`MQ&SMA=@_3tz3w9GhjK8Wov;8fkvsai&fCK`}ich~O7 z*}W}xc1u`CyFvfau(t&ay2>gJPR42wyVN?TylcayHxJCsm+`>c7_f10g59jy$7=YP zH!-~3tXghHRDTa1+ITwqXl2T=OT99Qd^N=zd%^C!U$@>K=qT%+t!;nU?5AQm`)&}O zh4uyeuF$==dZ4pwTN^xSck60KX!ji6IXf5XP>!Bs@%BcCT_Acu2Tg{JAZ_rs*xpoE zNBLz^yzaiYO>?q8LrHWlP-ptZ?z`#hM{7+9Huk%NgB`JHNZ)6N_*1#@9{ z9edO&$NEEx)9%UU&rDy@3di|snC`>=nv_!x)Q&*quwiOdKd`@A+INIPDs5;`ghS1v zRBzOKFXcGq@n`V1j5E*1dSji}ef-0WxZRJr(RDIkeZ&3ECAV{gPQm%yz*mRv;TW;o zhX3Qh6+_>1erJ$_a)$qC;MWI=gQG)NJD(Z;J?E)|R}bDl@a4gaoc9kq1N8&n9dL(J z2j0XndLx5#Lk|tV>%b3(t{T2*_`?TI4nOU{d8c&X`oZrGe$DyV;H!o{<=i>AGko|! zb6{)mnL~FCWt|@le9*adaM}4f=kL62=%qu~Ij0Av2Hxu2Km3k?nFF%}KNxm0KYk+( zycA=j^UJ@=j*LI&&Vh@O7WH0@2ayLZYH(6z54Cz=sKhHX&ZWK}aqA;1I|qi_gw=j2 z5t~fJ;*qH9u1v*i^=cx*YtE^W_|!-|b!51vB2FbHV~JQY5?$|hw>!s1M>#KQy|)s> zR5jXYob~?cl+It_9j!<*T}{-}>3E{fd(73T^lEAhcf3Byo6sxc)l_mK9k06a+7V~! y$(*6d)77O_x5}%EZfEeiC-WMLB Date: Thu, 16 Jan 2025 21:05:40 +0000 Subject: [PATCH 29/49] Compiled satisfiability/sat_optima --- tig-algorithms/src/satisfiability/mod.rs | 3 +- .../sat_optima/benchmarker_outbound.rs | 285 ++++++++++++++++++ .../satisfiability/sat_optima/commercial.rs | 285 ++++++++++++++++++ .../src/satisfiability/sat_optima/inbound.rs | 285 ++++++++++++++++++ .../sat_optima/innovator_outbound.rs | 285 ++++++++++++++++++ .../src/satisfiability/sat_optima/mod.rs | 4 + .../satisfiability/sat_optima/open_data.rs | 285 ++++++++++++++++++ tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/sat_optima.wasm | Bin 0 -> 162922 bytes 9 files changed, 1433 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/sat_optima/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_optima/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/sat_optima/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_optima/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_optima/mod.rs create mode 100644 tig-algorithms/src/satisfiability/sat_optima/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/sat_optima.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..9e4eaf5 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -58,7 +58,8 @@ // c001_a030 -// c001_a031 +pub mod sat_optima; +pub use sat_optima as c001_a031; // c001_a032 diff --git a/tig-algorithms/src/satisfiability/sat_optima/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/sat_optima/benchmarker_outbound.rs new file mode 100644 index 0000000..6a20e5b --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_optima/benchmarker_outbound.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + + let i = residual_[rng.gen_range(0..residual_.len())]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_optima/commercial.rs b/tig-algorithms/src/satisfiability/sat_optima/commercial.rs new file mode 100644 index 0000000..1c4e87a --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_optima/commercial.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + + let i = residual_[rng.gen_range(0..residual_.len())]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_optima/inbound.rs b/tig-algorithms/src/satisfiability/sat_optima/inbound.rs new file mode 100644 index 0000000..4d56d06 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_optima/inbound.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + + let i = residual_[rng.gen_range(0..residual_.len())]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_optima/innovator_outbound.rs b/tig-algorithms/src/satisfiability/sat_optima/innovator_outbound.rs new file mode 100644 index 0000000..d718930 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_optima/innovator_outbound.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + + let i = residual_[rng.gen_range(0..residual_.len())]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_optima/mod.rs b/tig-algorithms/src/satisfiability/sat_optima/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_optima/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/sat_optima/open_data.rs b/tig-algorithms/src/satisfiability/sat_optima/open_data.rs new file mode 100644 index 0000000..19eda8b --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_optima/open_data.rs @@ -0,0 +1,285 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + loop { + if !residual_.is_empty() { + + let i = residual_[rng.gen_range(0..residual_.len())]; + let mut min_sad = clauses.len(); + let mut v_min_sad = Vec::with_capacity(clauses[i].len()); // Preallocate with capacity + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad.clear(); + v_min_sad.push((l.abs() - 1) as usize); + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..(c.len() as u32)) as usize]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..(v_min_sad.len() as u32)) as usize] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= num_variables * 35 { + return Ok(None); + } + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/sat_optima.wasm b/tig-algorithms/wasm/satisfiability/sat_optima.wasm new file mode 100644 index 0000000000000000000000000000000000000000..22350fe777bf4028f1fdb58326e1e394c20a3f21 GIT binary patch literal 162922 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 LC80lrfjv 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 Date: Thu, 16 Jan 2025 21:05:54 +0000 Subject: [PATCH 30/49] Compiled satisfiability/schnoing --- tig-algorithms/src/satisfiability/mod.rs | 3 +- .../schnoing/benchmarker_outbound.rs | 87 ++++++++++ .../src/satisfiability/schnoing/commercial.rs | 87 ++++++++++ .../satisfiability/schnoing/cuda_example.rs | 156 ++++++++++++++++++ .../src/satisfiability/schnoing/inbound.rs | 87 ++++++++++ .../schnoing/innovator_outbound.rs | 87 ++++++++++ .../src/satisfiability/schnoing/mod.rs | 4 + .../src/satisfiability/schnoing/open_data.rs | 87 ++++++++++ tig-algorithms/src/satisfiability/template.rs | 26 +-- .../wasm/satisfiability/schnoing.wasm | Bin 0 -> 152268 bytes 10 files changed, 599 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/schnoing/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/schnoing/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/schnoing/cuda_example.rs create mode 100644 tig-algorithms/src/satisfiability/schnoing/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/schnoing/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/schnoing/mod.rs create mode 100644 tig-algorithms/src/satisfiability/schnoing/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/schnoing.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..e6bcd84 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -1,4 +1,5 @@ -// c001_a001 +pub mod schnoing; +pub use schnoing as c001_a001; // c001_a002 diff --git a/tig-algorithms/src/satisfiability/schnoing/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/schnoing/benchmarker_outbound.rs new file mode 100644 index 0000000..37de9a2 --- /dev/null +++ b/tig-algorithms/src/satisfiability/schnoing/benchmarker_outbound.rs @@ -0,0 +1,87 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + // Pre-generate a bunch of random integers + // IMPORTANT! When generating random numbers, never use usize! usize bytes varies from system to system + let rand_ints: Vec = (0..2 * num_variables) + .map(|_| rng.gen_range(0..1_000_000_000u32) as usize) + .collect(); + + for i in 0..num_variables { + // Evaluate clauses and find any that are unsatisfied + let substituted: Vec = challenge + .clauses + .iter() + .map(|clause| { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + let var_value = variables[var_idx]; + (literal > 0 && var_value) || (literal < 0 && !var_value) + }) + }) + .collect(); + + let unsatisfied_clauses: Vec = substituted + .iter() + .enumerate() + .filter_map(|(idx, &satisfied)| if !satisfied { Some(idx) } else { None }) + .collect(); + + let num_unsatisfied_clauses = unsatisfied_clauses.len(); + if num_unsatisfied_clauses == 0 { + break; + } + + // Flip the value of a random variable from a random unsatisfied clause + let rand_unsatisfied_clause_idx = rand_ints[2 * i] % num_unsatisfied_clauses; + let rand_unsatisfied_clause = unsatisfied_clauses[rand_unsatisfied_clause_idx]; + let rand_variable_idx = rand_ints[2 * i + 1] % 3; + let rand_variable = + challenge.clauses[rand_unsatisfied_clause][rand_variable_idx].abs() as usize - 1; + variables[rand_variable] = !variables[rand_variable]; + } + + Ok(Some(Solution { variables })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/schnoing/commercial.rs b/tig-algorithms/src/satisfiability/schnoing/commercial.rs new file mode 100644 index 0000000..fccc582 --- /dev/null +++ b/tig-algorithms/src/satisfiability/schnoing/commercial.rs @@ -0,0 +1,87 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + // Pre-generate a bunch of random integers + // IMPORTANT! When generating random numbers, never use usize! usize bytes varies from system to system + let rand_ints: Vec = (0..2 * num_variables) + .map(|_| rng.gen_range(0..1_000_000_000u32) as usize) + .collect(); + + for i in 0..num_variables { + // Evaluate clauses and find any that are unsatisfied + let substituted: Vec = challenge + .clauses + .iter() + .map(|clause| { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + let var_value = variables[var_idx]; + (literal > 0 && var_value) || (literal < 0 && !var_value) + }) + }) + .collect(); + + let unsatisfied_clauses: Vec = substituted + .iter() + .enumerate() + .filter_map(|(idx, &satisfied)| if !satisfied { Some(idx) } else { None }) + .collect(); + + let num_unsatisfied_clauses = unsatisfied_clauses.len(); + if num_unsatisfied_clauses == 0 { + break; + } + + // Flip the value of a random variable from a random unsatisfied clause + let rand_unsatisfied_clause_idx = rand_ints[2 * i] % num_unsatisfied_clauses; + let rand_unsatisfied_clause = unsatisfied_clauses[rand_unsatisfied_clause_idx]; + let rand_variable_idx = rand_ints[2 * i + 1] % 3; + let rand_variable = + challenge.clauses[rand_unsatisfied_clause][rand_variable_idx].abs() as usize - 1; + variables[rand_variable] = !variables[rand_variable]; + } + + Ok(Some(Solution { variables })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/schnoing/cuda_example.rs b/tig-algorithms/src/satisfiability/schnoing/cuda_example.rs new file mode 100644 index 0000000..908b9ed --- /dev/null +++ b/tig-algorithms/src/satisfiability/schnoing/cuda_example.rs @@ -0,0 +1,156 @@ +/*! +Copyright 2024 TIG Foundation + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(challenge.seeds[0] as u64); + let num_variables = challenge.difficulty.num_variables; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + // Pre-generate a bunch of random integers + // IMPORTANT! When generating random numbers, never use usize! usize bytes varies from system to system + let rand_ints: Vec = (0..2 * num_variables) + .map(|_| rng.gen_range(0..1_000_000_000u32) as usize) + .collect(); + + for i in 0..num_variables { + // Evaluate clauses and find any that are unsatisfied + let substituted: Vec = challenge + .clauses + .iter() + .map(|clause| { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + let var_value = variables[var_idx]; + (literal > 0 && var_value) || (literal < 0 && !var_value) + }) + }) + .collect(); + + let unsatisfied_clauses: Vec = substituted + .iter() + .enumerate() + .filter_map(|(idx, &satisfied)| if !satisfied { Some(idx) } else { None }) + .collect(); + + let num_unsatisfied_clauses = unsatisfied_clauses.len(); + if num_unsatisfied_clauses == 0 { + break; + } + + // Flip the value of a random variable from a random unsatisfied clause + let rand_unsatisfied_clause_idx = rand_ints[2 * i] % num_unsatisfied_clauses; + let rand_unsatisfied_clause = unsatisfied_clauses[rand_unsatisfied_clause_idx]; + let rand_variable_idx = rand_ints[2 * i + 1] % 3; + let rand_variable = + challenge.clauses[rand_unsatisfied_clause][rand_variable_idx].abs() as usize - 1; + variables[rand_variable] = !variables[rand_variable]; + } + + Ok(Some(Solution { variables })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = Some(CudaKernel { + // Example CUDA code from https://github.com/coreylowman/cudarc/blob/main/examples/matmul-kernel.rs + src: r#" +extern "C" __global__ void matmul(float* A, float* B, float* C, int N) { + int ROW = blockIdx.y*blockDim.y+threadIdx.y; + int COL = blockIdx.x*blockDim.x+threadIdx.x; + + float tmpSum = 0; + + if (ROW < N && COL < N) { + // each thread computes one element of the block sub-matrix + for (int i = 0; i < N; i++) { + tmpSum += A[ROW * N + i] * B[i * N + COL]; + } + } + C[ROW * N + COL] = tmpSum; +} +"#, + funcs: &["matmul"], + }); + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + // Example CUDA code from https://github.com/coreylowman/cudarc/blob/main/examples/matmul-kernel.rs + let start = std::time::Instant::now(); + + let a_host = [1.0f32, 2.0, 3.0, 4.0]; + let b_host = [1.0f32, 2.0, 3.0, 4.0]; + let mut c_host = [0.0f32; 4]; + + let a_dev = dev.htod_sync_copy(&a_host)?; + let b_dev = dev.htod_sync_copy(&b_host)?; + let mut c_dev = dev.htod_sync_copy(&c_host)?; + + println!("Copied in {:?}", start.elapsed()); + + let cfg = LaunchConfig { + block_dim: (2, 2, 1), + grid_dim: (1, 1, 1), + shared_mem_bytes: 0, + }; + unsafe { + funcs + .remove("matmul") + .unwrap() + .launch(cfg, (&a_dev, &b_dev, &mut c_dev, 2i32)) + }?; + + dev.dtoh_sync_copy_into(&c_dev, &mut c_host)?; + println!("Found {:?} in {:?}", c_host, start.elapsed()); + + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/schnoing/inbound.rs b/tig-algorithms/src/satisfiability/schnoing/inbound.rs new file mode 100644 index 0000000..60efa83 --- /dev/null +++ b/tig-algorithms/src/satisfiability/schnoing/inbound.rs @@ -0,0 +1,87 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + // Pre-generate a bunch of random integers + // IMPORTANT! When generating random numbers, never use usize! usize bytes varies from system to system + let rand_ints: Vec = (0..2 * num_variables) + .map(|_| rng.gen_range(0..1_000_000_000u32) as usize) + .collect(); + + for i in 0..num_variables { + // Evaluate clauses and find any that are unsatisfied + let substituted: Vec = challenge + .clauses + .iter() + .map(|clause| { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + let var_value = variables[var_idx]; + (literal > 0 && var_value) || (literal < 0 && !var_value) + }) + }) + .collect(); + + let unsatisfied_clauses: Vec = substituted + .iter() + .enumerate() + .filter_map(|(idx, &satisfied)| if !satisfied { Some(idx) } else { None }) + .collect(); + + let num_unsatisfied_clauses = unsatisfied_clauses.len(); + if num_unsatisfied_clauses == 0 { + break; + } + + // Flip the value of a random variable from a random unsatisfied clause + let rand_unsatisfied_clause_idx = rand_ints[2 * i] % num_unsatisfied_clauses; + let rand_unsatisfied_clause = unsatisfied_clauses[rand_unsatisfied_clause_idx]; + let rand_variable_idx = rand_ints[2 * i + 1] % 3; + let rand_variable = + challenge.clauses[rand_unsatisfied_clause][rand_variable_idx].abs() as usize - 1; + variables[rand_variable] = !variables[rand_variable]; + } + + Ok(Some(Solution { variables })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/schnoing/innovator_outbound.rs b/tig-algorithms/src/satisfiability/schnoing/innovator_outbound.rs new file mode 100644 index 0000000..d925304 --- /dev/null +++ b/tig-algorithms/src/satisfiability/schnoing/innovator_outbound.rs @@ -0,0 +1,87 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + // Pre-generate a bunch of random integers + // IMPORTANT! When generating random numbers, never use usize! usize bytes varies from system to system + let rand_ints: Vec = (0..2 * num_variables) + .map(|_| rng.gen_range(0..1_000_000_000u32) as usize) + .collect(); + + for i in 0..num_variables { + // Evaluate clauses and find any that are unsatisfied + let substituted: Vec = challenge + .clauses + .iter() + .map(|clause| { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + let var_value = variables[var_idx]; + (literal > 0 && var_value) || (literal < 0 && !var_value) + }) + }) + .collect(); + + let unsatisfied_clauses: Vec = substituted + .iter() + .enumerate() + .filter_map(|(idx, &satisfied)| if !satisfied { Some(idx) } else { None }) + .collect(); + + let num_unsatisfied_clauses = unsatisfied_clauses.len(); + if num_unsatisfied_clauses == 0 { + break; + } + + // Flip the value of a random variable from a random unsatisfied clause + let rand_unsatisfied_clause_idx = rand_ints[2 * i] % num_unsatisfied_clauses; + let rand_unsatisfied_clause = unsatisfied_clauses[rand_unsatisfied_clause_idx]; + let rand_variable_idx = rand_ints[2 * i + 1] % 3; + let rand_variable = + challenge.clauses[rand_unsatisfied_clause][rand_variable_idx].abs() as usize - 1; + variables[rand_variable] = !variables[rand_variable]; + } + + Ok(Some(Solution { variables })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/schnoing/mod.rs b/tig-algorithms/src/satisfiability/schnoing/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/schnoing/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/schnoing/open_data.rs b/tig-algorithms/src/satisfiability/schnoing/open_data.rs new file mode 100644 index 0000000..93bef13 --- /dev/null +++ b/tig-algorithms/src/satisfiability/schnoing/open_data.rs @@ -0,0 +1,87 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + // Pre-generate a bunch of random integers + // IMPORTANT! When generating random numbers, never use usize! usize bytes varies from system to system + let rand_ints: Vec = (0..2 * num_variables) + .map(|_| rng.gen_range(0..1_000_000_000u32) as usize) + .collect(); + + for i in 0..num_variables { + // Evaluate clauses and find any that are unsatisfied + let substituted: Vec = challenge + .clauses + .iter() + .map(|clause| { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + let var_value = variables[var_idx]; + (literal > 0 && var_value) || (literal < 0 && !var_value) + }) + }) + .collect(); + + let unsatisfied_clauses: Vec = substituted + .iter() + .enumerate() + .filter_map(|(idx, &satisfied)| if !satisfied { Some(idx) } else { None }) + .collect(); + + let num_unsatisfied_clauses = unsatisfied_clauses.len(); + if num_unsatisfied_clauses == 0 { + break; + } + + // Flip the value of a random variable from a random unsatisfied clause + let rand_unsatisfied_clause_idx = rand_ints[2 * i] % num_unsatisfied_clauses; + let rand_unsatisfied_clause = unsatisfied_clauses[rand_unsatisfied_clause_idx]; + let rand_variable_idx = rand_ints[2 * i + 1] % 3; + let rand_variable = + challenge.clauses[rand_unsatisfied_clause][rand_variable_idx].abs() as usize - 1; + variables[rand_variable] = !variables[rand_variable]; + } + + Ok(Some(Solution { variables })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/schnoing.wasm b/tig-algorithms/wasm/satisfiability/schnoing.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4d572671c884d4d3f9b03d0085d01aa3099f5d2e GIT binary patch literal 152268 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

B*{w{Y|?>8 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

SF79ZBlG5{i(-> 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 RkNE;?_qGlvNsY0L`5#gfATj^| literal 0 HcmV?d00001 From efa645e3f63c9ba7f7d7e7922f9c2612377f0ce5 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:05:56 +0000 Subject: [PATCH 31/49] Compiled satisfiability/sprint_sat --- tig-algorithms/src/satisfiability/mod.rs | 3 +- .../sprint_sat/benchmarker_outbound.rs | 267 ++++++++++++++++++ .../satisfiability/sprint_sat/commercial.rs | 267 ++++++++++++++++++ .../src/satisfiability/sprint_sat/inbound.rs | 267 ++++++++++++++++++ .../sprint_sat/innovator_outbound.rs | 267 ++++++++++++++++++ .../src/satisfiability/sprint_sat/mod.rs | 4 + .../satisfiability/sprint_sat/open_data.rs | 267 ++++++++++++++++++ tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/sprint_sat.wasm | Bin 0 -> 162266 bytes 9 files changed, 1343 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/sprint_sat/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sprint_sat/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/sprint_sat/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/sprint_sat/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sprint_sat/mod.rs create mode 100644 tig-algorithms/src/satisfiability/sprint_sat/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/sprint_sat.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..0b1d5ba 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -20,7 +20,8 @@ // c001_a011 -// c001_a012 +pub mod sprint_sat; +pub use sprint_sat as c001_a012; // c001_a013 diff --git a/tig-algorithms/src/satisfiability/sprint_sat/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/sprint_sat/benchmarker_outbound.rs new file mode 100644 index 0000000..648e969 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sprint_sat/benchmarker_outbound.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sprint_sat/commercial.rs b/tig-algorithms/src/satisfiability/sprint_sat/commercial.rs new file mode 100644 index 0000000..24e2b1a --- /dev/null +++ b/tig-algorithms/src/satisfiability/sprint_sat/commercial.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sprint_sat/inbound.rs b/tig-algorithms/src/satisfiability/sprint_sat/inbound.rs new file mode 100644 index 0000000..18ade15 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sprint_sat/inbound.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sprint_sat/innovator_outbound.rs b/tig-algorithms/src/satisfiability/sprint_sat/innovator_outbound.rs new file mode 100644 index 0000000..e71c8bb --- /dev/null +++ b/tig-algorithms/src/satisfiability/sprint_sat/innovator_outbound.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sprint_sat/mod.rs b/tig-algorithms/src/satisfiability/sprint_sat/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sprint_sat/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/sprint_sat/open_data.rs b/tig-algorithms/src/satisfiability/sprint_sat/open_data.rs new file mode 100644 index 0000000..61e8321 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sprint_sat/open_data.rs @@ -0,0 +1,267 @@ +/*! +Copyright 2024 Dominic Kennedy + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![vec![]; num_variables]; + let mut n_clauses: Vec> = vec![vec![]; num_variables]; + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + if p_single[v] { + variables[v] = true + } else if n_single[v] { + variables[v] = false + } else { + variables[v] = rng.gen_bool(0.5) + } + } + let mut num_good_so_far: Vec = vec![0; num_clauses]; + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + if variables[var] { + num_good_so_far[i] += 1 + } + } else { + n_clauses[var].push(i); + if !variables[var] { + num_good_so_far[i] += 1 + } + } + } + } + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = HashMap::with_capacity(num_clauses); + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices.insert(i, residual_.len() - 1); + } + } + + let mut attempts = 0; + loop { + if attempts >= num_variables * 25 { + return Ok(None); + } + if !residual_.is_empty() { + let i = residual_[0]; + let mut min_sad = clauses.len(); + let mut v_min_sad = vec![]; + let c = &clauses[i]; + for &l in c { + let mut sad = 0 as usize; + if variables[(l.abs() - 1) as usize] { + for &c in &p_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } else { + for &c in &n_clauses[(l.abs() - 1) as usize] { + if num_good_so_far[c] == 1 { + sad += 1; + if sad > min_sad { + break; + } + } + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = vec![(l.abs() - 1) as usize]; + } else if sad == min_sad { + v_min_sad.push((l.abs() - 1) as usize); + } + } + let v = if min_sad == 0 { + if v_min_sad.len() == 1 { + v_min_sad[0] + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + } else { + if rng.gen_bool(0.5) { + let l = c[rng.gen_range(0..c.len())]; + (l.abs() - 1) as usize + } else { + v_min_sad[rng.gen_range(0..v_min_sad.len())] + } + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices.insert(c, residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices.remove(&c).unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices.insert(last, i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + attempts += 1; + } + + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/sprint_sat.wasm b/tig-algorithms/wasm/satisfiability/sprint_sat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..38fb4ad39afe81c64e15e0114ebcd959df1dc37a GIT binary patch literal 162266 zcmeFa3z%KkRp)tL_kFACmQ=DzD#_aCNVa8LiWLa5EIYAwC4R`VW9KoMKrBF?C08RueJ8tYpuOc^gVa{U>rqJd~ZB)YkK(b;rQ^a ziT#ZaM|z4ZHM%t;kC#{l>U!2&6x~`YAHy%1R#2@RQr$z=R#}16mK8sI=+L3))<$?% zi`k)2FeOKQ@fxL%#2=5-EbFF89LG^SGMdG4o5xO^*#FWbt?>}YX(P$fW-H5@Nj;8R z8I_t@nx<6BY7u|qcGjR`n#IXjL+`Yd^+?gPlKGRyajjO%I;}D>BDI#K2_?u1|7*}F zy;3$y$N7!py1I|}0f$k2#mYK8#_x*X@s1k3kJnq^%g@c7j5>`xt^eTn{op^}_rpn) z{lM)%us?b@?tI_v`}h6uJKpurf8h50(Z}M}d*jH*oqyVV^;qM5@sGz>-|_x<)yD7q zw*PVeKfPmow7qiG4ST-(fB62{8@}hCz4q#{V{!BCABgXXe<7}~dF|ETd;V45w|2$U zd)9AS^M-4#ef>|xH@@}1d)qtzryrQP?S~G$`$rDm`TD!#H9vCje~#~pe>6T4&&U5V z{ul92GUq=X-xvQ({A2OY$K7~K`Zj%|r3QjgfycnJv;yQL?N5?{NQV zkhaT3o0A^HoSS>k9Yx}6j{kN~`FM7;7ALhdi#jgeoJ58G=&4ymvp&5k+vQ>xc_zXV za~0G3%=V?VMsH;Rt-MX2=`NT02a_2W&lHS))>-~s(Vl+(W9cE+I(P#3PoGZr`e&ot zi_FLS{qcuSMKhO0(Uo)``DE5*{(-}{xnw4kTT&vXQTlF8pFZ{F%ifkpuAR4BG!k_H zcmP$$b*8WWv7&XFP@Ix zJQ8jMDm0+4q@h=Et1 z@&B8^>vqFv3mZz=Z9J$6Dgkx4Ca4TGJ63aZUJnRLgdqMji%h_4P(qMW@~jiyXNsD@ zCoOX@=wBIc)<>8&rCIL@)++fUxe;tS@(dMnw)NkDxJqTgt^={f>*~sgVPhD~`pw)6>v2KOZ zokkJDJ;Q)tjnpE)YqqGn=CzPo84*;Spv5X)lk&&mcd;OwDCW##M*51!RA0 zP-lN|jK|2;y0r%Q!-}D)fe>wKlu|7hfdELc<-!O6gdl+r^KX~_O~xZ{#6JaKG*{x9mu~g4LybIB4H8UV&rFl_ zq$N?QJB;=n0qf0x^ml^GhClD-k;F%?bz&AA)#gt4_udA3`NMgf!eqz?Tk*c|!C(Kg zC;0EiL#(*r@re(9``b|*V8jOV1)ni3Fx29j?dhSK?deCX5o*3wHACG6=2W$XC^VeB zwYY`Wa0^TN?y~FzeW^YJLq7MhQ&Fi2#UA$hbKXnEt~Gu2u@gRJFd`a2-aUf&fQTeO zeUwMZVRW8nqq(B(M(lU=?a<;#KI%Fw{av7n)LZw2>sof}gw(7V|LQ$CT2}`owWr&v zJ*uD1+p@z$=qtJ;ni#PnouVuxB+w#l9iXXf52}QyX7o+_1Ql|enE|w2fJVg@kntnH zNR9zcfOP;%uXM*~+z9hHTC~;TwJ=FFgA(YcCHTHmAP)2Bc9;XnViuf>)yW(m(7`PiSFXYZqW|IFvU zIseOvy$e03f8oAg`M`R6WdPHsfB%nv>xGcZv`wG>-CsL?fn`e4pZ@8;csjMWmfoap ztKX(x9{Or#FAZKkGyht9uJim0AE?{2hW(RYjO-AHYiQ&7(S)YKzdIe`$`8A~h@hoO6+wuSU8QYnNAK|^Gnu=8% zsH-xx*Jt8{Zjt9v@1qQ0#=i={x{VLFzP;XrnxlAfkWcrU*5*h=M{%g?D%c6}Q z#RYR(!mOHS8L&qB(gQBLUDQ4-sP?M?BMbg^kVzB|RPJbY56~b4(G$uo8uHLKX0|fh z#<=&9cx@;n1zbpJp8y|Ui&Pgfo9)lEGKO}`0aMV4?VlG92SEoW+fqLkHvvN}nrN*7 zu+vZ{OvOG(Nn7UVEmnUh*O1P}8jS&UaJi8tXCEm_N`J744VKRT0S23tyb1=+GT5OK zVX%w{M6;)4KcGL_V28Pbm8uZ|t<4>r6ZjjuM${jX>FVO_CzF{?QRfrb&JJT{a%N?M zEFfW|IF0(qM?LIDp5@xMtpz(hbZRtU{@Wu>T#HZBo(|suUFHeXS(7dj<(XzIb?hhC z@uQvD_GCgS{7%w4TA12N{lrXfgG(6-bI1=$6oxGIYi|-YE)H9zWIOwd)H{LwbM?4& zSj}n!!;fuEOs;?hC2LO`S4Z+Jcn)%;6hH0D$z_5t}+cO76Btt}mI0jt5v`!yWCph+ z)glWmd)yf;Rq6^Ma7L!uqb+HRdtje5k6k8?k)1kE1V+kAr|BUpfPfs9c%B_=Anw(o&PVZ-NJejC`P+o_@<_riKac+2sYtRd zo{le1SnEjIJhNbv?&dE~9Vz5*whYHY*0ZO}v~6$B^bX6alxf_O5NNG8xyYy2nLdkU zD9RBG44HTHRA{U8l}}R%LE5jBYxn14!2+@+^7Le7|8!~Dol#eL!bsxL zvPblzHABPTPuy!UugTV^o2P0bDq?la9J0ZQuCuPfn`ENakQ5+IiRXQ5G=dOy>DFkS z&-Us`qkQZaU}cuEKET?aS z=&?V!s*&a-zDQkW_OTd$L7OTO+qKx{9a9<)%NSzDwn8Kq{4K9mobY z4G~h?<{u3}@%);S)pkZKcBN%rbH8iWsP5nXEvW80l3}VdGW}anU2iwbF|2K5XUfPq ziSFE8ElYH-tfyB%bHAM=wLo*o;o7FiUu5zjsyvBN-h^3T#+q-Td21P z8P?n7Th~IDh3(j)7SJjeEvFI+5-9yHaFB(~m{w6N^c4>LMJ7Z57%!OkI_*4@#SPp7 z&SWnfx-M_}ttjJiBT3d@ihA@|B;H+z28*zYr=+T*Ltyp-K6E-`Wdt9^Nl0Pr08qNO zXh=pUW|hP56;#w4X5<5tVvL1KP&W)+7=BPWO~ruJOgd12ekZU3@H2@<+t#wvlj8>k z9j&tw_zU5LjakyA!`7AM>E+jJ`sQ9Or0}|VqPA&SMqs(T8`_Z!ac2dCj?ohtNwNz{ zV^F9B*9bhUFaXy>EQq^&Jf>B13D{ahT-Ta+P5SjwP-fI<-n7LB$bbcn3zvBs#Stq_ zGiO4Lw!wXTfJXU2hdPi;84qZ+;TTFL#pmEKZ!Ccjo13dI17QQyR%njcNeIo9lg5Cl z7zi~^d}G>GYHL!?i1v>9ci1^Uj0H11YfLo+Cu;zyiq-js7f9Mw_nwu`2 zPNY4hL2p9+hs9nXprzR`xU3*tXov*~h3Js*fz)h2)T&6V~{Xj|z z#<2e40xKk?a^liO8cTD=e?g^LZ++V9`KLlvDKEIrd#Whag(Z#xL1n45lC_?hRa&_u zL}7C5xrVz=)+HQ|l`-H8O)DW4WftP%4rHcP%M0 z#=fS=@PsgtB2#m1DKe^Tij4hg5Ir1H_EN%b?^-R;aRjw;hg2M%OBE-_It^a6Q%7oO zr(UbHoDL`kw`j^MI)By(EQ)!{_8dmQyYyGnKm&-mI7y|Ns}jbkl5syR_1$(FCFXP1 zm5OoMTnueBVBD`iOX;xD@HKdq?Y%^_x%~^=eBmY+)?3CVM}Ozo|K8+8 z0icAZuv41AQLNudc7k$SfXl5BxopIOkom1JmVuDj8Ld;EY=MT=_4<)|9LC80lrfjv zwRNDLnruUtj0tN?3|fNZLp%ZiMZxlQY-Yg0dd2PliEY?ye)}6X=DM6$%E$|<$q)TZ z_}>+@!&}!%Xs-cggf=h>^Z;x%Pf>umSIrCW)uae2z*(~de1(kk!KYyj@tks-6&q-m zm)zvBUI#)hY;Ci0PiQz61yqr8xdy$g;r#o6j6}n0onu6px&|X+r}A<~1k`685iG&n zO!#s?J(xIS7PAJk=5LHtDv}vP77J*iX}@Y#ZBOW%ZKA89zDI9_ZKYq)ai$i7A)Q`gfAwB0q?zBa7g?M6 zcgUX9Cbr78_T_8?FmRbq+EJEJj=l6=-@nglyi2w-T)VzYf{Lb9QLsfvZNFkaTKDtO zUR6%}mG=RVlyMm?(VQ`&Wd5$XtoL2O+h8qYe~$lHk7N2X8c<+qi2mIppowTGN}z`d zT7G0;4$*Fvfw?9PoG3e$GP^84*NvE29ogTvG7~)jVdLimf!Gz9vTo6mj+hJqLm!x` zvD`S#CVFuYMuM$q+6XW{3tHUYR+%6r84NU%-SQGE0%{H~E{|w|dYQ|1Pcdn1Azpq_ zU?qHEwrB%D+OZ%D!vRawQ^dT=M5{3iMNy!pMQDOmiP57jY*9xajVe?z3#E6(@=APV z+TF->Zx&v?4dadHl9YvhAb75Xz|hH*GWvi{Q@D*EguEQd-U!yJZf7EJ7iA?TrJTo6 z=ST`WSwhYlEKf`;+Xa^}E+`Kl5^J?4Bn0}&Mi@w)p_A2*RgNWT6{|d2Q&6|8i)6bZ zR$a6sGL!wjGvnX0cQ%+CfvR_&Njf-Pn;(j0T6w^a9#o@x!DkD-?J_%BgPMYl*`$U6 ztQ`iACCq7WQp_$1;o6cBHVp0K@RWcy@JSxq)sQDSN2fJqb$%~lH!bW7)%@&GYJa>W z^zqz}2_N7YQ!LhPRSSdV;mpA%z4O>rk!6tcD^!P+jCGNOW>MF~_j_g6WLQS*_J{~8 zvAj(G6mUfX;5?uXESYB#C!j2TCXE=X4T+1Mt{n7*a|fc>qdLO6;wS0%Q?DwuwUcX%Q?Ts2ovjw+O(SVxE5`CRU2Y#X=e-DQ#U-Btodtr8>^fLaad2!)GnNu6GPxm-GdEnW(n$OyH$%TM==j*x0Bd|_UX6C#M{vxco3#SJk_ zL}Af~j70QHji!-p(mbH%Vn>`x$Dy%6R<;`@HiPi8{V08b&XV1FlggyMl#k4T%*2Qj6sZBy zm;j5^K&8;`Os+z;$%92|z_*Ij;0F&R>}HV~dJ{tA$pA`?T@@@+#kAN|9R z{9$y~A#`v12O7dBBy!F*3um8!nhrpeGtk@ZQzk$t&yZqNsxL;oZ`OPS3A|FwGg(V9 ze@{b(P@N&e5Gw0OUtAm0?ppqD-!3v9CuvrzH=35So1C>`gv-&L(@W zw8>}A*`(ARjz8H7P=ffSGeN9;@6W3}nLg4huptM0EIW82XH5Ho;f4nP{FyCR_?%15 zg-;Go37<)Jxiwi@DURWbKg$%|d(I}=2x!{;JaGB8o=Y=ASD`1$xmT|KMLF#<=Vz5; zf}*N}bs)W~kR?EWM5_z8i?+0HgyhA}JXTfi07+ySKL-4YZ zHT)7pp6B4MmZJ> z+*}s-7R+b#{O<%Ks$-v!7)vD%{z7?+d~0S~vU)El@4UtJ5DDO5h*{1sjGl}5R-F?8 zgaQtjg&`XPq;82_;8H^Y3w4PCo|c`?5vh&JS;p!qP4=vLVe7SgJ%rd<>q!AUOMhU^ zlu@j#_oYcAxK`N_)KExczbQD^Cuij03jb1mh50RLsUs0PD9s*ft8H|yGcS0ix;Vz#cIaY&tM2uUtjrdOD?k{SF#&@2;h(cV4MWILvi zjiR-CMDYjRnVb_>4yzZHg^v;21f>oDXGYUnJJ^dc(2F+P+VZ#Dm_Wn&#%Le}B&mO| zXLD4-TBK9?2#G`w9J~J{8$V0OVmbetC!-lW;?wEfoL*n~w;hpNxoAWjVd;+uf9ttM zls4ONeE|L%(2>abkW_=x5Q~&Su@OvN*Knb+@Y?l}e{UF!>v24ZL1Lq35{Z8%)Rqma zKh!Db6ywYd3St$7aX#p)@ZLPAA2()`Ks}XZe^Y zTShCkaMxqL5w*)SGD>4mIX`m0_xaz|u!g`}(*EnT&$+uuRuBTvDJc&THE1l@EQYH| zR#C0XPq$yq0s_oHQA4hDimo8xq$1)3I|Ks0Ev~`N%&|k?6uaffkT&$KL6=3 z%Z%^7?J zP@9B{d|rA383d4tBj0=Nl%JdL@|5oPFP=W#*n7aGvS)A$hYXkg=RSinekpu^*^GY@ z;`5Jw8lwAlC=eA`05TW_1~e@6tTP(<-+h2mmpY5Of0atyAsD`-T+<7=!_gD-)lHF}sGB-fl%9qsZ*f zALFoon+j-Y0CJgeut_250I1} zoAFP)k6C(wKblKL!6R-{voleEP!pCyW*D=S?13O6G$*RO|%V=o>5*Ye!-otP{eTE`~%0g#@r<#m7WhfXRs2Rz~H7|J7L$jpLL zZjgSNyoRB=;%FKStRf{_U_r7BuVRN}$dj1n)+NJpj@4O(Ool=} zDXW4SL1c=WGz7FPTEoD4tu=%jmMtLO?^zjGX}4&Vv6gg?yJ|K7Zwgt^DvD&dK+-S3 zr57rImbR!sy0ic=Eh<11tlc2hN`_mnkQb+$`fx+g8IPsPI2?STk(DL9DMI6U`81nvx0J^t03aX?`3wERWG)3PL_FYmpcGI~^?^#41uAs62&m9q z1=LG*ha3&Pd=e%LLx=O18G0D!qFzGB-JwB+ek)ttOZ5w%_pi1#FWM`y|`?SkSfYWne&z z#~1{{Ceaq}jwDS#q}1Nf6U79*PUpP=gCV>UVq^3Y)&`7mfMhJPtp|zmgUIy92zV4O zYl`>XWCU7G_%kAZ2$fRR^cqKOv!rdd)fBS?D*mcPuxZ z!WKhrNcoA<^JR-+V??eiTgcEAxy*#6OVJDbR?!O$J5i8$3!x#M!>y8HByf!up+M>m zQR!2hgFJEQl09qC6cU}3_kY8kT#db^Dx}&!Y38-bcNFrV^FVBYe83 z)UMg8(2jnYZHtR+?ziLmMJ5*)>Gg{|6G_itA}W1E$8i|J05&LmIdGTjWzZ~la-oQ1 z;4t0AC`v*f9xnSm1tXx?=!ArpQ(~(4pPhE%ti^uRd7&RWfN-qqn+B66ExN#otsMz= z0xS>gsA&yJfGXMNCe?yt?%KSBQ&m2#R*XCYN_b0_0@@w2{^J({n z{91lr&+m20xi=|x-(AXiI4Q2rcgxiDU{YMXpofQ& z;u6_7=8GHhJ?{GaMt8$>e6VnOW+VczXJT3AireK%)4Hk1v} ztP)1t`D%UMZCY?e*>EV`RKpu*iqI&fN!aAphBikQT%#&|!Z+0LEzY?s>C!E@HSWp) z(3-N0!6#5h-0MS5&p^G-KyA9$Q!fFCD~}ddQ;^l%nwf(Ck6KAN8??kb32YQe2@R|OJTSHgF*RRj2j zdqc^B7?I2o9wgN`s-4XreR;JqBZK zG95_?201{SxHr1>ZUo)*L{o+5l29D>Pq4m zUD`Gvqgw?!4``Mh4my9WO9vz9L%j_;-{Ib91Pj=&UJ&*1@NI_j{3>^oTj^d05v*Lm zGWfbcA`_N*w7AKt0es@F2{~azHy8#yQ@Bsv#7tog&}^8Dm1=$7F_dcR zLTQG=wy8oBrAgoxs?a9Wk(OYP1H`Gj(Vge6pv&_X+-i440BCht#^4j4>&}OqK)u%( zsBPz|*8zwtjuuu^kgam7XA1tGs|{x@(Y|t4yYtiycOnibH&g?}I+nYVHz-YljQiI3 z>IJu|geQ~+?0!?sd%Mgu2nFoW@O=qrIeeO`h962BYIEQvLp9aj>^*wVHE89Q{K^MGao=`{lHI)f4Pp?(c?eucZy2o|uvV?or%!*)YC zO3?~;H6*ZN!Hv7C1A&ZNmfLf**kjcIK6TfIyfC6Y<%q7OriOI&(ZcEq_i~*S)&vcQ z$ylNG=N&_-rY@9bC~QnCG*Oy_F$QC8G94WQgtiT0jPPzJ9d;Km+3gJAWC7IOM+<{b zcwXhELtdcXYYo&3+%z>W1c;qS3#%*0F2ZS7@c+xzinF%pK{=TW{#}Y+FreH}4G>p3 z$St%^X%a4Q7ly{O1$R*ib0`g>`%N+L7nGR>p@1D4{&WdwIeeO`h962BYA**~1D4V@ zfB~2lYD4J~wN+VOIn*jDxfTffE$=qO8pvVtMHwAu^Ce91*6vT|yg0;!2Xr2WXJidB zjJZ{Tgj~qD0MtQ^6<_x;K13V=(HDl!=3TzDZ9pD#s{)+|G`HOa8o{)?a4>>ENP-$V z-|2Q6!SYITfog)>LFq?r3~Cf=I~w#xn~MBRQm?|YgKm$;5wuj)d&E*;Q#JrdXcO@B z`AuTqFf6tWP;^+o*(hw*W^1$DO_&b0CvyqEH%w>vkRH_Iny`DB*`7QR^XkH;hS$gS zx*_a!czsN-=Z7s2uP607*{^$#Uf1{Q9@Fc(e%*P!PW9`~>2-C#?v1>i*ROg#Zxj8R zZ{TfZzal#$47``n{cCs{a~Jcpji;>N7RGDW?YD&i+l}|z(*AKpzb#BAu5j0%D9)pi z7X{HK_tq0UKd)!DTkPaA?JBk>&y^|c=(^exZBL#lQ|?Q+==)Lu3#r80pn--O#r!jnp9ux@QnKB>o^dyATWSkIZ%yzR+@dhR;3)9uM|J&(J~ zczi&QE1aw?_v>+``!*i$)8mADHIMh|F|nrK!_%mHvs!o;kD1oh?a4cN>^ii8?a8e? zj=OK?>AQJa;ao+jLP@N^4LVC4}xMz>=bw6}+Y>2wEgnIxGEmTDRK zD{Hx0zKExFJc)Z{R8q^Ucp?ytmUEu)FSxbtdT~ghGq2wg^c&qT$%qr7Zr4Ub9lo-=2I(uk!wGPY&`_b8jxoVT`6&(2}+%-)nDS zEx{_y8$Eu=D4;C=_0Vq6$+ssDh1a0EY)@{p`X=J&X-nrT=Oq(|yN9%spG5M+3&onF z)8G8uzx(`&d;aL-&qqg#sp;PP*sze_D^^c`<=LP4=70G5A3gf@h=6j(qu{KmLtxs*Iakr&ZjG6vWXZ|qkGk`xN8ab$ zQMRO0G(+b2`*=A>^)=*JI`yVc$4A||=~eG@1#lcJ*3n#5Rt>CS|0M9pq(${Zi8TO0 zbJ!B5RCAp&0}y1Me-wb!wbcumYpmv)gA7LXtQL)|(YV)??Shk4?>njv=>RjH?%1s=$rZ z@vIn^G2*;2H0kJ}ju)GOiwj+O!FAfW9Z~uH2Ph!xAf_+uR#d}b;pO(HD?E?%nc-<| z=prN*J$a3=n<+MfpS>8qGOLXdQf^3FM{(t0k1Zy-K6tuuZ2GProj-Q(@so4$5jQz~ z`fvXFul}$9<3E1pbTl8l`WV2V^Os)%cjF?PAqUs(W+mw_7{gy~$95EoaW&0I%4U`m z?NM|&fjrm(<#R{yj#zXMmphTPm_8?$(&t)cKc8ZJP{rCn0=bBIh|qIq?Gj)@^Rrm! zz;Vi?(@|QWY&5n;5l$QhgRQ_LpYAVkb*)tHtjgDtUEsItR$};OZ<;S=uhk;6)y^F( zRv*R00hO-@81aezCtg`CqMLM(?&^cZ20#;otfo-OAZtbD>yH*(dc9V)R~s{_7**F& zP7E^XHW-;xf_^rL&^M4pO$N<|xyq!S4XUY30JF^1mbn@RS*vhO_b@)q3v3csd+ksnTMQ8A8dND<2I?nSXYvzB?Y zF(dp4S5O~N1R3R0^MTqq+d5=4+>aGA=QdF$L`F1c@1RD>SE9U($rCTQj$d=2=qPT% z?gN>n7*P~@*G0bRew$0c#T_Ufs;M59U6=BpQW%c)Lwa1L$2%i=h)BmvjN`CMPj*Hu zM@naxhKI09Pj^Nzg3>XqbGE8VCjrzboep)=MA>gmS0z|FZjP-HM&$qxArio>@`N93 zjT%)tcPbEHrF8jObr|$CAZoyE2vq|v6X<@xQ;mQcfEzBUL-S~A1fXcbh)J>2P)^ED zYZgnmWD(AMP$wrQk_xr}SP2c28{DRh=gj}#$S>|Z$C{1{&k1+SiKfci$dEc@ zFD`JzL-Hum1rCsdO^O+!6ewx3c&G^&$5^0-QZZJs0pkX}D`zEPRxgz>YmJdzfp|EI zctDs)hy?i(B+OcOK4L-ZG2A=l)|!M_OQBN2te2eFVDe|ZYD)}A7zh;@2qnTKk)}*e zP=bD@Bq^rI3bJW+DSlL4nbcG!5(a^Tlq$s!@Ts2FqLETI2FbNLNSM`*Hh>HXvlE@jIZa-{~JHy=E_80a(KqLULeS zL!J2UBfQ~cWq%>ra2`R&FvKdwUaa|%Jf|>IvgQcNR@>z!^R1bpBV;m27mi_i^!8Z^ z1mX#`8o-3M9&lm&a`RP;&PaX}Eu!*n-3fj1qiAcMNYK2c2t#xUOEMi>mkq>wCEA?5 zOOcaYQu%UT*|bIgGCC{Z2+Nu^FJR}_YDxW2ougsW00~Hurj1v5p~x1i0yXgi-b-oP z^`Sq>^TZ-`l& zM~m!WQ9o$?iOsD6Tat~8tH*hHro2$eL-II|*$4jZ0s2x!{YV>SAQc`f&h?mtsO+&otS&3Jg zM^>aFUsg~f#vvl+TPX?_h8%D{pbG$l$TAg0rbk#*M_d{xo67-Fb)@=*=)JHO5^zg2 zu1FzI&<_~_k;=UK^oOK2j)=ujpqF+Vnp9E9u(KEY?^z1|u-IWHERwPCpnyy0lQmE` zKJk?W(Q`*+Y)@7OvW12T0EIlY1aiUzD1+2VVMR#z93gkh1&BQMn;}5t4>Cxd;85d` z9r)h}7D{aIb5)_BTsV|KiB@um{w(1HuiqXd3ia0X90mS6h%(U-`&^#N5YU8N?SQq>zg9q_=ZZfviphom zfAM5vlBJ^qMy}%QA-xm*#^%i86yC!HudZ$JY+C*e65S3*=XUuXFqEG$)4d>VqwfaX zXB;iY?sKz%G#;lmig#dHac2cr?I=oyU^k_(aTG-fLc?I}5+YE9O0c8x)dT!l7-LOm z$CspD?5Fjq-AS3p!wmjzvv)0H4SZN`W`?9W)D8H0(0i&mH{Mm{s6`RbR`d})f9 zY_Qe&Pg|4Chiiw}W1nQD%)%yH8*STcL-##@6m{l3+_OWGrY6s%c~U&f-!lb#Z!YC%*)JbkmG~?yINN zkfLldcL~|nw7Y}=2P+%mQ%e~f3zeCp745d4sj6})TgPW6b-_*8~J6~iGq^v$uC<8iS9vR#4EQU)+Vu|ifMCR-*lw)QfbO)B0frOXS&>9AW; zliW*;96HR>%z}^LTf_B2SonzUzLn5x!AIE))HNP|(f_>;VByYdmgJF0p$j zsy0_YXjjLcGX)Q9VYoL-IOmUKw~#`8!%coMR^AD2KI+dAM0OZOFW&FJrQHj6FKa`Q zY34Nnppl)Ah5g&1gIh&(b=#**VT9>7!-Qxy$F3P^VeKXObt6CQ0XF$wetxhqFwNs& zm5Jw!&nggzuYML;X=i-8a6QGxXo$Yh45RZ=qu$5MC_t2nO_0;EsC1yksZg$FwQ~bb|dakvv|52Lk-dj_aIr5 zBt<}U$-xt0RL@hy<79!Pm5gx?6ZxjSZ5qv)t4gwFioY-^Xr`Mj1Wo0-(rIpzLw zl&bJGUv(fl4nfPI4gcITr$XH7IAm)tcD&6edu0?{r5bc1DAW#SZ}oq;?3+VX1{_l< z-9V(KMs?MpAtd6x#+Bq)&?j!P+etYJ0(*(-S0Mq=bn2AkKZF>e-3o97iE888sUB{D zBpAtP{CmM!S2-4Cag=keD)mM zlQspr)d|b@L<9y3=5U+J%pxgiKqZ9W#>`EuH>nj$Z{ipmQY$EqA&XCurHW)X{)?A3 zq2-fSAVn)No9lZX)jy_gRC)wI8Wqr}J_l?7zXjZmKT$#G6BcHQDA`F~tkuV@QrAk7xMX=Eghtf*MY3mZZcn3ChrN9xwj7C6{hDAXCZnHm?cJbffa7^D)baKVkNZ^#S% zG~GsacC0!wKryj~E%78hh^p^PQ=aFJO1=2VC5YmxjUxHR! z6p2ca8=by!iWZDLT>WN_18~jD$UHZKC2|TUK=j6MfpmN^|Mes!R%pdW+z5gAA}9OuWtv=Uep?<*$-;GVSWnjiMK>E@Np_S3I*2b9gEWC$L`Swvtir{VZpW zF)S?%HnNBEEfj(pX~{7mt16q#rEbg7;u39pFBz0xY_lmY!GxGfMqJW2%&giA<$2j#1mB)>u`3;%lKYL$kxi$Z*YIT{^@-3p169+D#z~&7CGPSmMXKej)Nw#KKSw4M1Q(Ac8=1Ir!6V!;d=Aqapy1X zD=9DdfnzTcdW`njwid+sjc%=6aK>9rcM(!p`y~QOKw@E6j&W{zR<(sD;1zLm4>jx6 zc`y$m3WZcV>tQ=4WQ|VJWVjSC?AqEU<(V`ZRNZu$THpncD+WtvR4yFVGqYqHEDiT# zd>9t?1{%0);HEmy+*DxHW|RSjWOqeqvH=}*EhUtB?4E$%( z?4>28pF!oNOJKPgHtFRG0E{hnX;@I8clsEj_hpUnf!?v5ZWf&wS>_t&ER}H;7@6`O zXqwodLbNT?NTg$-mDj9)JZ(}!@H;AlH|8WYm*0hQ`2~V;=SgmkB1gH$p)u3HdG>^555@WuJ(hjKPqr7cu)suaExt}ar)#S|#4f_}5Ij@c)~gSCy%uEXcS zOy`gD(sWxw&V#%lK|~W~s3X*@E<~YLb_k4Mg!ghjkxcN0uQsvatY$d2b}DBz(7L8T zw-?6J0rb$|lvUV9)9i{_b%O;`Ha97xy`Qry-4RCqiDxTKXjtCom!Ov>$Us}i2WnVt zFHt6!$UlZwb4k#hBoWw03entKLYuh+j67F8^K?OSx?%imp01vGx-f94yqgGI)f1Q1 zf{Ux4R5BXGu({A)UxPycD^w0Q#b$!d(%=Exq&E={KfS_koIM}qnrJ^I0HU%3cZW=F zW>%V66m6?k(HEOGh3K*Q3g64(O&`|UplC@E9oQVuny80_VWK&MzRsYXr4&;RiR+oi z+2&$RS{D(Eb%UC(GofH~fzJzy7$EmxLASZq0{ z%C#vvV`_Ja-urn-Pq!LH+FNjGNLNq6fyw-4p<+zMNQf}&MkJ$ZA|*=y9Iu(K|CNr7 z$s*1p7H@viUGfaN1~xo1MJXsxe&q~qvrqD1Wn%(wMe)(DnH4YNDgeFM9sh$X z08%t3UBAz-ZVo_r>rpok=*ED3`#_Vf<2S1a21rcUsHw7&%A6sA7E~Wxs~DUBJrfk- zc_tvW{`pbQ{OEH9{d@njvSWU$j(I$z<6u1w*YzWnQH7Xd>38^DU*g>o>RhR7H@kYw-hs%2ls8nZ`h@HGlvASww*egoc?~J}6ahvZQ zApQz$a6LZktZ!3%3v!e_3(+r<4WOgGRG@=<#ICI5lsF5hTJf-om4!GxUpA8snlXE$ zY~3dHAR(Fa*`y1;rV{T|4e2F0LF*H`$q`cVwf#Bu%l=uotL~df=QE&)!~B*|ur>~$ zKrV^S*}TO_SU@%;(egSe>4rF7CPU6<9=o>lVM4;%X!8vX05z2ZwGE--VyyXq@39fjt#YA9zkn|7#nLx*VeGj)>V+YJYGP^ zg>)urS^kCE$jOMq)c}0|M;0-gw2XLPVZ|Bl5G?&BG%-W?5=nYpspO*$vy`&466f~6 zNBz6MjoIG}=cS_2ndT0epn60i0zM+3s|BH26q+8tA}A(-U;2R%Nc{*>1>WFQp;J-k zYiYQjADx=Z2tktg3uQJo#l?tLuUyoG2tj$%>LN)Yg>Ek9lc}Tj);-{#(HlAsc|>wl z*_{2b^=YWa*wQlnCi<1E#0sn$d;Rm)tk1;PHpU>dumZq_rF0`5PbG9}-)-<{?dCcm z1y>#vh=md519}(DBjHRL;PONX+PY+~_GNZe5tw4WeT*>f=v0*L%G5;4{P$i;pbz<%D4@j#M}~8HSsP0%8p~Do0(;EL(S3~>`T?3lmcBT$4KDn$XB=3b*`{!CReIBdZ(u!bsv^4mK~^{-A~k!A`5Ql zGKiHbQVv=%;TP^KGC9X42L|w8XRF2&Hbr|%{PFh_DYrY~D?imIO?5pT7fwpW(7p+@ z2{)|z;xp6`aWBC?nvc5wmTmnewXEu@B~mE%-TXZK^CzkIc0#_mbnAQhQGoX|3A3%+ zA9Wej^)#k8U0rcGQWFOwqGrJg+SN!qENKU*x|J;M72@6+-SEYSXr*kkj}1nWde{fE zk1gNXA?wLe5@HcF&-S6gDY+~(w=9B2u+^cD0L`3kAba@$R$D(KjEt?H5^NX3%Fl&W z)18ljY{C-Z4}ze=cTd0{+wgL$Yb-8*AX{k*VStfzGhzZIgpaf9``8gM!E4w~aTzBk z^jUV+y?s!UKFiK^`9q&&hkmAeZ$+<5rn6&glJw0qsEGbw;;Za0-3gz@{=4?^3O6$K{K>SVFJ7RUWcCZsx8;w$%^)>`A*LSE8g^u=$8Hm;`Im z$)#A^QMb~NP%ERTw8ER+8;=L)7+m9!BN(&!Iv!Es*+jUkN+vA0pTnwmmKyTeY38u7VEPC zvd9@8bscJo6a`(ZX`8FgwkoOyH0vwU3IEQ-GV8xB8ZV8}X?cjwz-pD+AItRIXTN;e zA=e0wJ8_v(F56BQ`ErCZrT2_}Bq6L|A4tM~5@PQ(wq?5CXwS#@hnJl3l&SK#M%*6?skdp6U1x61pWI8uoBmoyv&Nco~ zK@Qw)#;bQ3hFXk?{VK9~RAvGLRtp`#f+*pGZK&{318NSP(noWE50+YzUS!>; z=M>I5Yv`@Xvsk`kv0xPlc2e#2Azo~3!XCP#bagH^?DV;K2|-%9NTF&eQ>*m>QlLjP-pG+jB&K%2l)Olu z-81DwQ-M^A5S9#Mk74vt>HiDsfnOiqw`g*QGC?EzIS8$2Xq#ap(mo|%Nnx3gC$F=@ z3pj#MKu)&bHa?h`;sg7HCDP4jPf>}C;7czl9xsb_m{d~9NP&xBMm~eXLx5}#cW0vg z#|Ru$RjKN*YcRw=_UqCO*|I4_tVcS22n!GUW-|1_KcfoB_-%}YpF3Ui?R*VRY8cFP z=UercP#%)``(36bVWEB-&J&9Y18RK^K&7Z#sQTNG0I(w&M4dzWocq`%cI6}pYa_?r zbp1LTJY5103cFjQ$X|iwjIc&$FY%1LRq)bH$PUPV#&2_N5eU=Ajm@t%Gd^E|@UNyE zd7#e>Z<15t)xLb-xA8dyk@UrmH+2Lv85IKE)yYiPWO%L9~0% z&y|XSUcQc+V_E{u-K?dEv~N{u%+{5$2$LlUg;9F_pta6DDU+3;S%_jZ=4}enM7&)Y zM`3LsGd(3@zJVf)JnWwIS#gZA3Q?tWbY;v!A{JruDw`o|le3Iv zVeKJQ_^t;;4k1=Ps3!Jpb`#QS3MQ&UhBW5WoJGj7VV86z-p7zfPqOsd2DIOW!#Jq! z)s&yJ9?aa@FTn5wFwF`EJZH2;gH;`yi10GeZwgq-rUsC(nFA8c8)00V_>qrK&}MQ% z7`laZxqe}j=RbHd^0_~$*@J)mlRWqh{`vC#LrJ(KyxbVr1oXE9XfH_`7!T3U&j( zn(4#9@Rgz<5YD<@l^{oyXFxHSU|L)ql41$=geu%BCZh9C|?2|3h6L-Dh60UsW6QG zoA`r~*JdGdGqXn~`(V#%n7DwJX$P{!Y9=aXmudP%nh1p%&<&6TVgQVFl}%HM*A)Q3 zGIGd1JcZOPSSZZq=3xOE=zv>k%x(YhFPwx16$UQ5C~4ll{Oee(erwit+*`41u= zu@kH>!BlfUP{VoCop)ANlgi#KYYE7zge39=!@8gQrLf|qx)>T;vl%%Y`#~v(GJnV@ z&6^$_k0}DltfX2R?KbZJGhzQ5OO4mDAp0H&8ne{vi6Zs)V21HnQ!y~;+yss}8JU?O zo^xQARYsor7fcZ9LyA~s%#lB2Z`n+;tTYZIjZD1)hBK3(RV%dKbV?C@1JGo6)y`R8 zSk!Xke}|Sm%ZBw^VK`>uQvWlb!z@~6sXp;7GB=mj8f28-`_>!mT$Uq@sE5t;!1`?d zG>s=JVw=ujiyluS3-O$O@kc-S>7V(=@BH?^In1||EJ$toKYwcBfBC}EFa71$IM6K+ zhv7gT2wO~|Y<>o{%(|kjgwt3kEm;RDG7@1BvH$(!jIY*v9jh?m1A38V=f^uiS_gw$ zfSg%1rI!Xg7yGAi#EaVf9LFmJS-h9e*0Vv0{2WUz!}N6W}kc)n<5 zaL&cP!>37olqWtXtT3-OHpn>zC(8WI|2-b;MkVz!$@B!k5>gR zbAyeD5_>l^p;Z?(NzPX9^tA-(-ZWXYN6%lb+1*3rql{7fNp%%IiK5Uizv_V85}AGx zk=h{%pvNnKaPUNXWo?#i4vLh+cSf>{zT}2h2li*r6oAPpU@-%URs_W?NbF|*$w%lE zd863RhJDq6MAj6(M8!aB#{+%r!6mp;kKo(Cvsu18fvY} z`FBj_udNiRfoDDhTfUu1aE}?mC0bo#?-YL$W54WsMYN}rd&I73iv7dC+nx1Ke(F*w zud)C9hk4@$7(M^|3!Pd2)lPW%lc#z4rIq33D_`biEIj|xzv6i`JRjQYfBtzC1=YTF zuYcwX<@2Y$!D)LKSLx3m*)i+y{g#^5>KOZHKYUVWO&&eL&tm}gtNea8?fsKd)W!a@ z50SFj|I`9M#}ohYU*mTn>HQPAMgKocB10NA+A5SMY}ki+zL?-B6IbF?n!%U@iTq7U z%=&@6%QIeD?f1ezc#}v*rWKZS)r;`>rP*+lz!;Lt8wZPE&CC0i9Lidh>Z4^4e^P5&iG!A>~IEMzY-_X08- zENUWDfX>IZc{gdtICPH^pq?aPCq!F@&ukN%W5Sa$p<5xid{bY6fq58VO;+B=)R}_t z0Ev$=$#HKey8}>(VldzY`rgh}Yzx&ZooU%c-Fb_c3WdWzrp2D2p>70~i44lX{=K)D zEbjbN%uXIl;v5~SQ)lTs?6GPY^fi{Aap{7u%3%NSkHet308>!lMN~-XhG1}xSGV`(Ig5S zl)yuGCPV{>lFZx6uen+IonsG*E<;x35|xh>9c;+W%13sVakC-=Il@`0GyU3fFIxG^ zy~tcGYF<8B&X*l1-K|yeGkUKQMts)ky-uxY#+(h1$x`f#JeShkKuX+sG>cp1MAbG$ z$u9nByQ#SV8=6$~diMNMDp4Y}W#*ZPPTM!@IV;%gvefArl9c+3+T>&jqd#rT^d-`! z{8uf=SvPSh2bkmzi+i^QPOv1gmXb@ViWxFI?))|?VwjLh%(Wz}*I=FSnP)Km-Y-VG zr^GU}&gPk@D~}${^RuJ}9P&*LxPUzrngbnTq|lbf5GdURFbvGp(29{*QmBBrW=GQ~ zjuZ!zLvQTZjARLB(wZB>nzV(I$Y9pNhxS3BEC?Iw%buv7Q9r_*YO^DGVpvoSe5JSA z>`M#j-70r6tgPTAnM?E$)3sFxOPW#gETHX1!Z+VFHG$U8Nh_)}3> z$jd;KbQiP3m)x|8JV{?-|E30}O%f@4gcfm}~|w}-YheCwJC z8yY)2Bf6@Q#B_!ALCP>2{Xhh@&hz`y^@rCUiVte0*Rgbn^)F4M{0b%Lq6}T8$9)dW zeLlgW@Z=~2EOM!4n^9lkuJ~sx1Xp~zaMKE^;(v`JMPVCK_N2{u=dklm>wZ!GH+F>A z*YE|F({g%GaYtl)B;QDy_OJZ!MJ^Z1bDFm#;FOZeXdBwTdsaCz4{R z$XfgQM_4=jQQG1PWIbZoe294ao@qRq%Bt%I`9V7IkC)rVB?bN;jW@cUtz)+x@RMb}Tc(c6m1pZx1c;&OxLNH)o$ z<5%%fTpw{fb~Ht`Gf@shiZhYq@&wgcf8?hw_8 zIB=WF6si`7ETwAhpiyM=pb}{^_VS=`(~G)AuFfDC&^#83;_l)C&Px{?j}*CBggNF8 zZ1YB8t1L_7^P&T;m~KeON&FHpKL1+WC_Kn_uO+kT2`2Y*_x;7w>279&3ni5k54lHvF?{-D{C;AVbnr`f#63R({Yo(fg?IQT{-pxs_55^!fE10*u^HdzI@UPK#0gty`@%=arR zNqaZgjTv*s^>{nO zK)~ERFWD$oohz7t$nozx_(7U}{k~s5c!F)NI4#*I zX;8rjWxLj8*&*F&!_Jq^`6C}SWg5mKAkN#K)$u$7I(v=9tM;Tu}u zuZ7$VM=5nLK0yJhj%v(H4B`Ck;gNmoSCU|%TAvQgfmP67*E07BQr zsB2S4#b`Fah#~outZfKMhB~Mh>xzU5vJsL70dl_uCK{*3(h7wC3$WqgI<}XR z()NMoILt^)7AxkXtu7{Xb_wA;fT$1%SuZWT5U;gVn=mgTIoHLi-l)P16_chJvz#zn zFe74Hcd$Dn!E%nO&xay!^j;NA>2^n0>m_6V43q-bY}9d8TK!W z?{8u^gCOr~a?R+d7<>@i<9`zCxStvXp)ATAjy_vdO%odG4B{Cf!3z4J;R0C+rwr;Q zVLHwswm~GpSz*Z&DY0-6&&V9WGJ+neVPC8%(N?vcAhu6IXe+KS&KDPQwa%=Tby8++ zWp62@AD#-P=V#mHWG@od^Wedg<>TJXT*U)UknJLmW5jeDkqn9h$%E37$s)GzW^QgC z6h}#3rnoHyWcifhT>B>xkV9D33(LgjWWJlT+VMThk(@iCOkS=t+RpNE$$`N?aA1+J z#vt0q!3Of8_P1K;E{3j?O8^o$5%!e~U_4?`^O?q6SHpj>LsN2>UEf*R3Al&Yi56dy zorD0`X^pXxx7CA44!e&DAh`@b$TNQSa&u0zl4n#id1hQI`*GpTv0zQ{cR^eip5LHDMcusMNYkZ>aP@-J0Ddj3cmR2TVY?0_9 zIc$3?ZAf~}E6{?_IQ}1}1re|?rVZ^+lpET9B)Bw|rXNyYkmmmDDyAHT-1iS^EGo9B z0WN$A>(?t$d7wS8`m%H$jP0g`OG@jm!p#*}?9CUyJf$e&wS5K7zry!{$`PK}NSoqO zUZ^}O3j&wPytFRZ2B;o&kAjD+JqB%Ez`;l{HrFSPUumKXKA zXcuD=4wEux>oeRQZQEU%3e4_Z+ja|bkYVSck`ERU&tje4^caguK^$uB6-7wbQK-9R zGR-Lw*LB=~QIOYW373u`foaZ=$ckEoUkJG_n*|u$hX~)&zSg=~0X4E&v4`Kr4LORw zCRF6rOaa%S+2&E4`Dn$NiGs0vI1tS=p$MSF2d#^thT#6N?Q!RmN7WELjhEIcV8A!U zP2rAe_lZHfQq^>iXZMtBhl#v4D$})#%F$VSZtH=rrtDQ_!+$?^ z%+iKWb!~%RiTT}pb7yZ(cRHYq^0h;DGdci!uYT8WPwvt0dE1j?7QQVNuJ;ZD>&a1h zh!wAii=3-Sn|Xxq<|N+<$nf1T$@cH?uotIF9rEUE81m+9>^n;4WttoI=4|MDb81t? zh4SXexl;O7%$rltc<| z<|GK2RZGs%``!FmeSZ^;B)-nz8&Z4+HeNi!X-7Dic1;2FVQ|Y=3-PVJ}t8Dne?SZqG*b4V5 zY+vqG0FSdR;HGq}=#~QVH^{MqA#9L8S2|X7q|plCSXok_>LCdQvNH;lj+G5^tXN&V zb>CUw`iy2z5#~qqisAwbS6e7H++A#R8;%w3^7P5N!)2k*_*BDV%;j(|1k$UIsC1#y z8?5vOcj244q5{tn?VjPVsSO-9eJ{)gC<*V(mf)%>w%Y6W80;))glmbZxXsCUsfy97d zuPher@{eA-axXdaF?a0TEwpkKgB`j?h3 z^ye_`4ShZxhR`P-`JLfw8ocC|!AohAABzsY%Mmutra}Lf!9kyX_X#RWJ)mqq_2FRs zt?)sCi(40CFV*kE!B@1?MxBgwpH3(c zJe*L2;H3JcKsTKeIL9uv0Be=*4fef7??K4`4&HnVOR_xCzpUil&E@{<2OUC-d2}=9 zaqoL`+Ryc(vVZene{v`d2G{qq_!2D7g$qA7Gwl9bkP-&v{x|b8hIUl*UFCP5I8naz z1W12ndG!gPIk=3Da2VZwar*M|B{$=3{jQtK@4mTwJ_~KNC|xZ3Pn@{9|H9R*ysTt( z_qn;e1^JOd~CeL$ayQ&?~mtORg0#?-OsPV`-4FS;Jc z4)}~!O_xvf_w#OWGaVe12iWn3UxbS{_nSH+5=A&_=Ek20Q&0XxVqB2&9~LL|Cz84A zD2E;NTjl=ppyp(UJ`N4R!1J3~sVDj`1f}*dD%##hCvNT^Sk3x_7^C-0!t)*Dz%lNl z>x@ynvNJL*rA7xwPer@^*V8*b`TjHwH~0CTG4C&5g%vC}}b&Y-fl4`1qMlZXb6GOE1 z_E&eJ8~{NjDW{NHFS{Q6y$p<^*2}0huS>CQ&@%nix7@-S66(P31+QX@1mvJ9YH=XA z6(%8AXqEA-@lBT8a}>|eGZ`dg!&15;4gY=s|I^S@8IMo3Y2nbTipPEZRluvM&g~=8Mni*X$4=0X!G)PmWEKz{A*+XAD0kwUFYm%+lrKMY_bu!Hd(j zid(bscWzJqPOISg_T=syWW6^GEAcAT5l+ykf=OwdpoFH~m-nq235CDZb2j-vIo%X6 zVn`hQE6I&EcE?v3zS}sh*pi832L%{?A{8L#RA?+I6HkAx%KC+Gv^@;%GHxryFlM|y zT?8GY7yYbtqy*2ffI#{PNWvdRJRF(}$&6LX!gw5R$83N=oQyIC?UQFn158+sk+UF? zuh+*TOY2cqlpG=)f9kPocLTHz**D>G@ z-*=K?*@QfGfgNVDP-M5UAypdP;Y4#FC}Yy!gb;}AY? zpb`_7z$#p7xJvQ#M?>sfEx_w2AcZbR{&d8@X9l0-BVj6hWCDD{H{U_IT~tWQwa0W+Jjuacn%V4!i$NUH|N}*ao9B1%-bISWA0{>IhkmS zbIc6b^4w=eC5)NTxk;i1^qAz_Bq}+%cijCxHXYa{6(jgDo@U1BkUUe%cVIf;;3HY$ znWtmiwK;t{o)^<`iPM1v5T?TEB{%emU75kP$$vmT{K{;2Iw#~=*3TXCk_RBKXLB6# zGn?GX%|d|Z33+}5dAOBpO7b0m{Dc&T2k;pd!nj{^I^=;l3t_L2PwX7BO7x#j<(>W) z^a|FF$L*YuZ{)J)M2#q%gfuQTLBG4nt=T~T9At=}|Ga35~Q3@mHC5Y4cNaIEq|AaQL&h~CEZ)G>(RwIR;;oU??q@eJ!@BEYOx z#^j)U#Vm#SX{)l7r1}s6QHjuO2+K27H{pS@53|(DZy;te6#h%<5Ce2I@pFt#1PjtJ zM*rVl9SR20s6*-Sq#;N7R@9*oQ)^DQVs zOa{kQUV~g8Dp3S}q8qWra&dQrnN^v*f6*L4&mwg;T98}%Cxp)C#dlEVL@PmaQ3^w) zCnzq2DkLz3q|iJiP@QJUG;4*@yV=gxh*=a-EgzS*8v2IP8FLWoC)I3HKetde_<8Zh zr3>|e%#14!O2$K{{DiJHuEkNPE`}*;Lca?m)i>Uoj8qeEz_zifYr7}n+(`)KY$HmB zH%M7S^4U`FQ;6&y9dmNVm!bKOIXPSSfR~n{C;_rKIXmIxRPNT7sQ|OgvS#O;FP9@0 zez_vF3Zx^Z0>OuGzJnP4Po`CEBeaxx**30=Zj)3cxB;f{FcY^C8fh<)@}8PH z=R^MgiYfMyp0Lv}q^Yqtf*tTV+9z1AU2=#v=2W!4G0hJSX+vl-4y-uNbI+^SK?!B`3hCWsN>G)PXz7r)b z{OPdxh47~vt#F>vHk-?LFJ&WpGGc16X#Z^Zr(7L^B-OOe7FXR2ZpK1{4wz9nYm3(4vJQmGl<75Np&gwsYYJ+rQZsx2vX4h%N z-2z^?8`g+tK4|k3xvuy0=8@mom%j)xRG-GZU1hJchAeW}yFoUddX|OZ5fV+ABEhzP zx1?bVnz)zKX4TCcENKVa<{Q_;4dN*`Uo`1TT#3{70Tc zuH`s~fFsLJTW%OrZRsILJUcdGW;;g8{-TlrGwzh($pKe?Y-!ENdwZTXMgJL38lR)) z9!s6)&ZcK>y^akRc^aUN2b_P`@lo**&eFzs^E+weV{at(sl)NX&gxIjcanCFP12~d zG(kpbz(4Bl_!Pm%zCBZPXN`b|>#f1VX^HXQ@$Z2Qq0b}81hc!qgak!(H*XUzA}O2Z z>MQZUG*{#bFwIqgVg8=*KR;|P?*s8D<*~B|INF8 z_5&xc;@H+~_3LMT`Okj&Pj3DC#h&xD!-+m1?q}8GzimIV;duc6;ZqH{g^`!I1HoRfs2IW{>qMTtm49^wE{<9&cvA?ALEBg$~GZQ(D4!kEf-fQ9Vl7aT?j#+oM2dobQBuL%X zz0I@^d36gA46{seWc;7s?1d;$_sjf&W>ioA?4e_v^}`NGhHqj4!jIW#ZmM{8Qn2fF z4ZX*4Xmm0c#f5%;6-Ger-lXO4K=JxM5cS;-TWcM`SG&VKuEbp9Kw%*Kn$bb$)R=>6 z*tPNaC*E`LVeD$0|!uSq_7{J+r6TQ!^o@0{|vFz086$( zq#F!G3c0rHL zA2Rd|l}o0FHCI*OP%AKJh)P(GKqDqwlu7uBO@qU0X}2F7YmTX0U*GR(Q^__!3RX0v zlx1YW263iW>}94J(Ud=+=&q79wDaX9^lIOA3Rx^sKsi8~ugAOs9`>JCt@F+7+F)PR zI!@%;N}3T7eOCNhyCTcj^kndXSA=}T8B8kyT?Z<;u~1Kfub-a|6MubAsK*c=DkrpzLC` zUYH4K7n8Sy(VD$P+snPCmfK$LG4f`y>>nLmTHVURJxINa<@1gX_BXe;6Hgf#E*SR> zguujyd&=kg;dV7dLRj5*v{+tRy}Pbqe{;ph9L|mc3CydRHFcS|EF$P}5lkpw5NGT% zU1Cs+qf7}UEO(KOr{%A-)IPhh1Ei2NHAv!PF!7`_Ts{2RT05QPRNJT6$!qXRyxH)L z1fo+8&hbWVbFhtmY6E~=R8NkG%2K+Ks1Qn^7zXMa9MATikjjc~zvZ(?F~Kn#;4UPC zuz-}~%RZRedrr5jXHV5(FVca8{OJ5FV4gdfb<}&JKL%bge=NB`F%BKdAzdvNd`%5K z{iotsBtDLcc}LpRIx|?SI`zLZbbHpp-nX1=*OvupT{+1WTkkRA!U_a8D)$ zOH}I{=E3s?@1Zwtw$5G zFuxZ4N>kKx0W7Kqiq^;;IsD{tKf$klWx*9kM^Y)SrgYcT>09ce4NWpv+67S(=_pQs zZ9$uOE?suKqOBtt4wu5*zmlmQ8?F#_6x9g|HR9=dS#BGNr)&U$=5d%a5+MX)L6%+5MQ~%oVpw|ohoGR;Y1rtsun!tDOCnvU zWljb$g9wW+z7=O(HBxB{frW%^q!@sMQ3l zmJnKMZij+EyV?<~RWtKk^`5%$Qgc<8!@Pi4Ak`?8+z}Jy=rLMMWe)mvEIgpP5Q!1U zTQd{|;7`{gHf~Rx#}x^`f;9sYNta8UwW;id!)*+;DrPu>WXDLD*#%_<^+%wQA6+E9 z-%RZWIJGSb$4jJf#-e(< zs*)LtJi>Z3xh!%tr^McwlP!#_3|y=w3=mA-nEJ##&V;DpX%E2^M>2=#Rb#IFgAX3# zJI{3VHy#Af6XE$GYN&tnkq{4IQBtzMg1pWwylfT2=NvODnpDHqAg@os=`BVmco=Bn_%YB7cu- zLV1gr@14llcm6qWRT@=tjvFx$mQbAJatZUdp*6}WY3-Cfifl4Ylnmniy zlb5zRz#!@#GjAbJZkjik<=DKTUpQ}MS!FDIIGVNyrX8XIQ=qt`cOy+@nz6~beLZEA zIO*D$NU~||Wq#i-0qubT3nq3rue4zL?GVehjU*+-tf>-IBNL4aN;)d@YW}E8?J&|8 z%_Tz@A{2+MUWv?bK_5H}Lzn9AHvYSS|1RXeUHrG3|8#@Bl9R(sy-AuM8Sr*2Q=HSj z6Rg{~Cx6@EVog|F3F7TuycOeL3qH9NatxXksHd9PohvWo{e_sukl{z~JLz&8?=Rr}1 z8poQopX>OZVOY)3H8*T$3j;aoFlQ>EF2ieAFnnD6)-+t;raSe*_&ZzO{8l$SKlOKnz5v%}NMNFEFQKP5EoBF6Gl>bASq`P@sU|#YQ9uwK+;{^Iw zVz;0(P?ug$;+q-gDT7*nISN%WG?oI28d^4xBBL;4_{Iu?zjn)_Q6RT zXdmh1UTYdrf&D|u5cGgCv4?s>fgu@lt-u@}Q(&&vZpa(UdyGbRqn)>=h_+=>g(2D< zvkc`O$~$s4I&m5@T|k8W<(5G;0;BvQ{EI3AL!KasW5!5G%@y_XS9Sm>ku4|1#HmF| zC1O$5=kdYJrdm^-^x_n~sH;Y=qP$*J5U8<2Oq77%j+ICfB9Ex%4*QCSi*48TH8`|F zgLhGY9{QH%tRt8akBCd5e<4t^;6sWOXt)-l3$+MI1w-{)1qe@rM_Y}HkU65GlDWUt zBO1vLAsC%{bR(-?Qhk!2*v|N%_xVk-fckqkHWwh_V@lanyK z#a==l$GRho5*ILrgz=Gd-?2Mi)_KceaVwjQLXxV{~(7QD6Uz1V;@n8tG$VNA5M;f%Mk{`C+6XIdZm_F*_1*O0jKBG0J&59 z2#_WzhE!m<`xvP2LQz=@wI-PFgeIb{vef7*%*yOBO=3!FwV7>h(53Z2Awz^1~WpKs5Wz1}IKb;o+QW0k+9#4Y89`Enl8#dg~o0H*7)Mlrar$6vX17 z`~qKodFHmh-JAd!w##{4_7-B2q6xNPRlA$-7$_P&2fy*{e0B92c2wfa(o?9+pY$qD8T=0xjp3doD!xUYvFl#qfe~DjehGQVG>M&peW!U*UoU9IQ zh5w24ybhb5+hxXTUuWqntHIpw#HLzy-7bczNzu4b1xOTOxT+^r$?AbWg??O7J;_hd zk9o#F9BR1I^b5b1PJ&t5X?HdYn>HZrb8%Q3eTot5KOsws(nh(g6(L4EPtjSdh4mQO zAU9=Aw2MN~+~dsxL7V%cRNBw)mn7(Z~j zP4vYLu=AM7a5e^Qs(Yvo*!0_Pnl2HR8Ld~?ZoMMMU$IPlHZ0s5iuA(avKWoEn;Au##~w z8~7ya3LfDb1`r9^ALiVT^tOAD``?xNh(yP%_?!# z22nEiK6PSPV>N>n0Gpu?k+cJqaA`yfvqm`t&{^zh$as=MSVf&^yRw@o)=y=M5sVn85d&p~Lw z^s5Jb;b%Af;%6sJAy+`19XCV*5zUlH+?G^Y8GX1@O!an9uggv|W6qaVCK=7WU8Awg z9R~;yusQd|I?oj3v^saY!h!vd=?Zf?FF4CCW+W~D@wPTmy@V-LiX%Q0@EFjD+=W=@ z^hw!-ry@X*I}p(iYI4^JQh<@CSyJY{33rl>CxA^GcMKyI2IcB;58H_$4@X)LaSl+^ zi6KS?l1DcKTBs_4gvxKjiH2;BkTB3Z5NuHtuGbB}V^y*N?`e)t86q}y!Z^mULh#SA zOqel!GhdC<0WYH54uQ zrvj*30h@mWQB$B@eenaVDn%cf(88r$3np?Z3Et?FAY?NB*mWm{QU`)oz;{BoaDa5> zKpLxGqqpEPiS>gY2F%f!6Ni*>vWb3}nbasBDkwy|GvUh#bJGE8#IdV%%HR=ext~Ek zWp@ol8EAAx5~a{xJ-h+SPrm#4>M?JUjj%{JVLgn3Pc#KV)}(sER-lJfNg!VLeFdk4 zJ9s>9EF4-2-Ixb^hkKbE8?l#4{5YCAUhM_583p5vHO!OUp@=6a^GfIpX2e3qEe)~= z*<^VNNr@xA?49oa00hf~60CMOV8CRCnXT#ZN7VNnof&N6#?dL=e7t$nilN4>?{H-> zHER4qG6Y=C%MMru6z0m2h3++Kn=eCzI3~XQ-etv(lZA(Yo?$Y(`?Cgb>XC)%7u}T1 zPT2Rr=d(q%4FmBF+~id`yk6fUYvJ`JO3FXO`9Kvq{h{pNT~fz?9?Cu#mxWJ%IxbsY z{;zRK-G{Od#3i*H%I=Cw5(*#6{%u^&dihgv$zTp;KN*+vUfvm(3ts+2T%zM1%6>d9 z7rlIcTrPQeCN7t~{IR$sf%Bp4eR0|I^1X4n;^iH2x!ue6#N`ez-yN46Idv#|S6p7; z<>|P*(90i<%Uxc+GcI>~d3#)5Jo3>amk+UV2N~MpeVPW z;vHI64Dmj%i*)_(y>6xJ-}btlu7AsGG!(!6q}Ma)`V(Hyrt9DIT8V-(zv1yLT8Jzf8@*E`bn zFL`ZvC2jqp*B7Mgk9vJ!y1v`%UFrH4yxyIzf8Ohh()CBYzBpa~oY#u?)cIkrYw&*P z5*}&`e;~4@Y!qSZ;*<}}JZ30ass=}S1W}A&yTc|uznyGFCw1v7)L*-_fDmD4 zh|8)wQ&Gri^`mI$!>OSUPXUq*B_PdL`X1#uKxv*K(CL5@RD1^cmAS&ntQ6Wws>wWL z8%Zc;W|6^|JZd9)W2S)I*bfi%mmHU=&hDQItS+C72>~U2ZdvzT*MftVZxKMI-+#a_!uBNCpu# zpi2tyniZX)bJVUWjQsMtctRozJ59PmYFr!&x97m`M0tpWtk>UG{EjuXO(93yP=i#P z@Ea@&s>5$k9ex|CgU?v1u`TdBA2!6r2)|oFb)KEdjP+AQO;n~6R3@$c=;u~qv(0Fw zt$i308&Gu$v3Ubl0C=`Bs>5z0S2jea6GW$OS9~t29TkTyjIbh{GYKo4%p{wQ{!-Hj zLzvFDtVwQZ4!)hu4X#Zq=A!$_YazJku+ zmada$?v1u!@S32V5xz12Twe5MVXbW6gx;ww3AM@I%+ed|F>15t(HnKMExn%Pu?GwA!YtST!!rV>9`Du^owyB za_VQ|GNjjs;&M@O=7G2bSq^1ih|3UiJ{y-I6@NZ1LstG=T!sYw`*9g^_3y=HNZSv_ zCF4rSbVI_0May9F8uY+XvDi103(vVVD zXRxqWhpP>K!dhOJnMc4K+Bd?<1+Lqt^c7^T&as(IP10OcLXx2v~em%;xGw;?VJSHIy!55!3d4$6M9*u$;R zAj;UFQagbx;B<}Ga^im)L|IfcbQJJD|9|ginNj?$42rwQ!Se9FKFLLOS#{R8cvqjx z{6Cj6NMEFJM=)S@V6<4rD>09sJw@E#Ls*A9W6XL<;t>>sDYU0_06cUy>_(a2nz}%U!4Fx#$J`p@QRmB8f4w^eikQA+8wcT`n1z z8nx9T6uL%456W}d4W_NSX~P9Ein5%=SfV3#xq9;R&>`w2yl6$Dvke?iy@@cHXp_*F zCD5)q^Et{&uP&t8a1%Gs(0hek$jO&zJUEs%jOK9f)t!I9C$O5iaRF1%r>qW0@HOaz1P>l{h$bp`T?OJJEpi7JJsz6k zwajyrF=QlIA>xKPNq14^>SOnu7MEFF01f#Rtw_i4N_A&5WQEPgWCNNNsVLoOI-IXq z&(+5*$gTVjBLj?pjOZglQo0(tg+DA`-&v+O!m7mAZSrM+)Ps)i`K z^=irhYUzM2Zce52pHdz7m(O&6@p<`@u`OpVUmMKxWFGv&Tze`h#q-r2Y}jpXN2GI{ zq$VfBe03&_V(asvK648ngb=6O3?X}?%{l8K1j+hmnB8vmv)BS$%6!WmadMiz4Qyl4 zhdD_)3sQy-i1PuwXMPD{h%tP~{l?;yHSCP$dQIz1g^;6eG?lyE(*Sv}KNhb>L&o)t@32e=}c8Bj{nA8^>Hjr{+2GcCP_KRJ=a`H7B5e z-E8XUehqPagN))}v1q0)@X#~vkbBZOnO4oJ4f$s5A>MB=UH0QcH7A&Mg=79r&9v(i06)SSpvmHvhE4Op5rVvP`%hY4-taSeo6( zjab=VVAc?wIca)C6##U2=HMst$R&6Q;}V0Hx#7#WxenhQQi!q;+gO}6!9FtNF_4>)6}B92=jgiVtEN041x(K3!8azOL?VZhW)R-1z3<`ljjcsJFABqI3bBgVN?Q3jeUw)^4#&j(hyvPWw3~4`em=mWh0!SDs>kGH{apDu1*FM+Qy#kojxpJ@p2~z$=Xes-6V@~Iv4H_*1Mv{-u?3Yg-=o45Q;N2u>rMxEFic?1F47+F^!{Es*O`PLI2d1BCkl==cq$JW%xs9)}hJ+C9uEE@uBa~9Nm{|Jn z0cXHkT_Anju1d`qV~9aG2L%}>b))RCwENY)4Q)Yea%h^BAsl5*Lo!GSf&+tTBs#Tq znRu$awM^D{26GC{Qkw}KUoLy9TiD)U3u0Z@ z*(?{?SRK|b(yCI%bs=CZ7;&hYNu*v~`7>eG@Ooy(s0mhzl4VSUWDCHBnTl@39x@Yl zq=m`yifIFS;sx@H)To1Ksv#(a-D!~%8wc&Q5P_>S_OTjRt_C{7u7fjVBuw=y&}mevTaw zqA0C-*gmD`SXf@o1n6KZW~^ZZgD$u-K81tD2uYHwFEBJQC3r;JBQL176d0imq`o6$ z1(pffwEhsR0cBdY!R(Ufu3f$I7|@Kp#uPq|b%a5+ZX$-=X}Jm8zzsKWHFpf1l?Y)j z+sD(V$eORE)tPD=vANiat0xT7J&uP9+^Al1jGnwiQQZer{N>Ci{H4}Z5o%v}4+-V+ zb2s4*wuLQ3&2#Q}da}axZYakMH)xhVUj5k5-1ih!E!P(`?F(A`^}l)O11(>qi)b0_ z)dJM17VRoKR@XOvsbiURVOOf1YM+S1!(sK(u;$nzL@(^V%|6fW^EVodA^!T`#E|?M z8|E(&OwBm?84i??_84fV+F5tw?3e3q#6@(2_U#6}smt!J%l=YRHZG#93bPp1W0^!C zT+_(}8~Pa+<6RBdw8vrNtgvc3)m|IIs-7AGM@J1Y*LG@e-Kk~96}@qQUZf#{SK*vu z4OM5F1u&a9x{DUFLNjUFqgB}r=5;bw~4yy`jd}JwDN{*r<6)f37 zfp8aE(&ebfWQ=kBJy9?7-MHvnsQd{}L`vG$+u8l8JSF!_nd7KvELj=^2V+K6r-gg) zckhQvjHQLk?peity#CmxoN%nUNV4{|U%fx$%B*nk1_H1awTfU&*;)Pluip>t_Ij9# zQ2#mdSHV81{V?+Gr&@WNc#!nZ3d0luBXjp6<_72y-D51w0k`Hc2;&3}8g?%N*vNA7 zVy){*;ufkk6L(Ph(x9>;iW`X(;By4a>cy{xP{ybw@I1;#k1+hjqhj=ko%!*oHG0&p zAGJr1I`yN@=n+BQ@!<@QK#&+Z!L;oF1Ko$Oq76c~Q(YmDUN zjUY|%jmbA;1itau4V6y&w;zMEw%{;8rY)5f!kQuIau(GKIm)xyDgZX4X>JGt-BBilfi66FJDLCD%a5mrj!biHHY)ry&P041>M%-d}DEbnEc)fXia~zD$pe- zgWyliCyZPdrNIT;=!;2U-{oZyIM*{#9tnD0p1p~mk~Deg{pjwYUoL)s8-r)%g~maBZ>V(syaSt+#o` zlIPhR19}_3m<1d)S6|IoZm!w`#amcy{QN-gLr5pBrM-(}I&*o|y#cD`(QNl##mRjs z5h#81X%zBRnx3VL7ccTl8oBQ`Shu@-KeBWY8M)J=M|`x0N4pny)sOyfA6?9&U5gji zkKXm0Ji3TS7cO2I- zO8w}snnx?1KOd!@@R8N`Or8~wzR{H0=Fa_i^zKj8buBMqS%^n>`N&Qzxqm+%{j!f7 z5KrnypY_qJc+_1asxC_1_DLS?3o z;IV=`4P_St6J-|z3DHg%*d+XXq5#?60o-&pD+^ao{Ey% z9S}Of{bHD=M-+Aot@T%7I4-KwzwKh6uGN;iL|8g7kc9UoJ$ZXPSsZ%;`tvo-rb1_2 z9E^l#ECtz>3@11ZkLFw)Ao!-m0nNzeIxx-ZF4j?XRr^`mtgb@C2y3M4KK>DC&GUY%`rwa$TFNzOqa z4%sfr7S+?U#9{K6b`k5V2eRZ*&XppL8lqWcPUU3m4@7_!->UxAF%G=N6X=?4RX@v%*hDZ}?Q`$Mv1(C)WwyHI zSoK3}9HGb&9G9-E)o$JLL~0BZ;xEyk~pbyL3t zhyagWSU{pPS>W~Twp^b{(5D;2nLzTXKWu6a@ram{dc?1%UEHtrmynMT4D$Lrz++D zZN`-I9ca^L5%u$ul_C*hai-lA+_~P*&V^vzdl9RQPW9SZchy|t!Qdeu!R6Pq&%s*D zqW8bVBgNXF!7WS?JY|{k9%wa0iJ&+3fSp;OpJUq%7BXDxX;nLq_HI!W zn4{zTpjW{7Z8z@vj7~T(zLLH5MnT1{lD}@{Gj=;=1ur5_fzG7le?`Tl;0UbNd&Ul$ zaV$b7Xb=xGg^(VXZ63{%70R9LB-f$Tyk4qWAr+5(p(B5y%&>~#M^R$?aU8{H+*Vtq z7+j|&!4Q3J1ni*Dp>7$?+`wB^0ox@86iiU=e*w(=2bh(wNfD1%2waD-^LGNgRgMpt zB(pVqt>RhXI|E?Da)RG!48A;-ss|p*!EtN38!Z%;F_@U&I$*MqhFf&|Vvqw1>|;Am zLP%CsgOJd)KuD@AFkSK@;UqY1dr~}8zcZ4M%tN$PGQX`zk~ve}mR%Z}Qs!U{)6K$- zNEII`J0@J36RN}YR%qN3GmN%ik#gPp`5a1yq&eqg^C3vq`#GhQ@y`TBAXFUNQGZ5V zAgh$5U>!T`EKHaDgwo@oQ|5F7X#sGca1{w8$BOZ7yp^0}RT7SD3CLS9Ac$*b)Gi2= z?jMX4W|wQLBHMuhlGM<1><;YI@7(V=RvEpW(gf$iVkx!AZqH(Ezkx|>y-rSwFp|-C zB%o0B0waYUld+=q;7oZNVtm2h;0IJgE^wfYbGp9dhL zw6Wv?+w(ukv!INvP_?_)F>iDdmNlTOZ{U)C*>MD?te?@()G_-xXr&gTV?3~9S#&JA z*Zn>yS#+2INn*eC8vg-ZD2WAp~S?c_&@W> zQze|-#S+9~&8Da@`co%c9VfJUC+%uO6o*vu*$A!VGSs61YPhHT}rOlBRi+!ji1(Xc4IMl*JIwlSm2%N&;0P2%qVg*~ySLnM;z*nHk0!g-UCfc(9(J=*RYH&v1ON_VgrCO6r7{F#0%jLcWm>NAm9zeqF)3oIH5 zn*fhd+2I>#_fJF3Wy5z6e(h&sSMORB6`*(=0DESpn!q|)hFnW|^;kN+gp59rh_znu zwdc^(RkHAJt)(vgLCI`1;OJHLINgy3P2L@b9WWC?+qDN|)q{VU^nV8Skw2*~DN;{R zcW+vX14mK<-9$bi&MSG={fAaDJEZMk>4@W71ExpYQt}0B(LPW?@|e3L@%I-0@=zH* z3rbv*hIQmyKJwKsE$kT!lnyuw6o~=u2%6vOcgWDMnK#xedzsU|Owz5trrZy92_|s2aj4+$IG2 zaxNGY@i!rpCDz2>00Aumfdo~M;9e+bMFKpzXSR}Mti{-b*{<&Q&7;hwPoWYSNK%k{43rh66v^l}t2C@V-Lrd&`ID{K2(D`Y41D;# zpH6?ISa_fLJ_9-@6jcY(LOjXVp-e$k6|ZALPm( zKCl~2e_gwKC5_clOKDiX%ZV>YI0~adsw5{Vd|IxGN~62Z|wys zb)aK(=1uq`kd5$!BEVv0kRZ~2TH6Wp6piTTIOfeH1Q7EyX@(^-0K}Jx*UX?;mO){9 zD=je3gaz?aqSl_QP-TN-j$nKp_tJGjy=R#`mXVlTJqXlCQ|6=LW9Cc~ek)rj0*P}? zwals7=xp^9m@N)w>{~Xr@xAp8J&Eu}=s@j$QP(~e5s#CP8nf4Ha1jr%0UAymJ!%~( zai?g>0xW9m7_0Idt8L~_GM`mWczQ*&4!y1(s1fL7(o>OoJF=|G#MiChRIxhcrqw#; zL`X*w3%ITp#^U;&lPt1F!ByJseK;RLq{?}xp!!_a`zUzS)L1_+LujpjuYPGaq(D@b z)Se13)I#Sh!=@|Nt%EEMB`G|spi})bs6ZUgsX?&rZX#}amh2F{uoPd0CF@xWwVq2& zM6Bch!If_K13s-znk!5V$rY9{2c7Cu|M290B5Bdo684BFiE~!Lc%3S_c_%8<5b2=Q zGkN~cm_G`1x&wTZ*~Ro`7LZ_XjfrO?em%1T&PHOluRY~Uu4pwS-a{EQHv>IYx>W>%bEa-}Q>nm}|uO=Yok z$AjdtJp5V=Hgd{N5E)uH>fufBBW-mo=Jl2oKa2!HFb`CV*l>v8*9ImB3H}Myfl@$l z54L)%Pu==~PyOWo^|{af*2(WVzg{AWFSGh;q6!0D)lfW4h1rz9KS_Q|Y#-=MXJFUl1njap(nJ)?@brUOoGrl3!P z4;joNKM&p~XTe&kk)g)}h}l{61&s_<+3RH8VUM?b`viuP&EG!7v-s9QC;JQ|)*e+u zp|9c`Y-fx`A92Bjmx8Pwv(=L^5j@jYAbc$EnV`!sbVVw}0oTv0S36_NBXc}kordZU z!GC=M|C>ORyFPD6;ivkgWVbWbgN@HAM3Qypn1=^yKh$HgRMV5MWC3rwG9F!sc* zkyYi@cx4-&4_B7wA1GcbA|1*OGf22CVS33d192Bmn$Q68CFI{(WHaqWJW$Y^jg=7V z!Qlr8a2*+V1OLOS!sPru0G2Au##Kl$^o~88Zz*T7$_>Spm=*=iN1Vs|#st*+oz% z60%ZDv$(j9wi3uodc7h32US{)-hWY%S6hbVu!3sGBcj1+=194X|2v3mNZtB$p1U4b4Hw(D=H zsK)&Xs0kH6s0l~-K}~oiKk$aDDVNlQmvRLaKg>_9COolKO*mYy^w1LCL={2O(h7$L za%)uRP+9Rnolq058CMemG0sm-aH78l8{J5NKsFOlifDc8>@vKNgXd&2CD0U|(61F+ zNlo$vrw#lHIW`?Ny6M?~a%22F9Z))A{GL!6MgHzn@Bi69JN=iR_;i+v{M5VN^GAR5 z^4|7k;)Iyh%8eD6`VVbAAfz)4z+)gOFj-b|>A#L0(DgzK0fw!MN1FZ2VC&D_u z20uktU*))8l_bpDnbw3@ON3AiI#;z!lC0y~r%)eE&w%70kiw4A)JAzk9+YN_hNv5* zqsrag)1b)$=wxgi^pp;T!?N#Wk3_IElS3iRiIh~OR<%KYxX2ezidB#yU1(f|Cymg- zJ^}>xuM#gkVexaVBcOl}>TOOWQ4U|uVre<_1VYPs7p&S)Y#>J*XY8%Qa;PiJ`DDS8 zMsE+YE;c1D*UI@lNYS*+ekmYb9I-M8XvNl$5`!tt8UxDKgk`^PYJRpFzZbd_PGEM3 zgy^ZAqfRr#xn9tal8FHw-w7NmEOZyxUY*N z&-70m=q1Fz5u=FmFNb0&k#xY8wyU^a7f7(iT4n6H$F`(A3j!glDAf~wkyW>jJx8`x zXjSqId`S1I_OwTaYaqu3$s1Lg-D#=-sDiREhV)`PW?9A$$;48GJ|b_rZPX6r#h8F( zUJSvMsII2&@N~}gXRUj#BL4RR6H?7phu@02NmJoVAT8La#f~k)+}S`|XSO9wl&OgY z&MubT2XFzpC9#0o^B7uN545mIixUEd_ng4is;(ZrXSuXkhQs#~25M>U^%<6etW6nk^N9B53)6fp*G5B=i(0%pB=xF1O2644>&Rd-zQCc6Q!S&a`cA9U$do zn*sbiU(6B?GXyLO9N!u99~~|EDcN5|yavAJP_RlTNyBkh_~W8?GI#O=&yAxHqQ85Y zICG$@oMAd4@QSt0%4E2!CvRWXun-BdPTtlhe+f&n6T2sRZ=&Uye!=Q&qo@Jhf-fji zP}_hPWpC5xs$-fC$kJB!I1ZHk0Ixg-%`7`>TK8jzl9aZ$n%`XZY>gF*$xHPN9IDI*Qs8YBB5aRnH17KmL>V73 zckH)GnMEa_vxrFGq-$lywN&(enm^ypwZw_p?s8*p=3+uU^hU722f!t+2HVyMae=(6 zq%pmYMgaHQNkx`TiU*ul7l%G(RmA@!*2ko?#RmH!|3xTDY~k13%r*1#v-vQB2w;S2GXY|7|{Air!QmW^4Y=G)j*D)5?tV?2YqFWjpJj^N%OLL z^*c`u=58lOqxUZE=T8h~ZXe99Uhpnn1CtYjY_)vXiNWmcgLzAki3`;D;4;Drg=HhWc~46^K$g%u;KA9ZPW&%NA@f) z=T1<&)o@K4`D%HZ%iHhn{+fY+#gXQpN<|L@XH~Di{YGr=2=ad`{5zS*!ga^G#PfA5^b{H!i=YA)RM$gf-8Rzk*KZJ&sn#*o#HHE^NglCMxQCrY7#+w#0%M+Sr z>r98axm#S|b;u#kRaO&VBJ6GgMB&&>Bo-?896P`m#yh}(?Z74yYj?uklDeaODvj;_ zO-}4W$s98yk{X!^pTE>v5>0g)8k8xbZq5{FkfsIF@ZUJn@O2q+TwE9Pf}5$x$Y>) ztFJxP+#~2!Xa7sOgFF#!+->whzGEczie?L-_))$@crpz$n`EYIH9)JT@| zZL7;9g8a`VmgUPY&bxK|^1}YC`pEmjagV^kvVZ*j=_VA$C%9QX34gA>>ec%CDX5S( zM=X;(2u+?;0M(;z`b4coCa9j`MY5_q*j!=S^zv-I1>}RYH@vx*n@}jLkNMd1<*J7* z{n+116j=Lt^&oFlZ}0OrS_z!|c-$tdPqh(c>YIn_4;~nm2t^09sUEH0JrwWkGL_Ze zJreKYSq>3m{j3*QcDvO_>H1){`jCBUS0ANV^(37<5O$#IF7MAqe*>6N+<`pw^k=f_ zYOcn})QDg~vaq>$9C>vIHBcG2r5A}qmY-n9GI4c8V3Qi*_@Fo*j_39fzQM)EzLguQ z{6=^S?)IWefRI-OgsdphB^>EdHPn+Z_aF8MfJE(K7RVry-Pq`buE*D)GS%^ioea`= zI6?TDAY?vqPZ(#()jgdvdjZ7R4wVp6N=k2l@Y)T>evnR4RbKZ@?lJ5cG;MB>P4ukz zVOCY$H@HEvH83SKh#jErkHd4cN?X`{(u~O{rCrg%QW<^@As{u&hSIaCv&K*W0^|>1 zi6UVvL4pgmCWgks5iD5Q=FnEeMsmseBdY~ea4e;NqQf=0;n%fZ`GiUG33P2rv7!@Oj) zdsyjt$S)zl>_&2=H}DBEYyknTjI(x?(eSkwBU&)emu8~_%qZ6JJssgB$uqZkX+Vq!B6ShO3~QKbD}HPhf(+`j};)JT}jC>{_bS z3z$%{k}gtCbdJetG^@F!se z^S7w6aBQL(N-;S0iB*XQp*|Z&0I_5Fb zdT8!q!tF{?*Ypu905BZz;8m~zCGXTSX=+ndw5D~kO~4Sg3J|$wnIPlW5>KGZ)w8qC z#iJwEX`@pZC$en23&{iQ^auTboE$(c;xQ}FQDtPsk?Mietk{-S_tiI$EKf@)t^?ll zZ#)*Lo=<#P$)GVTo97clF*7qu-LmR_-qQ3Al;I?4nuIu(0P+R|c>MGBmo zARLjZ$_akOn$?V#nKuSRUV}oJXAtgi47(!JQF2ZjYq0vqwC+2jLC}ZM7|6v41$+ak zk(eyfVuE6}n3u$}Mg$F0zE%R4VNKK=eTv=(q8v5f0Atc%jfSN)*@Z$Zn;wOF&gd-X zTdmp0V$E>o)G@lB6S1wSjatbb6*xPiZ8SZn{LC7!E{-$`{iv2rkI)nFq{D~&#Ie`_ zi4fdw06qQyrm8LXarxC?fR<2~80`B`k`{wLmr2!h!Pa3=7m-RGLu&Z3U8!| zD08sA@+L65mP3XjA3E=7!YSg^!L6BVVz=TO?*}cnkxDw zn(-0g5HtgnJgU@}w%>`pyhz$SkX9Tq_BJ#Pt-qt&Y|rs=zP zH1;4~dQJcYt_SGDNGZFj9${p)S+NtHj+?o7&QddTMA9)fCNhD>^ilj`wTL-IseXEg>$vhaEU2| z6z%A}az+xg2Bs|?bV8O$t7*JBM=cB>MnEHgO=qb$tr5CVFW)#PsAk+P9wxkEMU#09 z(+kdu;z&aT%R*%&a7ZO2X>&>d^dt;%&Z&GOR;0YW7G~0i-Xug)02v8mSnwu3l;tG@ zdPp>qSmvIXG+MXg$NFG^+FHamk0o?B03g!THb`}DVYhv7I`u|;vM66zR1o1lOfotix%eaqrk^CbnLY`6A|U}?=16+Nh@&5o zDXO6fQVKa*s45Nb53N8XOT>bfrS*+|P^%gt_l?Whg<@xo?=XxBd}n>Y0mXOVV|8w; zi8+{0m_C^2snl)^*E`~RDjm3vzerF}H)?sabRh1Q0hv|pVu0eh>RY`|(rXuCIy*zh zDaZPZ6fMKygSZX}9}9zr47z4-hTA0Nh-cVk0wDEDK)z{K!e*9-9fy?=c$cg&Ovz@J z$8vLGV-O8RVqFCBkCh+=YF0u~y@0!Ou)J8*dvV-tVREs_k7mE-G*dSeZg1mB0FXVMz@|uZq zscfJezTSXKgK{u(l zX=;=^r`b=$%NwH~N}}qDBzY*xOql%`f@VMA$VkHM7XyMw1dn&UCnJN1?kEK{&VOXi za7cW95r?1vPb6O=FkCVr3#GFszrSOd`IN#h~34a(&X zGi0Gwq-|zQ!D$n)V_hI&h^a%#ns7XAKCzitd=kaY3!Ch%5#AZn8RHBx-P$Tgw1?6X zMt2Y$!Lybl(ief~!EI_eLfQ&PNQh3EF*!oR3nLsMdi}cO5N5C~jgLcXT#xBrn*Cbt zWcq^(B)%ecO$wpdnI?x1tVKz_d)?AZfGNbQco|e_RT9h^ zYkXeIuiCG)XSu<;928%~1GqtEQ)}R7q zEu4fOX;1?vI%?<_P_b{pRzkRet$d7C8Goob?4=cPYkOoa-5)62BMz;IgS8R9y*as^!LC_ftu^*l zz)4ncWf}OU=1Wqq0-Q2TNc?CMB4HL5EQl;V98$R^Ed&xAl6Wm?Vt9A9l68z}>U{)c zrL*eqbgVREbtBZ%2CyQ}wh>Pgwbl~6RYTAdxwSJG-nf<8H1ft6C5@^Z(wVAelt7^A zzSgi!4bg1f{nTSL9s6XXdv(Ow87aDkBsVE z*}?{UL7{xnVTyzRA(sXc@e_Myd0y+P!Q8n;fLI(CAdn3t=3y35TpAM~7KcnT=}$lk ztWR2@#C!p0#0=HGNGh82JcXn<^hJi4+Ez{g66k_rB%cI9rx%J0g#raIBM}!z{Kp44tg=Q73BO5=c$8I&Pz{`vTzLW7m2Fd%Y(l(| zb&ESxPetET}_BxF`Q2TMGM%xdGbFlx_H+br!-HGDb&hCtHj zOA=Y*g9bj`?&&r`xsU_`bE`NHLxEGCyEkNJ1k#v<$dg5<)xU8xAM9@ zGHh5K(v_$7q>4hez&N9X2Tj%EsD#JkTAZuJTZ>>)tzC&pL^Sls#>Zq}*65}}Jb}Y& zOH(|gM@h`b*G23?NKSg4!F~qQ$kH|zR&5FpnOsW`^ilRtM@Le1+qwbF+|e%Ug^8#R zgCx|)I14)0&c{CyX3G*~00Y zBo|Nn032ok@I?$AMi**c;qVz%S}zGW>`Ae*cmXol{fOORKDz%r3pMc71peJ5$RWPQ z9fLZ|!8P&IJANb^7>Cl#ka0)VN%0{*%DU|=&$|D8Y~U!R^9bx&K)H0=>L3|MY&ho= ze!vKplV`RdVzY?Z4W=!x)&0_Jeeekd;^ZQYf*41vm2aauhwJAowx~=CDCm|J0Ngb8 zcUZC>c9BYkw&`R~DOtQcljOt#MqgGhrQvBM0k*h87^AH-OP1s(?Jgo zeP!l*hE|}}FcKurh?j-q8-mtJD9N3c-;|1xAL3~~fYIEVELH&Fy4P^s#iY9Dy`Z*hdjEJ}0M2cU_)jN)i4Oy%>mOQVPQ4w0z^3el1G=n=`Rojm$xLZNz z=)sH%c$g~SoGI(40yX~?D_DvOmX03CtiZF|+PnZ^VO{kEnvfxO5m8G$D(lPgD^ouY zW}U0DXyb}(=@q0;jdI&wo^7M9P+rw9x-1JCKbE$eIM5ygm5YxLc{cT!aKzMO4x#FB z{1rV$@q^9LW6m_>a$DWwrRebvs<$d457Y;kh%$HROaN0Jq{1JFy#W{!S@=noT^aA0 zS_vdS@jhml2lWg~GUGiz@xG=&%_^0=d}w~OpySX=)^J3Bl(f$RlcyF+R1rAD%d~)G zmE1`8>m60x#gegyprw3(sSM-R*dqySD0NLAtlW*MQi`eRUO%@)}3kx@dA__x1HWdz%5wrrb8?dBP5D;BY1*jh`xO`(uhzS>tJGx zexuXMuaV$ru&F{W!;l-u1-@|~2Z~7f0BSv@A!iDZz^_VcE|dZIV!;de&cZY3w6>Ds zrUYBCs2*sIO^F2C0K<+Rb8;Is0nSm`xI*%WdiZ@LH;Ai6m7lok>DY>^ohc&#<#wx^ z#IwDIjpecqDP(XE_(@2S3DB&i416a2K;m;qTpic3hV=*lqnhEclMoW?aJ=J0vkW`l zlpU_l_&%0iT*bPID}=ci7AO0>9C91aW?r5tM$;lGy)em2B&eYq zeR)t!&o{bR;!mOvmvQ<4KS38z77#5&>M*Ou%P`2xomj~AYoeNl1!tYuC@8UR51-G+ zb7&wTrz|DVNO{8jb0k=pduy^qYb5V@IBJBX`gK@#@+!V&ai=woAK?b}xSqfs!UPlcWZlo@^$y??i63YL;6VbF59O&K;mr#%DMUg^ z_11?FxvdY=azzvE(zY8>_(AZ@Stld9TpOa#C2&XC5M*7#Aw_WqWs7x}GmqVWowsih zRaOa3d3xJz#t=1^?OUMe1&$guv|E^K-*K1Ua$?XTpE7&3BbN(G;>??I_LK#iw#xSP z=2k8#aQf7V69BUN=3LJZn6wdLqbfd>1&!dRuReCqJNdHZ#h`uqRG&9x`*h+Nl3<>n z?&qsnAN~hQrUnbEeJP3OBVa=J(Zw{7rIU|D; zC|kT@O3BNnmVEJ)k_V@jJT#@`3#XR6d`ijZPc3=bl#-WDE%~A;CHqrL9-LBg-_()^ zrj)#7YRMN)DY<8A$rns1dC}C8&!19q*VK~xr5|pIVaRCTG7Z%lgmvC-1+YoIRAa`h<9L@+ka@!xA_WBu19z7qy$F*AP20U%}I)SAijXur)T&2 zth5ZgA2G8zd?brT2(l)mfHZ(xNvD3wNFWKb()pF6io<94?W+ID_>>IXnASU}5FN1z z{t;FhMq^Jq#r;fi6bIflco!Q?87cT`ndfi+5n1#)Gi9lu!MWZQE|;j34ICa zHCTb2CM3Aw0~Hk0Dc91uTWJ~IEd8t*=4|AniiCnpsOd#`?;bH@GLY}mvDwqO76tO^ z^0g}VcbuaDmc?t>2uzm_W|t2JSG7*(FyO)XG&7h7oXJnV+o{{&(z$!c-&612STCuzgG%g5aZ+N&mh%Kz#Ju0togJ(q;1|0T0uv==U&v75lpDys3m54uK19;6Vy# zfTueBfPynoax|CoDO8wD(o(QLlAq#ue}xtn)vQ@EGz9Wz{Fk?ml%%pdO0)-hG(-j~ z-~m5Pf>;P=rH>w9!6+SehkJ3U2gN+vrKOO8Qn6G6q{X%WcFc7xj+mr%EgZ2jLG=|v ztRmM*D%_{fK_f*i5le~CEzxArB0Y-R$>|zMfti3x=IE?Ty9<(dKHC9Mp}&i0k40=- zXcY%Qg>qMPNzCo>8PDz@T%_Cu9-#`PRh$biNg3eEfZ8sHv?~ycH1TVLC2T=a*`QzD z>R_%}>Mz0wW5HGk>ybuoO3$Oy8Wt<}uWNHiqXV!*fw%5cg7}$ypkt&J6QVZHjVcIN zksgMV0ade+yP?^sMhE$tC7f0Q2yT+nl1^!e!-?x2r+$A&|DFR9Mv`xk8{$Vd*gcY9 zR6XL&S59xlakG)ZyM6&?Mcf*kgp7-FKk2f{g?m;Hzw=Z%f6r=< zpBJpoy|aA&sZ*=>{NtA&f8_T6`q4+TTUOuu@Q>d5TYvq3Jn$FU&4a<}q00|0iEL|4$w}|4$yD!s?47>0f#2-GfNO znD;M&&p%J5y&%)>@DqyYMKa#{`guwK!%uf5X0jdH5n-ykOh)?U?7F47AO+Ap>X{o+ z)=2Za9p(?gfnl*acl8Z7%gROKm8gYPpVno6u3*7RHVe!XHbX*ZyB2A&*2yU`5)XI= z!Jd(QVH9&OlIs~o-VNr;7y9|Phpk6S1TUzAH}t7QrcstYUqFAQ09{5nR(YM8n1^(M zCRb66g zJY9GG6lpR;Vk@(w{R=6#!c44q=xA|Me`Zu|>MGTTtmk1HgA430v6G0cOPqqfA_G{lQ9>U)i;Tktj`k4{%gAy(lls3$Qf$X4U);p3 z^)veAUVyM7<)z_FKt(B(DQl4lGFsKRzo}m^xlzAhWutx#-z*^<q?| z1L$fyRm6daIJBs{V9pljWIP9$qjE>$g{Rs_du4F6cAv9vca}Tsf5NYV8wS4vaSGp% z%$oGC<%KDwf<0KM?)b|6S?>@YqZOL{pf_-$c=NI5jm0Kzns1oAi|hJQuTHZd%N@%_ z(E@Gha$=N6tET3#n2UMW@-*VbHG7DL9!v(0v8>? z)jgi6I_15mAJEdkq2LVjfHye9O3>xJRZnfb*hGnD=5j7q$ngR6Z7*(|iXi2@!Q#qT zHd>&>tHT{oy*gIY%ZmG2{Q8G0V7Z^qH&%Bf5| z7(!}cKjwF){!RDPANTcdgZQK0a#6;Bw9S9P(iWpySlR+YHk|Cj1l**2IN^gGO5d;# zf`<4G-aM%NjI#l`X;>%ty92@{(aeAVw7p&G4+J(G8$ntB z#WxbxLCe!9S(7)Gi19Z~yn^MZ9aA^#2`fY}ujm-_ zU9%3ACh|qP-T?@`_}Zu!{~vj89uM{RJ&r%0vzf8Z3}au$L@0YgN|~rANvWiW!N^XQ zqEd)bgi5Kj(7w>BU3*DsUnD6liuP5d#P{4gGo@ay_xt^Me_y}vY-sp#V4(iId?A;5a6D0aFK<0_z{20_Fv;X_DQy6_L4P zju6BnKn2Z12Tm@72l&oWE*7}RlxfgK(WW^tawA0(PQ3yT*dGo7$f5-5D%w09O}I(7 zNe7`5x(!r^X^6k3mtg}6K$V(SP~BnL3U6x(^aGowgT4*Zps2n`1BV^Ru&D?L3C&Y? z!FMI-n;MiqG#xlpIDS1ET$YCp+K&;N)PlePW5fgrYR3X3;l^ksrYAt4R=e{RfnZT@ zL$wDM4w0!aAeqA6qwoTX04*cc(vgnKM_zfboj)QJ)QNsq)M0uTnM*KphjEl`&u5|~ z8Zr?DQ3fDKLT*T5g-<1rbgdN)C?rv3M(7v<8gO66;|}y?bp%6S$9*39I!JMt`Ga`| z4+aKMUL+U;lEK3hO&3A`hQdLcz(&Y%Lji;r?r5%)DcPkLz#Jq3HL0MI^ch$@rSm5_jdHc^M& zbuhj#Z&Cu5r^Yvs9zll_lqQR1OAx@kAZ>g?0Ol4*u$1*>wA;Kqypsr)Ki?pEpX93} zAru`3C@GVK_erKQVc`eFM3S7OW&~<W27zsIVr>LVvqw2pVFgZ41`rO2^sdnMmoUktxI!wi3iBII!2)#QR&dZ+CqP>; z&PZz|=_1nqGIF-k?huv|FKZeXaCzm;Y^8NO-aX0?PtkAQUY-a=_Pyu*3W?eAYnHD zsc9Q!1dGV)-%$ZZZcwmFl?gVC3ta;>g9~c3pXf%P*4GoIWI|#rrVx8TF zL{Ob!NJOc@b_F9JGiq@4j^0M!>6IfU0n{41mrcY?J|L5HFVk!YcG&0=nNN z=>x)xu%mm})xQn3Qb1Ua9#F(8=>y`g&|zU~X;4s6YyCDB>Y)KgkFSaKRXAdY26_ZO zS_hq;!*+yW0#MVRTuT$y-+g_5W)X+wq+}lOYt!eC@BzoaZ)Q_%QocyUnNzD zZ8ksC0(%Kg3rt`CeOg};(~%^_f~Wtnxuv_kMIIvR1M>J<2!q`jH?)bGGp>8DB2JmrCFg^a$-wyF;NfL-F z3Smzt$O@@xB#w|yI#3>hd4)try4K@|a*Hi(RL?k;a0P-k* zD84}TkQ?|*Qb+UXj#2>554J{~0-a__L1l6SeE$zL>lzZ(1}&CE&Pl)s=+@-l=+-sl zPv!a~9Ptb?r0^9~S$Z@X!V`rr7)WvdP2-jnB?w-DVlFca0#>LKezpz-k)C0Oj{Jo* zKz1BR@f5O<^eoX;;i%(>87Gm20PzY%VLbppKP`amq0&i+2R4OSkQTO6!LgZ8d&wbN zk|NYVke^M0YKio0dFcsqYca0crsib&n>bI z3Dny`0zr#uB%(EmHE3Ag0!v}!i~uu8v=-nY0IaScULe~5WQTbg_#_FqAQe#gT-bsX zrh#CjqQS?B=vX_j!BK@f;G8=T&~BkSgNOqAbHjglhqMJb^gSgMsN0ECczFn<1zH0> zaS9YnCeRW%is+x@#3=*;BZEN$jWeJEqV6CEYF2MIrkF zdSXXO$p*Ry)ClMfa0Bak(98s=hlp{bgb=B~*QI7?q8|v;jOvb1B>ak^bfdd>2(OUx zL+t*3781s};P=CjPNHx?Qmxqi{h+0KiQjK0EnNdY)CNF2EI20#eXZ?8f@;WFf*#odV|3(@G4i-Z3Sdh(WQv_7Fv8~~ z8D|SjP}Ahv$Oa-C;g>ib-&pn%Vj7{CMmQcH3M$}Bvz{gBnT_HJ&^bxy1u7Zj59cHy zuWWdRkOJs^@R+?tVs!}u8i7{ym#lt>#zq$)YxwMeW<*e2&<~K=44XTF#tHHxA_cUj z7ha&Rt-zBe(B#7z;4qql#Y(_JHY-h7dymdjBd75RB)x;I2?3A;L=UndGL7O*X0gQ$ zY;nli_J=JFpI%5nG|+LO#}X(j!jKJlviN%NVStbr2HTM!!w^gapb0H-rWz`PgDiRQ zv5yLLT7nM;^|AOYFf#($;Iu)xoWZah9-lWz&V`BouFfCdzr1Za(M4n-`ys>zW6ysR z0WHmf!l?*h6}t-R63W;TiZdo)LD&BcNw}1I8Ua zoIdh`MSE-z1P%EJJyg_Cu(=*u*v~~`S2|iHgPRWqZ0N#Zd}qPJ6hNW?pZ>?@v!YOJ zgeI6oMkgYfQs9tuzUEYcGE5361L~j%-Opzf7{Qs0VDUr;p0Xespequc7lFzG6^4d^ zxU9d41$cp4@QvV5M(Q+(gu)S#=1@v(6k`LHy61y~83HaLqbNQY=A#lJ1|l0UOW+oT z3ZkY4ex?Q6exNZ!9u*@Bg2D1V1OqCd%D9G*3UCATshA3i$KtEtPfAfJr~z`opOpCn z+aM+WB=V=o{PEW!I6O{=D)X0olZDcuqN!cLE?)r|Nl@t(gpJLoBr~5}%#cty(aLL7 zPN@gvZc1Q@2wEDkbf^&?1%HL)BN!={U~5IZ7Yoe_V5bBP4zx_PgE2O3g9ZtvVu1l_ zYdBH~5i)_g8Vh~M&Egv%+J-{}p%Rh~D79t(8Vq4Tg?MN~KpGkgVFyF7_=C-yK-O!? zRy>Fv(U7Si08NYnO#pjjgXvlYWFoMY8XyZ4EC9f^aY>Kmq8KWOL>SmZ04pkhX8Byy z=zpC5X#`Rzd|(5^GxT&IFhp;l0uk_t2_701oquP9+8WGdfDGKJyB#44B#Lp*mk}Ej z53nI0C2bir#iP=l-j(>ue@C+>Qc8!Mf2VR4sPeyvfipHjhl?7`g)*w}O@7il#OOxv zD*u|^RU*MS1{8qFl7Z?O1=&SP$rNA-O#`9GBicrkLjjV&Iz7%`7agWZ1KLnQF93oz zkxV2Qpos(t!SL~M@?79Iu$7TqB#?*1h>pG_H9|Ym;DbyFq>4fHe^D_E6>yj%G!X?I zm~N;KZX>wL;3DCemSBi2`k^oi;AH@+s4D94QICw}&NeiLrjVZYY z_^>xZazdt%g$zE5fJl7SDaZ=jz?c9QJppPr!CXic_$y!lnJGb+m33bza1eAHyjdm* zu`hLd;V;bspAs&Bcv`qkKoA20C52fM;R-FxWTGis!yXXC6rJr0hb;lOrh=FQO$Dfg zArfgK0vd#pcr{}$qey|mPp?8ma`{ z9R0upOXR4B0=uq@5OO5RZ)PsLhaMgBHQYBjlP=qM_wUIII{1{b&RuVgqLaTI&iHKgsu@ z=p!%)ha7bFBl_eGEbzeO`H&VA-vgbF{kJD968MNmNYEc?G&p;7+G7onq~sV|yp|hg z1A$(s&}tw^OHQ)QAt6m%!aPs_{Ly+^G#sGC0r1fv)^4z!#{HL>262Ml%`|9bm*fCt zq^HB_pc98auK(O87Br$R%E8~O`73%-~R^5FOI9 zCm`;J?Rola0VYI$Xg73pWcOYz>C3WS|EuQ#ek8^Q&|8tQ0U$d*tX#6ESOcVs?3%wi zCMh&R9>4k~GD3jIukI-!8GA|)6hyciT1p2gI{e$)S!$Xb1*U{NV=;#bRs}UillQVNF3Cqz;hNd_COt^i~zBbOOQG)*3S|l zZYHTAoGOn3^h#LNDuqm({D-P#KL3v1!f5>g%n-yvZBCb0-h$kgelodHB% zWKn?CEI_hIeknwCgFc2@8D2`3B)}ukxjh9{92>eR!mU8UWMu1&)Vi-e49R z+aADviP#z76}&uQPyokU!tEdcAu-fZTqHlh{a<y0z(L7XNc?Q zV#tI@Bp<34g(OaF_(BFQFjUw96N2vrVC;q^uCQnT8Dtz0Zh#SXKTa0-M3Mk)g#nYE zpl~VyZ-N_iOeL7L5*kcHk_KPG`+yv3pv6(@CxLBt<9cYaLmrIP-MF5+q!yl%M!?Ic zP@_Om|11=w2ot!d*pPu8>YCuXfr}1k7D&>B|L~m)I#@ssxEYudkkLd4XF)=FAQ)ee zwgvS$3*s#J;vFquI|d}vS7x7-nACr+F2UFq;uSYUH~S}WCFMxrb_fo_6WWgh$;ZNt z-Qm?g0m&CfFoKdmM(Sua6EuRvRtgaDt2^P(T6bJ$hV=O*_ zz9$|`&4L~dLK84>Y>@B%)c|6Mu=YZLCX5Y93H_q~@WBcw4sc{ilroK=l5`+N!Qk{x zDhPSi#AwN~NYa8}3VRGg)1tsvur!6|$fUD(&`Myy;vhi-Cg4z4lYaAndJ?2Z3Uz?~ z&}t1?Lg2}e?2Up)my^|}kcMt3K(qxbsz%^7T(GnlH5N>hF(5YDRY(9^Hx(~63W8_bddhB8S%LDo*(1YnOj~eX3|LK8g0!vyl_H+{1 zlQAF+D8yYT`5M`jQjhjT$6X9FWiybfpc+6ldC<^!9tOWed`N1NLvduCl7KQGx|h(r zu_S59x>-Dt^50@xHhF^CmMk0TpdnqX()!+^k_Hii&Lovx zK<&#Rg$?jRXacFh7$?k{4?q$8lcok;7c#6wL4XS(Ax{!yqcjL~Ul>9q3qSD}N(u^9 z3n}J^m7heLDn!I4i4$T)(R^`2LR^9qUyNXJC_gziK`aUh7X?R)`5|$kVyp1D7_n7q zMC_ChE6Wg3!jw3x1o4!J#H55&tHgv5tBBZ8af)R~f+$IxXc-Y_VIOAW5Nc~194xZw zYi(^4W|b&T2o(oLCdS2D*jQRy+Sv5P@xa?MA(0?V2{tjAAP5BpL2$v9!In&d=+g~0 z1lR>&e}TWhf3Sa;f2@Cke-a$m1zb#_U5) zR^%9B=g`+7IJloxbVP812%#h?RDzZ`tC+Y@TtgsSHsJwjZi6Zs{e!ec(a~`sIQ41b z5NYzWAZ<@ba~Yrqr5zDFO%xpw%7_2SVkf>iUz`#z4oQNBC5Uyc#RcJk@>iZp+zGg?-^oYY^qmWl23Acd|U$L z>;qn&5it=-KH{m#;@A-JNI(~K`1CNVDjGwlctGi6Ww-j%ktBJXCRfIFb|$J;T|fkPO<{lk&qe~9|tT2#)RS3o%U~}l3`Hi?;I4uDnjDOR@WMrjN~CA$1Iw}@iPJ9?h@!rL zz*BJ_=JkM31d;K!JbRKb#^MbPPLRz76seL1ktaf5JI1~)-qP@?eb962C`~xzEe~Gx zBnK|DWUXwX{&3`}ST!g0c-40GCbN-twqf_>Dy=zvHdA{VJof^5vKfWWkt1hWu9!6c z=JAAeckAa{@wnCA52E5@hhJ5xUv@io>fWBa!>TKc?0I?8gJXh zA55#peL3Hl-uOE9)0MZ4fnR*+Kilg!^n^tx*@gm>gPsH&U`nc0Xo6@8?xuf5b*N>G zC^m^dkZ(=wX0S2Eor3x!Jj&p2@Qm6`29wZesE5elsQ@GHDTAj0?Ar~V`$u>&z}?du z0WhKmbTtjpcBWp{es=2nrA`E2tX_3qqceU-OcDC(2h?S-hW|pMM3%0HKL9V4AW3$Wk6gZ~ZQ^$ST=tK#7+!wC_~?~&QA0X z9vD2|xHAjGKGvt~g1V|M4C>6o@VK5w7UyMqEuYv~gyCbe&Ek!ww>KqquEub|p{Iw| z9c$WL(76%AMQKSnR|B_w-PT!-;j1NE3zo*a9X{JxiQy=o%89S?s|pcOF7mSE4w~n*j%jjc!I{k>-)Mox(MOtrO+Yi zE~~F}K{wAT0w7O?z3EnKYZsDy&=p*M?qx~^74r|^udJf4Dg6dz=wl8_5c_y?7iL~5 zKIw?*pCID>U(CE>JG;P#KoSx`gr`5>vC;9y?HD{l!IDkEDvh2dqt@jR1Mqv3?`Ln> ztPgp;o^ZqP){2@uNAZK*$1pD=hgl^bH#4Vuk5w#Jh>XfPXbF<9$Po` z!6R}bhK=+3-LpGgvZ;eC$FTpp6&n&(Rex5eRATtb@#kZGPBb30p;Te`u~6_q<6}pT zC*>@LH>LRBEV;tJ6h^s(;U*u$nI9*rugIX>!myLIL2={$(#Iu~Mhq|C_r%}l`rWMu zD9soi;I?Js-B~5yuTfrMnC-T4i>K$bqc16+Fs!}W<-ptg9k~o@2ZqhFd^fY-8eHR3 zX~2U(2DVxJ{OLw^Nq<;+j^H(ltINKZuv^Aa)gS8$i>XsRkN4TZu zQjIWdalf4E(rkEQ6V)8U1zm2FtyWLTKS{O2u<%gc>NOgDZZ%K`V0fF(lgcIcwAQv! z-7tK2def2E6U$$!&^$5xw}anbxciiZh{R+pI}|Fiu`Ip~%1(z{2FpWgNG_1UE&wAA z|JUKbFgQ8PG9och921|EYHHRkP9wxYX`+J3!yO8DE0sVRHc>i{BwPlo5b4WT5X7T4 zI&pDTe_39AVEFBxcQn8#Z`u1N0Hcvw2EPS(csKZx6#lokkZKF7mDmItzY%qzvi`Mx zP`ydk0WjPJn^>C=vhc^G;X%W)={#E0kXmMB)pPvvhB|IreDwWCeA@#5%C$JD=e?HIU zCW=lJCnY3{?bwn$=KykgLY^`>2ViZ06GWhafTku&Kyovf1o*PXmAnayOW-F)M}&w` zdn0uvDV(1IX`*&q2(I8~8sgYcX$n=~kMJvdFNrTn9p{vYC<{{xM8W{k2yu|^_5oZ( z%jEy^MF?+ffI+zr<=1dq$cI#&;H5uKTUi}0u6C+U~Lsh~_t8wWa6Y+Mp_Wl$dzBpndU7ybE2 z?MZ|{i4mbHW_#fGE>Ea%AJJNa6F6S6`_9?EbJwxH54rOfdO=IzAbPaR!8V^@_}TA7%R zTT@!LVdIvq6-TPhG87bfdQO9ejNZKE(&bV)Ep37Ez(Fmq-gcd+ z&4{H>?@f23Dfdd>&M=}G(M*|k3SO=m4lH$fre^;k{iq>KmbE(Um7+r@T^wm6=vFj& zIhLFY--M>fvZp%HwdH7va^4;eeU$phSu*7_dX5>&JlatS|*OKc`)g4c&*ecB}2}@Q;KfBN~L@CRf ztzNJ_b6DBo%zkpcX#R|z@8hE#Cy7VU`pI!z5J?n&WoFg&Vy$n<=%YgFF_dY{ zjJyT3D7q4rCC6S8G>nxrApNs^A~T*hbcULunxZdDD}8>(FlzQ-6<(IN0fUi#yElEH zAsKH$)uvH0Tnso)bTZ>wugquZU(7sd@-)g^j+>`*`so1-k~WU6V@JtQHm8Luj+am0 z>8PhhbNXenwdv6U57XlyE%tIAR*HE5co7FAnLht#DQsPn14sTK;B zq&3ZkVoPqJY@=+a?O=YRe5e1QbW*!m6)CB?i`HBFPRPwG)V;5)GQ#tFho#jZ|A4^9 zS&Q-)FWI)|$kB7v7cM?%e%eLQFhM&w^>_9d9*~s}FDs86t-g5WYV%X#C%N}W^gcOM zoVB=Q&4nvhl{jWj{fBt?PV%1|7%E=0cpF4HccH2I={qIP5RXuCde+{m<0o$4dG~&9 zcJ9W_$4{I)f3@MB`-)?itFK)3@EYaoH#u-Ye!-pt2Tz=;K7WU!t})5~^Ow%9^q8p+ znv@M<<8<``(`N77HRouRx`w{N&|zMq5dF`dv;W+!+l}wuw8z)Dl>B_UHOsX7g zF-@f^(&$tL2C2-Wd&%j@`N~lk8j9XDSE>c@Ck{hJ(TS#K9LSHMMH;8q&@*>awHcW| zsD5(lEKL@oxJZUPL!03z*PA|6-kb)+LA6mZr)e`3sOjbK%F1RWHGLy<09A!LK+cib zo1WRl(PUb2EU1PmhAQcKw9H~H1zt`O-HP5HNKKQKeq4~Gm|mxy!KSA-DSTKAdbXSB(Y{9X)ryNH&4oT@`rQTmrGT*}G=Lq$m<#KoI5*)SbI5Z-VjR|fmTGa5hs zk`OYi1xHMgNJ%|GpTp%GIek`nm?_;!CI zM#q6*nt%*WqNF6GW+2;OXv8!$WCo}5XNVKxi0zO!vQH9Jf|yI=6N~9VM1X23!PVd! zDDs00-kO*8?q$t4kK6pvoU%2@LjQY^CDF;ZU)vRA|ATyFPs$rQ7%4q+*r6=yXQf%z z&staH{!zco)5T8Y{VuX>^r$$)F>9*I#t>IUKH}?TK174zSmNQMaXTN2d|x#i`dw=+ z^Wzh5{N9msCKB<49AN>15CwjuyMi^3O^SicC=`;`gVfiVq~OG2k(xA;1w$geH+2BB zmnO+~fCw}ukdvG|MUQks(P>NwB2UpKDU^ONcG6(m9xAC%fxUYWOosrZ8buvORtODg zGf6qBJVhVA{32m71dB9<FqJUFHMM4wC2-c|ggcEXr5ORz<6a`Y5!GH_^=Rj>JE0TcLwWHI~6Uk5^fYT75hDc=(r$)4{J<4b2*Q>S)>nZRu~Hdvz#;!{a-IinSR$BWnfClxP($ zN(>JMwjFB$^T~FWk_ly)ACf5zgr)%-&;E{wxM<{XhX{PE9q|7$=`Oz=*+#-LJ zst<0IU!_Wce1ae!S(*NEelZcT5iPZw45Rs4{p3z-Haxxd5ZKmc7sUBYYOX zsQ1g>Be;8dc>p_fd%yUP@QeNi_Usn^47}%cgWmv*qz_qspwQ!4YZ*)jxO@0d5Wai; zpZ<}a4unT^DvJ**t*{g%gH-_Tj^8?IdTb7t04;z48ZPXHr$kW(wknHL1aUMW&A+xW z(y2lE87Z6)=ss@f&_Ex*k%LEh`UKkkjJy1g{MG<$(5-we0C#VnRltDg-QFV@Y4)8L%u7RHd{1oA*B>gGFID~#G(jS|Y{5a(AKQ8(EPnG=r zr$+w%6}0n&iL#X_5czr%nE|pAPxYe!Ap8`RS1|dSHOrN{0}3 zT!azwDF?(8eEn&v`^_}>HvqQQ{eyq>|K00n?~n&3{V?6o?puj4%Aj0{&Ci|EJv> z(cS;%g=qf&&zFoJ=>k7OOHyTU4?KGOf_SD5~WMwVCKnlVxXIc>)1dakBLLNrBg3WM^=`X0HgV* z?l3Bw4CaHh)?GqY{sSHkFdC2kT9@d!xF}J$SQHwFSLZ=}05^fK@-haN68=m-AL4hH zC=HpCRa&zAjR5X0p(41uMsN}0yLT(^Re-x|5NQw|jZd=lW&@0D3o`fvz$pIzV@*%8 zJQj4lTNZ309P&eR;f8R_U^L&3ByDt&gz#-6NITbi@mH<Olp3;l~&S9JE%5NMPbz!dFp0#(1LW=cA2lN=cM6qnO#;Fmh_cNPjH>8a!QI@ZK z!VNE<6nuR}Xs_i@Cu^|z{q%j_Ji&t(VFdR0e#oUv)(_ zU7mBM!Y6f)P%Fs$_~+dQQ&Ff>+g+)5=DTOH?Ut>#dwkZ~d&DnbPv7Ov<&zAKOn-h>%{2H? zn#trH#p69Zzb~1;hOM+F{6hVO9UtB)&-i+9(=zRW8b_RFlvPcdm|$tKG|P=wJLJ1% zjrPS|JM(iF4qij$3}G$Vv-wNo-r>zD+t++|)S;CLPrY1{))qV0V%#39^E_(hlqn06 zL(fiF?K|D1XEf)=t4;Oya$;gWRU3a$aDJ$BugA-w>T<^yo^jMS-SERx-Q&Qx8=UCQ z*wsNJd`8*a;V3VBdtj7m@t&Y5L$Wn9b9FbBo%oudQf{JsIMShltW($bS{ZaYVf^7o zLF3GguDEfRsh_?~)%R`dd*!HV%o8WQ9Xm(`s%Ckw-t|_~7LE$m7{9O)(fCb$ z)YWEYG*0Kt^d0c{PRouLr#b9iSA+aACx586@42~rJMDnh(M{>Y!Zq8X&W(OKe%zYH zYRj_-#_QBxJ+5yNG~G{2Vvm=z+wiF1{`CyS>o&_fe6)u|e{R)IY~Et$yycVI4;9|p z@n3x&o9g)l6psmrEWXPQ_FZ(CdQbGy@`Lv>_K7y_)*&0uUv>AYnlP#1{$}xF9zSYs z^xd0NLnLrzuK@y=7}<2u&`xSpX)Hl?bzP37jBYHWHnPIyJ#ZjPw2 z@RV}>JZ|i;@qN>b_+rZWmhk4hje5rYQodO0rN1Df5A*fz9MH)6X5RjB_sXMoJwzil z%D-~^eHmuzy==-S>e<9s{5MXRvq6os^8I5^YrWd8O6=2 zpTAhMv`$Z6eaHIUw>qO+244dw%Bh7v^+;XR)2eCiG_9a#kLYav=-Bh}V@^E1I$2FS zJL9@ydc%8qobt-7yCc|bx56{kh_j>WnxA|sZR2Y9p8lHLzB1x2=b-MFeP8aWRh?T% z=BT}?NLsS{oPYZ+wmIuT_~zHMJjT9Ak1I0Wr)_kr+2@e&MJM*SRVSwL{dsez>A41_ z_76_nzO!p`x;kg=v6|~)ZN1kVV(+o7?c=L6Cp1Pl=4@43kkY68_5Mw~O9vKCxfk){ z;xp$n^8b<%GynM6zckW-!)IZ*F}SUt>bu*WsiwwvRf z$E_IN^@6jZlsb5%Fz2xEvZEi_wmWp6Kcj@-x!g~bI=$nx%cQsJW5-bVsZ&KQ7VQ_p zFZ2Ak^Ssxr%-Y{Z!YbgEQ9CJ0# z7sGb!swN(>9q8BdR`y^IyFX}%=f){lT8wU24lB5!#n^LcP2KY`^#%v;pIMi%Rn5^v ze#DSB_foaYXGdtAp2zm9-^N-0B+#s2sI^Lg{Z#E4szni%55G7nuW42G+_kD)RVTHVpY~FV`rSSWb;gg!Z{t~x8|JQZ zJatjysRsu$hK%LRI;zkxdwBt`XmxSkRpt?$Vyil}F3#rZ({?x2CB#^&r{+Cy?b?Sqd1@iXqKLDL`Zb|-WKeF8K z+5N(^y}28x{Q@;;pRbgk-q4|ai>o(xwS{MS#N>oyCAt^gT?cEdezLG`!C1|rlLy*e(?*hj`VHUJpbuf+UWarb7p)j8#J)|o2tSa zs|&4`$GgrPT5bCy^@~BB(Z^E$37atcZ|bLioL{1qqUW!zeaYm|VHZq8Wh=LrTysHG@1!z!BhWU_ib|wKB~36?7b%Y zO6kGfZ+4rO&Nifuco0_Vila=eP7)&9?8DGN;9Pcl5mTaRiIqvxHxU^C+8x$`?S*Z5pey?wBK7T)e-W1Wy+#~kJ{gS9V|MbrZRw;KCr}D z+;V=xw-W7@>}GQ%BJ^ub>?Xzh!uG;^?H4^Zu6*8pGxy+zsV<_1fxM%--j9wHZ5z;@ z`Dvv#uZ!JVw6Emsk>vCF*ZF4xj@sxf&^A+#Tz2+`f^tjvp5RlwvYAIXIn%G*iyLS# z-z0Mir({WhuFHvZ^JgI|1`fJbuj7&2?9t~^pJSsB3>9}N4pYAs;2m2~S8#F5Wcuh! z@i#a=7xq0F;j86#_eZQ(Max{>ZBCyvS*gRQkBW^qEqrxV{mA#T;kSaKucoJFy@-7` zh})}OPIg4omumx)=dZDU+xgp7n{yj7#$A1Mlf9ce zU*)dAa%xlI>TypgJ{iySq7~IfhMg(1_iW-n7N~Bc zZ(AQ3cq7fEc>nrU_G|UWUgu}#%(+)=Q2xb>w`8{F^eyRE7-6$gJ$qEw-%nbh`d)!- zmXEh6sEFENvV81BgNb#7_j*4g#p7@H8>Fy#TD&2v?q;az#dm)AvbX&%TWxyeOtlhM zjgy^|s!c6tDzQJfE_r>H{^8O^b9v>o-n+C;uU0;O&0&&!+_AE$7X^80SDp;4O>B2( z<`q|(+}4R_?_L+0@@kTDL%H!-#c}PP+9&6D2ff=haOSat+v?aNd0w9|m-LS(YL^w8 zzj=_TeVaWd{&Luuu=2MPhV2eLT=+)&p5Mkp%Go`74vYSH;M?Y0p2yAuOY5&?>PPo5 z3tW~xfRns?p}{BZwLi)ow|2f^7U_KPyS>+J*Ph~IOXsaHmSd}%&z#bVJm-mJ74CoDP-T}XA-ofcW)^|thf=#p=3P+3v2`nfm96#TS1TUe^BmXY_l zT&F!9LnZ};Y;Md_uHW_jg>KCv>!@ABb+dP08>sp*SfFw1PS2_ju}`k`Y^Y5>-I~Fj zm$$L{g~qzdJ7z-{tl6?!FDl-f{mP)L#$?*7LU;n*g84$gh1hDb>xW)^SFaGn-7l&zoaujWnE5}(+%Zt)flr> zml@_knkyPsES}q}XkcVm)Td_rzM-m*Rh8}a?r0tu>1tM*QsCHF*|>4>@FvxTyP~#+ znar6OWGE`j++J(2{>0nEFSBiVBP&N%mK=@K@>-cET5vsoyK~{j&yR!s)D$1pX_sxO ze{=Eff!?Dshp~s07)>roFqzHyys&8xRZ*Mfp{ME5mfflJwZGP+h&r`vrNdQk1+1;z z*lwjZYyJ-QNRl(g=R!!6lmD~B?F!}EQ>Qc}sg&tzS?$*U{w;1i@6;IgUU$=XR~Vlh zVP*A^!?7E!v_0Rhg~z(g-8=i`D4nxPQ{M~>FxU|zuVQMu;4W{?#znbe4Xr9)rXt_; z)(lRO(=6_Q51-8alpbEXd-#iv>$2tdIzF9w_t1G?{G;=M>QoL_y*R*rqIPXt&y0T0 zIJ8#XcSarK3kx3z6#5oy(7pERNuHi{)7{M@GS>$TdamBkW3KZazd4UyuD{~7$962& zzn`Pk{?oPLsn^q*y|R4to>&B(2a?_ut5x|G6yEFH1#``1$lo&E*H=qQADhsj$CW*|xavPSx?giVr^y zn(blKb_Dj?N;4QzUg!C_EA-a$!ey=Dt=3xC7o<3U?V9WQCeiJ+58p&BscdO+^`+HU z-xpBdtBf>YkD(kk-Z)G&M|U5+bh|Idyj`J}J&!Ty9t+n!d3%4$ zD9#?I$1`f=nOj}E9G_Wj-mCM;vh{nv_5FMzx|ZsH%raFE`0&c=d*%7$`CTe|+o(r4 zmM?}qTWok&FsN(J0l`gw-4A0K*}{w&`^8;di(9wuR&R54gw;;LXAj!;xi{~1Pwty_ zAK#PV_D+6TFBfm*Dd^>DeKsjMJo)>>h^i!S&H#|GzSvb#TD^pb<(A?z} z89s7x!*=IUE=l^Wm8l;F=J;%E*qIYLhzQV}A0RUAif(+qb3%;wrIl%_8<&MEiFXEV zYhL88J;UG1V6NM1=Yz-h9hp0|bf;+d2Tl7m3vJA8KU>`Co~ECBZK^%|!DP~R^3%R&H|kBH<-S(Cy+@NQpMCIbgx>5i z*OLsWinJNtRqjh`oQj@7AJ&{)e``{QBE84f-3;(K05*^@YqX6HVm2%$qs|9sB% zk{fgO`-RMtOYUoY?wq==@)PAgi`AZAzft(b+hJ{rx`OVYf`(~Fl9H9GUCcGVaZ;*n zo)#K?d41GE_c0Ks=MA5 zqCWpzg`(?+VMZFDNlcRrRB zw`s=oobiwKL%o+h8)y?6kazI=IRv(c?u)e2iSPrucNX>;Rz-28^aYEi6L`qk6SN*5{LJaJ=Z z5xePLdFTe;!Gwp#p)S4I^R=~}^mwtqiM(9O4O+R!rXO$Te%+kKo>$^WCO@k%$^XWF zJg#W-{)#8syN0vEqZ>;`dh0&7Z8FWj#khMxT{+ETYT^@q!Crp-G`d=U?kr1{%xrqe z>=&2)-uL(Et#|SGCUULAuG%+uhJCVX9H&vXBFk<>`vlgB^5`>p>N~iE@ap_igaXMX zgiZKC4Q#FWK?_c#LJt%d145XegPw9!3E^7&6bT<;SD+^<`kGsK3_o7u!u!kkG3$;7 zA-sPh8_61b(%W_$y6(jPjcp5YL}1`L(fXZzYgz!w;*@tP8I3TDW^VaR!LQ1b)`xnLp0 zlXJ=2pQ5*mM+jG9(EIJ)!Lc(tdserQu62j4|5H%z3-WKoXsHe4lzOegFPLsuDQ6B53u6p@Juy z(P|iiaCugrLY-CrqU0Kyp8$(~!TjEtBhM#@21~*}PK-G4n4fyJor}Y#H(aBJ>O}au z9@4$@OKZ}{p@^E2^$wy5p!juRXG&%8GaL90UP3SYBtnA!3DL8TARrnIR8e57>cK8q(QHJYF6_QDDvmn51?VaK8oX;=NK z-@=L^@5OZQ%9&Hw`n$e67zN=swlPA>8kDO0jD594Y%e|mf zcz>$=$shNp-4OBE_q6vhmyA_bvQAs3HtdM@i<)CslD#kI(zl<&J(X(I%fc`%!Z+p31Q43T5MUP!QQ+jL)>3EmAboq zJl)*c8_1-T%zA*LG&O%Yx z{D=hAKX*WQd(8Cwh1RsuoP@6hPtFGG@1K^cJA2QY8^ecv@27n92RHau@rL^W+vn?P z&8WFq6Ew@2JV;h7UPCb$j5fx_JZ6 z->*Ajsd2i_R(aWUoxH6I{g&lLeADe$-DG;K*J59fEw1;!Wax5h4r(MuCns0vEH1j0 zynciFkxlmt&fmDPB6$B{57k^x-JbiaH#Cn}DX2Hmw71c3qD^6rTJ~)Jve(}S@xCxFIZ0A^++%qob z@zbpC=ElyO*){q5yU}kuwC#2to1#Its|eItW51O5fPG4{bs(rWqxZW7@>bEOCe$jZvzr|cobMZLe%a~#d&}ebI%<07H}rFz zKRun*Z|9Mi1c6rbAw{NI#IlDY&ik3MRH6*>Kgwm7ivpqxbn6d`pRZR{kvzLrv}^HRB2X7^L?#hp?cLWRP*SM^Q!l~ z{3}`mzG_ehr#&$Had<*e@V@%s50P6^M$-V3QYetTKN;UTE zw?8*rWBQn(Pn^m&J#e#JH-G)k!FmfnDqK06WuowM)|n#X3J-3~yL|Gq`2A&L|4;3) znvLo?b2|}>v}Rdncw;-JP6-*=iZN^>S5Nyc|F>R zeBPO*m&Z6e9rCMMt$m>6WXfy5StotMj8qS`m9aORepeSBU$VP(&TEaHi5Jy8Kb9-b z^*r79xT$r`Ta_PLDw?lauE(`sc%8fFbeq{gwyJ)MsJeFgfah-l`XpA4R9o>h=-a{L z%W^kF_9Y4pMOvNu?O(-~Gh;o^4JJNC95x8tS8}wgL-E|Gqh{%jk8i3vlxA)pG{E~n zk6w1A)eSY8ZtgvnjT+Isq1;aBp2GRA-&@K47-v`PvzI4LEndFxS4}RQYhE`|{$bDe z312rE+i5OM+ITScz2b`aih^k>Rzm&8n(8rM)R}={m$bG=9BbvO&P*}cuY2+HoI#~( zXJ4(?xU+Kn)W;iUeF_{^qt&)0SFb0(ZeGl@%U?@u^sL+6r*o~h=C(3}$S;)HMzb7r zcp3wi>=Y;*I#oG$jm0E-aD%Q`r=pFa+U}i@*)hR2jmaGpthuko*Ws!1hW=JXs~4(Y zo$;XQh(-3=8@clR54&3Eo>F{STQop*LrC$nvp&ODbKYNl+Id(3%+k*M1Fim4^*65T zW^Yx#vNP9n>l1-pmQM1*9;3`IxQx7G<#Aa^J;zK_d@>VnfHW8-RJPe=WNOK zcG@3)a^e{73+*Ot`~B{|32sw`hf)$p+=#Db&wZ@@I)B;a(a*+vKVXdPR5L0OZT;}g z<=MLE=4I{=owdIZ&fD&+ZhiaY%+3Id@-ViYFmI{S*~c#%leG6c`lhR8jE?)tPQKSu zjVpT0h$+>2zhp>BFEfu7o;zlZG;*V{E54`?PEMS3+@S7Wki|H<+KTuHzvuI1jAeHW?vvMe^luYYjj(Zs6e z)XaFziVne*3Og{N8kQLQ* zU!x_}dd~162ba8ib+C4b?`=J6&)EfDxqBz6e;Ttccl;*qfTn;x0?TzDboQ9cw=DP4 zQ1I=0a(nyGx4~=Pn>zUl^rnnAJS6X*cRPSm?3XpIFPAJg3sXvbJG||5N1XAk)9@u+ z<%w&fcC6X^@q4Z1f<)b*{A<%6b8^>@N=X}ZtZxmcea~6mq&kb-kUIYYb5|$z=@0te zdq1K*rbEGQ-LDI1v<BfcSS8NU~_FR$65Q%*_vfDrDSNzKDFZ6Cv!W@*WGyf z!)5S<@ej51M@_h+H*3R&U2DV5zVtO`KMS@TX*?+}Z&LN?k4df$YU{qImcE&~EAGSe zeb!^thG@;7vBEvlt3=`LxQA6?e#;GtJOg7W9O0DxeHHB=emSOk#NZ&)bsXoEz_q}& zKDk13YfJORrQ63%eW{u_*6U`y{ss2c$*ULM?wR=N{3mwSFxBt3GN-w|Uihw{rQL=n z*3xuZ*kf$b){0k&qLkSaj~nQR+Apv4U+^lib$~*_*SeeBA7_p}+P9;nP@s5a?}1A> z8b2y~HIyzl?AyB_Be1zEQcr)+#2lkeJ91|G?xSy-g1O>=soN`@Gf&-hSQGi8_`CXv z%KuZ_m4G)XxgS}nif#fCZ&NiDM{M02~C@Hv1wAWP|6M!L|H{t zHrWKhS`ZXu6G0FyiUQ&l#0#>cqEeKL8_WNGGnphUc>m|Q_y7AmZ!>4+Y;(5nJKvo1 zo{kaGpIu5mF|9oOdg&+q+uVpq zX|?P?QR1n=2^}-;rF~-Vav|VA&8mBKIRkSP_lsA@g`D|t>q`OmHjElJ>L2_6nq3yr zX+q3o#XwE+`E5hDOe*^z=IC=x$Lg=1IRE@sO+&+l-N6GI`p<}noBeXasN&P7qppV! zJ)L&pY-yjnyPGmT>-eo8X5qCS$8wluuR6X`tSz5s2o|sH_h1xr|C!(p1&x_oE5e_c zd*}V}GX}jLzNm82spw~-HGOx87G5}Z{A+uwkvW2^an&o5&W-4Bp?hQN4QUCIn_(S4 zm=bR5JfiR3whLy<{BA_$-CFte(6FC(2-Qum&-tKt+!qu48ao{eAHO`YKzMsuP1u|- z{U(Jz82Q=seU;+m>G@HcRkNZ_Puq5ITz#wQ_s&1vt*v#qEn90nGr6zHHuh8huz5#& zw~xB~+~+Z=gW5}{9e(WR0XI9IAKibJaaMZas^T;0`4gOy8*qpU1zkYXkT)IAO_wS2)`gb)b{BlBHd?#RO568-kg6^Re zFZrGQs%C9q`VDR7(Kp|SJpAX(#m8Q1RJOi$dC9&=L+svx!_TgH>eYE4+CCqUx^PFx zck`~EXub29tvlNvdj8P%?va;&?si0Vsdvn43+)Lh-RisK=BISAZYgZ)y}a!1x4NMr z>-sY5k1ngS*rw0?>y_)PB2=j}+TFcBcgV1Bjekz<9@s_Gees;9*6i%CsNdB0eqMSh zcwM~YJ4x%y$L$kVRrby3646c~@8N&+o4U?R>ssw>QyeqrV0r$!rz+llZeVDy@ye;e zo0<;Qx7`^K*YIg%)n_lS3P0K4h)}Qpu+J;g#x%}&?{IX0x_p7{YR1HF$J3{t$d3sQ z79oSOCQcS|KJZH~C3 zC%QI-UOl|s5OR+Bp!epmyNce0h36&b-^dS_Cnqk68u0SZ{eOx$lpkI>@`ZbQ-|cp_ zYsA1ExyyI{cl%2Ri;*0nwmdjF%sx>2uf`E};p+e@E2YJF_~?u1^+9hO(GuTTE-#^j-0 zC%m}o-PN1M&Dq#)+!V(GO{igamTArlzg^dSHevLMq)U+%uN&@Ncl~SK`WXg$;Qke{ ze%~4HZ>vb(Xg=ZmxW~H}Li~IDFsSQ?&vu#~sH=VR@`%V;^`-^AcE*3PdiLvuFUQ{O z^1=^e&qzZ?Wz~f>-Z?kk6(Zd@Vc|0`C54-!COPvz|01GJ6aMzqy#u4Cu3mQXhnYjV zgl<fC$1jrVYM!Xb`)=M7Ye#g`hig(k>=UuD&U8tnY5e(WS#*!V zuId%zH{4sX*Es3vZ$F5O$WhH-&8j7PI1;O$R3mBae<;l{jz0@u+n( zzBt+R^4p^aRM-8LUvLCp%P|A1Ll7t=*ic?il+1{-EGvPdr&LU{K?d0c#dViV|9f zPw#ZR-}CB8HSIo@#15MBjHPphar9TVhUZJ~t_@MI+&DykIQ^GT=M^T*7|=E{&VNy~ zP}2DG`_?V8qUYyz(HsBhyj(CSKh13EIYpWlvU$&M`UBCtb_>f&2#(w@ zX42v4tGRccjmpm46fLd3QQJQz>8++e496>&G zRpGvnxgXtj>@MgS-S>3d`EI|)NA~^w*5cBJytql%P7VDv?QCw$fLCG;RxSyPzA(3I z(~-Q37upW$zO7YFR9w`JUY6fBM2}R@j~~;xv|HHC&{tlIX}xXNTfZ$&zZV;JrvCI4 z^~E-;Hm-Cra|d2|Y~f2CSFMR|AF#;(MW%k(xvsTS=Y9~-bj`T@;^@Icwe_K=SN*U( zu%T%0gRku;VlNvW>!>iF4?6w(=IM(U1zzdWx9i-Y59&+itURjS`}HjA@Ql#DT@So; zx6J>&%fiJyKe^f%>AWJ^_m^M5o3Sa*hJkUDV~=X?83tXv_`|I=jW=fhdMsq}`hoTH zN3Jmc{PEt>vZTS0F`s_=<;|PSgYRoA2G1OLqD#}*2Uq^}Qqhy*ALWfFdKZT@NS_?* z*e5&wM49eS#anwL3ImU)=KavBu9M$&S>)*WF7Fnv>u%Rw33;Pv@#UY+{1I&0*S5Y> z*;56lCOXH&Yh@9G`p!3eaqxD(Hm|(!&acIDVoFz>cGmqqv5WbBM&6lwF9tI+JL)z_ z3ybgXSD7^Tc83qXxVSVo@TWhgCXBhaZs7Ik#`!Tt+j@NYo^{p8C$7#;3jQ;(`@}K* z+FG6uXj(9K)4=fGC&#~>GNFCzsz6J!^yIMUJ7?dSd{F;+&fK7n_tza+9e1`oFL%W4 z840eERR#6|hr@#FHWXQtFHY1=$@)>#ZbuZjc#@OENhwK^q|_v7Qd*J>bg>}KOcp1n zBukQ0lcmXN$+F~hagsP$EEcDTCE`@ERGcQ3iPKY(Qj$}|DJdzEl++YyN?M8xJhMrX zWQkakB9TZ^B~nS6L?%g31ubo=I5j0zlA4+-O-)OcrKU@hq{&jTG({?rrb?yKG^tFQ zo|crBoF)c$EwR)})6&vpY3Z^gS+YzlOOZ)rsWPc7O(v72rz4B$NIo4=rz2Q8-4fAK zR__MeY+MfkXhADWBxAJOjkU#~i8ULof})~MAeo`Xwmhi|@e7w>mwXZK30pj4i7#pS zGs_t&C{o}#8S?nB#7ScWX)T2|;Te(6@XxT?l|nKza3AY&8IT4zIU*{KtBSj}nroc3 z)hu2y_t_t@<~QV7huL`f_aC~aBn0AP0; zOX^ERU&8<+zhhWXQH40FK#FQ)8A0u4r^{|77A*I^VNfjdq`a0|Y|q^Rb7@^G+Tupr z3I9{#VJ}fCS3o3{Y4-q2P$r8XO!LEx?xU^%*jiqR;rU_tmJ?pp;Mo9!bW z5g#wYKI#cekITQDpeUord)*(@VW`ii9=DB{VDv3DCa$-7Xa z#h|ig|MR>Q!Obhd(phs&uEYk)TAPI>KjIpiaEyysv=ZniIXhlZN|a;|6&CmA zmF8NGB(~U$0_5)TX}NiqJ?}t%!4ARtbY~GXPUBrR@G7z0K;Ro&DRu>bqe)l>Y9^4l zQHk|SOaPZ#yaj}7SmbUbie`bsg-z3CM2(EN%E7h86$rReD=hRJTh*~+z~kb3lf&Ea zSQaUeI(Y+Q^ScXe0-M_{#MKPm=n`z%Hk;Tj*~0#~Yi0SQOKcWbl{G`qG=?L6x`SV= zb7$`37tPE=iJO!xPLZTaL7r_aF2SZ`m;0~SX?2y_d6)N>mgdWs?n7Vj8^g$VzlRH# z-AvTNMD9xE!Z5P29KqFp+DAMTvn6JWCD8^dYHr(l9^%bK-iZP+6N&Ff;#+8cE!)fK z3Coa8JJnIY6{c~nYHT(zlbbeW5E-5xEa~v40r=5Di|agKIH2>nugA3k$Om`|6P`BQ zSq`delY1Y#n`Ro#r;Oc4kNQSa28T9?@yMG%rN|J_W^B+Xn^9rqMlctfP3hx;*^SJx z_@&Pa@8d;iV=(PGVRZR=!?;X&)-Z!ko~7hz{&Z%C%i?TF^M_#_wf_*!AL@iin~6S* z?IqP_dsPL;i-7K1VK$+6SwVzNZOM*`ceJZ4S6Mn6?Fb)H!jV$Y z=R)u#FcP@$B5wMe$sZ$NMEA2}oOjZYPoq7C$^T9DU>O66Ruj_AY`4&{4kW-puaA~( zqNWTwOQc046?}RUs9E&cw$x>@lo+klE8N0jf*EbySi;hvKq5kNA46CH{I+~=bcRGn ztre#hkgr6mZ5%g*IWPdZjduQ!xAPTk%U8RLl3`C{9s|c#llm#DS(-s{qFzJTcH^rABLd!aY(y{x~|O zmkl+e9Yp0l#7UAf$jrqZhF_}d2>g;Z$i4R8zFcwMfFa9lrNHhh2+2=?;e5ck-#W`G<0 z*GS4aZ8&S^ecw{j$g{8mWkyF~B*v?&go~Yc) ztKC*YL_BD_>=Ti1EPNaTOM|&X8#qx}0@;W@ZN(`h;IgFw_mQC)W(LC1CO?hMnN2!i zo=3VVSmBoC5pFt8w)x(GXv=|U%lDq4qTwW)&mFISIVabUlKY<7B!^g2S@Rucy1)ZD zFU;KWC?Lx+Sy`3`Q6}ePn35G?g(SE#X^f?C3SLgB9NufsP54j}J+cm?vK9g>+as^M zS850yJ;@w*()1*U6*wwxXjYNDabGJSWDF!^v@T~M$(T_PF?vq!Wuq4&LA_h7cvBco z0!8W8GwHU$W>b?ifjYuU6MT58ReFR8`Jg)rwN{kJ(NH(Q|MaM@s+17m>kz!YE!um-3H=zZuts4S^$2vmmT=F5Yg(>qbQ(EIVf^qj8)Di3;pzPu@3 zDoYAS;LDeuQ~dOt!jPNNr1IkZ@?}UHw&{8|#>Ioc3E(2o1Uvvj%Fw0(5s(H90d&9^ zpaO6JGl2QPtH4{pe&7`FE${>I3-A}v3jHGl5CRflFrWiQ0p)-br~_UG)&UK`9^gaZ zDA2>NP1{}_g+gJh5bSgT-Gt%74#F;hxY09QDC`_61Z7osaJ&kIut^DpeT2fOUadO? z3x&OeaQn5#uP`K<5k|(w`Tf_MwyA@HmrKUeld(F2TYqvGNl_E9vjzcct6+9pIIU|A z%9`rz|0Ml!G~-1kq#_AR4#rMvxH+$^Dwv^h>i|G~k`72RT1fYGmRE5HBRQZHIZAPy z7OVaZ>bW}B`-3|!k#x&lY12<{Ayjuk~<&}32g`4V%cQ1r{u#bBO+!`PE zA-D_RewdVkh}pqJf$0l4@cZ;(O>9{81095qJ0a-r$T@7KrO+J(-9ZOJLETO+v^Q^5 zkBEZ(qvNaSZ^U@4H*UHF-TRIB|9Z@TUTmHSD|v!gBo;|IR!L(uTfWsuhqQj2F3h%- z|EIJ(yoLWndgG8@0zhK{@76=XALQevx+B>|gmt9sRbgfTV~ajND@&=@i!)V2Aut}4GIKT4^t@h21-V!9InvjDu(B44ayNJy&+qk zuQp^OM>$%(As>O|h*P1#uRhnVQ80JN1Lzfef1>nw5F2jvl8KCJ`e) zQ}f&{HC9!?K0*&Y_9ziQwSfV~6QV!a0KJwh9}A?&J6ZG?EMf7ihV`HFAM- zyIL*lEG=7zKL^FRo`1~ z({A%y7{t7<$lLq`+#9h~f-E^OiQvpFyxG>xq-zr88Zpy?NvooSOgwIbpId%lwgUq; z9VJl;I~`e+-rHOocrX-*+kj*^v1w?&qZ~T{n=X({LI*6 zM-{mz`79GMYz)4&>NdX0}^Wuz~0`6;tr}5Jh zbjETtVX_H(oNTj3!}ZwNMP5q)*XB9vr@LGc3(ZtflAeADn*(I9%~e*;rAbk{I|h|0 zSwT^Tu#=bs^sQKgvqGIVeBNSvtZy{#EVCyH!9woNXtui*w&>snMCtrx+UwKj}MW@V#B7w&KE>5%D{$CF~bZ?M`yC9o^Wwrv7c?c)x(-hyv}Ct%{Lt|Bs+C(@yztPS5$D`IU^ zzMnbJdYxvRs)NI{1X(!ul$}#mpr7EV9T@#_gb#)x5xy~4Z5+)l)J#^_qq!hIpC{c9 z#(381+RFV)b9lPX&)dmAJtKDjK+kwkoOGX$n`{XDHQALY9Qoz%(KV$*ih9}hjFxX9)}aq)gBJ-SbMr)xfa@<;b5Jb#b>CI1wj+?38_doVK#m;=lM zmI7;mH-T-yE?^(<0dNHP4EO>#2Yd(I0)7Mh9kAU1?SRfeI1mN&1Re+a15zLZcoL8U zLjX092jl~z03%QVIDyGP9k2jc0xSbw1=a(bfi1v0z;0ka@G)=8q&I8wg+rVFd zpA)tcpdAnjbOGXko`4XL0#5=8AP2|=h64q_Sil6715%V5*@VazL^?Ftj%aR_2+&43 zvb9m$ivnoQ<_oep(HxQY&$m@56FRBU<}{@mH@8-dw`DsO5-d1 z-T?jtI!q>8v7S@6nMbNtjDl0`*nypK579KNEc9$Z144 zh`?g;`l54Wuu8yAGX|fj0RnQL!ATM^4@nh-e}wpA9-%cbKFU0msgu+>EQYZA6rD$! zHTTS};9wr1J2TCvLyV8KW;`0Nr=TCw8CpDnvzd0pG*^q6<`R}6ZP5JzoKpckMa54X z7&$+KXtrd4(Kl1LXl(^s`D8rc6ay#@j7OVg^;l{?>{=79?kMMMi}LE=K8h#vh*=qh zXg0lKJ-MeTT-)<#=iangZJ&a+P;I@<=225D=8*~_lV^WO@A9d8ejdm)Q)@lz3Ct$S zhL85_t2r|d&!HIl4u|zA4rqax2RJa3fg*@JxK(Vft5ZR_<3#jS`<$A_DE`ePk$Gr3 z*AaEqbBog7jnOmr!O>5qs6p};oUwHr{_lq>H|?`X-dg7TTt`ZNmPyX>c6uGMqPju`gyt!F(D zlMBVVq8tmF+=pVJy8l+uW#sGUA7a`s!%<8c<}1HTfqQTq-NK-s{38Q@VO|NqflEPe z`OWgv1|$Y{W4;Vp&x8ha3pnI=KA;WL5XAVI{BQZ0gCzd*aU5epfI9GU&>H{W17m~Q z1a0>p78Kz>kxx<)7_$Cy0^1w*VRJ0AnN5W(K}=;g6~JYYS;%a+Tmis(%{` zPCRvCTJ0C8!=fOjgIkc8O$o)d{y}!!E9sgnl3^XSK#29)60C$wO%q_*wo%rKOC>3|PBO``GQ2p|C=sVglZ@u1l6WS)Gwjg#Iy|n_Y{UX&vm>BiXDq%UNt#e* cu@&QB*M9AbU6#rOPGI$G Date: Thu, 16 Jan 2025 21:06:08 +0000 Subject: [PATCH 32/49] Compiled satisfiability/walk_sat --- tig-algorithms/src/satisfiability/mod.rs | 3 +- tig-algorithms/src/satisfiability/template.rs | 26 +----- .../walk_sat/benchmarker_outbound.rs | 76 ++++++++++++++++++ .../src/satisfiability/walk_sat/commercial.rs | 76 ++++++++++++++++++ .../src/satisfiability/walk_sat/inbound.rs | 76 ++++++++++++++++++ .../walk_sat/innovator_outbound.rs | 76 ++++++++++++++++++ .../src/satisfiability/walk_sat/mod.rs | 4 + .../src/satisfiability/walk_sat/open_data.rs | 76 ++++++++++++++++++ .../wasm/satisfiability/walk_sat.wasm | Bin 0 -> 152453 bytes 9 files changed, 388 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/walk_sat/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/walk_sat/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/walk_sat/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/walk_sat/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/walk_sat/mod.rs create mode 100644 tig-algorithms/src/satisfiability/walk_sat/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/walk_sat.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..4e5d854 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -6,7 +6,8 @@ // c001_a004 -// c001_a005 +pub mod walk_sat; +pub use walk_sat as c001_a005; // c001_a006 diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/src/satisfiability/walk_sat/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/walk_sat/benchmarker_outbound.rs new file mode 100644 index 0000000..68650eb --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat/benchmarker_outbound.rs @@ -0,0 +1,76 @@ +/*! +Copyright 2024 Chad Blanchard + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use rand::prelude::*; +use rand::rngs::StdRng; +use rand::SeedableRng; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + for _ in 0..max_flips { + let mut unsatisfied_clauses: Vec<&Vec> = challenge + .clauses + .iter() + .filter(|clause| !clause_satisfied(clause, &variables)) + .collect(); + + if unsatisfied_clauses.is_empty() { + return Ok(Some(Solution { variables })); + } + + let clause = unsatisfied_clauses.choose(&mut rng).unwrap(); + let literal = clause.choose(&mut rng).unwrap(); + let var_idx = literal.abs() as usize - 1; + variables[var_idx] = !variables[var_idx]; + } + + Ok(None) +} + +fn clause_satisfied(clause: &Vec, variables: &[bool]) -> bool { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + (literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx]) + }) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/walk_sat/commercial.rs b/tig-algorithms/src/satisfiability/walk_sat/commercial.rs new file mode 100644 index 0000000..9b49cbe --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat/commercial.rs @@ -0,0 +1,76 @@ +/*! +Copyright 2024 Chad Blanchard + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use rand::prelude::*; +use rand::rngs::StdRng; +use rand::SeedableRng; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + for _ in 0..max_flips { + let mut unsatisfied_clauses: Vec<&Vec> = challenge + .clauses + .iter() + .filter(|clause| !clause_satisfied(clause, &variables)) + .collect(); + + if unsatisfied_clauses.is_empty() { + return Ok(Some(Solution { variables })); + } + + let clause = unsatisfied_clauses.choose(&mut rng).unwrap(); + let literal = clause.choose(&mut rng).unwrap(); + let var_idx = literal.abs() as usize - 1; + variables[var_idx] = !variables[var_idx]; + } + + Ok(None) +} + +fn clause_satisfied(clause: &Vec, variables: &[bool]) -> bool { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + (literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx]) + }) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/walk_sat/inbound.rs b/tig-algorithms/src/satisfiability/walk_sat/inbound.rs new file mode 100644 index 0000000..9f6ebfe --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat/inbound.rs @@ -0,0 +1,76 @@ +/*! +Copyright 2024 Chad Blanchard + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use rand::prelude::*; +use rand::rngs::StdRng; +use rand::SeedableRng; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + for _ in 0..max_flips { + let mut unsatisfied_clauses: Vec<&Vec> = challenge + .clauses + .iter() + .filter(|clause| !clause_satisfied(clause, &variables)) + .collect(); + + if unsatisfied_clauses.is_empty() { + return Ok(Some(Solution { variables })); + } + + let clause = unsatisfied_clauses.choose(&mut rng).unwrap(); + let literal = clause.choose(&mut rng).unwrap(); + let var_idx = literal.abs() as usize - 1; + variables[var_idx] = !variables[var_idx]; + } + + Ok(None) +} + +fn clause_satisfied(clause: &Vec, variables: &[bool]) -> bool { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + (literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx]) + }) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/walk_sat/innovator_outbound.rs b/tig-algorithms/src/satisfiability/walk_sat/innovator_outbound.rs new file mode 100644 index 0000000..2eba628 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat/innovator_outbound.rs @@ -0,0 +1,76 @@ +/*! +Copyright 2024 Chad Blanchard + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use rand::prelude::*; +use rand::rngs::StdRng; +use rand::SeedableRng; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + for _ in 0..max_flips { + let mut unsatisfied_clauses: Vec<&Vec> = challenge + .clauses + .iter() + .filter(|clause| !clause_satisfied(clause, &variables)) + .collect(); + + if unsatisfied_clauses.is_empty() { + return Ok(Some(Solution { variables })); + } + + let clause = unsatisfied_clauses.choose(&mut rng).unwrap(); + let literal = clause.choose(&mut rng).unwrap(); + let var_idx = literal.abs() as usize - 1; + variables[var_idx] = !variables[var_idx]; + } + + Ok(None) +} + +fn clause_satisfied(clause: &Vec, variables: &[bool]) -> bool { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + (literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx]) + }) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/walk_sat/mod.rs b/tig-algorithms/src/satisfiability/walk_sat/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/walk_sat/open_data.rs b/tig-algorithms/src/satisfiability/walk_sat/open_data.rs new file mode 100644 index 0000000..4bdcf81 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat/open_data.rs @@ -0,0 +1,76 @@ +/*! +Copyright 2024 Chad Blanchard + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use rand::prelude::*; +use rand::rngs::StdRng; +use rand::SeedableRng; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + + let mut variables: Vec = (0..num_variables).map(|_| rng.gen::()).collect(); + + for _ in 0..max_flips { + let mut unsatisfied_clauses: Vec<&Vec> = challenge + .clauses + .iter() + .filter(|clause| !clause_satisfied(clause, &variables)) + .collect(); + + if unsatisfied_clauses.is_empty() { + return Ok(Some(Solution { variables })); + } + + let clause = unsatisfied_clauses.choose(&mut rng).unwrap(); + let literal = clause.choose(&mut rng).unwrap(); + let var_idx = literal.abs() as usize - 1; + variables[var_idx] = !variables[var_idx]; + } + + Ok(None) +} + +fn clause_satisfied(clause: &Vec, variables: &[bool]) -> bool { + clause.iter().any(|&literal| { + let var_idx = literal.abs() as usize - 1; + (literal > 0 && variables[var_idx]) || (literal < 0 && !variables[var_idx]) + }) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/wasm/satisfiability/walk_sat.wasm b/tig-algorithms/wasm/satisfiability/walk_sat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e0a7d8d83a05eaf0beda08e01d92a2ad1571a6ed GIT binary patch literal 152453 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

& zB#DcCL&M&}gX&tSwcCh)r!uLedTBxR9PU!Q%5qYev6l-yM*FC(IY#V}E2Xw+`-9D` zhPxatEPZJv-vAPG+gnPQ@Qg|eO~5PS=D;D=tMi#WuqXslO`#=I1(~ElcgbMbO|?`= z*5w@)s%*MUDe$7W*J6UD)5;ejRHWr;(*T<)rwDTbzbpopRbGX8FRpCw(Rm?Gy^r-? z!i&96c0_8O_l!1iW955ZfC9ZwUBE7DvvpYW`b*{19|^Ehi(q9@46y21Lp%b6i<62M zG`1nqx8qppnGAHM>du!qEBP&RV>1K;X@(G)NskqgmS3i^#+o8RMud|bh)E@Nmx;Eq z=>u$-fd7wV@!Jr-juli==wD?IyhboZ zlrhGe%b3A-kJ7gu529Nb)95*y%wjog?y19BVES96%&H#k(*Gii1+EsN66 zpmM%Uu~G>elwt({#-`gG78K~6HiqcExiY)GVP=!s za?`s4dP{m&>UW5WZGEtF+h!Q22?2^zqg+diWf<4D#DuDw$Wq2}#5j}CR(-vzvIGY# z8iRGYIxC=%K=K zE3k#C(TZ8_g9TDHHwmP@AG5Q&v`{=^1Ct3GD?aYSNFo{rU$Qz zi3Txj$ffJ6b1-1}Jt`uRNd_R^3;UhalZb~)FSnybE4xSC(-r(6f)F_^% ziJa6C*c{;cWTE7IDj7b+P_SMX501}M=&5k{utnT*8iI7dPLNw8EVc=2SB@INrz=^E z{IkD{9F3mkBIy?F(dp>ULTN>{l~6FYO$;DHo5Aylj9DTfN`H>Wiq3hFjLoVUXA%6( zzLS%HAX2RaZUMW`Y+;WDX2->YFx%Ka^n0h^B9fdbg953a=*kE`!C5F4j2&_$KrnPXkv!jo$Y5U$;55s_X;@x7qtV-6v6BM|dbd2dULVFFV1A z8;3*KxWI=iaF#kYJTpZh%%_@d6=s{Ay^58M0lW*rM_My0UWLeeJ=kGxgNXYS$w}v| zaaEu`%v-k#^jCoXzI~uc=dPJm1OueJGU<%0q%voSp#{;0Ek+oe0Jc)jd3umq`&`sA z7oAJjKj5uWG|aVZnAoRbbn1iVbM}m$qMrm(pR9117TbQL7s8SCJM`m9$svq>iUPDX)(8 z+w$&br(*;d04o&nDLVfWauAAYGaODNWwa`5SN#CuPwl`yj~LQ`ur%RUNMPsJ@+B~? zEt62O-CY=3v`!-}1kyt8QQ~@#kj(k$ z*PN(m;(g(*OnqQa#mMW-@Gye93&WgT0le(s)N+31vs{OlK|E^vZ zWZ@c=Y4p>Z{kvBGu5EXGP1aSUTQHI63bcZ)wNIe3yF#Ewr?@{suKK$CG^1$^&N`@K zxv>#t6U8byl6{>FswSIwq?q89kwd61xIOAFY38V7)7}AE=VC{JYXek(2N3LJb1n9` ztYuEA0z0Yf?NWDS_GM&yoFOQh&{5^f!_C5iuuS)j5_BLqF$Ae}qBPN09;Nug{@62K zG`*x|YZF7n?sj{vElUQD9h1^O6ZAia&tF|V}V2Xu9)v`wIe_x4Phmj;W{wYP0NIiO+3GueGNILZfac&8X#4{*x2EvjU zW0y|R2S5fHdCPzoqYHN4a)Ph!&nREC&%#|c z&o1OMp@_ry7Emyy2T;J5MCT}PF&q{k4N26zPC~jNj)%1&WdpYz(|j0^ur?M`RZvqn z%+>^0D_{s;aNeO~SU5=pYl zk)No|D5YptkXYRR@X!zaCT4%wMqkiqPqAeSCSv2y52wZTzr~jHcIZ6^Y#9@x9S8y9 z58q-#j6u<9(9l=YxF&+e797qIm(dq-G&aE{7>RgfU!-IhZHd%`;}*J@i}^Ojh&^=< z@L^`2kau^Ow!UjLF+>np`7z>;YF#R>q_#BOLB z3IX!7f_PgKy#;PK-r&lE03~*L~7;R0wSJYxV0~ry<+s80toYRcI zv@%tZGXA}@F|?YPh1AGMm4R>QfbG*fO5idzM8mDK0c8@@`aOh`Zi$nVp}UDF0A4`H zY#2GvMV1Rq^SB(@aA=KHDOHGsFu3TYYEMFetUdIESsnRub9npOh`As`phR)xPER{( zJ}g}rHP;<`_NUfa}ioG<`=dI8lOYS0aN~KsA}9{Q?$FpAO9FT7vCCj zoNM{*3hf5xd?Qa|<5Bakn(DV!%c^!Z@`h~R!R3jcI!U?r z5c0(y*YDz@_@n0&Mq7t1Xz%6CRHi3I+;4+x>Kmzx_U3N4SKC3=T_ov?*j4i(^2HGe z+vu^uNKy-WFnVn9jE1Z`M=6L!(7e!x2B+k*&=C7O1dU*;K_3eS_MA5SzIFgh)z1i{ zg6gLiriH*5*dKa*`_mwsutequK~UjX_{==)pH&s6UY+Fs2 z4Wz$o8?WOVNdKai@MXb6iFluExh#a-GyRUs7r9YPuQtWy;)3<=dDib^YdVSP^6k?ZP*9Ko24w{VM$kH@rbL(mKi&ilcdkAcyD;g^`8=JKxWWWe!&l+E_r49@yc4J?)EtHn*| z(HEHbUKw5Api`nW^-+K{hjpvdM^d;b=weOVP&S62l(H=9d@d;A?@TPS{@bE)c8pHR zLv#jK%eVc$*!Lh0IpSICOORWp7=DYyK7N-=?4$V1#JbSKa{qd;T-GuK86#BGG(fbg zgJcP2hGka!rC7ElUi|YovJsM&eTK<@1?HiE-*}r8 zCa5J-)FT3B*TrBD>am!U7Gjd_D-2^DLjob3q@!mByAk>qmI(>PFg`S3y2z9n$&led z6yX~SSr0-W7>P7}Yy3vBrj>1CKTzw z6HyqNIih@5B~71}&pGlK$pD8NBclkGRxvUxY((md_$`qow7q*Aks1KalnoSN-YI0j za3RyV;UWpRkZ`X4j|g($=KJsVEW=QfKB2F|n@40OFkrRN0W63TK2Sr2k19}eRyA!j z2KZp9<#jAWAa!BhU5t;qX8#KKC=5q3FM)ZYmoR0ic6m?@9~JNc?=SG7fXBcOSS3q* zD6x+Z05p66#Q+}>mK4b6(OA)e$76dkt1&1UVppJiP-exHKT0_$NEFCm&b7pb(QXbD zAt`|%rA5&*d6D@@9a{+qXVocXSSICJES|AgunGh_sdRma7aN&S;8*4a%+Sz2ePQO!!sVTxPd!^a}6mdh2YmNK+jA0Q1{ zMB;!vmW@1!}BH$?oc9VM4yAu^16gk z>yh-7fF*@xLQ*`=2ru9WLIF9I{igU}Vu}y!6P9>yK6|o?WdvV(LGcQyplcLT&Paia zU`9TJ!$W{<4M$ZV{l_pIL{*9EOxK|DJ^jm)4N=*`nM6__5sL^9`wz*`$?irKknx@L zgv&wKcn`;ZOAKQ&9lS%og!1s3-}jo7goXM}oF^6)2GlxzPq`?YtN5L80Hz}eM4dzW z?EBaWKAP+VVQu83FuAu;@O0)HDC}GrMgCeWXM{C6dx)pytzs_S3h#jYr~MY!5`i#$ z9FTOinejPv(O*qI(miDuMKqmgx@*SgfT`aH41AQQYn1Lne1Lyu6@9X$P|+7)m{*E|fH|{= zOBZ5RbZKZufUyt86z&CHG_7<9D5PxTOe8E^o+lJmbNe7aAa|R+Uil$8nZSJOCZ;-? z0CO>qv`+9qSvEZ|Nn|(C17EBW>fxqh`jP+WLytWh-D~cU8Hygh7uUo}DfruPn|x?5 zlSoIq!4R;3NTH*F!tlrWT+BNbafOgZ*%I&&NQc2wHoyYPvXDJ#e$eyk3`A~b_Q-f2 z)3fX*E}&)Vfo!pwiHgxOO}I z1*o76ZY43d{1ZQYl8K@)a2YUo7p(FQtjZgpiPMnl4f&*iR8tL6GZ^!9KrQlK15pMQ zt*nOTYWqyEirTl~!44#160S`6)FxY8Q~;5dK(Mea1|nwsjTO@%iE38&1jugo&+nFOs`q4lI) zipU#SWMp`i=By7IXu0vfMa`aN!`dw|95ZmK|D)f*ELtR~Jn=0uHy75LMAjTNP(I6r zj`c=ndbO~D7FeHyJx+5xk8L=EEqXMKEW~r_l^^`XXMgPLzxA8HdYGe&El6$Z|M<0q z|M~Zie(A5j8tK(yIN%4u7K138pFu6Nu4pS^Hx@!mr41DsiO`AI|G{zkSM6|i21BhMEtk4_u(ZN3EJqb%_mVTD<#*udu$oGdF0W}0UZ3C(=p-k;Yr-(g@ZW^CJ% zeHiWwM z<LFVJ>vi-tAND}Bw7&^ zvmmxx@y|X}MP9O|gfH`<@d<($TuMv8PHVBTsRSjsDmbhhz`W;Q{MGQa z&K*(L-r`(c^cGe_UQGRq-pW_Pgj@0!0hqUAOWq>+vSIdne1ggXB(-UA{l772fN#eC zbqLx;g(%Y@Qe%;xBTl3}Eozz>MEF`JodW_>&lcrSBDyPAB(>g?GpPiQn$b z_-B7@vxL{!|K2Bgs)qZYdbvI0|GXU@{^U6xes)cG__HtbuqxdD>`!t(67CP}^*{9z zf`V$gYp;L)_lx^q`+94}Gja9)r6b#C{DZHmTCI+;f8moSwb$gS6I`AKuwUT%`Ly?s z3RV~U&p*za4gN3xT4*#?kd%D(lbdo?Grho5N z_s^f{MHCfP)ga z=}d=c08x^8Te+H>mFpZjD6$MmrAt&kQe?0pH!Ek_F5_l}2eO5;5NGrF)UF zT2#G!vg|KAP`F#m>}RxIzUcZ&ySH7nXvCa#knlGXVIc@fZXhLYKUImF#Xwajqhu$4 zQa3f?U_*n7T#wE#p%NidDl_*)bei6*`$~?o%TlL%W{<>Q#3m<880|?h(}zDA@?SL} zXYG|x3NXnX7Wdv8W`e!a)Y9u9h*JW=aYy9*77AjRu(M6|$t~o2J|3E3SdwVLXkl1p z{bluSGpY5NJ$#&ZD|BfP(7}qsj2uZ4gm{sZ*$YHkhU%;0gdm6)>#P&L;^~Zk@Qcyz zaZMR&$CpObl}C=|{srF4`zs331V@KLa-czs6jFH%fs$PS!@x`ptr&^Lg>ohrpZ<>G zNO3S3^oGV}BuiM6*4z-*Bo#_5gHZ<`(t|)*U^bMOJy9(qeuOcVvLk+CSX2porSG-T zmlV=_uiV8(C5)X9K0RLQwve3Ro4?~d5Wz>_jk5uAD zK|OdD)k-V64yjx$`vpQ{cJR|3CN#fd+q zb7vWYA`m6r$>?x!x|GP1w593aP{-`-gp>Nxc?h22dw_#dVy-FzB2+@*N3l3I(jl$r zN8u#ageAkf&3pxofRbikIAaQ6_7k-vfm*miL825<$AD&%Kr?v|?qru{2&LS#?ty{- z$M|mKU_7_1X7p5xdpL6(7mKpBeCq=tbx|7DDpm19bkFkX@`e~dE2Lu!FtG0lb*uZ< zGy^u&cjk=fs>&;dE36L^hEenb5mehR?Mp8{e9@tJRwKQRr9)GHa~g337cX>BMgtzg zzGlWg8)H#;b_4M@O=Z>a7WvYtKS^w(b@c z^68L$xou2j21Wje7SMTq06BU$rNSGuQueNoXKdyq;f7-;^l&jYCh-j{RkBv`L9|3% zjBg+_ez4>xSv&ku>f$^r-D22$oNv3Rk_-~}k9Vg>eS3~Yu|9LkKO5cc zpE!ZD>WjU5bc6)r!w;hk67G>_M(e8}bYQA2+z-(1Sib&HKFJm8;~{hF56!xax&z_o z?hwU@IB=828;a(K>`htStWjiRRxhbC_VS=`(u_DkpMkK~!A z2xH8FX6B8;idp2w=S2gY7giUO6Z<7reE!Y2QMj4={FkrNlG*SCll!@c{_44OH=~iY zEG4`E*>G21^L1re`14AUgG%gC=@4k$zhH-oYmu$PM~hr9MBIlg9v=-!%{_T*9H!(S z&@8&tKgq>R>3{lhp4a(@`%=GaPG_-!f_}287Sy59Dj;&a$Q*{0>Rv%1X(sfVYx}^iJN#!@`yrT z(8h)*GY8FSr)8ws3=@nvl;XIuJu1rxd+g+M+m5j{_;7}w|V$1nU#ipK|k z;NaBffBe*ol|xf=KlA&?pG-6Tkb>6R)z1xb!yD`5^DTk{jQbXK9S`Au?6TwdMZCW5eYBGr5{|;e z!8m7VVSX)SZWuRgFA$Ot1_Ev-3f$BUH=_DJQjq%Ca4Y0vJK?{Zq>tPj<3UqP*R1BVJhAzWtE-byl?y;Mn-kyDw2riB8Zdp9`>@yZ-{uc#r(iBlN*cWoMY$%`wYUBkA zT>wJI#i(f$M>WxGd|^ZKDOueRk_>fFFV+O?h0jy9w?0 zgwbL$!nT=)xib$o2u1feWS9F9I)R7?ZfuS2_^SL`m36>!R+ z9^x6DgKdLIg0sSsCsJbJAfAyiV9E%3s0O`QLn2kRmB6-7L1?RQ&M(m{t{zuUM9OO1tj^D>>T?i7LY+$R*Yq0Lo(mVSnc>INjKw+DC3vwjJEmqocO@t4;)w| ztW6NfEB|oE>@iUtx)&t{bM4t0_(VE~js<3I+Tf7eBi|v5?3zi_i&BO)RrbHfS53If>nFnLL3E|?>I;(J45?lQ66-5zW)ED6V>wF%F9N`W{+60f{K;;ox z5I9Wc?3AGS669f))JzAz+nh1>cF16i)9AwyeyqE`zh-a}(Z*q*qw!jXx_KG4T>nPOS5*g-{ zh-+>1hyuShOSohVF-&uYM3&Ve{6ffeQ7ypWK7{!e_O<413aF9o4IO?9C*&ylnoyCe z83K+&Gv!e;^Qm%XCJM&x;XpLggd%_vAEYk28iMX&WY=4J`y*TCDA#cvakT++2-%&Cz)7-E(XQJ=TsgCED$(tkR zO5s;AZ%$6-!JD&Qrs`atf_x}P;myf|H^<3~ldZvc%88{O&kpGH^5`roGOojf*$F~o zS(9_Lem_5pd^(p+B~z#h%3Fq{T*hKC7Y40>Qy^T(|gV9$})IgGV0JN1D@GdD(Pd+NO2T18zvnCIKlR}^E{xh|el@JmNNm5#g2T{)Y-!MY#Z9yoi6t#EI^_T}CH z@HpE7ZVJbWPAMRLLXH&-VT1J2v7#-FmI24gk_=@FNytz+BSYa>nUG_}%Hpm2_8iw| zG;@kDKcZI@7nr;1LOyYSzTQn7%iXs0*_y*;@h(h;W%uZF(cQ~{^oAqKoh$c*<(_bt zy^A9%@GMd984jE3z+uz(!c0I(cxSE%u9|$SEdcoj$BS*VZUd<@1C^&Vt&t{tj zE*pvmmrYsWkjp0Hh+5CggK!5Po6=u1${Q~Z?^Nn8aVj-}Q;964 zQ>m*;`c5T;xzrDwN@?j->hiL1Dsi}f%QLevM(5peS%C<1ET&Q5$7f6<f3Cm z$7YLCfdeZ+lFy0sfUoO?OYX)%FE0;%$%$D!N}Jvb&#k&Had40cP1Z}lRF<)VkIGJK zb<)!bIiVnMb3)~|lFBayxyd1MOdj1H#{u}yb?*_>j` zYCxXNCP~W=0$}bou;UHC2sd|TWt|p@B0S2>jX#k^J@^v|2@)xaCgo4$~ctIf?m<~1V9|jHcUlXZhh1~K_tP&srK0Bv^V^fE~v#l5C`Go1zFnrr6NWLx>jNU>XiyCM!w`L zZLtUiS2ZKTXy^CAVhGAtF0D`UCo9XKWs!H z>yV*mk!`2C#GN$^aR&1aO~JCtbD}43XURT53$hQWSIx$u|{vIn_&Y4(RDSd$byrtp0VJQc(5xIWy>af!I|%uk>tBQV3vzAN`k4M!@O zUaDu~Orbi%RwqbDoHUDqWeNs{4j7^ZtP$gcuq~QgcXqZYU}1~$ZW8TgezKtjO-Fw+ z`E3!iLlaTp6LK|xe|X&zmoL^WDztS=3^7occyw5{3V-IgRxZo-cUZi1GD6$+7cVd{ zSi2T`J(pj)#;{@C*u`@YmL|NIxOro~+cUt-;Er({*FVOt7R<>+TdXiLP|Ir{87X0m zjGUVs)PNonoSQ@?2ltN7q!}6x?2-~AxG0{crr{8Krj~EPaIk_8WQns6$FOU2`Qf<4 zhT~!l2NpmU6~11wFJt>>4c8|Bz`XgD+3>U?=9$*79rI!jU|ychVa(T-xRuL^z?vt_ za}mnTtz2-DZvo~PNpW}p&oU8){hG^T9-K1~)(P`rIftzh{fC@{xtM>(uUOjgxUGo! zQZ9Q<+=$FUNaJP+_S;L`nkDvEU_(^!t;YV@=ssv2Ve_zJK~|EY!961|-e*P7KC2lc zFe2-P$PA0f2w(Xrotj!HkhoSMM6Y6aiWo%m+7Rcv;wT|)oMnD23NR~_F|DAKVwBAM zbf&Ttqk*~Dcpn+RIsWAy$XULCRp zq*RB($CLUi%HKsDGBKr4?C4%!9V!RbBpTEqJ|)86Sg8m}2U%%Z5jcMbMTo)RbCowE z*PBXYfuHC`Y_aUDwJ zsPqKInNWoUhLB{Mrx;YVoHFHHA@^Fgvz1~NS)`T^OIsy>L+P|JF!hsamZ+a+a5mQS zEHN-ws>3tF++tWi%PAh4e|B#b2#RuH&T2=yNadKA8$tk&8zDx-)BTQ>{&M9%k zV&O+Tp=B&kiT{7ud;4fhud=}NecyBL=ehURsY<0%NmZ)eb11daq@Wx^k`>ZgyBY#9 zL9rLB7c=7@u34@n-plGmB{gNHS4iC?RZu|GvW;};MhP^UsEM7{D8UX=fdCPMMvWR; zMl)l^g*|jn9O#*}(q?|YXTR?`=iWL+5`<;{F@(D3{n+pRc=q$XpZ)BEy5X|%3TFev za|UnXp-p$VbmxZc4KT$DGjSVXk@hny?`x@ZKIH#znBpJl3p*V{x*Gdq z*a4r_0l|9RnnSd4r(*R@Sbq4K!(le!02}Ch!;Kn%`648e547w1I$Ti*=Ab3X7Qw)J zKJ1OG_x=>U0guh;bppH7$z1n$_vO{sFlJp(XKmHvJ2B#7JslCh80+aqE4;*Lo6Y5W zFJmKn4(i84=BcyQjditlKi3>avG-U_PO#qIvbv=Q@;0YMT0;>z?A;(6Pd&@R@CccvLXluwzgyNZ22b40X|vj94wke7 zZu5=n(FXCHn{j*k@TVWy_zESLjx?LxKhz-!6u}Datv>B|jgsx3Y4 zi039o%xuR<*2bR{2yu0sdQ}mzlUOsgtyQVv@$4r3o@h1O9P$C#MKG_N|$s zI~xQ%*lZ0RO-qdbj(-Pa;`G~Q=4Gl?I^To@Rl0|_85hx%O>_0dcwm|l zyEp0i+h1Kj0HVG-&(=DJSgSqHJ+8!Dvw*@t_%)-0i&GN|RHLp<#y|0%gAZeG+ievY z?A-5Pb0DaB1#RoiWUa&Xq@re0LGluB)kC?pKF=QuE!6CK#_@n58XGq{bckIGdG8+~ zLV+t+ud)}KXfw8ht4j`hs(jsaYaH}TGgp(4f)WIk`zB`c%@k(K0unRD(X*DKGQ8qz zY;N@klObm+pp_{0tQ@nqSyy0bj7B(er0(5FZ0z>9TRfW9pDT~t==lw7lU!f#y04h5 zU=H(YMi!6iWu7=zxCSd*v5~@lcy9MrRUAg1?)?I4sWG%{gUB}?_8rh6yuma(%k_D{ zENaeu!=MB}B&MSGZv30*BHa(IDL=XqSh9V^1QP+-1;EK6Q4V(lBo|%`7+p7DJAZ&~ zPGlCFg&DgM0Z)3Gdwf>>y%GNW-iiDu@euZ2pkZ%22&*dRTK+1rBur zb4I9y_Xsp%vPGGUpZGL5yq0zcA+hF}%Jubqo;HhO+=BU*E8dNpt*R;4+L{<`JSN-)J@c0d^t@Y?>iblYuMcLihV z*4sorewP9XNo`c`94;Btxv~B&o9o}QpRm?OEY*Y6;mgu(5bh<^yHxL49qwyyPZCcV87>(24TQkNhr8-;^TTagh=j1Z z?`WxhS$a=X!@l;4k2#zj0}`00*)_#(TvieExCAED-yY|BGF@U&hoejhC9E$X8&Ahy zd8vJNV+TkfYig0i$6(@dXE@#Y>Bc&p<5b({*vV_~O1wGY8yUn*Jv_%7tZBDSO#GMImefMFtzu*%dVb1 z)r7ss2Qupp-JN1KXIzu^v5-1WTkkRA-a{a8D)$OQiKP=E3s?@8LIYw$2Qe%j7Qi*ZTKYPpx~r9`(C3c82Y9^+gJ?D9pPk%&$ei*cLSpzC~SuqBXKd zjy`$DPgqyKyyA+ZBdL_7rhM1d>09ce4P7#q+XYb)?I=!w?ZBIOE?;)MqOBtt4VS{) zU(Hlc3|ELciu7QMz4`Xh<~Ffojv3lfca3u3jD%DeTVSAi)ZU}|#d=5otvHm*Ag;}A z*k7Ja0MJ{(*Rf%D#dCk99wwm}>2TW-x5KoAP;9=CR|7>h2TzA0R*+DP(^SreB5yMk z#o`8vGk{DeqCAYCn93llNXJ#rvzIVE1u~~src?hOoF-_JUZCW-<$>;@pD522dGYsJ zGCm;Y>dqRXI}aFJEv5ueSn@fSQXvFb#Ty|Q@Hwup17ADoD`Z^ZR7${8`1L#$*ReN& z*4S+ECVs$<>-oX$vJa;M2j!M^m5Tn4;E?R2Y5Hg9t}uj{c$daKL=>}L?>o49h4t=3 zPBW#XZC{E|Qzm4L2?6cYVghaK!!pM5wk0axr>%)|EC$l!ABuunr(vVlB0gx$EQxfX(K#8!EGjH(@trv9s?|z6C@dsw zBgFs`j7mr~usYn%C10P1Yd)j#SaF@Nm;GKOiR5Z_iw?A*Gg&!Wq{|-Hx=}*{aX=LG zh`r*1AmC?au*I~w#B)K|K*=6U0qScgPMN0nqf2k){&~(9>ZYy7P;Q>z8#=D<&65Cj zBPNk!un8JL60Qv|9KbKWEqW?Tr}k2%+WncBBY&(|I9Ubu-VW_cn!> z+pDG=?gi8Wxkjbtj+&@MkI~{PbI`Ac@PO)KBt{@_%}5nMK3$92xGhT_DH3^wXa*#* zE=e5PRQF=RZ349tGaNy(6D-W^f{KFXBhbi?E|lMI=5|9CwJnOo%dBzMqI{V1fDV^O z0)tX5`?6DV$L1rx<|FrZPjJT#C5-PImxv?wbaH7|fCp3)LX88d4vvOn^)G|Xn`QUGvL-F?_^Te(RoorV5{lg&+w6&b2r`1VGFJd++C%IWTSfX zN$Zh9MN&@)AdPv@rrQ|e_-(!&ai~qV1lyPCuC^6>lA$g!NVcxO(|5@dDY zVxwVzV9LhaC+2ZBR1Hsi2%$LEIYh4pbCn-_@HF3frlY^{AY`6EXFHaEv&@srg)~Q} z#LD!Lk%r%Q(|>s$vOTFr$ zbyD#?%UfL<+B5@YI<92O#aw*kkTkd&iTpjT3F9qdzPF=eKP5IeLu$@(BL*T8(qrQ{ z3;BjO4cB!*t-c83ZXgrH7?9b(Aygyf5HE;X?g~76YA0uoDRSoG^q#Y|nsx%6|5mGE z&aMPHXQ_vI@|<5mCzl*g9$bmZ%iA0f5E)OLx6mgy%^Sk9tVi>Pec`;3WtFk;;dt62 zn0AB)Oo8f-s~dSL^Nekr+v_Q##!1&ERFX}!SNMImX0rEJcrfw9d8G%_Z%0_RWvnTw zW=++gTAgT8QF5WOObe@$+Cj80n#+zZR45Kxy&Rq4`~i3vjxNpJE&My5e>?bh0snUL z&tHpbSYn;X5@>d zdP~08g5DCJyW&laY6V+S>hnXLAv{D`@&_FlTv;&sdcM9Wn04eGTPCw&1DeGK6s*s0 zfWsU^-#GFm2D@>rTl83-9{|P68poP-U+nn4&am2{>u%V_7KT+c8FeLy99g>+$Hzr) z%fkh3E~oB@zjNswZ*#*#d6e>N_#$@V@I=D?{PPzu`El49!3wZd)TFr>HFj#eX^v{b z_&-p{y4%N`^v?Q6JSMWs#~JjmDcQHpc%24~#vtCSIZs*|kqMsqlZHL2f`;==f(RdY zLkr-6lzQ7NQG{ z3P}Yc^IHRml?IQtnp7dnbT=xb-sQWibeG;GRLJz$AD=1GtJ2r_iS3McJjMMWJ-|=g zpYi_H>AB{ft_aAH&m-XS0=KOa#+dwebc?^FN~fBRFiMtyaU@KRq<8Dia>d144wIX6 zjmb556l2}n8~@W{E_7J4R*TV(4o}(rScvKI_p^|I6__4=KgSvAYyZ?={Il%*o%Q@% zitCe&*g1gB3jS5a?t1Pp#jjw65h0*>Bn!AqJNoobJ#2sVhPChG3@ER5XW@M? zFBDvgG7xKG9)9Pl)H0M!DKrsqdR_~VJFSlZY1U#$1;%n81NB`PDr;fZgz(*9iD;`V zx3&tiGIvCmn3`JSW}6@O5}uD?KR2`lvB8}15wh(S03t;&a9ympF3lcIhtm3S(Cc|Z zw|KM8VWYc9(R+qfj&;uXPJ7=z*qWY0iXW|KZqT#oe+ZTz)e!@t*=IIDaheJb=QRuP zO-5^|ot$cURiW#xe~jF)6>U?-G_+9=kAwOPa`~#lZGAgA0Wy4-3ohAPh)Iej*oKvM zw%;*OEP4)p+WS<3!*#o>k^?u;AnqBw0m%cKhD3|Fr4qT6Zl2-6N>e zTx5H|7;T?%?i*dm>t*dTmhyO}{=y(3>ik>*$l#*OafCFT3gL*se&+o;faw568 zuD3F4H>N*hU22wNAn@uiU;<^>`8%4d8QO~VC(`peYNxK+!q8h0Pe*AU9=<^LdQar(Y3(ImS+m(N>$GI%EhQRfu)+ zNcnrbUdj~o^m=yyeKn+bB^nyQWC;su=^x>A21%AOkln#+Z|Jkc#Y!WKtUBu#2nAtb zFNmhcSw&Px$NPB27-HRHAS7VKlNdipx=sAW4X_Ki$yjU*-qiF^8?f!S-?UvKEHhe9 z_-Q@YIxPoV;r66H<>zzi%R!J?jQlq$nGi! zl$r3eK1G4)s5ErTQ*>u8+E9dYHDmrpfQ+Hzl#W0J5w~NXsGt|b zD)#YDe0K^)A1PLYg=$Gf%r&|j@nd?F%fpfKhP$V|lldOx2TZw?A3*m8jMK~c8#gAD zGBPbYn@|b?o5T|0L}e2c0jkj&h8FTu1=Ov8?SBMOQ=pqZ|9+@S)rThZaH-dViCc?; z)#&3OWOn}8bti^W`-4{?cfz)CfOKVl9;;ttx3FXq^usy~nB!tj98$)~Ca%NG=0^EY zMIqXqjkTO`H|?iJ7IxK689Zt&_p|7y?5?3G1C6f8qEx!mooWjz2+1F=E=@5#50t6C3J?fVj<&}2U%o@TyuGp69#qeF$|ag0}zl2C7^aBV8CRSnQi#- zmvnln7@5H)ZXBK3&2#OWs4U<5j-?EyMvGrahJwp^*#XOd!hAjU(7jG;^VO&jN5q%U zzPvi`cy$X#(P<{Lx36gNrWskdez8r-?1X<0e7?I%TW}D+jhiynqwDouiWXj9tfu_4 zoDY<+=?@e?b8!>@d7$_}Tvk5)S8>_#@?XX!bss3+AD7f}ptvh8Nho}v_!n_G=jHq2 zlEEA(emX7}yu33m7rp$cxWvXiQ2g__T=McK<8s-{({Z`tWS-m+y&7j+{DBygM$>_wrO+?(p(mae0B4?~KcxUY?B03%xuMmlt_? zdtC1F^0v6#oi6s~){A%bhRdWA14X(0IPb8sVu%lWUFGY4=XEDv|AyDyeEl)6u~7W> zA+Kli^+&y)%h$i|wHgIwe$DHJeEp!;i~0Ijy>{K5GQZ;WQojDTUN7hC2fSX%*Z;=r zE&2MFz3%7h`@PUZ0<@@AY~|zP`un3-a~Pd%ZJX z|D4ws=If7meNn#t*IujMQ|E`hZovDYi+QLo{L#pka;6Gj7pHvS<}pLbavHAk2&Nd% zSV+lB&~UUp_2OFG6zOLdRy{M-75BTwm#4>t)h0}5Zm54`d>2{Bs8LqitJ zwlEMinCDYJ3vlAJ^2upDg1hS`v0pIn1)M%`ES->u=BtW#&g9>d&F_hZ&mrZnMdg}L zXJ`y%R5)hU`UJalgP$QL7(j53ZfsnUpTNPQ)1AcWm1P}$|48n8WTvz4&9vBeLn3<7 z_l2~LzB_Ev^V`WzbkZe#mHKO!7ZD0}W^vS+L?NfukD{Ru=Y~E!1xPlOfwYiZdsOBC zr3HpSrvplG@mcg&=L?9Uh0bKtWC6B~CKNNX5b6H}xOUE_nOTULY5NDbttU6i;wMk_p6;mbe+?)uW34yyJ7%pMi4Z& zu~)E@K0z~c4ZJ(3la7IY>t+z8<9gUh?}@FW^T4T{iSd-r2T(ZnAutF($Upzv($?Lm z-UP@Aam-odH$b0@1&k1>kyh}Wxk%gZoU!gTFjg;GS z;CJRcL_#*q;B5R3n%bh0qi?81s!jL}76sSgH@FVJE!SbqSgx@p@Vgu}#KjoDJHd5c zn9EG`Q$)>NW+u2yrv2#W+015((Mo6cVZ>}e)hW#8jd%f&*~X}jx{Y4hGM$-VI!(Lc zbCu3Z9JVmViddY4Yw9U@y4b3#v6R;r+v$i+*?mPf@L_H80%49mT*Hf9;FP1`c{N25( z!l6$-5|^Q6{!Uzm?)k~M42|^jaT$8*r{XfS*T>^>Np@HWxDgxGe+0pq2wYWLuHIObWyvO7^6NiS4|@MKb2;0v8`pq z9dTP)u+u~u`cL;?O~cefN3KEh!8IwuHVk&MpBTJzArR^33}G`<$!Ep&WqglHwJ^H1 zg!>!0EyJJkEHG>XR~~Wd(59V;=)+!H@xW4I7z`l*)NDamK9n=nHW@VCy)i4Ztz-3{LMj>B~?R5f$a1DUj?rt zWN*p4dz-uT5Etp?=^5{t?-w)wFQyFI7irv444@927VE4^EU?a=Bku1ftiwI2@VoVG z?f}U?061PcLv#q-O!aI4ca{}|qpAyS2F0^cEZjWdt`buMEi zw5Wp^!;>E62_)saa8l{N=O1*s;_vsN1$)?aComHQ^62WTN_7uTLwsj_!3laUdcl9F z;MjmjVw@~}3yVmoD+YSk%Lb-KZH)?rtr5|K%3OAX>8ozrkRZlUmUB2uT!>vs4}A_k zM7@L;ZI$V40mpN1B1|UQB=lt&v`bHZnzHh%9aJ03#4R*jy+SYKIunb|gfrT)Z#!G8SgCg$xu0PZ zdEN=QoFG7Gs~?Qug(MiiWCeOWW%V52!X@hS0*OX7WK?4^1FcFHE)WW?DQg1~d=2`b z!GlLLL=)8)Tmj;vEpmqxJrVtH_1n zt?8+D$SRv3Q4Hu-q@sMI>u@1K&*?b}a;yBq$$%grBl=j8l&^+u;Sc2NJIhpupjv$K z{=+v~Z2-mv_Z-0psE+BhK!?n0{h*&KUCla-c3oq;V2Myy7%r+_9SC#6SP7Z0I;OS- z*f#2aoTL^Im?d=qC%nZ%>=ed!mLO%ccu&r3rJE=>>lY7aqqDT0yW1#{hWjDBBJgs; z?jqO&YhZ)=65Su^v9$M%Xw@)9w_Z&dK%*U0W9i+)ZR`H>+1@YgDVL3HIeqEcaDgWa z;1}-N6WJ(UNO!Pdx4j*+&PkS<7a11P>2QjzFNFEbEpiY>oNo)1>@!`?Sq~*h(Lc-V z_RPd+9^&Hx{R?VS6;!Z(DCG zgdO#wsgl)b{znj^(IGSlN|@%2m+`e;tkV_#8GT#1CoD}BwC6GaZkuE2Yja1C-TDcz z!psoVyoCE$9gg>083~eRLCRdMz7KeBTPyCb@rMYGrd|g=(OmN>TJbmXwLF3z#<_98 z2|l&Jk+-`I5TfFJ0jPNa1?)^g84sl|h$Igdk7n)y4?W`!y(gcOY1L3|=riuLZU85$^Z}gL|=$gAk2KNGuw~q|k(L8}%SSMfguU0aQ1PwQBJ}E!lech^#0e z1jqtABrI2aK^YfcOqV&`^zDy<$4)x z^G`S2q^M6C%Y^HZb|1ior`dhnsFj07W)0PulcvX90YJyf9P&gNxr{7fTw?GFH+&g4 z*Rgho7NRc1HWp{iuw~++@bJW!&UTL8YQHl2I`;Oj#Kx!K0+?8QK*{uxPc{SK>-5!j zG`=}8`R2~%rtR;zw{v0R(dA zdUN*)vT5MJ<@ivm^psD@Z3s<*3x&ftZiM4Je3(ztiN8G!z$M86skZe%Y&f#RzA( zO49|w&37zcr{f`nwm=leBk`f?ZF;on5^ciUJ(UTg&hccZXRK%F69WUx2I8UE;|r>1 zzb8DYVU+3>D~}>O*eH{O_9WLQ+fiq+Mjk$bc*BS{5DCJV7p<;a9wfsBZ{4T{WSz6h4pwEr1{kn>2 za#q2Oc1p;nm6!l`K#ceYh6#n4R49EFuB+YDK zL5ygI5y|5~5tskQ0E%X4-G(wjIl2)%P(tbsxjGDTV@x0h;T#lXnADB218EP^S6kkK+T_r5 zE5kVIhKCfe5)=mp(`s~D>*#rBa@;?L9hMWYCLcnm`-qysGUEV431s4_`qm0r;~C5e zEK7YRbbO`mYi{9tLoA4OJ!i9CVq-Nn*pf-4DWfw+z*sQiP<4~Yy?V-L!mN??%#2YJ zP>PymOoVI;z=fNNZp9ul6Lw_6guG(ffS!1P{vtP;K{eG8l*8`y$cc@^ZYD(FDv$k0 z!~3KLI>N4l(_|#f^*AaVn$T4@4P~?4G{`}iXw?z-vfBS16u8dLZf-Vp}X zIZ6z>({dEwzzsKWHGc$~l?Y)jyT|gU=$hA9QQAUmF23UQtUv<&v@0h&omc9k8g>t}v(#xnWBuB7c+pQyv5VfFK{=Gh`d zFYLa}?y>v)jRs?gzxo$3B!4D``HKWoGfsX+10|$A20D|rH{H1V51VepMRbGq?FPMR z%I<8+{$g7;E~2akvl!KfOd=4j?PP`x{fvvrt_Eznldy4CpxT+V+lE%s6S*Psn2#G` zuI<$Brc*17D|+Jqy~slZuVQfu8cI*L1TdE+xGG_8+!H9jm{_3}RWr z5_NI#4zCJnd=x2oN>;IvDoA!%q1=U)bSdUB8Dpfs2b&gmvP37L@+Vdza?-Z`_TKx- zoZK&Gj-#T9WN9!QoEfP}3wP=hkH94+(!v$@AI5*Y{`97taL`;NS-bDwK2mUHRybq> z0a#0VMR2BUPapf$N8sIFk5Ul^ofFl?KB;{O^4`ZgWp}jm$qK_2fgp4DBJKwG5#3`f z%mKINF$m`b3pDIr1hA3iFF`1bN4YvpfPpP(AHa$oNj|_Qi!O?+NKJ zn|}F=5B*hjBUzVD%&#%A$8Q8_LT+q)Lq^~mkKB-4w7=;HlC^_`0Ww{=tPs`>L6_m4 zR4?Qx&t|It*sQL(5eQt43b1uT8W4OC0u>>H+o;Qu7=f!?M<19{Dn!>D&1dgYPz|2_ zifhg;&Nmk4hsp161+D2%kpf*xG6=qJA!FpaC=D*y#z0I0`!21kz`4GO@<`BgRq-Z% zYSQH8_t*1t2IMlBG(Na8{~Q!K(mvSzYg-fQrT#`gfW^wrqO3aInc2Ddg~i^|^2(O} z)@|o)KYzysJ8>oYUmh|gJG;sXqOJUt&;^sSRBmJ{F7!(p`Pzff?auzu`s`k1pcT1xq`cN5AEx3wg9->HOx=|L&tD9-Y7BITu#{pL}#4kG3yy z=tVrb_1Bw6=b5)F9^L7q6-sSe+S)w&aQkSh=g&u}2Yh7peUoR!qlep4Tim%Hk3QEv zT3N!g5Rabnk)2p}|9(9BKYiqYc+xyN`B5Hy7ms>NMAb#9Px)v&j}~?E$D=>?(PcbZ zzyi&W-un=bF6Gg@3v^NHqdwZlqdB#vc=RVea*VS^`^2Na^pX7v-$6Y3nvZt#sNo-g z@UD;X$QjC<4X8TZL8$ZnqZqO>JScdk*R(SmL)wzi?vKC2aEX;<{a^0O{^`eKL}jq}%=munx=qJtYd(ddNPcMS6aYI86T1 zE^2*xw8$&UrCP+QA(}~j*~7$QtbUDbLr%IpKwCtQ5F<^K#4&`{fS>#3BT;y5fDte# zJIy%aSsgp_hcjoi@xz0!mg!)dTE_S9{ST6 z!K-QS?0e+T=?}A*r9`UWrEpzn8=L@I@0aZLc?Ju zJq;sXsk#&Y>ySfw<454~#*d!mqnR1LnME&%cQaO)K5_heU;(uz%P$UM zQpOyopP&I~2pcWN?;7i-ehCl(uHGOdW;PbMp52z~vl;qyV>A3>bGG+t+8Yr7 zUw5Pabn4xogaewFp#LvR6KXT2lJ7v9E<`jaYbZq~1aaow6x>Pgch84n-QN$D(W!p7 z=p8nfcnEmdM@acK{d0)cy6XSW@yJ-t;K3bS5j=%Vc@MN;Pi&>>{aVQ$fA|VJv^%hs z(n4&Iry96RVIvSst7^d|ASXaxS7^FW7qDWF9#@gO+!G=qFqbI}s{;yPO9Z{K2kguO z{T$m)u#n+ePbY0(?cby-Fi*$%!LC5?+iu(q7@crnd^LOPje?3@C4b&2XYF>*3SLE= z0-edp|Ek2Kun?Hm`^FBMaV!jL8f1l;LTHaGHjiZqg>t7jDP2%%UoWRlXvGs>xRAf0 z%&3adM^R$iF&2ukxUDwj7+j|&!w`FJ4D8_0VQv}C+`?M}4BsUV6kJg5e*w(=1I+5z zq=;u#2waD;^LGPyQ^^`KNoE`PTEz>(cNV~g=Y(~q3Hb6x-DqL3Ou)qa z)&Y|q0g1BZz?Seq*{m$56mRviDYzGENQbXUdJFrJT{)pqan$g=SU2qZ>q|_n1 zJ;d65Ba_y7y^<7Vq@eF;Kw;_yMhZP9W2@GKGv#fl@fCkV9#9Rrz^%UST+*2*he$lO zzx(L}oq_1nv!8i{2Oy)ovE~8W^Pl5caK=uU+P&+TH#&*P8q(D_a!J4JIEqs-DClSI znEf1fa*NS19@w!WIu_mQeLtKmI?RA%v48g({~B8>CrQsp2cH4ma1SFlkbr?o!KHms z-IC0{Pp8BTQbD2EF$iQ)VB$4u0#ntQk57+PP4QCHOU*3MW`~BGC!n*A`6Y@r8 zWfB-tS_X!QI-H>)jNSl7gYwP~!X*Ae&P+?fL6P|wQPg0Gb~m$4c+x=ELs-=rv+9}1 z@{9a(hIsKG%8jQ=IJrwThy~5&s4)7|BwL+iw60Ft)s`uaxa12lTG?gLz~t9wF&JSv zS>PMv%Dw*0!Xap?E;FMEvXRkTy=majJ}e$9BKT3nLt|g%S%4y6mFelv0=9ot^uDcd z9!0;v;`FfihJ)$^1e_vscjKT!v#M0Og$7=S zkXECv{FtTX7Q=o&-IYI;p3}?P6D^hoFiQ%k7#4A+g=chZI9ni43V)M_b~NO-J)M1~ zh3JLl@B(KGyl2d*F;%KV0g)lhC&R=F&N^OUAeDfwKvfO`FuaP!e$FukXc};AfItR| z*r`q$LS^6hbPRA_V)4{z$u0m8P31cz42*<;X*!7(&$^AS!Z^W%?&cFrWS*n*OSm38 z4fC7k_>%FP0erJTt)ll>RnDlW=ubjy1)~VTmw3H+IUDvYvM)EY36 z#h~g03&H`5zRe!MBUE3FOU( zmypm04zbn`vGyXGxo}oLkppAD2kp|2}uy)-61-VJG`Omy)_25ATG>Q+l;+3cwVn!L_gmC$gh-kd)>o;Xfhw0an$KQuaTzAmN zK=!=L7)b%iWn}}m;LnuWBRYfa5^BXjW_tipxltWR-hvuKF` z%Mi}dHlfeg^C6!IzX_ErOD6sX2xt)qWT=7!&wds%C;y$ovv8NZn5r~Qw?4+3rjm>C@V^-kJ4r6KyeTsN&_-O&O(5G3^#2)HM~LiI z#gFiVR{2(bfY%T4gC_KYd~xq@Tk{WaMN@p0cy5bdj_0 ztH9w>M=evTEjpL}0H?(z1-q7wZ2Ww4Lr)^O5jM~)zG!J5iFn72k6NeK>sTNj;sG?6 zIC|7MRI`|(9}BQ(uj8sJZ%kXvo@6$wlHl}2tPZ_Sk2d&oJX@*gyfaFyWZLUa2&y1X zg=t#HnF!~of_|H7kt>q#yj+nT3KD6z|ABG{kE$1(g7lf9|6!PKTVwOQ4wW^1ws~nc zWI$Awjh+M;8kKX7Vbc}sc6lt0B&j^9U?%-Tm_QuPiA1yvi|EZ9Ai>)j6VEpMW@d++jKp4Fd&-ww(P~b+g?>0^ z>Z8Fjz@8bB6Dg{R!*wRks0Dy5%emE=t}tpk!@hcZ0tylFWOqE7qbb0$KE;=7t0D5G z`FN4ef}V@?IDfp>bFxI!p>rB@#HzEj4SGf&m2(+it&ydXrJkX61jP+N2p2PE6mpD@ z-4^--AS~XDjQ_yNC0A|h`EY9SUF=c9HlNq+HUrQM+g#~Zolb!aoJprX`v?-P|F6r} z1)}q5E_;zxD|2JL^58YNYZR0-!DMJ*)uWpbM|$cY=Jk#YKU@UCF!!e=JUGPf>j6`M zg!qK(z$wV#cS)yz__^EO|M8#xPoMep$ButLq~>N&nBOIxornM9Z`}XupFjEJfBy60 z`;q(dW2yj7e4Xz7tw)TepXdPWuV zZ3mp%LOq}K9txNxeolpHhObl?gF8TBW>51>7elK1GevLIcf$*`sCxWgZ&()~V2Be=kueQgA zN9K4gJx1dr@V_~O|4ksuQ`8so)Kw|PEonsC6{pZh)(xkfP5YZwsHYXFNOK+`N+&cI zBrwmA>sef*sw%5llWj#lQdyC|zq(9BI#3*BkVspC^s-Y1<}RMJfdS%6sK0ZlW_pWw zpq@7ulo0gb@B;)a9ASJZ|M00WIll#fN$LJp+cxT{Q{IO)Tq8SyIC_-aETg)g~uBTzEybB}#agmg}zIuLD|+K9%Oap)}*I7DSHxogT+ z32B1tBBT=qS*@idF0O-|hE2~UGQSFQ$Wn>rvZj(Qg$(V~P|G&I2-^#duvMF*0CwdF z6kRN%qNfhr=*_|7&Y(k!ruQK$0wfJA#JS@yx$tH{Kl}I#1A2pg&ZrSCjjqH^ccm&y zgxMTCw-})8B9^!*1r;`i4#!9gS@a<5(Bm(a3U%YgFyS_%VZtjIs0VLKauAwz1#VE- zp1%c`y;{09LgEJ-;SfLA2v_riV7QWU*+#gGD_HnJei|F$fzeP$Hp0P%%)>-@6IBF1 z%j69MBuVprOQ5L-C*cCg2rN2;}Tm?0EmfTp`RH9Sd#%x zM0DJT?+rWsYEJsqLc+6M=t&4dB6?ytb4ADe$gX+&6xM^+8IW88r0}7%wNV~@2ZPz7 zq31^FXmbo^II!}5X$0S;7QT6rp=pRd_%-(K20+twJ*7jjRylC8$NE>E$&rTUM9QaftNNcmT$M}5#VUA^t7lxr zBAO9;Xb~JD~axCL4)t19ZAgY{qA*YSP26BXO#6`GV>AKIE}gc~W%3=yGJKE7nA86&%J?hN8GhVp|IRJdO3tG9L=h|3 z)uFHVpE%IZh<_sv5tU!g!%`iY0b9CKaYGlVuZCF_Y`4d+q%sQvp{S_R6M0dj+a{i) z*s8Irb_PD=d+mDKBe^w@toU2Wa5$T>fllN-+`LVwRU7u9?^_%>`!nu>J&P#}JQfn41`()rNGv7{D)`JglEVOjru_ z{_bYtOnS090A)-!8*_G5Drpy#{t9d+N@F(5J}Bwr{O)Ytgy0)T<{l2!2Rt979ABL zFRYUTCin}9q5JOxF~WC@YZv z(9;Qo&|Y3-!xiIkF9}19%~P{X{4B@FF!6a_Z7e398CHrp_aVFZuq&G!lW`De&?l-g zy^N?VbHOs7mGjA7M=So8@kaX=f|p)iN#Yyk?+WCYr$ zI=IKXu3{*MyG%}(UJYm91af(QIGC+2_3`s__$#0Lz2Cm)jxYSmSBsNZbn1BYAOG|x zzw*AvzxdH-eB|-de6!!5!~feeKmCS(I$w$B9qa4Ts zigo(0qFwW4e;>banQr=BRHV|!3}ir0@SY9OPG1e>^4TSwD}fw8HMqb}AO6bj8pp?+ zli}s?mG3+;oIgo;M*rR1FWfwwJvp2^eEz$64NPtx7KiJ1-#nZ-n3h-D;Pk!>;MC{1DjYZ;LmFIzqU(Fg$D{((n+& z%FYGSbXm|vBjz@6MqhQzu;3cq_?o~DcvIS+<8A8V6=lKbAe;xNDDzx5 z6R=+UmG&M*FWvS32Sm?$%C-u$pVm`a=#~LEledn$BXP$d91y{uj%F8&F+s6(BA0g0dB&eO!xWNqvg^Q zmVWyGb^#XJC({>iG;bgFH(Cjtd@yda*C(BcGR@5s%?FQ;ON5~V+N7tNcaO(AyG&)x zcTdK$w{9=pOV@{U=|lFZo9?Ap`YN3SYWDXo@9&QO1~8+zFCKsXQ$@Ox zs|hwWW>^(`Eq&RO$kQFv;1rlwi(O8u=~*@_6H`afAlVH>~fmW#xuc z-iT~rt-VSa5X!26kQHUV#437J4fnjI?)<3P|1-CT{iB1(c4LnhwjN)D%2dZ6HZjQK zkp!{O1S9i_+rikQ-t=_d#04;CJ5<9+DJj1J!fQ7ic`Kcws

s(qq&!c-s81*wC}; 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 THP^;Hg0+87hZC#D5o7)b_%KUE literal 0 HcmV?d00001 From 3529c344d0b11d25e4dc21d45dac94fa24e82842 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:06:14 +0000 Subject: [PATCH 33/49] Compiled vector_search/brute_force_bacalhau --- .../benchmarker_outbound.rs | 113 ++++++++++++++++++ .../brute_force_bacalhau/commercial.rs | 113 ++++++++++++++++++ .../brute_force_bacalhau/inbound.rs | 113 ++++++++++++++++++ .../innovator_outbound.rs | 113 ++++++++++++++++++ .../vector_search/brute_force_bacalhau/mod.rs | 4 + .../brute_force_bacalhau/open_data.rs | 113 ++++++++++++++++++ tig-algorithms/src/vector_search/mod.rs | 3 +- tig-algorithms/src/vector_search/template.rs | 26 +--- .../vector_search/brute_force_bacalhau.wasm | Bin 0 -> 125121 bytes 9 files changed, 573 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vector_search/brute_force_bacalhau/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vector_search/brute_force_bacalhau/commercial.rs create mode 100644 tig-algorithms/src/vector_search/brute_force_bacalhau/inbound.rs create mode 100644 tig-algorithms/src/vector_search/brute_force_bacalhau/innovator_outbound.rs create mode 100644 tig-algorithms/src/vector_search/brute_force_bacalhau/mod.rs create mode 100644 tig-algorithms/src/vector_search/brute_force_bacalhau/open_data.rs create mode 100644 tig-algorithms/wasm/vector_search/brute_force_bacalhau.wasm diff --git a/tig-algorithms/src/vector_search/brute_force_bacalhau/benchmarker_outbound.rs b/tig-algorithms/src/vector_search/brute_force_bacalhau/benchmarker_outbound.rs new file mode 100644 index 0000000..8a58f5c --- /dev/null +++ b/tig-algorithms/src/vector_search/brute_force_bacalhau/benchmarker_outbound.rs @@ -0,0 +1,113 @@ +/*! +Copyright 2024 Louis Silva + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. + */ + +use anyhow::Result; + +use tig_challenges::vector_search::*; + +#[inline] +fn l2_norm(x: &[f32]) -> f32 { + x.iter().map(|&val| val * val).sum::().sqrt() +} + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32 +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + let mut vector_norms_sq: Vec = Vec::with_capacity(vector_database.len()); + + let mut sum_norms_sq: f32 = 0.0; + let mut sum_squares: f32 = 0.0; + + for vector in vector_database { + let norm_sq: f32 = vector.iter().map(|&val| val * val).sum(); + sum_norms_sq += norm_sq.sqrt(); + sum_squares += norm_sq; + vector_norms_sq.push(norm_sq); + } + + let vector_norms_len: f32 = vector_norms_sq.len() as f32; + let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt(); + let norm_threshold: f32 = 2.0 * std_dev; + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + let distance: f32 = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; // Early exit + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/brute_force_bacalhau/commercial.rs b/tig-algorithms/src/vector_search/brute_force_bacalhau/commercial.rs new file mode 100644 index 0000000..bacee69 --- /dev/null +++ b/tig-algorithms/src/vector_search/brute_force_bacalhau/commercial.rs @@ -0,0 +1,113 @@ +/*! +Copyright 2024 Louis Silva + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. + */ + +use anyhow::Result; + +use tig_challenges::vector_search::*; + +#[inline] +fn l2_norm(x: &[f32]) -> f32 { + x.iter().map(|&val| val * val).sum::().sqrt() +} + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32 +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + let mut vector_norms_sq: Vec = Vec::with_capacity(vector_database.len()); + + let mut sum_norms_sq: f32 = 0.0; + let mut sum_squares: f32 = 0.0; + + for vector in vector_database { + let norm_sq: f32 = vector.iter().map(|&val| val * val).sum(); + sum_norms_sq += norm_sq.sqrt(); + sum_squares += norm_sq; + vector_norms_sq.push(norm_sq); + } + + let vector_norms_len: f32 = vector_norms_sq.len() as f32; + let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt(); + let norm_threshold: f32 = 2.0 * std_dev; + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + let distance: f32 = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; // Early exit + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/brute_force_bacalhau/inbound.rs b/tig-algorithms/src/vector_search/brute_force_bacalhau/inbound.rs new file mode 100644 index 0000000..fd0bf1d --- /dev/null +++ b/tig-algorithms/src/vector_search/brute_force_bacalhau/inbound.rs @@ -0,0 +1,113 @@ +/*! +Copyright 2024 Louis Silva + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. + */ + +use anyhow::Result; + +use tig_challenges::vector_search::*; + +#[inline] +fn l2_norm(x: &[f32]) -> f32 { + x.iter().map(|&val| val * val).sum::().sqrt() +} + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32 +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + let mut vector_norms_sq: Vec = Vec::with_capacity(vector_database.len()); + + let mut sum_norms_sq: f32 = 0.0; + let mut sum_squares: f32 = 0.0; + + for vector in vector_database { + let norm_sq: f32 = vector.iter().map(|&val| val * val).sum(); + sum_norms_sq += norm_sq.sqrt(); + sum_squares += norm_sq; + vector_norms_sq.push(norm_sq); + } + + let vector_norms_len: f32 = vector_norms_sq.len() as f32; + let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt(); + let norm_threshold: f32 = 2.0 * std_dev; + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + let distance: f32 = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; // Early exit + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/brute_force_bacalhau/innovator_outbound.rs b/tig-algorithms/src/vector_search/brute_force_bacalhau/innovator_outbound.rs new file mode 100644 index 0000000..a0996b6 --- /dev/null +++ b/tig-algorithms/src/vector_search/brute_force_bacalhau/innovator_outbound.rs @@ -0,0 +1,113 @@ +/*! +Copyright 2024 Louis Silva + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. + */ + +use anyhow::Result; + +use tig_challenges::vector_search::*; + +#[inline] +fn l2_norm(x: &[f32]) -> f32 { + x.iter().map(|&val| val * val).sum::().sqrt() +} + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32 +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + let mut vector_norms_sq: Vec = Vec::with_capacity(vector_database.len()); + + let mut sum_norms_sq: f32 = 0.0; + let mut sum_squares: f32 = 0.0; + + for vector in vector_database { + let norm_sq: f32 = vector.iter().map(|&val| val * val).sum(); + sum_norms_sq += norm_sq.sqrt(); + sum_squares += norm_sq; + vector_norms_sq.push(norm_sq); + } + + let vector_norms_len: f32 = vector_norms_sq.len() as f32; + let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt(); + let norm_threshold: f32 = 2.0 * std_dev; + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + let distance: f32 = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; // Early exit + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/brute_force_bacalhau/mod.rs b/tig-algorithms/src/vector_search/brute_force_bacalhau/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vector_search/brute_force_bacalhau/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vector_search/brute_force_bacalhau/open_data.rs b/tig-algorithms/src/vector_search/brute_force_bacalhau/open_data.rs new file mode 100644 index 0000000..c55800e --- /dev/null +++ b/tig-algorithms/src/vector_search/brute_force_bacalhau/open_data.rs @@ -0,0 +1,113 @@ +/*! +Copyright 2024 Louis Silva + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. + */ + +use anyhow::Result; + +use tig_challenges::vector_search::*; + +#[inline] +fn l2_norm(x: &[f32]) -> f32 { + x.iter().map(|&val| val * val).sum::().sqrt() +} + +#[inline] +fn euclidean_distance_with_precomputed_norm( + a_norm_sq: f32, + b_norm_sq: f32, + ab_dot_product: f32 +) -> f32 { + (a_norm_sq + b_norm_sq - 2.0 * ab_dot_product).sqrt() +} + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let vector_database: &Vec> = &challenge.vector_database; + let query_vectors: &Vec> = &challenge.query_vectors; + let max_distance: f32 = challenge.max_distance; + + let mut indexes: Vec = Vec::with_capacity(query_vectors.len()); + let mut vector_norms_sq: Vec = Vec::with_capacity(vector_database.len()); + + let mut sum_norms_sq: f32 = 0.0; + let mut sum_squares: f32 = 0.0; + + for vector in vector_database { + let norm_sq: f32 = vector.iter().map(|&val| val * val).sum(); + sum_norms_sq += norm_sq.sqrt(); + sum_squares += norm_sq; + vector_norms_sq.push(norm_sq); + } + + let vector_norms_len: f32 = vector_norms_sq.len() as f32; + let std_dev: f32 = ((sum_squares / vector_norms_len) - (sum_norms_sq / vector_norms_len).powi(2)).sqrt(); + let norm_threshold: f32 = 2.0 * std_dev; + + for query in query_vectors { + let query_norm_sq: f32 = query.iter().map(|&val| val * val).sum(); + + let mut closest_index: Option = None; + let mut closest_distance: f32 = f32::MAX; + + for (idx, vector) in vector_database.iter().enumerate() { + let vector_norm_sq = vector_norms_sq[idx]; + if ((vector_norm_sq.sqrt() - query_norm_sq.sqrt()).abs()) > norm_threshold { + continue; + } + + let ab_dot_product: f32 = query.iter().zip(vector).map(|(&x1, &x2)| x1 * x2).sum(); + let distance: f32 = euclidean_distance_with_precomputed_norm( + query_norm_sq, + vector_norm_sq, + ab_dot_product, + ); + + if distance <= max_distance { + closest_index = Some(idx); + break; // Early exit + } else if distance < closest_distance { + closest_index = Some(idx); + closest_distance = distance; + } + } + + if let Some(index) = closest_index { + indexes.push(index); + } else { + return Ok(None); + } + } + + Ok(Some(Solution { indexes })) +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/mod.rs b/tig-algorithms/src/vector_search/mod.rs index ae3472a..4990304 100644 --- a/tig-algorithms/src/vector_search/mod.rs +++ b/tig-algorithms/src/vector_search/mod.rs @@ -24,7 +24,8 @@ // c004_a013 -// c004_a014 +pub mod brute_force_bacalhau; +pub use brute_force_bacalhau as c004_a014; // c004_a015 diff --git a/tig-algorithms/src/vector_search/template.rs b/tig-algorithms/src/vector_search/template.rs index eddf8a0..0f5fa1e 100644 --- a/tig-algorithms/src/vector_search/template.rs +++ b/tig-algorithms/src/vector_search/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vector_search::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vector_search/brute_force_bacalhau.wasm b/tig-algorithms/wasm/vector_search/brute_force_bacalhau.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9c34e1557e8df39838143d92a078300a6100dd96 GIT binary patch literal 125121 zcmeFa3!Gg?edl>z_kC~oZK)--)v{gpSb+RctSn#&;|!|a_#tFtCwLepGrKXL$?Q0{ z9paWX9TJSywq;|C0fm^vW_SqVAY)K4gUL9=K${qF5+I%#7|17{#EHGEhsiRY;AA|X zA!xt9f1PvgZFS2JCYjyO?z*je>eQ)I^{;>Z>;I~M)rsD4&CkVA6vYSQ^;e_^4jhON zT#?vsd>~RPvfSv3jAz`$Do|6Jn{Xw=9&)YQ1D-^@d!63B&gv@iDbWZeR7-YUc$glz z?mE@aZIr$*es7#+SvO7MIEs4{SsXXxIBvy>{g)eL@!9bbNVjkc!hEpXwNmX@PVW02N=?w|hL&+q%iB+7pFjX%3Tx+CuV(>Lzl_lrM$ z_0Rw88}~e?zH6pm)bF*{P0#($7hm#|ubX?#&R0zQd}Gf) z-~ZNlX6Gv|J^7r={@0VHHvjaNm(2XVSMC0>UpoJS3t#&am;e2L@P>c#Ge7ooGjI4O z2d@0t;rw;K_zQ1(^R;hz27jH|VTt7-R^Jw0umt?zL>>{7_ zW6K<^q*UyQf?nJ0$K#}cHbiQ65uP!UAtBqF|DgmZXymL9) z7DZG9aM{p*ihHU#mwUBYQ=4TI&jDfq*wasZ<6{EM-Zz-Ly#mBqK$6hHfXz=NaT0a> zv_L;SZRV}HLtl$7rvt4)n>N$kF7%`BL7Vd+L+vb*^>o5#T>|EM4$V zL~qP9AMf{v-}hiNJUfbBN%N6U=3VCBci<|Q3`4o&O2kx3+pW1{4}SCP*9{`q9<*H4 zi#h;2f~w;>bFX@9-nz-P-m((J?*E-2b~?#!7o7>(rrgvUK-$!2f$muOUv%S~=#KxM zITrn7FYdUSsDhtn9v4Jbb6^0fQ071-bZWCLENwOr$D`FU~y5Q@ZN+Ov`gZ|HuQ*G1NPP2=>(B0YLmqkPTpxXSOC zAJm`(YV1Q&RLD5h&hI4*?T#T0sj@uLB|32%<1L&e+NNsi?M5BuR({@)9{Za4Hcx2_b;#-leuC7_PK2`a;z9eZ=zpdJvE2toWb^Gv|2Q$mn(23aTE z5A&M9CogkM(Enz0v)*IalxM9c^rw2SruVovxb3al(Ep9SMK5OZ5V z*3>OgdahA~a8EZNSR=K_@1D==u6Y5Z7WwZ-1!5xqOh__WK-%*o@m1t!n5nt!xPH~4 zZ2?*J4eG2qM}Lf5ty!ypA0&o`212x(ThiuQqHx4Fw@zc$D|CL|Rlh{WW0Y>WY zU%?5ly=2=-h#ut}UCro7}3cJFKncc+}S2acJva%PZR|YJ4)(7~ubwRn!VM z4_ie~g^Jn&`dd`g0gA^$MQzs+(4nH!(}&}Mb?uILK+jH3pNR(yyH73^$@*7a+RMPz z0hb)T8Q5>U?EGHVWsY~;D zZ(`-3z8#>2oN49B@?qB%`A?D$nnN!Pt~XtqPmr|~I?;0z>O{lE^C15ZKl1yLkS|WN z3i;cFW#K;W8}@sl<(Z*S8q!TQw!^@)wq`Z>e*nfV^y}3qz&l9u2kz zfbE*|KrC3*nwvaA9}Zlz4;-|AV!>ya4=p(FWwQuBTxYcH=F z-63jn^ct5W2mF*x!)r}ZyzvJg`IA4JqGy|ZpPG*yyYnO8`NZR~WlUPelQ(|yPd3{9 zgzi88``xtb9J;y$H`)|DCWV@vUbI1PhkAL^+@RVVjJND&I9Db2K6sJG-EC2D) z)b3ikle(>To4Wbt;JSLMHN47cEGbLDR?`-`|6ZAnOU+3t7&{T-d9P&lHri|J~FhYB>TEk~UEH$v&jN z%_nJ@VDO?|>XI&6S*v;(nbuDkg80=mSK2BL4QK}{%kA-Iv1d)jq8|U7kC|nn`N-UB zs;XGUfx0Y1ebppRXcm4R^?!v94E>J)Shq1Y$uocNqpHwDX7i@LnH0`6-GRUZuTNh+ z>y!BaOlS=)d#trCid-g@DKh;>l7z}Yq<=hmHHeFS>tZ^mxf3vx_)`}JRlQ&zHmg=z zUREU87m5UgSw9&a|4~}c#yeRz9XqcVys2*81eRh*=*y~ch#3deOSLS^FDf8I&oOI{ zR#q5#6c@~C0khsbL5J1TXYX*)?27uCf~vplFudT0gG{V=q;f~|dw~Wfh@McGXz)Wb z%uF(yVcdUryg8JS0xqPqkARQIBfSfm%~vC>2V>T9z!Y?1_Vdc_m|^MLQa@H!!Gu~d z(2@a6r?EyDihYohOy=md_WsyYLpp{v8UyO!T0Kop+*6d4s9l*`x z%*zB>K*DfwD)o_%`uL4J;@ZsC97_+K8V#6#d!>m>_%tnb;2P+1kR*_ibdhL~X~a^8 zJ-LO8EVFZxY2|R8p>?z{)sy<^VgD4D(iOrZA@EVXNQVl^%frc!ba_C@NQ!2TEY zxOG6)%7Ecwrit+tu%K+&w1Lm$X=M|^m~*io^${T+4_c|g3fNI8#**DD{}8inW?2i~ z5bWmKg&9SxpVUxSKOwP8P@S1OlsXG8`S}AE_W*$W`2s$d+CW9ZKnWtsmk{H`F38mc zv*$wtS;!joEH$IcdZyRFLom9E3anp~VmBy`nG=i4oBpo5@KGsQKVK>jR-hncMA6ju z6us!dQve_AbGe`wcd11kQ8#v9)SJ*0#pVeG8W;8^Z7L$_%YNb%Y^a|NgLbT+jWznI znq8ynDhh)}M{KM$L{Y2zN2B?P&2dZrHTYMzRI63vnzd3DW$@@p;_tYAIqGkRK0iYO zdgD>kPcHb|i%#SCX!>k%bZ>1%+gVhRxGqC(v{Uw^EGy7BJHAcdJ4$^4T=5p`5wMtm?kpobe#Baq~?v z#7K|6@XfQAa;~4d`;p1(aByOQea)PkT&`xWOWxsyS;3i@?>I*@k5}|6$d-3b>=ccC zeV#7MDT-HU4d!nwpUPAsU9*IVha)f=K!RTBMQYPc&eVj7DHY3rPJxJ8P;N4VTbycz zhZY-mh^Ip zfAz?NG^g|o41X%3DRAmzwFpYZ*p#xe|N8i{=SE#UQ@<0BF5S~bl0m@o6Ze~pUa~#v z4pLPS<*^!Oq%dQm3$3YeC(b8HkpiTt>fCRSdaz2DZjZM3Y(Xgj{KPw$un4OPU|C`I z}L`6${d(W%{8^{aVIh|$lW{U&a7yU~`%PC;& z4OWR?V(G=hM#+Nl^kvIFpSE`XEi{GY$ehQsBYZFi1eQMtJbiqQEAKv?B-w%ZIuN)U zR#W1S?qgnpsqIk{%T7qm@=U0t!$Kv`xSjUj)TsR#Kdy#hLT+tV4+@&=A#Ew>lBw7{ z45qgfZ~nR7#2dPzu$mZP!hqv*qgl!E)063)Qds=`ls_kV8F{Ing?gvM)8@`(t4c+K z1{th9>{34J%{j?gN^OtaxzTx~8aqu(N`04?cL1prqb(pC*wjTxZHIp}07d#`Vb*lc zjSy*t|6OChd)}z-cYX+}dwDWWbw;K?1l9HTAat49Ms}uhJSWjTcT;N;-3x2!1<>3V zlcW}C4sKi98u^zPe~8NU!f4QBvM?EIzJ-1*frCV}$4hxLEEh&d)A~p&8NkUwm{ zVe$ftd@aeCO2VQ370ZR_BAPYKVsxA%G#RWeQ~|VhgJh`uu{MKLUAy)UtS1Xb*NPg@ z9ajiVF$FP{stK0Cg2^RWi66eifq#iH5ddbbOY=JIJeI``od3>vFEez@py{`xa%-_j zy#7p-pob#O-7V;;Fspc03OSkr&Mn|WbF--o(+wMfBA} zt#nEK#SbN@8-_02J1CZ_LCn{<0|iJ=W9xI7L2qqK?DRd{7d0H+vJvJN%n47jxJw5E z74h`^-)s8jg2W|r-PW6SNch6A+@Krk5f5=E1cQdr2I)z%8%m>72>$a09=;ZU>%$f_ zyL>z;sd*gO5+bf^)w?F``Y5O@s&vpa;R9sAf?mbKcpAkKk_MqkLD4q2j~A(w3!2j6 zKq_}Yt98dvGNXA84hN0nAjGrg>T5vQ0JSBWBkTpCnQ{{TID~;v{bU79wxbM?VjNf2 zW|(lzJu1+-G9hk5vVnOj*}!a(+JMXtW}@jxslV-AV$DsL&Lz^3(x4roxIzP|&~ek3D3?iwPy2h1patOas^kSzAt{s-m#)xQ znnVBFDviALX@Al`6ke6`!UFlEUX<#Bh@(JISxT*Bvu9+LSBQitM{|>!BK^kT_|qUF zMsR32?a80$338=^P@eNbPl-GCt&|u>BPUc){(n(XC@bHuL>9)UQrzN0@XZ5)36yVim-P6 zNM(6=T-94(iQ>glWSEGBB2%jrnHmp+B4bZYk)c#14(?h~WNiAHB0~yhB1NX=+EQfn zvMDllRVVu5*UBtlw|hwobR0pgr(-G(=|aUB;DZLQvd7^Xvd3$smeT>n;1*4Js`Kqe zn4&nb%;qox-mPB^0~NsL;v|)7u9t8@6%+TPQs2$es4S7u#KpK6Gc}mFPd`lQU}*Rn za}^_AEZP?Lb2A0^(vwu|x-bm?APPAq;~)J(@Wu?7l(cznw4T0wOc1Ef^+UW7lLN4Y zOU$b5Ujf7WzS?Y9rf-b%2bz_!A}LdvHo-Ct%JJVCpU44}AQUX6X_kX^ox$GI;sm(d z9`O`IEHIf*guaZJ%(>APJ(I^T5j#9t7pcdgk65Af8OfurY#A_?nkq5_ zKG-7wP!ud*h%v*9Co4h+NX%ff@vRIt#=00+%4n@qgCE+N_MZ~87Ftn`eZjO(0%n*t zFbnhmY&lL*fO)bU7w*eJ5mbP4(sAG`Fr)&Xiq*yEUE|)Usd3EP=0U3$9*Lsc~;m{iO2utPp_6Vp?*dwL{V>9gwdwMi*Hd)N)i_AYQ zQmIJJ3i&0ViKbmut@XUWzH@&)?iBuqhVkUCW~k2vXFu;5w;@N0sgHtY6b*oE&=$W) z^9>zZ^A`j;KzuWluZ6mtcAXox%0R@8sXOCJvm?V>_QpautK7TG>CG! z4r;2KR-5RmsPEBhAaUteG@Pl%;4Y_?*uQE4KWOGpU4YkS{-@xDM z7PKsK+slL^$zh5HChJBHdh_GzZX9o<3sM&P zf#Bf+0z)T5O78UO5X?4qpLq?GeG>fD$zoh*Z3IPwXd&2}?O z7#}E)l_F%d1|)>@$rBhzo$=yP6_Ff^(<&l)qNdPn`4D;R@>uVp9q};P?>j^PriJ<7 zR0OL22IF+hbQwPs%OrWgkCOOQxHR*&&EqWHUA2pKK{O{GBTaaD+bt$~4G1U@ARw_C z0y3vj;JD1>DPSU}=y%-T2&}v32QWSp^k!vtB!ilQj?v@|16URY$pYrIze$r_9KyB5 zBdi;;<5&-o8x>!>)}LG9nfM%y*7T_JdkNOGurE||*`?g7zhqQ+t|Grjc*Yfr4_mK= z&XQQ&*`&V#tBOYkIai@N<87`6L5 z?CijMw1VQ~H`crc%09lXg5pv*3;$Me7WIpgtUez{ab$|(xDXvoq1qs2UZ+r8DvFa5 z2*o8vamck1#qowwT(K$wu*QqVC=QjBdEL1+Hl*2fGIZgquegw{neWl+c|7kG4hN?+ zF8FtY$Q;U;d)w0Q{q=W0_T8J`eqd*cNjmp$Z#($yryscS>+j({>Ksc#2mmWK8IF`< zB|HG7SR`yKqX2mX;BBH1F3nt03eri=@1>jl9e-&tfAXm(mw>b^EXv>=)p8T&7GZX? zjPpBUBBBs37Pu%*X>v{R2H}+^7h(g}dZmeJpl~V)I9oxkGX$msN*dA-3KwQcomSpn zh)!UOkB3cUgxcKY(p}XMTn)h&#`P8g0bzYsu{ERUAV!H;E838=31GPRNU@D$<>2h$ zDdvL(icwHH#0$mBS*e2}m@T<|xn5~p2r3qK_=>qyw&1Z_d zs^f-%WNQA}g`90QFhg-V=&3m$uKnUVW7l-Zf5s+Vcr9b0B*%5C_`^am;cQ zf=rVP_Myp#Qg2g}%tLQmrr-VZDB~Z#7aRHCY2Hl^{Zl;fcif7m{?e$U#dc4#M)VZG z*HvL(3<*SxP1t|aPA70a$8RY}CoFUunlmxQD6o6p2o#LkHxE$bg94RnF!H9xCiaT2 zsd^R20agP*bJzbqkjPza5=2$v6Eh*go)Pu5>8K;3j^;3L3rJ@F>6n>)css$}daeIc z0EL%P>yC+kls+pgAQ1t-jLY4(E-$&A=|$o#fspSNA@d5QcLTX1JhdE2c)=>F6-R_8 zC#NS{gt6c^_gf2$3E2LTyz9SlPtDoyo>}vmbv4D&!5COE#;WhoclWB?hBZU5(58(l zJWqW!k4&U@(vrCAhC6y8MB&XhAYUA)r3q~l@X`oqY70YR*i_RlFw}qH7^-Ib3qum2 zyeC8wYz`!~7-GVs^$U_@JYs5%5YvG>vd08^XDWBVK^&07;D{SCvJSq!jV^bk#WZ}f zIOuo+;TXu&q-;maE2W^Wh>VREtBFi3U|ZquJ4>1lf)#8AL}I`w^hf*m81OTqXfS2v zT`W!02EZxftO0US{tpJhP3r_-_o8oxvv|TPpIW05k4Fy87!)Q!61J?u$6an31ajW5a2xwd>az-GaCn0IV z{2Tsb-?--+(H==TEff@1{)BjhoZ;Q8La-!FNKz|vLW0Jo6m^n#j$lc&S~nR89EuRr zFhOk?VigyK2lW|aOMKG&_*#a8Xfz{&X{skIQO@VxVlc#5jPqR3V=99Um0;9WJ$#jS znc~nAQmgo-S#it%?2cs9{nMpHt;P@D3-R|s)yNi_u#x zCB+(}0H9Uc`2t(a3o&@yQb1C0jPhFOS&V0R{~Fwibj>%zw&`Y^q*<-rXo@JoWT18! z5axrw8y&z~y6_#ZSSrBNeSB0>w{&xN2VhM!biA9JDIG(c!Oh(bBnXb=<^}+ZO9KCe zo12utc;X#^!8@2T$WmN8Fb3@yT?Owwf_J?dexC65o)g~gIJ`8eDO149^fh={DlXt9 zbpm)>T{%LI$M0ufD7<4)UnjtCXB^&QiWl(WNiukeU@hRaSk^W1+Yp;D8hGS*7_op^ zRa@1U$5n3K3&8!vbK<@=-WQf%#&C~}w7xJcioTFK0q!kk*81{Sf)U%Tq7VAQbhl^k zx`}5i7Prm0YtQb`GaO>$@4@rIL0~FYXw}NZb4`}X=R^Pzx1-5Imkj~Q?l4{8W`+WI zOo0MYYoeehyG>8lSay)aJK?k7`&j!uRM8W@CkyZd?SVD323h8~Jxv-RwgF|nhR})8 zrzF9#8YDTwU&>XOKTG;=gpk)LL(>}u%d}$M6~;FL0bJY3g-J4H*#|(H!KZz zkp!g@BTTRYhfXAS4_z>+XXbZRCWsA-pNaiTv|Jhcr#~VUSXze>Di}UG=Wd=$52C{2 zT%^iMp3!Eo(to3!Na;rvAmW6jC3y;>d;$!UXy&X2XaPp94R#~4gj8pEY=NM~PG_Ek zU3+w&O`k|u?UQ27XhOo45G9sNc%m3i9$vn4sq%WTtu>guy{Cs_XfacHYfn!BKxkQPY;?Gtd0F`A0UidN z1Hj=#T5E>|8&oaPW?R~7%S{S2+~4#DLV%O{HzV3gSo3st&?6Ib=9W8_aYtAmSTb0? zXE_>T1<$27b4bMcAGSws{fZv3Q>^L{iqdoSC~dZx^#SH5gOIdsPL*#$@^StMc**=9gQy9}f2I>orcs}!5{{kxe83YFDvi3iwAZ8vRL z-&gFW6WUQ5@{>%v?$K-I^<%E`xrP>!1L%khSmu<%a_(ADZXn>n= z4Fi?ACB+qU5aPw5h8vQ79+Z?7!5dT-oQC7CiC6JjmkXgQ#cLLkV;;g9e5aFl1qmC1 zVJA2OVeoCu8nkJ4_hNl9-3DklZRk5_+E!FGTa36Q7cg@%l`lBmbT`0G5d+Xr8COe8 zmoI0(SdN%PQ&Sqk(-wY4ui!0t%ec!_7_{b)#X6`>%tedNx&s+7W6j7nUoh*J4t7bU z`~5S=jx`n*T?#eeBJx@}@NfMnPLng4_h%3NZ68J6Kf{lFCNOLBbJ7__<}@t!vojI- z|AK>oH*9mu^9vyriizp?C0&#m@F51&v!g@@u6Oq}v zt)?mD+VvUinfcP13+|eiN0G!KJB@w3PmXD9)8R`&UJDc*z1gJ)#jx0fsURwvJ3ZYY z7O^M0&Px91cu9PpOk1IL*TT`859aAk3e&C_xxI>ccN0=nhZ0?@h}6^5eqNjFy$!>R zbaZWAyD4v6%j{~*O}*`=ooS!fnse@L=E!HdW^?_wQNi4Xw?Qa19$cGe6q}(K1|KQw z6DkoR>73*|mh@ZL$H8xREakBbF)0z+{qj@F;sL-|b_H|2Gj zp@ACk;XddALMk9r0m<9Ut<`YVZh}$;JFTFE+{7d|mki1{%OFA}*irxL0sbuXv8FW& zhAv^~h@lH(l@wzIM8$BaGv#n4oWNlthR>@SF_evc11V=74(3Vlh~c+!W`)P+Mw>A`sHQC@&T@y>(w(t48>zmSeWYCG`Jna+3SkC!_hTU zDhztj8CO8b5xJQXm*}yR!lKkq4XL)-*0B`zZP=fMgyg*VF;3ymwK&f0&AU(bS zFs&$nN6GNRV!Udo`x+T?q%?3r%cB;o?qV|>TCGh(KrU+`9z%AIV+m7TND2Xp}*v)KCSskQ@cJ$aAB>7E}=<+-(|J`XnX3 z=J61{jLS3c7U$go@2Kre6vJ7=$_QTILkV8sD=n*B;jpYLEUVmVcCSjk-mmV0d&cl&dGtKNE(ntZ;E(I)eU9v6_fV%%zN}$p;feOv704g+B0;T2;RoAmMTM7MK(Mo7|d~qkV+k8IVWQ6~Mj`c61&zadanuVHVHjjN+i()xVj>c4~ zhvLwKO*03G@K@;qwxQY_gFKx9nH0uk1Cy(TZFpIHMK!{;x?FC%P1s#a*p3CnqwSwK zCjJ!_s1cw>0u_lPP`g~7x+l~h31}F)l&)`mjE)0hgW@u-p1p<#Sep@q<25`+o@#h= zXgkDQLs8bKvCbxU5RjN9J?%qcT;PlTZv13|uojl=MC0YCE{Rm3MzYE=G#cMwSz*mi zGa``cf7qUEJ5ZAixGW#?)=2EM{4;aQ-y{bo@qOf8=a<5&IO@eAQj(=-`ddOuY&`QE zeS`rfgD+^d(vMROelDRW@Y*o86 z=Hj5@hf&FF1!yUjXw4k8XodaB@>N!bG@-q_t=OkCip?pqqC3O%gm{R8HNSdlu$$rQ|!f`_< zA=cA$w>ycj#(Y}3?G;)<$oylJ?FH*YVplP^0fOma-Zg;~7R7lV$GF+y!3OD~@^*2# zvwsL(SI&1Wn_!MM&B>eW(H%y$n+WEJNsF4BAlpbql?96=NK+#V)k(sf?>Hc&u*(mj zpP;atSAuZFpyBtVx1-`z3j5Ct)=37fW1D|a=uaXxGbecdN;m=`Cp!FvMUWIcVEPlU zE1dIV3nCze_YF6L)7`Y6CgQRhBZ%yP>(h1reNJO+u;-I*fj_8S0{|LFW{a>ZqN|(P zGK&gORfGJH4`uTFUdyf1vdn2*u*A0d{@@feV~x=|Hw9K1cw8GBKww{%V%M8%ql*?u z@hPge$skE7i)@38fBZdKfnlE&w-NoaTY7G*s_48j`s@k9slIo%|I4g!q1z<$+PA4t zSDFDk4q=v?uWFrY@N6FU3&9lqres6xdKTJD6h3&;7Vr zPcXNzcZ#`{*xW+BMyxX{2d9A2Ns{S)0+#5}q#~A*0ajSZWtsAGF=aWb$rY2g}7w>s**PpHf$4?aDWna?Lw z0hpv|oLs|8kTj9pZsCW)ta$2z@HY?WDXG4c(5p-a=9h8G=OKDn)KI`kGm4vgzUKmtt1n zNX06l@g9`hQS2Cjads#Qb6$pu0ZeCV?8ijUr*}Fns<>++M+QvwL6a^(J%XOGwocGm z09(G=aqt!89Xg_^%j&ei7aQ%(yY_6}1IWBH8>TRPk!rBlNZ^*rY>!e zwJxwT$(gIUAm|bM{(Vet6Fk!Ut|UY9=inEa@zV02SLxf!(rkM7cj-pTGSm=m#4PH7 z*OTu{P_l7u-eLh;ZnK1L3z9h1lo96~T{h|y|6m5PIsvQH+~OPrV@dw&YT=Bi=9#J!GW5eK16n6R^o4#0zUUs<|X;+CfK%6Nx=_yJx7>tZ1DiHta?y!AE|I6t6A06IFEWA&AGppD9vou!$>&f(~X?$}#Zf_usBd zH24u|R~Mo&Y~ftp!D(D}4F?KvAfRijTQ}IOxhN*$Hj6$KNWitwH390Vy2M(0{TbXb{^FGH?Y*=Pg*=Wcl`|qZ#i^$c`3eba2AE2Mrv{l$OiUj zo15rNch*h$89H6^j8W;g+&H&XzIx!s{EW+N>8k6__@yI0U4;C3AIh^ynYtOo8^os| zUfXOCjt_=8152^xz~DrE?a)g67I%i~g#RLNL^m`3AvVzU&t@pkbZ5@RH?L@(f)Ei{ zfzCDYaH~_0w?@qSThUbwCKB>HAPEj}vW4WlOYip(tE z=r$_*WU$A`G_K-SPWS;z0Dd+40o71I7qnp{v=j#&&vxt>(|}_ixxqczcq78)pDj{A zN`fu-CAgZFg(^Uv1`w_;h;&J z?3)_)wmTW`A#To{tV2?nT$IYR#ZC4a7FmOwC(PKF2t7voxG{51*P7Ul*|wxeaGP6A z_Y%0W1k!xI4J76k>dbP@yxta?fLFxL{#WGHMkWs|3WZct=(wqZPST{i6fo?XY?H1_ z9u?}{bd9&bi`E~C36>63F1Y+`8(JGIjkja-FbMVr8n|oBU&9+B8$}*#MCqW08j*0) zV&2oLLQWSDgc}!z&h9~l=CFFGh|gnY0k-s>R{HR0*;)irUN4g`u59_(yb!1IW93VE zvHaxRNUf94XyXto^T{xuq%Hxw$Y$%X=JgZh)Sn2jQj1_krUzJcuOXfQ!j+GT3>w=O z>4ODQQDMTG+#99NN`D);>3Mt#@%5^H#Ox`07)V)Y@wabN7CoRU?V%3Y_00K6snC%dC+2;5VFvV&Q&iofM zgDtRZT8gU8Sc@%di9`mLf$}GsbrTx&12>J2```>Wfnv|r2q?M@ft_&~we&>XHU|_I zrxk)TfRe88fEm293ghPB<|QSu0Up@MjhQnbm!f^O#_4^V!(?4evuDz>D18-`D~9p} zZ`hy|5&#%m?#v)ipm*LFqxaT{>74^zwuz>VEC=f6JTDImGUYzdG=@zs+7@XbC!q|K z&w?l{)RZG_^?>b?j7hpEru<^x+A|vn#%&beoPdwAeBBsUJKkPoD}va6{Tl0b8IC^HBD+xv>SV} z)m}P_mz-YWtg7mzvrK`)%}6LTk8Y_Rp$#B~Q#sfW^8PS4O?Otfxtkj}h-ktbb%$6X z5TZ~kI}Qc!^z;x1xal>H6>k8rUfV=>Om^zYCZKgqL18!ar32`p!dZJ^2UX+kMCt|) zqte6@o?$qw$tqtlt7zwViYdA@BWq(b23x+FkR0Ye#sX7^ z5L!C{@N{vDbw(2SSO2Ui+rMl6*fip9@H+cJ_d+SiMxR)ietP|NdPw1g9 zqVi%AeKZ+AQ$w)6G9I1g;2=KX@Zq=1v)A3zL$b9@=Ko%mAXi_ z1+fxcrOd-Gs?EZSv8^S52<;2YBMP$UyF2_jZZmB=l#Y$mj0X|?%?@WyKoC#u4VH;5 zJutx@3(T(5I#>~P%=Q~VF~5l#z<|^bae^#UY>%e;i!fa@5SfzpGEn+^pX-e-?K?K? zWXxNg-pHHsZ>awMuFhX|*g>sM1Rz_MpQrnz=xaZj%CoD#5ejsJ3pWlY0&sx~XYiE< z9z1hI=~Q3MwT!uD2La(@WB9H>?UBulyvtZn)Qz1iFj`1V(VMjCpR3j%Vckk<{Zi|W z`}Tn(Ei9T(1oxxX=+k-mNaf5BI}3^rfgZS<05&UUJUvLQeJ*O5iw;SsP8-OIhPjpv zlU&ts@EwOk0^rFgLJY99J3E!u?2r^xRVUNJJJB2As}vzRj31toAipIA2=s>&bVw9Y z&*g^ZF7(-#vQG#%1Dp=E^@$^F;cIPejcU;{HXg0hp3Ix3zc#=lzmam$@6>;-Z42RX zv6yc+LD?vtq}3B@lUt_T{1o#)-8l>DHLG2B?3{P#I^Wd$|kYkiA}K; zZdG7W1w|N8m1$uJWXghJCx{ew&QY%ee+^v`LrD=k%Q`Dc(VvF09GjC z>j(ZRv>?n>c3Y_XGDU0hbu|`Ie(H<+JO+Ie!qRjy`yY>ydk7D;Jz zg+O{(MRJ@0k;oFId(j`e{`-vuQM#@}om+90?=MLVGAL zCFtNDrYj#gB~AdUoeZSYdGWY1v{OzOHGWr6NU)v8pzN;S$C5nH2on$`1Gm1PgI)kdoRxq zx0fY~|4XVRWIh!|M3kknh|)wK7SSw%{SmoaG<{sn)+TOs38f$fB=o8SYW|;a z;`%FXvVwh0X8k<(iR&MT*(DBs*smqGVTiy?MiH37B}G-`{&4YNoK+(?nW%;5CNV)~Qu|wMy87ig*Q!JKKt!UKw%Pekd;}5~vDSAZe zvEodHw}1&*)Z4_lr7}V>3TTF~q{P^zTg6PX6xPq%2D}&@TWX*NINKn0oTRoN#sfT@ zfg}Ifg6NOcC^|KWAP?;U7mF!S;6!AAr(SOj88!g02!i6V(cYU zbVD4?%!DV~NOsNcVL*axET*cU=H)P36NW7VL#zbnT{;HdxFAX7{AR@98y3(aJzF}N zp-P7=>Ccd|x*nD_RFAC(T0KNuT0MkK%E(syySA?j2(2rvMlI@~P#a;bYC%5d)FHWV zY|t{?FT#p5+`(9?Dl{--_!3DHyv0w{VU$wrDo8BuoWu{Kv-&8p&KyB1FcBMnemJ_I zI{rWDpR0V%oi^r4I}ifIACCV=jls-m($M348+FA_Ho1IJWn)uZf{Tb-!o6Z;6X~T1 zM-sYNiaDrt!tT0@d<%>_bRM1&MNvK`;8%;y<8MqJU#6;}ddW(x!>6&}pR#IwR!9Lv zqV*Ra0MoFDViE$h$_nCT2SnaJDS-o>h;bGh`D>tjK<}b?{L`ik5HW7*ig>T6#R8rf z5ysocFk@oHs$G#X#R<%KHilLgvyc%PsWR{l9qP=x*uto?kH)BTSp+T`&-@ZUA(vl^7MoK(MQ$cLM%R0H{ zrEWN$i&wNmTGJ@JE3sP#u0Tqx0XJ!|^7B)?= zu#ZbDgLN->YZN5d2s}LZg6>CH6JbEK<2y!r4I+evwxMAuH5~&_2a0NFuD;LA zil}ob{*gc;fzO9?!o}ZnDJKwG9{&O=%h8Ej_d$dX5M+7E1%!- z#*e*A{ukUBKlJg&|2au&VOyJm&TNwnGTG!>p85yg4i=Hta178?w%=oxqA&<3&{!?|8O97f)=f$Hjk1h8kYpj9**2 zX)vK;JC0JM?3aklk*wjw=(@I9M}8@LWehtlJkdC%Y_$?{i3$eVa%-5`pBJh2OgMmCAK0jbZY9RNRSRqVI>)P-! zt)AR+c-hXyp>ST;WQIFYhKN5Y7qC__6vY(gY%P$Vi1W5WWI$NX6LOpa_7iHO%CbIM z{wBLHpf;Y%qf+y?si|;?j#dr)hDGVLnXd3ZG!<3h50(I79YhOKRph~h+~h47iZRia zd>CrLe)i4+80rvVYU*w1BfAX*xxej0Gzv4(vSGtE7LbX+2xpAeMO55_ODQ!3S=+K| z>Omqb6CCpOqp=Sn=~M#cVwtbV#Q-E1J#j^@6m4eHak*{Fojxv?4ZQ5V`Dgf3#3v z@=)v_`&F)ANc(#WJ{J2Azn7eC{x5YoocMRYpX zc;{;gR+eTZ7Ku*D<@pcT`Pf_>bV=)I-d4$+nVTE}rv0f1E*#jbDx)7D9GRiHt)5}= zYM*?Q3EoDU5KU|c=QLbXla~h?Ry;_U`c!7|q+?|h#nb_Zf+kcawJ@#Z7C`?nvsE0z zbVJwl_dyC49EWQw+$cB>kdNVSlxc-#h9}urig7#o;c5ozGTz>>P#3o2;@LIkNuN-h z^f~LMHX<>3=%Ef_^LME+7KPMv>9~YH8Op8!R9s-erUvwd&UqNVYL&($Q_*F9T$4&x z^3#&!bggp7^MEq4n|^#CtE=Hy0k~D4#SYe zD~-&29v@1_57;R%!$8PGYIo*33+F1>u-~E{LpN2A2Y@9}MP!fTg6B$ZIj!*W4UBiG zG(+V#mZy-@pHZ*r4=W-HebjL!=eH5x98pt078Ra{oISqT34s#1#qwjtz_T|KTHG#==R9RWa+o-ypAC;veU! z3<@T5(CqKHyVUH7QIKYzNM|>F{weL9Lb)d^sP3xY2AUix@PTl41j5lU&K-H`?T2pq ztvfz=XK@ng_YQsRk3RPAZus72EuF5Xgx?mp^kIV|s$6WVz8Tl~${N!QULxYYSL4?+ zNP^{6lAN45kevC#LNC#pDw2czM0F7R^ZEn}O)&$XlkSi`2GlCLTQRLZyd%jgGpw(f zhLWCal^{3=4HL8CT*hU$M~tu%1gnzwIlFK{bw{&My)hsbLFN>{IjK02*k1=tGrGy{ zm4A9?3zSFQFV_X0;g5ZA`vS{nEP~N}8G0L}p~wDP#2}xd*FQ z=fDmPx{NI)WL_M+(EY)BwU=t`aJ@7r&IpDR(-E#ZoJ6bPwj#a~tl~)kR<5b_It?WC zHxLDd&kB~Vb5C)lsIAK})D_1KcI8JQvTlkMS|Tad>!Ly}w?S*7O*es$-bZ2WQVX}7 z;}7zvkZR5wO1oo8J@`FxzNsd+;PnGt(vQ$w%>KGAv|n4I375a|CxB<8o5MUYAhQ{&5dnf&ZB3R z<$C0(uW${PL?v+n8fOIt)s%DsdQKEOVp?iIi>)?xqv(0VPS2U9+H>oriU{Tao3`ah z2bXRRQH_HL_+wCC11=Huwhk=JwisX5T~}d}@XY8r(Fi{DxmQCSu3oFgaK=ss7CHQ5 zRLg2Bzt_lntW9UD5L0BiRHzbGSh|yS#B4#|3F{}W$EiC|@6wXd5Ec!TQ;`yWWXOoY z7Rdzq#WaH(g9ELOOSSBK0lQp!NC@W%&0|#-VlAwpB+Vm8OW$HRLZ%A zEo-s`OsIlJWR7xz5Y9J1=Cf$*?bzFEmy?QOHad_D3o^pUI)B3r3C=V6!d6WYAJ?uC zAqtby|vXec%lbf6~e zIH!Z8YB~*NU}za)Kumhqo#t;XYS>z) zc$wV}(xHj#tn&Jw6cVQQM%sDQu&z^GXGIQ%4Zv0FusF+~es~3BR-~?|>fg9DWlvzp<%{siT<}xCH0M z11@**_UL8IDOF;hla+bG`=%1?VmB*Q_Wz;T_L4P31B(T0XSQz^d4QK)Adgc?p#~kT zM#f`HVshu`s`|Gj-&#_{3iWIKujWn~y3G<|R+wj2n5&9DS{Bv9r;0L(WfJ03t6q6R zufRReNl4ilrSMNSH0!RcQ%|Xjds&v}2n3NNYOaM;rtl{6#h2AzbE43bs=E{hnB%%D zjH(x$dsR?xxLx{};g+FANTYUSw)JcPo#_}c0^tp+T^*`#yN&&ShzT3p@TCwYuj~@#2#dA?Q1-a&W{Jh(4e@HQNX*>YSj0OnyxFNEtFg$_fJISpfyEjq z6Bbb(Dl97Wf$nxty_h@b9RBt%El2)r{}313uEt5R9F`Tv=W_9TX&&+*^1Fw9eTX@& z&!B}Fz1UI(Nl>AZ&)A2@Q!xaN?ib-%dM|{V`tL<=Kt<|A-2xe5*{i$n{T8wo30cS7F~MJ!`NWe5&Q&%s; zkzu0Vo};x2j*E4?nEujAePvrbny{&_oULaM^;@c^OU_M>)Kfl31J5Hm8jTPJ(U!
*TaGmJ+ow0?iEgG%-x)L@Q;lrNYRymsR1MK`TwU@--i%Jrayc zI5AF#C-gWM0FXEyz{q%{xNr8^oPc5R{96Qx?Ts>EaFVv0HLyo~F zXaq?(pP$4V=rML!>g6<9BD-v(8(J`>jK~uhm$hI=Gz$QZ5cG(SHI0j6+yGbw0ZKb6;+y8=4uPivc$*2h;ywK0{roXWe??=$~jk1?%~Av!ZnG7OF8Vgw7@Bx09HcuBDIdnR9|kzImqTXI=23ophiuJxE8 z@=_nYx;NFGSS7Y%e?XEc6w|rfoNonJBX%Luxsd3H(qN|1v2Xt}6ew*}nzP-A0uc%R z;OI#!-0-B}y6RA=FT%JB%$P1A3>BkoR3hajaERxmtV=~%tA=)b5jacRQTgQ2eX|ZWa@b%B;mns6VGMUvL|8m+=6{% z^fui_o^Ql8h7Lb^UJFS+=!ia~!!H!j1RDfC^Mh^j|M^&YHT7-`iH&YUNNivWzdZt^ zkkDZ`meX~UAu$O>SdO;!Q=_zkDU|w^!GfdbT|hV|OM z3&IoInxbc9i*+l`SlMizuvf??f^{e#ih{6$NDQ277M3pIJJbZz$q2)GxpXQWe8IbN zL!f;}y4V@ImN#~*WxmU8Eh#W}mH~tHL2wb)M+k;hO(QO_wc-rM59m{0p-E!~%#Kh6 z2F#8z1I9Md@`u;~OQtSh&4s#F*y zFIYXrtpROUGC@3z5o7-wDIW4GaR4gOEmx|EYZkSw6H*hKi#dk^YbZmr)EesK_8?Fc zRby8vn1>Z42&nWztSEsScC19u5JRoE3079P&Phl)TTC}$q`R_NJ*7F-Q9{5PIq;c6 z$%Hp8Qb=QI!9XZfNGhnzZy7+WG$=%QT!oDNVN^(Ymk+bIR!%YkPW-`#mLvaCe?J#l z=Kf*b5By)0_utq3%Q;FWJO>mZvhsNtUY_H&5;~!9b&J0wVf$tPgkBOnfFohNC!OnO z;t4HH6P?}IYBMIYAOT!SEB|CMM{-!RRx39+1EsyVc$&X*CkqK!f&PIziQts4{dd3p zOTqced9aM6#l0!^z}C?Tb<$VB8*>!j$qFMvK;n@ANHKNaTwq>O_2v3O58IYpG=HhS zN=s@>5Z*h&LIEEW3TtBe?3-4lddzG}p@~4#P5E@WtMucb#q^4pD3D-kCIn-uvA{ev3KbBjk`ccEun?FmSb4v6r2dez#xJ zaoopheDqMCpp6%1IeTH5HM=Fk=KZNx52o0R?ppg@ZJ#`w`a`h%f@C^`rl~`?E-_|g z+Gcxvlhzt)C&!rUd<+iyS{xupX`3=S%ghSmaghFk+|^+j99_9c{G5E3EjvX$9OPk5 zu!F+Sl<(0|EP8gG@@~uTQaDr`|_P~00RIf zGnHGhnIXUnGW++)(PAQC;_G|ZR1+*n9Rsb~KGvh0#nSD4XW-niOHU68&O z*lDXSpI&_$b9**0q2CD|$*!q*cNk+G6QXtb;!P)>;W&>_7MXagp zl0Y<5lcIJb6CiMeYF4i_CH+Ic3;#IVKh4EFf7;$yb=8*Av@U!BKtfsa(@vKYn>JwW zoOUGTuIRDWPbTQGdOjhius-QP|LFkU!lI8Hi?K!@o1u@?C%+;HQy+D-1F=W)@P)4G zmI;q4#5%bmD$zLf(Lbi+V@uhfgA*6>;%*oE2FPRyi`mj=_b`z)R)%S}W6!?+KbC0; zaiK$?t8@Mk3wt(cIz(_%A%Jtf={#NJD;b)aT|9|)LDC(^Us$tqEes+S8;v&=5RSt= z4w8>U>s}u#(PXy?rcZZ_A-dx+i-T3(A9`0GE#P;xdg;P|$^I zj5{L1h`uBT{j2247pFz3WcTx8Ue?MH(|D=Mpra{nc+{8XB1nLNHCHyVuG=Sr)p$VITK%?e*u)t1yK2X}T=lf*ViQyYL`9KCi|i#Zm$ ztuIW#OzWXhUX-F>?dohmt>oe^+D0`j>_*(tdDL3&>*%Ljg7!OF9?zt7`$s;G$d`g3 z9qBRM1Rr4%ZuF#X1)nMl!i9tBiCBq{ueR;+o%&9&I?sw569=HoHYQ`sKwDfx9vfCw zW*Z+3V`k_flua+_r_Et zy7Ogiz8n>TZ!$!kFQ1vLKal81_d~qW*&3B}Q}ir+F+!)`EM+c&o)0H}(ntOOg`3#B zD!nu-(Zb`Cr73@a!&-c#GiBa?auNTzJ$j!d6XpI3OQuS`*OJt|J-XA9)UrLg!;?--omh35c$dVIEzRQvXL2r-V zY018l@37>QlDAoMosw_2#ywQ@gO5R|}E&gQPTz@iW>Gk+B805(9XL*L=WnFwg=_I6oN9i=AKcjRO(!Z@V z7K+}zU+H>C|0|^%A^lrQOQWF7r3fu(4(X37y&nUMYs zrB4d!yOiD>(!Z{>)O+Cnu+jy3KXfvg@`XQ?$l0}=7>nj0L)<)MpwHz)9V#fZmOK_x z!V)wb<`fQLv75+$pf%J^GRWNDF-jg9C2y&cDz^l&`X#<~H{l=9iIu$}8(d+xX7?do zO_`B{xo+%R5unz(xsSuKNXC2M?iPPTcn{o$*3XU)E1>X|T;TJcBgCi`x`r$k+rmJU z!QAxyQmG9!ume<9h1?{93+COz=>x}-6B5yUZgg`kTyH9_2Ma!jl-FnPnqW?jA;V)( ztq*c6h4Hh%N{xH4iP-Mw5S~T>oqG24!~yoZ0_=B{uouH3*cB4dG1yyv9k8q06Xv;R zm;|Jj^rh7A>9r9G0;XL7<9V!M)k}t?4~K?6TsAa@BsCO})bd(;M8YYWt<@0|3|NAT z>v%tn7Z^ng<>()?#bDcLLN+oS5Om^^Y1zn>@PTgC6B=c4TqA>ZM-XR#H?2m6(1Pb4 z6)7oHh}2bAgov_tSg&~H#zrL|V?;3lu5u93bq>|F$4tNkP!xa~2n5Y-#xR_i#+wYaTn_=ml! zbNH#v4%2rvXeULY@+~~!o>?`E2gM(XZ5kuVwhVoX)9K3FlZSI}edqh`f9kOZZ)}A5 zddsS;@e)h$;JKT$+x+l@H`I2vGEpB`Fh3)~uZ1=88uD z>5{hbV7-@!8ptICcr`@M@HzQvXo6jqtj|&wjm-QsA%#BC9n*-Ph4T;zX+z1pTt(kD zrzWN3$Tw6H)p7I<6&cszYFvkF$#qyW7T1_G`i`rHNQ~$^Wy;dg=j1YDaEed^m#G<- z33K1zT+M9wZoyhGRLllaU4z+hrU5~s*azs(-evo-JLOBlY!Wo=ohI=@p^!FQ)^fz|9`lA%ociWM{QcXQ_s$7pSbrwFoJ};yLm0e?ma8Aub{|Dim zCM07>;|`Lz~|z zj)tiRKrTS@!8Iv@Eg0-LoEW?f6NtcBBWz|Z28%Ky`#$e#8Q+X<MhAnbItE|TNk+og9x<-Eh_<7qO64;jQ zmSE7=%URwB!EkX@f*jHzWJXwREe5$$c;HK%N(eCsnf|bzYW4MbjQ{f~gZ9M^eiQ>{2TqG6mK<9;Yts1pQNlX3C)J^b@o5&=oB06X zcz&7a5V)DtGY(TWRsb5s3YgWJk!%z*CC}VdLY7WpKH!?TTu=rfCzf=zT17|&>LAAO zxJP-6NO{kkRPx{Rk9j(w>!oPHI@2pP%0z+OKYVE{-Q!h!jMi;B2;?F!_zx8YsiykS{ZxC<*P2+(1aL8SvGK%Xd!mOKmA+qA?hW( zXi7|nv(XD^EKDYx484JA$srnt6r?HaY@}LSCN8m|)hqPEh6nX%-pyr2b7=3??VsTl zL`^d+=<_8q+q`1HwKl_Mh;rcL;}`If@L{NBIu`h7tiT6^CVV6;u7V9d7FFT{M;~9n z=}{jI8FDuZ;OA9os!CVXhCp5vlpxlBy$GwM;?`*Q4sH0yN;(%R{S#o(jLDx8qaowC{G;@U4|f^TszQO(7)&Fpn1u_3g4UF!BNKcL z`RM2b?uaIGn|4C@`ZAjoi*(U6X)OU;8HJ3PC`8@RCs#L7hHoq1B3ecqf;99~tRgK8 zPx;%+E=$>bOkzN8MJft6avioj^V#30g4{~};qFBc$ndHZN%2(^(Z%%DGnGkpGi&k1 z>mR<+WCb!d9nqH`l0~_X(ILZHKjepPAlXYU;b{72Dgc%Ub(!HJ)hlIU=CI~*bxZ1) zv@OuKQvciJ62m~9qX}5q3a5y#W`3&#DZRyevOEQtC^t(d=XC>?)&sbO5~*-MgqI)| z6D#d5f<3SXR;Vuqex=9K-WqmSrN^E~tA;6R>(z-kvo@z!(wkB>?Juu)K6*;rQ*6tj zv-lVcIW6c5-`2rk6u10s#C?{xBi1?2Qo|xc%O5hQ*i!h2I=9F{7;(6bP_mb^t5EU9 zt|j{GV5Q?fh%Z1>nRmG(PEM|G1-3Ei&7CAZ3sIU4$npVn&-mJDl$8IFea8=)QVrAA zT;GiMu?k^F9cwDqrZ@%2L;dy~l3-a{ku>(UyjX|$7LEC~0wgR=MYN}70G-(Eb!f6Y z9b2^SW#<`%hb@0Q><^cMIz?rCN2*RgL$&gKzieayJm^$h#-pc?2nL0+4^#;u< zJwlt(M+{eHvic3Bmzn6bkXASm48NdpFZOW|qG4yAh>UDdXhL|aATU%B{!^ez_{eRo zTJ#_-S@rS~8%`hykO?~^ELZe`D?;=l$VWZ5qR3~IU^F-}pC79KfL-=%90v!F&ow06 zQsov{xGiL?Mnj1LdV}AJV{|0O=-X<9R3uU%1nqPjA1NG|q*b#Oxu$`=Xy|^ell~DqCUx3CS0$E#Fl|3-i?Sst+a35pgMCZ*N7{CXj_>>o=8UakR|j> z6wIfD7@Kgjm$f^z5a~i}W6`K7Y?-(yJUsTMMmt1rm2WBf+V=MIam+BxykOe^DCr;j z&7uRm?e8sb^klib(UV7tn-bm;v<=fp0g36Wl24Vdjes8O41&_)bJRtm->Oc!IzSrY zivHlWv3_EeuGODW`IWumH34z>6GgAok)`pQL*1q&gE}`U#AbA)^en581hrW2 z;e=3Ssnf7fBQNbfK{gH4adCWRtL78&Vvtxd(KO*q;ZPhm!m$Z2;T6AJm<@1G+F2^^ zP!t&&xvI3K3xNui-~iUJc{YTj5~KG8Chxn7c(5p+RPk?tMe0kJ5E?8o&s z;l69vnthExJ~y`Z%T-L13Lm1Y+NCDS1D99pta}E0h`0#7P#thfy=bsjBQIDva8OK~ zlSFbh`G=yC3IakPVjs-2H)9}`L7HIkg=d2pCr(g5OUOAsn$3Y@mfEyvmnt{8kp)jG;y9ysDAui>t4h2s}Vc-lS zgGt~ForE^jNlNOlwMpK_8)8bdJ`^2m<_JpxECv?5n!}W@-=oy(Mo!Z3^lGI@~N#7=edGTu>b7OsUbyT1U@Y4srh&c34hO2j?N!DkY+3 z(4!wvD1l7mO5d6|AEgiXJC>z>Ho>-Vh6-I!^pc7Io;Z_BeEP3?yajbGdfj zTo?P2FfO@C#JxI_&xBbc>lqnEO)yiWS=vB|ZGkv*QvsIkA=7~!VPZ19Y}mk_=mJfL zd5;?SLGxdQuseC=M2&1Lv55#?#rqyBkfqrGAna2(#8G#l9`y=`Ci5!ux!7#x2O$U( zjXE?vHWq=9HQP;Vz>dJWlWR{5_i*W_u>mUit@e;)d!+-h{T#**QxvaxL@>!(6R)rv zga80ys+5Dsc;+2wWqb&8q5~<_1_f=pCQ30G!I}Kl1t+hVSpxxflq{n%VK&S^*aD~w z(^mbhhn2g`pU0sm7R0AvSj0QRHQi4PyYBbn8`!sxRC58Fl?Y*y8Hazz4z&JK!b&8E^xfmKX=saFdPWMx(;4 zt@$l#2(Nl*2pSzV#8?ZcEd@{$^vj@82bBn2gs#k|z#Q_2$_da25?zxPwn8&u*sWEG z8`!_u63$kC6*Gus$;6P0gJ*bEIL1dJ1y9Kk8!2JR<_XH3SxIMM9&=)h=HFArG~dF6 zA#%=+>(h@dM-->Qb$+!K4)|MAC0kj{V@eMqDupYY<&rtP#7li0O~&4#bEC_+xO13U z_WWWlfARhYDS1)I;FxE<7tVLy$^`rIQfYmmSA&C&P0U1bl4iAfquFYAy1j|X{?xkl z(;GH!n!$OaKe;l%-TXv48z-QN6&!O~=opMdTSthCxPla3q^r0*@(ICvrAu^#_I^sU zns>XG-8x|dxEq2d$V0mXBg#LAw5!bVjT50cTdurG;;R`A0*_b|AyX4UA`J; zSWIvL-0Ct~*s^eXY!z>h_DQN0p`O`#P@3UVO4FHL1PQmi5jx9Fvv$Ps*Amt1Y*xW!?B;vR+13E1Ww`_5Vs) zXOY#^l8BXhTv=PmYDi95)?0s_EcLOj_zKHBIvUw8u0`uiURNZXi0$t#Rdv2k65(x(_asGH2#YIRis)dya^J zR7F5+925jaMX^RA3@}I^83x2y7zB*H#hPe>iHb%vXiPL}Y_V%>kA+yGCMKF@utiOb zyubgw_MSNdsQCCiZ{F+rz8RR^R=wBV*IM_2+jQntM#KS`Y(^Xb4s7}GU{i%v-v@vr zT=v3UdbkYS)nV%2$@m%h_tr4gEWV`T3g~p^|8bPhUj^cpU%9aEANK)toy9{uiIhDS zF5(?B-mY2Nh1)3fQ6`@7_U{JMEv(-NG*Oxg7rx0&-p^g^BQ2WEoW`kH*i^#GH~vyD ziWKLwyE!~xe0#1I#TBHX=m<>ft8&Yi?6a_zjUyh(i1tQ>s5|nS1_T^I#ka(q1&wU* z6UM=eouUoe9_W1PURh4=U`>2~2?eL}%>YZdibyBL#9TYlWm=AYP@8d}kyx`=pdQmn z7!X8KYJpc&-q6TKNQ}nB^b33)FKwkXGF zW}F6iQq@w0*I~q!917L?ld#h!iZg-BZ8L2oQz}K4G_Dl;=8pa2P(7Fc6rWJc>@~}o zdlAhh3P0e3E!!?B^wMf|&NP${q@+NY?S(AWC&_50ibTzJ=Ugs@Dwo!Lx4b{lku7B9 zxer57;%$rz=TQMPgjQ_**R0lN?Giu)aNG9+BvNe?Ok{ssbJ{_lX0(h1&Y~z|T(UR= zAdGuFk$Rc%igKDZi4CB@(#i*T6g~HJkv|hJjm+g{3g3pI6n3N-e0YUN?&RT9{)mWd zq{ytUXt=yq6k@>K=4iA54X5p`mluSDhCaqZpr6Zxxzu^Ku50cRNvZlV$q~W%dZkLQ z<8^Kq>T4kYawo0)mJ`3iBnN&egfV^c$w!})5K*lkLMdWG5T{$e&XeeUO~zCi9gA9^ zQ92@Z%om-rG(8mC6^x7rCm^V;oU`=*!g*}~JzI3DVY;LBQ||69jHMF^<{fKm#Wo7k4)p-i>ZnJ_DZmeAWtiAya82z`?_ z%!BhSu=N2#o9G0(jKoiTz&z*X;!Y@-e8)F5x4^wSTOLg_BT6SDNajxGE6yg#MK&!b zBXnhyjj}*iaS}VYuNJa3XAu`<4xG>&r7$z$Zpu)Y-Ja@uYclfMSFsbkwWb`YCgiCK z_M&?NwW0Ip%OTGoR`ieh|I;{*PhJfIk&FROT+(VZb`gS}RJ`_cv) z!=YuYL%EL^oK+^?81jM*(bw_LIAa1^ptqOZpYG5|L{^@r9>6W_Qp0V&*`uAVVQObS z;cB#o@j?yrtYOx??7_r`um;m1luYmIm-tU?EfbEamTO}qV2lWX_4{JOsIjUd9{e00JTVR z$zt25EUs!$xSclV%DwEL{kC!ZHMr#rxMH!tsr z2Nl*tp9B}#!-(zZ9sUJ_m8<}CNRfnBxZ-SulV`IVBk0HzGkgjkp0*E1vd|pMV;75eI#Fuwt( zLQ!8x2p8@`GNN#(O{>mPW7Q_;cPnk0v}K!IGsD;%Qp>+mGqtDM8o3xJ>T zz8EIz;;+?C>u%gd@fe65loS627`iiXJhOS)HV|-N5p7GOH<8?<^~eq6it6??7`Epd z0My3pjB~Z9#8gZgiPFqGLkt~B8h7G`q-ss!O;D-8^azG9ys?^P#*ndniH_T7{Zfn6 zi3ronJ`(sTjkLUR5&v~!Ut2Os)#lhnH5L5X`Y9zQTuD*OKzg-7!WcLXL(Dn$tSzFj zA&u1gqWK8epKMYSH<}bs7B^w#0r8~(nBd0JO=*%AZqe;Rv+z<&TAJzMyqujbs4@GJ zs{ZdD?KlJiXCzE8;)F&v-hQ8Rp3t$2?u~9q5<5aWJ5m((8KyZmMy`Dp_o-o|{)E23 zBGo~|idXiZiJ+uKVuHS{dX$RBN32HF;t>L!vFR}Ee3PA{?e8i2%S%~Cp2;^cT!y6? zmP96fI@c!#{n?RwgEOHxK2qP!s?%VYVZ`tnk;}9OsTrl*bi-8;M4AK64a(t&v8bE` z<&3j&l~iU=na?!0P{ubY&AM+>G?)!&ZeC_pQNAY!3H-YpC?uGPh6>zvSp+b&=yW)| zt!Mc%^I}^SZLL-^tRJ=+o;q5rGTEaamYOP%P=NK)G2!WJ^MWuuD1va_*3p=m6lP59 z^TIS;C-C`eOn@4(o*YyW?rV?$1p>KAC-FpiLSbN=EZnRw8yHPTj|C7mZVo;awF+e= z<||PBi%xB9<2wgPVlP*pC4s^SNXUr|dy!9Basm&TbE|1J9y*2& zM(j%(Jt6ma3V8}pB83u$%R(tBy0jbeH6L2`if%#a@`P<@hG(R~z}MfUoB9JZaU~gZ z1UfCD#Bh_yy;S&sBw!`M*>zXYIa!5`y3-%>x}1eq+!GALI}iqdKWR8#%XHxZe3NoM zrrGQnoW!z$wAS1W50-Hwue&|+7w1ZDmSVylsYM!7a>v3=SnD~;o5KZVeKr^1HIoZU z!!+gonM$6*9aWJ_%7kjX;^%RVI8I%SEuP5N<%t|Z8EF9H8u9d&jUX}%{EfCuVu}TY zlL_F-N@J~u#(8o&Qj!k$X0W(~ zoRPJ>fQQT^X)B0XRMb`co)ya_b{eO#hr$Mc-a29qw}a94njy2<4zG+OxWQpx{%?j< zCqT1Y?>aPakL|ZwqW4MkKuc_u9&8{wO_tyXoF;1o*5+00OB~BgXCB9DN)@R3#At}# zA~OOO(2@S;{l$eUg#pqE51&=m1s0vq>cVpo{w!zujJ{z?q$Jl1ir<_t31-A8DO)%n zhC~+vqu;1l1dhlzBd*9&dl6|ecc!0*M}?&tg2F>y?tGYUTw?S-G)tiHaP(2laIO?$ zXUX;|0ES4DF98xXMP-NRF?O~_pg;|&!X7XI?$kjxth(kp-e$+C+1me7r6Q-f2fs9t zgzlFnV&R1k5Jc#j#b3UdrfB=+JYtBslYKqLAXWI~KR@^rv3ItbLM>vl|8t9=zZxn@ z`IHr=KC1UJ-g&(DN5>SYxJ8F)On1~WEKTYzA zP{r74L(5!7V^tn4cA4j8#pi{ld5x)!5!0ulZP3%&C^_fwRV6axRG!ruF_!Swf)JV` zN=eAkKei3Xe+gmnrNyUWI9U$Mq(Dg><+IH;xI8lQW~Km|)*n_TgG9nZ2Br$P-waPp z<}UEZ8lsccrDgiSUbr(EhKyrMLV_}7Ookff>I@>pksP29b8|ur-_Oz)W0-vlZ(&iUsx|8>C)=ibEmxL8vv62P)qg%VQIi)a(NEhVT_ zmYq%%6J>(9@Ui0xd4+C-D-e^=D^i!lV+x@tK41(3v3w?C$E-jq=P=g_Zy(L^`^f6z}c8}_V^NDUU@TyrblMu$!GK$E=4baefG5|VrLBN!Ax%FB} z7HnBremP#i%+6lxEJ<3G^0F;0-XiZSFxk!XzR5dzeXLIB^rFX%U)+dVc_U2h6iH}6 zAVx0PT*{i1H*FwzCkKq+qx3o9R^Y+<_0(DPQ=+>X+quUWmlQsr@)qzP>cBqUTU^a;KMg$2H`2Klieq{alQ1JSmoHli^)96GWHhbZhTH%+otgj7U!wl)z3Sz1d> zT-+NuVK3Ssi^!DA%ps*JFbf$Kk|dGVD9TWC&Hy27)-=LcZMFoS)3B|;B$knvl@1)~ z&Bo*IMTeG(`^wFtQ9u&GLO3`7D<|F@&}(jsO8o*rx6#k(HNqv*m3T8BL=gUp^5OOXCu@$mcjfdu13wfMup_v~G-b zI2#bA*y+1-9;z%PCJdN5a85fdO39~W56e62IuGl?nGZ;C(L(+%h8(3)F#3*Wn@DPU zuFCMO&Q>tPfhF(DTi{zB!`IE-L>18n2k7(L0GcXnrslksPNS;Gy4SF6rPFMx+Pu>> zl)<*H;!dNbZPsZb*=aDg>@<7YHgAwk0oQ7kvx}e;kbJIc?4i0mcc4rd2wHGWr*T^X9uCC>w(qSa9Jepk=Ca1NWm~X5_wEaQ2 z6E(T0)DSG8;JTsc;!omsURc`A_Aem#G^HPmR$*|@0^1uxChDnH>lj&HU#a|Y$zN9D zf$$v`pJ?nVP4ASCBMOycOC%CHO9AYORQ?JA_u1-(EDMg-JcLXdRCt<;wv3-v6;!%# za8EPEk&v1CMZdyX? z6;3UBk76s0Rcfc26EhFOmzl$wizUlby8M@^ul(0wQy-IFArplNHDEz ztq1l3@e6WdDm;0tlZ(r?xnqte7dOrnSi{;uL6tIvA@i{{smdl82yN9`V@fq5+1Ugd zMQ2UPC{7rGvlAn?f=N|vzza0>l8~|)bu>_t1X1o=o;gJ(;HIA(xOnLSa&~_hwy%8YDT5vw}aXoivy= zT#&gI&fnVGoq${{Gb=ImWSe9AYEP-|_WW@c|4lFtf3_`|6VbXF#C@Ps7 ziW_*j9m$AgiU&eqg1->qIJXMK)YJ`uO^{aEymqY!!eSNWwwEf+rWxMO%dPgqvRb-h z`;=^VK~k{LE_jIs?y$9G@4&Rwtw6`D*e(j`xOR*~Z6#`wjPu=%Gm1|P<6vMihyzy@ z&|h<-w7Hd3VzHKqv18AOi;gJW~3Z9Ldi9QCMZ6Y`i`A;YvtUp@?{Nq z3TGL2gf|QfNa2}dL%kzSfQ5?;K=lhEzlb%qdyM(=Y0#k>n^UJG)Jf?1Uj6O27rcMT6R+I%re5hBW%BgZWy~9IUi|(GfBnr}f7KVl#`EgK$9}!? z#dR;=_P$;z{s%0Dsh8XUZWZ|XAonU+cnTHC*K0abOH1&c)uNs50_BpeUt%PX;}U`k zTypSNwuz{Jj5(o7#*92|RX($tRWG?ScrHCDpI)6W8PnkmJ_D1J^4^&6jFa*utMjEw zK|EnPS8+z@tr7l%! zrLQjqjRaxdhjh5<&PPIvuta*04*8m@SgU<0_n`Szr`bx0lPqfQ~ zZmzBbm?|NH0EU1hY;-4iZ;@75@Q0LV9g>h44yT>(EgTL;!m;(@aN;(O!%^Q>IGh5> z6md8?ADq2Ku_9l{6P~8p84kslPH@ANgAa2qco75k)c`)UHUKWTjSZj=#SNgtYQSbJ z7Vx{d7S|jJHSyT&AAJ@ygp4sGB32_ILiS-Q#qm@PLoho2wPMkmBBV)xH2g0{8ghrW zGL9Rqx@(Ixyu`3lDTXvjCUi9s9pQ|8)i%NcSIdJwXY%qmvQL9{CWJXsKY^Hb7TK{} zaUmLILr-*0>2}Vt$o@QVOLSUo#hSt|Uc%M@>1hi9W73)O0=bp8x-jR1M@S8CD%84# zIH5Pl_vjsjb1wxso{MG4Ab#*l{EVVkxb8LgR14?}AG=ayCgWDH6+IzZiUg=%c;$^a zABz+VpDL+v3pF@O`hRipkFbN;jN8A;f|s2Eh!0`wC>lkw@oN7&i(51aDc8{6wzC&DXyYPgElj0wI(eK|Wdlj< z4!@>u*Yiaw)=^)}!@WG1L0PyT3)H4$ z;dYW0R`Cjkp!Qy;=ku+-224w?4^M3Rl~)+aT@jm#*_E!4>|b@HcHs(2pfGStQ%D>X zypdhEEVd(36x;ik=7Xg+>zq^`=J(hM)L8HrBU_kj4+;(tKJ;i1QbCR{F^g_RgRG~7 ziQ^T<-f?@_KRSrmu5F#e)+0AaLUH_IZ-{t2lE5aKU}SouU3SUB)lo|`+JO&qR)azq zDJjKofbhJ=MYCxXMX}_F)XwSF(lU5jChxUrS#Ug56}1g%;Cu}X2^Hc8h}xs>skhR$ z@cXzClb{suvId6I;WH=!u37>gtapvAMMDGx=pVomLqaq24lekb=o*A0uwZIruvUbP zEMV6~q83mgv84TTzLQ*TOzNs!wbL_j81?|2!kP#+ZvJ_;CSiK5~IZcS=LN3HAzN4q_^E_fwJi@rU!LKbEGxM38F+% z6n9#=E-ItM30+Yw7-!st>Vs{JWcOp_lC!!=rdDWKHN2l)Fc4h=ox{h_YDtK8ptTUl zA?8m=F4o74qeiVPYo}s(g?pB>CdKfV`j6v$-xi2yEe5!u&Ifdy-{pFQywGvF7J6|j zUxt>naN9?4UV3capSM)F4#GFhE9^Npo6ocXsaB9-EZb5pE@Wius*KR3^Z46qT(_oL zF9vf2dwEe5aw37bl9pOWexOSkhLKDNRsYGO~?|; zCbG6Iir{!8*AhZugqap92ul|^sQPU%K@;^h%0L-fC~DXQij>R6b0eM7jp+b{Gu9Rs zDZ$o07Fk%$DVvmvNfPL)YYb0k2G4>Ou#8O@!K=0y0sc6QVEoGEEG!ymVWS&jVT2oV zhY@Hj`6AS@p*DJ}*G9jHPilg`iYeIV2uH_^`QQlO^euYs zxRMg&VLSqy5j_K_L1fpYIFsdVB~Ms}=v8jhq;Y8*M+;3McNYV$rUX$*l7ps+T2d2A zD9|zgA=IiXirVCzOtM-CUj>L9iA<33Qv?@P(By3pootY45uIWsTM?Y-vdJtg53nPp zun&kU0n}_QW2`QM_1mRevDm_8Dpjo-`JjFf% zk>|mo6e|zuun4~*!x3`U7&KV;%+Y_)~mfvO#==O)^6_S!Z zBEJX>@My{sM(aW^)kfbnm8Z~`dB}~KiWor_E?v-yL8btV&^GFm8;H;c+wH+HJJLg1 zR6cCp)&Zw@4PC;3#=z+=l{H(2fUX7TrG0PLSrhI^DPCBdBSJuKwDM)9M3TBVX7l2IagYr z#T)^hWLYC*WX%KMa^k+|1vLd$6Ka-IA%(9DAC5jMjZ_*Cf_I-=v$P-^OanBvu;bin zh66>T70Pj3G)jyd=$nX!U1T4O2KqMYCM{^As5@HhhFwR4rIoZGN-&y=9IA*nID9cW z<#boa9ZV>rofLypPHn)66l(z|ZQ-~yHCO;*H-AD9nOoRP$>jRZ&DA+lh3!5U0EX)Z z`Y28z#_O9P6|=usDG-*}nLT9eJ#V!4$k`a1<7o>vlBze%-jLGDb&Dfy4B-SJvsL_& zm`t*7T+O%UvM!CH`Hp@dY~i{|(ob-Rl~T>>$J#!F%qOX&UaLw9_Lxd)3nerbS#dxq zp~ya`lG16Zqzt|`V-|YVsu=N6S63gNwxT{s@nGD{qSrKu900L~3O>ZjE6&^yZ+qI7 zuR5#Az6lYnVOB9d^td%@o75)=8TAW7LH+Wb`UTGhsojcE%>kRT!|nlj6nZEOj#N8Z zFFnI4S{8bfb(PX3oCGeePQ4ZkWW>{F0h`8BuA>o}5RtE)brY!!30^w9f})9@h3kdM ziePli1j9hah~bco5NFNFq!T^L5vOV-H*8EL+aqPhWi(sBGD-nyRmL{YD3RwJ=%&%o zXvm&zjy|=k;hYo?VZ|6@_8U@5#vdzyo195At}CG86M9{2K*h*}ftPXyo*W#88>BV; zPUCJ$p%JcSw}WP^4lm{t7zzeb^SEnBO&Dc~xz{4P05}Ir^ z#gL&PxdxM&7r-dsdBQ`j>QZa8H_7~PW+%x=f_w}h##FWD){rm8&Q>#)UX~Pc1gjclO;ZppWnJi5WEBv<%>0~V(5K%i) zZyCo{qyRY_p$owy>r>0wiwRwJJkz?ZgpTfM^kgPEJ%`XKTBHrSiO|_cr6*8NC=cj* z)yigy30-y)vk%xwp0ktSI%X%Sxt6!PTS6t5!d3_!+6z4l>WI({g~bE_R$vXYg;Sj= zDr$||CfrRLp;H@#?nQQ%BXr+H2|IlvQcp^Gkr`F1i?f-4cSP!d7Q7L5AZZFCVJSr%iM%HsH@%VPXQj09B3WLvBPRC?re z%oD^ZB+<-NiDkcdf+3k>7St?WAKM3!sbP9wv7Q}B?TqjpfzbxvB}5S>F@k!bw$#`d zlsZh$a}=Q+=2e92DdD=PDRi?D%ohn1&Ss-1Ya`5EX~1=vb_QtSy3DtTPTXhO7P#{t zG;zw&G_8h~*1{WeZDD+{FkaH>NW5vejWf%5hhN435>W#BO-u=!i#+@|P=eu|^TIGB zn~OZiO#^G4h&d8HGv;rUAPy8$LQv?#)8~jh0|y(5w-kAru#~7Xnsm(4bI?ahIMNh) zvDjOUQ7hCYA!J|_ur*PKTtRC{worhbLT$nhZ(A(vSgvGV1t;v1PS{a46%Qc1dNd`+lIYz)-3Fml?Lz+ zp`1%=?3KA*gNxWJK0s6hY8T3p29y()wV<5vFGe~0Op_v%V?2vdjy`OOawdYI;8>$- zK{=2nLb=a%`w8*<*y)FnD04+DxkZ&}{oC!ez+)eth^{Hj-((}k9rU<<%N^OybfZ&pbAictR8%rSux5Q}(@JH)l>;jhQN}kTP z;Kwp2RFkP1m;w$UVzg58Oe3&W8Rk4Bxj7H%h1`Qlxr*3I5F!K^_`FL^t6PeO6UHg*11Ti)sP2y{zj zOl+w~BwMs4obDhxt9K(k!sReTH)#{;5khn%fd{~ zZdouecEZKN2;i>e~|4A`$KoXB$z?#JI~*jB2hwTcTPUF}@|L zZKdVKND?tFVG#ox8yTNtmSX9UWDB#@X{$wMDRqiv`X-fum@!Zj+!^rif5Ix2l@{Bj z30j4)w#ggVv{8wvN*IqOtdS)WN^R{@__Mb)VJ!qwa67nFLQE>QOsPZaDp==)wN`UR z!kUr6IcCO;)lN;V6-uF`NhYi*ju|dJwoW6(x~GX12cxW;@sx9nlFeUQU{yyq#weSe z68OXhfLCaqL;{6F5n0b8|V8ug-)h zXf_9I5Xc&%yLqvH6>t*STDlB!Q|?QmX3NB~^C!8;CzMkdYusO&!&%f=a8?K`*fjA- z)5P(v(Q^J3hADS8$Vy{nzl)D1#qMJ39nU#MYo>CuQX>ssMT51D-KsG--gp$ZiS>;l zPHIKRtkV@uFC~7W=F1zlSw(avYQAeRmX6w_O38-YiX2D+46CxDsa@A|OAA7_^cR_j z>s50dn<*q#oG4qc!Cw$azeq7eKv_#J<$B~|8)kVQ*{VWQG#n`q<;4mFx`CLvxkaQd z6)6zqEke`TPmcJVRVZd&1~hDhqPa*Tg<^JYnqtdbq^YS%=?OprTTmED7GvnNLMlTb zg@W@pLf_3sx`fs}eT~c>QDwPFJ^%@bv?kM}t%_6lleVgXMv|tt(SBn_gPO#AG~**D z6S^I$L77y8wj4+Xw9X#&2(V(%Js?G+Nv5j6*=pSu4u?hV`OKYUt1MexV?3p z^22OI5|)X>VO#Kou4N;-FapoJnNms;37=G0=CN73(}4UD~`!gm|Qt79lgR zt>(%H&@N0WOvz@5*Q&XaNJ@jJW<*MZ71*jgo0CjRD|9KP!6M1Ux~SH*C@aegTV}jl zl!l;4X$=^+Vx^U5xtt_Ir3NDn1O%tFB7*2nkj=%+M%BX1wJlW(*K3M1hX~598Jl9r4pjk&x(upHxW=E|a5A7# zkDc8fvs=?H&37umv{hSW&8eUk08mqm4qriSwSwFDR<}|xm}g8?Fc#kvB68P8ZQT;# z@MnA;r)x#Hwqph|k!P!F>*r&j@I_n>W&xE511ZBW5C+!9Y}!@~l-U%coB$k|*e#&` zGPTuk%hcAdh+AKzwlbTzIc~*j>(+5=Yb`HEl1Ob87BR4~6(Us}JHiFHx2(rT#OMrlQ1kraVQTw}N>3_4<-xfCkH#TqylD`*JzY@xHV zD^7@RI;$FoNc2%(X)MJHqKt^aYV-QdP zm4!})b=C0+H5Y$?mi&pAXg=4Uhayg^q`W3{D@hamuR zSl1DBX66OzKD~1E;()`R6g9v;fDCHBt=(Zfvio>8YJ0KpuSp<>`D)!UsKXeHw@Ys6 zXfLmIDDens-BDq=@WC>9*`()t*}ZK8M<_mz!tNQALthu?98twSS-5}^kdw|GK?8z_ z*$pONULyNciJ!DsVJGN4Q{=#mIB23$@F(A-POCN&wbd&7*SI=%_7A z+;wJCB;|zWfz?I5(0&8>m*G+1&TK6zO3rN_bzyAivq+E^Ah2*$JFe}!dav(6^nI}B z?FI;N#=7q!FMSt&8ao0dG=>p{D^LUg31vFF((^j?H5kon3RS=wpZwqm^E*OhwJANf zpU(mYCPtmnljFeLLT)KKHf*uL(b|5Y5)&a&sGQr^hiCAlsj7B!rMRnr)ZBg^1@O`p zz*$Kup8_DLLbVF0umURP_VrYN&L2zi0fa?#MFXgS47Q7iQleg|JkKBQ+S$)bjqt3F z!Cu8~Y~8SQRinHr$};0s_C*%5pz=j-yNNoQqob1I#a%9OEoM2QYcU5Yr8vTg7GwA! z=4dhJ@^M=gwYb7s+@9i96e|zJS{R7XSJxQ;hTK?%E--s7zz~x~o_OAH`_9mcL2|M0 zHp0A!MwroY-?`ZLhy%q`D)w?!X)8h7!Hdwat^Nq99|Dt14Wv=o!XZ970+OiYL3}?p zIc*&qyN+lfXeT+qP?~eA=#?0(8Fg_RRJb;#ic>hkC$;zmY6XcEP(>9LGYch-xr8NF z_u3|ICje>z_>L~#AZHmruFJLj2ecc8U^a)9P&~a{r zT5%k>L?p@Lq*6V$F{#{J+n7|#VzDV0C0D&jSl&i!Jc}(k8;nzu;b{sqPQN0KNWb7n zV4POzTTm}C3+u2*6Rz*pOmj4t3PVYA!^0(Q8RaZPO|v3!_>{|sq9VkIL=dgdQ~}Pm zJj{>6rqE;g^FijFp-r_?93uXtaDKL6d_m zSQK?M!X^rF{MDispmEEDtS+_VVX%y_%-la!*QlqJncwV?<#pHN2UMDn)2bVA&35#1# zGioJ+YqfU#hYjJQzPKAJ;b(g{M2KC`N+C1FWRLNV)P%X zrvP{$OHLsr&`A1(qLdL}Av53kqU9t{>3~mLlB(R1l4!ZOzyVab#A%q9v8)ECKnYtH|AkFAcM+H+&E^1wx*tf^by^g zN`mLwqSWdlKNA;uXaI7H(I8U}cfK*opkDP!UB2Vs(_ zgksfOzJ$qb`O=XqmS~o`wGo92il+~qwDRT15&d}zx1}{jRwQg%6i;wALtme9%ue@{ zrwCQXusCH?)k>`)if@yrz|jLHYGk3E!dUAYPyQ(<0VnlD#M4gpb_$PuSGYWM$Uw8^2yUz)$%1wp6+CZ zSTOHTul2`Zv6+l@6$UDH7@_=a!GujWQ305#v!sB$g-}&35^mAvdkfEyz5LKbxV+?) zl9Sm8{kjO5l@LHyoCczZ0yQa0{-HeMX_*v^{-60}4-IV##;kaZ&HCrl%fk7btks9L z_6)GbRt(%V1D-Ae9NXZFbAZ$>If8AgKT!amvPGY= z@h1?EPvu3Q%HvPiL42wx`cx5r5&~mK6n(0UKWV8P`gYN$s`!(bptPD+=;Y#0?3d-8UUuakKsa^bu9Xxz$U-YSc{He@7)f9cIi9b0K=uq^jL;OiI)qbI4(Wj2_ zCwGEKr=m}t;!o~qlFmh+6kJ@GRpE{m=~DEmOZ-W^-!F76`qVZ4RBfNS6@BU!e`;r+ zx)*)w9)D_YpL!I1>Jfi(``ddKed-y1>R`Eg6@BUzf9hzT5JfH69NH(OyLc8#_W>2G ztwj8CicdwStumZ0*_HrIE^-_UvR z_+)y)`h6s$7^$1psGE^mB8XfDL@Y|50$2bRuePGRyR2mejG^T2ttDfBMe9yWV$_@LC z6oK%7cdg6-T)`ecg}77D3_HepOZ{AA#%P?+cF%9$%k~kV3xyRv))=-&ns3!U5koD;#_bSQ%;?s*5^{GlmWA-SXKA-+`=u#I?`YQtx)g z@WgC+&`9dpap&3^8sAx!i(SDtYukq`##&Tc$#B7B-H24hg~lV&pIiyPGa^;BIeOHe z2537XjpeTW+_eNK>LLQ zVJic_sn8??83&GQ;;`z1AL29Y27G*cky=9c@vrXla)SXqP&`tT<-pYO6$w?62)g<9 zjGdT@Lao9gG>}?8bYxf)b`d+VWRt)lQriG=y~rKlSmOlfetE@PZ{gwv0tQ{4lIsFa zoGE_&dad3P@wTM+?Ww1h=`|cb-TM0NW;b&gcY6vj<2d-D>}usatv zsf}bHYUb)ap9|gRn$U4kVu^DdzaORvWLJbD60qRoeClF_E=5WrDVFT(&7iW59oS$u z>v(%FPZA?GiIyld*W&(bZ7zZ=l85FDRAq9xMHe-^FHcr{b2uUqMh_fvp@q|!JXmaM z)PO=W>-Gkkm#uIQwwm6+B7 z1ImfQ?~VxOs|*}xdj^LVN(7inu(uLdec{m+_j=JpS7qU$)`y_LX1ddT@)6*26<0^` zI(;CpRWHWg)rb~#LIb6KiZA<7_~R<>2!9M<##dCeHZ7qxSacwRvZ)jVBsG-+Tt}r4 z4Zss~wTXfcziV%tJE@qFdFc+~ZHE7%k9iQ0i$BQOU9=Q2ny=`K%3uQ7qB45~lHjZv ziRh+0(V6*GOsPyk!CDnSwUlPUZ!?7(2-`e?RlvliOowHJo8d3ti+Nm75txa@{j1iRtb?dwh;YEdso>-?C8Okd zVO~Scww!qmaT6|}9!$Z&Zam&+y)SI8TrAxnlC7-;t$<}MDBdqI34jHKN5n*mh~Qq0 zETylvYuoUiHX8Z1@jV$4L$%xO+BSCRb?#l8;Gt`HX9hzh_=XaNGk=u#bo>%40!>FT zL1`dO@flYO^GQ?tC9d;V=eJWN&h7McnSWA#yVd#LilT|#(J!&a&u_2m`F!YOp+9@C z&hMb~JCI|01=#GfI=?-+_~=4^^1Orc?-1t0-m5K@;(z8{nv?Pru)PY{o=d`O*LfhV;?PF*gO76&OXZ7$MS_VG#^G)pOoKzwesZ^`cog&GU_Mvr(Uh*o|Jj0 zlH1j5o!&DW(ak8%1Qoe7L1e*bP#Zs5x87dB13WJ%{FVJmIW5@Kp-#4piw{~rz{_O5 z3Ngp)j#^gCG}L?^GvInB7SXW+9_&nn+-ttB6Nt~0kk+bMSk6tHPnl#JMMA6KOBO;| z$1Wv-VzOXh!Gg&qo+t55Ri3G3$*?1HT?KDn__(Aj3{i`mxv-O;i{)NaTY{`LDbGqc zm?#}5fOg60W8x6n{z@RV>9c?T>!ZKFcF!hnm2o)*mCb*Z43Xq7VbaM3$L1I+*pXF? znzOAHGb0T&JG#XnrD4apVX0d!Al|0U)#wr(bKgNmb+qzrzjar>FrU&(;7KSyI}J2f zRfhN|G%LuZm0BM5GVYd#YC~};odZeQog6l z%ovi|iON4u?TlWa2|ID9kuGyRqRiryw$w}_4h@@`tB?cyRDLe(6joryD<}tAfwY0- z1a=~Ir{fS2VYVP3k(y?Lx$iFA<}ICb1QLKtOIuYok~@I?h6Pb+Q-8Na1W)ra|8#F!+OiYa$5loABl5GJe=-zAE$u+|jgMz(h9N&f% zna}E7#<(KvqONpgp88aGYJq=BZEp>&5*^1#cd?N!Gu~AKViQfw*hhXlq^2=!2Dl~j z$t#PQgpIVBuNCoF!&uUb1R)JJYKlaU$VTyt6E5g`p+xpiC4jWp@aPI0@u$dY(YR46 z{1ph0kB&bOlP?XPtr=y}hGf@sMIUBHRoKBdc|KzqFeWHtqSnGHr(kJ~wH;($yB$gT zYJS2FAxNT7lbkIK+Dd@Y5-mdv3b)#e-mNe0vKIqn_o@czQ^7DUR9rJ6xSBH<<>WI< z0Ff6s3q-cX8s4J81jB5?Hc?ouCXK7WXf^?naH@8jaNpI{)xIrTE3AR*kiT!Ro(0@= zpSyJe0jN_H_}kBQD9=R-q0LN1Hlo(llLM!#m2sJ$W8;(T$gvfbIC#$R%PX_tcEtl* zn@0gpNO6`_Zboyc!j5Z%Cwx%p~TNr%uq3OorCFv{<2XhY&U;W z;gP%U^9oD>8ZFiq9EZZ7^41Q8(2ox;sr|oDj)(%JnitoN0j5Sim56=PTCc zNLt0yFrK>TV6tMGb(HEmnikZxqG2{g6*d5C6x|kT)OigVN8-+29<5-gIU`?;Doi3m zDQ5`W-9o-qMx!!P!i?PxF^qB4HVGO4f0_+o&C41cjoNO$zQHQze(_^UW`?_P1?{m` z#pN4EQNVU^-?12Rlps37$9Rr7iZv|GBW_`)7xvE6nAfp^yhe@W-4Nq5h9YFd?#&Hr zF|tuPBNvDS0xW7Ic_0v26rTiGiol>89++K*#NZK(%>f)8J4r5SpzT~w#}Rpw=aID) z!D#oZ$7$~4V(bj93$`Hhi9{k|RT4^OrXQD2JM@^3Le-X0C1F%{%vQKv;YT=Rd24esQxx=k_3nmS3L?94>}11Eh|%7 znTTM5szrMTbuA z^%GMZxR55ypS)IdHqg6>x3)5xWxZ0QH>_qRq(7`Ip1lkV)P;`1ZRB6edw4(=>#<^m_4XocIcKbJoxSMX`U0{fBb?YB+2#}^R&2JsFqDWEvpKdT&em7N#j*1R z+q4b>U57)vhz<8c530=zXV%9!W3(q06uH5aCniXbHWU@^iZPreu#SwbyW3TNNOEh$LvDAOqMkCtBfJqNAv0OZnZ~k{ zf~Mh1%gYLvXz@rBC0JPMd7v#cn_r7R!SNDdN}8IoVa-?+lJ>eal&o>^VZTY4$Jh2@ zdjP>dP)zZ$JLBb6VotrYGqu5(!ucy9(Q;E<@F+so-@YDYWk=x4uod2BFba>H=H@fu z0iT!T_9#gKyCYJAFr9`JKI4612MQZ9w{YFJxxbx1(t;=plBK#0O3^2(&XPP5C4m;3 zohp)|zh146{iuQEAtECKK{U}{JXNpc_U*sDLLex-ZbwjHyZq%HzmEr8@Aa~OE1k2j zslIVx-Tbh=v9Y0XR9LU)`kCR9g^l%fGiKLKpI;x&XqZ_)cy_~r`oSm6S$OP>!GmVh zH6GhAxUv4&Ig6VbPZ+$oamL^|3uo3Z8#JS_uBm?Upg9c#htC>1V&+aeO`l#jbeADR zhRzzixV~{_{nWXO8x{^6I%vqCp+k4Ee7qjixY)z#pYx9KJTIH}ymnmU=Rq0I+qvku zmgmttPsvZ2GG+RdSyL8HX`IqzwbxUOm*;;u|C9XRZnNj!(YvRqYkThd@P7yX>mFO# zqcclpG=+Q5uA4u@sxNh>^SQGGgZuL+k5@3>i7BZp4fsyAIuX*I`3v?o>B&#;_5)jF>)s*TM7WOmD0c z+?r-O*fk7Z&@j`$;O#>l6Dad~;Gp*Ycgv;=4Ku9trS&tS;@432M9RFF#;fc(3zyc- zpEEO5XX)sGuzuNb^)s3neb4)pv{j_Z201Uwbwa}gm-?`#StG|T1SLR)=+&5phWVSl z0)~y7y*jk+*!ayp^$hzQ>i$-JVc`h}HZ)CMa@=tZjnq7uR}<$fnA0@5e$kTpg){08 z0JP)lmexw&-A2-Aj9ItR zGG;q+KX}2yrol4?A4WqKM`%5%v0*OVUp%;J&and*G%RdrtUs>79+ouCnZMY8Dg3p8 z`WU$ENjYq)^E&cBe#S~Pd~Ir&3UHUyhqLPD%m<WR!`$ zYWOC+9?1W=zE0eODlN|)xbMvWajx2?1|Snx3?;{??F}N0%F=&!j(r7qK>PJggO9IU zykO^@26CALBbZ-bx43@r^d)oV&oqGP>%ft_4jDSMZuri1Lub|Py3_Ps1~)8e8VnsZ zo-p;e2KbJ_F_X-BOR2vyYR3?+G5rqZekcBm{%6!3S2tr$(+Oe2()z|(^Ba!m-F4)x z<{HyeE6z989X}Pkr-lbe)15TJl@#aYu#zSFaoXo;*j97ST43!;faXU05= zspHh&=ttjo;lJ_25O{|Tv%={OOBT*tJSuFOT`y7(=PVAx>3q_&OUN6~+d%nTypOqx z1Mjg%-C{6DEexm4ubmqmHvyee1y($Cd=V@T{L#uD@iZJhK*@;}tiiEy&;#IgZpCx<8Z z?OQe=Mh{p;y%8~EJb2-f1z^XJWuEs3%IrqoIs8|fx0>AX=UH|07uO>s>T60|p5=5- zQY3ypo#(DRH`YmfEN-f66lNe9!+0>JmOBzauqK6|KT66aLWY{;LgDp{$Mh<`QdQE zWYQ~aRMyKo9X)8!pl?n`_gEP2z6bis%&@O+`h~~O2Dt?l@zZJ3KFs8dt0YQu#^p-5 z3Vxlq3ZJH*fVhYT2Lf2w&;;qhAR8UQAfR^(Mc9i;w0O?UdggDGWid4|Xw$rrlq1Qw zH&@}n9Iny|7I76vj_XkgZ79)YZ^^%g^ctf${TBF#goyQnr%5YZP0;~Kge}r8fhR~? zI}V}JBtc_L*87xv(sG2GqL=u&jC)B#FYWsYKN+M-N`g$LG$>1DgUV#iuMXO!+E>^3 z9fD3lmx}JG9+_T#Z+~ubUhtE|&B0y4Q^7OAbLG#Mz7V_^yyU;0dLwu%`F8M5xH0*0 z@Xy4*{PG=k+w;JK&inrNe{kYi-?`+6x7=~s&FPZT;d|_P`1?;kldS1He8k~LEdTLM zKl$Y@ueba58RvdKSy5TtuHVpMqsES#u-}0P&8(mD^V_<0FUgc;YdQ_zb<}m&Z}?5= zi1RPFuB2?YJ!j21@51VasrS6|_is%9;L}Z$5B=`<1`Xb!?_pQ2x#~w(uf6WZJMMZQ zoh|RsW7J+_559Ko6Te#u*`^x#7{*0=A|cju9##_u zj+r*2e%8Fj%T8W?=G8y`$@+VrzUe0m8}9zjG5HfyiR8ev~1kiZsRkPD<+hbHUDE=S&ziN6GkK|GiB*rOUhOZ?^3c`qWj@~)lL;Fzc*`1 zw)ufGC(fu?H6+*ZydR&s?^VAzb=Q*q$tmeRW#h{FrrMvn{^o#o6BUW$lT*{>Nx!N* zIg-xx&EyYUacFrD+A=&-Nz&4i=Et`$Tb1_wL@Jd|2PNrDNojRi_iUH)t`)h;@~UJm z(XL(l(vE(oWM{uiqH9SvzkATDW0=@6F)%yGACepz?BuTvt`B~kydm??;9seK2cIQ2 zmEO4QgtN~1;gBPaJnQW9yT4jlwg1F_eL86HUQ@m~^>?ezIroAKufO?@J0E=L(Z_!K z<{vhBNkixnqjuY4!hYXebuJ%oz2nY@9((esH~-+Zp!;sZ_hV+(ue#uSS3dgWQx(?GXPkT9%|E~Gp8Fnp_{HjuosORJkB>gv)VyHP zZ(pnIwXmUkkEti0a??+i-+5QZPCa{#+xNhOg#V{3|Jj4jJ^#wacRy@gd|uO%OZp5N zy!I!z-ShBMFTVC&Z}g>?4LPsZv(G)b>A-`&adb(hD!0SncmBSxVZ8|OcN_I?*D;tmmITAy&1Cm|S z*+la?J`EmvK%)8T%2Xsh4)ib~yc_)Zo-^ zpjzkB=DYJv<;^d2U6D&Qzm|RP`-u^yE2h*m-=1lHJXO|tw?tWbWM*8ZJl&M-nfONX zsM6+@ox7KHES;EaJ~Ms8)#aU%L$6A%c&SfGc`DU>ZEnSfC4SgH&DV31&37ldC8{bu zmxV{HeCo`)raE!)d#Z9~rA~rf%UX?}r*JR%;Uzq8Wis?GNKNy;*?x`Ju2a~nJe=0+ z?*rEC*ndbkpyArr2L#tm8`$$-(*}8;g~PwUY1;6A`)>^Q%W6mTu6Sd_4V87f4(@!_ zu0y)ljeo!ARTD=KtDCfO?o|gL)KGiKm3Liri1$?8n(Vz)d#LyN8;9NWySgL( z`eyA>PrrTDQK9$FQ5*f`-|&v}O1yz683Dik`0S7lIlrDE3<5v7o!_(D(b-X@rGDq6 zUy8U%?U>j-)4#JHjvzxa!%&r!1wH&x%AU-Sq%7#_2f?n$rDOnV_&tLJZXupiB=Bp3 zjz}$1Q+CELNt6XW{oTl0PR_m*PpOF%NK+DItyDEhUQIoB4|b(}aV<1;~ndyE|nk|_SbfY}qA5rO3V=C+SF7;<6{WOgVx&%q4$%vR0r2Q%%*e7}fJ^3BY z>BAX6$d>vbtiL44`%4qapwv$%UIqxXwnQZcnRHp;59u{DIfUnw-?y}!$q*7GM)Hx2 ziBXv#xGcdr8zrhR5j-^7^M6(ACC>4ug*x#8_gCIq}`v)2S9f8e95z6JHQv1L2FHYg~4N}RziKKrG z<$J-T#JKFxPqrKnD}`bEQxgIuK=H5i6PXSMS>NyI zSC%AFzsjgHodi|JO6mOIFSISqwOeplMsMZ{9(=7&FsLc7)DJ#njx>c*zLll|U=FsaD&J#thow@w^V3xxRP6bCB@gDkHz??Y0-Q*u zGMS*HXYwNay~&+2ex=_r8~Nr9!VfjvY^D9*1S=~)#^Ri)jhU&6J+u0 z-OTn*$J&ki-T5DTm9W2OG|n+guWvsogEQ-oYpkDvvts6MSd8`IK64fyH^1(L*n=^% zen!IrQY>C9%gfwyjW|Q*@CF5S=A0R_$k0x|AiYc`?_=82gX`D!+?iu#C-d6vG|~*H z1@$tXC_9Y2F$IjWoxH+D*Q24oS|ROvkN=&Tzcr`9)g1eQq|M5b-?x5YePiAH1&fb8 zu)cog;(c1cv`-5-$I8RGcLZB|AfJ9@QR+Vo)GUSZb&F?*ragySxE&7d;QR?+8>+LZ zWO`%6@e2py3>`LTX9FyLi%8X9Mm^JL&*A(RxBuGpEts=#&bOvEEW?ta#u0;dvI-+4 z8Z6t`0Q)xe&Z52$fb7jRer7hwtcUoSEk@>Ph@X$(IV63n&%impMupSoG%c2a*0=A_ zB|GmF?!LPWUhPrPW4}26_2ieQAb!4)XIZTA^Y3_;4=;W`j=_@eCw{()XIV1w^R+z7 z=Mz6)#&f@-=aoDQuJQNNH%q^Y=Yd7v^;}%u**weZ5a+*Ov-Ia|<#}RJ`d{%~zPdR7 zJ3RL+ddA>2zh(SA#dC4`4@h6!|37S2UN_Rq?-l2--c<_vca4=5Ztb^KxD#!a1k)B$4-nmBptPAz#a*{r@R zdG1xzp0{`|9-r^RL{dfH^(@ObZp%Y_EXJ4Tc$N=QKjJ@h2UWx~AGYe*i~q~{ze)c| zu!#hlNU#Yf_a+i-BEcpSY$8Ei$7hr&4k`g7DVsv@lpsq>rJL}Z<+q&Qis-izS)$*n z=r`xP-)g_@Z#%#3Z+pM(Z;jvfw}b!1za5dbU-a7v3H(LBo&7KV?c#sE->&}G`|aj` zz2ENs*ZJ+?$M}%Ot<#N@Nq>%%`$IJzr`u*f-M{3g$Cv!XqtlM|$bXBbmMP_@Ec(fA_EY{P zKNVl|$3uZnCgpHQr%U+BL_cbie&Tk;*Ug%j{gURDe_8V?zvfTX*Zjet_>cauHuXRH zBMcN{i21_iEa1yX|Lc;s@a{kPBb@*L@=wfbck=c#Qnxi_$;EbFl?+GU+9s z;{Ke%^N^zF_jp$R|7APZ-^V{C(6&LS)*8I2rb^W`~9xy$3$#bQ%^YfQ|>zV<3cmK=phIhST z?}evz+xSV{s*SUMfBMfpuE`HSa^!^fAHH??Ie+`vnvWjJ&E4*tdn*sQ_S;8{|H)n7 zeecDtryumOT)$arvdyA6L9E z`tDoH>+fH&bi(ZWx9|R)MZewovh$Dov;VyvuR85}t2*v|-zoiGzxMsw?{zr*t?&ON z(Qd>pL#uxM;_5j&-gU-?OK*9$`zPmrW3Lxq8&P}4CC`l;_hDtHe=Z*X#MnPheQV7P zdq4ZkHyhi1^R$X@?Y_sXYlf|R_U8vZy0XX3-+%F<8JG4v_?z2}xv>1ID>~h`{|T?2 z`r0WkoqW*uD$8#DeY@G~j-LL^Wi$I<@`qzO<-%QiPTu&t6>DGr`8)d@d`r90KONZl z`aTCedB_159yxdIxz}BO{}JsASD&)}wi74q@Z$|9zx3hL@AVuuss7%nk6wGoo$VjL z>4DMrcRv5xKVLraxxZ9CTDALo!@v7v`{uIhU)?zQgqsUprcJv0AM1M^n`sKpdS&2! z$L{;nYd<^Y8(j`r({GoC^}UCk_JjNX^4b35YhLd1&iU^h)#Lo{FQ0uz{R_M1ruJKZ zYmZ<3YvQFlt-bE~?f%i_mOGC6=FPiYvd6lkd);yTpB|{`JN=E5cR1#TOAntg@n08y z`^sF!m9rn+@aPTiZLIv($G2T`ao0UN-7)H0SKW2=HyQ^G{LZR#@h4 zv1H~0M_zHn@jLXHU;XS~ui5bO=?fNo@X+?}W%qc!+soViWn9OSyU+a9$ew+F^zVrs zC;a@dXRGIbw(yE+`%gY-=!?~rXa4=?2erTS=4r={UETTAv$|h%)jc0KR;}Bi>o4Yx zxY2*1W6uLGpLT!a;lFre+F=8FKeqO5ZKHTNWJKHb#{iq%{+~8*uo!9l> zal_!HXC5@Y)8S_fYY1My>(0a4zyH9(@}IRko(p zjxPJJm*3m}JGq8`z1u1M$ZLzd=dU~I!-lGtj{J0o_Aivr`qR#LuKnko6L0Bv?zC%r zR=>FF+xd8&H!%K$!=#BGUeP%`ZGea-=baL0R^Z)U7*TrwH9k$2X5BB+Y zRfq2%{_*7B_3bhFo0lH)&AFHUCO7?vbAFL{x$ZB6-kWrB?w$|3zCHHpho2gM;9W-^ z{nD$~)?d&eoVRlRZ~kl1j2nOVMyFmq3)R1_{=toXPwW1J@yW#dGiQ9STlVG7cTb+T z{txLZcR8=epE`8>VTXMmuR7!XKhN9mg2p$)8>&a#wP@MImv7%^+>g7hTHufEK6S=@ z553Ug;Y8}N35mPEdu7KifBN;jvA-QTe8ll3b3VJNQ@i^L9moA}#(jq$d1Pbdr8mFV z{mEl*cxFX+|Fsj^f4HdKxqUlb^Vh=)Pj(!(yzZ6r@2lK!TDyh&9=^-Ty~Fz8;kRbL zdG^&kw%>KxM?-ov|Jk4ai?GLwKku~ap94O5fBofm4%@EofKKZ^Znx`4`}UoD;FPI{ zj-1`$y>;K{IcUIt{bEV>=lrJn zw(cMO^rM$+?t1V{|MZ%7Zfv@6{ex3J`AKd->2GIW`?r%O9QxVG?la5~6)Zy@>dpvvXesAn~L3r@LJ3RQ0SNA!oQ)RCocHK1kfVGe9GvdOVpMJF6 zevi-jXy%hQWvdq+Iq9HVt9tIX`#TR#+HltHYu`O#@m}YbcYgHIcSpQ*Lg{0xKX~~5 zoks7`{^c=$y5}GFUw3`O9p&G@ZPp__fB(HZp4joJ$+dkhKJJtOhpg#*>V%7rJEQUX zuD8G1r+#D8PupL)#KkG`>FlT8#kot zyy1(weyjbRYgcst=^fY1dw;`*V|M5;`1*_g`rY66oqq5c=iT`F{(sGNTJrbTKN?wi z<=d4LfAZaR?Yo`O|ER9t8MtBnOzKH;2K?)&X+E5;sLebSxT zmrl9lybc##ap~DlW$x&9>EIV?HdSAH{L=NWz0kN|P{$L_{_WnI)*k%H1@GUo?89H) zU48D2|9-|m}r*`<+&wdVZuKeD)_aEi2JL-?G zp8vp(?S7Qlb!w;NKb~B7|BpWH`dqsnE3X(haowC_8h`n{?vIV%drYS*et+f*XB^u3 z!+-y-W2R)z_u4&_y5W~qho1A!;Cb(sZ)ke32k}=wYh3!`-Dl_TJ8oS6XF4VCI`Hwn z54PL%!hy%0Gw;rd_G>;ld0EqL2S48Xlnp1gn|e~u1L{8eq~D_zrMC^e@zcWc8J){k zJbqE_b1$xWaEIQH4SV9$_QyZD?9iuvanG?0UdZPZkY)^_q*G=ylX_XFhen z5ubE^_=8`beDJF;EdSQ~SM9aux_`FMzBBmIw+G$5=~uU3vD3d#_^8(lz29FG-ZOO8 z@PBr^|KAT^*kxIdDP6lhvBT}h{Obn)@%c+?roDO6i8qx_{!Zsd?>hE}8^4*me9c7< zt{?N4o*y(_wAb9X26uhqk4uv~1ZVtVO8-}PI=$EXHE;c8$17Jqx#qU@@2u~;X8HZO z4X?dc*RY>|)UEp+*tGK#Pj|g>;ve7I{`p_O-tUKdo`2p8i#xpg!ojB{h{m5TXoKoZp18kZSqcce0tQYXOz#q`lAlbW8Uh#ci%^5 z+_c}qZrEXDMMb((^?qGD?cKBcUutjs>wv5GnAfgp;fd|8IHlv^2Ojau zjom*x{pBe~ep>kbXVu&7xBc{6FS?{KweFm0!&8rU|I>3l-tPC~8;)K6)*c6Z{B)+% zw_oiP{-4_31RSb1{2w1{iZnB3?8_v|zVA`SmUTwfRAQL1WEl)uii(O-sFaF|R*6bz z(V}P}X-}nvWUC}CDwX{2=Zrz``+k4_>-v4K-(1&c&Uwyx_U*o(`<&-{muoWpe6jiJHc$!?lL9F`0(#<)t!9h zuZVtMn8{CzvgYQ0$}}~R&{uptlyTF+IqLU|#lknX+RTxb)f5okAML9rb#mYRqlF$4 zd9UtUHm9n}s!Hq2AKCU!(YpJnp5-3}$Fs?c->fUWAdOqN;YyRf%fst8Po(elY?5fH zKGe+R(ycanIezCf&TcpVC?nxAjUvv!W!~%Ifgd)EI?@bFku~!Q ztysM`7yT@h-Hq?knvP@sXpBBAmXkM}mm~Xb)}h^RhaYC2Js4}vXq!jAAn*RVHvgFA zaQfHXvgA>`CZnqGT5ZD3ocojp{|owZ8)elcm+!dtKvcXptTN~-xnyN6AuI9TbM`!? z%sJ_ygu-q9^44{0w0eSf&6|Jkshm?nmy_Xb!%GX#IIu>=7DztwcaP3(&AoMWnb5-9 zF%JlyO;vAPd}ZvOjYQKbdNbsYS$s>Mn!G^Zb%E;PP5pNzYkyq}dlVRXcTMuTchQ6M zi5kOG#50Xjy7vq{#Jk8EDk$fhi$><@EbP>S;}*tJdamBQW9J@2J99XxjUz{-OslF}*YRd_1A9#NDSF+fq-U*uUZ7O|okrf4CY^Y6%^l&8RmrZiZajS%zf0niC|}pK z813AOh=X%>dM#F3+=_EQ;HM&X`D2Yz5}qtWw%_w?ElDBf&*$%_ho9j|iOW}#3T>sX z{8@5CO=snF{Ab&3AFc^~zI{t;ns}<_Ntya0@yqv2mrP?{Dv7s z#gToC_lRi;@dzpZxM;yC=J~t<+2?+T&WW#|HG4timoq<)WRsmvoZ0^LUbn#Zhr#2~lmEAW|ZdUqdKqO!8yWiu}>L)7;E^XhiOLYofQfp=6P_1fCdBKZ)5{i*W z2ol#9DQiwmO{vrVbFL}bN`A%i3fjlw5yoxb=D?Et0?F$Gmqh(!|MX6knA+?1l1Q|u z{B6I)KlsRtb>dG?{(2|hxJ57Gq_h0`Q}^acdUU*J8ay0v`8T$#$D)RyR?SGfMAK#hEq}-zdYNkTGindk-gU~dEXaeyvMg7 z_WPF1BzxUeY6TvO{-w*(>y$+w-dJAOw1H?N-F4n2?6#ccjQv@o77xV3B%;)lty8oD zrFXUM+M3ZNrlc}6->}iA%0Z%2Lfk~*iS!vaTlM0kT(cL|FAi;WZkO0}GNLqO&g#X1 zGZ`i6Wz9+l>OP)-zutiCR_#_@c!4cL+r62w@qSL3RoloZzUD*2x};T^ z$MJ4_1P{-q;C73pJ?Do-%VlFj+u~=G$jj)SQvCIk?L)rm;i&O!&8Z62D=xaaUkC)F zh11J&jC#paTZpIA-qYo-O^+Rz=dW};YT67ngN@I~dk<~NW=Y9h^c4}Ks69#{9_jz`vZ4ql?GnKrRHY3HTBJ5 z$@f#cXzM%`-e?Eb`5U)-<{xFc#|f;ElF#m8Rca(1ThV{!NY34B#GiIg*fnLdXMB># zev*26iek|6_Bst^kNIfKqi=Pae7&l}w-W)jZRZ>z;SUuUznznf8Q zctvsUp{beQN|yVro1x74R=-<%=b0&yKY9l$OrBK_Z8bh2;o~dT@nwFRlgd!7a)xrM zl6`rr>$g$nqqliG`ojA3WbSWFvidQaVLQOF``}5LLy9liUU1`f(cMqE0-t8MDd9c% z&#N9c*A*xm|WW>TydnM!}G|@7ZH!Q9weV+wzysr7INCO|INpjN9lx0i_VnBX(FYz zqh>w2M^4Lq)#>|Xe!$!_e000wmvw5A{-67Ge^uX1$Q+$(x zB8w_y%Y2(tcz)Thj_`}|?gUGz)|6!%I)A3=_GSkATgUGt`fMF(z4yJKJNB7bP|Jo( z3dh@SB~9O+yzXj>cAwRQTT(t`wsx!yL>6;C&R;sypP@Dy`Qq(~MN#gzcc)4m+7ULLbt3Rs*A_?Fl%={#8FnA6 z&R(vn&4?{N!8r9<+GOu0eJz7;Iwn_pF8BHkNenU)(G{%Z(|__rt&3apr1&%`)4e z>D_Cte^8TkezlCxcUiabwL=P_g4rKPk1M75%G1tX3s*?QMuY6{4VRC_L$qZe+ zvsFi%WY;buJ2A@7x;_g(2}w8fyYp1Of4JayFto$3L0(&E z$ysX!t?Z_UHA-$L_@z_?R|T}B`mi6((CmrH4kHRj-Pq;WG?@0`G{K5Sj91uyXA1`{8HA19r)d3U_c%bFNp|trbyMOuwfcCH2%$ zV*Y*Ky?Y)~r0*P2QAu|AR?0eB?o3|cCXsr`qBzIuQat-`N@A8zry|pRN6$QcrvK)% zzpe`tYoyN}DSO|%<&1NT*3SD8;@2d650w!mBvlBq3kLg$QpHN`ZwJI**k!blBENXr zw7bfznOXOA)n5Ki+ga+iVXLi3hHDK{MpDCQCi0+08|dTD-Eq&`(PZs>>Be~%^@V*h zH$;yfNql4|qW|C~JG1RPDPn5B;*AyR#aqN5);&0pk8gio&OGRAgL9HPH>!}9DJ%14 z*1MW^zLsKQ;OTZ4AB zoJYdXnk2?a+(l}}bIuls4P7z1Fgq_|a!u=NPblnpv9Q7r3%4$)-QTx%UASIv}68w#w z_pNXQFPn$Z^#l2`;HMCs3t4v;>4B%?m?KZ|+C@nm^#bNFnmrUDkE7l~4&gN&rpXNi z9JLK|6+d0?YXlzOVXj76Xw+4BLjDtTS#1sMS_D2feuHpRtT)#rDV!wZsJk(z`^V1K zE?Mw<3^}){ZF}q{e0LC!S)Ji;%{S-QH~F>6JU69!@3UmvB(#44`{n2Ht7&OukNZDw zBw%p%YXxcSwskMxx<6g9&yal{{j&7d5|wm&=kWZOt(y6y=VyohnEy48p1<8(Z09tb3j&39NehuJ9gu zJ^>c~+)T}Mx0`Vc8?OIOPWYKlO7h)dBIbWh+dToMT)408IVsG4URF@LVr7Y>#=|>A z6uesFmvV@LbJ5q#bnJOHW9a;~?Aq`BycOdC@N;BdhT$zxtaci3Wz7TAD~%nXgG7%=^-ccK)gB zxBe}&{H=_vk;=lmGZja_Z!fiS@6>n4Q=?RE%ewyzog=z+EuP0DZEGBONZOxf80s6i z_mf+y%$byTB5e-KaMY-Cg3%=}lumEnX7AW>uU6&zCN}rU{&h|%bPD-YjvdRTMfi_ zB+6|r6*b?nIsB)*`Hgn9OB!2!osQbR{JvJ6*mzco6Pb`uA-6UEQNn?PlC_6l=H7hp zU{_Ghc_)c%SNZ(RcjqrQWbF4Rki`32PZE;VS$)T=nNw=Wy9q~c%Sk@rq;ldy!d_!D`((&s7V^lvLek+0h$TjxPH( zxbWj|S)&t|LZyU^Dgxy8nrtV(!e5oCp!y51-DMhNC-`FznQRa!z3uCQlCi zmdGku(x3mht-`(#>D@qhorIqwI*VwE8*qMiQ^Uwdrhqiqa=g`U&9RqV$;MZYw_lVw z6R0VyIk<6}ZsgTP&7zX{F0(T?jU%;MEN*_%xtt?MQn-20+%_|D%~bOfwNY`(GF|7y zL`dN~I$Uo0sZX5|p_KDw%KCDKe`K!w)AOvi2PEPKosLM=#xJOvMN_Lgm@SnNqn@=b{Yj~R+r2lY6muRZ*2PtidyGBgu3nMorUecCU^!fjg60k{qr9N_g zQGQU>)1ZWJ3N}vSnilio&lV}f-1MJXOWgMSeodp>!fpXoSM!?eFsVcjhc^}_hhN$0 z?9V)K!bV}!7tuS{*3A)pzp5c$wZe%QHJHQqjrFNTb?MjPXz3S{S>>PCa_XX}x}4Z^ zn-sUpKR#}%Us}+WHPY))CHdt!;nvReR@*Kqy#HMy9r)s-hh*ySp4;EFx>Z-*QcQYy zyjSjyKi*o_@?^o5c@mn;<`{xa0_h$_v~WaE{Hd|kpWEbZ+hhdaw(wb9BG58!TUpB$ z{>nX2Ntx*&*^Ga%f3^rA{imVmtFU8Fo{!|;SfJ-jo;8fiAvbJq@vyQu=XbG4_DtcG zqz`_pu6Ty1NSqrg!5^$2Yz>PkJk_`QgVb!!Et2b(a2vmyRv>reI8yyv6pe9Inna%fWIN9+KsfUJMib(%R90UE;q*I&1A^@Q5^ok(peep zdff*1HT=9%Kvm&|(cfa%=@-=3m~}psFfC3mn{VlUW|oFg@r|}dX*i8ROGIh zp-WYK(Rjn-yQD}!$ZeUj@JoF}iIqunYUFQyTRp#+bgln@)RWylv7HB3eGQ;D$_yRN zR+vp`-4NB&@}p2+L2uYGk*HUi-6s;r_nkjYWtFKMS;}(T31!i9SF1DjYA+EAYLjQl zRSXGB47pEe7f8H=e#JGrL8Du`j!ev4%qHbD;2+UBHOj}jk3`?xr9x#=;}?@ zZclWb?(8TwAP}Sa4{(0vQCe0$r(SSeP4>w;n(b~;6Lw{>2l1V3yR1o#qi>vDEcILx z$K^pxGd`nJ_CwB&mW4e&?yrR1{*Y7(8Ks|pTKDXa?AqbjVI}(=XLamJQQyZm4JZ7y z%R_*_Y4i5!*E-+7h?lJ~^Ibz)yO8|@pYVJ(iOA>_jw+V+w}?Dt3bS zitmy(37jRDm0F(%YI_TjcGVPZ^Kk!MakQCuKB-JtX0+4RG_)&v_trGuUP*69rS_;z z=~rmoqp~M=y6YHkk?@_WeK6+0tGd^VFLouT$4FPW{Ad<)Y1Q2m``%yr;ATZv-Ib%g z1Ks|(IaL-PyQL(Qe))}-EDuPyG8iy5qWz^*Z?fKMXZy3;2K&!8+xtFN&~r`8rDdO9 zBKg&0f40wIqGh|kp|Z~Y&vKP>GIh#nQlh@bSIUMRJ_hamq-No(tPtum^W3zhn;-l0 z7x=APVNB#JR}Y!a`RF`U|C_D)s2+BTRoC64AK!cW%dcjgjU4&FoO_9#gzN+Kq}2JB zj2j8VmDk8iTD7x-TbJf)*;+^@zA}FP$z?d|x2RFo_lJ#g(~k40C*HkqEj1H&FJ+xS zp@nSo#jD|+vHvmLy4~H?Wa~nro<=+?qxvDPdsI?+MazQ(dxo z^O76&U*c^|N&9~#7Z1dqWPeVq((@qM%VehPa$HU;6#eMkaWTYir&7LaKomcL8d_s4 zX43Kfl0>c2SrJ=r!c}G4T-&D!71E`>UAMNEdB?t&;CRsbO-(6O;>G5n%|IM%X zbqgeZJxX6;`(e{yZtt)@nI$7_v1yi9erZKNhmn-F__C5B(`0A$(vAI`K1~(`poStsgY`pw&I;LjWu)E26T-sS5T~6oTc)|h;QZasS5+`K}42+ zY*~d>`qgKqdzZf}_$66aJ)JDm(y4!My`4~ZYOVbDJhcklE80p29y-rDne&xkToboz zuHNvGO<4;4i%-W&rU&JwIj=hFUhz)l=qE*S`K^0pa!*vmd6W|qMSLG-5xxm_7|8m~ z2=#rEe@jc`J=surS6!g)Lu>8n79*y&SaKZ#b%{kI8IG2Yc7KBQDG{DtJF!DxXdUuO6XPDrj3KQ%ewEq+H`_eUfyvIKet?K4OwaZPVL1( zPo7BklANEIcC>}e{(WxHqGftJMK-5L`L-i&*IrJmU2#~p7v7*)b96E8&pN!APovF= zaMHZ2ZHHzOR#qQSiQTw_pQDVa* zz7*o9U(4zX5eE9Jy`@W?(xsoIow^cGCcJv&)pEtD(F@rQ^ysH6HJI#W&HNJCO{${O zZ#LeMHMSBpNo$ZAnEP@1t0h|LjC8S_2LEb>g!MIz%g-Y*Epw|(3jnPZ%WO&6E&js zo9ZiPO1>FTyzbPgDtj;|PTNqiOwP^QP%gT{cTjbA={WW5_nyD8D@;mV*>PCG=Q{LQ-TTv9DVIfDJ zW<*uyZ+f0FBR@m9MkGjf!<8`a694ei8!d?{vG%F>ql5KjQ)>j2%9|ylT6PwZ?w51O zt_QBoE=XJ2xaMMmjDTy{7IwEqlHy(S)O+5tc!7ZWnC5*>l07zAMRxBgq-)%B=4ep^Rs&nR{)kS(j-cGvWGK1q3HWFWDOCF?S?WVG|>^DQ!x z^?#(!{OQ;mzOnB4R*z5n3*-JM=8!9~d(oMFM*}t-o))l*vxP?VI%mt=u=RT{t!2fMdwQLc z;YYkidJ~394y^Ht6TGxXp0CgA?p!>oJpHl@Oi1k5r{i`;r`q@&gz$Ra`~fP`a0TS zjT_TWy0x7s+v4=+X)G|_*w_jBvJ34K^-Q4i1n3THtfaWs^**(vXUP)4uZ1OA8&0cX= zx@66R`-3}AFPR&Y@k`rFK#7?*E5m0WT}E}b!KETnkVIO1|E&J?2Ln!=OzUshZ6UpP zYp+&l=;rUcm+b2`vUXDBzkpv=eC4c#^>96YX5$B9!&_2e<0+O?-q|l+>K4bO3{~2$ z{*;MrjQxjEZcd(GUbl1lu!GHOPR1DB&K z%mO!M%Xu<}XY8g}d7H8#XRR_ZC6u23?pYVMSz~?L0xdbg^<-k{klM#|-Gtf8{Jzh$ z?~xVVxI6iArPb7@Z3Py_@9*Mi??<0FD{c|Zxo@?Rzej+q+%vl6B`YT9ll>OWjCMQO z#~0J25CvaTeFuvSU zX{CGT+j- zN@m7+bPY>-S-;K;aTEOTGgW)(NQq^yOyg$Rz*EYP zE=Cvm&FfyThySTNGij;j)W}T&gIkszwIuymslL;2h3J$h!AO0R`z|uy+NxGw@x0@h zC3N#r#`S$lZDHDozJb1>zLCDMzKOo6zL~zcfu4cBfq{Xcfsui+fr){sfti81p`M|> zp@E^Hp^>4np^2fXp&2lZ=^5!885kKF85tQHnHZTGnHiZI12LJgfw7^nk+HF{iLt4% znX$Qvo{7GRfr+7sk%_U1iHWI+nTffno~gd60kD@Lo-z|tQ&Tfjb2B|NeKP|yLo*{Y zV>1&oQ!_I&b92a|IV5imQJX_Bb96~Z4|`cw3=B}PlMAE9nh!^70E4%~l@cBm!=+MV zV5~xMtoVeuXvAPNw$Bw<=Xexkk;ypfwS8A{q8S)j6BXk5-&3K%yRYyXHQMLsO2^|| zAWi{bKtbEa?CB1`9|KHX6nOQT1D+@hN+i7e{2$k&DO~QZ|G0(N#~|!~-NDG5c!i?` zS_Dpl8|*b^#ISfSAUO&Gc0cT&$3cV9 z3h_LQaSUJ#e-w;S4Y^{d3sp^HtHtmYQ5t-62LqX6cShT+a=i%+?6<|ZPb@B3`+S>Cd z+B{gCgvj`RQnUdh8nphuDB7S+5JerLlEOww3@a`woV$-7hHp5F38O0-X#Y@Oii3vd z{NoCci-oaa$HgS_&1hD9B0DZ(@})_UFoH1FM3mszRTh)MWFqnaGyot0o(rS5S^=W~ zkwWnzWy1Dz*n=JcJ4Ir`V^Ehu*=IzeDoloIg9(tshC*qK@!B$E;Zypj=IvMeTW z5(LMy>#3!Q&wb{{;$-tEt$>JnKV??1L%z@C1P!=VM#SBk~ znsg7gY{VcBz|Vz6J2CpySURCP6i#v!47&&l@}~~$XE1T|3WZ{!gfXI-k>m4VcytW# z-C>(*STLpWW+cR{5+4p}qq#9L4$UtqtT-Tk9M2aA^&bw46@iHP5~9%=#@>k1n3P*I zX)(s<1uO&cY&InlQ6)lKuoL2-7X~G8l37VGmvET{Fkk2z6Y2)lX>c5y!_j84Rv=n9 ztZ-2NI94Va|7y1sCFgrSQqTMixS45J@VnPvk zOq>6n@lcM&h{}NA0h*N!41sZ628x7eL|75Zjsx;TCifDZ69KK`0>CChTNq(5($UDL zsIfU*KNNLrGd2t{Tm#S_io%gM3;uI_hx15KC&o6nNfN`@(4avHAt9_d2+y~OOTfV0 z>d7T;pun|0h#x(^;}NGaU()%KWrGVdm zmK_F!E+;t}cv_J!-Dq|IcN*isB-{mIv>*(6REf4HL1zz+paj7m6e^fyF%41#@q=br zU>1po5e|bf4u}OoR6&btQQ{J!xkR>sf6s7MD91qN2<^q70I6mW8@NculBcbu-F})$+7U_&{~4$($sRrWiZG`7}wE2&J0|n zXwrqA|L@Q^TnvOmo7!mXcn72hx{pgZJ50dYjte*su6&z(0mPdlh&S&EYH1o*x=p$= z;h)NtZIIH~H5QW!qRlDVo5MmUaD&PdXk+o%fjqOp-zNT}G4>6+-W7n|*hBnqe`m_&gGEy?!d z0tNX%SM0{bi7gT*ERG!}PdE_P4FrfQ$V|>yrceMh3aMk_!xf|lN&;ku6CwfUN(}D_ ziQzrIIJ0MmKuPE!ZkU*$BQfDUSwNs(Ix#9d;QXI61Df$-7?FrZI9!*F<~bms_2&|w zq7i2TKr=67(C$!sxgxt6#z`dys-br9E7nV zLCxW21GgB`m;-S@mM{{h5NN$fG>sx%fh*T6C~=9XFMHq&tV5MCF; zq8R{Dw4j$X&^ZEnAq2Tmp3#hnBnQ%pkz_>Y2*5~!-9vt71Iz$OvLT6w{30oU<^vQT zx`ytdxKNx(N}$;YZQw-LkRS9?2#Ob_fzFXXbdKV`2%7M1z)QeKz$jpbH?#x52tWnU z0Zc#)U?m_Aa2QYxs0Ta-`~n#I0KGUM7{CT30n!260LK9JfO~+KfKPy7012eFGQa@f z2=E7l0nz~3fSmwT9#jrgm#A(KP#q#SuRhRyR3=m}sC>L5y3cC^R3E7Py!uA*qPj%k z5P0>A?xXn8eG~?{QJSc}cz$_xh&Cvr^HP`>PXVp~?gKgi9|3$zp}ztY0doMR02_cS zzz+}%hyknuYys>CQ~(+PcL8mHPQZJ>Hvk`u5ov%5Ko4L8a0V;_FaaFE2EbN85#Si0 z8gL$<%qKEcWxAT0n!Fk?stG8nk<`T0MhCuan9%}Y$q^SErW+Ios!RZk zi(pm&h*?G?EWvbxfYCiHiV+vViVFZu6CcK)f_ab1sN$)ZRR|Be;7%+I{A+R)KZU;B4hc1Q@wRmx1D z20%f_oZQe8X%|9R6dny#Pp08!!vNb9;i~V>QW*+6|3qL|lc0U=(Ofun_h1 zwzakQ^fa(>T0li-R69ElCz`jXJvyP$y{JA^Cs(Qijqd5~?oRjcqAs91(WtH-aNo~{ zYUcoO^ITx#?dfMrrPO^yayde?>H;B;5k?u~Ty4ict>8^Cz z0vhCk?n{FM#AWXV$FY=XC@_s~rQqdBN3nT9&B8z2 zU*tqbcO6{mzK}C}4^&I;_8xBTPVV+t8ra`>Q(!JHKkz}NEwJ~XJ9v5`pPnAJj!>_z z_FhhI_I7k{FRw*jHlBWqu%dX`Qr)rC*<;I9b}*Rw;PWRs6i=>=0-1rpw1O6nu$Th# zVldLJ(aI@Vho?CGz3$eTP>EqZhKxQsNL!t-$c#IqVX4UAX|Us0Ll{xvux>;*$JgpQ zJe3a?rsE;BkbPugl|RWkqcfpIj~jEq8WaPTQIroI8!(WCfcA$z4S`Wpvg5F8V}n44 zt23hL!TW^qLWe$X3W9|$7z&|iG&Wd@;j0fQa#S<$@t4Vob5;1sm&VLqe_w(LY@&!f zdkBSFBCJUuOB_(HxXVzUNo$N1ld0pz01XtD`r*N7={sf&8Iv8L1%h!8QLcx84Fg%; zP)e-06_9zP0gqX^u=0;Z%)O4;QINW=gVrpFw{wzJP6tSsK^+VUhou|PvjB|N)ranhx8`=yR2QZunk1alWyD80XoZaSd4 zkJ*S=V0Zw^`4D6z3t|HwK6Fq}`?N}f*W^|mGfhHu@GNuVz9(1Yc%rC7Lat-L76~=QH4&oj#Wnm;FiYH|;R1JH zFor=FM)oq~5p!bQ8anQ{4N_-fg~aXiT&vJny+Sag!9z`sk3(3Cf&67Bgobg`L{X0o z3{qjOfJ~@V^17D4aXM^b<8?lq48Ot*Et^br;8>j2!ixhX?Q zC(Swkm|jr8@%ae*nhlm)9j9nU42*sF-T;ao^;pO__5^8sY8r1GZWUnp=gx>aKG-L6 z#(Nkl2Rsi#R$-`K=my9_%d2K+Xm$ehkVGJQh4zIJFfkvY+`;6;HF@c9O#qlld!l); zrR0S3ukuV>P6UGymLZ6Sp?3>l~!_fsqokMghkXc0_9%be*@JLH^M_L+2YfIWa> zz+pfc;1r++Z~<@ya2;?L@Cfh>&;jTH3;=!u1VZ3LJOFV35g-eg1(*ZS1n2?g0TuvU z0qy{AfFEEPAOsKzhyj>Dy`i--S|cMJ8m+0(x=0s*Hq0O!1nPTevkO`?PX=0xBdaRU zKd+zi*4QX4@{i&~Kz0ih2DKHshtg99P{z(t9#9@EQPg5BcZi6@^3bp!j%UP##bkJUb4`mnt9y z>g^QZCg2&M5AX*d7YgflKsX>3un|xKs0Q2wv;jT<{s82|@Hlfo7$6pq3fKtP1t9%5C~WU$OWJ{O5j)pXaKwh3;`tId%M~IOMnl613-Q^z_9>u z2G9cN2K)v{M?n4o%K-6!wSXGHGr$*sLL}I#0ZssK028nhum^A)a20R|fYNvc$1ec> zD6k6wba9?sbsIwehiZkhoWMh1MGVWiBIqrWD zAQ0w%&?d(3jLqseq!Nv(khp&eGh`GT7hqUO>}mAh3IPrbsYtKjK5aq8m{7RE3Nmho z9g{IYNFEtE;R_z4!gurF*#@i_CM^vhAomd=l0>+FNfikHAL0x5A6f%$k}{9#G#;sQ z@Sr$uQq_%1v%fuKPq%UZp*!RLUJl_VNo(A=#yeio|I!)$aR;1#X-Bxf)nb@nCrely z(gw#K402V#@v7o29B@?L3gWNFsW{KS6fX4K8EbrGJQ&x9apT%7rpIzs+Hq|b6Io?R(?8(?r^swvU4TL$#ggHvhH6!u_X$h;xtom)g0#{GK@g+tFL;MqL~v_Mz~a9||^iXh}+OvUC7byQJf zaYFQ{^|@*q&hG!PNW}eXIWrOMe=2;hge-E~?kr~Z>oHqI1*fI>Y!+)=&aGvAKj`1Z2?;6V0#Kno(lJ|zl zq$Ta7PBHl^_K1wz>$$IF;IPM97y zDl}XxU#H-C_-s>-&Y9j=nNVF)(51GfhnN^qKH}C&Sz%#K;iN^_XVxhG>v6!^ptYM32GJ3s%RO z&j1@VJT(glVKLz4OcqB#a|XPWfF!AQXe2ubK7gnx8k7(jq0JRonj)O=&}eO#WdA>? Cy6b!Z literal 0 HcmV?d00001 From c537c58e4c9920df8d1ba0d144cde403c89ee2f8 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:06:39 +0000 Subject: [PATCH 34/49] Compiled vector_search/invector --- .../invector/benchmarker_outbound.rs | 367 ++++++++++++++++++ .../src/vector_search/invector/commercial.rs | 367 ++++++++++++++++++ .../src/vector_search/invector/inbound.rs | 367 ++++++++++++++++++ .../invector/innovator_outbound.rs | 367 ++++++++++++++++++ .../src/vector_search/invector/mod.rs | 4 + .../src/vector_search/invector/open_data.rs | 367 ++++++++++++++++++ tig-algorithms/src/vector_search/mod.rs | 3 +- tig-algorithms/src/vector_search/template.rs | 26 +- .../wasm/vector_search/invector.wasm | Bin 0 -> 129340 bytes 9 files changed, 1843 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vector_search/invector/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vector_search/invector/commercial.rs create mode 100644 tig-algorithms/src/vector_search/invector/inbound.rs create mode 100644 tig-algorithms/src/vector_search/invector/innovator_outbound.rs create mode 100644 tig-algorithms/src/vector_search/invector/mod.rs create mode 100644 tig-algorithms/src/vector_search/invector/open_data.rs create mode 100644 tig-algorithms/wasm/vector_search/invector.wasm diff --git a/tig-algorithms/src/vector_search/invector/benchmarker_outbound.rs b/tig-algorithms/src/vector_search/invector/benchmarker_outbound.rs new file mode 100644 index 0000000..c7d3dac --- /dev/null +++ b/tig-algorithms/src/vector_search/invector/benchmarker_outbound.rs @@ -0,0 +1,367 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} + +#[inline(always)] +fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 { + let mut sum = 0.0; + for i in 0..180 { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + if sum > c { + sum; + } + for i in 180..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let subset_size = ((max_fuel - base_fuel) / alpha) as usize; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector/commercial.rs b/tig-algorithms/src/vector_search/invector/commercial.rs new file mode 100644 index 0000000..5ab2af2 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector/commercial.rs @@ -0,0 +1,367 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} + +#[inline(always)] +fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 { + let mut sum = 0.0; + for i in 0..180 { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + if sum > c { + sum; + } + for i in 180..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let subset_size = ((max_fuel - base_fuel) / alpha) as usize; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector/inbound.rs b/tig-algorithms/src/vector_search/invector/inbound.rs new file mode 100644 index 0000000..bbc5f58 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector/inbound.rs @@ -0,0 +1,367 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} + +#[inline(always)] +fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 { + let mut sum = 0.0; + for i in 0..180 { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + if sum > c { + sum; + } + for i in 180..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let subset_size = ((max_fuel - base_fuel) / alpha) as usize; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector/innovator_outbound.rs b/tig-algorithms/src/vector_search/invector/innovator_outbound.rs new file mode 100644 index 0000000..3496425 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector/innovator_outbound.rs @@ -0,0 +1,367 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} + +#[inline(always)] +fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 { + let mut sum = 0.0; + for i in 0..180 { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + if sum > c { + sum; + } + for i in 180..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let subset_size = ((max_fuel - base_fuel) / alpha) as usize; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector/mod.rs b/tig-algorithms/src/vector_search/invector/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vector_search/invector/open_data.rs b/tig-algorithms/src/vector_search/invector/open_data.rs new file mode 100644 index 0000000..cada64f --- /dev/null +++ b/tig-algorithms/src/vector_search/invector/open_data.rs @@ -0,0 +1,367 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} + +#[inline(always)] +fn squared_euclidean_distance_limited(a: &[f32], b: &[f32], c : f32) -> f32 { + let mut sum = 0.0; + for i in 0..180 { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + if sum > c { + sum; + } + for i in 180..a.len() { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + } + sum +} +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = squared_euclidean_distance_limited(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let subset_size = ((max_fuel - base_fuel) / alpha) as usize; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/mod.rs b/tig-algorithms/src/vector_search/mod.rs index ae3472a..cb6b177 100644 --- a/tig-algorithms/src/vector_search/mod.rs +++ b/tig-algorithms/src/vector_search/mod.rs @@ -64,7 +64,8 @@ // c004_a033 -// c004_a034 +pub mod invector; +pub use invector as c004_a034; // c004_a035 diff --git a/tig-algorithms/src/vector_search/template.rs b/tig-algorithms/src/vector_search/template.rs index eddf8a0..0f5fa1e 100644 --- a/tig-algorithms/src/vector_search/template.rs +++ b/tig-algorithms/src/vector_search/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vector_search::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vector_search/invector.wasm b/tig-algorithms/wasm/vector_search/invector.wasm new file mode 100644 index 0000000000000000000000000000000000000000..87fc64c13e02bf6ec48e6d9486b0eba4f9c1623b GIT binary patch literal 129340 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 jfh4I$a5ysnKJ2I_91t5Groj Date: Thu, 16 Jan 2025 21:06:41 +0000 Subject: [PATCH 35/49] Compiled vector_search/invector_hybrid --- .../invector_hybrid/benchmarker_outbound.rs | 395 ++++++++++++++++++ .../invector_hybrid/commercial.rs | 395 ++++++++++++++++++ .../vector_search/invector_hybrid/inbound.rs | 395 ++++++++++++++++++ .../invector_hybrid/innovator_outbound.rs | 395 ++++++++++++++++++ .../src/vector_search/invector_hybrid/mod.rs | 4 + .../invector_hybrid/open_data.rs | 395 ++++++++++++++++++ tig-algorithms/src/vector_search/mod.rs | 3 +- tig-algorithms/src/vector_search/template.rs | 26 +- .../wasm/vector_search/invector_hybrid.wasm | Bin 0 -> 144198 bytes 9 files changed, 1983 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vector_search/invector_hybrid/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vector_search/invector_hybrid/commercial.rs create mode 100644 tig-algorithms/src/vector_search/invector_hybrid/inbound.rs create mode 100644 tig-algorithms/src/vector_search/invector_hybrid/innovator_outbound.rs create mode 100644 tig-algorithms/src/vector_search/invector_hybrid/mod.rs create mode 100644 tig-algorithms/src/vector_search/invector_hybrid/open_data.rs create mode 100644 tig-algorithms/wasm/vector_search/invector_hybrid.wasm diff --git a/tig-algorithms/src/vector_search/invector_hybrid/benchmarker_outbound.rs b/tig-algorithms/src/vector_search/invector_hybrid/benchmarker_outbound.rs new file mode 100644 index 0000000..6a1aa55 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector_hybrid/benchmarker_outbound.rs @@ -0,0 +1,395 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0f64; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i] as f64; + } + } + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f64; + } + mean_vector.into_iter().map(|x| x as f32).collect() +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(f32, &'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = early_stopping_distance(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + heap.into_sorted_vec() + .into_iter() + .map(|(FloatOrd(dist), index)| (dist, &database[index][..], index)) + .collect() +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let m = ((max_fuel - base_fuel) / alpha) as usize; + let n = (m as f32 * 1.2) as usize; + let r = n - m; + + let closest_vectors = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + n, + ); + + let (m_slice, r_slice) = closest_vectors.split_at(m); + let m_vectors: Vec<_> = m_slice.to_vec(); + let r_vectors: Vec<_> = r_slice.to_vec(); + + let mut kd_tree_vectors: Vec<(&[f32], usize)> = m_vectors.iter().map(|&(_, v, i)| (v, i)).collect(); + let kd_tree = build_kd_tree(&mut kd_tree_vectors); + + let mut best_indexes = Vec::with_capacity(query_count); + let mut distances = Vec::with_capacity(query_count); + + for query in &challenge.query_vectors { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + distances.push(best.0); + best_indexes.push(best.1.unwrap_or(0)); + } + + let brute_force_count = (query_count as f32 * 0.1) as usize; + let mut distance_indices: Vec<_> = distances.iter().enumerate().collect(); + distance_indices.sort_unstable_by(|a, b| b.1.partial_cmp(a.1).unwrap()); + let high_distance_indices: Vec<_> = distance_indices.into_iter() + .take(brute_force_count) + .map(|(index, _)| index) + .collect(); + + for &query_index in &high_distance_indices { + let query = &challenge.query_vectors[query_index]; + let mut best = (distances[query_index], best_indexes[query_index]); + + for &(_, vec, index) in &r_vectors { + let dist = squared_euclidean_distance(query, vec); + if dist < best.0 { + best = (dist, index); + } + } + + best_indexes[query_index] = best.1; + } + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector_hybrid/commercial.rs b/tig-algorithms/src/vector_search/invector_hybrid/commercial.rs new file mode 100644 index 0000000..c82abc3 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector_hybrid/commercial.rs @@ -0,0 +1,395 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0f64; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i] as f64; + } + } + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f64; + } + mean_vector.into_iter().map(|x| x as f32).collect() +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(f32, &'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = early_stopping_distance(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + heap.into_sorted_vec() + .into_iter() + .map(|(FloatOrd(dist), index)| (dist, &database[index][..], index)) + .collect() +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let m = ((max_fuel - base_fuel) / alpha) as usize; + let n = (m as f32 * 1.2) as usize; + let r = n - m; + + let closest_vectors = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + n, + ); + + let (m_slice, r_slice) = closest_vectors.split_at(m); + let m_vectors: Vec<_> = m_slice.to_vec(); + let r_vectors: Vec<_> = r_slice.to_vec(); + + let mut kd_tree_vectors: Vec<(&[f32], usize)> = m_vectors.iter().map(|&(_, v, i)| (v, i)).collect(); + let kd_tree = build_kd_tree(&mut kd_tree_vectors); + + let mut best_indexes = Vec::with_capacity(query_count); + let mut distances = Vec::with_capacity(query_count); + + for query in &challenge.query_vectors { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + distances.push(best.0); + best_indexes.push(best.1.unwrap_or(0)); + } + + let brute_force_count = (query_count as f32 * 0.1) as usize; + let mut distance_indices: Vec<_> = distances.iter().enumerate().collect(); + distance_indices.sort_unstable_by(|a, b| b.1.partial_cmp(a.1).unwrap()); + let high_distance_indices: Vec<_> = distance_indices.into_iter() + .take(brute_force_count) + .map(|(index, _)| index) + .collect(); + + for &query_index in &high_distance_indices { + let query = &challenge.query_vectors[query_index]; + let mut best = (distances[query_index], best_indexes[query_index]); + + for &(_, vec, index) in &r_vectors { + let dist = squared_euclidean_distance(query, vec); + if dist < best.0 { + best = (dist, index); + } + } + + best_indexes[query_index] = best.1; + } + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector_hybrid/inbound.rs b/tig-algorithms/src/vector_search/invector_hybrid/inbound.rs new file mode 100644 index 0000000..9a283e9 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector_hybrid/inbound.rs @@ -0,0 +1,395 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0f64; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i] as f64; + } + } + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f64; + } + mean_vector.into_iter().map(|x| x as f32).collect() +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(f32, &'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = early_stopping_distance(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + heap.into_sorted_vec() + .into_iter() + .map(|(FloatOrd(dist), index)| (dist, &database[index][..], index)) + .collect() +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let m = ((max_fuel - base_fuel) / alpha) as usize; + let n = (m as f32 * 1.2) as usize; + let r = n - m; + + let closest_vectors = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + n, + ); + + let (m_slice, r_slice) = closest_vectors.split_at(m); + let m_vectors: Vec<_> = m_slice.to_vec(); + let r_vectors: Vec<_> = r_slice.to_vec(); + + let mut kd_tree_vectors: Vec<(&[f32], usize)> = m_vectors.iter().map(|&(_, v, i)| (v, i)).collect(); + let kd_tree = build_kd_tree(&mut kd_tree_vectors); + + let mut best_indexes = Vec::with_capacity(query_count); + let mut distances = Vec::with_capacity(query_count); + + for query in &challenge.query_vectors { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + distances.push(best.0); + best_indexes.push(best.1.unwrap_or(0)); + } + + let brute_force_count = (query_count as f32 * 0.1) as usize; + let mut distance_indices: Vec<_> = distances.iter().enumerate().collect(); + distance_indices.sort_unstable_by(|a, b| b.1.partial_cmp(a.1).unwrap()); + let high_distance_indices: Vec<_> = distance_indices.into_iter() + .take(brute_force_count) + .map(|(index, _)| index) + .collect(); + + for &query_index in &high_distance_indices { + let query = &challenge.query_vectors[query_index]; + let mut best = (distances[query_index], best_indexes[query_index]); + + for &(_, vec, index) in &r_vectors { + let dist = squared_euclidean_distance(query, vec); + if dist < best.0 { + best = (dist, index); + } + } + + best_indexes[query_index] = best.1; + } + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector_hybrid/innovator_outbound.rs b/tig-algorithms/src/vector_search/invector_hybrid/innovator_outbound.rs new file mode 100644 index 0000000..624671f --- /dev/null +++ b/tig-algorithms/src/vector_search/invector_hybrid/innovator_outbound.rs @@ -0,0 +1,395 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0f64; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i] as f64; + } + } + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f64; + } + mean_vector.into_iter().map(|x| x as f32).collect() +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(f32, &'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = early_stopping_distance(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + heap.into_sorted_vec() + .into_iter() + .map(|(FloatOrd(dist), index)| (dist, &database[index][..], index)) + .collect() +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let m = ((max_fuel - base_fuel) / alpha) as usize; + let n = (m as f32 * 1.2) as usize; + let r = n - m; + + let closest_vectors = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + n, + ); + + let (m_slice, r_slice) = closest_vectors.split_at(m); + let m_vectors: Vec<_> = m_slice.to_vec(); + let r_vectors: Vec<_> = r_slice.to_vec(); + + let mut kd_tree_vectors: Vec<(&[f32], usize)> = m_vectors.iter().map(|&(_, v, i)| (v, i)).collect(); + let kd_tree = build_kd_tree(&mut kd_tree_vectors); + + let mut best_indexes = Vec::with_capacity(query_count); + let mut distances = Vec::with_capacity(query_count); + + for query in &challenge.query_vectors { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + distances.push(best.0); + best_indexes.push(best.1.unwrap_or(0)); + } + + let brute_force_count = (query_count as f32 * 0.1) as usize; + let mut distance_indices: Vec<_> = distances.iter().enumerate().collect(); + distance_indices.sort_unstable_by(|a, b| b.1.partial_cmp(a.1).unwrap()); + let high_distance_indices: Vec<_> = distance_indices.into_iter() + .take(brute_force_count) + .map(|(index, _)| index) + .collect(); + + for &query_index in &high_distance_indices { + let query = &challenge.query_vectors[query_index]; + let mut best = (distances[query_index], best_indexes[query_index]); + + for &(_, vec, index) in &r_vectors { + let dist = squared_euclidean_distance(query, vec); + if dist < best.0 { + best = (dist, index); + } + } + + best_indexes[query_index] = best.1; + } + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/invector_hybrid/mod.rs b/tig-algorithms/src/vector_search/invector_hybrid/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector_hybrid/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vector_search/invector_hybrid/open_data.rs b/tig-algorithms/src/vector_search/invector_hybrid/open_data.rs new file mode 100644 index 0000000..e528466 --- /dev/null +++ b/tig-algorithms/src/vector_search/invector_hybrid/open_data.rs @@ -0,0 +1,395 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + let len = a.len(); + + if a.len() != b.len() || a.len() < 8 { + return f32::MAX; + } + + while i + 7 < len { + unsafe { + let diff0 = *a.get_unchecked(i) - *b.get_unchecked(i); + let diff1 = *a.get_unchecked(i + 1) - *b.get_unchecked(i + 1); + let diff2 = *a.get_unchecked(i + 2) - *b.get_unchecked(i + 2); + let diff3 = *a.get_unchecked(i + 3) - *b.get_unchecked(i + 3); + let diff4 = *a.get_unchecked(i + 4) - *b.get_unchecked(i + 4); + let diff5 = *a.get_unchecked(i + 5) - *b.get_unchecked(i + 5); + let diff6 = *a.get_unchecked(i + 6) - *b.get_unchecked(i + 6); + let diff7 = *a.get_unchecked(i + 7) - *b.get_unchecked(i + 7); + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3 + + diff4 * diff4 + diff5 * diff5 + diff6 * diff6 + diff7 * diff7; + } + + if sum > current_min { + return f32::MAX; + } + + i += 8; + } + + while i < len { + unsafe { + let diff = *a.get_unchecked(i) - *b.get_unchecked(i); + sum += diff * diff; + } + i += 1; + } + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + let (nearer, farther) = if diff < 0.0 { + (&node.left, &node.right) + } else { + (&node.right, &node.left) + }; + + if let Some(nearer_node) = nearer { + stack.push((nearer_node.as_ref(), depth + 1)); + } + + if sqr_diff < best.0 { + if let Some(farther_node) = farther { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + } +} +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0f64; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i] as f64; + } + } + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f64; + } + mean_vector.into_iter().map(|x| x as f32).collect() +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(f32, &'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + if heap.len() < k + { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() + { + let dist = early_stopping_distance(&mean_query_vector, vector, top_dist); + let ord_dist = FloatOrd(dist); + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + heap.into_sorted_vec() + .into_iter() + .map(|(FloatOrd(dist), index)| (dist, &database[index][..], index)) + .collect() +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let max_fuel = 2000000000.0; + let base_fuel = 760000000.0; + let alpha = 1700.0 * challenge.difficulty.num_queries as f64; + + let m = ((max_fuel - base_fuel) / alpha) as usize; + let n = (m as f32 * 1.2) as usize; + let r = n - m; + + let closest_vectors = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + n, + ); + + let (m_slice, r_slice) = closest_vectors.split_at(m); + let m_vectors: Vec<_> = m_slice.to_vec(); + let r_vectors: Vec<_> = r_slice.to_vec(); + + let mut kd_tree_vectors: Vec<(&[f32], usize)> = m_vectors.iter().map(|&(_, v, i)| (v, i)).collect(); + let kd_tree = build_kd_tree(&mut kd_tree_vectors); + + let mut best_indexes = Vec::with_capacity(query_count); + let mut distances = Vec::with_capacity(query_count); + + for query in &challenge.query_vectors { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + distances.push(best.0); + best_indexes.push(best.1.unwrap_or(0)); + } + + let brute_force_count = (query_count as f32 * 0.1) as usize; + let mut distance_indices: Vec<_> = distances.iter().enumerate().collect(); + distance_indices.sort_unstable_by(|a, b| b.1.partial_cmp(a.1).unwrap()); + let high_distance_indices: Vec<_> = distance_indices.into_iter() + .take(brute_force_count) + .map(|(index, _)| index) + .collect(); + + for &query_index in &high_distance_indices { + let query = &challenge.query_vectors[query_index]; + let mut best = (distances[query_index], best_indexes[query_index]); + + for &(_, vec, index) in &r_vectors { + let dist = squared_euclidean_distance(query, vec); + if dist < best.0 { + best = (dist, index); + } + } + + best_indexes[query_index] = best.1; + } + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/mod.rs b/tig-algorithms/src/vector_search/mod.rs index ae3472a..c627189 100644 --- a/tig-algorithms/src/vector_search/mod.rs +++ b/tig-algorithms/src/vector_search/mod.rs @@ -80,7 +80,8 @@ // c004_a041 -// c004_a042 +pub mod invector_hybrid; +pub use invector_hybrid as c004_a042; // c004_a043 diff --git a/tig-algorithms/src/vector_search/template.rs b/tig-algorithms/src/vector_search/template.rs index eddf8a0..0f5fa1e 100644 --- a/tig-algorithms/src/vector_search/template.rs +++ b/tig-algorithms/src/vector_search/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vector_search::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vector_search/invector_hybrid.wasm b/tig-algorithms/wasm/vector_search/invector_hybrid.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2ba2b5f4c35a61e1706f8fce70b6f92e2d8094b0 GIT binary patch literal 144198 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≦p)2C+--nlkgMd>s94qHN-=brD z7ZZq0RbiDC@2Lu_ti1OI8a!-NuQd;~M?F67&_LA^kpI(dqSyzfg|Lm`jYG$hFpkVt zEJId_WFQ7Sp{J2|fqJ(M^os}5o&M-aD-M?_JVxFWxm4D7_90Ucq(c^TI1C4N0yqsTwtPWY8~wBvbFQU$lThkZ(r#*UL*uv$TO ztjl$dja76}uPG3PMo8)4nb+o9tkkCXy0p8Q7dQ0W(Z{SxTT{z6^Vq7+Hcy9U7nvYL zJGS+`xlo#6oKXw%%iUeRisxFcmK9paAvNAzIdC>4*$QS`CxPLrcCbLjoeVLrT<#aSBQ1u_eoMq%y}upKLYIF*YM4%#E*oTY$)= zr62Fc*IgG^Nw!8**UmtLcB5(QwL0~4+NcDg*qm0(I&87bM}!@P7=aZ$NisugnA#0t zoF7~4@-G`=x?Sja9J>d!%)zqYrC^W|wh`n|{T=@gMsYS`31V2JAyhy_$vN)-UapbTtrlebCdereo{uSq4viFAZb1?*CRL<_q!w1pe{-B->kB2~56<2~GrwZNKS(E{5lFlwv? z?WJh2Q%NnztipEcrP&k}r`amf-BlR@HY8;DX_U0i2+dy-ZgZzU0q3rOfVcJBDWxBJ z8|Gro%|RVPQ2=# zr@|R-+tui{9JgTyOpx- z82N`yzroDiE!oyv^UXRoY4f1q8xKv-m2Vw9#N*&q$xc9AkxgZrf9;ry_v19kR>?D_ zppx7omnzvxfUfStnDPhXNm?*oK9UTm?qMGfT0S!HjoD^jD+82fTe2wz zxeCU%uqeckiMFyA9m0+oI{m;;hScsFG5bW9leQ#|$8_1iz(hMiYcVHgFIJFe3n6h3 zI}b1v8IyOij-=>nFh|Hp^~ z?@^vZw*~?SCIBl_G)y$z z&EN#_(`9h#YKjAIG;`x5glw0TIA>0AVbwBHw(C(Z~5E%@Myy&(;~qh4`tN z-ee!yw{QgkV7sW#_J%cFP%3kJGuz_H9x0}i z|IDFao2;Aa`Vg~NG3|1aKPk)HS_WLcNrg}nM=?5!)~$Os{E2(OACuWST{i~kL(=Tf zT_S5AhpFZ&dZ`C;p{|91L_TXB@(@8Nd!nNyhkCY}>il3U!c&oTHe4wrnDDTsT~fpg z$T^aDKRuUOMl~@yi`ftkhco09B7!OoOe##*ZXbw+Lk!WG@BhtT|F^&T^G}@#4to!; zM(_E±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%0Dj(@X z4q4Le>BlbyRr0PHeZE9NAl8Fr=N8+sLXE*%+=>&=YndS={c7E9s)4zXVftmMfjJ*V zo(HIB(X)i}F9+7jC#|k)UUD|z)5?3-jd#`;lJCFnUVgRWgvQgWBiAh6=u53BEqQ#( zL(#;`=*;)R={R|B8>~Hf%Sn8ZQbLKT?=*7cR%~Zmaw);&otst5k;9Uge`FTk+}do* z_xjc521#$ZdNYsCy@9*)uCQ*;G03ka_2>28<*Um%S|`-BzNu!K}%N`6U|zc`Lnoh7sbIKL-5z{-1$yj^dD){ z2(qrz3fBbgtrISoopx@ZsGC@(s^72A_v%jkvRzSXrm_@4eM===%ad%eqHNwe{t)V# zixEC$fsrTHnUR&3+NKdJ2Ae7c>hOwHt&%afw(XL*SCv3<-gjkYar&a>jB}S|@Xiq% zS-qwy3U^G>?)p%P_@Jh^);$iAudQ--*}S7kT>19dIi2@u$zHF@%aoa_GRtbUUdZ!5 z$XeT9q+h~HP;9v?Uq$Y{T;ol8hPgE3koYf~8Ge4x^`9U1k+9U!+AKY1+kpB9N|TR7 zl>esD`cn$M@)WaLmmPKQyW5vP*Vh$a%%nO^D;w*sc(PGOvgxq@6ykZ?+G&E>DG6is>d5BhDB`zX8V z?cxqY(n9NO@#b&OmL`!5%a`TnY}J-vNH0(HxqY9K?J9Aix86)9ZO`_5Z!_o1l53m` z?tHzfB^o-`q+26Q@_nY5fR8`jaBogy!m4L(l&Xh)ZaJ>yYS#phxpzI>Pkq-jSVQ)x z=xhj!*(}67NSvt@2aIbb8UiG z+i&OUbSvW00z=Q_xsmSud2>q_D0oS@>Rp*h$Dj(}++=3V)IGmTB1Akr@%{AotM>;TJ(m9d@(xq!-JANb2Uhlj-S@62PocYR}Xt79o zKpU%Seb}#3l5`_LA{{zA?9~{tL2Y&Ve05pEY6|(#kjlp_t)!WY{fDR7zNQMT+mZUH#$2GIv)I(& z-5sLqyRmjhg^fb__srMfUgIfBug5k%W5(rwvfVg4yUT|9=wo9_v;-m+!f~TH^fcBa4<_mg#l*wpQBCszSyv?p>-nRr}cBi1(eSb^JtY z=dkUkX}>z7jnyt+J0d9I-L)#cSEYfJd+lq&8D9|@wRXkl3SU$u)xLi!46AZiT>koj z;}@e&7wVj1>cyzd;xaFC38&nrH7b%E;Bp2_e z@*O{YY5gX=np7!sDRuvWydSun)a~;PjL)VnFxYa+- ziuEuhtI0QR{S}To_e!O3`iw4a{0Mgp+~4wMi(>nXB?DAtPlc3Ci}&my9S$yh^`T>g$Y>C# z6b%pbeUOs4NY%}lVy?Eq`~JnRv-yiRo%|BAh8niBJu&-xiY)WDse8xh79u87#Pfim zU&!zC4h+}PGZGdr3&UIpAAY21EE+8{>yv5TKn*^w^zdBlF8{f`tF?(gw5Fvjnk^8$ z9zVEoafO-0_Z6z!^p*+n#So%(4DUI~eCs^9;-c4W`&^#u=d-WwQS6LxcUkZ?LnA44 zmv8)>%i_fB1HrL6FH<~MTEBM{td-UVmuT9$+Irgh+6LN&+D6*O+9o>MIyySKI(j<# zItDt1Iz~FiIwrc>x;nbLx_Y|$x(2$2x<wPft%@&p;0xhk*|<6Mb!c z9erJWJ$-$B1ARk%BYk6i69a7n9RpnhJp+9M0|P??BLiat6GLr79YgRoW~gsyU}$J) zWN2(?Vx(=PW29@OXQXdrU}R`yWMph)VytbfW2|eeXRL2*U~Fh?WNd6~Vgf}pf$U8n zX>eTzf#4J@srw&#HJkLj#Bp}TemfPtgenYxl~Ne=g6dqrJb)CK*-!|0wPXKt{jRWG>2L##ud8Q!wxh8fV1=^485S#= z?fiufKG@=EDvOe!H)(62osOLNwE{tYfAI>;5fZm2zz+{8WRG0 zMH%|0e&Z=ebMaV5=bSkF7 zm56bI_`Hy`DD1U$boKNN42_IU=pmu70ySxT3rUPk3XA7blY8_cy5-#iP!{YW@F!Fb z2TAcve~fg3%}E8@alM2riRSs5z93Nk+k znO96hV-mp?s&F_V;g`i06C!I zp0|VJ`2h4HJlDen*X3bi1UT+vj4u%$_l5_ZGMv5o8F~>65b*y8F3V6bnu85z=`E~b zWdxdw|i z;!pta0K!goa!ji*HN!P&38^tKn4@SYoGSPc19rf@DU=E=f*#9=o|s)DW8=WT7<(BC z6DCUTJcoRgBt}BsXvR&BN3$04EE3C_C|3gNKM71`6mqSX6pN17ZWmD=Q%Z}bLi)sP z!!AG~i$#k@&JLk1SV{5F3qz6;Qkf|*LvhyEvqPbC45%Aar=jtzgai!+a~b$-Lo*fR z84tZAmL9`IiN(-kQ`x71=dp(xyDHID$R!DR8-=UTci0)TV#6ofjqNZMIl*Ky;1;-k zGbg7gwl^}EM^TB!U4DU=187zj_+g3X%xK`04yNjGRy?>bWN=QoB}75%I04wxt2K-; z80l!_(^Oaq;}fAOd-*a9G0YZlU?>E8t|P<0j_*jWOVi1*&1sV42o^MGNK#lBGalmK zgLKf+ZuWyQ*u%ka2nzxFjaEh=lxgHHrp2)ay|om zlNleK%H~sShoR8B8K^tM67U5?j4$NF%}*T``k;rDaddEE7KNBVBF$7VEGarVlpc%5 z$v9J5#)RHa4`sWv0L}{%0tT}a(xO4!zt#l|5UqsNSn$G%!fC~_f;iJ!0!+$RAdWi3 zK@Tp`f+gtup;5FD_=B4aWts{?p1|bLtP7J)B8+$#obgN=3m78w0CgI8?&Y}D3I21& zGe>0x*NxDAbQ-wH3}JzXj)`-Rkl1MR0GZJsW^VT$ghz|7C%7f}!g!i-Zx&?*7Zb zgCcu$0$X5kJd41cOxyu*-=~nSE2KM>=Qse3HdGG8(;N{28<$3$+ZlkkAd2e-Ko5~o zSa*O2z!TsFuwtc{F;Zh`;G%Xedum1mwDo`zMf!Ux(60lC z>;-GCtVBB`H@Z*oE*m!MvYudFo*d3K^#m|3dth8XlP;KDIecr%naS{$9Igd9jh|yO zEP-h`#QG#K(E(hrn{h^A zjTi+p!G_H#5+JS%um)#f7zyKrLJ`oovm9p(&cGK?7Qhu8kh5$KFYuY<1wIqvsx2!F z?!+z3h0Orm5Cia;I`u=xf{i;iyy5tt=B_w;G;;A9sl`Gw76y|M$Z-yeBI(8~H=v!Xb@mH#lYYe z6FF|XA)Oh3FlfDKG$A4>fWxs&Y4ORZFM(vip3Tv8i1-i;8$pa2#7A=n8vn@s2r4f+ zMnErmAR3h!&3%X`pmB+K9y&%41E6at>`Z_$0P!ru%TQP}pCkPPrH9U;YbZQQ6V3Oi z-qFHLbPk0Bf&;|GmAozgmV}K$+A7BA+0t5q+ z06BoofO0@90PhPt7oZ222e1ct0zv?(fX#qXKsDeJpaswa_zb`SUzP@F0E_{yfB--k z0NoF|4^)?^ZV*r%BAQzt=sLP5R4?fMxO;S++XkpU(EW4k8>Nft62(K{)-Sq_(nr@( z97Ll$QGId4a_f)>&Y@#>m=_lSA^@p?TtG44FrXfA4R8n01$Ybi3cxM`SqC5uPzD$P z<^y~HQGjGX7GOPK3t$i6FyJEKCZGfG7VsH>fpH-WkOgQ0OaL|j55OV-9iW8e7f=>a zQBjdo0gqvL1r-SuVHH^doR}`5q9R6C0rx}Gz%QnX3b_4IQJJZtBCX6PN>ou%Rsk9- z1bY>d3`Rv#P7(XBHAuFHUyjpZ5I5Pvt`;o6jvMzn>`sS%2K9<$I8^ulEBS$x6&l5L za|{l_AWu5vwGsg08U{IUr$-}cHZdZGLd=xW22t;JIzigP<^h01QKzc}zMg5ISqqz(9O)eb($J3|KL2#@-g3cZ~8zm*r)M_Cc%7YzB# z0iZVL()Q3Mh@Wt2Jvc_=3puAk!axj2kf3UN`B+=qdU@$uIn1|2N0v4=o(`@)Ubg7K z)y><|*V4h+($3Y*%g5c_&C}a*zNLe!rL!ko_qVdNu>-hx&A0OL^0&5hwYGJ(wXwE$ zaJI2_b9J?}g784Yp{K1KBsqTQW$SGN(Vd-aY`wfuHlDT~Hf}C99zJf~whJ7*yzMM~ zoW1R!MD}i8-aZi764JEsba8O@vh{Fqb@z0e?+ImgaP@|w`dT_WxH)*CTpYY?96X)f z+}vI49Gq=koE==9pln_a3vFE>K?i#`cUManTW>cvXE)dRu22d$KUdg6TDIP>AJ54Z zMRs+w^0D)Bb+fVcb#=3K^@hI%w$`q0&el%w2MN2nx!AhEpBH$Yg#tmM?hwe!9m06| zLqu;>AlN~P;NRB8-P<4bp0?I*zP6s0RwxH=FE^B$H`FZr!}SFYZs@9=vzs53%+?dt zlDn;^i@SrnEjthP-$YZeDQ|xWVd*;G*3-?-%L|3{^0c;xdUdw-c5tz^ar5!^Uf^xz z<-dS^E8f zX-+B=6Xq(gcY#0sa8wRWD=-0uffNko?VOCC4i90QLMQ} zyK(QA3{4k0NaA43hzv!_{J6Pi+*Sk9FqkiqA)Nc^0F)Cmei;-VX#(SRJof#MC(Jp| zwiF??Llfz4$Uo;4yQ3yJYzDD7ED|h0;Iai=r7$!d;JXvyATYCi&?k(4Ze>cHO>KcG zipFHQ#`TklThMgSa^v(%O)n&FvkMW*Q8ZD0f0z|DL4_H&f-%9E0`BR05nRn_!iuIjt^~5Zmx2yDwN=?lI1HH( z=*t{!cOvxEik!$4bx0_699UbSrZ`4W)V(;SEgIX_IAyrN6&Q>W(1npj5CvqD>~0O? zV#3O*IjJdf`aH+(HD0d}4QVn^lM~|*bW*UJu#&y&rzSIAWcHUoz&<)&P&@7iEdCte-jHmSXSmZTbIxC;7Zh<~K4O1p zhh17thgfeXG7rWUt%PT6Zm`un0(>07%&ZK+Q!GnvN*Ckvb!PO z49VMEeGkz{u1D9vZxnlvbUbvPtJk6M=o+H&0CbHD6c3%}rj2w=?lH1Epm-=O_Z&J# z`Jp^eJnk`yhputMPd%P`jvJ1f4!VZYLOLl*iyIc@ht8w2qhoIVC>%P6;&ab&_b5Dy zk7$&S9+QYM1t$Cct&TO~75i z6F?WB2QUEm3>XHC0LB1>FwpY>A^U+`vWILD&r1zmkJ>2lze#*5qptvYJN)rLu zZ%`c6R_Gea59waC@ncj5lm^-(TL?-Ug+qI!Z=mpKkMcw5qOd4ER8DjcsElZj^le=L zii`4P>zlAw2B7$A0CWw7=av=4L+POOxN%S!P##=+7An^afEv_WFd!MQ4zL|?6wnIz z0g#CR8xX(*-~nI&k^$=hy8%Z5Hvw+|(vi?d02%-jfIVOlfCX3$C;=P?TmrNLx&Q+J z;u0c88!!)m((r(NC?El_2~ZBW40r~358#a=Vx|L7SVP#)2ZRCA09yf7fM&oCfM_(> zasWC2CSV<4Kj0SNC13==6GOyE0A>Rm0Re!efOG)LBOmtr0Ve=g09qI?j<}8aN6`vn z!x8P+6FX*nibm6VJo2NS023qPa2QuO13V-#74tX29P?i=5Qy_%ND~uR#%Fa55{bq| zNX%cE88VJeFfevZ_Ve~X1p*8hl#yJ)d47a$WAb?@W~!Jt{#Y0(B$J`g*CF5;aXiu( zrle2dM!wMVLo*nux?XcoZH#rRpZQ*`I;g&+jq+AUk9J zREIEAxHV=%;+?4IzsU@LxdP-L(h=rQu^96whs7XiaQx9HM+BUxDz4&yvE*tHe>^b8 zc>N)8p(pF?#z)4333(VZAt0|nm zHz7Ms=FOJ&QPjVPwv*lFpHwW&KMIH#_xQibT~pegs|RBK5Nju%Br_SP7*nL@sgg71 z?>ZC)J*R=U6B0lQ1U(=DG#L;Cp$y|9HfN}#iW*N7l1Hu25z{a>|3OK_{7s#iO!gmz zzRAmIPMHww|2#b*5wi<2NpZXQKPmI&?4*#r4VZ0{PjtP~6dk&Rq^5o=f$$bZAib^}BQ%pSzoeCyMcCY8Wbb?_&-kW$E z1mgmNbz}s*&NBX~8))wTQqaX%c7Fnk<#zadiYulK+f6tNpLojW`GAuoe8v>x;Ug(L z$FNygH@p@>0rQY&ABK!qz&Bx^Dsoe8ga_Ts)1$;Jm`dg0qEMTEtK z*_Z`b6YOS86Hf`&8fS<7#)Gl^^%)8%48}&x#dwhLV8c{m1%O*Hl0xA@8;(C3z9N_e zYBiSN39p1;gvS{Plc5okisNCwA}X(=X$)WfrKwDO0G9@@x9V$Xn`mh3tMY`RBz$#@ zHFY#~X(|zkiE#<0T3X>?tWOHj1XGn(bo4ULANqnK`T#0T*O0EmFf`QGVZi&VbQ8lc zeLXnVW*Ea8uptI?eO)6%Z8}psR25?)2H!V?r-wmdOgg-_$4tP_7K7I;5GU0Lk7k9y aCmd%Bg(O8sX>b_UZ2pAE@K_B{WBwn#1|Dkw literal 0 HcmV?d00001 From 853c769430dc8483b9d994bccb6f5bf00c8e1852 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:07:02 +0000 Subject: [PATCH 36/49] Compiled vector_search/optimax_gpu --- tig-algorithms/src/vector_search/mod.rs | 3 +- .../optimax_gpu/benchmarker_outbound.rs | 468 ++++++++++++++++++ .../vector_search/optimax_gpu/commercial.rs | 468 ++++++++++++++++++ .../src/vector_search/optimax_gpu/inbound.rs | 468 ++++++++++++++++++ .../optimax_gpu/innovator_outbound.rs | 468 ++++++++++++++++++ .../src/vector_search/optimax_gpu/mod.rs | 4 + .../vector_search/optimax_gpu/open_data.rs | 468 ++++++++++++++++++ tig-algorithms/src/vector_search/template.rs | 26 +- .../wasm/vector_search/optimax_gpu.wasm | Bin 0 -> 129208 bytes 9 files changed, 2348 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vector_search/optimax_gpu/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vector_search/optimax_gpu/commercial.rs create mode 100644 tig-algorithms/src/vector_search/optimax_gpu/inbound.rs create mode 100644 tig-algorithms/src/vector_search/optimax_gpu/innovator_outbound.rs create mode 100644 tig-algorithms/src/vector_search/optimax_gpu/mod.rs create mode 100644 tig-algorithms/src/vector_search/optimax_gpu/open_data.rs create mode 100644 tig-algorithms/wasm/vector_search/optimax_gpu.wasm diff --git a/tig-algorithms/src/vector_search/mod.rs b/tig-algorithms/src/vector_search/mod.rs index ae3472a..1f1c315 100644 --- a/tig-algorithms/src/vector_search/mod.rs +++ b/tig-algorithms/src/vector_search/mod.rs @@ -48,7 +48,8 @@ // c004_a025 -// c004_a026 +pub mod optimax_gpu; +pub use optimax_gpu as c004_a026; // c004_a027 diff --git a/tig-algorithms/src/vector_search/optimax_gpu/benchmarker_outbound.rs b/tig-algorithms/src/vector_search/optimax_gpu/benchmarker_outbound.rs new file mode 100644 index 0000000..dd9b614 --- /dev/null +++ b/tig-algorithms/src/vector_search/optimax_gpu/benchmarker_outbound.rs @@ -0,0 +1,468 @@ +/*! +Copyright 2024 bw-dev36 + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + while i + 3 < a.len() { + let diff0 = a[i] - b[i]; + let diff1 = a[i + 1] - b[i + 1]; + let diff2 = a[i + 2] - b[i + 2]; + let diff3 = a[i + 3] - b[i + 3]; + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + + if sum > current_min { + return f32::MAX; + } + + i += 4; + } + + while i < a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + + if sum > current_min { + return f32::MAX; + } + + i += 1; + } + + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + if sqr_diff < best.0 { + if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + + if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } { + stack.push((nearer_node.as_ref(), depth + 1)); + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + + extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_vectors) { + float dist = 0.0; + for (int d = 0; d < num_dimensions; ++d) { + float diff = query_mean[d] - vectors[idx * num_dimensions + d]; + dist += diff * diff; + } + distances[idx] = dist; + } + } + + "#, + + funcs: &["filter_vectors"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = cuda_filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + dev, + funcs, + )?; + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + + + + Ok(Some(Solution { + indexes: best_indexes, + })) + } + + #[cfg(feature = "cuda")] + fn cuda_filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let num_vectors = database.len(); + let num_dimensions = 250; + let flattened_database: Vec = database.iter().flatten().cloned().collect(); + let database_dev = dev.htod_sync_copy(&flattened_database)?; + let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?; + let mut distances_dev = dev.alloc_zeros::(num_vectors)?; + let cfg = LaunchConfig { + block_dim: (256, 1, 1), + grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1), + shared_mem_bytes: 0, + }; + unsafe { + funcs.remove("filter_vectors").unwrap().launch( + cfg, + ( + &mean_query_dev, + &database_dev, + &mut distances_dev, + num_vectors as i32, + num_dimensions as i32, + ), + ) + }?; + let mut distances_host = vec![0.0f32; num_vectors]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?; + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, &distance) in distances_host.iter().enumerate() { + let ord_dist = FloatOrd(distance); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if distance < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&[f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + Ok(result) + } + + #[cfg(feature = "cuda")] + fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)], + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> Option>> { + None + } + + #[cfg(feature = "cuda")] + fn cuda_nearest_neighbor_search( + kd_tree: &Option>>, + query: &[f32], + best: &mut (f32, Option), + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result<()> { + Ok(()) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/optimax_gpu/commercial.rs b/tig-algorithms/src/vector_search/optimax_gpu/commercial.rs new file mode 100644 index 0000000..4bdaec5 --- /dev/null +++ b/tig-algorithms/src/vector_search/optimax_gpu/commercial.rs @@ -0,0 +1,468 @@ +/*! +Copyright 2024 bw-dev36 + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + while i + 3 < a.len() { + let diff0 = a[i] - b[i]; + let diff1 = a[i + 1] - b[i + 1]; + let diff2 = a[i + 2] - b[i + 2]; + let diff3 = a[i + 3] - b[i + 3]; + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + + if sum > current_min { + return f32::MAX; + } + + i += 4; + } + + while i < a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + + if sum > current_min { + return f32::MAX; + } + + i += 1; + } + + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + if sqr_diff < best.0 { + if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + + if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } { + stack.push((nearer_node.as_ref(), depth + 1)); + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + + extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_vectors) { + float dist = 0.0; + for (int d = 0; d < num_dimensions; ++d) { + float diff = query_mean[d] - vectors[idx * num_dimensions + d]; + dist += diff * diff; + } + distances[idx] = dist; + } + } + + "#, + + funcs: &["filter_vectors"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = cuda_filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + dev, + funcs, + )?; + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + + + + Ok(Some(Solution { + indexes: best_indexes, + })) + } + + #[cfg(feature = "cuda")] + fn cuda_filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let num_vectors = database.len(); + let num_dimensions = 250; + let flattened_database: Vec = database.iter().flatten().cloned().collect(); + let database_dev = dev.htod_sync_copy(&flattened_database)?; + let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?; + let mut distances_dev = dev.alloc_zeros::(num_vectors)?; + let cfg = LaunchConfig { + block_dim: (256, 1, 1), + grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1), + shared_mem_bytes: 0, + }; + unsafe { + funcs.remove("filter_vectors").unwrap().launch( + cfg, + ( + &mean_query_dev, + &database_dev, + &mut distances_dev, + num_vectors as i32, + num_dimensions as i32, + ), + ) + }?; + let mut distances_host = vec![0.0f32; num_vectors]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?; + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, &distance) in distances_host.iter().enumerate() { + let ord_dist = FloatOrd(distance); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if distance < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&[f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + Ok(result) + } + + #[cfg(feature = "cuda")] + fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)], + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> Option>> { + None + } + + #[cfg(feature = "cuda")] + fn cuda_nearest_neighbor_search( + kd_tree: &Option>>, + query: &[f32], + best: &mut (f32, Option), + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result<()> { + Ok(()) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/optimax_gpu/inbound.rs b/tig-algorithms/src/vector_search/optimax_gpu/inbound.rs new file mode 100644 index 0000000..23ae821 --- /dev/null +++ b/tig-algorithms/src/vector_search/optimax_gpu/inbound.rs @@ -0,0 +1,468 @@ +/*! +Copyright 2024 bw-dev36 + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + while i + 3 < a.len() { + let diff0 = a[i] - b[i]; + let diff1 = a[i + 1] - b[i + 1]; + let diff2 = a[i + 2] - b[i + 2]; + let diff3 = a[i + 3] - b[i + 3]; + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + + if sum > current_min { + return f32::MAX; + } + + i += 4; + } + + while i < a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + + if sum > current_min { + return f32::MAX; + } + + i += 1; + } + + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + if sqr_diff < best.0 { + if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + + if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } { + stack.push((nearer_node.as_ref(), depth + 1)); + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + + extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_vectors) { + float dist = 0.0; + for (int d = 0; d < num_dimensions; ++d) { + float diff = query_mean[d] - vectors[idx * num_dimensions + d]; + dist += diff * diff; + } + distances[idx] = dist; + } + } + + "#, + + funcs: &["filter_vectors"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = cuda_filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + dev, + funcs, + )?; + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + + + + Ok(Some(Solution { + indexes: best_indexes, + })) + } + + #[cfg(feature = "cuda")] + fn cuda_filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let num_vectors = database.len(); + let num_dimensions = 250; + let flattened_database: Vec = database.iter().flatten().cloned().collect(); + let database_dev = dev.htod_sync_copy(&flattened_database)?; + let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?; + let mut distances_dev = dev.alloc_zeros::(num_vectors)?; + let cfg = LaunchConfig { + block_dim: (256, 1, 1), + grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1), + shared_mem_bytes: 0, + }; + unsafe { + funcs.remove("filter_vectors").unwrap().launch( + cfg, + ( + &mean_query_dev, + &database_dev, + &mut distances_dev, + num_vectors as i32, + num_dimensions as i32, + ), + ) + }?; + let mut distances_host = vec![0.0f32; num_vectors]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?; + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, &distance) in distances_host.iter().enumerate() { + let ord_dist = FloatOrd(distance); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if distance < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&[f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + Ok(result) + } + + #[cfg(feature = "cuda")] + fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)], + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> Option>> { + None + } + + #[cfg(feature = "cuda")] + fn cuda_nearest_neighbor_search( + kd_tree: &Option>>, + query: &[f32], + best: &mut (f32, Option), + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result<()> { + Ok(()) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/optimax_gpu/innovator_outbound.rs b/tig-algorithms/src/vector_search/optimax_gpu/innovator_outbound.rs new file mode 100644 index 0000000..dc1c7e1 --- /dev/null +++ b/tig-algorithms/src/vector_search/optimax_gpu/innovator_outbound.rs @@ -0,0 +1,468 @@ +/*! +Copyright 2024 bw-dev36 + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + while i + 3 < a.len() { + let diff0 = a[i] - b[i]; + let diff1 = a[i + 1] - b[i + 1]; + let diff2 = a[i + 2] - b[i + 2]; + let diff3 = a[i + 3] - b[i + 3]; + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + + if sum > current_min { + return f32::MAX; + } + + i += 4; + } + + while i < a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + + if sum > current_min { + return f32::MAX; + } + + i += 1; + } + + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + if sqr_diff < best.0 { + if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + + if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } { + stack.push((nearer_node.as_ref(), depth + 1)); + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + + extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_vectors) { + float dist = 0.0; + for (int d = 0; d < num_dimensions; ++d) { + float diff = query_mean[d] - vectors[idx * num_dimensions + d]; + dist += diff * diff; + } + distances[idx] = dist; + } + } + + "#, + + funcs: &["filter_vectors"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = cuda_filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + dev, + funcs, + )?; + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + + + + Ok(Some(Solution { + indexes: best_indexes, + })) + } + + #[cfg(feature = "cuda")] + fn cuda_filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let num_vectors = database.len(); + let num_dimensions = 250; + let flattened_database: Vec = database.iter().flatten().cloned().collect(); + let database_dev = dev.htod_sync_copy(&flattened_database)?; + let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?; + let mut distances_dev = dev.alloc_zeros::(num_vectors)?; + let cfg = LaunchConfig { + block_dim: (256, 1, 1), + grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1), + shared_mem_bytes: 0, + }; + unsafe { + funcs.remove("filter_vectors").unwrap().launch( + cfg, + ( + &mean_query_dev, + &database_dev, + &mut distances_dev, + num_vectors as i32, + num_dimensions as i32, + ), + ) + }?; + let mut distances_host = vec![0.0f32; num_vectors]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?; + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, &distance) in distances_host.iter().enumerate() { + let ord_dist = FloatOrd(distance); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if distance < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&[f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + Ok(result) + } + + #[cfg(feature = "cuda")] + fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)], + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> Option>> { + None + } + + #[cfg(feature = "cuda")] + fn cuda_nearest_neighbor_search( + kd_tree: &Option>>, + query: &[f32], + best: &mut (f32, Option), + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result<()> { + Ok(()) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/optimax_gpu/mod.rs b/tig-algorithms/src/vector_search/optimax_gpu/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vector_search/optimax_gpu/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vector_search/optimax_gpu/open_data.rs b/tig-algorithms/src/vector_search/optimax_gpu/open_data.rs new file mode 100644 index 0000000..37097ab --- /dev/null +++ b/tig-algorithms/src/vector_search/optimax_gpu/open_data.rs @@ -0,0 +1,468 @@ +/*! +Copyright 2024 bw-dev36 + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use anyhow::Ok; +use tig_challenges::vector_search::*; +use std::cmp::Ordering; +use std::collections::BinaryHeap; + +struct KDNode<'a> { + point: &'a [f32], + left: Option>>, + right: Option>>, + index: usize, +} + +impl<'a> KDNode<'a> { + fn new(point: &'a [f32], index: usize) -> Self { + KDNode { + point, + left: None, + right: None, + index, + } + } +} +fn quickselect_by(arr: &mut [(&[f32], usize)], k: usize, compare: &F) +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr, compare); + if k < pivot_index { + quickselect_by(&mut arr[..pivot_index], k, compare); + } else if k > pivot_index { + quickselect_by(&mut arr[pivot_index + 1..], k - pivot_index - 1, compare); + } +} + +fn partition(arr: &mut [(&[f32], usize)], compare: &F) -> usize +where + F: Fn(&(&[f32], usize), &(&[f32], usize)) -> Ordering, +{ + let pivot_index = arr.len() >> 1; + arr.swap(pivot_index, arr.len() - 1); + + let mut store_index = 0; + for i in 0..arr.len() - 1 { + if compare(&arr[i], &arr[arr.len() - 1]) == Ordering::Less { + arr.swap(i, store_index); + store_index += 1; + } + } + arr.swap(store_index, arr.len() - 1); + store_index +} + +fn build_kd_tree<'a>(points: &mut [(&'a [f32], usize)]) -> Option>> { + if points.is_empty() { + return None; + } + + const NUM_DIMENSIONS: usize = 250; + let mut stack: Vec<(usize, usize, usize, Option<*mut KDNode<'a>>, bool)> = Vec::new(); + let mut root: Option>> = None; + + stack.push((0, points.len(), 0, None, false)); + + while let Some((start, end, depth, parent_ptr, is_left)) = stack.pop() { + if start >= end { + continue; + } + + let axis = depth % NUM_DIMENSIONS; + let median = (start + end) / 2; + quickselect_by(&mut points[start..end], median - start, &|a, b| { + a.0[axis].partial_cmp(&b.0[axis]).unwrap() + }); + + let (median_point, median_index) = points[median]; + let mut new_node = Box::new(KDNode::new(median_point, median_index)); + let new_node_ptr: *mut KDNode = &mut *new_node; + + if let Some(parent_ptr) = parent_ptr { + unsafe { + if is_left { + (*parent_ptr).left = Some(new_node); + } else { + (*parent_ptr).right = Some(new_node); + } + } + } else { + root = Some(new_node); + } + + stack.push((median + 1, end, depth + 1, Some(new_node_ptr), false)); + stack.push((start, median, depth + 1, Some(new_node_ptr), true)); + } + + root +} + +#[inline(always)] +fn squared_euclidean_distance(a: &[f32], b: &[f32]) -> f32 { + let mut sum = 0.0; + for i in 0..a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + } + sum +} + +#[inline(always)] +fn early_stopping_distance(a: &[f32], b: &[f32], current_min: f32) -> f32 { + let mut sum = 0.0; + let mut i = 0; + while i + 3 < a.len() { + let diff0 = a[i] - b[i]; + let diff1 = a[i + 1] - b[i + 1]; + let diff2 = a[i + 2] - b[i + 2]; + let diff3 = a[i + 3] - b[i + 3]; + + sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3; + + if sum > current_min { + return f32::MAX; + } + + i += 4; + } + + while i < a.len() { + let diff = a[i] - b[i]; + sum += diff * diff; + + if sum > current_min { + return f32::MAX; + } + + i += 1; + } + + sum +} + +fn nearest_neighbor_search<'a>( + root: &Option>>, + target: &[f32], + best: &mut (f32, Option), +) { + let num_dimensions = target.len(); + let mut stack = Vec::with_capacity(64); + + if let Some(node) = root { + stack.push((node.as_ref(), 0)); + } + + while let Some((node, depth)) = stack.pop() { + let axis = depth % num_dimensions; + let dist = early_stopping_distance(&node.point, target, best.0); + + if dist < best.0 { + best.0 = dist; + best.1 = Some(node.index); + } + + let diff = target[axis] - node.point[axis]; + let sqr_diff = diff * diff; + + if sqr_diff < best.0 { + if let Some(farther_node) = if diff < 0.0 { &node.right } else { &node.left } { + stack.push((farther_node.as_ref(), depth + 1)); + } + } + + if let Some(nearer_node) = if diff < 0.0 { &node.left } else { &node.right } { + stack.push((nearer_node.as_ref(), depth + 1)); + } + } +} + +fn calculate_mean_vector(vectors: &[&[f32]]) -> Vec { + let num_vectors = vectors.len(); + let num_dimensions = 250; + + let mut mean_vector = vec![0.0; num_dimensions]; + + for vector in vectors { + for i in 0..num_dimensions { + mean_vector[i] += vector[i]; + } + } + + for i in 0..num_dimensions { + mean_vector[i] /= num_vectors as f32; + } + + mean_vector +} + +#[derive(Debug)] +struct FloatOrd(f32); + +impl PartialEq for FloatOrd { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for FloatOrd {} + +impl PartialOrd for FloatOrd { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for FloatOrd { + fn cmp(&self, other: &Self) -> Ordering { + + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, +) -> Vec<(&'a [f32], usize)> { + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, vector) in database.iter().enumerate() { + let dist = squared_euclidean_distance(&mean_query_vector, vector); + let ord_dist = FloatOrd(dist); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if dist < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&'a [f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + result +} + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + ); + + + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + Ok(Some(Solution { + indexes: best_indexes, + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + pub const KERNEL: Option = Some(CudaKernel { + src: r#" + + extern "C" __global__ void filter_vectors(float* query_mean, float* vectors, float* distances, int num_vectors, int num_dimensions) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_vectors) { + float dist = 0.0; + for (int d = 0; d < num_dimensions; ++d) { + float diff = query_mean[d] - vectors[idx * num_dimensions + d]; + dist += diff * diff; + } + distances[idx] = dist; + } + } + + "#, + + funcs: &["filter_vectors"], + }); + + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + let query_count = challenge.query_vectors.len(); + + let subset_size = match query_count { + 10..=19 if challenge.difficulty.better_than_baseline <= 470 => 4200, + 10..=19 if challenge.difficulty.better_than_baseline > 470 => 4200, + 20..=28 if challenge.difficulty.better_than_baseline <= 465 => 3000, + 20..=28 if challenge.difficulty.better_than_baseline > 465 => 6000, // need more fuel + 29..=50 if challenge.difficulty.better_than_baseline <= 480 => 2000, + 29..=45 if challenge.difficulty.better_than_baseline > 480 => 6000, + 46..=50 if challenge.difficulty.better_than_baseline > 480 => 5000, // need more fuel + 51..=70 if challenge.difficulty.better_than_baseline <= 480 => 3000, + 51..=70 if challenge.difficulty.better_than_baseline > 480 => 3000, // need more fuel + 71..=100 if challenge.difficulty.better_than_baseline <= 480 => 1500, + 71..=100 if challenge.difficulty.better_than_baseline > 480 => 2500, // need more fuel + _ => 1000, // need more fuel + }; + let subset = cuda_filter_relevant_vectors( + &challenge.vector_database, + &challenge.query_vectors, + subset_size, + dev, + funcs, + )?; + let kd_tree = build_kd_tree(&mut subset.clone()); + + + let mut best_indexes = Vec::with_capacity(challenge.query_vectors.len()); + + for query in challenge.query_vectors.iter() { + let mut best = (std::f32::MAX, None); + nearest_neighbor_search(&kd_tree, query, &mut best); + + if let Some(best_index) = best.1 { + best_indexes.push(best_index); + } + } + + + + + + Ok(Some(Solution { + indexes: best_indexes, + })) + } + + #[cfg(feature = "cuda")] + fn cuda_filter_relevant_vectors<'a>( + database: &'a [Vec], + query_vectors: &[Vec], + k: usize, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + + let query_refs: Vec<&[f32]> = query_vectors.iter().map(|v| &v[..]).collect(); + let mean_query_vector = calculate_mean_vector(&query_refs); + + let num_vectors = database.len(); + let num_dimensions = 250; + let flattened_database: Vec = database.iter().flatten().cloned().collect(); + let database_dev = dev.htod_sync_copy(&flattened_database)?; + let mean_query_dev = dev.htod_sync_copy(&mean_query_vector)?; + let mut distances_dev = dev.alloc_zeros::(num_vectors)?; + let cfg = LaunchConfig { + block_dim: (256, 1, 1), + grid_dim: ((num_vectors as u32 + 255) / 256, 1, 1), + shared_mem_bytes: 0, + }; + unsafe { + funcs.remove("filter_vectors").unwrap().launch( + cfg, + ( + &mean_query_dev, + &database_dev, + &mut distances_dev, + num_vectors as i32, + num_dimensions as i32, + ), + ) + }?; + let mut distances_host = vec![0.0f32; num_vectors]; + dev.dtoh_sync_copy_into(&distances_dev, &mut distances_host)?; + let mut heap: BinaryHeap<(FloatOrd, usize)> = BinaryHeap::with_capacity(k); + + for (index, &distance) in distances_host.iter().enumerate() { + let ord_dist = FloatOrd(distance); + if heap.len() < k { + heap.push((ord_dist, index)); + } else if let Some(&(FloatOrd(top_dist), _)) = heap.peek() { + if distance < top_dist { + heap.pop(); + heap.push((ord_dist, index)); + } + } + } + let result: Vec<(&[f32], usize)> = heap + .into_iter() + .map(|(_, index)| (&database[index][..], index)) + .collect(); + + Ok(result) + } + + #[cfg(feature = "cuda")] + fn cuda_build_kd_tree<'a>(subset: &mut [(&'a [f32], usize)], + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> Option>> { + None + } + + #[cfg(feature = "cuda")] + fn cuda_nearest_neighbor_search( + kd_tree: &Option>>, + query: &[f32], + best: &mut (f32, Option), + dev: &Arc, + funcs: &mut HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result<()> { + Ok(()) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vector_search/template.rs b/tig-algorithms/src/vector_search/template.rs index eddf8a0..0f5fa1e 100644 --- a/tig-algorithms/src/vector_search/template.rs +++ b/tig-algorithms/src/vector_search/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vector_search::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vector_search/optimax_gpu.wasm b/tig-algorithms/wasm/vector_search/optimax_gpu.wasm new file mode 100644 index 0000000000000000000000000000000000000000..4653787f4517c69087b9f5b20ef6b72ef81d0e30 GIT binary patch literal 129208 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 literal 0 HcmV?d00001 From 42f87049cc3f1f5973bb3f14a34f0416f9a53b1f Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:07:42 +0000 Subject: [PATCH 38/49] Compiled vehicle_routing/clarke_wright --- .../clarke_wright/benchmarker_outbound.rs | 124 ++++++++++++++++++ .../clarke_wright/commercial.rs | 124 ++++++++++++++++++ .../vehicle_routing/clarke_wright/inbound.rs | 124 ++++++++++++++++++ .../clarke_wright/innovator_outbound.rs | 124 ++++++++++++++++++ .../src/vehicle_routing/clarke_wright/mod.rs | 4 + .../clarke_wright/open_data.rs | 124 ++++++++++++++++++ tig-algorithms/src/vehicle_routing/mod.rs | 3 +- .../src/vehicle_routing/template.rs | 26 +--- .../wasm/vehicle_routing/clarke_wright.wasm | Bin 0 -> 153037 bytes 9 files changed, 628 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright/commercial.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright/inbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright/innovator_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright/mod.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright/open_data.rs create mode 100644 tig-algorithms/wasm/vehicle_routing/clarke_wright.wasm diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright/benchmarker_outbound.rs b/tig-algorithms/src/vehicle_routing/clarke_wright/benchmarker_outbound.rs new file mode 100644 index 0000000..f6fdcb7 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright/benchmarker_outbound.rs @@ -0,0 +1,124 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::new(); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + scores.sort_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + Ok(Some(Solution { + routes: routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(), + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright/commercial.rs b/tig-algorithms/src/vehicle_routing/clarke_wright/commercial.rs new file mode 100644 index 0000000..03acd66 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright/commercial.rs @@ -0,0 +1,124 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::new(); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + scores.sort_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + Ok(Some(Solution { + routes: routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(), + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright/inbound.rs b/tig-algorithms/src/vehicle_routing/clarke_wright/inbound.rs new file mode 100644 index 0000000..728e063 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright/inbound.rs @@ -0,0 +1,124 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::new(); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + scores.sort_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + Ok(Some(Solution { + routes: routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(), + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright/innovator_outbound.rs b/tig-algorithms/src/vehicle_routing/clarke_wright/innovator_outbound.rs new file mode 100644 index 0000000..d523425 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright/innovator_outbound.rs @@ -0,0 +1,124 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::new(); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + scores.sort_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + Ok(Some(Solution { + routes: routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(), + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright/mod.rs b/tig-algorithms/src/vehicle_routing/clarke_wright/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright/open_data.rs b/tig-algorithms/src/vehicle_routing/clarke_wright/open_data.rs new file mode 100644 index 0000000..101edeb --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright/open_data.rs @@ -0,0 +1,124 @@ +/*! +Copyright 2024 Uncharted Trading Limited + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::new(); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + scores.sort_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + Ok(Some(Solution { + routes: routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(), + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 930b4fb..8976e9b 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -1,4 +1,5 @@ -// c002_a001 +pub mod clarke_wright; +pub use clarke_wright as c002_a001; // c002_a002 diff --git a/tig-algorithms/src/vehicle_routing/template.rs b/tig-algorithms/src/vehicle_routing/template.rs index 02a7cab..b5c872b 100644 --- a/tig-algorithms/src/vehicle_routing/template.rs +++ b/tig-algorithms/src/vehicle_routing/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vehicle_routing::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vehicle_routing/clarke_wright.wasm b/tig-algorithms/wasm/vehicle_routing/clarke_wright.wasm new file mode 100644 index 0000000000000000000000000000000000000000..c778d20a973980c14abfbf166fd3603e3b4e6430 GIT binary patch literal 153037 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

    }se86}@Jv*g7yN+={Iht8=?~IZc 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 Date: Thu, 16 Jan 2025 21:23:03 +0000 Subject: [PATCH 42/49] Compiled vehicle_routing/advanced_heuristics --- .../benchmarker_outbound.rs | 240 ++++++++++++++++++ .../advanced_heuristics/commercial.rs | 240 ++++++++++++++++++ .../advanced_heuristics/inbound.rs | 240 ++++++++++++++++++ .../advanced_heuristics/innovator_outbound.rs | 240 ++++++++++++++++++ .../advanced_heuristics/mod.rs | 4 + .../advanced_heuristics/open_data.rs | 240 ++++++++++++++++++ tig-algorithms/src/vehicle_routing/mod.rs | 3 +- .../src/vehicle_routing/template.rs | 26 +- .../vehicle_routing/advanced_heuristics.wasm | Bin 0 -> 173571 bytes 9 files changed, 1208 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vehicle_routing/advanced_heuristics/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_heuristics/commercial.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_heuristics/inbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_heuristics/innovator_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_heuristics/mod.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_heuristics/open_data.rs create mode 100644 tig-algorithms/wasm/vehicle_routing/advanced_heuristics.wasm diff --git a/tig-algorithms/src/vehicle_routing/advanced_heuristics/benchmarker_outbound.rs b/tig-algorithms/src/vehicle_routing/advanced_heuristics/benchmarker_outbound.rs new file mode 100644 index 0000000..09f6324 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_heuristics/benchmarker_outbound.rs @@ -0,0 +1,240 @@ +/*! +Copyright 2024 CodeAlchemist + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_heuristics/commercial.rs b/tig-algorithms/src/vehicle_routing/advanced_heuristics/commercial.rs new file mode 100644 index 0000000..3a77824 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_heuristics/commercial.rs @@ -0,0 +1,240 @@ +/*! +Copyright 2024 CodeAlchemist + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_heuristics/inbound.rs b/tig-algorithms/src/vehicle_routing/advanced_heuristics/inbound.rs new file mode 100644 index 0000000..a666f12 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_heuristics/inbound.rs @@ -0,0 +1,240 @@ +/*! +Copyright 2024 CodeAlchemist + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_heuristics/innovator_outbound.rs b/tig-algorithms/src/vehicle_routing/advanced_heuristics/innovator_outbound.rs new file mode 100644 index 0000000..35ca488 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_heuristics/innovator_outbound.rs @@ -0,0 +1,240 @@ +/*! +Copyright 2024 CodeAlchemist + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_heuristics/mod.rs b/tig-algorithms/src/vehicle_routing/advanced_heuristics/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_heuristics/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_heuristics/open_data.rs b/tig-algorithms/src/vehicle_routing/advanced_heuristics/open_data.rs new file mode 100644 index 0000000..2fc0fee --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_heuristics/open_data.rs @@ -0,0 +1,240 @@ +/*! +Copyright 2024 CodeAlchemist + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 930b4fb..c407db7 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -104,7 +104,8 @@ // c002_a053 -// c002_a054 +pub mod advanced_heuristics; +pub use advanced_heuristics as c002_a054; // c002_a055 diff --git a/tig-algorithms/src/vehicle_routing/template.rs b/tig-algorithms/src/vehicle_routing/template.rs index 02a7cab..b5c872b 100644 --- a/tig-algorithms/src/vehicle_routing/template.rs +++ b/tig-algorithms/src/vehicle_routing/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vehicle_routing::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vehicle_routing/advanced_heuristics.wasm b/tig-algorithms/wasm/vehicle_routing/advanced_heuristics.wasm new file mode 100644 index 0000000000000000000000000000000000000000..1c505285da0850916d05ffae0087b976737944a8 GIT binary patch literal 173571 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
    g7lMv!I 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 literal 0 HcmV?d00001 From 4d8ba0816599af62eaa70caaeed0516ab38ca583 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:23:16 +0000 Subject: [PATCH 43/49] Compiled vehicle_routing/enhanced_routing --- .../enhanced_routing/benchmarker_outbound.rs | 328 ++++++++++++++++++ .../enhanced_routing/commercial.rs | 328 ++++++++++++++++++ .../enhanced_routing/inbound.rs | 328 ++++++++++++++++++ .../enhanced_routing/innovator_outbound.rs | 328 ++++++++++++++++++ .../vehicle_routing/enhanced_routing/mod.rs | 4 + .../enhanced_routing/open_data.rs | 328 ++++++++++++++++++ tig-algorithms/src/vehicle_routing/mod.rs | 3 +- .../src/vehicle_routing/template.rs | 26 +- .../vehicle_routing/enhanced_routing.wasm | Bin 0 -> 176643 bytes 9 files changed, 1648 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vehicle_routing/enhanced_routing/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/enhanced_routing/commercial.rs create mode 100644 tig-algorithms/src/vehicle_routing/enhanced_routing/inbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/enhanced_routing/innovator_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/enhanced_routing/mod.rs create mode 100644 tig-algorithms/src/vehicle_routing/enhanced_routing/open_data.rs create mode 100644 tig-algorithms/wasm/vehicle_routing/enhanced_routing.wasm diff --git a/tig-algorithms/src/vehicle_routing/enhanced_routing/benchmarker_outbound.rs b/tig-algorithms/src/vehicle_routing/enhanced_routing/benchmarker_outbound.rs new file mode 100644 index 0000000..fa025be --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/enhanced_routing/benchmarker_outbound.rs @@ -0,0 +1,328 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + if let Some(best_sol) = &best_solution { + let mut solution = Solution { + routes: best_sol.routes.clone() + }; + if try_inter_route_swap(&mut solution, &challenge.distance_matrix, &challenge.demands, challenge.max_capacity) { + let new_cost = calculate_solution_cost(&solution, &challenge.distance_matrix); + if new_cost < best_cost { + best_solution = Some(solution); + } + } + } + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + + let max_distance = challenge.distance_matrix.iter().flat_map(|row| row.iter()).cloned().max().unwrap_or(0); + let threshold = max_distance / 2; + + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + if challenge.distance_matrix[i][j] <= threshold { + savings.push((0.0, i as u8, j as u8)); + } + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + + +#[inline] +fn try_inter_route_swap( + solution: &mut Solution, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> bool { + let mut improved = false; + let num_routes = solution.routes.len(); + + for i in 0..num_routes { + for j in i + 1..num_routes { + if let Some(better_routes) = find_best_swap( + &solution.routes[i], + &solution.routes[j], + distance_matrix, + demands, + max_capacity + ) { + solution.routes[i] = better_routes.0; + solution.routes[j] = better_routes.1; + improved = true; + } + } + } + + improved +} + +#[inline] +fn find_best_swap( + route1: &Vec, + route2: &Vec, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> Option<(Vec, Vec)> { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 1..route1.len() - 1 { + for j in 1..route2.len() - 1 { + let route1_demand: i32 = route1.iter().map(|&n| demands[n]).sum(); + let route2_demand: i32 = route2.iter().map(|&n| demands[n]).sum(); + let demand_delta = demands[route2[j]] - demands[route1[i]]; + + if route1_demand + demand_delta > max_capacity || + route2_demand - demand_delta > max_capacity { + continue; + } + + let old_cost = distance_matrix[route1[i-1]][route1[i]] + + distance_matrix[route1[i]][route1[i+1]] + + distance_matrix[route2[j-1]][route2[j]] + + distance_matrix[route2[j]][route2[j+1]]; + + let new_cost = distance_matrix[route1[i-1]][route2[j]] + + distance_matrix[route2[j]][route1[i+1]] + + distance_matrix[route2[j-1]][route1[i]] + + distance_matrix[route1[i]][route2[j+1]]; + + let improvement = old_cost - new_cost; + if improvement > best_improvement { + best_improvement = improvement; + let mut new_route1 = route1.clone(); + let mut new_route2 = route2.clone(); + new_route1[i] = route2[j]; + new_route2[j] = route1[i]; + best_swap = Some((new_route1, new_route2)); + } + } + } + + best_swap +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/enhanced_routing/commercial.rs b/tig-algorithms/src/vehicle_routing/enhanced_routing/commercial.rs new file mode 100644 index 0000000..56a44bf --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/enhanced_routing/commercial.rs @@ -0,0 +1,328 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + if let Some(best_sol) = &best_solution { + let mut solution = Solution { + routes: best_sol.routes.clone() + }; + if try_inter_route_swap(&mut solution, &challenge.distance_matrix, &challenge.demands, challenge.max_capacity) { + let new_cost = calculate_solution_cost(&solution, &challenge.distance_matrix); + if new_cost < best_cost { + best_solution = Some(solution); + } + } + } + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + + let max_distance = challenge.distance_matrix.iter().flat_map(|row| row.iter()).cloned().max().unwrap_or(0); + let threshold = max_distance / 2; + + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + if challenge.distance_matrix[i][j] <= threshold { + savings.push((0.0, i as u8, j as u8)); + } + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + + +#[inline] +fn try_inter_route_swap( + solution: &mut Solution, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> bool { + let mut improved = false; + let num_routes = solution.routes.len(); + + for i in 0..num_routes { + for j in i + 1..num_routes { + if let Some(better_routes) = find_best_swap( + &solution.routes[i], + &solution.routes[j], + distance_matrix, + demands, + max_capacity + ) { + solution.routes[i] = better_routes.0; + solution.routes[j] = better_routes.1; + improved = true; + } + } + } + + improved +} + +#[inline] +fn find_best_swap( + route1: &Vec, + route2: &Vec, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> Option<(Vec, Vec)> { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 1..route1.len() - 1 { + for j in 1..route2.len() - 1 { + let route1_demand: i32 = route1.iter().map(|&n| demands[n]).sum(); + let route2_demand: i32 = route2.iter().map(|&n| demands[n]).sum(); + let demand_delta = demands[route2[j]] - demands[route1[i]]; + + if route1_demand + demand_delta > max_capacity || + route2_demand - demand_delta > max_capacity { + continue; + } + + let old_cost = distance_matrix[route1[i-1]][route1[i]] + + distance_matrix[route1[i]][route1[i+1]] + + distance_matrix[route2[j-1]][route2[j]] + + distance_matrix[route2[j]][route2[j+1]]; + + let new_cost = distance_matrix[route1[i-1]][route2[j]] + + distance_matrix[route2[j]][route1[i+1]] + + distance_matrix[route2[j-1]][route1[i]] + + distance_matrix[route1[i]][route2[j+1]]; + + let improvement = old_cost - new_cost; + if improvement > best_improvement { + best_improvement = improvement; + let mut new_route1 = route1.clone(); + let mut new_route2 = route2.clone(); + new_route1[i] = route2[j]; + new_route2[j] = route1[i]; + best_swap = Some((new_route1, new_route2)); + } + } + } + + best_swap +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/enhanced_routing/inbound.rs b/tig-algorithms/src/vehicle_routing/enhanced_routing/inbound.rs new file mode 100644 index 0000000..ed90db1 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/enhanced_routing/inbound.rs @@ -0,0 +1,328 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + if let Some(best_sol) = &best_solution { + let mut solution = Solution { + routes: best_sol.routes.clone() + }; + if try_inter_route_swap(&mut solution, &challenge.distance_matrix, &challenge.demands, challenge.max_capacity) { + let new_cost = calculate_solution_cost(&solution, &challenge.distance_matrix); + if new_cost < best_cost { + best_solution = Some(solution); + } + } + } + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + + let max_distance = challenge.distance_matrix.iter().flat_map(|row| row.iter()).cloned().max().unwrap_or(0); + let threshold = max_distance / 2; + + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + if challenge.distance_matrix[i][j] <= threshold { + savings.push((0.0, i as u8, j as u8)); + } + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + + +#[inline] +fn try_inter_route_swap( + solution: &mut Solution, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> bool { + let mut improved = false; + let num_routes = solution.routes.len(); + + for i in 0..num_routes { + for j in i + 1..num_routes { + if let Some(better_routes) = find_best_swap( + &solution.routes[i], + &solution.routes[j], + distance_matrix, + demands, + max_capacity + ) { + solution.routes[i] = better_routes.0; + solution.routes[j] = better_routes.1; + improved = true; + } + } + } + + improved +} + +#[inline] +fn find_best_swap( + route1: &Vec, + route2: &Vec, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> Option<(Vec, Vec)> { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 1..route1.len() - 1 { + for j in 1..route2.len() - 1 { + let route1_demand: i32 = route1.iter().map(|&n| demands[n]).sum(); + let route2_demand: i32 = route2.iter().map(|&n| demands[n]).sum(); + let demand_delta = demands[route2[j]] - demands[route1[i]]; + + if route1_demand + demand_delta > max_capacity || + route2_demand - demand_delta > max_capacity { + continue; + } + + let old_cost = distance_matrix[route1[i-1]][route1[i]] + + distance_matrix[route1[i]][route1[i+1]] + + distance_matrix[route2[j-1]][route2[j]] + + distance_matrix[route2[j]][route2[j+1]]; + + let new_cost = distance_matrix[route1[i-1]][route2[j]] + + distance_matrix[route2[j]][route1[i+1]] + + distance_matrix[route2[j-1]][route1[i]] + + distance_matrix[route1[i]][route2[j+1]]; + + let improvement = old_cost - new_cost; + if improvement > best_improvement { + best_improvement = improvement; + let mut new_route1 = route1.clone(); + let mut new_route2 = route2.clone(); + new_route1[i] = route2[j]; + new_route2[j] = route1[i]; + best_swap = Some((new_route1, new_route2)); + } + } + } + + best_swap +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/enhanced_routing/innovator_outbound.rs b/tig-algorithms/src/vehicle_routing/enhanced_routing/innovator_outbound.rs new file mode 100644 index 0000000..50327a4 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/enhanced_routing/innovator_outbound.rs @@ -0,0 +1,328 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + if let Some(best_sol) = &best_solution { + let mut solution = Solution { + routes: best_sol.routes.clone() + }; + if try_inter_route_swap(&mut solution, &challenge.distance_matrix, &challenge.demands, challenge.max_capacity) { + let new_cost = calculate_solution_cost(&solution, &challenge.distance_matrix); + if new_cost < best_cost { + best_solution = Some(solution); + } + } + } + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + + let max_distance = challenge.distance_matrix.iter().flat_map(|row| row.iter()).cloned().max().unwrap_or(0); + let threshold = max_distance / 2; + + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + if challenge.distance_matrix[i][j] <= threshold { + savings.push((0.0, i as u8, j as u8)); + } + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + + +#[inline] +fn try_inter_route_swap( + solution: &mut Solution, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> bool { + let mut improved = false; + let num_routes = solution.routes.len(); + + for i in 0..num_routes { + for j in i + 1..num_routes { + if let Some(better_routes) = find_best_swap( + &solution.routes[i], + &solution.routes[j], + distance_matrix, + demands, + max_capacity + ) { + solution.routes[i] = better_routes.0; + solution.routes[j] = better_routes.1; + improved = true; + } + } + } + + improved +} + +#[inline] +fn find_best_swap( + route1: &Vec, + route2: &Vec, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> Option<(Vec, Vec)> { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 1..route1.len() - 1 { + for j in 1..route2.len() - 1 { + let route1_demand: i32 = route1.iter().map(|&n| demands[n]).sum(); + let route2_demand: i32 = route2.iter().map(|&n| demands[n]).sum(); + let demand_delta = demands[route2[j]] - demands[route1[i]]; + + if route1_demand + demand_delta > max_capacity || + route2_demand - demand_delta > max_capacity { + continue; + } + + let old_cost = distance_matrix[route1[i-1]][route1[i]] + + distance_matrix[route1[i]][route1[i+1]] + + distance_matrix[route2[j-1]][route2[j]] + + distance_matrix[route2[j]][route2[j+1]]; + + let new_cost = distance_matrix[route1[i-1]][route2[j]] + + distance_matrix[route2[j]][route1[i+1]] + + distance_matrix[route2[j-1]][route1[i]] + + distance_matrix[route1[i]][route2[j+1]]; + + let improvement = old_cost - new_cost; + if improvement > best_improvement { + best_improvement = improvement; + let mut new_route1 = route1.clone(); + let mut new_route2 = route2.clone(); + new_route1[i] = route2[j]; + new_route2[j] = route1[i]; + best_swap = Some((new_route1, new_route2)); + } + } + } + + best_swap +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/enhanced_routing/mod.rs b/tig-algorithms/src/vehicle_routing/enhanced_routing/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/enhanced_routing/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/enhanced_routing/open_data.rs b/tig-algorithms/src/vehicle_routing/enhanced_routing/open_data.rs new file mode 100644 index 0000000..22705ca --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/enhanced_routing/open_data.rs @@ -0,0 +1,328 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + if let Some(best_sol) = &best_solution { + let mut solution = Solution { + routes: best_sol.routes.clone() + }; + if try_inter_route_swap(&mut solution, &challenge.distance_matrix, &challenge.demands, challenge.max_capacity) { + let new_cost = calculate_solution_cost(&solution, &challenge.distance_matrix); + if new_cost < best_cost { + best_solution = Some(solution); + } + } + } + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + + let max_distance = challenge.distance_matrix.iter().flat_map(|row| row.iter()).cloned().max().unwrap_or(0); + let threshold = max_distance / 2; + + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + if challenge.distance_matrix[i][j] <= threshold { + savings.push((0.0, i as u8, j as u8)); + } + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + + +#[inline] +fn try_inter_route_swap( + solution: &mut Solution, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> bool { + let mut improved = false; + let num_routes = solution.routes.len(); + + for i in 0..num_routes { + for j in i + 1..num_routes { + if let Some(better_routes) = find_best_swap( + &solution.routes[i], + &solution.routes[j], + distance_matrix, + demands, + max_capacity + ) { + solution.routes[i] = better_routes.0; + solution.routes[j] = better_routes.1; + improved = true; + } + } + } + + improved +} + +#[inline] +fn find_best_swap( + route1: &Vec, + route2: &Vec, + distance_matrix: &Vec>, + demands: &Vec, + max_capacity: i32 +) -> Option<(Vec, Vec)> { + let mut best_improvement = 0; + let mut best_swap = None; + + for i in 1..route1.len() - 1 { + for j in 1..route2.len() - 1 { + let route1_demand: i32 = route1.iter().map(|&n| demands[n]).sum(); + let route2_demand: i32 = route2.iter().map(|&n| demands[n]).sum(); + let demand_delta = demands[route2[j]] - demands[route1[i]]; + + if route1_demand + demand_delta > max_capacity || + route2_demand - demand_delta > max_capacity { + continue; + } + + let old_cost = distance_matrix[route1[i-1]][route1[i]] + + distance_matrix[route1[i]][route1[i+1]] + + distance_matrix[route2[j-1]][route2[j]] + + distance_matrix[route2[j]][route2[j+1]]; + + let new_cost = distance_matrix[route1[i-1]][route2[j]] + + distance_matrix[route2[j]][route1[i+1]] + + distance_matrix[route2[j-1]][route1[i]] + + distance_matrix[route1[i]][route2[j+1]]; + + let improvement = old_cost - new_cost; + if improvement > best_improvement { + best_improvement = improvement; + let mut new_route1 = route1.clone(); + let mut new_route2 = route2.clone(); + new_route1[i] = route2[j]; + new_route2[j] = route1[i]; + best_swap = Some((new_route1, new_route2)); + } + } + } + + best_swap +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 930b4fb..bc5ff2e 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -102,7 +102,8 @@ // c002_a052 -// c002_a053 +pub mod enhanced_routing; +pub use enhanced_routing as c002_a053; // c002_a054 diff --git a/tig-algorithms/src/vehicle_routing/template.rs b/tig-algorithms/src/vehicle_routing/template.rs index 02a7cab..b5c872b 100644 --- a/tig-algorithms/src/vehicle_routing/template.rs +++ b/tig-algorithms/src/vehicle_routing/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vehicle_routing::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vehicle_routing/enhanced_routing.wasm b/tig-algorithms/wasm/vehicle_routing/enhanced_routing.wasm new file mode 100644 index 0000000000000000000000000000000000000000..a8b736e6ad6696c934711ae6647d77c88c6c3a19 GIT binary patch literal 176643 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 uov}|mfakMgPu|Q_s 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 Date: Thu, 16 Jan 2025 21:37:10 +0000 Subject: [PATCH 44/49] Minor edits to breakthrough flow. --- docs/agreements/invention_assignment.doc | Bin 74240 -> 73728 bytes tig-breakthroughs/README.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/agreements/invention_assignment.doc b/docs/agreements/invention_assignment.doc index deef5578bf755cd0def450bd24fff77ee8037fb1..cda06427828d1a9f98d6e82cb2138b132567d873 100644 GIT binary patch delta 2799 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\method\` +`\breakthrough\` ## Making a Submission From 101ca13ca3d695145157acffade4cca50365196c Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sat, 18 Jan 2025 14:18:43 +0000 Subject: [PATCH 45/49] Remove attribution section from evidence template. --- tig-breakthroughs/README.md | 2 +- tig-breakthroughs/{knapsack => }/evidence.md | 20 +-- tig-breakthroughs/satisfiability/evidence.md | 157 ------------------ tig-breakthroughs/vector_search/evidence.md | 157 ------------------ tig-breakthroughs/vehicle_routing/evidence.md | 157 ------------------ 5 files changed, 5 insertions(+), 488 deletions(-) rename tig-breakthroughs/{knapsack => }/evidence.md (92%) delete mode 100644 tig-breakthroughs/satisfiability/evidence.md delete mode 100644 tig-breakthroughs/vector_search/evidence.md delete mode 100644 tig-breakthroughs/vehicle_routing/evidence.md diff --git a/tig-breakthroughs/README.md b/tig-breakthroughs/README.md index b641eeb..369b16b 100644 --- a/tig-breakthroughs/README.md +++ b/tig-breakthroughs/README.md @@ -14,7 +14,7 @@ Each submissions is committed to their own branch with the naming pattern: * [Voting Guidelines for Token Holders](../docs/guides/voting.md) 2. Email the following to `breakthroughs@tig.foundation` with subject "Breakthrough Submission (``)": - * **Evidence form**: copy & fill in `evidence.md` from the relevant challenges folder (e.g. [`knapsack/evidence.md`](./knapsack/evidence.md)). Of particular importance is Section 1 which describes your breakthrough + * **Evidence form**: copy & fill in [`evidence.md`](./evidence.md). Of particular importance is Section 1 which describes your breakthrough * **Invention assignment**: copy & replace [invention_assignment.doc](../docs/agreements/invention_assignment.doc) the highlighted parts. Inventor and witness must sign. diff --git a/tig-breakthroughs/knapsack/evidence.md b/tig-breakthroughs/evidence.md similarity index 92% rename from tig-breakthroughs/knapsack/evidence.md rename to tig-breakthroughs/evidence.md index 033a260..3218dd9 100644 --- a/tig-breakthroughs/knapsack/evidence.md +++ b/tig-breakthroughs/evidence.md @@ -33,7 +33,7 @@ IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DES PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. -> knapsack +> YOUR RESPONSE HERE (options are satisfiability, vehicle_routing, knapsack, or vector_search) PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. @@ -51,18 +51,6 @@ TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOUL ## SECTION 3 -### ATTRIBUTION - -PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) - -> YOUR RESPONSE HERE - -PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. - -> YOUR RESPONSE HERE - -## SECTION 4 - ### NOVELTY AND INVENTIVENESS To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. @@ -132,7 +120,7 @@ To support your claim that an algorithmic method is novel and inventive, you sho > YOUR RESPONSE HERE -## SECTION 5 +## SECTION 4 ### EVIDENCE TO SUPPORT PATENTABILITY @@ -140,7 +128,7 @@ To support your claim that an algorithmic method is novel and inventive, you sho > YOUR RESPONSE HERE -## SECTION 6 +## SECTION 5 ### SUGGESTED APPLICATIONS @@ -148,7 +136,7 @@ To support your claim that an algorithmic method is novel and inventive, you sho > YOUR RESPONSE HERE -## SECTION 7 +## SECTION 6 ### ANY OTHER INFORMATION diff --git a/tig-breakthroughs/satisfiability/evidence.md b/tig-breakthroughs/satisfiability/evidence.md deleted file mode 100644 index adc25ce..0000000 --- a/tig-breakthroughs/satisfiability/evidence.md +++ /dev/null @@ -1,157 +0,0 @@ -# EVIDENCE - -THIS IS A TEMPLATE FOR EVIDENCE TO BE SUBMITTED IN SUPPORT OF A REQUEST FOR ELIGIBILITY FOR BREAKTHROUGH REWARDS - -## OVERVIEW - -- TIG TOKEN HOLDERS WILL VOTE ON WHETHER YOUR ALGORITHMIC METHOD IS ELIGIBLE FOR BREAKTHROUGH REWARDS - -- TIG TOKEN HOLDERS ARE FREE TO VOTE AS THEY LIKE BUT HAVE BEEN ADVISED THAT IF THEY WANT TO MAXIMISE THE VALUE OF THE TOKENS THAT THEY HOLD, THEN THEY SHOULD BE SATISFYING THEMSELVES THAT ALGORITHIC METHODS THAT THEY VOTE AS ELIGIBLE WILL BE BOTH NOVEL AND INVENTIVE - -- THE REASON WHY NOVELTY AND INVENTIVENESS ARE IMPORTANT ATTRIBUTES IS BECAUSE THEY ARE PREREQUISITES OF PATENTATBILITY. - -- **THE PURPOSE OF THIS DOCUMENT IS TO:** - - CAPTURE A DESCRIPTION OF THE ALGORITHMIC METHOD THAT YOU WANT TO BE CONSIDERED FOR ELIGIBILTY. - - - TO IDENTIFY THE CREATOR OF THE ALGORITHMIC METHOD. - - - TO PROMPT YOU TO PROVIDE THE BEST EVIDNCE TO SUPPORT THE CASE THAT THE ALGORITHMIC METHOD IS NOVEL AND INVENTIVE. - - - TO PROMPT YOU TO PROVIDE SUGGESTIONS FOR REAL WORLD APPLICATIONS OF YOUR ALGORITHMIC METHOD WHERE YIOU CAN. - -WHEN PROVIDING EVIDENCE, YOU MAY CITE LINKS TO EXTERNAL DATA SOURCES. - -## UNIQUE ALGORITHM IDENTIFIER (UAI) - -> UAI PLACEHOLDER - ASSIGNED BY TIG PROTOCOL - -## SECTION 1 - -IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DESCRIBE THE METHOD BECAUSE THIS WILL DEFINE THE METHOD THAT IS THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. - -### DESCRIPTION OF ALGORITHMIC METHOD - -PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. - -> satisfiability - -PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. - -> YOUR RESPONSE HERE - -## SECTION 2 - -THE COPYRIGHT IN THE IMPLEMENTATION WILL BE THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. - -### IMPLEMENTATION OF ALGORITHMIC METHOD - -TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOULD IDENTIFY THE CODE AND SUBMIT IT WITH THIS - -> YOUR RESPONSE HERE - -## SECTION 3 - -### ATTRIBUTION - -PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) - -> YOUR RESPONSE HERE - -PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. - -> YOUR RESPONSE HERE - -## SECTION 4 - -### NOVELTY AND INVENTIVENESS - -To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. - -### Establish the State of the Art - -- **Prior Art Search:** Conduct a comprehensive review of existing methods, algorithms, patents, academic papers, and industry practices to identify prior art in the domain. - - Highlight documents and technologies most closely related to your method. - - > YOUR RESPONSE HERE - - - Show where these existing methods fall short or lack the features your algorithmic method provides. - - > YOUR RESPONSE HERE - -- **Technical Context:** Describe the common approaches and challenges in the field prior to your innovation. - - > YOUR RESPONSE HERE - -### Evidence of Novelty - -- **Unique Features:** List the features, mechanisms, or aspects of your algorithmic method that are absent in prior art. - - > YOUR RESPONSE HERE - -- **New Problem Solved:** Describe how your algorithmic method provides a novel solution to an existing problem. - - > YOUR RESPONSE HERE - -- **Comparative Analysis:** Use a side-by-side comparison table to highlight the differences between your method and similar existing methods, clearly showing what’s new. - - > YOUR RESPONSE HERE - -### Evidence Inventiveness of Inventiveness - -- **Non-Obviousness:** Argue why a skilled person in the field would not have arrived at your method by simply combining existing ideas or extending known techniques. - - Demonstrate that the development involved an inventive step beyond straightforward application of existing principles. - - Unexpected Results: Highlight results or benefits that could not have been predicted based on prior art. - - > YOUR RESPONSE HERE - -- **Advantages:** Provide evidence of how your algorithm outperforms or offers significant advantages over existing methods, such as: - - Increased efficiency. - - Greater accuracy. - - Reduced computational complexity. - - Novel applications. - - > YOUR RESPONSE HERE - -### Supporting Data - -- **Experimental Results:** Include performance benchmarks, simulations, or empirical data that substantiate your claims of novelty and inventiveness. - - > YOUR RESPONSE HERE - -- **Proof of Concept:** If possible, show a working prototype or implementation that validates the method. - - > YOUR RESPONSE HERE - -### Citations and Expert Opinions - -- **Literature Gaps:** Affirm, to the best of your knowledge, the absence of similar solutions in published literature to reinforce your novelty claim. - - > YOUR RESPONSE HERE - -- **Endorsements:** Include reviews or opinions from industry experts, researchers, or peer-reviewed publications that evidences the novelty and impact of your algorithm. - - > YOUR RESPONSE HERE - -## SECTION 5 - -### EVIDENCE TO SUPPORT PATENTABILITY - -- **Development Records:** Please provide documentation of the invention process, including notes, sketches, and software versions, to establish a timeline of your innovation. - - > YOUR RESPONSE HERE - -## SECTION 6 - -### SUGGESTED APPLICATIONS - -- Please provide suggestions for any real world applications of your abstract algorithmic method that occur to you. - - > YOUR RESPONSE HERE - -## SECTION 7 - -### ANY OTHER INFORMATION - -- Please provide any other evidence or argument that you think might help support you request for eligibility for Breakthrough Rewards. - - > YOUR RESPONSE HERE \ No newline at end of file diff --git a/tig-breakthroughs/vector_search/evidence.md b/tig-breakthroughs/vector_search/evidence.md deleted file mode 100644 index 16965b1..0000000 --- a/tig-breakthroughs/vector_search/evidence.md +++ /dev/null @@ -1,157 +0,0 @@ -# EVIDENCE - -THIS IS A TEMPLATE FOR EVIDENCE TO BE SUBMITTED IN SUPPORT OF A REQUEST FOR ELIGIBILITY FOR BREAKTHROUGH REWARDS - -## OVERVIEW - -- TIG TOKEN HOLDERS WILL VOTE ON WHETHER YOUR ALGORITHMIC METHOD IS ELIGIBLE FOR BREAKTHROUGH REWARDS - -- TIG TOKEN HOLDERS ARE FREE TO VOTE AS THEY LIKE BUT HAVE BEEN ADVISED THAT IF THEY WANT TO MAXIMISE THE VALUE OF THE TOKENS THAT THEY HOLD, THEN THEY SHOULD BE SATISFYING THEMSELVES THAT ALGORITHIC METHODS THAT THEY VOTE AS ELIGIBLE WILL BE BOTH NOVEL AND INVENTIVE - -- THE REASON WHY NOVELTY AND INVENTIVENESS ARE IMPORTANT ATTRIBUTES IS BECAUSE THEY ARE PREREQUISITES OF PATENTATBILITY. - -- **THE PURPOSE OF THIS DOCUMENT IS TO:** - - CAPTURE A DESCRIPTION OF THE ALGORITHMIC METHOD THAT YOU WANT TO BE CONSIDERED FOR ELIGIBILTY. - - - TO IDENTIFY THE CREATOR OF THE ALGORITHMIC METHOD. - - - TO PROMPT YOU TO PROVIDE THE BEST EVIDNCE TO SUPPORT THE CASE THAT THE ALGORITHMIC METHOD IS NOVEL AND INVENTIVE. - - - TO PROMPT YOU TO PROVIDE SUGGESTIONS FOR REAL WORLD APPLICATIONS OF YOUR ALGORITHMIC METHOD WHERE YIOU CAN. - -WHEN PROVIDING EVIDENCE, YOU MAY CITE LINKS TO EXTERNAL DATA SOURCES. - -## UNIQUE ALGORITHM IDENTIFIER (UAI) - -> UAI PLACEHOLDER - ASSIGNED BY TIG PROTOCOL - -## SECTION 1 - -IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DESCRIBE THE METHOD BECAUSE THIS WILL DEFINE THE METHOD THAT IS THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. - -### DESCRIPTION OF ALGORITHMIC METHOD - -PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. - -> vector_search - -PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. - -> YOUR RESPONSE HERE - -## SECTION 2 - -THE COPYRIGHT IN THE IMPLEMENTATION WILL BE THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. - -### IMPLEMENTATION OF ALGORITHMIC METHOD - -TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOULD IDENTIFY THE CODE AND SUBMIT IT WITH THIS - -> YOUR RESPONSE HERE - -## SECTION 3 - -### ATTRIBUTION - -PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) - -> YOUR RESPONSE HERE - -PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. - -> YOUR RESPONSE HERE - -## SECTION 4 - -### NOVELTY AND INVENTIVENESS - -To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. - -### Establish the State of the Art - -- **Prior Art Search:** Conduct a comprehensive review of existing methods, algorithms, patents, academic papers, and industry practices to identify prior art in the domain. - - Highlight documents and technologies most closely related to your method. - - > YOUR RESPONSE HERE - - - Show where these existing methods fall short or lack the features your algorithmic method provides. - - > YOUR RESPONSE HERE - -- **Technical Context:** Describe the common approaches and challenges in the field prior to your innovation. - - > YOUR RESPONSE HERE - -### Evidence of Novelty - -- **Unique Features:** List the features, mechanisms, or aspects of your algorithmic method that are absent in prior art. - - > YOUR RESPONSE HERE - -- **New Problem Solved:** Describe how your algorithmic method provides a novel solution to an existing problem. - - > YOUR RESPONSE HERE - -- **Comparative Analysis:** Use a side-by-side comparison table to highlight the differences between your method and similar existing methods, clearly showing what’s new. - - > YOUR RESPONSE HERE - -### Evidence Inventiveness of Inventiveness - -- **Non-Obviousness:** Argue why a skilled person in the field would not have arrived at your method by simply combining existing ideas or extending known techniques. - - Demonstrate that the development involved an inventive step beyond straightforward application of existing principles. - - Unexpected Results: Highlight results or benefits that could not have been predicted based on prior art. - - > YOUR RESPONSE HERE - -- **Advantages:** Provide evidence of how your algorithm outperforms or offers significant advantages over existing methods, such as: - - Increased efficiency. - - Greater accuracy. - - Reduced computational complexity. - - Novel applications. - - > YOUR RESPONSE HERE - -### Supporting Data - -- **Experimental Results:** Include performance benchmarks, simulations, or empirical data that substantiate your claims of novelty and inventiveness. - - > YOUR RESPONSE HERE - -- **Proof of Concept:** If possible, show a working prototype or implementation that validates the method. - - > YOUR RESPONSE HERE - -### Citations and Expert Opinions - -- **Literature Gaps:** Affirm, to the best of your knowledge, the absence of similar solutions in published literature to reinforce your novelty claim. - - > YOUR RESPONSE HERE - -- **Endorsements:** Include reviews or opinions from industry experts, researchers, or peer-reviewed publications that evidences the novelty and impact of your algorithm. - - > YOUR RESPONSE HERE - -## SECTION 5 - -### EVIDENCE TO SUPPORT PATENTABILITY - -- **Development Records:** Please provide documentation of the invention process, including notes, sketches, and software versions, to establish a timeline of your innovation. - - > YOUR RESPONSE HERE - -## SECTION 6 - -### SUGGESTED APPLICATIONS - -- Please provide suggestions for any real world applications of your abstract algorithmic method that occur to you. - - > YOUR RESPONSE HERE - -## SECTION 7 - -### ANY OTHER INFORMATION - -- Please provide any other evidence or argument that you think might help support you request for eligibility for Breakthrough Rewards. - - > YOUR RESPONSE HERE \ No newline at end of file diff --git a/tig-breakthroughs/vehicle_routing/evidence.md b/tig-breakthroughs/vehicle_routing/evidence.md deleted file mode 100644 index 6577f8a..0000000 --- a/tig-breakthroughs/vehicle_routing/evidence.md +++ /dev/null @@ -1,157 +0,0 @@ -# EVIDENCE - -THIS IS A TEMPLATE FOR EVIDENCE TO BE SUBMITTED IN SUPPORT OF A REQUEST FOR ELIGIBILITY FOR BREAKTHROUGH REWARDS - -## OVERVIEW - -- TIG TOKEN HOLDERS WILL VOTE ON WHETHER YOUR ALGORITHMIC METHOD IS ELIGIBLE FOR BREAKTHROUGH REWARDS - -- TIG TOKEN HOLDERS ARE FREE TO VOTE AS THEY LIKE BUT HAVE BEEN ADVISED THAT IF THEY WANT TO MAXIMISE THE VALUE OF THE TOKENS THAT THEY HOLD, THEN THEY SHOULD BE SATISFYING THEMSELVES THAT ALGORITHIC METHODS THAT THEY VOTE AS ELIGIBLE WILL BE BOTH NOVEL AND INVENTIVE - -- THE REASON WHY NOVELTY AND INVENTIVENESS ARE IMPORTANT ATTRIBUTES IS BECAUSE THEY ARE PREREQUISITES OF PATENTATBILITY. - -- **THE PURPOSE OF THIS DOCUMENT IS TO:** - - CAPTURE A DESCRIPTION OF THE ALGORITHMIC METHOD THAT YOU WANT TO BE CONSIDERED FOR ELIGIBILTY. - - - TO IDENTIFY THE CREATOR OF THE ALGORITHMIC METHOD. - - - TO PROMPT YOU TO PROVIDE THE BEST EVIDNCE TO SUPPORT THE CASE THAT THE ALGORITHMIC METHOD IS NOVEL AND INVENTIVE. - - - TO PROMPT YOU TO PROVIDE SUGGESTIONS FOR REAL WORLD APPLICATIONS OF YOUR ALGORITHMIC METHOD WHERE YIOU CAN. - -WHEN PROVIDING EVIDENCE, YOU MAY CITE LINKS TO EXTERNAL DATA SOURCES. - -## UNIQUE ALGORITHM IDENTIFIER (UAI) - -> UAI PLACEHOLDER - ASSIGNED BY TIG PROTOCOL - -## SECTION 1 - -IT IS IMPORTANT THAT THIS SECTION IS COMPLETED IN SUFFICIENT DETAIL TO FULLY DESCRIBE THE METHOD BECAUSE THIS WILL DEFINE THE METHOD THAT IS THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. - -### DESCRIPTION OF ALGORITHMIC METHOD - -PLEASE IDENTIFY WHICH TIG CHALLENGE THE ALGORITHMIC METHOD ADDRESSES. - -> vehicle_routing - -PLEASE DESCRIBE THE ALGORITHMIC METHOD AND THE PROBLEM THAT IT SOLVES. - -> YOUR RESPONSE HERE - -## SECTION 2 - -THE COPYRIGHT IN THE IMPLEMENTATION WILL BE THE SUBJECT OF THE ASSIGNMENT THAT YOU EXECUTE. - -### IMPLEMENTATION OF ALGORITHMIC METHOD - -TO THE EXTENT THAT YOU HAVE IMPLEMENTED THE ALGORITHMIC METHOD IN CODE YOU SHOULD IDENTIFY THE CODE AND SUBMIT IT WITH THIS - -> YOUR RESPONSE HERE - -## SECTION 3 - -### ATTRIBUTION - -PLEASE PROVIDE THE IDENTITY OF THE CREATOR OF THE ALGORITHMIC METHOD (this should be a natural person or legal entity. If an artificial intelligence has been used to assist in the creation of the algorithmic method then the creator is the operator of the artificial intelligence) - -> YOUR RESPONSE HERE - -PLEASE PROVIDE EVIDENCE OF AUTHORSHIP WHERE IT IS AVAILABLE. - -> YOUR RESPONSE HERE - -## SECTION 4 - -### NOVELTY AND INVENTIVENESS - -To support your claim that an algorithmic method is novel and inventive, you should provide evidence that demonstrates both its uniqueness (novelty) and its non-obviousness (inventiveness) in relation to the existing state of the art. - -### Establish the State of the Art - -- **Prior Art Search:** Conduct a comprehensive review of existing methods, algorithms, patents, academic papers, and industry practices to identify prior art in the domain. - - Highlight documents and technologies most closely related to your method. - - > YOUR RESPONSE HERE - - - Show where these existing methods fall short or lack the features your algorithmic method provides. - - > YOUR RESPONSE HERE - -- **Technical Context:** Describe the common approaches and challenges in the field prior to your innovation. - - > YOUR RESPONSE HERE - -### Evidence of Novelty - -- **Unique Features:** List the features, mechanisms, or aspects of your algorithmic method that are absent in prior art. - - > YOUR RESPONSE HERE - -- **New Problem Solved:** Describe how your algorithmic method provides a novel solution to an existing problem. - - > YOUR RESPONSE HERE - -- **Comparative Analysis:** Use a side-by-side comparison table to highlight the differences between your method and similar existing methods, clearly showing what’s new. - - > YOUR RESPONSE HERE - -### Evidence Inventiveness of Inventiveness - -- **Non-Obviousness:** Argue why a skilled person in the field would not have arrived at your method by simply combining existing ideas or extending known techniques. - - Demonstrate that the development involved an inventive step beyond straightforward application of existing principles. - - Unexpected Results: Highlight results or benefits that could not have been predicted based on prior art. - - > YOUR RESPONSE HERE - -- **Advantages:** Provide evidence of how your algorithm outperforms or offers significant advantages over existing methods, such as: - - Increased efficiency. - - Greater accuracy. - - Reduced computational complexity. - - Novel applications. - - > YOUR RESPONSE HERE - -### Supporting Data - -- **Experimental Results:** Include performance benchmarks, simulations, or empirical data that substantiate your claims of novelty and inventiveness. - - > YOUR RESPONSE HERE - -- **Proof of Concept:** If possible, show a working prototype or implementation that validates the method. - - > YOUR RESPONSE HERE - -### Citations and Expert Opinions - -- **Literature Gaps:** Affirm, to the best of your knowledge, the absence of similar solutions in published literature to reinforce your novelty claim. - - > YOUR RESPONSE HERE - -- **Endorsements:** Include reviews or opinions from industry experts, researchers, or peer-reviewed publications that evidences the novelty and impact of your algorithm. - - > YOUR RESPONSE HERE - -## SECTION 5 - -### EVIDENCE TO SUPPORT PATENTABILITY - -- **Development Records:** Please provide documentation of the invention process, including notes, sketches, and software versions, to establish a timeline of your innovation. - - > YOUR RESPONSE HERE - -## SECTION 6 - -### SUGGESTED APPLICATIONS - -- Please provide suggestions for any real world applications of your abstract algorithmic method that occur to you. - - > YOUR RESPONSE HERE - -## SECTION 7 - -### ANY OTHER INFORMATION - -- Please provide any other evidence or argument that you think might help support you request for eligibility for Breakthrough Rewards. - - > YOUR RESPONSE HERE \ No newline at end of file From ec5e09d1c0b1fa6b8c920b0b78e5d7e3f9da4155 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sat, 18 Jan 2025 14:19:26 +0000 Subject: [PATCH 46/49] Add get-batch-data endpoint to master. --- .../master/master/client_manager.py | 39 +++++++++++++++++++ tig-benchmarker/nginx/nginx.conf | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tig-benchmarker/master/master/client_manager.py b/tig-benchmarker/master/master/client_manager.py index d6faf07..8483061 100644 --- a/tig-benchmarker/master/master/client_manager.py +++ b/tig-benchmarker/master/master/client_manager.py @@ -199,6 +199,45 @@ class ClientManager: headers = {"Accept-Encoding": "gzip"} ) + @self.app.get("/get-batch-data/{batch_id}") + async def get_batch_data(batch_id: str): + benchmark_id, batch_idx = batch_id.split("_") + result = get_db_conn().fetch_one( + f""" + SELECT + JSONB_BUILD_OBJECT( + 'id', A.benchmark_id || '_' || A.batch_idx, + 'benchmark_id', A.benchmark_id, + 'start_nonce', A.batch_idx * B.batch_size, + 'num_nonces', LEAST(B.batch_size, B.num_nonces - A.batch_idx * B.batch_size), + 'settings', B.settings, + 'sampled_nonces', NULL, + 'runtime_config', B.runtime_config, + 'download_url', B.download_url, + 'rand_hash', B.rand_hash, + 'batch_size', B.batch_size, + 'batch_idx', A.batch_idx + ) AS batch, + C.merkle_root, + C.solution_nonces, + C.merkle_proofs + FROM root_batch A + INNER JOIN job B + ON A.benchmark_id = '{benchmark_id}' + AND A.batch_idx = {batch_idx} + AND A.benchmark_id = B.benchmark_id + INNER JOIN batch_data C + ON A.benchmark_id = C.benchmark_id + AND A.batch_idx = C.batch_idx + """ + ) + + return JSONResponse( + content=dict(result), + status_code=200, + headers = {"Accept-Encoding": "gzip"} + ) + def start(self): def run(): self.app = FastAPI() diff --git a/tig-benchmarker/nginx/nginx.conf b/tig-benchmarker/nginx/nginx.conf index 26ceb36..e42d919 100644 --- a/tig-benchmarker/nginx/nginx.conf +++ b/tig-benchmarker/nginx/nginx.conf @@ -18,7 +18,7 @@ http { server_name localhost; # Master Service - specific endpoints - location ~ ^/(get-config|stop|update-config|get-jobs|get-latest-data) { + location ~ ^/(get-config|stop|update-config|get-batch-data|get-jobs|get-latest-data) { proxy_pass http://master:3336; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; From 1bb3a70e68f0682180958dba9ca3d87ac060841d Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sat, 18 Jan 2025 16:51:58 +0000 Subject: [PATCH 47/49] Add example batch verification script. --- .../master/master/client_manager.py | 5 +- tig-benchmarker/verify_batch.py | 134 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tig-benchmarker/verify_batch.py diff --git a/tig-benchmarker/master/master/client_manager.py b/tig-benchmarker/master/master/client_manager.py index 8483061..08bb10c 100644 --- a/tig-benchmarker/master/master/client_manager.py +++ b/tig-benchmarker/master/master/client_manager.py @@ -211,7 +211,7 @@ class ClientManager: 'start_nonce', A.batch_idx * B.batch_size, 'num_nonces', LEAST(B.batch_size, B.num_nonces - A.batch_idx * B.batch_size), 'settings', B.settings, - 'sampled_nonces', NULL, + 'sampled_nonces', D.sampled_nonces, 'runtime_config', B.runtime_config, 'download_url', B.download_url, 'rand_hash', B.rand_hash, @@ -229,6 +229,9 @@ class ClientManager: INNER JOIN batch_data C ON A.benchmark_id = C.benchmark_id AND A.batch_idx = C.batch_idx + LEFT JOIN proofs_batch D + ON A.benchmark_id = D.benchmark_id + AND A.batch_idx = D.batch_idx """ ) diff --git a/tig-benchmarker/verify_batch.py b/tig-benchmarker/verify_batch.py new file mode 100644 index 0000000..64a4f78 --- /dev/null +++ b/tig-benchmarker/verify_batch.py @@ -0,0 +1,134 @@ +from common.structs import * +import requests +import json +import random +import os +import subprocess + +print("THIS IS AN EXAMPLE SCRIPT TO VERIFY A BATCH") +TIG_WORKER_PATH = input("Enter path of tig-worker executable: ") +NUM_WORKERS = int(input("Enter number of workers: ")) +if not os.path.exists(TIG_WORKER_PATH): + raise FileNotFound[ERROR](f"tig-worker not found at path: {TIG_WORKER_PATH}") +MASTER_IP = input("Enter Master IP: ") +MASTER_PORT = input("Enter Master Port: ") + +jobs = requests.get(f"http://{MASTER_IP}:{MASTER_PORT}/get-jobs").json() +for i, j in enumerate(jobs): + print(f"{i + 1}) benchmark_id: {j['benchmark_id']}, challenge: {j['challenge']}, algorithm: {j['algorithm']}, status: {j['status']}") +job_idx = int(input("Enter the index of the batch you want to verify: ")) - 1 + +for i, b in enumerate(jobs[job_idx]["batches"]): + print(f"{i + 1}) slave: {b['slave']}, num_solutions: {b['num_solutions']}, status: {b['status']}") +batch_idx = int(input("Enter the index of the batch you want to verify: ")) - 1 + +benchmark_id = jobs[job_idx]["benchmark_id"] +url = f"http://{MASTER_IP}:{MASTER_PORT}/get-batch-data/{benchmark_id}_{batch_idx}" +print(f"Fetching batch data from {url}") +data = requests.get(url).json() + +batch = data['batch'] +merkle_root = data['merkle_root'] +solution_nonces = data['solution_nonces'] +merkle_proofs = data['merkle_proofs'] +if merkle_proofs is not None: + merkle_proofs = {x['leaf']['nonce']: x for x in merkle_proofs} + +if ( + merkle_proofs is None or + len(solution_nonces := set(merkle_proofs) & set(solution_nonces)) == 0 +): + print("No solution data to verify for this batch") +else: + for nonce in solution_nonces: + print(f"Verifying solution for nonce: {nonce}") + cmd = [ + TIG_WORKER_PATH, "verify_solution", + json.dumps(batch['settings'], separators=(',', ':')), + batch["rand_hash"], + str(nonce), + json.dumps(merkle_proofs[nonce]['leaf']['solution'], separators=(',', ':')), + ] + print(f"Running cmd: {' '.join(cmd)}") + ret = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + if ret.returncode == 0: + print(f"[SUCCESS]: {ret.stdout.decode()}") + else: + print(f"[ERROR]: {ret.stderr.decode()}") + +if merkle_root is not None: + download_url = batch["download_url"] + print(f"Downloading WASM from {download_url}") + resp = requests.get(download_url) + if resp.status_code != 200: + raise Exception(f"status {resp.status_code} when downloading WASM: {resp.text}") + wasm_path = f'{batch["settings"]["algorithm_id"]}.wasm' + with open(wasm_path, 'wb') as f: + f.write(resp.content) + print(f"WASM Path: {wasm_path}") + print("") + +if merkle_proofs is None: + print("No merkle proofs to verify for this batch") +else: + for nonce in merkle_proofs: + print(f"Verifying output data for nonce: {nonce}") + cmd = [ + TIG_WORKER_PATH, "compute_solution", + json.dumps(batch['settings'], separators=(',', ':')), + batch["rand_hash"], + str(nonce), + wasm_path, + "--mem", str(batch["runtime_config"]["max_memory"]), + "--fuel", str(batch["runtime_config"]["max_fuel"]), + ] + print(f"Running cmd: {' '.join(cmd)}") + ret = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + out = json.loads(ret.stdout.decode()) + expected = json.dumps(merkle_proofs[nonce]['leaf'], separators=(',', ':'), sort_keys=True) + actual = json.dumps(out, separators=(',', ':'), sort_keys=True) + if expected == actual: + print(f"[SUCCESS]: output data match") + else: + print(f"[ERROR]: output data mismatch") + print(f"Batch data: {expected}") + print(f"Recomputed: {actual}") + print(f"") + +if merkle_root is None: + print("No merkle root to verify for this batch") +else: + print("Verifying merkle root") + cmd = [ + TIG_WORKER_PATH, "compute_batch", + json.dumps(batch["settings"]), + batch["rand_hash"], + str(batch["start_nonce"]), + str(batch["num_nonces"]), + str(batch["batch_size"]), + wasm_path, + "--mem", str(batch["runtime_config"]["max_memory"]), + "--fuel", str(batch["runtime_config"]["max_fuel"]), + "--workers", str(NUM_WORKERS), + ] + print(f"Running cmd: {' '.join(cmd)}") + ret = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + if ret.returncode == 0: + out = json.loads(ret.stdout.decode()) + if out["merkle_root"] == merkle_root: + print(f"[SUCCESS]: merkle root match") + else: + print(f"[ERROR]: merkle root mismatch") + print(f"Batch data: {expected}") + print(f"Recomputed: {actual}") + else: + print(f"[ERROR]: {ret.stderr.decode()}") + +print("") +print("FINISHED") \ No newline at end of file From cb4d8779883716dc21b9118819f47ec4ffd4d097 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 23 Jan 2025 20:14:16 +0800 Subject: [PATCH 48/49] Address deposit edge case. --- tig-protocol/src/contracts/players.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tig-protocol/src/contracts/players.rs b/tig-protocol/src/contracts/players.rs index 7991a8c..407bd28 100644 --- a/tig-protocol/src/contracts/players.rs +++ b/tig-protocol/src/contracts/players.rs @@ -273,7 +273,7 @@ pub(crate) async fn update(cache: &mut AddBlockCache) { for deposit in active_deposit_details.values() { let total_time = PreciseNumber::from(deposit.end_timestamp - deposit.start_timestamp); for i in 0..lock_period_cap { - if round_timestamps[i + 1] <= deposit.start_timestamp { + if i + 1 < lock_period_cap && round_timestamps[i + 1] <= deposit.start_timestamp { continue; } if round_timestamps[i] >= deposit.end_timestamp { From 424d096a858fd5ee55ae9805385921b3026d0c70 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Sun, 26 Jan 2025 10:20:21 +0800 Subject: [PATCH 49/49] Only update adoption for active breakthroughs. --- tig-protocol/src/contracts/algorithms.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tig-protocol/src/contracts/algorithms.rs b/tig-protocol/src/contracts/algorithms.rs index 3cb824f..4a48514 100644 --- a/tig-protocol/src/contracts/algorithms.rs +++ b/tig-protocol/src/contracts/algorithms.rs @@ -216,10 +216,9 @@ pub(crate) async fn update(cache: &mut AddBlockCache) { if let Some(breakthrough_id) = &active_algorithms_details[algorithm_id].breakthrough_id { - active_breakthroughs_block_data - .get_mut(breakthrough_id) - .unwrap() - .adoption += adoption; + if let Some(block_data) = active_breakthroughs_block_data.get_mut(breakthrough_id) { + block_data.adoption += adoption; + } } } }

    FK}(aW># zLeK3yYsb8Ph@j2lUntC8+K}&euHP^E8{N!GK*?|==Ry%ZqTP$TiL~$O{l*U4^&@Cm z8-vEIV38QdHLndz%3fHg9?v*&+Kj&Y3-7&~Y0G)v@3@gKTWduMo6JFeV|}_S>-lv$ zT=bc6X3gj1sFpBOwWuu@cT4OSe_XAnQCTpYIXk-MobZ>Y#M=kcjrTRv-_MkY`aYX* z6po*HbLX4#i0qEr60r}fN5$@W5lm=CXA1VL6jBQ4iXfUrlY1Dvg|q4c{W`{TcPRVz zQ891tB8{7gcByxaO~eX+<({Z#NUag>B0M-QhbN}|)Z%>+e5&v1Y<}BXb!*D9X+#d? z#{RpG#)cQ&uN2YF7ha3~=)U8cQ=p8{#ikX+#RXnhM1L7>P+9{mSv_oXd%m(v#JZY7m=(!Fzy%B+vE7bCfb_;iJdokFgl78$# z7SD=H<|^XX`QCTs8ws8CFL-m)$-0R6se5GZ5-*sRHfEe3^F#}en;%c86u5Uf%ckBZQnbq^xC2&qG~k2weKu7Bg|@B(K$N0upMR(}3z^5%Hx@Bz!0#7dDHI^0+G5`_xy(1xr5EShUa4;d5<87wJ-L6^=STWUd!99DaCj zg4?LDi>2gX*zS~Tb|dq&GY1`1b@m9kajTq+IPs$8)y6BsaVZg^wd;O#@vZAsJF?}y zm*~lC2^+QT^P}ShaHF^~*CaHkp-<+P9 zA!61E``GwZ%{m>#>H3=#k6x85|6b2rWphK}m=`)vKdqY%o8i?y_x)jq7*91)A*TEC z&G-yN&$cu#;$4c_7v~%Abi7U>(hiR_P)rI*>WUmzYQsas$h?HK7`}wp`$(|l1BW!m zgpbn{!FpHD)HSmT9II^XonECT<*3qU%`HOfEwA1?F|yh*k!I%6*CJP=`BC?J`kBr9 za|tBPyKHs0#L6^{pu#n==g0*K*Rp1mk3SfhH8I=Z`jRSPzoC!5nIc6 z_^H~;dSqvNw6%WWnCp>`|B5Rf-*SfiIp&ox2`Vci{ z#GCu0_aSNa#^qP%sz>vr`R<)PefhZ+l2hquMDpsOG%9)BPk8=WfXuk&HN^{(J-9OQ{a7Qau&z z2_gRLJIHLo4T??Nd(!oh??oLx6+W>R8gG!dnJnLVT~_kJR%D039rBjLYOeBM55)-W ze1zOans>jOczLs?Mxu*orB#Y(UqanApDJA9?2Anj+~MolYwW}Ow<`v)y}M9CS)H=H zqOW(|qv;s)>L%O}8DBBM|H4B#g_**abHl5F9-ZFUvFVb>rycj(^^Y{=ksiDm+IFkr ztje=n(`_biwMBLo7d|<^Uedrx@8XZVWhlC{1=5nV{~S7BI;zmnRf-gH06EYbQ;acq zZ*SIFeMQ+MCYNfPpYc){F*~WeonFTq`Er%wuZ-sp5lR%a$F#X#cSmk5+N@|OM&Z#m zl}2?wO%=>f#WmvnXgjY3yOel^oZV$alG(C4o=`E_TE*RnmaOR#4!e7>hAL41!3jez!Pjp#aNCH%%Lv&iB-gM9PbXPX4vv%=#} zRW~Sd3e>F)YA@;dhbb9j`Pzl-D9O$qdJSphM!hrfIpVr(rO=Fptee{l?H8w9$fg=< z`$Sh999RBGX>}oQcHcYObY5bFPBCh*J>2+VsBhZ~ZA~F>7HyqW$^1~o(>+w-);W>$ zb1#pE>}q+s*YVTw!pJ$g#_<}|P@|hd;lpp{C0s3}xi+`djVEirBxioj-_;o|(tJ@x zMuYEg#EB}6pYOL@)3@#~I)ALfXJ;9Y&yJ`)b|mME<^enRejl~FyVavdeMmT@+E&(*#V5q}zVq(aC2llN73%o*d`mP@w`^-p+5vTPfao@k%e@DbR6Fw7ktQQ*{INs# z-zBdVBh}mFc7MI4Cg4Bcs#(uRbbT%vg^s*%`TmNQsKn>?l$uAQ_Gxxyifue+90nhq zq`e=WtS7Cn7`Pl5wvU%pPLPub^LCS6e}B_8e2m>(H@S|5*R2gNOHZG?O&*#>({fhb zGF4_}L^<`&+0-VO5w_&&I>xLFaTv>5S-e5QiEO8JQ;w388Ze}4*D*d4M3pv)jySTV zboNLSbK9oD>yi{ptDT7UoiB>X@!2Poo(R#7JgvAED=*yXUZj?_wOh0#>B0TUgJ(Tf zM5O*waYjo9W zKfTyh{*rj4+oj+jZ|ex19VE-p$kCHvJ;@CD&GU|#n8-#h`&Tw64wdXr*5V`z4T&EL zxX%~CnzxuyFRoOx;U`^f#uDzVT;;mLxMSan<9WiWDqQ45fo`SUHrrx&+O@@vccg_1 zhA=&P*)_WZXNrk7g=K4ch}*#$MweC{R0Jt zI`6v)cJJr=tR?pF=G-^lg?fXgNWYoi^PClt^J(=SrPM(S+T)3q*s4{y>GSLKf7SDD zCft5MA$*{F?S)cr+_q!)(#g$lsy(;eppMvn%Me{-RzcN`cps-sQ$I5~?cBY27oK2l z6L{zXV`d;!U-3p;H4oW&Ffn07{xUJM?Q7IUSAMEupX3XPFABnnKR)FJ);LIRd-Krx zi{5}OZAAg?+U9*i)R&p`$y*MuUUC~t)!|}9B}L!Mgnlojy4z$ccy{DV2u+X*PSUvQ zE}Z#(*kD#%sK2Ujhs`T|QCVR$BGYJCBGxK+M053o%JH>Jgjto*$k%;JZrmgK0;PtRJyPca?Y4 zW&Q4zL5@Ey&NNZnFx@&1?{KLC$WeV^4+XLFekD?oj>E)LzIm@d_D>T6E^}821bMkW zjOBPL*y~e_6|GkoFW7tTi{DOK;E_H~>W^44)|{b3|LlGOBAMT@RM*XKuGuQU zZuTPCfs3PWsW6@FMq>BkAgdpK4OEo=V)IyU~+-yeo{z zKej6z)Y{6+6^04b(7nHo`fcFc_G?b}EHkm~&8fGJNe%=%*lzfmq!R64S*d}>S^k08fd9&X=rI`X=!O|>1gR{>1pW$9kS}$8rquLTH4y$ zI@-G0dfNKh20B3Htd6FRmX5ZLj*hO5o{qkbfv&o)hOVZrmaew0j;^k*p02*Gfu6da zhMp$SOABeB)z#C})7LZ5SJ&6j*VNb2*Vfn3*VWh4*Vi{N08umm!5aY92EdyE^hy=l z#`@=h*tOsp+Q@1JEUXBI85zlp^8+egS`7 z!AdKkt^2W zMWm_@tMP=6EAjygPPV~Hy4Ha^bOeyObmEs05(aocii4T3HqS^FCpt15(qgrE?iZ+B zUQ)Ol8V8g-H-PEVR2+1?%@)}Iw@QWoRC2nILeRO-h>ZjU6hAc3S_2)UTW!Av===eC zXBl8CdK8q5Ohzc!4E}H4hcgy5(f%7JNS7|~`|n&})wW9?$M7}u{qtEFsQv=^>Gk(l z@qfMo$5?^bENLVD3pZ)sCB?@~>AB?NNR52&_=|1aqcf|W)s5iIJ3fYnkLIf(L7X0!o?w^b8KK zM+2DvhtoqLJtU5oU%!TogmAJ5s6|;$jEYhVH*h}#~~!f`2!Rzp#(>^ zY&o_`@E?(7gRICTij>dyRlTa314oLLmcCu}UcLMJ9k1&5>U?tTHq0m0sU!HdjIHRp z*X*se_!j+s-)Pe>apfI%=L)Vej%3Z*9vvOK?bz+(7{;zG)cGK}7yK5wt=?kWx}vLI z%g=n`ubB&p_d1yup5><3b{P(IrA&gYbXk8M@C{V`U*||@9=7f_n%AOq_%Cdn=`3uMU4CfeY0e7?zF<;$7%m30j% z6~ES06K>FKEPx%4O8Cq5C9l_PBp0xvzUmt|Yha_H-u2oW7lUO)sUBosBi7-D*#oW;W3Qz@vG^ymm{2#j-PAD;B4C)tXJj`tlpSZ8 ze6%*%{Eoq9Y1?+O*KF2nEzOF6SX^`&>$SQuV**8la({=oNy2WeHbGlg$kE4%yMxvXlM0yEwpW)S9mcMCl8&ch z((=ARV!4|C++#O>(&WDMo5B!Q=cS=0qOi10bhPX|J#I5YpwzvnR z?G&VKHab(K=u@_sPZU1m(=~Dm{M<$cvX*bnc3j=?AY+MHkWLm_HeqPl45TdM)3BT& zVP{3SCbY&XUe?m7d?&gOJqeMMdE+vxatVSp71FEdt^m=gh~@-Y>&YoYaPt1roEN9hL&`QI}PzDKrcH%p%g*0FcXlD0+d5pKsWLcwU~{l#cU{;<-A2| zQd*ogB%&mHZL@LWzyRTAXT<~DZ(SL*;;q-3IuK)J#M3&50pexfQ0*k)$AMN}!$h{t zdIP_Uk?b4#%7Heqr6VmQA=9=DJNMKVTC z7wNsGmQnEu_JE-FC5N3S(`L8xH1TyPhgJYyKru#mbuU~MxK(DYYFa(T z5sND7Ucfqwd8V+3z;PhTCTgd!q5@hEl%D+bOlc`iQ3Qf(vXMIgCtd+539q;^sL z>{ZWgAE-T4f7{+lS8b_yf^A;47S#h1bN!0W(S;-Da~ zAB=%}z)5fxEPy8101tr=gO7nH!QX$=a3(X;;H@i7#TkB5aRv*OOhhy$z z+#iX#Z|DA9F}LQ-R`fM+n32loFs?c8WVK?PkDtl@a|e4GIVp8 zJT(`sy8^=~e$~3wZyNV9ekSH&4Neo`y)rvFnX6RBCh{jUy31s<<$Q6rlG9DGRL#s~ z@`cP)u~eCznJJa4nUk4(F;gh>{QN{FI|ZgICnshr=O;78$y_0qojjEmg%q8CnQbCs&HDd)~)OVimiv!!b8T)tAB%FGt3Q`E(k~@lFgS3rP9pwRKAd#F64`+DVq#=Op{># zRB5J|na)*9r9!EAvPdaPXNz1&D_7+jD)Ab6)YxGAQGJ+P-TWNmGeZbsz6*Q5&yaAnd*72<=kXxE?3S>D2HmLq|~ak zEdM+|moMq*RH1a1lI6;3rI}oLdL}=UGkKW5uq%wKdY%xO;>ldOG*zi6WTiZLigqpJ zs`=?$wlrI5$Tj7 z%}7k6m5zjuPyvPpZ5?g0%8{NxskjK&O!<8Cre53l1(P7{^V&+D;E}>7T?*A_CODIwYV(g3WRt*?fR&AuG zYjEzUNLK1n83YQXfOfz=qYxDpSw!z6ZKZN@+ZQRkB!@sVXzCv%?E4$tlf;y?WIfq_ zjcGE|7#oL;UR*)s#l#P$Xd3A}XQ3>?V4H0-yB8H}vv?201e2&-fjkpdA)Oy7`QtKO z5qoY5QTGK?<+n*unMULZ^lB~&8;(|3lwxtgrUEUU#+U)FVc$H*3VJB3plU&fMo*4JEG;OTo>qTeviZo#QuwDZjrWrss4vX*9xA5BAWcNvC-k2GVNV zW~QUP9wX!u#)9czWJxciLeYkrRh!C^g@cA+ z#bDzkDkn=wd=J2mRy%>ES`RB7ER6A~5$fsFRrj(EkTg2s&PLkkI^t?`MsL^UlobwF zN=|YH<^nr^-4*5uN3Wnn=13Qop)@+{mgk#Hg~c>{p5#F*ABykO9=e`kr^{R=6UyJp zfeNo@a`yv0vrs(w+q5N<+I#6K6i;ETpYD~P@=!c`uXuW9!^iLAem0yWT<{?9Ko?vF4}pilBj7Rc7vOR5aqySmbKr~MOW-TutKdcO3ivj775oUi z0e%Ml8}zl&C4hb4DsUaR0o($PfKhN9+ykaS6}$)3z#@1Mw83wK-vqK!t1L1l&-=mJ(W{+P#JZVoF_R{ zah0!hA;-W0p!n|sdZzHUtcs^}l%9>FGAIwL|5CXQf`6plc6HD%fl+WTm;)W~1o$fW z2KYXB6YT907L0)$m;+t#C*beEi{KmJ``{;FpvPDR!(bAe11q2d9tMwrFN1G_9|EQE zCf7X|8Q6{F%Q7qpbFN(6W|%}68IL_cbPhZ)1U`F z2tE#;1kZwh0&jr+4R*S~A)q|Qxt<31fhHJnDn7gI?8sU<$OGZB!GfSH(P2Y)j|;FNqF zhrdguYD5e-T99zJGoUdTwQn|~P&!wHUFWtWhL~tTM1NaVl*rjeRfxX>`EqvP8k`t2 zuQu(9onv#o9c#Lfnr#kj-Ux7Z;GLb#;*b-g)=tRrh7G-qXV~%t=O*sR*~}I@n<=a# zZZOzT^;y8MsjT4OWUK_SX$#t^Y+|^yS8qCB$^)MnG>FgktDaI!8$^;6;6o2f6+ zTO00AyA2g1#y!V5XJ@+%QLfyeusM${VwGdvF~Mo`WD90y zAZW#(1!9=`VP{Fosf1!jC~{aol`8U#R!jRV-y%6)hBzuy0^&e!^Xci<)GGyNNb2l`rxwf>t1K0ATJyQ?P_uYW)HDCF;M0u6K8KgNZiKo!baa<8^8Juqv_+kmzzxS{_io~th@b=B(D)4 z8$No+@Yv|#LG~kBOLL>g)1&FJWNNwFUGJP28Q~nN<=#RXRn6x4Aa literal 0 HcmV?d00001 From cf3a14e974ef269377a90099064fc0ca14b40ed9 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:08:15 +0000 Subject: [PATCH 39/49] Compiled vehicle_routing/clarke_wright_super --- .../benchmarker_outbound.rs | 130 ++++++++++++++++++ .../clarke_wright_super/commercial.rs | 130 ++++++++++++++++++ .../clarke_wright_super/inbound.rs | 130 ++++++++++++++++++ .../clarke_wright_super/innovator_outbound.rs | 130 ++++++++++++++++++ .../clarke_wright_super/mod.rs | 4 + .../clarke_wright_super/open_data.rs | 130 ++++++++++++++++++ tig-algorithms/src/vehicle_routing/mod.rs | 3 +- .../src/vehicle_routing/template.rs | 26 +--- .../vehicle_routing/clarke_wright_super.wasm | Bin 0 -> 158220 bytes 9 files changed, 658 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright_super/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright_super/commercial.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright_super/inbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright_super/innovator_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright_super/mod.rs create mode 100644 tig-algorithms/src/vehicle_routing/clarke_wright_super/open_data.rs create mode 100644 tig-algorithms/wasm/vehicle_routing/clarke_wright_super.wasm diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright_super/benchmarker_outbound.rs b/tig-algorithms/src/vehicle_routing/clarke_wright_super/benchmarker_outbound.rs new file mode 100644 index 0000000..cb49161 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright_super/benchmarker_outbound.rs @@ -0,0 +1,130 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n * (n - 1)) / 2); +for i in 1..n { + let d_i0 = d[i][0]; // Cache this value to avoid repeated lookups + for j in (i + 1)..n { + let score = d_i0 + d[0][j] - d[i][j]; + scores.push((score, i, j)); + } +} + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + // Directly get the routes + let (left_route, right_route) = (routes[i].as_ref().unwrap(), routes[j].as_ref().unwrap()); + + // Cache indices and demands + let (left_startnode, left_endnode) = (left_route[0], *left_route.last().unwrap()); + let (right_startnode, right_endnode) = (right_route[0], *right_route.last().unwrap()); + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + // Check constraints + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + // Merge routes + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // Reverse if needed + if left_startnode == i { + left_route.reverse(); + } + if right_endnode == j { + right_route.reverse(); + } + + // Create new route + let mut new_route = left_route; + new_route.extend(right_route); + + // Update routes and demands + let (start, end) = (*new_route.first().unwrap(), *new_route.last().unwrap()); + routes[start] = Some(new_route.clone()); + routes[end] = Some(new_route); + route_demands[start] = merged_demand; + route_demands[end] = merged_demand; + } + + let mut final_routes = Vec::new(); + + for (i, opt_route) in routes.into_iter().enumerate() { + if let Some(mut route) = opt_route { + if route[0] == i { + let mut full_route = Vec::with_capacity(route.len() + 2); + full_route.push(0); + full_route.append(&mut route); + full_route.push(0); + final_routes.push(full_route); + } + } + } + + Ok(Some(Solution { routes: final_routes })) + +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright_super/commercial.rs b/tig-algorithms/src/vehicle_routing/clarke_wright_super/commercial.rs new file mode 100644 index 0000000..257991d --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright_super/commercial.rs @@ -0,0 +1,130 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n * (n - 1)) / 2); +for i in 1..n { + let d_i0 = d[i][0]; // Cache this value to avoid repeated lookups + for j in (i + 1)..n { + let score = d_i0 + d[0][j] - d[i][j]; + scores.push((score, i, j)); + } +} + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + // Directly get the routes + let (left_route, right_route) = (routes[i].as_ref().unwrap(), routes[j].as_ref().unwrap()); + + // Cache indices and demands + let (left_startnode, left_endnode) = (left_route[0], *left_route.last().unwrap()); + let (right_startnode, right_endnode) = (right_route[0], *right_route.last().unwrap()); + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + // Check constraints + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + // Merge routes + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // Reverse if needed + if left_startnode == i { + left_route.reverse(); + } + if right_endnode == j { + right_route.reverse(); + } + + // Create new route + let mut new_route = left_route; + new_route.extend(right_route); + + // Update routes and demands + let (start, end) = (*new_route.first().unwrap(), *new_route.last().unwrap()); + routes[start] = Some(new_route.clone()); + routes[end] = Some(new_route); + route_demands[start] = merged_demand; + route_demands[end] = merged_demand; + } + + let mut final_routes = Vec::new(); + + for (i, opt_route) in routes.into_iter().enumerate() { + if let Some(mut route) = opt_route { + if route[0] == i { + let mut full_route = Vec::with_capacity(route.len() + 2); + full_route.push(0); + full_route.append(&mut route); + full_route.push(0); + final_routes.push(full_route); + } + } + } + + Ok(Some(Solution { routes: final_routes })) + +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright_super/inbound.rs b/tig-algorithms/src/vehicle_routing/clarke_wright_super/inbound.rs new file mode 100644 index 0000000..0523dd0 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright_super/inbound.rs @@ -0,0 +1,130 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n * (n - 1)) / 2); +for i in 1..n { + let d_i0 = d[i][0]; // Cache this value to avoid repeated lookups + for j in (i + 1)..n { + let score = d_i0 + d[0][j] - d[i][j]; + scores.push((score, i, j)); + } +} + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + // Directly get the routes + let (left_route, right_route) = (routes[i].as_ref().unwrap(), routes[j].as_ref().unwrap()); + + // Cache indices and demands + let (left_startnode, left_endnode) = (left_route[0], *left_route.last().unwrap()); + let (right_startnode, right_endnode) = (right_route[0], *right_route.last().unwrap()); + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + // Check constraints + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + // Merge routes + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // Reverse if needed + if left_startnode == i { + left_route.reverse(); + } + if right_endnode == j { + right_route.reverse(); + } + + // Create new route + let mut new_route = left_route; + new_route.extend(right_route); + + // Update routes and demands + let (start, end) = (*new_route.first().unwrap(), *new_route.last().unwrap()); + routes[start] = Some(new_route.clone()); + routes[end] = Some(new_route); + route_demands[start] = merged_demand; + route_demands[end] = merged_demand; + } + + let mut final_routes = Vec::new(); + + for (i, opt_route) in routes.into_iter().enumerate() { + if let Some(mut route) = opt_route { + if route[0] == i { + let mut full_route = Vec::with_capacity(route.len() + 2); + full_route.push(0); + full_route.append(&mut route); + full_route.push(0); + final_routes.push(full_route); + } + } + } + + Ok(Some(Solution { routes: final_routes })) + +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright_super/innovator_outbound.rs b/tig-algorithms/src/vehicle_routing/clarke_wright_super/innovator_outbound.rs new file mode 100644 index 0000000..c87bd18 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright_super/innovator_outbound.rs @@ -0,0 +1,130 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n * (n - 1)) / 2); +for i in 1..n { + let d_i0 = d[i][0]; // Cache this value to avoid repeated lookups + for j in (i + 1)..n { + let score = d_i0 + d[0][j] - d[i][j]; + scores.push((score, i, j)); + } +} + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + // Directly get the routes + let (left_route, right_route) = (routes[i].as_ref().unwrap(), routes[j].as_ref().unwrap()); + + // Cache indices and demands + let (left_startnode, left_endnode) = (left_route[0], *left_route.last().unwrap()); + let (right_startnode, right_endnode) = (right_route[0], *right_route.last().unwrap()); + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + // Check constraints + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + // Merge routes + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // Reverse if needed + if left_startnode == i { + left_route.reverse(); + } + if right_endnode == j { + right_route.reverse(); + } + + // Create new route + let mut new_route = left_route; + new_route.extend(right_route); + + // Update routes and demands + let (start, end) = (*new_route.first().unwrap(), *new_route.last().unwrap()); + routes[start] = Some(new_route.clone()); + routes[end] = Some(new_route); + route_demands[start] = merged_demand; + route_demands[end] = merged_demand; + } + + let mut final_routes = Vec::new(); + + for (i, opt_route) in routes.into_iter().enumerate() { + if let Some(mut route) = opt_route { + if route[0] == i { + let mut full_route = Vec::with_capacity(route.len() + 2); + full_route.push(0); + full_route.append(&mut route); + full_route.push(0); + final_routes.push(full_route); + } + } + } + + Ok(Some(Solution { routes: final_routes })) + +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright_super/mod.rs b/tig-algorithms/src/vehicle_routing/clarke_wright_super/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright_super/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/clarke_wright_super/open_data.rs b/tig-algorithms/src/vehicle_routing/clarke_wright_super/open_data.rs new file mode 100644 index 0000000..5d71dbb --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/clarke_wright_super/open_data.rs @@ -0,0 +1,130 @@ +/*! +Copyright 2024 OvErLoDe + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n * (n - 1)) / 2); +for i in 1..n { + let d_i0 = d[i][0]; // Cache this value to avoid repeated lookups + for j in (i + 1)..n { + let score = d_i0 + d[0][j] - d[i][j]; + scores.push((score, i, j)); + } +} + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); // Sort in descending order by score + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + // Directly get the routes + let (left_route, right_route) = (routes[i].as_ref().unwrap(), routes[j].as_ref().unwrap()); + + // Cache indices and demands + let (left_startnode, left_endnode) = (left_route[0], *left_route.last().unwrap()); + let (right_startnode, right_endnode) = (right_route[0], *right_route.last().unwrap()); + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + // Check constraints + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + // Merge routes + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // Reverse if needed + if left_startnode == i { + left_route.reverse(); + } + if right_endnode == j { + right_route.reverse(); + } + + // Create new route + let mut new_route = left_route; + new_route.extend(right_route); + + // Update routes and demands + let (start, end) = (*new_route.first().unwrap(), *new_route.last().unwrap()); + routes[start] = Some(new_route.clone()); + routes[end] = Some(new_route); + route_demands[start] = merged_demand; + route_demands[end] = merged_demand; + } + + let mut final_routes = Vec::new(); + + for (i, opt_route) in routes.into_iter().enumerate() { + if let Some(mut route) = opt_route { + if route[0] == i { + let mut full_route = Vec::with_capacity(route.len() + 2); + full_route.push(0); + full_route.append(&mut route); + full_route.push(0); + final_routes.push(full_route); + } + } + } + + Ok(Some(Solution { routes: final_routes })) + +} +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 930b4fb..6103311 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -68,7 +68,8 @@ // c002_a035 -// c002_a036 +pub mod clarke_wright_super; +pub use clarke_wright_super as c002_a036; // c002_a037 diff --git a/tig-algorithms/src/vehicle_routing/template.rs b/tig-algorithms/src/vehicle_routing/template.rs index 02a7cab..b5c872b 100644 --- a/tig-algorithms/src/vehicle_routing/template.rs +++ b/tig-algorithms/src/vehicle_routing/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vehicle_routing::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vehicle_routing/clarke_wright_super.wasm b/tig-algorithms/wasm/vehicle_routing/clarke_wright_super.wasm new file mode 100644 index 0000000000000000000000000000000000000000..929b53de3bcdb6430840dd5a65dc50604d8141b4 GIT binary patch literal 158220 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�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%0Ayb&JcB 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 literal 0 HcmV?d00001 From 08f39b762cea1117f4107ad4945393026aef00d0 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:08:48 +0000 Subject: [PATCH 40/49] Compiled vehicle_routing/cw_heuristic --- .../cw_heuristic/benchmarker_outbound.rs | 133 ++++++++++++++++++ .../cw_heuristic/commercial.rs | 133 ++++++++++++++++++ .../vehicle_routing/cw_heuristic/inbound.rs | 133 ++++++++++++++++++ .../cw_heuristic/innovator_outbound.rs | 133 ++++++++++++++++++ .../src/vehicle_routing/cw_heuristic/mod.rs | 4 + .../vehicle_routing/cw_heuristic/open_data.rs | 133 ++++++++++++++++++ tig-algorithms/src/vehicle_routing/mod.rs | 3 +- .../src/vehicle_routing/template.rs | 26 +--- .../wasm/vehicle_routing/cw_heuristic.wasm | Bin 0 -> 158227 bytes 9 files changed, 673 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vehicle_routing/cw_heuristic/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/cw_heuristic/commercial.rs create mode 100644 tig-algorithms/src/vehicle_routing/cw_heuristic/inbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/cw_heuristic/innovator_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/cw_heuristic/mod.rs create mode 100644 tig-algorithms/src/vehicle_routing/cw_heuristic/open_data.rs create mode 100644 tig-algorithms/wasm/vehicle_routing/cw_heuristic.wasm diff --git a/tig-algorithms/src/vehicle_routing/cw_heuristic/benchmarker_outbound.rs b/tig-algorithms/src/vehicle_routing/cw_heuristic/benchmarker_outbound.rs new file mode 100644 index 0000000..5ddaecf --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/cw_heuristic/benchmarker_outbound.rs @@ -0,0 +1,133 @@ +/*! +Copyright 2024 Just + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n-1)*(n-2)/2); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + let routes = routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(); + + Ok(Some(Solution { + routes + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/cw_heuristic/commercial.rs b/tig-algorithms/src/vehicle_routing/cw_heuristic/commercial.rs new file mode 100644 index 0000000..334851d --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/cw_heuristic/commercial.rs @@ -0,0 +1,133 @@ +/*! +Copyright 2024 Just + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n-1)*(n-2)/2); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + let routes = routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(); + + Ok(Some(Solution { + routes + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/cw_heuristic/inbound.rs b/tig-algorithms/src/vehicle_routing/cw_heuristic/inbound.rs new file mode 100644 index 0000000..7dc1630 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/cw_heuristic/inbound.rs @@ -0,0 +1,133 @@ +/*! +Copyright 2024 Just + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n-1)*(n-2)/2); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + let routes = routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(); + + Ok(Some(Solution { + routes + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/cw_heuristic/innovator_outbound.rs b/tig-algorithms/src/vehicle_routing/cw_heuristic/innovator_outbound.rs new file mode 100644 index 0000000..3087299 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/cw_heuristic/innovator_outbound.rs @@ -0,0 +1,133 @@ +/*! +Copyright 2024 Just + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n-1)*(n-2)/2); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + let routes = routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(); + + Ok(Some(Solution { + routes + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/cw_heuristic/mod.rs b/tig-algorithms/src/vehicle_routing/cw_heuristic/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/cw_heuristic/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/cw_heuristic/open_data.rs b/tig-algorithms/src/vehicle_routing/cw_heuristic/open_data.rs new file mode 100644 index 0000000..d2545b8 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/cw_heuristic/open_data.rs @@ -0,0 +1,133 @@ +/*! +Copyright 2024 Just + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use tig_challenges::vehicle_routing::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let d = &challenge.distance_matrix; + let c = challenge.max_capacity; + let n = challenge.difficulty.num_nodes; + + let max_dist: f32 = challenge.distance_matrix[0].iter().sum::() as f32; + let p = challenge.max_total_distance as f32 / max_dist; + if p < 0.57 { + return Ok(None) + } + + // Clarke-Wright heuristic for node pairs based on their distances to depot + // vs distance between each other + let mut scores: Vec<(i32, usize, usize)> = Vec::with_capacity((n-1)*(n-2)/2); + for i in 1..n { + for j in (i + 1)..n { + scores.push((d[i][0] + d[0][j] - d[i][j], i, j)); + } + } + + scores.sort_unstable_by(|a, b| b.0.cmp(&a.0)); + + // Create a route for every node + let mut routes: Vec>> = (0..n).map(|i| Some(vec![i])).collect(); + routes[0] = None; + let mut route_demands: Vec = challenge.demands.clone(); + + // Iterate through node pairs, starting from greatest score + for (s, i, j) in scores { + // Stop if score is negative + if s < 0 { + break; + } + + // Skip if joining the nodes is not possible + if routes[i].is_none() || routes[j].is_none() { + continue; + } + + let left_route = routes[i].as_ref().unwrap(); + let right_route = routes[j].as_ref().unwrap(); + let mut left_startnode = left_route[0]; + let right_startnode = right_route[0]; + let left_endnode = left_route[left_route.len() - 1]; + let mut right_endnode = right_route[right_route.len() - 1]; + let merged_demand = route_demands[left_startnode] + route_demands[right_startnode]; + + if left_startnode == right_startnode || merged_demand > c { + continue; + } + + let mut left_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + routes[left_startnode] = None; + routes[right_startnode] = None; + routes[left_endnode] = None; + routes[right_endnode] = None; + + // reverse it + if left_startnode == i { + left_route.reverse(); + left_startnode = left_endnode; + } + if right_endnode == j { + right_route.reverse(); + right_endnode = right_startnode; + } + + let mut new_route = left_route; + new_route.extend(right_route); + + // Only the start and end nodes of routes are kept + routes[left_startnode] = Some(new_route.clone()); + routes[right_endnode] = Some(new_route); + route_demands[left_startnode] = merged_demand; + route_demands[right_endnode] = merged_demand; + } + + let routes = routes + .into_iter() + .enumerate() + .filter(|(i, x)| x.as_ref().is_some_and(|x| x[0] == *i)) + .map(|(_, mut x)| { + let mut route = vec![0]; + route.append(x.as_mut().unwrap()); + route.push(0); + route + }) + .collect(); + + Ok(Some(Solution { + routes + })) +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 930b4fb..bbe2d90 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -66,7 +66,8 @@ // c002_a034 -// c002_a035 +pub mod cw_heuristic; +pub use cw_heuristic as c002_a035; // c002_a036 diff --git a/tig-algorithms/src/vehicle_routing/template.rs b/tig-algorithms/src/vehicle_routing/template.rs index 02a7cab..b5c872b 100644 --- a/tig-algorithms/src/vehicle_routing/template.rs +++ b/tig-algorithms/src/vehicle_routing/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vehicle_routing::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vehicle_routing/cw_heuristic.wasm b/tig-algorithms/wasm/vehicle_routing/cw_heuristic.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0294ce07379416211247dc59b94230b0e961639e GIT binary patch literal 158227 zcmeFa3z%NVS?76f-~H?EucfvvtL3Q9QQK}=QY=G|9VaBJW_^(r$4=t)gzW6Z%!Va& z8xzS|5hg9E9m_^Z40yoI7%)T$Nn{|JaY(|1;1DU8nE)nuH(}!eFPOy3dV$G$X2Ps@ zFlc}Ox6b)4eX*>NEc-k&*5m%psZ*!wt+(EK?^P%Io;!aaj-n{OKc2cRJ#yqoeB`#o z{>DclT}4(J-Ino;n^+aiof4_fxdk;8`%N4GV?wOY&$ zhl(jr)EBo=`s?wp#c7sx(|Tn;<&|SJ5JN2`Y)~NDz4Y#RR0=DmUWu^pVXVR zEKO5h%xV#T<5t$-p$?lKk$S5e>jP< z@4w^w4@Cbe?tJeZ2loH)Ti@}6-+#w}=u>g)NF4dN^Va5N6OA8>e?0!KAG$w2@6!K% z`}e)`AOFzgczgYMufOTG?^*c4*MIN-=)Y^?N8{!{{E7IV#vh668~t~^J>UKPi__~i z9da+SImuKVZl&2Rqhx7U^KiVlCGt-s3q?8;CA<6oj`Xhv zWxHK8oAem!($YKcED~RH{J&?~#|z`NIH{#s)N%1_5*7NRt7Z```1HnXw~JlmnGY+> zRZaKvyOO3#G8fJJul@PQzFOZEMcwhxNKTar7a9Sk_kZA2G#f=U z1aR5B{{`-8=4$TMXH9)pU7Q8P60om7^9R2y!0dg4xjR;ZSPMuJTA0*@@gz>7j-L|f zThnIIn!EoC(Jc(1mAC0L-R)BUa5C@W`GV0eID5WSwCA4tSbEsC7Eb{GnKS7=|4ej8 zk@@(5f8>$V(fs96^eVcKe6rv&|Im@!T{0i)EvpgJD1Eo)&Yb@8<+tXMYv(N&jYS;* z9zfM`ow?WkNYT2-wGNGf*!}wivC~O*yJ#C|n{<=k1JWix19WGq|DwC+M0fo6{F&&D zV{ymTL>2sL7I8^rH3tTu3Kiz6p?f1TN;1$sATnmK-+1Hhyv^M!j-4>%MQ<9bbz2=x zYBR6tC(C)6-U`jLl^4miKqwN6>Cb8=yrKK~qAs%5YnrD&66rCr8ue>_=k0#yLSBOs zXt4)L(IE3wyJjq5YIn}lks2!!{X}c`G2Fv;(Kb!fZa3<%w(3JK*9QRB1t9l^5CgA1 z@BcS}*X@SU7B-Z!+j#IMs07sEH$i21vtw`0=JkM}LPII0%tav~*AW0bRGQY-$40HTx_L7oH3ooN{u8tmtu=090?I1exI^F2Ka--(9}SPHZ>}##6=(gN=#fB0e}!V@Dcv)*1x&fwV~596Z?vI z`eu_HKH1~ibI}|314+B$AkCwyHx1)W?uA3P=i;T+nYZ?{eAj#39+xuk9YsYx)Nqfy6c3fHh zd_2$7AG}ldMsHi#wfer%3Rlt-Fk8nBsKiU{_Q6>-|p8RT+n3sGtoYOD!Q|X zd%vm7tW!NA6jyx_y|^eqRq-U#H?hUk}HjuScw`>Z@$=+0bHu|4D186>dIn z4LuhcY76KmX{ZAfPltxut|OpBLtE3&$GHvd!8m7RThr&`oN4#TO(I$U+MC8QaCO8b zC+`LJyKlZ`EbFqyJ8t3-^&&TM^S&I?RT+q+@7SMyHoM)g6YA5w3jynif9|PMk&R8P z9Mrc1w2(8sJYPNRy0ZLP%0Y7&g~9dC#bTVIr7(ywH?Bc6T)Y7CfBz%D6AAg^G%Jul zD=Z85MbEH57JA+=FO-JsraIec;8|ZALSIfLuI#Er{F4Aib0x01=~g#asgb9vL4wKa znQ4liup(aS4x|05fc0iT`rE-}!=HcCSmGnsImn zr$7E@kMsZQ4kK~H<>Np7?QceLU?Db`FV-2;!h#xovpqNTW_#`v)(CIjqBq04Cz(^d zEkvQ=${8C3Hnlfo(1{*$4*D3CKP*?*MH`|RP0)FuRV6crwm3!1IT;E zU>^{X7^si(C^>@8^Gvi>tHSe$3o1=Ag zKvH|It+&VZ)44S}GK9XOOQEq5tI{dTNyfa9>E10l?XYc;lAD?gc z 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�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 literal 0 HcmV?d00001 From 72e4cb5ba1044ce5e45160d87f072997847c294e Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:21:56 +0000 Subject: [PATCH 41/49] Compiled satisfiability/sat_adaptive --- tig-algorithms/src/satisfiability/mod.rs | 3 +- .../sat_adaptive/benchmarker_outbound.rs | 315 ++++++++++++++++++ .../satisfiability/sat_adaptive/commercial.rs | 315 ++++++++++++++++++ .../satisfiability/sat_adaptive/inbound.rs | 315 ++++++++++++++++++ .../sat_adaptive/innovator_outbound.rs | 315 ++++++++++++++++++ .../src/satisfiability/sat_adaptive/mod.rs | 4 + .../satisfiability/sat_adaptive/open_data.rs | 315 ++++++++++++++++++ tig-algorithms/src/satisfiability/template.rs | 26 +- .../wasm/satisfiability/sat_adaptive.wasm | Bin 0 -> 157768 bytes 9 files changed, 1583 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/satisfiability/sat_adaptive/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_adaptive/commercial.rs create mode 100644 tig-algorithms/src/satisfiability/sat_adaptive/inbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_adaptive/innovator_outbound.rs create mode 100644 tig-algorithms/src/satisfiability/sat_adaptive/mod.rs create mode 100644 tig-algorithms/src/satisfiability/sat_adaptive/open_data.rs create mode 100644 tig-algorithms/wasm/satisfiability/sat_adaptive.wasm diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805..f295f6c 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -80,7 +80,8 @@ // c001_a041 -// c001_a042 +pub mod sat_adaptive; +pub use sat_adaptive as c001_a042; // c001_a043 diff --git a/tig-algorithms/src/satisfiability/sat_adaptive/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/sat_adaptive/benchmarker_outbound.rs new file mode 100644 index 0000000..f3dc67c --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_adaptive/benchmarker_outbound.rs @@ -0,0 +1,315 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let base_prob = 0.52; + let mut current_prob = base_prob; + let check_interval = 50; + let mut last_check_residual = residual_.len(); + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + if rounds % check_interval == 0 { + let progress = last_check_residual as i64 - residual_.len() as i64; + let progress_ratio = progress as f64 / last_check_residual as f64; + + let progress_threshold = 0.2 + 0.1 * f64::min(1.0, (clauses_ratio - 410.0) / 15.0); + + if progress <= 0 { + let prob_adjustment = 0.025 * (-progress as f64 / last_check_residual as f64).min(1.0); + current_prob = (current_prob + prob_adjustment).min(0.9); + } else if progress_ratio > progress_threshold { + current_prob = base_prob; + } else { + current_prob = current_prob * 0.8 + base_prob * 0.2; + } + + last_check_residual = residual_.len(); + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(current_prob) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_adaptive/commercial.rs b/tig-algorithms/src/satisfiability/sat_adaptive/commercial.rs new file mode 100644 index 0000000..324e53f --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_adaptive/commercial.rs @@ -0,0 +1,315 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let base_prob = 0.52; + let mut current_prob = base_prob; + let check_interval = 50; + let mut last_check_residual = residual_.len(); + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + if rounds % check_interval == 0 { + let progress = last_check_residual as i64 - residual_.len() as i64; + let progress_ratio = progress as f64 / last_check_residual as f64; + + let progress_threshold = 0.2 + 0.1 * f64::min(1.0, (clauses_ratio - 410.0) / 15.0); + + if progress <= 0 { + let prob_adjustment = 0.025 * (-progress as f64 / last_check_residual as f64).min(1.0); + current_prob = (current_prob + prob_adjustment).min(0.9); + } else if progress_ratio > progress_threshold { + current_prob = base_prob; + } else { + current_prob = current_prob * 0.8 + base_prob * 0.2; + } + + last_check_residual = residual_.len(); + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(current_prob) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_adaptive/inbound.rs b/tig-algorithms/src/satisfiability/sat_adaptive/inbound.rs new file mode 100644 index 0000000..885501f --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_adaptive/inbound.rs @@ -0,0 +1,315 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let base_prob = 0.52; + let mut current_prob = base_prob; + let check_interval = 50; + let mut last_check_residual = residual_.len(); + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + if rounds % check_interval == 0 { + let progress = last_check_residual as i64 - residual_.len() as i64; + let progress_ratio = progress as f64 / last_check_residual as f64; + + let progress_threshold = 0.2 + 0.1 * f64::min(1.0, (clauses_ratio - 410.0) / 15.0); + + if progress <= 0 { + let prob_adjustment = 0.025 * (-progress as f64 / last_check_residual as f64).min(1.0); + current_prob = (current_prob + prob_adjustment).min(0.9); + } else if progress_ratio > progress_threshold { + current_prob = base_prob; + } else { + current_prob = current_prob * 0.8 + base_prob * 0.2; + } + + last_check_residual = residual_.len(); + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(current_prob) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_adaptive/innovator_outbound.rs b/tig-algorithms/src/satisfiability/sat_adaptive/innovator_outbound.rs new file mode 100644 index 0000000..38b12c6 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_adaptive/innovator_outbound.rs @@ -0,0 +1,315 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let base_prob = 0.52; + let mut current_prob = base_prob; + let check_interval = 50; + let mut last_check_residual = residual_.len(); + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + if rounds % check_interval == 0 { + let progress = last_check_residual as i64 - residual_.len() as i64; + let progress_ratio = progress as f64 / last_check_residual as f64; + + let progress_threshold = 0.2 + 0.1 * f64::min(1.0, (clauses_ratio - 410.0) / 15.0); + + if progress <= 0 { + let prob_adjustment = 0.025 * (-progress as f64 / last_check_residual as f64).min(1.0); + current_prob = (current_prob + prob_adjustment).min(0.9); + } else if progress_ratio > progress_threshold { + current_prob = base_prob; + } else { + current_prob = current_prob * 0.8 + base_prob * 0.2; + } + + last_check_residual = residual_.len(); + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(current_prob) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/sat_adaptive/mod.rs b/tig-algorithms/src/satisfiability/sat_adaptive/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_adaptive/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/sat_adaptive/open_data.rs b/tig-algorithms/src/satisfiability/sat_adaptive/open_data.rs new file mode 100644 index 0000000..4a4711b --- /dev/null +++ b/tig-algorithms/src/satisfiability/sat_adaptive/open_data.rs @@ -0,0 +1,315 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use std::collections::HashMap; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut rng = SmallRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap()) as u64); + + let mut p_single = vec![false; challenge.difficulty.num_variables]; + let mut n_single = vec![false; challenge.difficulty.num_variables]; + + let mut clauses_ = challenge.clauses.clone(); + let mut clauses: Vec> = Vec::with_capacity(clauses_.len()); + + let mut rounds = 0; + + let mut dead = false; + + while !(dead) { + let mut done = true; + for c in &clauses_ { + let mut c_: Vec = Vec::with_capacity(c.len()); // Preallocate with capacity + let mut skip = false; + for (i, l) in c.iter().enumerate() { + if (p_single[(l.abs() - 1) as usize] && *l > 0) + || (n_single[(l.abs() - 1) as usize] && *l < 0) + || c[(i + 1)..].contains(&-l) + { + skip = true; + break; + } else if p_single[(l.abs() - 1) as usize] + || n_single[(l.abs() - 1) as usize] + || c[(i + 1)..].contains(&l) + { + done = false; + continue; + } else { + c_.push(*l); + } + } + if skip { + done = false; + continue; + }; + match c_[..] { + [l] => { + done = false; + if l > 0 { + if n_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + p_single[(l.abs() - 1) as usize] = true; + } + } else { + if p_single[(l.abs() - 1) as usize] { + dead = true; + break; + } else { + n_single[(l.abs() - 1) as usize] = true; + } + } + } + [] => { + dead = true; + break; + } + _ => { + clauses.push(c_); + } + } + } + if done { + break; + } else { + clauses_ = clauses; + clauses = Vec::with_capacity(clauses_.len()); + } + } + + if dead { + return Ok(None); + } + + let num_variables = challenge.difficulty.num_variables; + let num_clauses = clauses.len(); + + let mut p_clauses: Vec> = vec![Vec::new(); num_variables]; + let mut n_clauses: Vec> = vec![Vec::new(); num_variables]; + + // Preallocate capacity for p_clauses and n_clauses + for c in &clauses { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + if p_clauses[var].capacity() == 0 { + p_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } else { + if n_clauses[var].capacity() == 0 { + n_clauses[var] = Vec::with_capacity(clauses.len() / num_variables + 1); + } + } + } + } + + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 { + p_clauses[var].push(i); + } else { + n_clauses[var].push(i); + } + } + } + + let mut variables = vec![false; num_variables]; + for v in 0..num_variables { + let num_p = p_clauses[v].len(); + let num_n = n_clauses[v].len(); + + let nad = 1.28; + let mut vad = nad + 1.0; + if num_n > 0 { + vad = num_p as f32 / num_n as f32; + } + + if vad <= nad { + variables[v] = false; + } else { + let prob = num_p as f64 / (num_p + num_n).max(1) as f64; + variables[v] = rng.gen_bool(prob) + } + } + + let mut num_good_so_far: Vec = vec![0; num_clauses]; + for (i, &ref c) in clauses.iter().enumerate() { + for &l in c { + let var = (l.abs() - 1) as usize; + if l > 0 && variables[var] { + num_good_so_far[i] += 1 + } else if l < 0 && !variables[var] { + num_good_so_far[i] += 1 + } + } + } + + + let mut residual_ = Vec::with_capacity(num_clauses); + let mut residual_indices = vec![None; num_clauses]; + + for (i, &num_good) in num_good_so_far.iter().enumerate() { + if num_good == 0 { + residual_.push(i); + residual_indices[i] = Some(residual_.len() - 1); + } + } + + let base_prob = 0.52; + let mut current_prob = base_prob; + let check_interval = 50; + let mut last_check_residual = residual_.len(); + + let clauses_ratio = challenge.difficulty.clauses_to_variables_percent as f64; + let num_vars = challenge.difficulty.num_variables as f64; + let max_fuel = 2000000000.0; + let base_fuel = (2000.0 + 40.0 * clauses_ratio) * num_vars; + let flip_fuel = 350.0 + 0.9 * clauses_ratio; + let max_num_rounds = ((max_fuel - base_fuel) / flip_fuel) as usize; + loop { + if !residual_.is_empty() { + + let rand_val = rng.gen::(); + + let i = residual_[rand_val % residual_.len()]; + let mut min_sad = clauses.len(); + let mut v_min_sad = usize::MAX; + let c = &mut clauses[i]; + + if c.len() > 1 { + let random_index = rand_val % c.len(); + c.swap(0, random_index); + } + for &l in c.iter() { + let abs_l = l.abs() as usize - 1; + let clauses_to_check = if variables[abs_l] { &p_clauses[abs_l] } else { &n_clauses[abs_l] }; + + let mut sad = 0; + for &c in clauses_to_check { + if num_good_so_far[c] == 1 { + sad += 1; + } + } + + if sad < min_sad { + min_sad = sad; + v_min_sad = abs_l; + } + } + + if rounds % check_interval == 0 { + let progress = last_check_residual as i64 - residual_.len() as i64; + let progress_ratio = progress as f64 / last_check_residual as f64; + + let progress_threshold = 0.2 + 0.1 * f64::min(1.0, (clauses_ratio - 410.0) / 15.0); + + if progress <= 0 { + let prob_adjustment = 0.025 * (-progress as f64 / last_check_residual as f64).min(1.0); + current_prob = (current_prob + prob_adjustment).min(0.9); + } else if progress_ratio > progress_threshold { + current_prob = base_prob; + } else { + current_prob = current_prob * 0.8 + base_prob * 0.2; + } + + last_check_residual = residual_.len(); + } + + let v = if min_sad == 0 { + v_min_sad + } else if rng.gen_bool(current_prob) { + c[0].abs() as usize - 1 + } else { + v_min_sad + }; + + if variables[v] { + for &c in &n_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + for &c in &p_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + } else { + for &c in &n_clauses[v] { + if num_good_so_far[c] == 1 { + residual_.push(c); + residual_indices[c] = Some(residual_.len() - 1); + } + num_good_so_far[c] -= 1; + } + + for &c in &p_clauses[v] { + num_good_so_far[c] += 1; + if num_good_so_far[c] == 1 { + let i = residual_indices[c].take().unwrap(); + let last = residual_.pop().unwrap(); + if i < residual_.len() { + residual_[i] = last; + residual_indices[last] = Some(i); + } + } + } + } + + variables[v] = !variables[v]; + } else { + break; + } + rounds += 1; + if rounds >= max_num_rounds { + return Ok(None); + } + } + return Ok(Some(Solution { variables })); +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; diff --git a/tig-algorithms/src/satisfiability/template.rs b/tig-algorithms/src/satisfiability/template.rs index c2d7153..f11c4cd 100644 --- a/tig-algorithms/src/satisfiability/template.rs +++ b/tig-algorithms/src/satisfiability/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::satisfiability::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/satisfiability/sat_adaptive.wasm b/tig-algorithms/wasm/satisfiability/sat_adaptive.wasm new file mode 100644 index 0000000000000000000000000000000000000000..98eae0e0a370dba84b67609054d0f590560e9801 GIT binary patch literal 157768 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

j@qNY8fLvu=7iC%%Jce}pE+%7_cx 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 literal 0 HcmV?d00001 From 4c06bc5ea936207b1b0faac03a742a157c6d2cb0 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Thu, 16 Jan 2025 21:07:37 +0000 Subject: [PATCH 37/49] Compiled vehicle_routing/advanced_routing --- .../advanced_routing/benchmarker_outbound.rs | 234 ++++++++++++++++++ .../advanced_routing/commercial.rs | 234 ++++++++++++++++++ .../advanced_routing/inbound.rs | 234 ++++++++++++++++++ .../advanced_routing/innovator_outbound.rs | 234 ++++++++++++++++++ .../vehicle_routing/advanced_routing/mod.rs | 4 + .../advanced_routing/open_data.rs | 234 ++++++++++++++++++ tig-algorithms/src/vehicle_routing/mod.rs | 3 +- .../src/vehicle_routing/template.rs | 26 +- .../vehicle_routing/advanced_routing.wasm | Bin 0 -> 171363 bytes 9 files changed, 1178 insertions(+), 25 deletions(-) create mode 100644 tig-algorithms/src/vehicle_routing/advanced_routing/benchmarker_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_routing/commercial.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_routing/inbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_routing/innovator_outbound.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_routing/mod.rs create mode 100644 tig-algorithms/src/vehicle_routing/advanced_routing/open_data.rs create mode 100644 tig-algorithms/wasm/vehicle_routing/advanced_routing.wasm diff --git a/tig-algorithms/src/vehicle_routing/advanced_routing/benchmarker_outbound.rs b/tig-algorithms/src/vehicle_routing/advanced_routing/benchmarker_outbound.rs new file mode 100644 index 0000000..8d7001f --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_routing/benchmarker_outbound.rs @@ -0,0 +1,234 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Benchmarker Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_routing/commercial.rs b/tig-algorithms/src/vehicle_routing/advanced_routing/commercial.rs new file mode 100644 index 0000000..bb1110f --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_routing/commercial.rs @@ -0,0 +1,234 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Commercial License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_routing/inbound.rs b/tig-algorithms/src/vehicle_routing/advanced_routing/inbound.rs new file mode 100644 index 0000000..2b8f8e1 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_routing/inbound.rs @@ -0,0 +1,234 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later +version (the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_routing/innovator_outbound.rs b/tig-algorithms/src/vehicle_routing/advanced_routing/innovator_outbound.rs new file mode 100644 index 0000000..e768ec0 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_routing/innovator_outbound.rs @@ -0,0 +1,234 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Innovator Outbound Game License v1.0 (the "License"); you +may not use this file except in compliance with the License. You may obtain a copy +of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_routing/mod.rs b/tig-algorithms/src/vehicle_routing/advanced_routing/mod.rs new file mode 100644 index 0000000..fcec967 --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_routing/mod.rs @@ -0,0 +1,4 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; +#[cfg(feature = "cuda")] +pub use benchmarker_outbound::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/advanced_routing/open_data.rs b/tig-algorithms/src/vehicle_routing/advanced_routing/open_data.rs new file mode 100644 index 0000000..41bdafe --- /dev/null +++ b/tig-algorithms/src/vehicle_routing/advanced_routing/open_data.rs @@ -0,0 +1,234 @@ +/*! +Copyright 2024 syebastian + +Licensed under the TIG Open Data License v1.0 or (at your option) any later version +(the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/tig-foundation/tig-monorepo/tree/main/docs/licenses + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific +language governing permissions and limitations under the License. +*/ + +use rand::{rngs::{SmallRng, StdRng}, Rng, SeedableRng}; +use tig_challenges::vehicle_routing::{Challenge, Solution}; + +pub fn solve_challenge(challenge: &Challenge) -> anyhow::Result> { + let mut best_solution: Option = None; + let mut best_cost = std::i32::MAX; + + const INITIAL_TEMPERATURE: f32 = 2.0; + const COOLING_RATE: f32 = 0.995; + const ITERATIONS_PER_TEMPERATURE: usize = 2; + + let num_nodes = challenge.difficulty.num_nodes; + + let mut current_params = vec![1.0; num_nodes]; + let mut savings_list = create_initial_savings_list(challenge); + recompute_and_sort_savings(&mut savings_list, ¤t_params, challenge); + + let mut current_solution = create_solution(challenge, ¤t_params, &savings_list); + let mut current_cost = calculate_solution_cost(¤t_solution, &challenge.distance_matrix); + + if current_cost <= challenge.max_total_distance { + return Ok(Some(current_solution)); + } + + if (current_cost as f32 * 0.96) > challenge.max_total_distance as f32 { + return Ok(None); + } + + let mut temperature = INITIAL_TEMPERATURE; + let mut rng = StdRng::seed_from_u64(u64::from_le_bytes(challenge.seed[..8].try_into().unwrap())); + + while temperature > 1.0 { + for _ in 0..ITERATIONS_PER_TEMPERATURE { + let neighbor_params = generate_neighbor(¤t_params, &mut rng); + recompute_and_sort_savings(&mut savings_list, &neighbor_params, challenge); + let mut neighbor_solution = create_solution(challenge, &neighbor_params, &savings_list); + apply_local_search_until_no_improvement(&mut neighbor_solution, &challenge.distance_matrix); + let neighbor_cost = calculate_solution_cost(&neighbor_solution, &challenge.distance_matrix); + + let delta = neighbor_cost as f32 - current_cost as f32; + if delta < 0.0 || rng.gen::() < (-delta / temperature).exp() { + current_params = neighbor_params; + current_cost = neighbor_cost; + current_solution = neighbor_solution; + + if current_cost < best_cost { + best_cost = current_cost; + best_solution = Some(Solution { + routes: current_solution.routes.clone(), + }); + } + } + if best_cost <= challenge.max_total_distance { + return Ok(best_solution); + } + } + + temperature *= COOLING_RATE; + } + + Ok(best_solution) +} + +#[inline] +fn create_initial_savings_list(challenge: &Challenge) -> Vec<(f32, u8, u8)> { + let num_nodes = challenge.difficulty.num_nodes; + let capacity = ((num_nodes - 1) * (num_nodes - 2)) / 2; + let mut savings = Vec::with_capacity(capacity); + for i in 1..num_nodes { + for j in (i + 1)..num_nodes { + savings.push((0.0, i as u8, j as u8)); + } + } + savings +} + +#[inline] +fn recompute_and_sort_savings(savings_list: &mut [(f32, u8, u8)], params: &[f32], challenge: &Challenge) { + let distance_matrix = &challenge.distance_matrix; + + let mut zero_len = 0; + for (score, i, j) in savings_list.iter_mut() { + let i = *i as usize; + let j = *j as usize; + *score = params[i] * distance_matrix[0][i] as f32 + + params[j] * distance_matrix[j][0] as f32 - + params[i] * params[j] * distance_matrix[i][j] as f32; + } + + savings_list.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); +} + +#[inline] +fn generate_neighbor(current: &[f32], rng: &mut R) -> Vec { + current.iter().map(|¶m| { + let delta = rng.gen_range(-0.1..=0.1); + (param + delta).clamp(0.0, 2.0) + }).collect() +} + +#[inline] +fn apply_local_search_until_no_improvement(solution: &mut Solution, distance_matrix: &Vec>) { + let mut improved = true; + while improved { + improved = false; + for route in &mut solution.routes { + if two_opt(route, distance_matrix) { + improved = true; + } + } + } +} +#[inline] +fn two_opt(route: &mut Vec, distance_matrix: &Vec>) -> bool { + let n = route.len(); + let mut improved = false; + + for i in 1..n - 2 { + for j in i + 1..n - 1 { + let current_distance = distance_matrix[route[i - 1]][route[i]] + + distance_matrix[route[j]][route[j + 1]]; + let new_distance = distance_matrix[route[i - 1]][route[j]] + + distance_matrix[route[i]][route[j + 1]]; + + if new_distance < current_distance { + route[i..=j].reverse(); + improved = true; + } + } + } + + improved +} + +#[inline] +fn calculate_solution_cost(solution: &Solution, distance_matrix: &Vec>) -> i32 { + solution.routes.iter().map(|route| { + route.windows(2).map(|w| distance_matrix[w[0]][w[1]]).sum::() + }).sum() +} + +#[inline] +fn create_solution(challenge: &Challenge, params: &[f32], savings_list: &[(f32, u8, u8)]) -> Solution { + let distance_matrix = &challenge.distance_matrix; + let max_capacity = challenge.max_capacity; + let num_nodes = challenge.difficulty.num_nodes; + let demands = &challenge.demands; + + let mut routes = vec![None; num_nodes]; + for i in 1..num_nodes { + routes[i] = Some(vec![i]); + } + let mut route_demands = demands.clone(); + + for &(_, i, j) in savings_list { + let (i, j) = (i as usize, j as usize); + if let (Some(left_route), Some(right_route)) = (routes[i].as_ref(), routes[j].as_ref()) { + let (left_start, left_end) = (*left_route.first().unwrap(), *left_route.last().unwrap()); + let (right_start, right_end) = (*right_route.first().unwrap(), *right_route.last().unwrap()); + + if left_start == right_start || route_demands[left_start] + route_demands[right_start] > max_capacity { + continue; + } + + let mut new_route = routes[i].take().unwrap(); + let mut right_route = routes[j].take().unwrap(); + + if left_start == i { new_route.reverse(); } + if right_end == j { right_route.reverse(); } + + new_route.extend(right_route); + + let combined_demand = route_demands[left_start] + route_demands[right_start]; + let new_start = new_route[0]; + let new_end = *new_route.last().unwrap(); + + route_demands[new_start] = combined_demand; + route_demands[new_end] = combined_demand; + + routes[new_start] = Some(new_route.clone()); + routes[new_end] = Some(new_route); + } + } + + Solution { + routes: routes + .into_iter() + .enumerate() + .filter_map(|(i, route)| route.filter(|r| r[0] == i)) + .map(|mut route| { + route.insert(0, 0); + route.push(0); + route + }) + .collect(), + } +} + +#[cfg(feature = "cuda")] +mod gpu_optimisation { + use super::*; + use cudarc::driver::*; + use std::{collections::HashMap, sync::Arc}; + use tig_challenges::CudaKernel; + + // set KERNEL to None if algorithm only has a CPU implementation + pub const KERNEL: Option = None; + + // Important! your GPU and CPU version of the algorithm should return the same result + pub fn cuda_solve_challenge( + challenge: &Challenge, + dev: &Arc, + mut funcs: HashMap<&'static str, CudaFunction>, + ) -> anyhow::Result> { + solve_challenge(challenge) + } +} +#[cfg(feature = "cuda")] +pub use gpu_optimisation::{cuda_solve_challenge, KERNEL}; \ No newline at end of file diff --git a/tig-algorithms/src/vehicle_routing/mod.rs b/tig-algorithms/src/vehicle_routing/mod.rs index 930b4fb..d89fbd1 100644 --- a/tig-algorithms/src/vehicle_routing/mod.rs +++ b/tig-algorithms/src/vehicle_routing/mod.rs @@ -94,7 +94,8 @@ // c002_a048 -// c002_a049 +pub mod advanced_routing; +pub use advanced_routing as c002_a049; // c002_a050 diff --git a/tig-algorithms/src/vehicle_routing/template.rs b/tig-algorithms/src/vehicle_routing/template.rs index 02a7cab..b5c872b 100644 --- a/tig-algorithms/src/vehicle_routing/template.rs +++ b/tig-algorithms/src/vehicle_routing/template.rs @@ -1,11 +1,7 @@ /*! -Copyright [year copyright work created] [name of copyright owner] +Copyright [yyyy] [name of copyright owner] -Identity of Submitter [name of person or entity that submits the Work to TIG] - -UAI [UAI (if applicable)] - -Licensed under the TIG Inbound Game License v2.0 or (at your option) any later +Licensed under the TIG Inbound Game License v1.0 or (at your option) any later version (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -17,24 +13,6 @@ CONDITIONS OF ANY KIND, either express or implied. See the License for the speci language governing permissions and limitations under the License. */ -// REMOVE BELOW SECTION IF UNUSED -/* -REFERENCES AND ACKNOWLEDGMENTS - -This implementation is based on or inspired by existing work. Citations and -acknowledgments below: - -1. Academic Papers: - - [Author(s), "Paper Title", DOI (if available)] - -2. Code References: - - [Author(s), URL] - -3. Other: - - [Author(s), Details] - -*/ - // TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge use anyhow::{anyhow, Result}; use tig_challenges::vehicle_routing::{Challenge, Solution}; diff --git a/tig-algorithms/wasm/vehicle_routing/advanced_routing.wasm b/tig-algorithms/wasm/vehicle_routing/advanced_routing.wasm new file mode 100644 index 0000000000000000000000000000000000000000..87cc27b45555bceff4158fde4218441b302442c4 GIT binary patch literal 171363 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