libdatasketches_sys 0.1.3

Low-level bindings for Apache DataSketches C++ library
Documentation
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <iostream>
#include <iomanip>
#include <set>
#include <catch2/catch.hpp>
#include <tuple_sketch.hpp>
#include <tuple_union.hpp>
#include <stdexcept>

template<typename T>
class max_value_policy {
public:
    max_value_policy(const T& initial_value): initial_value(initial_value) {}
    T create() const { return initial_value; }
    void update(T& summary, const T& update) const { summary = std::max(summary, update); }
private:
    T initial_value;
};

using max_float_update_tuple_sketch = datasketches::update_tuple_sketch<float, float, max_value_policy<float>>;

template<typename T>
class always_one_policy {
public:
    always_one_policy(): initial_value(1) {}
    T create() const { return 1; }
    void update(T&, const T&) const {}
private:
    T initial_value;
};
using always_one_tuple_sketch = datasketches::update_tuple_sketch<int, int, always_one_policy<int>>;

template<typename T>
class update_sum_value_policy {
public:
    update_sum_value_policy(): initial_value(0) {}
    T create() const { return initial_value; }
    void update(T& summary, const T& update) const { summary += update; }
private:
    T initial_value;
};
using sum_update_tuple_sketch = datasketches::update_tuple_sketch<int, int, update_sum_value_policy<int>>;

template<typename Summary>
struct union_sum_value_policy {
    void operator()(Summary& summary, const Summary& other) const {
        summary += other;
    }
};

using sum_union_tuple_sketch = datasketches::tuple_union<int, union_sum_value_policy<int>>;


class EngagementTest {
public:
    uint8_t num_std_dev = 2;
    void test_always_one_update() {
        /*
         * Tests that updates into an update_tuple_sketch sketch only keeps a 1 in the column for stored values.
         */
        uint8_t lgK = 8;
        std::vector<datasketches::update_tuple_sketch<int, int, always_one_policy<int>>> sketch_array;

        auto always_one_sketch = always_one_tuple_sketch::builder(always_one_policy<int>()).set_lg_k(lgK).build();

        always_one_sketch.update(1, 1);
        always_one_sketch.update(1, 2);
        always_one_sketch.update(2, 1);
        always_one_sketch.update(3, 3);
        always_one_sketch.update(3, 7);

        int num_retained = 0;
        int sum = 0;
        for (const auto& entry: always_one_sketch) {
            sum += entry.second;
            ++num_retained;
        }
        REQUIRE(num_retained == 3);
        REQUIRE(sum == 3); // we only keep 1 for every stored key.
    }

    void test_sum_update_policy() {
        /*
        * Tests that updates into an sum_update_tuple_sketch sum the stored values on updates.
        */
        uint8_t lgK = 8;
        auto sum_sketch = sum_update_tuple_sketch::builder().set_lg_k(lgK).build();

        sum_sketch.update(1, 1);
        sum_sketch.update(1, 2);
        sum_sketch.update(2, 1);
        sum_sketch.update(3, 3);
        sum_sketch.update(3, 7);
        int num_retained = 0;
        int sum = 0;
        for (const auto& entry: sum_sketch) {
            sum += entry.second;
            ++num_retained;
        }
        REQUIRE(num_retained == 3);
        REQUIRE(sum == 14); // (1+2) + 1 + (3 + 7) = 14
    }

    void test_sum_union_policy(){
        /*
         * Tests that updates into two sketches of sum_update_tuple_sketch flavour, which have been unioned,
         * cause the stored values of two of the same keys to be summed.
         */
        auto sketch1 = sum_update_tuple_sketch::builder().build();
        auto sketch2 = sum_update_tuple_sketch::builder().build();

        sketch1.update(1, 1);
        sketch1.update(2, 1);
        sketch1.update(3, 3);

        sketch2.update(1, 2);
        sketch2.update(2, 1);
        sketch2.update(3, 7);

        auto union_sketch = sum_union_tuple_sketch::builder().build();
        union_sketch.update(sketch1);
        union_sketch.update(sketch2);
        auto union_result = union_sketch.get_result();

        int num_retained = 0;
        int sum = 0;
        for (const auto& entry: union_result) {
            sum += entry.second;
            ++num_retained;
        }
        REQUIRE(num_retained == 3);
        REQUIRE(sum == 15); // 1:(1+2) + 2:(1+1) + 3:(3+7) = 15
    }

