Skip to main content

dbsp/operator/
plus.rs

1//! Binary plus and minus operators.
2
3use crate::{
4    algebra::{AddAssignByRef, AddByRef, NegByRef},
5    circuit::{
6        Circuit, OwnershipPreference, Scope, Stream,
7        operator_traits::{BinaryOperator, Operator},
8    },
9};
10use std::{borrow::Cow, marker::PhantomData, ops::Neg};
11
12impl<C, D> Stream<C, D>
13where
14    C: Circuit,
15    D: AddByRef + AddAssignByRef + Clone + 'static,
16{
17    /// Apply the [`Plus`] operator to `self` and `other`.
18    /// Adding two indexed Z-sets adds the weights of matching key-value pairs.
19    ///
20    /// The stream type's addition operation must be commutative.
21    ///
22    /// # Examples
23    ///
24    /// ```
25    /// # use dbsp::{
26    /// #   operator::Generator,
27    /// #   Circuit, RootCircuit,
28    /// # };
29    /// let circuit = RootCircuit::build(move |circuit| {
30    ///     // Stream of non-negative values: 0, 1, 2, ...
31    ///     let mut n = 0;
32    ///     let source1 = circuit.add_source(Generator::new(move || {
33    ///         let res = n;
34    ///         n += 1;
35    ///         res
36    ///     }));
37    ///     // Stream of non-positive values: 0, -1, -2, ...
38    ///     let mut n = 0;
39    ///     let source2 = circuit.add_source(Generator::new(move || {
40    ///         let res = n;
41    ///         n -= 1;
42    ///         res
43    ///     }));
44    ///     // Compute pairwise sums of values in the stream; the output stream will contain zeros.
45    ///     source1.plus(&source2).inspect(|n| assert_eq!(*n, 0));
46    ///     Ok(())
47    /// })
48    /// .unwrap()
49    /// .0;
50    ///
51    /// # for _ in 0..5 {
52    /// #     circuit.transaction().unwrap();
53    /// # }
54    /// ```
55    #[track_caller]
56    pub fn plus(&self, other: &Stream<C, D>) -> Stream<C, D> {
57        // If both inputs are properly sharded then the sum of those inputs will be
58        // sharded
59        if self.has_sharded_version() && other.has_sharded_version() {
60            self.circuit()
61                .add_binary_operator(
62                    Plus::new(),
63                    &self.try_sharded_version(),
64                    &other.try_sharded_version(),
65                )
66                .mark_sharded()
67        } else {
68            self.circuit().add_binary_operator(Plus::new(), self, other)
69        }
70    }
71}
72
73impl<C, D> Stream<C, D>
74where
75    C: Circuit,
76    D: AddByRef + AddAssignByRef + Neg<Output = D> + NegByRef + Clone + 'static,
77{
78    /// Apply the [`Minus`] operator to `self` and `other`.
79    /// Subtracting two indexed Z-sets subtracts the weights of matching
80    /// key-value pairs.
81    #[track_caller]
82    pub fn minus(&self, other: &Stream<C, D>) -> Stream<C, D> {
83        // If both inputs are properly sharded then the difference of those inputs will
84        // be sharded
85        if self.has_sharded_version() && other.has_sharded_version() {
86            self.circuit()
87                .add_binary_operator(
88                    Minus::new(),
89                    &self.try_sharded_version(),
90                    &other.try_sharded_version(),
91                )
92                .mark_sharded()
93        } else {
94            self.circuit()
95                .add_binary_operator(Minus::new(), self, other)
96        }
97    }
98}
99
100/// Operator that computes the sum of values in its two input streams at each
101/// timestamp.
102///
103/// The stream type's addition operation must be commutative.
104pub struct Plus<D> {
105    phantom: PhantomData<D>,
106}
107
108impl<D> Default for Plus<D> {
109    fn default() -> Self {
110        Self {
111            phantom: PhantomData,
112        }
113    }
114}
115
116impl<D> Plus<D> {
117    pub const fn new() -> Self {
118        Self {
119            phantom: PhantomData,
120        }
121    }
122}
123
124impl<D> Operator for Plus<D>
125where
126    D: 'static,
127{
128    fn name(&self) -> Cow<'static, str> {
129        Cow::from("Plus")
130    }
131
132    fn fixedpoint(&self, _scope: Scope) -> bool {
133        true
134    }
135}
136
137impl<D> BinaryOperator<D, D, D> for Plus<D>
138where
139    D: AddByRef + AddAssignByRef + Clone + 'static,
140{
141    async fn eval(&mut self, i1: &D, i2: &D) -> D {
142        i1.add_by_ref(i2)
143    }
144
145    async fn eval_owned_and_ref(&mut self, mut i1: D, i2: &D) -> D {
146        i1.add_assign_by_ref(i2);
147        i1
148    }
149
150    async fn eval_ref_and_owned(&mut self, i1: &D, mut i2: D) -> D {
151        i2.add_assign_by_ref(i1);
152        i2
153    }
154
155    async fn eval_owned(&mut self, i1: D, i2: D) -> D {
156        i1.add_by_ref(&i2)
157    }
158
159    fn input_preference(&self) -> (OwnershipPreference, OwnershipPreference) {
160        (
161            OwnershipPreference::PREFER_OWNED,
162            OwnershipPreference::PREFER_OWNED,
163        )
164    }
165}
166
167/// Operator that computes the difference of values in its two input streams at
168/// each timestamp.
169pub struct Minus<D> {
170    phantom: PhantomData<D>,
171}
172
173impl<D> Default for Minus<D> {
174    fn default() -> Self {
175        Self {
176            phantom: PhantomData,
177        }
178    }
179}
180
181impl<D> Minus<D> {
182    pub const fn new() -> Self {
183        Self {
184            phantom: PhantomData,
185        }
186    }
187}
188
189impl<D> Operator for Minus<D>
190where
191    D: 'static,
192{
193    fn name(&self) -> Cow<'static, str> {
194        Cow::from("Minus")
195    }
196
197    fn fixedpoint(&self, _scope: Scope) -> bool {
198        true
199    }
200}
201
202// TODO: Add `subtract` operation to `GroupValue`, which
203// can be more efficient than negate followed by plus.
204impl<D> BinaryOperator<D, D, D> for Minus<D>
205where
206    D: AddByRef + AddAssignByRef + Neg<Output = D> + NegByRef + Clone + 'static,
207{
208    async fn eval(&mut self, i1: &D, i2: &D) -> D {
209        let mut i2neg = i2.neg_by_ref();
210        i2neg.add_assign_by_ref(i1);
211        i2neg
212    }
213
214    async fn eval_owned_and_ref(&mut self, i1: D, i2: &D) -> D {
215        i1.add_by_ref(&i2.neg_by_ref())
216    }
217
218    async fn eval_ref_and_owned(&mut self, i1: &D, i2: D) -> D {
219        i2.neg().add_by_ref(i1)
220    }
221
222    async fn eval_owned(&mut self, i1: D, i2: D) -> D {
223        i1.add_by_ref(&i2.neg())
224    }
225
226    fn input_preference(&self) -> (OwnershipPreference, OwnershipPreference) {
227        (
228            OwnershipPreference::PREFER_OWNED,
229            OwnershipPreference::PREFER_OWNED,
230        )
231    }
232}
233
234#[cfg(test)]
235mod test {
236    use crate::{
237        Circuit, RootCircuit,
238        algebra::HasZero,
239        circuit::OwnershipPreference,
240        operator::{Generator, Inspect},
241        typed_batch::OrdZSet,
242        zset,
243    };
244
245    #[test]
246    fn scalar_plus() {
247        let circuit = RootCircuit::build(move |circuit| {
248            let mut n = 0;
249            let source1 = circuit.add_source(Generator::new(move || {
250                let res = n;
251                n += 1;
252                res
253            }));
254            let mut n = 100;
255            let source2 = circuit.add_source(Generator::new(move || {
256                let res = n;
257                n -= 1;
258                res
259            }));
260            source1.plus(&source2).inspect(|n| assert_eq!(*n, 100));
261            Ok(())
262        })
263        .unwrap()
264        .0;
265
266        for _ in 0..100 {
267            circuit.transaction().unwrap();
268        }
269    }
270
271    #[test]
272    #[cfg_attr(miri, ignore)]
273    fn zset_plus() {
274        let build_plus_circuit = |circuit: &RootCircuit| {
275            let mut s = <OrdZSet<_>>::zero();
276            let delta = zset! { 5 => 1};
277            let source1 = circuit.add_source(Generator::new(move || {
278                s = s.merge(&delta);
279                s.clone()
280            }));
281            let mut s = <OrdZSet<_>>::zero();
282            let delta = zset! { 5 => -1};
283            let source2 = circuit.add_source(Generator::new(move || {
284                s = s.merge(&delta);
285                s.clone()
286            }));
287            source1
288                .plus(&source2)
289                .inspect(|s| assert_eq!(s, &<OrdZSet<u64>>::zero()));
290            (source1, source2)
291        };
292
293        let build_minus_circuit = |circuit: &RootCircuit| {
294            let mut s = <OrdZSet<_>>::zero();
295            let delta = zset! { 5 => 1};
296            let source1 = circuit.add_source(Generator::new(move || {
297                s = s.merge(&delta);
298                s.clone()
299            }));
300            let mut s = <OrdZSet<_>>::zero();
301            let delta = zset! { 5 => 1};
302            let source2 = circuit.add_source(Generator::new(move || {
303                s = s.merge(&delta);
304                s.clone()
305            }));
306            source1
307                .minus(&source2)
308                .inspect(|s| assert_eq!(s, &<OrdZSet<_>>::zero()));
309            (source1, source2)
310        };
311        // Allow `Plus` to consume both streams by value.
312        let circuit = RootCircuit::build(move |circuit| {
313            build_plus_circuit(circuit);
314            build_minus_circuit(circuit);
315            Ok(())
316        })
317        .unwrap()
318        .0;
319
320        for _ in 0..100 {
321            circuit.transaction().unwrap();
322        }
323
324        // Only consume source2 by value.
325        let circuit = RootCircuit::build(move |circuit| {
326            let (source1, _source2) = build_plus_circuit(circuit);
327            circuit.add_unary_operator_with_preference(
328                Inspect::new(|_| {}),
329                &source1,
330                OwnershipPreference::STRONGLY_PREFER_OWNED,
331            );
332            let (source3, _source4) = build_minus_circuit(circuit);
333            circuit.add_unary_operator_with_preference(
334                Inspect::new(|_| {}),
335                &source3,
336                OwnershipPreference::STRONGLY_PREFER_OWNED,
337            );
338            Ok(())
339        })
340        .unwrap()
341        .0;
342
343        for _ in 0..100 {
344            circuit.transaction().unwrap();
345        }
346
347        // Only consume source1 by value.
348        let circuit = RootCircuit::build(move |circuit| {
349            let (_source1, source2) = build_plus_circuit(circuit);
350            circuit.add_unary_operator_with_preference(
351                Inspect::new(|_| {}),
352                &source2,
353                OwnershipPreference::STRONGLY_PREFER_OWNED,
354            );
355
356            let (_source3, source4) = build_minus_circuit(circuit);
357            circuit.add_unary_operator_with_preference(
358                Inspect::new(|_| {}),
359                &source4,
360                OwnershipPreference::STRONGLY_PREFER_OWNED,
361            );
362            Ok(())
363        })
364        .unwrap()
365        .0;
366
367        for _ in 0..100 {
368            circuit.transaction().unwrap();
369        }
370
371        // Consume both streams by reference.
372        let circuit = RootCircuit::build(move |circuit| {
373            let (source1, source2) = build_plus_circuit(circuit);
374            circuit.add_unary_operator_with_preference(
375                Inspect::new(|_| {}),
376                &source1,
377                OwnershipPreference::STRONGLY_PREFER_OWNED,
378            );
379            circuit.add_unary_operator_with_preference(
380                Inspect::new(|_| {}),
381                &source2,
382                OwnershipPreference::STRONGLY_PREFER_OWNED,
383            );
384
385            let (source3, source4) = build_minus_circuit(circuit);
386            circuit.add_unary_operator_with_preference(
387                Inspect::new(|_| {}),
388                &source3,
389                OwnershipPreference::STRONGLY_PREFER_OWNED,
390            );
391            circuit.add_unary_operator_with_preference(
392                Inspect::new(|_| {}),
393                &source4,
394                OwnershipPreference::STRONGLY_PREFER_OWNED,
395            );
396            Ok(())
397        })
398        .unwrap()
399        .0;
400
401        for _ in 0..100 {
402            circuit.transaction().unwrap();
403        }
404    }
405}