use crate::construction::features::{JobDemandDimension, VehicleCapacityDimension};
use crate::models::common::{Cost, Demand, Dimensions, Duration, LoadOps, Location, TimeSpan, TimeWindow, Timestamp};
use crate::models::problem::{
Costs, Job, JobIdDimension, JobPermutation, Multi, Place, Single, Vehicle, VehicleDetail, VehicleIdDimension,
VehiclePlace,
};
use rosomaxa::prelude::{GenericError, GenericResult};
use std::sync::Arc;
#[derive(Debug)]
pub struct SingleBuilder(Single);
impl Default for SingleBuilder {
fn default() -> Self {
Self(Single { places: vec![], dimens: Default::default() })
}
}
impl SingleBuilder {
pub fn add_place(mut self, place: Place) -> Self {
self.0.places.push(place);
self
}
pub fn add_places(mut self, places: impl Iterator<Item = Place>) -> Self {
self.0.places.extend(places);
self
}
pub fn id(mut self, id: &str) -> Self {
self.0.dimens.set_job_id(id.to_string());
self
}
pub fn demand<T: LoadOps>(mut self, demand: Demand<T>) -> Self {
self.0.dimens.set_job_demand(demand);
self
}
pub fn dimension(mut self, func: impl FnOnce(&mut Dimensions)) -> Self {
func(&mut self.0.dimens);
self
}
pub fn location(mut self, location: Location) -> GenericResult<Self> {
self.ensure_single_place()?.location = Some(location);
Ok(self)
}
pub fn duration(mut self, duration: Duration) -> GenericResult<Self> {
self.ensure_single_place()?.duration = duration;
Ok(self)
}
pub fn times(mut self, times: Vec<TimeWindow>) -> GenericResult<Self> {
self.ensure_single_place()?.times = times.into_iter().map(TimeSpan::Window).collect();
Ok(self)
}
pub fn build(self) -> GenericResult<Single> {
Ok(self.0)
}
pub fn build_as_job(self) -> GenericResult<Job> {
Ok(Job::Single(Arc::new(self.0)))
}
fn ensure_single_place(&mut self) -> GenericResult<&mut Place> {
if self.0.places.len() > 1 {
return Err("cannot use the simple api with multiple places, use `SingleBuilder::add_place` and `JobPlaceBuilder` instead".into());
}
if self.0.places.is_empty() {
self.0.places.push(empty_place());
}
self.0.places.first_mut().ok_or_else(|| GenericError::from("no places"))
}
}
pub struct JobPlaceBuilder(Place);
impl Default for JobPlaceBuilder {
fn default() -> Self {
Self(empty_place())
}
}
impl JobPlaceBuilder {
pub fn location(mut self, loc: Option<Location>) -> Self {
self.0.location = loc;
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.0.duration = duration;
self
}
pub fn times(mut self, times: Vec<TimeWindow>) -> Self {
self.0.times = times.into_iter().map(TimeSpan::Window).collect();
self
}
pub fn build(self) -> GenericResult<Place> {
Ok(self.0)
}
}
#[derive(Default)]
pub struct MultiBuilder {
jobs: Vec<Arc<Single>>,
dimens: Dimensions,
permutator: Option<Box<dyn JobPermutation + Send + Sync>>,
}
impl MultiBuilder {
pub fn id(mut self, id: &str) -> Self {
self.dimens.set_job_id(id.to_string());
self
}
pub fn add_job(mut self, single: Single) -> Self {
self.jobs.push(Arc::new(single));
self
}
pub fn dimension(mut self, func: impl FnOnce(&mut Dimensions)) -> Self {
func(&mut self.dimens);
self
}
pub fn permutation(mut self, permutation: impl JobPermutation + Send + Sync + 'static) -> Self {
self.permutator = Some(Box::new(permutation));
self
}
pub fn build(self) -> GenericResult<Arc<Multi>> {
if self.jobs.len() < 2 {
return Err("the number of sub-jobs must be 2 or more".into());
}
Ok(if let Some(permutator) = self.permutator {
Multi::new_shared_with_permutator(self.jobs, self.dimens, permutator)
} else {
Multi::new_shared(self.jobs, self.dimens)
})
}
pub fn build_as_job(self) -> GenericResult<Job> {
Ok(Job::Multi(self.build()?))
}
}
fn empty_place() -> Place {
Place { location: None, duration: 0.0, times: vec![TimeSpan::Window(TimeWindow::max())] }
}
pub struct VehicleBuilder(Vehicle);
impl Default for VehicleBuilder {
fn default() -> Self {
Self(Vehicle {
profile: Default::default(),
costs: Costs {
fixed: 0.0,
per_distance: 1.,
per_driving_time: 0.0,
per_waiting_time: 0.0,
per_service_time: 0.0,
},
dimens: Default::default(),
details: vec![],
})
}
}
impl VehicleBuilder {
pub fn id(mut self, id: &str) -> Self {
self.0.dimens.set_vehicle_id(id.to_string());
self
}
pub fn add_detail(mut self, detail: VehicleDetail) -> Self {
self.0.details.push(detail);
self
}
pub fn set_profile_idx(mut self, idx: usize) -> Self {
self.0.profile.index = idx;
self
}
pub fn set_distance_cost(mut self, cost: Cost) -> Self {
self.0.costs.per_distance = cost;
self
}
pub fn set_duration_cost(mut self, cost: Cost) -> Self {
self.0.costs.per_driving_time = cost;
self.0.costs.per_service_time = cost;
self.0.costs.per_waiting_time = cost;
self
}
pub fn capacity<T: LoadOps>(mut self, value: T) -> Self {
self.0.dimens.set_vehicle_capacity(value);
self
}
pub fn dimension(mut self, func: impl FnOnce(&mut Dimensions)) -> Self {
func(&mut self.0.dimens);
self
}
pub fn build(self) -> GenericResult<Vehicle> {
if self.0.details.is_empty() {
Err("at least one vehicle detail needs to be added, use `VehicleDetailBuilder` and `add_detail` function"
.into())
} else {
Ok(self.0)
}
}
}
pub struct VehicleDetailBuilder(VehicleDetail);
impl Default for VehicleDetailBuilder {
fn default() -> Self {
Self(VehicleDetail { start: None, end: None })
}
}
impl VehicleDetailBuilder {
pub fn set_start_location(mut self, location: Location) -> Self {
self.ensure_start().location = location;
self
}
pub fn set_start_time(mut self, earliest: Timestamp) -> Self {
self.ensure_start().time.earliest = Some(earliest);
self.ensure_start().time.latest = Some(earliest);
self
}
pub fn set_start_time_latest(mut self, latest: Timestamp) -> Self {
self.ensure_start().time.latest = Some(latest);
self
}
pub fn set_end_location(mut self, location: Location) -> Self {
self.ensure_end().location = location;
self
}
pub fn set_end_time(mut self, latest: Timestamp) -> Self {
self.ensure_end().time.latest = Some(latest);
self
}
fn ensure_start(&mut self) -> &mut VehiclePlace {
if self.0.start.is_none() {
self.0.start = Some(VehiclePlace { location: 0, time: Default::default() });
}
self.0.start.as_mut().unwrap()
}
fn ensure_end(&mut self) -> &mut VehiclePlace {
if self.0.end.is_none() {
self.0.end = Some(VehiclePlace { location: 0, time: Default::default() });
}
self.0.end.as_mut().unwrap()
}
pub fn build(self) -> GenericResult<VehicleDetail> {
if self.0.start.is_none() {
Err("start place must be defined for vehicle detail".into())
} else {
Ok(self.0)
}
}
}