diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c1bef8d..d2a289c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest] steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@nightly @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest] steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@nightly @@ -40,7 +40,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: ubuntu-latest + os: [ubuntu-latest] steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@nightly @@ -51,7 +51,7 @@ jobs: run: cargo clippy --workspace --all-features -- -D warnings format: name: Format - runs-on: ubuntu-latest + runs-on: [ubuntu-latest] steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@nightly diff --git a/crates/solver_vrp/src/lib.rs b/crates/solver_vrp/src/lib.rs index 0e637ed..0d33386 100644 --- a/crates/solver_vrp/src/lib.rs +++ b/crates/solver_vrp/src/lib.rs @@ -2,80 +2,72 @@ //! //! A vehicle routing solver library. //! -//! This library provides functionalities to solve various vehicle routing problems (VRP), including +//! ! WARNING: Some APIs like the Plan API are still unimplemented and subject to change. +//! +//! Supported: //! //! - Pickup and Delivery Problem (PDP) //! -//! ``` -//! use solver_vrp::{Constraint, Expression, Model, ModelBuilder, Objective, Operator, Plan, Solution, SolverBuilder, SolverOptions}; +//! ```rust,ignore +//! use solver_vrp::model::{Constraint, Expression, Model, ModelBuilder, Objective}; +//! use solver_vrp::operator::Operator; +//! use solver_vrp::solution::{Plan, Solution}; +//! use solver_vrp::solver::{Solver, SolverBuilder, SolverOptions}; +//! use solver_vrp::random::Random; //! //! // Implement a custom objective to add optimized features like linehaul costs to the model. -//! struct CustomObjective; +//! struct ZeroObjective { +//! zero: f64, +//! } //! -//! impl Objective for CustomObjective { +//! impl Objective for ZeroObjective { //! fn name(&self) -> String { -//! String::from("Custom Objective") +//! String::from("My Zero Objective") //! } //! -//! // Returns the computed value of the objective for the given plan. +//! // Computes the value of the objective for the given plan. //! fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { -//! 0.0 +//! self.zero //! } //! } //! //! // Implement a custom constraint to enforce unique business rules in the model. -//! struct CustomConstraint; +//! struct MyVehicleCapacities(vec![f64; 2]); //! -//! impl Constraint for CustomConstraint { +//! impl Constraint for MyVehicleCapacities { //! fn name(&self) -> String { -//! String::from("Custom Objective") +//! String::from("My Vehicle Capacities") //! } //! //! // Returns true if the plan is feasible. -//! fn is_feasible(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> bool { -//! true -//! } -//! -//! // Returns true if the constraint is temporal. -//! fn is_temporal(&self) -> bool { -//! false -//! } -//! } -//! -//! // Implement a custom expression to add new calculations to the model. -//! struct CustomExpression; -//! -//! impl Expression for CustomExpression { -//! fn name(&self) -> String { -//! String::from("Custom Expression") -//! } -//! -//! // Returns the computed value for the expression. -//! fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { -//! 0.0 +//! 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) //! } //! } //! //! // Add new operators to refine the solver's search and heuristic capabilities. -//! struct CustomOperator; +//! struct SimpleOperator {}; //! -//! impl Operator for CustomOperator { +//! impl Operator for SimpleOperator { //! fn name(&self) -> String { -//! String::from("Custom Operator") +//! String::from("Simple Operator") //! } //! -//! // Returns the new solution after executing the operator. -//! fn execute(&self, _model: &Model, _solution: &Solution) -> Option { -//! None +//! // Returns the new plan after executing the operator. +//! fn execute(&self, _model: &Model, _solution: &Solution, _random: &mut Random) -> Plan { +//! return Plan::new(); //! } //! } //! //! fn run() { //! // Build the model with custom components. //! let model = ModelBuilder::new() -//! .objective(CustomObjective) -//! .constraint(CustomConstraint) -//! .expression(CustomExpression) +//! .objective(ZeroObjective { zero: 0.0 }) +//! .constraint(MyVehicleCapacities(vec![26.0, 40_000.0])) //! .build(); //! //! // Define options for the solver. @@ -87,8 +79,8 @@ //! let solver = SolverBuilder::new() //! .options(options) //! .model(model) -//! .plan(initial_solution) -//! .operator(CustomOperator) +//! .solution(initial_solution) +//! .operator(SimpleOperator {}) //! .build(); //! //! let best_solution = solver.solve(); @@ -116,15 +108,15 @@ //! # `Solver` //! //! The `Solver` is designed to implement specific defaults that can be extended from. Override internal -//! options and express your `Model` and `Solver` with custom objectives, constraints, expressions, -//! and operators. By default, this is a ALNS (Adaptive Large Neighborhood Search) solver that uses multiple -//! strategies to explore the solution space. +//! options and express your `Model` and `Solver` with custom objectives, constraints, and operators. By +//! default, this is a ALNS (Adaptive Large Neighborhood Search) solver that uses multiple strategies to +//! explore the solution space. //! //! # `Model` //! //! The `Model` struct represents the vehicle routing problem instance to be solved. It contains all //! of the input data as well as the definitions for the model. Inputs include stops, vehicles, and -//! a distance matrix. Objectives, constraints, and expressions are used to define and extend the model. +//! a distance matrix. //! //! # `Solution` //! @@ -158,9 +150,7 @@ //! Every model implements some number of expressions that are used for internal calculations. mod model; +mod operator; +mod random; mod solution; mod solver; - -pub use model::{Constraint, Expression, Model, ModelBuilder, Objective}; -pub use solution::{Plan, Solution}; -pub use solver::{Operator, Solver, SolverBuilder, SolverOptions}; diff --git a/crates/solver_vrp/src/model.rs b/crates/solver_vrp/src/model.rs index 3aef25d..0322ebe 100644 --- a/crates/solver_vrp/src/model.rs +++ b/crates/solver_vrp/src/model.rs @@ -25,7 +25,6 @@ pub trait Expression { fn compute(&self, model: &Model, solution: &Solution, plan: &Plan) -> f64; } -#[derive(Default)] pub struct Model { data: ModelData, objectives: Objectives, @@ -36,7 +35,7 @@ pub struct Model { impl Model { #[must_use] pub fn new() -> Self { - Self::default() + ModelBuilder::new().build() } #[must_use] @@ -75,6 +74,16 @@ impl Model { } } +impl Default for Model { + fn default() -> Self { + ModelBuilder::new() + .objective(UnplannedObjective {}) + .constraint(VehicleCapacityConstraint {}) + .constraint(VehicleCompatibilityConstraint {}) + .build() + } +} + #[derive(Default)] pub struct ModelData { stops: Stops, @@ -255,7 +264,7 @@ pub struct UnplannedObjective; impl Objective for UnplannedObjective { fn name(&self) -> String { - String::from("Unplanned Objective") + String::from("unplanned") } fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { @@ -263,66 +272,27 @@ impl Objective for UnplannedObjective { } } -pub enum VehicleCapacityConstraint { - MaxWeight, - MaxVolume, -} +pub struct VehicleCapacityConstraint {} -pub enum VehicleCompatibilityConstraint { - Match, -} +pub struct VehicleCompatibilityConstraint {} impl Constraint for VehicleCapacityConstraint { fn name(&self) -> String { - match self { - VehicleCapacityConstraint::MaxWeight => { - String::from("Vehicle Capacity Constraint (Max Weight)") - } - VehicleCapacityConstraint::MaxVolume => { - String::from("Vehicle Capacity Constraint (Max Volume)") - } - } + String::from("vehicle_capacity") } fn is_feasible(&self, model: &Model, solution: &Solution, plan: &Plan) -> bool { - match self { - VehicleCapacityConstraint::MaxWeight => true, - VehicleCapacityConstraint::MaxVolume => true, - } + true } } impl Constraint for VehicleCompatibilityConstraint { fn name(&self) -> String { - match self { - VehicleCompatibilityConstraint::Match => { - String::from("Vehicle Compatibility Constraint (Match)") - } - } + String::from("vehicle_compatibility") } fn is_feasible(&self, model: &Model, solution: &Solution, plan: &Plan) -> bool { - match self { - VehicleCompatibilityConstraint::Match => true, - } - } -} - -pub enum DistanceExpression { - Meters, -} - -impl Expression for DistanceExpression { - fn name(&self) -> String { - match self { - DistanceExpression::Meters => String::from("Distance Expression (Meters)"), - } - } - - fn compute(&self, _model: &Model, _solution: &Solution, _plan: &Plan) -> f64 { - match self { - DistanceExpression::Meters => 0.0, - } + true } } @@ -485,7 +455,7 @@ mod tests { } #[test] - fn test_model() { + fn test_model_build_and_access() { let stop = Stop::new(1, Location::new(1, 10.0, 20.0), vec![5.0]); let vehicle = Vehicle::new(1, vec![10.0]); let distance_matrix = DistanceMatrix::new(vec![vec![0.0, 1.0], vec![1.0, 0.0]]); @@ -494,58 +464,99 @@ mod tests { .stop(stop) .vehicle(vehicle) .distance_matrix(distance_matrix) - .objective(UnplannedObjective) - .objective(TestObjective) - .constraint(VehicleCapacityConstraint::MaxVolume) - .constraint(VehicleCompatibilityConstraint::Match) - .constraint(TestConstraint) - .expression(DistanceExpression::Meters) - .expression(TestExpression) + .objective(UnplannedObjective {}) + .objective(TestObjective {}) + .constraint(VehicleCapacityConstraint {}) + .constraint(VehicleCompatibilityConstraint {}) + .constraint(TestConstraint {}) + .expression(TestExpression {}) .build(); assert_eq!(model.stops().len(), 1); assert_eq!(model.vehicles().len(), 1); assert!(model.distance_matrix().is_some()); + } + #[test] + fn test_model_objective_count() { + let model = ModelBuilder::new() + .objective(UnplannedObjective) + .objective(TestObjective) + .build(); assert_eq!(model.objectives().len(), 2); + } + + #[test] + fn test_model_constraint_count() { + let model = ModelBuilder::new() + .constraint(VehicleCapacityConstraint {}) + .constraint(VehicleCompatibilityConstraint {}) + .constraint(TestConstraint {}) + .build(); assert_eq!(model.constraints().len(), 3); - assert_eq!(model.expressions().len(), 2); + } + + #[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() + .objective(UnplannedObjective) + .objective(TestObjective) + .build(); assert_eq!( model.objectives().first().map(|o| o.name()), - Some(String::from("Unplanned Objective")) + Some(String::from("unplanned")) ); assert_eq!( model.objectives().get(1).map(|o| o.name()), Some(String::from("Test Objective")) ); + } + + #[test] + fn test_model_constraint_names() { + let model = ModelBuilder::new() + .constraint(VehicleCapacityConstraint {}) + .constraint(VehicleCompatibilityConstraint {}) + .constraint(TestConstraint {}) + .build(); assert_eq!( model.constraints().first().map(|c| c.name()), - Some(String::from("Vehicle Capacity Constraint (Max Volume)")) + Some(String::from("vehicle_capacity")) ); assert_eq!( model.constraints().get(1).map(|c| c.name()), - Some(String::from("Vehicle Compatibility Constraint (Match)")) + Some(String::from("vehicle_compatibility")) ); assert_eq!( model.constraints().get(2).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("Distance Expression (Meters)")) + Some(String::from("Test Expression")) ); } #[test] - fn test_dag() { - let mut dag = DirectedAcyclicGraph::with_capacity(3); - dag.add_arc(0, 1); - dag.add_arc(1, 2); - assert_eq!(dag.edges().len(), 3); - assert_eq!(dag.edges()[0], vec![1]); - assert_eq!(dag.edges()[1], vec![2]); - assert_eq!(dag.edges()[2], vec![]); - assert_eq!(dag.arcs().len(), 2); + fn test_graph() { + let mut graph = DirectedAcyclicGraph::with_capacity(3); + graph.add_arc(0, 1); + graph.add_arc(1, 2); + assert_eq!(graph.edges().len(), 3); + assert_eq!(graph.edges()[0], vec![1]); + assert_eq!(graph.edges()[1], vec![2]); + assert_eq!(graph.edges()[2], vec![]); + assert_eq!(graph.arcs().len(), 2); } } diff --git a/crates/solver_vrp/src/operator.rs b/crates/solver_vrp/src/operator.rs new file mode 100644 index 0000000..175b98c --- /dev/null +++ b/crates/solver_vrp/src/operator.rs @@ -0,0 +1,143 @@ +use crate::model::Model; +use crate::random::Random; +use crate::solution::{Plan, Solution}; + +pub trait Operator { + /// Name of the operator. + fn name(&self) -> String; + /// Executes the operator to generate a new solution. + fn execute(&self, model: &Model, solution: &Solution, random: &mut Random) -> Plan; + /// Chance of applying the operator. + fn chance(&self) -> f64 { + 1.0 + } +} + +#[derive(Default)] +pub struct Operators(Vec>); + +impl Operators { + pub fn new() -> Self { + Self::default() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn first(&self) -> Option<&dyn Operator> { + self.0.first().map(AsRef::as_ref) + } + + pub fn get(&self, index: usize) -> Option<&dyn Operator> { + self.0.get(index).map(AsRef::as_ref) + } + + pub fn push(&mut self, operator: Box) { + self.0.push(operator); + } + + pub fn iter(&self) -> std::slice::Iter<'_, Box> { + self.0.iter() + } +} + +pub struct RepairOperator { + pub parameters: OperatorParameters, +} + +impl Default for RepairOperator { + fn default() -> Self { + Self { + parameters: OperatorParameters::new(1.0, 1.0), + } + } +} + +impl Operator for RepairOperator { + fn name(&self) -> String { + String::from("repair") + } + + fn execute(&self, model: &Model, solution: &Solution, random: &mut Random) -> Plan { + repair_nearest(model, solution, &self.parameters, random) + } + + fn chance(&self) -> f64 { + self.parameters.chance_f64 + } +} + +pub struct DestroyOperator { + pub parameters: OperatorParameters, +} + +impl Default for DestroyOperator { + fn default() -> Self { + Self { + parameters: OperatorParameters::new(1.0, 1.0), + } + } +} + +impl Operator for DestroyOperator { + fn name(&self) -> String { + String::from("destroy") + } + + fn execute(&self, model: &Model, solution: &Solution, random: &mut Random) -> Plan { + destroy_random(model, solution, &self.parameters, random) + } + + fn chance(&self) -> f64 { + self.parameters.chance_f64 + } +} + +fn repair_random( + _model: &Model, + _solution: &Solution, + _params: &OperatorParameters, + _random: &mut Random, +) -> Plan { + todo!() +} + +fn repair_nearest( + _model: &Model, + _solution: &Solution, + _params: &OperatorParameters, + _random: &mut Random, +) -> Plan { + todo!() +} + +fn destroy_random( + _model: &Model, + _solution: &Solution, + _params: &OperatorParameters, + _random: &mut Random, +) -> Plan { + todo!() +} + +fn destroy_nearest( + _model: &Model, + _solution: &Solution, + _params: &OperatorParameters, + _random: &mut Random, +) -> Plan { + todo!() +} + +pub struct OperatorParameters { + pub value: f64, + pub chance_f64: f64, +} + +impl OperatorParameters { + #[must_use] + pub fn new(value: f64, chance_f64: f64) -> Self { + Self { value, chance_f64 } + } +} diff --git a/crates/solver_vrp/src/random.rs b/crates/solver_vrp/src/random.rs new file mode 100644 index 0000000..6a0bbe8 --- /dev/null +++ b/crates/solver_vrp/src/random.rs @@ -0,0 +1,53 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use rand::{Rng, SeedableRng, rngs::StdRng}; + +pub struct Random { + rng: StdRng, +} + +impl Default for Random { + fn default() -> Self { + Self::new() + } +} + +impl Random { + pub fn new() -> Self { + Self::seed( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + ) + } + + pub fn seed(seed: u64) -> Self { + Self { + rng: StdRng::seed_from_u64(seed), + } + } + + pub fn u32(&mut self) -> u32 { + self.rng.random() + } + + pub fn f64(&mut self) -> f64 { + self.rng.random() + } + + pub fn range_u32(&mut self, low: u32, high: u32) -> u32 { + self.rng.random_range(low..high) + } + + pub fn range_f64(&mut self, low: f64, high: f64) -> f64 { + self.rng.random_range(low..high) + } + + pub fn chance(&mut self, (numerator, denominator): (f64, f64)) -> bool { + if numerator.eq(&denominator) { + return true; + } + self.f64() < (numerator / denominator) + } +} diff --git a/crates/solver_vrp/src/solution.rs b/crates/solver_vrp/src/solution.rs index f2e938d..474d57f 100644 --- a/crates/solver_vrp/src/solution.rs +++ b/crates/solver_vrp/src/solution.rs @@ -28,6 +28,11 @@ impl Solution { self.value } + #[must_use] + pub fn plan(&self, _plan: &Plan) -> Solution { + todo!() + } + #[must_use] pub fn best(self, other: Solution) -> Solution { if self.value < other.value { diff --git a/crates/solver_vrp/src/solver.rs b/crates/solver_vrp/src/solver.rs index 6c7b94d..bd4e079 100644 --- a/crates/solver_vrp/src/solver.rs +++ b/crates/solver_vrp/src/solver.rs @@ -1,14 +1,7 @@ use crate::model::Model; +use crate::operator::{DestroyOperator, Operator, Operators, RepairOperator}; +use crate::random::Random; use crate::solution::Solution; -use rand::prelude::{Rng, SeedableRng, StdRng}; -use std::time::{SystemTime, UNIX_EPOCH}; - -pub trait Operator { - /// Name of the operator. - fn name(&self) -> String; - /// Executes the operator to generate a new solution. - fn execute(&self, model: &Model, solution: &Solution) -> Option; -} pub struct Solver { model: Model, @@ -20,6 +13,18 @@ pub struct Solver { } impl Solver { + #[must_use] + pub fn new() -> Self { + Self { + model: Model::new(), + operators: Operators::new(), + options: SolverOptions::default(), + solution: None, + random: Random::new(), + iteration_count: 0, + } + } + #[must_use] pub fn model(&self) -> &Model { &self.model @@ -42,7 +47,7 @@ impl Solver { #[must_use] pub fn solve(mut self) -> Option { - while self.options.max_iterations > self.iteration_count { + while self.iteration_count < self.options.max_iterations { self.execute_operators(); self.increment_iteration(); } @@ -56,96 +61,62 @@ impl Solver { fn execute_operators(&mut self) { let mut solution = self.solution.take().unwrap_or_default(); for op in self.operators.iter() { - if let Some(s) = op.execute(&self.model, &solution) { - solution = s.best(solution); + if !self.random.chance((op.chance(), 1.0)) { + continue; } + solution = solution + .plan(&op.execute(&self.model, &solution, &mut self.random)) + .best(solution); } self.solution = Some(solution); } } -#[derive(Default)] -pub struct Operators(Vec>); - -impl Operators { - pub fn new() -> Self { - Self::default() - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn first(&self) -> Option<&dyn Operator> { - self.0.first().map(AsRef::as_ref) - } - - pub fn get(&self, index: usize) -> Option<&dyn Operator> { - self.0.get(index).map(AsRef::as_ref) - } - - pub fn push(&mut self, operator: Box) { - self.0.push(operator); - } - - pub fn iter(&self) -> std::slice::Iter<'_, Box> { - self.0.iter() +impl Default for Solver { + fn default() -> Self { + SolverBuilder::new() + .model(Model::default()) + .operator(DestroyOperator::default()) + .operator(RepairOperator::default()) + .options(SolverOptions::default()) + .build() } } #[derive(Default)] pub struct SolverBuilder { - operators: Operators, - options: SolverOptions, + solver: Solver, } impl SolverBuilder { #[must_use] pub fn new() -> Self { - Self::default() + Self { + solver: Solver::new(), + } } #[must_use] pub fn operator(mut self, operator: Op) -> Self { - self.operators.push(Box::new(operator)); + self.solver.operators.push(Box::new(operator)); self } #[must_use] pub fn options(mut self, options: SolverOptions) -> Self { - self.options = options; + self.solver.options = options; self } #[must_use] - pub fn model(self, model: Model) -> SolverBuilderWithModel { - SolverBuilderWithModel { - solver: Solver { - model, - operators: self.operators, - options: self.options, - solution: None, - random: Random::new(), - iteration_count: 0, - }, - } - } -} - -pub struct SolverBuilderWithModel { - solver: Solver, -} - -impl SolverBuilderWithModel { - #[must_use] - pub fn operator(mut self, operator: Op) -> Self { - self.solver.operators.push(Box::new(operator)); + pub fn model(mut self, model: Model) -> SolverBuilder { + self.solver.model = model; self } #[must_use] - pub fn options(mut self, options: SolverOptions) -> Self { - self.solver.options = options; + pub fn solution(mut self, solution: Solution) -> Self { + self.solver.solution = Some(solution); self } @@ -153,11 +124,6 @@ impl SolverBuilderWithModel { pub fn build(self) -> Solver { self.solver } - - pub fn plan(mut self, solution: Solution) -> Self { - self.solver.solution = Some(solution); - self - } } pub struct SolverOptions { @@ -179,145 +145,17 @@ impl Default for SolverOptions { } } -pub enum RepairOperator { - Random(OperatorParameters), - Nearest(OperatorParameters), -} - -impl Operator for RepairOperator { - fn name(&self) -> String { - match self { - Self::Random(_) => String::from("Random Repair Operator"), - Self::Nearest(_) => String::from("Nearest Repair Operator"), - } - } - - fn execute(&self, _model: &Model, _solution: &Solution) -> Option { - None - } -} - -pub enum DestroyOperator { - Random(OperatorParameters), - Nearest(OperatorParameters), -} - -impl Operator for DestroyOperator { - fn name(&self) -> String { - match self { - Self::Random(_) => String::from("Random Destroy Operator"), - Self::Nearest(_) => String::from("Nearest Destroy Operator"), - } - } - - fn execute(&self, _model: &Model, _solution: &Solution) -> Option { - None - } -} - -pub enum ResetOperator { - Full(OperatorParameters), - Partial(OperatorParameters), -} - -impl Operator for ResetOperator { - fn name(&self) -> String { - match self { - Self::Full(_) => String::from("Full Reset Operator"), - Self::Partial(_) => String::from("Partial Reset Operator"), - } - } - - fn execute(&self, _model: &Model, _solution: &Solution) -> Option { - None - } -} - -pub struct OperatorParameters { - value: f64, - chance: f64, -} - -impl OperatorParameters { - #[must_use] - pub fn new(value: f64, chance: f64) -> Self { - Self { value, chance } - } -} - -struct Random { - rng: StdRng, -} - -impl Random { - fn new() -> Self { - Self::seed( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - ) - } - - fn seed(seed: u64) -> Self { - Self { - rng: StdRng::seed_from_u64(seed), - } - } - - fn u32(&mut self) -> u32 { - self.rng.random() - } - - fn f64(&mut self) -> f64 { - self.rng.random() - } - - fn range_u32(&mut self, low: u32, high: u32) -> u32 { - self.rng.random_range(low..high) - } - - fn range_f64(&mut self, low: f64, high: f64) -> f64 { - self.rng.random_range(low..high) - } -} - #[cfg(test)] mod tests { - use crate::{ - ModelBuilder, - model::{ - DistanceExpression, UnplannedObjective, VehicleCapacityConstraint, - VehicleCompatibilityConstraint, - }, - }; - use super::*; #[test] fn test_solver() { let options = SolverOptions::new(10); - - let model = ModelBuilder::new() - .objective(UnplannedObjective) - .constraint(VehicleCapacityConstraint::MaxVolume) - .constraint(VehicleCompatibilityConstraint::Match) - .expression(DistanceExpression::Meters) - .build(); - - let solver = SolverBuilder::new() - .operator(RepairOperator::Random(OperatorParameters::new(1.0, 0.5))) - .operator(RepairOperator::Nearest(OperatorParameters::new(1.0, 0.5))) - .operator(DestroyOperator::Random(OperatorParameters::new(2.0, 0.3))) - .operator(DestroyOperator::Nearest(OperatorParameters::new(2.0, 0.3))) - .operator(ResetOperator::Partial(OperatorParameters::new(3.0, 0.2))) - .operator(ResetOperator::Full(OperatorParameters::new(3.0, 0.2))) - .options(options) - .model(model) - .build(); + let solver = SolverBuilder::default().options(options).build(); assert_eq!(solver.options.max_iterations, 10); - assert_eq!(solver.operators().len(), 6); + assert_eq!(solver.operators().len(), 2); assert_eq!(solver.iteration_count, 0); assert!(solver.solution.is_none()); assert_eq!(solver.model.objectives().len(), 1);