From 58c35762a281d63904cb13cf48bbedaae1278a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sejkora?= Date: Fri, 12 Feb 2021 04:01:28 +0100 Subject: [PATCH] Add subcity optimization --- Cargo.lock | 126 +++++++++++++++++++++++++++++- Cargo.toml | 5 ++ src/combine-layouts.rs | 63 +-------------- src/combine.rs | 86 ++++++++++++++++++-- src/db.rs | 12 ++- src/main.rs | 34 +------- src/optimization.rs | 59 ++++++++++++-- src/optimize-subcity.rs | 169 ++++++++++++++++++++++++++++++++++++++++ src/subcity.rs | 98 +++++++++++++++++++++++ 9 files changed, 542 insertions(+), 110 deletions(-) create mode 100644 src/optimize-subcity.rs create mode 100644 src/subcity.rs diff --git a/Cargo.lock b/Cargo.lock index 1e45f74..0f18a0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "bitflags" version = "1.2.1" @@ -33,6 +39,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "console" version = "0.13.0" @@ -49,6 +61,58 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "const_fn" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" +dependencies = [ + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + [[package]] name = "either" version = "1.6.1" @@ -79,7 +143,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi", ] @@ -102,6 +166,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + [[package]] name = "indicatif" version = "0.15.0" @@ -151,6 +224,25 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "number_prefix" version = "0.3.0" @@ -177,6 +269,7 @@ dependencies = [ "indicatif", "itertools", "rand", + "rayon", "regex", "rusqlite", ] @@ -221,6 +314,31 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "regex" version = "1.4.3" @@ -254,6 +372,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "smallvec" version = "1.6.1" diff --git a/Cargo.toml b/Cargo.toml index 2071832..c32a7b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ indicatif = "0.15.0" itertools = "0.10.0" rusqlite = "0.24.2" regex = "1.4.3" +rayon = "1.5.0" [[bin]] name = "prague" @@ -25,3 +26,7 @@ path = "src/import-logs.rs" [[bin]] name = "combine" path = "src/combine-layouts.rs" + +[[bin]] +name = "optimize-subcity" +path = "src/optimize-subcity.rs" \ No newline at end of file diff --git a/src/combine-layouts.rs b/src/combine-layouts.rs index 7e9cc46..81e8297 100644 --- a/src/combine-layouts.rs +++ b/src/combine-layouts.rs @@ -7,11 +7,6 @@ mod city; mod db; mod combine; -#[derive(Eq, PartialEq)] -enum LastStep { - None, Vertical, Horizontal -} - fn main() { let mut db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB"); eprintln!("Loaded the DB, {} stored layouts", db.layouts().len()); @@ -19,65 +14,9 @@ fn main() { 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()); - eprintln!("Building a transposed city..."); - let transposed_city = transpose_city(&city); - eprintln!("Finished building a transposed city"); - const TOP_LAYOUT_COUNT: usize = 1500; let mut cache = combine::CompatibilityCache::new(); - let mut last_improve_step = LastStep::None; - loop { - if last_improve_step == LastStep::Vertical { break; } - eprintln!("Starting to combine {} top houses DB; vertical cuts", TOP_LAYOUT_COUNT); - let sorted: Vec = db.layouts().iter() - .sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) - .map(|layout| (*layout).clone()) - .collect(); - let chosen_layouts: Vec<_> = sorted.into_iter().take(TOP_LAYOUT_COUNT).collect(); - if combine::create_new_best_combination(&city, &chosen_layouts, &chosen_layouts, &mut db, &mut cache, false) { - last_improve_step = LastStep::Vertical; - } - eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); - - if last_improve_step == LastStep::Horizontal { break; } - - let sorted: Vec = db.layouts().iter() - .sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) - .map(|layout| (*layout).clone()) - .collect(); - let chosen_layouts: Vec<_> = sorted.into_iter().take(TOP_LAYOUT_COUNT).collect(); - let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_saved_layout(x)).collect(); - - eprintln!("Starting to combine {} top houses DB; horizontal cuts", TOP_LAYOUT_COUNT); - if combine::create_new_best_combination(&transposed_city, &transposed_chosen_layouts, &transposed_chosen_layouts, &mut db, &mut cache, true) { - last_improve_step = LastStep::Horizontal; - } - eprintln!("Finished horizontal cuts, improvement: {}", last_improve_step == LastStep::Horizontal); - - if last_improve_step == LastStep::None { break; } - } -} - -fn transpose_city(city: &City) -> City { - let mut transposed_prices = vec![0u16; city.width() * city.height()]; - for y in 0..city.height() { - for x in 0..city.width() { - // Sorry, cache! Not worth optimizing with blocks, - // this is not going to be ran often. - transposed_prices[x * city.height() + y] = city.get_price_xy(x, y); - } - } - - City::new(transposed_prices, city.height(), city.width()) -} - -fn transpose_saved_layout(layout: &SavedLayout) -> SavedLayout { - let mut transposed = Vec::new(); - for house in layout.houses() { - transposed.push(House::new(house.y, house.x)); - } - - SavedLayout::new(layout.id(), transposed) + combine::iterate_combines(&mut db, TOP_LAYOUT_COUNT, &city, &mut cache, true) } diff --git a/src/combine.rs b/src/combine.rs index 16a5ec1..0e162d7 100644 --- a/src/combine.rs +++ b/src/combine.rs @@ -37,7 +37,73 @@ impl CompatibilityCache { } } -pub fn create_new_best_combination(city: &City, left_layouts: &Vec, right_layouts: &Vec, db: &mut TDB, cache: &mut CompatibilityCache, transposed: bool) -> bool { +pub fn iterate_combines(mut db: &mut TDB, top_layout_count: usize, city: &City, mut cache: &mut CompatibilityCache, print_progress: bool) { + #[derive(Eq, PartialEq)] + enum LastStep { + None, + Vertical, + Horizontal, + } + if print_progress { eprintln!("Building a transposed city..."); } + let transposed_city = transpose_city(&city); + if print_progress { eprintln!("Finished building a transposed city"); } + + let mut last_improve_step = LastStep::None; + loop { + if last_improve_step == LastStep::Vertical { break; } + if print_progress { eprintln!("Starting to combine {} top houses DB; vertical cuts", top_layout_count); } + let sorted: Vec = db.layouts().iter() + .sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) + .map(|layout| (*layout).clone()) + .collect(); + let chosen_layouts: Vec<_> = sorted.into_iter().take(top_layout_count).collect(); + if create_new_best_combination(&city, &chosen_layouts, &chosen_layouts, db, &mut cache, false, print_progress) { + last_improve_step = LastStep::Vertical; + } + if print_progress { eprintln!("Finished vertical cuts, improvement: {}", last_improve_step == LastStep::Vertical); } + + if last_improve_step == LastStep::Horizontal { break; } + + let sorted: Vec = db.layouts().iter() + .sorted_by(|x, y| city::get_price(&city, x.houses()).cmp(&city::get_price(&city, y.houses()))) + .map(|layout| (*layout).clone()) + .collect(); + let chosen_layouts: Vec<_> = sorted.into_iter().take(top_layout_count).collect(); + let transposed_chosen_layouts: Vec<_> = chosen_layouts.iter().map(|x| transpose_saved_layout(x)).collect(); + + if print_progress { eprintln!("Starting to combine {} top houses DB; horizontal cuts", top_layout_count); } + if create_new_best_combination(&transposed_city, &transposed_chosen_layouts, &transposed_chosen_layouts, db, &mut cache, true, print_progress) { + last_improve_step = LastStep::Horizontal; + } + if print_progress { eprintln!("Finished horizontal cuts, improvement: {}", last_improve_step == LastStep::Horizontal); } + + if last_improve_step == LastStep::None { break; } + } +} + +fn transpose_city(city: &City) -> City { + let mut transposed_prices = vec![0u16; city.width() * city.height()]; + for y in 0..city.height() { + for x in 0..city.width() { + // Sorry, cache! Not worth optimizing with blocks, + // this is not going to be ran often. + transposed_prices[x * city.height() + y] = city.get_price_xy(x, y); + } + } + + City::new(transposed_prices, city.height(), city.width()) +} + +fn transpose_saved_layout(layout: &SavedLayout) -> SavedLayout { + let mut transposed = Vec::new(); + for house in layout.houses() { + transposed.push(House::new(house.y, house.x)); + } + + SavedLayout::new(layout.id(), transposed) +} + +pub fn create_new_best_combination(city: &City, left_layouts: &Vec, right_layouts: &Vec, db: &mut TDB, cache: &mut CompatibilityCache, transposed: bool, print_progress: bool) -> bool { let mut best_price = left_layouts.iter().chain(right_layouts.iter()) .map(|layout| city::get_price(&city, layout.houses())) .min(); @@ -112,7 +178,7 @@ pub fn create_new_best_combination(city: &City, left_layouts: &Ve // x is the last left coordinate, x+1 is right for x in 0..city.width() { - eprintln!("Starting {} {}", axis, x); + if print_progress { eprintln!("Starting {} {}", axis, x); } // Update the lines for left in lefts.iter_mut() { @@ -173,15 +239,19 @@ pub fn create_new_best_combination(city: &City, left_layouts: &Ve if compatible { if best_price.is_none() || price < best_price.unwrap() { best_price = Some(price); - eprintln!("{} - new best score, cut on {} {}, left {} - right {}, printing", price, axis, x, left_i, right_i); - println!("{} - new best score, cut on {} {}, left {} - right {}", price, axis, x, left_i, right_i); + if print_progress { + eprintln!("{} - new best score, cut on {} {}, left {} - right {}, printing", price, axis, x, left_i, right_i); + println!("{} - new best score, cut on {} {}, left {} - right {}", price, axis, x, left_i, right_i); + } let mut new_houses: Vec<_> = left.line.houses().copied().chain(right.line.houses().copied()).collect(); if transposed { new_houses = transpose_layout(&new_houses); } - println!("{}", new_houses.len()); - for house in &new_houses { - println!("{} {}", house.y, house.x); + if print_progress { + println!("{}", new_houses.len()); + for house in &new_houses { + println!("{} {}", house.y, house.x); + } } // We only add best results to avoid overfilling the database with similar layouts @@ -197,7 +267,7 @@ pub fn create_new_best_combination(city: &City, left_layouts: &Ve } } - eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits); + if print_progress { eprintln!("{} incompatibles checked before {} compatible ({} cache hits)", incompatibles, compatibles, cache_hits); } } if let Some(lowest_price) = best_price { diff --git a/src/db.rs b/src/db.rs index 68a6c11..59ac746 100644 --- a/src/db.rs +++ b/src/db.rs @@ -62,7 +62,11 @@ impl SavedLayout { } impl MemoryLayoutDB { - pub fn new(layouts: Vec, merge_lower_bounds: HashMap<(usize, usize, bool), MergeLowerBound>) -> Self { + pub fn new() -> Self { + MemoryLayoutDB { layouts: Vec::new(), merge_lower_bounds: HashMap::new() } + } + + pub fn from_collections(layouts: Vec, merge_lower_bounds: HashMap<(usize, usize, bool), MergeLowerBound>) -> Self { MemoryLayoutDB { layouts, merge_lower_bounds } } @@ -175,10 +179,14 @@ impl SqliteLayoutDB { } ).collect(); - let memory_db = MemoryLayoutDB::new(layouts, merges); + let memory_db = MemoryLayoutDB::from_collections(layouts, merges); Ok(SqliteLayoutDB { connection, memory_db}) } + pub fn memory_db(&self) -> &MemoryLayoutDB { + &self.memory_db + } + pub fn layouts(&self) -> &Vec { self.memory_db.layouts() } diff --git a/src/main.rs b/src/main.rs index 0640ebe..65cf214 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,10 +13,6 @@ mod population; mod city; mod db; -#[derive(Eq, PartialEq)] -enum LastStep { - None, MovingIndividual, MergingPairs -} enum RunType { GenNew, TryImproveBest @@ -56,31 +52,8 @@ fn main() { //populate_from_saved_layout(&mut layout, &best_layout); //eprintln!("Finished loading DB layout ID {}, price: {}, houses: {}", best_layout.id(), layout.price(), layout.houses().len()); - let mut last_improved_step = LastStep::None; - loop { - if last_improved_step == LastStep::MovingIndividual { break; } - eprintln!("Starting moving individual houses..."); - if optimization::improve_move_individual_houses(&mut layout, &mut rng) { - dump_layout(&layout, &mut best_price, seed); - last_improved_step = LastStep::MovingIndividual; - } - eprintln!("Finished moving individual houses..."); - - if last_improved_step == LastStep::MergingPairs { break; } - eprintln!("Starting pairwise house merge..."); - if optimization::improve_merge_pairwise(&mut layout) { - dump_layout(&layout, &mut best_price, seed); - last_improved_step = LastStep::MergingPairs; - } - eprintln!("Finished pairwise house merge"); - //eprintln!("Starting pairwise house move..."); - //if optimization::improve_move_houses_pairwise(&mut layout) { - // dump_layout(&layout, &mut best_price, seed); - // improved = true; - //} - //eprintln!("Finished pairwise house move"); - if last_improved_step == LastStep::None { break; } - } + optimization::iterate_improvements(&mut layout, &mut rng, true); + dump_layout(&layout, &mut best_price, seed); db.add_layout(&layout.houses(), true).expect("Failed to insert into DB"); } } @@ -107,5 +80,4 @@ fn dump_layout(layout: &HouseLayout, best_price: &mut Option, seed: u64) { print_houses(&layout.houses()); println!(); } -} - +} \ No newline at end of file diff --git a/src/optimization.rs b/src/optimization.rs index 2f65d29..eb5d908 100644 --- a/src/optimization.rs +++ b/src/optimization.rs @@ -5,7 +5,48 @@ use itertools::iproduct; pub enum RectangleSearchError { Useless, - Unsatisfiable + Unsatisfiable, +} + +pub fn iterate_improvements(mut layout: &mut HouseLayout, mut rng: &mut StdRng, print_progress: bool) { + #[derive(Eq, PartialEq)] + enum LastStep { + None, + MovingIndividual, + MergingPairs, + } + + let mut last_improved_step = LastStep::None; + loop { + if last_improved_step == LastStep::MovingIndividual { break; } + if print_progress { + eprintln!("Starting moving individual houses..."); + } + if improve_move_individual_houses(&mut layout, &mut rng) { + last_improved_step = LastStep::MovingIndividual; + } + if print_progress { + eprintln!("Finished moving individual houses..."); + } + + if last_improved_step == LastStep::MergingPairs { break; } + if print_progress { + eprintln!("Starting pairwise house merge..."); + } + if improve_merge_pairwise(&mut layout, print_progress) { + last_improved_step = LastStep::MergingPairs; + } + if print_progress { + eprintln!("Finished pairwise house merge"); + } + //eprintln!("Starting pairwise house move..."); + //if optimization::improve_move_houses_pairwise(&mut layout) { + // dump_layout(&layout, &mut best_price, seed); + // improved = true; + //} + //eprintln!("Finished pairwise house move"); + if last_improved_step == LastStep::None { break; } + } } fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec) -> Result { @@ -90,7 +131,7 @@ fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result Result= layout.city.height() - 1 - dist_top { layout.city.height() - 1 } else { house.y + dist_top }; let valid_move_rectangle = Rectangle { - left, right, top, bottom + left, + right, + top, + bottom, }; Ok(valid_move_rectangle) @@ -176,7 +220,8 @@ pub fn improve_move_individual_houses(layout: &mut HouseLayout, mut rng: &mut St improved } -pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool { + +pub fn improve_merge_pairwise(layout: &mut HouseLayout, print_progress: bool) -> bool { let mut improved = false; loop { @@ -248,7 +293,9 @@ pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool { assert!(layout.is_valid()); let new_price = layout.price(); let price_diff = new_price as i32 - old_price as i32; - eprintln!("Merged two houses, new price {}, diff {}", new_price, price_diff); + if print_progress { + eprintln!("Merged two houses, new price {}, diff {}", new_price, price_diff); + } improved = true; loop_improved = true; } else { @@ -415,6 +462,6 @@ fn get_dual_move_distances(layout: &HouseLayout, house1_index: usize, house2_ind left: left_distance, right: right_distance, up: top_distance, - down: bottom_distance + down: bottom_distance, }) } \ No newline at end of file diff --git a/src/optimize-subcity.rs b/src/optimize-subcity.rs new file mode 100644 index 0000000..351e9c5 --- /dev/null +++ b/src/optimize-subcity.rs @@ -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 = best_layout.houses().iter() + .filter(|house| !(x_range.contains(&house.x) && y_range.contains(&house.y))) + .map(|&house| house) + .collect(); + + let removed_houses: Vec = 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, 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) { + println!("{}", houses.len()); + for house in houses { + println!("{} {}", house.y, house.x); + } +} diff --git a/src/subcity.rs b/src/subcity.rs new file mode 100644 index 0000000..9e6e398 --- /dev/null +++ b/src/subcity.rs @@ -0,0 +1,98 @@ +use crate::city::{Rectangle, HOUSE_RANGE, House, HouseLayout, City}; + +pub struct Subcity { + city: City, + bought_houses: Vec, + x_offset: usize, + y_offset: usize +} + +impl Subcity { + pub fn city(&self) -> &City { + &self.city + } + pub fn bought_houses(&self) -> &Vec { + &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 + } +} \ No newline at end of file