solverforge-scoring 0.19.8

Incremental constraint scoring for SolverForge
Documentation
use std::fmt::Debug;
use std::hash::Hash;

use crate::api::analysis::{ConstraintJustification, DetailedConstraintMatch, EntityRef};
use crate::api::constraint_set::IncrementalConstraint;
use crate::stream::collection_extract::CollectionExtract;
use solverforge_core::score::Score;
use solverforge_core::ConstraintRef;

use super::{CrossTriWeight, Tri};

impl<S, A, B, C, K, EA, EB, EC, KA, KB, KC, F, W, Sc> IncrementalConstraint<S, Sc>
    for Tri<S, A, B, C, K, EA, EB, EC, KA, KB, KC, F, W, Sc>
where
    S: Send + Sync + 'static,
    A: Clone + Debug + Send + Sync + 'static,
    B: Clone + Debug + Send + Sync + 'static,
    C: Clone + Debug + Send + Sync + 'static,
    K: Eq + Hash + Clone + Send + Sync,
    EA: CollectionExtract<S, Item = A> + Send + Sync,
    EB: CollectionExtract<S, Item = B> + Send + Sync,
    EC: CollectionExtract<S, Item = C> + Send + Sync,
    KA: Fn(&A) -> K + Send + Sync,
    KB: Fn(&B) -> K + Send + Sync,
    KC: Fn(&C) -> K + Send + Sync,
    F: Fn(&S, &A, &B, &C, usize, usize, usize) -> bool + Send + Sync,
    W: CrossTriWeight<S, A, B, C, Sc>,
    Sc: Score,
{
    fn evaluate(&self, solution: &S) -> Sc {
        let entities_a = self.extractor_a.extract(solution);
        let entities_b = self.extractor_b.extract(solution);
        let entities_c = self.extractor_c.extract(solution);
        let (b_by_key, c_by_key) = self.build_side_indexes(solution, entities_b, entities_c);
        let mut total = Sc::zero();

        for (a_idx, a) in entities_a.iter().enumerate() {
            if !self.extractor_a.contains(solution, a) {
                continue;
            }
            let key = (self.key_a)(a);
            let Some(b_indices) = b_by_key.get(&key) else {
                continue;
            };
            for &b_idx in b_indices {
                let b = &entities_b[b_idx];
                for &c_idx in self.matching_c_indices_in(&c_by_key, b) {
                    let c = &entities_c[c_idx];
                    if (self.filter)(solution, a, b, c, a_idx, b_idx, c_idx) {
                        total = total
                            + self.compute_score(
                                solution, entities_a, entities_b, entities_c, a_idx, b_idx, c_idx,
                            );
                    }
                }
            }
        }

        total
    }

    fn match_count(&self, solution: &S) -> usize {
        let entities_a = self.extractor_a.extract(solution);
        let entities_b = self.extractor_b.extract(solution);
        let entities_c = self.extractor_c.extract(solution);
        let (b_by_key, c_by_key) = self.build_side_indexes(solution, entities_b, entities_c);
        let mut count = 0;

        for (a_idx, a) in entities_a.iter().enumerate() {
            if !self.extractor_a.contains(solution, a) {
                continue;
            }
            let key = (self.key_a)(a);
            let Some(b_indices) = b_by_key.get(&key) else {
                continue;
            };
            for &b_idx in b_indices {
                let b = &entities_b[b_idx];
                for &c_idx in self.matching_c_indices_in(&c_by_key, b) {
                    let c = &entities_c[c_idx];
                    if (self.filter)(solution, a, b, c, a_idx, b_idx, c_idx) {
                        count += 1;
                    }
                }
            }
        }

        count
    }

    fn initialize(&mut self, solution: &S) -> Sc {
        self.reset();

        let entities_a = self.extractor_a.extract(solution);
        let entities_b = self.extractor_b.extract(solution);
        let entities_c = self.extractor_c.extract(solution);

        self.build_indexes(solution, entities_a, entities_b, entities_c);

        let mut total = Sc::zero();
        for a_idx in 0..entities_a.len() {
            if !self.extractor_a.contains(solution, &entities_a[a_idx]) {
                continue;
            }
            let key = (self.key_a)(&entities_a[a_idx]);
            let b_indices = self.engine.key_indexes_for(1, &key);
            let c_indices = self.engine.key_indexes_for(2, &key);
            for b_idx in b_indices {
                for c_idx in &c_indices {
                    total = total
                        + self.add_match(
                            solution, entities_a, entities_b, entities_c, a_idx, b_idx, *c_idx,
                        );
                }
            }
        }

        total
    }

    fn on_insert(&mut self, solution: &S, entity_index: usize, descriptor_index: usize) -> Sc {
        let a_changed = self
            .a_source
            .assert_localizes(descriptor_index, &self.constraint_ref.name);
        let b_changed = self
            .b_source
            .assert_localizes(descriptor_index, &self.constraint_ref.name);
        let c_changed = self
            .c_source
            .assert_localizes(descriptor_index, &self.constraint_ref.name);
        let mut total = Sc::zero();

        if !a_changed && !b_changed && !c_changed {
            return total;
        }

        let entities_a = self.extractor_a.extract(solution);
        let entities_b = self.extractor_b.extract(solution);
        let entities_c = self.extractor_c.extract(solution);
        if a_changed {
            total =
                total + self.insert_a(solution, entities_a, entities_b, entities_c, entity_index);
        }
        if b_changed {
            total =
                total + self.insert_b(solution, entities_a, entities_b, entities_c, entity_index);
        }
        if c_changed {
            total =
                total + self.insert_c(solution, entities_a, entities_b, entities_c, entity_index);
        }
        total
    }

    fn on_retract(&mut self, _solution: &S, entity_index: usize, descriptor_index: usize) -> Sc {
        let a_changed = self
            .a_source
            .assert_localizes(descriptor_index, &self.constraint_ref.name);
        let b_changed = self
            .b_source
            .assert_localizes(descriptor_index, &self.constraint_ref.name);
        let c_changed = self
            .c_source
            .assert_localizes(descriptor_index, &self.constraint_ref.name);
        let mut total = Sc::zero();

        if !a_changed && !b_changed && !c_changed {
            return total;
        }

        if a_changed {
            total = total + self.retract_a(entity_index);
        }
        if b_changed {
            total = total + self.retract_b(entity_index);
        }
        if c_changed {
            total = total + self.retract_c(entity_index);
        }
        total
    }

    fn reset(&mut self) {
        self.engine.clear();
    }

    fn name(&self) -> &str {
        &self.constraint_ref.name
    }

    fn is_hard(&self) -> bool {
        self.is_hard
    }

    fn constraint_ref(&self) -> &ConstraintRef {
        &self.constraint_ref
    }

    fn get_matches<'a>(&'a self, solution: &S) -> Vec<DetailedConstraintMatch<'a, Sc>> {
        let entities_a = self.extractor_a.extract(solution);
        let entities_b = self.extractor_b.extract(solution);
        let entities_c = self.extractor_c.extract(solution);
        let (b_by_key, c_by_key) = self.build_side_indexes(solution, entities_b, entities_c);
        let cref = self.constraint_ref();

        let mut matches = Vec::new();

        for (a_idx, a) in entities_a.iter().enumerate() {
            if !self.extractor_a.contains(solution, a) {
                continue;
            }
            let key = (self.key_a)(a);
            let Some(b_indices) = b_by_key.get(&key) else {
                continue;
            };
            for &b_idx in b_indices {
                let b = &entities_b[b_idx];
                for &c_idx in self.matching_c_indices_in(&c_by_key, b) {
                    let c = &entities_c[c_idx];
                    if (self.filter)(solution, a, b, c, a_idx, b_idx, c_idx) {
                        let entity_a = EntityRef::new(a);
                        let entity_b = EntityRef::new(b);
                        let entity_c = EntityRef::new(c);
                        let justification =
                            ConstraintJustification::new(vec![entity_a, entity_b, entity_c]);
                        let score = self.compute_score(
                            solution, entities_a, entities_b, entities_c, a_idx, b_idx, c_idx,
                        );
                        matches.push(DetailedConstraintMatch::new(cref, score, justification));
                    }
                }
            }
        }

        matches
    }
}