Skip to content

Commit 09eea8c

Browse files
committed
meanvar: Add merging function.
Add a function that allows to merge multiple meanvar instances. The current version is usable but not perfect: It would be good to better understand its numerical stability properties.
1 parent 3442eaa commit 09eea8c

File tree

3 files changed

+150
-12
lines changed

3 files changed

+150
-12
lines changed

src/csnip/meanvar.h

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,22 @@ extern "C" {
5353
* define an accumulator for _Decimal64.
5454
*/
5555
typedef struct {
56-
long int count; /**< Number of samples */
57-
double M; /**< Running mean */
58-
double S; /**< Running square difference */
56+
/** Number of samples. */
57+
long int count;
58+
59+
/** Running mean.
60+
*
61+
* Is equal to the mean of all the values added so far.
62+
*/
63+
double M;
64+
65+
/** Running square difference.
66+
*
67+
* If the values x[0], ..., x[count - 1] have been added so
68+
* far, this is equal to sum_i (x[i] - M)^2, where M is the
69+
* sample mean as above.
70+
*/
71+
double S;
5972
} csnip_meanvar;
6073

6174
/** Define an accumulator type.
@@ -84,7 +97,8 @@ CSNIP_MEANVAR_DEF_TYPE(csnip_meanvarl, long double)
8497
#define CSNIP_MEANVAR_DECL_FUNCS(scope, prefix, accum_type, val_type) \
8598
scope void prefix ## add(accum_type* A, val_type v); \
8699
scope val_type prefix ## mean(const accum_type* A); \
87-
scope val_type prefix ## var(const accum_type* A, val_type ddof);
100+
scope val_type prefix ## var(const accum_type* A, val_type ddof); \
101+
scope void prefix ## merge(accum_type* into, const accum_type* other);
88102

89103
/** Add sample to accumulator.
90104
*
@@ -122,6 +136,22 @@ double csnip_meanvar_mean(const csnip_meanvar* A);
122136
*/
123137
double csnip_meanvar_var(const csnip_meanvar* A, double ddof);
124138

139+
/** Combine two meanvar instances into one.
140+
*
141+
* This allows combining separately collected data. Note that with
142+
* the present implementation, numerical stability is not
143+
* guaranteed as well as individual add steps do.
144+
*
145+
* @param into
146+
* the merge destination. The summary of the samples from
147+
* *other will be added into this accumulator, so it
148+
* represents all the sammles.
149+
*
150+
* @param other
151+
* the data to merge.
152+
*/
153+
void csnip_meanvar_merge(csnip_meanvar* into, const csnip_meanvar* other);
154+
125155
CSNIP_MEANVAR_DECL_FUNCS(, csnip_meanvarf_, csnip_meanvarf, float)
126156
CSNIP_MEANVAR_DECL_FUNCS(, csnip_meanvarl_, csnip_meanvarl, long double)
127157

@@ -137,7 +167,7 @@ CSNIP_MEANVAR_DECL_FUNCS(, csnip_meanvarl_, csnip_meanvarl, long double)
137167
const val_type last_M = A->M; \
138168
++A->count; \
139169
A->M = last_M + (v - last_M) / A->count; \
140-
A->S = A->S + (v - last_M) * (v - A->M); \
170+
A->S += (v - last_M) * (v - A->M); \
141171
} \
142172
\
143173
scope val_type prefix ## mean(const accum_type* A) \
@@ -150,6 +180,19 @@ CSNIP_MEANVAR_DECL_FUNCS(, csnip_meanvarl_, csnip_meanvarl, long double)
150180
{ \
151181
return A->S / (A->count - ddof); \
152182
} \
183+
\
184+
scope void prefix ## merge(accum_type* into, const accum_type* other) \
185+
{ \
186+
const val_type last_M = into->M; \
187+
const long int new_count = into->count + other->count; \
188+
into->M = last_M + other->count * (other->M - last_M) / new_count; \
189+
val_type into_S = into->S; \
190+
into_S += into->count * (last_M - into->M) * (last_M - into->M); \
191+
val_type other_S = other->S; \
192+
other_S += other->count * (other->M - into->M) * (other->M - into->M); \
193+
into->S = into_S + other_S; \
194+
into->count = new_count; \
195+
}
153196

