2323#include < algorithm>
2424#include < sstream>
2525
26+ #include " memory_operations.hpp"
2627#include " conditional_forward.hpp"
2728
2829namespace datasketches {
@@ -35,7 +36,22 @@ dim_(dim),
3536num_retained_(0 ),
3637n_(0 ),
3738levels_(1 , Level(allocator), allocator)
38- {}
39+ {
40+ check_k (k);
41+ }
42+
43+ template <typename T, typename K, typename A>
44+ density_sketch<T, K, A>::density_sketch(uint16_t k, uint32_t dim, uint32_t num_retained, uint64_t n,
45+ Levels&& levels, const K& kernel):
46+ kernel_ (kernel),
47+ k_(k),
48+ dim_(dim),
49+ num_retained_(num_retained),
50+ n_(n),
51+ levels_(std::move(levels))
52+ {
53+ check_k (k);
54+ }
3955
4056template <typename T, typename K, typename A>
4157uint16_t density_sketch<T, K, A>::get_k() const {
@@ -146,6 +162,259 @@ void density_sketch<T, K, A>::compact_level(unsigned height) {
146162 level.clear ();
147163}
148164
165+ /* Serialized sketch layout:
166+ * Int || Start Byte Addr:
167+ * Addr:
168+ * || 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
169+ * 0 || Preamble_Ints | SerVer | FamID | Flags |------- K -------|---- unused -----|
170+ *
171+ * || 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
172+ * 2 ||------------- Num Dimensions --------------|------ Num Retained Items ---------|
173+ *
174+ * || 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
175+ * 4 ||---------------------------Items Seen Count (N)--------------------------------|
176+ *
177+ * Ints 2 and 3 are omitted when the sketch is empty, meaning Num Dimensions is stored at
178+ * offset 8 in that case. Otherwise, Int 5 is the start of level data, consisting of the
179+ * size of the level (as a uint32 value) followed by that number of points, with
180+ * Num Dimensions per point.
181+ */
182+
183+ template <typename T, typename K, typename A>
184+ void density_sketch<T, K, A>::serialize(std::ostream& os) const {
185+ const uint8_t preamble_ints = is_empty () ? PREAMBLE_INTS_SHORT : PREAMBLE_INTS_LONG ;
186+ write (os, preamble_ints);
187+ const uint8_t ser_ver = SERIAL_VERSION ;
188+ write (os, ser_ver);
189+ const uint8_t family = FAMILY_ID ;
190+ write (os, family);
191+
192+ // only empty is a valid flag
193+ const uint8_t flags_byte = (is_empty () ? 1 << flags::IS_EMPTY : 0 );
194+ write (os, flags_byte);
195+ write (os, k_);
196+ const uint16_t unused = 0 ;
197+ write (os, unused);
198+ write (os, dim_);
199+
200+ if (is_empty ())
201+ return ;
202+
203+ write (os, num_retained_);
204+ write (os, n_);
205+
206+ // levels array -- uint32_t since a single level may be larger than k
207+ size_t pt_size = sizeof (T) * dim_;
208+ for (const Level& lvl : levels_) {
209+ const uint32_t level_size = static_cast <uint32_t >(lvl.size ());
210+ write (os, level_size);
211+ for (const Vector& pt : lvl) {
212+ write (os, pt.data (), pt_size);
213+ }
214+ }
215+ }
216+
217+ template <typename T, typename K, typename A>
218+ auto density_sketch<T, K, A>::serialize(unsigned header_size_bytes) const -> vector_bytes {
219+ const uint8_t preamble_ints = (is_empty () ? PREAMBLE_INTS_SHORT : PREAMBLE_INTS_LONG );
220+
221+ // pre-compute size
222+ size_t size = header_size_bytes + preamble_ints * sizeof (uint32_t );
223+ if (!is_empty ())
224+ for (const Level& lvl : levels_)
225+ size += sizeof (uint32_t ) + (lvl.size () * dim_ * sizeof (T));
226+
227+ vector_bytes bytes (size, 0 , levels_.get_allocator ());
228+ uint8_t * ptr = bytes.data () + header_size_bytes;
229+ const uint8_t * end_ptr = ptr + size;
230+
231+ ptr += copy_to_mem (preamble_ints, ptr);
232+ const uint8_t ser_ver = SERIAL_VERSION ;
233+ ptr += copy_to_mem (ser_ver, ptr);
234+ const uint8_t family = FAMILY_ID ;
235+ ptr += copy_to_mem (family, ptr);
236+
237+ // empty is the only valid flat
238+ const uint8_t flags_byte = (is_empty () ? 1 << flags::IS_EMPTY : 0 );
239+ ptr += copy_to_mem (flags_byte, ptr);
240+ ptr += copy_to_mem (k_, ptr);
241+ ptr += sizeof (uint16_t ); // 2 unused bytes
242+ ptr += copy_to_mem (dim_, ptr);
243+
244+ if (is_empty ())
245+ return bytes;
246+
247+ ptr += copy_to_mem (num_retained_, ptr);
248+ ptr += copy_to_mem (n_, ptr);
249+
250+ // levels array -- uint32_t since a single level may be larger than k
251+ size_t pt_size = sizeof (T) * dim_;
252+ for (const Level& lvl : levels_) {
253+ ptr += copy_to_mem (static_cast <uint32_t >(lvl.size ()), ptr);
254+ for (const Vector& pt : lvl) {
255+ ptr += copy_to_mem (pt.data (), ptr, pt_size);
256+ }
257+ }
258+
259+ if (ptr != end_ptr)
260+ throw std::runtime_error (" Actual output size does not equal expected output size" );
261+
262+ return bytes;
263+ }
264+
265+ template <typename T, typename K, typename A>
266+ density_sketch<T, K, A> density_sketch<T, K, A>::deserialize(std::istream& is, const K& kernel, const A& allocator) {
267+ const auto preamble_ints = read<uint8_t >(is);
268+ const auto serial_version = read<uint8_t >(is);
269+ const auto family_id = read<uint8_t >(is);
270+ const auto flags_byte = read<uint8_t >(is);
271+ const auto k = read<uint16_t >(is);
272+ read<uint16_t >(is); // unused
273+ const auto dim = read<uint32_t >(is);
274+
275+ check_k (k); // do we have constraints?
276+ check_serial_version (serial_version); // a little redundant with the header check
277+ check_family_id (family_id);
278+ check_header_validity (preamble_ints, flags_byte, serial_version);
279+
280+ if (!is.good ()) throw std::runtime_error (" error reading from std::istream" );
281+ const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY )) > 0 ;
282+ if (is_empty) {
283+ return density_sketch (k, dim, kernel, allocator);
284+ }
285+
286+ const auto num_retained = read<uint32_t >(is);
287+ const auto n = read<uint64_t >(is);
288+
289+ // levels arrays
290+ size_t pt_size = sizeof (T) * dim;
291+ Levels levels (allocator);
292+ int64_t num_to_read = num_retained; // num_retrained is uint32_t so this allows error checking
293+ while (num_to_read > 0 ) {
294+ const auto level_size = read<uint32_t >(is);
295+ Level lvl (allocator);
296+ lvl.reserve (level_size);
297+ for (uint32_t i = 0 ; i < level_size; ++i) {
298+ Vector pt (dim, 0 , allocator);
299+ read (is, pt.data (), pt_size);
300+ lvl.push_back (pt);
301+ }
302+ levels.push_back (lvl);
303+ num_to_read -= lvl.size ();
304+ }
305+
306+ if (num_to_read != 0 )
307+ throw std::runtime_error (" Error deserializing sketch: Incorrect number of items read" );
308+ if (!is.good ()) throw std::runtime_error (" error reading from std::istream" );
309+
310+ return density_sketch (k, dim, num_retained, n, std::move (levels), kernel);
311+ }
312+
313+ template <typename T, typename K, typename A>
314+ density_sketch<T, K, A> density_sketch<T, K, A>::deserialize(const void * bytes, size_t size, const K& kernel, const A& allocator) {
315+ ensure_minimum_memory (size, PREAMBLE_INTS_SHORT * sizeof (uint32_t ));
316+ const char * ptr = static_cast <const char *>(bytes);
317+ const char * end_ptr = static_cast <const char *>(bytes) + size;
318+ uint8_t preamble_ints;
319+ ptr += copy_from_mem (ptr, preamble_ints);
320+ uint8_t serial_version;
321+ ptr += copy_from_mem (ptr, serial_version);
322+ uint8_t family_id;
323+ ptr += copy_from_mem (ptr, family_id);
324+ uint8_t flags_byte;
325+ ptr += copy_from_mem (ptr, flags_byte);
326+ uint16_t k;
327+ ptr += copy_from_mem (ptr, k);
328+ uint16_t unused;
329+ ptr += copy_from_mem (ptr, unused);
330+ uint32_t dim;
331+ ptr += copy_from_mem (ptr, dim);
332+
333+ check_k (k);
334+ check_serial_version (serial_version); // a little redundant with the header check
335+ check_family_id (family_id);
336+ check_header_validity (preamble_ints, flags_byte, serial_version);
337+
338+ const bool is_empty = (flags_byte & (1 << flags::IS_EMPTY )) > 0 ;
339+ if (is_empty) {
340+ return density_sketch (k, dim, kernel, allocator);
341+ }
342+
343+ ensure_minimum_memory (size, PREAMBLE_INTS_LONG * sizeof (uint32_t ));
344+ uint32_t num_retained;
345+ ptr += copy_from_mem (ptr, num_retained);
346+ uint64_t n;
347+ ptr += copy_from_mem (ptr, n);
348+
349+ // Predicting the number of levels seems hard so determining the exact remaining
350+ // size is also hard. But we need at least num_retained * dim * sizeof(T)
351+ // bytes for the points so we can check that.
352+ size_t pt_size = sizeof (T) * dim;
353+ ensure_minimum_memory (end_ptr - ptr, num_retained * pt_size);
354+
355+ // levels arrays
356+ Levels levels (allocator);
357+ int64_t num_to_read = num_retained; // num_retrained is uint32_t so this allows error checking
358+ while (num_to_read > 0 ) {
359+ uint32_t level_size;
360+ ptr += copy_from_mem (ptr, level_size);
361+ Level lvl (allocator);
362+ lvl.reserve (level_size);
363+ for (uint32_t i = 0 ; i < level_size; ++i) {
364+ Vector pt (dim, 0 , allocator);
365+ ptr += copy_from_mem (ptr, pt.data (), pt_size);
366+ lvl.push_back (pt);
367+ }
368+ levels.push_back (lvl);
369+ num_to_read -= lvl.size ();
370+ }
371+
372+ if (num_to_read != 0 )
373+ throw std::runtime_error (" Error deserializing sketch: Incorrect number of items read" );
374+ if (ptr > end_ptr) throw std::runtime_error (" Error deserializing sketch: Read beyond provided memory" );
375+
376+ return density_sketch (k, dim, num_retained, n, std::move (levels), kernel);
377+ }
378+
379+ template <typename T, typename K, typename A>
380+ void density_sketch<T, K, A>::check_k(uint16_t k) {
381+ if (k < 2 )
382+ throw std::invalid_argument (" k must be > 1. Found: " + std::to_string (k));
383+ }
384+
385+ template <typename T, typename K, typename A>
386+ void density_sketch<T, K, A>::check_serial_version(uint8_t serial_version) {
387+ if (serial_version == SERIAL_VERSION )
388+ return ;
389+ else
390+ throw std::invalid_argument (" Possible corruption. Unrecognized serialization version: " + std::to_string (serial_version));
391+ }
392+
393+ template <typename T, typename K, typename A>
394+ void density_sketch<T, K, A>::check_family_id(uint8_t family_id) {
395+ if (family_id == FAMILY_ID )
396+ return ;
397+ else
398+ throw std::invalid_argument (" Possible corruption. Family id does not indicate density sketch: " + std::to_string (family_id));
399+ }
400+
401+ template <typename T, typename K, typename A>
402+ void density_sketch<T, K, A>::check_header_validity(uint8_t preamble_ints, uint8_t flags_byte, uint8_t serial_version) {
403+ const bool empty = (flags_byte & (1 << flags::IS_EMPTY )) > 0 ;
404+
405+ if ((empty && preamble_ints == PREAMBLE_INTS_SHORT )
406+ || (!empty && preamble_ints == PREAMBLE_INTS_LONG ))
407+ return ;
408+ else {
409+ std::ostringstream os;
410+ os << " Possible sketch corruption. Inconsistent state: "
411+ << " preamble_ints = " << preamble_ints
412+ << " , empty = " << (empty ? " true" : " false" )
413+ << " , serialization_version = " << serial_version;
414+ throw std::invalid_argument (os.str ());
415+ }
416+ }
417+
149418template <typename T, typename K, typename A>
150419string<A> density_sketch<T, K, A>::to_string(bool print_levels, bool print_items) const {
151420 // Using a temporary stream for implementation here does not comply with AllocatorAwareContainer requirements.
0 commit comments