vrp-cli 1.19.1

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

use super::*;
use vrp_cli::extensions::analyze::get_clusters;

const FORMAT_ARG_NAME: &str = "FORMAT";
const PROBLEM_ARG_NAME: &str = "PROBLEM";
const MATRIX_ARG_NAME: &str = "matrix";
const MIN_POINTS_ARG_NAME: &str = "min-points";
const EPSILON_ARG_NAME: &str = "epsilon";
const OUT_RESULT_ARG_NAME: &str = "out-result";

pub fn get_analyze_app() -> Command {
    Command::new("analyze").about("Provides helper functionality to analyze problem or solution").subcommand(
        Command::new("clusters")
            .about("Analyzes job clusters")
            .arg(
                Arg::new(FORMAT_ARG_NAME)
                    .help("Specifies input type")
                    .required(true)
                    .value_parser(["pragmatic"])
                    .index(1),
            )
            .arg(Arg::new(PROBLEM_ARG_NAME).help("Sets the problem file to use").required(true).index(2))
            .arg(
                Arg::new(MIN_POINTS_ARG_NAME)
                    .help("Minimum cluster size")
                    .short('c')
                    .default_value("3")
                    .long(MIN_POINTS_ARG_NAME)
                    .required(false),
            )
            .arg(
                Arg::new(EPSILON_ARG_NAME)
                    .help("Epsilon parameter in DBSCAN")
                    .short('e')
                    .long(EPSILON_ARG_NAME)
                    .required(false),
            )
            .arg(
                Arg::new(MATRIX_ARG_NAME)
                    .help("Specifies path to file with routing matrix")
                    .short('m')
                    .long(MATRIX_ARG_NAME)
                    .num_args(1..)
                    .required(false),
            )
            .arg(
                Arg::new(OUT_RESULT_ARG_NAME)
                    .help("Specifies path to the file for result output")
                    .short('o')
                    .long(OUT_RESULT_ARG_NAME)
                    .required(true),
            ),
    )
}

pub fn run_analyze(
    matches: &ArgMatches,
    out_writer_func: fn(Option<File>) -> BufWriter<Box<dyn Write>>,
) -> Result<(), String> {
    match matches.subcommand() {
        Some(("clusters", clusters_matches)) => {
            let problem_path = clusters_matches.get_one::<String>(PROBLEM_ARG_NAME).unwrap();
            let problem_format = clusters_matches.get_one::<String>(FORMAT_ARG_NAME).unwrap();

            if problem_format != "pragmatic" {
                return Err(format!("unknown problem format: '{}'", problem_format));
            }

            let problem_reader = BufReader::new(open_file(problem_path, "problem"));

            let matrices_readers = clusters_matches
                .get_many::<String>(MATRIX_ARG_NAME)
                .map(|paths| paths.map(|path| BufReader::new(open_file(path, "routing matrix"))).collect());

            let min_points = parse_int_value::<usize>(clusters_matches, MIN_POINTS_ARG_NAME, "min points")?;
            let epsilon = parse_float_value::<f64>(clusters_matches, EPSILON_ARG_NAME, "epsilon")?;

            let clusters = get_clusters(problem_reader, matrices_readers, min_points, epsilon)
                .map_err(|err| format!("cannot get clusters: '{}'", err))?;

            let out_geojson =
                clusters_matches.get_one::<String>(OUT_RESULT_ARG_NAME).map(|path| create_file(path, "out geojson"));
            let mut geo_writer = out_writer_func(out_geojson);

            geo_writer.write_all(clusters.as_bytes()).map_err(|err| format!("cannot write result: '{}'", err))
        }
        _ => Err("no argument with analyze subcommand was used. Use -h to print help information".to_string()),
    }
}