forked from apache/datasketches-cpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtdigest.hpp
More file actions
340 lines (292 loc) · 11.4 KB
/
Copy pathtdigest.hpp
File metadata and controls
340 lines (292 loc) · 11.4 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 _TDIGEST_HPP_
#define _TDIGEST_HPP_
#include <cstddef>
#include <limits>
#include <type_traits>
#include <vector>
#include "common_defs.hpp"
namespace datasketches {
// this is equivalent of K_2 (default) in the Java implementation mentioned below
// Generates cluster sizes proportional to q*(1-q).
// The use of a normalizing function results in a strictly bounded number of clusters no matter how many samples.
struct scale_function {
double max(double q, double normalizer) const {
return q * (1 - q) / normalizer;
}
double normalizer(double compression, double n) const {
return compression / z(compression, n);
}
double z(double compression, double n) const {
return 4 * std::log(n / compression) + 24;
}
};
// forward declaration
template <typename T, typename Allocator = std::allocator<T>> class tdigest;
/// TDigest float sketch
using tdigest_float = tdigest<float>;
/// TDigest double sketch
using tdigest_double = tdigest<double>;
/**
* t-Digest for estimating quantiles and ranks.
* This implementation is based on the following paper:
* Ted Dunning, Otmar Ertl. Extremely Accurate Quantiles Using t-Digests
* and the following implementation in Java:
* https://github.com/tdunning/t-digest
* This implementation is similar to MergingDigest in the above Java implementation
*/
template <typename T, typename Allocator>
class tdigest {
// exclude long double by not using std::is_floating_point
static_assert(std::is_same<T, double>::value || std::is_same<T, float>::value, "Either double or float type expected");
static_assert(std::numeric_limits<T>::is_iec559, "IEEE 754 compatibility required");
public:
using value_type = T;
using allocator_type = Allocator;
static const uint16_t DEFAULT_K = 200;
using W = typename std::conditional<std::is_same<T, double>::value, uint64_t, uint32_t>::type;
class centroid {
public:
centroid(T value, W weight): mean_(value), weight_(weight) {}
void add(const centroid& other) {
weight_ += other.weight_;
mean_ += (other.mean_ - mean_) * other.weight_ / weight_;
}
T get_mean() const { return mean_; }
W get_weight() const { return weight_; }
private:
T mean_;
W weight_;
};
using vector_t = std::vector<T, Allocator>;
using vector_centroid = std::vector<centroid, typename std::allocator_traits<Allocator>::template rebind_alloc<centroid>>;
using vector_bytes = std::vector<uint8_t, typename std::allocator_traits<Allocator>::template rebind_alloc<uint8_t>>;
using vector_double = std::vector<double, typename std::allocator_traits<Allocator>::template rebind_alloc<double>>;
struct centroid_cmp {
centroid_cmp() {}
bool operator()(const centroid& a, const centroid& b) const {
if (a.get_mean() < b.get_mean()) return true;
return false;
}
};
/**
* Constructor
* @param k affects the size of the sketch and its estimation error
* @param allocator used to allocate memory
*/
explicit tdigest(uint16_t k = DEFAULT_K, const Allocator& allocator = Allocator());
/**
* Update this t-Digest with the given value
* NaN and infinity values are ignored
* @param value to update the t-Digest with
*/
void update(T value);
/**
* Merge the given t-Digest into this one
* @param other t-Digest to merge
*/
void merge(const tdigest& other);
/**
* Process buffered values and merge centroids if needed
*/
void compress();
/**
* @return true if t-Digest has not seen any data
*/
bool is_empty() const;
/**
* @return minimum value seen by t-Digest
*/
T get_min_value() const;
/**
* @return maximum value seen by t-Digest
*/
T get_max_value() const;
/**
* @return total weight
*/
uint64_t get_total_weight() const;
/**
* Returns an instance of the allocator for this t-Digest.
* @return allocator
*/
Allocator get_allocator() const;
/**
* Compute approximate normalized rank of the given value.
*
* <p>If the sketch is empty this throws std::runtime_error.
* <p>NaN value throw std::invalid_argument.
*
* @param value to be ranked
* @return normalized rank (from 0 to 1 inclusive)
*/
double get_rank(T value) const;
/**
* Compute approximate quantile value corresponding to the given normalized rank
*
* <p>If the sketch is empty this throws std::runtime_error.
*
* @param rank normalized rank (from 0 to 1 inclusive)
* @return quantile value corresponding to the given rank
*/
T get_quantile(double rank) const;
/**
* Returns an approximation to the Probability Mass Function (PMF) of the input stream
* given a set of split points.
*
* <p>If the sketch is empty this throws std::runtime_error.
*
* @param split_points an array of <i>m</i> unique, monotonically increasing values
* that divide the input domain into <i>m+1</i> consecutive disjoint intervals (bins).
*
* @param size the number of split points in the array
*
* @return an array of m+1 doubles each of which is an approximation
* to the fraction of the input stream values (the mass) that fall into one of those intervals.
*/
vector_double get_PMF(const T* split_points, uint32_t size) const;
/**
* Returns an approximation to the Cumulative Distribution Function (CDF), which is the
* cumulative analog of the PMF, of the input stream given a set of split points.
*
* <p>If the sketch is empty this throws std::runtime_error.
*
* @param split_points an array of <i>m</i> unique, monotonically increasing values
* that divide the input domain into <i>m+1</i> consecutive disjoint intervals.
*
* @param size the number of split points in the array
*
* @return an array of m+1 doubles, which are a consecutive approximation to the CDF
* of the input stream given the split_points. The value at array position j of the returned
* CDF array is the sum of the returned values in positions 0 through j of the returned PMF
* array. This can be viewed as array of ranks of the given split points plus one more value
* that is always 1.
*/
vector_double get_CDF(const T* split_points, uint32_t size) const;
/**
* @return parameter k (compression) that was used to configure this t-Digest
*/
uint16_t get_k() const;
/**
* Human-readable summary of this t-Digest as a string
* @param print_centroids if true append the list of centroids with weights
* @return summary of this t-Digest
*/
string<Allocator> to_string(bool print_centroids = false) const;
/**
* Computes size needed to serialize the current state.
* @param with_buffer optionally serialize buffered values avoiding compression
* @return size in bytes needed to serialize this tdigest
*/
size_t get_serialized_size_bytes(bool with_buffer = false) const;
/**
* This method serializes t-Digest into a given stream in a binary form
* @param os output stream
* @param with_buffer optionally serialize buffered values avoiding compression
*/
void serialize(std::ostream& os, bool with_buffer = false) const;
/**
* This method serializes t-Digest as a vector of bytes.
* An optional header can be reserved in front of the sketch.
* It is an uninitialized space of a given size.
* @param header_size_bytes space to reserve in front of the sketch
* @param with_buffer optionally serialize buffered values avoiding compression
* @return serialized sketch as a vector of bytes
*/
vector_bytes serialize(unsigned header_size_bytes = 0, bool with_buffer = false) const;
/**
* This method deserializes t-Digest from a given stream.
* @param is input stream
* @param allocator instance of an Allocator
* @return an instance of t-Digest
*/
static tdigest deserialize(std::istream& is, const Allocator& allocator = Allocator());
/**
* This method deserializes t-Digest from a given array of bytes.
* @param bytes pointer to the array of bytes
* @param size the size of the array
* @param allocator instance of an Allocator
* @return an instance of t-Digest
*/
static tdigest deserialize(const void* bytes, size_t size, const Allocator& allocator = Allocator());
class const_iterator;
/**
* Iterator pointing to the first centroid in the sketch.
* If the sketch is empty, the returned iterator must not be dereferenced or incremented.
* @return iterator pointing to the first centroid in the sketch
*/
const_iterator begin() const;
/**
* Iterator pointing to the past-the-end centroid in the sketch.
* It does not point to any centroid, and must not be dereferenced or incremented.
* @return iterator pointing to the past-the-end centroid in the sketch
*/
const_iterator end() const;
private:
bool reverse_merge_;
uint16_t k_;
T min_;
T max_;
size_t centroids_capacity_;
vector_centroid centroids_;
uint64_t centroids_weight_;
vector_t buffer_;
static const size_t BUFFER_MULTIPLIER = 4;
static const uint8_t PREAMBLE_LONGS_EMPTY_OR_SINGLE = 1;
static const uint8_t PREAMBLE_LONGS_MULTIPLE = 2;
static const uint8_t SERIAL_VERSION = 1;
static const uint8_t SKETCH_TYPE = 20;
static const uint8_t COMPAT_DOUBLE = 1;
static const uint8_t COMPAT_FLOAT = 2;
enum flags { IS_EMPTY, IS_SINGLE_VALUE, REVERSE_MERGE };
bool is_single_value() const;
uint8_t get_preamble_longs() const;
void merge(vector_centroid& buffer, W weight);
// for deserialize
tdigest(bool reverse_merge, uint16_t k, T min, T max, vector_centroid&& centroids, uint64_t total_weight_, vector_t&& buffer);
static double weighted_average(double x1, double w1, double x2, double w2);
// for compatibility with format of the reference implementation
static tdigest deserialize_compat(std::istream& is, const Allocator& allocator = Allocator());
static tdigest deserialize_compat(const void* bytes, size_t size, const Allocator& allocator = Allocator());
static inline void check_split_points(const T* values, uint32_t size);
};
template<typename T, typename A>
class tdigest<T, A>::const_iterator {
public:
using iterator_category = std::input_iterator_tag;
using value_type = std::pair<T, W>;
using difference_type = void;
using pointer = const return_value_holder<value_type>;
using reference = const value_type;
const_iterator& operator++();
const_iterator& operator++(int);
bool operator==(const const_iterator& other) const;
bool operator!=(const const_iterator& other) const;
reference operator*() const;
pointer operator->() const;
private:
friend class tdigest;
uint32_t index_;
vector_centroid centroids_;
const_iterator(const tdigest& tdigest_, bool is_end);
};
} /* namespace datasketches */
#include "tdigest_impl.hpp"
#endif // _TDIGEST_HPP_