154197
#ifdef __cplusplus
155198
}
@@ -223,6 +266,30 @@ CSNIP_MEANVAR_DECL_FUNCS(, csnip_meanvarl_, csnip_meanvarl, long double)
223266
csnip_meanvarl: \
224267
csnip_meanvarl_var((csnip_meanvarl*)(accumptr), (ddof)))
225268

269+
/** Merge another accumulator into the current one.
270+
*
271+
* @param into
272+
* Pointer of destination accumulator.
273+
*
274+
* @param other
275+
* Pointer of source accumulator.
276+
*
277+
* *Requirements*: C11's _Generic or C++ templates. The
278+
* corresponding typed functions csnip_meanvar_merge{f,,l} are not
279+
* dependent on C11.
280+
*/
281+
#define csnip_meanvar_Merge(into, other) \
282+
_Generic(*(into), \
283+
csnip_meanvarf: \
284+
csnip_meanvarf_merge((csnip_meanvarf*)into, \
285+
(const csnip_meanvarf*)other), \
286+
csnip_meanvar: \
287+
csnip_meanvar_merge((csnip_meanvar*)into, \
288+
(const csnip_meanvar*)other), \
289+
csnip_meanvarl: \
290+
csnip_meanvarl_merge((csnip_meanvarl*)into, \
291+
(const csnip_meanvarl*)other))
292+
226293
#else /* __cplusplus */
227294

228295
/* C++ version of the generic csnip_meanvar_* API */
@@ -304,10 +371,28 @@ CSNIP__DEF_VAR(dummy,f)
304371
CSNIP__DEF_VAR(dummy,)
305372
CSNIP__DEF_VAR(dummy,l)
306373
#undef CSNIP__DEF_VAR
307-
#undef CSNIP__SCALAR
308374

309375
#define csnip_meanvar_Var(accum, ddof) csnip_meanvar__cxx_var(accum, ddof)
310376

