solverforge-solver 0.15.0

Solver engine for SolverForge
Documentation
use std::fmt::{self, Debug};
use std::marker::PhantomData;

use solverforge_config::{ConstructionHeuristicType, ConstructionObligation};
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;

use super::assignment_candidate::{remaining_required_count, ScalarAssignmentMoveOptions};
use super::assignment_stream::ScalarAssignmentMoveCursor;
use super::move_build::compound_move_for_group_candidate;
use super::placement::{
    assignment_target_binding, group_slot_id, never_completed, placement_for_group_candidate,
    push_or_merge_placement, scalar_slots_for_candidate,
};
use super::placer_stream::{
    assignment_placement_move_limit, next_assignment_placement, next_candidate_placement,
    sort_grouped_placements, AssignmentPlacementGenerator, CandidatePlacementGenerator,
};
use crate::builder::context::{
    ScalarAssignmentBinding, ScalarGroupBinding, ScalarGroupBindingKind, ScalarGroupLimits,
};
use crate::descriptor::ResolvedVariableBinding;
use crate::heuristic::r#move::{CompoundScalarMove, Move};
use crate::phase::construction::capabilities::{
    grouped_heuristic_requires_entity_order, grouped_heuristic_requires_value_order,
};
use crate::phase::construction::{EntityPlacer, Placement};

pub(super) type ScalarGroupPlacement<S> = Placement<S, CompoundScalarMove<S>>;

pub(crate) struct ScalarGroupPlacer<S> {
    group_index: usize,
    group: ScalarGroupBinding<S>,
    scalar_bindings: Vec<ResolvedVariableBinding<S>>,
    limits: ScalarGroupLimits,
    heuristic: ConstructionHeuristicType,
    construction_obligation: ConstructionObligation,
    required_only: bool,
    _phantom: PhantomData<fn() -> S>,
}

impl<S> Clone for ScalarGroupPlacer<S> {
    fn clone(&self) -> Self {
        Self {
            group_index: self.group_index,
            group: self.group.clone(),
            scalar_bindings: self.scalar_bindings.clone(),
            limits: self.limits,
            heuristic: self.heuristic,
            construction_obligation: self.construction_obligation,
            required_only: self.required_only,
            _phantom: PhantomData,
        }
    }
}

impl<S> ScalarGroupPlacer<S> {
    pub(crate) fn new(
        group_index: usize,
        group: ScalarGroupBinding<S>,
        scalar_bindings: Vec<ResolvedVariableBinding<S>>,
        limits: ScalarGroupLimits,
        heuristic: ConstructionHeuristicType,
        construction_obligation: ConstructionObligation,
        required_only: bool,
    ) -> Self {
        Self {
            group_index,
            group,
            scalar_bindings,
            limits,
            heuristic,
            construction_obligation,
            required_only,
            _phantom: PhantomData,
        }
    }
}

impl<S> Debug for ScalarGroupPlacer<S> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ScalarGroupPlacer")
            .field("group_index", &self.group_index)
            .field("group_name", &self.group.group_name)
            .field("limits", &self.limits)
            .field("heuristic", &self.heuristic)
            .field("construction_obligation", &self.construction_obligation)
            .field("required_only", &self.required_only)
            .finish()
    }
}

impl<S> EntityPlacer<S, CompoundScalarMove<S>> for ScalarGroupPlacer<S>
where
    S: PlanningSolution + 'static,
{
    fn get_placements<D: Director<S>>(&self, score_director: &D) -> Vec<ScalarGroupPlacement<S>> {
        let mut keep = never_completed::<S>;
        let mut never_stop = || false;
        let mut generated_moves = 0;
        let mut placements = Vec::new();
        match self.group.kind {
            ScalarGroupBindingKind::Candidates { candidate_provider } => {
                let mut generator =
                    self.candidate_placement_generator(score_director, candidate_provider);
                while let Some(placement) = next_candidate_placement(
                    &mut generator,
                    &mut generated_moves,
                    &mut keep,
                    &mut never_stop,
                ) {
                    placements.push(placement);
                }
            }
            ScalarGroupBindingKind::Assignment(assignment) => {
                if let Some(mut generator) =
                    self.assignment_placement_generator(score_director, assignment)
                {
                    while let Some(placement) = next_assignment_placement(
                        &mut generator,
                        score_director,
                        &mut generated_moves,
                        &mut keep,
                        &mut never_stop,
                    ) {
                        placements.push(placement);
                    }
                }
            }
        }
        placements
    }

    fn get_next_placement<D, IsCompleted, ShouldStop>(
        &self,
        score_director: &D,
        mut is_completed: IsCompleted,
        mut should_stop: ShouldStop,
    ) -> Option<(ScalarGroupPlacement<S>, u64)>
    where
        D: Director<S>,
        IsCompleted: FnMut(&ScalarGroupPlacement<S>) -> bool,
        ShouldStop: FnMut() -> bool,
    {
        let mut generated_moves = 0;
        let placement = match self.group.kind {
            ScalarGroupBindingKind::Candidates { candidate_provider } => {
                let mut generator =
                    self.candidate_placement_generator(score_director, candidate_provider);
                next_candidate_placement(
                    &mut generator,
                    &mut generated_moves,
                    &mut is_completed,
                    &mut should_stop,
                )
            }
            ScalarGroupBindingKind::Assignment(assignment) => {
                let mut generator =
                    self.assignment_placement_generator(score_director, assignment)?;
                next_assignment_placement(
                    &mut generator,
                    score_director,
                    &mut generated_moves,
                    &mut is_completed,
                    &mut should_stop,
                )
            }
        };
        placement.map(|placement| (placement, generated_moves))
    }
}

