# Jordan Code v2c
```
Basic genetic algorithm to mutate results with pooled multi-threading.
This version uses a variable mutation rate and an improved output formatter.
Usage
v2c <file_num> <thread_count> <thread_delay> <mutation_rate> <mutation_decrease_secs> <stuck_limit_secs>
file_num: The input file to use (0 - 5).
thread_count: The number of threads to use. (Usually 2 per core.)
thread_delay: The number of milliseconds wait before checking threads for new answers and respawning threads.
mutation_rate: The number of changes to make per trial to start with.
mutation_decrease_secs: The number of seconds to wait without finding an answer before decreasing the mutation rate by 1. (Never drops below 1.)
stuck_limit_secs: The number of seconds to wait without finding an answer before restarting.
Usage example: cargo run --release 5 8 1000 10 10 300
```
```
use num_format::{Locale, ToFormattedString};
use rand::prelude::*;
use simple_error::bail;
use std::collections::{HashMap, VecDeque};
use std::cmp::{max, min};
// use std::cmp::{Reverse};
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::sync::mpsc::channel;
use std::thread::sleep;
use std::time::{Duration as TimeDuration, Instant};
use threadpool::ThreadPool;
// Types
type Duration = u32;
type IntersectionId = u32;
type Path = Vec<StreetId>;
type Paths = Vec<Path>;
type Score = u64;
#[derive(Clone, Debug)]
struct Street
{
street_id: StreetId,
start_intersection: IntersectionId,
end_intersection: IntersectionId,
length: Duration
}
type StreetId = usize;
type StreetName = String;
type StreetNames = Vec<StreetName>;
type Streets = Vec<Street>;
#[derive(Clone, Debug)]
struct StreetSchedule
{
street_id: StreetId,
duration: Duration
}
#[derive(Clone, Debug)]
struct IntersectionSchedule
{
intersection_id: IntersectionId,
schedule: Vec<StreetSchedule>
}
type Submission = Vec<IntersectionSchedule>;
// Simulation Types
#[derive(Clone, Debug)]
struct Car
{
active: bool, // Active if the car has not reached its destination.
current_path_index: usize, // The current path index.
duration_remaining: Duration, // The duration remaining before the car reaches the intersection.
path: Path // The path the car will follow.
}
#[derive(Clone, Debug)]
struct Intersection
{
intersection_id: IntersectionId,
car_queue: HashMap<StreetId, VecDeque<usize>>,
current_schedule_index: usize,
duration_remaining: Duration,
schedule: Vec<StreetSchedule>,
available: bool // Only one car can pass per second. Is the intersection still available to allow a car through?
}
// Utility Functions
fn get_filename(file_number: usize) -> Result<String, Box<dyn Error>>
{
match file_number
{
0 => Ok("../data/input/a.txt".to_owned()),
1 => Ok("../data/input/b.txt".to_owned()),
2 => Ok("../data/input/c.txt".to_owned()),
3 => Ok("../data/input/d.txt".to_owned()),
4 => Ok("../data/input/e.txt".to_owned()),
5 => Ok("../data/input/f.txt".to_owned()),
_ => bail!("Incorrect file number specified."),
}
}
fn get_cli_args() -> Result<(usize, usize, usize, usize, usize, usize), Box<dyn Error>>
{
let args: Vec<String> = env::args().collect();
let input_file_index = args[1].parse()?;
let thread_count = args[2].parse()?;
let thread_delay = args[3].parse()?;
let mutation_rate = args[4].parse()?;
let mutation_decrease_secs = args[5].parse()?;
let stuck_limit_secs = args[6].parse()?;
Ok((input_file_index, thread_count, thread_delay, mutation_rate, mutation_decrease_secs, stuck_limit_secs))
}
fn parse_input_data(input_data: &Vec<String>) -> Result<(Duration, u32, u32, u32, Score, Streets, StreetNames, Paths), Box<dyn Error>>
{
let mut line01 = input_data[0].split_whitespace();
let duration_total: Duration = line01.next().unwrap().parse()?;
let intersection_count = line01.next().unwrap().parse()?;
let streets_count = line01.next().unwrap().parse()?;
let cars_count = line01.next().unwrap().parse()?;
let car_bonus = line01.next().unwrap().parse()?;
let mut streets: Streets = vec!();
let mut street_names: StreetNames = vec!();
for i in 0..streets_count as usize
{
// println!("Processing Streets {}/{}", i+1, streets_count);
let mut line = input_data[i+1].split_whitespace();
let start: IntersectionId = line.next().unwrap().parse()?;
let end: IntersectionId = line.next().unwrap().parse()?;
let name: StreetName = line.next().unwrap().parse()?;
let duration: Duration = line.next().unwrap().parse()?;
street_names.push(name);
let street = Street
{
street_id: i as StreetId,
start_intersection: start,
end_intersection: end,
length: duration
};
streets.push(street);
}
let mut paths: Paths = vec!();
for i in 0..cars_count as usize
{
// println!("Processing Cars {}/{}", i+1, cars_count);
let ii = i + 1 + streets_count as usize;
let mut line = input_data[ii].split_whitespace();
let mut path: Path = vec!();
let count: usize = line.next().unwrap().parse()?;
for _c in 0..count
{
let street_name = line.next().unwrap();
let street_id = street_names.iter().position(|x|x==street_name).unwrap();
path.push(street_id);
}
paths.push(path);
}
// dbg!(&streets);
// dbg!(&paths);
Ok((duration_total, intersection_count, streets_count, cars_count, car_bonus, streets, street_names, paths))
}
fn read_input(file_path: &str) -> Result<Vec<String>, Box<dyn Error>>
{
let file = File::open(file_path)?;
let reader = BufReader::new(file);
let mut lines = Vec::new();
for line in reader.lines()
{
let line = line?;
let line = line.trim().parse()?;
lines.push(line);
}
Ok(lines)
}
fn write_output(file_path: &str, submissions: &Submission, street_names: &StreetNames) -> Result<(), Box<dyn Error>>
{
// Create a new submissions that omits any street schedules with a duration of 0.
let mut submissions_new = vec!();
for intersection_schedule in submissions.iter()
{
// Create a replacement empty intersection schedule.
let mut intersection_schedule_new = IntersectionSchedule
{
intersection_id: intersection_schedule.intersection_id,
schedule: vec!()
};
// Cycle through each street schedule.
for street_schedule in intersection_schedule.schedule.iter()
{
// Populate the intersection schedule.
if street_schedule.duration > 0
{
intersection_schedule_new.schedule.push(street_schedule.clone());
}
}
// If there is at least a single street schedule with duration > 0, add the intersection schedule the submissions.
let duration_total: Duration = intersection_schedule_new.schedule.iter().map(|x|x.duration).sum();
if duration_total > 0
{
submissions_new.push(intersection_schedule_new);
}
}
let mut file = File::create(file_path)?;
file.write_all(format!("{}\n", submissions_new.len()).as_bytes())?;
for intersection_schedule in submissions_new
{
file.write_all(format!("{}\n", intersection_schedule.intersection_id).as_bytes())?;
file.write_all(format!("{}\n", intersection_schedule.schedule.len()).as_bytes())?;
for i in 0..intersection_schedule.schedule.len()
{
file.write_all(format!("{} {}\n", street_names[intersection_schedule.schedule[i].street_id as usize], intersection_schedule.schedule[i].duration).as_bytes())?;
}
}
Ok(())
}
fn validate_args() -> Result<(), Box<dyn Error>>
{
let args: Vec<String> = env::args().collect();
if args.len() != 7
{
bail!("Incorrect number of arguments. Correct usage is <file_num> <thread_count> <thread_delay> <mutation_rate> <mutation_decrease_secs> <stuck_limit_secs>.");
}
Ok(())
}
// Calculation Functions
fn calculate_submission_score(submission: &Submission, paths: &Paths, streets: &Streets, duration_total: Duration, car_bonus: Score, _street_names: &StreetNames) -> Result<Score, Box<dyn Error>>
{
let mut score = 0;
// Create initial intersection state from submissions.
let mut intersections = HashMap::new();
for (_i, intersection_schedule) in submission.iter().enumerate()
{
let intersection_id = intersection_schedule.intersection_id;
let intersection = Intersection
{
intersection_id: intersection_id,
car_queue: intersection_schedule.schedule.iter().map(|x|(x.street_id, VecDeque::new())).collect(),
current_schedule_index: 0,
duration_remaining: intersection_schedule.schedule[0].duration,
schedule: intersection_schedule.schedule.clone(),
available: true
};
intersections.insert(intersection_id, intersection);
}
// Create initial car position from paths.
let mut cars = vec!();
for (i, path) in paths.iter().enumerate()
{
// Create the car instance.
let car = Car
{
active: true,
current_path_index: 0,
duration_remaining: 0,
path: path.clone()
};
cars.push(car);
// Add the car to the car_queue on the intersection.
let street_id = path[0];
let intersection = intersections.get_mut(&streets[street_id as usize].end_intersection).unwrap();
intersection.car_queue.get_mut(&street_id).unwrap().push_back(i);
}
// Run simulation time indexes.
for t in 1..duration_total+1
{
// Update car positions.
for (car_id, car) in cars.iter_mut().enumerate()
{
// Skip all inactive cars.
if !car.active
{
continue;
}
// If car is at an intersection.
if car.duration_remaining == 0
{
let car_street_id = car.path[car.current_path_index];
let car_intersection = streets[car_street_id as usize].end_intersection;
let intersection = intersections.get_mut(&car_intersection).unwrap();
// If car is on the final street.
if car.current_path_index == car.path.len() - 1
{
// Remove car and add to score immediately.
car.active = false;
score += car_bonus + (duration_total - t) as Score;
continue;
}
// Car has reached an intersection, but is not on the final street.
else
{
let intersection_car_queue = intersection.car_queue.get_mut(&car_street_id).unwrap();
// If the intersection queue does not already has this car waiting.
if !intersection_car_queue.contains(&car_id)
{
// Add to the car to the back of the intersection queue.
intersection_car_queue.push_back(car_id);
}
}
// Is there a green light at the intersection for the street the car is currently on?
let is_green_light = intersection.schedule[intersection.current_schedule_index].street_id == car_street_id;
// If the car is at an intersection with a green light on the current street and no other car has already passed in the same time slice.
if is_green_light && intersection.available
{
let intersection_car_queue = intersection.car_queue.get_mut(&car_street_id).unwrap();
let front_car_id = intersection_car_queue.front().unwrap();
// If the car is at the front of the line.
if *front_car_id == car_id
{
// Remove car from the car queue.
intersection_car_queue.pop_front();
// Mark the intersection as no longer available until the next time slot.
intersection.available = false;
// Update the path index and duration. This should never be out of index since it is detected above.
car.current_path_index += 1;
car.duration_remaining = streets[car.path[car.current_path_index]].length;
}
}
}
// If we have not hit the end of the street.
if car.duration_remaining > 0
{
// Move car forward a step.
car.duration_remaining -= 1;
// If we hit the end of a street.
if car.duration_remaining == 0
{
// If car is on the final street.
if car.current_path_index == car.path.len() - 1
{
// Remove car and add to score immediately.
car.active = false;
score += car_bonus + (duration_total - t) as Score;
continue;
}
}
}
}
// Update intersections.
for (_i, intersection) in intersections.iter_mut()
{
// If there are no schedules with a duration of at least 1, disable the intersection.
let intersection_total_duration: Duration = intersection.schedule.iter().map(|x|x.duration).sum();
if intersection_total_duration < 1
{
intersection.available = false;
continue;
}
// Step forward in time.
intersection.duration_remaining -= 1;
// Intersection is at end of cycle (duration_remaining == 0)
if intersection.duration_remaining == 0
{
// We loop here until we find a schedule with a duration > 0 or there are no schedules available.
loop
{
// Set to the next index.
intersection.current_schedule_index += 1;
// If the index is out of range, go back to the first.
if intersection.current_schedule_index >= intersection.schedule.len()
{
intersection.current_schedule_index = 0;
}
// Set the duration remaining to the duration in the schedule.
intersection.duration_remaining = intersection.schedule[intersection.current_schedule_index].duration;
// Break if we have an intersection with a duration > 0.
if intersection.duration_remaining > 0
{
break;
}
}
}
// Reset the available flag.
intersection.available = true;
}
}
Ok(score)
}
fn generate_submission(_duration_total: Duration, _intersection_count: u32, _streets_count: u32, _cars_count: u32, _car_bonus: u64, streets: &Streets, _street_names: &StreetNames, paths: &Paths) -> Result<Submission, Box<dyn Error>>
{
// let mut rng = rand::thread_rng();
// Cycle through each path and create a hashmap of intersections with schedules.
let mut intersections: HashMap<IntersectionId, Vec<StreetSchedule>> = HashMap::new();
for path in paths
{
for (_path_order, street_id) in path.iter().enumerate()
{
let intersection = streets[*street_id as usize].end_intersection;
if !intersections.contains_key(&intersection)
{
intersections.insert(intersection, vec!());
}
let schedules = intersections.get_mut(&intersection).unwrap();
let mut found = false;
for schedule in schedules.iter_mut()
{
if schedule.street_id == *street_id
{
// schedule.duration += 1;
found = true;
}
}
if found == false
{
let street_schedule = StreetSchedule
{
street_id: *street_id,
duration: 1 //rng.gen_range(0, 5)
};
schedules.push(street_schedule);
}
}
}
// Adjust the duration by taking into account the street length and order in paths.
// for (_intersection_id, street_schedule) in intersections.iter_mut()
// {
// street_schedule.sort_by_key(|x|Reverse(x.duration));
// // Adjust durations.
// let count = street_schedule.len() as i32;
// for (i, street_schedule) in street_schedule.iter_mut().enumerate()
// {
// let duration = count - (i as i32);
// duration = max(duration, 1); // Ensure minimum.
// duration = min(duration, 2); // Ensure maximum.
// street_schedule.duration = duration as u32;
// }
// // dbg!(&street_schedule);
// }
// Create schedule for all intersections.
let mut intersection_schedules = vec!();
for intersection in intersections
{
let intersection_schedule = IntersectionSchedule
{
intersection_id: intersection.0,
schedule: intersection.1
};
intersection_schedules.push(intersection_schedule);
}
Ok(intersection_schedules)
}
fn mutate_submission(mut submission: Submission, mut rng: ThreadRng, duration_total: Duration, mutation_rate: usize) -> Submission
{
for _ in 0..mutation_rate
{
// Select a random action.
let action = rng.gen_range(0, 5);
// Action 0 = swap schedule order.
if action == 0
{
// Select a random intersection.
let index = rng.gen_range(0, submission.len()-1);
let intersection_schedule = &mut submission[index];
if intersection_schedule.schedule.len() > 1
{
let index_0 = rng.gen_range(0, intersection_schedule.schedule.len()-1);
let index_1 = rng.gen_range(0, intersection_schedule.schedule.len()-1);
if index_0 != index_1
{
intersection_schedule.schedule.swap(index_0, index_1);
}
}
}
// Action 1+ = modify duration.
if action >= 1
{
// Select a random intersection.
let index = rng.gen_range(0, submission.len()-1);
let intersection_schedule = &mut submission[index];
if intersection_schedule.schedule.len() > 1
{
let index = rng.gen_range(0, intersection_schedule.schedule.len()-1);
let street_schedule = &mut intersection_schedule.schedule[index];
if rng.gen_range(0, 1) == 0
{
street_schedule.duration -= 1;
}
else
{
street_schedule.duration += 1;
}
street_schedule.duration = max(street_schedule.duration, 0); // Ensure minimum.
street_schedule.duration = min(street_schedule.duration, duration_total); // Ensure maximum.
}
}
}
submission
}
// Main Entry Point
fn main() -> Result<(), Box<dyn Error>>
{
validate_args()?;
let (input_file_index, thread_count, thread_delay, mutation_rate, mutation_decrease_secs, stuck_limit_secs) = get_cli_args()?;
let filename = get_filename(input_file_index)?;
let input_data = read_input(&filename)?;
let (duration_total, intersection_count, streets_count, cars_count, car_bonus, streets, street_names, paths) = parse_input_data(&input_data)?;
let baseline_timer = Instant::now();
let baseline_submission = generate_submission(duration_total, intersection_count, streets_count, cars_count, car_bonus, &streets, &street_names, &paths)?;
let baseline_score = calculate_submission_score(&baseline_submission, &paths, &streets, duration_total, car_bonus, &street_names)?;
println!("Baseline Score: {} (Generated in {:#?}.)", baseline_score.to_formatted_string(&Locale::en), baseline_timer.elapsed());
let mut best_score = baseline_score;
let mut best_submission = baseline_submission.clone();
let mut current_mutation_rate = mutation_rate;
let mut mutation_timer = Instant::now();
let mut solution_timer = Instant::now();
let mut thread_channels = vec!();
let (tx, rx) = channel();
let pool = ThreadPool::new(thread_count);
loop
{
while pool.active_count() < thread_count
{
let mut thread_best_score = best_score;
let mut thread_best_submission = best_submission.clone();
let thread_paths = paths.clone();
let thread_street_names = street_names.clone();
let thread_streets = streets.clone();
let tx = tx.clone();
let (tx_thread, rx_thread) = channel();
thread_channels.push(tx_thread);
pool.execute(move ||
{
let rng = rand::thread_rng();
loop
{
let thread_submission = mutate_submission(thread_best_submission.clone(), rng, duration_total, current_mutation_rate);
let thread_score = calculate_submission_score(&thread_submission, &thread_paths, &thread_streets, duration_total, car_bonus, &thread_street_names).unwrap();
if thread_score > thread_best_score
{
// println!("New Best Thread Score: {}", thread_score.to_formatted_string(&Locale::en));
thread_best_score = thread_score;
thread_best_submission = thread_submission.clone();
tx.send((thread_best_score, thread_best_submission.clone())).expect("Thread tx channel failed.");
}
if let Ok(_) = rx_thread.try_recv()
{
// println!("Thread received kill notice.");
break;
}
}
});
}
// Delay before checking for messages to avoid non-stop checking on this thread.
sleep(TimeDuration::from_millis(thread_delay as u64));
// Check new best scores were received from the threads.
let mut new_best_score_found = false;
loop
{
if let Ok((trial_score, trial_submission)) = rx.try_recv()
{
if trial_score > best_score
{
best_score = trial_score;
best_submission = trial_submission;
new_best_score_found = true;
}
}
else
{
break;
}
}
// If a new best score was found, record it and restart the threads.
if new_best_score_found
{
println!("New Best Score: {}", best_score.to_formatted_string(&Locale::en));
// Restart both timers.
mutation_timer = Instant::now();
solution_timer = Instant::now();
// Write the output file.
let filename = filename.replace(".txt", &format!(".{}.{}.jm.out", best_score, env!("CARGO_PKG_NAME"))).replace("/input/", "/output/");
write_output(&filename, &best_submission, &street_names)?;
// Restart all threads.
thread_channels.drain(..).for_each(|x|{let _ = x.send(());});
}
// If no scores were generated for x seconds, decrease mutation rate.
if current_mutation_rate > 1 && mutation_timer.elapsed().as_secs() > mutation_decrease_secs as u64
{
current_mutation_rate -= 1;
mutation_timer = Instant::now();
println!("Decreasing mutation rate to: {}.", current_mutation_rate);
// Restart all threads.
thread_channels.drain(..).for_each(|x|{let _ = x.send(());});
}
// If no scores were generated for x seconds, restart.
if solution_timer.elapsed().as_secs() > stuck_limit_secs as u64
{
println!("Stuck time limit exceeded. Restarting.");
best_score = baseline_score;
best_submission = baseline_submission.clone();
current_mutation_rate = mutation_rate;
mutation_timer = Instant::now();
solution_timer = Instant::now();
// Restart all threads.
thread_channels.drain(..).for_each(|x|{let _ = x.send(());});
}
}
#[allow(unreachable_code)]
Ok(())
}
```