---
# System prepended metadata

title: Jordan Code v2e

---

# Jordan Code v2e

```
Basic genetic algorithm to mutate results with pooled multi-threading.
- Alternative odds on mutation type. (Swap vs. duration.)
- Upside random bias on duration mutation.
- Displays time taken on every new best score.
- When stuck, mutates the best score then restarts.
- Mutation rate changed to affect duration steps.
- Mutation modifier added to bias towards the mutation action that is most beneficial.

Usage
v2e <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 step of a duration to increase or decrease in a single mutation. (duration [+-]= mutation_rate) 
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 500 10 5 300
```

```
[package]
name = "v2e"
version = "0.1.0"
authors = ["Jordan Mack <jordan.mack@brilliantnotion.com>"]
edition = "2018"

[[bin]]
name = "v2e"
path = "src/v2e.jm.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
num-format = "0.4.0"
rand = "0.7.3"
simple-error = "0.2.1"
threadpool = "1.7.1"
```

```
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 first_position = intersection_schedule.schedule.iter().position(|x|x.duration > 0);

		let intersection = Intersection
		{
			intersection_id: intersection_id,
			car_queue: intersection_schedule.schedule.iter().map(|x|(x.street_id, VecDeque::new())).collect(),
			current_schedule_index: if first_position.is_some() { first_position.unwrap() } else { 0 },
			duration_remaining: if first_position.is_some() { intersection_schedule.schedule[first_position.unwrap()].duration } else { 0 },
			schedule: intersection_schedule.schedule.clone(),
			available: if first_position.is_some() { true } else { false }
		};
		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);
	}

	// println!("Simulating Time Index: {}", 0);
	// println!("Car 0 {} {}", &street_names[cars[0].path[cars[0].current_path_index] as usize], cars[0].duration_remaining);
	// println!("Car 1 {} {}", &street_names[cars[1].path[cars[1].current_path_index] as usize], cars[1].duration_remaining);
	// println!("{}", score);

	// Run simulation time indexes.
	for t in 1..duration_total+1
	{
		// println!("Simulating Time Index: {}", t);
		// println!("Car 0 {} {} {}", &_street_names[cars[0].path[cars[0].current_path_index] as usize], cars[0].duration_remaining, cars[0].active);
		// println!("Car 1 {} {} {}", &_street_names[cars[1].path[cars[1].current_path_index] as usize], cars[1].duration_remaining, cars[1].active);

		// 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;
					}
				}
			}

			// if car_id == 703
			// {
			// 	dbg!(car.active, car.current_path_index, car.duration_remaining);
			// }
		}

		// 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;
		}

		// println!("Car 0 {} {} {}", &_street_names[cars[0].path[cars[0].current_path_index] as usize], cars[0].duration_remaining, cars[0].active);
		// println!("Car 1 {} {} {}", &_street_names[cars[1].path[cars[1].current_path_index] as usize], cars[1].duration_remaining, cars[1].active);
		// println!("{}", score);
		// std::thread::sleep(std::time::Duration::from_secs(1));
	}

	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, mutation_modifier: &[usize; 2]) -> (Submission, usize)
{
	// Select a random action.
	let mut action = rng.gen_range(0, mutation_modifier[0] + mutation_modifier[1] + 1);
	if action <= mutation_modifier[0]
	{
		action = 0;
	}
	else
	{
		action = 1;
	}

	// Action 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 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 -= mutation_rate as u32;
			}
			else
			{
				street_schedule.duration += mutation_rate as u32;
			}
	
			street_schedule.duration = max(street_schedule.duration, 0); // Ensure minimum.
			street_schedule.duration = min(street_schedule.duration, duration_total); // Ensure maximum.
		}
	}

	(submission, action)
}

// 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 mutation_modifier = [1, 1];

	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 {:#.2?}.)", 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 current_mutation_modifier = mutation_modifier;
	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 mut thread_mutation_modifier = current_mutation_modifier.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, thread_mutation_action) = mutate_submission(thread_best_submission.clone(), rng, duration_total, current_mutation_rate, &thread_mutation_modifier);
					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();
						thread_mutation_modifier[thread_mutation_action] += 1;

						tx.send((thread_best_score, thread_best_submission.clone(), thread_mutation_modifier.clone())).expect("Thread tx channel failed.");
					}
					// else
					// {
					// 	if thread_mutation_modifier[thread_mutation_action] > 1
					// 	{
					// 		thread_mutation_modifier[thread_mutation_action] -= 1;
					// 	}
					// 	else
					// 	{
					// 		thread_mutation_action = if thread_mutation_action == 0 { 1 } else { 0 };
					// 		thread_mutation_modifier[thread_mutation_action] += 1;
					// 	}
					// }

					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, trial_mutation_modifier)) = rx.try_recv()
			{
				if trial_score > best_score
				{
					best_score = trial_score;
					best_submission = trial_submission;
					current_mutation_modifier = trial_mutation_modifier;
					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: {} (Found in {:#.2?}.) (MR: {}; MM: {:?})", best_score.to_formatted_string(&Locale::en), solution_timer.elapsed(), current_mutation_rate, current_mutation_modifier);

			// 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 found 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.");

			let (mutated_best_submission, _) = mutate_submission(best_submission.clone(), rand::thread_rng(), duration_total, mutation_rate, &mutation_modifier);
			best_submission = mutated_best_submission;
			best_score = calculate_submission_score(&best_submission, &paths, &streets, duration_total, car_bonus, &street_names).unwrap();
			current_mutation_rate = mutation_rate;
			current_mutation_modifier = [1, 1];
			mutation_timer = Instant::now();
			solution_timer = Instant::now();

			// Restart all threads.
			thread_channels.drain(..).for_each(|x|{let _ = x.send(());});
		}
	}

	#[allow(unreachable_code)]
	Ok(())
}
```