mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-03 16:56:54 +00:00
TimeSeries.pm: new helper module
git-svn-id: https://svn.fhem.de/fhem/trunk@7700 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
20da4c165c
commit
a1bd6ad690
@ -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
|
||||
|
225
fhem/FHEM/TimeSeries.pm
Normal file
225
fhem/FHEM/TimeSeries.pm
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
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<TimeSeries> 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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user