vrp-core 1.25.0

A core algorithms to solve a Vehicle Routing Problem
Documentation
use super::*;
use crate::helpers::models::solution::test_actor_with_profile;

fn create_matrix_data(
    profile: Profile,
    timestamp: Option<Timestamp>,
    duration: (Duration, usize),
    distance: (Distance, usize),
) -> MatrixData {
    MatrixData {
        index: profile.index,
        timestamp,
        durations: vec![duration.0; duration.1],
        distances: vec![distance.0; distance.1],
    }
}

#[test]
fn can_detect_dimensions_mismatch() {
    assert_eq!(
        create_matrix_transport_cost(vec![
            create_matrix_data(Profile::default(), Some(0.), (0., 2), (0., 2)),
            create_matrix_data(Profile::default(), Some(1.), (0., 1), (0., 2)),
        ])
        .err(),
        Some("distance and duration collections have different length".into())
    );
}

#[test]
fn can_return_error_when_mixing_timestamps() {
    let p0 = Profile::default();
    let p1 = Profile::new(1, None);

    assert_eq!(
        TimeAwareMatrixTransportCost::new(
            vec![create_matrix_data(Profile::default(), None, (0., 1), (0., 1))],
            1,
            NoFallback
        )
        .err(),
        Some("time-aware routing requires all matrices to have timestamp".into())
    );

    assert_eq!(
        TimeAwareMatrixTransportCost::new(
            vec![
                create_matrix_data(p0.clone(), Some(0.), (0., 1), (0., 1)),
                create_matrix_data(p0.clone(), None, (0., 1), (0., 1))
            ],
            1,
            NoFallback
        )
        .err(),
        Some("time-aware routing requires all matrices to have timestamp".into())
    );

    assert_eq!(
        TimeAwareMatrixTransportCost::new(
            vec![create_matrix_data(p0.clone(), Some(0.), (0., 1), (0., 1))],
            1,
            NoFallback
        )
        .err(),
        Some("should not use time aware matrix routing with single matrix".into())
    );

    assert_eq!(
        TimeAwareMatrixTransportCost::new(
            vec![
                create_matrix_data(p0.clone(), Some(0.), (1., 1), (1., 1)), //
                create_matrix_data(p0, Some(1.), (1., 1), (1., 1)),         //
                create_matrix_data(p1, Some(0.), (1., 1), (1., 1)),         //
            ],
            1,
            NoFallback
        )
        .err(),
        Some("should not use time aware matrix routing with single matrix".into())
    );
}

#[test]
fn can_interpolate_durations() {
    let route0 = Route { actor: test_actor_with_profile(0), tour: Default::default() };
    let route1 = Route { actor: test_actor_with_profile(1), tour: Default::default() };
    let p0 = route0.actor.vehicle.profile.clone();
    let p1 = route1.actor.vehicle.profile.clone();

    let costs = TimeAwareMatrixTransportCost::new(
        vec![
            create_matrix_data(p0.clone(), Some(0.), (100., 2), (1., 2)),
            create_matrix_data(p0.clone(), Some(10.), (200., 2), (1., 2)),
            create_matrix_data(p1.clone(), Some(0.), (300., 2), (5., 2)),
            create_matrix_data(p1.clone(), Some(10.), (400., 2), (5., 2)),
        ],
        2,
        NoFallback,
    )
    .unwrap();

    for &(timestamp, duration) in &[(0., 100.), (10., 200.), (15., 200.), (3., 130.), (5., 150.), (7., 170.)] {
        assert_eq!(costs.duration(&route0, 0, 1, TravelTime::Departure(timestamp)), duration);
    }

    for &(timestamp, duration) in &[(0., 300.), (10., 400.), (15., 400.), (3., 330.), (5., 350.), (7., 370.)] {
        assert_eq!(costs.duration(&route1, 0, 1, TravelTime::Departure(timestamp)), duration);
    }

    assert_eq!(costs.distance(&route0, 0, 1, TravelTime::Departure(0.)), 1.);
    assert_eq!(costs.distance(&route1, 0, 1, TravelTime::Departure(0.)), 5.);

    assert_eq!(costs.distance_approx(&p0, 0, 1), 1.);
    assert_eq!(costs.distance_approx(&p1, 0, 1), 5.);
}

mod objective {
    use super::*;
    use crate::construction::heuristics::{InsertionContext, MoveContext};
    use crate::helpers::construction::heuristics::TestInsertionContextBuilder;
    use crate::models::{Feature, FeatureBuilder, FeatureObjective, GoalContextBuilder};
    use rosomaxa::prelude::HeuristicObjective;
    use std::cmp::Ordering;

    struct TestObjective {
        index: usize,
    }

    impl FeatureObjective for TestObjective {
        fn fitness(&self, solution: &InsertionContext) -> Cost {
            solution
                .solution
                .state
                .get_value::<(), Vec<Float>>()
                .and_then(|data| data.get(self.index))
                .cloned()
                .unwrap()
        }

        fn estimate(&self, _: &MoveContext<'_>) -> Cost {
            Cost::default()
        }
    }

    fn create_objective_feature(index: usize) -> Feature {
        FeatureBuilder::default()
            .with_name(format!("test_{index}").as_str())
            .with_objective(TestObjective { index })
            .build()
            .unwrap()
    }

    fn create_individual(data: Vec<Float>) -> InsertionContext {
        TestInsertionContextBuilder::default().with_state(|state| state.set_value::<(), _>(data)).build()
    }

    parameterized_test! {can_use_total_order, (data_a, data_b, expected), {
        can_use_total_order_impl(data_a, data_b, expected);
    }}

    can_use_total_order! {
        case01: (vec![0., 1., 2.], vec![0., 1., 2.], Ordering::Equal),
        case02: (vec![1., 1., 2.], vec![0., 1., 2.], Ordering::Greater),
        case03: (vec![0., 1., 2.], vec![1., 1., 2.], Ordering::Less),
        case04: (vec![0., 1., 2.], vec![0., 2., 2.], Ordering::Less),
        case05: (vec![0., 2., 2.], vec![1., 0., 0.], Ordering::Less),
    }

    fn can_use_total_order_impl(data_a: Vec<Float>, data_b: Vec<Float>, expected: Ordering) {
        let features = vec![create_objective_feature(0), create_objective_feature(1), create_objective_feature(2)];
        let goal_ctx = GoalContextBuilder::with_features(&features)
            .expect("cannot create builder")
            .build()
            .expect("cannot build context");

        let a = create_individual(data_a);
        let b = create_individual(data_b);

        let result = goal_ctx.total_order(&a, &b);

        assert_eq!(result, expected);
    }
}