use super::tsp_instance::{TspInstanceError, TspInstanceYaml};
use super::{CriterionStatus, EddDemo, FalsificationStatus};
use crate::edd::audit::{
Decision, EquationEval, SimulationAuditLog, StepEntry, TspStateSnapshot, TspStepType,
};
use crate::engine::rng::SimRng;
use crate::engine::SimTime;
use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
#[cfg(target_arch = "wasm32")]
#[derive(Clone, Copy)]
struct Instant(f64);
#[cfg(target_arch = "wasm32")]
impl Instant {
fn now() -> Self {
#[cfg(feature = "wasm")]
{
if let Some(window) = web_sys::window() {
if let Some(perf) = window.performance() {
return Self(perf.now());
}
}
}
Self(0.0)
}
fn elapsed(&self) -> std::time::Duration {
#[cfg(feature = "wasm")]
{
if let Some(window) = web_sys::window() {
if let Some(perf) = window.performance() {
let now = perf.now();
let elapsed_ms = now - self.0;
return std::time::Duration::from_micros((elapsed_ms * 1000.0) as u64);
}
}
}
std::time::Duration::ZERO
}
}
fn gen_range_usize(rng: &mut SimRng, max: usize) -> usize {
if max == 0 {
return 0;
}
(rng.gen_u64() as usize) % max
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct City {
pub x: f64,
pub y: f64,
}
impl City {
#[must_use]
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
#[must_use]
pub fn distance_to(&self, other: &Self) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ConstructionMethod {
#[default]
RandomizedGreedy,
NearestNeighbor,
Random,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TspGraspDemo {
pub cities: Vec<City>,
pub tour: Vec<usize>,
pub tour_length: f64,
pub best_tour: Vec<usize>,
pub best_tour_length: f64,
pub n: usize,
pub rcl_size: usize,
pub construction_method: ConstructionMethod,
pub two_opt_iterations: u64,
pub two_opt_improvements: u64,
pub restarts: u64,
pub restart_history: Vec<f64>,
pub lower_bound: f64,
pub area: f64,
pub seed: u64,
#[serde(default = "default_units")]
pub units: String,
#[serde(default)]
pub optimal_known: Option<u32>,
#[serde(default)]
pub stagnation_count: u64,
#[serde(default = "default_stagnation_threshold")]
pub stagnation_threshold: u64,
#[serde(default)]
pub converged: bool,
#[serde(default = "default_euclidean")]
pub is_euclidean: bool,
#[serde(skip)]
rng: Option<SimRng>,
#[serde(skip)]
distance_matrix: Vec<Vec<f64>>,
#[serde(skip)]
audit_log: Vec<StepEntry<TspStateSnapshot>>,
#[serde(skip)]
step_counter: u64,
#[serde(default = "default_audit_enabled")]
pub audit_enabled: bool,
}
fn default_units() -> String {
"units".to_string()
}
fn default_audit_enabled() -> bool {
true
}
fn default_stagnation_threshold() -> u64 {
10
}
fn default_euclidean() -> bool {
true
}
impl Default for TspGraspDemo {
fn default() -> Self {
Self::new(42, 20)
}
}
impl TspGraspDemo {
pub fn reinitialize(&mut self) {
if self.rng.is_none() {
self.rng = Some(SimRng::new(self.seed));
}
if self.distance_matrix.is_empty() && !self.cities.is_empty() {
self.precompute_distances();
}
if self.lower_bound == 0.0 && !self.cities.is_empty() {
self.compute_lower_bound();
}
}
}
impl TspGraspDemo {
#[must_use]
pub fn new(seed: u64, n: usize) -> Self {
let mut rng = SimRng::new(seed);
let n = n.max(4);
let mut cities = Vec::with_capacity(n);
for _ in 0..n {
let x = rng.gen_range_f64(0.0, 1.0);
let y = rng.gen_range_f64(0.0, 1.0);
cities.push(City::new(x, y));
}
let mut demo = Self {
cities,
tour: Vec::new(),
tour_length: 0.0,
best_tour: Vec::new(),
best_tour_length: 0.0,
n,
rcl_size: 3, construction_method: ConstructionMethod::RandomizedGreedy,
two_opt_iterations: 0,
two_opt_improvements: 0,
restarts: 0,
restart_history: Vec::new(),
lower_bound: 0.0,
area: 1.0, seed,
units: "units".to_string(), optimal_known: None, stagnation_count: 0, stagnation_threshold: 10, converged: false, is_euclidean: true, rng: Some(rng),
distance_matrix: Vec::new(),
audit_log: Vec::new(),
step_counter: 0,
audit_enabled: true,
};
demo.precompute_distances();
demo.compute_lower_bound();
demo
}
#[must_use]
pub fn with_cities(seed: u64, cities: Vec<City>) -> Self {
let n = cities.len();
let rng = SimRng::new(seed);
let (min_x, max_x, min_y, max_y) = cities.iter().fold(
(
f64::INFINITY,
f64::NEG_INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
),
|(min_x, max_x, min_y, max_y), c| {
(
min_x.min(c.x),
max_x.max(c.x),
min_y.min(c.y),
max_y.max(c.y),
)
},
);
let area = (max_x - min_x) * (max_y - min_y);
let mut demo = Self {
cities,
tour: Vec::new(),
tour_length: 0.0,
best_tour: Vec::new(),
best_tour_length: 0.0,
n,
rcl_size: 3,
construction_method: ConstructionMethod::RandomizedGreedy,
two_opt_iterations: 0,
two_opt_improvements: 0,
restarts: 0,
restart_history: Vec::new(),
lower_bound: 0.0,
area: area.max(0.001), seed,
units: "units".to_string(), optimal_known: None, stagnation_count: 0, stagnation_threshold: 10, converged: false, is_euclidean: true, rng: Some(rng),
distance_matrix: Vec::new(),
audit_log: Vec::new(),
step_counter: 0,
audit_enabled: true,
};
demo.precompute_distances();
demo.compute_lower_bound();
demo
}
#[must_use]
pub fn from_instance(instance: &TspInstanceYaml) -> Self {
let cities: Vec<City> = instance
.cities
.iter()
.map(|c| City::new(c.coords.lon, c.coords.lat))
.collect();
let seed = instance.algorithm.params.seed;
let mut demo = Self::with_cities(seed, cities);
demo.set_rcl_size(instance.algorithm.params.rcl_size);
let method = match instance.algorithm.method.as_str() {
"nearest_neighbor" | "nn" => ConstructionMethod::NearestNeighbor,
"random" => ConstructionMethod::Random,
_ => ConstructionMethod::RandomizedGreedy, };
demo.set_construction_method(method);
demo.distance_matrix = instance
.matrix
.iter()
.map(|row| row.iter().map(|&d| f64::from(d)).collect())
.collect();
demo.is_euclidean = false;
demo.units.clone_from(&instance.meta.units);
demo.optimal_known = instance.meta.optimal_known;
demo.compute_lower_bound();
demo
}
pub fn from_yaml(yaml: &str) -> Result<Self, TspInstanceError> {
let instance = TspInstanceYaml::from_yaml(yaml)?;
instance.validate()?;
Ok(Self::from_instance(&instance))
}
pub fn from_yaml_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, TspInstanceError> {
let instance = TspInstanceYaml::from_yaml_file(path)?;
instance.validate()?;
Ok(Self::from_instance(&instance))
}
pub fn set_construction_method(&mut self, method: ConstructionMethod) {
self.construction_method = method;
}
pub fn set_rcl_size(&mut self, size: usize) {
self.rcl_size = size.max(1).min(self.n);
}
fn precompute_distances(&mut self) {
self.distance_matrix = vec![vec![0.0; self.n]; self.n];
for i in 0..self.n {
for j in (i + 1)..self.n {
let d = self.cities[i].distance_to(&self.cities[j]);
self.distance_matrix[i][j] = d;
self.distance_matrix[j][i] = d;
}
}
}
#[must_use]
pub fn distance(&self, i: usize, j: usize) -> f64 {
if self.distance_matrix.is_empty() {
self.cities[i].distance_to(&self.cities[j])
} else {
self.distance_matrix[i][j]
}
}
#[must_use]
pub fn compute_tour_length(&self, tour: &[usize]) -> f64 {
if tour.len() < 2 {
return 0.0;
}
let mut length = 0.0;
for i in 0..tour.len() {
let j = (i + 1) % tour.len();
length += self.distance(tour[i], tour[j]);
}
length
}
fn compute_mst_excluding(&self, exclude_vertex: usize) -> f64 {
if self.n < 3 {
return 0.0;
}
let mut in_mst = vec![false; self.n];
let mut min_edge = vec![f64::INFINITY; self.n];
let start = (0..self.n).find(|&v| v != exclude_vertex).unwrap_or(0);
min_edge[start] = 0.0;
in_mst[exclude_vertex] = true;
let mut mst_weight = 0.0;
let vertices_to_add = self.n - 1;
for _ in 0..vertices_to_add {
let mut u = None;
let mut min_val = f64::INFINITY;
for (i, (&in_tree, &edge)) in in_mst.iter().zip(min_edge.iter()).enumerate() {
if !in_tree && edge < min_val {
min_val = edge;
u = Some(i);
}
}
let Some(u) = u else { break };
in_mst[u] = true;
mst_weight += min_val;
for v in 0..self.n {
if !in_mst[v] && v != exclude_vertex {
let d = self.distance(u, v);
if d < min_edge[v] {
min_edge[v] = d;
}
}
}
}
mst_weight
}
fn compute_one_tree_bound(&self) -> f64 {
if self.n < 3 {
return 0.0;
}
let mut best_bound = 0.0;
for exclude in 0..self.n {
let mst_weight = self.compute_mst_excluding(exclude);
let mut edges: Vec<f64> = (0..self.n)
.filter(|&v| v != exclude)
.map(|v| self.distance(exclude, v))
.collect();
edges.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let one_tree =
mst_weight + edges.first().unwrap_or(&0.0) + edges.get(1).unwrap_or(&0.0);
if one_tree > best_bound {
best_bound = one_tree;
}
}
best_bound
}
fn compute_lower_bound(&mut self) {
if self.n < 2 {
self.lower_bound = 0.0;
return;
}
self.lower_bound = self.compute_one_tree_bound();
}
fn segments_intersect(p1: &City, p2: &City, p3: &City, p4: &City) -> bool {
fn cross(o: &City, a: &City, b: &City) -> f64 {
(a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)
}
let d1 = cross(p3, p4, p1);
let d2 = cross(p3, p4, p2);
let d3 = cross(p1, p2, p3);
let d4 = cross(p1, p2, p4);
if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
&& ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
{
return true;
}
false
}
#[must_use]
pub fn count_crossings(&self) -> usize {
let n = self.tour.len();
if n < 4 {
return 0;
}
let mut crossings = 0;
for i in 0..n {
let i_next = (i + 1) % n;
let p1 = &self.cities[self.tour[i]];
let p2 = &self.cities[self.tour[i_next]];
for j in (i + 2)..n {
let j_next = (j + 1) % n;
if j_next == i {
continue;
}
let p3 = &self.cities[self.tour[j]];
let p4 = &self.cities[self.tour[j_next]];
if Self::segments_intersect(p1, p2, p3, p4) {
crossings += 1;
}
}
}
crossings
}
#[must_use]
pub fn is_two_opt_optimal(&self) -> bool {
let n = self.tour.len();
if n < 4 {
return true;
}
for i in 0..(n - 2) {
for j in (i + 2)..n {
if i == 0 && j == n - 1 {
continue;
}
if self.two_opt_improvement(i, j) > f64::EPSILON {
return false;
}
}
}
true
}
fn construct_randomized_greedy(&mut self) {
let n = self.n;
let rcl_size_param = self.rcl_size;
let Some(mut rng) = self.rng.take() else {
return;
};
let mut visited = vec![false; n];
let mut tour = Vec::with_capacity(n);
let start = gen_range_usize(&mut rng, n);
tour.push(start);
visited[start] = true;
while tour.len() < n {
let Some(¤t) = tour.last() else {
break;
};
let mut candidates: Vec<(usize, f64)> = (0..n)
.filter(|&i| !visited[i])
.map(|i| (i, self.distance_matrix[current][i]))
.collect();
candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
let rcl_size = rcl_size_param.min(candidates.len());
let rcl = &candidates[..rcl_size];
let idx = gen_range_usize(&mut rng, rcl_size);
let next = rcl[idx].0;
tour.push(next);
visited[next] = true;
}
self.rng = Some(rng);
self.tour = tour;
self.tour_length = self.compute_tour_length(&self.tour);
}
fn construct_nearest_neighbor(&mut self) {
let n = self.n;
let Some(mut rng) = self.rng.take() else {
return;
};
let mut visited = vec![false; n];
let mut tour = Vec::with_capacity(n);
let start = gen_range_usize(&mut rng, n);
tour.push(start);
visited[start] = true;
while tour.len() < n {
let Some(¤t) = tour.last() else {
break;
};
let mut best_next = None;
let mut best_dist = f64::INFINITY;
for i in 0..n {
if !visited[i] {
let d = self.distance_matrix[current][i];
if d < best_dist {
best_dist = d;
best_next = Some(i);
}
}
}
if let Some(next) = best_next {
tour.push(next);
visited[next] = true;
}
}
self.rng = Some(rng);
self.tour = tour;
self.tour_length = self.compute_tour_length(&self.tour);
}
fn construct_random(&mut self) {
let n = self.n;
let Some(mut rng) = self.rng.take() else {
return;
};
let mut tour: Vec<usize> = (0..n).collect();
for i in (1..n).rev() {
let j = gen_range_usize(&mut rng, i + 1);
tour.swap(i, j);
}
self.rng = Some(rng);
self.tour = tour;
self.tour_length = self.compute_tour_length(&self.tour);
}
pub fn construct_tour(&mut self) {
match self.construction_method {
ConstructionMethod::RandomizedGreedy => self.construct_randomized_greedy(),
ConstructionMethod::NearestNeighbor => self.construct_nearest_neighbor(),
ConstructionMethod::Random => self.construct_random(),
}
}
#[must_use]
pub fn two_opt_improvement(&self, i: usize, j: usize) -> f64 {
let n = self.tour.len();
if n < 4 || i >= j || j >= n {
return 0.0;
}
let i_next = (i + 1) % n;
let j_next = (j + 1) % n;
let d_current = self.distance(self.tour[i], self.tour[i_next])
+ self.distance(self.tour[j], self.tour[j_next]);
let d_new = self.distance(self.tour[i], self.tour[j])
+ self.distance(self.tour[i_next], self.tour[j_next]);
d_current - d_new
}
fn apply_two_opt(&mut self, i: usize, j: usize) {
let mut left = i + 1;
let mut right = j;
while left < right {
self.tour.swap(left, right);
left += 1;
right -= 1;
}
self.tour_length = self.compute_tour_length(&self.tour);
}
pub fn two_opt_pass(&mut self) -> bool {
let n = self.tour.len();
if n < 4 {
return false;
}
self.two_opt_iterations += 1;
let mut best_improvement = 0.0;
let mut best_i = 0;
let mut best_j = 0;
for i in 0..(n - 2) {
for j in (i + 2)..n {
if i == 0 && j == n - 1 {
continue;
}
let improvement = self.two_opt_improvement(i, j);
if improvement > best_improvement {
best_improvement = improvement;
best_i = i;
best_j = j;
}
}
}
if best_improvement > f64::EPSILON {
self.apply_two_opt(best_i, best_j);
self.two_opt_improvements += 1;
true
} else {
false
}
}
pub fn two_opt_to_local_optimum(&mut self) {
while self.two_opt_pass() {}
}
pub fn grasp_iteration(&mut self) {
let start_time = Instant::now();
let input_snapshot = self.snapshot();
let rng_before = self.rng_state_hash();
self.construct_tour();
self.two_opt_to_local_optimum();
self.restarts += 1;
self.restart_history.push(self.tour_length);
let equations = vec![
EquationEval::new("tour_length", self.tour_length).with_input("n", self.n as f64),
EquationEval::new("optimality_gap", self.optimality_gap())
.with_input("tour_length", self.tour_length)
.with_input("lower_bound", self.lower_bound),
];
let decisions = vec![Decision::new(
"construction_method",
format!("{:?}", self.construction_method),
)
.with_rationale("rcl_size", self.rcl_size as f64)];
self.log_audit_step(
TspStepType::GraspIteration,
input_snapshot,
rng_before,
start_time,
equations,
decisions,
);
if self.best_tour_length == 0.0 || self.tour_length < self.best_tour_length {
self.best_tour_length = self.tour_length;
self.best_tour = self.tour.clone();
self.stagnation_count = 0; } else {
self.stagnation_count += 1;
if self.stagnation_count >= self.stagnation_threshold {
self.converged = true;
}
}
}
pub fn run_grasp(&mut self, iterations: usize) {
for _ in 0..iterations {
if self.converged {
break;
}
self.grasp_iteration();
}
}
#[must_use]
pub const fn is_converged(&self) -> bool {
self.converged
}
pub fn set_stagnation_threshold(&mut self, threshold: u64) {
self.stagnation_threshold = threshold;
}
#[must_use]
pub fn optimality_gap(&self) -> f64 {
if self.lower_bound > f64::EPSILON {
(self.best_tour_length - self.lower_bound) / self.lower_bound
} else {
0.0
}
}
#[must_use]
pub fn bhh_expected_length(&self) -> f64 {
0.7124 * (self.n as f64 * self.area).sqrt()
}
#[must_use]
pub fn restart_variance(&self) -> f64 {
if self.restart_history.len() < 2 {
return 0.0;
}
let n = self.restart_history.len() as f64;
let mean: f64 = self.restart_history.iter().sum::<f64>() / n;
let variance: f64 = self
.restart_history
.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>()
/ n;
variance
}
#[must_use]
pub fn restart_cv(&self) -> f64 {
if self.restart_history.is_empty() {
return 0.0;
}
let mean: f64 =
self.restart_history.iter().sum::<f64>() / self.restart_history.len() as f64;
if mean > f64::EPSILON {
self.restart_variance().sqrt() / mean
} else {
0.0
}
}
#[must_use]
fn snapshot(&self) -> TspStateSnapshot {
TspStateSnapshot {
tour: self.tour.clone(),
tour_length: self.tour_length,
best_tour: self.best_tour.clone(),
best_tour_length: self.best_tour_length,
restarts: self.restarts,
two_opt_iterations: self.two_opt_iterations,
two_opt_improvements: self.two_opt_improvements,
}
}
fn rng_state_hash(&self) -> [u8; 32] {
self.rng.as_ref().map_or([0u8; 32], |rng| {
let state_bytes = rng.state_bytes();
*blake3::hash(&state_bytes).as_bytes()
})
}
fn log_audit_step(
&mut self,
step_type: TspStepType,
input_snapshot: TspStateSnapshot,
rng_before: [u8; 32],
start_time: Instant,
equations: Vec<EquationEval>,
decisions: Vec<Decision>,
) {
if !self.audit_enabled {
return;
}
let rng_after = self.rng_state_hash();
let output_snapshot = self.snapshot();
let duration_us = start_time.elapsed().as_micros() as u64;
let mut entry = StepEntry::new(
self.step_counter,
SimTime::from_secs(self.step_counter as f64 * 0.001), step_type.to_string(),
input_snapshot,
output_snapshot,
)
.with_rng_states(rng_before, rng_after)
.with_duration(duration_us);
for eq in equations {
entry.add_equation_eval(eq);
}
for dec in decisions {
entry.add_decision(dec);
}
self.audit_log.push(entry);
self.step_counter += 1;
}
pub fn set_audit_enabled(&mut self, enabled: bool) {
self.audit_enabled = enabled;
}
}
impl SimulationAuditLog for TspGraspDemo {
type StateSnapshot = TspStateSnapshot;
fn log_step(&mut self, entry: StepEntry<Self::StateSnapshot>) {
self.audit_log.push(entry);
}
fn audit_log(&self) -> &[StepEntry<Self::StateSnapshot>] {
&self.audit_log
}
fn audit_log_mut(&mut self) -> &mut Vec<StepEntry<Self::StateSnapshot>> {
&mut self.audit_log
}
fn clear_audit_log(&mut self) {
self.audit_log.clear();
self.step_counter = 0;
}
}
impl EddDemo for TspGraspDemo {
fn name(&self) -> &'static str {
"TSP Randomized Greedy Start with 2-Opt"
}
fn emc_ref(&self) -> &'static str {
"optimization/tsp_grasp_2opt"
}
fn step(&mut self, _dt: f64) {
self.grasp_iteration();
}
fn verify_equation(&self) -> bool {
let gap_ok = self.optimality_gap() < 0.20;
let cv_ok = self.restart_cv() < 0.05 || self.restart_history.len() < 5;
let no_crossings = !self.is_euclidean || self.best_tour.is_empty() || {
let mut temp = self.clone();
temp.tour.clone_from(&self.best_tour);
temp.count_crossings() == 0
};
gap_ok && cv_ok && no_crossings
}
fn get_falsification_status(&self) -> FalsificationStatus {
let gap = self.optimality_gap();
let cv = self.restart_cv();
let crossings = if self.best_tour.is_empty() {
0
} else {
let mut temp = self.clone();
temp.tour.clone_from(&self.best_tour);
temp.count_crossings()
};
let gap_passed = gap < 0.20;
let cv_passed = cv < 0.05 || self.restart_history.len() < 5;
let crossings_passed = !self.is_euclidean || crossings == 0;
FalsificationStatus {
verified: gap_passed && cv_passed && crossings_passed,
criteria: vec![
CriterionStatus {
id: "TSP-CROSSINGS".to_string(),
name: if self.is_euclidean {
"Edge crossings (Jidoka)".to_string()
} else {
"Edge crossings (N/A: non-Euclidean)".to_string()
},
passed: crossings_passed,
value: crossings as f64,
threshold: 0.0,
},
CriterionStatus {
id: "TSP-GAP".to_string(),
name: "Optimality gap (1-tree, Held-Karp 1970)".to_string(),
passed: gap_passed,
value: gap,
threshold: 0.20,
},
CriterionStatus {
id: "TSP-VARIANCE".to_string(),
name: "Restart consistency (CV)".to_string(),
passed: cv_passed,
value: cv,
threshold: 0.05,
},
],
message: if gap_passed && cv_passed && crossings_passed {
if self.is_euclidean {
format!(
"TSP GRASP verified: crossings=0, gap={:.1}%, CV={:.1}%, best={:.1}",
gap * 100.0,
cv * 100.0,
self.best_tour_length
)
} else {
format!(
"TSP GRASP verified (non-Euclidean): gap={:.1}%, CV={:.1}%, best={:.1}",
gap * 100.0,
cv * 100.0,
self.best_tour_length
)
}
} else if !crossings_passed && self.is_euclidean {
format!(
"JIDOKA STOP: Tour has {crossings} edge crossing(s) - \
Euclidean TSP tours MUST have 0 (Lin & Kernighan, 1973)"
)
} else if !gap_passed {
format!(
"Optimality gap too large: {:.1}% (expected <20%, Held-Karp 1970)",
gap * 100.0
)
} else {
format!(
"Restart variance too high: CV={:.1}% (expected <5%)",
cv * 100.0
)
},
}
}
fn reset(&mut self) {
let seed = self.seed;
let n = self.n;
let method = self.construction_method;
let rcl_size = self.rcl_size;
let cities = self.cities.clone();
*self = Self::with_cities(seed, cities);
self.construction_method = method;
self.rcl_size = rcl_size;
self.n = n;
}
}
#[cfg(feature = "wasm")]
mod wasm {
use super::{City, ConstructionMethod, EddDemo, TspGraspDemo};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct WasmTspGrasp {
inner: TspGraspDemo,
}
#[wasm_bindgen]
impl WasmTspGrasp {
#[wasm_bindgen(constructor)]
pub fn new(seed: u32, n: usize) -> Self {
Self {
inner: TspGraspDemo::new(u64::from(seed), n),
}
}
#[wasm_bindgen(js_name = fromYaml)]
pub fn from_yaml(yaml: &str) -> Result<Self, String> {
let inner = TspGraspDemo::from_yaml(yaml).map_err(|e| e.to_string())?;
Ok(Self { inner })
}
#[wasm_bindgen(js_name = withCities)]
pub fn with_cities_js(seed: u32, cities_js: &JsValue) -> Result<Self, JsValue> {
let cities_array: Vec<Vec<f64>> = serde_wasm_bindgen::from_value(cities_js.clone())
.map_err(|e| JsValue::from_str(&format!("Invalid cities format: {e}")))?;
let cities: Vec<City> = cities_array
.into_iter()
.filter_map(|coords| {
if coords.len() >= 2 {
Some(City::new(coords[0], coords[1]))
} else {
None
}
})
.collect();
if cities.len() < 3 {
return Err(JsValue::from_str("Need at least 3 cities"));
}
Ok(Self {
inner: TspGraspDemo::with_cities(u64::from(seed), cities),
})
}
pub fn step(&mut self) {
self.inner.step(0.0);
}
#[wasm_bindgen(js_name = graspIteration)]
pub fn grasp_iteration(&mut self) {
self.inner.grasp_iteration();
}
#[wasm_bindgen(js_name = runGrasp)]
pub fn run_grasp(&mut self, iterations: usize) {
self.inner.run_grasp(iterations);
}
#[wasm_bindgen(js_name = constructTour)]
pub fn construct_tour(&mut self) {
self.inner.construct_tour();
}
#[wasm_bindgen(js_name = twoOptPass)]
pub fn two_opt_pass(&mut self) -> bool {
self.inner.two_opt_pass()
}
#[wasm_bindgen(js_name = twoOptToLocalOptimum)]
pub fn two_opt_to_local_optimum(&mut self) {
self.inner.two_opt_to_local_optimum();
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = setConstructionMethod)]
pub fn set_construction_method(&mut self, method: u8) {
self.inner.construction_method = match method {
1 => ConstructionMethod::NearestNeighbor,
2 => ConstructionMethod::Random,
_ => ConstructionMethod::RandomizedGreedy, };
}
#[wasm_bindgen(js_name = setRclSize)]
pub fn set_rcl_size(&mut self, size: usize) {
self.inner.set_rcl_size(size);
}
#[wasm_bindgen(js_name = getTourLength)]
pub fn get_tour_length(&self) -> f64 {
self.inner.tour_length
}
#[wasm_bindgen(js_name = getBestTourLength)]
pub fn get_best_tour_length(&self) -> f64 {
self.inner.best_tour_length
}
#[wasm_bindgen(js_name = getOptimalityGap)]
pub fn get_optimality_gap(&self) -> f64 {
self.inner.optimality_gap()
}
#[wasm_bindgen(js_name = getLowerBound)]
pub fn get_lower_bound(&self) -> f64 {
self.inner.lower_bound
}
#[wasm_bindgen(js_name = getRestarts)]
pub fn get_restarts(&self) -> u64 {
self.inner.restarts
}
#[wasm_bindgen(js_name = getTwoOptIterations)]
pub fn get_two_opt_iterations(&self) -> u64 {
self.inner.two_opt_iterations
}
#[wasm_bindgen(js_name = getTwoOptImprovements)]
pub fn get_two_opt_improvements(&self) -> u64 {
self.inner.two_opt_improvements
}
#[wasm_bindgen(js_name = getN)]
pub fn get_n(&self) -> usize {
self.inner.n
}
#[wasm_bindgen(js_name = getUnits)]
pub fn get_units(&self) -> String {
self.inner.units.clone()
}
#[wasm_bindgen(js_name = getOptimalKnown)]
pub fn get_optimal_known(&self) -> Option<u32> {
self.inner.optimal_known
}
#[wasm_bindgen(js_name = getRclSize)]
pub fn get_rcl_size(&self) -> usize {
self.inner.rcl_size
}
#[wasm_bindgen(js_name = getConstructionMethod)]
pub fn get_construction_method(&self) -> u8 {
match self.inner.construction_method {
ConstructionMethod::RandomizedGreedy => 0,
ConstructionMethod::NearestNeighbor => 1,
ConstructionMethod::Random => 2,
}
}
#[wasm_bindgen(js_name = getRestartVariance)]
pub fn get_restart_variance(&self) -> f64 {
self.inner.restart_variance()
}
#[wasm_bindgen(js_name = getRestartCv)]
pub fn get_restart_cv(&self) -> f64 {
self.inner.restart_cv()
}
#[wasm_bindgen(js_name = getCities)]
pub fn get_cities_js(&self) -> JsValue {
let cities: Vec<[f64; 2]> = self.inner.cities.iter().map(|c| [c.x, c.y]).collect();
serde_wasm_bindgen::to_value(&cities).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = getTour)]
pub fn get_tour_js(&self) -> JsValue {
serde_wasm_bindgen::to_value(&self.inner.tour).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = getBestTour)]
pub fn get_best_tour_js(&self) -> JsValue {
serde_wasm_bindgen::to_value(&self.inner.best_tour).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = getRestartHistory)]
pub fn get_restart_history_js(&self) -> JsValue {
serde_wasm_bindgen::to_value(&self.inner.restart_history).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = getState)]
pub fn get_state_js(&self) -> JsValue {
serde_wasm_bindgen::to_value(&self.inner).unwrap_or(JsValue::NULL)
}
pub fn verify(&self) -> bool {
self.inner.verify_equation()
}
#[wasm_bindgen(js_name = getFalsificationStatus)]
pub fn get_falsification_status_js(&self) -> JsValue {
let status = self.inner.get_falsification_status();
serde_wasm_bindgen::to_value(&status).unwrap_or(JsValue::NULL)
}
#[wasm_bindgen(js_name = getName)]
pub fn get_name(&self) -> String {
self.inner.name().to_string()
}
#[wasm_bindgen(js_name = getEmcRef)]
pub fn get_emc_ref(&self) -> String {
self.inner.emc_ref().to_string()
}
}
}
#[cfg(test)]
mod quality_tests;
#[cfg(test)]
mod tests;