Add beltfinding timeout and debugging.

This commit is contained in:
hal8174 2024-09-01 01:05:34 +02:00
parent f20a1841c9
commit 79c8b0c710
13 changed files with 278 additions and 53 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
flamegraph.svg flamegraph.svg
perf.data* perf.data*
out

View file

@ -11,7 +11,7 @@ macro_rules! bench_bruteforce {
b.iter(|| { b.iter(|| {
let mut b = p.clone(); let mut b = p.clone();
while b.next_finish_state() {} while b.next_finish_state(None) {}
b.solution_count() b.solution_count()
}); });

View file

@ -48,7 +48,7 @@ fn main() {
match args.mode { match args.mode {
Mode::Solutions => { Mode::Solutions => {
while b.next_finish_state() { while b.next_finish_state(None) {
println!("{}\n{}\n{}", b.count(), b.solution_count(), b.cost()); println!("{}\n{}\n{}", b.count(), b.solution_count(), b.cost());
b.print(); b.print();
} }
@ -64,7 +64,7 @@ fn main() {
println!("Solutions: {}\nStates: {}", b.solution_count(), b.count()); println!("Solutions: {}\nStates: {}", b.solution_count(), b.count());
} }
Mode::Statistics => { Mode::Statistics => {
while b.next_finish_state() {} while b.next_finish_state(None) {}
println!("Solutions: {}\nStates: {}", b.solution_count(), b.count()); println!("Solutions: {}\nStates: {}", b.solution_count(), b.count());
} }

View file

@ -1,9 +1,5 @@
use clap::Parser; use clap::Parser;
use factorio_blueprint::{ use factorio_blueprint::layout::GeneticAlgorithm;
belt_finding::{conflict_avoidance::ConflictAvoidance, Problem},
common::visualize::Visualize,
layout::{GeneticAlgorithm, Layout, PathLayout},
};
use rand::{rngs::SmallRng, SeedableRng}; use rand::{rngs::SmallRng, SeedableRng};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
@ -15,7 +11,7 @@ struct Args {
fn main() { fn main() {
let args = Args::parse(); 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(); let p = serde_yaml::from_reader(file).unwrap();
@ -23,7 +19,7 @@ fn main() {
dbg!(&p); 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 { for i in 0..100 {
println!("Generatrion {i}"); println!("Generatrion {i}");

View file

@ -1,6 +1,6 @@
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use factorio_blueprint::belt_finding::{conflict_avoidance::ConflictAvoidance, problems, Problem}; use factorio_blueprint::belt_finding::{conflict_avoidance::ConflictAvoidance, problems, Problem};
use std::io; use std::{io, path::PathBuf};
#[derive(ValueEnum, Clone)] #[derive(ValueEnum, Clone)]
enum Mode { enum Mode {
@ -59,9 +59,9 @@ fn main() {
p.print(); p.print();
p.find_path(); p.find_path();
p.print(); p.print();
let mut c = ConflictAvoidance::new(p); let mut c = ConflictAvoidance::new(&p);
c.print(); c.print();
while c.remove_conflict() { while c.remove_conflict(None) {
c.print(); c.print();
} }
} }
@ -75,9 +75,9 @@ fn main() {
p.print(); p.print();
p.find_path(); p.find_path();
p.print(); p.print();
let mut c = ConflictAvoidance::new(p); let mut c = ConflictAvoidance::new(&p);
c.print(); c.print();
while c.remove_conflict() { while c.remove_conflict(None) {
c.print(); c.print();
let mut s = String::new(); let mut s = String::new();
let _ = io::stdin().read_line(&mut s); let _ = io::stdin().read_line(&mut s);

115
layout3.yml Normal file
View file

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

View file

@ -1,3 +1,5 @@
use std::time::Instant;
use super::{ use super::{
common::{print_map, PathField}, common::{print_map, PathField},
Position, Position,
@ -548,11 +550,12 @@ impl Bruteforce {
self.modify_remove() self.modify_remove()
} }
pub fn next_finish_state(&mut self) -> bool { pub fn next_finish_state(&mut self, timeout: Option<Instant>) -> bool {
while self.next_state() { while self.next_state() {
// if self.count % 1000000 == 0 { if self.count % 10000 == 0 && timeout.is_some_and(|t| t < std::time::Instant::now()) {
// println!("{}\n{}", self.count, self); return false;
// } }
if self.problems.iter().all(|p| p.finished) { if self.problems.iter().all(|p| p.finished) {
self.solution_count += 1; self.solution_count += 1;
return true; return true;
@ -679,7 +682,7 @@ mod test {
fn $i() { fn $i() {
let mut b = problems::$i(); let mut b = problems::$i();
while b.next_finish_state() {} while b.next_finish_state(None) {}
assert_eq!(b.solution_count(), $x); assert_eq!(b.solution_count(), $x);
} }

View file

@ -4,7 +4,10 @@ use super::{
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::{belt_finding::brute_force::BruteforceBuilder, misc::Map}; use crate::{belt_finding::brute_force::BruteforceBuilder, misc::Map};
use std::ops::RangeInclusive; use std::{
ops::RangeInclusive,
time::{Duration, Instant},
};
use termcolor::ColorSpec; use termcolor::ColorSpec;
#[derive(Default)] #[derive(Default)]
@ -68,7 +71,7 @@ impl Candidate {
} }
impl ConflictAvoidance { impl ConflictAvoidance {
pub fn new(problem: Problem) -> Self { pub fn new(problem: &Problem) -> Self {
let mut map: Map<Field> = Map::new(problem.map.width, problem.map.height); let mut map: Map<Field> = Map::new(problem.map.width, problem.map.height);
for x in 0..problem.map.width { for x in 0..problem.map.width {
for y in 0..problem.map.height { for y in 0..problem.map.height {
@ -130,7 +133,11 @@ impl ConflictAvoidance {
&self.belts &self.belts
} }
fn try_bruteforce(&self, candidate: &Candidate) -> Option<Vec<BruteForceEntry>> { fn try_bruteforce(
&self,
candidate: &Candidate,
timeout: Option<Instant>,
) -> Option<Vec<BruteForceEntry>> {
let xrange = candidate.min.x as usize..=candidate.max.x as usize; let xrange = candidate.min.x as usize..=candidate.max.x as usize;
let yrange = candidate.min.y as usize..=candidate.max.y 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; let p = start_pos - offset;
// println!("Blocked {:?}", p); // println!("Blocked {:?}", p);
if b.get_blocked(p.x as usize, p.y as usize) { // if b.get_blocked(p.x as usize, p.y as usize) {
return None; // return None;
} // }
b.set_blocked(p.x as usize, p.y as usize, true); 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 min_cost = f64::INFINITY;
let mut solutions = Vec::new(); let mut solutions = Vec::new();
while b.next_finish_state() { while b.next_finish_state(timeout) {
// println!("{}", b); // println!("{}", b);
// b.print(); // b.print();
let c = b.cost(); 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<Instant>) -> bool {
let mut conflicts: Map<usize> = Map::new(self.map.width, self.map.height); let mut conflicts: Map<usize> = Map::new(self.map.width, self.map.height);
for x in 0..self.map.width { for x in 0..self.map.width {
@ -347,7 +354,7 @@ impl ConflictAvoidance {
} }
// dbg!(&candidates); // dbg!(&candidates);
loop { while timeout.is_some_and(|t| t < Instant::now()) {
candidates.sort_by_key(|c| -c.area()); candidates.sort_by_key(|c| -c.area());
// dbg!(&candidates); // dbg!(&candidates);
let c = match candidates.pop() { 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)); 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); // dbg!(&solutions);
@ -442,10 +449,13 @@ impl ConflictAvoidance {
candidates.push(candidate); candidates.push(candidate);
} }
} }
false
} }
pub fn remove_all_conflicts(&mut self) -> bool { pub fn remove_all_conflicts(&mut self, timeout: Option<Duration>) -> bool {
while self.remove_conflict() {} let end = timeout.map(|t| std::time::Instant::now() + t);
while self.remove_conflict(end) {}
let mut conflicts: Map<bool> = Map::new(self.map.width, self.map.height); let mut conflicts: Map<bool> = Map::new(self.map.width, self.map.height);

View file

@ -1,11 +1,10 @@
use crate::common::color::COLORS; use crate::common::color::COLORS;
use crate::graph::wheighted_graph::shortest_path::dijkstra;
use crate::graph::wheighted_graph::WheightedGraph; use crate::graph::wheighted_graph::WheightedGraph;
use crate::layout::Layout; use crate::layout::Layout;
use crate::misc::Map; use crate::misc::Map;
use crate::priority_queue::{BinaryHeap, Trace}; use crate::priority_queue::BinaryHeap;
use crate::{ use serde::{Deserialize, Serialize};
graph::wheighted_graph::shortest_path::dijkstra, priority_queue::fibonacci_heap::FibonacciHeap,
};
use termcolor::ColorSpec; use termcolor::ColorSpec;
use self::common::print_map; use self::common::print_map;
@ -15,12 +14,13 @@ pub mod brute_force;
pub mod common; pub mod common;
pub mod conflict_avoidance; pub mod conflict_avoidance;
#[derive(Default, Clone, Copy)] #[derive(Debug, Default, Serialize, Deserialize, Clone, Copy)]
pub struct Field { pub struct Field {
pub blocked: bool, pub blocked: bool,
weight: f64, weight: f64,
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct Problem { pub struct Problem {
map: Map<Field>, map: Map<Field>,
start: Vec<(Position, Direction)>, start: Vec<(Position, Direction)>,

80
src/bin/beltfinding.rs Normal file
View file

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

View file

@ -61,6 +61,10 @@ where
G::Node: Eq + Hash + Clone + Debug, G::Node: Eq + Hash + Clone + Debug,
G: WheightedGraph, G: WheightedGraph,
{ {
if start == end {
return Some(vec![start]);
}
let mut map: HashMap<G::Node, MapObject<G::Node, P::Handle>> = HashMap::new(); let mut map: HashMap<G::Node, MapObject<G::Node, P::Handle>> = HashMap::new();
let mut q = P::new(); let mut q = P::new();

View file

@ -1,3 +1,6 @@
use std::sync::atomic::AtomicU32;
use std::time::Instant;
use crate::belt_finding::common::PathField; use crate::belt_finding::common::PathField;
use crate::belt_finding::conflict_avoidance::ConflictAvoidance; use crate::belt_finding::conflict_avoidance::ConflictAvoidance;
use crate::common::visualize::{Color, Symbol, Visualization, Visualize}; use crate::common::visualize::{Color, Symbol, Visualization, Visualize};
@ -5,6 +8,8 @@ use crate::prelude::*;
use rand::{seq::SliceRandom, Rng}; use rand::{seq::SliceRandom, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
static OUTFILEINDEX: AtomicU32 = AtomicU32::new(0);
pub struct GeneticAlgorithm<'a> { pub struct GeneticAlgorithm<'a> {
problem: &'a Problem, problem: &'a Problem,
population: Vec<PathLayout<'a>>, population: Vec<PathLayout<'a>>,
@ -107,8 +112,8 @@ pub struct Problem {
pub(crate) connections: Vec<Connection>, pub(crate) connections: Vec<Connection>,
} }
#[derive(Debug, Clone, Copy)] // #[derive(Debug, Clone, Copy)]
pub struct BlockHandle(usize); // pub struct BlockHandle(usize);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Layout<'a> { pub struct Layout<'a> {
@ -134,16 +139,25 @@ impl<'a> PathLayout<'a> {
// println!("Find Path: {:.2}", start.elapsed().as_secs_f32()); // 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; return None;
} }
// println!("Conflict avoidance: {:.2}", start.elapsed().as_secs_f32());
let paths = c.get_paths().to_vec(); let paths = c.get_paths().to_vec();
let score = paths let score = paths
@ -210,15 +224,15 @@ impl Problem {
} }
} }
pub fn add_block(&mut self, size: Position) -> BlockHandle { // pub fn add_block(&mut self, size: Position) -> BlockHandle {
self.blocks.push(Block { // self.blocks.push(Block {
size, // size,
input: Vec::new(), // input: Vec::new(),
output: Vec::new(), // output: Vec::new(),
}); // });
BlockHandle(self.blocks.len() - 1) // BlockHandle(self.blocks.len() - 1)
} // }
// pub fn add_connection( // pub fn add_connection(
// &mut self, // &mut self,

View file

@ -1,4 +1,6 @@
#[derive(Clone, Debug)] use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Map<T> { pub struct Map<T> {
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,