#[cfg(test)]
#[path = "../../../tests/unit/construction/features/fleet_usage_test.rs"]
mod fleet_usage_test;
use super::*;
pub fn create_minimize_tours_feature(name: &str) -> GenericResult<Feature> {
FeatureBuilder::default()
.with_name(name)
.with_objective(FleetUsageObjective {
route_estimate_fn: Box::new(|route_ctx| if route_ctx.route().tour.job_count() == 0 { 1. } else { 0. }),
solution_estimate_fn: Box::new(|solution_ctx| solution_ctx.routes.iter().len() as Cost),
})
.build()
}
pub fn create_maximize_tours_feature(name: &str) -> GenericResult<Feature> {
FeatureBuilder::default()
.with_name(name)
.with_objective(FleetUsageObjective {
route_estimate_fn: Box::new(|route_ctx| if route_ctx.route().tour.job_count() == 0 { -1. } else { 0. }),
solution_estimate_fn: Box::new(|solution_ctx| -1. * solution_ctx.routes.iter().len() as Cost),
})
.build()
}
pub fn create_minimize_arrival_time_feature(name: &str) -> GenericResult<Feature> {
FeatureBuilder::default()
.with_name(name)
.with_objective(FleetUsageObjective {
route_estimate_fn: Box::new(|route_ctx| route_ctx.route().actor.detail.time.start),
solution_estimate_fn: Box::new(|solution_ctx| {
if solution_ctx.routes.is_empty() {
0.
} else {
let total: Float = solution_ctx
.routes
.iter()
.filter_map(|route_ctx| route_ctx.route().tour.end())
.map(|end| end.schedule.arrival)
.sum();
total / solution_ctx.routes.len() as Float
}
}),
})
.build()
}
struct FleetUsageObjective {
route_estimate_fn: Box<dyn Fn(&RouteContext) -> Cost + Send + Sync>,
solution_estimate_fn: Box<dyn Fn(&SolutionContext) -> Cost + Send + Sync>,
}
impl FeatureObjective for FleetUsageObjective {
fn fitness(&self, solution: &InsertionContext) -> Cost {
(self.solution_estimate_fn)(&solution.solution)
}
fn estimate(&self, move_ctx: &MoveContext<'_>) -> Cost {
match move_ctx {
MoveContext::Route { route_ctx, .. } => (self.route_estimate_fn)(route_ctx),
_ => Cost::default(),
}
}
}