1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! This example shows how to model Pickup and Delivery with Time Windows (PDPTW) variant where
//! each job is represented by pickup and delivery tasks and has time window constraints.
//!
//! The code below looks almost the same as CVPR example and differs only in jobs/vehicle construction
//! and time constraint configuration.
//!
//! Key points here:
//! - how to create PUDO (pickup and drop off) jobs
//! - how to set time window constraints on jobs and vehicles
//!

#[path = "./common/routing.rs"]
mod common;
use crate::common::define_routing_data;

use std::iter::once;
use std::sync::Arc;
use vrp_core::models::common::TimeWindow;
use vrp_core::prelude::*;

/// Specifies a PDPTW problem variant: two PUDO (pick up/drop off) jobs with demand=1 and 1 vehicle with capacity 1
fn define_problem(goal: GoalContext, transport: Arc<dyn TransportCost + Send + Sync>) -> GenericResult<Problem> {
    // build two PUDO (pick up/drop off) jobs with demand=1 and permissive time windows (just to show API usage)
    let pudos = (1..=2)
        .map(|idx| {
            let location_idx = if idx == 1 { 1 } else { 3 };
            MultiBuilder::default()
                .id(format!("pudo{idx}").as_str())
                .add_job(
                    SingleBuilder::default()
                        .demand(Demand::pudo_pickup(1))
                        .times(vec![TimeWindow::new(0., 1000.)])?
                        .duration(10.)?
                        .location(location_idx)?
                        .build()?,
                )
                .add_job(
                    SingleBuilder::default()
                        .demand(Demand::pudo_delivery(1))
                        .times(vec![TimeWindow::new(0., 1000.)])?
                        .duration(10.)?
                        .location(location_idx + 1)?
                        .build()?,
                )
                .build_as_job()
        })
        .collect::<Result<Vec<_>, _>>()?;

    // define a single vehicle with limited capacity
    let vehicle = VehicleBuilder::default()
        .id("v1".to_string().as_str())
        .add_detail(
            VehicleDetailBuilder::default()
                // vehicle starts at location with index 0 in routing matrix
                .set_start_location(0)
                .set_start_time(0.)
                // vehicle should return to location with index 0
                .set_end_location(0)
                .set_end_time(10000.)
                .build()?,
        )
        // the vehicle has capacity=1, so it is forced to do delivery after each pickup
        .capacity(SingleDimLoad::new(1))
        .build()?;

    ProblemBuilder::default()
        .add_jobs(pudos.into_iter())
        .add_vehicles(once(vehicle))
        .with_goal(goal)
        .with_transport_cost(transport)
        .build()
}

/// Defines PDPTW variant as a goal of optimization.
fn define_goal(transport: Arc<dyn TransportCost + Send + Sync>) -> GenericResult<GoalContext> {
    // configure features needed to model PDPTW
    let minimize_unassigned = MinimizeUnassignedBuilder::new("min-unassigned").build()?;
    let capacity_feature = CapacityFeatureBuilder::<SingleDimLoad>::new("capacity").build()?;
    let transport_feature = TransportFeatureBuilder::new("min-distance")
        .set_transport_cost(transport)
        // enable time constraint (not necessary, default behavior, here for demonstration purpose only)
        .set_time_constrained(true)
        .build_minimize_distance()?;

    // configure goal of optimization
    GoalContextBuilder::with_features(&[minimize_unassigned, transport_feature, capacity_feature])?.build()
}

fn main() -> GenericResult<()> {
    // get routing data, see `./common/routing.rs` for details
    let transport = Arc::new(define_routing_data()?);

    // specify PDPTW variant as problem definition and the goal of optimization
    let goal = define_goal(transport.clone())?;
    let problem = Arc::new(define_problem(goal, transport)?);

    // build a solver config with the predefined settings to run 5 secs or 10 generations at most
    let config = VrpConfigBuilder::new(problem.clone())
        .prebuild()?
        .with_max_time(Some(5))
        .with_max_generations(Some(10))
        .build()?;

    // run the VRP solver and get the best known solution
    let solution = Solver::new(problem, config).solve()?;

    assert!(solution.unassigned.is_empty(), "has unassigned jobs, but all jobs must be assigned");
    assert_eq!(solution.routes.len(), 1, "one tour should be there");
    assert_eq!(solution.cost, 1105., "unexpected cost (total distance traveled)");

    // simple way to explore the solution, more advanced are available too
    println!(
        "\nIn solution, locations are visited in the following order:\n{:?}\n",
        solution.get_locations().map(Iterator::collect::<Vec<_>>).collect::<Vec<_>>()
    );

    Ok(())
}