summaryrefslogtreecommitdiff
path: root/lib/mov-avg.h
blob: 36a6ceb767b50dbe691f9d3e3aba09af63692357 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
 * Copyright (c) 2021 NVIDIA Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _MOV_AVG_H
#define _MOV_AVG_H 1

#include <math.h>

/* Moving average helpers. */

/* Cumulative Moving Average.
 *
 * Computes the arithmetic mean over a whole series of value.
 * Online equivalent of sum(V) / len(V).
 *
 * As all values have equal weight, this average will
 * be slow to show recent changes in the series.
 *
 */

struct mov_avg_cma {
    unsigned long long int count;
    double mean;
    double sum_dsquared;
};

#define MOV_AVG_CMA_INITIALIZER \
    { .count = 0, .mean = .0, .sum_dsquared = .0 }

static inline void
mov_avg_cma_init(struct mov_avg_cma *cma)
{
    *cma = (struct mov_avg_cma) MOV_AVG_CMA_INITIALIZER;
}

static inline void
mov_avg_cma_update(struct mov_avg_cma *cma, double new_val)
{
    double new_mean;

    cma->count++;
    new_mean = cma->mean + (new_val - cma->mean) / cma->count;

    cma->sum_dsquared += (new_val - new_mean) * (new_val - cma->mean);
    cma->mean = new_mean;
}

static inline double
mov_avg_cma(struct mov_avg_cma *cma)
{
    return cma->mean;
}

static inline double
mov_avg_cma_std_dev(struct mov_avg_cma *cma)
{
    double variance = 0.0;

    if (cma->count > 1) {
        variance = cma->sum_dsquared / (cma->count - 1);
    }

    return sqrt(variance);
}

/* Exponential Moving Average.
 *
 * Each value in the series has an exponentially decreasing weight,
 * the older they get the less weight they have.
 *
 * The smoothing factor 'alpha' must be within 0 < alpha < 1.
 * The closer this factor to zero, the more equal the weight between
 * recent and older values. As it approaches one, the more recent values
 * will have more weight.
 *
 * The EMA can be thought of as an estimator for the next value when measures
 * are dependent. In this case, it can make sense to consider the mean square
 * error of the prediction. An 'alpha' minimizing this error would be the
 * better choice to improve the estimation.
 *
 * A common way to choose 'alpha' is to use the following formula:
 *
 *   a = 2 / (N + 1)
 *
 * With this 'alpha', the EMA will have the same 'center of mass' as an
 * equivalent N-values Simple Moving Average.
 *
 * When using this factor, the N last values of the EMA will have a sum weight
 * converging toward 0.8647, meaning that those values will account for 86% of
 * the average[1].
 *
 * [1] https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
 */

struct mov_avg_ema {
    double alpha; /* 'Smoothing' factor. */
    double mean;
    double variance;
    bool initialized;
};

/* Choose alpha explicitly. */
#define MOV_AVG_EMA_INITIALIZER_ALPHA(a) { \
    .initialized = false, \
    .alpha = (a), .variance = 0.0, .mean = 0.0 \
}

/* Choose alpha to consider 'N' past periods as 86% of the EMA. */
#define MOV_AVG_EMA_INITIALIZER(n_elem) \
    MOV_AVG_EMA_INITIALIZER_ALPHA(2.0 / ((double)(n_elem) + 1.0))

static inline void
mov_avg_ema_init_alpha(struct mov_avg_ema *ema,
                       double alpha)
{
    *ema = (struct mov_avg_ema) MOV_AVG_EMA_INITIALIZER_ALPHA(alpha);
}

static inline void
mov_avg_ema_init(struct mov_avg_ema *ema,
                 unsigned long long int n_elem)
{
    *ema = (struct mov_avg_ema) MOV_AVG_EMA_INITIALIZER(n_elem);
}

static inline void
mov_avg_ema_update(struct mov_avg_ema *ema, double new_val)
{
    const double alpha = ema->alpha;
    double alpha_diff;
    double diff;

    if (!ema->initialized) {
        ema->initialized = true;
        ema->mean = new_val;
        return;
    }

    diff = new_val - ema->mean;
    alpha_diff = alpha * diff;

    ema->variance = (1.0 - alpha) * (ema->variance + alpha_diff * diff);
    ema->mean = ema->mean + alpha_diff;
}

static inline double
mov_avg_ema(struct mov_avg_ema *ema)
{
    return ema->mean;
}

static inline double
mov_avg_ema_std_dev(struct mov_avg_ema *ema)
{
    return sqrt(ema->variance);
}

#endif /* _MOV_AVG_H */