From ea44bf39cdbf44d25b21583bc1219372939afa88 Mon Sep 17 00:00:00 2001 From: NewBornRustacean Date: Sat, 2 May 2026 07:18:57 +0900 Subject: [PATCH] early return and test case fto verify it --- .../cross_bi_incremental/incremental.rs | 12 ++- .../src/constraint/tests/cross_bi_incr.rs | 77 ++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/crates/solverforge-scoring/src/constraint/cross_bi_incremental/incremental.rs b/crates/solverforge-scoring/src/constraint/cross_bi_incremental/incremental.rs index 54b5c604..4c1a5d75 100644 --- a/crates/solverforge-scoring/src/constraint/cross_bi_incremental/incremental.rs +++ b/crates/solverforge-scoring/src/constraint/cross_bi_incremental/incremental.rs @@ -88,9 +88,14 @@ where let b_changed = self .b_source .assert_localizes(descriptor_index, &self.constraint_ref.name); + let mut total = Sc::zero(); + + if !a_changed && !b_changed { + return total; + } + let entities_a = self.extractor_a.extract(solution); let entities_b = self.extractor_b.extract(solution); - let mut total = Sc::zero(); if a_changed { total = total + self.insert_a(solution, entities_a, entities_b, entity_index); } @@ -108,6 +113,11 @@ where .b_source .assert_localizes(descriptor_index, &self.constraint_ref.name); let mut total = Sc::zero(); + + if !a_changed && !b_changed { + return total; + } + if a_changed { total = total + self.retract_a(entity_index); } diff --git a/crates/solverforge-scoring/src/constraint/tests/cross_bi_incr.rs b/crates/solverforge-scoring/src/constraint/tests/cross_bi_incr.rs index 7d588b03..edab23ad 100644 --- a/crates/solverforge-scoring/src/constraint/tests/cross_bi_incr.rs +++ b/crates/solverforge-scoring/src/constraint/tests/cross_bi_incr.rs @@ -1,6 +1,11 @@ +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; + use crate::api::constraint_set::IncrementalConstraint; use crate::constraint::IncrementalCrossBiConstraint; -use crate::stream::collection_extract::{source, ChangeSource}; +use crate::stream::collection_extract::{source, ChangeSource, CollectionExtract}; use crate::stream::joiner::equal_bi; use crate::stream::ConstraintFactory; use solverforge_core::score::{Score, SoftScore}; @@ -24,6 +29,42 @@ struct Schedule { employees: Vec, } +#[derive(Clone)] +struct CountingShiftExtract { + calls: Arc, +} + +impl CollectionExtract for CountingShiftExtract { + type Item = Shift; + + fn extract<'s>(&self, schedule: &'s Schedule) -> &'s [Self::Item] { + self.calls.fetch_add(1, Ordering::Relaxed); + schedule.shifts.as_slice() + } + + fn change_source(&self) -> ChangeSource { + ChangeSource::Descriptor(0) + } +} + +#[derive(Clone)] +struct CountingEmployeeExtract { + calls: Arc, +} + +impl CollectionExtract for CountingEmployeeExtract { + type Item = Employee; + + fn extract<'s>(&self, schedule: &'s Schedule) -> &'s [Self::Item] { + self.calls.fetch_add(1, Ordering::Relaxed); + schedule.employees.as_slice() + } + + fn change_source(&self) -> ChangeSource { + ChangeSource::Descriptor(1) + } +} + fn create_unavailable_employee_constraint() -> impl IncrementalConstraint { IncrementalCrossBiConstraint::new( ConstraintRef::new("", "Unavailable employee"), @@ -65,6 +106,40 @@ fn sample_schedule() -> Schedule { } } +#[test] +fn cross_bi_unrelated_insert_skips_extractors() { + let shift_extract_calls = Arc::new(AtomicUsize::new(0)); + let employee_extract_calls = Arc::new(AtomicUsize::new(0)); + let mut constraint = IncrementalCrossBiConstraint::new( + ConstraintRef::new("", "Unavailable employee"), + ImpactType::Penalty, + CountingShiftExtract { + calls: Arc::clone(&shift_extract_calls), + }, + CountingEmployeeExtract { + calls: Arc::clone(&employee_extract_calls), + }, + |shift: &Shift| shift.employee_id, + |employee: &Employee| Some(employee.id), + |_schedule: &Schedule, shift: &Shift, employee: &Employee| { + shift.employee_id.is_some() && employee.unavailable_days.contains(&shift.day) + }, + |_schedule: &Schedule, _shift_idx: usize, _employee_idx: usize| SoftScore::of(1), + false, + ); + let schedule = sample_schedule(); + + assert_eq!(constraint.initialize(&schedule), SoftScore::of(-1)); + shift_extract_calls.store(0, Ordering::Relaxed); + employee_extract_calls.store(0, Ordering::Relaxed); + + let delta = constraint.on_insert(&schedule, 0, 2); + + assert_eq!(delta, SoftScore::zero()); + assert_eq!(shift_extract_calls.load(Ordering::Relaxed), 0); + assert_eq!(employee_extract_calls.load(Ordering::Relaxed), 0); +} + #[test] fn test_cross_bi_evaluate_works_without_initialize() { let constraint = create_unavailable_employee_constraint();