diff --git a/crates/solver_vrp/src/constraint.rs b/crates/solver_vrp/src/constraint.rs new file mode 100644 index 0000000..49d5fe8 --- /dev/null +++ b/crates/solver_vrp/src/constraint.rs @@ -0,0 +1,72 @@ +use crate::solution::Plan; + +pub trait Constraint { + /// Name of the constraint. + fn name(&self) -> String; + /// Checks if the plan violates the constraint. + fn is_feasible(&self, plan: &Plan) -> bool; + /// Indicates if the constraint is temporal. + fn is_temporal(&self) -> bool { + false + } +} + +#[derive(Default)] +pub struct Constraints(Vec>); + +impl Constraints { + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get(&self, index: usize) -> Option<&dyn Constraint> { + self.0.get(index).map(AsRef::as_ref) + } + + pub fn first(&self) -> Option<&dyn Constraint> { + self.0.first().map(AsRef::as_ref) + } + + pub fn push(&mut self, constraint: Box) { + self.0.push(constraint); + } +} + +pub struct VehicleCompatibilityConstraint { + compatible: StopCompatibilities, +} + +impl VehicleCompatibilityConstraint { + pub fn new(compatible: StopCompatibilities) -> Self { + Self { compatible } + } +} + +impl Default for VehicleCompatibilityConstraint { + fn default() -> Self { + Self { + compatible: StopCompatibilities(Vec::new()), + } + } +} + +impl Constraint for VehicleCompatibilityConstraint { + fn name(&self) -> String { + String::from("vehicle_compatibility") + } + + fn is_feasible(&self, plan: &Plan) -> bool { + self.compatible + .is_compatible(plan.stop().id, plan.vehicle().id) + } +} + +pub struct StopCompatibilities(Vec>); + +impl StopCompatibilities { + pub fn is_compatible(&self, stop_index: usize, vehicle_index: usize) -> bool { + self.0 + .get(stop_index) + .is_some_and(|v| v.get(vehicle_index).copied().unwrap_or(true)) + } +} diff --git a/crates/solver_vrp/src/lib.rs b/crates/solver_vrp/src/lib.rs index 0d33386..72733bb 100644 --- a/crates/solver_vrp/src/lib.rs +++ b/crates/solver_vrp/src/lib.rs @@ -32,7 +32,7 @@ //! } //! //! // Implement a custom constraint to enforce unique business rules in the model. -//! struct MyVehicleCapacities(vec![f64; 2]); +//! struct MyVehicleCapacities([f64; 2]); //! //! impl Constraint for MyVehicleCapacities { //! fn name(&self) -> String { @@ -41,11 +41,7 @@ //! //! // Returns true if the plan is feasible. //! fn is_feasible(&self, plan: &Plan) -> bool { -//! let i = 1; -//! self.0.get(i) -//! .zip(plan.route().changes().last()) -//! .and_then(|(max, change)| change.requirements().capacity().get(i).map(|r| r <= max)) -//! .unwrap_or(true) +//! todo!() //! } //! } //! @@ -67,7 +63,7 @@ //! // Build the model with custom components. //! let model = ModelBuilder::new() //! .objective(ZeroObjective { zero: 0.0 }) -//! .constraint(MyVehicleCapacities(vec![26.0, 40_000.0])) +//! .constraint(MyVehicleCapacities([26.0, 40_000.0])) //! .build(); //! //! // Define options for the solver. @@ -144,12 +140,10 @@ //! # `Constraint` //! //! Constraints define the rules for each solution plan. -//! -//! # `Expression` -//! -//! Every model implements some number of expressions that are used for internal calculations. +mod constraint; mod model; +mod objective; mod operator; mod random; mod solution; diff --git a/crates/solver_vrp/src/model.rs b/crates/solver_vrp/src/model.rs index 0322ebe..bbde08e 100644 --- a/crates/solver_vrp/src/model.rs +++ b/crates/solver_vrp/src/model.rs @@ -1,35 +1,12 @@ -use crate::solution::{Plan, Solution}; - -pub trait Objective { - /// Name of the objective. - fn name(&self) -> String; - /// Computes the value of the objective for the given plan. - fn compute(&self, model: &Model, solution: &Solution, plan: &Plan) -> f64; -} - -pub trait Constraint { - /// Name of the constraint. - fn name(&self) -> String; - /// Checks if the plan violates the constraint. - fn is_feasible(&self, model: &Model, solution: &Solution, plan: &Plan) -> bool; - /// Indicates if the constraint is temporal. - fn is_temporal(&self) -> bool { - false - } -} - -pub trait Expression { - /// Name of the expression. - fn name(&self) -> String; - /// Computes the value of the expression for the given plan. - fn compute(&self, model: &Model, solution: &Solution, plan: &Plan) -> f64; -} +use crate::{ + constraint::{Constraint, Constraints, VehicleCompatibilityConstraint}, + objective::{Objective, Objectives, UnplannedObjective}, +}; pub struct Model { data: ModelData, objectives: Objectives, constraints: Constraints, - expressions: Expressions, } impl Model { @@ -67,19 +44,13 @@ impl Model { pub fn constraints(&self) -> &Constraints { &self.constraints } - - #[must_use] - pub fn expressions(&self) -> &Expressions { - &self.expressions - } } impl Default for Model { fn default() -> Self { ModelBuilder::new() - .objective(UnplannedObjective {}) - .constraint(VehicleCapacityConstraint {}) - .constraint(VehicleCompatibilityConstraint {}) + .objective(UnplannedObjective) + .constraint(VehicleCompatibilityConstraint::default()) .build() } } @@ -126,74 +97,10 @@ impl Vehicles { } } -#[derive(Default)] -pub struct Objectives(Vec>); - -impl Objectives { - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn get(&self, index: usize) -> Option<&dyn Objective> { - self.0.get(index).map(AsRef::as_ref) - } - - pub fn first(&self) -> Option<&dyn Objective> { - self.0.first().map(AsRef::as_ref) - } - - pub fn push(&mut self, objective: Box) { - self.0.push(objective); - } -} - -#[derive(Default)] -pub struct Constraints(Vec>); - -impl Constraints { - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn get(&self, index: usize) -> Option<&dyn Constraint> { - self.0.get(index).map(AsRef::as_ref) - } - - pub fn first(&self) -> Option<&dyn Constraint> { - self.0.first().map(AsRef::as_ref) - } - - pub fn push(&mut self, constraint: Box) { - self.0.push(constraint); - } -} - -#[derive(Default)] -pub struct Expressions(Vec>); - -impl Expressions { - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn get(&self, index: usize) -> Option<&dyn Expression> { - self.0.get(index).map(AsRef::as_ref) - } - - pub fn first(&self) -> Option<&dyn Expression> { - self.0.first().map(AsRef::as_ref) - } - - pub fn push(&mut self, expression: Box) { - self.0.push(expression); - } -} - pub struct ModelBuilder { data: ModelData, objectives: Objectives, constraints: Constraints, - expressions: Expressions, } impl Default for ModelBuilder { @@ -209,7 +116,6 @@ impl ModelBuilder { data: ModelData::default(), objectives: Objectives::default(), constraints: Constraints::default(), - expressions: Expressions::default(), } } @@ -237,7 +143,6 @@ impl ModelBuilder { data: self.data, objectives: self.objectives, constraints: self.constraints, - expressions: self.expressions, } } @@ -252,52 +157,10 @@ impl ModelBuilder { self.constraints.push(Box::new(constraint)); self } - - #[must_use] - pub fn expression(mut self, expression: E) -> Self { - self.expressions.push(Box::new(expression)); - self - } -} - -pub struct UnplannedObjective; - -impl Objective for UnplannedObjective { - fn name(&self) -> String { - String::from("unplanned") - } - - fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { - 0.0 - } -} - -pub struct VehicleCapacityConstraint {} - -pub struct VehicleCompatibilityConstraint {} - -impl Constraint for VehicleCapacityConstraint { - fn name(&self) -> String { - String::from("vehicle_capacity") - } - - fn is_feasible(&self, model: &Model, solution: &Solution, plan: &Plan) -> bool { - true - } -} - -impl Constraint for VehicleCompatibilityConstraint { - fn name(&self) -> String { - String::from("vehicle_compatibility") - } - - fn is_feasible(&self, model: &Model, solution: &Solution, plan: &Plan) -> bool { - true - } } pub struct Stop { - id: usize, + pub id: usize, location: Location, quantities: Vec, compatibility_attributes: Option>, @@ -315,7 +178,7 @@ impl Stop { } pub struct Vehicle { - id: usize, + pub id: usize, capacity: Vec, start_location: Option, end_location: Option, @@ -419,6 +282,8 @@ struct Arc { #[cfg(test)] mod tests { + use crate::solution::Plan; + use super::*; struct TestObjective; @@ -427,7 +292,7 @@ mod tests { String::from("Test Objective") } - fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { + fn compute(&self, _plan: &Plan) -> f64 { 0.0 } } @@ -438,22 +303,11 @@ mod tests { String::from("Test Constraint") } - fn is_feasible(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> bool { + fn is_feasible(&self, _plan: &Plan) -> bool { true } } - struct TestExpression; - impl Expression for TestExpression { - fn name(&self) -> String { - String::from("Test Expression") - } - - fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { - 0.0 - } - } - #[test] fn test_model_build_and_access() { let stop = Stop::new(1, Location::new(1, 10.0, 20.0), vec![5.0]); @@ -466,10 +320,8 @@ mod tests { .distance_matrix(distance_matrix) .objective(UnplannedObjective {}) .objective(TestObjective {}) - .constraint(VehicleCapacityConstraint {}) - .constraint(VehicleCompatibilityConstraint {}) + .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) - .expression(TestExpression {}) .build(); assert_eq!(model.stops().len(), 1); @@ -489,17 +341,10 @@ mod tests { #[test] fn test_model_constraint_count() { let model = ModelBuilder::new() - .constraint(VehicleCapacityConstraint {}) - .constraint(VehicleCompatibilityConstraint {}) + .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) .build(); - assert_eq!(model.constraints().len(), 3); - } - - #[test] - fn test_model_expression_count() { - let model = ModelBuilder::new().expression(TestExpression).build(); - assert_eq!(model.expressions().len(), 1); + assert_eq!(model.constraints().len(), 2); } #[test] @@ -521,33 +366,19 @@ mod tests { #[test] fn test_model_constraint_names() { let model = ModelBuilder::new() - .constraint(VehicleCapacityConstraint {}) - .constraint(VehicleCompatibilityConstraint {}) + .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) .build(); assert_eq!( model.constraints().first().map(|c| c.name()), - Some(String::from("vehicle_capacity")) - ); - assert_eq!( - model.constraints().get(1).map(|c| c.name()), Some(String::from("vehicle_compatibility")) ); assert_eq!( - model.constraints().get(2).map(|c| c.name()), + model.constraints().get(1).map(|c| c.name()), Some(String::from("Test Constraint")) ); } - #[test] - fn test_model_expression_names() { - let model = ModelBuilder::new().expression(TestExpression).build(); - assert_eq!( - model.expressions().first().map(|e| e.name()), - Some(String::from("Test Expression")) - ); - } - #[test] fn test_graph() { let mut graph = DirectedAcyclicGraph::with_capacity(3); diff --git a/crates/solver_vrp/src/objective.rs b/crates/solver_vrp/src/objective.rs new file mode 100644 index 0000000..059c91a --- /dev/null +++ b/crates/solver_vrp/src/objective.rs @@ -0,0 +1,42 @@ +use crate::solution::Plan; + +#[derive(Default)] +pub struct Objectives(Vec>); + +pub trait Objective { + /// Name of the objective. + fn name(&self) -> String; + /// Computes the value of the objective for the given plan. + fn compute(&self, plan: &Plan) -> f64; +} + +impl Objectives { + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get(&self, index: usize) -> Option<&dyn Objective> { + self.0.get(index).map(AsRef::as_ref) + } + + pub fn first(&self) -> Option<&dyn Objective> { + self.0.first().map(AsRef::as_ref) + } + + pub fn push(&mut self, objective: Box) { + self.0.push(objective); + } +} + +#[derive(Default)] +pub struct UnplannedObjective; + +impl Objective for UnplannedObjective { + fn name(&self) -> String { + String::from("unplanned") + } + + fn compute(&self, _plan: &Plan) -> f64 { + todo!() + } +} diff --git a/crates/solver_vrp/src/solution.rs b/crates/solver_vrp/src/solution.rs index 474d57f..5c360f0 100644 --- a/crates/solver_vrp/src/solution.rs +++ b/crates/solver_vrp/src/solution.rs @@ -1,3 +1,5 @@ +use crate::model::{Stop, Vehicle}; + #[derive(Clone, Debug)] pub struct Solution { vehicles: SolutionVehicles, @@ -129,3 +131,20 @@ struct SolutionStatistics { } pub struct Plan {} + +impl Plan { + #[must_use] + pub fn new() -> Self { + Plan {} + } + + #[must_use] + pub fn stop(&self) -> &Stop { + todo!() + } + + #[must_use] + pub fn vehicle(&self) -> &Vehicle { + todo!() + } +} diff --git a/crates/solver_vrp/src/solver.rs b/crates/solver_vrp/src/solver.rs index bd4e079..e1df688 100644 --- a/crates/solver_vrp/src/solver.rs +++ b/crates/solver_vrp/src/solver.rs @@ -159,7 +159,7 @@ mod tests { assert_eq!(solver.iteration_count, 0); assert!(solver.solution.is_none()); assert_eq!(solver.model.objectives().len(), 1); - assert_eq!(solver.model.constraints().len(), 2); + assert_eq!(solver.model.constraints().len(), 1); } #[test]