Skip to content

Commit

Permalink
Refactor clustering logic
Browse files Browse the repository at this point in the history
  • Loading branch information
reinterpretcat committed Oct 1, 2024
1 parent ea87a60 commit 751f7fd
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 28 deletions.
16 changes: 5 additions & 11 deletions vrp-cli/src/extensions/solve/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,9 +600,7 @@ fn create_operator(
) -> Result<(TargetSearchOperator, TargetHeuristicProbability), GenericError> {
Ok(match operator {
SearchOperatorType::RuinRecreate { probability, ruins, recreates } => {
let ruin = Arc::new(WeightedRuin::new(
ruins.iter().map(|g| create_ruin_group(&problem, environment.clone(), g)).collect(),
));
let ruin = Arc::new(WeightedRuin::new(ruins.iter().map(|g| create_ruin_group(&problem, g)).collect()));
let recreate = Arc::new(WeightedRecreate::new(
recreates.iter().map(|r| create_recreate_method(r, environment.clone())).collect(),
));
Expand Down Expand Up @@ -654,15 +652,11 @@ fn create_operator_probability(
}
}

fn create_ruin_group(problem: &Arc<Problem>, environment: Arc<Environment>, group: &RuinGroupConfig) -> RuinGroup {
(group.methods.iter().map(|r| create_ruin_method(problem, environment.clone(), r)).collect(), group.weight)
fn create_ruin_group(problem: &Arc<Problem>, group: &RuinGroupConfig) -> RuinGroup {
(group.methods.iter().map(|r| create_ruin_method(problem, r)).collect(), group.weight)
}

fn create_ruin_method(
problem: &Arc<Problem>,
environment: Arc<Environment>,
method: &RuinMethod,
) -> (Arc<dyn Ruin>, Float) {
fn create_ruin_method(problem: &Arc<Problem>, method: &RuinMethod) -> (Arc<dyn Ruin>, Float) {
let limits = RemovalLimits::new(problem.as_ref());
let get_limits = |min: usize, max: usize| RemovalLimits {
removed_activities_range: min..max,
Expand All @@ -687,7 +681,7 @@ fn create_ruin_method(
}
RuinMethod::Cluster { probability, min, max } => (
// TODO: remove unwrap
Arc::new(ClusterRemoval::new(problem.clone(), environment, get_limits(*min, *max)).unwrap()),
Arc::new(ClusterRemoval::new(problem.clone(), get_limits(*min, *max)).unwrap()),
*probability,
),
RuinMethod::CloseRoute { probability } => (Arc::new(CloseRouteRemoval::new(limits)), *probability),
Expand Down
13 changes: 12 additions & 1 deletion vrp-core/src/models/problem/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub struct Jobs {
jobs: Vec<Job>,
index: HashMap<usize, JobIndex>,
clusters: Vec<HashSet<Job>>,
clusters_reverse: HashMap<Job, usize>,
}

impl Jobs {
Expand All @@ -248,8 +249,13 @@ impl Jobs {
let index = create_index(fleet, jobs.clone(), transport, logger);
let clusters =
create_job_clusters(&jobs, fleet, Some(3), None, |profile, job| neighbors(&index, profile, job))?;
let clusters_reverse = clusters
.iter()
.enumerate()
.flat_map(|(idx, cluster)| cluster.iter().map(move |job| (job.clone(), idx)))
.collect();

Ok(Jobs { jobs, index, clusters })
Ok(Jobs { jobs, index, clusters, clusters_reverse })
}

/// Returns all jobs in the original order as a slice.
Expand All @@ -268,6 +274,11 @@ impl Jobs {
&self.clusters
}

/// Returns index of the cluster for the given job.
pub fn cluster_index(&self, job: &Job) -> Option<usize> {
self.clusters_reverse.get(job).copied()
}

/// Returns job rank as relative cost from any vehicle's start position.
/// Returns `None` if a job is not found in index.
pub fn rank(&self, profile: &Profile, job: &Job) -> Option<Cost> {
Expand Down
9 changes: 4 additions & 5 deletions vrp-core/src/solver/heuristic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ mod statik {
(
vec![
// TODO avoid unwrap
(Arc::new(ClusterRemoval::new_with_defaults(problem.clone(), environment.clone()).unwrap()), 1.),
(Arc::new(ClusterRemoval::new_with_defaults(problem.clone()).unwrap()), 1.),
(extra_random_job.clone(), 0.1),
],
5,
Expand Down Expand Up @@ -510,11 +510,10 @@ mod dynamic {
]
}

fn get_ruins(problem: Arc<Problem>, environment: Arc<Environment>) -> Vec<(Arc<dyn Ruin>, String, Float)> {
// NOTE: creating cluster removal is not fast on large problems
fn get_ruins(problem: Arc<Problem>) -> Vec<(Arc<dyn Ruin>, String, Float)> {
vec![(
// TODO avoid unwrap
Arc::new(ClusterRemoval::new_with_defaults(problem.clone(), environment).unwrap()),
Arc::new(ClusterRemoval::new_with_defaults(problem.clone()).unwrap()),
"cluster_removal".to_string(),
4.,
)]
Expand Down Expand Up @@ -586,7 +585,7 @@ mod dynamic {
let ruins = get_ruins_with_limits(normal_limits.clone(), "normal")
.into_iter()
.chain(get_ruins_with_limits(small_limits.clone(), "small"))
.chain(get_ruins(problem.clone(), environment.clone()))
.chain(get_ruins(problem.clone()))
.collect::<Vec<_>>();

let extra_random_job = Arc::new(RandomJobRemoval::new(small_limits));
Expand Down
10 changes: 4 additions & 6 deletions vrp-core/src/solver/search/ruin/cluster_removal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,22 @@ pub struct ClusterRemoval {

impl ClusterRemoval {
/// Creates a new instance of `ClusterRemoval`.
pub fn new(problem: Arc<Problem>, environment: Arc<Environment>, limits: RemovalLimits) -> GenericResult<Self> {
let mut clusters = problem
pub fn new(problem: Arc<Problem>, limits: RemovalLimits) -> GenericResult<Self> {
let clusters = problem
.jobs
.clusters()
.iter()
.cloned()
.map(|cluster| cluster.into_iter().collect::<Vec<_>>())
.collect::<Vec<_>>();

clusters.shuffle(&mut environment.random.get_rng());

Ok(Self { clusters, limits })
}

/// Creates a new instance of `ClusterRemoval` with default parameters.
pub fn new_with_defaults(problem: Arc<Problem>, environment: Arc<Environment>) -> GenericResult<Self> {
pub fn new_with_defaults(problem: Arc<Problem>) -> GenericResult<Self> {
let limits = RemovalLimits::new(problem.as_ref());
Self::new(problem, environment, limits)
Self::new(problem, limits)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use std::sync::Arc;

#[test]
fn can_create_ruin_cluster_with_default_params() {
let environment = Arc::new(Environment::default());
let (problem, _) = generate_matrix_routes(
8,
1,
Expand All @@ -19,7 +18,7 @@ fn can_create_ruin_cluster_with_default_params() {
|_| (vec![0.; 64], create_test_distances()),
);

let removal = ClusterRemoval::new_with_defaults(Arc::new(problem), environment).unwrap();
let removal = ClusterRemoval::new_with_defaults(Arc::new(problem)).unwrap();

assert!(!removal.clusters.is_empty());
}
Expand All @@ -29,7 +28,7 @@ fn can_handle_empty_problem() {
let problem = Arc::new(ProblemBuilder::default().build());
let limits = RemovalLimits::new(&problem);

let removal = ClusterRemoval::new(problem, Arc::new(Environment::default()), limits).unwrap();
let removal = ClusterRemoval::new(problem, limits).unwrap();

assert!(removal.clusters.is_empty());
}
Expand Down Expand Up @@ -57,9 +56,9 @@ fn can_ruin_jobs_impl(limit: usize, expected: usize) {
);
let problem = Arc::new(problem);
let environment = Arc::new(Environment::default());
let insertion_ctx = InsertionContext::new_from_solution(problem.clone(), (solution, None), environment.clone());
let insertion_ctx = InsertionContext::new_from_solution(problem.clone(), (solution, None), environment);

let insertion_ctx = ClusterRemoval::new(problem, environment, limits)
let insertion_ctx = ClusterRemoval::new(problem, limits)
.expect("cannot create clusters")
.run(&create_default_refinement_ctx(insertion_ctx.problem.clone()), insertion_ctx);

Expand Down

0 comments on commit 751f7fd

Please sign in to comment.