From 16c40e62ea28f9ba6d77f6f1d2b1656773a413b2 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 3 May 2019 18:25:04 -0700 Subject: [PATCH 01/22] theta draft --- Makefile | 4 +- theta/include/theta_sketch.hpp | 248 +++++++ theta/include/theta_sketch_impl.hpp | 689 ++++++++++++++++++ theta/include/theta_union.hpp | 48 ++ theta/include/theta_union_impl.hpp | 68 ++ theta/test/theta_compact_empty_from_java.bin | Bin 0 -> 280 bytes .../theta_compact_estimation_from_java.bin | Bin 0 -> 34760 bytes .../theta_compact_single_item_from_java.bin | Bin 0 -> 16 bytes theta/test/theta_sketch_test.cpp | 159 ++++ theta/test/theta_union_test.cpp | 70 ++ theta/test/theta_update_empty_from_java.bin | Bin 0 -> 1048 bytes .../theta_update_estimation_from_java.bin | Bin 0 -> 65560 bytes theta/theta.mk | 31 + 13 files changed, 1316 insertions(+), 1 deletion(-) create mode 100644 theta/include/theta_sketch.hpp create mode 100644 theta/include/theta_sketch_impl.hpp create mode 100644 theta/include/theta_union.hpp create mode 100644 theta/include/theta_union_impl.hpp create mode 100644 theta/test/theta_compact_empty_from_java.bin create mode 100644 theta/test/theta_compact_estimation_from_java.bin create mode 100644 theta/test/theta_compact_single_item_from_java.bin create mode 100644 theta/test/theta_sketch_test.cpp create mode 100644 theta/test/theta_union_test.cpp create mode 100644 theta/test/theta_update_empty_from_java.bin create mode 100644 theta/test/theta_update_estimation_from_java.bin create mode 100644 theta/theta.mk diff --git a/Makefile b/Makefile index e32e5399..340661c4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ include config.mk +TEST_BINARY_INPUT_PATH = test + BUILDDIR := build TARGETDIR := lib @@ -10,7 +12,7 @@ TARGET := $(TARGETDIR)/$(LIBRARY) INC := -I /usr/local/include LIB := -L /usr/local/lib -lcppunit -L lib -l$(LIB_BASE_NAME) -MODULES := hll cpc kll fi +MODULES := hll cpc kll fi theta .PHONY: all all: $(MODULES) $(LIBRARY) diff --git a/theta/include/theta_sketch.hpp b/theta/include/theta_sketch.hpp new file mode 100644 index 00000000..8148d4d7 --- /dev/null +++ b/theta/include/theta_sketch.hpp @@ -0,0 +1,248 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_SKETCH_HPP_ +#define THETA_SKETCH_HPP_ + +#include +#include +#include + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +// forward-declarations +template class theta_sketch_alloc; +template class compact_theta_sketch_alloc; +template class theta_union_alloc; + +// for serialization as raw bytes +typedef std::unique_ptr> void_ptr_with_deleter; + +template +class theta_sketch_alloc { +public: + static const uint64_t MAX_THETA = LLONG_MAX; // signed max for compatibility with Java + + theta_sketch_alloc(bool is_empty, uint64_t theta); + theta_sketch_alloc(const theta_sketch_alloc& other); + theta_sketch_alloc(theta_sketch_alloc&& other) noexcept; + virtual ~theta_sketch_alloc(); + + theta_sketch_alloc& operator=(const theta_sketch_alloc& other); + theta_sketch_alloc& operator=(theta_sketch_alloc&& other); + + bool is_empty() const; + double get_estimate() const; + double get_lower_bound(uint8_t num_std_dev) const; + double get_upper_bound(uint8_t num_std_dev) const; + bool is_estimation_mode() const; + double get_theta() const; + uint64_t get_theta64() const; + + virtual uint32_t get_num_retained() const = 0; + virtual uint16_t get_seed_hash() const = 0; + virtual bool is_ordered() const = 0; + virtual void to_stream(std::ostream& os, bool print_items = false) const = 0; + virtual void serialize(std::ostream& os) const = 0; + //virtual std::pair serialize(unsigned header_size_bytes = 0) const = 0; + + typedef std::unique_ptr, std::function*)>> unique_ptr; + static unique_ptr deserialize(std::istream& is); + //static unique_ptr deserialize(const void* bytes, size_t size); + + class const_iterator; + virtual const_iterator begin() const = 0; + virtual const_iterator end() const = 0; + +protected: + enum flags { IS_BIG_ENDIAN, IS_READ_ONLY, IS_EMPTY, IS_COMPACT, IS_ORDERED }; + + bool is_empty_; + uint64_t theta_; + + static uint16_t get_seed_hash(uint64_t seed); +}; + +// update sketch + +template +class update_theta_sketch_alloc: public theta_sketch_alloc { +public: + class builder; + enum resize_factor { X1, X2, X4, X8 }; + static const uint8_t SKETCH_TYPE = 2; + static const uint8_t SERIAL_VERSION = 3; + + update_theta_sketch_alloc(const update_theta_sketch_alloc& other); + update_theta_sketch_alloc(update_theta_sketch_alloc&& other) noexcept ; + virtual ~update_theta_sketch_alloc(); + + update_theta_sketch_alloc& operator=(const update_theta_sketch_alloc& other); + update_theta_sketch_alloc& operator=(update_theta_sketch_alloc&& other); + + virtual uint32_t get_num_retained() const; + virtual uint16_t get_seed_hash() const; + virtual bool is_ordered() const; + virtual void to_stream(std::ostream& os, bool print_items = false) const; + virtual void serialize(std::ostream& os) const; + + //void update(const std::string& value); + void update(uint64_t value); + void update(int64_t value); + + // for compatibility with Java implementation + void update(uint32_t value); + void update(int32_t value); + void update(uint16_t value); + void update(int16_t value); + void update(uint8_t value); + void update(int8_t value); + void update(double value); + void update(float value); + + // Be very careful to hash input values consistently using the same approach + // either over time or on different platforms + // or while passing sketches from Java environment or to Java environment + // Otherwise two sketches that should represent overlapping sets will be disjoint + // For instance, for signed 32-bit values call update(int32_t) method above, + // which does widening conversion to int64_t, if compatibility with Java is expected + void update(const void* data, unsigned length); + + compact_theta_sketch_alloc compact(bool ordered = true) const; + + virtual typename theta_sketch_alloc::const_iterator begin() const; + virtual typename theta_sketch_alloc::const_iterator end() const; + +private: + // resize threshold = 0.5 tuned for speed + static constexpr double RESIZE_THRESHOLD = 0.5; + // hash table rebuild threshold = 15/16 + static constexpr double REBUILD_THRESHOLD = 15.0 / 16.0; + + static constexpr uint8_t STRIDE_HASH_BITS = 7; + static constexpr uint32_t STRIDE_MASK = (1 << STRIDE_HASH_BITS) - 1; + + uint8_t lg_cur_size_; + uint8_t lg_nom_size_; + uint64_t* keys_; + uint32_t num_keys_; + resize_factor rf_; + float p_; + uint64_t seed_; + uint32_t capacity_; + + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + + update_theta_sketch_alloc(uint8_t lg_cur_size, uint8_t lg_nom_size, resize_factor rf, float p, uint64_t seed); + void resize(); + void rebuild(); + + friend theta_union_alloc; + void internal_update(uint64_t hash); + + static inline uint32_t get_capacity(uint8_t lg_cur_size, uint8_t lg_nom_size); + static inline uint32_t get_stride(uint64_t hash, uint8_t lg_size); + static bool hash_search_or_insert(uint64_t hash, uint64_t* table, uint8_t lg_size); +}; + +// compact sketch + +template +class compact_theta_sketch_alloc: public theta_sketch_alloc { +public: + static const uint8_t SKETCH_TYPE = 3; + static const uint8_t SERIAL_VERSION = 3; + + compact_theta_sketch_alloc(const compact_theta_sketch_alloc& other); + compact_theta_sketch_alloc(compact_theta_sketch_alloc&& other) noexcept; + virtual ~compact_theta_sketch_alloc(); + + compact_theta_sketch_alloc& operator=(const compact_theta_sketch_alloc& other); + compact_theta_sketch_alloc& operator=(compact_theta_sketch_alloc&& other); + + virtual uint32_t get_num_retained() const; + virtual uint16_t get_seed_hash() const; + virtual bool is_ordered() const; + virtual void to_stream(std::ostream& os, bool print_items = false) const; + virtual void serialize(std::ostream& os) const; + + virtual typename theta_sketch_alloc::const_iterator begin() const; + virtual typename theta_sketch_alloc::const_iterator end() const; + +private: + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + + uint64_t* keys_; + uint32_t num_keys_; + uint16_t seed_hash_; + bool is_ordered_; + + friend theta_sketch_alloc; + friend update_theta_sketch_alloc; + friend theta_union_alloc; + compact_theta_sketch_alloc(bool is_empty, uint64_t theta, uint64_t* keys, uint32_t num_keys, uint16_t seed_hash, bool is_ordered); +}; + +// builder + +template +class update_theta_sketch_alloc::builder { +public: + static const uint8_t MIN_LG_K = 5; + static const uint8_t DEFAULT_LG_K = 12; + static const resize_factor DEFAULT_RESIZE_FACTOR = X8; + static const uint64_t DEFAULT_SEED = 9001; + + builder(); + builder& set_lg_k(uint8_t lg_k); + builder& set_resize_factor(resize_factor rf); + builder& set_p(float p); + builder& set_seed(uint64_t seed); + update_theta_sketch_alloc build() const; +private: + uint8_t lg_k_; + resize_factor rf_; + float p_; + uint64_t seed_; + + static uint8_t starting_sub_multiple(uint8_t lg_tgt, uint8_t lg_min, uint8_t lg_rf); +}; + +// iterator +template +class theta_sketch_alloc::const_iterator: public std::iterator { +public: + const_iterator& operator++(); + const_iterator operator++(int); + bool operator==(const const_iterator& other) const; + bool operator!=(const const_iterator& other) const; + uint64_t operator*() const; + +private: + const uint64_t* keys_; + uint32_t size_; + uint32_t index_; + const_iterator(const uint64_t* keys, uint32_t size, uint32_t index); + friend class update_theta_sketch_alloc; + friend class compact_theta_sketch_alloc; +}; + + +// aliases with default allocator for convenience +typedef theta_sketch_alloc> theta_sketch; +typedef update_theta_sketch_alloc> update_theta_sketch; +typedef compact_theta_sketch_alloc> compact_theta_sketch; + +} /* namespace datasketches */ + +#include "theta_sketch_impl.hpp" + +# endif diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp new file mode 100644 index 00000000..f8664906 --- /dev/null +++ b/theta/include/theta_sketch_impl.hpp @@ -0,0 +1,689 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_SKETCH_IMPL_HPP_ +#define THETA_SKETCH_IMPL_HPP_ + +#include +#include +#include +#include + +#include "MurmurHash3.h" + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +template +theta_sketch_alloc::theta_sketch_alloc(bool is_empty, uint64_t theta): +is_empty_(is_empty), theta_(theta) +{ +// std::cerr << "sketch constructor" << std::endl; +} + +template +theta_sketch_alloc::theta_sketch_alloc(const theta_sketch_alloc& other): +is_empty_(other.is_empty_), theta_(other.theta_) +{ +// std::cerr << "sketch copy constructor" << std::endl; +} + +template +theta_sketch_alloc::theta_sketch_alloc(theta_sketch_alloc&& other) noexcept: +is_empty_(other.is_empty_), theta_(other.theta_) +{ +// std::cerr << "sketch move constructor" << std::endl; +} + +template +theta_sketch_alloc& theta_sketch_alloc::operator=(const theta_sketch_alloc& other) { + is_empty_ = other.is_empty_; + theta_ = other.theta_; +// std::cerr << "sketch copy assignment" << std::endl; + return *this; +} + +template +theta_sketch_alloc& theta_sketch_alloc::operator=(theta_sketch_alloc&& other) { + std::swap(is_empty_, other.is_empty_); + std::swap(theta_, other.theta_); + //std::cerr << "sketch move assignment" << std::endl; + return *this; +} + +template +theta_sketch_alloc::~theta_sketch_alloc() { +// std::cerr << "sketch destructor" << std::endl; +} + +template +bool theta_sketch_alloc::is_empty() const { + return is_empty_; +} + +template +double theta_sketch_alloc::get_estimate() const { + return get_num_retained() / get_theta(); +} + +template +double theta_sketch_alloc::get_lower_bound(uint8_t num_std_dev) const { + return 0; +} + +template +double theta_sketch_alloc::get_upper_bound(uint8_t num_std_dev) const { + return 0; +} + +template +bool theta_sketch_alloc::is_estimation_mode() const { + return theta_ < MAX_THETA and !is_empty_; +} + +template +double theta_sketch_alloc::get_theta() const { + return (double) theta_ / MAX_THETA; +} + +template +uint64_t theta_sketch_alloc::get_theta64() const { + return theta_; +} + +template +typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(std::istream& is) { + uint8_t preamble_longs; + is.read((char*)&preamble_longs, sizeof(preamble_longs)); + uint8_t serial_version; + is.read((char*)&serial_version, sizeof(serial_version)); + uint8_t family_id; + is.read((char*)&family_id, sizeof(family_id)); + uint16_t unused16; + is.read((char*)&unused16, sizeof(unused16)); + uint8_t flags_byte; + is.read((char*)&flags_byte, sizeof(flags_byte)); + uint16_t seed_hash; + is.read((char*)&seed_hash, sizeof(seed_hash)); + + uint64_t theta = MAX_THETA; + uint64_t* keys = nullptr; + uint32_t num_keys = 0; + + const bool is_empty = flags_byte & (1 << flags::IS_EMPTY); + if (!is_empty) { + if (preamble_longs == 1) { + num_keys = 1; + } else { + is.read((char*)&num_keys, sizeof(num_keys)); + uint32_t unused32; + is.read((char*)&unused32, sizeof(unused32)); + if (preamble_longs > 2) { + is.read((char*)&theta, sizeof(theta)); + } + } + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + keys = AllocU64().allocate(num_keys); + is.read((char*)keys, sizeof(uint64_t) * num_keys); + } + + const bool is_ordered = flags_byte & (1 << flags::IS_ORDERED); + typedef typename std::allocator_traits::template rebind_alloc> AA; + return unique_ptr( + static_cast*>(new (AA().allocate(1)) compact_theta_sketch_alloc(is_empty, theta, keys, num_keys, seed_hash, is_ordered)), + [](theta_sketch_alloc* ptr) { + ptr->~theta_sketch_alloc(); + AA().deallocate(static_cast*>(ptr), 1); + } + ); +} + +template +uint16_t theta_sketch_alloc::get_seed_hash(uint64_t seed) { + HashState hashes; + MurmurHash3_x64_128(&seed, sizeof(seed), 0, hashes); + return hashes.h1; +} + +// update sketch + +template +update_theta_sketch_alloc::update_theta_sketch_alloc(uint8_t lg_cur_size, uint8_t lg_nom_size, resize_factor rf, float p, uint64_t seed): +theta_sketch_alloc(true, theta_sketch_alloc::MAX_THETA), +lg_cur_size_(lg_cur_size), +lg_nom_size_(lg_nom_size), +keys_(AllocU64().allocate(1 << lg_cur_size_)), +num_keys_(0), +rf_(rf), +p_(p), +seed_(seed), +capacity_(get_capacity(lg_cur_size, lg_nom_size)) +{ + if (p < 1) this->theta_ *= p; + std::fill(keys_, &keys_[1 << lg_cur_size_], 0); +// std::cerr << "update sketch constructor" << std::endl; +} + +template +update_theta_sketch_alloc::update_theta_sketch_alloc(const update_theta_sketch_alloc& other): +theta_sketch_alloc(other), +lg_cur_size_(other.lg_cur_size_), +lg_nom_size_(other.lg_nom_size_), +keys_(AllocU64().allocate(1 << lg_cur_size_)), +num_keys_(other.num_keys_), +rf_(other.rf_), +p_(other.p_), +seed_(other.seed_), +capacity_(other.capacity_) +{ + std::copy(other.keys_, &other.keys_[1 << lg_cur_size_], keys_); +// std::cerr << "update sketch copy constructor" << std::endl; +} + +template +update_theta_sketch_alloc::update_theta_sketch_alloc(update_theta_sketch_alloc&& other) noexcept: +theta_sketch_alloc(std::move(other)), +lg_cur_size_(other.lg_cur_size_), +lg_nom_size_(other.lg_nom_size_), +keys_(nullptr), +num_keys_(other.num_keys_), +rf_(other.rf_), +p_(other.p_), +seed_(other.seed_), +capacity_(other.capacity_) +{ + std::swap(keys_, other.keys_); +// std::cerr << "update sketch move constructor" << std::endl; +} + +template +update_theta_sketch_alloc::~update_theta_sketch_alloc() { + AllocU64().deallocate(keys_, 1 << lg_cur_size_); +// std::cerr << "update sketch destructor" << std::endl; +} + +template +update_theta_sketch_alloc& update_theta_sketch_alloc::operator=(const update_theta_sketch_alloc& other) { + theta_sketch_alloc::operator=(other); + if (lg_cur_size_ != other.lg_cur_size_) { + AllocU64().deallocate(keys_, 1 << lg_cur_size_); + lg_cur_size_ = other.lg_cur_size_; + keys_ = AllocU64().allocate(1 << lg_cur_size_); + } + lg_nom_size_ = other.lg_nom_size_; + std::copy(other.keys_, &other.keys_[1 << lg_cur_size_], keys_); + num_keys_ = other.num_keys_; + rf_ = other.rf_; + p_ = other.p_; + seed_ = other.seed_; + capacity_ = other.capacity_; +// std::cerr << "update sketch copy assignment" << std::endl; + return *this; +} + +template +update_theta_sketch_alloc& update_theta_sketch_alloc::operator=(update_theta_sketch_alloc&& other) { + theta_sketch_alloc::operator=(std::move(other)); + std::swap(lg_cur_size_, other.lg_cur_size_); + lg_nom_size_ = other.lg_nom_size_; + std::swap(keys_, other.keys_); + num_keys_ = other.num_keys_; + rf_ = other.rf_; + p_ = other.p_; + seed_ = other.seed_; + capacity_ = other.capacity_; +// std::cerr << "update sketch move assignment" << std::endl; + return *this; +} + +template +uint32_t update_theta_sketch_alloc::get_num_retained() const { + return num_keys_; +} + +template +uint16_t update_theta_sketch_alloc::get_seed_hash() const { + return theta_sketch_alloc::get_seed_hash(seed_); +} + +template +bool update_theta_sketch_alloc::is_ordered() const { + return false; +} + +template +void update_theta_sketch_alloc::to_stream(std::ostream& os, bool print_items) const { + os << "### Update Theta sketch summary:" << std::endl; + os << " lg nominal size : " << (int) lg_nom_size_ << std::endl; + os << " lg current size : " << (int) lg_cur_size_ << std::endl; + os << " num retained keys : " << num_keys_ << std::endl; + os << " resize factor : " << (1 << rf_) << std::endl; + os << " sampling probability : " << p_ << std::endl; + os << " seed hash : " << this->get_seed_hash() << std::endl; + os << " ordered? : " << (this->is_ordered() ? "true" : "false") << std::endl; + os << " theta (fraction) : " << this->get_theta() << std::endl; + os << " theta (raw 64-bit) : " << this->theta_ << std::endl; + os << " estimation mode? : " << (this->is_estimation_mode() ? "true" : "false") << std::endl; + os << " estimate : " << this->get_estimate() << std::endl; + os << " lower bound 95% conf : " << this->get_lower_bound(2) << std::endl; + os << " upper bound 95% conf : " << this->get_upper_bound(2) << std::endl; + os << "### End sketch summary" << std::endl; + if (print_items) { + os << "### Retained keys" << std::endl; + for (auto key: *this) os << " " << key << std::endl; + os << "### End retained keys" << std::endl; + } +} + +template +void update_theta_sketch_alloc::serialize(std::ostream& os) const { + const uint8_t preamble_longs_and_rf = 3 | (rf_ << 6); + os.write((char*)&preamble_longs_and_rf, sizeof(preamble_longs_and_rf)); + const uint8_t serial_version = SERIAL_VERSION; + os.write((char*)&serial_version, sizeof(serial_version)); + const uint8_t type = SKETCH_TYPE; + os.write((char*)&type, sizeof(type)); + os.write((char*)&lg_nom_size_, sizeof(lg_nom_size_)); + os.write((char*)&lg_cur_size_, sizeof(lg_cur_size_)); + const uint8_t flags_byte( + (this->is_empty() ? 1 << theta_sketch_alloc::flags::IS_EMPTY : 0) + ); + os.write((char*)&flags_byte, sizeof(flags_byte)); + const uint16_t seed_hash = get_seed_hash(); + os.write((char*)&seed_hash, sizeof(seed_hash)); + os.write((char*)&num_keys_, sizeof(num_keys_)); + os.write((char*)&p_, sizeof(p_)); + os.write((char*)&(this->theta_), sizeof(uint64_t)); + os.write((char*)keys_, sizeof(uint64_t) * (1 << lg_cur_size_)); +} + +template +void update_theta_sketch_alloc::update(uint64_t value) { + update(&value, sizeof(value)); +} + +template +void update_theta_sketch_alloc::update(int64_t value) { + update(&value, sizeof(value)); +} + +template +void update_theta_sketch_alloc::update(uint32_t value) { + update(static_cast(value)); +} + +template +void update_theta_sketch_alloc::update(int32_t value) { + update(static_cast(value)); +} + +template +void update_theta_sketch_alloc::update(uint16_t value) { + update(static_cast(value)); +} + +template +void update_theta_sketch_alloc::update(int16_t value) { + update(static_cast(value)); +} + +template +void update_theta_sketch_alloc::update(uint8_t value) { + update(static_cast(value)); +} + +template +void update_theta_sketch_alloc::update(int8_t value) { + update(static_cast(value)); +} + +template +void update_theta_sketch_alloc::update(double value) { + union { + int64_t long_value; + double double_value; + } long_double_union; + + if (value == 0.0) { + long_double_union.double_value = 0.0; // canonicalize -0.0 to 0.0 + } else if (std::isnan(value)) { + long_double_union.long_value = 0x7ff8000000000000L; // canonicalize NaN using value from Java's Double.doubleToLongBits() + } else { + long_double_union.double_value = value; + } + update(&long_double_union, sizeof(long_double_union)); +} + +template +void update_theta_sketch_alloc::update(float value) { + update(static_cast(value)); +} + +template +void update_theta_sketch_alloc::update(const void* data, unsigned length) { + HashState hashes; + MurmurHash3_x64_128(data, length, seed_, hashes); + const uint64_t hash = hashes.h1 >> 1; // Java implementation does logical shift >>> to make values positive + internal_update(hash); +} + +template +compact_theta_sketch_alloc update_theta_sketch_alloc::compact(bool ordered) const { + if (this->is_empty()) return compact_theta_sketch_alloc(true, theta_sketch_alloc::MAX_THETA, nullptr, 0, get_seed_hash(), true); + uint64_t* keys = AllocU64().allocate(num_keys_); + uint32_t i = 0; + for (auto value: *this) keys[i++] = value; + if (ordered) std::sort(keys, &keys[num_keys_]); + return compact_theta_sketch_alloc(false, this->theta_, keys, num_keys_, get_seed_hash(), ordered); +} + +template +void update_theta_sketch_alloc::internal_update(uint64_t hash) { + this->is_empty_ = false; + if (hash >= this->theta_ or hash == 0) return; // hash == 0 is reserved to mark empty slots in the table + if (hash_search_or_insert(hash, keys_, lg_cur_size_)) { + num_keys_++; + if (num_keys_ > capacity_) { + if (lg_cur_size_ <= lg_nom_size_) { + resize(); + } else { + rebuild(); + } + } + } +} + +template +void update_theta_sketch_alloc::resize() { + const uint32_t cur_size = 1 << lg_cur_size_; + const uint8_t lg_tgt_size = lg_nom_size_ + 1; + const uint8_t factor = std::max(1, std::min(static_cast(rf_), lg_tgt_size - lg_cur_size_)); + const uint8_t lg_new_size = lg_cur_size_ + factor; + const uint32_t new_size = 1 << lg_new_size; + uint64_t* new_keys = AllocU64().allocate(new_size); + std::fill(new_keys, &new_keys[new_size], 0); + for (uint32_t i = 0; i < cur_size; i++) { + if (keys_[i] != 0) { + hash_search_or_insert(keys_[i], new_keys, lg_new_size); // TODO hash_insert + } + } + AllocU64().deallocate(keys_, cur_size); + keys_ = new_keys; + lg_cur_size_ += factor; + capacity_ = get_capacity(lg_cur_size_, lg_nom_size_); +} + +template +void update_theta_sketch_alloc::rebuild() { + const uint32_t cur_size = 1 << lg_cur_size_; + const uint32_t pivot = (1 << lg_nom_size_) + cur_size - num_keys_; + std::nth_element(&keys_[0], &keys_[pivot], &keys_[cur_size]); + this->theta_ = keys_[pivot]; + uint64_t* new_keys = AllocU64().allocate(cur_size); + std::fill(new_keys, &new_keys[cur_size], 0); + num_keys_ = 0; + for (uint32_t i = 0; i < cur_size; i++) { + if (keys_[i] != 0 and keys_[i] < this->theta_) { + hash_search_or_insert(keys_[i], new_keys, lg_cur_size_); // TODO hash_insert + num_keys_++; + } + } + AllocU64().deallocate(keys_, cur_size); + keys_ = new_keys; +} + +template +uint32_t update_theta_sketch_alloc::get_capacity(uint8_t lg_cur_size, uint8_t lg_nom_size) { + const double fraction = (lg_cur_size <= lg_nom_size) ? RESIZE_THRESHOLD : REBUILD_THRESHOLD; + return std::floor(fraction * (1 << lg_cur_size)); +} + +template +uint32_t update_theta_sketch_alloc::get_stride(uint64_t hash, uint8_t lg_size) { + // odd and independent of index assuming lg_size lowest bits of the hash were used for the index + return (2 * static_cast((hash >> lg_size) & STRIDE_MASK)) + 1; +} + +template +bool update_theta_sketch_alloc::hash_search_or_insert(uint64_t hash, uint64_t* table, uint8_t lg_size) { + //std::cerr << "hash_search_or_insert: lg=" << (int)lg_size << ", hash=" << hash << std::endl; + const uint32_t mask = (1 << lg_size) - 1; + const uint32_t stride = get_stride(hash, lg_size); + uint32_t cur_probe = static_cast(hash) & mask; + + // search for duplicate or zero + const uint32_t loop_index = cur_probe; + do { + const uint64_t value = table[cur_probe]; + //std::cerr << "probe=" << cur_probe << std::endl; + if (value == 0) { + table[cur_probe] = hash; // insert value + return true; + } else if (value == hash) { + return false; // found a duplicate + } + cur_probe = (cur_probe + stride) & mask; + } while (cur_probe != loop_index); + std::cerr << "hash_search_or_insert: lg=" << (int)lg_size << ", hash=" << hash << std::endl; + std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; + throw std::logic_error("key not found and no empty slots!"); +} + +template +typename theta_sketch_alloc::const_iterator update_theta_sketch_alloc::begin() const { + return typename theta_sketch_alloc::const_iterator(keys_, 1 << lg_cur_size_, 0); +} + +template +typename theta_sketch_alloc::const_iterator update_theta_sketch_alloc::end() const { + return typename theta_sketch_alloc::const_iterator(keys_, 1 << lg_cur_size_, 1 << lg_cur_size_); +} + +// compact sketch + +template +compact_theta_sketch_alloc::compact_theta_sketch_alloc(bool is_empty, uint64_t theta, uint64_t* keys, uint32_t num_keys, uint16_t seed_hash, bool is_ordered): +theta_sketch_alloc(is_empty, theta), +keys_(keys), +num_keys_(num_keys), +seed_hash_(seed_hash), +is_ordered_(is_ordered) +{ + std::cerr << "compact sketch constructor" << std::endl; +} + +template +compact_theta_sketch_alloc::compact_theta_sketch_alloc(const compact_theta_sketch_alloc& other): +theta_sketch_alloc(other), +keys_(AllocU64().allocate(other.num_keys_)), +num_keys_(other.num_keys_), +seed_hash_(other.seed_hash_), +is_ordered_(other.is_ordered_) +{ + std::copy(other.keys_, &other.keys_[num_keys_], keys_); +} + +template +compact_theta_sketch_alloc::compact_theta_sketch_alloc(compact_theta_sketch_alloc&& other) noexcept: +theta_sketch_alloc(std::move(other)), +keys_(nullptr), +num_keys_(other.num_keys_), +seed_hash_(other.seed_hash_), +is_ordered_(other.is_ordered_) +{ + std::swap(keys_, other.keys_); +} + +template +compact_theta_sketch_alloc::~compact_theta_sketch_alloc() { + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + AllocU64().deallocate(keys_, num_keys_); + //std::cerr << "compact sketch destructor" << std::endl; +} + +template +compact_theta_sketch_alloc& compact_theta_sketch_alloc::operator=(const compact_theta_sketch_alloc& other) { + theta_sketch_alloc::operator=(other); + if (num_keys_ != other.num_keys_) { + AllocU64().deallocate(keys_, num_keys_); + num_keys_ = other.num_keys_; + keys_ = AllocU64().allocate(num_keys_); + } + keys_(AllocU64().allocate(other.num_keys_)), + seed_hash_ = other.seed_hash_; + is_ordered_ = other.is_ordered_; + std::copy(other.keys_, &other.keys_[num_keys_], keys_); + return *this; +} + +template +compact_theta_sketch_alloc& compact_theta_sketch_alloc::operator=(compact_theta_sketch_alloc&& other) { + theta_sketch_alloc::operator=(std::move(other)); + std::swap(keys_, other.keys_); + std::swap(num_keys_, other.num_keys_); + std::swap(seed_hash_, other.seed_hash_); + std::swap(is_ordered_, other.is_ordered_); + return *this; +} + +template +uint32_t compact_theta_sketch_alloc::get_num_retained() const { + return num_keys_; +} + +template +uint16_t compact_theta_sketch_alloc::get_seed_hash() const { + return seed_hash_; +} + +template +bool compact_theta_sketch_alloc::is_ordered() const { + return is_ordered_; +} + +template +void compact_theta_sketch_alloc::to_stream(std::ostream& os, bool print_items) const { + os << "### Compact Theta sketch summary:" << std::endl; + os << " num retained keys : " << num_keys_ << std::endl; + os << " seed hash : " << this->get_seed_hash() << std::endl; + os << " ordered? : " << (this->is_ordered() ? "true" : "false") << std::endl; + os << " theta (fraction) : " << this->get_theta() << std::endl; + os << " theta (raw 64-bit) : " << this->theta_ << std::endl; + os << " estimation mode? : " << (this->is_estimation_mode() ? "true" : "false") << std::endl; + os << " estimate : " << this->get_estimate() << std::endl; + os << " lower bound 95% conf : " << this->get_lower_bound(2) << std::endl; + os << " upper bound 95% conf : " << this->get_upper_bound(2) << std::endl; + os << "### End sketch summary" << std::endl; + if (print_items) { + os << "### Retained keys" << std::endl; + for (auto key: *this) os << " " << key << std::endl; + os << "### End retained keys" << std::endl; + } +} + +template +void compact_theta_sketch_alloc::serialize(std::ostream& os) const { + +} + +template +typename theta_sketch_alloc::const_iterator compact_theta_sketch_alloc::begin() const { + return typename theta_sketch_alloc::const_iterator(keys_, num_keys_, 0); +} + +template +typename theta_sketch_alloc::const_iterator compact_theta_sketch_alloc::end() const { + return typename theta_sketch_alloc::const_iterator(keys_, num_keys_, num_keys_); +} + +// builder + +template +update_theta_sketch_alloc::builder::builder(): +lg_k_(DEFAULT_LG_K), rf_(DEFAULT_RESIZE_FACTOR), p_(1), seed_(DEFAULT_SEED) {} + +template +typename update_theta_sketch_alloc::builder& update_theta_sketch_alloc::builder::set_lg_k(uint8_t lg_k) { + if (lg_k < MIN_LG_K) { + throw std::invalid_argument("lg_k must not be less than " + std::to_string(MIN_LG_K) + ": " + std::to_string(lg_k)); + } + lg_k_ = lg_k; + return *this; +} + +template +typename update_theta_sketch_alloc::builder& update_theta_sketch_alloc::builder::set_resize_factor(resize_factor rf) { + rf_ = rf; + return *this; +} + +template +typename update_theta_sketch_alloc::builder& update_theta_sketch_alloc::builder::set_p(float p) { + p_ = p; + return *this; +} + +template +typename update_theta_sketch_alloc::builder& update_theta_sketch_alloc::builder::set_seed(uint64_t seed) { + seed_ = seed; + return *this; +} + +template +uint8_t update_theta_sketch_alloc::builder::starting_sub_multiple(uint8_t lg_tgt, uint8_t lg_min, uint8_t lg_rf) { + return (lg_tgt <= lg_min) ? lg_min : (lg_rf == 0) ? lg_tgt : ((lg_tgt - lg_min) % lg_rf) + lg_min; +} + +template +update_theta_sketch_alloc update_theta_sketch_alloc::builder::build() const { + return update_theta_sketch_alloc(starting_sub_multiple(lg_k_ + 1, MIN_LG_K, static_cast(rf_)), lg_k_, rf_, p_, seed_); +} + +// iterator + +template +theta_sketch_alloc::const_iterator::const_iterator(const uint64_t* keys, uint32_t size, uint32_t index): +keys_(keys), size_(size), index_(index) { + while (index_ < size_ and keys_[index_] == 0) ++index_; +} + +template +typename theta_sketch_alloc::const_iterator& theta_sketch_alloc::const_iterator::operator++() { + do { + ++index_; + } while (index_ < size_ and keys_[index_] == 0); + return *this; +} + +template +typename theta_sketch_alloc::const_iterator theta_sketch_alloc::const_iterator::operator++(int) { + const_iterator tmp(*this); + operator++(); + return tmp; +} + +template +bool theta_sketch_alloc::const_iterator::operator==(const const_iterator& other) const { + return index_ == other.index_; +} + +template +bool theta_sketch_alloc::const_iterator::operator!=(const const_iterator& other) const { + return index_ != other.index_; +} + +template +uint64_t theta_sketch_alloc::const_iterator::operator*() const { + return keys_[index_]; +} + +} /* namespace datasketches */ + +# endif diff --git a/theta/include/theta_union.hpp b/theta/include/theta_union.hpp new file mode 100644 index 00000000..7475bbfc --- /dev/null +++ b/theta/include/theta_union.hpp @@ -0,0 +1,48 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_UNION_HPP_ +#define THETA_UNION_HPP_ + +#include +#include +#include + +#include + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +template +class theta_union_alloc { +public: + static const uint64_t MAX_THETA = LLONG_MAX; // signed max for compatibility with Java + + theta_union_alloc(uint8_t lg_k); + + bool is_empty() const; + void update(const theta_sketch_alloc& sketch); + compact_theta_sketch_alloc get_result(bool ordered = true) const; + //void to_stream(std::ostream& os, bool print_items = false) const; + +private: + + uint64_t theta_; + update_theta_sketch_alloc state_; +}; + +// alias with default allocator for convenience +typedef theta_union_alloc> theta_union; + +} /* namespace datasketches */ + +#include "theta_union_impl.hpp" + +# endif diff --git a/theta/include/theta_union_impl.hpp b/theta/include/theta_union_impl.hpp new file mode 100644 index 00000000..444d7e78 --- /dev/null +++ b/theta/include/theta_union_impl.hpp @@ -0,0 +1,68 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_UNION_IMPL_HPP_ +#define THETA_UNION_IMPL_HPP_ + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +template +theta_union_alloc::theta_union_alloc(uint8_t lg_k): +theta_(MAX_THETA), state_(typename update_theta_sketch_alloc::builder().set_lg_k(lg_k).build()) +{ +} + +template +void theta_union_alloc::update(const theta_sketch_alloc& sketch) { + if (sketch.is_empty()) return; + // check seed hash + if (sketch.get_theta64() < theta_) theta_ = sketch.get_theta64(); + if (sketch.is_ordered()) { + for (auto value: sketch) { + if (value >= theta_) break; // early stop + state_.internal_update(value); + } + } else { + for (auto value: sketch) state_.internal_update(value); + } +} + +template +compact_theta_sketch_alloc theta_union_alloc::get_result(bool ordered) const { + if (state_.is_empty()) return state_.compact(ordered); + const uint32_t nom_num_keys = 1 << state_.lg_nom_size_; + if (theta_ >= state_.theta_ and state_.get_num_retained() <= nom_num_keys) return state_.compact(ordered); + uint64_t theta = std::min(theta_, state_.get_theta64()); + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + uint64_t* keys = AllocU64().allocate(state_.get_num_retained()); + uint32_t num_keys = 0; + for (auto key: state_) { + if (key < theta) keys[num_keys++] = key; + } + if (num_keys == 0) return compact_theta_sketch_alloc(true, theta_sketch_alloc::MAX_THETA, nullptr, 0, state_.get_seed_hash(), true); + if (num_keys > nom_num_keys) { + std::nth_element(keys, &keys[nom_num_keys], &keys[num_keys]); + theta = keys[nom_num_keys]; + num_keys = nom_num_keys; + } + if (num_keys != state_.get_num_retained()) { + uint64_t* new_keys = AllocU64().allocate(num_keys); + std::copy(keys, &keys[num_keys], new_keys); + AllocU64().deallocate(keys, state_.get_num_retained()); + keys = new_keys; + } + if (ordered) std::sort(keys, &keys[num_keys]); + return compact_theta_sketch_alloc(false, theta, keys, num_keys, state_.get_seed_hash(), ordered); +} + +} /* namespace datasketches */ + +# endif diff --git a/theta/test/theta_compact_empty_from_java.bin b/theta/test/theta_compact_empty_from_java.bin new file mode 100644 index 0000000000000000000000000000000000000000..c9fb6f428a8beb10b300426ce7cdd7f8f19e15b6 GIT binary patch literal 280 bcmZo>X5wLGIWw673>xhJLqI)*KXOR`g~19m literal 0 HcmV?d00001 diff --git a/theta/test/theta_compact_estimation_from_java.bin b/theta/test/theta_compact_estimation_from_java.bin new file mode 100644 index 0000000000000000000000000000000000000000..7c6babf92ce3e4a2c66c4087b41155e1621e7ba6 GIT binary patch literal 34760 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

