End Conditions and hysteresis
Real Channels hover around their thresholds. A Rule that only has when Ends its Event the moment the Condition stops holding, so a weight fluctuating between 490 and 520 around a threshold of 500 produces a new Event every few Samples. An End Condition fixes this: once an Event is Confirmed, it continues until the End Condition holds, whatever when does.
A separate end threshold
Give the End Condition a threshold below the start threshold, and the weight has to fall clearly before the Event Ends:
Rule loadLifted = Rule.named("LoadLifted")
.when(weight.above(500))
.endWhen(weight.below(50));
The gap between the two thresholds is the hysteresis: noise that stays inside it starts no Event and Ends none.
A swinging load
A crane's load cell reports the weight once a second. At 3 s it lifts a load that reads around 500 kg as it swings, at 14 s a deep swing reads 440 kg, and at 22 s the load is set down. LoadLifted starts above 500 kg; the End threshold starts out equal to it, which is no hysteresis at all.
The live chart and its controls need JavaScript. The tables show the Events at the default parameters.
The site cannot run the default parameters: (JavaScript) TypeError: Cannot read properties of undefined (reading 'length')
import io.github.romanhaendler.rbed.*;
import java.time.Duration;
import java.util.Map;
Channel weight = Channel.named("weight");
Rule loadLifted = Rule.named("LoadLifted")
.when(weight.above(500))
.endWhen(weight.below(500)).forDuration(Duration.ofSeconds(0)); // with 0 s, it Ends at once
Detector detector = Detector.builder()
.rules(loadLifted)
.build();
double[] values = {0, 0, 0, 260, 515, 492, 508, 521, 489, 497, 512, 506, 494, 518, 440,
510, 503, 485, 514, 509, 496, 517, 280, 30, 0, 0, 0, 0, 0, 0};
for (int i = 0; i < values.length; i++) {
detector.push(i * 1_000L, Map.of(weight, values[i])); // one Sample per second
}
detector.close();
At the default End threshold of 500 kg the Rule flaps: four Events, the first only one second long.
Things to try
- Move the End threshold down to 450 kg: the noise around 500 no longer Ends anything. One Event runs from 4 s until the swing at 14 s, and a second one until the load is set down.
- Move it to 440 kg: even the swing stays inside the hysteresis, and a single Event runs from 4 s to 22 s, and so it does down to 290 kg. Lower End thresholds End it later: the README's 50 kg at 23 s, the first Sample below 50.
- Lower it to 0 kg: the End Condition never holds. The Event runs until
close(), which Ends it at the last Sample with a Truncated end.
When does the next Event start?
At an End threshold of 500 kg, the weight is above 500 again at 6 s and 7 s, yet no Event starts there. While an Event runs on its End Condition, when is not evaluated, and a new Event only starts once when has been seen not holding at a Sample after the previous one Ended (or right after a data gap longer than maxGap): 489 kg at 8 s does that, and the next Event starts at 10 s. Each Rule has at most one Event in progress, and this is why a Rule with an End Condition does not flicker.
A duration for the End Condition
The End Condition can have its own duration, written right after it. This Ends the Event only once the weight has stayed below 450 for a second:
Rule loadLifted = Rule.named("LoadLifted")
.when(weight.above(500))
.endWhen(weight.below(450)).forDuration(Duration.ofSeconds(1));
Set the End threshold to 450 kg and the End duration to 1 s: the swing at 14 s lasts a single Sample, so it no longer Ends the Event, and the first Event now lasts until the load is set down.
The Event's end() is where the End Condition started holding, 22 s here, not where its duration was reached, so the Event's extent is what you would draw by hand. The listener's onEnded comes later, once the duration is reached: at 23 s with an End duration of 1 s, at 24 s with 2 s. Until then the Event is still Confirmed, and during(...) of it still holds.
Reference
- End Conditions and hysteresis and Durations: forDuration and forSamples in the README, the canonical text
- Rule in the Javadoc
Methods used: Channel.named, Rule.named, Rule.when, Expression.above, Rule.endWhen, Expression.below, Rule.forDuration, Detector.builder, Detector.Builder.rules, Detector.Builder.build, Detector.push, Detector.close.