vrp-cli 1.19.1

A command line interface for VRP solver
Documentation
#[cfg(test)]
#[path = "../../tests/unit/commands/generate_test.rs"]
mod generate_test;

use super::*;
use std::io::BufReader;
use vrp_cli::extensions::generate::generate_problem;
use vrp_pragmatic::format::problem::{serialize_problem, Problem};
use vrp_pragmatic::format::{CoordIndex, FormatError};
use vrp_pragmatic::validation::ValidationContext;

pub const FORMAT_ARG_NAME: &str = "FORMAT";
pub const PROTOTYPES_ARG_NAME: &str = "prototypes";
pub const OUT_RESULT_ARG_NAME: &str = "out-result";
pub const JOBS_SIZE_ARG_NAME: &str = "jobs-size";
pub const VEHICLES_SIZE_ARG_NAME: &str = "vehicles-size";
pub const LOCATIONS_ARG_NAME: &str = "locations";
pub const AREA_SIZE_ARG_NAME: &str = "area-size";

pub fn get_generate_app() -> Command {
    Command::new("generate")
        .about("Provides the way to generate meaningful problems for testing")
        .arg(Arg::new(FORMAT_ARG_NAME).help("Specifies input type").required(true).value_parser(["pragmatic"]).index(1))
        .arg(
            Arg::new(PROTOTYPES_ARG_NAME)
                .help("Sets input files which contains a VRP definition prototype")
                .short('p')
                .long(PROTOTYPES_ARG_NAME)
                .required(true)
                .num_args(1..),
        )
        .arg(
            Arg::new(OUT_RESULT_ARG_NAME)
                .help("Specifies path to the file for result output")
                .short('o')
                .long(OUT_RESULT_ARG_NAME)
                .required(false),
        )
        .arg(
            Arg::new(LOCATIONS_ARG_NAME)
                .help("Specifies path to the file with a list of job locations")
                .short('l')
                .long(LOCATIONS_ARG_NAME)
                .required(false),
        )
        .arg(
            Arg::new(JOBS_SIZE_ARG_NAME)
                .help("Amount of jobs in the plan of generated problem")
                .short('j')
                .long(JOBS_SIZE_ARG_NAME)
                .required(true),
        )
        .arg(
            Arg::new(VEHICLES_SIZE_ARG_NAME)
                .help("Amount of vehicle types in the fleet of generated problem")
                .short('v')
                .long(VEHICLES_SIZE_ARG_NAME)
                .required(true),
        )
        .arg(
            Arg::new(AREA_SIZE_ARG_NAME)
                .help("Half side size of job distribution bounding box. Center is calculated using prototype locations")
                .short('a')
                .long(AREA_SIZE_ARG_NAME)
                .required(false),
        )
}

pub fn run_generate(matches: &ArgMatches) -> Result<(), String> {
    match generate_problem_from_args(matches) {
        Ok((problem, input_format)) => {
            let out_result = matches.get_one::<String>(OUT_RESULT_ARG_NAME).map(|path| create_file(path, "out result"));
            let out_buffer = create_write_buffer(out_result);

            match input_format.as_str() {
                "pragmatic" => serialize_problem(out_buffer, &problem)
                    .map_err(|err| format!("cannot serialize as pragmatic problem: '{}'", err)),
                _ => Err(format!("unknown output format: '{}'", input_format)),
            }
        }
        Err(err) => Err(format!("cannot generate problem: '{}'", err)),
    }
}

fn generate_problem_from_args(matches: &ArgMatches) -> Result<(Problem, String), String> {
    let input_format = matches.get_one::<String>(FORMAT_ARG_NAME).unwrap();

    let input_files = matches
        .get_many::<String>(PROTOTYPES_ARG_NAME)
        .map(|paths| paths.map(|path| BufReader::new(open_file(path, "input"))).collect::<Vec<_>>());

    let locations_file =
        matches.get_one::<String>(LOCATIONS_ARG_NAME).map(|path| BufReader::new(open_file(path, "locations")));

    let jobs_size = parse_int_value::<usize>(matches, JOBS_SIZE_ARG_NAME, "jobs size")?.unwrap();
    let vehicles_size = parse_int_value::<usize>(matches, VEHICLES_SIZE_ARG_NAME, "vehicles size")?.unwrap();
    let area_size = parse_float_value::<f64>(matches, AREA_SIZE_ARG_NAME, "area size")?;

    generate_problem(input_format, input_files, locations_file, jobs_size, vehicles_size, area_size).and_then(
        |problem| {
            let coord_index = CoordIndex::new(&problem);
            ValidationContext::new(&problem, None, &coord_index)
                .validate()
                .map_err(|errors| {
                    format!(
                        "generated problem has some validation errors:\n{}",
                        FormatError::format_many(errors.as_slice(), "\n")
                    )
                })
                .map(|_| (problem, input_format.to_owned()))
        },
    )
}