#[cfg(test)]
#[path = "../../../../tests/unit/construction/clustering/vicinity/vicinity_test.rs"]
mod vicinity_test;
use crate::construction::heuristics::*;
use crate::models::common::Dimensions;
use crate::models::common::*;
use crate::models::problem::{Actor, Job};
use crate::models::Problem;
use rosomaxa::prelude::*;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::ops::ControlFlow;
use std::sync::Arc;
mod estimations;
use self::estimations::*;
use crate::models::solution::Commute;
use crate::prelude::ViolationCode;
custom_dimension!(ClusterInfo typeof Vec<ClusterInfo>);
pub type ClusterCandidate<'a> = (&'a Job, &'a HashSet<Job>);
type CheckInsertionFn = (dyn Fn(&Job) -> Result<(), ViolationCode> + Send + Sync);
#[derive(Clone)]
pub struct ClusterConfig {
pub profile: Profile,
pub threshold: ThresholdPolicy,
pub visiting: VisitPolicy,
pub serving: ServingPolicy,
pub filtering: FilterPolicy,
pub building: BuilderPolicy,
}
#[derive(Clone)]
pub struct ThresholdPolicy {
pub moving_duration: Duration,
pub moving_distance: Distance,
pub min_shared_time: Option<Duration>,
pub smallest_time_window: Option<Duration>,
pub max_jobs_per_cluster: Option<usize>,
}
#[derive(Clone)]
pub enum VisitPolicy {
Return,
ClosedContinuation,
OpenContinuation,
}
#[derive(Clone)]
pub struct FilterPolicy {
pub job_filter: Arc<dyn Fn(&Job) -> bool + Send + Sync>,
pub actor_filter: Arc<dyn Fn(&Actor) -> bool + Send + Sync>,
}
#[derive(Clone)]
pub enum ServingPolicy {
Original {
parking: Duration,
},
Multiplier {
multiplier: Float,
parking: Duration,
},
Fixed {
value: Duration,
parking: Duration,
},
}
pub type OrderingGlobalFn = Arc<dyn Fn(ClusterCandidate, ClusterCandidate) -> Ordering + Send + Sync>;
pub type OrderingLocalFn = Arc<dyn Fn(&ClusterInfo, &ClusterInfo) -> Ordering + Send + Sync>;
#[derive(Clone)]
pub struct BuilderPolicy {
pub ordering_global_fn: OrderingGlobalFn,
pub ordering_local_fn: OrderingLocalFn,
}
#[derive(Clone)]
pub struct ClusterInfo {
pub job: Job,
pub service_time: Duration,
pub place_idx: usize,
pub commute: Commute,
}
pub fn create_job_clusters(
problem: Arc<Problem>,
environment: Arc<Environment>,
config: &ClusterConfig,
) -> Vec<(Job, Vec<Job>)> {
let insertion_ctx = InsertionContext::new_empty(problem.clone(), environment);
let constraint = insertion_ctx.problem.goal.clone();
let check_insertion = get_check_insertion_fn(insertion_ctx, config.filtering.actor_filter.clone());
let transport = problem.transport.as_ref();
let jobs = problem
.jobs
.all()
.iter()
.filter(|job| (config.filtering.job_filter)(job))
.filter(|job| job.as_single().is_some())
.cloned()
.collect::<Vec<_>>();
let estimates = get_jobs_dissimilarities(jobs.as_slice(), transport, config);
get_clusters(&constraint, estimates, config, &check_insertion)
}
fn get_check_insertion_fn(
insertion_ctx: InsertionContext,
actor_filter: Arc<dyn Fn(&Actor) -> bool + Send + Sync>,
) -> impl Fn(&Job) -> Result<(), ViolationCode> {
move |job: &Job| -> Result<(), ViolationCode> {
let eval_ctx = EvaluationContext {
goal: &insertion_ctx.problem.goal,
job,
leg_selection: &LegSelection::Exhaustive,
result_selector: &BestResultSelector::default(),
};
insertion_ctx
.solution
.registry
.next_route()
.filter(|route_ctx| (actor_filter)(&route_ctx.route().actor))
.try_fold(Err(ViolationCode::unknown()), |_, route_ctx| {
let result = eval_job_insertion_in_route(
&insertion_ctx,
&eval_ctx,
route_ctx,
InsertionPosition::Any,
InsertionResult::make_failure(),
);
match result {
InsertionResult::Success(_) => ControlFlow::Break(Ok(())),
InsertionResult::Failure(failure) => ControlFlow::Continue(Err(failure.constraint)),
}
})
.unwrap_value()
}
}
impl ServingPolicy {
pub fn get_parking(&self) -> Duration {
match &self {
Self::Original { parking } => *parking,
Self::Multiplier { parking, .. } => *parking,
Self::Fixed { parking, .. } => *parking,
}
}
}