solverforge-solver 0.12.0

Solver engine for SolverForge
Documentation
use std::any::Any;

use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};

use crate::phase::construction::ConstructionFrontier;

use super::variable::{ResolvedVariableBinding, VariableBinding};

pub(crate) fn collect_bindings(descriptor: &SolutionDescriptor) -> Vec<VariableBinding> {
    let mut bindings = Vec::new();
    for (descriptor_index, entity_descriptor) in descriptor.entity_descriptors.iter().enumerate() {
        let mut variable_index = 0usize;
        for variable in entity_descriptor.genuine_variable_descriptors() {
            let Some(getter) = variable.usize_getter else {
                continue;
            };
            let Some(setter) = variable.usize_setter else {
                continue;
            };
            bindings.push(VariableBinding {
                binding_index: bindings.len(),
                descriptor_index,
                variable_index,
                entity_type_name: entity_descriptor.type_name,
                variable_name: variable.name,
                allows_unassigned: variable.allows_unassigned,
                getter,
                setter,
                value_range_provider: variable.value_range_provider,
                provider: variable.entity_value_provider,
                candidate_values: variable.candidate_values,
                nearby_value_candidates: variable.nearby_value_candidates,
                nearby_entity_candidates: variable.nearby_entity_candidates,
                range_type: variable.value_range_type.clone(),
                nearby_value_distance_meter: variable.nearby_value_distance_meter,
                nearby_entity_distance_meter: variable.nearby_entity_distance_meter,
                construction_entity_order_key: variable.construction_entity_order_key,
                construction_value_order_key: variable.construction_value_order_key,
            });
            variable_index += 1;
        }
    }
    bindings
}

pub(crate) fn find_binding(
    bindings: &[VariableBinding],
    entity_class: Option<&str>,
    variable_name: Option<&str>,
) -> Vec<VariableBinding> {
    bindings
        .iter()
        .filter(|binding| entity_class.is_none_or(|name| name == binding.entity_type_name))
        .filter(|binding| variable_name.is_none_or(|name| name == binding.variable_name))
        .cloned()
        .collect()
}

pub(crate) fn find_resolved_binding<S>(
    bindings: &[ResolvedVariableBinding<S>],
    entity_class: Option<&str>,
    variable_name: Option<&str>,
) -> Vec<ResolvedVariableBinding<S>> {
    bindings
        .iter()
        .filter(|binding| entity_class.is_none_or(|name| name == binding.entity_type_name))
        .filter(|binding| variable_name.is_none_or(|name| name == binding.variable_name))
        .cloned()
        .collect()
}

pub fn descriptor_has_bindings(descriptor: &SolutionDescriptor) -> bool {
    !collect_bindings(descriptor).is_empty()
}

pub(crate) fn scalar_work_remaining_with_frontier<S>(
    descriptor: &SolutionDescriptor,
    frontier: &ConstructionFrontier,
    solution_revision: u64,
    entity_class: Option<&str>,
    variable_name: Option<&str>,
    solution: &S,
) -> bool
where
    S: PlanningSolution + 'static,
{
    let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
    for binding in bindings {
        let Some(entity_count) = descriptor
            .entity_descriptors
            .get(binding.descriptor_index)
            .and_then(|entity| entity.entity_count(solution as &dyn Any))
        else {
            continue;
        };
        for entity_index in 0..entity_count {
            let entity = descriptor
                .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
                .expect("entity lookup failed while checking scalar work");
            if (binding.getter)(entity).is_some()
                || frontier.is_scalar_completed(binding.slot_id(entity_index), solution_revision)
            {
                continue;
            }
            if !binding
                .values_for_entity(descriptor, solution as &dyn Any, entity)
                .is_empty()
            {
                return true;
            }
        }
    }
    false
}

pub fn scalar_work_remaining<S>(
    descriptor: &SolutionDescriptor,
    entity_class: Option<&str>,
    variable_name: Option<&str>,
    solution: &S,
) -> bool
where
    S: PlanningSolution + 'static,
{
    let bindings = find_binding(&collect_bindings(descriptor), entity_class, variable_name);
    for binding in bindings {
        let Some(entity_count) = descriptor
            .entity_descriptors
            .get(binding.descriptor_index)
            .and_then(|entity| entity.entity_count(solution as &dyn Any))
        else {
            continue;
        };
        for entity_index in 0..entity_count {
            let entity = descriptor
                .get_entity(solution as &dyn Any, binding.descriptor_index, entity_index)
                .expect("entity lookup failed while checking scalar work");
            if (binding.getter)(entity).is_none()
                && !binding
                    .values_for_entity(descriptor, solution as &dyn Any, entity)
                    .is_empty()
            {
                return true;
            }
        }
    }
    false
}

pub fn scalar_target_matches(
    descriptor: &SolutionDescriptor,
    entity_class: Option<&str>,
    variable_name: Option<&str>,
) -> bool {
    !find_binding(&collect_bindings(descriptor), entity_class, variable_name).is_empty()
}