pub struct CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>where
Sc: Score,{ /* private fields */ }Expand description
Zero-erasure constraint stream over cross-entity pairs.
CrossBiConstraintStream joins entities from collection A with collection B,
accumulates filters on joined pairs, and finalizes into an
IncrementalCrossBiConstraint via penalize() or reward().
All type parameters are concrete - no trait objects, no Arc allocations.
§Type Parameters
S- Solution typeA- Entity type A (e.g., Shift)B- Entity type B (e.g., Employee)K- Join key typeEA- Extractor function for A entitiesEB- Extractor function for B entitiesKA- Key extractor for AKB- Key extractor for BF- Combined filter typeSc- Score type
Implementations§
Source§impl<S, A, B, K, EA, EB, KA, KB, Sc> CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, TrueFilter, Sc>
impl<S, A, B, K, EA, EB, KA, KB, Sc> CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, TrueFilter, Sc>
Source§impl<S, A, B, K, EA, EB, KA, KB, F, Sc> CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>where
S: Send + Sync + 'static,
A: Clone + Send + Sync + 'static,
B: Clone + Send + Sync + 'static,
K: Eq + Hash + Clone + Send + Sync,
EA: Fn(&S) -> &[A] + Send + Sync,
EB: Fn(&S) -> &[B] + Send + Sync,
KA: Fn(&A) -> K + Send + Sync,
KB: Fn(&B) -> K + Send + Sync,
F: BiFilter<S, A, B>,
Sc: Score + 'static,
impl<S, A, B, K, EA, EB, KA, KB, F, Sc> CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>where
S: Send + Sync + 'static,
A: Clone + Send + Sync + 'static,
B: Clone + Send + Sync + 'static,
K: Eq + Hash + Clone + Send + Sync,
EA: Fn(&S) -> &[A] + Send + Sync,
EB: Fn(&S) -> &[B] + Send + Sync,
KA: Fn(&A) -> K + Send + Sync,
KB: Fn(&B) -> K + Send + Sync,
F: BiFilter<S, A, B>,
Sc: Score + 'static,
Sourcepub fn new_with_filter(
extractor_a: EA,
extractor_b: EB,
key_a: KA,
key_b: KB,
filter: F,
) -> Self
pub fn new_with_filter( extractor_a: EA, extractor_b: EB, key_a: KA, key_b: KB, filter: F, ) -> Self
Creates a new cross-bi constraint stream with an initial filter.
This is called from UniConstraintStream::join() when there are
accumulated filters on the uni-stream.
Sourcepub fn filter<P>(
self,
predicate: P,
) -> CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, AndBiFilter<F, FnBiFilter<impl Fn(&S, &A, &B) -> bool + Send + Sync>>, Sc>
pub fn filter<P>( self, predicate: P, ) -> CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, AndBiFilter<F, FnBiFilter<impl Fn(&S, &A, &B) -> bool + Send + Sync>>, Sc>
Adds a filter predicate to the stream.
Multiple filters are combined with AND semantics at compile time. Each filter adds a new type layer, preserving zero-erasure.
§Example
// Chain multiple filters on a cross-bi stream
let filtered = stream
.filter(|shift, emp| shift.employee_id.is_some())
.filter(|shift, emp| !emp.available);Sourcepub fn penalize(
self,
weight: Sc,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, impl Fn(&A, &B) -> Sc + Send + Sync, Sc>where
Sc: Copy,
pub fn penalize(
self,
weight: Sc,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, impl Fn(&A, &B) -> Sc + Send + Sync, Sc>where
Sc: Copy,
Penalizes each matching pair with a fixed weight.
Sourcepub fn penalize_with<W>(
self,
weight_fn: W,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
pub fn penalize_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
Penalizes each matching pair with a dynamic weight.
Sourcepub fn penalize_hard_with<W>(
self,
weight_fn: W,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
pub fn penalize_hard_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
Penalizes each matching pair with a dynamic weight, explicitly marked as hard.
Sourcepub fn reward(
self,
weight: Sc,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, impl Fn(&A, &B) -> Sc + Send + Sync, Sc>where
Sc: Copy,
pub fn reward(
self,
weight: Sc,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, impl Fn(&A, &B) -> Sc + Send + Sync, Sc>where
Sc: Copy,
Rewards each matching pair with a fixed weight.
Sourcepub fn reward_with<W>(
self,
weight_fn: W,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
pub fn reward_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
Rewards each matching pair with a dynamic weight.
Sourcepub fn reward_hard_with<W>(
self,
weight_fn: W,
) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
pub fn reward_hard_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
Rewards each matching pair with a dynamic weight, explicitly marked as hard.
Sourcepub fn flatten_last<C, CK, Flatten, CKeyFn, ALookup>(
self,
flatten: Flatten,
c_key_fn: CKeyFn,
a_lookup_fn: ALookup,
) -> FlattenedBiConstraintStream<S, A, B, C, K, CK, EA, EB, KA, KB, Flatten, CKeyFn, ALookup, TrueFilter, Sc>
pub fn flatten_last<C, CK, Flatten, CKeyFn, ALookup>( self, flatten: Flatten, c_key_fn: CKeyFn, a_lookup_fn: ALookup, ) -> FlattenedBiConstraintStream<S, A, B, C, K, CK, EA, EB, KA, KB, Flatten, CKeyFn, ALookup, TrueFilter, Sc>
Expands items from entity B into separate (A, C) pairs with O(1) lookup.
Pre-indexes C items by key for O(1) lookup on entity changes.
§Arguments
flatten- Extracts a slice of C items from Bc_key_fn- Extracts the index key from each C itema_lookup_fn- Extracts the lookup key from A (must match c_key type)
§Example
use solverforge_scoring::stream::ConstraintFactory;
use solverforge_scoring::stream::joiner::equal_bi;
use solverforge_scoring::api::constraint_set::IncrementalConstraint;
use solverforge_core::score::SimpleScore;
#[derive(Clone)]
struct Employee {
id: usize,
unavailable_days: Vec<u32>,
}
#[derive(Clone)]
struct Shift {
employee_id: Option<usize>,
day: u32,
}
#[derive(Clone)]
struct Schedule {
shifts: Vec<Shift>,
employees: Vec<Employee>,
}
// O(1) lookup by indexing unavailable_days by day number
let constraint = ConstraintFactory::<Schedule, SimpleScore>::new()
.for_each(|s: &Schedule| &s.shifts)
.join(
|s: &Schedule| &s.employees,
equal_bi(|shift: &Shift| shift.employee_id, |emp: &Employee| Some(emp.id)),
)
.flatten_last(
|emp: &Employee| emp.unavailable_days.as_slice(),
|day: &u32| *day, // C → index key
|shift: &Shift| shift.day, // A → lookup key
)
.filter(|shift: &Shift, day: &u32| shift.employee_id.is_some() && shift.day == *day)
.penalize(SimpleScore::of(1))
.as_constraint("Unavailable employee");
let schedule = Schedule {
shifts: vec![
Shift { employee_id: Some(0), day: 5 },
Shift { employee_id: Some(0), day: 10 },
],
employees: vec![
Employee { id: 0, unavailable_days: vec![5, 15] },
],
};
// Day 5 shift matches via O(1) lookup
assert_eq!(constraint.evaluate(&schedule), SimpleScore::of(-1));