~ruther/ctu-fee-eoa

c90789c104b6d77738efdef0828a4d101b54c550 — Rutherther a month ago 2079f94
feat(tsp): add data loading
3 files changed, 100 insertions(+), 3 deletions(-)

M codes/tsp_hw01/Cargo.toml
M codes/tsp_hw01/src/main.rs
M codes/tsp_hw01/src/tsp.rs
M codes/tsp_hw01/Cargo.toml => codes/tsp_hw01/Cargo.toml +1 -0
@@ 5,6 5,7 @@ edition = "2024"

[dependencies]
eoa_lib = { path = "../eoa_lib" }
flate2 = "1.0"
itertools = "0.14.0"
nalgebra = "0.33.2"
plotters = "0.3"

M codes/tsp_hw01/src/main.rs => codes/tsp_hw01/src/main.rs +95 -3
@@ 1,8 1,100 @@
pub mod tsp;
pub mod graph;

use eoa_lib::local_search::local_search_first_improving;
use tsp::{TSPInstance, TSPRandomInitializer, SwapPerturbation};
use nalgebra::{Const, Dyn};
use eoa_lib::{
    initializer::Initializer,
    local_search::local_search_first_improving,
    terminating::NoBetterForCyclesTerminatingCondition,
    comparison::MinimizingOperator,
};
use rand::rng;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use flate2::read::GzDecoder;

fn main() {
    println!("Hello, world!");
fn load_tsp_instance(filename: &str) -> Result<TSPInstance<Dyn>, Box<dyn std::error::Error>> {
    let file = File::open(filename)?;
    let reader: Box<dyn BufRead> = if filename.ends_with(".gz") {
        Box::new(BufReader::new(GzDecoder::new(file)))
    } else {
        Box::new(BufReader::new(file))
    };

    let mut cities = Vec::new();
    let mut in_coord_section = false;
    let mut dimension = 0;

    for line in reader.lines() {
        let line = line?.trim().to_string();

        if line.starts_with("DIMENSION") {
            dimension = line.split(':').nth(1)
                .or_else(|| line.split_whitespace().nth(1))
                .ok_or("Could not parse dimension")?
                .trim()
                .parse::<usize>()?;
        } else if line == "NODE_COORD_SECTION" {
            in_coord_section = true;
        } else if line == "EOF" {
            break;
        } else if in_coord_section && !line.is_empty() {
            let parts: Vec<&str> = line.split_whitespace().collect();
            if parts.len() >= 3 {
                let x: f64 = parts[1].parse()?;
                let y: f64 = parts[2].parse()?;
                cities.push((x, y));
            }
        }
    }

    if cities.len() != dimension {
        return Err(format!("Expected {} cities, found {}", dimension, cities.len()).into());
    }

    Ok(TSPInstance::new_dyn(cities))
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load a TSP instance from file
    let instance = load_tsp_instance("instances/berlin52.tsp.gz")?;
    let dimension = instance.dimension();

    // Plot just the instance
    instance.plot("tsp_instance.png")?;
    println!("Created tsp_instance.png");

    // Create a random solution
    let initializer = TSPRandomInitializer::new();
    let mut rng = rng();
    let initial_solution = initializer.initialize_single(dimension, &mut rng);

    // Plot the initial solution
    instance.draw_solution(&initial_solution, "tsp_initial_solution.png")?;
    println!("Created tsp_initial_solution.png with cost: {:.2}", instance.solution_cost(&initial_solution));

    // Run local search to improve the solution
    let mut perturbation = SwapPerturbation::new();
    let mut terminating_condition = NoBetterForCyclesTerminatingCondition::new(100000);
    let better_than_operator = MinimizingOperator::new();

    let result = local_search_first_improving(
        &instance,
        &mut terminating_condition,
        &mut perturbation,
        &better_than_operator,
        &initial_solution,
        &mut rng,
    )?;

    // Plot the improved solution
    instance.draw_solution(&result.best_candidate.pos, "tsp_improved_solution.png")?;
    println!("Created tsp_improved_solution.png with cost: {:.2}", result.best_candidate.fit);
    println!("Improvement: {:.2} -> {:.2} (cycles: {})",
        instance.solution_cost(&initial_solution),
        result.best_candidate.fit,
        result.cycles);

    Ok(())
}

M codes/tsp_hw01/src/tsp.rs => codes/tsp_hw01/src/tsp.rs +4 -0
@@ 87,6 87,10 @@ where
    DefaultAllocator: Allocator<D, D>,
    DefaultAllocator: Allocator<D>,
{
    pub fn dimension(&self) -> D {
        self.distances.shape_generic().0
    }

    pub fn verify_solution(&self, solution: &NodePermutation<D>) -> bool {
        let mut seen_vertices = OVector::from_element_generic(
            solution.permutation.shape_generic().0,