use crate::qubo::Qubo;
use ndarray::Array1;
pub fn one_step_local_search_improved(
qubo: &Qubo,
x_0: &Array1<usize>,
selected_vars: &Vec<usize>,
) -> Array1<usize> {
let (_, objs) = one_flip_objective(qubo, x_0);
let best_neighbor = objs
.iter()
.enumerate()
.filter(|(i, _)| selected_vars.contains(i))
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.unwrap()
.0;
let best_obj = objs[best_neighbor];
if best_obj < 0.0f64 {
let mut x_1 = x_0.clone();
x_1[best_neighbor] = 1 - x_1[best_neighbor];
x_1
} else {
x_0.clone()
}
}
pub fn two_step_local_search_improved(qubo: &Qubo, x_0: &Array1<usize>) -> Array1<usize> {
let (_, obj_1d) = one_flip_objective(qubo, x_0);
let best_1d_neighbor = obj_1d
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.unwrap()
.0;
let best_obj_1d = obj_1d[best_1d_neighbor];
let mut best_obj_2d = f64::INFINITY;
let mut best_2d_neighbor = (0, 1);
for (q_ij, (i, j)) in &qubo.q {
if i > j {
let current_obj_2d = obj_1d[i]
+ obj_1d[j]
+ q_ij * (1.0 - 2.0 * (x_0[i] as f64)) * (1.0 - 2.0 * (x_0[j] as f64));
if current_obj_2d < best_obj_2d {
best_obj_2d = current_obj_2d;
best_2d_neighbor = (i, j);
}
}
}
if best_obj_2d < best_obj_1d && best_obj_2d < 0.0f64 {
let mut x_1 = x_0.clone();
x_1[best_2d_neighbor.0] = 1 - x_1[best_2d_neighbor.0];
x_1[best_2d_neighbor.1] = 1 - x_1[best_2d_neighbor.1];
x_1
} else if best_obj_1d < 0.0f64 {
let mut x_1 = x_0.clone();
x_1[best_1d_neighbor] = 1 - x_1[best_1d_neighbor];
x_1
} else {
x_0.clone()
}
}
pub fn get_gain_criteria(qubo: &Qubo, x: &Array1<usize>) -> Array1<usize> {
let mut fixed = Array1::zeros(qubo.num_x());
let grad = qubo.eval_grad_usize(x);
for i in 0..qubo.num_x() {
if grad[i] <= 0.0 {
fixed[i] = 1;
} else {
fixed[i] = 0;
}
}
fixed
}
pub fn one_flip_objective(qubo: &Qubo, x_0: &Array1<usize>) -> (f64, Array1<f64>) {
let mut objs = Array1::<f64>::zeros(qubo.num_x());
let x_0f = x_0.mapv(|x| x as f64);
let x_q = 0.5 * (&qubo.q * &x_0f);
let q_x = 0.5 * (&qubo.q.transpose_view() * &x_0f);
let q_jj = 0.5 * qubo.q.diag().to_dense();
let delta = 1.0 - 2.0 * &x_0f;
for i in 0..qubo.num_x() {
objs[i] = q_jj[i] + delta[i] * (x_q[i] + q_x[i] + qubo.c[i]);
}
let obj_0 = x_0f.dot(&x_q) + qubo.c.dot(&x_0f);
(obj_0, objs)
}
pub fn contract_point(
x_0: &Array1<usize>,
x_1: &Array1<usize>,
num_contract: usize,
) -> Array1<usize> {
let mut x_1 = x_1.clone();
let mut flipped = 0;
for i in 0..x_0.len() {
if x_0[i] != x_1[i] {
flipped += 1;
if x_0[i] != x_1[i] {
if x_0[i] == 1 {
x_1[i] = 0;
} else {
x_1[i] = 1;
}
}
if flipped > num_contract {
break;
}
}
}
x_1
}
#[cfg(test)]
mod tests {
use crate::local_search_utils::{
one_step_local_search_improved, two_step_local_search_improved,
};
use crate::qubo::Qubo;
use smolprng::{JsfLarge, PRNG};
#[test]
fn test_one_step_local_search_improved() {
let mut prng = PRNG {
generator: JsfLarge::default(),
};
let p = Qubo::make_random_qubo(50, &mut prng, 0.2);
let selected_vars: Vec<usize> = (0..p.num_x()).collect();
for _ in 0..100 {
let x_0 =
crate::initial_points::generate_random_binary_point(p.num_x(), &mut prng, 0.5);
let x_1 = one_step_local_search_improved(&p, &x_0, &selected_vars);
let obj_0 = p.eval_usize(&x_0);
let obj_1 = p.eval_usize(&x_1);
assert!(obj_1 <= obj_0);
}
}
#[test]
fn test_two_step_local_search_improved() {
let mut prng = PRNG {
generator: JsfLarge::default(),
};
let p = Qubo::make_random_qubo(50, &mut prng, 0.2);
let selected_vars: Vec<usize> = (0..p.num_x()).collect();
for _ in 0..100 {
let x_0 =
crate::initial_points::generate_random_binary_point(p.num_x(), &mut prng, 0.5);
let x_1 = one_step_local_search_improved(&p, &x_0, &selected_vars);
let x_2 = two_step_local_search_improved(&p, &x_0);
let obj_0 = p.eval_usize(&x_0);
let obj_1 = p.eval_usize(&x_1);
let obj_2 = p.eval_usize(&x_2);
assert!(obj_2 <= obj_1);
assert!(obj_1 <= obj_0);
}
}
}