diff --git a/lab5/src/algo/config.rs b/lab5/src/algo/config.rs new file mode 100644 index 0000000..312b88f --- /dev/null +++ b/lab5/src/algo/config.rs @@ -0,0 +1,3 @@ +pub struct GeneticSimulationConfig { + pub mutation_chance: f64, +} diff --git a/lab5/src/algo/mod.rs b/lab5/src/algo/mod.rs index fef34b6..abcda07 100644 --- a/lab5/src/algo/mod.rs +++ b/lab5/src/algo/mod.rs @@ -1,3 +1,7 @@ mod product; mod mutate; -mod simulation; \ No newline at end of file +mod simulation; +mod config; + +pub use config::GeneticSimulationConfig; +pub use simulation::GeneticSimulationState; \ No newline at end of file diff --git a/lab5/src/algo/simulation.rs b/lab5/src/algo/simulation.rs index e69de29..9871680 100644 --- a/lab5/src/algo/simulation.rs +++ b/lab5/src/algo/simulation.rs @@ -0,0 +1,85 @@ +use crate::algo::config::GeneticSimulationConfig; +use crate::algo::mutate::mutate; +use crate::algo::product::product; +use bgtu_ai_utility::graph::CompleteGraph; +use bgtu_ai_utility::weighted_random_index; +use rand::prelude::SliceRandom; +use rand::Rng; + +pub struct GeneticSimulationState<'a> { + pub config: &'a GeneticSimulationConfig, + pub graph: &'a CompleteGraph<()>, + pub chromosome_size: usize, + pub population: Vec>, +} + +impl<'a> GeneticSimulationState<'a> { + pub fn new( + config: &'a GeneticSimulationConfig, + graph: &'a CompleteGraph<()>, + chromosome_size: usize, + population_size: usize, + rng: &mut impl Rng, + ) -> Self { + let mut population = vec![vec![0; chromosome_size]; population_size]; + for chromosome in population.iter_mut() { + for (i, gene) in chromosome.iter_mut().enumerate() { + *gene = i; + } + chromosome.shuffle(rng) + } + + return Self { + config, + graph, + chromosome_size, + population, + }; + } + + pub fn next_generation(&mut self, rng: &mut impl Rng) { + let mut new_generation = Vec::new(); + let weights = self.calculate_population_weights(self.population.as_slice()); + + for _ in 0..self.population.len() { + let mut new_chromosome = vec![0; self.chromosome_size]; + + product( + &self.population[weighted_random_index(rng, weights.as_slice(), |w| *w).unwrap()], + &self.population[weighted_random_index(rng, weights.as_slice(), |w| *w).unwrap()], + &mut new_chromosome, + rng, + ); + + new_generation.push(new_chromosome); + } + + for h in new_generation.iter_mut() { + if rng.random::() < self.config.mutation_chance { + mutate(h.as_mut(), rng); + } + } + + self.population = new_generation; + } + + fn calculate_population_weights(&self, population: &[Vec]) -> Vec { + return population + .iter() + .map(|h| { + let mut weight = 0.0; + h.iter().reduce(|a, b| { + match self.graph.vertices[*a] + .iter() + .find(|e| self.graph.edges[**e].another(*a) == *b) + { + None => unreachable!(), + Some(e) => weight += self.graph.edges[*e].length, + }; + return b; + }); + return 1.0 / weight; + }) + .collect::>(); + } +} diff --git a/lab5/src/gui/data.rs b/lab5/src/gui/data.rs new file mode 100644 index 0000000..8e42979 --- /dev/null +++ b/lab5/src/gui/data.rs @@ -0,0 +1,52 @@ +use crate::algo::{GeneticSimulationConfig, GeneticSimulationState}; +use bgtu_ai_utility::UpdatePending; +use bgtu_ai_utility::graph::CompleteGraph; +use std::pin::Pin; +use std::ptr::NonNull; + +pub(crate) struct GeneticVisualisationData { + pub config: GeneticSimulationConfig, + pub graph: CompleteGraph<()>, +} + +pub(crate) enum GeneticVisualisationState<'cfg> { + Edit { + config: &'cfg mut GeneticSimulationConfig, + graph: &'cfg mut CompleteGraph<()>, + vertex_update: UpdatePending, + population_size: usize, + generations_count: usize, + }, + Running { + simulation: GeneticSimulationState<'cfg>, + generations_done: usize, + generations_count: usize, + vertex_locations: Vec<(f32, f32)>, + }, +} + +pub(crate) struct GeneticVisualisationApp { + pub data: Pin>, + pub state: GeneticVisualisationState<'static /* actually not but fuck rust */>, +} + +impl GeneticVisualisationApp { + pub fn new() -> Self { + let mut data = Box::pin(GeneticVisualisationData { + config: GeneticSimulationConfig { + mutation_chance: 0.1, + }, + graph: CompleteGraph::new(), + }); + + let state = GeneticVisualisationState::Edit { + config: unsafe { NonNull::from_mut(&mut data.config).as_mut() }, + graph: unsafe { NonNull::from_mut(&mut data.graph).as_mut() }, + population_size: 1, + generations_count: 2, + vertex_update: UpdatePending::NoChange, + }; + + return Self { data, state }; + } +} diff --git a/lab5/src/gui/graph.rs b/lab5/src/gui/graph.rs new file mode 100644 index 0000000..eb1b11c --- /dev/null +++ b/lab5/src/gui/graph.rs @@ -0,0 +1,69 @@ +use crate::algo::GeneticSimulationState; +use bgtu_ai_utility::gui::render_graph; +use eframe::egui::Ui; +use rand::{Rng, rng}; + +pub(crate) fn graph_with_controls( + ui: &mut Ui, + vertex_locations: &mut [(f32, f32)], + data: &mut GeneticSimulationState, + generations_left: &mut usize, + exit: &mut bool, +) { + ui.vertical(|ui| { + ui.horizontal(|ui| { + if ui.button("Exit").clicked() { + *exit = true; + } + + ui.add_enabled_ui(*generations_left > 0, |ui| { + if ui.button("Step").clicked() { + data.next_generation(&mut rng()); + *generations_left -= 1; + } + }); + + ui.label(""); + }); + draw_ants(ui, vertex_locations, data); + if ui.input(|i| i.viewport().close_requested()) { + *exit = true; + } + }); +} + +fn draw_ants(ui: &mut Ui, vertex_locations: &mut [(f32, f32)], data: &GeneticSimulationState) { + let cap = data.population.len() as f64; + let mut usage = vec![0usize; data.graph.edges.capacity()]; + for chromosome in data.population.iter() { + chromosome.iter().reduce(|a, b| { + match data.graph.vertices[*a] + .iter() + .position(|e| data.graph.edges[*e].another(*a) == *b) + { + None => unreachable!(), + Some(e) => usage[e] += 1, + }; + return b; + }); + } + + render_graph( + ui, + &data.graph.vertices, + vertex_locations, + &data.graph.edges, + |ei, _| usage[ei] as f64 / cap, + ) +} + +pub(crate) fn gen_vertex_locations(rng: &mut impl Rng, count: usize) -> Vec<(f32, f32)> { + let mut v = Vec::new(); + for _ in 0..count { + v.push(( + rng.random::() * 0.8 + 0.1, + rng.random::() * 0.8 + 0.1, + )); + } + return v; +} diff --git a/lab5/src/gui/input.rs b/lab5/src/gui/input.rs new file mode 100644 index 0000000..8534e0e --- /dev/null +++ b/lab5/src/gui/input.rs @@ -0,0 +1,60 @@ +use crate::algo::GeneticSimulationConfig; +use bgtu_ai_utility::gui::const_mut_switch::{ + ConstMutSwitchUi, RefType, _ConstMutSwitchUiCallback, +}; +use bgtu_ai_utility::gui::graph_lengths_table::{draw_lengths_table, Graph}; +use bgtu_ai_utility::UpdatePending; + + +pub(crate) fn input( + ctx: &mut Ctx, + mut config: ::Ref<'_, GeneticSimulationConfig>, + graph: &mut impl Graph, + mut population_size: ::Ref<'_, usize>, + mut generations_count: ::Ref<'_, usize>, + mut vertex_update: ::Ref<'_, UpdatePending>, + mut launch: ::Ref<'_, bool>, +) { + ctx.labeled_slider("Population size", 1..=100, 1.0, &mut population_size); + + ctx.space(); + + ctx.labeled_slider("Generations", 1..=100, 1.0, &mut generations_count); + + ctx.space(); + + ctx.labeled_slider( + "Mutation chance", + 0.0..=1.0, + 0.001, + &mut Ctx::RefType::_get( + &mut config, + |config| &config.mutation_chance, + |config| &mut config.mutation_chance, + ), + ); + + ctx.space(); + + ctx.space(); + + ctx.horizontal(ControlsRow { + update: &mut vertex_update, + run: &mut launch, + }); + + draw_lengths_table(ctx, graph, vertex_update) +} + +struct ControlsRow<'uu, 'u, 'rr, 'r, RT: RefType> { + update: &'uu mut RT::Ref<'u, UpdatePending>, + run: &'rr mut RT::Ref<'r, bool>, +} + +impl _ConstMutSwitchUiCallback for ControlsRow<'_, '_, '_, '_, RT> { + fn render(self, ctx: &mut impl ConstMutSwitchUi) { + ctx.button("Add vertex", self.update, |d| *d = UpdatePending::Add); + ctx.separator(); + ctx.button("Run", self.run, |d| *d = true); + } +} diff --git a/lab5/src/gui/mod.rs b/lab5/src/gui/mod.rs new file mode 100644 index 0000000..93fa629 --- /dev/null +++ b/lab5/src/gui/mod.rs @@ -0,0 +1,7 @@ +mod data; +mod input; +mod graph; + +pub(crate) use data::{GeneticVisualisationApp, GeneticVisualisationData, GeneticVisualisationState}; +pub(crate) use input::input; +pub(crate) use graph::{gen_vertex_locations, graph_with_controls}; \ No newline at end of file diff --git a/lab5/src/main.rs b/lab5/src/main.rs index 48ca370..76b21a5 100644 --- a/lab5/src/main.rs +++ b/lab5/src/main.rs @@ -1,176 +1,128 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release mod algo; +mod gui; +use crate::algo::GeneticSimulationState; +use bgtu_ai_utility::UpdatePending; +use bgtu_ai_utility::gui::boot_eframe; +use bgtu_ai_utility::gui::const_mut_switch::{ConstUI, MutUI}; +use bgtu_ai_utility::gui::graph_lengths_table::{ConstGraph, MutableGraph}; +use bgtu_ai_utility::gui::subwindow; use eframe::egui; -use eframe::egui::{Frame, Ui}; -use eframe::emath::Numeric; -use std::collections::HashSet; -use std::ops::RangeInclusive; -use tsp_utility::graph::{EdgesVec, VerticesVec}; -use tsp_utility::gui::lengths_table::{UpdatePending, draw_lengths_table}; +use rand::rng; +use std::ptr::NonNull; fn main() -> eframe::Result { - let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default().with_inner_size([640.0, 400.0]), - ..Default::default() - }; - eframe::run_native( - "Ants simulation", - options, - Box::new(|_cc| Ok(Box::::default())), - ) + return boot_eframe("Genetic simulation", || gui::GeneticVisualisationApp::new()); } -enum ViewState { - Stop, - Running { lastUpdateTimestamp: u64 }, - VertexMove { vertexId: usize }, -} - -enum GlobalState { - Edit {}, - Running { view_state: ViewState }, -} - -struct MyApp { - edges: EdgesVec<()>, - vertices: VerticesVec, - vertex_update: UpdatePending, - population_size: usize, - rounds: usize, - mutation_chance: f64, - state: GlobalState, -} - -impl Default for MyApp { - fn default() -> Self { - return Self { - edges: EdgesVec::new(), - vertices: VerticesVec::new(), - vertex_update: UpdatePending::NoChange, - population_size: 2, - rounds: 1, - mutation_chance: 0.1, - state: GlobalState::Edit {}, - }; - } -} - -fn _slider( - ui: &mut Ui, - name: &str, - storage: &mut T, - range: RangeInclusive, - step: f64, -) { - let label = ui.label(name); - - ui.scope(|ui| { - ui.spacing_mut().slider_width = ui.available_width() - - ui.spacing().interact_size.x - - ui.spacing().button_padding.x * 2.0; - ui.add(egui::Slider::new(storage, range).step_by(step)) - .labelled_by(label.id); - }); -} - -impl eframe::App for MyApp { +impl eframe::App for gui::GeneticVisualisationApp { fn update(&mut self, ui: &eframe::egui::Context, _frame: &mut eframe::Frame) { - egui::CentralPanel::default().show(ui, |ui| match self.state { - GlobalState::Edit {} => { - match self.vertex_update { + let state_ptr = NonNull::from_mut(&mut self.state); + egui::CentralPanel::default().show(ui, |ui| match &mut self.state { + gui::GeneticVisualisationState::Edit { + config, + graph, + vertex_update, + population_size, + generations_count, + } => { + match vertex_update { UpdatePending::NoChange => {} UpdatePending::Add => { - let new_vi = self.vertices.add(HashSet::new()); - let mut newEdgesSet = HashSet::new(); - for (vi, v) in self.vertices.iter_indexed_mut() { - if (vi == new_vi) { - continue; - } - let ei = self.edges.add(new_vi, vi, 1.0, ()); - newEdgesSet.insert(ei); - v.insert(ei); - } - self.vertices[new_vi] = newEdgesSet; - self.vertex_update = UpdatePending::NoChange; + graph.add_vertex(|| ()); + *vertex_update = UpdatePending::NoChange; } UpdatePending::Remove(vi) => { - let mut eis = Vec::with_capacity(self.vertices[vi].len()); - for ei in self.vertices[vi].iter() { - eis.push(*ei) - } - for ei in eis { - self.vertices[self.edges[ei].another(vi)].remove(&ei); - self.edges.remove(ei); - } - self.vertices.remove(vi); - self.vertex_update = UpdatePending::NoChange; + graph.remove_vertex(*vi); + *vertex_update = UpdatePending::NoChange; } } - edit_panel(self, ui) - } - GlobalState::Running { .. } => { - ui.add_enabled_ui(false, |ui| edit_panel(self, ui)); - ui.ctx().show_viewport_immediate( - egui::ViewportId::from_hash_of("Visualisation"), - egui::ViewportBuilder::default() - .with_title("Visualisation") - .with_inner_size([640.0, 480.0]) - .with_resizable(false), - |ui, _| { - egui::CentralPanel::default() - .frame(Frame::default().inner_margin(0.0)) - .show(ui, |ui| visualization_panel(self, ui)); - }, + + let mut run = false; + gui::input( + &mut MutUI { ui }, + *config, + &mut MutableGraph { graph }, + population_size, + generations_count, + vertex_update, + &mut run, ); + + if run { + let ns = gui::GeneticVisualisationState::Running { + simulation: GeneticSimulationState::new( + *config, + *graph, + graph.vertex_count(), + *population_size, + &mut rng(), + ), + generations_done: 0, + vertex_locations: gui::gen_vertex_locations( + &mut rng(), + graph.vertices.capacity(), + ), + generations_count: *generations_count, + }; + unsafe { + state_ptr.write(ns); + } + } + } + gui::GeneticVisualisationState::Running { + simulation, + generations_done, + generations_count, + vertex_locations, + } => { + gui::input( + &mut ConstUI { ui }, + &simulation.config, + &mut ConstGraph { + graph: &simulation.graph, + }, + &simulation.population.len(), + generations_count, + &UpdatePending::NoChange, + &false, + ); + + let mut exit = false; + let mut generations_left = *generations_count - *generations_done; + subwindow( + ui, + "visualisation", + "Visualisation", + |vb| vb.with_inner_size([640.0, 480.0]), + |ui| { + gui::graph_with_controls( + ui, + vertex_locations, + simulation, + &mut generations_left, + &mut exit, + ) + }, + ) + .on_close(|| exit = true); + *generations_done = *generations_count - generations_left; + + if exit { + let ns = gui::GeneticVisualisationState::Edit { + config: unsafe { NonNull::from_mut(&mut self.data.config).as_mut() }, + graph: unsafe { NonNull::from_mut(&mut self.data.graph).as_mut() }, + vertex_update: UpdatePending::NoChange, + population_size: simulation.population.len(), + generations_count: *generations_count, + }; + unsafe { + state_ptr.write(ns); + } + } } }); } } - -fn edit_panel(data: &mut MyApp, ui: &mut Ui) { - let run: bool; - - _slider( - ui, - "Population size", - &mut data.population_size, - 2..=1000, - 1.0, - ); - ui.label(""); - _slider(ui, "Rounds", &mut data.rounds, 1..=100, 1.0); - ui.label(""); - _slider( - ui, - "Mutation chance", - &mut data.mutation_chance, - 0.0..=1.0, - 0.001, - ); - - ui.horizontal(|ui| { - if ui.button("Add vertex").clicked() { - data.vertex_update = UpdatePending::Add; - } - ui.separator(); - let run = ui.button("Run").clicked(); - }); - - draw_lengths_table( - ui, - &mut data.vertices, - &mut data.edges, - &mut data.vertex_update, - ) -} - -fn visualization_panel(data: &mut MyApp, ui: &mut Ui) { - ui.horizontal(|ui| { - if ui.button("Exit").clicked() {} - - if ui.button("Step").clicked() {} - ui.label(""); - }); -}