diff --git a/tig-algorithms/src/satisfiability/mod.rs b/tig-algorithms/src/satisfiability/mod.rs index 22d6805c..d7f405e1 100644 --- a/tig-algorithms/src/satisfiability/mod.rs +++ b/tig-algorithms/src/satisfiability/mod.rs @@ -16,7 +16,7 @@ // c001_a009 -// c001_a010 +pub mod walk_sat_adapt_tabu; // c001_a010 // c001_a011 diff --git a/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/benchmarker_outbound.rs b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/benchmarker_outbound.rs new file mode 100644 index 00000000..560998c3 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/benchmarker_outbound.rs @@ -0,0 +1,145 @@ +/*! +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. + */ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{Rng, SeedableRng}; +use rand::rngs::StdRng; +use rand::seq::IteratorRandom; +use std::collections::{HashSet, VecDeque}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let mut rng = StdRng::seed_from_u64(challenge.seed as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + let initial_noise: f64 = 0.3; + let mut noise: f64 = initial_noise; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen()).collect(); + let mut best_solution: Option = None; + let mut best_unsatisfied = usize::MAX; + let mut clause_weights: Vec = vec![1; challenge.clauses.len()]; + let mut unsatisfied_clauses: HashSet = challenge.clauses.iter() + .enumerate() + .filter_map(|(i, clause)| if !clause_satisfied(clause, &variables) { Some(i) } else { None }) + .collect(); + let mut tabu_list: VecDeque = VecDeque::with_capacity(10); + let mut tabu_tenure = 10; + + for flip in 0..max_flips { + let num_unsatisfied = unsatisfied_clauses.len(); + + // Update the best solution found so far + if num_unsatisfied < best_unsatisfied { + best_unsatisfied = num_unsatisfied; + best_solution = Some(Solution { variables: variables.clone() }); + + if num_unsatisfied == 0 { + return Ok(best_solution); + } + } + + // Adaptive noise adjustment based on progress + if num_unsatisfied == best_unsatisfied { + noise = (noise + 0.005).min(1.0); + } else { + noise = (noise - 0.01).max(0.1); + } + + // Dynamic adjustment of tabu tenure + tabu_tenure = (num_unsatisfied as f64 / best_unsatisfied as f64 * 10.0).max(5.0).min(20.0) as usize; + + // Choose an unsatisfied clause + if let Some(&clause_idx) = unsatisfied_clauses.iter().choose(&mut rng) { + let clause = &challenge.clauses[clause_idx]; + + // Flip a variable with a heuristic + if rng.gen::() < noise { + // Random flip + if let Some(&literal) = clause.iter().choose(&mut rng) { + let var_idx = literal.abs() as usize - 1; + if !tabu_list.contains(&var_idx) { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + } else { + // Greedy flip + let mut best_var = None; + let mut best_reduction = usize::MAX; + for &literal in clause { + let var_idx = literal.abs() as usize - 1; + if tabu_list.contains(&var_idx) { + continue; + } + variables[var_idx] = !variables[var_idx]; // Tentative flip + let reduction = challenge.clauses.iter().enumerate() + .filter(|(i, clause)| !clause_satisfied(clause, &variables) && clause_weights[*i] > 0) + .count(); + variables[var_idx] = !variables[var_idx]; // Revert flip + + if reduction < best_reduction { + best_reduction = reduction; + best_var = Some(var_idx); + } + } + + if let Some(var_idx) = best_var { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + + // Adaptive weight update for unsatisfied clauses + for &clause_idx in &unsatisfied_clauses { + clause_weights[clause_idx] += 1 + (best_unsatisfied.saturating_sub(num_unsatisfied)) / best_unsatisfied; + } + } + } + + Ok(best_solution) +} + +fn clause_satisfied(clause: &[i32], 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]) + }) +} + +fn update_unsatisfied_clauses( + unsatisfied_clauses: &mut HashSet, + clauses: &[Vec], + flipped_var: usize, + variables: &[bool] +) { + for (i, clause) in clauses.iter().enumerate() { + if clause.iter().any(|&literal| literal.abs() as usize - 1 == flipped_var) { + if clause_satisfied(clause, variables) { + unsatisfied_clauses.remove(&i); + } else { + unsatisfied_clauses.insert(i); + } + } + } +} diff --git a/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/commercial.rs b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/commercial.rs new file mode 100644 index 00000000..7c6b0145 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/commercial.rs @@ -0,0 +1,145 @@ +/*! +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. + */ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{Rng, SeedableRng}; +use rand::rngs::StdRng; +use rand::seq::IteratorRandom; +use std::collections::{HashSet, VecDeque}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let mut rng = StdRng::seed_from_u64(challenge.seed as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + let initial_noise: f64 = 0.3; + let mut noise: f64 = initial_noise; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen()).collect(); + let mut best_solution: Option = None; + let mut best_unsatisfied = usize::MAX; + let mut clause_weights: Vec = vec![1; challenge.clauses.len()]; + let mut unsatisfied_clauses: HashSet = challenge.clauses.iter() + .enumerate() + .filter_map(|(i, clause)| if !clause_satisfied(clause, &variables) { Some(i) } else { None }) + .collect(); + let mut tabu_list: VecDeque = VecDeque::with_capacity(10); + let mut tabu_tenure = 10; + + for flip in 0..max_flips { + let num_unsatisfied = unsatisfied_clauses.len(); + + // Update the best solution found so far + if num_unsatisfied < best_unsatisfied { + best_unsatisfied = num_unsatisfied; + best_solution = Some(Solution { variables: variables.clone() }); + + if num_unsatisfied == 0 { + return Ok(best_solution); + } + } + + // Adaptive noise adjustment based on progress + if num_unsatisfied == best_unsatisfied { + noise = (noise + 0.005).min(1.0); + } else { + noise = (noise - 0.01).max(0.1); + } + + // Dynamic adjustment of tabu tenure + tabu_tenure = (num_unsatisfied as f64 / best_unsatisfied as f64 * 10.0).max(5.0).min(20.0) as usize; + + // Choose an unsatisfied clause + if let Some(&clause_idx) = unsatisfied_clauses.iter().choose(&mut rng) { + let clause = &challenge.clauses[clause_idx]; + + // Flip a variable with a heuristic + if rng.gen::() < noise { + // Random flip + if let Some(&literal) = clause.iter().choose(&mut rng) { + let var_idx = literal.abs() as usize - 1; + if !tabu_list.contains(&var_idx) { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + } else { + // Greedy flip + let mut best_var = None; + let mut best_reduction = usize::MAX; + for &literal in clause { + let var_idx = literal.abs() as usize - 1; + if tabu_list.contains(&var_idx) { + continue; + } + variables[var_idx] = !variables[var_idx]; // Tentative flip + let reduction = challenge.clauses.iter().enumerate() + .filter(|(i, clause)| !clause_satisfied(clause, &variables) && clause_weights[*i] > 0) + .count(); + variables[var_idx] = !variables[var_idx]; // Revert flip + + if reduction < best_reduction { + best_reduction = reduction; + best_var = Some(var_idx); + } + } + + if let Some(var_idx) = best_var { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + + // Adaptive weight update for unsatisfied clauses + for &clause_idx in &unsatisfied_clauses { + clause_weights[clause_idx] += 1 + (best_unsatisfied.saturating_sub(num_unsatisfied)) / best_unsatisfied; + } + } + } + + Ok(best_solution) +} + +fn clause_satisfied(clause: &[i32], 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]) + }) +} + +fn update_unsatisfied_clauses( + unsatisfied_clauses: &mut HashSet, + clauses: &[Vec], + flipped_var: usize, + variables: &[bool] +) { + for (i, clause) in clauses.iter().enumerate() { + if clause.iter().any(|&literal| literal.abs() as usize - 1 == flipped_var) { + if clause_satisfied(clause, variables) { + unsatisfied_clauses.remove(&i); + } else { + unsatisfied_clauses.insert(i); + } + } + } +} diff --git a/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/inbound.rs b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/inbound.rs new file mode 100644 index 00000000..281fb0f0 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/inbound.rs @@ -0,0 +1,145 @@ +/*! +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. + */ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{Rng, SeedableRng}; +use rand::rngs::StdRng; +use rand::seq::IteratorRandom; +use std::collections::{HashSet, VecDeque}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let mut rng = StdRng::seed_from_u64(challenge.seed as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + let initial_noise: f64 = 0.3; + let mut noise: f64 = initial_noise; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen()).collect(); + let mut best_solution: Option = None; + let mut best_unsatisfied = usize::MAX; + let mut clause_weights: Vec = vec![1; challenge.clauses.len()]; + let mut unsatisfied_clauses: HashSet = challenge.clauses.iter() + .enumerate() + .filter_map(|(i, clause)| if !clause_satisfied(clause, &variables) { Some(i) } else { None }) + .collect(); + let mut tabu_list: VecDeque = VecDeque::with_capacity(10); + let mut tabu_tenure = 10; + + for flip in 0..max_flips { + let num_unsatisfied = unsatisfied_clauses.len(); + + // Update the best solution found so far + if num_unsatisfied < best_unsatisfied { + best_unsatisfied = num_unsatisfied; + best_solution = Some(Solution { variables: variables.clone() }); + + if num_unsatisfied == 0 { + return Ok(best_solution); + } + } + + // Adaptive noise adjustment based on progress + if num_unsatisfied == best_unsatisfied { + noise = (noise + 0.005).min(1.0); + } else { + noise = (noise - 0.01).max(0.1); + } + + // Dynamic adjustment of tabu tenure + tabu_tenure = (num_unsatisfied as f64 / best_unsatisfied as f64 * 10.0).max(5.0).min(20.0) as usize; + + // Choose an unsatisfied clause + if let Some(&clause_idx) = unsatisfied_clauses.iter().choose(&mut rng) { + let clause = &challenge.clauses[clause_idx]; + + // Flip a variable with a heuristic + if rng.gen::() < noise { + // Random flip + if let Some(&literal) = clause.iter().choose(&mut rng) { + let var_idx = literal.abs() as usize - 1; + if !tabu_list.contains(&var_idx) { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + } else { + // Greedy flip + let mut best_var = None; + let mut best_reduction = usize::MAX; + for &literal in clause { + let var_idx = literal.abs() as usize - 1; + if tabu_list.contains(&var_idx) { + continue; + } + variables[var_idx] = !variables[var_idx]; // Tentative flip + let reduction = challenge.clauses.iter().enumerate() + .filter(|(i, clause)| !clause_satisfied(clause, &variables) && clause_weights[*i] > 0) + .count(); + variables[var_idx] = !variables[var_idx]; // Revert flip + + if reduction < best_reduction { + best_reduction = reduction; + best_var = Some(var_idx); + } + } + + if let Some(var_idx) = best_var { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + + // Adaptive weight update for unsatisfied clauses + for &clause_idx in &unsatisfied_clauses { + clause_weights[clause_idx] += 1 + (best_unsatisfied.saturating_sub(num_unsatisfied)) / best_unsatisfied; + } + } + } + + Ok(best_solution) +} + +fn clause_satisfied(clause: &[i32], 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]) + }) +} + +fn update_unsatisfied_clauses( + unsatisfied_clauses: &mut HashSet, + clauses: &[Vec], + flipped_var: usize, + variables: &[bool] +) { + for (i, clause) in clauses.iter().enumerate() { + if clause.iter().any(|&literal| literal.abs() as usize - 1 == flipped_var) { + if clause_satisfied(clause, variables) { + unsatisfied_clauses.remove(&i); + } else { + unsatisfied_clauses.insert(i); + } + } + } +} diff --git a/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/innovator_outbound.rs b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/innovator_outbound.rs new file mode 100644 index 00000000..b346be35 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/innovator_outbound.rs @@ -0,0 +1,145 @@ +/*! +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. + */ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{Rng, SeedableRng}; +use rand::rngs::StdRng; +use rand::seq::IteratorRandom; +use std::collections::{HashSet, VecDeque}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let mut rng = StdRng::seed_from_u64(challenge.seed as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + let initial_noise: f64 = 0.3; + let mut noise: f64 = initial_noise; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen()).collect(); + let mut best_solution: Option = None; + let mut best_unsatisfied = usize::MAX; + let mut clause_weights: Vec = vec![1; challenge.clauses.len()]; + let mut unsatisfied_clauses: HashSet = challenge.clauses.iter() + .enumerate() + .filter_map(|(i, clause)| if !clause_satisfied(clause, &variables) { Some(i) } else { None }) + .collect(); + let mut tabu_list: VecDeque = VecDeque::with_capacity(10); + let mut tabu_tenure = 10; + + for flip in 0..max_flips { + let num_unsatisfied = unsatisfied_clauses.len(); + + // Update the best solution found so far + if num_unsatisfied < best_unsatisfied { + best_unsatisfied = num_unsatisfied; + best_solution = Some(Solution { variables: variables.clone() }); + + if num_unsatisfied == 0 { + return Ok(best_solution); + } + } + + // Adaptive noise adjustment based on progress + if num_unsatisfied == best_unsatisfied { + noise = (noise + 0.005).min(1.0); + } else { + noise = (noise - 0.01).max(0.1); + } + + // Dynamic adjustment of tabu tenure + tabu_tenure = (num_unsatisfied as f64 / best_unsatisfied as f64 * 10.0).max(5.0).min(20.0) as usize; + + // Choose an unsatisfied clause + if let Some(&clause_idx) = unsatisfied_clauses.iter().choose(&mut rng) { + let clause = &challenge.clauses[clause_idx]; + + // Flip a variable with a heuristic + if rng.gen::() < noise { + // Random flip + if let Some(&literal) = clause.iter().choose(&mut rng) { + let var_idx = literal.abs() as usize - 1; + if !tabu_list.contains(&var_idx) { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + } else { + // Greedy flip + let mut best_var = None; + let mut best_reduction = usize::MAX; + for &literal in clause { + let var_idx = literal.abs() as usize - 1; + if tabu_list.contains(&var_idx) { + continue; + } + variables[var_idx] = !variables[var_idx]; // Tentative flip + let reduction = challenge.clauses.iter().enumerate() + .filter(|(i, clause)| !clause_satisfied(clause, &variables) && clause_weights[*i] > 0) + .count(); + variables[var_idx] = !variables[var_idx]; // Revert flip + + if reduction < best_reduction { + best_reduction = reduction; + best_var = Some(var_idx); + } + } + + if let Some(var_idx) = best_var { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + + // Adaptive weight update for unsatisfied clauses + for &clause_idx in &unsatisfied_clauses { + clause_weights[clause_idx] += 1 + (best_unsatisfied.saturating_sub(num_unsatisfied)) / best_unsatisfied; + } + } + } + + Ok(best_solution) +} + +fn clause_satisfied(clause: &[i32], 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]) + }) +} + +fn update_unsatisfied_clauses( + unsatisfied_clauses: &mut HashSet, + clauses: &[Vec], + flipped_var: usize, + variables: &[bool] +) { + for (i, clause) in clauses.iter().enumerate() { + if clause.iter().any(|&literal| literal.abs() as usize - 1 == flipped_var) { + if clause_satisfied(clause, variables) { + unsatisfied_clauses.remove(&i); + } else { + unsatisfied_clauses.insert(i); + } + } + } +} diff --git a/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/mod.rs b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/mod.rs new file mode 100644 index 00000000..73f7d110 --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/mod.rs @@ -0,0 +1,2 @@ +mod benchmarker_outbound; +pub use benchmarker_outbound::solve_challenge; \ No newline at end of file diff --git a/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/open_data.rs b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/open_data.rs new file mode 100644 index 00000000..683e567c --- /dev/null +++ b/tig-algorithms/src/satisfiability/walk_sat_adapt_tabu/open_data.rs @@ -0,0 +1,145 @@ +/*! +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. + */ + +// TIG's UI uses the pattern `tig_challenges::` to automatically detect your algorithm's challenge +use anyhow::Result; +use rand::{Rng, SeedableRng}; +use rand::rngs::StdRng; +use rand::seq::IteratorRandom; +use std::collections::{HashSet, VecDeque}; +use tig_challenges::satisfiability::*; + +pub fn solve_challenge(challenge: &Challenge) -> Result> { + let mut rng = StdRng::seed_from_u64(challenge.seed as u64); + let num_variables = challenge.difficulty.num_variables; + let max_flips = 1000; + let initial_noise: f64 = 0.3; + let mut noise: f64 = initial_noise; + let mut variables: Vec = (0..num_variables).map(|_| rng.gen()).collect(); + let mut best_solution: Option = None; + let mut best_unsatisfied = usize::MAX; + let mut clause_weights: Vec = vec![1; challenge.clauses.len()]; + let mut unsatisfied_clauses: HashSet = challenge.clauses.iter() + .enumerate() + .filter_map(|(i, clause)| if !clause_satisfied(clause, &variables) { Some(i) } else { None }) + .collect(); + let mut tabu_list: VecDeque = VecDeque::with_capacity(10); + let mut tabu_tenure = 10; + + for flip in 0..max_flips { + let num_unsatisfied = unsatisfied_clauses.len(); + + // Update the best solution found so far + if num_unsatisfied < best_unsatisfied { + best_unsatisfied = num_unsatisfied; + best_solution = Some(Solution { variables: variables.clone() }); + + if num_unsatisfied == 0 { + return Ok(best_solution); + } + } + + // Adaptive noise adjustment based on progress + if num_unsatisfied == best_unsatisfied { + noise = (noise + 0.005).min(1.0); + } else { + noise = (noise - 0.01).max(0.1); + } + + // Dynamic adjustment of tabu tenure + tabu_tenure = (num_unsatisfied as f64 / best_unsatisfied as f64 * 10.0).max(5.0).min(20.0) as usize; + + // Choose an unsatisfied clause + if let Some(&clause_idx) = unsatisfied_clauses.iter().choose(&mut rng) { + let clause = &challenge.clauses[clause_idx]; + + // Flip a variable with a heuristic + if rng.gen::() < noise { + // Random flip + if let Some(&literal) = clause.iter().choose(&mut rng) { + let var_idx = literal.abs() as usize - 1; + if !tabu_list.contains(&var_idx) { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + } else { + // Greedy flip + let mut best_var = None; + let mut best_reduction = usize::MAX; + for &literal in clause { + let var_idx = literal.abs() as usize - 1; + if tabu_list.contains(&var_idx) { + continue; + } + variables[var_idx] = !variables[var_idx]; // Tentative flip + let reduction = challenge.clauses.iter().enumerate() + .filter(|(i, clause)| !clause_satisfied(clause, &variables) && clause_weights[*i] > 0) + .count(); + variables[var_idx] = !variables[var_idx]; // Revert flip + + if reduction < best_reduction { + best_reduction = reduction; + best_var = Some(var_idx); + } + } + + if let Some(var_idx) = best_var { + variables[var_idx] = !variables[var_idx]; + update_unsatisfied_clauses(&mut unsatisfied_clauses, &challenge.clauses, var_idx, &variables); + tabu_list.push_back(var_idx); + if tabu_list.len() > tabu_tenure { + tabu_list.pop_front(); + } + } + } + + // Adaptive weight update for unsatisfied clauses + for &clause_idx in &unsatisfied_clauses { + clause_weights[clause_idx] += 1 + (best_unsatisfied.saturating_sub(num_unsatisfied)) / best_unsatisfied; + } + } + } + + Ok(best_solution) +} + +fn clause_satisfied(clause: &[i32], 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]) + }) +} + +fn update_unsatisfied_clauses( + unsatisfied_clauses: &mut HashSet, + clauses: &[Vec], + flipped_var: usize, + variables: &[bool] +) { + for (i, clause) in clauses.iter().enumerate() { + if clause.iter().any(|&literal| literal.abs() as usize - 1 == flipped_var) { + if clause_satisfied(clause, variables) { + unsatisfied_clauses.remove(&i); + } else { + unsatisfied_clauses.insert(i); + } + } + } +}