    void compute_engagement_histogram() {
        /*
         * Returns the estimated histogram from the synthetic data.
         * On inspection one can verify this agrees with the
         * https://github.com/apache/datasketches-java/blob/master/src/test/java/org/apache/datasketches/tuple/aninteger/EngagementTest.java
         */
        uint8_t lgK = 8;
        const int days = 30;
        int v = 0;
        std::set<int> set_array[days];
        std::vector<datasketches::update_tuple_sketch<int, int, always_one_policy<int>>> sketch_array;


        for (int i = 0; i < days; ++i) {
            auto builder = always_one_tuple_sketch::builder(always_one_policy<int>());
            builder.set_lg_k(lgK);
            auto sketch = builder.build();
            sketch_array.push_back(sketch);
        }
        REQUIRE(sketch_array.size() == days);

        for (int i = 0; i <= days; ++i) {
            int32_t num_ids = get_num_ids(days, i);
            int32_t num_days = get_num_days(days, i);

            int my_v = v++;
            for (int d = 0; d < num_days; ++d) {
                for (int id = 0; id < num_ids; ++id) {
                    set_array[d].insert(my_v + id);
                    sketch_array[d].update(my_v + id, 1);
                }
            }
            v += num_ids;
        }
        union_ops(lgK, sketch_array);
    }
private:
    int32_t get_num_ids(int total_days, int index) {
        /*
         * Generates power law distributed synthetic data
         */
        double d = total_days;
        double i = index;
        return int(round(exp(i * log(d) / d)));
    }

    int32_t get_num_days(int total_days, int index) {
        double d = total_days;
        double i = index;
        return int(round(exp((d-i) * log(d) / d )));
    }

    int32_t round_double_to_int(double x) {
        return int(std::round(x));
    }

    void union_ops(uint8_t lgk, std::vector<datasketches::update_tuple_sketch<int, int, always_one_policy<int>>> sketches) {
        auto num_sketches = sketches.size();
        auto u = sum_union_tuple_sketch::builder().set_lg_k(lgk).build();

        for (auto sk: sketches) {
            u.update(sk);
        }
        auto union_result = u.get_result();
        std::vector<uint64_t> num_days_arr(num_sketches+1);

        for (const auto& entry: union_result) {
            int num_days_visited = entry.second;
            ++num_days_arr[num_days_visited];
        }

        uint64_t sum_visits = 0;
        double theta = union_result.get_theta();
        std::cout <<"\t\tEngagement Histogram.\t\t\t\n";
        std::cout << "Number of Unique Visitors by Number of Days Visited" << std::endl;
        std::cout << "---------------------------------------------------" << std::endl;

        std::cout << std::setw(12) << "Days Visited"
                  << std::setw(12) << "Estimate"
                  << std::setw(12) << "LB"
                  << std::setw(12) << "UB"
                  << std:: endl;

        for (size_t i = 0; i < num_days_arr.size(); ++i) {
            auto visitors_at_days_visited = num_days_arr[i];
            if (visitors_at_days_visited == 0) continue;
            sum_visits += visitors_at_days_visited * i;

            double est_visitors_at_days_visited = visitors_at_days_visited / theta;
            double lower_bound_at_days_visited =  union_result.get_lower_bound(num_std_dev, visitors_at_days_visited);
            double upper_bound_at_days_visited =  union_result.get_upper_bound(num_std_dev, visitors_at_days_visited);

            std::cout << std::setw(12) << i
                      << std::setw(12) << est_visitors_at_days_visited
                      << std::setw(12) << lower_bound_at_days_visited
                      << std::setw(12) << upper_bound_at_days_visited
                      << std:: endl;
        }
        std::cout << std::endl << std::endl;
        std::cout << std::setw(12) << "Totals"
                  << std::setw(12) << "Estimate"
                  << std::setw(12) << "LB"
                  << std::setw(12) << "UB"
                  << std:: endl;
        std::cout << "---------------------------------------------------" << std::endl;

        const double total_visitors = union_result.get_estimate();
        const double lb_visitors = union_result.get_lower_bound(num_std_dev);
        const double ub_visitors = union_result.get_upper_bound(num_std_dev);

        std::cout << std::setw(12) << "Visitors"
                  << std::setw(12) << total_visitors
                  << std::setw(12) << lb_visitors
                  << std::setw(12) << ub_visitors
                  << std:: endl;

        // The total number of visits, however, is a scaled metric and takes advantage of the fact that
        // the retained entries in the sketch is a uniform random sample of all unique visitors, and
        // the  rest of the unique users will likely behave in the same way.
        const double est_visits = sum_visits / theta;
        const double lb_visits = est_visits * lb_visitors / total_visitors;
        const double ub_visits = est_visits * ub_visitors / total_visitors;

        std::cout << std::setw(12) << "Visits"
                  << std::setw(12) << est_visits
                  << std::setw(12) << lb_visits
                  << std::setw(12) << ub_visits
                  << std:: endl;
    }

};

namespace datasketches {

  TEST_CASE("engagement", "[engagement]") {
    EngagementTest E;
    E.test_always_one_update();
    E.test_sum_update_policy();
    E.test_sum_union_policy();
    E.compute_engagement_histogram();
  }

} /* namespace datasketches */