From 442bc5904d78bc94de68e86b291610c05c5bb286 Mon Sep 17 00:00:00 2001 From: reinterpretcat Date: Wed, 28 Aug 2024 23:40:28 +0200 Subject: [PATCH] Add benches for goal evaluation of CVRPTW --- Cargo.toml | 3 + .../scientific/solomon/C101.100.partial.txt | 11 ++ examples/json-pragmatic/Cargo.toml | 2 +- vrp-scientific/Cargo.toml | 7 + vrp-scientific/benches/solomon_goal.rs | 127 ++++++++++++++++++ 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 examples/data/scientific/solomon/C101.100.partial.txt create mode 100644 vrp-scientific/benches/solomon_goal.rs diff --git a/Cargo.toml b/Cargo.toml index 8918087a9..03b162a95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,9 @@ rayon = "1.10.0" rustc-hash = "2.0.0" paste = "1.0.15" +# dev dependencies +criterion = "0.5.1" + [profile.release] lto = "fat" # enables "fat" LTO, for faster release builds codegen-units = 1 # makes sure that all code is compiled together, for LTO diff --git a/examples/data/scientific/solomon/C101.100.partial.txt b/examples/data/scientific/solomon/C101.100.partial.txt new file mode 100644 index 000000000..3d42d31d6 --- /dev/null +++ b/examples/data/scientific/solomon/C101.100.partial.txt @@ -0,0 +1,11 @@ +Route 1 : 81 78 76 80 +Route 2 : 57 55 54 53 56 58 60 59 +Route 3 : 98 96 95 94 92 93 97 100 99 +Route 4 : 32 33 31 35 37 38 39 36 34 +Route 5 : 13 17 18 19 15 16 14 12 +Route 6 : 90 87 86 83 89 91 +Route 7 : 43 42 41 40 48 51 50 52 49 47 +Route 8 : 67 65 63 62 74 72 61 64 68 66 69 +Route 9 : 5 3 7 8 10 11 9 6 4 2 1 75 +Route 10 : 20 24 30 28 26 23 22 21 +Cost 1000 diff --git a/examples/json-pragmatic/Cargo.toml b/examples/json-pragmatic/Cargo.toml index 36e4cdc4f..0597e0fd8 100644 --- a/examples/json-pragmatic/Cargo.toml +++ b/examples/json-pragmatic/Cargo.toml @@ -16,7 +16,7 @@ edition.workspace = true vrp-pragmatic.workspace = true [dev-dependencies] -criterion = "0.5.1" +criterion.workspace = true [[bench]] name = "general_benchmark" diff --git a/vrp-scientific/Cargo.toml b/vrp-scientific/Cargo.toml index 2fc841b01..d48b7c385 100644 --- a/vrp-scientific/Cargo.toml +++ b/vrp-scientific/Cargo.toml @@ -14,3 +14,10 @@ edition.workspace = true [dependencies] vrp-core.workspace = true paste.workspace = true + +[dev-dependencies] +criterion.workspace = true + +[[bench]] +name = "solomon_goal" +harness = false diff --git a/vrp-scientific/benches/solomon_goal.rs b/vrp-scientific/benches/solomon_goal.rs new file mode 100644 index 000000000..f3be12597 --- /dev/null +++ b/vrp-scientific/benches/solomon_goal.rs @@ -0,0 +1,127 @@ +//! This benchmark evaluates the goal pipeline for the Solomon problem variant (CVRPTW). + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use std::fs::File; +use std::io::BufReader; +use std::sync::Arc; +use vrp_core::construction::heuristics::ActivityContext; +use vrp_core::models::common::{Schedule, Timestamp}; +use vrp_core::models::problem::{JobIdDimension, Single}; +use vrp_core::models::solution::{Activity, Place}; +use vrp_core::prelude::*; +use vrp_scientific::common::read_init_solution; +use vrp_scientific::solomon::SolomonProblem; + +pub fn get_bench_resource(resource_path: &str) -> std::io::Result { + let mut path = std::env::current_dir()?; + path.push("benches"); + path.push(resource_path); + + File::open(path) +} + +/// Returns a partial insertion context: some jobs are excluded from the used initial solution. +fn get_partial_insertion_context() -> InsertionContext { + let environment = Arc::new(Environment::default()); + let problem = Arc::new( + BufReader::new(get_bench_resource("../../examples/data/scientific/solomon/C101.100.txt").unwrap()) + .read_solomon(false) + .unwrap(), + ); + assert_eq!(problem.jobs.size(), 100); + + let solution = read_init_solution( + BufReader::new(get_bench_resource("../../examples/data/scientific/solomon/C101.100.partial.txt").unwrap()), + problem.clone(), + environment.random.clone(), + ) + .expect("cannot read initial solution"); + + assert!(!solution.routes.is_empty()); + assert!(!solution.unassigned.is_empty()); + + InsertionContext::new_from_solution(problem, (solution, None), environment) +} + +fn get_insertion_entities(solution_ctx: &SolutionContext) -> (&RouteContext, &Arc) { + // get route for insertion + let route_ctx = solution_ctx + .routes + .iter() + .find(|route_ctx| { + route_ctx + .route() + .tour + .get(1) + .and_then(|a| a.retrieve_job()) + .and_then(|job| job.dimens().get_job_id().cloned()) + .map_or(false, |job_id| job_id == "67") + }) + .expect("cannot find expected route in the solution"); + assert_eq!(route_ctx.route().tour.job_count(), 11, "unexpected job count in the route"); + + // get job for insertion + let job = solution_ctx + .unassigned + .iter() + .find(|(job, _)| job.dimens().get_job_id().map_or(false, |id| id == "45")) + .and_then(|(job, _)| job.as_single()) + .expect("cannot find single job in the unassigned jobs"); + + (route_ctx, job) +} + +fn bench_evaluate_route(c: &mut Criterion) { + c.bench_function("CVRPTW: run Goal::evaluate for route on C101.100", |b| { + let insertion_ctx = get_partial_insertion_context(); + let solution_ctx = &insertion_ctx.solution; + let (route_ctx, job) = get_insertion_entities(solution_ctx); + + b.iter(|| { + insertion_ctx.problem.goal.evaluate(&MoveContext::Route { + solution_ctx, + route_ctx, + job: &Job::Single(job.clone()), + }); + black_box(()) + }) + }); +} + +fn bench_evaluate_activity(c: &mut Criterion) { + c.bench_function("CVRPTW: run Goal::evaluate for activity on C101.100", |b| { + let insertion_ctx = get_partial_insertion_context(); + let solution_ctx = &insertion_ctx.solution; + let (route_ctx, job) = get_insertion_entities(solution_ctx); + + // get activities for insertion context + let prev = route_ctx.route().tour.get(7).unwrap(); + let target = Activity { + place: Place { + idx: 7, + location: job.places[0].location.unwrap(), + duration: job.places[0].duration.clone(), + time: job.places[0].times[0].to_time_window(Timestamp::default()), + }, + schedule: Schedule { arrival: 0.0, departure: 0.0 }, + job: Some(job.clone()), + commute: None, + }; + let next = route_ctx.route().tour.get(8); + + b.iter(|| { + insertion_ctx.problem.goal.evaluate(&MoveContext::Activity { + route_ctx, + activity_ctx: &ActivityContext { index: 0, prev, target: &target, next }, + }); + }) + }); +} + +criterion_group! { + name = benches; + config = Criterion::default().sample_size(64); + targets = bench_evaluate_route, + bench_evaluate_activity, +} +criterion_main!(benches);