yDuvV+q1=ZMb&!vZQ7x3UI!M}j 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 F{{Z$>l^Fm4 literal 0 HcmV?d00001 diff --git a/theta/test/theta_compact_single_item_from_java.bin b/theta/test/theta_compact_single_item_from_java.bin new file mode 100644 index 0000000000000000000000000000000000000000..be5ee687cceae259a6b26ef15d6661f025e91637 GIT binary patch literal 16 XcmZQ%W@ccJIx|`HXYJ{|Z3|feBH;zV literal 0 HcmV?d00001 diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp new file mode 100644 index 00000000..c64fe647 --- /dev/null +++ b/theta/test/theta_sketch_test.cpp @@ -0,0 +1,159 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#include +#include + +#include + +namespace datasketches { + +class theta_sketch_test: public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(theta_sketch_test); + CPPUNIT_TEST(test); + CPPUNIT_TEST(empty); + CPPUNIT_TEST(single_item); + CPPUNIT_TEST(resize_exact); + CPPUNIT_TEST(estimation); + CPPUNIT_TEST(deserialize_compact_empty_from_java); + CPPUNIT_TEST(deserialize_single_item_from_java); + CPPUNIT_TEST(deserialize_compact_estimation_from_java); + CPPUNIT_TEST_SUITE_END(); + + void test() { + update_theta_sketch update_sketch = update_theta_sketch::builder().build(); + std::cerr << "after build" << std::endl; + std::ofstream ofs("theta_update_empty.bin"); + update_sketch.serialize(ofs); + + CPPUNIT_ASSERT(update_sketch.is_empty()); + + int value = 1; + update_sketch.update(value++); + CPPUNIT_ASSERT(!update_sketch.is_empty()); + CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_estimate()); + + theta_sketch& sketch1 = update_sketch; + CPPUNIT_ASSERT_EQUAL(1.0, sketch1.get_estimate()); + + compact_theta_sketch compact_sketch = update_sketch.compact(); + CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_estimate()); + compact_sketch.to_stream(std::cerr, true); + + theta_sketch& sketch2 = compact_sketch; + CPPUNIT_ASSERT_EQUAL(1.0, sketch2.get_estimate()); + + std::cerr << "copy construct" << std::endl; + + update_theta_sketch sketch3 = update_sketch; + + std::cerr << "move construct" << std::endl; + + update_theta_sketch sketch4 = std::move(update_sketch); + + std::cerr << "copy assign" << std::endl; + + sketch3 = sketch4; + + std::cerr << "move" << std::endl; + + sketch3 = std::move(sketch4); + + std::cerr << "end" << std::endl; + + sketch3.update(value++); + sketch3.to_stream(std::cerr, true); + + update_theta_sketch usk = update_theta_sketch::builder().build(); + for (int i = 0; i < 10000; i++) usk.update(i); + std::ofstream ofs2("theta_update_estimation.bin"); + usk.serialize(ofs2); + } + + void empty() { + update_theta_sketch update_sketch = update_theta_sketch::builder().build(); + CPPUNIT_ASSERT(update_sketch.is_empty()); + CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate()); + } + + void single_item() { + update_theta_sketch update_sketch = update_theta_sketch::builder().build(); + update_sketch.update(1); + CPPUNIT_ASSERT(!update_sketch.is_empty()); + CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_estimate()); + } + + void resize_exact() { + update_theta_sketch update_sketch = update_theta_sketch::builder().build(); + for (int i = 0; i < 2000; i++) update_sketch.update(i); + CPPUNIT_ASSERT(!update_sketch.is_empty()); + CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_estimate()); + } + + void estimation() { + update_theta_sketch update_sketch = update_theta_sketch::builder().set_resize_factor(update_theta_sketch::resize_factor::X1).build(); + const int n = 8000; + for (int i = 0; i < n; i++) update_sketch.update(i); + CPPUNIT_ASSERT(!update_sketch.is_empty()); + CPPUNIT_ASSERT(update_sketch.is_estimation_mode()); + CPPUNIT_ASSERT(update_sketch.get_theta() < 1.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, update_sketch.get_estimate(), n * 0.01); + } + + void deserialize_compact_empty_from_java() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_compact_empty_from_java.bin", std::ios::binary); + auto sketchptr = theta_sketch::deserialize(is); + CPPUNIT_ASSERT(sketchptr->is_empty()); + CPPUNIT_ASSERT(!sketchptr->is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0U, sketchptr->get_num_retained()); + CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta()); + CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_estimate()); + } + + void deserialize_single_item_from_java() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_compact_single_item_from_java.bin", std::ios::binary); + auto sketchptr = theta_sketch::deserialize(is); + CPPUNIT_ASSERT(!sketchptr->is_empty()); + CPPUNIT_ASSERT(!sketchptr->is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1U, sketchptr->get_num_retained()); + CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta()); + CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_estimate()); + } + + void deserialize_compact_estimation_from_java() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_compact_estimation_from_java.bin", std::ios::binary); + auto sketchptr = theta_sketch::deserialize(is); + CPPUNIT_ASSERT(!sketchptr->is_empty()); + CPPUNIT_ASSERT(sketchptr->is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(4342U, sketchptr->get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.531700444213199, sketchptr->get_theta(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8166.25234614053, sketchptr->get_estimate(), 1e-10); + + update_theta_sketch update_sketch = update_theta_sketch::builder().build(); + const int n = 8192; + for (int i = 0; i < n; i++) update_sketch.update(i); + CPPUNIT_ASSERT_EQUAL(update_sketch.get_num_retained(), sketchptr->get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_theta(), sketchptr->get_theta(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketchptr->get_estimate(), 1e-10); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(theta_sketch_test); + +} /* namespace datasketches */ diff --git a/theta/test/theta_union_test.cpp b/theta/test/theta_union_test.cpp new file mode 100644 index 00000000..2dc5d821 --- /dev/null +++ b/theta/test/theta_union_test.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#include +#include + +#include + +namespace datasketches { + +class theta_union_test: public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(theta_union_test); + CPPUNIT_TEST(empty); + CPPUNIT_TEST(exact_mode_half_overlap); + CPPUNIT_TEST(estimation_mode_half_overlap); + CPPUNIT_TEST_SUITE_END(); + + void empty() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + theta_union u(12); + u.update(sketch1); + compact_theta_sketch sketch2 = u.get_result(); + CPPUNIT_ASSERT(sketch2.is_empty()); + } + + void exact_mode_half_overlap() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + value = 500; + for (int i = 0; i < 1000; i++) sketch2.update(value++); + + theta_union u(12); + u.update(sketch1); + u.update(sketch2); + compact_theta_sketch sketch3 = u.get_result(); + CPPUNIT_ASSERT(!sketch3.is_empty()); + CPPUNIT_ASSERT(!sketch3.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1500, sketch3.get_estimate(), 1500 * 0.01); + } + + void estimation_mode_half_overlap() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + value = 5000; + for (int i = 0; i < 10000; i++) sketch2.update(value++); + + theta_union u(12); + u.update(sketch1); + u.update(sketch2); + compact_theta_sketch sketch3 = u.get_result(); + CPPUNIT_ASSERT(!sketch3.is_empty()); + CPPUNIT_ASSERT(sketch3.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(15000, sketch3.get_estimate(), 15000 * 0.01); + //sketch3.to_stream(std::cerr, true); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(theta_union_test); + +} /* namespace datasketches */ diff --git a/theta/test/theta_update_empty_from_java.bin b/theta/test/theta_update_empty_from_java.bin new file mode 100644 index 0000000000000000000000000000000000000000..7456dfb2c103a39450c142d983315852ab7d0ab6 GIT binary patch literal 1048 jcmX@i%*4aaa%M6E7&O@bhk$wrf0P^zfzc2ci6H<0G06&c literal 0 HcmV?d00001 diff --git a/theta/test/theta_update_estimation_from_java.bin b/theta/test/theta_update_estimation_from_java.bin new file mode 100644 index 0000000000000000000000000000000000000000..43b11b0d950b987988d03672ede2d7fc26f78efb GIT binary patch literal 65560 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 lPuq}xbLMHW Date: Mon, 6 May 2019 12:10:28 -0700 Subject: [PATCH 02/22] correct binary --- theta/test/theta_compact_empty_from_java.bin | Bin 280 -> 8 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/theta/test/theta_compact_empty_from_java.bin b/theta/test/theta_compact_empty_from_java.bin index c9fb6f428a8beb10b300426ce7cdd7f8f19e15b6..44730d3f0478502769267daff9738730b30dd1f8 100644 GIT binary patch literal 8 PcmZQ%W@ccJJ2M#o0=xl* literal 280 bcmZo>X5wLGIWw673>xhJLqI)*KXOR`g~19m From 4a2be2798308a8de1ee67dd3c6c41405617113da Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Mon, 6 May 2019 18:08:31 -0700 Subject: [PATCH 03/22] check union theta first --- theta/include/theta_union_impl.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/theta/include/theta_union_impl.hpp b/theta/include/theta_union_impl.hpp index 444d7e78..11240e1c 100644 --- a/theta/include/theta_union_impl.hpp +++ b/theta/include/theta_union_impl.hpp @@ -26,12 +26,12 @@ void theta_union_alloc::update(const theta_sketch_alloc& sketch) { // check seed hash if (sketch.get_theta64() < theta_) theta_ = sketch.get_theta64(); if (sketch.is_ordered()) { - for (auto value: sketch) { - if (value >= theta_) break; // early stop - state_.internal_update(value); + for (auto hash: sketch) { + if (hash >= theta_) break; // early stop + state_.internal_update(hash); } } else { - for (auto value: sketch) state_.internal_update(value); + for (auto hash: sketch) if (hash < theta_) state_.internal_update(hash); } } From 8d08772b64ff9779e9963337a9805f66cbf24792 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Mon, 6 May 2019 18:09:38 -0700 Subject: [PATCH 04/22] stream serialize and deserialize --- theta/include/theta_sketch.hpp | 17 ++- theta/include/theta_sketch_impl.hpp | 210 ++++++++++++++++++++-------- theta/test/theta_sketch_test.cpp | 114 +++++++++++++-- 3 files changed, 274 insertions(+), 67 deletions(-) diff --git a/theta/include/theta_sketch.hpp b/theta/include/theta_sketch.hpp index 8148d4d7..8cb1c0b3 100644 --- a/theta/include/theta_sketch.hpp +++ b/theta/include/theta_sketch.hpp @@ -20,6 +20,7 @@ namespace datasketches { // forward-declarations template class theta_sketch_alloc; +template class update_theta_sketch_alloc; template class compact_theta_sketch_alloc; template class theta_union_alloc; @@ -55,8 +56,8 @@ class theta_sketch_alloc { //virtual std::pair serialize(unsigned header_size_bytes = 0) const = 0; typedef std::unique_ptr, std::function*)>> unique_ptr; - static unique_ptr deserialize(std::istream& is); - //static unique_ptr deserialize(const void* bytes, size_t size); + static unique_ptr deserialize(std::istream& is, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); + //static unique_ptr deserialize(const void* bytes, size_t size, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); class const_iterator; virtual const_iterator begin() const = 0; @@ -121,6 +122,8 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { virtual typename theta_sketch_alloc::const_iterator begin() const; virtual typename theta_sketch_alloc::const_iterator end() const; + static update_theta_sketch_alloc deserialize(std::istream& is, uint64_t seed = builder::DEFAULT_SEED); + private: // resize threshold = 0.5 tuned for speed static constexpr double RESIZE_THRESHOLD = 0.5; @@ -141,7 +144,11 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { typedef typename std::allocator_traits::template rebind_alloc AllocU64; + // for builder update_theta_sketch_alloc(uint8_t lg_cur_size, uint8_t lg_nom_size, resize_factor rf, float p, uint64_t seed); + // for deserialize + update_theta_sketch_alloc(bool is_empty, uint64_t theta, uint8_t lg_cur_size, uint8_t lg_nom_size, uint64_t* keys, uint32_t num_keys, resize_factor rf, float p, uint64_t seed); + void resize(); void rebuild(); @@ -151,6 +158,9 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { static inline uint32_t get_capacity(uint8_t lg_cur_size, uint8_t lg_nom_size); static inline uint32_t get_stride(uint64_t hash, uint8_t lg_size); static bool hash_search_or_insert(uint64_t hash, uint64_t* table, uint8_t lg_size); + + friend theta_sketch_alloc; + static update_theta_sketch_alloc internal_deserialize(std::istream& is, resize_factor rf, uint8_t lg_nom_size, uint8_t lg_cur_size, uint8_t flags_byte, uint64_t seed); }; // compact sketch @@ -177,6 +187,8 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc { virtual typename theta_sketch_alloc::const_iterator begin() const; virtual typename theta_sketch_alloc::const_iterator end() const; + static compact_theta_sketch_alloc deserialize(std::istream& is, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); + private: typedef typename std::allocator_traits::template rebind_alloc AllocU64; @@ -189,6 +201,7 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc { friend update_theta_sketch_alloc; friend theta_union_alloc; compact_theta_sketch_alloc(bool is_empty, uint64_t theta, uint64_t* keys, uint32_t num_keys, uint16_t seed_hash, bool is_ordered); + static compact_theta_sketch_alloc internal_deserialize(std::istream& is, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash); }; // builder diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index f8664906..deb89cb7 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -24,29 +24,22 @@ namespace datasketches { template theta_sketch_alloc::theta_sketch_alloc(bool is_empty, uint64_t theta): is_empty_(is_empty), theta_(theta) -{ -// std::cerr << "sketch constructor" << std::endl; -} +{} template theta_sketch_alloc::theta_sketch_alloc(const theta_sketch_alloc& other): is_empty_(other.is_empty_), theta_(other.theta_) -{ -// std::cerr << "sketch copy constructor" << std::endl; -} +{} template theta_sketch_alloc::theta_sketch_alloc(theta_sketch_alloc&& other) noexcept: is_empty_(other.is_empty_), theta_(other.theta_) -{ -// std::cerr << "sketch move constructor" << std::endl; -} +{} template theta_sketch_alloc& theta_sketch_alloc::operator=(const theta_sketch_alloc& other) { is_empty_ = other.is_empty_; theta_ = other.theta_; -// std::cerr << "sketch copy assignment" << std::endl; return *this; } @@ -54,13 +47,11 @@ template theta_sketch_alloc& theta_sketch_alloc::operator=(theta_sketch_alloc&& other) { std::swap(is_empty_, other.is_empty_); std::swap(theta_, other.theta_); - //std::cerr << "sketch move assignment" << std::endl; return *this; } template theta_sketch_alloc::~theta_sketch_alloc() { -// std::cerr << "sketch destructor" << std::endl; } template @@ -99,50 +90,45 @@ uint64_t theta_sketch_alloc::get_theta64() const { } template -typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(std::istream& is) { +typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(std::istream& is, uint64_t seed) { uint8_t preamble_longs; is.read((char*)&preamble_longs, sizeof(preamble_longs)); uint8_t serial_version; is.read((char*)&serial_version, sizeof(serial_version)); - uint8_t family_id; - is.read((char*)&family_id, sizeof(family_id)); - uint16_t unused16; - is.read((char*)&unused16, sizeof(unused16)); + uint8_t type; + is.read((char*)&type, sizeof(type)); + uint8_t lg_nom_size; + is.read((char*)&lg_nom_size, sizeof(lg_nom_size)); + uint8_t lg_cur_size; + is.read((char*)&lg_cur_size, sizeof(lg_cur_size)); uint8_t flags_byte; is.read((char*)&flags_byte, sizeof(flags_byte)); uint16_t seed_hash; is.read((char*)&seed_hash, sizeof(seed_hash)); - uint64_t theta = MAX_THETA; - uint64_t* keys = nullptr; - uint32_t num_keys = 0; + // TODO: checks and redirects to the right subclass here - const bool is_empty = flags_byte & (1 << flags::IS_EMPTY); - if (!is_empty) { - if (preamble_longs == 1) { - num_keys = 1; - } else { - is.read((char*)&num_keys, sizeof(num_keys)); - uint32_t unused32; - is.read((char*)&unused32, sizeof(unused32)); - if (preamble_longs > 2) { - is.read((char*)&theta, sizeof(theta)); + if (type == update_theta_sketch_alloc::SKETCH_TYPE) { + typename update_theta_sketch_alloc::resize_factor rf = static_cast::resize_factor>(preamble_longs >> 6); + typedef typename std::allocator_traits::template rebind_alloc> AU; + return unique_ptr( + static_cast*>(new (AU().allocate(1)) update_theta_sketch_alloc(update_theta_sketch_alloc::internal_deserialize(is, rf, lg_nom_size, lg_cur_size, flags_byte, seed))), + [](theta_sketch_alloc* ptr) { + ptr->~theta_sketch_alloc(); + AU().deallocate(static_cast*>(ptr), 1); } - } - typedef typename std::allocator_traits::template rebind_alloc AllocU64; - keys = AllocU64().allocate(num_keys); - is.read((char*)keys, sizeof(uint64_t) * num_keys); + ); + } else if (type == compact_theta_sketch_alloc::SKETCH_TYPE) { + typedef typename std::allocator_traits::template rebind_alloc> AC; + return unique_ptr( + static_cast*>(new (AC().allocate(1)) compact_theta_sketch_alloc(compact_theta_sketch_alloc::internal_deserialize(is, preamble_longs, flags_byte, seed_hash))), + [](theta_sketch_alloc* ptr) { + ptr->~theta_sketch_alloc(); + AC().deallocate(static_cast*>(ptr), 1); + } + ); } - - const bool is_ordered = flags_byte & (1 << flags::IS_ORDERED); - typedef typename std::allocator_traits::template rebind_alloc> AA; - return unique_ptr( - static_cast*>(new (AA().allocate(1)) compact_theta_sketch_alloc(is_empty, theta, keys, num_keys, seed_hash, is_ordered)), - [](theta_sketch_alloc* ptr) { - ptr->~theta_sketch_alloc(); - AA().deallocate(static_cast*>(ptr), 1); - } - ); + throw std::invalid_argument("unsupported sketch type " + std::to_string((int) type)); } template @@ -168,9 +154,21 @@ capacity_(get_capacity(lg_cur_size, lg_nom_size)) { if (p < 1) this->theta_ *= p; std::fill(keys_, &keys_[1 << lg_cur_size_], 0); -// std::cerr << "update sketch constructor" << std::endl; } +template +update_theta_sketch_alloc::update_theta_sketch_alloc(bool is_empty, uint64_t theta, uint8_t lg_cur_size, uint8_t lg_nom_size, uint64_t* keys, uint32_t num_keys, resize_factor rf, float p, uint64_t seed): +theta_sketch_alloc(is_empty, theta), +lg_cur_size_(lg_cur_size), +lg_nom_size_(lg_nom_size), +keys_(keys), +num_keys_(num_keys), +rf_(rf), +p_(p), +seed_(seed), +capacity_(get_capacity(lg_cur_size, lg_nom_size)) +{} + template update_theta_sketch_alloc::update_theta_sketch_alloc(const update_theta_sketch_alloc& other): theta_sketch_alloc(other), @@ -184,7 +182,6 @@ seed_(other.seed_), capacity_(other.capacity_) { std::copy(other.keys_, &other.keys_[1 << lg_cur_size_], keys_); -// std::cerr << "update sketch copy constructor" << std::endl; } template @@ -200,13 +197,11 @@ seed_(other.seed_), capacity_(other.capacity_) { std::swap(keys_, other.keys_); -// std::cerr << "update sketch move constructor" << std::endl; } template update_theta_sketch_alloc::~update_theta_sketch_alloc() { AllocU64().deallocate(keys_, 1 << lg_cur_size_); -// std::cerr << "update sketch destructor" << std::endl; } template @@ -224,7 +219,6 @@ update_theta_sketch_alloc& update_theta_sketch_alloc::operator=(const upda p_ = other.p_; seed_ = other.seed_; capacity_ = other.capacity_; -// std::cerr << "update sketch copy assignment" << std::endl; return *this; } @@ -239,7 +233,6 @@ update_theta_sketch_alloc& update_theta_sketch_alloc::operator=(update_the p_ = other.p_; seed_ = other.seed_; capacity_ = other.capacity_; -// std::cerr << "update sketch move assignment" << std::endl; return *this; } @@ -304,6 +297,43 @@ void update_theta_sketch_alloc::serialize(std::ostream& os) const { os.write((char*)keys_, sizeof(uint64_t) * (1 << lg_cur_size_)); } +template +update_theta_sketch_alloc update_theta_sketch_alloc::deserialize(std::istream& is, uint64_t seed) { + uint8_t preamble_longs; + is.read((char*)&preamble_longs, sizeof(preamble_longs)); + resize_factor rf = static_cast(preamble_longs >> 6); + preamble_longs &= 0x3f; // remove resize factor + uint8_t serial_version; + is.read((char*)&serial_version, sizeof(serial_version)); + uint8_t type; + is.read((char*)&type, sizeof(type)); + uint8_t lg_nom_size; + is.read((char*)&lg_nom_size, sizeof(lg_nom_size)); + uint8_t lg_cur_size; + is.read((char*)&lg_cur_size, sizeof(lg_cur_size)); + uint8_t flags_byte; + is.read((char*)&flags_byte, sizeof(flags_byte)); + uint16_t seed_hash; + is.read((char*)&seed_hash, sizeof(seed_hash)); + // TODO: checks here + return internal_deserialize(is, rf, lg_nom_size, lg_cur_size, flags_byte, seed); +} + +template +update_theta_sketch_alloc update_theta_sketch_alloc::internal_deserialize(std::istream& is, resize_factor rf, uint8_t lg_nom_size, uint8_t lg_cur_size, uint8_t flags_byte, uint64_t seed) { + uint32_t num_keys; + is.read((char*)&num_keys, sizeof(num_keys)); + float p; + is.read((char*)&p, sizeof(p)); + uint64_t theta; + is.read((char*)&theta, sizeof(theta)); + //typedef typename std::allocator_traits::template rebind_alloc AllocU64; + uint64_t* keys = AllocU64().allocate(num_keys); + is.read((char*)keys, sizeof(uint64_t) * num_keys); + const bool is_empty = flags_byte & (1 << theta_sketch_alloc::flags::IS_EMPTY); + return update_theta_sketch_alloc(is_empty, theta, lg_nom_size, lg_cur_size, keys, num_keys, rf, p, seed); +} + template void update_theta_sketch_alloc::update(uint64_t value) { update(&value, sizeof(value)); @@ -453,7 +483,6 @@ uint32_t update_theta_sketch_alloc::get_stride(uint64_t hash, uint8_t lg_size template bool update_theta_sketch_alloc::hash_search_or_insert(uint64_t hash, uint64_t* table, uint8_t lg_size) { - //std::cerr << "hash_search_or_insert: lg=" << (int)lg_size << ", hash=" << hash << std::endl; const uint32_t mask = (1 << lg_size) - 1; const uint32_t stride = get_stride(hash, lg_size); uint32_t cur_probe = static_cast(hash) & mask; @@ -462,7 +491,6 @@ bool update_theta_sketch_alloc::hash_search_or_insert(uint64_t hash, uint64_t const uint32_t loop_index = cur_probe; do { const uint64_t value = table[cur_probe]; - //std::cerr << "probe=" << cur_probe << std::endl; if (value == 0) { table[cur_probe] = hash; // insert value return true; @@ -495,9 +523,7 @@ keys_(keys), num_keys_(num_keys), seed_hash_(seed_hash), is_ordered_(is_ordered) -{ - std::cerr << "compact sketch constructor" << std::endl; -} +{} template compact_theta_sketch_alloc::compact_theta_sketch_alloc(const compact_theta_sketch_alloc& other): @@ -525,7 +551,6 @@ template compact_theta_sketch_alloc::~compact_theta_sketch_alloc() { typedef typename std::allocator_traits::template rebind_alloc AllocU64; AllocU64().deallocate(keys_, num_keys_); - //std::cerr << "compact sketch destructor" << std::endl; } template @@ -590,7 +615,80 @@ void compact_theta_sketch_alloc::to_stream(std::ostream& os, bool print_items template void compact_theta_sketch_alloc::serialize(std::ostream& os) const { + const bool is_single_item = num_keys_ == 1 and !this->is_estimation_mode(); + const uint8_t preamble_longs = this->is_empty() or is_single_item ? 1 : this->is_estimation_mode() ? 3 : 2; + os.write((char*)&preamble_longs, sizeof(preamble_longs)); + const uint8_t serial_version = SERIAL_VERSION; + os.write((char*)&serial_version, sizeof(serial_version)); + const uint8_t type = SKETCH_TYPE; + os.write((char*)&type, sizeof(type)); + const uint16_t unused16 = 0; + os.write((char*)&unused16, sizeof(unused16)); + const uint8_t flags_byte( + (1 << theta_sketch_alloc::flags::IS_COMPACT) | + (1 << theta_sketch_alloc::flags::IS_READ_ONLY) | + (this->is_empty() ? 1 << theta_sketch_alloc::flags::IS_EMPTY : 0) | + (this->is_ordered() ? 1 << theta_sketch_alloc::flags::IS_ORDERED : 0) + ); + os.write((char*)&flags_byte, sizeof(flags_byte)); + const uint16_t seed_hash = get_seed_hash(); + os.write((char*)&seed_hash, sizeof(seed_hash)); + if (!this->is_empty()) { + if (!is_single_item) { + os.write((char*)&num_keys_, sizeof(num_keys_)); + const uint32_t unused32 = 0; + os.write((char*)&unused32, sizeof(unused32)); + if (this->is_estimation_mode()) { + os.write((char*)&(this->theta_), sizeof(uint64_t)); + } + } + os.write((char*)keys_, sizeof(uint64_t) * num_keys_); + } +} + +template +compact_theta_sketch_alloc compact_theta_sketch_alloc::deserialize(std::istream& is, uint64_t seed) { + uint8_t preamble_longs; + is.read((char*)&preamble_longs, sizeof(preamble_longs)); + uint8_t serial_version; + is.read((char*)&serial_version, sizeof(serial_version)); + uint8_t family_id; + is.read((char*)&family_id, sizeof(family_id)); + uint16_t unused16; + is.read((char*)&unused16, sizeof(unused16)); + uint8_t flags_byte; + is.read((char*)&flags_byte, sizeof(flags_byte)); + uint16_t seed_hash; + is.read((char*)&seed_hash, sizeof(seed_hash)); + // TODO: checks here + return internal_deserialize(is, preamble_longs, flags_byte, seed_hash); +} + +template +compact_theta_sketch_alloc compact_theta_sketch_alloc::internal_deserialize(std::istream& is, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash) { + uint64_t theta = theta_sketch_alloc::MAX_THETA; + uint64_t* keys = nullptr; + uint32_t num_keys = 0; + + const bool is_empty = flags_byte & (1 << theta_sketch_alloc::flags::IS_EMPTY); + if (!is_empty) { + if (preamble_longs == 1) { + num_keys = 1; + } else { + is.read((char*)&num_keys, sizeof(num_keys)); + uint32_t unused32; + is.read((char*)&unused32, sizeof(unused32)); + if (preamble_longs > 2) { + is.read((char*)&theta, sizeof(theta)); + } + } + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + keys = AllocU64().allocate(num_keys); + is.read((char*)keys, sizeof(uint64_t) * num_keys); + } + const bool is_ordered = flags_byte & (1 << theta_sketch_alloc::flags::IS_ORDERED); + return compact_theta_sketch_alloc(is_empty, theta, keys, num_keys, seed_hash, is_ordered); } template diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp index c64fe647..4105e1bd 100644 --- a/theta/test/theta_sketch_test.cpp +++ b/theta/test/theta_sketch_test.cpp @@ -18,9 +18,16 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_TEST(single_item); CPPUNIT_TEST(resize_exact); CPPUNIT_TEST(estimation); - CPPUNIT_TEST(deserialize_compact_empty_from_java); - CPPUNIT_TEST(deserialize_single_item_from_java); - CPPUNIT_TEST(deserialize_compact_estimation_from_java); + CPPUNIT_TEST(deserialize_update_empty_from_java_as_base); + CPPUNIT_TEST(deserialize_update_empty_from_java_as_subclass); + CPPUNIT_TEST(deserialize_update_estimation_from_java_as_base); + CPPUNIT_TEST(deserialize_update_estimation_from_java_as_subclass); + CPPUNIT_TEST(deserialize_compact_empty_from_java_as_base); + CPPUNIT_TEST(deserialize_compact_empty_from_java_as_subclass); + CPPUNIT_TEST(deserialize_single_item_from_java_as_base); + CPPUNIT_TEST(deserialize_single_item_from_java_as_subclass); + CPPUNIT_TEST(deserialize_compact_estimation_from_java_as_base); + CPPUNIT_TEST(deserialize_compact_estimation_from_java_as_subclass); CPPUNIT_TEST_SUITE_END(); void test() { @@ -68,9 +75,9 @@ class theta_sketch_test: public CppUnit::TestFixture { sketch3.to_stream(std::cerr, true); update_theta_sketch usk = update_theta_sketch::builder().build(); - for (int i = 0; i < 10000; i++) usk.update(i); - std::ofstream ofs2("theta_update_estimation.bin"); - usk.serialize(ofs2); + //for (int i = 0; i < 10000; i++) usk.update(i); + std::ofstream ofs2("theta_compact_empty.bin"); + usk.compact().serialize(ofs2); } void empty() { @@ -109,7 +116,53 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, update_sketch.get_estimate(), n * 0.01); } - void deserialize_compact_empty_from_java() { + void deserialize_update_empty_from_java_as_base() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_update_empty_from_java.bin", std::ios::binary); + auto sketchptr = theta_sketch::deserialize(is); + CPPUNIT_ASSERT(sketchptr->is_empty()); + CPPUNIT_ASSERT(!sketchptr->is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0U, sketchptr->get_num_retained()); + CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta()); + CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_estimate()); + } + + void deserialize_update_empty_from_java_as_subclass() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_update_empty_from_java.bin", std::ios::binary); + auto sketch = update_theta_sketch::deserialize(is); + CPPUNIT_ASSERT(sketch.is_empty()); + CPPUNIT_ASSERT(!sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained()); + CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_estimate()); + } + + void deserialize_update_estimation_from_java_as_base() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_update_estimation_from_java.bin", std::ios::binary); + auto sketchptr = theta_sketch::deserialize(is); + CPPUNIT_ASSERT(!sketchptr->is_empty()); + CPPUNIT_ASSERT(sketchptr->is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(5324U, sketchptr->get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10000.0, sketchptr->get_estimate(), 10000 * 0.01); + } + + void deserialize_update_estimation_from_java_as_subclass() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_update_estimation_from_java.bin", std::ios::binary); + auto sketch = update_theta_sketch::deserialize(is); + CPPUNIT_ASSERT(!sketch.is_empty()); + CPPUNIT_ASSERT(sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(5324U, sketch.get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10000.0, sketch.get_estimate(), 10000 * 0.01); + } + + void deserialize_compact_empty_from_java_as_base() { std::ifstream is; is.exceptions(std::ios::failbit | std::ios::badbit); is.open("test/theta_compact_empty_from_java.bin", std::ios::binary); @@ -121,7 +174,19 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_estimate()); } - void deserialize_single_item_from_java() { + void deserialize_compact_empty_from_java_as_subclass() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_compact_empty_from_java.bin", std::ios::binary); + auto sketch = compact_theta_sketch::deserialize(is); + CPPUNIT_ASSERT(sketch.is_empty()); + CPPUNIT_ASSERT(!sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained()); + CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_estimate()); + } + + void deserialize_single_item_from_java_as_base() { std::ifstream is; is.exceptions(std::ios::failbit | std::ios::badbit); is.open("test/theta_compact_single_item_from_java.bin", std::ios::binary); @@ -133,7 +198,19 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_estimate()); } - void deserialize_compact_estimation_from_java() { + void deserialize_single_item_from_java_as_subclass() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_compact_single_item_from_java.bin", std::ios::binary); + auto sketch = compact_theta_sketch::deserialize(is); + CPPUNIT_ASSERT(!sketch.is_empty()); + CPPUNIT_ASSERT(!sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1U, sketch.get_num_retained()); + CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_estimate()); + } + + void deserialize_compact_estimation_from_java_as_base() { std::ifstream is; is.exceptions(std::ios::failbit | std::ios::badbit); is.open("test/theta_compact_estimation_from_java.bin", std::ios::binary); @@ -152,6 +229,25 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketchptr->get_estimate(), 1e-10); } + void deserialize_compact_estimation_from_java_as_subclass() { + std::ifstream is; + is.exceptions(std::ios::failbit | std::ios::badbit); + is.open("test/theta_compact_estimation_from_java.bin", std::ios::binary); + auto sketch = compact_theta_sketch::deserialize(is); + CPPUNIT_ASSERT(!sketch.is_empty()); + CPPUNIT_ASSERT(sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(4342U, sketch.get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.531700444213199, sketch.get_theta(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8166.25234614053, sketch.get_estimate(), 1e-10); + + update_theta_sketch update_sketch = update_theta_sketch::builder().build(); + const int n = 8192; + for (int i = 0; i < n; i++) update_sketch.update(i); + CPPUNIT_ASSERT_EQUAL(update_sketch.get_num_retained(), sketch.get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_theta(), sketch.get_theta(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketch.get_estimate(), 1e-10); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(theta_sketch_test); From 10890962204d938f3501785935ae2cd75133fcab Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Wed, 8 May 2019 09:46:57 -0700 Subject: [PATCH 05/22] raw bytes serialize and deserialize --- theta/include/theta_sketch.hpp | 14 ++- theta/include/theta_sketch_impl.hpp | 166 +++++++++++++++++++++++++++- theta/test/theta_sketch_test.cpp | 68 ++++++++++++ 3 files changed, 243 insertions(+), 5 deletions(-) diff --git a/theta/include/theta_sketch.hpp b/theta/include/theta_sketch.hpp index 8cb1c0b3..3e7d9613 100644 --- a/theta/include/theta_sketch.hpp +++ b/theta/include/theta_sketch.hpp @@ -53,11 +53,11 @@ class theta_sketch_alloc { virtual bool is_ordered() const = 0; virtual void to_stream(std::ostream& os, bool print_items = false) const = 0; virtual void serialize(std::ostream& os) const = 0; - //virtual std::pair serialize(unsigned header_size_bytes = 0) const = 0; + virtual std::pair serialize(unsigned header_size_bytes = 0) const = 0; typedef std::unique_ptr, std::function*)>> unique_ptr; static unique_ptr deserialize(std::istream& is, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); - //static unique_ptr deserialize(const void* bytes, size_t size, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); + static unique_ptr deserialize(const void* bytes, size_t size, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); class const_iterator; virtual const_iterator begin() const = 0; @@ -83,7 +83,7 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { static const uint8_t SERIAL_VERSION = 3; update_theta_sketch_alloc(const update_theta_sketch_alloc& other); - update_theta_sketch_alloc(update_theta_sketch_alloc&& other) noexcept ; + update_theta_sketch_alloc(update_theta_sketch_alloc&& other) noexcept; virtual ~update_theta_sketch_alloc(); update_theta_sketch_alloc& operator=(const update_theta_sketch_alloc& other); @@ -94,6 +94,8 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { virtual bool is_ordered() const; virtual void to_stream(std::ostream& os, bool print_items = false) const; virtual void serialize(std::ostream& os) const; + // header space is reserved, but not initialized + virtual std::pair serialize(unsigned header_size_bytes = 0) const; //void update(const std::string& value); void update(uint64_t value); @@ -123,6 +125,7 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { virtual typename theta_sketch_alloc::const_iterator end() const; static update_theta_sketch_alloc deserialize(std::istream& is, uint64_t seed = builder::DEFAULT_SEED); + static update_theta_sketch_alloc deserialize(const void* bytes, size_t size, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); private: // resize threshold = 0.5 tuned for speed @@ -161,6 +164,7 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { friend theta_sketch_alloc; static update_theta_sketch_alloc internal_deserialize(std::istream& is, resize_factor rf, uint8_t lg_nom_size, uint8_t lg_cur_size, uint8_t flags_byte, uint64_t seed); + static update_theta_sketch_alloc internal_deserialize(const void* bytes, size_t size, resize_factor rf, uint8_t lg_nom_size, uint8_t lg_cur_size, uint8_t flags_byte, uint64_t seed); }; // compact sketch @@ -183,11 +187,14 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc { virtual bool is_ordered() const; virtual void to_stream(std::ostream& os, bool print_items = false) const; virtual void serialize(std::ostream& os) const; + // header space is reserved, but not initialized + virtual std::pair serialize(unsigned header_size_bytes = 0) const; virtual typename theta_sketch_alloc::const_iterator begin() const; virtual typename theta_sketch_alloc::const_iterator end() const; static compact_theta_sketch_alloc deserialize(std::istream& is, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); + static compact_theta_sketch_alloc deserialize(const void* bytes, size_t size, uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); private: typedef typename std::allocator_traits::template rebind_alloc AllocU64; @@ -202,6 +209,7 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc { friend theta_union_alloc; compact_theta_sketch_alloc(bool is_empty, uint64_t theta, uint64_t* keys, uint32_t num_keys, uint16_t seed_hash, bool is_ordered); static compact_theta_sketch_alloc internal_deserialize(std::istream& is, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash); + static compact_theta_sketch_alloc internal_deserialize(const void* bytes, size_t size, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash); }; // builder diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index deb89cb7..aac3c3ab 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -12,6 +12,7 @@ #include #include "MurmurHash3.h" +#include "serde.hpp" namespace datasketches { @@ -106,7 +107,7 @@ typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(st uint16_t seed_hash; is.read((char*)&seed_hash, sizeof(seed_hash)); - // TODO: checks and redirects to the right subclass here + // TODO: checks here if (type == update_theta_sketch_alloc::SKETCH_TYPE) { typename update_theta_sketch_alloc::resize_factor rf = static_cast::resize_factor>(preamble_longs >> 6); @@ -297,6 +298,39 @@ void update_theta_sketch_alloc::serialize(std::ostream& os) const { os.write((char*)keys_, sizeof(uint64_t) * (1 << lg_cur_size_)); } +template +std::pair update_theta_sketch_alloc::serialize(unsigned header_size_bytes) const { + const uint8_t preamble_longs = 3; + const size_t size = header_size_bytes + sizeof(uint64_t) * preamble_longs + sizeof(uint64_t) * (1 << lg_cur_size_); + typedef typename std::allocator_traits::template rebind_alloc AllocChar; + void_ptr_with_deleter data_ptr( + static_cast(AllocChar().allocate(size)), + [size](void* ptr) { AllocChar().deallocate(static_cast(ptr), size); } + ); + char* ptr = static_cast(data_ptr.get()) + header_size_bytes; + + const uint8_t preamble_longs_and_rf = preamble_longs | (rf_ << 6); + copy_to_mem(&preamble_longs_and_rf, &ptr, sizeof(preamble_longs_and_rf)); + const uint8_t serial_version = SERIAL_VERSION; + copy_to_mem(&serial_version, &ptr, sizeof(serial_version)); + const uint8_t type = SKETCH_TYPE; + copy_to_mem(&type, &ptr, sizeof(type)); + copy_to_mem(&lg_nom_size_, &ptr, sizeof(lg_nom_size_)); + copy_to_mem(&lg_cur_size_, &ptr, sizeof(lg_cur_size_)); + const uint8_t flags_byte( + (this->is_empty() ? 1 << theta_sketch_alloc::flags::IS_EMPTY : 0) + ); + copy_to_mem(&flags_byte, &ptr, sizeof(flags_byte)); + const uint16_t seed_hash = get_seed_hash(); + copy_to_mem(&seed_hash, &ptr, sizeof(seed_hash)); + copy_to_mem(&num_keys_, &ptr, sizeof(num_keys_)); + copy_to_mem(&p_, &ptr, sizeof(p_)); + copy_to_mem(&(this->theta_), &ptr, sizeof(uint64_t)); + copy_to_mem(keys_, &ptr, sizeof(uint64_t) * (1 << lg_cur_size_)); + + return std::make_pair(std::move(data_ptr), size);; +} + template update_theta_sketch_alloc update_theta_sketch_alloc::deserialize(std::istream& is, uint64_t seed) { uint8_t preamble_longs; @@ -327,13 +361,50 @@ update_theta_sketch_alloc update_theta_sketch_alloc::internal_deserialize( is.read((char*)&p, sizeof(p)); uint64_t theta; is.read((char*)&theta, sizeof(theta)); - //typedef typename std::allocator_traits::template rebind_alloc AllocU64; uint64_t* keys = AllocU64().allocate(num_keys); is.read((char*)keys, sizeof(uint64_t) * num_keys); const bool is_empty = flags_byte & (1 << theta_sketch_alloc::flags::IS_EMPTY); return update_theta_sketch_alloc(is_empty, theta, lg_nom_size, lg_cur_size, keys, num_keys, rf, p, seed); } +template +update_theta_sketch_alloc update_theta_sketch_alloc::deserialize(const void* bytes, size_t size, uint64_t seed) { + const char* ptr = static_cast(bytes); + uint8_t preamble_longs; + copy_from_mem(&ptr, &preamble_longs, sizeof(preamble_longs)); + resize_factor rf = static_cast(preamble_longs >> 6); + preamble_longs &= 0x3f; // remove resize factor + uint8_t serial_version; + copy_from_mem(&ptr, &serial_version, sizeof(serial_version)); + uint8_t type; + copy_from_mem(&ptr, &type, sizeof(type)); + uint8_t lg_nom_size; + copy_from_mem(&ptr, &lg_nom_size, sizeof(lg_nom_size)); + uint8_t lg_cur_size; + copy_from_mem(&ptr, &lg_cur_size, sizeof(lg_cur_size)); + uint8_t flags_byte; + copy_from_mem(&ptr, &flags_byte, sizeof(flags_byte)); + uint16_t seed_hash; + copy_from_mem(&ptr, &seed_hash, sizeof(seed_hash)); + // TODO: checks here + return internal_deserialize(ptr, size - (ptr - static_cast(bytes)), rf, lg_nom_size, lg_cur_size, flags_byte, seed); +} + +template +update_theta_sketch_alloc update_theta_sketch_alloc::internal_deserialize(const void* bytes, size_t size, resize_factor rf, uint8_t lg_nom_size, uint8_t lg_cur_size, uint8_t flags_byte, uint64_t seed) { + const char* ptr = static_cast(bytes); + uint32_t num_keys; + copy_from_mem(&ptr, &num_keys, sizeof(num_keys)); + float p; + copy_from_mem(&ptr, &p, sizeof(p)); + uint64_t theta; + copy_from_mem(&ptr, &theta, sizeof(theta)); + uint64_t* keys = AllocU64().allocate(num_keys); + copy_from_mem(&ptr, keys, sizeof(uint64_t) * num_keys); + const bool is_empty = flags_byte & (1 << theta_sketch_alloc::flags::IS_EMPTY); + return update_theta_sketch_alloc(is_empty, theta, lg_nom_size, lg_cur_size, keys, num_keys, rf, p, seed); +} + template void update_theta_sketch_alloc::update(uint64_t value) { update(&value, sizeof(value)); @@ -646,6 +717,49 @@ void compact_theta_sketch_alloc::serialize(std::ostream& os) const { } } +template +std::pair compact_theta_sketch_alloc::serialize(unsigned header_size_bytes) const { + const bool is_single_item = num_keys_ == 1 and !this->is_estimation_mode(); + const uint8_t preamble_longs = this->is_empty() or is_single_item ? 1 : this->is_estimation_mode() ? 3 : 2; + const size_t size = header_size_bytes + sizeof(uint64_t) * preamble_longs + sizeof(uint64_t) * num_keys_; + typedef typename std::allocator_traits::template rebind_alloc AllocChar; + void_ptr_with_deleter data_ptr( + static_cast(AllocChar().allocate(size)), + [size](void* ptr) { AllocChar().deallocate(static_cast(ptr), size); } + ); + char* ptr = static_cast(data_ptr.get()) + header_size_bytes; + + copy_to_mem(&preamble_longs, &ptr, sizeof(preamble_longs)); + const uint8_t serial_version = SERIAL_VERSION; + copy_to_mem(&serial_version, &ptr, sizeof(serial_version)); + const uint8_t type = SKETCH_TYPE; + copy_to_mem(&type, &ptr, sizeof(type)); + const uint16_t unused16 = 0; + copy_to_mem(&unused16, &ptr, sizeof(unused16)); + const uint8_t flags_byte( + (1 << theta_sketch_alloc::flags::IS_COMPACT) | + (1 << theta_sketch_alloc::flags::IS_READ_ONLY) | + (this->is_empty() ? 1 << theta_sketch_alloc::flags::IS_EMPTY : 0) | + (this->is_ordered() ? 1 << theta_sketch_alloc::flags::IS_ORDERED : 0) + ); + copy_to_mem(&flags_byte, &ptr, sizeof(flags_byte)); + const uint16_t seed_hash = get_seed_hash(); + copy_to_mem(&seed_hash, &ptr, sizeof(seed_hash)); + if (!this->is_empty()) { + if (!is_single_item) { + copy_to_mem(&num_keys_, &ptr, sizeof(num_keys_)); + const uint32_t unused32 = 0; + copy_to_mem(&unused32, &ptr, sizeof(unused32)); + if (this->is_estimation_mode()) { + copy_to_mem(&(this->theta_), &ptr, sizeof(uint64_t)); + } + } + copy_to_mem(keys_, &ptr, sizeof(uint64_t) * num_keys_); + } + + return std::make_pair(std::move(data_ptr), size);; +} + template compact_theta_sketch_alloc compact_theta_sketch_alloc::deserialize(std::istream& is, uint64_t seed) { uint8_t preamble_longs; @@ -691,6 +805,54 @@ compact_theta_sketch_alloc compact_theta_sketch_alloc::internal_deserializ return compact_theta_sketch_alloc(is_empty, theta, keys, num_keys, seed_hash, is_ordered); } +template +compact_theta_sketch_alloc compact_theta_sketch_alloc::deserialize(const void* bytes, size_t size, uint64_t seed) { + const char* ptr = static_cast(bytes); + uint8_t preamble_longs; + copy_from_mem(&ptr, &preamble_longs, sizeof(preamble_longs)); + uint8_t serial_version; + copy_from_mem(&ptr, &serial_version, sizeof(serial_version)); + uint8_t family_id; + copy_from_mem(&ptr, &family_id, sizeof(family_id)); + uint16_t unused16; + copy_from_mem(&ptr, &unused16, sizeof(unused16)); + uint8_t flags_byte; + copy_from_mem(&ptr, &flags_byte, sizeof(flags_byte)); + uint16_t seed_hash; + copy_from_mem(&ptr, &seed_hash, sizeof(seed_hash)); + // TODO: checks here + return internal_deserialize(ptr, size - (ptr - static_cast(bytes)), preamble_longs, flags_byte, seed_hash); +} + +template +compact_theta_sketch_alloc compact_theta_sketch_alloc::internal_deserialize(const void* bytes, size_t size, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash) { + const char* ptr = static_cast(bytes); + + uint64_t theta = theta_sketch_alloc::MAX_THETA; + uint64_t* keys = nullptr; + uint32_t num_keys = 0; + + const bool is_empty = flags_byte & (1 << theta_sketch_alloc::flags::IS_EMPTY); + if (!is_empty) { + if (preamble_longs == 1) { + num_keys = 1; + } else { + copy_from_mem(&ptr, &num_keys, sizeof(num_keys)); + uint32_t unused32; + copy_from_mem(&ptr, &unused32, sizeof(unused32)); + if (preamble_longs > 2) { + copy_from_mem(&ptr, &theta, sizeof(theta)); + } + } + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + keys = AllocU64().allocate(num_keys); + copy_from_mem(&ptr, keys, sizeof(uint64_t) * num_keys); + } + + const bool is_ordered = flags_byte & (1 << theta_sketch_alloc::flags::IS_ORDERED); + return compact_theta_sketch_alloc(is_empty, theta, keys, num_keys, seed_hash, is_ordered); +} + template typename theta_sketch_alloc::const_iterator compact_theta_sketch_alloc::begin() const { return typename theta_sketch_alloc::const_iterator(keys_, num_keys_, 0); diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp index 4105e1bd..527298b5 100644 --- a/theta/test/theta_sketch_test.cpp +++ b/theta/test/theta_sketch_test.cpp @@ -28,6 +28,7 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_TEST(deserialize_single_item_from_java_as_subclass); CPPUNIT_TEST(deserialize_compact_estimation_from_java_as_base); CPPUNIT_TEST(deserialize_compact_estimation_from_java_as_subclass); + CPPUNIT_TEST(serialize_deserialize_stream_and_bytes_equivalency); CPPUNIT_TEST_SUITE_END(); void test() { @@ -217,16 +218,25 @@ class theta_sketch_test: public CppUnit::TestFixture { auto sketchptr = theta_sketch::deserialize(is); CPPUNIT_ASSERT(!sketchptr->is_empty()); CPPUNIT_ASSERT(sketchptr->is_estimation_mode()); + CPPUNIT_ASSERT(sketchptr->is_ordered()); CPPUNIT_ASSERT_EQUAL(4342U, sketchptr->get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.531700444213199, sketchptr->get_theta(), 1e-10); CPPUNIT_ASSERT_DOUBLES_EQUAL(8166.25234614053, sketchptr->get_estimate(), 1e-10); + // the same construction process in Java must have produced exactly the same sketch update_theta_sketch update_sketch = update_theta_sketch::builder().build(); const int n = 8192; for (int i = 0; i < n; i++) update_sketch.update(i); CPPUNIT_ASSERT_EQUAL(update_sketch.get_num_retained(), sketchptr->get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_theta(), sketchptr->get_theta(), 1e-10); CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketchptr->get_estimate(), 1e-10); + compact_theta_sketch compact_sketch = update_sketch.compact(); + // the sketches are ordered, so the iteration sequence must match exactly + auto iter = sketchptr->begin(); + for (auto key: compact_sketch) { + CPPUNIT_ASSERT_EQUAL(key, *iter); + ++iter; + } } void deserialize_compact_estimation_from_java_as_subclass() { @@ -248,6 +258,64 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketch.get_estimate(), 1e-10); } + void serialize_deserialize_stream_and_bytes_equivalency() { + update_theta_sketch update_sketch = update_theta_sketch::builder().build(); + const int n = 8192; + for (int i = 0; i < n; i++) update_sketch.update(i); + + // update sketch stream and bytes comparison + { + std::stringstream s(std::ios::in | std::ios::out | std::ios::binary); + update_sketch.serialize(s); + auto data = update_sketch.serialize(); + CPPUNIT_ASSERT_EQUAL((size_t) s.tellp(), data.second); + for (size_t i = 0; i < data.second; ++i) { + CPPUNIT_ASSERT_EQUAL((char)s.get(), ((char*)data.first.get())[i]); + } + + s.seekg(0); // rewind + update_theta_sketch deserialized_update_sketch1 = update_theta_sketch::deserialize(s); + update_theta_sketch deserialized_update_sketch2 = update_theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.is_empty(), deserialized_update_sketch2.is_empty()); + CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.is_ordered(), deserialized_update_sketch2.is_ordered()); + CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.get_num_retained(), deserialized_update_sketch2.get_num_retained()); + CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.get_theta(), deserialized_update_sketch2.get_theta()); + CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.get_estimate(), deserialized_update_sketch2.get_estimate()); + // hash tables must be identical since they are restored from dumps, and iteration is deterministic + auto iter = deserialized_update_sketch1.begin(); + for (auto key: deserialized_update_sketch2) { + CPPUNIT_ASSERT_EQUAL(key, *iter); + ++iter; + } + } + + // compact sketch stream and bytes comparison + { + std::stringstream s(std::ios::in | std::ios::out | std::ios::binary); + update_sketch.compact().serialize(s); + auto data = update_sketch.compact().serialize(); + CPPUNIT_ASSERT_EQUAL((size_t) s.tellp(), data.second); + for (size_t i = 0; i < data.second; ++i) { + CPPUNIT_ASSERT_EQUAL((char)s.get(), ((char*)data.first.get())[i]); + } + + s.seekg(0); // rewind + compact_theta_sketch deserialized_sketch1 = compact_theta_sketch::deserialize(s); + compact_theta_sketch deserialized_sketch2 = compact_theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate()); + // the sketches are ordered, so the iteration sequence must match exactly + auto iter = deserialized_sketch1.begin(); + for (auto key: deserialized_sketch2) { + CPPUNIT_ASSERT_EQUAL(key, *iter); + ++iter; + } + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(theta_sketch_test); From 4fa74c7af8fe19a867950a99a4555fb9fedeaf5a Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Wed, 8 May 2019 13:47:11 -0700 Subject: [PATCH 06/22] raw bytes deserialize as base class --- theta/include/theta_sketch_impl.hpp | 45 ++++++++++++++ theta/test/theta_sketch_test.cpp | 94 +++++++++++++++++++++-------- 2 files changed, 113 insertions(+), 26 deletions(-) diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index aac3c3ab..679038af 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -132,6 +132,51 @@ typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(st throw std::invalid_argument("unsupported sketch type " + std::to_string((int) type)); } +template +typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(const void* bytes, size_t size, uint64_t seed) { + const char* ptr = static_cast(bytes); + uint8_t preamble_longs; + copy_from_mem(&ptr, &preamble_longs, sizeof(preamble_longs)); + uint8_t serial_version; + copy_from_mem(&ptr, &serial_version, sizeof(serial_version)); + uint8_t type; + copy_from_mem(&ptr, &type, sizeof(type)); + uint8_t lg_nom_size; + copy_from_mem(&ptr, &lg_nom_size, sizeof(lg_nom_size)); + uint8_t lg_cur_size; + copy_from_mem(&ptr, &lg_cur_size, sizeof(lg_cur_size)); + uint8_t flags_byte; + copy_from_mem(&ptr, &flags_byte, sizeof(flags_byte)); + uint16_t seed_hash; + copy_from_mem(&ptr, &seed_hash, sizeof(seed_hash)); + + // TODO: checks here + + if (type == update_theta_sketch_alloc::SKETCH_TYPE) { + typename update_theta_sketch_alloc::resize_factor rf = static_cast::resize_factor>(preamble_longs >> 6); + typedef typename std::allocator_traits::template rebind_alloc> AU; + return unique_ptr( + static_cast*>(new (AU().allocate(1)) + update_theta_sketch_alloc(update_theta_sketch_alloc::internal_deserialize(ptr, size - (ptr - static_cast(bytes)), rf, lg_nom_size, lg_cur_size, flags_byte, seed))), + [](theta_sketch_alloc* ptr) { + ptr->~theta_sketch_alloc(); + AU().deallocate(static_cast*>(ptr), 1); + } + ); + } else if (type == compact_theta_sketch_alloc::SKETCH_TYPE) { + typedef typename std::allocator_traits::template rebind_alloc> AC; + return unique_ptr( + static_cast*>(new (AC().allocate(1)) + compact_theta_sketch_alloc(compact_theta_sketch_alloc::internal_deserialize(ptr, size - (ptr - static_cast(bytes)), preamble_longs, flags_byte, seed_hash))), + [](theta_sketch_alloc* ptr) { + ptr->~theta_sketch_alloc(); + AC().deallocate(static_cast*>(ptr), 1); + } + ); + } + throw std::invalid_argument("unsupported sketch type " + std::to_string((int) type)); +} + template uint16_t theta_sketch_alloc::get_seed_hash(uint64_t seed) { HashState hashes; diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp index 527298b5..0ef7040f 100644 --- a/theta/test/theta_sketch_test.cpp +++ b/theta/test/theta_sketch_test.cpp @@ -273,19 +273,40 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL((char)s.get(), ((char*)data.first.get())[i]); } - s.seekg(0); // rewind - update_theta_sketch deserialized_update_sketch1 = update_theta_sketch::deserialize(s); - update_theta_sketch deserialized_update_sketch2 = update_theta_sketch::deserialize(data.first.get(), data.second); - CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.is_empty(), deserialized_update_sketch2.is_empty()); - CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.is_ordered(), deserialized_update_sketch2.is_ordered()); - CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.get_num_retained(), deserialized_update_sketch2.get_num_retained()); - CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.get_theta(), deserialized_update_sketch2.get_theta()); - CPPUNIT_ASSERT_EQUAL(deserialized_update_sketch1.get_estimate(), deserialized_update_sketch2.get_estimate()); - // hash tables must be identical since they are restored from dumps, and iteration is deterministic - auto iter = deserialized_update_sketch1.begin(); - for (auto key: deserialized_update_sketch2) { - CPPUNIT_ASSERT_EQUAL(key, *iter); - ++iter; + // deserialize as base class + { + s.seekg(0); // rewind + auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s); + auto deserialized_sketch_ptr2 = theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_empty(), deserialized_sketch_ptr2->is_empty()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_ordered(), deserialized_sketch_ptr2->is_ordered()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_theta(), deserialized_sketch_ptr2->get_theta()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_estimate(), deserialized_sketch_ptr2->get_estimate()); + // hash tables must be identical since they are restored from dumps, and iteration is deterministic + auto iter = deserialized_sketch_ptr1->begin(); + for (auto key: *deserialized_sketch_ptr2) { + CPPUNIT_ASSERT_EQUAL(key, *iter); + ++iter; + } + } + + // deserialize as subclass + { + s.seekg(0); // rewind + update_theta_sketch deserialized_sketch1 = update_theta_sketch::deserialize(s); + update_theta_sketch deserialized_sketch2 = update_theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate()); + // hash tables must be identical since they are restored from dumps, and iteration is deterministic + auto iter = deserialized_sketch1.begin(); + for (auto key: deserialized_sketch2) { + CPPUNIT_ASSERT_EQUAL(key, *iter); + ++iter; + } } } @@ -299,19 +320,40 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL((char)s.get(), ((char*)data.first.get())[i]); } - s.seekg(0); // rewind - compact_theta_sketch deserialized_sketch1 = compact_theta_sketch::deserialize(s); - compact_theta_sketch deserialized_sketch2 = compact_theta_sketch::deserialize(data.first.get(), data.second); - CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty()); - CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered()); - CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); - CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta()); - CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate()); - // the sketches are ordered, so the iteration sequence must match exactly - auto iter = deserialized_sketch1.begin(); - for (auto key: deserialized_sketch2) { - CPPUNIT_ASSERT_EQUAL(key, *iter); - ++iter; + // deserialize as base class + { + s.seekg(0); // rewind + auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s); + auto deserialized_sketch_ptr2 = theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_empty(), deserialized_sketch_ptr2->is_empty()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_ordered(), deserialized_sketch_ptr2->is_ordered()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_theta(), deserialized_sketch_ptr2->get_theta()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_estimate(), deserialized_sketch_ptr2->get_estimate()); + // the sketches are ordered, so the iteration sequence must match exactly + auto iter = deserialized_sketch_ptr1->begin(); + for (auto key: *deserialized_sketch_ptr2) { + CPPUNIT_ASSERT_EQUAL(key, *iter); + ++iter; + } + } + + // deserialize as subclass + { + s.seekg(0); // rewind + compact_theta_sketch deserialized_sketch1 = compact_theta_sketch::deserialize(s); + compact_theta_sketch deserialized_sketch2 = compact_theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate()); + // the sketches are ordered, so the iteration sequence must match exactly + auto iter = deserialized_sketch1.begin(); + for (auto key: deserialized_sketch2) { + CPPUNIT_ASSERT_EQUAL(key, *iter); + ++iter; + } } } } From 4ec3b03ddf6294398695848da274ae7144173d8a Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Thu, 9 May 2019 13:08:21 -0700 Subject: [PATCH 07/22] added checking in deserialize --- theta/include/theta_sketch.hpp | 8 ++- theta/include/theta_sketch_impl.hpp | 96 ++++++++++++++++++++++------- theta/test/theta_sketch_test.cpp | 4 ++ 3 files changed, 83 insertions(+), 25 deletions(-) diff --git a/theta/include/theta_sketch.hpp b/theta/include/theta_sketch.hpp index 3e7d9613..85bc45ec 100644 --- a/theta/include/theta_sketch.hpp +++ b/theta/include/theta_sketch.hpp @@ -31,6 +31,7 @@ template class theta_sketch_alloc { public: static const uint64_t MAX_THETA = LLONG_MAX; // signed max for compatibility with Java + static const uint8_t SERIAL_VERSION = 3; theta_sketch_alloc(bool is_empty, uint64_t theta); theta_sketch_alloc(const theta_sketch_alloc& other); @@ -70,6 +71,11 @@ class theta_sketch_alloc { uint64_t theta_; static uint16_t get_seed_hash(uint64_t seed); + + static void check_sketch_type(uint8_t actual, uint8_t expected); + static void check_serial_version(uint8_t actual, uint8_t expected); + static void check_seed_hash(uint16_t actual, uint16_t expected); + static void check_size(size_t actual, size_t expected); }; // update sketch @@ -80,7 +86,6 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { class builder; enum resize_factor { X1, X2, X4, X8 }; static const uint8_t SKETCH_TYPE = 2; - static const uint8_t SERIAL_VERSION = 3; update_theta_sketch_alloc(const update_theta_sketch_alloc& other); update_theta_sketch_alloc(update_theta_sketch_alloc&& other) noexcept; @@ -173,7 +178,6 @@ template class compact_theta_sketch_alloc: public theta_sketch_alloc { public: static const uint8_t SKETCH_TYPE = 3; - static const uint8_t SERIAL_VERSION = 3; compact_theta_sketch_alloc(const compact_theta_sketch_alloc& other); compact_theta_sketch_alloc(compact_theta_sketch_alloc&& other) noexcept; diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index 679038af..d1892a53 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -107,7 +107,8 @@ typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(st uint16_t seed_hash; is.read((char*)&seed_hash, sizeof(seed_hash)); - // TODO: checks here + check_serial_version(serial_version, SERIAL_VERSION); + check_seed_hash(seed_hash, get_seed_hash(seed)); if (type == update_theta_sketch_alloc::SKETCH_TYPE) { typename update_theta_sketch_alloc::resize_factor rf = static_cast::resize_factor>(preamble_longs >> 6); @@ -134,6 +135,7 @@ typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(st template typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(const void* bytes, size_t size, uint64_t seed) { + check_size(size, static_cast(8)); const char* ptr = static_cast(bytes); uint8_t preamble_longs; copy_from_mem(&ptr, &preamble_longs, sizeof(preamble_longs)); @@ -150,14 +152,16 @@ typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(co uint16_t seed_hash; copy_from_mem(&ptr, &seed_hash, sizeof(seed_hash)); - // TODO: checks here + check_serial_version(serial_version, SERIAL_VERSION); + check_seed_hash(seed_hash, get_seed_hash(seed)); if (type == update_theta_sketch_alloc::SKETCH_TYPE) { typename update_theta_sketch_alloc::resize_factor rf = static_cast::resize_factor>(preamble_longs >> 6); typedef typename std::allocator_traits::template rebind_alloc> AU; return unique_ptr( - static_cast*>(new (AU().allocate(1)) - update_theta_sketch_alloc(update_theta_sketch_alloc::internal_deserialize(ptr, size - (ptr - static_cast(bytes)), rf, lg_nom_size, lg_cur_size, flags_byte, seed))), + static_cast*>(new (AU().allocate(1)) update_theta_sketch_alloc( + update_theta_sketch_alloc::internal_deserialize(ptr, size - (ptr - static_cast(bytes)), rf, lg_nom_size, lg_cur_size, flags_byte, seed)) + ), [](theta_sketch_alloc* ptr) { ptr->~theta_sketch_alloc(); AU().deallocate(static_cast*>(ptr), 1); @@ -166,8 +170,9 @@ typename theta_sketch_alloc::unique_ptr theta_sketch_alloc::deserialize(co } else if (type == compact_theta_sketch_alloc::SKETCH_TYPE) { typedef typename std::allocator_traits::template rebind_alloc> AC; return unique_ptr( - static_cast*>(new (AC().allocate(1)) - compact_theta_sketch_alloc(compact_theta_sketch_alloc::internal_deserialize(ptr, size - (ptr - static_cast(bytes)), preamble_longs, flags_byte, seed_hash))), + static_cast*>(new (AC().allocate(1)) compact_theta_sketch_alloc( + compact_theta_sketch_alloc::internal_deserialize(ptr, size - (ptr - static_cast(bytes)), preamble_longs, flags_byte, seed_hash)) + ), [](theta_sketch_alloc* ptr) { ptr->~theta_sketch_alloc(); AC().deallocate(static_cast*>(ptr), 1); @@ -184,6 +189,35 @@ uint16_t theta_sketch_alloc::get_seed_hash(uint64_t seed) { return hashes.h1; } +template +void theta_sketch_alloc::check_sketch_type(uint8_t actual, uint8_t expected) { + if (actual != expected) { + throw std::invalid_argument("Sketch type mismatch: expected " + std::to_string((int)expected) + ", actual " + std::to_string((int)actual)); + } +} + +template +void theta_sketch_alloc::check_serial_version(uint8_t actual, uint8_t expected) { + if (actual != expected) { + throw std::invalid_argument("Sketch serial version mismatch: expected " + std::to_string((int)expected) + ", actual " + std::to_string((int)actual)); + } +} + +template +void theta_sketch_alloc::check_seed_hash(uint16_t actual, uint16_t expected) { + if (actual != expected) { + throw std::invalid_argument("Sketch seed hash mismatch: expected " + std::to_string(expected) + ", actual " + std::to_string(actual)); + } +} + +template +void theta_sketch_alloc::check_size(size_t actual, size_t expected) { + std::cerr << "check size: expected " + std::to_string((int)expected) + ", actual " + std::to_string((int) actual) << std::endl; + if (actual < expected) { + throw std::invalid_argument("Given memory is smaller than expected: expected " + std::to_string((int)expected) + ", actual " + std::to_string((int) actual)); + } +} + // update sketch template @@ -325,7 +359,7 @@ template void update_theta_sketch_alloc::serialize(std::ostream& os) const { const uint8_t preamble_longs_and_rf = 3 | (rf_ << 6); os.write((char*)&preamble_longs_and_rf, sizeof(preamble_longs_and_rf)); - const uint8_t serial_version = SERIAL_VERSION; + const uint8_t serial_version = theta_sketch_alloc::SERIAL_VERSION; os.write((char*)&serial_version, sizeof(serial_version)); const uint8_t type = SKETCH_TYPE; os.write((char*)&type, sizeof(type)); @@ -356,7 +390,7 @@ std::pair update_theta_sketch_alloc::ser const uint8_t preamble_longs_and_rf = preamble_longs | (rf_ << 6); copy_to_mem(&preamble_longs_and_rf, &ptr, sizeof(preamble_longs_and_rf)); - const uint8_t serial_version = SERIAL_VERSION; + const uint8_t serial_version = theta_sketch_alloc::SERIAL_VERSION; copy_to_mem(&serial_version, &ptr, sizeof(serial_version)); const uint8_t type = SKETCH_TYPE; copy_to_mem(&type, &ptr, sizeof(type)); @@ -394,7 +428,9 @@ update_theta_sketch_alloc update_theta_sketch_alloc::deserialize(std::istr is.read((char*)&flags_byte, sizeof(flags_byte)); uint16_t seed_hash; is.read((char*)&seed_hash, sizeof(seed_hash)); - // TODO: checks here + theta_sketch_alloc::check_sketch_type(type, SKETCH_TYPE); + theta_sketch_alloc::check_serial_version(serial_version, theta_sketch_alloc::SERIAL_VERSION); + theta_sketch_alloc::check_seed_hash(seed_hash, theta_sketch_alloc::get_seed_hash(seed)); return internal_deserialize(is, rf, lg_nom_size, lg_cur_size, flags_byte, seed); } @@ -406,14 +442,15 @@ update_theta_sketch_alloc update_theta_sketch_alloc::internal_deserialize( is.read((char*)&p, sizeof(p)); uint64_t theta; is.read((char*)&theta, sizeof(theta)); - uint64_t* keys = AllocU64().allocate(num_keys); - is.read((char*)keys, sizeof(uint64_t) * num_keys); + uint64_t* keys = AllocU64().allocate(1 << lg_cur_size); + is.read((char*)keys, sizeof(uint64_t) * (1 << lg_cur_size)); const bool is_empty = flags_byte & (1 << theta_sketch_alloc::flags::IS_EMPTY); return update_theta_sketch_alloc(is_empty, theta, lg_nom_size, lg_cur_size, keys, num_keys, rf, p, seed); } template update_theta_sketch_alloc update_theta_sketch_alloc::deserialize(const void* bytes, size_t size, uint64_t seed) { + theta_sketch_alloc::check_size(size, 8); const char* ptr = static_cast(bytes); uint8_t preamble_longs; copy_from_mem(&ptr, &preamble_longs, sizeof(preamble_longs)); @@ -431,12 +468,16 @@ update_theta_sketch_alloc update_theta_sketch_alloc::deserialize(const voi copy_from_mem(&ptr, &flags_byte, sizeof(flags_byte)); uint16_t seed_hash; copy_from_mem(&ptr, &seed_hash, sizeof(seed_hash)); - // TODO: checks here + theta_sketch_alloc::check_sketch_type(type, SKETCH_TYPE); + theta_sketch_alloc::check_serial_version(serial_version, theta_sketch_alloc::SERIAL_VERSION); + theta_sketch_alloc::check_seed_hash(seed_hash, theta_sketch_alloc::get_seed_hash(seed)); return internal_deserialize(ptr, size - (ptr - static_cast(bytes)), rf, lg_nom_size, lg_cur_size, flags_byte, seed); } template update_theta_sketch_alloc update_theta_sketch_alloc::internal_deserialize(const void* bytes, size_t size, resize_factor rf, uint8_t lg_nom_size, uint8_t lg_cur_size, uint8_t flags_byte, uint64_t seed) { + const uint32_t table_size = 1 << lg_cur_size; + theta_sketch_alloc::check_size(size, 16 + sizeof(uint64_t) * table_size); const char* ptr = static_cast(bytes); uint32_t num_keys; copy_from_mem(&ptr, &num_keys, sizeof(num_keys)); @@ -444,8 +485,8 @@ update_theta_sketch_alloc update_theta_sketch_alloc::internal_deserialize( copy_from_mem(&ptr, &p, sizeof(p)); uint64_t theta; copy_from_mem(&ptr, &theta, sizeof(theta)); - uint64_t* keys = AllocU64().allocate(num_keys); - copy_from_mem(&ptr, keys, sizeof(uint64_t) * num_keys); + uint64_t* keys = AllocU64().allocate(table_size); + copy_from_mem(&ptr, keys, sizeof(uint64_t) * table_size); const bool is_empty = flags_byte & (1 << theta_sketch_alloc::flags::IS_EMPTY); return update_theta_sketch_alloc(is_empty, theta, lg_nom_size, lg_cur_size, keys, num_keys, rf, p, seed); } @@ -734,7 +775,7 @@ void compact_theta_sketch_alloc::serialize(std::ostream& os) const { const bool is_single_item = num_keys_ == 1 and !this->is_estimation_mode(); const uint8_t preamble_longs = this->is_empty() or is_single_item ? 1 : this->is_estimation_mode() ? 3 : 2; os.write((char*)&preamble_longs, sizeof(preamble_longs)); - const uint8_t serial_version = SERIAL_VERSION; + const uint8_t serial_version = theta_sketch_alloc::SERIAL_VERSION; os.write((char*)&serial_version, sizeof(serial_version)); const uint8_t type = SKETCH_TYPE; os.write((char*)&type, sizeof(type)); @@ -775,7 +816,7 @@ std::pair compact_theta_sketch_alloc::se char* ptr = static_cast(data_ptr.get()) + header_size_bytes; copy_to_mem(&preamble_longs, &ptr, sizeof(preamble_longs)); - const uint8_t serial_version = SERIAL_VERSION; + const uint8_t serial_version = theta_sketch_alloc::SERIAL_VERSION; copy_to_mem(&serial_version, &ptr, sizeof(serial_version)); const uint8_t type = SKETCH_TYPE; copy_to_mem(&type, &ptr, sizeof(type)); @@ -811,15 +852,17 @@ compact_theta_sketch_alloc compact_theta_sketch_alloc::deserialize(std::is is.read((char*)&preamble_longs, sizeof(preamble_longs)); uint8_t serial_version; is.read((char*)&serial_version, sizeof(serial_version)); - uint8_t family_id; - is.read((char*)&family_id, sizeof(family_id)); + uint8_t type; + is.read((char*)&type, sizeof(type)); uint16_t unused16; is.read((char*)&unused16, sizeof(unused16)); uint8_t flags_byte; is.read((char*)&flags_byte, sizeof(flags_byte)); uint16_t seed_hash; is.read((char*)&seed_hash, sizeof(seed_hash)); - // TODO: checks here + theta_sketch_alloc::check_sketch_type(type, SKETCH_TYPE); + theta_sketch_alloc::check_serial_version(serial_version, theta_sketch_alloc::SERIAL_VERSION); + theta_sketch_alloc::check_seed_hash(seed_hash, theta_sketch_alloc::get_seed_hash(seed)); return internal_deserialize(is, preamble_longs, flags_byte, seed_hash); } @@ -852,20 +895,23 @@ compact_theta_sketch_alloc compact_theta_sketch_alloc::internal_deserializ template compact_theta_sketch_alloc compact_theta_sketch_alloc::deserialize(const void* bytes, size_t size, uint64_t seed) { + theta_sketch_alloc::check_size(size, 8); const char* ptr = static_cast(bytes); uint8_t preamble_longs; copy_from_mem(&ptr, &preamble_longs, sizeof(preamble_longs)); uint8_t serial_version; copy_from_mem(&ptr, &serial_version, sizeof(serial_version)); - uint8_t family_id; - copy_from_mem(&ptr, &family_id, sizeof(family_id)); + uint8_t type; + copy_from_mem(&ptr, &type, sizeof(type)); uint16_t unused16; copy_from_mem(&ptr, &unused16, sizeof(unused16)); uint8_t flags_byte; copy_from_mem(&ptr, &flags_byte, sizeof(flags_byte)); uint16_t seed_hash; copy_from_mem(&ptr, &seed_hash, sizeof(seed_hash)); - // TODO: checks here + theta_sketch_alloc::check_sketch_type(type, SKETCH_TYPE); + theta_sketch_alloc::check_serial_version(serial_version, theta_sketch_alloc::SERIAL_VERSION); + theta_sketch_alloc::check_seed_hash(seed_hash, theta_sketch_alloc::get_seed_hash(seed)); return internal_deserialize(ptr, size - (ptr - static_cast(bytes)), preamble_longs, flags_byte, seed_hash); } @@ -882,16 +928,20 @@ compact_theta_sketch_alloc compact_theta_sketch_alloc::internal_deserializ if (preamble_longs == 1) { num_keys = 1; } else { + theta_sketch_alloc::check_size(size, 8); copy_from_mem(&ptr, &num_keys, sizeof(num_keys)); uint32_t unused32; copy_from_mem(&ptr, &unused32, sizeof(unused32)); if (preamble_longs > 2) { + theta_sketch_alloc::check_size(size - (ptr - static_cast(bytes)), 8); copy_from_mem(&ptr, &theta, sizeof(theta)); } } + const size_t keys_size_bytes = sizeof(uint64_t) * num_keys; + theta_sketch_alloc::check_size(size - (ptr - static_cast(bytes)), keys_size_bytes); typedef typename std::allocator_traits::template rebind_alloc AllocU64; keys = AllocU64().allocate(num_keys); - copy_from_mem(&ptr, keys, sizeof(uint64_t) * num_keys); + copy_from_mem(&ptr, keys, keys_size_bytes); } const bool is_ordered = flags_byte & (1 << theta_sketch_alloc::flags::IS_ORDERED); diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp index 0ef7040f..ceaa4ea8 100644 --- a/theta/test/theta_sketch_test.cpp +++ b/theta/test/theta_sketch_test.cpp @@ -278,6 +278,7 @@ class theta_sketch_test: public CppUnit::TestFixture { s.seekg(0); // rewind auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s); auto deserialized_sketch_ptr2 = theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(data.second, (size_t) s.tellg()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_empty(), deserialized_sketch_ptr2->is_empty()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_ordered(), deserialized_sketch_ptr2->is_ordered()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained()); @@ -296,6 +297,7 @@ class theta_sketch_test: public CppUnit::TestFixture { s.seekg(0); // rewind update_theta_sketch deserialized_sketch1 = update_theta_sketch::deserialize(s); update_theta_sketch deserialized_sketch2 = update_theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(data.second, (size_t) s.tellg()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); @@ -325,6 +327,7 @@ class theta_sketch_test: public CppUnit::TestFixture { s.seekg(0); // rewind auto deserialized_sketch_ptr1 = theta_sketch::deserialize(s); auto deserialized_sketch_ptr2 = theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(data.second, (size_t) s.tellg()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_empty(), deserialized_sketch_ptr2->is_empty()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->is_ordered(), deserialized_sketch_ptr2->is_ordered()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained()); @@ -343,6 +346,7 @@ class theta_sketch_test: public CppUnit::TestFixture { s.seekg(0); // rewind compact_theta_sketch deserialized_sketch1 = compact_theta_sketch::deserialize(s); compact_theta_sketch deserialized_sketch2 = compact_theta_sketch::deserialize(data.first.get(), data.second); + CPPUNIT_ASSERT_EQUAL(data.second, (size_t) s.tellg()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_empty(), deserialized_sketch2.is_empty()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.is_ordered(), deserialized_sketch2.is_ordered()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); From d8d3d7b50bdb82d19f685219c8b31278ec08f612 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Thu, 9 May 2019 17:02:13 -0700 Subject: [PATCH 08/22] removed debug print --- theta/include/theta_sketch_impl.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index d1892a53..bc2735bf 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -212,7 +212,6 @@ void theta_sketch_alloc::check_seed_hash(uint16_t actual, uint16_t expected) template void theta_sketch_alloc::check_size(size_t actual, size_t expected) { - std::cerr << "check size: expected " + std::to_string((int)expected) + ", actual " + std::to_string((int) actual) << std::endl; if (actual < expected) { throw std::invalid_argument("Given memory is smaller than expected: expected " + std::to_string((int)expected) + ", actual " + std::to_string((int) actual)); } From ac3866bd64753b8fd024aa9279b5605fc0fc3245 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Thu, 9 May 2019 17:42:52 -0700 Subject: [PATCH 09/22] union builder --- theta/include/theta_union.hpp | 24 +++++++++++++++---- theta/include/theta_union_impl.hpp | 38 ++++++++++++++++++++++++++---- theta/test/theta_union_test.cpp | 6 ++--- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/theta/include/theta_union.hpp b/theta/include/theta_union.hpp index 7475bbfc..a4086309 100644 --- a/theta/include/theta_union.hpp +++ b/theta/include/theta_union.hpp @@ -23,19 +23,33 @@ namespace datasketches { template class theta_union_alloc { public: - static const uint64_t MAX_THETA = LLONG_MAX; // signed max for compatibility with Java - - theta_union_alloc(uint8_t lg_k); - + class builder; bool is_empty() const; void update(const theta_sketch_alloc& sketch); compact_theta_sketch_alloc get_result(bool ordered = true) const; - //void to_stream(std::ostream& os, bool print_items = false) const; private: uint64_t theta_; update_theta_sketch_alloc state_; + + // for builder + theta_union_alloc(uint64_t theta, update_theta_sketch_alloc&& state); +}; + +// builder + +template +class theta_union_alloc::builder { +public: + typedef typename update_theta_sketch_alloc::resize_factor resize_factor; + builder& set_lg_k(uint8_t lg_k); + builder& set_resize_factor(resize_factor rf); + builder& set_p(float p); + builder& set_seed(uint64_t seed); + theta_union_alloc build() const; +private: + typename update_theta_sketch_alloc::builder sketch_builder; }; // alias with default allocator for convenience diff --git a/theta/include/theta_union_impl.hpp b/theta/include/theta_union_impl.hpp index 11240e1c..fbb055b2 100644 --- a/theta/include/theta_union_impl.hpp +++ b/theta/include/theta_union_impl.hpp @@ -15,10 +15,8 @@ namespace datasketches { */ template -theta_union_alloc::theta_union_alloc(uint8_t lg_k): -theta_(MAX_THETA), state_(typename update_theta_sketch_alloc::builder().set_lg_k(lg_k).build()) -{ -} +theta_union_alloc::theta_union_alloc(uint64_t theta, update_theta_sketch_alloc&& state): +theta_(theta), state_(std::move(state)) {} template void theta_union_alloc::update(const theta_sketch_alloc& sketch) { @@ -63,6 +61,38 @@ compact_theta_sketch_alloc theta_union_alloc::get_result(bool ordered) con return compact_theta_sketch_alloc(false, theta, keys, num_keys, state_.get_seed_hash(), ordered); } +// builder + +template +typename theta_union_alloc::builder& theta_union_alloc::builder::set_lg_k(uint8_t lg_k) { + sketch_builder.set_lg_k(lg_k); + return *this; +} + +template +typename theta_union_alloc::builder& theta_union_alloc::builder::set_resize_factor(resize_factor rf) { + sketch_builder.set_resize_factor(rf); + return *this; +} + +template +typename theta_union_alloc::builder& theta_union_alloc::builder::set_p(float p) { + sketch_builder.set_p(p); + return *this; +} + +template +typename theta_union_alloc::builder& theta_union_alloc::builder::set_seed(uint64_t seed) { + sketch_builder.set_seed(seed); + return *this; +} + +template +theta_union_alloc theta_union_alloc::builder::build() const { + update_theta_sketch_alloc sketch = sketch_builder.build(); + return theta_union_alloc(sketch.get_theta64(), std::move(sketch)); +} + } /* namespace datasketches */ # endif diff --git a/theta/test/theta_union_test.cpp b/theta/test/theta_union_test.cpp index 2dc5d821..0740ea14 100644 --- a/theta/test/theta_union_test.cpp +++ b/theta/test/theta_union_test.cpp @@ -20,7 +20,7 @@ class theta_union_test: public CppUnit::TestFixture { void empty() { update_theta_sketch sketch1 = update_theta_sketch::builder().build(); - theta_union u(12); + theta_union u = theta_union::builder().build(); u.update(sketch1); compact_theta_sketch sketch2 = u.get_result(); CPPUNIT_ASSERT(sketch2.is_empty()); @@ -35,7 +35,7 @@ class theta_union_test: public CppUnit::TestFixture { value = 500; for (int i = 0; i < 1000; i++) sketch2.update(value++); - theta_union u(12); + theta_union u = theta_union::builder().build(); u.update(sketch1); u.update(sketch2); compact_theta_sketch sketch3 = u.get_result(); @@ -53,7 +53,7 @@ class theta_union_test: public CppUnit::TestFixture { value = 5000; for (int i = 0; i < 10000; i++) sketch2.update(value++); - theta_union u(12); + theta_union u = theta_union::builder().build(); u.update(sketch1); u.update(sketch2); compact_theta_sketch sketch3 = u.get_result(); From e91cb718775f0a1ea57c23c4324da708441084a9 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Thu, 9 May 2019 18:04:57 -0700 Subject: [PATCH 10/22] performance optimization --- theta/include/theta_union_impl.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/theta/include/theta_union_impl.hpp b/theta/include/theta_union_impl.hpp index fbb055b2..15b2fbcd 100644 --- a/theta/include/theta_union_impl.hpp +++ b/theta/include/theta_union_impl.hpp @@ -31,6 +31,7 @@ void theta_union_alloc::update(const theta_sketch_alloc& sketch) { } else { for (auto hash: sketch) if (hash < theta_) state_.internal_update(hash); } + if (state_.get_theta64() < theta_) theta_ = state_.get_theta64(); } template From c031cec5754b3b72337b37ceda33518cecad050e Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 10 May 2019 11:06:31 -0700 Subject: [PATCH 11/22] removed unimplemented method for now --- theta/include/theta_union.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/theta/include/theta_union.hpp b/theta/include/theta_union.hpp index a4086309..3e7a316f 100644 --- a/theta/include/theta_union.hpp +++ b/theta/include/theta_union.hpp @@ -24,7 +24,6 @@ template class theta_union_alloc { public: class builder; - bool is_empty() const; void update(const theta_sketch_alloc& sketch); compact_theta_sketch_alloc get_result(bool ordered = true) const; From 992a2a2fd1c29d01347acca458e5ed1b16d435ae Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 10 May 2019 11:43:59 -0700 Subject: [PATCH 12/22] check seed --- theta/include/theta_union_impl.hpp | 2 +- theta/test/theta_union_test.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/theta/include/theta_union_impl.hpp b/theta/include/theta_union_impl.hpp index 15b2fbcd..4a602119 100644 --- a/theta/include/theta_union_impl.hpp +++ b/theta/include/theta_union_impl.hpp @@ -21,7 +21,7 @@ theta_(theta), state_(std::move(state)) {} template void theta_union_alloc::update(const theta_sketch_alloc& sketch) { if (sketch.is_empty()) return; - // check seed hash + if (sketch.get_seed_hash() != state_.get_seed_hash()) throw std::invalid_argument("seed hash mismatch"); if (sketch.get_theta64() < theta_) theta_ = sketch.get_theta64(); if (sketch.is_ordered()) { for (auto hash: sketch) { diff --git a/theta/test/theta_union_test.cpp b/theta/test/theta_union_test.cpp index 0740ea14..0bcb41bf 100644 --- a/theta/test/theta_union_test.cpp +++ b/theta/test/theta_union_test.cpp @@ -16,6 +16,7 @@ class theta_union_test: public CppUnit::TestFixture { CPPUNIT_TEST(empty); CPPUNIT_TEST(exact_mode_half_overlap); CPPUNIT_TEST(estimation_mode_half_overlap); + CPPUNIT_TEST(seed_mismatch); CPPUNIT_TEST_SUITE_END(); void empty() { @@ -63,6 +64,13 @@ class theta_union_test: public CppUnit::TestFixture { //sketch3.to_stream(std::cerr, true); } + void seed_mismatch() { + update_theta_sketch sketch = update_theta_sketch::builder().build(); + sketch.update(1); // non-empty should not be ignored + theta_union u = theta_union::builder().set_seed(123).build(); + CPPUNIT_ASSERT_THROW(u.update(sketch), std::invalid_argument); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(theta_union_test); From c29b11b1d7fd7548fb7956c92596f375fa5c2a25 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 10 May 2019 16:02:05 -0700 Subject: [PATCH 13/22] slight optimization --- theta/include/theta_sketch_impl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index bc2735bf..16d6ecad 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -562,7 +562,7 @@ void update_theta_sketch_alloc::update(const void* data, unsigned length) { template compact_theta_sketch_alloc update_theta_sketch_alloc::compact(bool ordered) const { - if (this->is_empty()) return compact_theta_sketch_alloc(true, theta_sketch_alloc::MAX_THETA, nullptr, 0, get_seed_hash(), true); + if (this->get_num_retained() == 0) return compact_theta_sketch_alloc(this->is_empty_, this->theta_, nullptr, 0, get_seed_hash(), ordered); uint64_t* keys = AllocU64().allocate(num_keys_); uint32_t i = 0; for (auto value: *this) keys[i++] = value; From 6426472c6d5431eb77e30eca5d02bee31e339d19 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 10 May 2019 16:04:27 -0700 Subject: [PATCH 14/22] removed unstructured test, added test for non-empty sketch with no retained keys in sampling mode --- theta/test/theta_sketch_test.cpp | 61 ++++++-------------------------- 1 file changed, 10 insertions(+), 51 deletions(-) diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp index ceaa4ea8..ded4a7be 100644 --- a/theta/test/theta_sketch_test.cpp +++ b/theta/test/theta_sketch_test.cpp @@ -13,8 +13,8 @@ namespace datasketches { class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(theta_sketch_test); - CPPUNIT_TEST(test); CPPUNIT_TEST(empty); + CPPUNIT_TEST(non_empty_no_retained_keys); CPPUNIT_TEST(single_item); CPPUNIT_TEST(resize_exact); CPPUNIT_TEST(estimation); @@ -31,56 +31,6 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_TEST(serialize_deserialize_stream_and_bytes_equivalency); CPPUNIT_TEST_SUITE_END(); - void test() { - update_theta_sketch update_sketch = update_theta_sketch::builder().build(); - std::cerr << "after build" << std::endl; - std::ofstream ofs("theta_update_empty.bin"); - update_sketch.serialize(ofs); - - CPPUNIT_ASSERT(update_sketch.is_empty()); - - int value = 1; - update_sketch.update(value++); - CPPUNIT_ASSERT(!update_sketch.is_empty()); - CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_estimate()); - - theta_sketch& sketch1 = update_sketch; - CPPUNIT_ASSERT_EQUAL(1.0, sketch1.get_estimate()); - - compact_theta_sketch compact_sketch = update_sketch.compact(); - CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_estimate()); - compact_sketch.to_stream(std::cerr, true); - - theta_sketch& sketch2 = compact_sketch; - CPPUNIT_ASSERT_EQUAL(1.0, sketch2.get_estimate()); - - std::cerr << "copy construct" << std::endl; - - update_theta_sketch sketch3 = update_sketch; - - std::cerr << "move construct" << std::endl; - - update_theta_sketch sketch4 = std::move(update_sketch); - - std::cerr << "copy assign" << std::endl; - - sketch3 = sketch4; - - std::cerr << "move" << std::endl; - - sketch3 = std::move(sketch4); - - std::cerr << "end" << std::endl; - - sketch3.update(value++); - sketch3.to_stream(std::cerr, true); - - update_theta_sketch usk = update_theta_sketch::builder().build(); - //for (int i = 0; i < 10000; i++) usk.update(i); - std::ofstream ofs2("theta_compact_empty.bin"); - usk.compact().serialize(ofs2); - } - void empty() { update_theta_sketch update_sketch = update_theta_sketch::builder().build(); CPPUNIT_ASSERT(update_sketch.is_empty()); @@ -89,6 +39,15 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate()); } + void non_empty_no_retained_keys() { + update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001).build(); + update_sketch.update(1); + CPPUNIT_ASSERT_EQUAL(0U, update_sketch.get_num_retained()); + CPPUNIT_ASSERT(!update_sketch.is_empty()); + CPPUNIT_ASSERT(update_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate()); + } + void single_item() { update_theta_sketch update_sketch = update_theta_sketch::builder().build(); update_sketch.update(1); From 84d85dda3bbe198ea04ccc17574efb797e7fad69 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 10 May 2019 16:14:57 -0700 Subject: [PATCH 15/22] handling of empty flag --- theta/include/theta_union.hpp | 2 +- theta/include/theta_union_impl.hpp | 7 ++++--- theta/test/theta_union_test.cpp | 22 +++++++++++++++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/theta/include/theta_union.hpp b/theta/include/theta_union.hpp index 3e7a316f..993204a9 100644 --- a/theta/include/theta_union.hpp +++ b/theta/include/theta_union.hpp @@ -28,7 +28,7 @@ class theta_union_alloc { compact_theta_sketch_alloc get_result(bool ordered = true) const; private: - + bool is_empty_; uint64_t theta_; update_theta_sketch_alloc state_; diff --git a/theta/include/theta_union_impl.hpp b/theta/include/theta_union_impl.hpp index 4a602119..f276f642 100644 --- a/theta/include/theta_union_impl.hpp +++ b/theta/include/theta_union_impl.hpp @@ -16,12 +16,13 @@ namespace datasketches { template theta_union_alloc::theta_union_alloc(uint64_t theta, update_theta_sketch_alloc&& state): -theta_(theta), state_(std::move(state)) {} +is_empty_(true), theta_(theta), state_(std::move(state)) {} template void theta_union_alloc::update(const theta_sketch_alloc& sketch) { if (sketch.is_empty()) return; if (sketch.get_seed_hash() != state_.get_seed_hash()) throw std::invalid_argument("seed hash mismatch"); + is_empty_ = false; if (sketch.get_theta64() < theta_) theta_ = sketch.get_theta64(); if (sketch.is_ordered()) { for (auto hash: sketch) { @@ -36,7 +37,7 @@ void theta_union_alloc::update(const theta_sketch_alloc& sketch) { template compact_theta_sketch_alloc theta_union_alloc::get_result(bool ordered) const { - if (state_.is_empty()) return state_.compact(ordered); + if (is_empty_) return state_.compact(ordered); const uint32_t nom_num_keys = 1 << state_.lg_nom_size_; if (theta_ >= state_.theta_ and state_.get_num_retained() <= nom_num_keys) return state_.compact(ordered); uint64_t theta = std::min(theta_, state_.get_theta64()); @@ -46,7 +47,7 @@ compact_theta_sketch_alloc theta_union_alloc::get_result(bool ordered) con for (auto key: state_) { if (key < theta) keys[num_keys++] = key; } - if (num_keys == 0) return compact_theta_sketch_alloc(true, theta_sketch_alloc::MAX_THETA, nullptr, 0, state_.get_seed_hash(), true); + if (num_keys == 0) return compact_theta_sketch_alloc(is_empty_, theta, nullptr, 0, state_.get_seed_hash(), ordered); if (num_keys > nom_num_keys) { std::nth_element(keys, &keys[nom_num_keys], &keys[num_keys]); theta = keys[nom_num_keys]; diff --git a/theta/test/theta_union_test.cpp b/theta/test/theta_union_test.cpp index 0bcb41bf..11edde61 100644 --- a/theta/test/theta_union_test.cpp +++ b/theta/test/theta_union_test.cpp @@ -14,6 +14,7 @@ class theta_union_test: public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(theta_union_test); CPPUNIT_TEST(empty); + CPPUNIT_TEST(non_empty_no_retained_keys); CPPUNIT_TEST(exact_mode_half_overlap); CPPUNIT_TEST(estimation_mode_half_overlap); CPPUNIT_TEST(seed_mismatch); @@ -22,9 +23,28 @@ class theta_union_test: public CppUnit::TestFixture { void empty() { update_theta_sketch sketch1 = update_theta_sketch::builder().build(); theta_union u = theta_union::builder().build(); - u.update(sketch1); compact_theta_sketch sketch2 = u.get_result(); + CPPUNIT_ASSERT_EQUAL(0U, sketch2.get_num_retained()); + CPPUNIT_ASSERT(sketch2.is_empty()); + CPPUNIT_ASSERT(!sketch2.is_estimation_mode()); + + u.update(sketch1); + sketch2 = u.get_result(); + CPPUNIT_ASSERT_EQUAL(0U, sketch2.get_num_retained()); CPPUNIT_ASSERT(sketch2.is_empty()); + CPPUNIT_ASSERT(!sketch2.is_estimation_mode()); + } + + void non_empty_no_retained_keys() { + update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001).build(); + update_sketch.update(1); + theta_union u = theta_union::builder().build(); + u.update(update_sketch); + compact_theta_sketch sketch = u.get_result(); + CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained()); + CPPUNIT_ASSERT(!sketch.is_empty()); + CPPUNIT_ASSERT(sketch.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, sketch.get_theta(), 1e-10); } void exact_mode_half_overlap() { From 70e32c52080b0d6f777af57162a0cc588ba70a7b Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 10 May 2019 16:24:18 -0700 Subject: [PATCH 16/22] test compact --- theta/test/theta_sketch_test.cpp | 36 +++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp index ded4a7be..42e05afb 100644 --- a/theta/test/theta_sketch_test.cpp +++ b/theta/test/theta_sketch_test.cpp @@ -37,6 +37,12 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate()); + + compact_theta_sketch compact_sketch = update_sketch.compact(); + CPPUNIT_ASSERT(compact_sketch.is_empty()); + CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_estimate()); } void non_empty_no_retained_keys() { @@ -46,7 +52,13 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(!update_sketch.is_empty()); CPPUNIT_ASSERT(update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate()); - } + + compact_theta_sketch compact_sketch = update_sketch.compact(); + CPPUNIT_ASSERT_EQUAL(0U, compact_sketch.get_num_retained()); + CPPUNIT_ASSERT(!compact_sketch.is_empty()); + CPPUNIT_ASSERT(compact_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_estimate()); +} void single_item() { update_theta_sketch update_sketch = update_theta_sketch::builder().build(); @@ -55,6 +67,12 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_estimate()); + + compact_theta_sketch compact_sketch = update_sketch.compact(); + CPPUNIT_ASSERT(!compact_sketch.is_empty()); + CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_estimate()); } void resize_exact() { @@ -64,7 +82,13 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_estimate()); - } + + compact_theta_sketch compact_sketch = update_sketch.compact(); + CPPUNIT_ASSERT(!compact_sketch.is_empty()); + CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta()); + CPPUNIT_ASSERT_EQUAL(2000.0, compact_sketch.get_estimate()); +} void estimation() { update_theta_sketch update_sketch = update_theta_sketch::builder().set_resize_factor(update_theta_sketch::resize_factor::X1).build(); @@ -74,7 +98,13 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(update_sketch.is_estimation_mode()); CPPUNIT_ASSERT(update_sketch.get_theta() < 1.0); CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, update_sketch.get_estimate(), n * 0.01); - } + + compact_theta_sketch compact_sketch = update_sketch.compact(); + CPPUNIT_ASSERT(!compact_sketch.is_empty()); + CPPUNIT_ASSERT(compact_sketch.is_estimation_mode()); + CPPUNIT_ASSERT(compact_sketch.get_theta() < 1.0); + CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, compact_sketch.get_estimate(), n * 0.01); +} void deserialize_update_empty_from_java_as_base() { std::ifstream is; From a7521283578e7402f5b0f628fd01587f89576f89 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Wed, 15 May 2019 14:38:08 -0700 Subject: [PATCH 17/22] theta intersection --- theta/include/theta_intersection.hpp | 58 ++++++ theta/include/theta_intersection_impl.hpp | 197 +++++++++++++++++++ theta/include/theta_sketch.hpp | 3 + theta/test/theta_intersection_test.cpp | 226 ++++++++++++++++++++++ 4 files changed, 484 insertions(+) create mode 100644 theta/include/theta_intersection.hpp create mode 100644 theta/include/theta_intersection_impl.hpp create mode 100644 theta/test/theta_intersection_test.cpp diff --git a/theta/include/theta_intersection.hpp b/theta/include/theta_intersection.hpp new file mode 100644 index 00000000..4bab6abc --- /dev/null +++ b/theta/include/theta_intersection.hpp @@ -0,0 +1,58 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_INTERSECTION_HPP_ +#define THETA_INTERSECTION_HPP_ + +#include +#include +#include + +#include + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +template +class theta_intersection_alloc { +public: + explicit theta_intersection_alloc(uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); + theta_intersection_alloc(const theta_intersection_alloc& other); + theta_intersection_alloc(theta_intersection_alloc&& other) noexcept; + ~theta_intersection_alloc(); + + theta_intersection_alloc& operator=(theta_intersection_alloc other); + theta_intersection_alloc& operator=(theta_intersection_alloc&& other); + + void update(const theta_sketch_alloc& sketch); + compact_theta_sketch_alloc get_result(bool ordered = true) const; + bool has_result() const; + +private: + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + bool is_valid_; + bool is_empty_; + uint64_t theta_; + uint8_t lg_size_; + uint64_t* keys_; + uint32_t num_keys_; + uint16_t seed_hash_; + + static bool hash_search(uint64_t hash, const uint64_t* table, uint8_t lg_size); +}; + +// alias with default allocator for convenience +typedef theta_intersection_alloc> theta_intersection; + +} /* namespace datasketches */ + +#include "theta_intersection_impl.hpp" + +# endif diff --git a/theta/include/theta_intersection_impl.hpp b/theta/include/theta_intersection_impl.hpp new file mode 100644 index 00000000..0b25b449 --- /dev/null +++ b/theta/include/theta_intersection_impl.hpp @@ -0,0 +1,197 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_INTERSECTION_IMPL_HPP_ +#define THETA_INTERSECTION_IMPL_HPP_ + +#include + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +template +theta_intersection_alloc::theta_intersection_alloc(uint64_t seed): +is_valid_(false), +is_empty_(false), +theta_(theta_sketch_alloc::MAX_THETA), +lg_size_(0), +keys_(nullptr), +num_keys_(0), +seed_hash_(theta_sketch_alloc::get_seed_hash(seed)) +{} + +template +theta_intersection_alloc::theta_intersection_alloc(const theta_intersection_alloc& other): +is_valid_(other.is_valid_), +is_empty_(other.is_empty_), +theta_(other.theta_), +lg_size_(other.lg_size_), +keys_(other.keys_ == nullptr ? nullptr : AllocU64().allocate(1 << lg_size_)), +num_keys_(other.num_keys_), +seed_hash_(other.seed_hash_) +{ + if (keys_ != nullptr) std::copy(other.keys_, &other.keys_[1 << lg_size_], keys_); +} + +template +theta_intersection_alloc::theta_intersection_alloc(theta_intersection_alloc&& other) noexcept: +is_valid_(false), +is_empty_(false), +theta_(theta_sketch_alloc::MAX_THETA), +lg_size_(0), +keys_(nullptr), +num_keys_(0), +seed_hash_(other.seed_hash_) +{ + std::swap(is_valid_, other.is_valid_); + std::swap(is_empty_, other.is_empty_); + std::swap(theta_, other.theta_); + std::swap(lg_size_, other.lg_size_); + std::swap(keys_, other.keys_); + std::swap(num_keys_, other.num_keys_); +} + +template +theta_intersection_alloc::~theta_intersection_alloc() { + if (keys_ != nullptr) { + AllocU64().deallocate(keys_, 1 << lg_size_); + } +} + +template +theta_intersection_alloc& theta_intersection_alloc::operator=(theta_intersection_alloc other) { + std::swap(is_valid_, other.is_valid_); + std::swap(is_empty_, other.is_empty_); + std::swap(theta_, other.theta_); + std::swap(lg_size_, other.lg_size_); + std::swap(keys_, other.keys_); + std::swap(num_keys_, other.num_keys_); + std::swap(seed_hash_, other.seed_hash_); + return *this; +} + +template +theta_intersection_alloc& theta_intersection_alloc::operator=(theta_intersection_alloc&& other) { + std::swap(is_valid_, other.is_valid_); + std::swap(is_empty_, other.is_empty_); + std::swap(theta_, other.theta_); + std::swap(lg_size_, other.lg_size_); + std::swap(keys_, other.keys_); + std::swap(num_keys_, other.num_keys_); + std::swap(seed_hash_, other.seed_hash_); + return *this; +} + +template +bool theta_intersection_alloc::hash_search(uint64_t hash, const uint64_t* table, uint8_t lg_size) { + const uint32_t mask = (1 << lg_size) - 1; + const uint32_t stride = update_theta_sketch_alloc::get_stride(hash, lg_size); + uint32_t cur_probe = static_cast(hash) & mask; + const uint32_t loop_index = cur_probe; + do { + const uint64_t value = table[cur_probe]; + if (value == 0) { + return false; + } else if (value == hash) { + return true; + } + cur_probe = (cur_probe + stride) & mask; + } while (cur_probe != loop_index); + std::cerr << "hash_search: lg=" << (int)lg_size << ", hash=" << hash << std::endl; + std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; + throw std::logic_error("key not found and search wrapped"); +} + +constexpr uint8_t log2(uint32_t n) { + return (n > 1) ? 1 + log2(n >> 1) : 0; +} + +constexpr uint8_t lg_size_from_count(uint32_t n, double load_factor) { + uint8_t lg = log2(n) + 1; + if (n > (1 << lg) * load_factor) lg++; + return lg; +} + +template +void theta_intersection_alloc::update(const theta_sketch_alloc& sketch) { + if (is_empty_) return; + if (sketch.get_seed_hash() != seed_hash_) throw std::invalid_argument("seed hash mismatch"); + is_empty_ |= sketch.is_empty(); + theta_ = std::min(theta_, sketch.get_theta64()); + if (is_valid_ and num_keys_ == 0) return; + if (sketch.get_num_retained() == 0) { + is_valid_ = true; + if (keys_ != nullptr) { + AllocU64().deallocate(keys_, 1 << lg_size_); + keys_ = nullptr; + lg_size_ = 0; + num_keys_ = 0; + } + return; + } + if (!is_valid_) { // first update, clone incoming sketch + is_valid_ = true; + lg_size_ = lg_size_from_count(sketch.get_num_retained(), update_theta_sketch_alloc::REBUILD_THRESHOLD); + keys_ = AllocU64().allocate(1 << lg_size_); + std::fill(keys_, &keys_[1 << lg_size_], 0); + num_keys_ = sketch.get_num_retained(); + for (auto key: sketch) update_theta_sketch_alloc::hash_search_or_insert(key, keys_, lg_size_); + } else { // intersection + const uint32_t max_matches = std::min(num_keys_, sketch.get_num_retained()); + uint64_t* matched_keys = AllocU64().allocate(max_matches); + uint32_t match_count = 0; + for (auto key: sketch) { + if (key < theta_) { + if (hash_search(key, keys_, lg_size_)) matched_keys[match_count++] = key; + } else if (sketch.is_ordered()) { + break; // early stop + } + } + if (match_count == 0) { + AllocU64().deallocate(keys_, 1 << lg_size_); + keys_ = nullptr; + lg_size_ = 0; + num_keys_ = 0; + if (theta_ == theta_sketch_alloc::MAX_THETA) is_empty_ = true; + } else { + const uint8_t lg_size = lg_size_from_count(match_count, update_theta_sketch_alloc::REBUILD_THRESHOLD); + if (lg_size != lg_size_) { + AllocU64().deallocate(keys_, 1 << lg_size_); + lg_size_ = lg_size; + keys_ = AllocU64().allocate(1 << lg_size_); + std::fill(keys_, &keys_[1 << lg_size_], 0); + } + for (uint32_t i = 0; i < match_count; i++) { + update_theta_sketch_alloc::hash_search_or_insert(matched_keys[i], keys_, lg_size_); + } + num_keys_ = match_count; + } + AllocU64().deallocate(matched_keys, max_matches); + } +} + +template +compact_theta_sketch_alloc theta_intersection_alloc::get_result(bool ordered) const { + if (!is_valid_) throw std::invalid_argument("calling get_result() before calling update() is undefined"); + if (num_keys_ == 0) return compact_theta_sketch_alloc(is_empty_, theta_, nullptr, 0, seed_hash_, ordered); + uint64_t* keys = AllocU64().allocate(num_keys_); + std::copy_if(keys_, &keys_[1 << lg_size_], keys, [](uint64_t key) { return key != 0; }); + if (ordered) std::sort(keys, &keys[num_keys_]); + return compact_theta_sketch_alloc(false, this->theta_, keys, num_keys_, seed_hash_, ordered); +} + +template +bool theta_intersection_alloc::has_result() const { + return is_valid_; +} + +} /* namespace datasketches */ + +# endif diff --git a/theta/include/theta_sketch.hpp b/theta/include/theta_sketch.hpp index 85bc45ec..a72eaf9f 100644 --- a/theta/include/theta_sketch.hpp +++ b/theta/include/theta_sketch.hpp @@ -23,6 +23,7 @@ template class theta_sketch_alloc; template class update_theta_sketch_alloc; template class compact_theta_sketch_alloc; template class theta_union_alloc; +template class theta_intersection_alloc; // for serialization as raw bytes typedef std::unique_ptr> void_ptr_with_deleter; @@ -163,6 +164,7 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { friend theta_union_alloc; void internal_update(uint64_t hash); + friend theta_intersection_alloc; static inline uint32_t get_capacity(uint8_t lg_cur_size, uint8_t lg_nom_size); static inline uint32_t get_stride(uint64_t hash, uint8_t lg_size); static bool hash_search_or_insert(uint64_t hash, uint64_t* table, uint8_t lg_size); @@ -211,6 +213,7 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc { friend theta_sketch_alloc; friend update_theta_sketch_alloc; friend theta_union_alloc; + friend theta_intersection_alloc; compact_theta_sketch_alloc(bool is_empty, uint64_t theta, uint64_t* keys, uint32_t num_keys, uint16_t seed_hash, bool is_ordered); static compact_theta_sketch_alloc internal_deserialize(std::istream& is, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash); static compact_theta_sketch_alloc internal_deserialize(const void* bytes, size_t size, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash); diff --git a/theta/test/theta_intersection_test.cpp b/theta/test/theta_intersection_test.cpp new file mode 100644 index 00000000..44ab400e --- /dev/null +++ b/theta/test/theta_intersection_test.cpp @@ -0,0 +1,226 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#include +#include + +#include + +namespace datasketches { + +class theta_intersection_test: public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(theta_intersection_test); + CPPUNIT_TEST(invalid); + CPPUNIT_TEST(empty); + CPPUNIT_TEST(non_empty_no_retained_keys); + CPPUNIT_TEST(exact_mode_half_overlap_unordered); + CPPUNIT_TEST(exact_mode_half_overlap_ordered); + CPPUNIT_TEST(exact_mode_disjoint_unordered); + CPPUNIT_TEST(exact_mode_disjoint_ordered); + CPPUNIT_TEST(estimation_mode_half_overlap_unordered); + CPPUNIT_TEST(estimation_mode_half_overlap_ordered); + CPPUNIT_TEST(estimation_mode_disjoint_unordered); + CPPUNIT_TEST(estimation_mode_disjoint_ordered); + CPPUNIT_TEST(seed_mismatch); + CPPUNIT_TEST_SUITE_END(); + + void invalid() { + theta_intersection intersection; + CPPUNIT_ASSERT(!intersection.has_result()); + CPPUNIT_ASSERT_THROW(intersection.get_result(), std::invalid_argument); + } + + void empty() { + theta_intersection intersection; + update_theta_sketch sketch = update_theta_sketch::builder().build(); + intersection.update(sketch); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained()); + CPPUNIT_ASSERT(result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + + intersection.update(sketch); + result = intersection.get_result(); + CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained()); + CPPUNIT_ASSERT(result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void non_empty_no_retained_keys() { + update_theta_sketch sketch = update_theta_sketch::builder().set_p(0.001).build(); + sketch.update(1); + theta_intersection intersection; + intersection.update(sketch); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained()); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, result.get_theta(), 1e-10); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + + intersection.update(sketch); + result = intersection.get_result(); + CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained()); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, result.get_theta(), 1e-10); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void exact_mode_half_overlap_unordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + value = 500; + for (int i = 0; i < 1000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1); + intersection.update(sketch2); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate()); + } + + void exact_mode_half_overlap_ordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + value = 500; + for (int i = 0; i < 1000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1.compact()); + intersection.update(sketch2.compact()); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate()); + } + + void exact_mode_disjoint_unordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + for (int i = 0; i < 1000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1); + intersection.update(sketch2); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void exact_mode_disjoint_ordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + for (int i = 0; i < 1000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1.compact()); + intersection.update(sketch2.compact()); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void estimation_mode_half_overlap_unordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + value = 5000; + for (int i = 0; i < 10000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1); + intersection.update(sketch2); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02); + } + + void estimation_mode_half_overlap_ordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + value = 5000; + for (int i = 0; i < 10000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1.compact()); + intersection.update(sketch2.compact()); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02); + } + + void estimation_mode_disjoint_unordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + for (int i = 0; i < 10000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1); + intersection.update(sketch2); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void estimation_mode_disjoint_ordered() { + update_theta_sketch sketch1 = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) sketch1.update(value++); + + update_theta_sketch sketch2 = update_theta_sketch::builder().build(); + for (int i = 0; i < 10000; i++) sketch2.update(value++); + + theta_intersection intersection; + intersection.update(sketch1.compact()); + intersection.update(sketch2.compact()); + compact_theta_sketch result = intersection.get_result(); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void seed_mismatch() { + update_theta_sketch sketch = update_theta_sketch::builder().build(); + sketch.update(1); // non-empty should not be ignored + theta_intersection intersection(123); + CPPUNIT_ASSERT_THROW(intersection.update(sketch), std::invalid_argument); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(theta_intersection_test); + +} /* namespace datasketches */ From 0f3c7dc801a505f79ba3a77f239369b1a8f44a64 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 24 May 2019 10:18:22 -0700 Subject: [PATCH 18/22] a-not-b set operation --- theta/include/theta_a_not_b.hpp | 43 +++++ theta/include/theta_a_not_b_impl.hpp | 104 +++++++++++++ theta/include/theta_sketch.hpp | 5 + theta/include/theta_sketch_impl.hpp | 40 ++++- theta/test/theta_a_not_b_test.cpp | 225 +++++++++++++++++++++++++++ 5 files changed, 410 insertions(+), 7 deletions(-) create mode 100644 theta/include/theta_a_not_b.hpp create mode 100644 theta/include/theta_a_not_b_impl.hpp create mode 100644 theta/test/theta_a_not_b_test.cpp diff --git a/theta/include/theta_a_not_b.hpp b/theta/include/theta_a_not_b.hpp new file mode 100644 index 00000000..7a32adf5 --- /dev/null +++ b/theta/include/theta_a_not_b.hpp @@ -0,0 +1,43 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_A_NOT_B_HPP_ +#define THETA_A_NOT_B_HPP_ + +#include +#include +#include + +#include + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +template +class theta_a_not_b_alloc { +public: + explicit theta_a_not_b_alloc(uint64_t seed = update_theta_sketch_alloc::builder::DEFAULT_SEED); + + compact_theta_sketch_alloc compute(const theta_sketch_alloc& a, const theta_sketch_alloc& b, bool ordered = true) const; + +private: + typedef typename std::allocator_traits::template rebind_alloc AllocU64; + uint16_t seed_hash_; + +}; + +// alias with default allocator for convenience +typedef theta_a_not_b_alloc> theta_a_not_b; + +} /* namespace datasketches */ + +#include "theta_a_not_b_impl.hpp" + +# endif diff --git a/theta/include/theta_a_not_b_impl.hpp b/theta/include/theta_a_not_b_impl.hpp new file mode 100644 index 00000000..a794b63e --- /dev/null +++ b/theta/include/theta_a_not_b_impl.hpp @@ -0,0 +1,104 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef THETA_A_NOT_B_IMPL_HPP_ +#define THETA_A_NOT_B_IMPL_HPP_ + +#include + +namespace datasketches { + +/* + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +template +theta_a_not_b_alloc::theta_a_not_b_alloc(uint64_t seed): +seed_hash_(theta_sketch_alloc::get_seed_hash(seed)) +{} + +constexpr uint8_t log2(uint32_t n) { + return (n > 1) ? 1 + log2(n >> 1) : 0; +} + +constexpr uint8_t lg_size_from_count(uint32_t n, double load_factor) { + uint8_t lg = log2(n) + 1; + if (n > (1 << lg) * load_factor) lg++; + return lg; +} + +template +compact_theta_sketch_alloc theta_a_not_b_alloc::compute(const theta_sketch_alloc& a, const theta_sketch_alloc& b, bool ordered) const { + if (a.is_empty()) return compact_theta_sketch_alloc(a, ordered); + if (a.get_seed_hash() != seed_hash_) throw std::invalid_argument("A seed hash mismatch"); + if (b.get_seed_hash() != seed_hash_) throw std::invalid_argument("B seed hash mismatch"); + if (a.get_num_retained() == 0 or b.is_empty()) return compact_theta_sketch_alloc(a, ordered); + + const uint64_t theta = std::min(a.get_theta64(), b.get_theta64()); + uint64_t* keys = nullptr; + uint32_t keys_size = 0; + uint32_t count = 0; + bool is_empty = a.is_empty(); + + if (b.get_num_retained() == 0) { + for (auto key: a) if (key < theta) ++count; + keys_size = count; + keys = AllocU64().allocate(keys_size); + std::copy_if(a.begin(), a.end(), keys, [theta](uint64_t key) { return key < theta; }); + if (ordered and !a.is_ordered()) std::sort(keys, &keys[keys_size]); + if (count == 0 and theta == theta_sketch_alloc::MAX_THETA) is_empty = true; + return compact_theta_sketch_alloc(is_empty, theta, keys, count, seed_hash_, a.is_ordered() or ordered); + } + + keys_size = a.get_num_retained(); + keys = AllocU64().allocate(keys_size); + + if (a.is_ordered() and b.is_ordered()) { // sort-based + const auto end = std::set_difference(a.begin(), a.end(), b.begin(), b.end(), keys); + count = end - keys; + } else { // hash-based + const uint8_t lg_size = lg_size_from_count(b.get_num_retained(), update_theta_sketch_alloc::REBUILD_THRESHOLD); + uint64_t* b_hash_table = AllocU64().allocate(1 << lg_size); + std::fill(b_hash_table, &b_hash_table[1 << lg_size], 0); + for (auto key: b) { + if (key < theta) { + update_theta_sketch_alloc::hash_search_or_insert(key, b_hash_table, lg_size); + } else if (b.is_ordered()) { + break; // early stop + } + } + + // scan A lookup B + for (auto key: a) { + if (key < theta) { + if (!update_theta_sketch_alloc::hash_search(key, b_hash_table, lg_size)) keys[count++] = key; + } else if (a.is_ordered()) { + break; // early stop + } + } + + AllocU64().deallocate(b_hash_table, 1 << lg_size); + } + + if (count == 0) { + AllocU64().deallocate(keys, keys_size); + keys = nullptr; + if (theta == theta_sketch_alloc::MAX_THETA) is_empty = true; + } else if (count < keys_size) { + uint64_t* keys_copy = AllocU64().allocate(count); + std::copy(keys, &keys[count], keys_copy); + AllocU64().deallocate(keys, keys_size); + keys = keys_copy; + if (ordered and !a.is_ordered()) std::sort(keys, &keys[count]); + } + + return compact_theta_sketch_alloc(is_empty, theta, keys, count, seed_hash_, a.is_ordered() or ordered); +} + +} /* namespace datasketches */ + +# endif diff --git a/theta/include/theta_sketch.hpp b/theta/include/theta_sketch.hpp index a72eaf9f..5ec618d8 100644 --- a/theta/include/theta_sketch.hpp +++ b/theta/include/theta_sketch.hpp @@ -24,6 +24,7 @@ template class update_theta_sketch_alloc; template class compact_theta_sketch_alloc; template class theta_union_alloc; template class theta_intersection_alloc; +template class theta_a_not_b_alloc; // for serialization as raw bytes typedef std::unique_ptr> void_ptr_with_deleter; @@ -165,9 +166,11 @@ class update_theta_sketch_alloc: public theta_sketch_alloc { void internal_update(uint64_t hash); friend theta_intersection_alloc; + friend theta_a_not_b_alloc; static inline uint32_t get_capacity(uint8_t lg_cur_size, uint8_t lg_nom_size); static inline uint32_t get_stride(uint64_t hash, uint8_t lg_size); static bool hash_search_or_insert(uint64_t hash, uint64_t* table, uint8_t lg_size); + static bool hash_search(uint64_t hash, const uint64_t* table, uint8_t lg_size); friend theta_sketch_alloc; static update_theta_sketch_alloc internal_deserialize(std::istream& is, resize_factor rf, uint8_t lg_nom_size, uint8_t lg_cur_size, uint8_t flags_byte, uint64_t seed); @@ -182,6 +185,7 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc { static const uint8_t SKETCH_TYPE = 3; compact_theta_sketch_alloc(const compact_theta_sketch_alloc& other); + compact_theta_sketch_alloc(const theta_sketch_alloc& other, bool ordered); compact_theta_sketch_alloc(compact_theta_sketch_alloc&& other) noexcept; virtual ~compact_theta_sketch_alloc(); @@ -214,6 +218,7 @@ class compact_theta_sketch_alloc: public theta_sketch_alloc { friend update_theta_sketch_alloc; friend theta_union_alloc; friend theta_intersection_alloc; + friend theta_a_not_b_alloc; compact_theta_sketch_alloc(bool is_empty, uint64_t theta, uint64_t* keys, uint32_t num_keys, uint16_t seed_hash, bool is_ordered); static compact_theta_sketch_alloc internal_deserialize(std::istream& is, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash); static compact_theta_sketch_alloc internal_deserialize(const void* bytes, size_t size, uint8_t preamble_longs, uint8_t flags_byte, uint16_t seed_hash); diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index 16d6ecad..234585dc 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -562,12 +562,7 @@ void update_theta_sketch_alloc::update(const void* data, unsigned length) { template compact_theta_sketch_alloc update_theta_sketch_alloc::compact(bool ordered) const { - if (this->get_num_retained() == 0) return compact_theta_sketch_alloc(this->is_empty_, this->theta_, nullptr, 0, get_seed_hash(), ordered); - uint64_t* keys = AllocU64().allocate(num_keys_); - uint32_t i = 0; - for (auto value: *this) keys[i++] = value; - if (ordered) std::sort(keys, &keys[num_keys_]); - return compact_theta_sketch_alloc(false, this->theta_, keys, num_keys_, get_seed_hash(), ordered); + return compact_theta_sketch_alloc(*this, ordered); } template @@ -660,6 +655,26 @@ bool update_theta_sketch_alloc::hash_search_or_insert(uint64_t hash, uint64_t throw std::logic_error("key not found and no empty slots!"); } +template +bool update_theta_sketch_alloc::hash_search(uint64_t hash, const uint64_t* table, uint8_t lg_size) { + const uint32_t mask = (1 << lg_size) - 1; + const uint32_t stride = update_theta_sketch_alloc::get_stride(hash, lg_size); + uint32_t cur_probe = static_cast(hash) & mask; + const uint32_t loop_index = cur_probe; + do { + const uint64_t value = table[cur_probe]; + if (value == 0) { + return false; + } else if (value == hash) { + return true; + } + cur_probe = (cur_probe + stride) & mask; + } while (cur_probe != loop_index); + std::cerr << "hash_search: lg=" << (int)lg_size << ", hash=" << hash << std::endl; + std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; + throw std::logic_error("key not found and search wrapped"); +} + template typename theta_sketch_alloc::const_iterator update_theta_sketch_alloc::begin() const { return typename theta_sketch_alloc::const_iterator(keys_, 1 << lg_cur_size_, 0); @@ -692,6 +707,18 @@ is_ordered_(other.is_ordered_) std::copy(other.keys_, &other.keys_[num_keys_], keys_); } +template +compact_theta_sketch_alloc::compact_theta_sketch_alloc(const theta_sketch_alloc& other, bool ordered): +theta_sketch_alloc(other), +keys_(AllocU64().allocate(other.get_num_retained())), +num_keys_(other.get_num_retained()), +seed_hash_(other.get_seed_hash()), +is_ordered_(other.is_ordered() or ordered) +{ + std::copy(other.begin(), other.end(), keys_); + if (ordered and !other.is_ordered()) std::sort(keys_, &keys_[num_keys_]); +} + template compact_theta_sketch_alloc::compact_theta_sketch_alloc(compact_theta_sketch_alloc&& other) noexcept: theta_sketch_alloc(std::move(other)), @@ -717,7 +744,6 @@ compact_theta_sketch_alloc& compact_theta_sketch_alloc::operator=(const co num_keys_ = other.num_keys_; keys_ = AllocU64().allocate(num_keys_); } - keys_(AllocU64().allocate(other.num_keys_)), seed_hash_ = other.seed_hash_; is_ordered_ = other.is_ordered_; std::copy(other.keys_, &other.keys_[num_keys_], keys_); diff --git a/theta/test/theta_a_not_b_test.cpp b/theta/test/theta_a_not_b_test.cpp new file mode 100644 index 00000000..6ec90504 --- /dev/null +++ b/theta/test/theta_a_not_b_test.cpp @@ -0,0 +1,225 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#include +#include + +#include + +namespace datasketches { + +class theta_a_not_b_test: public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(theta_a_not_b_test); + CPPUNIT_TEST(empty); + CPPUNIT_TEST(non_empty_no_retained_keys); + CPPUNIT_TEST(exact_mode_half_overlap); + CPPUNIT_TEST(exact_mode_disjoint); + CPPUNIT_TEST(exact_mode_full_overlap); + CPPUNIT_TEST(estimation_mode_half_overlap); + CPPUNIT_TEST(estimation_mode_disjoint); + CPPUNIT_TEST(estimation_mode_full_overlap); + CPPUNIT_TEST(seed_mismatch); + CPPUNIT_TEST_SUITE_END(); + + void empty() { + theta_a_not_b a_not_b; + update_theta_sketch a = update_theta_sketch::builder().build(); + update_theta_sketch b = update_theta_sketch::builder().build(); + compact_theta_sketch result = a_not_b.compute(a, b); + CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained()); + CPPUNIT_ASSERT(result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void non_empty_no_retained_keys() { + update_theta_sketch a = update_theta_sketch::builder().build(); + a.update(1); + update_theta_sketch b = update_theta_sketch::builder().set_p(0.001).build(); + theta_a_not_b a_not_b; + + // B is still empty + compact_theta_sketch result = a_not_b.compute(a, b); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1U, result.get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result.get_theta(), 1e-10); + CPPUNIT_ASSERT_EQUAL(1.0, result.get_estimate()); + + // B is not empty in estimation mode and no entries + b.update(1); + CPPUNIT_ASSERT_EQUAL(0U, b.get_num_retained()); + + result = a_not_b.compute(a, b); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0U, result.get_num_retained()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.001, result.get_theta(), 1e-10); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void exact_mode_half_overlap() { + update_theta_sketch a = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) a.update(value++); + + update_theta_sketch b = update_theta_sketch::builder().build(); + value = 500; + for (int i = 0; i < 1000; i++) b.update(value++); + + theta_a_not_b a_not_b; + + // unordered inputs, ordered result + compact_theta_sketch result = a_not_b.compute(a, b); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT(result.is_ordered()); + CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate()); + + // unordered inputs, unordered result + result = a_not_b.compute(a, b, false); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT(!result.is_ordered()); + CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate()); + + // ordered inputs + result = a_not_b.compute(a.compact(), b.compact()); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT(result.is_ordered()); + CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate()); + + // A is ordered, so the result is ordered regardless + result = a_not_b.compute(a.compact(), b, false); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT(result.is_ordered()); + CPPUNIT_ASSERT_EQUAL(500.0, result.get_estimate()); +} + + void exact_mode_disjoint() { + update_theta_sketch a = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) a.update(value++); + + update_theta_sketch b = update_theta_sketch::builder().build(); + for (int i = 0; i < 1000; i++) b.update(value++); + + theta_a_not_b a_not_b; + + // unordered inputs + compact_theta_sketch result = a_not_b.compute(a, b); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1000.0, result.get_estimate()); + + // ordered inputs + result = a_not_b.compute(a.compact(), b.compact()); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(1000.0, result.get_estimate()); + } + + void exact_mode_full_overlap() { + update_theta_sketch sketch = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 1000; i++) sketch.update(value++); + + theta_a_not_b a_not_b; + + // unordered inputs + compact_theta_sketch result = a_not_b.compute(sketch, sketch); + CPPUNIT_ASSERT(result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + + // ordered inputs + result = a_not_b.compute(sketch.compact(), sketch.compact()); + CPPUNIT_ASSERT(result.is_empty()); + CPPUNIT_ASSERT(!result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void estimation_mode_half_overlap() { + update_theta_sketch a = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) a.update(value++); + + update_theta_sketch b = update_theta_sketch::builder().build(); + value = 5000; + for (int i = 0; i < 10000; i++) b.update(value++); + + theta_a_not_b a_not_b; + + // unordered inputs + compact_theta_sketch result = a_not_b.compute(a, b); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02); + + // ordered inputs + result = a_not_b.compute(a.compact(), b.compact()); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(5000, result.get_estimate(), 5000 * 0.02); + } + + void estimation_mode_disjoint() { + update_theta_sketch a = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) a.update(value++); + + update_theta_sketch b = update_theta_sketch::builder().build(); + for (int i = 0; i < 10000; i++) b.update(value++); + + theta_a_not_b a_not_b; + + // unordered inputs + compact_theta_sketch result = a_not_b.compute(a, b); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10000, result.get_estimate(), 10000 * 0.02); + + // ordered inputs + result = a_not_b.compute(a.compact(), b.compact()); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_DOUBLES_EQUAL(10000, result.get_estimate(), 10000 * 0.02); + } + + void estimation_mode_full_overlap() { + update_theta_sketch sketch = update_theta_sketch::builder().build(); + int value = 0; + for (int i = 0; i < 10000; i++) sketch.update(value++); + + theta_a_not_b a_not_b; + + // unordered inputs + compact_theta_sketch result = a_not_b.compute(sketch, sketch); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + + // ordered inputs + result = a_not_b.compute(sketch.compact(), sketch.compact()); + CPPUNIT_ASSERT(!result.is_empty()); + CPPUNIT_ASSERT(result.is_estimation_mode()); + CPPUNIT_ASSERT_EQUAL(0.0, result.get_estimate()); + } + + void seed_mismatch() { + update_theta_sketch sketch = update_theta_sketch::builder().build(); + sketch.update(1); // non-empty should not be ignored + theta_a_not_b a_not_b(123); + CPPUNIT_ASSERT_THROW(a_not_b.compute(sketch, sketch), std::invalid_argument); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(theta_a_not_b_test); + +} /* namespace datasketches */ From e6f994ff289c869cd26762a5b86fc9e205c65784 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Wed, 29 May 2019 15:12:09 -0700 Subject: [PATCH 19/22] error bounds --- theta/include/binomial_bounds.hpp | 444 ++++++++++++++++++++++++++++ theta/include/theta_sketch.hpp | 4 +- theta/include/theta_sketch_impl.hpp | 11 +- theta/test/theta_sketch_test.cpp | 62 ++++ 4 files changed, 515 insertions(+), 6 deletions(-) create mode 100644 theta/include/binomial_bounds.hpp diff --git a/theta/include/binomial_bounds.hpp b/theta/include/binomial_bounds.hpp new file mode 100644 index 00000000..dc0d0ad2 --- /dev/null +++ b/theta/include/binomial_bounds.hpp @@ -0,0 +1,444 @@ +/* + * Copyright 2019, Verizon Media. + * Licensed under the terms of the Apache License 2.0. See LICENSE file at the project root for terms. + */ + +#ifndef BINOMIAL_BOUNDS_HPP_ +#define BINOMIAL_BOUNDS_HPP_ + +#include +#include + +/* + * This class enables the estimation of error bounds given a sample set size, the sampling + * probability theta, the number of standard deviations and a simple noDataSeen flag. This can + * be used to estimate error bounds for fixed threshold sampling as well as the error bounds + * calculations for sketches. + * + * author Alexander Saydakov + * author Lee Rhodes + * author Kevin Lang + */ + +namespace datasketches { + +class binomial_bounds { + +public: + static double get_lower_bound(unsigned long long num_samples, double theta, unsigned num_std_devs) { + check_theta(theta); + check_num_std_devs(num_std_devs); + const double estimate = num_samples / theta; + const double lb = compute_approx_binomial_lower_bound(num_samples, theta, num_std_devs); + return std::min(estimate, std::max(static_cast(num_samples), lb)); + } + + static double get_upper_bound(unsigned long long num_samples, double theta, unsigned num_std_devs) { + check_theta(theta); + check_num_std_devs(num_std_devs); + const double estimate = num_samples / theta; + const double ub = compute_approx_binomial_upper_bound(num_samples, theta, num_std_devs); + return std::max(estimate, ub); + } + +private: + static constexpr double delta_of_num_std_devs[] = { + 0.5000000000000000000, // not actually using this value + 0.1586553191586026479, + 0.0227502618904135701, + 0.0013498126861731796 + }; + + static constexpr double lb_equiv_table[] = { + 1.0, 2.0, 3.0, // fake values for k = 0 + 0.78733703534118149, 3.14426768537558132, 13.56789685109913535, // k = 1 + 0.94091379266077979, 2.64699271711145911, 6.29302733018320737, // k = 2 + 0.96869128474958188, 2.46531676590527127, 4.97375283467403051, // k = 3 + 0.97933572521046131, 2.37418810664669877, 4.44899975481712318, // k = 4 + 0.98479165917274258, 2.31863116255024693, 4.16712379778553554, // k = 5 + 0.98806033915698777, 2.28075536565225434, 3.99010556144099837, // k = 6 + 0.99021896790580399, 2.25302005857281529, 3.86784477136922078, // k = 7 + 0.99174267079089873, 2.23168103978522936, 3.77784896945266269, // k = 8 + 0.99287147837287648, 2.21465899260871879, 3.70851932988722410, // k = 9 + 0.99373900046805375, 2.20070155496262032, 3.65326029076638292, // k = 10 + 0.99442519013851438, 2.18900651202670815, 3.60803817612955413, // k = 11 + 0.99498066823221620, 2.17903457780744247, 3.57024330407946877, // k = 12 + 0.99543899410224412, 2.17040883161922693, 3.53810982030634591, // k = 13 + 0.99582322541263579, 2.16285726913676513, 3.51039837124298515, // k = 14 + 0.99614973311747690, 2.15617827879603396, 3.48621230377099778, // k = 15 + 0.99643042892560629, 2.15021897666090922, 3.46488605693562590, // k = 16 + 0.99667418783778317, 2.14486114872480016, 3.44591466064832730, // k = 17 + 0.99688774875812669, 2.14001181420209718, 3.42890765690452781, // k = 18 + 0.99707632299691795, 2.13559675336844634, 3.41355809420343803, // k = 19 + 0.99724399084971083, 2.13155592217421486, 3.39962113251016262, // k = 20 + 0.99739400151915447, 2.12784018863251845, 3.38689892877548004, // k = 21 + 0.99752896842633731, 2.12440890875851096, 3.37522975271599535, // k = 22 + 0.99765101725122918, 2.12122815311133195, 3.36448003577621080, // k = 23 + 0.99776189496810730, 2.11826934724291505, 3.35453840911279144, // k = 24 + 0.99786304821586214, 2.11550823850916458, 3.34531123809287578, // k = 25 + 0.99795568665180667, 2.11292409529477254, 3.33671916527694634, // k = 26 + 0.99804083063483517, 2.11049908609763293, 3.32869446834217797, // k = 27 + 0.99811933910984862, 2.10821776918189130, 3.32117898316676019, // k = 28 + 0.99819195457286014, 2.10606671027090897, 3.31412243534683171, // k = 29 + 0.99825930555178388, 2.10403415237001923, 3.30748113008135647, // k = 30 + 0.99832193858154028, 2.10210975877822648, 3.30121691946897045, // k = 31 + 0.99838032666573895, 2.10028440670842542, 3.29529629751144171, // k = 32 + 0.99843488390555990, 2.09855000145353188, 3.28968974413223236, // k = 33 + 0.99848596721417948, 2.09689934193824001, 3.28437111460505093, // k = 34 + 0.99853390005924325, 2.09532599155502908, 3.27931717312372939, // k = 35 + 0.99857895741078551, 2.09382418262592296, 3.27450718840060517, // k = 36 + 0.99862138880970974, 2.09238872751677718, 3.26992261182860489, // k = 37 + 0.99866141580770318, 2.09101494715108061, 3.26554677962434425, // k = 38 + 0.99869923565267982, 2.08969860402822860, 3.26136468165239535, // k = 39 + 0.99873502010169091, 2.08843585627218431, 3.25736275677081721, // k = 40 + 0.99876893292508839, 2.08722321436752623, 3.25352872241415980, // k = 41 + 0.99880111078502409, 2.08605749165553789, 3.24985141664350863, // k = 42 + 0.99883168573342118, 2.08493577529222307, 3.24632068399498053, // k = 43 + 0.99886077231613513, 2.08385540129560809, 3.24292724848112357, // k = 44 + 0.99888847451828155, 2.08281392374021834, 3.23966263299664092, // k = 45 + 0.99891488795844907, 2.08180908991394631, 3.23651906111521726, // k = 46 + 0.99894010085196783, 2.08083882998420222, 3.23348939240611344, // k = 47 + 0.99896419358239541, 2.07990122528650545, 3.23056705515594444, // k = 48 + 0.99898723510594323, 2.07899450946285924, 3.22774598963252402, // k = 49 + 0.99900929266780736, 2.07811704477046533, 3.22502059972006805, // k = 50 + 0.99903043086155208, 2.07726730587160091, 3.22238570890294795, // k = 51 + 0.99905070073845081, 2.07644388314946582, 3.21983651940365689, // k = 52 + 0.99907015770423868, 2.07564546080757850, 3.21736857351049821, // k = 53 + 0.99908884779227947, 2.07487081196367740, 3.21497773796417619, // k = 54 + 0.99910681586905525, 2.07411879634256024, 3.21266015316183484, // k = 55 + 0.99912410177549305, 2.07338834403498140, 3.21041222805715165, // k = 56 + 0.99914074347179849, 2.07267845454973099, 3.20823061166797174, // k = 57 + 0.99915677607464204, 2.07198819052374006, 3.20611216970604573, // k = 58 + 0.99917223149395795, 2.07131667846186929, 3.20405396962596001, // k = 59 + 0.99918714153457699, 2.07066309019154460, 3.20205326110445299, // k = 60 + 0.99920153247185794, 2.07002665203046377, 3.20010746990493544, // k = 61 + 0.99921543193525508, 2.06940663431663552, 3.19821417453343315, // k = 62 + 0.99922886570365677, 2.06880235245998279, 3.19637109973109546, // k = 63 + 0.99924185357357942, 2.06821315729285971, 3.19457610621114441, // k = 64 + 0.99925441845175555, 2.06763843812092318, 3.19282717869864996, // k = 65 + 0.99926658263325407, 2.06707761824370095, 3.19112241228646099, // k = 66 + 0.99927836173816331, 2.06653015295219689, 3.18946001739936946, // k = 67 + 0.99928977431994781, 2.06599552505539918, 3.18783829446098821, // k = 68 + 0.99930083753795884, 2.06547324585920933, 3.18625564538041317, // k = 69 + 0.99931156864562354, 2.06496285191821016, 3.18471055124089730, // k = 70 + 0.99932197985521043, 2.06446390392778767, 3.18320157510865442, // k = 71 + 0.99933208559809827, 2.06397598606787369, 3.18172735837393361, // k = 72 + 0.99934190032416836, 2.06349869971447220, 3.18028661102792398, // k = 73 + 0.99935143390791836, 2.06303166975550312, 3.17887810481605015, // k = 74 + 0.99936070171270330, 2.06257453607466346, 3.17750067581857820, // k = 75 + 0.99936971103502970, 2.06212696042919674, 3.17615321728274580, // k = 76 + 0.99937847392385493, 2.06168861430600714, 3.17483467831510779, // k = 77 + 0.99938700168914352, 2.06125918927764928, 3.17354405480557489, // k = 78 + 0.99939530099953799, 2.06083838987589729, 3.17228039269048168, // k = 79 + 0.99940338278830154, 2.06042593411496000, 3.17104278166036124, // k = 80 + 0.99941125463777780, 2.06002155276328835, 3.16983035274597569, // k = 81 + 0.99941892470027938, 2.05962498741951094, 3.16864227952240185, // k = 82 + 0.99942640059737187, 2.05923599161263837, 3.16747776846497686, // k = 83 + 0.99943368842187397, 2.05885433061945378, 3.16633606416374391, // k = 84 + 0.99944079790603269, 2.05847977868873500, 3.16521644518826406, // k = 85 + 0.99944773295734990, 2.05811212058944193, 3.16411821883858124, // k = 86 + 0.99945450059186669, 2.05775114781260982, 3.16304072400711789, // k = 87 + 0.99946110646314423, 2.05739666442039493, 3.16198332650733960, // k = 88 + 0.99946755770463369, 2.05704847678819647, 3.16094541781455973, // k = 89 + 0.99947385746861528, 2.05670640500335367, 3.15992641851471490, // k = 90 + 0.99948001256305474, 2.05637027420314666, 3.15892576988736096, // k = 91 + 0.99948602689656241, 2.05603991286400856, 3.15794293484717059, // k = 92 + 0.99949190674294641, 2.05571516158917689, 3.15697740043813724, // k = 93 + 0.99949765436329585, 2.05539586490317561, 3.15602867309343083, // k = 94 + 0.99950327557880314, 2.05508187237845164, 3.15509627710042651, // k = 95 + 0.99950877461972709, 2.05477304104951486, 3.15417975753007340, // k = 96 + 0.99951415481862682, 2.05446923022574879, 3.15327867462917766, // k = 97 + 0.99951942042375208, 2.05417030908833453, 3.15239260700215596, // k = 98 + 0.99952457390890004, 2.05387614661762541, 3.15152114915238712, // k = 99 + 0.99952962005008317, 2.05358662050909402, 3.15066390921020911, // k = 100 + 0.99953456216121594, 2.05330161104427589, 3.14982051097524618, // k = 101 + 0.99953940176368405, 2.05302100378725072, 3.14899059183684926, // k = 102 + 0.99954414373920031, 2.05274468493067275, 3.14817379948561893, // k = 103 + 0.99954879047621148, 2.05247255013657082, 3.14736979964868624, // k = 104 + 0.99955334485656522, 2.05220449388099269, 3.14657826610371671, // k = 105 + 0.99955780993869325, 2.05194041831310869, 3.14579888316276879, // k = 106 + 0.99956218652590678, 2.05168022402710903, 3.14503134811607765, // k = 107 + 0.99956647932785359, 2.05142381889103831, 3.14427536967733090, // k = 108 + 0.99957069025060719, 2.05117111251445294, 3.14353066260227365, // k = 109 + 0.99957482032178291, 2.05092201793428330, 3.14279695558593630, // k = 110 + 0.99957887261450651, 2.05067645094720774, 3.14207398336887422, // k = 111 + 0.99958284988383639, 2.05043432833224415, 3.14136149076028914, // k = 112 + 0.99958675435604505, 2.05019557189746138, 3.14065923143530767, // k = 113 + 0.99959058650074439, 2.04996010556124020, 3.13996696426707445, // k = 114 + 0.99959434898201494, 2.04972785368377686, 3.13928445867830419, // k = 115 + 0.99959804437042976, 2.04949874512311681, 3.13861149103462367, // k = 116 + 0.99960167394553423, 2.04927271043337100, 3.13794784369528656, // k = 117 + 0.99960523957651048, 2.04904968140490951, 3.13729330661277572, // k = 118 + 0.99960874253329735, 2.04882959397491504, 3.13664767767019725, // k = 119 + 0.99961218434327748, 2.04861238220240693, 3.13601075688413289 // k = 120 + }; + + static constexpr double ub_equiv_table[] = { + 1.0, 2.0, 3.0, // fake values for k = 0 + 0.99067760836669549, 1.75460517119302040, 2.48055626001627161, // k = 1 + 0.99270518097577565, 1.78855957509907171, 2.53863835259832626, // k = 2 + 0.99402032633599902, 1.81047286499563143, 2.57811676180597260, // k = 3 + 0.99492607629539975, 1.82625928017762362, 2.60759550546498531, // k = 4 + 0.99558653966013821, 1.83839160339161367, 2.63086812358551470, // k = 5 + 0.99608981951632813, 1.84812399034444752, 2.64993712523727254, // k = 6 + 0.99648648035983456, 1.85617372053235385, 2.66598485907860550, // k = 7 + 0.99680750790483330, 1.86298655802610824, 2.67976541374471822, // k = 8 + 0.99707292880049181, 1.86885682585270274, 2.69178781407745760, // k = 9 + 0.99729614928489241, 1.87398826101983218, 2.70241106542158604, // k = 10 + 0.99748667952445658, 1.87852708449801753, 2.71189717290596377, // k = 11 + 0.99765127712748836, 1.88258159501103250, 2.72044290303773550, // k = 12 + 0.99779498340305395, 1.88623391878036273, 2.72819957382063194, // k = 13 + 0.99792160418357412, 1.88954778748873764, 2.73528576807902368, // k = 14 + 0.99803398604944960, 1.89257337682371940, 2.74179612106766513, // k = 15 + 0.99813449883217231, 1.89535099316557876, 2.74780718300419835, // k = 16 + 0.99822494122659577, 1.89791339232732525, 2.75338173141955167, // k = 17 + 0.99830679915913834, 1.90028752122407241, 2.75857186416826039, // k = 18 + 0.99838117410831728, 1.90249575897183831, 2.76342117562634826, // k = 19 + 0.99844913407071090, 1.90455689090418900, 2.76796659454200267, // k = 20 + 0.99851147736424650, 1.90648682834171268, 2.77223944710058845, // k = 21 + 0.99856879856019987, 1.90829917277082473, 2.77626682032629901, // k = 22 + 0.99862183849734265, 1.91000561415842185, 2.78007199816156003, // k = 23 + 0.99867096266018507, 1.91161621560812023, 2.78367524259661536, // k = 24 + 0.99871656986212543, 1.91313978579765376, 2.78709435016625662, // k = 25 + 0.99875907577771272, 1.91458400425526065, 2.79034488416175463, // k = 26 + 0.99879885565047744, 1.91595563175945927, 2.79344064132371273, // k = 27 + 0.99883610756373287, 1.91726064301425936, 2.79639384757751941, // k = 28 + 0.99887095169674467, 1.91850441099725799, 2.79921543574803877, // k = 29 + 0.99890379414739527, 1.91969155477030995, 2.80191513182441554, // k = 30 + 0.99893466279047516, 1.92082633358913313, 2.80450167352080371, // k = 31 + 0.99896392088177777, 1.92191254955568525, 2.80698295731653502, // k = 32 + 0.99899147889385631, 1.92295362479495680, 2.80936614404217266, // k = 33 + 0.99901764688726757, 1.92395267400968351, 2.81165765979318394, // k = 34 + 0.99904238606342233, 1.92491244978191389, 2.81386337393604435, // k = 35 + 0.99906590152386343, 1.92583552644848055, 2.81598868034527072, // k = 36 + 0.99908829040739988, 1.92672418013918900, 2.81803841726804194, // k = 37 + 0.99910959420023460, 1.92758051694144683, 2.82001709302821268, // k = 38 + 0.99912996403594434, 1.92840654943159961, 2.82192875763732332, // k = 39 + 0.99914930224576892, 1.92920397044028391, 2.82377730628954282, // k = 40 + 0.99916781270195543, 1.92997447498220254, 2.82556612075063640, // k = 41 + 0.99918553179077207, 1.93071949211818605, 2.82729843191989971, // k = 42 + 0.99920250730914972, 1.93144048613876862, 2.82897728689417249, // k = 43 + 0.99921873345181211, 1.93213870990595638, 2.83060537017752267, // k = 44 + 0.99923435180002684, 1.93281536508689555, 2.83218527795750674, // k = 45 + 0.99924930425362390, 1.93347145882316340, 2.83371938965598247, // k = 46 + 0.99926370394567243, 1.93410820221384938, 2.83520990872793277, // k = 47 + 0.99927750755296074, 1.93472643138986200, 2.83665891945119597, // k = 48 + 0.99929082941537217, 1.93532697329771963, 2.83806833931606661, // k = 49 + 0.99930366295501472, 1.93591074716263734, 2.83943997143404658, // k = 50 + 0.99931598804721489, 1.93647857274021362, 2.84077557836653227, // k = 51 + 0.99932789059798210, 1.93703110239354714, 2.84207662106302905, // k = 52 + 0.99933946180485123, 1.93756904936378760, 2.84334468086129277, // k = 53 + 0.99935053819703512, 1.93809302131219852, 2.84458116874117195, // k = 54 + 0.99936126637970801, 1.93860365411038060, 2.84578731838604426, // k = 55 + 0.99937166229284458, 1.93910149816429112, 2.84696443486512862, // k = 56 + 0.99938169190727422, 1.93958709548454067, 2.84811369085281285, // k = 57 + 0.99939136927613959, 1.94006085573701625, 2.84923617230361970, // k = 58 + 0.99940074328745254, 1.94052339623206649, 2.85033291216254270, // k = 59 + 0.99940993070470086, 1.94097508636855309, 2.85140492437699322, // k = 60 + 0.99941868577388959, 1.94141633372043998, 2.85245314430358121, // k = 61 + 0.99942734443487780, 1.94184757038001976, 2.85347839582286156, // k = 62 + 0.99943556385736088, 1.94226915100517772, 2.85448160365493209, // k = 63 + 0.99944374522542034, 1.94268143723749631, 2.85546346373061510, // k = 64 + 0.99945159955424856, 1.94308482059116727, 2.85642486111805738, // k = 65 + 0.99945915301904620, 1.94347956957849988, 2.85736639994965458, // k = 66 + 0.99946660663832176, 1.94386600964031686, 2.85828887832701639, // k = 67 + 0.99947383703224091, 1.94424436597356021, 2.85919278275500233, // k = 68 + 0.99948075442870277, 1.94461502153473020, 2.86007887186090670, // k = 69 + 0.99948766082269458, 1.94497821937304138, 2.86094774077355396, // k = 70 + 0.99949422748713346, 1.94533411296001191, 2.86179981848076181, // k = 71 + 0.99950070756119658, 1.94568300035135167, 2.86263579405672886, // k = 72 + 0.99950704321753392, 1.94602523449961495, 2.86345610449197352, // k = 73 + 0.99951320334216121, 1.94636083782822311, 2.86426125541271404, // k = 74 + 0.99951920293474927, 1.94669011080745236, 2.86505169255406145, // k = 75 + 0.99952501670378524, 1.94701327348536779, 2.86582788270862920, // k = 76 + 0.99953071209267819, 1.94733044372333097, 2.86659027602854621, // k = 77 + 0.99953632734991515, 1.94764180764266825, 2.86733927778843167, // k = 78 + 0.99954171164873173, 1.94794766430732125, 2.86807526143834934, // k = 79 + 0.99954699274462655, 1.94824807472994621, 2.86879864789403882, // k = 80 + 0.99955216611081710, 1.94854317889829076, 2.86950970901679625, // k = 81 + 0.99955730019613043, 1.94883320227168610, 2.87020887436986527, // k = 82 + 0.99956213770650493, 1.94911826561721568, 2.87089648477021342, // k = 83 + 0.99956704264963037, 1.94939848545763539, 2.87157281693902178, // k = 84 + 0.99957166306481327, 1.94967401618316671, 2.87223821840905202, // k = 85 + 0.99957632713136491, 1.94994497791333288, 2.87289293193450135, // k = 86 + 0.99958087233392234, 1.95021155752212394, 2.87353731228213860, // k = 87 + 0.99958532555996271, 1.95047376805584349, 2.87417154907075201, // k = 88 + 0.99958956246481989, 1.95073180380688882, 2.87479599765507032, // k = 89 + 0.99959389351869277, 1.95098572880579013, 2.87541081987382086, // k = 90 + 0.99959807862052230, 1.95123574036898617, 2.87601637401948551, // k = 91 + 0.99960214057801977, 1.95148186921983324, 2.87661283691068093, // k = 92 + 0.99960607527256684, 1.95172415829728152, 2.87720042968334155, // k = 93 + 0.99960996433179616, 1.95196280898670693, 2.87777936649376898, // k = 94 + 0.99961379137860717, 1.95219787713926962, 2.87834989933620022, // k = 95 + 0.99961756088146103, 1.95242944583677058, 2.87891216133900230, // k = 96 + 0.99962125605327401, 1.95265762420910960, 2.87946647367488140, // k = 97 + 0.99962486179100551, 1.95288245314810638, 2.88001290210658567, // k = 98 + 0.99962843240297161, 1.95310404286672679, 2.88055166523392359, // k = 99 + 0.99963187276145504, 1.95332251980147475, 2.88108300006589957, // k = 100 + 0.99963525453173929, 1.95353785898848287, 2.88160703591438505, // k = 101 + 0.99963855412988778, 1.95375019354571577, 2.88212393551896184, // k = 102 + 0.99964190254169694, 1.95395953472205974, 2.88263389761985422, // k = 103 + 0.99964506565942202, 1.95416607430155409, 2.88313700661564098, // k = 104 + 0.99964834424233118, 1.95436972855640079, 2.88363350163803034, // k = 105 + 0.99965136548857458, 1.95457068540693513, 2.88412349413960101, // k = 106 + 0.99965436594726498, 1.95476896383092935, 2.88460710620208260, // k = 107 + 0.99965736463468602, 1.95496457504532373, 2.88508450078833789, // k = 108 + 0.99966034130443404, 1.95515761150707590, 2.88555580586194083, // k = 109 + 0.99966326130828520, 1.95534810382198998, 2.88602118761679094, // k = 110 + 0.99966601446035952, 1.95553622237747504, 2.88648066384146773, // k = 111 + 0.99966887679593697, 1.95572186728168163, 2.88693444915907094, // k = 112 + 0.99967161286551232, 1.95590523410490391, 2.88738271495714116, // k = 113 + 0.99967435412270333, 1.95608626483223702, 2.88782540459769166, // k = 114 + 0.99967701261934394, 1.95626497627117146, 2.88826277189363623, // k = 115 + 0.99967963265157778, 1.95644153684824573, 2.88869486674335008, // k = 116 + 0.99968216317182623, 1.95661589936000269, 2.88912184353694101, // k = 117 + 0.99968479674396349, 1.95678821614791332, 2.88954376359643561, // k = 118 + 0.99968729031337489, 1.95695842061650183, 2.88996069422501023, // k = 119 + 0.99968963358631413, 1.95712651709766305, 2.89037285320668502 // k = 120 + }; + + // our "classic" bounds, but now with continuity correction + static double cont_classic_lb(unsigned long long num_samples, double theta, double num_std_devs) { + const double n_hat = (num_samples - 0.5) / theta; + const double b = num_std_devs * std::sqrt((1.0 - theta) / theta); + const double d = 0.5 * b * std::sqrt((b * b) + (4.0 * n_hat)); + const double center = n_hat + (0.5 * (b * b)); + return (center - d); + } + + // our "classic" bounds, but now with continuity correction + static double cont_classic_ub(unsigned long long num_samples, double theta, double num_std_devs) { + const double n_hat = (num_samples + 0.5) / theta; + const double b = num_std_devs * std::sqrt((1.0 - theta) / theta); + const double d = 0.5 * b * std::sqrt((b * b) + (4.0 * n_hat)); + const double center = n_hat + (0.5 * (b * b)); + return (center + d); + } + + // This is a special purpose calculator for NStar, using a computational + // strategy inspired by its Bayesian definition. It is only appropriate + // for a very limited set of inputs. However, the procedure compute_approx_binomial_lower_bound() + // below does in fact only call it for suitably limited inputs. + // Outside of this limited range, two different bad things will happen. + // First, because we are not using logarithms, the values of intermediate + // quantities will exceed the dynamic range of doubles. Second, even if that + // problem were fixed, the running time of this procedure is essentially linear + // in est = (numSamples / p), and that can be Very, Very Big. + static unsigned long long special_n_star(unsigned long long num_samples, double p, double delta) { + const double q = 1.0 - p; + // Use a different algorithm if the following is true; this one will be too slow, or worse. + if ((num_samples / p) >= 500.0) throw std::invalid_argument("out of range"); + double cur_term = std::pow(p, num_samples); // curTerm = posteriorProbability (k, k, p) + if (cur_term <= 1e-100) throw std::logic_error("out of range"); // sanity check for non-use of logarithms + double tot = cur_term; + unsigned long long m = num_samples; + while (tot <= delta) { // this test can fail even the first time + cur_term = (cur_term * q * (m)) / ((m + 1) - num_samples); + tot += cur_term; + m += 1; + } + // we have reached a state where tot > delta, so back up one + return (m - 1); + } + + // The following procedure has very limited applicability. + // The above remarks about special_n_star() also apply here. + static unsigned long long special_n_prime_b(unsigned long long num_samples, double p, double delta) { + const double q = 1.0 - p; + const double one_minus_delta = 1.0 - delta; + double cur_term = std::pow(p, num_samples); // curTerm = posteriorProbability (k, k, p) + if (cur_term <= 1e-100) throw std::logic_error("out of range"); // sanity check for non-use of logarithms + double tot = cur_term; + unsigned long long m = num_samples; + while (tot < one_minus_delta) { + cur_term = (cur_term * q * (m)) / ((m + 1) - num_samples); + tot += cur_term; + m += 1; + } + return m; // no need to back up + } + + static unsigned long long special_n_prime_f(unsigned long long num_samples, double p, double delta) { + // Use a different algorithm if the following is true; this one will be too slow, or worse. + if ((num_samples / p) >= 500.0) throw std::invalid_argument("out of range"); //A super-small delta could also make it slow. + return special_n_prime_b(num_samples + 1, p, delta); + } + + // The following computes an approximation to the lower bound of a Frequentist + // confidence interval based on the tails of the Binomial distribution. + static double compute_approx_binomial_lower_bound(unsigned long long num_samples, double theta, unsigned num_std_devs) { + if (theta == 1) return num_samples; + if (num_samples == 0) return 0; + if (num_samples == 1) { + const double delta = delta_of_num_std_devs[num_std_devs]; + const double raw_lb = std::log(1 - delta) / std::log(1 - theta); + return std::floor(raw_lb); // round down + } + if (num_samples > 120) { + // plenty of samples, so gaussian approximation to binomial distribution isn't too bad + const double raw_lb = cont_classic_lb(num_samples, theta, num_std_devs); + return (raw_lb - 0.5); // fake round down + } + // at this point we know 2 <= num_samples <= 120 + if (theta > (1 - 1e-5)) { // empirically-determined threshold + return num_samples; + } + if (theta < (num_samples / 360.0)) { // empirically-determined threshold + // here we use the Gaussian approximation, but with a modified num_std_devs + const unsigned index = 3 * num_samples + (num_std_devs - 1); + const double raw_lb = cont_classic_lb(num_samples, theta, lb_equiv_table[index]); + return raw_lb - 0.5; // fake round down + } + // This is the most difficult range to approximate; we will compute an "exact" LB. + // We know that est <= 360, so specialNStar() shouldn't be ridiculously slow. + const double delta = delta_of_num_std_devs[num_std_devs]; + return special_n_star(num_samples, theta, delta); // no need to round + } + + // The following computes an approximation to the upper bound of a Frequentist + // confidence interval based on the tails of the Binomial distribution. + static double compute_approx_binomial_upper_bound(unsigned long long num_samples, double theta, unsigned num_std_devs) { + if (theta == 1) return num_samples; + if (num_samples == 0) { + const double delta = delta_of_num_std_devs[num_std_devs]; + const double raw_ub = std::log(delta) / std::log(1 - theta); + return std::ceil(raw_ub); // round up + } + if (num_samples > 120) { + // plenty of samples, so gaussian approximation to binomial distribution isn't too bad + const double raw_ub = cont_classic_ub(num_samples, theta, num_std_devs); + return (raw_ub + 0.5); // fake round up + } + // at this point we know 2 <= num_samples <= 120 + if (theta > (1 - 1e-5)) { // empirically-determined threshold + return num_samples + 1; + } + if (theta < (num_samples / 360.0)) { // empirically-determined threshold + // here we use the Gaussian approximation, but with a modified num_std_devs + const unsigned index = 3 * num_samples + (num_std_devs - 1); + const double raw_ub = cont_classic_ub(num_samples, theta, ub_equiv_table[index]); + return raw_ub + 0.5; // fake round up + } + // This is the most difficult range to approximate; we will compute an "exact" UB. + // We know that est <= 360, so specialNPrimeF() shouldn't be ridiculously slow. + const double delta = delta_of_num_std_devs[num_std_devs]; + return special_n_prime_f(num_samples, theta, delta); // no need to round + } + + static void check_theta(double theta) { + if (theta < 0 or theta > 1) { + throw std::invalid_argument("theta must be in [0, 1]"); + } + } + + static void check_num_std_devs(unsigned num_std_devs) { + if (num_std_devs < 1 or num_std_devs > 3) { + throw std::invalid_argument("num_std_devs must be 1, 2 or 3"); + } + } + +}; + +} /* namespace datasketches */ + +# endif diff --git a/theta/include/theta_sketch.hpp b/theta/include/theta_sketch.hpp index 5ec618d8..3eab4961 100644 --- a/theta/include/theta_sketch.hpp +++ b/theta/include/theta_sketch.hpp @@ -45,8 +45,8 @@ class theta_sketch_alloc { bool is_empty() const; double get_estimate() const; - double get_lower_bound(uint8_t num_std_dev) const; - double get_upper_bound(uint8_t num_std_dev) const; + double get_lower_bound(uint8_t num_std_devs) const; + double get_upper_bound(uint8_t num_std_devs) const; bool is_estimation_mode() const; double get_theta() const; uint64_t get_theta64() const; diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index 234585dc..b33ae5a0 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -13,6 +13,7 @@ #include "MurmurHash3.h" #include "serde.hpp" +#include "binomial_bounds.hpp" namespace datasketches { @@ -66,13 +67,15 @@ double theta_sketch_alloc::get_estimate() const { } template -double theta_sketch_alloc::get_lower_bound(uint8_t num_std_dev) const { - return 0; +double theta_sketch_alloc::get_lower_bound(uint8_t num_std_devs) const { + if (!is_estimation_mode()) return get_num_retained(); + return binomial_bounds::get_lower_bound(get_num_retained(), get_theta(), num_std_devs); } template -double theta_sketch_alloc::get_upper_bound(uint8_t num_std_dev) const { - return 0; +double theta_sketch_alloc::get_upper_bound(uint8_t num_std_devs) const { + if (!is_estimation_mode()) return get_num_retained(); + return binomial_bounds::get_upper_bound(get_num_retained(), get_theta(), num_std_devs); } template diff --git a/theta/test/theta_sketch_test.cpp b/theta/test/theta_sketch_test.cpp index 42e05afb..62f61921 100644 --- a/theta/test/theta_sketch_test.cpp +++ b/theta/test/theta_sketch_test.cpp @@ -37,27 +37,36 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_upper_bound(1)); compact_theta_sketch compact_sketch = update_sketch.compact(); CPPUNIT_ASSERT(compact_sketch.is_empty()); CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_upper_bound(1)); } void non_empty_no_retained_keys() { update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001).build(); update_sketch.update(1); + //update_sketch.to_stream(std::cerr); CPPUNIT_ASSERT_EQUAL(0U, update_sketch.get_num_retained()); CPPUNIT_ASSERT(!update_sketch.is_empty()); CPPUNIT_ASSERT(update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, update_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT(update_sketch.get_upper_bound(1) > 0); compact_theta_sketch compact_sketch = update_sketch.compact(); CPPUNIT_ASSERT_EQUAL(0U, compact_sketch.get_num_retained()); CPPUNIT_ASSERT(!compact_sketch.is_empty()); CPPUNIT_ASSERT(compact_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, compact_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT(compact_sketch.get_upper_bound(1) > 0); } void single_item() { @@ -67,12 +76,16 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_upper_bound(1)); compact_theta_sketch compact_sketch = update_sketch.compact(); CPPUNIT_ASSERT(!compact_sketch.is_empty()); CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_upper_bound(1)); } void resize_exact() { @@ -82,28 +95,37 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(!update_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, update_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(2000.0, update_sketch.get_upper_bound(1)); compact_theta_sketch compact_sketch = update_sketch.compact(); CPPUNIT_ASSERT(!compact_sketch.is_empty()); CPPUNIT_ASSERT(!compact_sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(1.0, compact_sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(2000.0, compact_sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(2000.0, compact_sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(2000.0, compact_sketch.get_upper_bound(1)); } void estimation() { update_theta_sketch update_sketch = update_theta_sketch::builder().set_resize_factor(update_theta_sketch::resize_factor::X1).build(); const int n = 8000; for (int i = 0; i < n; i++) update_sketch.update(i); + //update_sketch.to_stream(std::cerr); CPPUNIT_ASSERT(!update_sketch.is_empty()); CPPUNIT_ASSERT(update_sketch.is_estimation_mode()); CPPUNIT_ASSERT(update_sketch.get_theta() < 1.0); CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, update_sketch.get_estimate(), n * 0.01); + CPPUNIT_ASSERT(update_sketch.get_lower_bound(1) < n); + CPPUNIT_ASSERT(update_sketch.get_upper_bound(1) > n); compact_theta_sketch compact_sketch = update_sketch.compact(); CPPUNIT_ASSERT(!compact_sketch.is_empty()); CPPUNIT_ASSERT(compact_sketch.is_estimation_mode()); CPPUNIT_ASSERT(compact_sketch.get_theta() < 1.0); CPPUNIT_ASSERT_DOUBLES_EQUAL((double) n, compact_sketch.get_estimate(), n * 0.01); + CPPUNIT_ASSERT(compact_sketch.get_lower_bound(1) < n); + CPPUNIT_ASSERT(compact_sketch.get_upper_bound(1) > n); } void deserialize_update_empty_from_java_as_base() { @@ -116,6 +138,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(0U, sketchptr->get_num_retained()); CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta()); CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_upper_bound(1)); } void deserialize_update_empty_from_java_as_subclass() { @@ -128,6 +152,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained()); CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_upper_bound(1)); } void deserialize_update_estimation_from_java_as_base() { @@ -139,6 +165,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(sketchptr->is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(5324U, sketchptr->get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(10000.0, sketchptr->get_estimate(), 10000 * 0.01); + CPPUNIT_ASSERT(sketchptr->get_lower_bound(1) < 10000); + CPPUNIT_ASSERT(sketchptr->get_upper_bound(1) > 10000); } void deserialize_update_estimation_from_java_as_subclass() { @@ -150,6 +178,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT(sketch.is_estimation_mode()); CPPUNIT_ASSERT_EQUAL(5324U, sketch.get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(10000.0, sketch.get_estimate(), 10000 * 0.01); + CPPUNIT_ASSERT(sketch.get_lower_bound(1) < 10000); + CPPUNIT_ASSERT(sketch.get_upper_bound(1) > 10000); } void deserialize_compact_empty_from_java_as_base() { @@ -162,6 +192,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(0U, sketchptr->get_num_retained()); CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta()); CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(0.0, sketchptr->get_upper_bound(1)); } void deserialize_compact_empty_from_java_as_subclass() { @@ -174,6 +206,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(0U, sketch.get_num_retained()); CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(0.0, sketch.get_upper_bound(1)); } void deserialize_single_item_from_java_as_base() { @@ -186,6 +220,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1U, sketchptr->get_num_retained()); CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_theta()); CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_estimate()); + CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(1.0, sketchptr->get_upper_bound(1)); } void deserialize_single_item_from_java_as_subclass() { @@ -198,6 +234,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1U, sketch.get_num_retained()); CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_theta()); CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_estimate()); + CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(1.0, sketch.get_upper_bound(1)); } void deserialize_compact_estimation_from_java_as_base() { @@ -211,6 +249,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(4342U, sketchptr->get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.531700444213199, sketchptr->get_theta(), 1e-10); CPPUNIT_ASSERT_DOUBLES_EQUAL(8166.25234614053, sketchptr->get_estimate(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(7996.956955317471, sketchptr->get_lower_bound(2), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8339.090301078124, sketchptr->get_upper_bound(2), 1e-10); // the same construction process in Java must have produced exactly the same sketch update_theta_sketch update_sketch = update_theta_sketch::builder().build(); @@ -219,6 +259,12 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(update_sketch.get_num_retained(), sketchptr->get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_theta(), sketchptr->get_theta(), 1e-10); CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketchptr->get_estimate(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(1), sketchptr->get_lower_bound(1), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(1), sketchptr->get_upper_bound(1), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(2), sketchptr->get_lower_bound(2), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(2), sketchptr->get_upper_bound(2), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(3), sketchptr->get_lower_bound(3), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(3), sketchptr->get_upper_bound(3), 1e-10); compact_theta_sketch compact_sketch = update_sketch.compact(); // the sketches are ordered, so the iteration sequence must match exactly auto iter = sketchptr->begin(); @@ -238,6 +284,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(4342U, sketch.get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.531700444213199, sketch.get_theta(), 1e-10); CPPUNIT_ASSERT_DOUBLES_EQUAL(8166.25234614053, sketch.get_estimate(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(7996.956955317471, sketch.get_lower_bound(2), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(8339.090301078124, sketch.get_upper_bound(2), 1e-10); update_theta_sketch update_sketch = update_theta_sketch::builder().build(); const int n = 8192; @@ -245,6 +293,12 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(update_sketch.get_num_retained(), sketch.get_num_retained()); CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_theta(), sketch.get_theta(), 1e-10); CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_estimate(), sketch.get_estimate(), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(1), sketch.get_lower_bound(1), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(1), sketch.get_upper_bound(1), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(2), sketch.get_lower_bound(2), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(2), sketch.get_upper_bound(2), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_lower_bound(3), sketch.get_lower_bound(3), 1e-10); + CPPUNIT_ASSERT_DOUBLES_EQUAL(update_sketch.get_upper_bound(3), sketch.get_upper_bound(3), 1e-10); } void serialize_deserialize_stream_and_bytes_equivalency() { @@ -273,6 +327,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_theta(), deserialized_sketch_ptr2->get_theta()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_estimate(), deserialized_sketch_ptr2->get_estimate()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_lower_bound(1), deserialized_sketch_ptr2->get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_upper_bound(1), deserialized_sketch_ptr2->get_upper_bound(1)); // hash tables must be identical since they are restored from dumps, and iteration is deterministic auto iter = deserialized_sketch_ptr1->begin(); for (auto key: *deserialized_sketch_ptr2) { @@ -292,6 +348,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_lower_bound(1), deserialized_sketch2.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_upper_bound(1), deserialized_sketch2.get_upper_bound(1)); // hash tables must be identical since they are restored from dumps, and iteration is deterministic auto iter = deserialized_sketch1.begin(); for (auto key: deserialized_sketch2) { @@ -322,6 +380,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_num_retained(), deserialized_sketch_ptr2->get_num_retained()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_theta(), deserialized_sketch_ptr2->get_theta()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_estimate(), deserialized_sketch_ptr2->get_estimate()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_lower_bound(1), deserialized_sketch_ptr2->get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch_ptr1->get_upper_bound(1), deserialized_sketch_ptr2->get_upper_bound(1)); // the sketches are ordered, so the iteration sequence must match exactly auto iter = deserialized_sketch_ptr1->begin(); for (auto key: *deserialized_sketch_ptr2) { @@ -341,6 +401,8 @@ class theta_sketch_test: public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_num_retained(), deserialized_sketch2.get_num_retained()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_theta(), deserialized_sketch2.get_theta()); CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_estimate(), deserialized_sketch2.get_estimate()); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_lower_bound(1), deserialized_sketch2.get_lower_bound(1)); + CPPUNIT_ASSERT_EQUAL(deserialized_sketch1.get_upper_bound(1), deserialized_sketch2.get_upper_bound(1)); // the sketches are ordered, so the iteration sequence must match exactly auto iter = deserialized_sketch1.begin(); for (auto key: deserialized_sketch2) { From 6e3dbe747204682bd54dd045fc146eb8672b8af0 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 31 May 2019 16:12:11 -0700 Subject: [PATCH 20/22] disabled debug print --- theta/include/theta_sketch_impl.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/theta/include/theta_sketch_impl.hpp b/theta/include/theta_sketch_impl.hpp index b33ae5a0..3a45e1d6 100644 --- a/theta/include/theta_sketch_impl.hpp +++ b/theta/include/theta_sketch_impl.hpp @@ -653,8 +653,8 @@ bool update_theta_sketch_alloc::hash_search_or_insert(uint64_t hash, uint64_t } cur_probe = (cur_probe + stride) & mask; } while (cur_probe != loop_index); - std::cerr << "hash_search_or_insert: lg=" << (int)lg_size << ", hash=" << hash << std::endl; - std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; + //std::cerr << "hash_search_or_insert: lg=" << (int)lg_size << ", hash=" << hash << std::endl; + //std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; throw std::logic_error("key not found and no empty slots!"); } @@ -673,8 +673,8 @@ bool update_theta_sketch_alloc::hash_search(uint64_t hash, const uint64_t* ta } cur_probe = (cur_probe + stride) & mask; } while (cur_probe != loop_index); - std::cerr << "hash_search: lg=" << (int)lg_size << ", hash=" << hash << std::endl; - std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; + //std::cerr << "hash_search: lg=" << (int)lg_size << ", hash=" << hash << std::endl; + //std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; throw std::logic_error("key not found and search wrapped"); } From 49fcec131387455863f5c90146c593233f89ebd2 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 31 May 2019 16:12:42 -0700 Subject: [PATCH 21/22] moved static tables out of class --- theta/include/binomial_bounds.hpp | 510 +++++++++++++++--------------- 1 file changed, 255 insertions(+), 255 deletions(-) diff --git a/theta/include/binomial_bounds.hpp b/theta/include/binomial_bounds.hpp index dc0d0ad2..58de181e 100644 --- a/theta/include/binomial_bounds.hpp +++ b/theta/include/binomial_bounds.hpp @@ -22,6 +22,261 @@ namespace datasketches { +static constexpr double delta_of_num_std_devs[] = { + 0.5000000000000000000, // not actually using this value + 0.1586553191586026479, + 0.0227502618904135701, + 0.0013498126861731796 +}; + +static constexpr double lb_equiv_table[] = { + 1.0, 2.0, 3.0, // fake values for k = 0 + 0.78733703534118149, 3.14426768537558132, 13.56789685109913535, // k = 1 + 0.94091379266077979, 2.64699271711145911, 6.29302733018320737, // k = 2 + 0.96869128474958188, 2.46531676590527127, 4.97375283467403051, // k = 3 + 0.97933572521046131, 2.37418810664669877, 4.44899975481712318, // k = 4 + 0.98479165917274258, 2.31863116255024693, 4.16712379778553554, // k = 5 + 0.98806033915698777, 2.28075536565225434, 3.99010556144099837, // k = 6 + 0.99021896790580399, 2.25302005857281529, 3.86784477136922078, // k = 7 + 0.99174267079089873, 2.23168103978522936, 3.77784896945266269, // k = 8 + 0.99287147837287648, 2.21465899260871879, 3.70851932988722410, // k = 9 + 0.99373900046805375, 2.20070155496262032, 3.65326029076638292, // k = 10 + 0.99442519013851438, 2.18900651202670815, 3.60803817612955413, // k = 11 + 0.99498066823221620, 2.17903457780744247, 3.57024330407946877, // k = 12 + 0.99543899410224412, 2.17040883161922693, 3.53810982030634591, // k = 13 + 0.99582322541263579, 2.16285726913676513, 3.51039837124298515, // k = 14 + 0.99614973311747690, 2.15617827879603396, 3.48621230377099778, // k = 15 + 0.99643042892560629, 2.15021897666090922, 3.46488605693562590, // k = 16 + 0.99667418783778317, 2.14486114872480016, 3.44591466064832730, // k = 17 + 0.99688774875812669, 2.14001181420209718, 3.42890765690452781, // k = 18 + 0.99707632299691795, 2.13559675336844634, 3.41355809420343803, // k = 19 + 0.99724399084971083, 2.13155592217421486, 3.39962113251016262, // k = 20 + 0.99739400151915447, 2.12784018863251845, 3.38689892877548004, // k = 21 + 0.99752896842633731, 2.12440890875851096, 3.37522975271599535, // k = 22 + 0.99765101725122918, 2.12122815311133195, 3.36448003577621080, // k = 23 + 0.99776189496810730, 2.11826934724291505, 3.35453840911279144, // k = 24 + 0.99786304821586214, 2.11550823850916458, 3.34531123809287578, // k = 25 + 0.99795568665180667, 2.11292409529477254, 3.33671916527694634, // k = 26 + 0.99804083063483517, 2.11049908609763293, 3.32869446834217797, // k = 27 + 0.99811933910984862, 2.10821776918189130, 3.32117898316676019, // k = 28 + 0.99819195457286014, 2.10606671027090897, 3.31412243534683171, // k = 29 + 0.99825930555178388, 2.10403415237001923, 3.30748113008135647, // k = 30 + 0.99832193858154028, 2.10210975877822648, 3.30121691946897045, // k = 31 + 0.99838032666573895, 2.10028440670842542, 3.29529629751144171, // k = 32 + 0.99843488390555990, 2.09855000145353188, 3.28968974413223236, // k = 33 + 0.99848596721417948, 2.09689934193824001, 3.28437111460505093, // k = 34 + 0.99853390005924325, 2.09532599155502908, 3.27931717312372939, // k = 35 + 0.99857895741078551, 2.09382418262592296, 3.27450718840060517, // k = 36 + 0.99862138880970974, 2.09238872751677718, 3.26992261182860489, // k = 37 + 0.99866141580770318, 2.09101494715108061, 3.26554677962434425, // k = 38 + 0.99869923565267982, 2.08969860402822860, 3.26136468165239535, // k = 39 + 0.99873502010169091, 2.08843585627218431, 3.25736275677081721, // k = 40 + 0.99876893292508839, 2.08722321436752623, 3.25352872241415980, // k = 41 + 0.99880111078502409, 2.08605749165553789, 3.24985141664350863, // k = 42 + 0.99883168573342118, 2.08493577529222307, 3.24632068399498053, // k = 43 + 0.99886077231613513, 2.08385540129560809, 3.24292724848112357, // k = 44 + 0.99888847451828155, 2.08281392374021834, 3.23966263299664092, // k = 45 + 0.99891488795844907, 2.08180908991394631, 3.23651906111521726, // k = 46 + 0.99894010085196783, 2.08083882998420222, 3.23348939240611344, // k = 47 + 0.99896419358239541, 2.07990122528650545, 3.23056705515594444, // k = 48 + 0.99898723510594323, 2.07899450946285924, 3.22774598963252402, // k = 49 + 0.99900929266780736, 2.07811704477046533, 3.22502059972006805, // k = 50 + 0.99903043086155208, 2.07726730587160091, 3.22238570890294795, // k = 51 + 0.99905070073845081, 2.07644388314946582, 3.21983651940365689, // k = 52 + 0.99907015770423868, 2.07564546080757850, 3.21736857351049821, // k = 53 + 0.99908884779227947, 2.07487081196367740, 3.21497773796417619, // k = 54 + 0.99910681586905525, 2.07411879634256024, 3.21266015316183484, // k = 55 + 0.99912410177549305, 2.07338834403498140, 3.21041222805715165, // k = 56 + 0.99914074347179849, 2.07267845454973099, 3.20823061166797174, // k = 57 + 0.99915677607464204, 2.07198819052374006, 3.20611216970604573, // k = 58 + 0.99917223149395795, 2.07131667846186929, 3.20405396962596001, // k = 59 + 0.99918714153457699, 2.07066309019154460, 3.20205326110445299, // k = 60 + 0.99920153247185794, 2.07002665203046377, 3.20010746990493544, // k = 61 + 0.99921543193525508, 2.06940663431663552, 3.19821417453343315, // k = 62 + 0.99922886570365677, 2.06880235245998279, 3.19637109973109546, // k = 63 + 0.99924185357357942, 2.06821315729285971, 3.19457610621114441, // k = 64 + 0.99925441845175555, 2.06763843812092318, 3.19282717869864996, // k = 65 + 0.99926658263325407, 2.06707761824370095, 3.19112241228646099, // k = 66 + 0.99927836173816331, 2.06653015295219689, 3.18946001739936946, // k = 67 + 0.99928977431994781, 2.06599552505539918, 3.18783829446098821, // k = 68 + 0.99930083753795884, 2.06547324585920933, 3.18625564538041317, // k = 69 + 0.99931156864562354, 2.06496285191821016, 3.18471055124089730, // k = 70 + 0.99932197985521043, 2.06446390392778767, 3.18320157510865442, // k = 71 + 0.99933208559809827, 2.06397598606787369, 3.18172735837393361, // k = 72 + 0.99934190032416836, 2.06349869971447220, 3.18028661102792398, // k = 73 + 0.99935143390791836, 2.06303166975550312, 3.17887810481605015, // k = 74 + 0.99936070171270330, 2.06257453607466346, 3.17750067581857820, // k = 75 + 0.99936971103502970, 2.06212696042919674, 3.17615321728274580, // k = 76 + 0.99937847392385493, 2.06168861430600714, 3.17483467831510779, // k = 77 + 0.99938700168914352, 2.06125918927764928, 3.17354405480557489, // k = 78 + 0.99939530099953799, 2.06083838987589729, 3.17228039269048168, // k = 79 + 0.99940338278830154, 2.06042593411496000, 3.17104278166036124, // k = 80 + 0.99941125463777780, 2.06002155276328835, 3.16983035274597569, // k = 81 + 0.99941892470027938, 2.05962498741951094, 3.16864227952240185, // k = 82 + 0.99942640059737187, 2.05923599161263837, 3.16747776846497686, // k = 83 + 0.99943368842187397, 2.05885433061945378, 3.16633606416374391, // k = 84 + 0.99944079790603269, 2.05847977868873500, 3.16521644518826406, // k = 85 + 0.99944773295734990, 2.05811212058944193, 3.16411821883858124, // k = 86 + 0.99945450059186669, 2.05775114781260982, 3.16304072400711789, // k = 87 + 0.99946110646314423, 2.05739666442039493, 3.16198332650733960, // k = 88 + 0.99946755770463369, 2.05704847678819647, 3.16094541781455973, // k = 89 + 0.99947385746861528, 2.05670640500335367, 3.15992641851471490, // k = 90 + 0.99948001256305474, 2.05637027420314666, 3.15892576988736096, // k = 91 + 0.99948602689656241, 2.05603991286400856, 3.15794293484717059, // k = 92 + 0.99949190674294641, 2.05571516158917689, 3.15697740043813724, // k = 93 + 0.99949765436329585, 2.05539586490317561, 3.15602867309343083, // k = 94 + 0.99950327557880314, 2.05508187237845164, 3.15509627710042651, // k = 95 + 0.99950877461972709, 2.05477304104951486, 3.15417975753007340, // k = 96 + 0.99951415481862682, 2.05446923022574879, 3.15327867462917766, // k = 97 + 0.99951942042375208, 2.05417030908833453, 3.15239260700215596, // k = 98 + 0.99952457390890004, 2.05387614661762541, 3.15152114915238712, // k = 99 + 0.99952962005008317, 2.05358662050909402, 3.15066390921020911, // k = 100 + 0.99953456216121594, 2.05330161104427589, 3.14982051097524618, // k = 101 + 0.99953940176368405, 2.05302100378725072, 3.14899059183684926, // k = 102 + 0.99954414373920031, 2.05274468493067275, 3.14817379948561893, // k = 103 + 0.99954879047621148, 2.05247255013657082, 3.14736979964868624, // k = 104 + 0.99955334485656522, 2.05220449388099269, 3.14657826610371671, // k = 105 + 0.99955780993869325, 2.05194041831310869, 3.14579888316276879, // k = 106 + 0.99956218652590678, 2.05168022402710903, 3.14503134811607765, // k = 107 + 0.99956647932785359, 2.05142381889103831, 3.14427536967733090, // k = 108 + 0.99957069025060719, 2.05117111251445294, 3.14353066260227365, // k = 109 + 0.99957482032178291, 2.05092201793428330, 3.14279695558593630, // k = 110 + 0.99957887261450651, 2.05067645094720774, 3.14207398336887422, // k = 111 + 0.99958284988383639, 2.05043432833224415, 3.14136149076028914, // k = 112 + 0.99958675435604505, 2.05019557189746138, 3.14065923143530767, // k = 113 + 0.99959058650074439, 2.04996010556124020, 3.13996696426707445, // k = 114 + 0.99959434898201494, 2.04972785368377686, 3.13928445867830419, // k = 115 + 0.99959804437042976, 2.04949874512311681, 3.13861149103462367, // k = 116 + 0.99960167394553423, 2.04927271043337100, 3.13794784369528656, // k = 117 + 0.99960523957651048, 2.04904968140490951, 3.13729330661277572, // k = 118 + 0.99960874253329735, 2.04882959397491504, 3.13664767767019725, // k = 119 + 0.99961218434327748, 2.04861238220240693, 3.13601075688413289 // k = 120 +}; + +static constexpr double ub_equiv_table[] = { + 1.0, 2.0, 3.0, // fake values for k = 0 + 0.99067760836669549, 1.75460517119302040, 2.48055626001627161, // k = 1 + 0.99270518097577565, 1.78855957509907171, 2.53863835259832626, // k = 2 + 0.99402032633599902, 1.81047286499563143, 2.57811676180597260, // k = 3 + 0.99492607629539975, 1.82625928017762362, 2.60759550546498531, // k = 4 + 0.99558653966013821, 1.83839160339161367, 2.63086812358551470, // k = 5 + 0.99608981951632813, 1.84812399034444752, 2.64993712523727254, // k = 6 + 0.99648648035983456, 1.85617372053235385, 2.66598485907860550, // k = 7 + 0.99680750790483330, 1.86298655802610824, 2.67976541374471822, // k = 8 + 0.99707292880049181, 1.86885682585270274, 2.69178781407745760, // k = 9 + 0.99729614928489241, 1.87398826101983218, 2.70241106542158604, // k = 10 + 0.99748667952445658, 1.87852708449801753, 2.71189717290596377, // k = 11 + 0.99765127712748836, 1.88258159501103250, 2.72044290303773550, // k = 12 + 0.99779498340305395, 1.88623391878036273, 2.72819957382063194, // k = 13 + 0.99792160418357412, 1.88954778748873764, 2.73528576807902368, // k = 14 + 0.99803398604944960, 1.89257337682371940, 2.74179612106766513, // k = 15 + 0.99813449883217231, 1.89535099316557876, 2.74780718300419835, // k = 16 + 0.99822494122659577, 1.89791339232732525, 2.75338173141955167, // k = 17 + 0.99830679915913834, 1.90028752122407241, 2.75857186416826039, // k = 18 + 0.99838117410831728, 1.90249575897183831, 2.76342117562634826, // k = 19 + 0.99844913407071090, 1.90455689090418900, 2.76796659454200267, // k = 20 + 0.99851147736424650, 1.90648682834171268, 2.77223944710058845, // k = 21 + 0.99856879856019987, 1.90829917277082473, 2.77626682032629901, // k = 22 + 0.99862183849734265, 1.91000561415842185, 2.78007199816156003, // k = 23 + 0.99867096266018507, 1.91161621560812023, 2.78367524259661536, // k = 24 + 0.99871656986212543, 1.91313978579765376, 2.78709435016625662, // k = 25 + 0.99875907577771272, 1.91458400425526065, 2.79034488416175463, // k = 26 + 0.99879885565047744, 1.91595563175945927, 2.79344064132371273, // k = 27 + 0.99883610756373287, 1.91726064301425936, 2.79639384757751941, // k = 28 + 0.99887095169674467, 1.91850441099725799, 2.79921543574803877, // k = 29 + 0.99890379414739527, 1.91969155477030995, 2.80191513182441554, // k = 30 + 0.99893466279047516, 1.92082633358913313, 2.80450167352080371, // k = 31 + 0.99896392088177777, 1.92191254955568525, 2.80698295731653502, // k = 32 + 0.99899147889385631, 1.92295362479495680, 2.80936614404217266, // k = 33 + 0.99901764688726757, 1.92395267400968351, 2.81165765979318394, // k = 34 + 0.99904238606342233, 1.92491244978191389, 2.81386337393604435, // k = 35 + 0.99906590152386343, 1.92583552644848055, 2.81598868034527072, // k = 36 + 0.99908829040739988, 1.92672418013918900, 2.81803841726804194, // k = 37 + 0.99910959420023460, 1.92758051694144683, 2.82001709302821268, // k = 38 + 0.99912996403594434, 1.92840654943159961, 2.82192875763732332, // k = 39 + 0.99914930224576892, 1.92920397044028391, 2.82377730628954282, // k = 40 + 0.99916781270195543, 1.92997447498220254, 2.82556612075063640, // k = 41 + 0.99918553179077207, 1.93071949211818605, 2.82729843191989971, // k = 42 + 0.99920250730914972, 1.93144048613876862, 2.82897728689417249, // k = 43 + 0.99921873345181211, 1.93213870990595638, 2.83060537017752267, // k = 44 + 0.99923435180002684, 1.93281536508689555, 2.83218527795750674, // k = 45 + 0.99924930425362390, 1.93347145882316340, 2.83371938965598247, // k = 46 + 0.99926370394567243, 1.93410820221384938, 2.83520990872793277, // k = 47 + 0.99927750755296074, 1.93472643138986200, 2.83665891945119597, // k = 48 + 0.99929082941537217, 1.93532697329771963, 2.83806833931606661, // k = 49 + 0.99930366295501472, 1.93591074716263734, 2.83943997143404658, // k = 50 + 0.99931598804721489, 1.93647857274021362, 2.84077557836653227, // k = 51 + 0.99932789059798210, 1.93703110239354714, 2.84207662106302905, // k = 52 + 0.99933946180485123, 1.93756904936378760, 2.84334468086129277, // k = 53 + 0.99935053819703512, 1.93809302131219852, 2.84458116874117195, // k = 54 + 0.99936126637970801, 1.93860365411038060, 2.84578731838604426, // k = 55 + 0.99937166229284458, 1.93910149816429112, 2.84696443486512862, // k = 56 + 0.99938169190727422, 1.93958709548454067, 2.84811369085281285, // k = 57 + 0.99939136927613959, 1.94006085573701625, 2.84923617230361970, // k = 58 + 0.99940074328745254, 1.94052339623206649, 2.85033291216254270, // k = 59 + 0.99940993070470086, 1.94097508636855309, 2.85140492437699322, // k = 60 + 0.99941868577388959, 1.94141633372043998, 2.85245314430358121, // k = 61 + 0.99942734443487780, 1.94184757038001976, 2.85347839582286156, // k = 62 + 0.99943556385736088, 1.94226915100517772, 2.85448160365493209, // k = 63 + 0.99944374522542034, 1.94268143723749631, 2.85546346373061510, // k = 64 + 0.99945159955424856, 1.94308482059116727, 2.85642486111805738, // k = 65 + 0.99945915301904620, 1.94347956957849988, 2.85736639994965458, // k = 66 + 0.99946660663832176, 1.94386600964031686, 2.85828887832701639, // k = 67 + 0.99947383703224091, 1.94424436597356021, 2.85919278275500233, // k = 68 + 0.99948075442870277, 1.94461502153473020, 2.86007887186090670, // k = 69 + 0.99948766082269458, 1.94497821937304138, 2.86094774077355396, // k = 70 + 0.99949422748713346, 1.94533411296001191, 2.86179981848076181, // k = 71 + 0.99950070756119658, 1.94568300035135167, 2.86263579405672886, // k = 72 + 0.99950704321753392, 1.94602523449961495, 2.86345610449197352, // k = 73 + 0.99951320334216121, 1.94636083782822311, 2.86426125541271404, // k = 74 + 0.99951920293474927, 1.94669011080745236, 2.86505169255406145, // k = 75 + 0.99952501670378524, 1.94701327348536779, 2.86582788270862920, // k = 76 + 0.99953071209267819, 1.94733044372333097, 2.86659027602854621, // k = 77 + 0.99953632734991515, 1.94764180764266825, 2.86733927778843167, // k = 78 + 0.99954171164873173, 1.94794766430732125, 2.86807526143834934, // k = 79 + 0.99954699274462655, 1.94824807472994621, 2.86879864789403882, // k = 80 + 0.99955216611081710, 1.94854317889829076, 2.86950970901679625, // k = 81 + 0.99955730019613043, 1.94883320227168610, 2.87020887436986527, // k = 82 + 0.99956213770650493, 1.94911826561721568, 2.87089648477021342, // k = 83 + 0.99956704264963037, 1.94939848545763539, 2.87157281693902178, // k = 84 + 0.99957166306481327, 1.94967401618316671, 2.87223821840905202, // k = 85 + 0.99957632713136491, 1.94994497791333288, 2.87289293193450135, // k = 86 + 0.99958087233392234, 1.95021155752212394, 2.87353731228213860, // k = 87 + 0.99958532555996271, 1.95047376805584349, 2.87417154907075201, // k = 88 + 0.99958956246481989, 1.95073180380688882, 2.87479599765507032, // k = 89 + 0.99959389351869277, 1.95098572880579013, 2.87541081987382086, // k = 90 + 0.99959807862052230, 1.95123574036898617, 2.87601637401948551, // k = 91 + 0.99960214057801977, 1.95148186921983324, 2.87661283691068093, // k = 92 + 0.99960607527256684, 1.95172415829728152, 2.87720042968334155, // k = 93 + 0.99960996433179616, 1.95196280898670693, 2.87777936649376898, // k = 94 + 0.99961379137860717, 1.95219787713926962, 2.87834989933620022, // k = 95 + 0.99961756088146103, 1.95242944583677058, 2.87891216133900230, // k = 96 + 0.99962125605327401, 1.95265762420910960, 2.87946647367488140, // k = 97 + 0.99962486179100551, 1.95288245314810638, 2.88001290210658567, // k = 98 + 0.99962843240297161, 1.95310404286672679, 2.88055166523392359, // k = 99 + 0.99963187276145504, 1.95332251980147475, 2.88108300006589957, // k = 100 + 0.99963525453173929, 1.95353785898848287, 2.88160703591438505, // k = 101 + 0.99963855412988778, 1.95375019354571577, 2.88212393551896184, // k = 102 + 0.99964190254169694, 1.95395953472205974, 2.88263389761985422, // k = 103 + 0.99964506565942202, 1.95416607430155409, 2.88313700661564098, // k = 104 + 0.99964834424233118, 1.95436972855640079, 2.88363350163803034, // k = 105 + 0.99965136548857458, 1.95457068540693513, 2.88412349413960101, // k = 106 + 0.99965436594726498, 1.95476896383092935, 2.88460710620208260, // k = 107 + 0.99965736463468602, 1.95496457504532373, 2.88508450078833789, // k = 108 + 0.99966034130443404, 1.95515761150707590, 2.88555580586194083, // k = 109 + 0.99966326130828520, 1.95534810382198998, 2.88602118761679094, // k = 110 + 0.99966601446035952, 1.95553622237747504, 2.88648066384146773, // k = 111 + 0.99966887679593697, 1.95572186728168163, 2.88693444915907094, // k = 112 + 0.99967161286551232, 1.95590523410490391, 2.88738271495714116, // k = 113 + 0.99967435412270333, 1.95608626483223702, 2.88782540459769166, // k = 114 + 0.99967701261934394, 1.95626497627117146, 2.88826277189363623, // k = 115 + 0.99967963265157778, 1.95644153684824573, 2.88869486674335008, // k = 116 + 0.99968216317182623, 1.95661589936000269, 2.88912184353694101, // k = 117 + 0.99968479674396349, 1.95678821614791332, 2.88954376359643561, // k = 118 + 0.99968729031337489, 1.95695842061650183, 2.88996069422501023, // k = 119 + 0.99968963358631413, 1.95712651709766305, 2.89037285320668502 // k = 120 +}; + class binomial_bounds { public: @@ -42,261 +297,6 @@ class binomial_bounds { } private: - static constexpr double delta_of_num_std_devs[] = { - 0.5000000000000000000, // not actually using this value - 0.1586553191586026479, - 0.0227502618904135701, - 0.0013498126861731796 - }; - - static constexpr double lb_equiv_table[] = { - 1.0, 2.0, 3.0, // fake values for k = 0 - 0.78733703534118149, 3.14426768537558132, 13.56789685109913535, // k = 1 - 0.94091379266077979, 2.64699271711145911, 6.29302733018320737, // k = 2 - 0.96869128474958188, 2.46531676590527127, 4.97375283467403051, // k = 3 - 0.97933572521046131, 2.37418810664669877, 4.44899975481712318, // k = 4 - 0.98479165917274258, 2.31863116255024693, 4.16712379778553554, // k = 5 - 0.98806033915698777, 2.28075536565225434, 3.99010556144099837, // k = 6 - 0.99021896790580399, 2.25302005857281529, 3.86784477136922078, // k = 7 - 0.99174267079089873, 2.23168103978522936, 3.77784896945266269, // k = 8 - 0.99287147837287648, 2.21465899260871879, 3.70851932988722410, // k = 9 - 0.99373900046805375, 2.20070155496262032, 3.65326029076638292, // k = 10 - 0.99442519013851438, 2.18900651202670815, 3.60803817612955413, // k = 11 - 0.99498066823221620, 2.17903457780744247, 3.57024330407946877, // k = 12 - 0.99543899410224412, 2.17040883161922693, 3.53810982030634591, // k = 13 - 0.99582322541263579, 2.16285726913676513, 3.51039837124298515, // k = 14 - 0.99614973311747690, 2.15617827879603396, 3.48621230377099778, // k = 15 - 0.99643042892560629, 2.15021897666090922, 3.46488605693562590, // k = 16 - 0.99667418783778317, 2.14486114872480016, 3.44591466064832730, // k = 17 - 0.99688774875812669, 2.14001181420209718, 3.42890765690452781, // k = 18 - 0.99707632299691795, 2.13559675336844634, 3.41355809420343803, // k = 19 - 0.99724399084971083, 2.13155592217421486, 3.39962113251016262, // k = 20 - 0.99739400151915447, 2.12784018863251845, 3.38689892877548004, // k = 21 - 0.99752896842633731, 2.12440890875851096, 3.37522975271599535, // k = 22 - 0.99765101725122918, 2.12122815311133195, 3.36448003577621080, // k = 23 - 0.99776189496810730, 2.11826934724291505, 3.35453840911279144, // k = 24 - 0.99786304821586214, 2.11550823850916458, 3.34531123809287578, // k = 25 - 0.99795568665180667, 2.11292409529477254, 3.33671916527694634, // k = 26 - 0.99804083063483517, 2.11049908609763293, 3.32869446834217797, // k = 27 - 0.99811933910984862, 2.10821776918189130, 3.32117898316676019, // k = 28 - 0.99819195457286014, 2.10606671027090897, 3.31412243534683171, // k = 29 - 0.99825930555178388, 2.10403415237001923, 3.30748113008135647, // k = 30 - 0.99832193858154028, 2.10210975877822648, 3.30121691946897045, // k = 31 - 0.99838032666573895, 2.10028440670842542, 3.29529629751144171, // k = 32 - 0.99843488390555990, 2.09855000145353188, 3.28968974413223236, // k = 33 - 0.99848596721417948, 2.09689934193824001, 3.28437111460505093, // k = 34 - 0.99853390005924325, 2.09532599155502908, 3.27931717312372939, // k = 35 - 0.99857895741078551, 2.09382418262592296, 3.27450718840060517, // k = 36 - 0.99862138880970974, 2.09238872751677718, 3.26992261182860489, // k = 37 - 0.99866141580770318, 2.09101494715108061, 3.26554677962434425, // k = 38 - 0.99869923565267982, 2.08969860402822860, 3.26136468165239535, // k = 39 - 0.99873502010169091, 2.08843585627218431, 3.25736275677081721, // k = 40 - 0.99876893292508839, 2.08722321436752623, 3.25352872241415980, // k = 41 - 0.99880111078502409, 2.08605749165553789, 3.24985141664350863, // k = 42 - 0.99883168573342118, 2.08493577529222307, 3.24632068399498053, // k = 43 - 0.99886077231613513, 2.08385540129560809, 3.24292724848112357, // k = 44 - 0.99888847451828155, 2.08281392374021834, 3.23966263299664092, // k = 45 - 0.99891488795844907, 2.08180908991394631, 3.23651906111521726, // k = 46 - 0.99894010085196783, 2.08083882998420222, 3.23348939240611344, // k = 47 - 0.99896419358239541, 2.07990122528650545, 3.23056705515594444, // k = 48 - 0.99898723510594323, 2.07899450946285924, 3.22774598963252402, // k = 49 - 0.99900929266780736, 2.07811704477046533, 3.22502059972006805, // k = 50 - 0.99903043086155208, 2.07726730587160091, 3.22238570890294795, // k = 51 - 0.99905070073845081, 2.07644388314946582, 3.21983651940365689, // k = 52 - 0.99907015770423868, 2.07564546080757850, 3.21736857351049821, // k = 53 - 0.99908884779227947, 2.07487081196367740, 3.21497773796417619, // k = 54 - 0.99910681586905525, 2.07411879634256024, 3.21266015316183484, // k = 55 - 0.99912410177549305, 2.07338834403498140, 3.21041222805715165, // k = 56 - 0.99914074347179849, 2.07267845454973099, 3.20823061166797174, // k = 57 - 0.99915677607464204, 2.07198819052374006, 3.20611216970604573, // k = 58 - 0.99917223149395795, 2.07131667846186929, 3.20405396962596001, // k = 59 - 0.99918714153457699, 2.07066309019154460, 3.20205326110445299, // k = 60 - 0.99920153247185794, 2.07002665203046377, 3.20010746990493544, // k = 61 - 0.99921543193525508, 2.06940663431663552, 3.19821417453343315, // k = 62 - 0.99922886570365677, 2.06880235245998279, 3.19637109973109546, // k = 63 - 0.99924185357357942, 2.06821315729285971, 3.19457610621114441, // k = 64 - 0.99925441845175555, 2.06763843812092318, 3.19282717869864996, // k = 65 - 0.99926658263325407, 2.06707761824370095, 3.19112241228646099, // k = 66 - 0.99927836173816331, 2.06653015295219689, 3.18946001739936946, // k = 67 - 0.99928977431994781, 2.06599552505539918, 3.18783829446098821, // k = 68 - 0.99930083753795884, 2.06547324585920933, 3.18625564538041317, // k = 69 - 0.99931156864562354, 2.06496285191821016, 3.18471055124089730, // k = 70 - 0.99932197985521043, 2.06446390392778767, 3.18320157510865442, // k = 71 - 0.99933208559809827, 2.06397598606787369, 3.18172735837393361, // k = 72 - 0.99934190032416836, 2.06349869971447220, 3.18028661102792398, // k = 73 - 0.99935143390791836, 2.06303166975550312, 3.17887810481605015, // k = 74 - 0.99936070171270330, 2.06257453607466346, 3.17750067581857820, // k = 75 - 0.99936971103502970, 2.06212696042919674, 3.17615321728274580, // k = 76 - 0.99937847392385493, 2.06168861430600714, 3.17483467831510779, // k = 77 - 0.99938700168914352, 2.06125918927764928, 3.17354405480557489, // k = 78 - 0.99939530099953799, 2.06083838987589729, 3.17228039269048168, // k = 79 - 0.99940338278830154, 2.06042593411496000, 3.17104278166036124, // k = 80 - 0.99941125463777780, 2.06002155276328835, 3.16983035274597569, // k = 81 - 0.99941892470027938, 2.05962498741951094, 3.16864227952240185, // k = 82 - 0.99942640059737187, 2.05923599161263837, 3.16747776846497686, // k = 83 - 0.99943368842187397, 2.05885433061945378, 3.16633606416374391, // k = 84 - 0.99944079790603269, 2.05847977868873500, 3.16521644518826406, // k = 85 - 0.99944773295734990, 2.05811212058944193, 3.16411821883858124, // k = 86 - 0.99945450059186669, 2.05775114781260982, 3.16304072400711789, // k = 87 - 0.99946110646314423, 2.05739666442039493, 3.16198332650733960, // k = 88 - 0.99946755770463369, 2.05704847678819647, 3.16094541781455973, // k = 89 - 0.99947385746861528, 2.05670640500335367, 3.15992641851471490, // k = 90 - 0.99948001256305474, 2.05637027420314666, 3.15892576988736096, // k = 91 - 0.99948602689656241, 2.05603991286400856, 3.15794293484717059, // k = 92 - 0.99949190674294641, 2.05571516158917689, 3.15697740043813724, // k = 93 - 0.99949765436329585, 2.05539586490317561, 3.15602867309343083, // k = 94 - 0.99950327557880314, 2.05508187237845164, 3.15509627710042651, // k = 95 - 0.99950877461972709, 2.05477304104951486, 3.15417975753007340, // k = 96 - 0.99951415481862682, 2.05446923022574879, 3.15327867462917766, // k = 97 - 0.99951942042375208, 2.05417030908833453, 3.15239260700215596, // k = 98 - 0.99952457390890004, 2.05387614661762541, 3.15152114915238712, // k = 99 - 0.99952962005008317, 2.05358662050909402, 3.15066390921020911, // k = 100 - 0.99953456216121594, 2.05330161104427589, 3.14982051097524618, // k = 101 - 0.99953940176368405, 2.05302100378725072, 3.14899059183684926, // k = 102 - 0.99954414373920031, 2.05274468493067275, 3.14817379948561893, // k = 103 - 0.99954879047621148, 2.05247255013657082, 3.14736979964868624, // k = 104 - 0.99955334485656522, 2.05220449388099269, 3.14657826610371671, // k = 105 - 0.99955780993869325, 2.05194041831310869, 3.14579888316276879, // k = 106 - 0.99956218652590678, 2.05168022402710903, 3.14503134811607765, // k = 107 - 0.99956647932785359, 2.05142381889103831, 3.14427536967733090, // k = 108 - 0.99957069025060719, 2.05117111251445294, 3.14353066260227365, // k = 109 - 0.99957482032178291, 2.05092201793428330, 3.14279695558593630, // k = 110 - 0.99957887261450651, 2.05067645094720774, 3.14207398336887422, // k = 111 - 0.99958284988383639, 2.05043432833224415, 3.14136149076028914, // k = 112 - 0.99958675435604505, 2.05019557189746138, 3.14065923143530767, // k = 113 - 0.99959058650074439, 2.04996010556124020, 3.13996696426707445, // k = 114 - 0.99959434898201494, 2.04972785368377686, 3.13928445867830419, // k = 115 - 0.99959804437042976, 2.04949874512311681, 3.13861149103462367, // k = 116 - 0.99960167394553423, 2.04927271043337100, 3.13794784369528656, // k = 117 - 0.99960523957651048, 2.04904968140490951, 3.13729330661277572, // k = 118 - 0.99960874253329735, 2.04882959397491504, 3.13664767767019725, // k = 119 - 0.99961218434327748, 2.04861238220240693, 3.13601075688413289 // k = 120 - }; - - static constexpr double ub_equiv_table[] = { - 1.0, 2.0, 3.0, // fake values for k = 0 - 0.99067760836669549, 1.75460517119302040, 2.48055626001627161, // k = 1 - 0.99270518097577565, 1.78855957509907171, 2.53863835259832626, // k = 2 - 0.99402032633599902, 1.81047286499563143, 2.57811676180597260, // k = 3 - 0.99492607629539975, 1.82625928017762362, 2.60759550546498531, // k = 4 - 0.99558653966013821, 1.83839160339161367, 2.63086812358551470, // k = 5 - 0.99608981951632813, 1.84812399034444752, 2.64993712523727254, // k = 6 - 0.99648648035983456, 1.85617372053235385, 2.66598485907860550, // k = 7 - 0.99680750790483330, 1.86298655802610824, 2.67976541374471822, // k = 8 - 0.99707292880049181, 1.86885682585270274, 2.69178781407745760, // k = 9 - 0.99729614928489241, 1.87398826101983218, 2.70241106542158604, // k = 10 - 0.99748667952445658, 1.87852708449801753, 2.71189717290596377, // k = 11 - 0.99765127712748836, 1.88258159501103250, 2.72044290303773550, // k = 12 - 0.99779498340305395, 1.88623391878036273, 2.72819957382063194, // k = 13 - 0.99792160418357412, 1.88954778748873764, 2.73528576807902368, // k = 14 - 0.99803398604944960, 1.89257337682371940, 2.74179612106766513, // k = 15 - 0.99813449883217231, 1.89535099316557876, 2.74780718300419835, // k = 16 - 0.99822494122659577, 1.89791339232732525, 2.75338173141955167, // k = 17 - 0.99830679915913834, 1.90028752122407241, 2.75857186416826039, // k = 18 - 0.99838117410831728, 1.90249575897183831, 2.76342117562634826, // k = 19 - 0.99844913407071090, 1.90455689090418900, 2.76796659454200267, // k = 20 - 0.99851147736424650, 1.90648682834171268, 2.77223944710058845, // k = 21 - 0.99856879856019987, 1.90829917277082473, 2.77626682032629901, // k = 22 - 0.99862183849734265, 1.91000561415842185, 2.78007199816156003, // k = 23 - 0.99867096266018507, 1.91161621560812023, 2.78367524259661536, // k = 24 - 0.99871656986212543, 1.91313978579765376, 2.78709435016625662, // k = 25 - 0.99875907577771272, 1.91458400425526065, 2.79034488416175463, // k = 26 - 0.99879885565047744, 1.91595563175945927, 2.79344064132371273, // k = 27 - 0.99883610756373287, 1.91726064301425936, 2.79639384757751941, // k = 28 - 0.99887095169674467, 1.91850441099725799, 2.79921543574803877, // k = 29 - 0.99890379414739527, 1.91969155477030995, 2.80191513182441554, // k = 30 - 0.99893466279047516, 1.92082633358913313, 2.80450167352080371, // k = 31 - 0.99896392088177777, 1.92191254955568525, 2.80698295731653502, // k = 32 - 0.99899147889385631, 1.92295362479495680, 2.80936614404217266, // k = 33 - 0.99901764688726757, 1.92395267400968351, 2.81165765979318394, // k = 34 - 0.99904238606342233, 1.92491244978191389, 2.81386337393604435, // k = 35 - 0.99906590152386343, 1.92583552644848055, 2.81598868034527072, // k = 36 - 0.99908829040739988, 1.92672418013918900, 2.81803841726804194, // k = 37 - 0.99910959420023460, 1.92758051694144683, 2.82001709302821268, // k = 38 - 0.99912996403594434, 1.92840654943159961, 2.82192875763732332, // k = 39 - 0.99914930224576892, 1.92920397044028391, 2.82377730628954282, // k = 40 - 0.99916781270195543, 1.92997447498220254, 2.82556612075063640, // k = 41 - 0.99918553179077207, 1.93071949211818605, 2.82729843191989971, // k = 42 - 0.99920250730914972, 1.93144048613876862, 2.82897728689417249, // k = 43 - 0.99921873345181211, 1.93213870990595638, 2.83060537017752267, // k = 44 - 0.99923435180002684, 1.93281536508689555, 2.83218527795750674, // k = 45 - 0.99924930425362390, 1.93347145882316340, 2.83371938965598247, // k = 46 - 0.99926370394567243, 1.93410820221384938, 2.83520990872793277, // k = 47 - 0.99927750755296074, 1.93472643138986200, 2.83665891945119597, // k = 48 - 0.99929082941537217, 1.93532697329771963, 2.83806833931606661, // k = 49 - 0.99930366295501472, 1.93591074716263734, 2.83943997143404658, // k = 50 - 0.99931598804721489, 1.93647857274021362, 2.84077557836653227, // k = 51 - 0.99932789059798210, 1.93703110239354714, 2.84207662106302905, // k = 52 - 0.99933946180485123, 1.93756904936378760, 2.84334468086129277, // k = 53 - 0.99935053819703512, 1.93809302131219852, 2.84458116874117195, // k = 54 - 0.99936126637970801, 1.93860365411038060, 2.84578731838604426, // k = 55 - 0.99937166229284458, 1.93910149816429112, 2.84696443486512862, // k = 56 - 0.99938169190727422, 1.93958709548454067, 2.84811369085281285, // k = 57 - 0.99939136927613959, 1.94006085573701625, 2.84923617230361970, // k = 58 - 0.99940074328745254, 1.94052339623206649, 2.85033291216254270, // k = 59 - 0.99940993070470086, 1.94097508636855309, 2.85140492437699322, // k = 60 - 0.99941868577388959, 1.94141633372043998, 2.85245314430358121, // k = 61 - 0.99942734443487780, 1.94184757038001976, 2.85347839582286156, // k = 62 - 0.99943556385736088, 1.94226915100517772, 2.85448160365493209, // k = 63 - 0.99944374522542034, 1.94268143723749631, 2.85546346373061510, // k = 64 - 0.99945159955424856, 1.94308482059116727, 2.85642486111805738, // k = 65 - 0.99945915301904620, 1.94347956957849988, 2.85736639994965458, // k = 66 - 0.99946660663832176, 1.94386600964031686, 2.85828887832701639, // k = 67 - 0.99947383703224091, 1.94424436597356021, 2.85919278275500233, // k = 68 - 0.99948075442870277, 1.94461502153473020, 2.86007887186090670, // k = 69 - 0.99948766082269458, 1.94497821937304138, 2.86094774077355396, // k = 70 - 0.99949422748713346, 1.94533411296001191, 2.86179981848076181, // k = 71 - 0.99950070756119658, 1.94568300035135167, 2.86263579405672886, // k = 72 - 0.99950704321753392, 1.94602523449961495, 2.86345610449197352, // k = 73 - 0.99951320334216121, 1.94636083782822311, 2.86426125541271404, // k = 74 - 0.99951920293474927, 1.94669011080745236, 2.86505169255406145, // k = 75 - 0.99952501670378524, 1.94701327348536779, 2.86582788270862920, // k = 76 - 0.99953071209267819, 1.94733044372333097, 2.86659027602854621, // k = 77 - 0.99953632734991515, 1.94764180764266825, 2.86733927778843167, // k = 78 - 0.99954171164873173, 1.94794766430732125, 2.86807526143834934, // k = 79 - 0.99954699274462655, 1.94824807472994621, 2.86879864789403882, // k = 80 - 0.99955216611081710, 1.94854317889829076, 2.86950970901679625, // k = 81 - 0.99955730019613043, 1.94883320227168610, 2.87020887436986527, // k = 82 - 0.99956213770650493, 1.94911826561721568, 2.87089648477021342, // k = 83 - 0.99956704264963037, 1.94939848545763539, 2.87157281693902178, // k = 84 - 0.99957166306481327, 1.94967401618316671, 2.87223821840905202, // k = 85 - 0.99957632713136491, 1.94994497791333288, 2.87289293193450135, // k = 86 - 0.99958087233392234, 1.95021155752212394, 2.87353731228213860, // k = 87 - 0.99958532555996271, 1.95047376805584349, 2.87417154907075201, // k = 88 - 0.99958956246481989, 1.95073180380688882, 2.87479599765507032, // k = 89 - 0.99959389351869277, 1.95098572880579013, 2.87541081987382086, // k = 90 - 0.99959807862052230, 1.95123574036898617, 2.87601637401948551, // k = 91 - 0.99960214057801977, 1.95148186921983324, 2.87661283691068093, // k = 92 - 0.99960607527256684, 1.95172415829728152, 2.87720042968334155, // k = 93 - 0.99960996433179616, 1.95196280898670693, 2.87777936649376898, // k = 94 - 0.99961379137860717, 1.95219787713926962, 2.87834989933620022, // k = 95 - 0.99961756088146103, 1.95242944583677058, 2.87891216133900230, // k = 96 - 0.99962125605327401, 1.95265762420910960, 2.87946647367488140, // k = 97 - 0.99962486179100551, 1.95288245314810638, 2.88001290210658567, // k = 98 - 0.99962843240297161, 1.95310404286672679, 2.88055166523392359, // k = 99 - 0.99963187276145504, 1.95332251980147475, 2.88108300006589957, // k = 100 - 0.99963525453173929, 1.95353785898848287, 2.88160703591438505, // k = 101 - 0.99963855412988778, 1.95375019354571577, 2.88212393551896184, // k = 102 - 0.99964190254169694, 1.95395953472205974, 2.88263389761985422, // k = 103 - 0.99964506565942202, 1.95416607430155409, 2.88313700661564098, // k = 104 - 0.99964834424233118, 1.95436972855640079, 2.88363350163803034, // k = 105 - 0.99965136548857458, 1.95457068540693513, 2.88412349413960101, // k = 106 - 0.99965436594726498, 1.95476896383092935, 2.88460710620208260, // k = 107 - 0.99965736463468602, 1.95496457504532373, 2.88508450078833789, // k = 108 - 0.99966034130443404, 1.95515761150707590, 2.88555580586194083, // k = 109 - 0.99966326130828520, 1.95534810382198998, 2.88602118761679094, // k = 110 - 0.99966601446035952, 1.95553622237747504, 2.88648066384146773, // k = 111 - 0.99966887679593697, 1.95572186728168163, 2.88693444915907094, // k = 112 - 0.99967161286551232, 1.95590523410490391, 2.88738271495714116, // k = 113 - 0.99967435412270333, 1.95608626483223702, 2.88782540459769166, // k = 114 - 0.99967701261934394, 1.95626497627117146, 2.88826277189363623, // k = 115 - 0.99967963265157778, 1.95644153684824573, 2.88869486674335008, // k = 116 - 0.99968216317182623, 1.95661589936000269, 2.88912184353694101, // k = 117 - 0.99968479674396349, 1.95678821614791332, 2.88954376359643561, // k = 118 - 0.99968729031337489, 1.95695842061650183, 2.88996069422501023, // k = 119 - 0.99968963358631413, 1.95712651709766305, 2.89037285320668502 // k = 120 - }; - // our "classic" bounds, but now with continuity correction static double cont_classic_lb(unsigned long long num_samples, double theta, double num_std_devs) { const double n_hat = (num_samples - 0.5) / theta; From 27d00b88662afc7fd8056356c08e3e995e411210 Mon Sep 17 00:00:00 2001 From: AlexanderSaydakov Date: Fri, 7 Jun 2019 16:55:39 -0700 Subject: [PATCH 22/22] method was moved to update_theta_sketch --- theta/include/theta_intersection.hpp | 2 -- theta/include/theta_intersection_impl.hpp | 22 +--------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/theta/include/theta_intersection.hpp b/theta/include/theta_intersection.hpp index 4bab6abc..3273af46 100644 --- a/theta/include/theta_intersection.hpp +++ b/theta/include/theta_intersection.hpp @@ -44,8 +44,6 @@ class theta_intersection_alloc { uint64_t* keys_; uint32_t num_keys_; uint16_t seed_hash_; - - static bool hash_search(uint64_t hash, const uint64_t* table, uint8_t lg_size); }; // alias with default allocator for convenience diff --git a/theta/include/theta_intersection_impl.hpp b/theta/include/theta_intersection_impl.hpp index 0b25b449..9174e65f 100644 --- a/theta/include/theta_intersection_impl.hpp +++ b/theta/include/theta_intersection_impl.hpp @@ -89,26 +89,6 @@ theta_intersection_alloc& theta_intersection_alloc::operator=(theta_inters return *this; } -template -bool theta_intersection_alloc::hash_search(uint64_t hash, const uint64_t* table, uint8_t lg_size) { - const uint32_t mask = (1 << lg_size) - 1; - const uint32_t stride = update_theta_sketch_alloc::get_stride(hash, lg_size); - uint32_t cur_probe = static_cast(hash) & mask; - const uint32_t loop_index = cur_probe; - do { - const uint64_t value = table[cur_probe]; - if (value == 0) { - return false; - } else if (value == hash) { - return true; - } - cur_probe = (cur_probe + stride) & mask; - } while (cur_probe != loop_index); - std::cerr << "hash_search: lg=" << (int)lg_size << ", hash=" << hash << std::endl; - std::cerr << "cur_probe=" << cur_probe << ", stride=" << stride << std::endl; - throw std::logic_error("key not found and search wrapped"); -} - constexpr uint8_t log2(uint32_t n) { return (n > 1) ? 1 + log2(n >> 1) : 0; } @@ -149,7 +129,7 @@ void theta_intersection_alloc::update(const theta_sketch_alloc& sketch) { uint32_t match_count = 0; for (auto key: sketch) { if (key < theta_) { - if (hash_search(key, keys_, lg_size_)) matched_keys[match_count++] = key; + if (update_theta_sketch_alloc::hash_search(key, keys_, lg_size_)) matched_keys[match_count++] = key; } else if (sketch.is_ordered()) { break; // early stop }