Browse Source

Add subcity optimization

master
Jirka Sejkora 4 years ago
parent
commit
58c35762a2
  1. 126
      Cargo.lock
  2. 5
      Cargo.toml
  3. 63
      src/combine-layouts.rs
  4. 86
      src/combine.rs
  5. 12
      src/db.rs
  6. 34
      src/main.rs
  7. 59
      src/optimization.rs
  8. 169
      src/optimize-subcity.rs
  9. 98
      src/subcity.rs

126
Cargo.lock

@ -15,6 +15,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
@ -33,6 +39,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "console" name = "console"
version = "0.13.0" version = "0.13.0"
@ -49,6 +61,58 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -79,7 +143,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 0.1.10",
"libc", "libc",
"wasi", "wasi",
] ]
@ -102,6 +166,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "indicatif" name = "indicatif"
version = "0.15.0" version = "0.15.0"
@ -151,6 +224,25 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 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]] [[package]]
name = "number_prefix" name = "number_prefix"
version = "0.3.0" version = "0.3.0"
@ -177,6 +269,7 @@ dependencies = [
"indicatif", "indicatif",
"itertools", "itertools",
"rand", "rand",
"rayon",
"regex", "regex",
"rusqlite", "rusqlite",
] ]
@ -221,6 +314,31 @@ dependencies = [
"rand_core", "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]] [[package]]
name = "regex" name = "regex"
version = "1.4.3" version = "1.4.3"
@ -254,6 +372,12 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.6.1" version = "1.6.1"

5
Cargo.toml

@ -13,6 +13,7 @@ indicatif = "0.15.0"
itertools = "0.10.0" itertools = "0.10.0"
rusqlite = "0.24.2" rusqlite = "0.24.2"
regex = "1.4.3" regex = "1.4.3"
rayon = "1.5.0"
[[bin]] [[bin]]
name = "prague" name = "prague"
@ -25,3 +26,7 @@ path = "src/import-logs.rs"
[[bin]] [[bin]]
name = "combine" name = "combine"
path = "src/combine-layouts.rs" path = "src/combine-layouts.rs"
[[bin]]
name = "optimize-subcity"
path = "src/optimize-subcity.rs"

63
src/combine-layouts.rs

@ -7,11 +7,6 @@ mod city;
mod db; mod db;
mod combine; mod combine;
#[derive(Eq, PartialEq)]
enum LastStep {
None, Vertical, Horizontal
}
fn main() { fn main() {
let mut db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB"); let mut db = SqliteLayoutDB::from_file("layouts.sqlite").expect("Failed to load the DB");
eprintln!("Loaded the DB, {} stored layouts", db.layouts().len()); 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); 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!("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; const TOP_LAYOUT_COUNT: usize = 1500;
let mut cache = combine::CompatibilityCache::new(); let mut cache = combine::CompatibilityCache::new();
let mut last_improve_step = LastStep::None; combine::iterate_combines(&mut db, TOP_LAYOUT_COUNT, &city, &mut cache, true)
loop {
if last_improve_step == LastStep::Vertical { break; }
eprintln!("Starting to combine {} top houses DB; vertical cuts", TOP_LAYOUT_COUNT);
let sorted: Vec<SavedLayout> = 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<SavedLayout> = 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)
} }

86
src/combine.rs

@ -37,7 +37,73 @@ impl CompatibilityCache {
} }
} }
pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Vec<SavedLayout>, right_layouts: &Vec<SavedLayout>, db: &mut TDB, cache: &mut CompatibilityCache, transposed: bool) -> bool { pub fn iterate_combines<TDB: LayoutDB>(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<SavedLayout> = 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<SavedLayout> = 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<TDB: LayoutDB>(city: &City, left_layouts: &Vec<SavedLayout>, right_layouts: &Vec<SavedLayout>, db: &mut TDB, cache: &mut CompatibilityCache, transposed: bool, print_progress: bool) -> bool {
let mut best_price = left_layouts.iter().chain(right_layouts.iter()) let mut best_price = left_layouts.iter().chain(right_layouts.iter())
.map(|layout| city::get_price(&city, layout.houses())) .map(|layout| city::get_price(&city, layout.houses()))
.min(); .min();
@ -112,7 +178,7 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
// x is the last left coordinate, x+1 is right // x is the last left coordinate, x+1 is right
for x in 0..city.width() { for x in 0..city.width() {
eprintln!("Starting {} {}", axis, x); if print_progress { eprintln!("Starting {} {}", axis, x); }
// Update the lines // Update the lines
for left in lefts.iter_mut() { for left in lefts.iter_mut() {
@ -173,15 +239,19 @@ pub fn create_new_best_combination<TDB: LayoutDB>(city: &City, left_layouts: &Ve
if compatible { if compatible {
if best_price.is_none() || price < best_price.unwrap() { if best_price.is_none() || price < best_price.unwrap() {
best_price = Some(price); best_price = Some(price);
eprintln!("{} - new best score, cut on {} {}, left {} - right {}, printing", price, axis, x, left_i, right_i); if print_progress {
println!("{} - new best score, cut on {} {}, left {} - right {}", price, axis, x, left_i, right_i); 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(); let mut new_houses: Vec<_> = left.line.houses().copied().chain(right.line.houses().copied()).collect();
if transposed { if transposed {
new_houses = transpose_layout(&new_houses); new_houses = transpose_layout(&new_houses);
} }
println!("{}", new_houses.len()); if print_progress {
for house in &new_houses { println!("{}", new_houses.len());
println!("{} {}", house.y, house.x); for house in &new_houses {
println!("{} {}", house.y, house.x);
}
} }
// We only add best results to avoid overfilling the database with similar layouts // We only add best results to avoid overfilling the database with similar layouts
@ -197,7 +267,7 @@ pub fn create_new_best_combination<TDB: LayoutDB>(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 { if let Some(lowest_price) = best_price {

12
src/db.rs

@ -62,7 +62,11 @@ impl SavedLayout {
} }
impl MemoryLayoutDB { impl MemoryLayoutDB {
pub fn new(layouts: Vec<SavedLayout>, 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<SavedLayout>, merge_lower_bounds: HashMap<(usize, usize, bool), MergeLowerBound>) -> Self {
MemoryLayoutDB { layouts, merge_lower_bounds } MemoryLayoutDB { layouts, merge_lower_bounds }
} }
@ -175,10 +179,14 @@ impl SqliteLayoutDB {
} }
).collect(); ).collect();
let memory_db = MemoryLayoutDB::new(layouts, merges); let memory_db = MemoryLayoutDB::from_collections(layouts, merges);
Ok(SqliteLayoutDB { connection, memory_db}) Ok(SqliteLayoutDB { connection, memory_db})
} }
pub fn memory_db(&self) -> &MemoryLayoutDB {
&self.memory_db
}
pub fn layouts(&self) -> &Vec<SavedLayout> { pub fn layouts(&self) -> &Vec<SavedLayout> {
self.memory_db.layouts() self.memory_db.layouts()
} }

34
src/main.rs

@ -13,10 +13,6 @@ mod population;
mod city; mod city;
mod db; mod db;
#[derive(Eq, PartialEq)]
enum LastStep {
None, MovingIndividual, MergingPairs
}
enum RunType { enum RunType {
GenNew, TryImproveBest GenNew, TryImproveBest
@ -56,31 +52,8 @@ fn main() {
//populate_from_saved_layout(&mut layout, &best_layout); //populate_from_saved_layout(&mut layout, &best_layout);
//eprintln!("Finished loading DB layout ID {}, price: {}, houses: {}", best_layout.id(), layout.price(), layout.houses().len()); //eprintln!("Finished loading DB layout ID {}, price: {}, houses: {}", best_layout.id(), layout.price(), layout.houses().len());
let mut last_improved_step = LastStep::None; optimization::iterate_improvements(&mut layout, &mut rng, true);
loop { dump_layout(&layout, &mut best_price, seed);
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; }
}
db.add_layout(&layout.houses(), true).expect("Failed to insert into DB"); 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<u32>, seed: u64) {
print_houses(&layout.houses()); print_houses(&layout.houses());
println!(); println!();
} }
} }

59
src/optimization.rs

@ -5,7 +5,48 @@ use itertools::iproduct;
pub enum RectangleSearchError { pub enum RectangleSearchError {
Useless, 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<House>) -> Result<Rectangle, RectangleSearchError> { fn get_valid_move_rectangle_multiple(layout: &HouseLayout, houses: &Vec<House>) -> Result<Rectangle, RectangleSearchError> {
@ -90,7 +131,7 @@ fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectan
} }
if covered_rect.is_none() { if covered_rect.is_none() {
return Err(RectangleSearchError::Useless) return Err(RectangleSearchError::Useless);
} }
let covered_rect = covered_rect.unwrap(); let covered_rect = covered_rect.unwrap();
@ -107,7 +148,10 @@ fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectan
let bottom = if house.y >= layout.city.height() - 1 - dist_top { layout.city.height() - 1 } else { house.y + dist_top }; let bottom = if house.y >= layout.city.height() - 1 - dist_top { layout.city.height() - 1 } else { house.y + dist_top };
let valid_move_rectangle = Rectangle { let valid_move_rectangle = Rectangle {
left, right, top, bottom left,
right,
top,
bottom,
}; };
Ok(valid_move_rectangle) Ok(valid_move_rectangle)
@ -176,7 +220,8 @@ pub fn improve_move_individual_houses(layout: &mut HouseLayout, mut rng: &mut St
improved 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; let mut improved = false;
loop { loop {
@ -248,7 +293,9 @@ pub fn improve_merge_pairwise(layout: &mut HouseLayout) -> bool {
assert!(layout.is_valid()); assert!(layout.is_valid());
let new_price = layout.price(); let new_price = layout.price();
let price_diff = new_price as i32 - old_price as i32; 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; improved = true;
loop_improved = true; loop_improved = true;
} else { } else {
@ -415,6 +462,6 @@ fn get_dual_move_distances(layout: &HouseLayout, house1_index: usize, house2_ind
left: left_distance, left: left_distance,
right: right_distance, right: right_distance,
up: top_distance, up: top_distance,
down: bottom_distance down: bottom_distance,
}) })
} }

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

98
src/subcity.rs

@ -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…
Cancel
Save