CrossBiConstraintStream

Struct CrossBiConstraintStream 

Source
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 type
  • A - Entity type A (e.g., Shift)
  • B - Entity type B (e.g., Employee)
  • K - Join key type
  • EA - Extractor function for A entities
  • EB - Extractor function for B entities
  • KA - Key extractor for A
  • KB - Key extractor for B
  • F - Combined filter type
  • Sc - 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>
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, Sc: Score + 'static,

Source

pub fn new(extractor_a: EA, extractor_b: EB, key_a: KA, key_b: KB) -> Self

Creates a new zero-erasure cross-bi constraint stream.

This is typically called from UniConstraintStream::join().

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,

Source

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.

Source

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>
where P: Fn(&A, &B) -> bool + Send + Sync,

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);
Source

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.

Source

pub fn penalize_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
where W: Fn(&A, &B) -> Sc + Send + Sync,

Penalizes each matching pair with a dynamic weight.

Source

pub fn penalize_hard_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
where W: Fn(&A, &B) -> Sc + Send + Sync,

Penalizes each matching pair with a dynamic weight, explicitly marked as hard.

Source

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.

Source

pub fn reward_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
where W: Fn(&A, &B) -> Sc + Send + Sync,

Rewards each matching pair with a dynamic weight.

Source

pub fn reward_hard_with<W>( self, weight_fn: W, ) -> CrossBiConstraintBuilder<S, A, B, K, EA, EB, KA, KB, F, W, Sc>
where W: Fn(&A, &B) -> Sc + Send + Sync,

Rewards each matching pair with a dynamic weight, explicitly marked as hard.

Source

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>
where C: Clone + Send + Sync + 'static, CK: Eq + Hash + Clone + Send + Sync, Flatten: Fn(&B) -> &[C] + Send + Sync, CKeyFn: Fn(&C) -> CK + Send + Sync, ALookup: Fn(&A) -> CK + Send + Sync,

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 B
  • c_key_fn - Extracts the index key from each C item
  • a_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));

Trait Implementations§

Source§

impl<S, A, B, K, EA, EB, KA, KB, F, Sc: Score> Debug for CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<S, A, B, K, EA, EB, KA, KB, F, Sc> Freeze for CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>
where EA: Freeze, EB: Freeze, KA: Freeze, KB: Freeze, F: Freeze,

§

impl<S, A, B, K, EA, EB, KA, KB, F, Sc> RefUnwindSafe for CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>

§

impl<S, A, B, K, EA, EB, KA, KB, F, Sc> Send for CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>
where EA: Send, EB: Send, KA: Send, KB: Send, F: Send, S: Send, A: Send, B: Send, K: Send,

§

impl<S, A, B, K, EA, EB, KA, KB, F, Sc> Sync for CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>
where EA: Sync, EB: Sync, KA: Sync, KB: Sync, F: Sync, S: Sync, A: Sync, B: Sync, K: Sync,

§

impl<S, A, B, K, EA, EB, KA, KB, F, Sc> Unpin for CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>
where EA: Unpin, EB: Unpin, KA: Unpin, KB: Unpin, F: Unpin, S: Unpin, A: Unpin, B: Unpin, K: Unpin, Sc: Unpin,

§

impl<S, A, B, K, EA, EB, KA, KB, F, Sc> UnwindSafe for CrossBiConstraintStream<S, A, B, K, EA, EB, KA, KB, F, Sc>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.