From bb0d963190fd1877f626a449ad1a6bd9125363a7 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Mon, 27 Oct 2025 11:36:14 +0100 Subject: [PATCH] feat: init tsp_hw01 --- codes/Cargo.toml | 2 +- codes/tsp_hw01/Cargo.toml | 10 + codes/tsp_hw01/src/graph.rs | 528 ++++++++++++++++++++++++++++++++++++ codes/tsp_hw01/src/main.rs | 8 + codes/tsp_hw01/src/tsp.rs | 190 +++++++++++++ 5 files changed, 737 insertions(+), 1 deletion(-) create mode 100644 codes/tsp_hw01/Cargo.toml create mode 100644 codes/tsp_hw01/src/graph.rs create mode 100644 codes/tsp_hw01/src/main.rs create mode 100644 codes/tsp_hw01/src/tsp.rs diff --git a/codes/Cargo.toml b/codes/Cargo.toml index fa30a3723a5d9406e72fb2f05519b810addab4b3..28906b229fec4eca6cffd594d58df2a225d694ed 100644 --- a/codes/Cargo.toml +++ b/codes/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "3" -members = ["eoa_lib"] \ No newline at end of file +members = ["eoa_lib", "tsp_hw01"] diff --git a/codes/tsp_hw01/Cargo.toml b/codes/tsp_hw01/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ed229b7aeccaf2240281432be58aaee2941b8622 --- /dev/null +++ b/codes/tsp_hw01/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tsp_hw01" +version = "0.1.0" +edition = "2024" + +[dependencies] +eoa_lib = { path = "../eoa_lib" } +itertools = "0.14.0" +nalgebra = "0.33.2" +rand = "0.9.2" diff --git a/codes/tsp_hw01/src/graph.rs b/codes/tsp_hw01/src/graph.rs new file mode 100644 index 0000000000000000000000000000000000000000..8ca45f98a822f34270b69ca58e248bbdadbff694 --- /dev/null +++ b/codes/tsp_hw01/src/graph.rs @@ -0,0 +1,528 @@ +use std::collections::VecDeque; + +pub type Distance = usize; + +pub trait Graph { + type Node; + type Edge: Edge; + + /// All nodes. + fn nodes(&self) -> impl Iterator; + /// All edges. + fn edges(&self) -> impl Iterator; + + /// Indices of neighbors reachable from node. + fn neighbor_idxs(&self, node: usize) -> Option>; + /// Indices of neighbors that can reach node. + /// For directed graphs, returns same result as neighbor_idxs + fn reverse_neighbor_idxs(&self, node: usize) -> Option>; + /// All edges going to or from node. + /// For directed graphs, mind the from and to distinction. + fn edges_of_idxs(&self, node: usize) -> Option>; + + /// Look if there is an edge between nodes from and to. + /// It is expected this function will be overriden with more performant version. + fn has_edge(&self, from: usize, to: usize) -> bool { + self.edges() + .any(|edge| edge.from_node() == from && edge.to_node() == to) + } + + /// Find an edge that connects nodes from and to, if it exists. + /// If it doesn't, return none. + /// It is expected this function will be overriden with more performant version. + fn get_edge_between(&self, from: usize, to: usize) -> Option { + self.edges_of_idxs(from) + .map(|edges| edges + .filter(|&edge| self.edge(edge).unwrap().to_node() == to) + .next()) + .flatten() + } + + /// Get a single edge at the given index. + fn edge(&self, id: usize) -> Option<&Self::Edge> { + self.edges().skip(id).next() + } + + /// Get a single node at the given index. + fn node(&self, id: usize) -> Option<&Self::Node> { + self.nodes().skip(id).next() + } +} + +pub trait MutGraph: Graph { + fn nodes_mut(&mut self) -> impl Iterator; + fn edges_mut(&mut self) -> impl Iterator; + + fn node_mut(&mut self, id: usize) -> Option<&mut Self::Node> { + self.nodes_mut().skip(id).next() + } + + fn edge_mut(&mut self, id: usize) -> Option<&mut Self::Edge> { + self.edges_mut().skip(id).next() + } +} + +pub trait WeightedEdge { + type Cost: PartialOrd; + fn cost(&self) -> Self::Cost; +} + +/// An edge. +pub trait Edge { + /// Index of a node this edge goes from. + /// For undirected graphs, from_node and to_node have the same meaning. + fn from_node(&self) -> usize; + /// Index of a node this edge goes to. + /// For undirected graphs, from_node and to_node have the same meaning. + fn to_node(&self) -> usize; +} + +/// An edge that might be reversed easily. +pub trait ReversibleEdge: Edge { + fn reverse(self) -> Self; +} + +#[derive(Debug, Clone, PartialEq)] +pub struct GenericEdge { + from: usize, + to: usize, +} + +impl GenericEdge { + pub fn new(from: usize, to: usize) -> Self { + Self { + from, + to + } + } +} + +impl From<(usize, usize)> for GenericEdge { + fn from(value: (usize, usize)) -> Self { + Self { + from: value.0, + to: value.1 + } + } +} + +impl Edge for GenericEdge { + fn from_node(&self) -> usize { + self.from + } + + fn to_node(&self) -> usize { + self.to + } + +} + +impl ReversibleEdge for GenericEdge { + fn reverse(mut self) -> Self { + (self.from, self.to) = (self.to, self.from); + self + } +} + + +/// A directed graph that owns nodes and edges of +/// specific types given by generics. +#[derive(Debug, Clone, PartialEq)] +pub struct GenericDirectedGraph +{ + nodes: Vec, + edges: Vec, + node_edges: Vec>, + reverse_node_edges: Vec>, +} + +impl GenericDirectedGraph { + pub fn new(nodes: Vec) -> Self { + let nodes_len = nodes.len(); + Self { + nodes, + edges: vec![], + node_edges: vec![vec![]; nodes_len], + reverse_node_edges: vec![vec![]; nodes_len], + } + } + + pub fn add_generic_edge(&mut self, edge: TEdge) -> usize { + let idx = self.edges.len(); + + self.node_edges[edge.from_node()].push(idx); + self.reverse_node_edges[edge.to_node()].push(idx); + self.edges.push(edge); + + idx + } + + pub fn decompose(self) -> Vec { + self.nodes + } +} + +impl GenericDirectedGraph { + pub fn reverse(mut self) -> Self { + (self.node_edges, self.reverse_node_edges) = + (self.reverse_node_edges, self.node_edges); + + self.edges = self.edges + .into_iter() + .map(|edge| edge.reverse()) + .collect(); + + self + } +} + +impl> GenericDirectedGraph { + pub fn add_edge(&mut self, from: usize, to: usize) -> usize { + let edge = (from, to).into(); + self.add_generic_edge(edge) + } +} + +impl Graph for GenericDirectedGraph { + type Node = T; + type Edge = TEdge; + + fn nodes(&self) -> impl Iterator { + self.nodes.iter() + } + + fn edges(&self) -> impl Iterator { + self.edges.iter() + } + + fn neighbor_idxs(&self, node: usize) -> Option> { + self.node_edges.get(node) + .map(|edges| edges.iter() + .map(|i| self.edges[*i].to_node())) + } + + fn reverse_neighbor_idxs(&self, node: usize) -> Option> { + self.reverse_node_edges.get(node) + .map(|edges| edges.iter() + .map(|i| self.edges[*i].from_node())) + } + + fn edges_of_idxs(&self, node: usize) -> Option> { + let reverse_edges = self.reverse_node_edges.get(node)?; + + self.node_edges.get(node) + .map(|edges| + edges.iter() + .chain(reverse_edges.iter()) + .map(|i| *i)) + } +} + +impl MutGraph for GenericDirectedGraph { + fn nodes_mut(&mut self) -> impl Iterator { + self.nodes.iter_mut() + } + + fn edges_mut(&mut self) -> impl Iterator { + self.edges.iter_mut() + } +} + +/// An undirected graph that owns nodes and edges of +/// specific types given by generics. +pub struct GenericGraph { + nodes: Vec, + edges: Vec, + node_neighbors: Vec>, + node_edges: Vec>, + adjacency_matrix: Option>>> +} + +impl GenericGraph +{ + pub fn new(nodes: Vec, adjacency_matrix: bool) -> Self { + let nodes_count = nodes.len(); + Self { + nodes, + edges: vec![], + node_neighbors: vec![vec![]; nodes_count], + node_edges: vec![vec![]; nodes_count], + adjacency_matrix: if adjacency_matrix { + Some(vec![vec![None; nodes_count]; nodes_count]) + } else { + None + } + } + } + + pub fn add_generic_edge(&mut self, edge: TEdge) -> usize { + let idx = self.edges.len(); + + if let Some(adjacency_matrix) = self.adjacency_matrix.as_deref_mut() { + adjacency_matrix[edge.from_node()][edge.to_node()] = Some(idx); + adjacency_matrix[edge.to_node()][edge.from_node()] = Some(idx); + } + + self.node_edges[edge.from_node()].push(idx); + self.node_edges[edge.to_node()].push(idx); + self.node_neighbors[edge.from_node()].push(edge.to_node()); + self.node_neighbors[edge.to_node()].push(edge.from_node()); + self.edges.push(edge); + + + idx + } + + // NOTE: it's expected the edges will not reconnect, only the type will change. + // from_node() and to_node() should stay the same! + pub fn map_edges(self, map: impl Fn(TEdge) -> TNewEdge) -> GenericGraph { + GenericGraph:: { + nodes: self.nodes, + edges: self.edges.into_iter().map(|edge| map(edge)).collect(), + node_neighbors: self.node_neighbors, + node_edges: self.node_edges, + adjacency_matrix: self.adjacency_matrix + } + } + + pub fn map_nodes(self, map: impl Fn(T) -> TNewNode) -> GenericGraph { + GenericGraph:: { + nodes: self.nodes.into_iter().map(|node| map(node)).collect(), + edges: self.edges, + node_neighbors: self.node_neighbors, + node_edges: self.node_edges, + adjacency_matrix: self.adjacency_matrix + } + } +} + +impl Graph for GenericGraph { + type Node = T; + type Edge = TEdge; + + fn nodes(&self) -> impl Iterator { + self.nodes.iter() + } + + fn edges(&self) -> impl Iterator { + self.edges.iter() + } + + fn neighbor_idxs(&self, node: usize) -> Option> { + self.node_neighbors.get(node).map(|neighbors| neighbors.iter().map(|&node| node)) + } + + fn reverse_neighbor_idxs(&self, node: usize) -> Option> { + self.node_neighbors.get(node).map(|neighbors| neighbors.iter().map(|&node| node)) + } + + fn edges_of_idxs(&self, node: usize) -> Option> { + self.node_edges.get(node).map(|edges| edges.iter().map(|&edge| edge)) + } + + fn has_edge(&self, from: usize, to: usize) -> bool { + if let Some(adjacency_matrix) = &self.adjacency_matrix { + adjacency_matrix[from][to].is_some() + } else { + self.edges() + .any(|edge| edge.from_node() == from && edge.to_node() == to) + } + } + + fn get_edge_between(&self, from: usize, to: usize) -> Option { + if let Some(adjacency_matrix) = &self.adjacency_matrix { + adjacency_matrix[from][to] + } else { + self.edges_of_idxs(from) + .map(|edges| edges + .filter(|&edge| self.edge(edge).unwrap().to_node() == to) + .next()) + .flatten() + } + } +} + +impl MutGraph for GenericGraph { + fn nodes_mut(&mut self) -> impl Iterator { + self.nodes.iter_mut() + } + + fn edges_mut(&mut self) -> impl Iterator { + self.edges.iter_mut() + } +} + +pub struct ReversedEdge<'a, T: Edge> { + edge: &'a T +} + +impl<'a, T: Edge> ReversedEdge<'a, T> { + pub fn new(edge: &'a T) -> Self { + Self { + edge + } + } +} + +impl<'a, T: Edge> Edge for ReversedEdge<'a, T> { + fn from_node(&self) -> usize { + self.edge.to_node() + } + + fn to_node(&self) -> usize { + self.edge.from_node() + } +} + +/// A view on a graph that reverses all its +/// edges. +pub struct ReversedGraph<'a, T: Graph> { + graph: &'a T, + edges: Vec> +} + +impl<'a, T: Graph> ReversedGraph<'a, T> { + pub fn new(graph: &'a T) -> Self { + Self { + graph, + edges: graph.edges() + .map(|edge| ReversedEdge::new(edge)) + .collect() + } + } +} + +impl<'a, T: Graph> Graph for ReversedGraph<'a, T> { + type Node = T::Node; + type Edge = ReversedEdge<'a, T::Edge>; + + fn nodes(&self) -> impl Iterator { + self.graph.nodes() + } + + fn edges(&self) -> impl Iterator { + self.edges.iter() + } + + fn neighbor_idxs(&self, node: usize) -> Option> { + self.graph.reverse_neighbor_idxs(node) + } + + fn reverse_neighbor_idxs(&self, node: usize) -> Option> { + self.graph.neighbor_idxs(node) + } + + fn edges_of_idxs(&self, node: usize) -> Option> { + self.graph.edges_of_idxs(node) + } +} + +/// Make a search out of the starting node, visiting every +/// node that can be visited from it. Denoting the +/// distances between the nodes and the parents that visited the +/// given node for reconstructing the shortest path. +pub fn breadth_first_search( + graph: &TGraph, + starting_node: usize, +) -> (Vec>, Vec) +where + TGraph: Graph +{ + let nodes_len = graph.nodes().count(); + let mut distances = vec![None; nodes_len]; + let mut visited = vec![false; nodes_len]; + let mut parents = vec![0; nodes_len]; + + let mut node_queue = VecDeque::with_capacity(nodes_len / 4); + + node_queue.push_back(starting_node); + distances[starting_node] = Some(0); + visited[starting_node] = true; + + while let Some(current) = node_queue.pop_front() { + for neighbor in graph.neighbor_idxs(current).unwrap() { + if visited[neighbor] { + continue + } + + visited[neighbor] = true; + distances[neighbor] = Some(distances[current].unwrap() + 1); + parents[neighbor] = current; + + node_queue.push_back(neighbor); + } + } + + (distances, parents) +} + +/// Like breadth first search, but only look if a node +/// is reachable, do not figure out the distance, do not +/// figure out the shortest path. +pub fn breadth_first_reachable( + graph: &TGraph, + starting_node: usize, +) -> Vec +where + TGraph: Graph +{ + let nodes_len = graph.nodes().count(); + let mut visited = vec![false; nodes_len]; + + let mut node_queue = VecDeque::with_capacity(nodes_len / 4); + + node_queue.push_back(starting_node); + visited[starting_node] = true; + + while let Some(current) = node_queue.pop_front() { + for neighbor in graph.neighbor_idxs(current).unwrap() { + if visited[neighbor] { + continue + } + + visited[neighbor] = true; + + node_queue.push_back(neighbor); + } + } + + visited +} + +/// Figure out distance from each node to each node. +/// In case the node is unreachable, Distance::MAX is +/// in the matrix. +pub fn floyd_warshall( + graph: &TGraph +) -> Vec> +where + TEdge: Edge, + TGraph: Graph +{ + let nodes = graph.nodes().count(); + let mut distances = vec![vec![Distance::MAX; nodes]; nodes]; + + for edge in graph.edges() { + distances[edge.from_node()][edge.to_node()] = 1; + } + + for v in 0..nodes { + distances[v][v] = 0; + } + + for k in 0..nodes { + for i in 0..nodes { + for j in 00..nodes { + if distances[i][k] == Distance::MAX || distances[k][j] == Distance::MAX { + continue; + } + + if distances[i][j] > distances[i][k] + distances[k][j] { + distances[i][j] = distances[i][k] + distances[k][j]; + } + } + } + } + + distances +} diff --git a/codes/tsp_hw01/src/main.rs b/codes/tsp_hw01/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..50276aa691f7e04cd15afc6d298e49288f7f82c5 --- /dev/null +++ b/codes/tsp_hw01/src/main.rs @@ -0,0 +1,8 @@ +pub mod tsp; +pub mod graph; + +use eoa_lib::local_search::local_search_first_improving; + +fn main() { + println!("Hello, world!"); +} diff --git a/codes/tsp_hw01/src/tsp.rs b/codes/tsp_hw01/src/tsp.rs new file mode 100644 index 0000000000000000000000000000000000000000..5932f0b5e4779ff545c16496eca63a1d238510e8 --- /dev/null +++ b/codes/tsp_hw01/src/tsp.rs @@ -0,0 +1,190 @@ +use std::{convert::Infallible, marker::PhantomData}; + +use eoa_lib::{fitness::FitnessFunction, initializer::Initializer, perturbation::PerturbationOperator}; +use itertools::Itertools; +use nalgebra::{allocator::Allocator, distance, Const, DefaultAllocator, Dim, Dyn, OMatrix, OVector, Point, U1}; +use rand::{seq::SliceRandom, Rng, RngCore}; + +#[derive(PartialEq, Clone, Debug)] +pub struct TSPCity { + point: Point +} + +#[derive(PartialEq, Clone, Debug)] +pub struct NodePermutation +where + DefaultAllocator: Allocator +{ + permutation: OVector +} + +/// An instance of TSP, a fully connected graph +/// with cities that connect to each other. +/// The D parameter represents the number of cities. +#[derive(PartialEq, Clone, Debug)] +pub struct TSPInstance +where + D: Dim, + DefaultAllocator: Allocator +{ + cities: Vec, + distances: OMatrix +} + +impl TSPInstance +where +{ + pub fn new_dyn(cities: Vec<(f64, f64)>) -> Self { + let dim = Dyn(cities.len()); + + let cities = OMatrix::>::from_fn_generic(dim, Const::<2>, |i, j| if j == 0 { cities[i].0 } else { cities[i].1 }); + TSPInstance::new(cities) + } +} + +impl TSPInstance> +where +{ + pub fn new_const(cities: Vec<(f64, f64)>) -> Self { + let cities = OMatrix::, Const<2>>::from_fn(|i, j| if j == 0 { cities[i].0 } else { cities[i].1 }); + TSPInstance::new(cities) + } +} + +impl TSPInstance +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator>, +{ + pub fn new(cities: OMatrix>) -> Self { + let dim = cities.shape_generic().0; + + let cities = cities.column_iter() + .map(|position| + TSPCity { point: Point::::new(position[0], position[1]) } + ) + .collect::>(); + + let distances = OMatrix::from_fn_generic( + dim, + dim, + |i, j| distance(&cities[i].point, &cities[j].point) + ); + + Self { + cities, + distances + } + } +} + +impl TSPInstance +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator, +{ + pub fn verify_solution(&self, solution: &NodePermutation) -> bool { + let mut seen_vertices = OVector::from_element_generic( + solution.permutation.shape_generic().0, + solution.permutation.shape_generic().1, + false + ); + + for &vertex in solution.permutation.iter() { + // This vertex index is out of bounds + if vertex >= self.cities.len() { + return false; + } + + // A node is repeating + if seen_vertices[vertex] { + return false; + } + + seen_vertices[vertex] = true; + } + + true + } + + pub fn solution_cost(&self, solution: &NodePermutation) -> f64 { + solution.permutation + .iter() + .circular_tuple_windows() + .map(|(&node1, &node2): (&usize, &usize)| self.distances[(node1, node2)]) + .sum() + } +} + +impl FitnessFunction for TSPInstance +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator, +{ + type In = NodePermutation; + type Out = f64; + type Err = Infallible; + + fn fit(self: &Self, inp: &Self::In) -> Result { + Ok(self.solution_cost(inp)) + } +} + +pub struct TSPRandomInitializer +where + D: Dim, + DefaultAllocator: Allocator, +{ + _phantom: PhantomData, + rng: Box +} + +impl Initializer> for TSPRandomInitializer +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator, +{ + fn initialize_single(&mut self, size: D) -> NodePermutation { + let len = size.value(); + let mut indices = OVector::::from_iterator_generic(size, U1, 0..len); + indices.as_mut_slice().shuffle(&mut self.rng); + + NodePermutation { permutation: indices } + } +} + +pub struct SwapPerturbation { + _phantom: PhantomData, + rng: Box, +} + +impl PerturbationOperator for SwapPerturbation +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator, +{ + type Chromosome = NodePermutation; + + fn perturb(self: &mut Self, chromosome: &Self::Chromosome) -> Self::Chromosome { + let first = self.rng.random_range(0..=chromosome.permutation.len()); + let second = self.rng.random_range(0..=chromosome.permutation.len()); + + let mut new = chromosome.clone(); + + ( + new.permutation[first], + new.permutation[second] + ) = ( + new.permutation[second], + new.permutation[first] + ); + + new + } +}