#[path = "./common/routing.rs"]
mod common;
use crate::common::define_routing_data;
use std::iter::once;
use std::sync::Arc;
use vrp_core::prelude::*;
custom_dimension!(JobPriority typeof bool);
custom_solution_state!(PriorityFitness typeof Cost);
struct PriorityObjective;
impl FeatureObjective for PriorityObjective {
fn fitness(&self, solution: &InsertionContext) -> Cost {
let solution_ctx = &solution.solution;
solution_ctx.state.get_priority_fitness().copied().unwrap_or_else(|| calculate_solution_fitness(solution_ctx))
}
fn estimate(&self, move_ctx: &MoveContext<'_>) -> Cost {
match move_ctx {
MoveContext::Route { job, .. } => estimate_job_cost(job),
MoveContext::Activity { .. } => 0.,
}
}
}
struct PriorityState;
impl FeatureState for PriorityState {
fn accept_insertion(&self, solution_ctx: &mut SolutionContext, route_index: usize, _job: &Job) {
self.accept_route_state(solution_ctx.routes.get_mut(route_index).unwrap());
}
fn accept_route_state(&self, _route_ctx: &mut RouteContext) {
}
fn accept_solution_state(&self, solution_ctx: &mut SolutionContext) {
let fitness = calculate_solution_fitness(solution_ctx);
solution_ctx.state.set_priority_fitness(fitness);
}
}
fn estimate_job_cost(job: &Job) -> Cost {
job.dimens().get_job_priority().filter(|&is_high_prio| *is_high_prio).map_or(1., |_| 0.)
}
fn calculate_solution_fitness(solution_ctx: &SolutionContext) -> Cost {
solution_ctx.routes.iter().flat_map(|route_ctx| route_ctx.route().tour.jobs()).map(estimate_job_cost).sum::<Cost>()
}
fn define_problem(goal: GoalContext, transport: Arc<dyn TransportCost + Send + Sync>) -> GenericResult<Problem> {
let single_jobs = (1..=4)
.map(|idx| {
SingleBuilder::default()
.id(format!("job{idx}").as_str())
.demand(Demand::delivery(1))
.dimension(|dimens| {
dimens.set_job_priority(idx % 2 == 0);
})
.location(idx)?
.build_as_job()
})
.collect::<Result<Vec<_>, _>>()?;
let vehicle = VehicleBuilder::default()
.id("v1".to_string().as_str())
.add_detail(VehicleDetailBuilder::default().set_start_location(0).build()?)
.capacity(SingleDimLoad::new(2))
.build()?;
ProblemBuilder::default()
.add_jobs(single_jobs.into_iter())
.add_vehicles(once(vehicle))
.with_goal(goal)
.with_transport_cost(transport)
.build()
}
fn define_goal(transport: Arc<dyn TransportCost + Send + Sync>) -> GenericResult<GoalContext> {
let minimize_unassigned = MinimizeUnassignedBuilder::new("min-unassigned").build()?;
let capacity_feature = CapacityFeatureBuilder::<SingleDimLoad>::new("capacity").build()?;
let transport_feature = TransportFeatureBuilder::new("min-distance")
.set_transport_cost(transport)
.set_time_constrained(false)
.build_minimize_distance()?;
let priority_feature = FeatureBuilder::default()
.with_name("maximize-priority")
.with_objective(PriorityObjective)
.with_state(PriorityState)
.build()?;
GoalContextBuilder::with_features(&[priority_feature, minimize_unassigned, transport_feature, capacity_feature])?
.build()
}
fn main() -> GenericResult<()> {
let transport = Arc::new(define_routing_data()?);
let goal = define_goal(transport.clone())?;
let problem = Arc::new(define_problem(goal, transport)?);
let config = VrpConfigBuilder::new(problem.clone()).prebuild()?.with_max_generations(Some(10)).build()?;
let solution = Solver::new(problem, config).solve()?;
assert_eq!(solution.unassigned.len(), 2, "expected two assigned jobs due to capacity constraint");
assert_eq!(solution.routes.len(), 1, "only one tour should be there");
assert_eq!(
solution.get_locations().map(Iterator::collect::<Vec<_>>).collect::<Vec<_>>(),
vec![vec![0, 2, 4]],
"tour doesn't serve only top-prio jobs"
);
assert_eq!(solution.cost, 545., "unexpected cost - closest to depot jobs should be assigned");
Ok(())
}