[lab5] Simulation and visualization

This commit is contained in:
Andrew Golovashevich 2026-02-25 17:09:45 +03:00
parent 0e735f4db5
commit dab2d16e38
8 changed files with 388 additions and 156 deletions

3
lab5/src/algo/config.rs Normal file
View File

@ -0,0 +1,3 @@
pub struct GeneticSimulationConfig {
pub mutation_chance: f64,
}

View File

@ -1,3 +1,7 @@
mod product;
mod mutate;
mod simulation;
mod config;
pub use config::GeneticSimulationConfig;
pub use simulation::GeneticSimulationState;

View File

@ -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<Vec<usize>>,
}
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::<f64>() < self.config.mutation_chance {
mutate(h.as_mut(), rng);
}
}
self.population = new_generation;
}
fn calculate_population_weights(&self, population: &[Vec<usize>]) -> Vec<f64> {
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::<Vec<f64>>();
}
}

52
lab5/src/gui/data.rs Normal file
View File

@ -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<Box<GeneticVisualisationData>>,
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 };
}
}

69
lab5/src/gui/graph.rs Normal file
View File

@ -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::<f32>() * 0.8 + 0.1,
rng.random::<f32>() * 0.8 + 0.1,
));
}
return v;
}

60
lab5/src/gui/input.rs Normal file
View File

@ -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: ConstMutSwitchUi>(
ctx: &mut Ctx,
mut config: <Ctx::RefType as RefType>::Ref<'_, GeneticSimulationConfig>,
graph: &mut impl Graph<RefType = Ctx::RefType>,
mut population_size: <Ctx::RefType as RefType>::Ref<'_, usize>,
mut generations_count: <Ctx::RefType as RefType>::Ref<'_, usize>,
mut vertex_update: <Ctx::RefType as RefType>::Ref<'_, UpdatePending>,
mut launch: <Ctx::RefType as RefType>::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<RT: RefType> _ConstMutSwitchUiCallback<RT> for ControlsRow<'_, '_, '_, '_, RT> {
fn render(self, ctx: &mut impl ConstMutSwitchUi<RefType = RT>) {
ctx.button("Add vertex", self.update, |d| *d = UpdatePending::Add);
ctx.separator();
ctx.button("Run", self.run, |d| *d = true);
}
}

7
lab5/src/gui/mod.rs Normal file
View File

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

View File

@ -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::<MyApp>::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<T: Numeric>(
ui: &mut Ui,
name: &str,
storage: &mut T,
range: RangeInclusive<T>,
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)
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);
}
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));
}
}
gui::GeneticVisualisationState::Running {
simulation,
generations_done,
generations_count,
vertex_locations,
} => {
gui::input(
&mut ConstUI { ui },
&simulation.config,
&mut ConstGraph {
graph: &simulation.graph,
},
);
}
});
}
}
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,
&simulation.population.len(),
generations_count,
&UpdatePending::NoChange,
&false,
);
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(
let mut exit = false;
let mut generations_left = *generations_count - *generations_done;
subwindow(
ui,
&mut data.vertices,
&mut data.edges,
&mut data.vertex_update,
"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 visualization_panel(data: &mut MyApp, ui: &mut Ui) {
ui.horizontal(|ui| {
if ui.button("Exit").clicked() {}
if ui.button("Step").clicked() {}
ui.label("");
});
}
}