Add subcity optimization
This commit is contained in:
parent
083d6a97b9
commit
58c35762a2
9 changed files with 542 additions and 110 deletions
126
Cargo.lock
generated
126
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
|
@ -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<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)
|
||||
combine::iterate_combines(&mut db, TOP_LAYOUT_COUNT, &city, &mut cache, true)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
.map(|layout| city::get_price(&city, layout.houses()))
|
||||
.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
|
||||
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<TDB: LayoutDB>(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<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 {
|
||||
|
|
12
src/db.rs
12
src/db.rs
|
@ -62,7 +62,11 @@ impl SavedLayout {
|
|||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
|
@ -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<SavedLayout> {
|
||||
self.memory_db.layouts()
|
||||
}
|
||||
|
|
34
src/main.rs
34
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<u32>, seed: u64) {
|
|||
print_houses(&layout.houses());
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<House>) -> Result<Rectangle, RectangleSearchError> {
|
||||
|
@ -90,7 +131,7 @@ fn get_valid_move_rectangle(layout: &HouseLayout, house: House) -> Result<Rectan
|
|||
}
|
||||
|
||||
if covered_rect.is_none() {
|
||||
return Err(RectangleSearchError::Useless)
|
||||
return Err(RectangleSearchError::Useless);
|
||||
}
|
||||
|
||||
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 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,
|
||||
})
|
||||
}
|
169
src/optimize-subcity.rs
Normal file
169
src/optimize-subcity.rs
Normal file
|
@ -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
Normal file
98
src/subcity.rs
Normal file
|
@ -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 a new issue