| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- # $Id: TimeSeries.pm 10907 2016-02-21 17:38:02Z borisneubert $
- ##############################################################################
- #
- # TimeSeries.pm
- # Copyright by Dr. Boris Neubert
- # e-mail: omega at online dot de
- #
- # This file is part of fhem.
- #
- # Fhem is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 2 of the License, or
- # (at your option) any later version.
- #
- # Fhem is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with fhem. If not, see <http://www.gnu.org/licenses/>.
- #
- ##############################################################################
- #
- # 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
- #
- # 24.01.2016 knxhm at forum dot fhem dot de & Jens Beyer (jensb at forum dot fhem dot de)
- # new: property median (out)
- #
- # 29.01.2016 Jens Beyer (jensb at forum dot fhem dot de)
- # modified: method elapsed reverted to version from 2015-01-31 to provide downsampling and buffering through fhem.pl
- # modified: method _housekeeping does not reset time series if hold time is specified
- #
- ##############################################################################
- package TimeSeries;
- use warnings;
- use strict;
- #use Data::Dumper;
- no if $] >= 5.017011, warnings => 'experimental::smartmatch';
- # If two subsequent points in the time series are less than
- # EPS seconds apart, the second value is ignored. This feature catches
- # - time running backwards,
- # - two points at the same time (within the resolution of time),
- # - precision loss due to points too close in time.
- # Ignored values are counted in the lost property.
- use constant EPS => 0.001; # 1 millisecond
- #
- # A time series is a sequence of points (t,v) with timestamp t and value v.
- #
- sub new() {
- my ($class, $args)= @_;
- my @METHODS= qw(none linear const);
- # none = no time weighting at all
- # we must have an assumption about how the value varies between
- # two data points in the time series (discretization method).
- # The const method assumes, that the value was constant
- # since the previous one.
- # The linear method assumes, that the value changed linearly
- # from the previous one to the current one.
- my $self= {
- method => $args->{method} || "none",
- autoreset => $args->{autoreset}, # if set, resets series every autoreset seconds
- 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
- v0 => undef, # first value added
- 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 (non time weighted) or number of intervals (time weighted)
- mean => undef, # arithmetic mean of values
- sd => undef, # standard deviation of values
- integral => undef, # sum (holdTime undefined) or integral area (holdTime defined) of all values
- median => undef, # median of all values (method must be "none" and holdTime must be defined)
- _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
- _M => undef, # see below
- _S => undef, # see below
- }; # we are a hash reference
- $self->{method}= "none" unless($self->{method} ~~ @METHODS);
- return bless($self, $class); # make $self an object of class $class
- }
- #
- # reset the series
- #
- 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->{median}= undef;
- $self->{_M}= undef;
- $self->{_S}= undef;
- $self->{_t0}= $self->{_t};
- #
- $self->{count}= 0;
- $self->{lost}= 0;
- $self->{t0}= undef;
- $self->{t}= undef;
- $self->{v0}= undef;
- $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;
- }
- }
- #
- # 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.
- my $n= ++$self->{n};
- if($n> 1) {
- my $M= $self->{_M};
- $self->{_M}= $M + ($V - $M) / $n;
- $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");
- }
-
- #
- # has autoreset period elapsed?
- # used by fhem.pl for downsampling
- #
- sub elapsed($$) {
- my ($self, $t)= @_;
- return defined($self->{autoreset}) && defined($self->{_t0}) && ($t - $self->{_t0} >= $self->{autoreset});
- }
- #
- # reset or trim series
- #
- sub _housekeeping($) {
- my ($self, $t)= @_;
- if($self->elapsed($t) && !defined($self->{holdTime})) {
- #main::Debug("TimeSeries::_housekeeping: reset\n");
- $self->reset();
- } elsif(defined($self->{holdTime}) && defined($self->{_t0}) && ($t - $self->{_t0} >= $self->{holdTime})) {
- #main::Debug("TimeSeries::_housekeeping: trimToHoldTime\n");
- $self->trimToHoldTime();
- }
- }
- #
- # add a point to the series
- #
- sub add($$$) {
- my ($self, $t, $v)= @_;
- # reject values if time resolution is insufficient
- if(defined($self->{_t}) && $t - $self->{_t} < EPS) {
- $self->{lost}++;
- return; # note: for consistency, the value is not considered at all
- }
-
- # 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($v);
-
- # median
- if(defined($self->{holdTime})) {
- my @sortedVSeries = sort {$TimeSeries::a <=> $TimeSeries::b} @{$self->{vSeries}};
- my $center = int($self->{count} / 2);
- if($self->{count} % 2 == 0) {
- $self->{median} = ($sortedVSeries[$center - 1] + $sortedVSeries[$center]) / 2;
- } else {
- $self->{median} = $sortedVSeries[$center];
- }
- }
- } else {
- # time-weighting
- 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;
- $self->{_v}= $v;
-
- # first point
- if(!defined($self->{t0})) {
- $self->{t0}= $t;
- $self->{v0}= $v;
- }
- if(!defined($self->{_t0})) {
- $self->{_t0}= $t;
- }
-
- # last point
- $self->{t}= $t;
- $self->{v}= $v;
-
- # min, max
- $self->{min}= $v if(!defined($self->{min}) || $v< $self->{min});
- $self->{max}= $v if(!defined($self->{max}) || $v> $self->{max});
-
- # mean, standard deviation
- my $n= $self->{n};
- if($n) {
- my $T= $self->{method} eq "none" ? 1 : ( $self->{t} - $self->{_t0} ) / $n;
- if($T> 0) {
- #main::Debug("T= $T _M= " . $self->{_M} );
- $self->{mean}= $self->{_M} / $T;
- # in the time-weighted methods, this is just a measure for the variation of the values
- $self->{sd}= sqrt($self->{_S}/ ($n-1)) / $T if($n> 1);
- }
- }
- #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"); }
- if ($tsm->{median} != 1.0) { $success = 0; main::Debug("unweighed moving add test failed: median mismatch $tsm->{median}/1.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"); }
- if ($tsm->{median} != 1.0) { $success = 0; main::Debug("unweighed block autoreset test failed: median mismatch $tsm->{median}/1.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<TimeSeries> 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);
- $ts->add(5.1, 1.8);
- $ts->add(8.8, 2.4);
- printf("count= %d, n= %d, lost= %d, first= %f, last= %f, min= %f, max= %f, mean= %f, sd= %f\n",
- $ts->{count}, $ts->{n}, $ts->{lost}, $ts->{v0}, $ts->{v},
- $ts->{min}, $ts->{max},
- $ts->{mean}, $ts->{sd}
- );
-
- 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();
-
- 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.
-
- If method is none and holdtime is defined then the median of the values will be calculated additionally.
-
- 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
|