amiga-ntimed/combine_delta.c

216 lines
5.5 KiB
C

/*-
* Copyright (c) 2014 Poul-Henning Kamp
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Source Combiner based on delta-pdfs
* ===================================
*
* The basic principle here is that sources gives us four values:
* - The highest low value were the probability is zero.
* - The lowest high value were the probability is zero.
* - The most probable value
* - The relative trust in that value [0...1]
* Together this defines a triangular probability density function.
*
* The combiner adds all these pdfs' together weighted by trust
* and finds the highest probability which sports a quorum.
*
* See also: http://phk.freebsd.dk/time/20141107.html
*
* XXX: decay trust by age
* XXX: auto determine quorom if param not set
* XXX: param for minimum probability density
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "ntimed.h"
#include "atimed.h"
struct cd_stat {
double x;
double prob;
unsigned quorum;
};
struct cd_source {
unsigned magic;
#define CD_SOURCE_MAGIC 0x2799775c
TAILQ_ENTRY(cd_source) list;
struct combiner combiner;
struct combine_delta *cd;
double trust, low, mid, high;
int tb_gen;
};
struct combine_delta {
unsigned magic;
#define COMBINE_DELTA_MAGIC 0x8dc5030c
unsigned nsrc;
TAILQ_HEAD(, cd_source) head;
};
struct combine_delta *
CD_New(void)
{
struct combine_delta *cd;
ALLOC_OBJ(cd, COMBINE_DELTA_MAGIC);
AN(cd);
TAILQ_INIT(&cd->head);
return (cd);
}
static void
cd_try_peak(const struct combine_delta *cd, double *mx, double *my, double x,
struct cd_stat *st)
{
struct cd_source *cs;
// XXX: Hack to make plots with log zscale and only one
// XXX: source look sensible.
st->x = x;
st->prob = 0.001;
st->quorum = 0;
TAILQ_FOREACH(cs, &cd->head, list) {
if (cs->tb_gen != TB_generation)
continue;
if (x < cs->low)
continue;
if (x > cs->high)
continue;
if (cs->low >= cs->high)
continue;
st->quorum++;
if (x < cs->mid) {
st->prob += cs->trust * 2.0 * (x - cs->low) /
((cs->high - cs->low) * (cs->mid - cs->low));
} else {
st->prob += cs->trust * 2.0 * (cs->high - x) /
((cs->high - cs->low) * (cs->high - cs->mid));
}
if (isnan(st->prob)) {
Fail(NULL, 0, "lo %.3e hi %.3e mid %.3e",
cs->low, cs->high, cs->mid);
}
}
if (st->prob > *my) {
*my = st->prob;
*mx = x;
}
}
static int
stat_cmp(const void *p1, const void *p2)
{
const struct cd_stat *left = p1;
const struct cd_stat *right = p2;
/*lint -save -e514 */
return ((left->x > right->x) - (left->x < right->x));
/*lint -restore */
}
static void
cd_find_peak(struct ocx *ocx, const struct combine_delta *cd)
{
struct cd_source *cs;
double max_x = 0;
double max_y = 1;
struct cd_stat st[cd->nsrc * 3L];
size_t m;
m = 0;
TAILQ_FOREACH(cs, &cd->head, list) {
if (cs->tb_gen != TB_generation)
continue;
cd_try_peak(cd, &max_x, &max_y, cs->low, &st[m++]);
cd_try_peak(cd, &max_x, &max_y, cs->mid, &st[m++]);
cd_try_peak(cd, &max_x, &max_y, cs->high, &st[m++]);
}
Put(ocx, OCX_TRACE,
" %.3e %.3e %.3e\n", max_x, max_y, log(max_y)/log(10.));
PLL(ocx, max_x, max_y);
qsort(st, cd->nsrc * 3L, sizeof st[0], stat_cmp);
}
static void __match_proto__(combine_f)
cd_filter(struct ocx *ocx, const struct combiner *cb,
double trust, double low, double mid, double high)
{
struct cd_source *cs;
struct combine_delta *cd;
CHECK_OBJ_NOTNULL(cb, COMBINER_MAGIC);
CAST_OBJ_NOTNULL(cs, cb->priv, CD_SOURCE_MAGIC);
cd = cs->cd;
CHECK_OBJ_NOTNULL(cd, COMBINE_DELTA_MAGIC);
/* Sign: local - remote -> postive is ahead */
assert(trust >= 0 && trust <= 1.0);
cs->trust = trust;
cs->low = low;
cs->mid = mid;
cs->high = high;
cs->tb_gen = TB_generation;
Put(ocx, OCX_TRACE,
"Combine %s %s %.6f %.6f %.6f", cb->name1, cb->name2,
cs->low, cs->mid, cs->high);
cd_find_peak(ocx, cd);
}
struct combiner *
CD_AddSource(struct combine_delta *cd, const char *name1, const char *name2)
{
struct cd_source *cs;
CHECK_OBJ_NOTNULL(cd, COMBINE_DELTA_MAGIC);
ALLOC_OBJ(cs, CD_SOURCE_MAGIC);
AN(cs);
cs->cd = cd;
cs->low = cs->mid = cs->high = nan("");
TAILQ_INSERT_TAIL(&cd->head, cs, list);
INIT_OBJ(&cs->combiner, COMBINER_MAGIC);
cs->combiner.func = cd_filter;
cs->combiner.priv = cs;
cs->combiner.name1 = name1;
cs->combiner.name2 = name2;
cd->nsrc += 1;
return (&cs->combiner);
}