From b03af12e67ec1ce9a9904759bb9337cfe66978ca Mon Sep 17 00:00:00 2001
From: borisneubert <>
Date: Sun, 2 Aug 2015 10:09:55 +0000
Subject: [PATCH] TimeSeries: rolling window extensions by jensb (forum #38479)
git-svn-id: https://svn.fhem.de/fhem/trunk@9013 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/FHEM/TimeSeries.pm | 400 ++++++++++++++++++++++++++++++++++++----
1 file changed, 369 insertions(+), 31 deletions(-)
diff --git a/fhem/FHEM/TimeSeries.pm b/fhem/FHEM/TimeSeries.pm
index ecf84861c..618368da4 100644
--- a/fhem/FHEM/TimeSeries.pm
+++ b/fhem/FHEM/TimeSeries.pm
@@ -22,6 +22,22 @@
# along with fhem. If not, see .
#
##############################################################################
+#
+# CHANGES
+#
+# 27.06.2015 Jens Beyer (jensb at forum dot fhem dot de)
+# new: properties holdTime (in), integral (out) and tSeries/vSeries (data buffer)
+# new: defining holdTime will enable data buffer and calculation of moving stat values instead of block stat values
+# modified: method _updatestat requires only one parameter apart from self
+# modified: when property 'method' is set to 'none' _updatestat() will be called with new data value instead of const 1
+#
+# 19.07.2015 Jens Beyer (jensb at forum dot fhem dot de)
+# new: static method selftest
+#
+# 23.07.2015 Jens Beyer (jensb at forum dot fhem dot de)
+# new: method getValue
+#
+##############################################################################
package TimeSeries;
@@ -58,7 +74,8 @@ sub new() {
my $self= {
method => $args->{method} || "none",
autoreset => $args->{autoreset}, # if set, resets series every autoreset seconds
- count => 0, # number of points added
+ holdTime => $args->{holdTime}, # if set, enables data buffer and limits series to holdTime seconds
+ count => 0, # number of points successfully added
lost => 0, # number of points rejected
t0 => undef, # timestamp of first value added
t => undef, # timestamp of last value added
@@ -66,10 +83,13 @@ sub new() {
v => undef, # last value added
min => undef, # smallest value in the series
max => undef, # largest value in the series
+ tSeries => undef,# array of timestamps, used if holdTime is defined
+ vSeries => undef,# array of values, used if holdTime is defined
# statistics
- n => 0, # size of sample
- mean => undef, # arithmetic mean of time-weighted values
- sd => undef, # standard deviation of time-weighted values
+ n => 0, # size of sample (non time weighted) or number of intervals (time weighted)
+ mean => undef, # arithmetic mean of values
+ sd => undef, # standard deviation of values
+ integral => undef, # integral area of all values in the series
_t0 => undef, # same as t0; moved to _t on reset
_t => undef, # same as t but survives a reset
_v => undef, # same as v but survives a reset
@@ -85,14 +105,16 @@ sub new() {
#
sub reset() {
my $self= shift;
+
# statistics
# _t and _v is taken care of in new() and in add()
$self->{n}= 0;
$self->{mean}= undef;
$self->{sd}= undef;
+ $self->{integral}= 0;
$self->{_M}= undef;
$self->{_S}= undef;
- $self->{_t0}= $self->{_t};
+ $self->{_t0}= $self->{_t};
#
$self->{count}= 0;
$self->{lost}= 0;
@@ -102,9 +124,111 @@ sub reset() {
$self->{v}= undef;
$self->{min}= undef;
$self->{max}= undef;
+ #
+ $self->{tSeries}= undef;
+ $self->{vSeries}= undef;
+
+ if (!defined($self->{autoreset})) {
+ $self->{_t0}= undef;
+ $self->{_t}= undef;
+ $self->{_v}= undef;
+ }
}
-sub _updatestat($$$) {
+#
+# trim series depth to holdTime relative to now
+#
+sub trimToHoldTime() {
+ my $self= shift;
+
+ my $n = @{$self->{tSeries}};
+ #main::Debug("TimeSeries::trimToHoldTime: old count=$n\n");
+
+ if (defined($self->{holdTime}) && defined($self->{tSeries})) {
+ # trim series cache depth to holdTime relative to now
+ my $keepTime = time() - $self->{holdTime};
+ my $trimCount = 0;
+ foreach (@{$self->{tSeries}}) {
+ if ($_ >= $keepTime) {
+ last;
+ }
+ $trimCount++;
+ }
+
+ if ($trimCount > 0) {
+ # remove aged out samples
+ splice(@{$self->{tSeries}}, 0, $trimCount);
+ splice(@{$self->{vSeries}}, 0, $trimCount);
+
+ # update properties
+ # - lost is kept untouched because it cannot be consistently manipulated
+ $self->{count} = @{$self->{tSeries}};
+ #main::Debug("TimeSeries::trimToHoldTime: new count=$count before\n");
+ if ($self->{count} > 0) {
+ $self->{t0} = $self->{tSeries}[0];
+ $self->{t} = $self->{tSeries}[$#{$self->{tSeries}}];
+ $self->{v0} = $self->{vSeries}[0];
+ $self->{v} = $self->{vSeries}[$#{$self->{vSeries}}];
+ $self->{_t0}= $self->{t0};
+ $self->{_t} = $self->{t};
+ $self->{_v} = $self->{v};
+ } else {
+ $self->{t0} = undef;
+ $self->{t} = undef;
+ $self->{v0} = undef;
+ $self->{v} = undef;
+ $self->{_t0}= undef;
+ $self->{_t} = undef;
+ $self->{_v} = undef;
+ }
+
+ # reset statistics
+ $self->{n} = 0;
+ $self->{min} = undef;
+ $self->{max} = undef;
+ $self->{mean} = undef;
+ $self->{sd} = undef;
+ $self->{integral}= 0;
+ $self->{_M} = undef;
+ $self->{_S} = undef;
+
+ # rebuild statistic for remaining samples
+ for my $i (0 .. $#{$self->{tSeries}}) {
+ my $tn= $self->{tSeries}[$i];
+ my $vn= $self->{vSeries}[$i];
+
+ # min, max
+ $self->{min}= $vn if(!defined($self->{min}) || $vn< $self->{min});
+ $self->{max}= $vn if(!defined($self->{max}) || $vn> $self->{max});
+
+ # statistics
+ if($self->{method} eq "none") {
+ # no time-weighting
+ $self->_updatestat($vn);
+ } else {
+ # time-weighting
+ if($i > 0) {
+ my $to= $self->{tSeries}[$i-1];
+ my $vo= $self->{vSeries}[$i-1];
+ my $dt= $tn - $to;
+ if($self->{method} eq "const") {
+ # steps
+ $self->_updatestat($vo * $dt);
+ } else {
+ # linear interpolation
+ $self->_updatestat(0.5 * ($vo + $vn) * $dt);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ #my $count = @{$self->{tSeries}};
+ #main::Debug("TimeSeries::trimToHoldTime: new count=$count\n");
+}
+
+sub _updatestat($$) {
my ($self, $V)= @_;
# see Donald Knuth, The Art of Computer Programming, ch. 4.2.2, formulas 14ff.
@@ -112,24 +236,50 @@ sub _updatestat($$$) {
if($n> 1) {
my $M= $self->{_M};
$self->{_M}= $M + ($V - $M) / $n;
- $self->{_S}= $self->{_S} + ($V - $M) * ($V - $self->{_M});
- #main::Debug("V= $V M= $M _M= ".$self->{_M}." _S= " . $self->{_S});
+ $self->{_S}= $self->{_S} + ($V - $M) * ($V - $M);
+ $self->{integral}+= $V;
+ #main::Debug("V= $V M= $M _M= ".$self->{_M}." _S= " .$self->{_S}." int= ".$self->{integral});
} else {
$self->{_M}= $V;
$self->{_S}= 0;
+ $self->{integral}= $V;
}
- #main::Debug("STAT UPD n= $n");
+ #main::Debug("STAT UPD n=$n");
}
#
-# has autoreset period elapsed?
+# has autoreset or holdTime period elapsed?
#
-
sub elapsed($$) {
my ($self, $t)= @_;
- return defined($self->{autoreset}) &&
- defined($self->{_t0}) &&
- ($t - $self->{_t0} >= $self->{autoreset});
+
+ my $duration;
+ if (defined($self->{autoreset})) {
+ $duration = $self->{autoreset};
+ #main::Debug("TimeSeries::elapsed: autoreset=$duration\n");
+ } elsif (defined($self->{holdTime})) {
+ $duration = $self->{holdTime};
+ #main::Debug("TimeSeries::elapsed: holdTime=$duration\n");
+ }
+
+ return defined($duration) && defined($self->{_t0}) && ($t - $self->{_t0} >= $duration);
+}
+
+#
+# reset or trim series
+#
+sub _housekeeping($) {
+ my ($self, $t)= @_;
+
+ if ($self->elapsed($t)) {
+ if (defined($self->{autoreset})) {
+ #main::Debug("TimeSeries::_housekeeping: reset\n");
+ $self->reset();
+ } else {
+ #main::Debug("TimeSeries::_housekeeping: trimToHoldTime\n");
+ $self->trimToHoldTime();
+ }
+ }
}
#
@@ -144,27 +294,35 @@ sub add($$$) {
return; # note: for consistency, the value is not considered at all
}
- # autoreset
- $self->reset() if($self->elapsed($t));
+ # reset or trim series
+ $self->_housekeeping($t);
#main::Debug("ADD ($t,$v)"); ###
+ # add point to data buffer
+ if(defined($self->{holdTime})) {
+ $self->{tSeries}[$self->{count}] = $t;
+ $self->{vSeries}[$self->{count}] = $v;
+ }
+
# count
$self->{count}++;
-
+
# statistics
if($self->{method} eq "none") {
# no time-weighting
- $self->_updatestat(1, $v);
- } elsif(defined($self->{_t})) {
+ $self->_updatestat($v);
+ } else {
# time-weighting
- my $dt= $t - $self->{_t};
- if($self->{method} eq "const") {
- # steps
- $self->_updatestat($self->{_v} * $dt);
- } else {
- # linear interpolation
- $self->_updatestat(0.5 * ($self->{_v} + $v) * $dt);
+ if(defined($self->{_t})) {
+ my $dt= $t - $self->{_t};
+ if($self->{method} eq "const") {
+ # steps
+ $self->_updatestat($self->{_v} * $dt);
+ } else {
+ # linear interpolation
+ $self->_updatestat(0.5 * ($self->{_v} + $v) * $dt);
+ }
}
}
$self->{_t}= $t;
@@ -198,17 +356,183 @@ sub add($$$) {
$self->{sd}= sqrt($self->{_S}/ ($n-1)) / $T if($n> 1);
}
}
- #main::Debug(Dumper($self)); ###
-
+ #main::Debug(Dumper($self)); ###
}
+#
+# get corresponding value for given timestamp (data buffer must be enabled by setting holdTime)
+#
+# - if there is no exact match found for timestamp,
+# the value of the next smallest timestamp available is returned
+# - if timestamp is not inside the current time range undef is returned
+#
+sub getValue($$) {
+ my ($self, $t)= @_;
+
+ my $v = undef;
+ if (defined($self->{tSeries}) && $t >= $self->{t0} && $t <= $self->{t}) {
+ my $index = 0;
+ for my $i (0 .. $#{$self->{tSeries}}) {
+ my $ti= $self->{tSeries}[$i];
+ if ($ti > $t) {
+ last;
+ }
+ $index++;
+ }
+ $v = $self->{vSeries}[--$index];
+ }
+
+ return $v;
+}
+
+#
+# static class selftest performs unit test and logs validation errors
+#
+sub selftest() {
+ my ($self, @params) = @_;
+ die "static sub selftest may not be called as object method" if ref($self);
+
+ my $success = 1;
+
+ # block operation tests
+ my $tsb = TimeSeries->new( { method => "none", autoreset => 3 } );
+ $tsb->add(0, 0.8);
+ $tsb->add(1, 1.0);
+ $tsb->add(2, 1.2);
+ if ($tsb->{count} != 3) { $success = 0; main::Debug("unweighed block add test failed: count mismatch $tsb->{count}/3\n"); }
+ if ($tsb->{lost} != 0) { $success = 0; main::Debug("unweighed block add test failed: lost mismatch $tsb->{lost}/0\n"); }
+ if ($tsb->{n} != 3) { $success = 0; main::Debug("unweighed block add test failed: n mismatch $tsb->{n}/3\n"); }
+ if ($tsb->{t0} != 0) { $success = 0; main::Debug("unweighed block add test failed: first time mismatch $tsb->{t0}/0\n"); }
+ if ($tsb->{t} != 2) { $success = 0; main::Debug("unweighed block add test failed: last time mismatch $tsb->{t}/2\n"); }
+ if ($tsb->{v0} != 0.8) { $success = 0; main::Debug("unweighed block add test failed: first value mismatch $tsb->{v0}/0.8\n"); }
+ if ($tsb->{v} != 1.2) { $success = 0; main::Debug("unweighed block add test failed: last value mismatch $tsb->{v}/1.2\n"); }
+ if ($tsb->{min} != 0.8) { $success = 0; main::Debug("unweighed block add test failed: min mismatch $tsb->{min}/0.8\n"); }
+ if ($tsb->{max} != 1.2) { $success = 0; main::Debug("unweighed block add test failed: max mismatch $tsb->{max}/1.2\n"); }
+ if ($tsb->{mean} != 1.0) { $success = 0; main::Debug("unweighed block add test failed: mean mismatch $tsb->{mean}/1.0\n"); }
+ if (!defined($tsb->{sd}) || $tsb->{sd} ne sqrt(0.13/2)) { $success = 0; main::Debug("unweighed block add test failed: sd mismatch $tsb->{sd}/0.254950975679639\n"); }
+ if ($tsb->{integral} != 3.0) { $success = 0; main::Debug("unweighed block add test failed: sum mismatch $tsb->{integral}/3.0\n"); }
+ $tsb->add(3, 0.8);
+ $tsb->add(4, 1.2);
+ if ($tsb->{count} != 2) { $success = 0; main::Debug("unweighed block autoreset test failed: count mismatch $tsb->{count}/2\n"); }
+ if ($tsb->{lost} != 0) { $success = 0; main::Debug("unweighed block autoreset test failed: lost mismatch $tsb->{lost}/0\n"); }
+ if ($tsb->{n} != 2) { $success = 0; main::Debug("unweighed block autoreset test failed: n mismatch $tsb->{n}/2\n"); }
+ if ($tsb->{t0} != 3) { $success = 0; main::Debug("unweighed block autoreset test failed: first time mismatch $tsb->{t0}/3\n"); }
+ if ($tsb->{t} != 4) { $success = 0; main::Debug("unweighed block autoreset test failed: last time mismatch $tsb->{t}/4\n"); }
+ if ($tsb->{v0} != 0.8) { $success = 0; main::Debug("unweighed block autoreset test failed: first value mismatch $tsb->{v0}/0.8\n"); }
+ if ($tsb->{v} != 1.2) { $success = 0; main::Debug("unweighed block autoreset test failed: last value mismatch $tsb->{v}/1.2\n"); }
+ if ($tsb->{min} != 0.8) { $success = 0; main::Debug("unweighed block autoreset test failed: min mismatch $tsb->{min}/0.8\n"); }
+ if ($tsb->{max} != 1.2) { $success = 0; main::Debug("unweighed block autoreset test failed: max mismatch $tsb->{max}/1.2\n"); }
+ if ($tsb->{mean} != 1.0) { $success = 0; main::Debug("unweighed block autoreset test failed: mean mismatch $tsb->{mean}/1.0\n"); }
+ if (!defined($tsb->{sd}) || $tsb->{sd} ne "0.4") { $success = 0; main::Debug("unweighed block autoreset test failed: sd mismatch $tsb->{sd}/0.4\n"); }
+ if ($tsb->{integral} != 2.0) { $success = 0; main::Debug("unweighed block autoreset test failed: sum mismatch $tsb->{integral}/2.0\n"); }
+ $tsb->reset();
+ $tsb->{_t0} = undef;
+ $tsb->{_t} = undef;
+ $tsb->{_v} = undef;
+ $tsb->{method} = 'const';
+ $tsb->{autoreset} = 4;
+ $tsb->add(0, 1.0);
+ $tsb->add(1, 2.0);
+ $tsb->add(3, 0.5);
+ if ($tsb->{count} != 3) { $success = 0; main::Debug("const weighed block add test failed: count mismatch $tsb->{count}/3\n"); }
+ if ($tsb->{lost} != 0) { $success = 0; main::Debug("const weighed block add test failed: lost mismatch $tsb->{lost}/0\n"); }
+ if ($tsb->{n} != 2) { $success = 0; main::Debug("const weighed block add test failed: n mismatch $tsb->{n}/2\n"); }
+ if ($tsb->{t0} != 0) { $success = 0; main::Debug("const weighed block add test failed: first time mismatch $tsb->{t0}/0\n"); }
+ if ($tsb->{t} != 3) { $success = 0; main::Debug("const weighed block add test failed: last time mismatch $tsb->{t}/3\n"); }
+ if ($tsb->{v0} != 1.0) { $success = 0; main::Debug("const weighed block add test failed: first value mismatch $tsb->{v0}/1.0\n"); }
+ if ($tsb->{v} != 0.5) { $success = 0; main::Debug("const weighed block add test failed: last value mismatch $tsb->{v}/0.5\n"); }
+ if ($tsb->{min} != 0.5) { $success = 0; main::Debug("const weighed block add test failed: min mismatch $tsb->{min}/0.5\n"); }
+ if ($tsb->{max} != 2.0) { $success = 0; main::Debug("const weighed block add test failed: max mismatch $tsb->{max}/2.0\n"); }
+ if ($tsb->{mean} ne (2.5/1.5)) { $success = 0; main::Debug("const weighed block add test failed: mean mismatch $tsb->{mean}/1.66666666666667\n"); }
+ if (!defined($tsb->{sd}) || $tsb->{sd} ne 2) { $success = 0; main::Debug("const weighed block add test failed: sd mismatch $tsb->{sd}/2\n"); }
+ if ($tsb->{integral} != 5.0) { $success = 0; main::Debug("const weighed block add test failed: sum mismatch $tsb->{integral}/5.0\n"); }
+
+ # moving operation tests
+ my $now = time();
+ my $tsm = TimeSeries->new( { method => "none", holdTime => 3 } );
+ $tsm->add($now-2, 0.8);
+ $tsm->add($now-1, 1.0);
+ $tsm->add($now, 1.2);
+ if ($tsm->{count} != 3) { $success = 0; main::Debug("unweighed moving add test failed: count mismatch $tsm->{count}/3\n"); }
+ if ($tsm->{lost} != 0) { $success = 0; main::Debug("unweighed moving add test failed: lost mismatch $tsm->{lost}/0\n"); }
+ if ($tsm->{n} != 3) { $success = 0; main::Debug("unweighed moving add test failed: n mismatch $tsm->{n}/3\n"); }
+ if ($tsm->{t0} != ($now-2)) { $success = 0; main::Debug("unweighed moving add test failed: first time mismatch $tsm->{t0}\n"); }
+ if ($tsm->{t} != $now) { $success = 0; main::Debug("unweighed moving add test failed: last time mismatch $tsm->{t}\n"); }
+ if ($tsm->{v0} != 0.8) { $success = 0; main::Debug("unweighed moving add test failed: first value mismatch $tsm->{v0}/0.8\n"); }
+ if ($tsm->{v} != 1.2) { $success = 0; main::Debug("unweighed moving add test failed: last value mismatch $tsm->{v}/1.2\n"); }
+ if ($tsm->{min} != 0.8) { $success = 0; main::Debug("unweighed moving add test failed: min mismatch $tsm->{min}/0.8\n"); }
+ if ($tsm->{max} != 1.2) { $success = 0; main::Debug("unweighed moving add test failed: max mismatch $tsm->{max}/1.2\n"); }
+ if ($tsm->{mean} != 1.0) { $success = 0; main::Debug("unweighed moving add test failed: mean mismatch $tsm->{mean}/1.0\n"); }
+ if (!defined($tsm->{sd}) || $tsm->{sd} ne sqrt(0.13/2)) { $success = 0; main::Debug("unweighed moving add test failed: sd mismatch $tsm->{sd}/0.254950975679639\n"); }
+ if ($tsm->{integral} != 3.0) { $success = 0; main::Debug("unweighed moving add test failed: sum mismatch $tsm->{integral}/3.0\n"); }
+ sleep(3);
+ $tsm->add($now+1, 1.0);
+ $tsm->add($now+2, 0.8);
+ if ($tsm->{count} != 3) { $success = 0; main::Debug("unweighed moving holdTime test failed: count mismatch $tsm->{count}/3\n"); }
+ if ($tsm->{lost} != 0) { $success = 0; main::Debug("unweighed moving holdTime test failed: lost mismatch $tsm->{lost}/0\n"); }
+ if ($tsm->{n} != 3) { $success = 0; main::Debug("unweighed moving holdTime test failed: n mismatch $tsm->{n}/3\n"); }
+ if ($tsm->{t0} != $now) { $success = 0; main::Debug("unweighed moving holdTime test failed: first time mismatch $tsm->{t0}\n"); }
+ if ($tsm->{t} != ($now+2)) { $success = 0; main::Debug("unweighed moving holdTime test failed: last time mismatch $tsm->{t}\n"); }
+ if ($tsm->{v0} != 1.2) { $success = 0; main::Debug("unweighed moving holdTime test failed: first value mismatch $tsm->{v0}/1.2\n"); }
+ if ($tsm->{v} != 0.8) { $success = 0; main::Debug("unweighed moving holdTime test failed: last value mismatch $tsm->{v}/0.8\n"); }
+ if ($tsm->{min} != 0.8) { $success = 0; main::Debug("unweighed moving holdTime test failed: min mismatch $tsm->{min}/0.8\n"); }
+ if ($tsm->{max} != 1.2) { $success = 0; main::Debug("unweighed moving holdTime test failed: max mismatch $tsm->{max}/1.2\n"); }
+ if ($tsm->{mean} != 1.0) { $success = 0; main::Debug("unweighed moving holdTime test failed: mean mismatch $tsm->{mean}/1.0\n"); }
+ if (!defined($tsm->{sd}) || $tsm->{sd} ne sqrt(0.13/2)) { $success = 0; main::Debug("unweighed moving holdTime test failed: sd mismatch $tsm->{sd}/0.254950975679639\n"); }
+ if ($tsm->{integral} != 3.0) { $success = 0; main::Debug("unweighed moving holdTime test failed: sum mismatch $tsm->{integral}/3.0\n"); }
+ $tsm->reset();
+ $tsm->{method} = 'const';
+ $tsm->{holdTime} = 5;
+ $now = time();
+ $tsm->add($now-4, 1.0);
+ $tsm->add($now-3, 2.0);
+ $tsm->add($now-1, -1.0);
+ if ($tsm->{count} != 3) { $success = 0; main::Debug("const weighed moving add test 1 failed: count mismatch $tsm->{count}/3\n"); }
+ if ($tsm->{lost} != 0) { $success = 0; main::Debug("const weighed moving add test 1 failed: lost mismatch $tsm->{lost}/0\n"); }
+ if ($tsm->{n} != 2) { $success = 0; main::Debug("const weighed moving add test 1 failed: n mismatch $tsm->{n}/2\n"); }
+ if ($tsm->{t0} != ($now-4)) { $success = 0; main::Debug("const weighed moving add test 1 failed: first time mismatch $tsm->{t0}\n"); }
+ if ($tsm->{t} != ($now-1)) { $success = 0; main::Debug("const weighed moving add test 1 failed: last time mismatch $tsm->{t}\n"); }
+ if ($tsm->{v0} != 1.0) { $success = 0; main::Debug("const weighed moving add test 1 failed: first value mismatch $tsm->{v0}/1.0\n"); }
+ if ($tsm->{v} != -1.0) { $success = 0; main::Debug("const weighed moving add test 1 failed: last value mismatch $tsm->{v}/-1.0\n"); }
+ if ($tsm->{min} != -1.0) { $success = 0; main::Debug("const weighed moving add test 1 failed: min mismatch $tsm->{min}/-1.0\n"); }
+ if ($tsm->{max} != 2.0) { $success = 0; main::Debug("const weighed moving add test 1 failed: max mismatch $tsm->{max}/2.0\n"); }
+ if ($tsm->{mean} ne (2.5/1.5)) { $success = 0; main::Debug("const weighed moving add test 1 failed: mean mismatch $tsm->{mean}/1.66666666666667\n"); }
+ if (!defined($tsm->{sd}) || $tsm->{sd} ne 2) { $success = 0; main::Debug("const weighed moving add test 1 failed: sd mismatch $tsm->{sd}/2\n"); }
+ if ($tsm->{integral} != 5.0) { $success = 0; main::Debug("const weighed moving add test 1 failed: sum mismatch $tsm->{integral}/5.0\n"); }
+ $tsm->add($now, 0.5);
+ if ($tsm->{count} != 4) { $success = 0; main::Debug("const weighed moving add test 2 failed: count mismatch $tsm->{count}/4\n"); }
+ if ($tsm->{lost} != 0) { $success = 0; main::Debug("const weighed moving add test 2 failed: lost mismatch $tsm->{lost}/0\n"); }
+ if ($tsm->{n} != 3) { $success = 0; main::Debug("const weighed moving add test 2 failed: n mismatch $tsm->{n}/3\n"); }
+ if ($tsm->{t0} != ($now-4)) { $success = 0; main::Debug("const weighed moving add test 2 failed: first time mismatch $tsm->{t0}\n"); }
+ if ($tsm->{t} != ($now)) { $success = 0; main::Debug("const weighed moving add test 2 failed: last time mismatch $tsm->{t}\n"); }
+ if ($tsm->{v0} != 1.0) { $success = 0; main::Debug("const weighed moving add test 2 failed: first value mismatch $tsm->{v0}/1.0\n"); }
+ if ($tsm->{v} != 0.5) { $success = 0; main::Debug("const weighed moving add test 2 failed: last value mismatch $tsm->{v}/0.5\n"); }
+ if ($tsm->{min} != -1.0) { $success = 0; main::Debug("const weighed moving add test 2 failed: min mismatch $tsm->{min}/-1.0\n"); }
+ if ($tsm->{max} != 2.0) { $success = 0; main::Debug("const weighed moving add test 2 failed: max mismatch $tsm->{max}/2.0\n"); }
+ if ($tsm->{mean} != 1) { $success = 0; main::Debug("const weighed moving add test 2 failed: mean mismatch $tsm->{mean}/1\n"); }
+ if (!defined($tsm->{sd}) || $tsm->{sd} ne sqrt(21.25/2)*3/4) { $success = 0; main::Debug("const weighed moving add test 2 failed: sd mismatch $tsm->{sd}/2.44470090195099\n"); }
+ if ($tsm->{integral} != 4.0) { $success = 0; main::Debug("const weighed moving add test 2 failed: sum mismatch $tsm->{integral}/4.0\n"); }
+
+ # get value tests
+ if ($tsm->getValue($now-4) ne 1.0) { $success = 0; main::Debug("getValue test failed: first value mismatch ".$tsm->getValue($now-4)."/1.0\n"); }
+ if ($tsm->getValue($now-3) ne 2.0) { $success = 0; main::Debug("getValue test failed: exact value mismatch ".$tsm->getValue($now-3)."/2.0\n"); }
+ if ($tsm->getValue($now-2) ne 2.0) { $success = 0; main::Debug("getValue test failed: before value mismatch ".$tsm->getValue($now-2)."/2.0\n"); }
+ if ($tsm->getValue($now) ne 0.5) { $success = 0; main::Debug("getValue test failed: last value mismatch ".$tsm->getValue($now)."/0.5\n"); }
+ if (defined($tsm->getValue($now+1))) { $success = 0; main::Debug("getValue test failed: out of range value mismatch ".$tsm->getValue($now+1)."/undef\n"); }
+
+ if ($success) {
+ return "selftest passed";
+ } else {
+ return "selftest failed, see log for details";
+ }
+}
1;
=pod
-B is a perl module to feed data points and get some statistics on them as you go.
+B is a perl module to feed time/value data points and get some statistics on them as you go:
my $ts= TimeSeries->new( { method => "const" } );
$ts->add(3.3, 2.1);
@@ -220,6 +544,20 @@ B is a perl module to feed data points and get some statistics on th
$ts->{mean}, $ts->{sd}
);
-=cut
+ Mean, standard deviation and integral calculation also depends on the property method. You may choose from
+ none (no time weighting), const (time weighted, step) or linear (time weighted, linear interpolation).
+
+ The statistics may be reset manually using
+ $ts->reset();
-
\ No newline at end of file
+ By defining autoreset, the reset will occur automatically when the specified duration (seconds)
+ is accumulated.
+
+ If alternatively holdTime is defined, all data points are kept in a time limited data buffer that is
+ re-evaluated each time a data point is added. Note that this may require significant amounts
+ of memory depending on the sample rate and the holdTime.
+
+ It is also possible to define autoreset and holdtime at the same time. In this case the data buffer
+ is enabled and will be cleared each time an autoreset occurs, independent of the value of holdtime.
+
+=cut