Spaces:
Sleeping
Sleeping
File size: 5,777 Bytes
f6213fc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | //! Planning solution for the delivery-routing problem.
//!
//! The `Plan` owns facts, planning entities, score, routing mode, and transient
//! prepared route matrices. It is both the solver input and the shape serialized
//! through the API after `PlanDto` flattens it.
use std::collections::HashMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use solverforge::cvrp::ProblemData;
use solverforge::prelude::*;
// @solverforge:begin solution-imports
use super::route_metrics::preview_for_plan;
use super::{Delivery, PlanViewState, RoutingMode, Vehicle};
// @solverforge:end solution-imports
#[planning_solution(
constraints = "crate::constraints::create_constraints",
solver_toml = "../../solver.toml"
)]
#[shadow_variable_updates(
list_owner = "vehicles",
post_update_listener = "refresh_vehicle_route_shadows"
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Plan {
pub name: String,
#[serde(default)]
pub routing_mode: RoutingMode,
#[serde(default)]
pub view_state: PlanViewState,
// @solverforge:begin solution-collections
#[problem_fact_collection]
pub deliveries: Vec<Delivery>,
#[planning_entity_collection]
pub vehicles: Vec<Vehicle>,
// @solverforge:end solution-collections
#[planning_score]
pub score: Option<HardSoftScore>,
/// Transient CVRP matrices shared with SolverForge list-variable hooks.
///
/// This is skipped in JSON because it is rebuilt from coordinates before a
/// solve or route-geometry request.
#[serde(skip, default)]
pub prepared_problem_data: Vec<Arc<ProblemData>>,
}
impl Plan {
/// Builds a normalized plan from facts and route-owning vehicles.
pub fn new(name: impl Into<String>, deliveries: Vec<Delivery>, vehicles: Vec<Vehicle>) -> Self {
let mut plan = Self {
name: name.into(),
routing_mode: RoutingMode::default(),
view_state: PlanViewState::default(),
deliveries,
vehicles,
score: None,
prepared_problem_data: Vec::new(),
};
plan.normalize();
plan
}
/// Reassigns dense ids and clears transient routing caches after decoding.
///
/// SolverForge list variables store delivery indexes. If transport data
/// arrived with older public ids, this maps route entries back onto the
/// current dense delivery positions before scoring.
pub fn normalize(&mut self) {
let delivery_id_map: HashMap<usize, usize> = self
.deliveries
.iter()
.enumerate()
.map(|(idx, delivery)| (delivery.id, idx))
.collect();
for (idx, delivery) in self.deliveries.iter_mut().enumerate() {
delivery.id = idx;
}
for (idx, vehicle) in self.vehicles.iter_mut().enumerate() {
vehicle.id = idx;
vehicle.prepared_routing = None;
vehicle.delivery_order = vehicle
.delivery_order
.iter()
.filter_map(|old_id| delivery_id_map.get(old_id).copied())
.collect();
vehicle.refresh_route_shadows();
}
self.prepared_problem_data.clear();
}
/// Removes one delivery id from every route before insertion previewing.
pub fn remove_delivery_assignments(&mut self, delivery_id: usize) {
for vehicle in &mut self.vehicles {
vehicle
.delivery_order
.retain(|assigned| *assigned != delivery_id);
}
}
/// List-variable post-update hook used by SolverForge shadow variables.
pub fn refresh_vehicle_route_shadows(&mut self, vehicle_idx: usize) {
if let Some(vehicle) = self.vehicles.get_mut(vehicle_idx) {
vehicle.refresh_route_shadows();
}
}
/// Clones the plan and attaches the UI-facing route preview.
pub fn refreshed_for_transport(&self) -> Self {
let mut plan = self.clone();
plan.view_state.preview = Some(preview_for_plan(&plan));
plan
}
}
impl solverforge::cvrp::VrpSolution for Plan {
/// Gives SolverForge's CVRP move selectors access to prepared matrices.
fn vehicle_data_ptr(&self, entity_idx: usize) -> *const ProblemData {
self.vehicles[entity_idx]
.prepared_routing
.as_ref()
.and_then(|prepared| self.prepared_problem_data.get(prepared.problem_data_index))
.map(Arc::as_ptr)
.unwrap_or(std::ptr::null())
}
/// Reads the mutable list variable for one vehicle as visit ids.
fn vehicle_visits(&self, entity_idx: usize) -> &[usize] {
&self.vehicles[entity_idx].delivery_order
}
/// Lets CVRP construction and local-search hooks replace one vehicle route.
fn vehicle_visits_mut(&mut self, entity_idx: usize) -> &mut Vec<usize> {
&mut self.vehicles[entity_idx].delivery_order
}
/// Reports the number of route-owning planning entities.
fn vehicle_count(&self) -> usize {
self.vehicles.len()
}
}
#[cfg(test)]
impl Plan {
pub(crate) fn test_has_list_variable() -> bool {
Self::__solverforge_has_list_variable()
}
pub(crate) fn test_total_list_entities(plan: &Self) -> usize {
Self::__solverforge_total_list_entities(plan)
}
pub(crate) fn test_total_list_elements(plan: &Self) -> usize {
Self::__solverforge_total_list_elements(plan)
}
pub(crate) fn test_is_trivial(plan: &Self) -> bool {
Self::__solverforge_is_trivial(plan)
}
pub(crate) fn test_phase_count(config: &solverforge::SolverConfig) -> usize {
Self::__solverforge_build_phases(config).phases().len()
}
}
|