Skip to main content

solverforge_scoring/stream/
existence_stream.rs

1/* Semi/anti existence streams over the count-backed membership engine.
2
3An existence constraint is a semi/anti join: it retains only left rows whose
4matching-right key is alive (or dead, for `NotExists`). There is no third
5retention engine — the relationship is a named pair of key adapters over the
6two operators' views, the left is a collection over the left stream (whose
7membership is the stream's own filter), the right is a collection over the
8right stream (children expanded in place by the key adapter), and scoring is
9the generic `OperatorTerminal`.
10*/
11
12use std::marker::PhantomData;
13
14use solverforge_core::score::Score;
15use solverforge_core::{ConstraintRef, ImpactType};
16
17use super::relational::operator::ExistenceNode;
18use super::relational::Leaf;
19use super::weighting_support::ConstraintWeight;
20use crate::constraint::relational::OperatorTerminal;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ExistenceMode {
24    Exists,
25    NotExists,
26}
27
28/* Zero-erasure existence stream: a left operator, a right operator, and the
29named key adapters between them. `named()` builds the count-backed
30`ExistenceNode` and its generic terminal. */
31pub struct ExistsConstraintStream<S, A, K, L, R, LK, RK, Sc>
32where
33    Sc: Score,
34{
35    pub(super) mode: ExistenceMode,
36    pub(super) left: L,
37    pub(super) right: R,
38    pub(super) left_key: LK,
39    pub(super) right_keys: RK,
40    pub(super) _phantom: PhantomData<(fn() -> S, fn() -> A, fn() -> K, fn() -> Sc)>,
41}
42
43impl<S, A, K, L, R, LK, RK, Sc> ExistsConstraintStream<S, A, K, L, R, LK, RK, Sc>
44where
45    Sc: Score,
46{
47    pub(super) fn new(
48        mode: ExistenceMode,
49        left: L,
50        right: R,
51        left_key: LK,
52        right_keys: RK,
53    ) -> Self {
54        Self {
55            mode,
56            left,
57            right,
58            left_key,
59            right_keys,
60            _phantom: PhantomData,
61        }
62    }
63}
64
65impl<S, A, K, L, R, LK, RK, Sc> ExistsConstraintStream<S, A, K, L, R, LK, RK, Sc>
66where
67    S: Send + Sync + 'static,
68    A: Clone + Send + Sync + 'static,
69    K: Eq + std::hash::Hash + Clone + Send + Sync + 'static,
70    L: super::relational::operator::Operator<S>,
71    for<'a> LK: super::relational::view_plan::ViewKey<L::View<'a>, Key = K>,
72    R: super::relational::operator::Operator<S>,
73    for<'a> RK: crate::stream::relational::operator::RightKeySet<R::View<'a>, Key = K>,
74    Sc: Score + 'static,
75{
76    fn into_weighted_builder<W>(
77        self,
78        impact_type: ImpactType,
79        weight: W,
80        is_hard: bool,
81    ) -> ExistsConstraintBuilder<S, A, K, L, R, LK, RK, W, Sc>
82    where
83        W: for<'a> Fn(&S, &Leaf<'a, A>) -> Sc,
84    {
85        ExistsConstraintBuilder {
86            mode: self.mode,
87            left: self.left,
88            right: self.right,
89            left_key: self.left_key,
90            right_keys: self.right_keys,
91            impact_type,
92            weight,
93            is_hard,
94            _phantom: PhantomData,
95        }
96    }
97
98    pub fn penalize<W>(
99        self,
100        weight: W,
101    ) -> ExistsConstraintBuilder<
102        S,
103        A,
104        K,
105        L,
106        R,
107        LK,
108        RK,
109        impl for<'a> Fn(&S, &Leaf<'a, A>) -> Sc + Send + Sync,
110        Sc,
111    >
112    where
113        W: for<'w> ConstraintWeight<(&'w A,), Sc> + Send + Sync,
114    {
115        let is_hard = weight.is_hard();
116        self.into_weighted_builder(
117            ImpactType::Penalty,
118            move |_s: &S, row: &Leaf<'_, A>| weight.score((row.entity,)),
119            is_hard,
120        )
121    }
122
123    pub fn reward<W>(
124        self,
125        weight: W,
126    ) -> ExistsConstraintBuilder<
127        S,
128        A,
129        K,
130        L,
131        R,
132        LK,
133        RK,
134        impl for<'a> Fn(&S, &Leaf<'a, A>) -> Sc + Send + Sync,
135        Sc,
136    >
137    where
138        W: for<'w> ConstraintWeight<(&'w A,), Sc> + Send + Sync,
139    {
140        let is_hard = weight.is_hard();
141        self.into_weighted_builder(
142            ImpactType::Reward,
143            move |_s: &S, row: &Leaf<'_, A>| weight.score((row.entity,)),
144            is_hard,
145        )
146    }
147}
148
149pub struct ExistsConstraintBuilder<S, A, K, L, R, LK, RK, W, Sc>
150where
151    Sc: Score,
152{
153    mode: ExistenceMode,
154    left: L,
155    right: R,
156    left_key: LK,
157    right_keys: RK,
158    impact_type: ImpactType,
159    weight: W,
160    is_hard: bool,
161    _phantom: PhantomData<(fn() -> S, fn() -> A, fn() -> K, fn() -> Sc)>,
162}
163
164impl<S, A, K, L, R, LK, RK, W, Sc> ExistsConstraintBuilder<S, A, K, L, R, LK, RK, W, Sc>
165where
166    S: Send + Sync + 'static,
167    A: 'static,
168    K: Eq + std::hash::Hash + Clone + Send + Sync + 'static,
169    L: super::relational::operator::Operator<S>,
170    for<'a> LK: super::relational::view_plan::ViewKey<L::View<'a>, Key = K>,
171    R: super::relational::operator::Operator<S>,
172    for<'a> RK: crate::stream::relational::operator::RightKeySet<R::View<'a>, Key = K>,
173    W: for<'a> Fn(&S, &Leaf<'a, A>) -> Sc + Send + Sync,
174    Sc: Score + 'static,
175{
176    pub fn named(
177        self,
178        name: &str,
179    ) -> OperatorTerminal<S, ExistenceNode<S, L, R, K, LK, RK>, W, Sc> {
180        let exists = matches!(self.mode, ExistenceMode::Exists);
181        let node = ExistenceNode::new(
182            self.left,
183            self.right,
184            self.left_key,
185            self.right_keys,
186            exists,
187        );
188        OperatorTerminal::new(
189            ConstraintRef::new("", name),
190            self.impact_type,
191            node,
192            self.weight,
193            self.is_hard,
194        )
195    }
196}
197
198impl<S, A, K, L, R, LK, RK, Sc: Score> std::fmt::Debug
199    for ExistsConstraintStream<S, A, K, L, R, LK, RK, Sc>
200{
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        f.debug_struct("ExistsConstraintStream")
203            .field("mode", &self.mode)
204            .finish()
205    }
206}
207
208impl<S, A, K, L, R, LK, RK, W, Sc: Score> std::fmt::Debug
209    for ExistsConstraintBuilder<S, A, K, L, R, LK, RK, W, Sc>
210{
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        f.debug_struct("ExistsConstraintBuilder")
213            .field("mode", &self.mode)
214            .field("impact_type", &self.impact_type)
215            .finish()
216    }
217}