/// <summary> /// Finds patterns of A on the left followed immediately by B on the right (with no other intermediate events), occurring within (strictly less than) a given time duration /// </summary> /// <typeparam name="TKey">Key type</typeparam> /// <typeparam name="TPayload">Payload type for left input</typeparam> /// <typeparam name="TPayload2">Payload type for right input</typeparam> /// <typeparam name="TResult">Result type</typeparam> /// <param name="stream">Left input stream</param> /// <param name="stream2">Right input stream</param> /// <param name="firstMatch">First element in pattern</param> /// <param name="secondMatch">Second element in pattern</param> /// <param name="resultSelector">Compose result tuple using matching input events</param> /// <param name="withinDuration">Pattern occurs within (strictly less than) given time duration</param> /// <returns>Pattern result stream</returns> public static IStreamable <TKey, TResult> FollowedByImmediate <TKey, TPayload, TPayload2, TResult>( this IStreamable <TKey, TPayload> stream, IStreamable <TKey, TPayload2> stream2, Expression <Func <TPayload, bool> > firstMatch, Expression <Func <TPayload2, bool> > secondMatch, Expression <Func <TPayload, TPayload2, TResult> > resultSelector, long withinDuration) { if (withinDuration >= 0) { // Set match duration if (withinDuration < 2) { throw new Exception("Duration has to be at least 2 chronons"); } if (withinDuration > StreamEvent.MaxSyncTime) { throw new Exception("Duration is too large"); } stream = stream.AlterEventDuration(withinDuration - 1); } // Clip the stream with itself, to make it a signal stream var clippedStream = stream.ClipEventDuration(stream2); // Shift the left side (first match) by one chronon, set right side (second match) to one chronon duration, join to find matches var result = clippedStream.Multicast(xs => xs.Where(firstMatch).ShiftEventLifetime(1).Join(stream2.Where(secondMatch).AlterEventDuration(1), resultSelector)); return(result); }
/// <summary> /// Finds patterns of events that are repeated consecutively /// </summary> /// <typeparam name="TKey">Key type</typeparam> /// <typeparam name="TPayload">Payload type</typeparam> /// <typeparam name="TResult">Result type</typeparam> /// <param name="stream">Input stream</param> /// <param name="resultSelector">Compose result tuple using matching input events</param> /// <returns>Pattern result stream</returns> public static IStreamable <TKey, TResult> ConsecutivePairs <TKey, TPayload, TResult>( this IStreamable <TKey, TPayload> stream, Expression <Func <TPayload, TPayload, TResult> > resultSelector) { // Clip the stream with itself, to make it a signal stream var clippedStream = stream.AlterEventDuration(StreamEvent.InfinitySyncTime).Multicast(xs => xs.ClipEventDuration(xs)); // Shift the left side (first match) by one chronon, set right side (second match) to one chronon duration, join to find pairs var result = clippedStream.Multicast(xs => xs.ShiftEventLifetime(1).Join(xs.AlterEventDuration(1), resultSelector)); return(result); }
/// <summary> /// Index continuous intervals of the stream. /// </summary> /// <param name="source">Input stream</param> /// <param name="gap_tol">Gap tolerance</param> /// <returns>Events corresponding to continuous intervals</returns> public static IStreamable <TKey, Unit> Index <TKey, TPayload>( this IStreamable <TKey, TPayload> source, long gap_tol ) { return(source .AlterEventDuration((s, e) => e - s + gap_tol) .Multicast(s => s.ClipEventDuration(s)) .Select(e => Unit.Default) .Stitch() .AlterEventDuration((s, e) => e - s - gap_tol) ); }
/// <summary> /// Performs the 'Chop' operator to chop (partition) gap intervals across beat boundaries with gap tolerance. /// </summary> /// <param name="source">Input stream</param> /// <param name="offset">Stream offset</param> /// <param name="period">Beat period to chop</param> /// <param name="gap_tol">Gap tolerance</param> /// <returns>Signal stream after gaps chopped</returns> public static IStreamable <TKey, TPayload> Chop <TKey, TPayload>( this IStreamable <TKey, TPayload> source, long offset, long period, long gap_tol) { gap_tol = Math.Max(period, gap_tol); return(source .AlterEventDuration((s, e) => e - s + gap_tol) .Multicast(t => t.ClipEventDuration(t)) .AlterEventDuration((s, e) => (e - s > gap_tol) ? period : e - s) .Chop(offset, period) ); }
private static void JoinPointsTest(bool fixedInterval = false) { var left = new Subject <StreamEvent <string> >(); var right = new Subject <StreamEvent <string> >(); var qc = new QueryContainer(); IStreamable <Empty, string> leftInput = qc.RegisterInput(left); IStreamable <Empty, string> rightInput = qc.RegisterInput(right); if (fixedInterval) { leftInput = leftInput.AlterEventDuration(1); rightInput = rightInput.AlterEventDuration(1); } var query = leftInput.Join( rightInput, l => (l != null ? l[0].ToString() : null), r => (r != null ? r[0].ToString() : null), (l, r) => $"{l},{r}"); var output = new List <StreamEvent <string> >(); qc.RegisterOutput(query).ForEachAsync(o => output.Add(o)); var process = qc.Restore(); // Should match and egress immediately left.OnNext(StreamEvent.CreatePoint(100, "A1")); right.OnNext(StreamEvent.CreatePoint(100, "A2")); process.Flush(); var expected = new StreamEvent <string>[] { StreamEvent.CreatePoint(100, "A1,A2"), }; Assert.IsTrue(expected.SequenceEqual(output)); output.Clear(); left.OnCompleted(); right.OnCompleted(); }
/// <summary> /// ConsecutivePairs with gap_tolerance /// </summary> /// <typeparam name="TKey">Key type</typeparam> /// <typeparam name="TPayload">Payload type</typeparam> /// <typeparam name="TResult">Result type</typeparam> /// <param name="stream">Input stream</param> /// <param name="resultSelector">Compose result tuple using matching input events</param> /// <param name="gap_tol">Gap tolerance</param> /// <param name="period">Period of input stream (default: 1)</param> /// <returns>Pattern result stream</returns> public static IStreamable <TKey, TResult> ConsecutivePairs <TKey, TPayload, TResult>( this IStreamable <TKey, TPayload> stream, Expression <Func <TPayload, TPayload, TResult> > resultSelector, long gap_tol, long period = 1) { var clippedStream = stream .AlterEventDuration((s, e) => e - s + gap_tol) .Multicast(xs => xs.ClipEventDuration(xs)) .AlterEventDuration((s, e) => (e - s > gap_tol) ? period : e - s) ; var result = clippedStream .Multicast(xs => xs .ShiftEventLifetime(1) .Join(xs.AlterEventDuration(1), resultSelector)); return(result); }