~ruther/ctu-fee-eoa

fa9a5aff839b19a79677e6fdc8f605456c389f08 — Rutherther a month ago 1d88957
feat: add plotting of TSP
2 files changed, 69 insertions(+), 0 deletions(-)

M codes/tsp_hw01/Cargo.toml
M codes/tsp_hw01/src/tsp.rs
M codes/tsp_hw01/Cargo.toml => codes/tsp_hw01/Cargo.toml +1 -0
@@ 7,4 7,5 @@ edition = "2024"
eoa_lib = { path = "../eoa_lib" }
itertools = "0.14.0"
nalgebra = "0.33.2"
plotters = "0.3"
rand = "0.9.2"

M codes/tsp_hw01/src/tsp.rs => codes/tsp_hw01/src/tsp.rs +68 -0
@@ 3,6 3,7 @@ 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 plotters::prelude::*;
use rand::{seq::SliceRandom, Rng, RngCore};

#[derive(PartialEq, Clone, Debug)]


@@ 117,6 118,73 @@ where
            .map(|(&node1, &node2): (&usize, &usize)| self.distances[(node1, node2)])
            .sum()
    }

    fn plot_internal(&self, solution: Option<&NodePermutation<D>>, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
        let root = BitMapBackend::new(filename, (800, 600)).into_drawing_area();
        root.fill(&WHITE)?;

        let x_coords: Vec<f64> = self.cities.iter().map(|city| city.point.x).collect();
        let y_coords: Vec<f64> = self.cities.iter().map(|city| city.point.y).collect();

        let x_min = x_coords.iter().fold(f64::INFINITY, |a, &b| a.min(b));
        let x_max = x_coords.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
        let y_min = y_coords.iter().fold(f64::INFINITY, |a, &b| a.min(b));
        let y_max = y_coords.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));

        let x_padding = (x_max - x_min) * 0.1;
        let y_padding = (y_max - y_min) * 0.1;

        let x_range = (x_min - x_padding)..(x_max + x_padding);
        let y_range = (y_min - y_padding)..(y_max + y_padding);

        let title = if let Some(sol) = solution {
            format!("TSP Solution (Cost: {:.2})", self.solution_cost(sol))
        } else {
            "TSP Instance".to_string()
        };

        let mut chart = ChartBuilder::on(&root)
            .caption(&title, ("sans-serif", 40))
            .margin(10)
            .x_label_area_size(40)
            .y_label_area_size(40)
            .build_cartesian_2d(x_range, y_range)?;

        chart.configure_mesh().draw()?;

        if let Some(sol) = solution {
            chart.draw_series(
                sol.permutation.iter().circular_tuple_windows().map(|(&city1_idx, &city2_idx)| {
                    let city1 = &self.cities[city1_idx];
                    let city2 = &self.cities[city2_idx];
                    PathElement::new(vec![(city1.point.x, city1.point.y), (city2.point.x, city2.point.y)], BLUE)
                })
            )?;
        }

        chart.draw_series(
            self.cities.iter().map(|city| {
                Circle::new((city.point.x, city.point.y), 5, RED.filled())
            })
        )?;

        chart.draw_series(
            self.cities.iter().enumerate().map(|(i, city)| {
                Text::new(format!("{}", i), (city.point.x + 0.1, city.point.y + 0.1), ("sans-serif", 15))
            })
        )?;

        root.present()?;
        Ok(())
    }

    pub fn plot(&self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
        self.plot_internal(None, filename)
    }

    pub fn draw_solution(&self, solution: &NodePermutation<D>, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
        self.plot_internal(Some(solution), filename)
    }
}

impl<D> FitnessFunction for TSPInstance<D>