use itertools::Itertools;
use rand_chacha::ChaChaRng;
use serde::{Deserialize, Serialize};
use structopt::StructOpt;
use std::{
error::Error,
fmt::Write as fmt_write,
fs::File,
io::{BufRead, BufReader, BufWriter, Write},
path::{Path, PathBuf},
};
use super::{
clique_tree::{CliqueTree, InputParameters},
codomain::{read_codomain, generate_write_return},
io::{get_clique_tree_from_codomain_file, get_clique_trees_paths_from_codomain_folder,
get_output_folder_path_from_configuration_file},
configuration::{get_rng}
};
use super::configuration::ConfigurationParameters;
#[derive(StructOpt, Debug)]
#[structopt(
name = "Problem Generator",
about = "Generate TD Mk Landscape problem using a codomain file"
)]
pub struct ProblemOpt {
#[structopt(subcommand)]
pub problem_command: ProblemCommand,
#[structopt(short = "s", long = "seed")]
pub seed: Option<u64>,
}
#[derive(StructOpt, Debug)]
pub enum ProblemCommand {
#[structopt(name = "codomain_folder")]
CodomainFolder {
#[structopt(parse(from_os_str))]
folder_paths: Vec<PathBuf>,
#[structopt(short = "g")]
generated: bool,
},
#[structopt(name = "configuration_folder")]
ConfigurationFolder {
#[structopt(parse(from_os_str))]
folder_paths: Vec<PathBuf>,
#[structopt(default_value = "1", short = "n")]
number_of_problems_to_generate: u32,
},
#[structopt(name = "codomain_file")]
CodomainFile {
#[structopt(parse(from_os_str))]
input_codomain_file_path: PathBuf,
#[structopt(parse(from_os_str))]
output_problem_file_path: PathBuf,
#[structopt(short = "g")]
generated: bool,
},
#[structopt(name = "configuration_file")]
ConfigurationFile {
#[structopt(parse(from_os_str))]
input_configuration_file_path: PathBuf,
#[structopt(parse(from_os_str))]
output_codomain_folder_path: PathBuf,
#[structopt(parse(from_os_str))]
output_problem_folder_path: PathBuf,
#[structopt(default_value = "1", short = "n")]
number_of_problems_to_generate: u32,
},
}
pub fn run_opt(problem_opt: ProblemOpt) -> Result<(), Box<dyn Error>> {
let mut rng = get_rng(problem_opt.seed);
match problem_opt.problem_command {
ProblemCommand::CodomainFolder {
folder_paths,
generated,
} => {
for folder_path in folder_paths {
generate_problems_from_codomain_folder(&folder_path, generated, &mut rng)?;
}
Ok(())
}
ProblemCommand::ConfigurationFolder {
folder_paths,
number_of_problems_to_generate,
} => {
for folder_path in folder_paths {
generate_codomain_and_problem_from_folder(
&folder_path,
number_of_problems_to_generate,
&mut rng,
)?;
}
Ok(())
}
ProblemCommand::CodomainFile {
input_codomain_file_path,
output_problem_file_path,
generated,
} => {
generate_problem_from_codomain_file(
&input_codomain_file_path,
&output_problem_file_path,
generated,
&mut rng
)
},
ProblemCommand::ConfigurationFile {
input_configuration_file_path,
output_codomain_folder_path,
output_problem_folder_path,
number_of_problems_to_generate,
} => {
generate_codomain_and_problem(
&input_configuration_file_path,
Some(&output_codomain_folder_path),
Some(&output_problem_folder_path),
number_of_problems_to_generate,
&mut rng
)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Problem {
pub input_parameters: InputParameters,
pub glob_optima_score: f64,
pub glob_optima_strings: Vec<Vec<u32>>,
pub cliques: Vec<Vec<u32>>,
}
impl Problem {
fn new(clique_tree: &CliqueTree) -> Problem {
Problem {
input_parameters: clique_tree.input_parameters.clone(),
cliques: clique_tree.cliques.clone(),
glob_optima_score: clique_tree.glob_optima_score,
glob_optima_strings: clique_tree.glob_optima_strings.clone(),
}
}
}
pub fn generate_problems_from_codomain_folder(
parent_folder_path: &Path,
generated: bool,
rng: &mut ChaChaRng
) -> Result<(), Box<dyn Error>> {
let mut codomain_folder_path = PathBuf::from(parent_folder_path);
codomain_folder_path.push("codomain_files");
let mut problems_folder_path = PathBuf::from(parent_folder_path);
problems_folder_path.push("problems");
let folder_entries: Vec<PathBuf> = codomain_folder_path
.read_dir()?
.map(|folder| folder.unwrap().path())
.sorted()
.collect();
for folder in folder_entries {
let mut output_folder_path = problems_folder_path.clone();
output_folder_path.push(
folder
.file_name()
.ok_or("could not get file name of folder")?,
);
std::fs::create_dir_all(&output_folder_path)?;
let clique_trees_paths = get_clique_trees_paths_from_codomain_folder(&folder, generated, rng)?;
for (clique_tree, path_buf) in clique_trees_paths {
let mut output_path = output_folder_path.clone();
output_path.push(
path_buf
.file_name()
.ok_or("could not get filename of codomain file")?,
);
write_problem_to_file(&clique_tree, &output_path)?;
}
}
Ok(())
}
pub fn generate_codomain_and_problem_from_folder(
input_folder_path: &Path,
number_of_problems_to_generate: u32,
rng: &mut ChaChaRng
) -> Result<(), Box<dyn Error>> {
let mut problem_generation_path = PathBuf::from(input_folder_path);
problem_generation_path.push("problem_generation");
let file_entries: Vec<PathBuf> = problem_generation_path
.read_dir()?
.map(|file| file.unwrap().path())
.sorted()
.collect();
for file in file_entries {
generate_codomain_and_problem(&file, None, None, number_of_problems_to_generate, rng)?;
}
Ok(())
}
pub fn generate_codomain_and_problem(
input_configuration_file_path: &Path,
output_codomain_folder_path: Option<&Path>,
output_problem_folder_path: Option<&Path>,
number_of_problems_to_generate: u32,
rng: &mut ChaChaRng
) -> Result<(), Box<dyn Error>> {
let configuration_parameters =
ConfigurationParameters::from_file(input_configuration_file_path)?;
let codomain_function = configuration_parameters.codomain_function.clone();
let output_problem_folder_path_buf = match output_problem_folder_path {
Some(folder) => PathBuf::from(folder),
None => get_output_folder_path_from_configuration_file(
input_configuration_file_path,
"problems",
)?,
};
let output_codomain_folder_path_buf = match output_codomain_folder_path {
Some(folder) => PathBuf::from(folder),
None => get_output_folder_path_from_configuration_file(
input_configuration_file_path,
"codomain_files",
)?,
};
for input_parameters in configuration_parameters {
for num in 0..number_of_problems_to_generate {
let mut output_problem_file_path = output_problem_folder_path_buf.clone();
let mut output_codomain_file_path = output_codomain_folder_path_buf.clone();
let output_file_name = format!(
"{}_{}_{}_{}_{}_{}.txt",
codomain_function.to_io_string(),
input_parameters.m,
input_parameters.k,
input_parameters.o,
input_parameters.b,
num
);
output_problem_file_path.push(output_file_name.clone());
output_codomain_file_path.push(output_file_name);
let codomain =
generate_write_return(&input_parameters, &codomain_function, &output_codomain_file_path, rng)?;
let clique_tree = CliqueTree::new(
input_parameters.clone(),
codomain_function.clone(),
codomain,
rng
);
write_problem_to_file(&clique_tree, &output_problem_file_path)?;
}
}
Ok(())
}
pub fn generate_problem_from_codomain_file(
codomain_file_path: &Path,
output_problem_file_path: &Path,
generated: bool,
rng: &mut ChaChaRng
) -> Result<(), Box<dyn Error>> {
let clique_tree = get_clique_tree_from_codomain_file(codomain_file_path, generated, rng)?;
write_problem_to_file(&clique_tree, output_problem_file_path)
}
pub fn read_clique_tree_from_files(
problem_path: &Path,
codomain_path: &Path,
generated: bool,
) -> Result<CliqueTree, Box<dyn Error>> {
let problem = read_problem_from_file(problem_path)?;
let skip_lines = if generated { 2 } else { 1 };
let codomain = read_codomain(&problem.input_parameters, codomain_path, skip_lines)?;
Ok(CliqueTree::construct_from_problem_codomain(
problem, codomain,
))
}
pub fn read_clique_trees_paths_from_folders(
codomain_folder_path: &Path,
problem_folder_path: &Path,
generated: bool,
) -> Result<Vec<(CliqueTree, PathBuf)>, Box<dyn Error>> {
let codomain_file_entries: Vec<PathBuf> = codomain_folder_path
.read_dir()?
.map(|file| file.unwrap().path())
.sorted()
.collect();
let problem_file_entries: Vec<PathBuf> = problem_folder_path
.read_dir()?
.map(|file| file.unwrap().path())
.sorted()
.collect();
assert_eq!(codomain_file_entries.len(), problem_file_entries.len());
let mut result_vec = Vec::new();
for (codomain_file_entry, problem_file_entry) in codomain_file_entries
.into_iter()
.zip(problem_file_entries.into_iter())
{
result_vec.push((
read_clique_tree_from_files(&problem_file_entry, &codomain_file_entry, generated)?,
codomain_file_entry,
));
}
Ok(result_vec)
}
pub fn write_problem_to_file(
clique_tree: &CliqueTree,
output_problem_file_path: &Path,
) -> Result<(), Box<dyn Error>> {
let file = File::create(output_problem_file_path)?;
let mut buf_writer = BufWriter::new(file);
let mut write_buffer = String::new();
writeln!(
write_buffer,
"{} {} {} {}",
clique_tree.input_parameters.m,
clique_tree.input_parameters.k,
clique_tree.input_parameters.o,
clique_tree.input_parameters.b
)?;
buf_writer.write_all(write_buffer.as_bytes())?;
write_buffer.clear();
writeln!(write_buffer, "{}", clique_tree.glob_optima_score)?;
buf_writer.write_all(write_buffer.as_bytes())?;
write_buffer.clear();
writeln!(write_buffer, "{}", clique_tree.glob_optima_strings.len())?;
buf_writer.write_all(write_buffer.as_bytes())?;
write_buffer.clear();
for sol in &clique_tree.glob_optima_strings {
for bit in sol {
write!(write_buffer, "{}", bit)?;
}
writeln!(write_buffer)?;
}
buf_writer.write_all(write_buffer.as_bytes())?;
write_buffer.clear();
for clique in &clique_tree.cliques {
for variable_index in clique {
write!(write_buffer, "{} ", variable_index)?;
}
write_buffer.pop().ok_or(
"could not remove trailing white space from clique indices while writing problem",
)?;
writeln!(write_buffer)?;
}
buf_writer.write_all(write_buffer.as_bytes())?;
write_buffer.clear();
buf_writer.flush()?;
Ok(())
}
pub fn write_problem_to_file_ser(
clique_tree: &CliqueTree,
file_path: &Path,
) -> Result<(), Box<dyn Error>> {
let file = File::create(file_path)?;
let mut buf_writer = BufWriter::new(file);
let mut write_buffer = String::new();
let problem = Problem::new(clique_tree);
let my_config = ron::ser::PrettyConfig::new().with_depth_limit(4);
let string =
ron::ser::to_string_pretty(&problem, my_config).map_err(|_| "Serialization error!")?;
write!(write_buffer, "{}", string)?;
buf_writer.write_all(write_buffer.as_bytes())?;
write_buffer.clear();
buf_writer.flush()?;
Ok(())
}
pub fn read_problem_from_file(file_path: &Path) -> Result<Problem, Box<dyn Error>> {
let file = File::open(file_path)?;
let reader = BufReader::new(file);
let mut content_iter = reader.lines();
let mut line = content_iter.next().ok_or("Empty problem file")??;
let parameters: Vec<&str> = line.split(' ').collect();
if parameters.len() != 4 {
return Err("not enough input parameters on first line of input file".into());
}
let m: u32 = parameters[0].parse()?;
let k: u32 = parameters[1].parse()?;
let o: u32 = parameters[2].parse()?;
let b: u32 = parameters[3].parse()?;
let input_parameters = InputParameters::new_from_primitives(m, k, o, b);
let problem_size = (m - 1) * (k - o) + k;
line = content_iter
.next()
.ok_or("No global optimum score in problem file")??;
let glob_optima_score: f64 = line.parse()?;
line = content_iter
.next()
.ok_or("No number_of_global_optima line in problem file")??;
let number_of_global_optima: usize = line.parse()?;
let mut glob_optima_strings = Vec::with_capacity(number_of_global_optima);
for _i in 0..number_of_global_optima {
line = content_iter
.next()
.ok_or("Not enough global optima strings in problem file")??;
let mut chars = line.chars();
let mut global_optimum: Vec<u32> = Vec::with_capacity(problem_size as usize);
for _j in 0..problem_size as usize {
let bit = chars
.next()
.ok_or("global optimum in problem file does not contain enough bits")?;
global_optimum.push(
bit.to_digit(10)
.ok_or("Could not convert global optimum bit from char to u32")?,
);
}
glob_optima_strings.push(global_optimum);
}
let mut cliques = Vec::with_capacity(m as usize);
for _i in 0..m as usize {
line = content_iter
.next()
.ok_or("Not enough cliques in problem file")??;
let variable_indices: Vec<&str> = line.split(' ').collect();
if variable_indices.len() != k as usize {
return Err("not enough variable indices in clique indices".into());
}
let mut clique_indices: Vec<u32> = Vec::with_capacity(k as usize);
for j in 0..k as usize {
clique_indices.push(variable_indices[j].parse()?);
}
cliques.push(clique_indices);
}
let problem = Problem {
input_parameters,
glob_optima_score,
glob_optima_strings,
cliques,
};
Ok(problem)
}
pub fn read_problem_from_file_de(file_path: &Path) -> Result<Problem, Box<dyn Error>> {
let f = File::open(file_path)?;
let mut reader = BufReader::new(f);
let problem = ron::de::from_reader(&mut reader)?;
Ok(problem)
}