From a1bd6ad6903385f259e17c934ac9ce165065e2dc Mon Sep 17 00:00:00 2001
From: borisneubert <>
Date: Sat, 24 Jan 2015 19:55:15 +0000
Subject: [PATCH] TimeSeries.pm: new helper module
git-svn-id: https://svn.fhem.de/fhem/trunk@7700 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/CHANGED | 1 +
fhem/FHEM/TimeSeries.pm | 225 ++++++++++++++++++++++++++++++++++++++++
fhem/MAINTAINER.txt | 3 +-
3 files changed, 228 insertions(+), 1 deletion(-)
create mode 100644 fhem/FHEM/TimeSeries.pm
diff --git a/fhem/CHANGED b/fhem/CHANGED
index 6ad10e7dc..eba28ad82 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it.
+ - feature: new helper module TimeSeries.pm (Boris)
- feature: 71_YAMAHA_NP.pm: Make timer setting more convenient.
- changed: FB_CALLMONITOR: allow chars in phone numbers for reverse search
- changed: SYSMON: non-blocking
diff --git a/fhem/FHEM/TimeSeries.pm b/fhem/FHEM/TimeSeries.pm
new file mode 100644
index 000000000..e5c8ae722
--- /dev/null
+++ b/fhem/FHEM/TimeSeries.pm
@@ -0,0 +1,225 @@
+# $Id$
+
+##############################################################################
+#
+# 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 .
+#
+##############################################################################
+
+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
+ count => 0, # number of points 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
+ # statistics
+ n => 0, # size of sample
+ mean => undef, # arithmetic mean of time-weighted values
+ sd => undef, # standard deviation of time-weighted values
+ _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->{_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;
+}
+
+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 - $self->{_M});
+ #main::Debug("V= $V M= $M _M= ".$self->{_M}." _S= " . $self->{_S});
+ } else {
+ $self->{_M}= $V;
+ $self->{_S}= 0;
+ }
+ #main::Debug("STAT UPD n= $n");
+}
+
+#
+# has autoreset period elapsed?
+#
+
+sub elapsed($$) {
+ my ($self, $t)= @_;
+ return defined($self->{autoreset}) &&
+ defined($self->{_t0}) &&
+ ($t - $self->{_t0} >= $self->{autoreset});
+}
+
+#
+# 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
+ }
+
+ # autoreset
+ $self->reset() if($self->elapsed($t));
+
+ main::Debug("ADD ($t,$v)"); ###
+
+ # count
+ $self->{count}++;
+
+ # statistics
+ if($self->{method} eq "none") {
+ # no time-weighting
+ $self->_updatestat(1, $v);
+ } elsif(defined($self->{_t})) {
+ # 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);
+ }
+ }
+ $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)); ###
+
+}
+
+
+1;
+
+
+=pod
+
+B is a perl module to feed 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}
+ );
+
+=cut
+
+
\ No newline at end of file
diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt
index f0cc5b9b8..0a4977938 100644
--- a/fhem/MAINTAINER.txt
+++ b/fhem/MAINTAINER.txt
@@ -300,6 +300,7 @@ FHEM/SetExtensions.pm rudolfkoenig http://forum.fhem.de Automatis
FHEM/SHC_datafields.pm rr2000 http://forum.fhem.de Sonstige Systeme
FHEM/SHC_parser.pm rr2000 http://forum.fhem.de Sonstige Systeme
FHEM/TcpServerUtils.pm rudolfkoenig http://forum.fhem.de Automatisierung
+FHEM/TimeSeries.pm borisneubert http://forum.fhem.de FHEM Development
FHEM/lib/Device/Firmata/* ntruchsess http://forum.fhem.de Sonstige Systeme
FHEM/lib/Device/MySensors/* ntruchsess http://forum.fhem.de Sonstige Systeme
FHEM/lib/MP3/* Reinerlein http://forum.fhem.de Multimedia
@@ -332,5 +333,5 @@ docs/* rudolfkoenig http://forum.fhem.de Sonstiges
Files that every developer should modify/extend
MAINTAINER.txt
- CHANGES
+ CHANGED
HISTORY