Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion vrp-pragmatic/src/format/problem/goal_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,15 @@ fn get_objective_feature_layer(
}

fn get_hierarchical_areas_feature(blocks: &ProblemBlocks, levels: usize) -> GenericResult<Feature> {
let locations = (0..blocks.transport.size()).collect::<Vec<_>>();
let profile =
blocks.fleet.profiles.first().cloned().ok_or_else(|| GenericError::from("should have at least one profile"))?;
let locations: Vec<_> = (0..blocks.transport.size())
.filter(|&loc| {
// Exclude locations with zero distance to all others (no real coordinates)
(0..blocks.transport.size())
.any(|other| other != loc && blocks.transport.distance_approx(&profile, loc, other) > 0.0)
})
.collect();

let clusters = create_hierarchical_kmedoids(&locations, levels, {
let transport = blocks.transport.clone();
Expand All @@ -237,6 +243,13 @@ fn get_hierarchical_areas_feature(blocks: &ProblemBlocks, levels: usize) -> Gene
.set_activity_cost(blocks.activity.clone())
.build_minimize_distance()?;

// With too few distinct locations, clustering cannot produce a usable hierarchy
// (the first tier must have exactly two clusters), so fall back to the plain
// distance minimization the feature is based on.
if clusters.first().is_none_or(|clusters| clusters.len() != 2) {
return Ok(cost_feature);
}

create_hierarchical_areas_feature(cost_feature, &clusters, {
let transport = blocks.transport.clone();
move |profile, from, to| transport.distance_approx(profile, from, to)
Expand Down
1 change: 1 addition & 0 deletions vrp-pragmatic/tests/features/tour_shape/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mod basic_tour_compactness;
mod small_hierarchical_areas;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::format::problem::Objective::*;
use crate::format::problem::*;
use crate::helpers::*;

#[test]
fn can_handle_hierarchical_areas_with_too_few_locations() {
let problem = Problem {
plan: Plan {
jobs: vec![create_delivery_job("job1", (1., 0.)), create_delivery_job("job2", (2., 0.))],
..create_empty_plan()
},
fleet: create_default_fleet(),
objectives: Some(vec![
MinimizeUnassigned { breaks: None },
HierarchicalAreas { levels: 3 },
MinimizeCost,
]),
..create_empty_problem()
};
let matrix = create_matrix_from_problem(&problem);

let solution = solve_with_metaheuristic(problem, Some(vec![matrix]));

assert!(solution.unassigned.is_none());
assert_eq!(solution.tours.len(), 1);
}
Loading