solverforge-solver 0.18.0

Solver engine for SolverForge
Documentation
use std::sync::Arc;

use solverforge_core::domain::PlanningSolution;
use solverforge_core::score::SoftScore;

use crate::builder::context::{
    ProviderReasonArena, RawProviderCandidate, RawProviderEdit, RuntimeProviderSlotResolver,
    RuntimeScalarSlot, ScalarVariableSlot, ValueSource,
};

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

impl PlanningSolution for ProviderTestSolution {
    type Score = SoftScore;

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

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

fn first_count(solution: &ProviderTestSolution) -> usize {
    solution.first.len()
}

fn second_count(solution: &ProviderTestSolution) -> usize {
    solution.second.len()
}

fn first_get(solution: &ProviderTestSolution, row: usize, _variable: usize) -> Option<usize> {
    solution.first.get(row).copied().flatten()
}

fn second_get(solution: &ProviderTestSolution, row: usize, _variable: usize) -> Option<usize> {
    solution.second.get(row).copied().flatten()
}

fn first_set(
    solution: &mut ProviderTestSolution,
    row: usize,
    _variable: usize,
    value: Option<usize>,
) {
    solution.first[row] = value;
}

fn second_set(
    solution: &mut ProviderTestSolution,
    row: usize,
    _variable: usize,
    value: Option<usize>,
) {
    solution.second[row] = value;
}

fn first_slot() -> RuntimeScalarSlot<ProviderTestSolution> {
    RuntimeScalarSlot::Static(ScalarVariableSlot::new(
        0,
        0,
        "First",
        first_count,
        "value",
        first_get,
        first_set,
        ValueSource::CountableRange { from: 0, to: 3 },
        false,
    ))
}

fn second_slot() -> RuntimeScalarSlot<ProviderTestSolution> {
    RuntimeScalarSlot::Static(ScalarVariableSlot::new(
        1,
        0,
        "Second",
        second_count,
        "value",
        second_get,
        second_set,
        ValueSource::CountableRange { from: 0, to: 3 },
        false,
    ))
}

fn unqualified_value_edit() -> RawProviderEdit {
    RawProviderEdit {
        entity_class: None,
        variable_name: Arc::from("value"),
        entity_index: 0,
        to_value: Some(1),
    }
}

#[test]
fn unqualified_name_resolves_in_allowed_model_order_not_global_first_match() {
    let first = first_slot();
    let second = second_slot();
    let allowed = vec![second.id()];
    let resolver = RuntimeProviderSlotResolver::new(vec![first, second]).unwrap();
    let solution = ProviderTestSolution {
        score: None,
        first: vec![Some(0)],
        second: vec![Some(0)],
    };

    let mut reasons = ProviderReasonArena::default();
    let resolved = resolver
        .resolve_and_normalize(
            &solution,
            vec![RawProviderCandidate {
                reason: Arc::from("candidate"),
                edits: vec![unqualified_value_edit()],
            }],
            &allowed,
            &mut reasons,
        )
        .unwrap();

    assert_eq!(resolved.len(), 1);
    assert_eq!(resolved[0].edits[0].descriptor_index, 1);
    assert!(matches!(
        &resolved[0].edits[0].slot,
        RuntimeScalarSlot::Static(slot) if slot.entity_type_name == "Second"
    ));
}

#[test]
fn aliases_resolve_before_duplicate_target_filtering() {
    let first = first_slot();
    let second = second_slot();
    let allowed = vec![second.id()];
    let resolver = RuntimeProviderSlotResolver::new(vec![first, second]).unwrap();
    let solution = ProviderTestSolution {
        score: None,
        first: vec![Some(0)],
        second: vec![Some(0)],
    };
    let qualified_alias = RawProviderEdit {
        entity_class: Some(Arc::from("Second")),
        ..unqualified_value_edit()
    };

    let mut reasons = ProviderReasonArena::default();
    let resolved = resolver
        .resolve_and_normalize(
            &solution,
            vec![RawProviderCandidate {
                reason: Arc::from("candidate"),
                edits: vec![unqualified_value_edit(), qualified_alias],
            }],
            &allowed,
            &mut reasons,
        )
        .unwrap();

    assert!(resolved.is_empty());
}