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