use db::{MemoryLayoutDB, SqliteLayoutDB, LayoutDB, SavedLayout}; use city::{City, House, HouseLayout, get_price}; 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| get_price(&city, x.houses()).cmp(&get_price(&city, y.houses()))) .map(|layout| (*layout).clone()) .next().expect("No best layout found"); eprintln!("Found best layout, ID {}, price {}", best_layout.id(), 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(); //for layout in subcity_db.layouts().iter() // .filter(|x| get_price(subcity.city(), x.houses()) < removed_price) // .sorted_by(|x, y| get_price(subcity.city(), x.houses()).cmp(&get_price(subcity.city(), y.houses()))) { // let price = get_price(subcity.city(), layout.houses()); // let mut full_houses = subcity.to_full_houses(layout.houses()); // assert!(city::is_valid(&city, &full_houses).is_some()); // let mut house_layout = HouseLayout::new(&city); // for house in &full_houses { // house_layout.add_house(*house); // } // let seed: u64 = thread_rng().gen(); // let mut rng = StdRng::seed_from_u64(seed); // optimization::iterate_improvements(&mut house_layout, &mut rng, true); // eprintln!("Improvements finished"); // assert!(house_layout.is_valid()); // let improved_price = city::get_price(&city, house_layout.houses()); // if improved_price < city::get_price(&city, &full_houses) { // eprintln!("Found improvement, new price {}, updating houses", improved_price); // full_houses = house_layout.houses().clone(); // } // assert!(city::is_valid(&city, &full_houses).is_some()); // println!("Layout {}, price {}, full price {}", layout.id(), price, city::get_price(&city, &full_houses)); // // Be careful with duplicates here // //sqlite_db.add_layout(&full_houses, true); // //println!("Inserted into the global DB"); //} //return; // 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()); } }