Refactor into different crates
This commit is contained in:
		
							parent
							
								
									94473c64e0
								
							
						
					
					
						commit
						dfdeae5638
					
				
					 82 changed files with 624 additions and 647 deletions
				
			
		|  | @ -1,573 +0,0 @@ | |||
| use crate::belt_finding::common::PathField; | ||||
| use crate::belt_finding::conflict_avoidance::ConflictAvoidance; | ||||
| use crate::common::visualize::{image_grid, Color, Symbol, Visualization, Visualize}; | ||||
| use crate::prelude::*; | ||||
| use rand::{seq::SliceRandom, Rng}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::sync::atomic::AtomicU32; | ||||
| use std::time::Instant; | ||||
| 
 | ||||
| static OUTFILEINDEX: AtomicU32 = AtomicU32::new(0); | ||||
| 
 | ||||
| pub struct GeneticAlgorithm<'a> { | ||||
|     problem: &'a Problem, | ||||
|     population: Vec<PathLayout<'a>>, | ||||
|     population_size: usize, | ||||
|     population_keep: usize, | ||||
|     population_new: usize, | ||||
| } | ||||
| 
 | ||||
| impl<'a> GeneticAlgorithm<'a> { | ||||
|     pub fn new<R: Rng + ?Sized>( | ||||
|         problem: &'a Problem, | ||||
|         population_size: usize, | ||||
|         population_keep: usize, | ||||
|         population_new: usize, | ||||
|         rng: &mut R, | ||||
|     ) -> GeneticAlgorithm<'a> { | ||||
|         let mut population = Vec::new(); | ||||
| 
 | ||||
|         let start = Instant::now(); | ||||
| 
 | ||||
|         let mut count: usize = 0; | ||||
| 
 | ||||
|         while population.len() < population_size { | ||||
|             count += 1; | ||||
|             if let Some(p) = PathLayout::new(Layout::new(problem, rng)) { | ||||
|                 population.push(p); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         println!("Layouts accepted: {}/{}", population_size, count); | ||||
| 
 | ||||
|         population.sort_by_key(|p| p.score()); | ||||
| 
 | ||||
|         println!( | ||||
|             "Best score: {}. Time: {:.2}s", | ||||
|             population[0].score(), | ||||
|             start.elapsed().as_secs_f32() | ||||
|         ); | ||||
|         population[0].print_visualization(); | ||||
| 
 | ||||
|         GeneticAlgorithm { | ||||
|             problem, | ||||
|             population, | ||||
|             population_size, | ||||
|             population_keep, | ||||
|             population_new, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn generation<R: Rng + ?Sized>(&mut self, rng: &mut R) { | ||||
|         let start_new = Instant::now(); | ||||
|         for i in self.population_keep..(self.population_keep + self.population_new) { | ||||
|             loop { | ||||
|                 if let Some(p) = PathLayout::new(Layout::new(self.problem, rng)) { | ||||
|                     self.population[i] = p; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let duration_new = start_new.elapsed(); | ||||
|         let start_mutate = Instant::now(); | ||||
| 
 | ||||
|         for i in (self.population_keep + self.population_new)..self.population_size { | ||||
|             let j = i - (self.population_keep + self.population_new); | ||||
|             loop { | ||||
|                 if let Some(p) = | ||||
|                     PathLayout::new(self.population[j % self.population_keep].layout.mutate(rng)) | ||||
|                 { | ||||
|                     self.population[i] = p; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         let duration_mutate = start_mutate.elapsed(); | ||||
| 
 | ||||
|         self.population.sort_by_key(|p| p.score()); | ||||
|         println!( | ||||
|             "Best score: {}. Time new: {:.2}s. Time mutate: {:.2}s", | ||||
|             self.population[0].score(), | ||||
|             duration_new.as_secs_f32(), | ||||
|             duration_mutate.as_secs_f32() | ||||
|         ); | ||||
|         self.population[0].print_visualization(); | ||||
|         let v: Vec<_> = self.population.iter().map(|p| p.visualize()).collect(); | ||||
|         let img = image_grid(&v, v[0].size().x as u32, v[0].size().y as u32, 5); | ||||
|         let mut file = std::fs::File::create("generation.png").unwrap(); | ||||
|         img.write_to(&mut file, image::ImageFormat::Png).unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     pub fn output_population(&self) { | ||||
|         println!("Population:"); | ||||
|         for (i, p) in self.population.iter().enumerate() { | ||||
|             println!("{i:3}: {}", p.score()); | ||||
|             p.print_visualization(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn genetic_algorithm2<'a, R: Rng + ?Sized>( | ||||
|     problem: &'a Problem, | ||||
|     new_layouts: usize, | ||||
|     mutation_timeout: usize, | ||||
|     max_mutations: usize, | ||||
|     rng: &'_ mut R, | ||||
| ) -> PathLayout<'a> { | ||||
|     let mut m = (0..new_layouts) | ||||
|         .map(|_| valid_path_layout(problem, rng)) | ||||
|         .min_by_key(|p| p.score()) | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     // m.print_visualization();
 | ||||
| 
 | ||||
|     let mut last_improvement = 0; | ||||
|     let mut count = 0; | ||||
| 
 | ||||
|     while last_improvement < mutation_timeout && count < max_mutations { | ||||
|         last_improvement += 1; | ||||
|         count += 1; | ||||
|         if let Some(p) = PathLayout::new(m.layout.mutate(rng)) { | ||||
|             if p.score() < m.score() { | ||||
|                 m = p; | ||||
|                 // println!("Step: {count}");
 | ||||
|                 // m.print_visualization();
 | ||||
|                 last_improvement = 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     m | ||||
| } | ||||
| 
 | ||||
| pub fn valid_path_layout<'a, R: Rng + ?Sized>( | ||||
|     problem: &'a Problem, | ||||
|     rng: &'_ mut R, | ||||
| ) -> PathLayout<'a> { | ||||
|     loop { | ||||
|         if let Some(p) = PathLayout::new(Layout::new(problem, rng)) { | ||||
|             return p; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub(crate) struct MacroBlock { | ||||
|     pub(crate) size: Position, | ||||
|     pub(crate) input: Vec<Interface>, | ||||
|     pub(crate) output: Vec<Interface>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub(crate) struct Interface { | ||||
|     pub(crate) offset: Position, | ||||
|     pub(crate) dir: Direction, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub(crate) struct Connection { | ||||
|     pub(crate) startblock: usize, | ||||
|     pub(crate) startpoint: usize, | ||||
|     pub(crate) endblock: usize, | ||||
|     pub(crate) endpoint: usize, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub struct Problem { | ||||
|     pub(crate) size: Position, | ||||
|     pub(crate) blocks: Vec<MacroBlock>, | ||||
|     pub(crate) connections: Vec<Connection>, | ||||
| } | ||||
| 
 | ||||
| // #[derive(Debug, Clone, Copy)]
 | ||||
| // pub struct BlockHandle(usize);
 | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct Layout<'a> { | ||||
|     pub(crate) problem: &'a Problem, | ||||
|     pub(crate) blocks: Vec<Block>, | ||||
| } | ||||
| 
 | ||||
| pub struct PathLayout<'a> { | ||||
|     layout: Layout<'a>, | ||||
|     paths: Vec<Vec<PathField>>, | ||||
|     score: usize, | ||||
| } | ||||
| 
 | ||||
| impl<'a> PathLayout<'a> { | ||||
|     pub fn new(layout: Layout<'a>) -> Option<PathLayout<'a>> { | ||||
|         let mut p = crate::belt_finding::Problem::from_layout(&layout); | ||||
| 
 | ||||
|         if !p.find_path() { | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         let mut c = ConflictAvoidance::new(&p); | ||||
| 
 | ||||
|         let start = std::time::Instant::now(); | ||||
| 
 | ||||
|         if !c.remove_all_conflicts(Some(std::time::Duration::from_secs(2))) { | ||||
|             if start.elapsed().as_secs_f32() > 0.5 { | ||||
|                 // println!("Conflict avoidance: {:.2}", start.elapsed().as_secs_f32());
 | ||||
|                 // c.print_visualization();
 | ||||
|                 let file = std::fs::File::create(format!( | ||||
|                     "out/{}.json", | ||||
|                     OUTFILEINDEX.fetch_add(1, std::sync::atomic::Ordering::Relaxed) | ||||
|                 )) | ||||
|                 .unwrap(); | ||||
|                 serde_json::to_writer(file, &p).unwrap(); | ||||
|                 // println!("Saved slow solve.");
 | ||||
|             } | ||||
|             return None; | ||||
|         } | ||||
| 
 | ||||
|         let paths = c.get_paths().to_vec(); | ||||
| 
 | ||||
|         let score = paths | ||||
|             .iter() | ||||
|             .map(|path| path.iter().skip(1).map(|p| p.cost()).sum::<usize>()) | ||||
|             .sum(); | ||||
| 
 | ||||
|         Some(PathLayout { | ||||
|             layout, | ||||
|             paths, | ||||
|             score, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn score(&self) -> usize { | ||||
|         self.score | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> Visualize for PathLayout<'a> { | ||||
|     fn visualize(&self) -> Visualization { | ||||
|         let mut v = self.layout.visualize(); | ||||
|         let offset = self.layout.blocks.len(); | ||||
| 
 | ||||
|         for (i, path) in self.paths.iter().enumerate() { | ||||
|             for p in &path[1..] { | ||||
|                 match p { | ||||
|                     PathField::Belt { pos, dir } => { | ||||
|                         v.add_symbol( | ||||
|                             *pos, | ||||
|                             Symbol::Arrow(*dir), | ||||
|                             Some(Color::index(i + offset)), | ||||
|                             None, | ||||
|                         ); | ||||
|                     } | ||||
|                     PathField::Underground { pos, dir, len } => { | ||||
|                         v.add_symbol( | ||||
|                             *pos, | ||||
|                             Symbol::ArrowEnter(*dir), | ||||
|                             Some(Color::index(i + offset)), | ||||
|                             None, | ||||
|                         ); | ||||
|                         v.add_symbol( | ||||
|                             pos.in_direction(dir, *len as i32), | ||||
|                             Symbol::ArrowEnter(*dir), | ||||
|                             Some(Color::index(i + offset)), | ||||
|                             None, | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         v | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Problem { | ||||
|     pub fn new(size: Position) -> Self { | ||||
|         Self { | ||||
|             size, | ||||
|             blocks: Vec::new(), | ||||
|             connections: Vec::new(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // pub fn add_block(&mut self, size: Position) -> BlockHandle {
 | ||||
|     //     self.blocks.push(Block {
 | ||||
|     //         size,
 | ||||
|     //         input: Vec::new(),
 | ||||
|     //         output: Vec::new(),
 | ||||
|     //     });
 | ||||
| 
 | ||||
|     //     BlockHandle(self.blocks.len() - 1)
 | ||||
|     // }
 | ||||
| 
 | ||||
|     // pub fn add_connection(
 | ||||
|     //     &mut self,
 | ||||
|     //     starthandle: BlockHandle,
 | ||||
|     //     startoffset: Position,
 | ||||
|     //     endhandle: BlockHandle,
 | ||||
|     //     endoffset: Position,
 | ||||
|     // ) {
 | ||||
|     //     let startinterface = self.blocks[starthandle.0].output.len();
 | ||||
|     //     let endinterface = self.blocks[endhandle.0].input.len();
 | ||||
| 
 | ||||
|     //     self.blocks[starthandle.0].output.push(Interface {
 | ||||
|     //         offset: startoffset,
 | ||||
|     //         target: (endhandle.0, endinterface),
 | ||||
|     //     });
 | ||||
| 
 | ||||
|     //     self.blocks[endhandle.0].input.push(Interface {
 | ||||
|     //         offset: endoffset,
 | ||||
|     //         target: (starthandle.0, startinterface),
 | ||||
|     //     })
 | ||||
|     // }
 | ||||
| } | ||||
| 
 | ||||
| impl Layout<'_> { | ||||
|     /// Create a new valid layout
 | ||||
|     pub fn new<'a, R: Rng + ?Sized>(problem: &'a Problem, rng: &'_ mut R) -> Layout<'a> { | ||||
|         let mut blocks = Vec::new(); | ||||
| 
 | ||||
|         assert!(Self::place_block(problem, &mut blocks, rng)); | ||||
| 
 | ||||
|         let mut l = Layout { problem, blocks }; | ||||
|         l.center(); | ||||
|         l | ||||
|     } | ||||
| 
 | ||||
|     fn place_block<R: Rng + ?Sized>( | ||||
|         problem: &'_ Problem, | ||||
|         blocks: &mut Vec<Block>, | ||||
|         rng: &'_ mut R, | ||||
|     ) -> bool { | ||||
|         if problem.blocks.len() == blocks.len() { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         let b = &problem.blocks[blocks.len()]; | ||||
|         for _ in 0..1000 { | ||||
|             let dir = rng.gen::<Direction>(); | ||||
| 
 | ||||
|             let pos = match dir { | ||||
|                 Direction::Up => Position::new( | ||||
|                     rng.gen_range(0..=(problem.size.x - b.size.x)), | ||||
|                     rng.gen_range(0..=(problem.size.y - b.size.y)), | ||||
|                 ), | ||||
|                 Direction::Right => Position::new( | ||||
|                     rng.gen_range((b.size.y - 1)..problem.size.x), | ||||
|                     rng.gen_range(0..=(problem.size.y - b.size.x)), | ||||
|                 ), | ||||
|                 Direction::Down => Position::new( | ||||
|                     rng.gen_range((b.size.x - 1)..problem.size.x), | ||||
|                     rng.gen_range((b.size.y - 1)..problem.size.y), | ||||
|                 ), | ||||
|                 Direction::Left => Position::new( | ||||
|                     rng.gen_range(0..=(problem.size.x - b.size.y)), | ||||
|                     rng.gen_range((b.size.x - 1)..problem.size.y), | ||||
|                 ), | ||||
|             }; | ||||
| 
 | ||||
|             let current = Block::new(pos, dir, problem.blocks[blocks.len()].size); | ||||
| 
 | ||||
|             let current_aabb = current.get_aabb(); | ||||
| 
 | ||||
|             if blocks | ||||
|                 .iter() | ||||
|                 .all(|b| !AABB::collision(b.get_aabb(), current_aabb)) | ||||
|             { | ||||
|                 blocks.push(current); | ||||
| 
 | ||||
|                 if Self::place_block(problem, blocks, rng) { | ||||
|                     return true; | ||||
|                 } | ||||
| 
 | ||||
|                 blocks.pop(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         false | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_aabb(&self) -> AABB { | ||||
|         self.blocks | ||||
|             .iter() | ||||
|             .map(|b| b.get_aabb()) | ||||
|             .reduce(AABB::combine) | ||||
|             .expect("At least one block is required.") | ||||
|     } | ||||
| 
 | ||||
|     pub fn center(&mut self) { | ||||
|         let aabb = self.get_aabb(); | ||||
| 
 | ||||
|         let rest = self.problem.size - aabb.size(); | ||||
| 
 | ||||
|         let new_min = Position::new(rest.x / 2, rest.y / 2); | ||||
| 
 | ||||
|         let t = Transformation::new(Direction::Up, new_min - aabb.min()); | ||||
| 
 | ||||
|         for b in &mut self.blocks { | ||||
|             *b = b.transform(t); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Mutate existing layout, creating a valid layout
 | ||||
|     pub fn mutate<R: Rng + ?Sized>(&self, rng: &mut R) -> Self { | ||||
|         let mut s = self.clone(); | ||||
| 
 | ||||
|         #[allow(clippy::type_complexity)] | ||||
|         let r: &[(&dyn Fn(&mut Layout, &mut R) -> bool, _)] = &[ | ||||
|             (&Self::mutate_replace::<R>, 30), | ||||
|             (&Self::mutate_flip::<R>, 50), | ||||
|             (&Self::mutate_jiggle::<R>, 160), | ||||
|         ]; | ||||
| 
 | ||||
|         loop { | ||||
|             let p = r.choose_weighted(rng, |i| i.1).unwrap(); | ||||
| 
 | ||||
|             if p.0(&mut s, rng) && rng.gen_bool(0.5) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         s.center(); | ||||
|         s | ||||
|     } | ||||
| 
 | ||||
|     fn mutate_replace<R: Rng + ?Sized>(layout: &mut Layout, rng: &mut R) -> bool { | ||||
|         let i = rng.gen_range(0..layout.blocks.len()); | ||||
| 
 | ||||
|         let dir = rng.gen::<Direction>(); | ||||
| 
 | ||||
|         let b = &layout.problem.blocks[i]; | ||||
| 
 | ||||
|         let pos = match dir { | ||||
|             Direction::Up => Position::new( | ||||
|                 rng.gen_range(0..=(layout.problem.size.x - b.size.x)), | ||||
|                 rng.gen_range(0..=(layout.problem.size.y - b.size.y)), | ||||
|             ), | ||||
|             Direction::Right => Position::new( | ||||
|                 rng.gen_range((b.size.y - 1)..layout.problem.size.x), | ||||
|                 rng.gen_range(0..=(layout.problem.size.y - b.size.x)), | ||||
|             ), | ||||
|             Direction::Down => Position::new( | ||||
|                 rng.gen_range((b.size.x - 1)..layout.problem.size.x), | ||||
|                 rng.gen_range((b.size.y - 1)..layout.problem.size.y), | ||||
|             ), | ||||
|             Direction::Left => Position::new( | ||||
|                 rng.gen_range(0..=(layout.problem.size.x - b.size.y)), | ||||
|                 rng.gen_range((b.size.x - 1)..layout.problem.size.y), | ||||
|             ), | ||||
|         }; | ||||
| 
 | ||||
|         let current = Block::new(pos, dir, b.size); | ||||
|         let current_aabb = current.get_aabb(); | ||||
| 
 | ||||
|         if layout | ||||
|             .blocks | ||||
|             .iter() | ||||
|             .enumerate() | ||||
|             .all(|(j, b)| j == i || !AABB::collision(b.get_aabb(), current_aabb)) | ||||
|         { | ||||
|             layout.blocks[i] = current; | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn mutate_flip<R: Rng + ?Sized>(layout: &mut Layout, rng: &mut R) -> bool { | ||||
|         let i = rng.gen_range(0..layout.blocks.len()); | ||||
|         let b = &mut layout.blocks[i]; | ||||
|         let block = &layout.problem.blocks[i]; | ||||
| 
 | ||||
|         let new_pos = match b.dir() { | ||||
|             Direction::Up => b.pos() + block.size - Position::new(1, 1), | ||||
|             Direction::Right => b.pos() + Position::new(1 - block.size.y, block.size.x - 1), | ||||
|             Direction::Down => b.pos() - block.size + Position::new(1, 1), | ||||
|             Direction::Left => b.pos() + Position::new(block.size.y - 1, 1 - block.size.x), | ||||
|         }; | ||||
|         let new_dir = b.dir().reverse(); | ||||
| 
 | ||||
|         *b = Block::new(new_pos, new_dir, b.size()); | ||||
| 
 | ||||
|         true | ||||
|     } | ||||
| 
 | ||||
|     fn mutate_jiggle<R: Rng + ?Sized>(layout: &mut Layout, rng: &mut R) -> bool { | ||||
|         let i = rng.gen_range(0..layout.blocks.len()); | ||||
|         let dir = rng.gen::<Direction>(); | ||||
|         let step = [(1, 10), (2, 5), (3, 5)] | ||||
|             .choose_weighted(rng, |i| i.1) | ||||
|             .unwrap() | ||||
|             .0; | ||||
|         // let step = 1;
 | ||||
| 
 | ||||
|         let b = &layout.blocks[i]; | ||||
| 
 | ||||
|         let current = Block::new(b.pos().in_direction(&dir, step), b.dir(), b.size()); | ||||
|         let current_aabb = current.get_aabb(); | ||||
| 
 | ||||
|         if current_aabb.min().x < 0 | ||||
|             || current_aabb.min().y < 0 | ||||
|             || current_aabb.max().x >= layout.problem.size.x | ||||
|             || current_aabb.max().y >= layout.problem.size.y | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if layout | ||||
|             .blocks | ||||
|             .iter() | ||||
|             .enumerate() | ||||
|             .all(|(j, b)| j == i || !AABB::collision(b.get_aabb(), current_aabb)) | ||||
|         { | ||||
|             layout.blocks[i] = current; | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a> Visualize for Layout<'a> { | ||||
|     fn visualize(&self) -> Visualization { | ||||
|         let mut v = Visualization::new(self.problem.size); | ||||
| 
 | ||||
|         for (i, (b, mb)) in self | ||||
|             .blocks | ||||
|             .iter() | ||||
|             .zip(self.problem.blocks.iter()) | ||||
|             .enumerate() | ||||
|         { | ||||
|             let c = Color::index(i); | ||||
| 
 | ||||
|             let aabb = b.get_aabb(); | ||||
| 
 | ||||
|             for x in aabb.min().x..=aabb.max().x { | ||||
|                 for y in aabb.min().y..=aabb.max().y { | ||||
|                     v.add_symbol(Position::new(x, y), Symbol::Block, Some(c), None); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             v.add_symbol(b.pos(), Symbol::Char('X'), Some(c), None); | ||||
| 
 | ||||
|             let transform = b.block_to_world(); | ||||
| 
 | ||||
|             for input in &mb.input { | ||||
|                 v.add_symbol( | ||||
|                     input.offset.transform(transform), | ||||
|                     Symbol::Char('i'), | ||||
|                     Some(c), | ||||
|                     None, | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             for output in &mb.output { | ||||
|                 v.add_symbol( | ||||
|                     output.offset.transform(transform), | ||||
|                     Symbol::Char('o'), | ||||
|                     Some(c), | ||||
|                     None, | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         v | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue