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 <cmath>
#include <catch2/catch.hpp>

#include <density_sketch.hpp>

namespace datasketches {

TEST_CASE("density sketch: empty", "[density_sketch]") {
  density_sketch<float> sketch(10, 3);
  REQUIRE(sketch.is_empty());
  REQUIRE_THROWS_AS(sketch.get_estimate({0, 0, 0}), std::runtime_error);
}

TEST_CASE("density sketch: one item", "[density_sketch]") {
  density_sketch<float> sketch(10, 3);

  // dimension mismatch
  REQUIRE_THROWS_AS(sketch.update(std::vector<float>({0, 0})), std::invalid_argument);

  sketch.update(std::vector<float>({0, 0, 0}));
  REQUIRE_FALSE(sketch.is_empty());
  REQUIRE_FALSE(sketch.is_estimation_mode());
  REQUIRE(sketch.get_estimate({0, 0, 0}) == 1);
  REQUIRE(sketch.get_estimate({0.01, 0.01, 0.01}) > 0.95);
  REQUIRE(sketch.get_estimate({1, 1, 1}) < 0.05);
}

TEST_CASE("density sketch: merge", "[density_sketch]") {
  density_sketch<float> sketch1(10, 4);
  sketch1.update(std::vector<float>({0, 0, 0, 0}));
  sketch1.update(std::vector<float>({1, 2, 3, 4}));

  density_sketch<float> sketch2(10, 4);
  sketch2.update(std::vector<float>({5, 6, 7, 8}));

  sketch1.merge(sketch2);

  REQUIRE(sketch1.get_n() == 3);
  REQUIRE(sketch1.get_num_retained() == 3);
}

TEST_CASE("density sketch: iterator", "[density_sketch]") {
  density_sketch<float> sketch(10, 3);
  unsigned n = 1000;
  for (unsigned i = 1; i <= n; ++i) sketch.update(std::vector<float>(3, i));
  REQUIRE(sketch.get_n() == n);
  REQUIRE(sketch.is_estimation_mode());
  //std::cout << sketch.to_string(true, true);
  unsigned count = 0;
  for (auto pair: sketch) {
    ++count;
    // just to assert something about the output
    REQUIRE(pair.first.size() == sketch.get_dim());
  }
  REQUIRE(count == sketch.get_num_retained());
}

// spherical kernel for testing, returns 1 for vectors within radius and 0 otherwise
template<typename T>
struct spherical_kernel {
  spherical_kernel(T radius = 1.0) : _radius_squared(radius * radius) {}
  T operator()(const std::vector<T>& v1, const std::vector<T>& v2) const {
    return std::inner_product(v1.begin(), v1.end(), v2.begin(), 0.0, std::plus<T>(), [](T a, T b){return (a-b)*(a-b);}) <= _radius_squared ? 1.0 : 0.0;
  }
  private:
    T _radius_squared;
};

TEST_CASE("custom kernel", "[density_sketch]") {
  density_sketch<float, spherical_kernel<float>> sketch(10, 3, spherical_kernel<float>(0.5));

  // update with (1,1,1) and test points inside and outside the kernel
  sketch.update(std::vector<float>(3, 1.0));
  REQUIRE(sketch.get_estimate(std::vector<float>(3, 1.001)) == 1.0);
  REQUIRE(sketch.get_estimate(std::vector<float>(3, 2.0)) == 0.0);

  // rest of test follows iterator test above
  unsigned n = 1000;
  for (unsigned i = 2; i <= n; ++i) sketch.update(std::vector<float>(3, i));
  REQUIRE(sketch.get_n() == n);
  REQUIRE(sketch.is_estimation_mode());
  unsigned count = 0;
  for (auto pair: sketch) {
    ++count;
    // just to assert something about the output
    REQUIRE(pair.first.size() == sketch.get_dim());
  }
  REQUIRE(count == sketch.get_num_retained());
}

TEST_CASE("serialize empty", "[density_sketch]") {
  density_sketch<double> sk(10, 2);
  auto bytes = sk.serialize();
  auto sk2 = density_sketch<double>::deserialize(bytes.data(), bytes.size());
  REQUIRE(sk2.is_empty());
  REQUIRE(!sk2.is_estimation_mode());
  REQUIRE(sk.get_k() == sk2.get_k());
  REQUIRE(sk.get_dim() == sk2.get_dim());
  REQUIRE(sk.get_n() == sk2.get_n());
  REQUIRE(sk.get_num_retained() == sk2.get_num_retained());

  std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
  sk.serialize(s);
  auto sk3 = density_sketch<double>::deserialize(s);
  REQUIRE(sk3.is_empty());
  REQUIRE(!sk3.is_estimation_mode());
  REQUIRE(sk.get_k() == sk3.get_k());
  REQUIRE(sk.get_dim() == sk3.get_dim());
  REQUIRE(sk.get_n() == sk3.get_n());
  REQUIRE(sk.get_num_retained() == sk3.get_num_retained());
}

TEST_CASE("serialize bytes", "[density_sketch]") {
  uint16_t k = 10;
  uint32_t dim = 3;
  density_sketch<double> sk(k, dim);

  for (uint16_t i = 0; i < k; ++i) {
    double val = static_cast<double>(i);
    sk.update(std::vector<double>({val, std::sqrt(val), -val}));
  }
  REQUIRE(!sk.is_estimation_mode());

  // exact mode
  auto bytes = sk.serialize();
  auto sk2 = density_sketch<double>::deserialize(bytes.data(), bytes.size());
  REQUIRE(!sk2.is_empty());
  REQUIRE(!sk2.is_estimation_mode());
  REQUIRE(sk.get_k() == sk2.get_k());
  REQUIRE(sk.get_dim() == sk2.get_dim());
  REQUIRE(sk.get_n() == sk2.get_n());
  REQUIRE(sk.get_num_retained() == sk2.get_num_retained());
  auto it1 = sk.begin();
  auto it2 = sk2.begin();
  while (it1 != sk.end()) {
    REQUIRE(it1->first[0] == it2->first[0]);
    REQUIRE(it1->second == it2->second);
    ++it1;
    ++it2;
  }

  // estimation mode
  size_t n = 1031;
  for (uint32_t i = k; i < n; ++i) {
    double val = static_cast<double>(i);
    sk.update(std::vector<double>({val, std::sqrt(val), -val}));
  }
  REQUIRE(sk.is_estimation_mode());

  bytes = sk.serialize();
  sk2 = density_sketch<double>::deserialize(bytes.data(), bytes.size());
  REQUIRE(!sk2.is_empty());
  REQUIRE(sk2.is_estimation_mode());
  REQUIRE(sk.get_k() == sk2.get_k());
  REQUIRE(sk.get_dim() == sk2.get_dim());
  REQUIRE(sk.get_n() == sk2.get_n());
  REQUIRE(sk.get_num_retained() == sk2.get_num_retained());
  it1 = sk.begin();
  it2 = sk2.begin();
  while (it1 != sk.end()) {
    REQUIRE(it1->first[0] == it2->first[0]);
    REQUIRE(it1->second == it2->second);
    ++it1;
    ++it2;
  }
}

TEST_CASE("serialize stream", "[density_sketch]") {
  uint16_t k = 10;
  uint32_t dim = 3;
  density_sketch<float> sk(k, dim);

  for (uint16_t i = 0; i < k; ++i) {
    float val = static_cast<float>(i);
    sk.update(std::vector<float>({val, std::sin(val), std::cos(val)}));
  }
  REQUIRE(!sk.is_estimation_mode());

  // exact mode
  std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
  sk.serialize(s);
  auto sk2 = density_sketch<float>::deserialize(s);
  REQUIRE(!sk2.is_empty());
  REQUIRE(!sk2.is_estimation_mode());
  REQUIRE(sk.get_k() == sk2.get_k());
  REQUIRE(sk.get_dim() == sk2.get_dim());
  REQUIRE(sk.get_n() == sk2.get_n());
  REQUIRE(sk.get_num_retained() == sk2.get_num_retained());
  auto it1 = sk.begin();
  auto it2 = sk2.begin();
  while (it1 != sk.end()) {
    REQUIRE(it1->first[0] == it2->first[0]);
    REQUIRE(it1->second == it2->second);
    ++it1;
    ++it2;
  }

  // estimation mode
  size_t n = 1031;
  for (uint32_t i = k; i < n; ++i) {
    float val = static_cast<float>(i);
    sk.update(std::vector<float>({val, std::sqrt(val), -val}));
  }
  REQUIRE(sk.is_estimation_mode());

  std::stringstream s2(std::ios::in | std::ios::out | std::ios::binary);
  sk.serialize(s2);
  sk2 = density_sketch<float>::deserialize(s2);
  REQUIRE(!sk2.is_empty());
  REQUIRE(sk2.is_estimation_mode());
  REQUIRE(sk.get_k() == sk2.get_k());
  REQUIRE(sk.get_dim() == sk2.get_dim());
  REQUIRE(sk.get_n() == sk2.get_n());
  REQUIRE(sk.get_num_retained() == sk2.get_num_retained());
  it1 = sk.begin();
  it2 = sk2.begin();
  while (it1 != sk.end()) {
    REQUIRE(it1->first[0] == it2->first[0]);
    REQUIRE(it1->second == it2->second);
    ++it1;
    ++it2;
  }
}

} /* namespace datasketches */