//! 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, #[planning_entity_collection] pub vehicles: Vec, // @solverforge:end solution-collections #[planning_score] pub score: Option, /// 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>, } impl Plan { /// Builds a normalized plan from facts and route-owning vehicles. pub fn new(name: impl Into, deliveries: Vec, vehicles: Vec) -> 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 = 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 { &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() } }