diff --git a/blocks/DiscreteStatistics.mon b/blocks/DiscreteStatistics.mon new file mode 100644 index 0000000..5aac0a1 --- /dev/null +++ b/blocks/DiscreteStatistics.mon @@ -0,0 +1,126 @@ +/* + * $Copyright (c) 2020 Software AG, Darmstadt, Germany and/or Software AG USA Inc., Reston, VA, USA, and/or its subsidiaries and/or its affiliates and/or their licensors.$ + * Use, reproduction, transfer, publication or disclosure is prohibited except as specifically provided for in your License Agreement with Software AG + */ + +package apamax.analyticsbuilder.blocks; + +using apama.analyticsbuilder.BlockBase; +using apama.analyticsbuilder.Activation; +using apama.analyticsbuilder.Value; +using apama.analyticsbuilder.TimerParams; +using com.apama.exceptions.Exception; +using apama.analyticsbuilder.L10N; +using apama.analyticsbuilder.Promise; + + +event DiscreteStatistics_$State { + float sum; + float sum_squared; + integer count; + float min; + float max; + + + action reset() { + sum := 0.0; + sum_squared := 0.0; + count := 0; + min := float.INFINITY; + max := -float.INFINITY; + } + + action update(float value) { + sum := sum + value; + sum_squared := sum_squared + value * value; + count := count + 1; + min := float.min(min, value); + max := float.max(max, value); + } +} +/** + * Discrete Statistics + * + * Statistics for discrete measurements. + * + * Generates statistics - minimum, maximum, sum, count, mean and standard deviation for discrete time inputs. + * + * If the sample input is not connected, every re-evaluation will count, including when reset. If connected, block will + * only update when a signal on the sample input is received. A sample and reset can co-incide, in which case the block + * resets its state and then updates for the given value. + * + * @$blockCategory Aggregates + */ +event DiscreteStatistics { + + BlockBase $base; + + + + /** + * Calculates statistics. + * @param $activation The current activation. + * @param $input_value The input value. + * @param $input_sample A new sample is provided. + * @param $input_reset Reset the state of the block. + */ + action $process(Activation $activation, float $input_value, boolean $input_reset, boolean $input_sample, DiscreteStatistics_$State $blockState) { + if $blockState.count = 0 or $input_reset { + $blockState.reset(); + } + if $base.getInputCount("sample") = 0 or $input_sample { + $blockState.update($input_value); + } + $setOutput_sum($activation, $blockState.sum); + $setOutput_count($activation, $blockState.count.toFloat()); + $setOutput_min($activation, $blockState.min); + $setOutput_max($activation, $blockState.max); + float mean := $blockState.sum / $blockState.count.toFloat(); + $setOutput_mean($activation, mean); + $setOutput_standardDeviation($activation, (($blockState.sum_squared / $blockState.count.toFloat()) - (mean * mean)).sqrt()); + } + + /** + * Sum + * + * Sum of the received input values. + */ + action $setOutput_sum; + /** + * Count + * + * Count of the received input values. + */ + action $setOutput_count; + /** + * Mean + * + * Mean of the received input values. + */ + action $setOutput_mean; + /** + * Standard deviation + * + * Standard deviation of the received input values. + */ + action $setOutput_standardDeviation; + /** + * Minimum + * + * Minimum of the received input values. + */ + action $setOutput_min; + /** + * Maximum + * + * Maximum of the received input values. + */ + action $setOutput_max; + + /**Defines type for input reset.*/ + constant string $INPUT_TYPE_reset := "pulse"; + + /**Defines type for input sample.*/ + constant string $INPUT_TYPE_sample := "pulse"; + +} diff --git a/TimeTicker.mon b/cumulocity-blocks/TimeTicker.mon similarity index 98% rename from TimeTicker.mon rename to cumulocity-blocks/TimeTicker.mon index a5d6b9e..78c70a0 100644 --- a/TimeTicker.mon +++ b/cumulocity-blocks/TimeTicker.mon @@ -3,7 +3,7 @@ * Use, reproduction, transfer, publication or disclosure is prohibited except as specifically provided for in your License Agreement with Software AG */ -package apama.analyticskit.blocks.cumulocity; +package apamax.analyticsbuilder.blocks; using apama.analyticsbuilder.BlockBase; using apama.analyticsbuilder.Activation; diff --git a/pysysproject.xml b/pysysproject.xml new file mode 100644 index 0000000..f2e56f5 --- /dev/null +++ b/pysysproject.xml @@ -0,0 +1,33 @@ + + + 1.3.0 + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DiscreteStatistics/pysystest.xml b/tests/DiscreteStatistics/pysystest.xml new file mode 100644 index 0000000..64f4ca7 --- /dev/null +++ b/tests/DiscreteStatistics/pysystest.xml @@ -0,0 +1,27 @@ + + + + + Discrete stats: To check the basic working of the block + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DiscreteStatistics/run.py b/tests/DiscreteStatistics/run.py new file mode 100644 index 0000000..bcb46cc --- /dev/null +++ b/tests/DiscreteStatistics/run.py @@ -0,0 +1,49 @@ +# +# $Copyright (c) 2019 Software AG, Darmstadt, Germany and/or Software AG USA Inc., Reston, VA, USA, and/or its subsidiaries and/or its affiliates and/or their licensors.$ +# This file is licensed under the Apache 2.0 license - see https://www.apache.org/licenses/LICENSE-2.0 +# + +from pysys.constants import * +from apamax.analyticsbuilder.basetest import AnalyticsBuilderBaseTest + + +class PySysTest(AnalyticsBuilderBaseTest): + def execute(self): + correlator = self.startAnalyticsBuilderCorrelator( + blockSourceDir=f'{self.project.SOURCE}/blocks/') + modelId = self.createTestModel('apamax.analyticsbuilder.blocks.DiscreteStatistics', inputs={'value':'float', 'sample':'pulse', 'reset':'pulse'}) + self.sendEventStrings(correlator, + self.timestamp(.9), + self.inputEvent('value', 100, id=modelId), + self.inputEvent('sample', 'true', id=modelId, eplType = 'boolean'), + self.timestamp(1.9), + self.inputEvent('value', 110, id=modelId), + self.inputEvent('sample', 'true', id=modelId, eplType = 'boolean'), + self.timestamp(2.9), + self.inputEvent('value', 120, id=modelId), + self.inputEvent('sample', 'true', id=modelId, eplType = 'boolean'), + self.timestamp(3.9), + self.inputEvent('value', 30, id=modelId), + self.inputEvent('sample', 'true', id=modelId, eplType = 'boolean'), + self.inputEvent('reset', 'true', id=modelId, eplType = 'boolean'), # reset and sample at same time : sum = mean = value + self.timestamp(4.9), + self.inputEvent('reset', 'true', id=modelId, eplType = 'boolean'), # reset on its own : sum = count = 0 (mean and std dev are NaN!) + self.timestamp(6), + ) + + def validate(self): + self.assertGrep('output.evt', expr=self.outputExpr('sum', 100, time=1)) + self.assertGrep('output.evt', expr=self.outputExpr('count', 1, time=1)) + self.assertGrep('output.evt', expr=self.outputExpr('sum', 100, time=1)) + self.assertGrep('output.evt', expr=self.outputExpr('sum', 210, time=2)) + self.assertGrep('output.evt', expr=self.outputExpr('sum', 330, time=3)) + self.assertGrep('output.evt', expr=self.outputExpr('min', 100, time=3)) + self.assertGrep('output.evt', expr=self.outputExpr('max', time=3)) + self.assertGrep('output.evt', expr=self.outputExpr('mean', 110, time=3)) + self.assertGrep('output.evt', expr=self.outputExpr('standardDeviation', 8.164965809277223, time=3)) + self.assertGrep('output.evt', expr=self.outputExpr('sum', 30, time=4)) + self.assertGrep('output.evt', expr=self.outputExpr('count', 1, time=4)) + self.assertGrep('output.evt', expr=self.outputExpr('mean', 30, time=4)) + self.assertGrep('output.evt', expr=self.outputExpr('standardDeviation', 0, time=4)) + + self.assertGrep('output.evt', expr=self.outputExpr('sum', 0, time=5))