2023-08-28 12:31:18 +02:00
|
|
|
|
/* Histogram class.
|
2018-08-22 11:29:39 +02:00
|
|
|
|
*
|
2023-08-31 11:17:07 +02:00
|
|
|
|
* Author: Steffen Vogel <post@steffenvogel.de>
|
|
|
|
|
* SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
2023-08-28 12:31:18 +02:00
|
|
|
|
*/
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2019-06-26 20:15:26 +02:00
|
|
|
|
#include <algorithm>
|
2023-09-07 13:19:19 +02:00
|
|
|
|
#include <cmath>
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2021-08-11 12:40:19 -04:00
|
|
|
|
#include <villas/config.hpp>
|
2019-10-27 20:23:47 +01:00
|
|
|
|
#include <villas/exceptions.hpp>
|
2023-09-07 13:19:19 +02:00
|
|
|
|
#include <villas/hist.hpp>
|
|
|
|
|
#include <villas/table.hpp>
|
|
|
|
|
#include <villas/utils.hpp>
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2021-02-16 14:15:38 +01:00
|
|
|
|
using namespace villas;
|
2019-05-30 12:43:37 +02:00
|
|
|
|
using namespace villas::utils;
|
|
|
|
|
|
2019-06-03 17:31:12 +02:00
|
|
|
|
namespace villas {
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
Hist::Hist(int buckets, Hist::cnt_t wu)
|
|
|
|
|
: resolution(0), high(0), low(0),
|
|
|
|
|
highest(std::numeric_limits<double>::min()),
|
|
|
|
|
lowest(std::numeric_limits<double>::max()), last(0), total(0), warmup(wu),
|
|
|
|
|
higher(0), lower(0), data(buckets, 0), _m{0, 0}, _s{0, 0} {}
|
|
|
|
|
|
|
|
|
|
void Hist::put(double value) {
|
|
|
|
|
last = value;
|
|
|
|
|
|
|
|
|
|
// Update min/max
|
|
|
|
|
if (value > highest)
|
|
|
|
|
highest = value;
|
|
|
|
|
if (value < lowest)
|
|
|
|
|
lowest = value;
|
|
|
|
|
|
|
|
|
|
if (data.size()) {
|
|
|
|
|
if (total < warmup) {
|
|
|
|
|
// We are still in warmup phase... Waiting for more samples...
|
|
|
|
|
} else if (data.size() && total == warmup && warmup != 0) {
|
|
|
|
|
low = getMean() - 3 * getStddev();
|
|
|
|
|
high = getMean() + 3 * getStddev();
|
|
|
|
|
resolution = (high - low) / data.size();
|
|
|
|
|
} else if (data.size() && (total == warmup) && (warmup == 0)) {
|
|
|
|
|
// There is no warmup phase
|
|
|
|
|
// TODO resolution = ?
|
|
|
|
|
} else {
|
|
|
|
|
idx_t idx = std::round((value - low) / resolution);
|
|
|
|
|
|
|
|
|
|
// Check bounds and increment
|
|
|
|
|
if (idx >= (idx_t)data.size())
|
|
|
|
|
higher++;
|
|
|
|
|
else if (idx < 0)
|
|
|
|
|
lower++;
|
|
|
|
|
else
|
|
|
|
|
data[idx]++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
total++;
|
|
|
|
|
|
|
|
|
|
// Online / running calculation of variance and mean
|
|
|
|
|
// by Donald Knuth’s Art of Computer Programming, Vol 2, page 232, 3rd edition
|
|
|
|
|
if (total == 1) {
|
|
|
|
|
_m[1] = _m[0] = value;
|
|
|
|
|
_s[1] = 0.0;
|
|
|
|
|
} else {
|
|
|
|
|
_m[0] = _m[1] + (value - _m[1]) / total;
|
|
|
|
|
_s[0] = _s[1] + (value - _m[1]) * (value - _m[0]);
|
|
|
|
|
|
|
|
|
|
// Set up for next iteration
|
|
|
|
|
_m[1] = _m[0];
|
|
|
|
|
_s[1] = _s[0];
|
|
|
|
|
}
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
void Hist::reset() {
|
|
|
|
|
total = 0;
|
|
|
|
|
higher = 0;
|
|
|
|
|
lower = 0;
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
highest = std::numeric_limits<double>::min();
|
|
|
|
|
lowest = std::numeric_limits<double>::max();
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
for (auto &elm : data)
|
|
|
|
|
elm = 0;
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
double Hist::getMean() const {
|
|
|
|
|
return total > 0 ? _m[0] : std::numeric_limits<double>::quiet_NaN();
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
double Hist::getVar() const {
|
|
|
|
|
return total > 1 ? _s[0] / (total - 1)
|
|
|
|
|
: std::numeric_limits<double>::quiet_NaN();
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
double Hist::getStddev() const { return sqrt(getVar()); }
|
|
|
|
|
|
2024-04-09 11:18:22 +02:00
|
|
|
|
void Hist::print(Logger logger, bool details, std::string prefix) const {
|
2023-09-07 13:19:19 +02:00
|
|
|
|
if (total > 0) {
|
|
|
|
|
Hist::cnt_t missed = total - higher - lower;
|
|
|
|
|
|
2024-05-27 08:42:13 +02:00
|
|
|
|
logger->info("{}Counted values: {} ({} between {} and {})", prefix, total,
|
|
|
|
|
missed, low, high);
|
2024-04-09 11:18:22 +02:00
|
|
|
|
logger->info("{}Highest: {:g}", prefix, highest);
|
|
|
|
|
logger->info("{}Lowest: {:g}", prefix, lowest);
|
|
|
|
|
logger->info("{}Mu: {:g}", prefix, getMean());
|
|
|
|
|
logger->info("{}1/Mu: {:g}", prefix, 1.0 / getMean());
|
|
|
|
|
logger->info("{}Variance: {:g}", prefix, getVar());
|
|
|
|
|
logger->info("{}Stddev: {:g}", prefix, getStddev());
|
2023-09-07 13:19:19 +02:00
|
|
|
|
|
|
|
|
|
if (details && total - higher - lower > 0) {
|
|
|
|
|
char *buf = dump();
|
2024-04-09 11:18:22 +02:00
|
|
|
|
logger->info("{}Matlab: {}", prefix, buf);
|
2023-09-07 13:19:19 +02:00
|
|
|
|
free(buf);
|
|
|
|
|
|
|
|
|
|
plot(logger);
|
|
|
|
|
}
|
|
|
|
|
} else
|
2024-04-09 11:18:22 +02:00
|
|
|
|
logger->info("{}Counted values: {}", prefix, total);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
void Hist::plot(Logger logger) const {
|
|
|
|
|
// Get highest bar
|
|
|
|
|
Hist::cnt_t max = *std::max_element(data.begin(), data.end());
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
std::vector<TableColumn> cols = {
|
|
|
|
|
{-9, TableColumn::Alignment::RIGHT, "Value", "%+9.3g"},
|
|
|
|
|
{-6, TableColumn::Alignment::RIGHT, "Count", "%6ju"},
|
2024-04-09 11:17:49 +02:00
|
|
|
|
{0, TableColumn::Alignment::LEFT, "Plot", "%s", "occurrences"}};
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
Table table = Table(logger, cols);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
// Print plot
|
|
|
|
|
table.header();
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
for (size_t i = 0; i < data.size(); i++) {
|
|
|
|
|
double value = low + (i)*resolution;
|
|
|
|
|
Hist::cnt_t cnt = data[i];
|
|
|
|
|
int bar = cols[2].getWidth() * ((double)cnt / max);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
char *buf = strf("%s", "");
|
|
|
|
|
for (int i = 0; i < bar; i++)
|
|
|
|
|
buf = strcatf(&buf, "\u2588");
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
table.row(3, value, cnt, buf);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
free(buf);
|
|
|
|
|
}
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
char *Hist::dump() const {
|
|
|
|
|
char *buf = new char[128];
|
|
|
|
|
if (!buf)
|
|
|
|
|
throw MemoryAllocationError();
|
2019-10-27 20:23:47 +01:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
memset(buf, 0, 128);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
strcatf(&buf, "[ ");
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
for (auto elm : data)
|
|
|
|
|
strcatf(&buf, "%ju ", elm);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
strcatf(&buf, "]");
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
return buf;
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
json_t *Hist::toJson() const {
|
|
|
|
|
json_t *json_buckets, *json_hist;
|
|
|
|
|
|
|
|
|
|
json_hist = json_pack("{ s: f, s: f, s: i }", "low", low, "high", high,
|
|
|
|
|
"total", total);
|
|
|
|
|
|
|
|
|
|
if (total > 0) {
|
|
|
|
|
json_object_update(json_hist,
|
|
|
|
|
json_pack("{ s: i, s: i, s: f, s: f, s: f, s: f, s: f }",
|
|
|
|
|
"higher", higher, "lower", lower, "highest",
|
|
|
|
|
highest, "lowest", lowest, "mean", getMean(),
|
|
|
|
|
"variance", getVar(), "stddev", getStddev()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (total - lower - higher > 0) {
|
|
|
|
|
json_buckets = json_array();
|
|
|
|
|
|
|
|
|
|
for (auto elm : data)
|
|
|
|
|
json_array_append(json_buckets, json_integer(elm));
|
|
|
|
|
|
|
|
|
|
json_object_set(json_hist, "buckets", json_buckets);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return json_hist;
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
int Hist::dumpJson(FILE *f) const {
|
|
|
|
|
json_t *j = Hist::toJson();
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
int ret = json_dumpf(j, f, 0);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
json_decref(j);
|
2018-08-22 11:29:39 +02:00
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
return ret;
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:19:19 +02:00
|
|
|
|
int Hist::dumpMatlab(FILE *f) const {
|
|
|
|
|
fprintf(f, "struct(");
|
|
|
|
|
fprintf(f, "'low', %f, ", low);
|
|
|
|
|
fprintf(f, "'high', %f, ", high);
|
|
|
|
|
fprintf(f, "'total', %ju, ", total);
|
|
|
|
|
fprintf(f, "'higher', %ju, ", higher);
|
|
|
|
|
fprintf(f, "'lower', %ju, ", lower);
|
|
|
|
|
fprintf(f, "'highest', %f, ", highest);
|
|
|
|
|
fprintf(f, "'lowest', %f, ", lowest);
|
|
|
|
|
fprintf(f, "'mean', %f, ", getMean());
|
|
|
|
|
fprintf(f, "'variance', %f, ", getVar());
|
|
|
|
|
fprintf(f, "'stddev', %f, ", getStddev());
|
|
|
|
|
|
|
|
|
|
if (total - lower - higher > 0) {
|
|
|
|
|
char *buf = dump();
|
|
|
|
|
fprintf(f, "'buckets', %s", buf);
|
|
|
|
|
free(buf);
|
|
|
|
|
} else
|
|
|
|
|
fprintf(f, "'buckets', zeros(1, %zu)", data.size());
|
|
|
|
|
|
|
|
|
|
fprintf(f, ")");
|
|
|
|
|
|
|
|
|
|
return 0;
|
2018-08-22 11:29:39 +02:00
|
|
|
|
}
|
2019-06-03 17:31:12 +02:00
|
|
|
|
|
2022-12-02 17:16:44 +01:00
|
|
|
|
} // namespace villas
|