377+
template<typename T> \
378+
void csnip_meanvar__cxx_merge(T*, const T*);
379+
380+
#define CSNIP__DEF_MERGE(dummy,suffix) \
381+
template<> void \
382+
csnip_meanvar__cxx_merge(csnip_meanvar##suffix* into, \
383+
const csnip_meanvar##suffix* other) \
384+
{ \
385+
csnip_meanvar##suffix##_merge(into, other); \
386+
}
387+
CSNIP__DEF_MERGE(dummy,f)
388+
CSNIP__DEF_MERGE(dummy,)
389+
CSNIP__DEF_MERGE(dummy,l)
390+
#undef CSNIP__DEF_MERGE
391+
392+
#define csnip_meanvar_Merge(into, other) csnip_meanvar__cxx_merge(into, other)
393+
394+
#undef CSNIP__SCALAR
395+
311396
#endif /* __cplusplus */
312397

313398
/** @} */
@@ -328,8 +413,12 @@ CSNIP__DEF_VAR(dummy,l)
328413
#define meanvarf_var csnip_meanvarf_var
329414
#define meanvar_var csnip_meanvar_var
330415
#define meanvarl_var csnip_meanvarl_var
416+
#define meanvarf_merge csnip_meanvarf_merge
417+
#define meanvar_merge csnip_meanvar_merge
418+
#define meanvarl_merge csnip_meanvarl_merge
331419
#define meanvar_Add csnip_meanvar_Add
332420
#define meanvar_Mean csnip_meanvar_Mean
333421
#define meanvar_Var csnip_meanvar_Var
422+
#define meanvar_Merge csnip_meanvar_Merge
334423
#define CSNIP_MEANVAR_HAVE_SHORT_NAMES
335424
#endif /* CSNIP_SHORT_NAMES && !CSNIP_MEANVAR_HAVE_SHORT_NAMES */

test/meanvar_test0.c

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ typedef struct {
1616
const float v1[] = { 1, 2, 3, 4, 3.2f };
1717
const resultf exp1 = { .mean = 2.64f, .var0 = 1.0784f, .var1 = 1.348f };
1818

19-
static _Bool check_got_vs_exp(const resultf* got,
19+
static bool check_got_vs_exp(const resultf* got,
2020
const resultf* exp,
2121
float epsilon)
2222
{
23-
_Bool success = 1;
23+
bool success = 1;
2424
if (fabsf(exp->mean - got->mean) > epsilon) {
2525
fprintf(stderr, " Mismatch in mean, got %g, expected %g\n",
2626
got->mean, exp->mean);
@@ -39,7 +39,7 @@ static _Bool check_got_vs_exp(const resultf* got,
3939
return success;
4040
}
4141

42-
static _Bool check_testcase(const float* f, int N,
42+
static bool check_testcase(const float* f, int N,
4343
const resultf* exp,
4444
float epsilon)
4545
{
@@ -67,7 +67,7 @@ static _Bool check_testcase(const float* f, int N,
6767
};
6868
printf(" got: mean %g var0 %g var1 %g\n",
6969
got.mean, got.var0, got.var1);
70-
_Bool result = check_got_vs_exp(&got, exp, epsilon);
70+
bool result = check_got_vs_exp(&got, exp, epsilon);
7171
if (result) {
7272
printf("-> success\n");
7373
} else {
@@ -76,6 +76,32 @@ static _Bool check_testcase(const float* f, int N,
7676
return result;
7777
}
7878

79+
static bool check_merge(const float* f, int N1, int N2,
80+
const resultf* exp,
81+
float epsilon)
82+
{
83+
csnip_meanvarf A = { 0 };
84+
csnip_meanvarf B = { 0 };
85+
for (int i = 0; i < N1; ++i)
86+
csnip_meanvar_Add(&A, f[i]);
87+
for (int i = 0; i < N2; ++i)
88+
csnip_meanvar_Add(&B, f[i + N1]);
89+
csnip_meanvarf_merge(&A, &B);
90+
const resultf got = {
91+
.mean = csnip_meanvar_Mean(&A),
92+
.var0 = csnip_meanvar_Var(&A, 0),
93+
.var1 = csnip_meanvar_Var(&A, 1),
94+
};
95+
bool result = check_got_vs_exp(&got, exp, epsilon);
96+
if (result) {
97+
printf("-> success\n");
98+
} else {
99+
printf("-> FAIL\n");
100+
}
101+
return result;
102+
}
103+
104+
79105

80106
int main(int argc, char** argv)
81107
{
@@ -87,7 +113,11 @@ int main(int argc, char** argv)
87113
if (csnip_clopts_process(&opts, argc - 1, argv + 1, NULL, true) < 0)
88114
return EXIT_FAILURE;
89115

90-
check_testcase(v1, csnip_Static_len(v1), &exp1, 0.001f);
116+
const int l1 = (int)csnip_Static_len(v1);
117+
if (!check_testcase(v1, l1, &exp1, 0.001f))
118+
return EXIT_FAILURE;
119+
if (!check_merge(v1, l1/2, l1 - l1/2, &exp1, 0.001f))
120+
return EXIT_FAILURE;
91121

92122
return EXIT_SUCCESS;
93123
}

test/meanvar_test0_cxx.cc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,31 @@ using std::printf;
99

1010
int main(void)
1111
{
12+
/* X set */
1213
float f[] = {1, 2, 3, 4};
1314
meanvar X = { 0 };
1415
for (int i = 0; i < (int)Static_len(f); ++i) {
1516
meanvar_Add(&X, f[i]);
1617
}
17-
printf("-> got %ld elements, mean=%g, var=%g\n",
18+
printf("X: got %ld elements, mean=%g, var=%g\n",
19+
X.count,
20+
meanvar_Mean(&X),
21+
meanvar_Var(&X, 1));
22+
23+
/* Y set */
24+
float g[] = {5, 6, 7, 8};
25+
meanvar Y = { 0 };
26+
for (int i = 0; i < (int)Static_len(g); ++i) {
27+
meanvar_Add(&Y, g[i]);
28+
}
29+
printf("Y: got %ld elements, mean=%g, var=%g\n",
30+
Y.count,
31+
meanvar_Mean(&Y),
32+
meanvar_Var(&Y, 1));
33+
34+
/* Merge the two */
35+
meanvar_Merge(&X, &Y);
36+
printf("X + Y: got %ld elements, mean=%g, var=%g\n",
1837
X.count,
1938
meanvar_Mean(&X),
2039
meanvar_Var(&X, 1));

0 commit comments

Comments
 (0)