impl<S> ScalarGroupPlacer<S>
where
    S: PlanningSolution + 'static,
{
    fn candidate_placement_generator<D>(
        &self,
        score_director: &D,
        candidate_provider: crate::builder::context::ScalarCandidateProvider<S>,
    ) -> CandidatePlacementGenerator<S>
    where
        D: Director<S>,
    {
        let solution = score_director.working_solution();
        let raw_candidates = candidate_provider(solution, self.limits);
        let total_limit = self.limits.group_candidate_limit.unwrap_or(usize::MAX);
        if total_limit == 0 {
            return CandidatePlacementGenerator {
                placements: Vec::new().into_iter(),
            };
        }

        let mut seen_candidates = Vec::new();
        let mut placements = Vec::new();
        let mut accepted = 0usize;
        for (sequence, candidate) in raw_candidates.into_iter().enumerate() {
            if accepted >= total_limit {
                break;
            }
            if candidate.edits().is_empty()
                || seen_candidates
                    .iter()
                    .any(|seen_candidate| seen_candidate == &candidate)
            {
                continue;
            }
            let Some((scalar_slots, keep_current_legal, has_unfinished_unassigned_slot)) =
                scalar_slots_for_candidate(
                    &self.group,
                    &self.scalar_bindings,
                    solution,
                    &candidate,
                )
            else {
                continue;
            };
            if !has_unfinished_unassigned_slot {
                continue;
            }
            if grouped_heuristic_requires_entity_order(self.heuristic)
                && candidate.construction_entity_order_key().is_none()
            {
                panic!(
                    "grouped scalar construction heuristic {:?} requires construction entity order metadata",
                    self.heuristic
                );
            }
            if grouped_heuristic_requires_value_order(self.heuristic)
                && candidate.construction_value_order_key().is_none()
            {
                panic!(
                    "grouped scalar construction heuristic {:?} requires construction value order metadata",
                    self.heuristic
                );
            }

            let group_slot = group_slot_id(self.group_index, &candidate, &scalar_slots);
            let Some(mut mov) =
                compound_move_for_group_candidate(&self.group, solution, &candidate)
            else {
                continue;
            };
            if !mov.is_doable(score_director) {
                continue;
            }
            mov = mov.with_construction_value_order_key(candidate.construction_value_order_key());
            let placement = placement_for_group_candidate(
                sequence,
                &candidate,
                group_slot,
                scalar_slots,
                keep_current_legal,
                mov,
            )
            .with_construction_entity_order_key(candidate.construction_entity_order_key());
            push_or_merge_placement(&mut placements, placement);
            seen_candidates.push(candidate);
            accepted += 1;
        }
        sort_grouped_placements(&mut placements, self.heuristic);
        CandidatePlacementGenerator {
            placements: placements.into_iter(),
        }
    }

    fn assignment_placement_generator<D>(
        &self,
        score_director: &D,
        assignment: ScalarAssignmentBinding<S>,
    ) -> Option<AssignmentPlacementGenerator<S>>
    where
        D: Director<S>,
    {
        let solution = score_director.working_solution();
        let Some(target_binding) = assignment_target_binding(&assignment, &self.scalar_bindings)
        else {
            panic!(
                "assignment-backed grouped scalar construction targets unknown scalar slot {}.{}",
                assignment.target.entity_type_name, assignment.target.variable_name
            );
        };
        let options = ScalarAssignmentMoveOptions::for_construction(self.limits);
        if options.max_moves == 0 {
            return None;
        }

        let required_remaining = remaining_required_count(&assignment, solution);
        if self.required_only && required_remaining == 0 {
            return None;
        }
        let target_required = self.required_only || required_remaining > 0;
        let max_moves = assignment_placement_move_limit(
            self.heuristic,
            self.construction_obligation,
            self.required_only,
            assignment.entity_count(solution),
            options,
        );
        let options = options.with_max_moves(max_moves);
        let cursor = if self.required_only {
            ScalarAssignmentMoveCursor::required_construction(assignment, solution.clone(), options)
        } else if target_required {
            ScalarAssignmentMoveCursor::required(assignment, solution.clone(), options)
        } else {
            ScalarAssignmentMoveCursor::optional_construction(assignment, solution.clone(), options)
        };

        Some(AssignmentPlacementGenerator {
            group_index: self.group_index,
            assignment,
            target_binding: target_binding.clone(),
            cursor,
            pending: None,
            options,
            accepted: 0,
        })
    }
}