@@ 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>