Jirka Sejkora
4 years ago
9 changed files with 542 additions and 110 deletions
@ -0,0 +1,169 @@ |
|||||
|
use db::{MemoryLayoutDB, SqliteLayoutDB, LayoutDB, SavedLayout}; |
||||
|
use city::{City, House, HouseLayout}; |
||||
|
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| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) |
||||
|
.map(|layout| (*layout).clone()) |
||||
|
.next().expect("No best layout found"); |
||||
|
eprintln!("Found best layout, ID {}, price {}", best_layout.id(), city::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(); |
||||
|
|
||||
|
// 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()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn dump_layout(layout: &HouseLayout, best_price: &mut Option<u32>, seed: u64) { |
||||
|
let price = layout.price(); |
||||
|
if best_price.is_none() || price < best_price.unwrap() { |
||||
|
*best_price = Some(price); |
||||
|
eprintln!("Printing {} - new best", price); |
||||
|
println!("New best!"); |
||||
|
println!("Price {}, seed {}", price, seed); |
||||
|
print_houses(&layout.houses()); |
||||
|
println!(); |
||||
|
} else { |
||||
|
eprintln!("Printing {}", price); |
||||
|
println!("Price {}, seed {}", price, seed); |
||||
|
print_houses(&layout.houses()); |
||||
|
println!(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fn print_houses(houses: &Vec<House>) { |
||||
|
println!("{}", houses.len()); |
||||
|
for house in houses { |
||||
|
println!("{} {}", house.y, house.x); |
||||
|
} |
||||
|
} |
@ -0,0 +1,98 @@ |
|||||
|
use crate::city::{Rectangle, HOUSE_RANGE, House, HouseLayout, City}; |
||||
|
|
||||
|
pub struct Subcity { |
||||
|
city: City, |
||||
|
bought_houses: Vec<House>, |
||||
|
x_offset: usize, |
||||
|
y_offset: usize |
||||
|
} |
||||
|
|
||||
|
impl Subcity { |
||||
|
pub fn city(&self) -> &City { |
||||
|
&self.city |
||||
|
} |
||||
|
pub fn bought_houses(&self) -> &Vec<House> { |
||||
|
&self.bought_houses |
||||
|
} |
||||
|
pub fn x_offset(&self) -> usize { |
||||
|
self.x_offset |
||||
|
} |
||||
|
pub fn y_offset(&self) -> usize { |
||||
|
self.y_offset |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// Creates a new city that is a subset of the original city.
|
||||
|
/// The provided houses and houses they cover are removed. If there is an empty margin
|
||||
|
/// around uncovered houses, it is removed - this may result in a smaller city.
|
||||
|
pub fn build_subcity(city: &City, bought_houses: &[House]) -> Subcity { |
||||
|
let mut covered = vec![false; city.width() * city.height()]; |
||||
|
|
||||
|
for house in bought_houses { |
||||
|
assert!(city.is_house(*house)); |
||||
|
|
||||
|
let range_rect = house.range_rectangle(city); |
||||
|
for y in range_rect.top..=range_rect.bottom { |
||||
|
for x in range_rect.left..=range_rect.right { |
||||
|
covered[y as usize * city.width() + x as usize] = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Inclusive bounds for uncovered houses in the new subcity
|
||||
|
let mut min_x = None; |
||||
|
let mut min_y = None; |
||||
|
let mut max_x = None; |
||||
|
let mut max_y = None; |
||||
|
|
||||
|
for y in 0..city.height() { |
||||
|
for x in 0..city.width() { |
||||
|
if !covered[y * city.width() + x] && city.is_house_xy(x, y) { |
||||
|
min_x = Some(min_x.unwrap_or(usize::MAX).min(x)); |
||||
|
min_y = Some(min_y.unwrap_or(usize::MAX).min(y)); |
||||
|
max_x = Some(max_x.unwrap_or(usize::MIN).max(x)); |
||||
|
max_y = Some(max_x.unwrap_or(usize::MIN).max(y)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if min_x.is_none() { |
||||
|
assert!(min_y.is_none()); |
||||
|
assert!(max_x.is_none()); |
||||
|
assert!(max_y.is_none()); |
||||
|
return Subcity { |
||||
|
city: City::new(Vec::new(), 0, 0), |
||||
|
bought_houses: bought_houses.iter().map(|&h| h).collect(), |
||||
|
x_offset: 0, |
||||
|
y_offset: 0, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
let min_x = min_x.unwrap(); |
||||
|
let min_y = min_y.unwrap(); |
||||
|
let max_x = max_x.unwrap(); |
||||
|
let max_y = max_y.unwrap(); |
||||
|
|
||||
|
let width = max_x - min_x + 1; |
||||
|
let height = max_y - min_y + 1; |
||||
|
|
||||
|
let mut prices = vec![0; height * width]; |
||||
|
for y in 0..height { |
||||
|
for x in 0..width { |
||||
|
let original_x = min_x + x; |
||||
|
let original_y = min_y + y; |
||||
|
|
||||
|
// Copy prices for uncovered tiles, the covered tiles default to 0 - non-houses.
|
||||
|
if !covered[original_y * city.width() + original_x] { |
||||
|
prices[y * width + x] = city.get_price_xy(original_x, original_y) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Subcity { |
||||
|
city: City::new(prices, width, height), |
||||
|
bought_houses: bought_houses.iter().map(|&h| h).collect(), |
||||
|
x_offset: min_x, |
||||
|
y_offset: min_y |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue