176 lines
9 KiB
Rust
176 lines
9 KiB
Rust
use db::{MemoryLayoutDB, SqliteLayoutDB, LayoutDB, SavedLayout};
|
|
use city::{City, House, HouseLayout, get_price};
|
|
use itertools::Itertools;
|
|
use rand::{thread_rng, Rng, SeedableRng};
|
|
use rand::rngs::StdRng;
|
|
use rayon::prelude::*;
|
|
|
|
mod city;
|
|
mod db;
|
|
mod combine;
|
|
mod subcity;
|
|
mod optimization;
|
|
mod population;
|
|
|
|
fn main() {
|
|
let mut sqlite_db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB");
|
|
eprintln!("Loaded the DB, {} stored layouts", sqlite_db.layouts().len());
|
|
|
|
let city = City::read_from_file("01.in", city::INPUT_CITY_WIDTH, city::INPUT_CITY_HEIGHT);
|
|
eprintln!("Loaded the city file, {} houses", city.get_house_count());
|
|
|
|
let best_layout: SavedLayout = sqlite_db.layouts().iter()
|
|
.sorted_by(|x, y| get_price(&city, x.houses()).cmp(&get_price(&city, y.houses())))
|
|
.map(|layout| (*layout).clone())
|
|
.next().expect("No best layout found");
|
|
eprintln!("Found best layout, ID {}, price {}", best_layout.id(), get_price(&city, best_layout.houses()));
|
|
|
|
let x_range = 5533..=12000;
|
|
let y_range = 4750..=12500;
|
|
//let x_range = 5533..=8000;
|
|
//let y_range = 4750..=8000;
|
|
eprintln!("X {}-{}, Y {}-{}", x_range.start(), x_range.end(), y_range.start(), y_range.end());
|
|
|
|
let static_houses: Vec<House> = best_layout.houses().iter()
|
|
.filter(|house| !(x_range.contains(&house.x) && y_range.contains(&house.y)))
|
|
.map(|&house| house)
|
|
.collect();
|
|
|
|
let removed_houses: Vec<House> = best_layout.houses().iter()
|
|
.filter(|house| x_range.contains(&house.x) && y_range.contains(&house.y))
|
|
.map(|&house| house)
|
|
.collect();
|
|
|
|
let removed_price: u32 = removed_houses.iter().map(|x| city.get_price(*x) as u32).sum();
|
|
eprintln!("Price of all removed houses: {}", removed_price);
|
|
|
|
let subcity = subcity::build_subcity(&city, &static_houses);
|
|
|
|
eprintln!("Built subcity, width {}, height {}, {} houses, offset ({},{})",
|
|
subcity.city().width(),
|
|
subcity.city().height(),
|
|
subcity.city().get_house_count(),
|
|
subcity.x_offset(),
|
|
subcity.y_offset()
|
|
);
|
|
|
|
//let mut subcity_db = MemoryLayoutDB::new();
|
|
let filename = format!("X{}_{}Y{}_{}ID{}.sqlite", x_range.start(), x_range.end(), y_range.start(), y_range.end(), best_layout.id());
|
|
let mut subcity_db = SqliteLayoutDB::from_file(&filename).unwrap();
|
|
|
|
//for layout in subcity_db.layouts().iter()
|
|
// .filter(|x| get_price(subcity.city(), x.houses()) < removed_price)
|
|
// .sorted_by(|x, y| get_price(subcity.city(), x.houses()).cmp(&get_price(subcity.city(), y.houses()))) {
|
|
// let price = get_price(subcity.city(), layout.houses());
|
|
// let mut full_houses = subcity.to_full_houses(layout.houses());
|
|
// assert!(city::is_valid(&city, &full_houses).is_some());
|
|
|
|
// let mut house_layout = HouseLayout::new(&city);
|
|
// for house in &full_houses {
|
|
// house_layout.add_house(*house);
|
|
// }
|
|
// let seed: u64 = thread_rng().gen();
|
|
// let mut rng = StdRng::seed_from_u64(seed);
|
|
// optimization::iterate_improvements(&mut house_layout, &mut rng, true);
|
|
// eprintln!("Improvements finished");
|
|
// assert!(house_layout.is_valid());
|
|
// let improved_price = city::get_price(&city, house_layout.houses());
|
|
// if improved_price < city::get_price(&city, &full_houses) {
|
|
// eprintln!("Found improvement, new price {}, updating houses", improved_price);
|
|
// full_houses = house_layout.houses().clone();
|
|
// }
|
|
|
|
// assert!(city::is_valid(&city, &full_houses).is_some());
|
|
// println!("Layout {}, price {}, full price {}", layout.id(), price, city::get_price(&city, &full_houses));
|
|
// // Be careful with duplicates here
|
|
// //sqlite_db.add_layout(&full_houses, true);
|
|
// //println!("Inserted into the global DB");
|
|
//}
|
|
//return;
|
|
|
|
|
|
// Architecture:
|
|
// Build `FULL_RANDOM_LAYOUTS` random layouts
|
|
// loop {
|
|
// Try combining `CUT_COMBINE_TOP_LAYOUTS` top layouts using vertical/horizontal cuts
|
|
// Generate `WEIGHTED_RANDOM_LAYOUTS` layouts weighted by scores from existing layouts
|
|
// }
|
|
|
|
const FULL_RANDOM_LAYOUTS: usize = 100;
|
|
const DB_CHOICE_PROBABILITY: f64 = 0.90;
|
|
const WEIGHTED_RANDOM_LAYOUTS: usize = 200;
|
|
const CUT_COMBINE_TOP_LAYOUTS: usize = 500;
|
|
const IGNORED_WEIGHT_RATIO: f64 = 0.5;
|
|
|
|
if subcity_db.layouts().len() == 0 {
|
|
let mut full_random_layouts = Vec::new();
|
|
|
|
full_random_layouts.par_extend((0..FULL_RANDOM_LAYOUTS).into_par_iter().map(|i| {
|
|
let seed: u64 = thread_rng().gen();
|
|
let mut rng = StdRng::seed_from_u64(seed);
|
|
let mut layout = HouseLayout::new(subcity.city());
|
|
|
|
//eprintln!("Starting random population {}", i);
|
|
population::populate_random(&mut layout, &mut rng);
|
|
//eprintln!("Finished random init {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
|
optimization::iterate_improvements(&mut layout, &mut rng, false);
|
|
//eprintln!("Finished iterated improvements {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
|
layout.houses().clone()
|
|
}));
|
|
|
|
for houses in &full_random_layouts {
|
|
subcity_db.add_layout(houses, true);
|
|
}
|
|
|
|
eprintln!("Finished initial full random population");
|
|
} else {
|
|
eprintln!("Skipping initial full random population because DB is non-empty [{} layouts]", subcity_db.layouts().len());
|
|
}
|
|
|
|
let best_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
|
let worst_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
|
eprintln!("Best {}, worst {} [{} layouts]", best_price, worst_price, subcity_db.layouts().len());
|
|
|
|
let mut cache = combine::CompatibilityCache::new();
|
|
|
|
// TODO: Deduplication of the DB
|
|
loop {
|
|
// This is a bottleneck when it comes to multithreading, it only runs on a single thread
|
|
combine::iterate_combines(&mut subcity_db, CUT_COMBINE_TOP_LAYOUTS, subcity.city(), &mut cache, false);
|
|
|
|
let best_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
|
let worst_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
|
eprintln!("Finished cut combines");
|
|
eprintln!("Best {}, worst {} [{} layouts]", best_price, worst_price, subcity_db.layouts().len());
|
|
|
|
let mut weighted_random_layouts = Vec::new();
|
|
// Only using the underlying memory-based DB is required here as the SqliteLayoutDB
|
|
// is not thread-safe. We are only reading in the parallel iterator, so that's fine.
|
|
let memory_db = subcity_db.memory_db();
|
|
weighted_random_layouts.par_extend((0..WEIGHTED_RANDOM_LAYOUTS).into_par_iter().map(|i| {
|
|
let seed: u64 = thread_rng().gen();
|
|
let mut rng = StdRng::seed_from_u64(seed);
|
|
let mut layout = HouseLayout::new(subcity.city());
|
|
|
|
let price_range = worst_price - best_price;
|
|
let max_price = best_price as f64 + (price_range as f64 * (1. - IGNORED_WEIGHT_RATIO));
|
|
//eprintln!("Starting random weighted population {}, using DB, score range {}-{}, DB use probability {}...", i, best_price, worst_price, DB_CHOICE_PROBABILITY);
|
|
population::populate_using_db(&mut layout, &mut rng, memory_db, best_price as f64, max_price as f64, DB_CHOICE_PROBABILITY);
|
|
//eprintln!("Finished random init {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
|
optimization::iterate_improvements(&mut layout, &mut rng, false);
|
|
//eprintln!("Finished iterated improvements {}, price: {}, houses: {}", i, layout.price(), layout.houses().len());
|
|
layout.houses().clone()
|
|
}));
|
|
|
|
for houses in &weighted_random_layouts {
|
|
subcity_db.add_layout(houses, true);
|
|
}
|
|
|
|
let w_best: u32 = weighted_random_layouts.iter().map(|houses| houses.iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
|
let w_worst: u32 = weighted_random_layouts.iter().map(|houses| houses.iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
|
let best_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).min().unwrap();
|
|
let worst_price: u32 = subcity_db.layouts().iter().map(|layout| layout.houses().iter().map(|h| subcity.city().get_price(*h) as u32).sum()).max().unwrap();
|
|
eprintln!("Finished weighted random population, price range {}-{}", w_best, w_worst);
|
|
eprintln!("Best {}, worst {} [{} layouts]", best_price, worst_price, subcity_db.layouts().len());
|
|
}
|
|
}
|