diff --git a/.gitignore b/.gitignore index 7fef46d..6fd4b5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target flamegraph.svg perf.data* +out diff --git a/benches/bruteforce.rs b/benches/bruteforce.rs index e2cc202..9516667 100644 --- a/benches/bruteforce.rs +++ b/benches/bruteforce.rs @@ -11,7 +11,7 @@ macro_rules! bench_bruteforce { b.iter(|| { let mut b = p.clone(); - while b.next_finish_state() {} + while b.next_finish_state(None) {} b.solution_count() }); diff --git a/examples/brute_force.rs b/examples/brute_force.rs index f605d9c..b86fc35 100644 --- a/examples/brute_force.rs +++ b/examples/brute_force.rs @@ -48,7 +48,7 @@ fn main() { match args.mode { Mode::Solutions => { - while b.next_finish_state() { + while b.next_finish_state(None) { println!("{}\n{}\n{}", b.count(), b.solution_count(), b.cost()); b.print(); } @@ -64,7 +64,7 @@ fn main() { println!("Solutions: {}\nStates: {}", b.solution_count(), b.count()); } Mode::Statistics => { - while b.next_finish_state() {} + while b.next_finish_state(None) {} println!("Solutions: {}\nStates: {}", b.solution_count(), b.count()); } diff --git a/examples/layout.rs b/examples/layout.rs index 1250375..051417f 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -1,9 +1,5 @@ use clap::Parser; -use factorio_blueprint::{ - belt_finding::{conflict_avoidance::ConflictAvoidance, Problem}, - common::visualize::Visualize, - layout::{GeneticAlgorithm, Layout, PathLayout}, -}; +use factorio_blueprint::layout::GeneticAlgorithm; use rand::{rngs::SmallRng, SeedableRng}; #[derive(Debug, Parser)] @@ -15,7 +11,7 @@ struct Args { fn main() { let args = Args::parse(); - let file = std::fs::File::open("layout2.yml").unwrap(); + let file = std::fs::File::open("layout3.yml").unwrap(); let p = serde_yaml::from_reader(file).unwrap(); @@ -23,7 +19,7 @@ fn main() { dbg!(&p); - let mut g = GeneticAlgorithm::new(&p, 20, 4, 2, &mut rng); + let mut g = GeneticAlgorithm::new(&p, 40, 5, 5, &mut rng); for i in 0..100 { println!("Generatrion {i}"); diff --git a/examples/solve_belt.rs b/examples/solve_belt.rs index 8313d50..a144b65 100644 --- a/examples/solve_belt.rs +++ b/examples/solve_belt.rs @@ -1,6 +1,6 @@ use clap::{Parser, ValueEnum}; use factorio_blueprint::belt_finding::{conflict_avoidance::ConflictAvoidance, problems, Problem}; -use std::io; +use std::{io, path::PathBuf}; #[derive(ValueEnum, Clone)] enum Mode { @@ -59,9 +59,9 @@ fn main() { p.print(); p.find_path(); p.print(); - let mut c = ConflictAvoidance::new(p); + let mut c = ConflictAvoidance::new(&p); c.print(); - while c.remove_conflict() { + while c.remove_conflict(None) { c.print(); } } @@ -75,9 +75,9 @@ fn main() { p.print(); p.find_path(); p.print(); - let mut c = ConflictAvoidance::new(p); + let mut c = ConflictAvoidance::new(&p); c.print(); - while c.remove_conflict() { + while c.remove_conflict(None) { c.print(); let mut s = String::new(); let _ = io::stdin().read_line(&mut s); diff --git a/layout3.yml b/layout3.yml new file mode 100644 index 0000000..578b0ba --- /dev/null +++ b/layout3.yml @@ -0,0 +1,115 @@ +size: + x: 50 + y: 50 +blocks: + - size: + x: 3 + y: 2 + input: + - offset: + x: 1 + y: 1 + dir: Up + output: + - offset: + x: 1 + y: 0 + dir: Up + - size: + x: 5 + y: 2 + input: + output: + - offset: + x: 1 + y: 1 + dir: Down + - size: + x: 5 + y: 7 + input: + - offset: + x: 0 + y: 1 + dir: Right + output: + - size: + x: 5 + y: 5 + input: + - offset: + x: 0 + y: 1 + dir: Right + output: + - offset: + x: 0 + y: 3 + dir: Left + - size: + x: 10 + y: 10 + input: + - offset: + x: 0 + y: 1 + dir: Right + - offset: + x: 0 + y: 3 + dir: Right + output: + - offset: + x: 9 + y: 1 + dir: Right + - offset: + x: 9 + y: 3 + dir: Right + - size: + x: 10 + y: 5 + input: + - offset: + x: 0 + y: 1 + dir: Right + - offset: + x: 0 + y: 3 + dir: Right + output: + - offset: + x: 9 + y: 1 + dir: Right + - offset: + x: 9 + y: 3 + dir: Right +connections: + - startblock: 1 + startpoint: 0 + endblock: 0 + endpoint: 0 + - startblock: 0 + startpoint: 0 + endblock: 3 + endpoint: 0 + - startblock: 3 + startpoint: 0 + endblock: 4 + endpoint: 0 + - startblock: 4 + startpoint: 0 + endblock: 5 + endpoint: 0 + - startblock: 4 + startpoint: 1 + endblock: 5 + endpoint: 1 + - startblock: 5 + startpoint: 0 + endblock: 2 + endpoint: 0 diff --git a/src/belt_finding/brute_force.rs b/src/belt_finding/brute_force.rs index 176326b..e178a12 100644 --- a/src/belt_finding/brute_force.rs +++ b/src/belt_finding/brute_force.rs @@ -1,3 +1,5 @@ +use std::time::Instant; + use super::{ common::{print_map, PathField}, Position, @@ -548,11 +550,12 @@ impl Bruteforce { self.modify_remove() } - pub fn next_finish_state(&mut self) -> bool { + pub fn next_finish_state(&mut self, timeout: Option) -> bool { while self.next_state() { - // if self.count % 1000000 == 0 { - // println!("{}\n{}", self.count, self); - // } + if self.count % 10000 == 0 && timeout.is_some_and(|t| t < std::time::Instant::now()) { + return false; + } + if self.problems.iter().all(|p| p.finished) { self.solution_count += 1; return true; @@ -679,7 +682,7 @@ mod test { fn $i() { let mut b = problems::$i(); - while b.next_finish_state() {} + while b.next_finish_state(None) {} assert_eq!(b.solution_count(), $x); } diff --git a/src/belt_finding/conflict_avoidance.rs b/src/belt_finding/conflict_avoidance.rs index f648e37..d1368da 100644 --- a/src/belt_finding/conflict_avoidance.rs +++ b/src/belt_finding/conflict_avoidance.rs @@ -4,7 +4,10 @@ use super::{ }; use crate::prelude::*; use crate::{belt_finding::brute_force::BruteforceBuilder, misc::Map}; -use std::ops::RangeInclusive; +use std::{ + ops::RangeInclusive, + time::{Duration, Instant}, +}; use termcolor::ColorSpec; #[derive(Default)] @@ -68,7 +71,7 @@ impl Candidate { } impl ConflictAvoidance { - pub fn new(problem: Problem) -> Self { + pub fn new(problem: &Problem) -> Self { let mut map: Map = Map::new(problem.map.width, problem.map.height); for x in 0..problem.map.width { for y in 0..problem.map.height { @@ -130,7 +133,11 @@ impl ConflictAvoidance { &self.belts } - fn try_bruteforce(&self, candidate: &Candidate) -> Option> { + fn try_bruteforce( + &self, + candidate: &Candidate, + timeout: Option, + ) -> Option> { let xrange = candidate.min.x as usize..=candidate.max.x as usize; let yrange = candidate.min.y as usize..=candidate.max.y as usize; @@ -218,9 +225,9 @@ impl ConflictAvoidance { { let p = start_pos - offset; // println!("Blocked {:?}", p); - if b.get_blocked(p.x as usize, p.y as usize) { - return None; - } + // if b.get_blocked(p.x as usize, p.y as usize) { + // return None; + // } b.set_blocked(p.x as usize, p.y as usize, true); } @@ -258,7 +265,7 @@ impl ConflictAvoidance { let mut min_cost = f64::INFINITY; let mut solutions = Vec::new(); - while b.next_finish_state() { + while b.next_finish_state(timeout) { // println!("{}", b); // b.print(); let c = b.cost(); @@ -286,7 +293,7 @@ impl ConflictAvoidance { ) } - pub fn remove_conflict(&mut self) -> bool { + pub fn remove_conflict(&mut self, timeout: Option) -> bool { let mut conflicts: Map = Map::new(self.map.width, self.map.height); for x in 0..self.map.width { @@ -347,7 +354,7 @@ impl ConflictAvoidance { } // dbg!(&candidates); - loop { + while timeout.is_some_and(|t| t < Instant::now()) { candidates.sort_by_key(|c| -c.area()); // dbg!(&candidates); let c = match candidates.pop() { @@ -359,7 +366,7 @@ impl ConflictAvoidance { self.range = Some((c.min.x..=c.max.x, c.min.y..=c.max.y)); - let result = self.try_bruteforce(&c); + let result = self.try_bruteforce(&c, timeout); // dbg!(&solutions); @@ -442,10 +449,13 @@ impl ConflictAvoidance { candidates.push(candidate); } } + + false } - pub fn remove_all_conflicts(&mut self) -> bool { - while self.remove_conflict() {} + pub fn remove_all_conflicts(&mut self, timeout: Option) -> bool { + let end = timeout.map(|t| std::time::Instant::now() + t); + while self.remove_conflict(end) {} let mut conflicts: Map = Map::new(self.map.width, self.map.height); diff --git a/src/belt_finding/mod.rs b/src/belt_finding/mod.rs index 78d3629..b224220 100644 --- a/src/belt_finding/mod.rs +++ b/src/belt_finding/mod.rs @@ -1,11 +1,10 @@ use crate::common::color::COLORS; +use crate::graph::wheighted_graph::shortest_path::dijkstra; use crate::graph::wheighted_graph::WheightedGraph; use crate::layout::Layout; use crate::misc::Map; -use crate::priority_queue::{BinaryHeap, Trace}; -use crate::{ - graph::wheighted_graph::shortest_path::dijkstra, priority_queue::fibonacci_heap::FibonacciHeap, -}; +use crate::priority_queue::BinaryHeap; +use serde::{Deserialize, Serialize}; use termcolor::ColorSpec; use self::common::print_map; @@ -15,12 +14,13 @@ pub mod brute_force; pub mod common; pub mod conflict_avoidance; -#[derive(Default, Clone, Copy)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy)] pub struct Field { pub blocked: bool, weight: f64, } +#[derive(Debug, Serialize, Deserialize)] pub struct Problem { map: Map, start: Vec<(Position, Direction)>, diff --git a/src/bin/beltfinding.rs b/src/bin/beltfinding.rs new file mode 100644 index 0000000..7bf9fdb --- /dev/null +++ b/src/bin/beltfinding.rs @@ -0,0 +1,80 @@ +use clap::{Parser, Subcommand, ValueEnum}; +use factorio_blueprint::belt_finding::{conflict_avoidance::ConflictAvoidance, problems, Problem}; +use std::{io, path::PathBuf}; + +#[derive(ValueEnum, Clone)] +enum Mode { + Solve, + ConflictAvoidance, + ConflictStep, +} + +#[derive(Subcommand)] +enum ProblemCase { + Simple, + Level1, + Level2, + Level3, + Level5, + File { filename: PathBuf }, +} + +impl ProblemCase { + fn get_problem(&self) -> Problem { + match self { + ProblemCase::Simple => problems::simple(), + ProblemCase::Level1 => problems::belt_madness_level_1(), + ProblemCase::Level2 => problems::belt_madness_level_2(), + ProblemCase::Level3 => problems::belt_madness_level_3(), + ProblemCase::Level5 => problems::belt_madness_level_5(), + ProblemCase::File { filename } => { + let file = std::fs::File::open(filename).unwrap(); + serde_json::from_reader(file).unwrap() + } + } + } +} + +#[derive(Parser)] +struct Args { + #[arg(value_enum, default_value = "conflict-avoidance")] + mode: Mode, + #[command(subcommand)] + problem: ProblemCase, +} + +fn main() { + let args = Args::parse(); + + let mut p = args.problem.get_problem(); + + match args.mode { + Mode::Solve => { + p.print(); + p.find_path(); + p.print(); + } + Mode::ConflictAvoidance => { + p.print(); + p.find_path(); + p.print(); + let mut c = ConflictAvoidance::new(&p); + c.print(); + while c.remove_conflict(None) { + c.print(); + } + } + Mode::ConflictStep => { + p.print(); + p.find_path(); + p.print(); + let mut c = ConflictAvoidance::new(&p); + c.print(); + while c.remove_conflict(None) { + c.print(); + let mut s = String::new(); + let _ = io::stdin().read_line(&mut s); + } + } + } +} diff --git a/src/graph/wheighted_graph/shortest_path.rs b/src/graph/wheighted_graph/shortest_path.rs index 27887f1..b5917cc 100644 --- a/src/graph/wheighted_graph/shortest_path.rs +++ b/src/graph/wheighted_graph/shortest_path.rs @@ -61,6 +61,10 @@ where G::Node: Eq + Hash + Clone + Debug, G: WheightedGraph, { + if start == end { + return Some(vec![start]); + } + let mut map: HashMap> = HashMap::new(); let mut q = P::new(); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 85a14f0..5d9ab55 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,3 +1,6 @@ +use std::sync::atomic::AtomicU32; +use std::time::Instant; + use crate::belt_finding::common::PathField; use crate::belt_finding::conflict_avoidance::ConflictAvoidance; use crate::common::visualize::{Color, Symbol, Visualization, Visualize}; @@ -5,6 +8,8 @@ use crate::prelude::*; use rand::{seq::SliceRandom, Rng}; use serde::{Deserialize, Serialize}; +static OUTFILEINDEX: AtomicU32 = AtomicU32::new(0); + pub struct GeneticAlgorithm<'a> { problem: &'a Problem, population: Vec>, @@ -107,8 +112,8 @@ pub struct Problem { pub(crate) connections: Vec, } -#[derive(Debug, Clone, Copy)] -pub struct BlockHandle(usize); +// #[derive(Debug, Clone, Copy)] +// pub struct BlockHandle(usize); #[derive(Debug, Clone)] pub struct Layout<'a> { @@ -134,16 +139,25 @@ impl<'a> PathLayout<'a> { // println!("Find Path: {:.2}", start.elapsed().as_secs_f32()); - let mut c = ConflictAvoidance::new(p); + let mut c = ConflictAvoidance::new(&p); - // let start = std::time::Instant::now(); + let start = std::time::Instant::now(); - if !c.remove_all_conflicts() { + 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(); + 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; } - // println!("Conflict avoidance: {:.2}", start.elapsed().as_secs_f32()); - let paths = c.get_paths().to_vec(); let score = paths @@ -210,15 +224,15 @@ impl Problem { } } - pub fn add_block(&mut self, size: Position) -> BlockHandle { - self.blocks.push(Block { - size, - input: Vec::new(), - output: 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) - } + // BlockHandle(self.blocks.len() - 1) + // } // pub fn add_connection( // &mut self, diff --git a/src/misc/map.rs b/src/misc/map.rs index 27eb011..18c8db1 100644 --- a/src/misc/map.rs +++ b/src/misc/map.rs @@ -1,4 +1,6 @@ -#[derive(Clone, Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Map { pub width: usize, pub height: usize,