From 5c368c24c4b82f57bf47c98f8370d0849f491ebe Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Mon, 27 Oct 2025 20:58:33 -0400 Subject: [PATCH 1/5] More fixes --- crates/solver_vrp/src/constraints.rs | 90 ++++++++++++++++ crates/solver_vrp/src/lib.rs | 15 +-- crates/solver_vrp/src/model.rs | 152 ++++----------------------- crates/solver_vrp/src/solution.rs | 19 ++++ 4 files changed, 135 insertions(+), 141 deletions(-) create mode 100644 crates/solver_vrp/src/constraints.rs diff --git a/crates/solver_vrp/src/constraints.rs b/crates/solver_vrp/src/constraints.rs new file mode 100644 index 0000000..ce9fdea --- /dev/null +++ b/crates/solver_vrp/src/constraints.rs @@ -0,0 +1,90 @@ +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 VehicleCapacityConstraint {} + +impl Default for VehicleCapacityConstraint { + fn default() -> Self { + Self {} + } +} + +impl Constraint for VehicleCapacityConstraint { + fn name(&self) -> String { + String::from("vehicle_capacity") + } + + fn is_feasible(&self, plan: &Plan) -> bool { + todo!() + } +} + +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) + .map_or(false, |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..d29ec20 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,11 +140,8 @@ //! # `Constraint` //! //! Constraints define the rules for each solution plan. -//! -//! # `Expression` -//! -//! Every model implements some number of expressions that are used for internal calculations. +mod constraints; mod model; mod operator; mod random; diff --git a/crates/solver_vrp/src/model.rs b/crates/solver_vrp/src/model.rs index 0322ebe..f9cb2b9 100644 --- a/crates/solver_vrp/src/model.rs +++ b/crates/solver_vrp/src/model.rs @@ -1,4 +1,9 @@ -use crate::solution::{Plan, Solution}; +use crate::{ + constraints::{ + Constraint, Constraints, VehicleCapacityConstraint, VehicleCompatibilityConstraint, + }, + solution::{Plan, Solution}, +}; pub trait Objective { /// Name of the objective. @@ -7,17 +12,6 @@ pub trait Objective { 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; @@ -29,7 +23,6 @@ pub struct Model { data: ModelData, objectives: Objectives, constraints: Constraints, - expressions: Expressions, } impl Model { @@ -67,19 +60,14 @@ 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::default()) + .constraint(VehicleCapacityConstraint::default()) + .constraint(VehicleCompatibilityConstraint::default()) .build() } } @@ -147,53 +135,10 @@ impl Objectives { } } -#[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 +154,6 @@ impl ModelBuilder { data: ModelData::default(), objectives: Objectives::default(), constraints: Constraints::default(), - expressions: Expressions::default(), } } @@ -237,7 +181,6 @@ impl ModelBuilder { data: self.data, objectives: self.objectives, constraints: self.constraints, - expressions: self.expressions, } } @@ -252,52 +195,28 @@ 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 Default for UnplannedObjective { + fn default() -> Self { + Self {} } } -impl Constraint for VehicleCompatibilityConstraint { +impl Objective for UnplannedObjective { fn name(&self) -> String { - String::from("vehicle_compatibility") + String::from("unplanned") } - fn is_feasible(&self, model: &Model, solution: &Solution, plan: &Plan) -> bool { - true + fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { + todo!() } } pub struct Stop { - id: usize, + pub id: usize, location: Location, quantities: Vec, compatibility_attributes: Option>, @@ -315,7 +234,7 @@ impl Stop { } pub struct Vehicle { - id: usize, + pub id: usize, capacity: Vec, start_location: Option, end_location: Option, @@ -438,22 +357,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]); @@ -467,9 +375,8 @@ mod tests { .objective(UnplannedObjective {}) .objective(TestObjective {}) .constraint(VehicleCapacityConstraint {}) - .constraint(VehicleCompatibilityConstraint {}) + .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) - .expression(TestExpression {}) .build(); assert_eq!(model.stops().len(), 1); @@ -490,18 +397,12 @@ mod tests { 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); - } - #[test] fn test_model_objective_names() { let model = ModelBuilder::new() @@ -522,7 +423,7 @@ mod tests { fn test_model_constraint_names() { let model = ModelBuilder::new() .constraint(VehicleCapacityConstraint {}) - .constraint(VehicleCompatibilityConstraint {}) + .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) .build(); assert_eq!( @@ -539,15 +440,6 @@ mod tests { ); } - #[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/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!() + } +} From 8b5444a6703216ac3210ae12c049a50d0f0b8813 Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Mon, 27 Oct 2025 20:58:46 -0400 Subject: [PATCH 2/5] Clippy --- crates/solver_vrp/src/constraints.rs | 8 ++------ crates/solver_vrp/src/model.rs | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/crates/solver_vrp/src/constraints.rs b/crates/solver_vrp/src/constraints.rs index ce9fdea..e72b485 100644 --- a/crates/solver_vrp/src/constraints.rs +++ b/crates/solver_vrp/src/constraints.rs @@ -32,13 +32,9 @@ impl Constraints { } } +#[derive(Default)] pub struct VehicleCapacityConstraint {} -impl Default for VehicleCapacityConstraint { - fn default() -> Self { - Self {} - } -} impl Constraint for VehicleCapacityConstraint { fn name(&self) -> String { @@ -85,6 +81,6 @@ impl StopCompatibilities { pub fn is_compatible(&self, stop_index: usize, vehicle_index: usize) -> bool { self.0 .get(stop_index) - .map_or(false, |v| v.get(vehicle_index).copied().unwrap_or(true)) + .is_some_and(|v| v.get(vehicle_index).copied().unwrap_or(true)) } } diff --git a/crates/solver_vrp/src/model.rs b/crates/solver_vrp/src/model.rs index f9cb2b9..f01b20c 100644 --- a/crates/solver_vrp/src/model.rs +++ b/crates/solver_vrp/src/model.rs @@ -65,7 +65,7 @@ impl Model { impl Default for Model { fn default() -> Self { ModelBuilder::new() - .objective(UnplannedObjective::default()) + .objective(UnplannedObjective) .constraint(VehicleCapacityConstraint::default()) .constraint(VehicleCompatibilityConstraint::default()) .build() @@ -197,13 +197,9 @@ impl ModelBuilder { } } +#[derive(Default)] pub struct UnplannedObjective; -impl Default for UnplannedObjective { - fn default() -> Self { - Self {} - } -} impl Objective for UnplannedObjective { fn name(&self) -> String { From 3e4cadbb66108dc991d68ec68868e22af39d736f Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Mon, 27 Oct 2025 20:58:53 -0400 Subject: [PATCH 3/5] Fmt --- crates/solver_vrp/src/constraints.rs | 1 - crates/solver_vrp/src/model.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/crates/solver_vrp/src/constraints.rs b/crates/solver_vrp/src/constraints.rs index e72b485..426ef72 100644 --- a/crates/solver_vrp/src/constraints.rs +++ b/crates/solver_vrp/src/constraints.rs @@ -35,7 +35,6 @@ impl Constraints { #[derive(Default)] pub struct VehicleCapacityConstraint {} - impl Constraint for VehicleCapacityConstraint { fn name(&self) -> String { String::from("vehicle_capacity") diff --git a/crates/solver_vrp/src/model.rs b/crates/solver_vrp/src/model.rs index f01b20c..b79aec1 100644 --- a/crates/solver_vrp/src/model.rs +++ b/crates/solver_vrp/src/model.rs @@ -200,7 +200,6 @@ impl ModelBuilder { #[derive(Default)] pub struct UnplannedObjective; - impl Objective for UnplannedObjective { fn name(&self) -> String { String::from("unplanned") From 70976c0e0d69f355d51b23d959bc02bbce1fa0df Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Mon, 27 Oct 2025 21:04:11 -0400 Subject: [PATCH 4/5] Simpler --- .../src/{constraints.rs => constraint.rs} | 13 ---- crates/solver_vrp/src/lib.rs | 3 +- crates/solver_vrp/src/model.rs | 62 ++----------------- crates/solver_vrp/src/objective.rs | 42 +++++++++++++ 4 files changed, 49 insertions(+), 71 deletions(-) rename crates/solver_vrp/src/{constraints.rs => constraint.rs} (87%) create mode 100644 crates/solver_vrp/src/objective.rs diff --git a/crates/solver_vrp/src/constraints.rs b/crates/solver_vrp/src/constraint.rs similarity index 87% rename from crates/solver_vrp/src/constraints.rs rename to crates/solver_vrp/src/constraint.rs index 426ef72..49d5fe8 100644 --- a/crates/solver_vrp/src/constraints.rs +++ b/crates/solver_vrp/src/constraint.rs @@ -32,19 +32,6 @@ impl Constraints { } } -#[derive(Default)] -pub struct VehicleCapacityConstraint {} - -impl Constraint for VehicleCapacityConstraint { - fn name(&self) -> String { - String::from("vehicle_capacity") - } - - fn is_feasible(&self, plan: &Plan) -> bool { - todo!() - } -} - pub struct VehicleCompatibilityConstraint { compatible: StopCompatibilities, } diff --git a/crates/solver_vrp/src/lib.rs b/crates/solver_vrp/src/lib.rs index d29ec20..72733bb 100644 --- a/crates/solver_vrp/src/lib.rs +++ b/crates/solver_vrp/src/lib.rs @@ -141,8 +141,9 @@ //! //! Constraints define the rules for each solution plan. -mod constraints; +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 b79aec1..65e869c 100644 --- a/crates/solver_vrp/src/model.rs +++ b/crates/solver_vrp/src/model.rs @@ -1,24 +1,8 @@ use crate::{ - constraints::{ - Constraint, Constraints, VehicleCapacityConstraint, VehicleCompatibilityConstraint, - }, - solution::{Plan, Solution}, + constraint::{Constraint, Constraints, VehicleCompatibilityConstraint}, + objective::{Objective, Objectives, UnplannedObjective}, }; -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 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; -} - pub struct Model { data: ModelData, objectives: Objectives, @@ -66,7 +50,6 @@ impl Default for Model { fn default() -> Self { ModelBuilder::new() .objective(UnplannedObjective) - .constraint(VehicleCapacityConstraint::default()) .constraint(VehicleCompatibilityConstraint::default()) .build() } @@ -114,27 +97,6 @@ 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); - } -} - pub struct ModelBuilder { data: ModelData, objectives: Objectives, @@ -197,19 +159,6 @@ impl ModelBuilder { } } -#[derive(Default)] -pub struct UnplannedObjective; - -impl Objective for UnplannedObjective { - fn name(&self) -> String { - String::from("unplanned") - } - - fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { - todo!() - } -} - pub struct Stop { pub id: usize, location: Location, @@ -333,6 +282,8 @@ struct Arc { #[cfg(test)] mod tests { + use crate::solution::Plan; + use super::*; struct TestObjective; @@ -341,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 } } @@ -369,7 +320,6 @@ mod tests { .distance_matrix(distance_matrix) .objective(UnplannedObjective {}) .objective(TestObjective {}) - .constraint(VehicleCapacityConstraint {}) .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) .build(); @@ -391,7 +341,6 @@ mod tests { #[test] fn test_model_constraint_count() { let model = ModelBuilder::new() - .constraint(VehicleCapacityConstraint {}) .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) .build(); @@ -417,7 +366,6 @@ mod tests { #[test] fn test_model_constraint_names() { let model = ModelBuilder::new() - .constraint(VehicleCapacityConstraint {}) .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) .build(); 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!() + } +} From 64aa057de188f0e68f9bd2b9992d9189287a589e Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Mon, 27 Oct 2025 21:10:25 -0400 Subject: [PATCH 5/5] Fix --- crates/solver_vrp/src/model.rs | 8 ++------ crates/solver_vrp/src/solver.rs | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/solver_vrp/src/model.rs b/crates/solver_vrp/src/model.rs index 65e869c..bbde08e 100644 --- a/crates/solver_vrp/src/model.rs +++ b/crates/solver_vrp/src/model.rs @@ -344,7 +344,7 @@ mod tests { .constraint(VehicleCompatibilityConstraint::default()) .constraint(TestConstraint {}) .build(); - assert_eq!(model.constraints().len(), 3); + assert_eq!(model.constraints().len(), 2); } #[test] @@ -371,14 +371,10 @@ mod tests { .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")) ); } 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]