timely/dataflow/operators/core/feedback.rs
1//! Create cycles in a timely dataflow graph.
2
3use crate::Container;
4use crate::dataflow::channels::pact::Pipeline;
5use crate::dataflow::operators::generic::builder_rc::OperatorBuilder;
6use crate::dataflow::scope::Iterative;
7use crate::dataflow::{Stream, Scope};
8use crate::order::Product;
9use crate::progress::frontier::Antichain;
10use crate::progress::{Timestamp, PathSummary};
11
12/// Creates a `Stream` and a `Handle` to later bind the source of that `Stream`.
13pub trait Feedback<'scope, T: Timestamp> {
14
15 /// Creates a [Stream] and a [Handle] to later bind the source of that `Stream`.
16 ///
17 /// The resulting `Stream` will have its data defined by a future call to `connect_loop` with
18 /// its `Handle` passed as an argument. Messages passed through the stream will have the
19 /// elements of their stamps advanced by `summary`. Container contents are not inspected or
20 /// changed; containers whose contents carry times must advance them before the loop.
21 ///
22 /// A message is dropped if its advanced stamp is empty, which includes messages that
23 /// arrive with empty stamps.
24 ///
25 /// # Examples
26 /// ```
27 /// use timely::dataflow::Scope;
28 /// use timely::dataflow::operators::{Feedback, ConnectLoop, ToStream, Concat, Inspect};
29 /// use timely::dataflow::operators::vec::{Map, Filter};
30 ///
31 /// timely::example(|scope| {
32 /// // circulate 0..10, incrementing each, until each reaches 100.
33 /// let (handle, cycle) = scope.feedback(1);
34 /// (0..10).to_stream(scope)
35 /// .container::<Vec<_>>()
36 /// .concat(cycle)
37 /// .inspect(|x| println!("seen: {:?}", x))
38 /// .map(|x| x + 1)
39 /// .filter(|x| *x < 100)
40 /// .connect_loop(handle);
41 /// });
42 /// ```
43 fn feedback<C: Container>(&self, summary: <T as Timestamp>::Summary) -> (Handle<'scope, T, C>, Stream<'scope, T, C>);
44}
45
46/// Creates a `Stream` and a `Handle` to later bind the source of that `Stream`.
47pub trait LoopVariable<'scope, TOuter: Timestamp, TInner: Timestamp> {
48 /// Creates a `Stream` and a `Handle` to later bind the source of that `Stream`.
49 ///
50 /// The resulting `Stream` will have its data defined by a future call to `connect_loop` with
51 /// its `Handle` passed as an argument. Messages passed through the stream will have the
52 /// elements of their stamps advanced by `summary`. Container contents are not inspected or
53 /// changed; containers whose contents carry times must advance them before the loop.
54 ///
55 /// A message is dropped if its advanced stamp is empty, which includes messages that
56 /// arrive with empty stamps.
57 ///
58 /// # Examples
59 /// ```
60 /// use timely::dataflow::Scope;
61 /// use timely::dataflow::operators::{LoopVariable, ConnectLoop, ToStream, Concat, Inspect};
62 /// use timely::dataflow::operators::vec::{Map, Filter};
63 ///
64 /// timely::example(|scope| {
65 /// // circulate 0..10, incrementing each, until each reaches 100.
66 /// scope.iterative::<usize,_,_>(|inner| {
67 /// let (handle, cycle) = inner.loop_variable(1);
68 /// (0..10).to_stream(inner)
69 /// .container::<Vec<_>>()
70 /// .concat(cycle)
71 /// .inspect(|x| println!("seen: {:?}", x))
72 /// .map(|x| x + 1)
73 /// .filter(|x| *x < 100)
74 /// .connect_loop(handle);
75 /// });
76 /// });
77 /// ```
78 fn loop_variable<C: Container>(&self, summary: TInner::Summary) -> (Handle<'scope, Product<TOuter, TInner>, C>, Stream<'scope, Product<TOuter, TInner>, C>);
79}
80
81impl<'scope, T: Timestamp> Feedback<'scope, T> for Scope<'scope, T> {
82
83 fn feedback<C: Container>(&self, summary: <T as Timestamp>::Summary) -> (Handle<'scope, T, C>, Stream<'scope, T, C>) {
84
85 let mut builder = OperatorBuilder::new("Feedback".to_owned(), *self);
86 let (output, stream) = builder.new_output();
87
88 (Handle { builder, summary, output }, stream)
89 }
90}
91
92impl<'scope, TOuter: Timestamp, TInner: Timestamp> LoopVariable<'scope, TOuter, TInner> for Iterative<'scope, TOuter, TInner> {
93 fn loop_variable<C: Container>(&self, summary: TInner::Summary) -> (Handle<'scope, Product<TOuter, TInner>, C>, Stream<'scope, Product<TOuter, TInner>, C>) {
94 self.feedback(Product::new(Default::default(), summary))
95 }
96}
97
98/// Connect a `Stream` to the input of a loop variable.
99pub trait ConnectLoop<'scope, T: Timestamp, C: Container> {
100 /// Connect a `Stream` to be the input of a loop variable.
101 ///
102 /// # Examples
103 /// ```
104 /// use timely::dataflow::Scope;
105 /// use timely::dataflow::operators::{Feedback, ConnectLoop, ToStream, Concat, Inspect};
106 /// use timely::dataflow::operators::vec::{Map, Filter};
107 ///
108 /// timely::example(|scope| {
109 /// // circulate 0..10, incrementing each, until each reaches 100.
110 /// let (handle, cycle) = scope.feedback(1);
111 /// (0..10).to_stream(scope)
112 /// .container::<Vec<_>>()
113 /// .concat(cycle)
114 /// .inspect(|x| println!("seen: {:?}", x))
115 /// .map(|x| x + 1)
116 /// .filter(|x| *x < 100)
117 /// .connect_loop(handle);
118 /// });
119 /// ```
120 fn connect_loop(self, handle: Handle<'scope, T, C>);
121}
122
123impl<'scope, T: Timestamp, C: Container> ConnectLoop<'scope, T, C> for Stream<'scope, T, C> {
124 fn connect_loop(self, handle: Handle<'scope, T, C>) {
125
126 let mut builder = handle.builder;
127 let summary = handle.summary;
128 let mut output = handle.output;
129
130 let mut input = builder.new_input_connection(self, Pipeline, [(0, Antichain::from_elem(summary.clone()))]);
131 builder.set_notify_for(0, crate::progress::operate::FrontierInterest::Never);
132
133 builder.build(move |_capability| move |_frontier| {
134 let mut output = output.activate();
135 input.for_each(|cap, data| {
136 // Advance each stamp element by the summary, discarding elements that
137 // cannot traverse the feedback edge and restoring minimality. The edge
138 // maps capabilities and does not inspect contents: advancing any times
139 // the contents carry, and discarding those that cannot advance, is the
140 // container's responsibility. A message whose advanced stamp is empty
141 // is dropped.
142 let new_caps = cap.stamp()
143 .map_into(|time| summary.results_in(time))
144 .iter()
145 .map(|time| cap.delayed(time, output.output_index()))
146 .collect::<crate::dataflow::operators::CapabilitySet<_>>();
147 if !new_caps.is_empty() {
148 output.give(&new_caps, data);
149 }
150 });
151 });
152 }
153}
154
155/// A handle used to bind the source of a loop variable.
156#[derive(Debug)]
157pub struct Handle<'scope, T: Timestamp, C: Container> {
158 builder: OperatorBuilder<'scope, T>,
159 summary: <T as Timestamp>::Summary,
160 output: crate::dataflow::channels::pushers::Output<T, C>,
161}