solverforge-solver 0.18.0

Solver engine for SolverForge
Documentation
#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use solverforge_core::domain::{
        DynamicListAccess, DynamicListAccessCapabilities, DynamicListMetadata,
        DynamicListMetadataCapabilities, EntityClassId, PlanningSolution, VariableId,
    };
    use solverforge_core::score::SoftScore;

    use crate::builder::context::list_access::{RouteAccess, SavingsAccess};
    use crate::builder::context::{RuntimeListSlot, SavingsMetricClassPolicy};
    use crate::heuristic::selector::nearby_list_change::DefaultCrossEntityDistanceMeter;

    #[derive(Clone, Debug)]
    struct Plan {
        score: Option<SoftScore>,
        routes: Vec<Vec<usize>>,
    }

    impl PlanningSolution for Plan {
        type Score = SoftScore;

        fn score(&self) -> Option<Self::Score> {
            self.score
        }

        fn set_score(&mut self, score: Option<Self::Score>) {
            self.score = score;
        }
    }

    #[derive(Debug)]
    struct Access;

    impl DynamicListAccess<Plan> for Access {
        fn entity_class(&self) -> EntityClassId {
            EntityClassId(0)
        }

        fn variable(&self) -> VariableId {
            VariableId(0)
        }

        fn entity_count(&self, solution: &Plan) -> usize {
            solution.routes.len()
        }

        fn element_count(&self, solution: &Plan) -> usize {
            solution
                .routes
                .iter()
                .flatten()
                .copied()
                .max()
                .map_or(0, |value| value + 1)
        }

        fn element(&self, _solution: &Plan, element_index: usize) -> Option<usize> {
            Some(element_index)
        }

        fn assigned_elements(&self, solution: &Plan) -> Vec<usize> {
            solution.routes.iter().flatten().copied().collect()
        }

        fn len(&self, solution: &Plan, row: usize) -> usize {
            solution.routes[row].len()
        }

        fn get(&self, solution: &Plan, row: usize, position: usize) -> Option<usize> {
            solution.routes.get(row)?.get(position).copied()
        }

        fn insert(&self, solution: &mut Plan, row: usize, position: usize, value: usize) {
            solution.routes[row].insert(position, value);
        }

        fn remove(&self, solution: &mut Plan, row: usize, position: usize) -> Option<usize> {
            (position < solution.routes[row].len()).then(|| solution.routes[row].remove(position))
        }

        fn capabilities(&self) -> DynamicListAccessCapabilities {
            DynamicListAccessCapabilities {
                replace: true,
                ..DynamicListAccessCapabilities::default()
            }
        }

        fn replace(&self, solution: &mut Plan, row: usize, values: Vec<usize>) -> bool {
            let Some(route) = solution.routes.get_mut(row) else {
                return false;
            };
            *route = values;
            true
        }
    }

    #[derive(Debug)]
    struct Metadata;

    impl DynamicListMetadata<Plan> for Metadata {
        fn entity_class(&self) -> EntityClassId {
            EntityClassId(0)
        }

        fn variable(&self) -> VariableId {
            VariableId(0)
        }

        fn capabilities(&self) -> DynamicListMetadataCapabilities {
            DynamicListMetadataCapabilities {
                route: true,
                savings: true,
                ..DynamicListMetadataCapabilities::default()
            }
        }

        fn element_owner(&self, _solution: &Plan, _element: usize) -> Option<usize> {
            None
        }

        fn construction_order_key(&self, _solution: &Plan, _element: usize) -> Option<i64> {
            None
        }

        fn precedence_duration(&self, _solution: &Plan, _element: usize) -> Option<usize> {
            None
        }

        fn extend_precedence_successors(
            &self,
            _solution: &Plan,
            _element: usize,
            _successors: &mut Vec<usize>,
        ) -> bool {
            false
        }

        fn cross_position_distance(
            &self,
            _solution: &Plan,
            _from_entity: usize,
            _from_position: usize,
            _to_entity: usize,
            _to_position: usize,
        ) -> Option<f64> {
            None
        }

        fn intra_position_distance(
            &self,
            _solution: &Plan,
            _entity: usize,
            _from_position: usize,
            _to_position: usize,
        ) -> Option<f64> {
            None
        }

        fn route_depot(&self, _solution: &Plan, _entity: usize) -> Option<usize> {
            Some(7)
        }

        fn route_distance(
            &self,
            _solution: &Plan,
            _entity: usize,
            _from: usize,
            _to: usize,
        ) -> Option<i64> {
            Some(101)
        }

        fn route_feasible(
            &self,
            _solution: &Plan,
            _entity: usize,
            _route: &[usize],
        ) -> Option<bool> {
            Some(false)
        }

        fn savings_depot(&self, _solution: &Plan, _entity: usize) -> Option<usize> {
            Some(3)
        }

        fn savings_metric_class(&self, _solution: &Plan, _entity: usize) -> Option<usize> {
            Some(11)
        }

        fn savings_distance(
            &self,
            _solution: &Plan,
            _entity: usize,
            _from: usize,
            _to: usize,
        ) -> Option<i64> {
            Some(202)
        }

        fn savings_feasible(
            &self,
            _solution: &Plan,
            _entity: usize,
            _route: &[usize],
        ) -> Option<bool> {
            Some(true)
        }
    }

    fn slot() -> RuntimeListSlot<
        Plan,
        usize,
        DefaultCrossEntityDistanceMeter,
        DefaultCrossEntityDistanceMeter,
    > {
        RuntimeListSlot::from_dynamic(
            solverforge_core::domain::DynamicListVariableSlot::with_access_and_metadata(
                EntityClassId(0),
                VariableId(0),
                "Vehicle",
                "visits",
                Arc::new(Access),
                Arc::new(Metadata),
            )
            .expect("access and metadata identities match"),
        )
    }

    #[test]
    fn route_and_savings_bundles_remain_independent_for_runtime_slots() {
        let slot = slot();
        let plan = Plan {
            score: None,
            routes: vec![vec![4, 9]],
        };

        assert_eq!(RouteAccess::route_depot(&slot, &plan, 0), Ok(7));
        assert_eq!(RouteAccess::route_distance(&slot, &plan, 0, 4, 9), Ok(101));
        assert_eq!(
            RouteAccess::route_feasible(&slot, &plan, 0, &[4, 9]),
            Ok(false)
        );

        assert_eq!(SavingsAccess::savings_depot(&slot, &plan, 0), Ok(3));
        assert_eq!(
            slot.savings_metric_class_policy(),
            SavingsMetricClassPolicy::ExplicitDynamicProvider
        );
        assert_eq!(SavingsAccess::savings_metric_class(&slot, &plan, 0), Ok(11));
        assert_eq!(
            SavingsAccess::savings_distance(&slot, &plan, 0, 4, 9),
            Ok(202)
        );
        assert_eq!(
            SavingsAccess::savings_feasible(&slot, &plan, 0, &[4, 9]),
            Ok(true)
        );
    }
}