Skip to main content

routik_solver/
objective.rs

1//! The objective function the metaheuristic minimises.
2//!
3//! Centralised here so every part of the solver scores a solution the same
4//! way (per the solver rules: "Objectif centralisé dans `cost`"). By default
5//! it minimises total distance; optional weights let callers also penalise the
6//! number of vehicles used or the total time on the road.
7
8use crate::model::Solution;
9
10/// Weights of the linear objective `cost = w_d·distance + w_v·vehicles + w_t·time`.
11///
12/// The default is pure distance minimisation (`distance = 1`, the rest `0`),
13/// which is what the classic VRPTW benchmarks score against.
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct Objective {
16    /// Weight on total travelled distance.
17    pub distance: f64,
18    /// Weight on the number of vehicles (routes) used.
19    pub vehicles: f64,
20    /// Weight on total elapsed time (travel + wait + service).
21    pub time: f64,
22}
23
24impl Default for Objective {
25    fn default() -> Self {
26        Self {
27            distance: 1.0,
28            vehicles: 0.0,
29            time: 0.0,
30        }
31    }
32}
33
34impl Objective {
35    /// Minimise total distance only.
36    #[must_use]
37    pub fn distance_only() -> Self {
38        Self::default()
39    }
40
41    /// Score from already rolled-up totals. Kept separate from [`Self::of`] so
42    /// the search loop can score incrementally without owning a [`Solution`].
43    #[must_use]
44    pub fn score(&self, total_distance: f64, route_count: usize, total_time: f64) -> f64 {
45        self.distance * total_distance + self.vehicles * route_count as f64 + self.time * total_time
46    }
47
48    /// Score a complete [`Solution`].
49    #[must_use]
50    pub fn of(&self, solution: &Solution) -> f64 {
51        self.score(
52            solution.total_distance,
53            solution.routes.len(),
54            solution.total_time,
55        )
56    }
57}