[
  {
    "path": ".bazelrc",
    "content": "# This is from Bazel's former travis setup, to avoid blowing up the RAM usage.\nstartup --host_jvm_args=-Xmx2500m\nstartup --host_jvm_args=-Xms2500m\n# test --ram_utilization_factor=10 # comment-out for github actions.\n\n# This is so we understand failures better\nbuild --verbose_failures\n\n# This is so we don't use sandboxed execution. Sandboxed execution\n# runs stuff in a container, and since Travis already runs its script\n# in a container (unless you require sudo in your .travis.yml) this\n# fails to run tests.\nbuild --spawn_strategy=standalone --genrule_strategy=standalone\ntest --test_strategy=standalone\n\n# Below this line, .travis.yml will cat the default bazelrc.\n# This is needed so Bazel starts with the base workspace in its\n# package path.\n\n\n# By default build in C++17 mode using the Homegrown scheduler for parallelism.\n#build --repo_env=CC=clang++-12\nbuild --repo_env=CC=g++\nbuild --cxxopt=-std=c++17\nbuild --cxxopt=-mcx16        # 16 byte CAS\nbuild --cxxopt=-DHOMEGROWN   # use the homegrown scheduler\nbuild --cxxopt=-DLONG        # use 8 byte vertex identifiers\nbuild --cxxopt=-DAMORTIZEDPD # use amortized_bytepd encoding scheme for compressed graphs\nbuild --cxxopt=-DUSEMALLOC\nbuild --cxxopt=-DPARLAY_USE_STD_ALLOC\nbuild --cxxopt=-pthread      # necessary for homegrown scheduler\nbuild --cxxopt=-march=native\nbuild --cxxopt=-fvisibility=hidden\nbuild --cxxopt=-fvisibility-inlines-hidden\nbuild --cxxopt=-fsized-deallocation  # https://github.com/pybind/pybind11/issues/1604 (for clang)\nbuild -c opt\n\n# C++ warning flags.\nbuild --cxxopt=-Wall\nbuild --cxxopt=-Wextra\nbuild --cxxopt=-Wcast-qual\nbuild --cxxopt=-Wno-unused-parameter\nbuild --cxxopt=-Wpointer-arith\n# Turning on -Wshadow rather than just -Wshadow=local would be nice, but the\n# codebase currently contains lots of instances of global shadowing.\n#build --cxxopt=-Wshadow=local\nbuild --cxxopt=-Wvla\n\n# Build without parallelism.\nbuild:serial --cxxopt=-UHOMEGROWN\nbuild:serial --cxxopt=-DPARLAY_SEQUENTIAL\n\n# Build using CilkPlus for parallelism.\nbuild:cilk --cxxopt=-UHOMEGROWN\nbuild:cilk --cxxopt=-DCILK\nbuild:cilk --cxxopt=-fcilkplus\nbuild:cilk --linkopt=-lcilkrts\n\n# Build using OpenMP for parallelism.\nbuild:openmp --cxxopt=-UHOMEGROWN\nbuild:openmp --cxxopt=-DOPENMP\nbuild:openmp --cxxopt=-fopenmp\nbuild:openmp --linkopt=-fopenmp\n\n# Instruments the build with AddressSanitizer\n# (https://github.com/google/sanitizers/wiki/AddressSanitizer).\n# Invoke by adding the `--config=asan` flag, e.g.,\n#     bazel run --config=asan <build target>`\nbuild:asan --strip=never\nbuild:asan --cxxopt=-fsanitize=address\nbuild:asan --cxxopt=-O1\nbuild:asan --cxxopt=-g\nbuild:asan --cxxopt=-fno-omit-frame-pointer\nbuild:asan --cxxopt=-Wno-macro-redefined\nbuild:asan --linkopt=-fsanitize=address\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"parlaylib\"]\n\tpath = parlaylib\n\turl = https://github.com/cmuparlay/parlaylib.git\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.15)\nproject(PARLAYANN VERSION 1\n        DESCRIPTION \"ParlayANN is a library of approximate nearest neighbor search algorithms, along with a set of useful tools for designing such algorithms. It is written in C++ and uses parallel primitives from ParlayLib. Currently it includes implementations of the ANNS algorithms DiskANN, HNSW, HCNNG, and pyNNDescent.\"\n        LANGUAGES CXX)\n\ninclude(CheckCXXCompilerFlag)\ninclude(GNUInstallDirs)\n\n# Set a default build type\nif(NOT CMAKE_BUILD_TYPE)\n  set(CMAKE_BUILD_TYPE \"RELEASE\" CACHE STRING \"Build type (Release)\" FORCE)\n  message(STATUS \"No build type specified. Defaulted to RELEASE.\")\n  message(STATUS \"To specify a build type, add -DCMAKE_BUILD_TYPE=<DEBUG/RELEASE/RELWITHDEBINFO/MINSIZEREL>\")\nendif(NOT CMAKE_BUILD_TYPE)\n\nset (CMAKE_CXX_FLAGS_DEBUG \"${CMAKE_CXX_FLAGS_DEBUG} -O0 -fno-omit-frame-pointer\")\nset (CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} -march=native\")\n\nmessage(STATUS \"PARLAYANN VERSION ${PARLAYANN_VERSION}\")\nmessage(STATUS \"---------------------------- General configuration -----------------------------\")\nmessage(STATUS \"CMake Generator:                ${CMAKE_GENERATOR}\")\nmessage(STATUS \"Compiler:                       ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}\")\nmessage(STATUS \"Build type:                     ${CMAKE_BUILD_TYPE}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS:                ${CMAKE_CXX_FLAGS}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS_DEBUG:          ${CMAKE_CXX_FLAGS_DEBUG}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS_RELEASE:        ${CMAKE_CXX_FLAGS_RELEASE}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}\")\nmessage(STATUS \"CMAKE_EXE_LINKER_FLAGS          ${CMAKE_EXE_LINKER_FLAGS}\")\nmessage(STATUS \"CMAKE_INSTALL_PREFIX:           ${CMAKE_INSTALL_PREFIX}\" )\n\n# Set module path\nlist(APPEND CMAKE_MODULE_PATH \"${PROJECT_SOURCE_DIR}/cmake\")\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS ON)\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\nadd_library(parlay INTERFACE)\ntarget_include_directories(parlay INTERFACE \"${PROJECT_SOURCE_DIR}/parlaylib/include\")\n\n# Link against system threads\nfind_package(Threads REQUIRED)\ntarget_link_libraries(parlay INTERFACE Threads::Threads)\n\nadd_subdirectory(algorithms)\n\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 magdalendobson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "WORKSPACE",
    "content": "load(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/cpp:cc_configure.bzl\", \"cc_configure\")\n\ncc_configure()\n\nhttp_archive(\n    name = \"parlaylib\",\n    sha256 = \"68c062ad116fd49d77651d7a24fb985aa66e8ec9ad05176b6af3ab5d29a16b1f\",\n    strip_prefix = \"parlaylib-bazel/include/\",\n    urls = [\"https://github.com/ParAlg/parlaylib/archive/refs/tags/bazel.tar.gz\"],\n)\n\nhttp_archive(\n    name = \"googletest\",\n    sha256 = \"b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5\",\n    strip_prefix = \"googletest-release-1.11.0\",\n    urls = [\"https://github.com/google/googletest/archive/release-1.11.0.tar.gz\"],\n)\n"
  },
  {
    "path": "algorithms/CMakeLists.txt",
    "content": "add_subdirectory(HCNNG)\nadd_subdirectory(HNSW)\nadd_subdirectory(pyNNDescent)\nadd_subdirectory(vamana)\nadd_subdirectory(vamanaRange)\n"
  },
  {
    "path": "algorithms/HCNNG/CMakeLists.txt",
    "content": "add_executable(neighbors-hcnng ../bench/neighborsTime.C)\n  target_link_libraries(neighbors-hcnng PRIVATE parlay)\n  target_precompile_headers(neighbors-hcnng PRIVATE neighbors.h)\n\n"
  },
  {
    "path": "algorithms/HCNNG/Makefile",
    "content": "include ../bench/parallelDefsANN   \n\nREQUIRE =  ../utils/beamSearch.h hcnng_index.h ../utils/graph.h clusterEdge.h\nBENCH = neighbors\n\ninclude ../bench/MakeBench   \n"
  },
  {
    "path": "algorithms/HCNNG/clusterEdge.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <math.h>\n\n#include <algorithm>\n#include <functional>\n#include <queue>\n#include <random>\n#include <set>\n\n#include \"../utils/graph.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n\nnamespace parlayANN {\n  \nstd::pair<size_t, size_t>\nselect_two_random(parlay::sequence<size_t> &active_indices,\n                  parlay::random &rnd) {\n  size_t first_index = rnd.ith_rand(0) % active_indices.size();\n  size_t second_index_unshifted = rnd.ith_rand(1) % (active_indices.size() - 1);\n  size_t second_index = (second_index_unshifted < first_index)\n                            ? second_index_unshifted\n                            : (second_index_unshifted + 1);\n\n  return {active_indices[first_index], active_indices[second_index]};\n}\n\ntemplate <typename Point, typename PointRange, typename indexType>\nstruct cluster {\n  using distanceType = typename Point::distanceType;\n  using edge = std::pair<indexType, indexType>;\n  using labelled_edge = std::pair<edge, distanceType>;\n  using GraphI = Graph<indexType>;\n  using PR = PointRange;\n\n  cluster() {}\n\n  int generate_index(int N, int i) {\n    return (N * (N - 1) - (N - i) * (N - i - 1)) / 2;\n  }\n\n  template <typename F>\n  void recurse(GraphI &G, PR &Points, parlay::sequence<size_t> &active_indices,\n               parlay::random &rnd, size_t cluster_size, F f, long MSTDeg,\n               indexType first, indexType second) {\n    // Split points based on which of the two points are closer.\n    auto closer_first =\n        parlay::filter(parlay::make_slice(active_indices), [&](size_t ind) {\n          distanceType dist_first = Points[ind].distance(Points[first]);\n          distanceType dist_second = Points[ind].distance(Points[second]);\n          return dist_first <= dist_second;\n        });\n\n    auto closer_second =\n        parlay::filter(parlay::make_slice(active_indices), [&](size_t ind) {\n          distanceType dist_first = Points[ind].distance(Points[first]);\n          distanceType dist_second = Points[ind].distance(Points[second]);\n          return dist_second < dist_first;\n        });\n\n    auto left_rnd = rnd.fork(0);\n    auto right_rnd = rnd.fork(1);\n\n    parlay::par_do(\n        [&]() {\n          random_clustering(G, Points, closer_first, left_rnd, cluster_size, f,\n                            MSTDeg);\n        },\n        [&]() {\n          random_clustering(G, Points, closer_second, right_rnd, cluster_size,\n                            f, MSTDeg);\n        });\n  }\n\n  template <typename F>\n  void random_clustering(GraphI &G, PR &Points,\n                         parlay::sequence<size_t> &active_indices,\n                         parlay::random &rnd, size_t cluster_size, F g,\n                         long MSTDeg) {\n    if (active_indices.size() <= cluster_size)\n      g(G, Points, active_indices, MSTDeg);\n    else {\n      auto [f, s] = select_two_random(active_indices, rnd);\n      if (Points[f] == Points[s]) {\n        parlay::sequence<size_t> closer_first;\n        parlay::sequence<size_t> closer_second;\n        for (int i = 0; i < active_indices.size(); i++) {\n          if (i < active_indices.size() / 2)\n            closer_first.push_back(active_indices[i]);\n          else\n            closer_second.push_back(active_indices[i]);\n        }\n        auto left_rnd = rnd.fork(0);\n        auto right_rnd = rnd.fork(1);\n        parlay::par_do(\n            [&]() {\n              random_clustering(G, Points, closer_first, left_rnd, cluster_size,\n                                g, MSTDeg);\n            },\n            [&]() {\n              random_clustering(G, Points, closer_second, right_rnd,\n                                cluster_size, g, MSTDeg);\n            });\n      } else {\n        recurse(G, Points, active_indices, rnd, cluster_size, g, MSTDeg, f, s);\n      }\n    }\n  }\n\n  template <typename F>\n  void random_clustering_wrapper(GraphI &G, PR &Points, size_t cluster_size,\n                                 F f, long MSTDeg) {\n    std::random_device rd;\n    std::mt19937 rng(rd());\n    std::uniform_int_distribution<int> uni(0, Points.size());\n    parlay::random rnd(uni(rng));\n    auto active_indices =\n        parlay::tabulate(Points.size(), [&](size_t i) { return i; });\n    random_clustering(G, Points, active_indices, rnd, cluster_size, f, MSTDeg);\n  }\n\n  template <typename F>\n  void multiple_clustertrees(GraphI &G, PR &Points, long cluster_size,\n                             long num_clusters, F f, long MSTDeg) {\n    for (long i = 0; i < num_clusters; i++) {\n      random_clustering_wrapper(G, Points, cluster_size, f, MSTDeg);\n      std::cout << \"Built cluster \" << i << \" of \" << num_clusters << std::endl;\n    }\n  }\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/HCNNG/hcnng_index.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <math.h>\n\n#include <algorithm>\n#include <queue>\n#include <random>\n#include <set>\n\n#include \"../utils/graph.h\"\n#include \"clusterEdge.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n\nnamespace parlayANN {\n\nstruct DisjointSet {\n  parlay::sequence<int> parent;\n  parlay::sequence<int> rank;\n  size_t N;\n\n  DisjointSet(size_t size) {\n    N = size;\n    parent = parlay::sequence<int>(N);\n    rank = parlay::sequence<int>(N);\n    parlay::parallel_for(0, N, [&](size_t i) {\n      parent[i] = i;\n      rank[i] = 0;\n    });\n  }\n\n  void _union(int x, int y) {\n    int xroot = parent[x];\n    int yroot = parent[y];\n    int xrank = rank[x];\n    int yrank = rank[y];\n    if (xroot == yroot)\n      return;\n    else if (xrank < yrank)\n      parent[xroot] = yroot;\n    else {\n      parent[yroot] = xroot;\n      if (xrank == yrank) rank[xroot] = rank[xroot] + 1;\n    }\n  }\n\n  int find(int x) {\n    if (parent[x] == x) return x;\n    int c = x;\n    while (parent[c] != c) {\n      c = parent[c];\n    }\n    while (x != c) {\n      int s = parent[x];\n      parent[x] = c;\n      x = s;\n    }\n    return c;\n  }\n\n  void flatten() {\n    for (int i = 0; i < N; i++) find(i);\n  }\n\n  bool is_full() {\n    flatten();\n    parlay::sequence<bool> truthvals(N);\n    parlay::parallel_for(\n        0, N, [&](size_t i) { truthvals[i] = (parent[i] == parent[0]); });\n    auto ff = [&](bool a) { return not a; };\n    auto filtered = parlay::filter(truthvals, ff);\n    if (filtered.size() == 0) return true;\n    return false;\n  }\n};\n\ntemplate <typename Point, typename PointRange, typename indexType>\nstruct hcnng_index {\n  using distanceType = typename Point::distanceType;\n  using edge = std::pair<indexType, indexType>;\n  using labelled_edge = std::pair<edge, distanceType>;\n  using pid = std::pair<indexType, distanceType>;\n  using GraphI = Graph<indexType>;\n  using PR = PointRange;\n\n  static constexpr indexType kNullId = std::numeric_limits<indexType>::max();\n  static constexpr distanceType kNullDist =\n      std::numeric_limits<distanceType>::max();\n  static constexpr labelled_edge kNullEdge = {{kNullId, kNullId}, kNullDist};\n\n  hcnng_index() {}\n\n  static void remove_edge_duplicates(indexType p, GraphI &G) {\n    parlay::sequence<indexType> points;\n    for (indexType i = 0; i < G[p].size(); i++) {\n      points.push_back(G[p][i]);\n    }\n    auto np = parlay::remove_duplicates(points);\n    G[p].update_neighbors(np);\n  }\n\n  void remove_all_duplicates(GraphI &G) {\n    parlay::parallel_for(0, G.size(),\n                         [&](size_t i) { remove_edge_duplicates(i, G); });\n  }\n\n  // inserts each edge after checking for duplicates\n  static void process_edges(GraphI &G, parlay::sequence<edge> edges) {\n    long maxDeg = G.max_degree();\n    auto grouped = parlay::group_by_key(edges);\n    parlay::parallel_for(0, grouped.size(), [&](size_t i) {\n      int32_t index = grouped[i].first;\n      for (auto c : grouped[i].second) {\n        if (G[index].size() < maxDeg) {\n          G[index].append_neighbor(c);\n        } else {\n          remove_edge_duplicates(index, G);\n          G[index].append_neighbor(c);\n        }\n      }\n    });\n  }\n\n  // parameters dim and K are just to interface with the cluster tree code\n  static void MSTk(GraphI &G, PR &Points,\n                   parlay::sequence<size_t> &active_indices, long MSTDeg) {\n    // preprocessing for Kruskal's\n    size_t N = active_indices.size();\n    long dim = Points.dimension();\n    DisjointSet disjset(N);\n    size_t m = 10;\n    auto less = [&](labelled_edge a, labelled_edge b) {\n      return a.second < b.second;\n    };\n    parlay::sequence<labelled_edge> candidate_edges(N * m, kNullEdge);\n    parlay::parallel_for(0, N, [&](size_t i) {\n      std::priority_queue<labelled_edge, std::vector<labelled_edge>,\n                          decltype(less)>\n          Q(less);\n      for (indexType j = i + 1; j < N; j++) {\n        distanceType dist_ij =\n            Points[active_indices[i]].distance(Points[active_indices[j]]);\n        if (Q.size() >= m) {\n          distanceType topdist = Q.top().second;\n          if (dist_ij < topdist) {\n            labelled_edge e;\n            e = std::make_pair(std::make_pair(i, j), dist_ij);\n            Q.pop();\n            Q.push(e);\n          }\n        } else {\n          labelled_edge e;\n          e = std::make_pair(std::make_pair(i, j), dist_ij);\n          Q.push(e);\n        }\n      }\n      indexType limit = std::min(Q.size(), m);\n      for (indexType j = 0; j < limit; j++) {\n        candidate_edges[i * m + j] = Q.top();\n        Q.pop();\n      }\n    });\n\n    parlay::sort_inplace(candidate_edges, less);\n\n    auto degrees =\n        parlay::tabulate(active_indices.size(), [&](size_t i) { return 0; });\n    parlay::sequence<edge> MST_edges = parlay::sequence<edge>();\n    // modified Kruskal's algorithm\n    for (indexType i = 0; i < candidate_edges.size(); i++) {\n      // Since we sorted, any null edges form the suffix.\n      if (candidate_edges[i].second == kNullDist) break;\n      labelled_edge e_l = candidate_edges[i];\n      edge e = e_l.first;\n      if ((disjset.find(e.first) != disjset.find(e.second)) &&\n          (degrees[e.first] < MSTDeg) && (degrees[e.second] < MSTDeg)) {\n        MST_edges.push_back(\n            std::make_pair(active_indices[e.first], active_indices[e.second]));\n        MST_edges.push_back(\n            std::make_pair(active_indices[e.second], active_indices[e.first]));\n        degrees[e.first] += 1;\n        degrees[e.second] += 1;\n        disjset._union(e.first, e.second);\n      }\n      if (i % N == 0) {\n        if (disjset.is_full()) {\n          break;\n        }\n      }\n    }\n    process_edges(G, std::move(MST_edges));\n  }\n\n  // robustPrune routine as found in DiskANN paper, with the exception that the\n  // new candidate set is added to the field new_nbhs instead of directly\n  // replacing the out_nbh of p\n  void robustPrune(indexType p, PR &Points, GraphI &G, double alpha) {\n    // add out neighbors of p to the candidate set.\n    parlay::sequence<pid> candidates;\n    for (size_t i = 0; i < G[p].size(); i++) {\n      candidates.push_back(\n          std::make_pair(G[p][i], Points[p].distance(Points[G[p][i]])));\n    }\n\n    // Sort the candidate set in reverse order according to distance from p.\n    auto less = [&](pid a, pid b) { return a.second < b.second; };\n    parlay::sort_inplace(candidates, less);\n\n    parlay::sequence<int> new_nbhs = parlay::sequence<int>();\n\n    size_t candidate_idx = 0;\n    while (new_nbhs.size() < G.max_degree() &&\n           candidate_idx < candidates.size()) {\n      // Don't need to do modifications.\n      indexType p_star = candidates[candidate_idx].first;\n      candidate_idx++;\n      if (p_star == p || p_star == kNullId) continue;\n\n      new_nbhs.push_back(p_star);\n\n      for (size_t i = candidate_idx; i < candidates.size(); i++) {\n        indexType p_prime = candidates[i].first;\n        if (p_prime != kNullId) {\n          distanceType dist_starprime =\n              Points[p_star].distance(Points[p_prime]);\n          distanceType dist_pprime = candidates[i].second;\n          if (alpha * dist_starprime <= dist_pprime)\n            candidates[i].first = kNullId;\n        }\n      }\n    }\n    G[p].update_neighbors(new_nbhs);\n  }\n\n  void build_index(GraphI &G, PR &Points, long cluster_rounds,\n                   long cluster_size, long MSTDeg) {\n    cluster<Point, PointRange, indexType> C;\n    C.multiple_clustertrees(G, Points, cluster_size, cluster_rounds, MSTk,\n                            MSTDeg);\n    remove_all_duplicates(G);\n    // TODO: enable optional pruning (what is below now works, but\n    // should be connected cleanly)\n    // parlay::parallel_for(0, G.size(), [&] (size_t i){robustPrune(i, Points,\n    // G, 1.1);});\n  }\n};\n\n}  // namespace parlayANN\n"
  },
  {
    "path": "algorithms/HCNNG/neighbors.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <algorithm>\n#include <cmath>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"../utils/NSGDist.h\"  \n#include \"../utils/types.h\"\n#include \"../utils/beamSearch.h\"\n#include \"../utils/stats.h\"\n#include \"../utils/parse_results.h\"\n#include \"../utils/check_nn_recall.h\"\n#include \"../utils/graph.h\"\n#include \"hcnng_index.h\"\n\nnamespace parlayANN {\n\ntemplate<typename Point, typename PointRange, typename indexType>\nvoid ANN(Graph<indexType> &G, long k, BuildParams &BP,\n         PointRange &Query_Points,\n         groundTruth<indexType> GT, char *res_file,\n         bool graph_built, PointRange &Points) {\n\n  parlay::internal::timer t(\"ANN\"); \n  using findex = hcnng_index<Point, PointRange, indexType>;\n\n  double idx_time;\n  if(!graph_built){\n    findex I;\n    I.build_index(G, Points, BP.num_clusters, BP.cluster_size, BP.MST_deg);\n    idx_time = t.next_time();\n  } else{idx_time=0;}\n  std::string name = \"HCNNG\";\n  std::string params = \"Trees = \" + std::to_string(BP.num_clusters);\n  auto [avg_deg, max_deg] = graph_stats_(G);\n  Graph_ G_(name, params, G.size(), avg_deg, max_deg, idx_time);\n  G_.print();\n  if(Query_Points.size() != 0)\n    search_and_parse(G_, G, Points, Query_Points, GT, res_file, k, BP.verbose);\n}\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/HCNNG/scripts/fashion",
    "content": "# bash\n\nnumactl -i all ./neighbors -R 64 -L 128 -alpha 1.15 -data_type float -file_type bin -dist_func Euclidian -base_path data/fashion-mnist-784-euclidean/fashion-mnist-784-euclidean_base.fbin -query_path data/fashion-mnist-784-euclidean/fashion-mnist-784-euclidean_query.fbin -gt_path data/fashion-mnist-784-euclidean/fashion-mnist-784-euclidean_groundtruth -quantize 8 -two_pass 1 -graph_path data/fashion-mnist-784-euclidean/graph_64 -verbose \n\n"
  },
  {
    "path": "algorithms/HCNNG/scripts/gist_1",
    "content": "# bash\n\nnumactl -i all ./neighbors -R 100 -L 200 -alpha 1.1 -data_type float -file_type bin -dist_func Euclidian -base_path data/gist/gist_base.fbin -query_path data/gist/gist_query.fbin -gt_path data/gist/gist-1M -quantize 16 -two_pass 1 -quantize_build 1 -graph_path data/gist/graph\n"
  },
  {
    "path": "algorithms/HCNNG/scripts/glove100",
    "content": "# bash\n\nnumactl -i all ./neighbors -R 150 -L 300 -alpha 1 -data_type float -file_type bin -dist_func mips -base_path data/glove-100-angular/glove-100-angular_base.fbin -query_path data/glove-100-angular/glove-100-angular_query.fbin -gt_path data/glove-100-angular/glove-100-angular_groundtruth -num_passes 2 -quantize_build 1 -quantize 16 -normalize -verbose -graph_path data/glove-100-angular/graph\n"
  },
  {
    "path": "algorithms/HCNNG/scripts/glove25",
    "content": "# bash\n\nnumactl -i all ./neighbors -R 150 -L 300 -alpha 1 -data_type float -file_type bin -dist_func mips -base_path data/glove-25-angular/glove-25-angular_base.fbin -query_path data/glove-25-angular/glove-25-angular_query.fbin -gt_path data/glove-25-angular/glove-25-angular_groundtruth -num_passes 2 -quantize_build -normalize -verbose\n"
  },
  {
    "path": "algorithms/HCNNG/scripts/nytimes",
    "content": "# bash\nBUILD_ARGS=\"-cluster_size 1000 -mst_deg 3 -num_clusters 30 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -verbose\"\nTYPE_ARGS=\"-data_type float -dist_func mips -normalize -file_type bin\"\n\nPATH=data/nytimes-256-angular\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_hcnng_1000_3_30\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n\n"
  },
  {
    "path": "algorithms/HCNNG/scripts/sift",
    "content": "# bash\nBUILD_ARGS=\"-cluster_size 1000 -mst_deg 3 -num_clusters 30 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 8 -verbose\"\nTYPE_ARGS=\"-data_type float -dist_func Euclidian -file_type bin\"\n\nPATH=data/sift-128-euclidean\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_hcnng_1000_3_30\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/HNSW/CMakeLists.txt",
    "content": "# add_executable(neighbors-hnsw ../bench/neighborsTime.C)\n#   target_link_libraries(neighbors-hnsw PRIVATE parlay)\n#   target_precompile_headers(neighbors-hnsw PRIVATE HNSW.hpp)\n\n"
  },
  {
    "path": "algorithms/HNSW/HNSW.hpp",
    "content": "#ifndef _HNSW_HPP\n#define _HNSW_HPP\n\n#include <cstdint>\n#include <cstdio>\n#include <cstdarg>\n#include <cassert>\n#include <cmath>\n#include <algorithm>\n#include <numeric>\n#include <random>\n#include <memory>\n#include <atomic>\n#include <fstream>\n#include <string>\n#include <vector>\n#include <unordered_set>\n#include <unordered_map>\n#include <queue>\n#include <set>\n#include <iterator>\n#include <type_traits>\n#include <limits>\n#include <thread>\n// #include \"parallelize.h\"\n#include <parlay/parallel.h>\n#include <parlay/primitives.h>\n#include <parlay/delayed_sequence.h>\n#include <parlay/random.h>\n#include \"debug.hpp\"\n#include \"../utils/beamSearch.h\"\n#define DEBUG_OUTPUT 0\n#if DEBUG_OUTPUT\n#define debug_output(...) fprintf(stderr, __VA_ARGS__)\n#else\n#define debug_output(...) do{[](...){}(__VA_ARGS__);}while(0)\n#endif // DEBUG_OUTPUT\n\nnamespace ANN{\n\n  using namespace parlayANN;\n  \nenum class type_metric{\n\tL2, ANGULAR, DOT\n};\n\nstruct point{\n\tfloat x, y;\n};\n\ntemplate<typename U, template<typename> class Allocator=std::allocator>\nclass HNSW\n{\n\tusing T = typename U::type_point;\n\ttypedef uint32_t node_id;\npublic:\n\t/*\n\t\tConstruct from the vectors [begin, end).\n\t\tstd::iterator_trait<Iter>::value_type ought to be convertible to T\n\t\tdim: \t\t\t\tvector dimension\n\t\tm_l: \t\t\t\tcontrol the # of levels (larger m_l leads to more layer)\n\t\tm: \t\t\t\t\tmax degree\n\t\tef_construction:\tbeam size during the construction\n\t\talpha:\t\t\t\tparameter of the heuristic (similar to the one in vamana)\n\t\tbatch_base: \t\tgrowth rate of the batch size (discarded because of two passes)\n\t*/\n\ttemplate<typename Iter>\n\tHNSW(Iter begin, Iter end, uint32_t dim, float m_l=1, uint32_t m=100, uint32_t ef_construction=50, float alpha=5, float batch_base=2);\n\n\t/*\n\t\tConstruct from the saved model\n\t\tgetter(i) returns the actual data (convertible to type T) of the vector with id i\n\t*/\n\ttemplate<typename G>\n\tHNSW(const std::string &filename_model, G getter);\n\n\tparlay::sequence<std::pair<uint32_t,float>> search(\n\t\tconst T &q, uint32_t k, uint32_t ef, const search_control &ctrl={}\n\t);\n\t// parlay::sequence<std::tuple<uint32_t,uint32_t,float>> search_ex(const T &q, uint32_t k, uint32_t ef, uint64_t verbose=0);\n\t// save the current model to a file\n\tvoid save(const std::string &filename_model) const;\npublic:\n\ttypedef uint32_t type_index;\n\n\tstruct node{\n\t\t// uint32_t id;\n\t\tuint32_t level;\n\t\tparlay::sequence<node_id> *neighbors;\n\t\tT data;\n\t};\n\n\tstruct dist{\n\t\tfloat d;\n\t\tnode_id u;\n\n\t\tconstexpr bool operator<(const dist &rhs) const{\n\t\treturn d<rhs.d;\n\t\t}\n\n\t\tconstexpr bool operator>(const dist &rhs) const{\n\t\t\treturn d>rhs.d;\n\t\t}\n\t};\n\n\tstruct dist_ex : dist\n\t{\n\t\tuint32_t depth;\n\t};\n\n\tstruct nearest{\n\t\tconstexpr bool operator()(const dist &lhs, const dist &rhs) const{\n\t\t\treturn lhs.d>rhs.d;\n\t\t}\n\t};\n\n\tstruct farthest{\n\t\tconstexpr bool operator()(const dist &lhs, const dist &rhs) const{\n\t\t\treturn lhs.d<rhs.d;\n\t\t}\n\t};\n\n/*\n\tstruct cmp_id{\n\t\tconstexpr bool operator()(const dist &lhs, const dist &rhs) const{\n\t\t\treturn U::get_id(get_node(lhs.u).data)<U::get_id(get_node(rhs.u).data);\n\t\t}\n\t};\n*/\n\tparlay::sequence<node_id> entrance; // To init\n\t// auto m, max_m0, m_L; // To init\n\tuint32_t dim;\n\tfloat m_l;\n\tuint32_t m;\n\t// uint32_t level_max = 30; // To init\n\tuint32_t ef_construction;\n\tfloat alpha;\n\tuint32_t n;\n\tAllocator<node> allocator;\n\tparlay::sequence<node> node_pool;\n\tmutable parlay::sequence<size_t> total_visited = parlay::sequence<size_t>(parlay::num_workers());\n\tmutable parlay::sequence<size_t> total_eval = parlay::sequence<size_t>(parlay::num_workers());\n\tmutable parlay::sequence<size_t> total_size_C = parlay::sequence<size_t>(parlay::num_workers());\n\tmutable parlay::sequence<size_t> total_range_candidate = parlay::sequence<size_t>(parlay::num_workers());\n\n\tstatic auto neighbourhood(node &u, uint32_t level)\n\t\t-> parlay::sequence<node_id>&\n\t{\n\t\t// const constexpr auto level_none = std::numeric_limits<uint32_t>::max();\n\t\t// return level==level_none? u.final_nbh: u.neighbors[level];\n\t\t// return level==0? u.final_nbh: u.neighbors[level];\n\t\treturn u.neighbors[level];\n\t}\n\n\tstatic auto neighbourhood(const node &u, uint32_t level)\n\t\t-> const parlay::sequence<node_id>&\n\t{\n\t\treturn neighbourhood(const_cast<node&>(u),level);\n\t}\n\n\tnode& get_node(node_id id)\n\t{\n\t\treturn node_pool[id];\n\t}\n\n\tconst node& get_node(const node_id id) const\n\t{\n\t\treturn node_pool[id];\n\t}\n\n/*\n\tstatic void add_connection(parlay::sequence<node_id> &neighbors, node &u, uint32_t level)\n\t{\n\t\tfor(auto pv : neighbors)\n\t\t{\n\t\t\tassert(&u!=pv);\n\t\t\tpv->neighbors[level].push_back(&u);\n\t\t\tu.neighbors[level].push_back(pv);\n\t\t}\n\t}\n*/\n\tclass dist_evaluator{\n\t\tusing point_t = T;\n\t\tusing dist_t = float;\n\n\t\tstd::reference_wrapper<const point_t> p;\n\t\tuint32_t dim;\n\tpublic:\n\t\tdist_evaluator(const point_t &p, uint32_t dim) :\n\t\t\tp(p), dim(dim){\n\t\t}\n\t\tdist_t operator()(const point_t &pv) const{\n\t\t\treturn U::distance(p, pv, dim);\n\t\t}\n\t\tdist_t operator()(const point_t &pu, const point_t &pv) const{\n\t\t\treturn U::distance(pu, pv, dim);\n\t\t}\n\t};\n\n\tstruct graph{\n\t\ttemplate<class Nbh>\n\t\tstruct edgeRange{\n\t\t\tedgeRange(Nbh &nbh) : nbh(nbh){\n\t\t\t}\n\t\t\tdecltype(auto) operator[](node_id pu) const{\n\t\t\t\treturn nbh.get()[pu];\n\t\t\t}\n\t\t\tauto size() const{\n\t\t\t\treturn nbh.get().size();\n\t\t\t}\n\t\t\tvoid prefetch() const{\n\t\t\t\tint l = (size() * sizeof(node_id))/64;\n\t\t\t\tfor (int i=0; i < l; i++)\n\t\t\t\t\t__builtin_prefetch((char*) nbh.get().data() + i*64);\n\t\t\t}\n\n\t\t\tstd::reference_wrapper<Nbh> nbh;\n\t\t};\n\n\t\tusing nid_t = node_id;\n\n\t\tgraph(const HNSW<U,Allocator> &hnsw, uint32_t l) :\n\t\t\thnsw(hnsw), l(l){\n\t\t}\n\n\t\tdecltype(auto) num_nodes() const{\n\t\t\treturn hnsw.get().n;\n\t\t}\n\t\tdecltype(auto) get_node(node_id pu) const{\n\t\t\treturn hnsw.get().get_node(pu);\n\t\t}\n\t\tdecltype(auto) get_edges(node_id pu){\n\t\t\treturn hnsw.get().neighbourhood(hnsw.get().get_node(pu),l);\n\t\t}\n\t\tdecltype(auto) get_edges(node_id pu) const{\n\t\t\treturn hnsw.get().neighbourhood(hnsw.get().get_node(pu),l);\n\t\t}\n\n\t\tuint32_t max_degree() const{\n\t\t\treturn hnsw.get().get_threshold_m(l);\n\t\t}\n\n\t\tauto operator[](node_id pu){\n\t\t\treturn edgeRange(get_edges(pu));\n\t\t}\n\t\tauto operator[](node_id pu) const{\n\t\t\treturn edgeRange(get_edges(pu));\n\t\t}\n\n\t\tstd::reference_wrapper<const HNSW<U,Allocator>> hnsw;\n\t\tuint32_t l;\n\t};\n\n\t// node* insert(const T &q, uint32_t id);\n\ttemplate<typename Iter>\n\tvoid insert(Iter begin, Iter end, bool from_blank);\n\n\ttemplate<typename Queue>\n\tvoid select_neighbors_simple_impl(const T &u, Queue &C, uint32_t M)\n\t{\n\t\t/*\n\t\tlist res;\n\t\tfor(uint32_t i=0; i<M; ++i)\n\t\t{\n\t\t\tres.insert(C.pop_front());\n\t\t}\n\t\treturn res;\n\t\t*/\n\t\t(void)u;\n\t\tparlay::sequence<typename Queue::value_type> tie;\n\t\tfloat dist_tie = 1e20;\n\t\twhile(C.size()>M)\n\t\t{\n\t\t\tconst auto &t = C.top();\n\t\t\tif(t.d+1e-6<dist_tie) // t.d<dist_tie\n\t\t\t{\n\t\t\t\tdist_tie = t.d;\n\t\t\t\ttie.clear();\n\t\t\t}\n\t\t\tif(fabs(dist_tie-t.d)<1e-6) // t.d==dist_tie\n\t\t\t\ttie.push_back(t);\n\t\t\tC.pop();\n\t\t}\n\t\tif(fabs(dist_tie-C.top().d)<1e-6) // C.top().d==dist_tie\n\t\t\twhile(!tie.empty())\n\t\t\t{\n\t\t\t//\tC.push({dist_tie,tie.back()});\n\t\t\t\tC.push(tie.back());\n\t\t\t\ttie.pop_back();\n\t\t\t}\n\t}\n\n\ttemplate<typename Queue>\n\tauto select_neighbors_simple(const T &u, const Queue &C, uint32_t M)\n\t{\n\t\t// The parameter C is intended to be copy constructed\n\t\t/*\n\t\tselect_neighbors_simple_impl(u, C, M);\n\t\treturn C;\n\t\t*/\n\t\t// auto R = parlay::sort(C, farthest());\n\t\tauto R = C;\n\t\t\n\t\tif(R.size()>M)\n\t\t{\n\t\t\tstd::nth_element(R.begin(), R.begin()+M, R.end(), farthest());\n\t\t\tR.resize(M);\n\t\t}\n\t\t\n\t\tstd::sort(R.begin(), R.end(), farthest());\n\t\t// if(R.size()>M) R.resize(M);\n\t\t/*\n\t\tuint32_t size_R = std::min(C.size(),M);\n\t\tparlay::sequence<node*> R;\n\t\tR.reserve(size_R);\n\t\tfor(const auto &e : C)\n\t\t\tR.push_back(e.u);\n\t\t*/\n\n\t\treturn R;\n\t}\n\n\t// To optimize\n\tauto select_neighbors_heuristic(const T &u, \n\t\t/*const std::priority_queue<dist,parlay::sequence<dist>,farthest> &C*/\n\t\tconst parlay::sequence<dist> &C, uint32_t M,\n\t\tuint32_t level, bool extendCandidate, bool keepPrunedConnections)\n\t{\n\t\t(void)extendCandidate;\n\n\t\t// std::priority_queue<dist,parlay::sequence<dist>,farthest> C_cp=C, W_d;\n\t\tparlay::sequence<dist> W_d;\n\t\tstd::set<node_id> W_tmp;\n\t\t// while(!C_cp.empty())\n\t\tfor(auto &e : C) // TODO: add const?\n\t\t{\n\t\t\t// auto &e = C_cp.top();\n\t\t\tW_tmp.insert(e.u);\n\t\t\tif(extendCandidate)\n\t\t\t{\n\t\t\t\tfor(node_id e_adj : neighbourhood(get_node(e.u),level))\n\t\t\t\t{\n\t\t\t\t\t// if(e_adj==nullptr) continue; // TODO: check\n\t\t\t\t\tif(W_tmp.find(e_adj)==W_tmp.end())\n\t\t\t\t\t\tW_tmp.insert(e_adj);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// C_cp.pop();\n\t\t}\n\n\t\t// std::priority_queue<dist,parlay::sequence<dist>,nearest> W;\n\t\tparlay::sequence<dist> W;\n\t\tW.reserve(W_tmp.size());\n\t\tfor(node_id p : W_tmp)\n\t\t\tW.push_back({U::distance(get_node(p).data,u,dim), p});\n\t\tstd::sort(W.begin(), W.end(), farthest());\n\t\t/*\n\t\tfor(auto &e : W_tmp)\n\t\t\tW.push(e);\n\t\t*/\n\t\tW_tmp.clear();\n\n\t\tparlay::sequence<node_id> R;\n\t\tstd::set<node_id> nbh;\n\t\t// while(W.size()>0 && R.size()<M)\n\t\tfor(const auto &e : W)\n\t\t{\n\t\t\tif(R.size()>=M) break;\n\t\t\t// const auto e = W.top();\n\t\t\t// W.pop();\n\t\t\tconst auto d_q = e.d;\n\n\t\t\tbool is_good = true;\n\t\t\tfor(const auto &r : R)\n\t\t\t{\n\t\t\t\tconst auto d_r = U::distance(get_node(e.u).data, get_node(r).data, dim);\n\t\t\t\t//if(d_r*(level+1)>d_q*alpha*(entrance->level+1))\n\t\t\t\tif(d_r<d_q*alpha)\n\t\t\t\t{\n\t\t\t\t\tis_good = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t/*\n\t\t\t\tfor(auto *pv : neighbourhood(*e.u,level))\n\t\t\t\t\tif(pv==e.u)\n\t\t\t\t\t{\n\t\t\t\t\t\tis_good = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t*/\n\t\t\t\t/*\n\t\t\t\tif(nbh.find(e.u)!=nbh.end())\n\t\t\t\t\tis_good = false;\n\t\t\t\t*/\n\t\t\t}\n\n\t\t\tif(is_good)\n\t\t\t{\n\t\t\t\tR.push_back(e.u);\n\t\t\t\t/*\t\t\t\t\n\t\t\t\tfor(auto *pv : neighbourhood(*e.u,level))\n\t\t\t\t\tnbh.insert(pv);\n\t\t\t\t*/\n\t\t\t}\n\t\t\telse\n\t\t\t\tW_d.push_back(e);\n\t\t}\n\n\t\t// std::sort(W_d.begin(), W_d.end(), nearest());\n\t\tauto it = W_d.begin();\n\t\t// std::priority_queue<dist,parlay::sequence<dist>,farthest> res;\n\t\tauto &res = R;\n\t\t/*\n\t\tfor(const auto &r : R)\n\t\t{\n\t\t\tres.push({U::distance(u,get_node(r).data,dim), r});\n\t\t}\n\t\t*/\n\t\tif(keepPrunedConnections)\n\t\t{\n\t\t\t// while(W_d.size()>0 && res.size()<M)\n\t\t\t\t// res.push(W_d.top()), W_d.pop();\n\t\t\twhile(it!=W_d.end() && res.size()<M)\n\t\t\t\t// res.push(*(it++));\n\t\t\t\tres.push_back((it++)->u);\n\t\t}\n\t\treturn res;\n\t}\n\n\ttemplate<class Seq_, class D, class G, class Seq=std::remove_cv_t<std::remove_reference_t<Seq_>>>\n\tSeq prune_heuristic(\n\t\tSeq_ &&cand, uint32_t size, D f_dist, G g) const\n\t{\n\t\tusing nid_t = node_id;\n\t\tusing conn = dist;\n\n\t\tSeq workset = std::forward<Seq_>(cand);\n\t\t/*\n\t\tif(ctrl.extend_nbh)\n\t\t{\n\t\t\tconst auto &g = ctrl.graph;\n\t\t\tstd::unordered_set<nid_t> cand_ext;\n\t\t\tfor(const conn &c : workset)\n\t\t\t{\n\t\t\t\tcand_ext.insert(c.u);\n\t\t\t\tfor(nid_t pv : g.get_edges(c.u))\n\t\t\t\t\tcand_ext.insert(pv);\n\t\t\t}\n\n\t\t\tworkset.reserve(workset.size()+cand_ext.size());\n\t\t\tfor(nid_t pc : cand_ext)\n\t\t\t\tworkset.push_back({f_dist(g.get_node(pc).get_coord()), pc});\n\t\t\tcand_ext.clear();\n\t\t}\n\t\t*/\n\t\tparlay::sort_inplace(workset);\n\n\t\tSeq res, pruned;\n\t\tstd::unordered_set<nid_t> nbh;\n\t\tfor(conn &c : workset)\n\t\t{\n\t\t\tconst auto d_cu = c.d*alpha;\n\n\t\t\tbool is_pruned = false;\n\t\t\tfor(const conn &r : res)\n\t\t\t{\n\t\t\t\tconst auto d_cr = f_dist(\n\t\t\t\t\tg.get_node(c.u).data,\n\t\t\t\t\tg.get_node(r.u).data\n\t\t\t\t);\n\t\t\t\tif(d_cr<d_cu)\n\t\t\t\t{\n\t\t\t\t\tis_pruned = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif(!is_pruned)\n\t\t\t{\n\t\t\t\tres.push_back(std::move(c));\n\t\t\t\tif(res.size()==size) break;\n\t\t\t}\n\t\t\telse pruned.push_back(std::move(c));\n\t\t}\n\t\treturn res;\n\t}\n\n\tauto select_neighbors(const T &u, \n\t\t/*const std::priority_queue<dist,parlay::sequence<dist>,farthest> &C,*/\n\t\tconst parlay::sequence<dist> &C, uint32_t M,\n\t\tuint32_t level, bool extendCandidate=false, bool keepPrunedConnections=false)\n\t{\n\t\t/*\n\t\t(void)level, (void)extendCandidate, (void)keepPrunedConnections;\n\t\treturn select_neighbors_simple(u,C,M);\n\t\t*/\n\t\t// return select_neighbors_heuristic(u, C, M, level, extendCandidate, keepPrunedConnections);\n\n\t\tdist_evaluator f_dist(u, dim);\n\t\tgraph g(*this, level);\n\t\tauto res = prune_heuristic(C, M, f_dist, g);\n\t\treturn parlay::tabulate(res.size(), [&](size_t i){return res[i].u;});\n\t}\n\n\tuint32_t get_level_random()\n\t{\n\t\t// static thread_local int32_t anchor;\n\t\t// uint32_t esp;\n\t\t// asm volatile(\"movl %0, %%esp\":\"=a\"(esp));\n\t\t// static thread_local std::hash<std::thread::id> h;\n\t\t// static thread_local std::mt19937 gen{h(std::this_thread::get_id())};\n\t\tstatic thread_local std::mt19937 gen{parlay::worker_id()};\n\t\tstatic thread_local std::uniform_real_distribution<> dis(std::numeric_limits<float>::min(), 1.0);\n\t\tconst uint32_t res = uint32_t(-log(dis(gen))*m_l);\n\t\treturn res;\n\t}\n\n\t// auto search_layer(const node &u, const parlay::sequence<node_id> &eps, uint32_t ef, uint32_t l_c, uint64_t verbose=0) const; // To static\n\tauto search_layer(const node &u, const parlay::sequence<node_id> &eps, uint32_t ef, uint32_t l_c, search_control ctrl={}) const; // To static\n\tauto search_layer_bak(const node &u, const parlay::sequence<node_id> &eps, uint32_t ef, uint32_t l_c, search_control ctrl={}) const; // To static\n\tauto search_layer_new_ex(const node &u, const parlay::sequence<node_id> &eps, uint32_t ef, uint32_t l_c, search_control ctrl={}) const; // To static\n\tauto beam_search_ex(const node &u, const parlay::sequence<node_id> &eps, uint32_t beamSize, uint32_t l_c, search_control ctrl={}) const;\n\tparlay::sequence<node_id> search_layer_to(\n\t\tconst node &u, uint32_t ef, uint32_t l_stop, const search_control &ctrl={}\n\t);\n\n\tauto get_threshold_m(uint32_t level) const{\n\t\treturn level==0? m*2: m;\n\t\t// (void)level;\n\t\t// return m;\n\t}\n\npublic:\n\tauto get_deg(uint32_t level=0)\n\t{\n\t\tparlay::sequence<uint32_t> res;\n\t\tres.reserve(node_pool.size());\n\t\tfor(const node &e : node_pool)\n\t\t{\n\t\t\tif(e.level>=level)\n\t\t\t\tres.push_back(e.neighbors[level].size());\n\t\t}\n\t\treturn res;\n\t}\n\n\tauto get_indeg(uint32_t level) const\n\t{\n\t\tstatic uint32_t *indeg[16] = {nullptr};\n\t\tauto *&res = indeg[level];\n\t\tif(!res)\n\t\t{\n\t\t\tres = new uint32_t[n];\n\t\t\tfor(uint32_t i=0; i<n; ++i)\n\t\t\t\tres[i] = 0;\n\t\t\tfor(const node_id pu : node_pool)\n\t\t\t{\n\t\t\t\tif(get_node(pu).level<level) continue;\n\t\t\t\tfor(const node_id pv : get_node(pu).neighbors[level])\n\t\t\t\t\tres[U::get_id(get_node(pv).data)]++;\n\t\t\t}\n\t\t}\n\t\treturn res;\n\t}\n\n\tuint32_t get_height() const\n\t{\n\t\treturn get_node(entrance[0]).level;\n\t}\n\n\tsize_t cnt_degree(uint32_t l) const\n\t{\n\t\tauto cnt_each = parlay::delayed_seq<size_t>(n, [&](size_t i){\n\t\t\tnode_id pu = i;\n\t\t\treturn get_node(pu).level<l? 0:\n\t\t\t\tneighbourhood(get_node(pu),l).size();\n\t\t});\n\t\treturn parlay::reduce(cnt_each, parlay::addm<size_t>());\n\t}\n\n\tsize_t cnt_vertex(uint32_t l) const\n\t{\n\t\tauto cnt_each = parlay::delayed_seq<size_t>(n, [&](size_t i){\n\t\t\tnode_id pu = i;\n\t\t\treturn get_node(pu).level<l? 0: 1;\n\t\t});\n\t\treturn parlay::reduce(cnt_each, parlay::addm<size_t>());\n\t}\n\n\tsize_t get_degree_max(uint32_t l) const\n\t{\n\t\tauto cnt_each = parlay::delayed_seq<size_t>(n, [&](size_t i){\n\t\t\tnode_id pu = i;\n\t\t\treturn get_node(pu).level<l? 0:\n\t\t\t\tneighbourhood(get_node(pu),l).size();\n\t\t});\n\t\treturn parlay::reduce(cnt_each, parlay::maxm<size_t>());\n\t}\n/*\n\tvoid debug_output_graph(uint32_t l)\n\t{\n\t\t// return;\n\t\tdebug_output(\"Printing the graph at level %u\\n\", l);\n\t\tauto node_exist = parlay::pack(\n\t\t\tnode_pool,\n\t\t\tparlay::delayed_seq<bool>(node_pool.size(),[&](size_t i){\n\t\t\t\treturn node_pool[i]->level>=l;\n\t\t\t})\n\t\t);\n\t\tconst auto num_vertices = node_exist.size();\n\t\tconst auto num_edges = parlay::reduce(\n\t\t\tparlay::delayed_seq<uint64_t>(node_exist.size(),[&](size_t i){\n\t\t\t\treturn node_exist[i]->neighbors[l].size();\n\t\t\t}),\n\t\t\tparlay::addm<uint64_t>{}\n\t\t);\n\t\tdebug_output(\"# vertices: %lu, # edges: %llu\\n\", num_vertices, num_edges);\n\n\t\tfor(node_id pu : node_exist)\n\t\t{\n\t\t\tdebug_output(\"node_id: %u\\n\", U::get_id(get_node(pu).data));\n\t\t\t// if(!res[i]) continue;\n\t\t\tdebug_output(\"\\tneighbors:\");\n\t\t\tfor(node_id pv : neighbourhood(get_node(pu),l))\n\t\t\t\tdebug_output(\" %u\", U::get_id(get_node(pv).data));\n\t\t\tdebug_output(\"\\n\");\n\t\t}\n\t}\n*/\n};\n\ntemplate<typename U, template<typename> class Allocator>\ntemplate<typename G>\nHNSW<U,Allocator>::HNSW(const std::string &filename_model, G getter)\n{\n\tstd::ifstream model(filename_model, std::ios::binary);\n\tif(!model.is_open())\n\t\tthrow std::runtime_error(\"Failed to open the model\");\n\n\tconst auto size_buffer = 1024*1024*1024; // 1G\n\tauto buffer = std::make_unique<char[]>(size_buffer);\n\tmodel.rdbuf()->pubsetbuf(buffer.get(), size_buffer);\n\n\tauto read = [&](auto &data, auto ...args){\n\t\tauto read_impl = [&](auto &f, auto &data, auto ...args){\n\t\t\tusing T = std::remove_reference_t<decltype(data)>;\n\t\t\tif constexpr(std::is_pointer_v<std::decay_t<T>>)\n\t\t\t{\n\t\t\t\tauto read_array = [&](auto &data, size_t size, auto ...args){\n\t\t\t\t\tfor(size_t i=0; i<size; ++i)\n\t\t\t\t\t\tf(f, data[i], args...);\n\t\t\t\t};\n\t\t\t\t// use the array extent as the size\n\t\t\t\tif constexpr(sizeof...(args)==0 && std::is_array_v<T>)\n\t\t\t\t{\n\t\t\t\t\tread_array(data, std::extent_v<T>);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tstatic_assert(sizeof...(args), \"size was not provided\");\n\t\t\t\t\tread_array(data, args...);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstatic_assert(std::is_standard_layout_v<T>);\n\t\t\t\tmodel.read((char*)&data, sizeof(data));\n\t\t\t}\n\t\t};\n\t\tread_impl(read_impl, data, args...);\n\t};\n\n\tchar model_type[5] = {'\\000'};\n\tread(model_type, 4);\n\tif(strcmp(model_type,\"HNSW\"))\n\t\tthrow std::runtime_error(\"Wrong type of model\");\n\tuint32_t version;\n\tread(version);\n\tif(version!=3)\n\t\tthrow std::runtime_error(\"Unsupported version\");\n\n\tsize_t code_U, size_node;\n\tread(code_U);\n\tread(size_node);\n\tfprintf(stderr, \"U type loading %s\\n\", typeid(U).name());\n\t// if((typeid(U).hash_code()^sizeof(U))!=code_U)\n\t\t// throw std::runtime_error(\"Inconsistent type `U`\");\n\t// if(sizeof(node)!=size_node)\n\t\t// throw std::runtime_error(\"Inconsistent type `node`\");\n\n\t// read parameter configuration\n\tread(dim);\n\tread(m_l);\n\tread(m);\n\tread(ef_construction);\n\tread(alpha);\n\tread(n);\n\tputs(\"Configuration loaded\");\n\tprintf(\"dim = %u\\n\", dim);\n\tprintf(\"m_l = %f\\n\", m_l);\n\tprintf(\"m = %u\\n\", m);\n\tprintf(\"efc = %u\\n\", ef_construction);\n\tprintf(\"alpha = %f\\n\", alpha);\n\tprintf(\"n = %u\\n\", n);\n\t// read indices\n\t// std::unordered_map<uint32_t,node*> addr;\n\tnode_pool.resize(n);\n\tfor(uint32_t i=0; i<n; ++i)\n\t{\n\t\t// auto *u = new node;\n\t\tnode &u = get_node(i);\n\t\tread(u.level);\n\t\tuint32_t id_u; // TODO: use generic type\n\t\tread(id_u);\n\t\tu.data = getter(id_u);\n\t\t// addr[id_u] = u;\n\t}\n\tfor(node &u : node_pool)\n\t{\n\t\tu.neighbors = new parlay::sequence<node_id>[u.level+1];\n\t\tfor(uint32_t l=0; l<=u.level; ++l)\n\t\t{\n\t\t\tsize_t size;\n\t\t\tread(size);\n\t\t\tauto &nbh_u = u.neighbors[l];\n\t\t\tnbh_u.reserve(size);\n\t\t\tfor(size_t i=0; i<size; ++i)\n\t\t\t{\n\t\t\t\tuint32_t id_v;\n\t\t\t\tread(id_v);\n\t\t\t\tnbh_u.push_back(id_v);\n\t\t\t}\n\t\t}\n\t}\n\t// read entrances\n\tsize_t size;\n\tread(size);\n\tentrance.reserve(size);\n\tfor(size_t i=0; i<size; ++i)\n\t{\n\t\tuint32_t id_u;\n\t\tread(id_u);\n\t\tentrance.push_back(id_u);\n\t}\n}\n\ntemplate<typename U, template<typename> class Allocator>\ntemplate<typename Iter>\nHNSW<U,Allocator>::HNSW(Iter begin, Iter end, uint32_t dim_, float m_l_, uint32_t m_, uint32_t ef_construction_, float alpha_, float batch_base)\n\t: dim(dim_), m_l(m_l_), m(m_), ef_construction(ef_construction_), alpha(alpha_), n(std::distance(begin,end))\n{\n\tstatic_assert(std::is_same_v<typename std::iterator_traits<Iter>::value_type, T>);\n\tstatic_assert(std::is_base_of_v<\n\t\tstd::random_access_iterator_tag, typename std::iterator_traits<Iter>::iterator_category>);\n\n\tif(n==0) return;\n\n\tstd::random_device rd;\n\tauto perm = parlay::random_permutation<uint32_t>(n, rd());\n\tauto rand_seq = parlay::delayed_seq<T>(n, [&](uint32_t i){\n\t\t//return *(begin+perm[i]);\n\t\treturn *(begin+i);\n\t});\n\n\tconst auto level_ep = get_level_random();\n\t// node *entrance_init = allocator.allocate(1);\n\t// node_pool.push_back(entrance_init);\n\tnode_pool.resize(1);\n\tnode_id entrance_init = 0;\n\tnew(&get_node(entrance_init)) node{\n\t\tlevel_ep, \n\t\tnew parlay::sequence<node_id>[level_ep+1], \n\t\t*rand_seq.begin()\n\t\t/*anything else*/\n\t};\n\tentrance.push_back(entrance_init);\n\n\tuint32_t batch_begin=0, batch_end=1, size_limit=n*0.02;\n\tfloat progress = 0.0;\n\twhile(batch_end<n)\n\t{\n\t\tbatch_begin = batch_end;\n\t\tbatch_end = std::min({n, (uint32_t)std::ceil(batch_begin*batch_base)+1, batch_begin+size_limit});\n\t\t/*\n\t\tif(batch_end>batch_begin+100)\n\t\t\tbatch_end = batch_begin+100;\n\t\t*/\n\t\t// batch_end = batch_begin+1;\n\n\t\tinsert(rand_seq.begin()+batch_begin, rand_seq.begin()+batch_end, true);\n\t\t// insert(rand_seq.begin()+batch_begin, rand_seq.begin()+batch_end, false);\n\n\t\tif(batch_end>n*(progress+0.05))\n\t\t{\n\t\t\tprogress = float(batch_end)/n;\n\t\t\tfprintf(stderr, \"Built: %3.2f%%\\n\", progress*100);\n\t\t\t// fprintf(stderr, \"# visited: %lu\\n\", parlay::reduce(total_visited,parlay::addm<size_t>{}));\n\t\t\t// fprintf(stderr, \"# eval: %lu\\n\", parlay::reduce(total_eval,parlay::addm<size_t>{}));\n\t\t\t// fprintf(stderr, \"size of C: %lu\\n\", parlay::reduce(total_size_C,parlay::addm<size_t>{}));\n\t\t}\n\t}\n\n\t// fprintf(stderr, \"# visited: %lu\\n\", parlay::reduce(total_visited,parlay::addm<size_t>{}));\n\t// fprintf(stderr, \"# eval: %lu\\n\", parlay::reduce(total_eval,parlay::addm<size_t>{}));\n\t// fprintf(stderr, \"size of C: %lu\\n\", parlay::reduce(total_size_C,parlay::addm<size_t>{}));\n\tfprintf(stderr, \"Index built\\n\");\n\n\t#if 0\n\t\tfor(const auto *pu : node_pool)\n\t\t{\n\t\t\tfprintf(stderr, \"[%u] (%.2f,%.2f)\\n\", U::get_id(get_node(pu).data), get_node(pu).data[0], get_node(pu).data[1]);\n\t\t\tfor(int32_t l=pu->level; l>=0; --l)\n\t\t\t{\n\t\t\t\tfprintf(stderr, \"\\tlv. %d:\", l);\n\t\t\t\tfor(const auto *k : pu->neighbors[l])\n\t\t\t\t\tfprintf(stderr, \" %u\", U::get_id(get_node(k).data));\n\t\t\t\tfputs(\"\\n\", stderr);\n\t\t\t}\n\t\t}\n\t#endif\n/*\n\tfor(uint32_t l=0; l<entrance[0]->level; ++l)\n\t\tdebug_output_graph(l);\n*/\n}\n\ntemplate<typename U, template<typename> class Allocator>\ntemplate<typename Iter>\nvoid HNSW<U,Allocator>::insert(Iter begin, Iter end, bool from_blank)\n{\n\tconst auto level_ep = get_node(entrance[0]).level;\n\tconst auto size_batch = std::distance(begin,end);\n\tauto node_new = std::make_unique<node_id[]>(size_batch);\n\tauto nbh_new = std::make_unique<parlay::sequence<node_id>[]>(size_batch);\n\tauto eps = std::make_unique<parlay::sequence<node_id>[]>(size_batch);\n\t//const float factor_m = from_blank? 0.5: 1;\n\tconst auto factor_m = 1;\n\n\tdebug_output(\"Insert %lu elements; from blank? [%c]\\n\", size_batch, \"NY\"[from_blank]);\n\n\t// auto *pool = allocator.allocate(size_batch);\n\t// first, query the nearest point as the starting point for each node to insert\n\tif(from_blank)\n\t{\n\t\tauto offset = node_pool.size();\n\t\tnode_pool.resize(offset+size_batch);\n\tparlay::parallel_for(0, size_batch, [&](uint32_t i){\n\t\tconst T &q = *(begin+i);\n\t\tconst auto level_u = get_level_random();\n\t\t// auto *const pu = &pool[i];\t\t// TODO: add pointer manager\n\t\tnode_id pu = offset+i;\n\n\t\tnew(&get_node(pu)) node{\n\t\t\tlevel_u,\n\t\t\tnew parlay::sequence<node_id>[level_u+1],\n\t\t\tq\n\t\t};\n\t\tnode_new[i] = pu;\n\t});\n\t}\n\telse\n\t{\n\tparlay::parallel_for(0, size_batch, [&](uint32_t i){\n\t\tnode_new[i] = node_pool.size()-size_batch+i;\n\t});\n\t}\n\n\tdebug_output(\"Nodes are settled\\n\");\n\t// TODO: merge ops\n\tparlay::parallel_for(0, size_batch, [&](uint32_t i){\n\t\tauto &u = get_node(node_new[i]);\n\t\tconst auto level_u = u.level;\n\t\tauto &eps_u = eps[i]; \n\t\t// eps_u.push_back(entrance);\n\t\teps_u = entrance;\n\t\tfor(uint32_t l=level_ep; l>level_u; --l)\n\t\t{\n\t\t\tconst auto res = search_layer(u, eps_u, 1, l); // TODO: optimize\n\t\t\teps_u.clear();\n\t\t\teps_u.push_back(res[0].u);\n\t\t}\n\t});\n\n\tdebug_output(\"Finish searching entrances\\n\");\n\t// then we process them layer by layer (from high to low)\n\tfor(int32_t l_c=level_ep; l_c>=0; --l_c) // TODO: fix the type\n\t{\n\t\tparlay::sequence<parlay::sequence<std::pair<node_id,node_id>>> edge_add(size_batch);\n\n\t\tdebug_output(\"Finding neighbors on lev. %d\\n\", l_c);\n\t\tparlay::parallel_for(0, size_batch, [&](uint32_t i){\n\t\t\tnode_id pu = node_new[i];\n\t\t\tauto &u = get_node(pu);\n\t\t\tif((uint32_t)l_c>u.level) return;\n\n\t\t\tauto &eps_u = eps[i]; // TODO: check\n\t\t\tauto res = search_layer(u, eps_u, ef_construction, l_c);\n\t\t\tauto neighbors_vec = select_neighbors(u.data, res, get_threshold_m(l_c)/**factor_m*/, l_c);\n\t\t\t// move the content from `neighbors_vec` to `u.neighbors[l_c]`\n\t\t\t// auto &nbh_u = nbh_new[i];\n\t\t\tauto &edge_u = edge_add[i];\n\t\t\t// nbh_u.clear();\n\t\t\tedge_u.clear();\n\t\t\t// nbh_u.reserve(neighbors_vec.size());\n\t\t\tedge_u.reserve(neighbors_vec.size());\n\t\t\t/*\n\t\t\tfor(uint32_t j=0; neighbors_vec.size()>0; ++j)\n\t\t\t{\n\t\t\t\tauto *pv = neighbors_vec.top().u;\n\t\t\t\tneighbors_vec.pop();\n\t\t\t\t// nbh_u[j] = pv;\n\t\t\t\t// edge_u[j] = std::make_pair(pv, &u);\n\t\t\t\tnbh_u.push_back(pv);\n\t\t\t\tedge_u.emplace_back(pv, &u);\n\t\t\t}\n\t\t\t*/\n\t\t\tfor(node_id pv : neighbors_vec)\n\t\t\t\tedge_u.emplace_back(pv, pu);\n\t\t\tnbh_new[i] = std::move(neighbors_vec);\n\n\t\t\teps_u.clear();\n\t\t\t/*\n\t\t\twhile(res.size()>0)\n\t\t\t{\n\t\t\t\teps_u.push_back(res.top().u); // TODO: optimize\n\t\t\t\tres.pop();\n\t\t\t}\n\t\t\t*/\n\t\t\teps_u.reserve(res.size());\n\t\t\tfor(const auto e : res)\n\t\t\t\teps_u.push_back(e.u);\n\t\t});\n\n\t\tdebug_output(\"Adding forward edges\\n\");\n\t\tparlay::parallel_for(0, size_batch, [&](uint32_t i){\n\t\t\tauto &u = get_node(node_new[i]);\n\t\t\tif((uint32_t)l_c<=u.level)\n\t\t\t\tneighbourhood(u,l_c) = std::move(nbh_new[i]);\n\t\t});\n\n\t\tdebug_output(\"Adding reverse edges\\n\");\n\t\t// now we add edges in the other direction\n\t\tauto edge_add_flatten = parlay::flatten(edge_add);\n\t\tauto edge_add_grouped = parlay::group_by_key(edge_add_flatten);\n\n\t\tparlay::parallel_for(0, edge_add_grouped.size(), [&](size_t j){\n\t\t\tnode_id pv = edge_add_grouped[j].first;\n\t\t\tauto &nbh_v = neighbourhood(get_node(pv),l_c);\n\t\t\tauto &nbh_v_add = edge_add_grouped[j].second;\n\n\t\t\t// std::unordered_set<node_id> hash_table(nbh_v.begin(),nbh_v.end());\n\t\t\t/*\n\t\t\tfor(auto it=nbh_v_add.begin(); it!=nbh_v_add.end();)\n\t\t\t{\n\t\t\t\tbool is_extant = *it==pv||std::find_if(nbh_v.begin(), nbh_v.end(), [&](const node_id pu_extant){\n\t\t\t\t\treturn *it==pu_extant;\n\t\t\t\t})!=nbh_v.end();\n\t\t\t\t\n\t\t\t\t// bool is_extant = hash_table.find(*it)!=hash_table.end();\n\t\t\t\tit = is_extant? nbh_v_add.erase(it): std::next(it);\n\t\t\t}\n\t\t\t*/\n\n\t\t\tconst uint32_t size_nbh_total = nbh_v.size()+nbh_v_add.size();\n\n\t\t\tconst auto m_s = get_threshold_m(l_c)*factor_m;\n\t\t\tif(size_nbh_total>m_s)\n\t\t\t{\n\t\t\t\tauto candidates = parlay::sequence<dist>(size_nbh_total);\n\t\t\t\tfor(size_t k=0; k<nbh_v.size(); ++k)\n\t\t\t\t\tcandidates[k] = dist{U::distance(get_node(nbh_v[k]).data,get_node(pv).data,dim), nbh_v[k]};\n\t\t\t\tfor(size_t k=0; k<nbh_v_add.size(); ++k)\n\t\t\t\t\tcandidates[k+nbh_v.size()] = dist{U::distance(get_node(nbh_v_add[k]).data,get_node(pv).data,dim), nbh_v_add[k]};\n\n\t\t\t\tstd::sort(candidates.begin(), candidates.end(), farthest());\n\n\t\t\t\tnbh_v.resize(m_s);\n\t\t\t\tfor(size_t k=0; k<m_s; ++k)\n\t\t\t\t\tnbh_v[k] = candidates[k].u;\n\t\t\t\t/*\n\t\t\t\tauto res = select_neighbors(get_node(pv).data, candidates, m_s, l_c);\n\t\t\t\tnbh_v.clear();\n\t\t\t\tfor(auto *pu : res)\n\t\t\t\t\tnbh_v.push_back(pu);\n\t\t\t\t*/\n\t\t\t\t// nbh_v = select_neighbors(get_node(pv).data, candidates, m_s, l_c);\n\t\t\t}\n\t\t\telse nbh_v.insert(nbh_v.end(),nbh_v_add.begin(), nbh_v_add.end());\n\t\t});\n\t}\n\n\tdebug_output(\"Updating entrance\\n\");\n\t// finally, update the entrance\n\tnode_id node_highest = *std::max_element(\n\t\tnode_new.get(), node_new.get()+size_batch, [&](const node_id u, const node_id v){\n\t\t\treturn get_node(u).level < get_node(v).level;\n\t});\n\tif(get_node(node_highest).level>level_ep)\n\t{\n\t\tentrance.clear();\n\t\tentrance.push_back(node_highest);\n\t\tdebug_output(\"New entrance [%u] at lev %u\\n\", U::get_id(get_node(node_highest).data), get_node(node_highest).level);\n\t}\n\telse if(get_node(node_highest).level==level_ep)\n\t{\n\t\tentrance.push_back(node_highest);\n\t\tdebug_output(\"New entrance [%u] at lev %u\\n\", U::get_id(get_node(node_highest).data), get_node(node_highest).level);\n\t}\n\n\t// and add new nodes to the pool\n\t/*\n\tif(from_blank)\n\tnode_pool.insert(node_pool.end(), node_new.get(), node_new.get()+size_batch);\n\t*/\n}\n\ntemplate<class Conn, class G, class D, class Seq>\nauto beamSearch(\n\tconst G &g, D f_dist, const Seq &eps, uint32_t ef, const search_control &ctrl={})\n{\n\tusing nid_t = typename G::nid_t;\n\tusing conn = Conn;\n\n\tconst auto n = g.num_nodes();\n\tconst uint32_t bits = ef>2? std::ceil(std::log2(ef))*2-2: 2;\n\tconst uint32_t mask = (1u<<bits)-1;\n\tSeq visited(mask+1, n+1);\n\tuint32_t cnt_visited = 0;\n\tparlay::sequence<conn> workset;\n\tstd::set<conn> cand; // TODO: test dual heaps\n\tstd::unordered_set<nid_t> is_inw; // TODO: test merge instead\n\t// TODO: get statistics about the merged size\n\t// TODO: switch to the alternative if exceeding a threshold\n\tworkset.reserve(ef+1);\n\n\t// debug_output(\"look at eps\\n\");\n\tfor(nid_t pe : eps)\n\t{\n\t\tvisited[parlay::hash64(pe)&mask] = pe;\n\t\tconst auto d = f_dist(g.get_node(pe).data);\n\t\tcand.insert({d,pe});\n\t\tworkset.push_back({d,pe});\n\t\tis_inw.insert(pe);\n\t\tcnt_visited++;\n\t}\n\tstd::make_heap(workset.begin(), workset.end());\n\n\tuint32_t cnt_eval = 0;\n\tuint32_t limit_eval = ctrl.limit_eval.value_or(n);\n\twhile(cand.size()>0)\n\t{\n\t\tif(cand.begin()->d>workset[0].d*ctrl.beta) break;\n\n\t\tif(++cnt_eval>limit_eval) break;\n\n\t\tnid_t u = cand.begin()->u;\n\t\tcand.erase(cand.begin());\n\t\tfor(nid_t pv: g.get_edges(u))\n\t\t{\n\t\t\tconst auto h_pv = parlay::hash64_2(pv)&mask;\n\t\t\tif(visited[h_pv]==pv) continue;\n\t\t\tvisited[h_pv] = pv;\n\t\t\tcnt_visited++;\n\n\t\t\tconst auto d = f_dist(g.get_node(pv).data);\n\t\t\tif(!(workset.size()<ef||d<workset[0].d)) continue;\n\t\t\tif(!is_inw.insert(pv).second) continue;\n\n\t\t\tcand.insert({d,pv});\n\t\t\tworkset.push_back({d,pv});\n\t\t\tstd::push_heap(workset.begin(), workset.end());\n\t\t\tif(workset.size()>ef)\n\t\t\t{\n\t\t\t\tstd::pop_heap(workset.begin(), workset.end());\n\t\t\t\t// is_inw.erase(workset.back().u);\n\t\t\t\tworkset.pop_back();\n\t\t\t}\n\t\t\tif(cand.size()>ef)\n\t\t\t\tcand.erase(std::prev(cand.end()));\n\t\t}\n\t}\n\n\tif(ctrl.count_cmps)\n\t\t*ctrl.count_cmps.value() += cnt_visited;\n\n\treturn workset;\n}\n\ntemplate<typename U, template<typename> class Allocator>\nauto HNSW<U,Allocator>::search_layer(const node &u, const parlay::sequence<node_id> &eps, uint32_t ef, uint32_t l_c, search_control ctrl) const\n{\n\tgraph g(*this,l_c);\n\t/*\n\tdist_evaluator f_dist(u.data,dim);\n\treturn beamSearch<dist>(g, f_dist, eps, ef, ctrl);\n\t*/\n\tQueryParams QP(ef, ef, 1.35, ctrl.limit_eval.value_or(n), get_threshold_m(l_c));\n\tauto points = parlay::delayed_seq<const T&>(node_pool.size(), [&](size_t i) -> const T&{\n\t\treturn node_pool[i].data;\n\t});\n\tauto res = beam_search_impl<node_id>(u.data, g, points, eps, QP);\n\tconst auto &pairElts = std::get<0>(res);\n\tconst auto &frontier = std::get<0>(pairElts);\n\tif(ctrl.count_cmps)\n\t\t*ctrl.count_cmps.value() += std::get<1>(res);\n\treturn parlay::tabulate(frontier.size(), [&](size_t i){\n\t\tconst auto &f = frontier[i];\n\t\treturn dist{f.second, f.first};\n\t});\n}\n\ntemplate<typename U, template<typename> class Allocator>\nauto HNSW<U,Allocator>::search_layer_bak(const node &u, const parlay::sequence<node_id> &eps, uint32_t ef, uint32_t l_c, search_control ctrl) const\n{\n\t#define USE_HASHTBL\n\t// #define USE_BOOLARRAY\n\t// #define USE_UNORDERED_SET\n#ifdef USE_HASHTBL\n\tconst uint32_t bits = ef>2? std::ceil(std::log2(ef*ef))-2: 2;\n\tconst uint32_t mask = (1u<<bits)-1;\n\tparlay::sequence<uint32_t> visited(mask+1, n+1);\n#endif\n#ifdef USE_BOOLARRAY\n\tstd::vector<bool> visited(n+1);\n#endif\n\t// TODO: Try hash to an array\n\t// TODO: monitor the size of `visited`\n\tuint32_t cnt_visited = 0;\n#ifdef USE_UNORDERED_SET\n\tstd::unordered_set<uint32_t> visited;\n#endif\n\tparlay::sequence<dist> W, discarded;\n\tstd::set<dist,farthest> C;\n\tstd::set<node_id> w_inserted;\n\tW.reserve(ef+1);\n\n\tfor(node_id ep : eps)\n\t{\n\t#ifdef USE_HASHTBL\n\t\tconst auto id = U::get_id(get_node(ep).data);\n\t\tvisited[parlay::hash64_2(id)&mask] = id;\n\t#endif\n\t#ifdef USE_BOOLARRAY\n\t\tvisited[id] = true;\n\t#endif\n\t#ifdef USE_UNORDERED_SET\n\t\tvisited.insert(U::get_id(get_node(ep).data));\n\t#endif\n\t\tcnt_visited++;\n\t\tconst auto d = U::distance(u.data,get_node(ep).data,dim);\n\t\tC.insert({d,ep});\n\t\tW.push_back({d,ep});\n\t\tw_inserted.insert(ep);\n\t}\n\t// std::make_heap(C.begin(), C.end(), nearest());\n\tstd::make_heap(W.begin(), W.end(), farthest());\n\n\tuint32_t cnt_eval = 0;\n\tuint32_t limit_eval = ctrl.limit_eval.value_or(n);\n\twhile(C.size()>0)\n\t{\n\t\tif(ctrl.skip_search) break;\n\t\tif(C.begin()->d>W[0].d*ctrl.beta) break;\n\n\t\tif(++cnt_eval>limit_eval) break;\n\t\tif(ctrl.log_dist)\n\t\t{\n\t\t\tstd::array<float,5> t;\n\n\t\t\tif(ctrl.log_size)\n\t\t\t{\n\t\t\t\tt[0] = W[0].d;\n\t\t\t\tt[1] = W.size();\n\t\t\t\tt[2] = C.size();\n\t\t\t\tvc_in_search[*ctrl.log_size].push_back(t);\n\t\t\t}\n\n\t\t\tauto it = C.begin();\n\t\t\tconst auto step = C.size()/4;\n\t\t\tfor(uint32_t i=0; i<4; ++i)\n\t\t\t\tt[i]=it->d, std::advance(it,step);\n\t\t\tt[4] = C.rbegin()->d;\n\n\t\t\tdist_in_search[*ctrl.log_dist].push_back(t);\n\t\t}\n\n\t\tconst auto &c = get_node(C.begin()->u);\n\t\t// std::pop_heap(C.begin(), C.end(), nearest());\n\t\t// C.pop_back();\n\t\tC.erase(C.begin());\n\t\tfor(node_id pv: neighbourhood(c, l_c))\n\t\t{\n\t\t#ifdef USE_HASHTBL\n\t\t\tconst auto id = U::get_id(get_node(pv).data);\n\t\t\tconst auto idx = parlay::hash64_2(id)&mask;\n\t\t\tif(visited[idx]==id) continue;\n\t\t\tvisited[idx] = id;\n\t\t#endif\n\t\t#ifdef USE_BOOLARRAY\n\t\t\tif(visited[id]) continue;\n\t\t\tvisited[id] = true;\n\t\t#endif\n\t\t#ifdef USE_UNORDERED_SET\n\t\t\tif(!visited.insert(U::get_id(get_node(pv).data)).second) continue;\n\t\t#endif\n\t\t\tcnt_visited++;\n\t\t\tconst auto d = U::distance(u.data,get_node(pv).data,dim);\n\t\t\tif((W.size()<ef||d<W[0].d) && w_inserted.insert(pv).second)\n\t\t\t{\n\t\t\t\tC.insert({d,pv});\n\t\t\t\t// C.push_back({d,pv,dc+1});\n\t\t\t\t// std::push_heap(C.begin(), C.end(), nearest());\n\t\t\t\tW.push_back({d,pv});\n\t\t\t\tstd::push_heap(W.begin(), W.end(), farthest());\n\t\t\t\tif(W.size()>ef)\n\t\t\t\t{\n\t\t\t\t\tstd::pop_heap(W.begin(), W.end(), farthest());\n\t\t\t\t\t// w_inserted.erase(W.back().u);\n\t\t\t\t\tif(ctrl.radius && W.back().d<=*ctrl.radius)\n\t\t\t\t\t\tdiscarded.push_back(W.back());\n\t\t\t\t\tW.pop_back();\n\t\t\t\t}\n\t\t\t\tif(C.size()>ef)\n\t\t\t\t\tC.erase(std::prev(C.end()));\n\t\t\t}\n\t\t}\n\t}\n\n\t//total_visited += visited.size();\n\t//total_visited += visited.size()-std::count(visited.begin(),visited.end(),n+1);\n\tconst auto id = parlay::worker_id();\n\ttotal_visited[id] += cnt_visited;\n\ttotal_size_C[id] += C.size()+cnt_eval;\n\ttotal_eval[id] += cnt_eval;\n\n\tif(ctrl.count_cmps)\n\t\t*ctrl.count_cmps.value() += cnt_visited;\n\n\tif(ctrl.radius)\n\t{\n\t\tconst auto rad = *ctrl.radius;\n\t\tauto split = std::partition(W.begin(), W.end(), [rad](const dist &e){\n\t\t\treturn e.d <= rad;\n\t\t});\n\t\tW.resize(split-W.begin());\n\t\tW.append(discarded);\n\t\ttotal_range_candidate[parlay::worker_id()] += W.size();\n\t}\n\treturn W;\n}\n\ntemplate<typename U, template<typename> class Allocator>\nauto HNSW<U,Allocator>::search_layer_new_ex(const node &u, const parlay::sequence<node_id> &eps, uint32_t ef, uint32_t l_c, search_control ctrl) const\n{\n\tauto verbose_output = [&](const char *fmt, ...){\n\t\tif(!ctrl.verbose_output) return;\n\n\t\tva_list args;\n\t\tva_start(args, fmt);\n\t\tvfprintf(stderr, fmt, args);\n\t\tva_end(args);\n\t};\n\n\tparlay::sequence<std::array<float,5>> dummy;\n\tauto &dist_range = ctrl.log_dist? dist_in_search[*ctrl.log_dist]: dummy;\n\tuint32_t cnt_eval = 0;\n\n\tauto *indeg = ctrl.verbose_output? get_indeg(l_c): reinterpret_cast<const uint32_t*>(node_pool.data());\n\t// parlay::sequence<bool> visited(n);\n\t// TODO: Try hash to an array\n\t// TODO: monitor the size of `visited`\n\tstd::set<uint32_t> visited;\n\t// std::priority_queue<dist_ex,parlay::sequence<dist_ex>,nearest> C;\n\t// std::priority_queue<dist_ex,parlay::sequence<dist_ex>,farthest> W;\n\tparlay::sequence<dist_ex> /*C, W, */W_;\n\tstd::set<dist_ex,farthest> C, C_acc;\n\tuint32_t cnt_used = 0;\n\n\tfor(node_id ep : eps)\n\t{\n\t\t// visited[U::get_id(get_node(ep).data)] = true;\n\t\tconst auto id = U::get_id(get_node(ep).data);\n\t\tvisited.insert(id);\n\t\tconst auto d = U::distance(u.data,get_node(ep).data,dim);\n\t\tC.insert({d,ep,1});\n\t\tC_acc.insert({d,ep,1});\n\t\t// C.push_back({d,ep,1});\n\t\t// W.push_back({d,ep,1});\n\t\tverbose_output(\"Insert\\t[%u](%f) initially\\n\", id, d);\n\t}\n\t// std::make_heap(C.begin(), C.end(), nearest());\n\t// std::make_heap(W.begin(), W.end(), farthest());\n\n\t// static thread_local std::mt19937 gen{parlay::worker_id()};\n\t// static thread_local std::exponential_distribution<float> distro{48};\n\twhile(C.size()>0)\n\t{\n\t\t// const auto &f = *(W[0].u);\n\t\t// if(U::distance(c.data,u.data,dim)>U::distance(f.data,u.data,dim))\n\t\t// if(C[0].d>W[0].d) break;\n\t\tif(C_acc.size()==cnt_used) break;\n\t\tcnt_eval++;\n\n\t\tif(ctrl.log_dist)\n\t\t\tdist_range.push_back({C.begin()->d,C.rbegin()->d});\n\t\t/*\n\t\tconst auto dc = C[0].depth;\n\t\tconst auto &c = *(C[0].u);\n\t\t*/\n\t\tauto it = C.begin();\n\t\t/*\n\t\tfloat quantile = distro(gen);\n\t\tif(quantile>C.size())\n\t\t\tquantile = C.size();\n\t\tconst auto dis_min = C.begin()->d;\n\t\tconst auto dis_max = C.rbegin()->d;\n\t\tconst auto threshold = quantile/C.size()*(dis_max-dis_min) + dis_min - 1e-6;\n\t\tauto it = C.lower_bound(dist_ex{threshold,nullptr,0});\n\t\t*/\n\t\tconst auto dc = it->depth;\n\t\tconst auto &c = *(it->u);\n\t\t// W_.push_back(C[0]);\n\t\tW_.push_back(*it);\n\t\t// std::pop_heap(C.begin(), C.end(), nearest());\n\t\t// C.pop_back();\n\t\tC.erase(it);\n\t\tcnt_used++;\n\n\t\tverbose_output(\"------------------------------------\\n\");\n\t\tconst uint32_t id_c = U::get_id(c.data);\n\t\tverbose_output(\"Eval\\t[%u](%f){%u}\\t[%u]\\n\", id_c, it->d, dc, indeg[id_c]);\n\t\tuint32_t cnt_insert = 0;\n\t\tfor(node_id pv: neighbourhood(c, l_c))\n\t\t{\n\t\t\t// if(visited[U::get_id(get_node(pv).data)]) continue;\n\t\t\t// visited[U::get_id(get_node(pv).data)] = true;\n\t\t\tif(!visited.insert(U::get_id(get_node(pv).data)).second) continue;\n\t\t\t// const auto &f = *(W[0].u);\n\t\t\t// if(W.size()<ef||U::distance(get_node(pv).data,u.data,dim)<U::distance(f.data,u.data,dim))\n\t\t\tconst auto d = U::distance(u.data,get_node(pv).data,dim);\n\t\t\t// if(W.size()<ef||d<W[0].d)\n\t\t\t// if(C.size()<ef||d<C.rend()->d)\n\t\t\t{\n\t\t\t\t// C.push_back({d,pv,dc+1});\n\t\t\t\t// std::push_heap(C.begin(), C.end(), nearest());\n\t\t\t\t/*\n\t\t\t\tW.push_back({d,pv,dc+1});\n\t\t\t\tstd::push_heap(W.begin(), W.end(), farthest());\n\t\t\t\tif(W.size()>ef)\n\t\t\t\t{\n\t\t\t\t\tstd::pop_heap(W.begin(), W.end(), farthest());\n\t\t\t\t\tW.pop_back();\n\t\t\t\t}\n\t\t\t\t*/\n\t\t\t\tif(C.size()<ef || d<C.rbegin()->d)\n\t\t\t\t{\n\t\t\t\tC.insert({d,pv,dc+1});\n\t\t\t\tconst uint32_t id_v = U::get_id(get_node(pv).data);\n\t\t\t\tverbose_output(\"Insert\\t[%u](%f){%u}\\t[%u](%f)\\n\", \n\t\t\t\t\tid_v, d, dc+1, \n\t\t\t\t\tindeg[id_v], U::distance(c.data,get_node(pv).data,dim)\n\t\t\t\t);\n\t\t\t\tcnt_insert++;\n\t\t\t\tif(C.size()>ef)\n\t\t\t\t{\n\t\t\t\t\t// std::pop_heap(C.begin(), C.end(), nearest());\n\t\t\t\t\t// C.pop_back();\n\t\t\t\t\tC.erase(std::prev(C.end()));\n\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif(C_acc.size()<ef || d<C_acc.rbegin()->d)\n\t\t\t\t{\n\t\t\t\tC_acc.insert({d,pv,dc+1});\n\t\t\t\tif(C_acc.size()>ef)\n\t\t\t\t{\n\t\t\t\t\tauto it = std::prev(C_acc.end());\n\t\t\t\t\tif(std::find_if(W_.begin(), W_.end(), [&](const dist_ex &a){\n\t\t\t\t\t\treturn a.u==it->u;\n\t\t\t\t\t})!=W_.end())\n\t\t\t\t\t\tcnt_used--;\n\t\t\t\t\tC_acc.erase(it);\n\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tverbose_output(\"%u inserts in this round\\n\", cnt_insert);\n\t}\n\tif(l_c==0)\n\t{\n\t\tconst auto id = parlay::worker_id();\n\t\ttotal_visited[id] += visited.size();\n\t\ttotal_size_C[id] += C.size()+cnt_eval;\n\t\ttotal_eval[id] += cnt_eval;\n\t}\n\t/*\n\tstd::sort(W.begin(), W.end(), farthest());\n\tif(W.size()>ef) W.resize(ef);\n\t*/\n\treturn W_;\n}\n\ntemplate<typename U, template<typename> class Allocator>\nauto HNSW<U,Allocator>::beam_search_ex(const node &u, const parlay::sequence<node_id> &eps, uint32_t beamSize, uint32_t l_c, search_control ctrl) const\n// std::pair<parlay::sequence<dist_ex>, parlay::sequence<dist_ex>> beam_search(\n\t\t// T* p_coords, int beamSize)\n{\n\t// beamSize *= 2;\n\t// beamSize = 20000;\n\t// initialize data structures\n\tparlay::sequence<dist_ex> visited;\n\tparlay::sequence<dist_ex> frontier;\n\tauto dist_less = [&](const dist_ex &a, const dist_ex &b) {\n\t\treturn a.d < b.d || (a.d == b.d && a.u < b.u);\n\t\t// return a.u<b.u;\n\t};\n\tauto dist_eq = [&](const dist_ex &a, const dist_ex &b){\n\t\treturn a.u == b.u;\n\t};\n\n\t// int bits = std::ceil(std::log2(beamSize * beamSize));\n\t// parlay::sequence<uint32_t> hash_table(1 << bits, std::numeric_limits<uint32_t>::max());\n\tstd::set<uint32_t> accessed;\n\n\tauto make_pid = [&] (node_id ep) {\n\t\tconst auto d = U::distance(u.data,get_node(ep).data,dim);\n\t\treturn dist_ex{d,ep,1};\n\t};\n\n\t// the frontier starts with the medoid\n\t// frontier.push_back(make_pid(medoid->id));\n\t\n\tfor(node_id ep : eps)\n\t\tfrontier.push_back(make_pid(ep));\n\tstd::sort(frontier.begin(), frontier.end(), dist_less);\n\t\n\t// frontier.push_back(make_pid(eps[0]));\n\n\tparlay::sequence<dist_ex> unvisited_frontier;\n\t// parlay::sequence<dist_ex> unvisited_frontier(beamSize);\n\tparlay::sequence<dist_ex> new_frontier;\n\t// parlay::sequence<dist_ex> new_frontier(2 * beamSize);\n\tbool not_done = true;\n\n\n\tfor(size_t i=0; i<frontier.size(); ++i)\n\t{\n\t\tunvisited_frontier.push_back(frontier[i]);\n\t\t// unvisited_frontier[i] = frontier[i];\n\t\taccessed.insert(U::get_id(frontier[i].get_node(u).data));\n\t}\n\n\t// terminate beam search when the entire frontier has been visited\n\twhile (not_done) {\n\t\t// the next node to visit is the unvisited frontier node that is closest\n\t\t// to p\n\t\tdist_ex currentPid = unvisited_frontier[0];\n\t\tnode_id current_vtx = currentPid.u;\n\t\tdebug_output(\"current_vtx ID: %u\\n\", U::get_id(get_node(current_vtx).data));\n\n\t\tauto g = [&](node_id a) {\n\t\t\tuint32_t id_a = U::get_id(get_node(a).data);\n\t\t\t/*\n\t\t\tuint32_t loc = parlay::hash64_2(id_a) & ((1 << bits) - 1);\n\t\t\tif (hash_table[loc] == id_a) return false;\n\t\t\thash_table[loc] = id_a;\n\t\t\treturn true;\n\t\t\t*/\n\t\t\treturn accessed.insert(id_a).second;\n\t\t};\n\n\t\tparlay::sequence<node_id> candidates;\n\t\tauto f = [&](node_id pu, node_id pv/*, empty_weight wgh*/) {\n\t\t\tif (g(pv)) {\n\t\t\t\tcandidates.push_back(pv);\n\t\t\t}\n\t\t\treturn true;\n\t\t};\n\t\tfor(node_id pv : neighbourhood(get_node(current_vtx),l_c))\n\t\t\t// current_vtx.out_neighbors().foreach_cond(f);\n\t\t\tf(current_vtx, pv);\n\n\t\tdebug_output(\"candidates:\\n\");\n\t\tfor(node_id p : candidates)\n\t\t\tdebug_output(\"%u \", U::get_id(get_node(p).data));\n\t\tdebug_output(\"\\n\");\n\t\tauto pairCandidates =\n\t\t\t\tparlay::map(candidates, make_pid);\n\t\t/*\n\t\tauto sortedCandidates =\n\t\t\t\tparlay::unique(parlay::sort(pairCandidates, dist_less), dist_eq);\n\t\t*/\n\t\tauto &sortedCandidates = pairCandidates;\n\t\tdebug_output(\"size of sortedCandidates: %lu\\n\", sortedCandidates.size());\n\t\t/*\n\t\tauto f_iter = std::set_union(\n\t\t\t\tfrontier.begin(), frontier.end(), sortedCandidates.begin(),\n\t\t\t\tsortedCandidates.end(), new_frontier.begin(), dist_less);\\\n\t\t*/\n\t\tsortedCandidates.insert(sortedCandidates.end(), frontier);\n\t\tnew_frontier = parlay::unique(parlay::sort(sortedCandidates,dist_less), dist_eq);\n\n\t\t// size_t f_size = std::min<size_t>(beamSize, f_iter - new_frontier.begin());\n\t\tsize_t f_size = std::min<size_t>(beamSize, new_frontier.size());\n\t\tdebug_output(\"f_size: %lu\\n\", f_size);\n\n\t\tdebug_output(\"frontier (size: %lu)\\n\", frontier.size());\n\t\tfor(const auto &e : frontier)\n\t\t\tdebug_output(\"%u \", U::get_id(e.get_node(u).data));\n\t\tdebug_output(\"\\n\");\n\t\t\n\t\tfrontier =\n\t\t\t\tparlay::tabulate(f_size, [&](size_t i) { return new_frontier[i]; });\n\t\tdebug_output(\"size of frontier: %lu\\n\", frontier.size());\n\t\tvisited.insert(\n\t\t\t\tstd::upper_bound(visited.begin(), visited.end(), currentPid, dist_less),\n\t\t\t\tcurrentPid);\n\t\tdebug_output(\"size of visited: %lu\\n\", visited.size());\n\t\tunvisited_frontier.reserve(frontier.size());\n\t\tauto uf_iter =\n\t\t\t\tstd::set_difference(frontier.begin(), frontier.end(), visited.begin(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvisited.end(), unvisited_frontier.begin(), dist_less);\n\t\tdebug_output(\"uf_iter - unvisited_frontier.begin(): %lu\\n\", uf_iter - unvisited_frontier.begin());\n\t\tnot_done = uf_iter > unvisited_frontier.begin();\n\n\t\tif(l_c==0)\n\t\t\ttotal_visited[parlay::worker_id()] += candidates.size();\n\t}\n\tparlay::sequence<dist_ex> W;\n\tW.insert(W.end(), visited);\n\treturn W;\n}\n/*\ntemplate<typename U, template<typename> class Allocator>\nparlay::sequence<std::pair<uint32_t,float>> HNSW<U,Allocator>::search(const T &q, uint32_t k, uint32_t ef, search_control ctrl)\n{\n\tauto res_ex = search_ex(q,k,ef,ctrl);\n\tparlay::sequence<std::pair<uint32_t,float>> res;\n\tres.reserve(res_ex.size());\n\tfor(const auto &e : res_ex)\n\t\tres.emplace_back(std::get<0>(e), std::get<2>(e));\n\n\treturn res;\n}\n*/\n\ntemplate<typename U, template<typename> class Allocator>\nparlay::sequence<typename HNSW<U,Allocator>::node_id> HNSW<U,Allocator>::search_layer_to(\n\tconst node &u, uint32_t ef, uint32_t l_stop, const search_control &ctrl)\n{\n\tauto eps = entrance;\n\tfor(uint32_t l_c=get_node(entrance[0]).level; l_c>l_stop; --l_c)\n\t{\n\t\tsearch_control c{};\n\t\tc.log_per_stat = ctrl.log_per_stat; // whether count dist calculations at all layers\n\t\t// c.limit_eval = ctrl.limit_eval; // whether apply the limit to all layers\n\t\tc.count_cmps = ctrl.count_cmps;\n\t\tconst auto W = search_layer(u, eps, ef, l_c, c);\n\t\teps.clear();\n\t\teps.push_back(W[0].u);\n\t\t/*\n\t\twhile(!W.empty())\n\t\t{\n\t\t\teps.push_back(W.top().u);\n\t\t\tW.pop();\n\t\t}\n\t\t*/\n\t}\n\treturn eps;\n}\n\ntemplate<typename U, template<typename> class Allocator>\nparlay::sequence<std::pair<uint32_t,float>> HNSW<U,Allocator>::search(\n\tconst T &q, uint32_t k, uint32_t ef, const search_control &ctrl)\n{\n\tconst auto id = parlay::worker_id();\n\ttotal_range_candidate[id] = 0;\n\ttotal_visited[id] = 0;\n\ttotal_eval[id] = 0;\n\ttotal_size_C[id] = 0;\n\n\tnode u{n, nullptr, q}; // To optimize\n\t// std::priority_queue<dist,parlay::sequence<dist>,farthest> W;\n\tparlay::sequence<node_id> eps;\n\tif(ctrl.indicate_ep)\n\t\teps.push_back(*ctrl.indicate_ep);\n\telse\n\t\teps = search_layer_to(u, 1, 0, ctrl);\n\tauto W_ex = search_layer(u, eps, ef, 0, ctrl);\n\t// auto W_ex = search_layer_new_ex(u, eps, ef, 0, ctrl);\n\t// auto W_ex = beam_search_ex(u, eps, ef, 0);\n\t// auto R = select_neighbors_simple(q, W_ex, k);\n\n\tauto &R = W_ex;\n\tif(!ctrl.radius && R.size()>k) // the range search ignores the given k\n\t{\n\t\tstd::sort(R.begin(), R.end(), farthest());\n\t\tif(k>0)\n\t\t\tk = std::upper_bound(R.begin()+k, R.end(), R[k-1], farthest())-R.begin();\n\t\tR.resize(k);\n\t}\n\n\tparlay::sequence<std::pair<uint32_t,float>> res;\n\tres.reserve(R.size());\n\t/*\n\twhile(W_ex.size()>0)\n\t{\n\t\tres.push_back({U::get_id(W_ex.top().get_node(u).data), W_ex.top().depth, W_ex.top().d});\n\t\tW_ex.pop();\n\t}\n\t*/\n\tfor(const auto &e : R)\n\t\tres.push_back({U::get_id(get_node(e.u).data),/* e.depth,*/ e.d});\n\treturn res;\n}\n\ntemplate<typename U, template<typename> class Allocator>\nvoid HNSW<U,Allocator>::save(const std::string &filename_model) const\n{\n\tstd::ofstream model(filename_model, std::ios::binary);\n\tif(!model.is_open())\n\t\tthrow std::runtime_error(\"Failed to create the model\");\n\n\tconst auto size_buffer = 1024*1024*1024; // 1G\n\tauto buffer = std::make_unique<char[]>(size_buffer);\n\tmodel.rdbuf()->pubsetbuf(buffer.get(), size_buffer);\n\n\tconst auto write = [&](const auto &data, auto ...args){\n\t\tauto write_impl = [&](auto &f, const auto &data, auto ...args){\n\t\t\tusing T = std::remove_reference_t<decltype(data)>;\n\t\t\tif constexpr(std::is_pointer_v<std::decay_t<T>>)\n\t\t\t{\n\t\t\t\tauto write_array = [&](const auto &data, size_t size, auto ...args){\n\t\t\t\t\tfor(size_t i=0; i<size; ++i)\n\t\t\t\t\t\tf(f, data[i], args...);\n\t\t\t\t};\n\t\t\t\t// use the array extent as the size\n\t\t\t\tif constexpr(sizeof...(args)==0 && std::is_array_v<T>)\n\t\t\t\t{\n\t\t\t\t\twrite_array(data, std::extent_v<T>);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tstatic_assert(sizeof...(args), \"size was not provided\");\n\t\t\t\t\twrite_array(data, args...);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstatic_assert(std::is_standard_layout_v<T>);\n\t\t\t\tmodel.write((const char*)&data, sizeof(data));\n\t\t\t}\n\t\t};\n\t\twrite_impl(write_impl, data, args...);\n\t};\n\t// write header (version number, type info, etc)\n\twrite(\"HNSW\", 4);\n\twrite(uint32_t(3)); // version\n\twrite(typeid(U).hash_code()^sizeof(U));\n\tfprintf(stderr, \"U type written %s\\n\", typeid(U).name());\n\twrite(sizeof(node));\n\t// write parameter configuration\n\twrite(dim);\n\twrite(m_l);\n\twrite(m);\n\twrite(ef_construction);\n\twrite(alpha);\n\twrite(n);\n\t// write indices\n\tfor(const auto &u : node_pool)\n\t{\n\t\twrite(u.level);\n\t\twrite(uint32_t(U::get_id(u.data)));\n\t}\n\tfor(const auto &u : node_pool)\n\t{\n\t\tfor(uint32_t l=0; l<=u.level; ++l)\n\t\t{\n\t\t\twrite(u.neighbors[l].size());\n\t\t\tfor(node_id pv : u.neighbors[l])\n\t\t\t\twrite(pv);\n\t\t}\n\t}\n\t// write entrances\n\twrite(entrance.size());\n\tfor(node_id pu : entrance)\n\t\twrite(pu);\n} \n\n} // namespace HNSW\n\n#endif // _HNSW_HPP\n\n"
  },
  {
    "path": "algorithms/HNSW/debug.hpp",
    "content": "#ifndef __DEBUG_HPP__\n#define __DEBUG_HPP__\n\nextern parlay::sequence<parlay::sequence<std::array<float,5>>> dist_in_search;\nextern parlay::sequence<parlay::sequence<std::array<float,5>>> vc_in_search;\n// extern parlay::sequence<uint32_t> round_in_search;\nextern parlay::sequence<size_t> per_visited;\nextern parlay::sequence<size_t> per_eval;\nextern parlay::sequence<size_t> per_size_C;\n\n#include <optional>\n\nstruct search_control{\n\tbool verbose_output;\n\tbool skip_search;\n\tfloat beta = 1;\n\tstd::optional<float> radius;\n\tstd::optional<uint32_t> log_per_stat;\n\tstd::optional<uint32_t> log_dist;\n\tstd::optional<uint32_t> log_size;\n\tstd::optional<uint32_t> indicate_ep;\n\tstd::optional<uint32_t> limit_eval;\n\tstd::optional<uint32_t*> count_cmps;\n};\n\n#endif // _DEBUG_HPP_\n"
  },
  {
    "path": "algorithms/HNSW/dist.hpp",
    "content": "#ifndef __DIST_HPP__\n#define __DIST_HPP__\n\n#include <type_traits>\n#include \"type_point.hpp\"\n#include \"../utils/NSGDist.h\"\n\ntemplate<typename T>\nclass descr_ang\n{\n\tusing promoted_type = std::conditional_t<std::is_integral_v<T>&&sizeof(T)<=4,\n\t\tstd::conditional_t<sizeof(T)==4, int64_t, int32_t>,\n\t\tfloat\n\t>;\npublic:\n\ttypedef T type_elem;\n\ttypedef point<T> type_point;\n\tstatic float distance(const type_point &u, const type_point &v, uint32_t dim)\n\t{\n\t\tconst auto *uc=u.coord, *vc=v.coord;\n\t\tpromoted_type dot=0, nu=0, nv=0;\n\t\tfor(uint32_t i=0; i<dim; ++i)\n\t\t{\n\t\t\tnu += promoted_type(uc[i])*uc[i];\n\t\t\tnv += promoted_type(vc[i])*vc[i];\n\t\t\tdot += promoted_type(uc[i])*vc[i];\n\t\t}\n\t\treturn 1-dot/(sqrtf(nu)*sqrtf(nv));\n\t}\n\n\tstatic auto get_id(const type_point &u)\n\t{\n\t\treturn u.id;\n\t}\n};\n\ntemplate<typename T>\nclass descr_ndot\n{\n\tusing promoted_type = std::conditional_t<std::is_integral_v<T>&&sizeof(T)<=4,\n\t\tstd::conditional_t<sizeof(T)==4, int64_t, int32_t>,\n\t\tfloat\n\t>;\npublic:\n\ttypedef T type_elem;\n\ttypedef point<T> type_point;\n\tstatic float distance(const type_point &u, const type_point &v, uint32_t dim)\n\t{\n\t\tconst auto *uc=u.coord, *vc=v.coord;\n\t\tpromoted_type dot=0;\n\t\tfor(uint32_t i=0; i<dim; ++i)\n\t\t\tdot += promoted_type(uc[i])*vc[i];\n\t\treturn -float(dot);\n\t}\n\n\tstatic auto get_id(const type_point &u)\n\t{\n\t\treturn u.id;\n\t}\n};\n\ntemplate<typename T>\nclass descr_l2\n{\n\tusing promoted_type = std::conditional_t<std::is_integral_v<T>&&sizeof(T)<=4,\n\t\tstd::conditional_t<sizeof(T)==4, int64_t, int32_t>,\n\t\tfloat\n\t>;\npublic:\n\ttypedef T type_elem;\n\ttypedef point<T> type_point;\n\tstatic float distance(const type_point &u, const type_point &v, uint32_t dim)\n\t{\n\t\tif constexpr(std::is_integral_v<T>)\n\t\t{\n\t\t\tconst auto *uc=u.coord, *vc=v.coord;\n\t\t\tpromoted_type sum = 0;\n\t\t\tfor(uint32_t i=0; i<dim; ++i)\n\t\t\t{\n\t\t\t\tconst auto d = promoted_type(uc[i])-vc[i];\n\t\t\t\tsum += d*d;\n\t\t\t}\n\t\t\treturn sum;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tconst auto *uc=u.coord, *vc=v.coord;\n\t\t\tefanna2e::DistanceL2 distfunc;\n\t\t\treturn distfunc.compare(uc, vc, dim);\n\t\t}\n\t}\n\n\tstatic auto get_id(const type_point &u)\n\t{\n\t\treturn u.id;\n\t}\n};\n\n#endif // __DIST_HPP__\n"
  },
  {
    "path": "algorithms/HNSW/h5_ops.hpp",
    "content": "#ifndef __H5_OPS_HPP__\n#define __H5_OPS_HPP__\n\n#include <cstdio>\n#include <array>\n#include <tuple>\n#include <memory>\n#include <type_traits>\n#include <H5Cpp.h>\n\n// Return a {reader, dims} tuple\n// reader(buffer, index, cnt) reads data with the first dimension \n// from  `index` to `index+cnt` and writes it into the 1D `buffer`\ntemplate<typename T>\nauto get_reader(const char *file, const char *dir)\n{\n\tH5::H5File file_h5(file, H5F_ACC_RDONLY);\n\tH5::DataSet dset = file_h5.openDataSet(dir);\n\tH5::DataSpace dspace_src = dset.getSpace();\n\thsize_t dim[2];\n\tdspace_src.getSimpleExtentDims(dim);\n\tfprintf(stderr, \"%s: [%llu,%llu]\\n\", dir, dim[0], dim[1]);\n\n\tH5::DataType type_dst = dset.getDataType();\n\tif constexpr(std::is_same_v<T,uint32_t>)\n\t\ttype_dst = H5::PredType::NATIVE_UINT32;\n\telse if constexpr(std::is_same_v<T,int32_t>)\n\t\ttype_dst = H5::PredType::NATIVE_INT32;\n\telse if constexpr(std::is_same_v<T,uint8_t>)\n\t\ttype_dst = H5::PredType::NATIVE_UINT8;\n\telse if constexpr(std::is_same_v<T,int8_t>)\n\t\ttype_dst = H5::PredType::NATIVE_INT8;\n\telse if constexpr(std::is_same_v<T,float>)\n\t\ttype_dst = H5::PredType::NATIVE_FLOAT;\n\telse static_assert(std::is_same_v<T,uint32_t>/*always false*/, \"Unsupported type\");\n\n\tauto reader = [=,_=std::move(file_h5)](T *buffer, hsize_t index, hsize_t cnt=1){\n\t\thsize_t size = dim[1]*cnt;\n\t\tH5::DataSpace dspace_dst(1,&size,NULL);\n\n\t\thsize_t offset[2] = {index, 0};\n\t\thsize_t count[2] = {cnt, dim[1]};\n\t\tH5::DataSpace dspace_slice;\n\t\tdspace_slice.copy(dspace_src);\n\t\tdspace_slice.selectHyperslab(H5S_SELECT_SET, count, offset);\n\n\t\tdset.read(buffer, type_dst, dspace_dst, dspace_slice);\n\t};\n\n\treturn std::tuple{reader, std::array{dim[0], dim[1]}};\n}\n\n// read a 2D array from H5 file and return a 1D array\ntemplate<typename T>\nstd::pair<std::unique_ptr<T[]>,std::array<hsize_t,2>> read_array_from_HDF5(const char *file, const char *dir)\n{\n\tauto [reader,dims] = get_reader<T>(file, dir);\n\tauto buffer = std::make_unique<T[]>(dims[0]*dims[1]);\n\treader(buffer.get(), 0, dims[0]);\n\treturn {std::move(buffer), dims};\n}\n\n#endif // __H5_OPS_HPP__\n"
  },
  {
    "path": "algorithms/HNSW/type_point.hpp",
    "content": "#ifndef __TYPE_POINT_HPP__\n#define __TYPE_POINT_HPP__\n\n#include <cstdint>\n#include <cstddef>\n#include <iterator>\n#include <algorithm>\n#include <memory>\n#include <type_traits>\n#include <stdexcept>\n#include <any>\n#include \"benchUtils.h\"\n\n#ifdef SUPPORT_HDF5\n#include \"h5_ops.hpp\"\n#endif\n\nclass internal_termination{\nprotected:\n\tinternal_termination(){}\n\tinternal_termination(int){std::terminate();}\n};\n\ntemplate<typename T>\nclass fake_copyable : public internal_termination{\n\tT content;\npublic:\n\tfake_copyable(const T &c) : content(c){}\n\tfake_copyable(T &&c) : content(std::move(c)){}\n\n\tfake_copyable(fake_copyable&&) = default;\n\tfake_copyable(const fake_copyable &other [[maybe_unused]])\n\t// The users have to guarantee to hold the points while it is being used in graph.\n\t// Otherwise, uncomment the following guarding code and forbid copy constructions\n\t// or alternatively pass in copy-constructible objects (e.g., `std::shared_ptr`) \n\t// to `point` instead of using this hack\n\t/*\n\t\t: internal_termination(0), \n\t\t  content(std::move(const_cast<fake_copyable&>(other).content))\n\t*/\n\t\t: internal_termination()\n\t{\n\t}\n};\ntemplate<typename T>\nfake_copyable(const T&) -> fake_copyable<T>;\ntemplate<typename T>\nfake_copyable(T&&) -> fake_copyable<T>;\n\ntemplate<typename T>\nstruct point\n{\n\ttypedef T type;\n\n\tuint32_t id;\n\tconst T *coord;\n\n\tpoint()\n\t\t: id(~0u), coord(NULL), closure()\n\t{\n\t}\n\tpoint(uint32_t id_, const T *coord_)\n\t\t: id(id_), coord(coord_), closure()\n\t{\n\t}\n\ttemplate<class C>\n\tpoint(uint32_t id_, const T *coord_, C &&closure_)\n\t\t: id(id_), coord(coord_), closure(std::forward<C>(closure_))\n\t{\n\t}\nprivate:\n\tstd::any closure;\n};\n\nenum class file_format{\n\tVEC, HDF5, BIN\n};\n\ntemplate<typename T>\nclass point_converter_default\n{\npublic:\n\tusing type = point<T>;\n\n\ttemplate<typename Iter>\n\ttype operator()(uint32_t id, Iter begin, [[maybe_unused]] Iter end)\n\t{\n\t\tusing type_src = typename std::iterator_traits<Iter>::value_type;\n\t\tstatic_assert(std::is_convertible_v<type_src,T>, \"Cannot convert to the target type\");\n\n\t\tif constexpr(std::is_same_v<Iter,ptr_mapped<T,ptr_mapped_src::PERSISTENT>>||\n\t\t\tstd::is_same_v<Iter,ptr_mapped<const T,ptr_mapped_src::PERSISTENT>>)\n\t\t\treturn point<T>(id, &*begin);\n\t\telse if constexpr(std::is_same_v<Iter,ptr_mapped<T,ptr_mapped_src::TRANSITIVE>>||\n\t\t\tstd::is_same_v<Iter,ptr_mapped<const T,ptr_mapped_src::TRANSITIVE>>)\n\t\t{\n\t\t\tconst T *p = &*begin; // TODO: fix the type to T(*)[]\n\t\t\treturn point<T>(id, p, fake_copyable(std::unique_ptr<const T>(p)));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tconst uint32_t dim = std::distance(begin, end);\n\n\t\t\t// T *coord = new T[dim];\n\t\t\tauto coord = std::make_unique<T[]>(dim);\n\t\t\tfor(uint32_t i=0; i<dim; ++i)\n\t\t\t\tcoord[i] = *(begin+i);\n\t\t\treturn point<T>(id, coord.get(), fake_copyable(std::move(coord)));\n\t\t}\n\t}\n};\n\ntemplate<typename Src, class Conv>\ninline std::pair<parlay::sequence<typename Conv::type>,uint32_t>\nload_from_vec(const char *file, Conv converter, uint32_t max_num)\n{\n\tconst auto [fileptr, length] = mmapStringFromFile(file);\n\n\t// Each vector is 4 + sizeof(Src)*dim bytes.\n\t// * first 4 bytes encode the dimension (as an uint32_t)\n\t// * next dim values are Src-type variables representing vector components\n\t// See http://corpus-texmex.irisa.fr/ for more details.\n\n\tconst uint32_t dim = *((const uint32_t*)fileptr);\n\tstd::cout << \"Dimension = \" << dim << std::endl;\n\n\tconst size_t vector_size = sizeof(dim) + sizeof(Src)*dim;\n\tconst uint32_t n = std::min<size_t>(length/vector_size, max_num);\n\t// std::cout << \"Num vectors = \" << n << std::endl;\n\n\ttypedef ptr_mapped<const Src,ptr_mapped_src::PERSISTENT> type_ptr;\n\tparlay::sequence<typename Conv::type> ps(n);\n\n\tparlay::parallel_for(0, n, [&,fp=fileptr] (size_t i) {\n\t\tconst Src *coord = (const Src*)(fp+sizeof(dim)+i*vector_size);\n\t\tps[i] = converter(i, type_ptr(coord), type_ptr(coord+dim));\n\t});\n\n\treturn {std::move(ps), dim};\n}\n\ntemplate<class, class=void>\nclass trait_type{\n};\n\ntemplate<class T>\nclass trait_type<T,std::void_t<typename T::type>>{\npublic:\n\tusing type = typename T::type;\n};\n\ntemplate<class T>\nclass trait_type<T*,void>{\npublic:\n\tusing type = T;\n};\n\ntemplate<class T>\nclass trait_type<parlay::sequence<T>,void>{\npublic:\n\tusing type = T;\n};\n\ntemplate<class Conv>\ninline std::pair<parlay::sequence<typename Conv::type>,uint32_t>\nload_from_HDF5(const char *file, const char *dir, Conv converter, uint32_t max_num)\n{\n#ifndef SUPPORT_HDF5\n\t(void)file;\n\t(void)dir;\n\t(void)converter;\n\t(void)max_num;\n\tthrow std::invalid_argument(\"HDF5 support is not enabled\");\n#else\n\tusing T = typename trait_type<typename Conv::type>::type;\n\tauto [reader,bound] = get_reader<T>(file, dir);\n\tconst size_t n = std::min<size_t>(bound[0], max_num);\n\tconst uint32_t dim = bound[1];\n\n\tparlay::sequence<typename Conv::type> ps(n);\n\t// TODO: parallel for-loop\n\tfor(uint32_t i=0; i<n; ++i){\n\t\tT *coord = new T[dim];\n\t\treader(coord, i);\n\t\ttypedef ptr_mapped<T,ptr_mapped_src::TRANSITIVE> type_ptr;\n\t\tps[i] = converter(i, type_ptr(coord), type_ptr(coord+dim));\n\t}\n\treturn {std::move(ps), dim};\n#endif\n}\n\ntemplate<typename Src, class Conv>\ninline std::pair<parlay::sequence<typename Conv::type>,uint32_t>\nload_from_bin(const char *file, Conv converter, uint32_t max_num)\n{\n\tauto [fileptr, length] = mmapStringFromFile(file); (void)length;\n\tconst uint32_t n = std::min(max_num, *((uint32_t*)fileptr));\n\tconst uint32_t dim = *((uint32_t*)(fileptr+sizeof(n)));\n\tconst size_t vector_size = sizeof(Src)*dim;\n\tconst size_t header_size = sizeof(n)+sizeof(dim);\n\n\ttypedef ptr_mapped<const Src,ptr_mapped_src::PERSISTENT> type_ptr;\n\tparlay::sequence<typename Conv::type> ps(n);\n\tparlay::parallel_for(0, n, [&,fp=fileptr](uint32_t i){\n\t\tconst Src *coord = (const Src*)(fp+header_size+i*vector_size);\n\t\tps[i] = converter(i, type_ptr(coord), type_ptr(coord+dim));\n\t});\n\n\treturn {std::move(ps), dim};\n}\n\ntemplate<typename Src, class Conv>\ninline std::pair<parlay::sequence<typename Conv::type>,uint32_t>\nload_from_range(const char *file, Conv converter, uint32_t max_num)\n{\n\tauto [fileptr, length] = mmapStringFromFile(file); (void)length;\n\tconst int32_t num_points = *(int32_t*)fileptr;\n\tconst int32_t num_matches = *(int32_t*)(fileptr+sizeof(num_points));\n\tconst size_t header_size = sizeof(num_points)+sizeof(num_matches);\n\n\tint32_t* begin = (int32_t*)(fileptr+header_size);\n\tint32_t* end = begin + num_points;\n\tauto [offsets, total] = parlay::scan(parlay::make_slice(begin,end));\n\toffsets.push_back(total);\n\tstd::cout << \"num_matches: \" << num_matches << ' ' << total << std::endl;\n\n\tconst size_t index_size = header_size+num_points*sizeof(*begin);\n\tstd::cout << \"index_size: \" << index_size << std::endl;\n\n\ttypedef ptr_mapped<const Src,ptr_mapped_src::PERSISTENT> type_ptr;\n\tconst uint32_t n = std::min<uint32_t>(max_num, num_points);\n\tparlay::sequence<typename Conv::type> ps(n);\n\tparlay::parallel_for(0, n, [&,fp=fileptr](uint32_t i){\n\t\tconst Src *begin = (const Src*)(fp+index_size+offsets[i]*sizeof(Src));\n\t\tconst Src *end = (const Src*)(fp+index_size+offsets[i+1]*sizeof(Src));\n\t\tps[i] = converter(i, type_ptr(begin), type_ptr(end));\n\t});\n\n\treturn {std::move(ps), 0};\n}\n/*\ntemplate<typename Src=void, class Conv>\ninline auto load_point(const char *file, file_format input_format, Conv converter, size_t max_num=0, std::any aux={})\n{\n\tif(!max_num)\n\t\tmax_num = std::numeric_limits<decltype(max_num)>::max();\n\n\tswitch(input_format)\n\t{\n\tcase file_format::VEC:\n\t\treturn load_from_vec<Src>(file, converter, max_num);\n\tcase file_format::HDF5:\n\t\treturn load_from_HDF5(file, std::any_cast<const char*>(aux), converter, max_num);\n\tcase file_format::BIN:\n\t\treturn load_from_bin<Src>(file, converter, max_num);\n\tdefault:\n\t\t__builtin_unreachable();\n\t}\n}\n*/\ntemplate<class Conv>\ninline auto load_point(const char *input_name, Conv converter, size_t max_num=0)\n{\n\tauto buffer = std::make_unique<char[]>(strlen(input_name)+1);\n\tstrcpy(buffer.get(), input_name);\n\n\tchar *splitter = strchr(buffer.get(), ':');\n\tif(splitter==nullptr)\n\t\tthrow std::invalid_argument(\"The input spec is not specified\");\n\n\t*(splitter++) = '\\0';\n\tconst char *file = buffer.get();\n\tconst char *input_spec = splitter;\n\n\tif(!max_num)\n\t\tmax_num = std::numeric_limits<decltype(max_num)>::max();\n\n\tif(input_spec[0]=='/')\n\t\treturn load_from_HDF5(file, input_spec, converter, max_num);\n\tif(!strcmp(input_spec,\"fvecs\"))\n\t\treturn load_from_vec<float>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"bvecs\"))\n\t\treturn load_from_vec<uint8_t>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"ivecs\"))\n\t\treturn load_from_vec<int32_t>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"u8bin\"))\n\t\treturn load_from_bin<uint8_t>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"i8bin\"))\n\t\treturn load_from_bin<int8_t>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"ibin\"))\n\t\treturn load_from_bin<int32_t>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"ubin\"))\n\t\treturn load_from_bin<uint32_t>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"fbin\"))\n\t\treturn load_from_bin<float>(file, converter, max_num);\n\tif(!strcmp(input_spec,\"irange\"))\n\t\treturn load_from_range<int32_t>(file, converter, max_num);\n\n\tthrow std::invalid_argument(\"Unsupported input spec\");\n}\n\n#endif // __TYPE_POINT_HPP_\n"
  },
  {
    "path": "algorithms/bench/BUILD",
    "content": "package(default_visibility = [\"//visibility:public\"])\n\ncc_library(\n  name = \"parse_command_line\",\n  hdrs = [\"parse_command_line.h\"],\n)"
  },
  {
    "path": "algorithms/bench/IO.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <string>\n#include <cstring>\n#include \"parlay/primitives.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/io.h\"\n#include \"parlay/internal/get_time.h\"\n\nnamespace benchIO {\n  using namespace std;\n  using parlay::sequence;\n  using parlay::tabulate;\n  using parlay::make_slice;\n\n  auto is_space = [] (char c) {\n    switch (c)  {\n    case '\\r': \n    case '\\t': \n    case '\\n': \n    case 0:\n    case ' ' : return true;\n    default : return false;\n    }\n  };\n\n  // parallel code for converting a string to word pointers\n  // side effects string by setting to null after each word\n  template <class Seq>\n    parlay::sequence<char*> stringToWords(Seq &Str) {\n    size_t n = Str.size();\n    \n    parlay::parallel_for(0, n, [&] (long i) {\n\tif (is_space(Str[i])) Str[i] = 0;}); \n\n    // mark start of words\n    auto FL = parlay::tabulate(n, [&] (long i) -> bool {\n\treturn (i==0) ? Str[0] : Str[i] && !Str[i-1];});\n    \n    // offset for each start of word\n    auto Offsets = parlay::pack_index<long>(FL);\n\n    // pointer to each start of word\n    auto SA = parlay::tabulate(Offsets.size(), [&] (long j) -> char* {\n\treturn Str.begin() + Offsets[j];});\n    \n    return SA;\n  }\n\n  //using this as a typename so we can replace with parlay::chars easily if desired\n  using charstring = typename parlay::sequence<char>;\n\n  inline int xToStringLen(charstring const &a) { return a.size();}\n  inline void xToString(char* s, charstring const &a) {\n    for (int i=0; i < a.size(); i++) s[i] = a[i];}\n\n  inline int xToStringLen(long a) { return 21;}\n  inline void xToString(char* s, long a) { sprintf(s,\"%ld\",a);}\n\n  inline int xToStringLen(unsigned long a) { return 21;}\n  inline void xToString(char* s, unsigned long a) { sprintf(s,\"%lu\",a);}\n\n  inline uint xToStringLen(uint a) { return 12;}\n  inline void xToString(char* s, uint a) { sprintf(s,\"%u\",a);}\n\n  inline int xToStringLen(int a) { return 12;}\n  inline void xToString(char* s, int a) { sprintf(s,\"%d\",a);}\n\n  inline int xToStringLen(double a) { return 18;}\n  inline void xToString(char* s, double a) { sprintf(s,\"%.11le\", a);}\n\n  inline int xToStringLen(char* a) { return strlen(a)+1;}\n  inline void xToString(char* s, char* a) { sprintf(s,\"%s\",a);}\n\n  template <class A, class B>\n  inline int xToStringLen(pair<A,B> a) { \n    return xToStringLen(a.first) + xToStringLen(a.second) + 1;\n  }\n\n  template <class A, class B>\n  inline void xToString(char* s, pair<A,B> a) { \n    int l = xToStringLen(a.first);\n    xToString(s, a.first);\n    s[l] = ' ';\n    xToString(s+l+1, a.second);\n  }\n\n  template <class Seq>\n  charstring seqToString(Seq const &A) {\n    size_t n = A.size();\n    auto L = parlay::tabulate(n, [&] (size_t i) -> long {\n\ttypename Seq::value_type x = A[i];\n\treturn xToStringLen(x)+1;});\n    size_t m;\n    std::tie(L,m) = parlay::scan(std::move(L));\n\n    charstring B(m+1, (char) 0);\n    char* Bs = B.begin();\n\n    parlay::parallel_for(0, n-1, [&] (long i) {\n      xToString(Bs + L[i], A[i]);\n      Bs[L[i+1] - 1] = '\\n';\n      });\n    xToString(Bs + L[n-1], A[n-1]);\n    Bs[m] = Bs[m-1] = '\\n';\n    \n    charstring C = parlay::filter(B, [&] (char c) {return c != 0;}); \n    C[C.size()-1] = 0;\n    return C;\n  }\n\n  template <class T>\n  void writeSeqToStream(ofstream& os, parlay::sequence<T> const &A) {\n    size_t bsize = 10000000;\n    size_t offset = 0;\n    size_t n = A.size();\n    while (offset < n) {\n      // Generates a string for a sequence of size at most bsize\n      // and then wrties it to the output stream\n      charstring S = seqToString(A.cut(offset, min(offset + bsize, n)));\n      os.write(S.begin(), S.size()-1);\n      offset += bsize;\n    }\n  }\n\n  template <class T>\n  int writeSeqToFile(string header,\n\t\t     parlay::sequence<T> const &A,\n\t\t     char const *fileName) {\n    auto a = A[0];\n    //xToStringLena(a);\n    ofstream file (fileName, ios::out | ios::binary);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      return 1;\n    }\n    file << header << endl;\n    writeSeqToStream(file, A);\n    file.close();\n    return 0;\n  }\n\n  template <class T1, class T2>\n  int write2SeqToFile(string header,\n\t\t      parlay::sequence<T1> const &A,\n\t\t      parlay::sequence<T2> const &B,\n\t\t      char const *fileName) {\n    ofstream file (fileName, ios::out | ios::binary);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      return 1;\n    }\n    file << header << endl;\n    writeSeqToStream(file, A);\n    writeSeqToStream(file, B);\n    file.close();\n    return 0;\n  }\n\n  charstring readStringFromFile(char const *fileName) {\n    ifstream file (fileName, ios::in | ios::binary | ios::ate);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      abort();\n    }\n    long end = file.tellg();\n    file.seekg (0, ios::beg);\n    long n = end - file.tellg();\n    charstring bytes(n, (char) 0);\n    file.read (bytes.begin(), n);\n    file.close();\n    return bytes;\n  }\n\n  string intHeaderIO = \"sequenceInt\";\n\n  template <class T>\n  int writeIntSeqToFile(parlay::sequence<T> const &A, char const *fileName) {\n    return writeSeqToFile(intHeaderIO, A, fileName);\n  }\n\n  sequence<sequence<char>> get_tokens(char const *fileName) {\n    // parlay::internal::timer t(\"get_tokens\");\n    // auto S = parlay::chars_from_file(fileName);\n    auto S = parlay::file_map(fileName);\n    // t.next(\"file map\");\n    auto r =  parlay::tokens(S, benchIO::is_space);\n    // t.next(\"tokens\");\n    return r;\n  }\n\n  template <class T>\n  parlay::sequence<T> readIntSeqFromFile(char const *fileName) {\n    auto W = get_tokens(fileName);\n    string header(W[0].begin(),W[0].end());\n    if (header != intHeaderIO) {\n      cout << \"readIntSeqFromFile: bad input\" << endl;\n      abort();\n    }\n    long n = W.size()-1;\n    auto A = parlay::tabulate(n, [&] (long i) -> T {\n\treturn parlay::chars_to_long(W[i+1]);});\n    return A;\n  }\n};\n\n"
  },
  {
    "path": "algorithms/bench/MakeBench",
    "content": "# ********************\n# GENERIC MAKEFILE FOR MOST BENCHMARKS THAT #include <name>.h\n# USES FOLLOWING DEFINITIONS\n#    BENCH : the name of the benchmark\n#    REQUIRE : dependences\n#    CC : the compiler\n#    CFLAGS : compiler flags\n#    LFLAGS : compiler link flags\n# ********************\n\nTIME = ../bench/$(BENCH)Time.C\nINCLUDE = -I ../../parlaylib/include/\n\nall : $(BENCH) \n\n$(BENCH) : $(TIME) $(BENCH).h $(REQUIRE)\n\t$(CC) -DSTATS $(CFLAGS) $(INCLUDE) -include $(BENCH).h -o $(BENCH) $(TIME) $(LFLAGS)\n\nclean :\n\trm -f $(BENCH)\n\ncleanall : clean\n\trm -f testInputs*; cd ../bench; make -s clean\n"
  },
  {
    "path": "algorithms/bench/Makefile",
    "content": "include parallelDefsANN\r\nBNCHMRK = neighbors\r\n\r\nCHECKFILES = $(BNCHMRK)Check.o\r\n\r\nCOMMON =\r\n\r\nINCLUDE = -Icommon\r\n\r\n%.o : %.C $(COMMON)\r\n\t$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@\r\n\r\n# $(BNCHMRK)Check : $(CHECKFILES)\r\n# \t$(CC) $(LFLAGS) -o $@ $(CHECKFILES)\r\n\r\nclean :\r\n\trm -f $(BNCHMRK)Check *.o *.pyc\r\n"
  },
  {
    "path": "algorithms/bench/benchUtils.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n#ifndef __BENCHUTILS_H__\n#define __BENCHUTILS_H__\n\n#include <iostream>\n#include <algorithm>\n#include <iterator>\n#include <type_traits>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"common/geometry.h\"\n#include \"common/geometryIO.h\"\n#include \"common/parse_command_line.h\"\n// #include \"../utils/types.h\"\n// #include \"common/time_loop.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nusing namespace benchIO;\n\nenum class ptr_mapped_src{\n  NATIVE, VOLATILE, PERSISTENT, TRANSITIVE\n};\n\nnamespace detail{\n\ntemplate<typename T, ptr_mapped_src Src>\nclass ptr_mapped_impl\n{\n  T *ptr_raw;\npublic:\n  using difference_type = std::ptrdiff_t;\n  using value_type = std::remove_cv_t<T>;\n  using pointer = T*;\n  using reference = T&;\n  using iterator_category = std::random_access_iterator_tag;\n\n  ptr_mapped_impl(){\n  }\n\n  ptr_mapped_impl(T *p) : ptr_raw(p){\n  }\n\n  template<typename U, ptr_mapped_src SrcOther>\n  ptr_mapped_impl(const ptr_mapped_impl<U,SrcOther> &ptr) :\n    ptr_raw(ptr.get()){\n    static_assert(std::is_convertible_v<U*,T*>);\n  }\n\n  ptr_mapped_impl& operator=(T *p){\n    ptr_raw = p;\n    return *this;\n  }\n\n  template<typename U, ptr_mapped_src SrcOther>\n  ptr_mapped_impl& operator=(const ptr_mapped_impl<U,SrcOther> &ptr){\n    static_assert(std::is_convertible_v<U*,T*>);\n    ptr_raw = ptr.get();\n  }\n\n  T* get() const{\n    return ptr_raw;\n  }\n\n  operator T*() const{\n    return get();\n  }\n\n  // For simplicity, we only keep the least methods to satisfy the requirements of LegacyIterator\n\n  T& operator*() const{\n    return *get();\n  }\n\n  ptr_mapped_impl& operator++(){\n    ++ptr_raw;\n    return *this;\n  }\n\n  ptr_mapped_impl& operator+=(size_t n){\n    ptr_raw += n;\n    return *this;\n  }\n\n  ptr_mapped_impl operator+(size_t n) const{\n    return ptr_raw+n;\n  }\n\n  ptr_mapped_impl& operator-=(size_t n){\n    ptr_raw -= n;\n    return *this;\n  }\n\n  ptr_mapped_impl operator-(size_t n) const{\n    return ptr_raw - n;\n  }\n\n  difference_type operator-(const ptr_mapped_impl &other) const{\n    return ptr_raw - other.ptr_raw;\n  }\n\n  reference operator[](size_t i) const{\n    return ptr_raw[i];\n  }\n\n  bool operator<(const ptr_mapped_impl &other) const{\n    return ptr_raw < other.ptr_raw;\n  }\n\n  bool operator>(const ptr_mapped_impl &other) const{\n    return other<*this;\n  }\n\n  bool operator>=(const ptr_mapped_impl &other) const{\n    return !(*this<other);\n  }\n\n  bool operator<=(const ptr_mapped_impl &other) const{\n    return !(*this>other);\n  }\n};\n\n} // namespace detail\n\ntemplate<typename T, ptr_mapped_src Src>\nusing ptr_mapped = std::conditional_t<Src==ptr_mapped_src::NATIVE, T*, detail::ptr_mapped_impl<T,Src>>;\n/*\ntemplate<typename T, ptr_mapped_src Src>\nstruct std::iterator_traits<detail::ptr_mapped_impl<T,Src>>\n{\n  using difference_type = std::ptrdiff_t;\n  using value_type = std::remove_cv_t<T>;\n  using pointer = T*;\n  using reference = T&;\n  using iterator_category = void;\n};\n*/\n// *************************************************************\n//  SOME DEFINITIONS\n// *************************************************************\n\n\n// *************************************************************\n// Parsing code (should move to common?)\n// *************************************************************\n\n// returns a pointer and a length\nstd::pair<char*, size_t> mmapStringFromFile(const char* filename) {\n  struct stat sb;\n  int fd = open(filename, O_RDONLY);\n  if (fd == -1) {\n    perror(\"open\");\n    exit(-1);\n  }\n  if (fstat(fd, &sb) == -1) {\n    perror(\"fstat\");\n    exit(-1);\n  }\n  if (!S_ISREG(sb.st_mode)) {\n    perror(\"not a file\\n\");\n    exit(-1);\n  }\n  char* p =\n      static_cast<char*>(mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));\n  if (p == MAP_FAILED) {\n    perror(\"mmap\");\n    exit(-1);\n  }\n  if (close(fd) == -1) {\n    perror(\"close\");\n    exit(-1);\n  }\n  size_t n = sb.st_size;\n  return std::make_pair(p, n);\n}\n\n/*\nauto parse_fvecs(const char* filename)\n{\n  return parse_vecs<float>(filename, [](size_t id, auto begin, auto end){\n    typedef typename std::iterator_traits<decltype(begin)>::value_type type_elem;\n    static_assert(std::is_same_v<decltype(begin),ptr_mapped<type_elem,ptr_mapped_src::DISK>>);\n\n    Tvec_point<type_elem> point;\n    point.id = id;\n    point.coordinates = parlay::make_slice(begin.get(), end.get());\n    return point;\n  }).first;\n}\n\nauto parse_ivecs(const char* filename)\n{\n  return parse_vecs<float>(filename, [](size_t id, auto begin, auto end){\n    typedef typename std::iterator_traits<decltype(begin)>::value_type type_elem;\n    static_assert(std::is_same_v<decltype(begin),ptr_mapped<type_elem,ptr_mapped_src::DISK>>);\n\n    ivec_point point;\n    point.id = id;\n    point.coordinates = parlay::make_slice(begin.get(), end.get());\n    return point;\n  }).first;\n}\n*/\n\n// auto parse_bvecs_to_fvecs(const char* filename) {\n\n//   using slice_f = typename parlay::slice<float*, float*>;\n//   auto [fileptr, length] = mmapStringFromFile(filename);\n//   // std::cout << \"Successfully mmap'd\" << std::endl;\n\n//   // Each vector is 4 + d bytes.\n//   // * first 4 bytes encode the dimension (as an integer)\n//   // * next d values are unsigned chars representing vector components\n//   // See http://corpus-texmex.irisa.fr/ for more details.\n\n//   int d = *((int*)fileptr);\n//   std::cout << \"Dimension = \" << d << std::endl;\n\n//   size_t vector_size = 4 + d;\n//   size_t num_vectors = length / vector_size;\n//   std::cout << \"Num vectors = \" << num_vectors << std::endl;\n\n//   parlay::sequence<fvec_point> points(num_vectors);\n\n//   // parlay::parallel_for(0, num_vectors, [&] (size_t i) {\n//   //   size_t offset_in_bytes = vector_size * i + 4;  // skip dimension\n//   //   float* start = (float*)(fileptr + offset_in_bytes);\n//   //   float* end = start + d;\n//   //   points[i].id = i; \n//   //   points[i].coordinates = parlay::make_slice(start, end);\n//   // });\n\n//   parlay::parallel_for(0, num_vectors, [&] (size_t i) {\n//     size_t offset_in_bytes = vector_size * i + 4;  // skip dimension\n//     points[i].id = i; \n//     unsigned char* start = (unsigned char*)(fileptr + offset_in_bytes);\n//     parlay::sequence<float> coords = *new parlay::sequence<float>(d);\n//     for(int j=0; j<d; j++){\n//       float elt = *new float;\n//       elt = static_cast<float>(*(start+j));\n//       coords[j] = elt;\n//       // std::cout << coords[j] << std::endl; \n//     }\n//     slice_f slicecoords = *new slice_f(coords.begin(), coords.end());\n//     // slicecoords =  parlay::make_slice(coords.begin(), coords.end());\n//     points[i].coordinates = slicecoords;\n//   });\n\n\n\n//   return points;\n// }\n\n#endif // __BENCHUTILS_H__\n"
  },
  {
    "path": "algorithms/bench/common/IO.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <string>\n#include <cstring>\n#include \"../parlay/primitives.h\"\n#include \"../parlay/parallel.h\"\n#include \"../parlay/io.h\"\n#include \"../parlay/internal/get_time.h\"\n\nnamespace benchIO {\n  using namespace std;\n  using parlay::sequence;\n  using parlay::tabulate;\n  using parlay::make_slice;\n\n  auto is_space = [] (char c) {\n    switch (c)  {\n    case '\\r': \n    case '\\t': \n    case '\\n': \n    case 0:\n    case ' ' : return true;\n    default : return false;\n    }\n  };\n\n  // parallel code for converting a string to word pointers\n  // side effects string by setting to null after each word\n  template <class Seq>\n    parlay::sequence<char*> stringToWords(Seq &Str) {\n    size_t n = Str.size();\n    \n    parlay::parallel_for(0, n, [&] (long i) {\n\tif (is_space(Str[i])) Str[i] = 0;}); \n\n    // mark start of words\n    auto FL = parlay::tabulate(n, [&] (long i) -> bool {\n\treturn (i==0) ? Str[0] : Str[i] && !Str[i-1];});\n    \n    // offset for each start of word\n    auto Offsets = parlay::pack_index<long>(FL);\n\n    // pointer to each start of word\n    auto SA = parlay::tabulate(Offsets.size(), [&] (long j) -> char* {\n\treturn Str.begin() + Offsets[j];});\n    \n    return SA;\n  }\n\n  //using this as a typename so we can replace with parlay::chars easily if desired\n  using charstring = typename parlay::sequence<char>;\n\n  inline int xToStringLen(charstring const &a) { return a.size();}\n  inline void xToString(char* s, charstring const &a) {\n    for (int i=0; i < a.size(); i++) s[i] = a[i];}\n\n  inline int xToStringLen(long a) { return 21;}\n  inline void xToString(char* s, long a) { sprintf(s,\"%ld\",a);}\n\n  inline int xToStringLen(unsigned long a) { return 21;}\n  inline void xToString(char* s, unsigned long a) { sprintf(s,\"%lu\",a);}\n\n  inline uint xToStringLen(uint a) { return 12;}\n  inline void xToString(char* s, uint a) { sprintf(s,\"%u\",a);}\n\n  inline int xToStringLen(int a) { return 12;}\n  inline void xToString(char* s, int a) { sprintf(s,\"%d\",a);}\n\n  inline int xToStringLen(double a) { return 18;}\n  inline void xToString(char* s, double a) { sprintf(s,\"%.11le\", a);}\n\n  inline int xToStringLen(char* a) { return strlen(a)+1;}\n  inline void xToString(char* s, char* a) { sprintf(s,\"%s\",a);}\n\n  template <class A, class B>\n  inline int xToStringLen(pair<A,B> a) { \n    return xToStringLen(a.first) + xToStringLen(a.second) + 1;\n  }\n\n  template <class A, class B>\n  inline void xToString(char* s, pair<A,B> a) { \n    int l = xToStringLen(a.first);\n    xToString(s, a.first);\n    s[l] = ' ';\n    xToString(s+l+1, a.second);\n  }\n\n  template <class Seq>\n  charstring seqToString(Seq const &A) {\n    size_t n = A.size();\n    auto L = parlay::tabulate(n, [&] (size_t i) -> long {\n\ttypename Seq::value_type x = A[i];\n\treturn xToStringLen(x)+1;});\n    size_t m;\n    std::tie(L,m) = parlay::scan(std::move(L));\n\n    charstring B(m+1, (char) 0);\n    char* Bs = B.begin();\n\n    parlay::parallel_for(0, n-1, [&] (long i) {\n      xToString(Bs + L[i], A[i]);\n      Bs[L[i+1] - 1] = '\\n';\n      });\n    xToString(Bs + L[n-1], A[n-1]);\n    Bs[m] = Bs[m-1] = '\\n';\n    \n    charstring C = parlay::filter(B, [&] (char c) {return c != 0;}); \n    C[C.size()-1] = 0;\n    return C;\n  }\n\n  template <class T>\n  void writeSeqToStream(ofstream& os, parlay::sequence<T> const &A) {\n    size_t bsize = 10000000;\n    size_t offset = 0;\n    size_t n = A.size();\n    while (offset < n) {\n      // Generates a string for a sequence of size at most bsize\n      // and then wrties it to the output stream\n      charstring S = seqToString(A.cut(offset, min(offset + bsize, n)));\n      os.write(S.begin(), S.size()-1);\n      offset += bsize;\n    }\n  }\n\n  template <class T>\n  int writeSeqToFile(string header,\n\t\t     parlay::sequence<T> const &A,\n\t\t     char const *fileName) {\n    auto a = A[0];\n    //xToStringLena(a);\n    ofstream file (fileName, ios::out | ios::binary);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      return 1;\n    }\n    file << header << endl;\n    writeSeqToStream(file, A);\n    file.close();\n    return 0;\n  }\n\n  template <class T1, class T2>\n  int write2SeqToFile(string header,\n\t\t      parlay::sequence<T1> const &A,\n\t\t      parlay::sequence<T2> const &B,\n\t\t      char const *fileName) {\n    ofstream file (fileName, ios::out | ios::binary);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      return 1;\n    }\n    file << header << endl;\n    writeSeqToStream(file, A);\n    writeSeqToStream(file, B);\n    file.close();\n    return 0;\n  }\n\n  charstring readStringFromFile(char const *fileName) {\n    ifstream file (fileName, ios::in | ios::binary | ios::ate);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      abort();\n    }\n    long end = file.tellg();\n    file.seekg (0, ios::beg);\n    long n = end - file.tellg();\n    charstring bytes(n, (char) 0);\n    file.read (bytes.begin(), n);\n    file.close();\n    return bytes;\n  }\n\n  string intHeaderIO = \"sequenceInt\";\n\n  template <class T>\n  int writeIntSeqToFile(parlay::sequence<T> const &A, char const *fileName) {\n    return writeSeqToFile(intHeaderIO, A, fileName);\n  }\n\n  sequence<sequence<char>> get_tokens(char const *fileName) {\n    // parlay::internal::timer t(\"get_tokens\");\n    // auto S = parlay::chars_from_file(fileName);\n    auto S = parlay::file_map(fileName);\n    // t.next(\"file map\");\n    auto r =  parlay::tokens(S, benchIO::is_space);\n    // t.next(\"tokens\");\n    return r;\n  }\n\n  template <class T>\n  parlay::sequence<T> readIntSeqFromFile(char const *fileName) {\n    auto W = get_tokens(fileName);\n    string header(W[0].begin(),W[0].end());\n    if (header != intHeaderIO) {\n      cout << \"readIntSeqFromFile: bad input\" << endl;\n      abort();\n    }\n    long n = W.size()-1;\n    auto A = parlay::tabulate(n, [&] (long i) -> T {\n\treturn parlay::chars_to_long(W[i+1]);});\n    return A;\n  }\n};\n\n"
  },
  {
    "path": "algorithms/bench/common/MakeBench",
    "content": "# ********************\n# GENERIC MAKEFILE FOR MOST BENCHMARKS THAT #include <name>.h\n# USES FOLLOWING DEFINITIONS\n#    BENCH : the name of the benchmark\n#    REQUIRE : dependences\n#    CC : the compiler\n#    CFLAGS : compiler flags\n#    LFLAGS : compiler link flags\n# ********************\n\nTIME = ../bench/$(BENCH)Time.C\nCHECK = $(BENCH)Check\nINCLUDE = \n\nall : $(BENCH) testInputs\n\tcd ../bench; make -s $(CHECK)\n\n$(BENCH) : $(TIME) $(BENCH).h $(REQUIRE)\n\t$(CC) $(CFLAGS) $(INCLUDE) -include $(BENCH).h -o $(BENCH) $(TIME) $(LFLAGS)\n\ntestInputs : ../bench/testInputs ../bench/testInputs_small\n\tcp ../bench/testInputs ../bench/testInputs_small .\n\nclean :\n\trm -f $(BENCH)\n\ncleanall : clean\n\trm -f testInputs*; cd ../bench; make -s clean\n"
  },
  {
    "path": "algorithms/bench/common/MakeBenchLink",
    "content": "# ********************\n# GENERIC MAKEFILE FOR MOST BENCHMARKS THAT LINK\n# THE TIMING CODE WITH THE IMPLEMENTATION\n# USES FOLLOWING DEFINITIONS\n#    BENCH : the name of the benchmark\n#    OBJS : implementation object files\n#    REQUIRE : dependences for the object files\n#    CC : the compiler\n#    CFLAGS : compiler flags\n#    LFLAGS : compiler link flags\n# ********************\n\nTIME = $(BENCH)Time\nCHECK = ../bench/$(BENCH)Check\nINCLUDE =\n\n# Make benchmark\n$(BENCH) : $(TIME).o $(OBJS) $(CHECK) testInputs\n\t$(CC) -o $@ $(TIME).o $(OBJS) $(LFLAGS)\n\n# Timing Code\n$(TIME).o : ../bench/$(TIME).C \n\t$(CC) $(CFLAGS) $(INCLUDE) -o $@ -c ../bench/$(TIME).C\n\t\n# The check code\n$(CHECK) : $(CHECK).C\n\tcd ../bench; make -s $(BENCH)Check\n\n# object files\n%.o : %.C $(REQUIRE)\n\t$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@\n\n# copy over the generic test code\ntestInputs : ../bench/testInputs ../bench/testInputs_small\n\tcp ../bench/testInputs ../bench/testInputs_small .\n\nclean :\n\trm -f $(BENCH) *.o\n\ncleanall : clean\n\trm -f testInputs*; cd ../bench; make -s clean\n"
  },
  {
    "path": "algorithms/bench/common/atomics.h",
    "content": "#pragma once\n\nnamespace pbbs {\n\n  template <typename ET>\n  inline bool atomic_compare_and_swap(ET* a, ET oldval, ET newval) {\n    static_assert(sizeof(ET) <= 8, \"Bad CAS length\");\n    if (sizeof(ET) == 1) {\n      uint8_t r_oval, r_nval;\n      std::memcpy(&r_oval, &oldval, sizeof(ET));\n      std::memcpy(&r_nval, &newval, sizeof(ET));\n      return __sync_bool_compare_and_swap(reinterpret_cast<uint8_t*>(a), r_oval, r_nval);\n    } else if (sizeof(ET) == 4) {\n      uint32_t r_oval, r_nval;\n      std::memcpy(&r_oval, &oldval, sizeof(ET));\n      std::memcpy(&r_nval, &newval, sizeof(ET));\n      return __sync_bool_compare_and_swap(reinterpret_cast<uint32_t*>(a), r_oval, r_nval);\n    } else { // if (sizeof(ET) == 8) {\n      uint64_t r_oval, r_nval;\n      std::memcpy(&r_oval, &oldval, sizeof(ET));\n      std::memcpy(&r_nval, &newval, sizeof(ET));\n      return __sync_bool_compare_and_swap(reinterpret_cast<uint64_t*>(a), r_oval, r_nval);\n    } \n  }\n\n  template <typename E, typename EV>\n  inline E fetch_and_add(E *a, EV b) {\n    volatile E newV, oldV;\n    do {oldV = *a; newV = oldV + b;}\n    while (!atomic_compare_and_swap(a, oldV, newV));\n    return oldV;\n  }\n\n  template <typename E, typename EV>\n  inline void write_add(E *a, EV b) {\n    //volatile E newV, oldV;\n    E newV, oldV;\n    do {oldV = *a; newV = oldV + b;}\n    while (!atomic_compare_and_swap(a, oldV, newV));\n  }\n\n  template <typename E, typename EV>\n  inline void write_add(std::atomic<E> *a, EV b) {\n    //volatile E newV, oldV;\n    E newV, oldV;\n    do {oldV = a->load(); newV = oldV + b;}\n    while (!std::atomic_compare_exchange_strong(a, &oldV, newV));\n  }\n\n  template <typename ET, typename F>\n  inline bool write_min(ET *a, ET b, F less) {\n    ET c; bool r=0;\n    do c = *a;\n    while (less(b,c) && !(r=atomic_compare_and_swap(a,c,b)));\n    return r;\n  }\n\n  template <typename ET, typename F>\n  inline bool write_min(std::atomic<ET> *a, ET b, F less) {\n    ET c; bool r=0;\n    do c = a->load();\n    while (less(b,c) && !(r=std::atomic_compare_exchange_strong(a, &c, b)));\n    return r;\n  }\n\n  template <typename ET, typename F>\n  inline bool write_max(ET *a, ET b, F less) {\n    ET c; bool r=0;\n    do c = *a;\n    while (less(c,b) && !(r=atomic_compare_and_swap(a,c,b)));\n    return r;\n  }\n\n  template <typename ET, typename F>\n  inline bool write_max(std::atomic<ET> *a, ET b, F less) {\n    ET c; bool r=0;\n    do c = a->load();\n    while (less(c,b) && !(r=std::atomic_compare_exchange_strong(a, &c, b)));\n    return r;\n  }\n}\n"
  },
  {
    "path": "algorithms/bench/common/dataGen.h",
    "content": "#pragma once\n#include \"../parlay/utilities.h\"\n\nnamespace dataGen {\n\n#define HASH_MAX_INT ((unsigned) 1 << 31)\n\n  //#define HASH_MAX_LONG ((unsigned long) 1 << 63)\n\n  template <class T> T hash(size_t i);\n  \n  template <>\n  inline int hash<int>(size_t i) {\n    return parlay::hash64(i) & ((((size_t) 1) << 31) - 1);}\n\n  template <>\n  inline long  hash<long>(size_t i) {\n    return parlay::hash64(i) & ((((size_t) 1) << 63) - 1);}\n\n  template <>\n  inline unsigned int hash<unsigned int>(size_t i) {\n    return parlay::hash64(i);}\n\n  template <>\n  inline size_t hash<size_t>(size_t i) {\n    return parlay::hash64(i);}\n\n  template <>\n  inline double hash<double>(size_t i) {\n    return ((double) hash<int>(i)/((double) ((((size_t) 1) << 31) - 1)));}\n\n  template <>\n  inline float hash<float>(size_t i) {\n    return ((double) hash<int>(i)/((double) ((((size_t) 1) << 31) - 1)));}\n};\n"
  },
  {
    "path": "algorithms/bench/common/geometry.h",
    "content": "#pragma once\n#include <iostream>\n#include <algorithm>\n#include <math.h>\n#include <iomanip>\n#include \"../parlay/parallel.h\"\n#include \"../parlay/primitives.h\"\nusing namespace std;\n\n// *************************************************************\n//    POINTS AND VECTORS (3d),  2d is below\n// *************************************************************\n\n\n  template <class Coord>\n  class point3d;\n\n  template <class Coord>\n  class vector3d {\n  public:\n    using coord = Coord;\n    using vector = vector3d;\n    using point = point3d<coord>;\n    coord x;\n    coord y;\n    coord z;\n    vector3d(coord x, coord y, coord z) : x(x), y(y), z(z) {}\n    vector3d() :x(0), y(0), z(0) {}\n    vector3d(point p);\n    vector3d(parlay::slice<coord*,coord*> p) : x(p[0]), y(p[1]), z(p[2]) {};\n    vector operator+(vector op2) {\n      return vector(x + op2.x, y + op2.y, z + op2.z);}\n    vector operator-(vector op2) {\n      return vector(x - op2.x, y - op2.y, z - op2.z);}\n    point operator+(point op2);\n    vector operator*(coord s) {return vector(x * s, y * s, z * s);}\n    vector operator/(coord s) {return vector(x / s, y / s, z / s);}\n    coord& operator[] (int i) {return (i==0) ? x : (i==1) ? y : z;}\n    coord dot(vector v) {return x * v.x + y * v.y + z * v.z;}\n    vector cross(vector v) {\n      return vector(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x);\n    }\n    coord maxDim() {return max(x,max(y,z));}\n    void print() {cout << std::setprecision(10) << \":(\" << x << \",\" << y << \",\" << z << \"):\";}\n    coord Length(void) { return sqrt(x*x+y*y+z*z);}\n    coord sqLength(void) { return x*x+y*y+z*z;}\n    static const int dim = 3;\n  };\n\n  template <class Coord>\n  class point3d {\n  public:\n    using coord = Coord;\n    using vector = vector3d<coord>;\n    using point = point3d;\n    coord x; coord y; coord z;\n    int dimension() {return 3;}\n    point3d(coord x, coord y, coord z) : x(x), y(y), z(z) {}\n    point3d() : x(0), y(0), z(0) {}\n    point3d(vector v) : x(v.x), y(v.y), z(v.z) {};\n    point3d(parlay::slice<coord*,coord*> p) : x(p[0]), y(p[1]), z(p[2]) {};\n    void print() {cout << \":(\" << x << \",\" << y << \",\" << z << \"):\";}\n    vector operator-(point op2) {\n      return vector(x - op2.x, y - op2.y, z - op2.z);}\n    point operator+(vector op2) {\n      return point(x + op2.x, y + op2.y, z + op2.z);}\n    point minCoords(point b) {\n      return point(min(x,b.x),min(y,b.y),min(z,b.z)); }\n    point maxCoords(point b) { \n      return point(max(x,b.x),max(y,b.y),max(z,b.z)); }\n    coord& operator[] (int i) {return (i==0) ? x : (i==1) ? y : z;}\n    int quadrant(point center) {\n      int index = 0;\n      if (x > center.x) index += 1;\n      if (y > center.y) index += 2;\n      if (z > center.z) index += 4;\n      return index;\n    }\n    // returns a point offset by offset in one of 8 directions \n    // depending on dir (an integer from [0..7])\n    point offsetPoint(int dir, coord offset) {\n      coord xx = x + ((dir & 1) ? offset : -offset);\n      coord yy = y + ((dir & 2) ? offset : -offset);\n      coord zz = z + ((dir & 4) ? offset : -offset);\n      return point(xx, yy, zz);\n    }\n    point changeCoords(std::vector<coord> v){\n      return point(v[0], v[1], v[2]);\n    }\n    // checks if pt is outside of a box centered at this point with\n    // radius hsize\n    bool outOfBox(point pt, coord hsize) { \n      return ((x - hsize > pt.x) || (x + hsize < pt.x) ||\n\t      (y - hsize > pt.y) || (y + hsize < pt.y) ||\n\t      (z - hsize > pt.z) || (z + hsize < pt.z));\n    }\n    static const int dim = 3;\n  };\n\n  template <class coord>\n  inline point3d<coord> vector3d<coord>::operator+(point3d<coord> op2) {\n    return point3d<coord>(x + op2.x, y + op2.y, z + op2.z);}\n\n  template <class coord>\n  inline vector3d<coord>::vector3d(point3d<coord> p) { x = p.x; y = p.y; z = p.z;}\n\n  // *************************************************************\n  //    POINTS AND VECTORS (2d)\n  // *************************************************************\n\n  template <class Coord>\n  class point2d;\n\n  template <class Coord>\n  class vector2d {\n  public: \n    using coord = Coord;\n    using point = point2d<coord>;\n    using vector = vector2d;\n    coord x; coord y;\n    vector2d(coord x, coord y) : x(x), y(y) {}\n    vector2d() : x(0), y(0)  {}\n    vector2d(point p);\n    vector2d(parlay::slice<coord*,coord*> p) : x(p[0]), y(p[1]) {};\n    vector operator+(vector op2) {return vector(x + op2.x, y + op2.y);}\n    vector operator-(vector op2) {return vector(x - op2.x, y - op2.y);}\n    point operator+(point op2);\n    vector operator*(coord s) {return vector(x * s, y * s);}\n    vector operator/(coord s) {return vector(x / s, y / s);}\n    coord operator[] (int i) {return (i==0) ? x : y;};\n    coord dot(vector v) {return x * v.x + y * v.y;}\n    coord cross(vector v) { return x*v.y - y*v.x; }  \n    coord maxDim() {return max(x,y);}\n    void print() {cout << \":(\" << x << \",\" << y << \"):\";}\n    coord Length(void) { return sqrt(x*x+y*y);}\n    coord sqLength(void) { return x*x+y*y;}\n    static const int dim = 2;\n  };\n\n  template <class coord>\n  static std::ostream& operator<<(std::ostream& os, const vector3d<coord> v) {\n    return os << v.x << \" \" << v.y << \" \" << v.z; }\n\n  template <class coord>\n  static std::ostream& operator<<(std::ostream& os, const point3d<coord> v) {\n    return os << v.x << \" \" << v.y << \" \" << v.z;\n  }\n\n  template <class Coord>\n  class point2d {\n  public: \n    using coord = Coord;\n    using vector = vector2d<coord>;\n    using point = point2d;\n    coord x; coord y; \n    int dimension() {return 2;}\n    point2d(coord x, coord y) : x(x), y(y) {}\n    point2d() : x(0), y(0) {}\n    point2d(vector v) : x(v.x), y(v.y) {};\n    point2d(parlay::slice<coord*,coord*> p) : x(p[0]), y(p[1]) {};\n    void print() {cout << \":(\" << x << \",\" << y << \"):\";}\n    vector operator-(point op2) {return vector(x - op2.x, y - op2.y);}\n    point operator+(vector op2) {return point(x + op2.x, y + op2.y);}\n    coord operator[] (int i) {return (i==0) ? x : y;};\n    point minCoords(point b) { return point(min(x,b.x),min(y,b.y)); }\n    point maxCoords(point b) { return point(max(x,b.x),max(y,b.y)); }\n    int quadrant(point center) {\n      int index = 0;\n      if (x > center.x) index += 1;\n      if (y > center.y) index += 2;\n      return index;\n    }\n    // returns a point offset by offset in one of 4 directions \n    // depending on dir (an integer from [0..3])\n    point offsetPoint(int dir, coord offset) {\n      coord xx = x + ((dir & 1) ? offset : -offset);\n      coord yy = y + ((dir & 2) ? offset : -offset);\n      return point(xx,yy);\n    }\n    bool outOfBox(point pt, coord hsize) { \n      return ((x - hsize > pt.x) || (x + hsize < pt.x) ||\n\t      (y - hsize > pt.y) || (y + hsize < pt.y));\n    }\n    static const int dim = 2;\n  };\n\n  template <class coord>\n  inline point2d<coord> vector2d<coord>::operator+(point2d<coord> op2) {\n    return point2d<coord>(x + op2.x, y + op2.y);}\n\n  template <class coord>\n  inline vector2d<coord>::vector2d(point2d<coord> p) { x = p.x; y = p.y;}\n\n  template <class coord>\n  static std::ostream& operator<<(std::ostream& os, const vector2d<coord> v) {\n    return os << v.x << \" \" << v.y;}\n\n  template <class coord>\n  static std::ostream& operator<<(std::ostream& os, const point2d<coord> v) {\n    return os << v.x << \" \" << v.y; }\n\n  // *************************************************************\n  //    GEOMETRY\n  // *************************************************************\n\n  // Returns twice the area of the oriented triangle (a, b, c)\n  template <class coord>\n  inline coord triArea(point2d<coord> a, point2d<coord> b, point2d<coord> c) {\n    return (b-a).cross(c-a);\n  }\n\n  template <class coord>\n  inline coord triAreaNormalized(point2d<coord> a, point2d<coord> b, point2d<coord> c) {\n    return triArea(a,b,c)/((b-a).Length()*(c-a).Length());\n  }\n\n  // Returns TRUE if the points a, b, c are in a counterclockise order\n  template <class coord>\n  inline bool counterClockwise(point2d<coord> a, point2d<coord> b, point2d<coord> c) {\n    return (b-a).cross(c-a) > 0.0;\n  }\n\n  template <class coord>\n  inline vector3d<coord> onParabola(vector2d<coord> v) {\n    return vector3d<coord>(v.x, v.y, v.x*v.x + v.y*v.y);}\n\n  // Returns TRUE if the point d is inside the circle defined by the\n  // points a, b, c. \n  // Projects a, b, c onto a parabola centered with d at the origin\n  //   and does a plane side test (tet volume > 0 test)\n  template <class coord>\n  inline bool inCircle(point2d<coord> a, point2d<coord> b, \n\t\t       point2d<coord> c, point2d<coord> d) {\n    vector3d<coord> ad = onParabola(a-d);\n    vector3d<coord> bd = onParabola(b-d);\n    vector3d<coord> cd = onParabola(c-d);\n    return (ad.cross(bd)).dot(cd) > 0.0;\n  }\n\n  // returns a number between -1 and 1, such that -1 is out at infinity,\n  // positive numbers are on the inside, and 0 is at the boundary\n  template <class coord>\n  inline double inCircleNormalized(point2d<coord> a, point2d<coord> b, \n\t\t\t\t   point2d<coord> c, point2d<coord> d) {\n    vector3d<coord> ad = onParabola(a-d);\n    vector3d<coord> bd = onParabola(b-d);\n    vector3d<coord> cd = onParabola(c-d);\n    return (ad.cross(bd)).dot(cd)/(ad.Length()*bd.Length()*cd.Length());\n  }\n\n  // *************************************************************\n  //    TRIANGLES\n  // *************************************************************\n\n  using tri = std::array<int,3>;\n\n  template <class point>\n  struct triangles {\n    size_t numPoints() {return P.size();};\n    size_t numTriangles() {return T.size();}\n    parlay::sequence<point> P;\n    parlay::sequence<tri> T;\n    triangles() {}\n    triangles(parlay::sequence<point> P, parlay::sequence<tri> T) \n      : P(std::move(P)), T(std::move(T)) {}\n  };\n\n  template <class point>\n  struct ray {\n    using vector = typename point::vector;\n    point o;\n    vector d;\n    ray(point _o, vector _d) : o(_o), d(_d) {}\n    ray() {}\n  };\n\n  template<class coord>\n  inline coord angle(point2d<coord> a, point2d<coord> b, point2d<coord> c) {\n    vector2d<coord> ba = (b-a);\n    vector2d<coord> ca = (c-a);\n    coord lba = ba.Length();\n    coord lca = ca.Length();\n    coord pi = 3.14159;\n    return 180/pi*acos(ba.dot(ca)/(lba*lca));\n  }\n\n  template<class coord>\n  inline coord minAngleCheck(point2d<coord> a, point2d<coord> b, point2d<coord> c, coord angle) {\n    vector2d<coord> ba = (b-a);\n    vector2d<coord> ca = (c-a);\n    vector2d<coord> cb = (c-b);\n    coord lba = ba.Length();\n    coord lca = ca.Length();\n    coord lcb = cb.Length();\n    coord pi = 3.14159;\n    coord co = cos(angle*pi/180.);\n    return (ba.dot(ca)/(lba*lca) > co || ca.dot(cb)/(lca*lcb) > co || \n\t    -ba.dot(cb)/(lba*lcb) > co);\n  }\n\n  template<class coord>\n  inline point2d<coord> triangleCircumcenter(point2d<coord> a, point2d<coord> b, point2d<coord> c) {\n    vector2d<coord> v1 = b-a;\n    vector2d<coord> v2 = c-a;\n    vector2d<coord> v11 = v1 * v2.dot(v2);\n    vector2d<coord> v22 = v2 * v1.dot(v1);\n    return a + vector2d<coord>(v22.y - v11.y, v11.x - v22.x)/(2.0 * v1.cross(v2));\n  }\n\n"
  },
  {
    "path": "algorithms/bench/common/geometryIO.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n#include \"../parlay/parallel.h\"\n#include \"../parlay/primitives.h\"\n#include \"geometry.h\"\n#include \"IO.h\"\n\n//using namespace geometry;\nusing namespace benchIO;\n\n  template <class coord>\n  inline int xToStringLen(point2d<coord> a) { \n    return xToStringLen(a.x) + xToStringLen(a.y) + 1;\n  }\n\n  template <class coord>\n  inline void xToString(char* s, point2d<coord> a) { \n    int l = xToStringLen(a.x);\n    xToString(s, a.x);\n    s[l] = ' ';\n    xToString(s+l+1, a.y);\n  }\n\n  template <class coord>\n  inline int xToStringLen(point3d<coord> a) { \n    return xToStringLen(a.x) + xToStringLen(a.y) + xToStringLen(a.z) + 2;\n  }\n\n  template <class coord>\n  inline void xToString(char* s, point3d<coord> a) { \n    int lx = xToStringLen(a.x);\n    int ly = xToStringLen(a.y);\n    xToString(s, a.x);\n    s[lx] = ' ';\n    xToString(s+lx+1, a.y);\n    s[lx+ly+1] = ' ';\n    xToString(s+lx+ly+2, a.z);\n  }\n\n  // inline int xToStringLen(tri a) { \n  //   return xToStringLen(a[0]) + xToStringLen(a[1]) + xToStringLen(a[2]) + 2;\n  // }\n\n  // inline void xToString(char* s, tri a) { \n  //   int lx = xToStringLen(a[0]);\n  //   int ly = xToStringLen(a[1]);\n  //   xToString(s, a[0]);\n  //   s[lx] = ' ';\n  //   xToString(s+lx+1, a[1]);\n  //   s[lx+ly+1] = ' ';\n  //   xToString(s+lx+ly+2, a[2]);\n  // }\n\nnamespace benchIO {\n  using namespace std;\n\n  string HeaderPoint2d = \"pbbs_sequencePoint2d\";\n  string HeaderPoint3d = \"pbbs_sequencePoint3d\";\n  string HeaderTriangles = \"pbbs_triangles\";\n\n  template <class Point>\n    int writePointsToFile(parlay::sequence<Point> const &P, char const *fname) {\n    string Header = (Point::dim == 2) ? HeaderPoint2d : HeaderPoint3d;\n    int r = writeSeqToFile(Header, P, fname);\n    return r;\n  }\n\n  template <class Point, class Seq>\n  parlay::sequence<Point> parsePoints(Seq W) {\n    using coord = typename Point::coord;\n    int d = Point::dim;\n    size_t n = W.size()/d;\n    auto a = parlay::tabulate(d * n, [&] (size_t i) -> coord {\n\treturn atof(W[i]);});\n    auto points = parlay::tabulate(n, [&] (size_t i) -> Point {\n\treturn Point(a.cut(d*i,d*(i + 1)));});\n    return points;\n  }\n\n  template <class Point>\n  parlay::sequence<Point> readPointsFromFile(char const *fname) {\n    parlay::sequence<char> S = readStringFromFile(fname);\n    parlay::sequence<char*> W = stringToWords(S);\n    int d = Point::dim;\n    if (W.size() == 0 || W[0] != (d == 2 ? HeaderPoint2d : HeaderPoint3d)) {\n      cout << \"readPointsFromFile wrong file type\" << endl;\n      abort();\n    }\n    return parsePoints<Point>(W.cut(1,W.size()));\n  }\n\n  // triangles<point2d> readTrianglesFromFileNodeEle(char const *fname) {\n  //   string nfilename(fname);\n  //   _seq<char> S = readStringFromFile((char*)nfilename.append(\".node\").c_str());\n  //   words W = stringToWords(S.A, S.n);\n  //   triangles<point2d> Tr;\n  //   Tr.numPoints = atol(W.Strings[0]);\n  //   if (W.m < 4*Tr.numPoints + 4) {\n  //     cout << \"readStringFromFileNodeEle inconsistent length\" << endl;\n  //     abort();\n  //   }\n\n  //   Tr.P = newA(point2d, Tr.numPoints);\n  //   for(intT i=0; i < Tr.numPoints; i++) \n  //     Tr.P[i] = point2d(atof(W.Strings[4*i+5]), atof(W.Strings[4*i+6]));\n\n  //   string efilename(fname);\n  //   _seq<char> SN = readStringFromFile((char*)efilename.append(\".ele\").c_str());\n  //   words WE = stringToWords(SN.A, SN.n);\n  //   Tr.numTriangles = atol(WE.Strings[0]);\n  //   if (WE.m < 4*Tr.numTriangles + 3) {\n  //     cout << \"readStringFromFileNodeEle inconsistent length\" << endl;\n  //     abort();\n  //   }\n\n  //   Tr.T = newA(triangle, Tr.numTriangles);\n  //   for (long i=0; i < Tr.numTriangles; i++)\n  //     for (int j=0; j < 3; j++)\n  // \tTr.T[i].C[j] = atol(WE.Strings[4*i + 4 + j]);\n\n  //   return Tr;\n  // }\n\n  template <class pointT>\n  triangles<pointT> readTrianglesFromFile(char const *fname, int offset) {\n    int d = pointT::dim;\n    parlay::sequence<char> S = readStringFromFile(fname);\n    parlay::sequence<char*> W = stringToWords(S);\n    if (W[0] != HeaderTriangles) {\n      cout << \"readTrianglesFromFile wrong file type\" << endl;\n      abort();\n    }\n\n    int headerSize = 3;\n    size_t n = atol(W[1]);\n    size_t m = atol(W[2]);\n    if (W.size() != headerSize + 3 * m + d * n) {\n      cout << \"readTrianglesFromFile inconsistent length\" << endl;\n      abort();\n    }\n\n    auto pts_slice = W.cut(headerSize, headerSize + d * n);\n    auto tri_slice = W.cut(headerSize + d * n, W.size());\n    parlay::sequence<pointT> Pts = parsePoints<pointT>(pts_slice);\n    auto Tri = parlay::tabulate(m, [&] (size_t i ) -> tri {\n\t\t\t\t     return {(int) atol(tri_slice[3*i])-offset,\n\t\t\t\t\t     (int) atol(tri_slice[3*i+1])-offset,\n\t\t\t\t\t     (int) atol(tri_slice[3*i+2])-offset};});\n    return triangles<pointT>(Pts,Tri);\n  }\n\n  template <class pointT>\n  int writeTrianglesToFile(triangles<pointT> Tr, char* fileName) {\n    ofstream file (fileName, ios::binary);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      return 1;\n    }\n    file << HeaderTriangles << endl;\n    file << Tr.numPoints() << endl; \n    file << Tr.numTriangles() << endl; \n    writeSeqToStream(file, Tr.P);\n    //writeSeqToStream(file, Tr.T);\n    auto A = parlay::tabulate(3*Tr.numTriangles(), [&] (size_t i) -> int {\n      \t\t\t\t\t\t     return (Tr.T[i/3])[i%3];});\n    writeSeqToStream(file, A);\n    file.close();\n    return 0;\n  }\n\n};\n\n"
  },
  {
    "path": "algorithms/bench/common/get_time.h",
    "content": "#pragma once\n\n#include <stdlib.h>\n#include <sys/time.h>\n#include <iomanip>\n#include <iostream>\n#include <string>\n\nstruct timer {\n  double total_time;\n  double last_time;\n  bool on;\n  std::string name;\n  struct timezone tzp;\n\n  timer(std::string name = \"PBBS time\", bool _start = true)\n  : total_time(0.0), on(false), name(name), tzp({0,0}) {\n    if (_start) start();\n  }\n\n  double get_time() {\n    timeval now;\n    gettimeofday(&now, &tzp);\n    return ((double) now.tv_sec) + ((double) now.tv_usec)/1000000.;\n  }\n\n  void start () {\n    on = 1;\n    last_time = get_time();\n  }\n\n  double stop () {\n    on = 0;\n    double d = (get_time()-last_time);\n    total_time += d;\n    return d;\n  }\n\n  void reset() {\n     total_time=0.0;\n     on=0;\n  }\n\n  double get_total() {\n    if (on) return total_time + get_time() - last_time;\n    else return total_time;\n  }\n\n  double get_next() {\n    if (!on) return 0.0;\n    double t = get_time();\n    double td = t - last_time;\n    total_time += td;\n    last_time = t;\n    return td;\n  }\n\n  void report(double time, std::string str) {\n    std::ios::fmtflags cout_settings = std::cout.flags();\n    std::cout.precision(4);\n    std::cout << std::fixed;\n    std::cout << name << \": \";\n    if (str.length() > 0)\n      std::cout << str << \": \";\n    std::cout << time << std::endl;\n    std::cout.flags(cout_settings);\n  }\n\n  void total() {\n    report(get_total(),\"total\");\n    total_time = 0.0;\n  }\n\n  void reportTotal(std::string str) {\n    report(get_total(), str);\n  }\n\n  void next(std::string str) {\n    if (on) report(get_next(), str);\n  }\n};\n\nstatic timer _tm;\n#define startTime() _tm.start();\n#define nextTime(_string) _tm.next(_string);\n"
  },
  {
    "path": "algorithms/bench/common/glue.h",
    "content": "#pragma once\n\n#include \"../pbbslib/hash_table.h\"\n#include \"../pbbslib/integer_sort.h\"\n\nusing intT = int;\nusing uintT = unsigned int;\n\n#define newA(__E,__n) (__E*) malloc((__n)*sizeof(__E))\n\nnamespace utils {\n\n  static void myAssert(int cond, std::string s) {\n    if (!cond) {\n      std::cout << s << std::endl;\n      abort();\n    }\n  }\n\n  inline unsigned int hash(unsigned int a)\n  {\n    a = (a+0x7ed55d16) + (a<<12);\n    a = (a^0xc761c23c) ^ (a>>19);\n    a = (a+0x165667b1) + (a<<5);\n    a = (a+0xd3a2646c) ^ (a<<9);\n    a = (a+0xfd7046c5) + (a<<3);\n    a = (a^0xb55a4f09) ^ (a>>16);\n    return a;\n  }\n\n  inline int hashInt(unsigned int a) {  \n    return hash(a) & (((unsigned) 1 << 31) - 1);\n  }\n\n  // template <class E>\n  // struct identityF { E operator() (const E& x) {return x;}};\n\n  // template <class E>\n  // struct addF { E operator() (const E& a, const E& b) const {return a+b;}};\n\n  // template <class E>\n  // struct absF { E operator() (const E& a) const {return std::abs(a);}};\n\n  // template <class E>\n  // struct zeroF { E operator() (const E& a) const {return 0;}};\n\n  // template <class E>\n  // struct maxF { E operator() (const E& a, const E& b) const {return (a>b) ? a : b;}};\n\n  // template <class E>\n  // struct minF { E operator() (const E& a, const E& b) const {return (a<b) ? a : b;}};\n\n  // template <class E1, class E2>\n  // struct firstF {E1 operator() (std::pair<E1,E2> a) {return a.first;} };\n\n  // template <class E1, class E2>\n  // struct secondF {E2 operator() (std::pair<E1,E2> a) {return a.second;} };\n\n}\n\n// template <class T>\n// struct _seq {\n//   T* A;\n//   long n;\n//   _seq() {A = NULL; n=0;}\n//   _seq(T* _A, long _n) : A(_A), n(_n) {}\n//   void del() {free(A);}\n// };\n\n// namespace osequence {\n\n//   template <class ET, class intT, class F>\n//   ET scan(ET *In, ET* Out, intT n, F f, ET zero) {\n//     if (In == Out)\n//       return pbbs::scan_inplace(pbbs::range<ET*>(In,In+n),pbbs::make_monoid(f,zero));\n//     else {\n//       std::cout << \"NYI in scan\" << std::endl;\n//       return zero;\n//     }\n//   }\n\n//   template <class ET>\n//   ET plusScan(ET *In, ET* Out, size_t n) {\n//     return scan(In, Out, n, [&] (ET a, ET b) {return a + b;}, (ET) 0);\n//   }\n  \n//   template <class ET, class PRED>\n//   size_t filter(ET* In, ET* Out, size_t n, PRED p) {\n//     pbbs::sequence<ET> r = pbbs::filter(pbbs::range<ET*>(In,In+n), p);\n//     parallel_for(0, r.size(), [&] (size_t i) {Out[i] = r[i];});\n//     return r.size();\n//   }\n\n// };\n\nnamespace dataGen {\n\n  using namespace std;\n\n#define HASH_MAX_INT ((unsigned) 1 << 31)\n\n  //#define HASH_MAX_LONG ((unsigned long) 1 << 63)\n\n  template <class T> T hash(intT i);\n  \n  template <>\n  inline intT hash<intT>(intT i) {\n    return utils::hash(i) & (HASH_MAX_INT-1);}\n\n  template <>\n  inline uintT hash<uintT>(intT i) {\n    return utils::hash(i);}\n\n  template <>\n  inline double hash<double>(intT i) {\n    return ((double) hash<intT>(i)/((double) HASH_MAX_INT));}\n\n};\n\n// template <class HASH, class ET>\n// _seq<ET> removeDuplicates(_seq<ET> S, HASH hashF) {\n//   return pbbs::remove_duplicates(pbbs::range<ET*>(S.A, S.A+S.n), hashF);\n// }\n\n"
  },
  {
    "path": "algorithms/bench/common/graph.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <algorithm>\n#include \"../parlay/parallel.h\"\n#include \"../parlay/primitives.h\"\n\n// IntV and IntE should be set depending on the size of the graphs\n//  intV should have enough range to represent |V|\n//  intE should have enough range to represent |E|\n//  intE defaults to intV if not specified\nusing DefaultIntV = int;\nusing DefaultWeight = float;\n\n// **************************************************************\n//    EDGE ARRAY REPRESENTATION\n// **************************************************************\n\ntemplate <class intV = DefaultIntV>\nstruct edge {\n  intV u;\n  intV v;\n  edge() {}\n  edge(intV f, intV s) : u(f), v(s) {}\n};\n\ntemplate <class intV = DefaultIntV>\nstruct edgeArray {\n  parlay::sequence<edge<intV>> E;\n  size_t numRows;\n  size_t numCols;\n  size_t nonZeros;\n  edgeArray(parlay::sequence<edge<intV>> EE, size_t r, size_t c) :\n    E(std::move(EE)), numRows(r), numCols(c), nonZeros(E.size()) {}\n  edgeArray() {}\n  edge<intV> operator[] (const size_t i) const {return E[i];}\n};\n\n// **************************************************************\n//    WEIGHED EDGE ARRAY\n// **************************************************************\n\ntemplate <class intV = DefaultIntV, class Weight=DefaultWeight>\nstruct wghEdge {\n  intV u, v;\n  Weight weight;\n  wghEdge() {}\n  wghEdge(intV _u, intV _v, Weight w) : u(_u), v(_v), weight(w) {}\n};\n\ntemplate <class intV = DefaultIntV, class Weight=DefaultWeight>\nstruct wghEdgeArray {\n  using W = Weight;\n  parlay::sequence<wghEdge<intV,W>> E;\n  size_t n; size_t m;\n  wghEdgeArray(parlay::sequence<wghEdge<intV,W>> E_, intV n) \n    : E(std::move(E_)), n(n), m(E.size()) {}\n  wghEdgeArray() {}\n  wghEdge<intV> operator[] (const size_t i) const {return E[i];}\n};\n\n// **************************************************************\n//    ADJACENCY ARRAY REPRESENTATION\n// **************************************************************\n\ntemplate <class intV = DefaultIntV>\nstruct vertex {\n  const intV* Neighbors;\n  intV degree;\n  vertex(const intV* N, const intV d) : Neighbors(N), degree(d) {}\n  vertex() : Neighbors(NULL), degree(0) {}\n};\n\ntemplate <class intV = DefaultIntV>\nstruct mod_vertex {\n  intV* Neighbors;\n  intV degree;\n  mod_vertex(intV* N, intV d) : Neighbors(N), degree(d) {}\n  mod_vertex() : Neighbors(NULL), degree(0) {}\n};\n\ntemplate <class intV = DefaultIntV, class intE = intV>\nstruct graph {\n  using vertexId = intV;\n  using edgeId = intE;\n  using MVT = mod_vertex<intV>;\n  using VT = vertex<intV>;\n  parlay::sequence<intE> offsets;\n  parlay::sequence<intV> edges;\n  parlay::sequence<intV> degrees; // not always used\n  size_t n;\n  size_t m;\n  size_t numVertices() const {return n;}\n  size_t numEdges() const {\n    if (degrees.size() == 0) return m;\n    else {\n      std::cout << \"hello numEdges\" << std::endl;\n      auto dgs = parlay::delayed_seq<intE>(n, [&] (size_t i) {\n\t  return degrees[i];});\n      return parlay::reduce(dgs, parlay::addm<intE>());\n    }\n  }\n\n  const parlay::sequence<intE>& get_offsets() const {\n    return offsets;\n  }\n\n  void addDegrees() {\n    degrees = parlay::tabulate(n, [&] (size_t i) -> intV {\n\treturn offsets[i+1] - offsets[i];});\n  }\n\n  MVT operator[] (const size_t i) {\n    return MVT(edges.data() + offsets[i],\n\t       (degrees.size() == 0)\n\t       ? offsets[i+1] - offsets[i] : degrees[i]);}\n\n  const VT operator[] (const size_t i) const {\n    return VT(edges.data() + offsets[i],\n\t      (degrees.size() == 0)\n\t      ? offsets[i+1] - offsets[i] : degrees[i]);\n  }\n  \n  graph(parlay::sequence<intE> offsets_,\n\tparlay::sequence<intV> edges_,\n\tsize_t n) \n    : offsets(std::move(offsets_)), edges(std::move(edges_)), n(n), m(edges.size()) {\n    if (offsets.size() != n + 1) { std::cout << \"error in graph constructor\" << std::endl;}\n  }\n};\n\n// **************************************************************\n//    WEIGHTED ADJACENCY ARRAY REPRESENTATION\n// **************************************************************\n\ntemplate <class intV = DefaultIntV, class Weight = DefaultWeight>\nstruct wghVertex {\n  intV* Neighbors;\n  intV degree;\n  Weight* nghWeights;\n  wghVertex(intV* N, Weight* W, intV d) : Neighbors(N), nghWeights(W), degree(d) {}\n};\n\ntemplate <class intV = DefaultIntV, class Weight=DefaultWeight,\n          class intE = intV>\nstruct wghGraph {\n  using VT = wghVertex<intV,Weight>;\n  using W = Weight;\n  parlay::sequence<intE> offsets;\n  parlay::sequence<intV> edges;\n  parlay::sequence<Weight> weights;\n  size_t n;\n  size_t m;\n  size_t numVertices() const {return n;}\n  size_t numEdges() const {return m;}\n  //const parlay::sequence<intV>& get_offsets() const {\n  //  return offsets;\n  //}\n  parlay::sequence<intV> get_offsets() {\n    return offsets;\n  }\n  VT operator[] (const size_t i) {\n    return VT(edges.begin() + offsets[i],\n\t      weights.begin() + offsets[i],\n\t      offsets[i+1] - offsets[i]);}\n\nwghGraph(parlay::sequence<intE> offsets_,\n\t parlay::sequence<intV> edges_,\n\t parlay::sequence<Weight> weights_,\n\t   size_t n) \n    : offsets(std::move(offsets_)), edges(std::move(edges_)),\n      weights(std::move(weights_)), n(n), m(edges.size()) {\n    if (offsets.size() != n + 1 || weights.size() != edges.size()) {\n      std::cout << \"error in weighted graph constructor\" << std::endl;}\n  }\n};\n\ntemplate <typename intV>\nstruct FlowGraph {\n  wghGraph<intV> g;\n  intV source, sink;\n  FlowGraph(wghGraph<intV> g, intV source, intV sink)\n    : g(g), source(source), sink(sink) {}\n  FlowGraph copy() {\n    return FlowGraph(g.copy(), source, sink);\n  }\n  void del() { g.del(); }\n};\n\n"
  },
  {
    "path": "algorithms/bench/common/graphIO.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2010 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n#include <iostream>\n#include <stdint.h>\n#include <cstring>\n#include \"../parlay/parallel.h\"\n#include \"IO.h\"\n#include \"graphUtils.h\"\n\n#include <sys/mman.h>\n#include <stdio.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n\nusing namespace benchIO;\n\ntemplate <class intV>\nint xToStringLen(edge<intV> a) {\n  return xToStringLen(a.u) + xToStringLen(a.v) + 1;\n}\n\ntemplate <class intV>\nvoid xToString(char* s, edge<intV> a) {\n  int l = xToStringLen(a.u);\n  xToString(s, a.u);\n  s[l] = ' ';\n  xToString(s+l+1, a.v);\n}\n\ntemplate <class intV, class Weight>\nint xToStringLen(wghEdge<intV,Weight> a) {\n  return xToStringLen(a.u) + xToStringLen(a.v) + xToStringLen(a.weight) + 2;\n}\n\ntemplate <class intV, class Weight>\nvoid xToString(char* s, wghEdge<intV, Weight> a) {\n  int lu = xToStringLen(a.u);\n  int lv = xToStringLen(a.v);\n  xToString(s, a.u);\n  s[lu] = ' ';\n  xToString(s+lu+1, a.v);\n  s[lu+lv+1] = ' ';\n  xToString(s+lu+lv+2, a.weight);\n}\n\nnamespace benchIO {\n  using namespace std;\n\n  string AdjGraphHeader = \"AdjacencyGraph\";\n  string EdgeArrayHeader = \"EdgeArray\";\n  string WghEdgeArrayHeader = \"WeightedEdgeArray\";\n  string WghAdjGraphHeader = \"WeightedAdjacencyGraph\";\n\n  template <class intV, class intE>\n  int writeGraphToFile(graph<intV, intE> const &G, char* fname) {\n    if (G.degrees.size() > 0) {\n      graph<intV, intE> GP = packGraph(G);\n      return writeGraphToFile(GP, fname);\n    }\n    size_t m = G.numEdges();\n    size_t n = G.numVertices();\n    size_t totalLen = 2 + n + m;\n    parlay::sequence<size_t> Out(totalLen);\n    Out[0] = n;\n    Out[1] = m;\n\n    // write offsets to Out[2,..,2+n)\n    parlay::sequence<intE> const &offsets = G.get_offsets();\n    parlay::parallel_for (0, n, [&] (size_t i) {\n    \tOut[i+2] = offsets[i];});\n\n    // write out edges to Out[2+n,..,2+n+m)\n    parlay::parallel_for(0, n, [&] (size_t i) {\n    \tsize_t o = offsets[i] + 2 + n;\n    \tfor (intV j = 0; j < G[i].degree; j++) \n    \t  Out[o + j] = G[i].Neighbors[j];\n      });\n\n    int r = writeSeqToFile(AdjGraphHeader, Out, fname);\n    return r;\n  }\n\n  template <class intV, class Weight, class intE>\n  int writeWghGraphToFile(wghGraph<intV,Weight,intE> G, char* fname) {\n    size_t m = G.m;\n    size_t n = G.n;\n    // weights have to separate since they could be floats\n    parlay::sequence<size_t> Out1(2 + n + m);\n    parlay::sequence<Weight> Out2(m);\n    Out1[0] = n;\n    Out2[1] = m;\n\n    // write offsets to Out[2,..,2+n)\n    auto offsets = G.get_offsets();\n    parlay::parallel_for (0, n, [&] (size_t i) {\n\tOut1[i+2] = offsets[i];});\n\n    // write out edges to Out1[2+n,..,2+n+m)\n    // and weights to Out2[0,..,m)\n    parlay::parallel_for(0, n, [&] (size_t i) {\n\tsize_t o = offsets[i];\n\twghVertex<intV,Weight> v = G[i];\n\tfor (intV j = 0; j < v.degree; j++) {\n\t  Out1[2 + n + o + j] = v.Neighbors[j];\n\t  Out2[o + j] = v.nghWeights[j]; }\n      });\n    int r = write2SeqToFile(WghAdjGraphHeader, Out1, Out2, fname);\n    return r;\n  }\n\n  template <class intV>\n  int writeEdgeArrayToFile(edgeArray<intV> const &EA, char* fname) {\n    return writeSeqToFile(EdgeArrayHeader, EA.E, fname);\n  }\n\n  template <class intV, class intE>\n  int writeWghEdgeArrayToFile(wghEdgeArray<intV,intE>\n\t\t\t      const &EA, char* fname) {\n    return writeSeqToFile(WghEdgeArrayHeader, EA.E, fname);\n  }\n\n  template <class intV>\n  edgeArray<intV> readEdgeArrayFromFile(char* fname) {\n    parlay::sequence<char> S = readStringFromFile(fname);\n    parlay::sequence<char*> W = stringToWords(S);\n    if (W[0] != EdgeArrayHeader) {\n      cout << \"Bad input file\" << endl;\n      abort();\n    }\n    long n = (W.size()-1)/2;\n    auto E = parlay::tabulate(n, [&] (long i) -> edge<intV> {\n\treturn edge<intV>(atol(W[2*i + 1]),\n\t\t\t  atol(W[2*i + 2]));});\n\n    auto mon = parlay::make_monoid([&] (edge<intV> a, edge<intV> b) {\n\treturn edge<intV>(std::max(a.u, b.u), std::max(a.v, b.v));},\n      edge<intV>(0,0));\n    auto r = parlay::reduce(E, mon);\n\n    intV maxrc = std::max(r.u, r.v) + 1;\n    return edgeArray<intV>(std::move(E), maxrc, maxrc);\n  }\n\n  template <class intV, class Weight>\n  wghEdgeArray<intV,Weight> readWghEdgeArrayFromFile(char* fname) {\n    using WE = wghEdge<intV,Weight>;\n    parlay::sequence<char> S = readStringFromFile(fname);\n    parlay::sequence<char*> W = stringToWords(S);\n    if (W[0] != WghEdgeArrayHeader) {\n      cout << \"Bad input file\" << endl;\n      abort();\n    }\n    long n = (W.size()-1)/3;\n    auto E = parlay::tabulate(n, [&] (size_t i) -> WE {\n\treturn WE(atol(W[3*i + 1]),\n\t\t  atol(W[3*i + 2]),\n\t\t  (Weight) atof(W[3*i + 3]));});\n\n    auto mon = parlay::make_monoid([&] (WE a, WE b) {\n\treturn WE(std::max(a.u, b.u), std::max(a.v, b.v), 0);},\n      WE(0,0,0));\n    auto r = parlay::reduce(E, mon);\n\n    return wghEdgeArray<intV,Weight>(std::move(E), max<intV>(r.u, r.v) + 1);\n  }\n\n  template <class intV, class intE=intV>\n  graph<intV, intE> readGraphFromFile(char* fname) {\n    auto W = get_tokens(fname);\n    string header(W[0].begin(), W[0].end());\n    if (header != AdjGraphHeader) {\n      cout << \"Bad input file: missing header: \" << AdjGraphHeader << endl;\n      abort();\n    }\n\n    // file consists of [type, num_vertices, num_edges, <vertex offsets>, <edges>]\n    // in compressed sparse row format\n    long n = parlay::chars_to_long(W[1]);\n    long m = parlay::chars_to_long(W[2]);\n    if (W.size() != n + m + 3) {\n      cout << \"Bad input file: length = \"<< W.size() << \" n+m+3 = \" << n+m+3 << endl;\n      abort(); }\n    \n    // tags on m at the end (so n+1 total offsets)\n    auto offsets = parlay::tabulate(n+1, [&] (size_t i) -> intE {\n\treturn (i == n) ? m : parlay::chars_to_long(W[i+3]);});\n    auto edges = parlay::tabulate(m, [&] (size_t i) -> intV {\n\treturn parlay::chars_to_long(W[n+i+3]);});\n\n    return graph<intV, intE>(std::move(offsets), std::move(edges), n);\n  }\n\n  // parlay::sequence<char> mmapStringFromFile(const char *filename) {\n  //   struct stat sb;\n  //   int fd = open(filename, O_RDONLY);\n  //   if (fd == -1) {\n  //     perror(\"open\");\n  //     exit(-1);\n  //   }\n  //   if (fstat(fd, &sb) == -1) {\n  //     perror(\"fstat\");\n  //     exit(-1);\n  //   }\n  //   if (!S_ISREG (sb.st_mode)) {\n  //     perror(\"not a file\\n\");\n  //     exit(-1);\n  //   }\n  //   char *p = static_cast<char*>(mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));\n  //   if (p == MAP_FAILED) {\n  //     perror(\"mmap\");\n  //     exit(-1);\n  //   }\n  //   if (close(fd) == -1) {\n  //     perror(\"close\");\n  //     exit(-1);\n  //   }\n  //   size_t n = sb.st_size;\n  //   return parlay::sequence<char>(p, n); // Yikes!\n  // }\n\n  // template <class intV, class intV>\n  // graphC<intV, intV> readGraphCFromFile(char* fname, bool mmap=false) {\n\n  //   parlay::sequence<char*> W;\n  //   if (mmap) {\n  //     cout << \"mmapping file\" << endl;\n  //     parlay::sequence<char> S = mmapStringFromFile(fname);\n  //     // copy to new sequence\n  //     parlay::sequence<char> bytes = S;\n  //     // and unmap\n  //     if (munmap(S.begin(), S.size()) == -1) {\n  //       perror(\"munmap\");\n  //       exit(-1);\n  //     }\n  //     W = stringToWords(S);\n  //     cout << \"mmap'd\" << endl;\n  //   } else {\n  //     auto S = readStringFromFile(fname);\n  //     W = stringToWords(S);\n  //   }\n\n  //   if (W[0] != AdjGraphHeader) {\n  //     cout << \"Bad input file: missing header: \" << AdjGraphHeader << endl;\n  //     abort();\n  //   }\n\n  //   // num vertices, num edges, edge offsets, edge pointers\n  //   long len = W.size() -1;\n  //   long n = atol(W[1]);\n  //   long m = atol(W[2]);\n  //   if (len != n + m + 2) {\n  //     cout << \"Bad input file: length = \"<<len<< \" n+m+2 = \" << n+m+2 << endl;\n  //     abort();\n  //   }\n  //   sequence<intV> offsets(n+1, [&] (size_t i) {\n  // \treturn (i == n) ? m : atol(W[i+3]);});\n  //   sequence<intV> edges(m, [&] (size_t i) {\n  // \treturn atol(W[n+i+3]);});\n\n  //   return graphC<intV,intV>(offsets,edges,n,m);\n  // }\n\n  template <class intV, class Weight, class intE>\n  wghGraph<intV, Weight, intE> readWghGraphFromFile(char* fname) {\n    parlay::sequence<char> S = readStringFromFile(fname);\n    parlay::sequence<char*> W = stringToWords(S);\n    if (W[0] != WghAdjGraphHeader) {\n      cout << \"Bad input file\" << endl;\n      abort();\n    }\n\n    long n = atol(W[1]);\n    long m = atol(W[2]);\n    if (W.size() != n + 2*m + 3) {\n      cout << \"Bad input file: length = \"<< W.size()\n\t   << \" n + 2*m + 3 = \" << n+2*m+3 << endl;\n      abort(); }\n    \n    // tags on m at the end (so n+1 total offsets)\n    auto offsets = parlay::tabulate(n+1, [&] (size_t i) -> intE {\n\treturn (i == n) ? m : atol(W[i+3]);});\n    auto edges = parlay::tabulate(m, [&] (size_t i) -> intV {\n\treturn atol(W[n+i+3]);});\n    auto weights = parlay::tabulate(m, [&] (size_t i) -> Weight {\n\treturn (Weight) atof(W[n+i+3+m]);});\n\n    return wghGraph<intV,Weight,intE>(std::move(offsets),\n\t\t\t\t      std::move(edges),\n\t\t\t\t      std::move(weights), n);\n  }\n\n  // The following two are used by the graph generators to write out in either format\n  // and either with reordering or not\n  template <class intV, class intE>\n  void writeGraphFromAdj(graph<intV,intE> const &G,\n\t\t\t char* fname, bool adjArray, bool ordered) {\n    if (adjArray)\n      if (ordered) writeGraphToFile(G, fname);\n      else writeGraphToFile(graphReorder(G), fname);\n    else {\n      if (ordered)\n\twriteEdgeArrayToFile(edgesFromGraph(G), fname);\n      else {\n\tauto B = edgesFromGraph(graphReorder(G));\n\tB = randomShuffle(B);\n\twriteEdgeArrayToFile(B, fname);\n      }\n    }\n  }\n\n  template <class intV, class intE=intV>\n  void writeGraphFromEdges(edgeArray<intV> &EA, char* fname, bool adjArray, bool ordered) {\n    writeGraphFromAdj(graphFromEdges<intV,intE>(EA, adjArray),\n\t\t      fname, adjArray, ordered);\n  }\n\n  // void errorOut(const char* s) {\n  //   cerr << s << endl;\n  //   throw s;\n  // }\n\n  // void packInt64(int64_t x, uint8_t buf[8]) {\n  //   uint64_t xu = x;\n  //   for (int i = 0; i < 8; ++i)\n  //     buf[i] = (xu >> (8 * i)) & 0xff;\n  // }\n  // int64_t unpackInt64(const uint8_t buf[8]) {\n  //   uint64_t xu = 0;\n  //   for (int i = 0; i < 8; ++i)\n  //     xu |= ((uint64_t)buf[i]) << (i * 8);\n  //   return (int64_t)xu;\n  // }\n\n  // void writeInt(ostream& out, char buf[8], int64_t x) {\n  //   packInt64(x, (uint8_t*)buf);\n  //   out.write(buf, 8);\n  // }\n  // int64_t readInt(istream& in, char buf[8]) {\n  //   in.read(buf, 8);\n  //   return unpackInt64((uint8_t*)buf);\n  // }\n\n  // template<typename intV>\n  // void writeFlowGraph(ostream& out, FlowGraph<intV> g) {\n  //   char buf[8];\n  //   out.write(\"FLOWFLOW\", 8);\n  //   writeInt(out, buf, g.g.n);\n  //   writeInt(out, buf, g.g.m);\n  //   writeInt(out, buf, g.source);\n  //   writeInt(out, buf, g.sink);\n  //   intV o = 0;\n  //   for (intV i = 0; i < g.g.n; ++i) {\n  //     writeInt(out, buf, o);\n  //     o += g.g.V[i].degree;\n  //   }\n  //   for (intV i = 0; i < g.g.n; ++i) {\n  //     wghVertex<intV>& v = g.g.V[i];\n  //     for (intV j = 0; j < v.degree; ++j) {\n  //       writeInt(out, buf, v.Neighbors[j]);\n  //       writeInt(out, buf, v.nghWeights[j]);\n  //     }\n  //   }\n  // }\n  // template<typename intV>\n  // FlowGraph<intV> readFlowGraph(istream& in) {\n  //   char buf[10];\n  //   in.read(buf, 8);\n  //   buf[8] = 0;\n  //   if (strcmp(buf, \"FLOWFLOW\"))\n  //     errorOut(\"Invalid flow graph input file\");\n  //   intV n = readInt(in, buf);\n  //   intV m = readInt(in, buf);\n  //   intV S = readInt(in, buf);\n  //   intV T = readInt(in, buf);\n  //   intV *offset = newA(intV, n);\n  //   intV* adj = newA(intV, m);\n  //   intV* weights = newA(intV, m);\n  //   wghVertex<intV>* v = newA(wghVertex<intV>, n);\n  //   for (intV i = 0; i < n; ++i) {\n  //     offset[i] = readInt(in, buf);\n  //     v[i].Neighbors = adj + offset[i];\n  //     v[i].nghWeights = weights + offset[i];\n  //     if (i > 0)\n  //       v[i - 1].degree = offset[i] - offset[i - 1];\n  //   }\n  //   v[n - 1].degree = m - offset[n - 1];\n  //   free(offset);\n  //   for (intV i = 0; i < m; ++i) {\n  //     adj[i] = readInt(in, buf);\n  //     weights[i] = readInt(in, buf);\n  //   }\n  //   return FlowGraph<intV>(wghGraph<intV>(v, n, m, adj, weights), S, T);\n  // }\n\n  // const char nl = '\\n';\n  // template <typename intV>\n  // FlowGraph<intV> writeFlowGraphDimacs(ostream& out, FlowGraph<intV> g) {\n  //   out << \"c DIMACS flow network description\" << nl;\n  //   out << \"c (problem-id, nodes, arcs)\" << nl;\n  //   out << \"p max \" << g.g.n << \" \" << g.g.m << nl;\n\n  //   out << \"c source\" << nl;\n  //   out << \"n \" << g.source + 1 << \" s\" << nl;\n  //   out << \"c sink\" << nl;\n  //   out << \"n \" << g.sink + 1 << \" t\" << nl;\n\n  //   out << \"c arc description (from, to, capacity)\" << nl;\n\n  //   for (intV i = 0; i < g.g.n; ++i) {\n  //     wghVertex<intV>& v = g.g.V[i];\n  //     for (intV j = 0; j < v.degree; ++j) {\n  //       out << \"a \" << i + 1 << \" \" << v.Neighbors[j] + 1 << \" \"\n  //           << v.nghWeights[j] << nl;\n  //     }\n  //   }\n  // }\n\n  // template<typename intV>\n  // struct intWghEdge {\n  //   intV from, to, w;\n  // };\n  // int readDimacsLinePref(istream& in, const char* expected) {\n  //   char type;\n  //   while (in >> type) {\n  //     if (type == 'c') {\n  //       while (in.peek() != EOF && in.peek() != '\\n')\n  //         in.ignore();\n  //       in >> ws;\n  //       continue;\n  //     } else if (!strchr(expected, type)) {\n  //       errorOut((string(\"Unexpected DIMACS line (expected 'c' or one of '\")\n  // \t\t  + expected + \"')\").c_str());\n  //     }\n  //     return type;\n  //   }\n  //   return EOF;\n  // }\n\n  // template <typename intV>\n  // FlowGraph<intV> readFlowGraphDimacs(istream& in) {\n  //   string tmp;\n  //   intV n, m;\n  //   int type = readDimacsLinePref(in, \"p\");\n  //   if (type == EOF)\n  //     errorOut(\"Unexpected EOF while reading DIMACS file\");\n  //   in >> tmp >> n >> m;\n  //   intWghEdge<intV>* edges = newA(intWghEdge<intV>, m);\n  //   intV edgei = 0;\n  //   intV* pos = newA(intV, n + 1);\n  //   intV S = -1, T = -1;\n  //   while (EOF != (type = readDimacsLinePref(in, \"an\"))) {\n  //     if (type == 'n') {\n  //       intV x;\n  //       char st;\n  //       in >> x >> st;\n  //       x--;\n  //       if (st == 's') S = x;\n  //       else T = x;\n  //     } else { // type == 'a'\n  //       intV from, to, cap;\n  //       in >> from >> to >> cap;\n  //       from--; to--;\n  //       edges[edgei] = (intWghEdge<intV>) { from, to, cap };\n  //       edgei++;\n  //       pos[from + 1]++;\n  //     }\n  //   }\n  //   if (S < 0)\n  //     errorOut(\"No source was specified in DIMACS input file\");\n  //   if (T < 0)\n  //     errorOut(\"No sink was specified in DIMACS input file\");\n  //   if (m != edgei)\n  //     errorOut(\"Inconsistent edge count in DIMACS input file\");\n  //   intV* adj = newA(intV, m);\n  //   intV* weights = newA(intV, m);\n  //   wghVertex<intV>* v = newA(wghVertex<intV>, n);\n  //   for (intV i = 0; i < n; ++i) {\n  //     pos[i + 1] += pos[i];\n  //     v[i].Neighbors = adj + pos[i];\n  //     v[i].nghWeights = weights + pos[i];\n  //     v[i].degree = pos[i + 1] - pos[i];\n  //   }\n  //   for (intV i = 0; i < m; ++i) {\n  //     intV& p = pos[edges[i].from];\n  //     adj[p] = edges[i].to;\n  //     weights[p] = edges[i].w;\n  //     p++;\n  //   }\n  //   free(edges);\n  //   free(pos);\n  //   return FlowGraph<intV>(wghGraph<intV>(v, n, m, adj, weights), S, T);\n  // }\n};\n"
  },
  {
    "path": "algorithms/bench/common/graphUtils.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <fstream>\n#include <cstdlib>\n#include <math.h>\n#include \"graph.h\"\n#include \"../parlay/parallel.h\"\n#include \"../parlay/primitives.h\"\n#include \"../parlay/random.h\"\n#include \"dataGen.h\"\n\nusing namespace std;\n\ntemplate <class intV, class Weight = DefaultWeight>\nwghEdgeArray<intV,Weight> addRandWeights(edgeArray<intV> const &G) {\n  using WE = wghEdge<intV,Weight>;\n  parlay::random r(257621);\n  intV m = G.nonZeros;\n  intV n = G.numRows;\n  auto E = parlay::tabulate(m, [&] (size_t i) -> WE {\n      return WE(G.E[i].u, G.E[i].v, (Weight) dataGen::hash<Weight>(i));});\n  return wghEdgeArray<intV,Weight>(std::move(E), n);\n}\n\ntemplate <class intV>\nedgeArray<intV> randomShuffle(edgeArray<intV> const &A) {\n  auto E =  parlay::random_shuffle(A.E);\n  return edgeArray<intV>(std::move(E), A.numRows, A.numCols);\n}\n\ntemplate <class intV>\nedgeArray<intV> remDuplicates(edgeArray<intV> const &A) {\n  auto lessE = [&] (edge<intV> a, edge<intV> b) {\n    return (a.u < b.u) || ((a.u == b.u) && (a.v < b.v));};\n  parlay::sequence<edge<intV>> E =\n    parlay::remove_duplicates_ordered(A.E, lessE);\n  return edgeArray<intV>(std::move(E), A.numRows, A.numCols);\n}\n\ntemplate <class intV>\nedgeArray<intV> makeSymmetric(edgeArray<intV> const &A) {\n  parlay::sequence<edge<intV>> EF = parlay::filter(A.E, [&] (edge<intV> e) {\n      return e.u != e.v;});\n  auto FE = parlay::delayed_seq<edge<intV>>(EF.size(), [&] (size_t i) {\n      return edge<intV>(EF[i].v, EF[i].u);});\n  return remDuplicates(edgeArray<intV>(parlay::append(EF, FE),\n\t\t\t\t       A.numRows, A.numCols));\n}\n\ntemplate <class intV, class intE = intV>\ngraph<intV,intE> graphFromEdges(edgeArray<intV> const &EA, bool makeSym) {\n  edgeArray<intV> SA;\n  if (makeSym) SA = makeSymmetric<intV>(EA);\n  edgeArray<intV> const &A = (makeSym) ? SA : EA;\n\n  size_t m = A.nonZeros;\n  size_t n = std::max(A.numCols, A.numRows);\n\n  parlay::sequence<size_t> counts;\n  parlay::sequence<intE> offsets;\n  parlay::sequence<edge<intV>> E;\n  size_t nn;\n  auto getu = [&] (edge<intV> e) {return e.u;};\n  std::tie(E, counts) = parlay::internal::integer_sort_with_counts(parlay::make_slice(A.E), getu, n);\n  std::tie(offsets,nn) = parlay::scan(parlay::delayed_seq<intE>(n+1, [&] (size_t i) {\n\treturn (i == n) ? 0 : counts[i];}), parlay::addm<intE>());\n\n  return graph<intV,intE>(std::move(offsets),\n\t\t\t  parlay::tabulate(m, [&] (size_t i) -> intV {return E[i].v;}),\n\t\t\t  n);\n}\n\ntemplate <class intV, class Weight, class intE=intV>\nwghGraph<intV,Weight,intE>\nwghGraphFromEdges(wghEdgeArray<intV,Weight> const &A) {\n  using WE = wghEdge<intV,Weight>;\n  size_t n = A.n;\n  size_t m = A.m;\n\n  parlay::sequence<size_t> counts;\n  parlay::sequence<intE> offsets;\n  parlay::sequence<WE> E;\n  size_t nn;\n  auto getu = [&] (WE e) {return e.u;};\n  std::tie(E, counts) = parlay::internal::integer_sort_with_counts(parlay::make_slice(A.E), getu, n);\n  std::tie(offsets,nn) = parlay::scan(parlay::delayed_seq<intE>(n+1, [&] (size_t i) {\n\treturn (i == n) ? 0 : counts[i];}), parlay::addm<intE>());\n\n  return wghGraph<intV,Weight,intE>(std::move(offsets),\n\t\t\t\t    parlay::tabulate(m, [&] (size_t i)->intV {return E[i].v;}),\n\t\t\t\t    parlay::tabulate(m, [&] (size_t i) -> Weight {\n\t\t\t\t\treturn E[i].weight;}),\n\t\t\t\t    n);\n}\n\ntemplate <class intV, class intE>\nedgeArray<intV> edgesFromGraph(graph<intV,intE> const &G) {\n  size_t numRows = G.numVertices();\n  size_t nonZeros = G.numEdges();\n\n  // flatten\n  parlay::sequence<edge<intV>> E(nonZeros);\n  parlay::parallel_for(0, numRows, [&] (size_t j) {\n      size_t off = G.get_offsets()[j];\n      vertex<intV> v = G[j];\n      for (size_t i = 0; i < v.degree; i++)\n\tE[off+i] = edge<intV>(j, v.Neighbors[i]);\n    });\n  return edgeArray<intV>(std::move(E), numRows, numRows);\n}\n\n// offset for start of each vertex if flattening the edge listd\ntemplate <class intV, class intE, class Vtx>\nparlay::sequence<intE> getOffsets(parlay::sequence<Vtx> const &V) {\n  size_t n = V.size();\n  auto degrees = parlay::delayed_seq<intE>(n+1, [&] (size_t i) -> intE {\n      return (i == n) ? 0 : V[i].degree;});\n  auto x = parlay::scan(degrees, parlay::addm<intE>());\n  return x.first;\n}\n\n// packs a graph so that there are no gaps in the edge array (i.e. into CSR)\ntemplate <class intV, class intE>\ngraph<intV,intE> packGraph(graph<intV,intE> const &G) {\n  size_t n = G.numVertices();\n  auto degrees = parlay::delayed_seq<intE>(n+1, [&] (size_t i) -> intE {\n\t\t\t\t\t\t  return (i == n) ? 0 : G[i].degree;});\n  // calculate new offsets\n  auto sr = parlay::scan(degrees, parlay::addm<intE>());\n  // allocate new edge array\n  parlay::sequence<intV> outEdges(sr.second);\n  // copy edges so they are contiguous\n  parlay::parallel_for (0, G.n, [&] (size_t i) {\n      vertex<intV> v = G[i];\n      size_t offset = sr.first[i];\n      for (size_t j=0; j < v.degree; j++)\n\toutEdges[offset + j] = v.Neighbors[j];\n    });\n  return graph<intV,intE>(std::move(sr.first), std::move(outEdges), n);\n}\n\n// if I is NULL then it randomly reorders\ntemplate <class intV, class intE>\ngraph<intV,intE> graphReorder(graph<intV,intE> const &Gr,\n\t\t\t      parlay::sequence<intV> const &I = parlay::sequence<intV>(0)) {\n  intV n = Gr.numVertices();\n  intV m = Gr.numEdges();\n\n  bool noI = (I.size()==0);\n  parlay::sequence<intV> const &II = noI ? parlay::random_permutation<intV>(n) : I;\n\n  // now write vertices to new locations\n  // inverse permutation\n  parlay::sequence<vertex<intV>> V(n);\n  parlay::parallel_for (0, n, [&] (size_t i) {\n      V[II[i]] = Gr[i];});\n  parlay::sequence<intE> offsets = getOffsets<intV,intE>(V);\n  parlay::sequence<intV> E(m);\n  parlay::parallel_for (0, n, [&] (size_t i) {\n      size_t o = offsets[i];\n      for (size_t j=0; j < V[i].degree; j++) \n\tE[o + j] = II[V[i].Neighbors[j]];\n      std::sort(E.begin() + o, E.begin() + o + V[i].degree);\n    }, 1000);\n  return graph<intV>(std::move(offsets), std::move(E), n);\n}\n\ntemplate <class intV, class intE>\nint graphCheckConsistency(graph<intV,intE> const &Gr) {\n  size_t n = Gr.numVertices();\n  size_t m = Gr.numEdges();\n  size_t edgecount = parlay::reduce(parlay::delayed_seq<size_t>(n, [&] (size_t i) {\n\treturn Gr[i].degree;}), parlay::addm<size_t>());\n  if (m != edgecount) {\n    cout << \"bad edge count in graphCheckConsistency: m = \" \n\t << m << \" sum of degrees = \" << edgecount << endl;\n    return 1;\n  }\n  size_t error_loc = parlay::reduce(parlay::delayed_seq<size_t>(n, [&] (size_t i) {\n\tfor (size_t j=0; j < Gr[i].degree; j++) \n\t  if (Gr[i].Neighbors[j] >= n) return i;\n\treturn n;\n      }), parlay::minm<size_t>());\n  if (error_loc < n) {\n    cout << \"edge out of range in graphCheckConsistency: at i = \" \n\t << error_loc << endl;\n    return 1;\n  }\n}\n\n// template <class intV>\n// sparseRowMajor<double,intV> sparseFromCsrFile(const char* fname) {\n//   FILE *f = fopen(fname,\"r\");\n//   if (f == NULL) {\n//     cout << \"Trying to open nonexistant file: \" << fname << endl;\n//     abort();\n//   }\n\n//   intV numRows;  intV numCols;  intV nonZeros;\n//   intV nc = fread(&numRows, sizeof(intV), 1, f);\n//   nc = fread(&numCols, sizeof(intV), 1, f);\n//   nc = fread(&nonZeros, sizeof(intV), 1, f); \n\n//   double *Values = (double *) malloc(sizeof(double)*nonZeros);\n//   intV *ColIds = (intV *) malloc(sizeof(intV)*nonZeros);\n//   intV *Starts = (intV *) malloc(sizeof(intV)*(1 + numRows));\n//   Starts[numRows] = nonZeros;\n\n//   size_t r;\n//   r = fread(Values, sizeof(double), nonZeros, f);\n//   r = fread(ColIds, sizeof(intV), nonZeros, f);\n//   r = fread(Starts, sizeof(intV), numRows, f); \n//   fclose(f);\n//   return sparseRowMajor<double,intV>(numRows,numCols,nonZeros,Starts,ColIds,Values);\n// }\n\n// template <class intV>\n// edgeArray<intV> edgesFromMtxFile(const char* fname) {\n//   ifstream file (fname, ios::in);\n//   char* line = newA(char,1000);\n//   intV i,j = 0;\n//   while (file.peek() == '%') {\n//     j++;\n//     file.getline(line,1000);\n//   }\n//   intV numRows, numCols, nonZeros;\n//   file >> numRows >> numCols >> nonZeros;\n//   //cout << j << \",\" << numRows << \",\" << numCols << \",\" << nonZeros << endl;\n//   edge<intV> *E = newA(edge<intV>,nonZeros);\n//   double toss;\n//   for (i=0, j=0; i < nonZeros; i++) {\n//     file >> E[j].u >> E[j].v >> toss;\n//     E[j].u--;\n//     E[j].v--;\n//     if (toss != 0.0) j++;\n//   }\n//   nonZeros = j;\n//   //cout << \"nonzeros = \" << nonZeros << endl;\n//   file.close();  \n//   return edgeArray<intV>(E,numRows,numCols,nonZeros);\n// }\n\n"
  },
  {
    "path": "algorithms/bench/common/ligraLight.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <limits>\n#include \"parlay/primitives.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/internal/get_time.h\"\n#include \"parlay/internal/block_delayed.h\"\n#include \"common/graph.h\"\n\nnamespace delayed = parlay::block_delayed;\n\nnamespace ligra {\n  \ntemplate<typename vertexId>\nstruct vertex_subset {\n  using sparse_t = parlay::sequence<vertexId>;\n  using dense_t = parlay::sequence<bool>;\n  bool is_sparse;\n  size_t n;\n  size_t size() const {return n;}\n  sparse_t sparse;\n  dense_t dense;\n  vertex_subset(sparse_t x) :\n    sparse(std::move(x)), is_sparse(true), n(x.size()) {}\n  vertex_subset(vertexId v) :\n    sparse(sparse_t(1,v)), is_sparse(true), n(1) {}\n  vertex_subset(dense_t x) :\n    dense(std::move(x)), is_sparse(false),\n    n(parlay::count(x,true)) {}\n};\n\ntemplate<typename Graph, typename Fa, typename Cond> \nstruct edge_map {\n  using vertexId = typename Graph::vertexId;\n  using vertex_subset_ = vertex_subset<vertexId>;\n  using vertex_subset_sparse = parlay::sequence<vertexId>;\n  using vertex_subset_dense = parlay::sequence<bool>;\n  Fa fa;\n  Cond cond;\n  const Graph& G;\n  bool dedup;\n  bool verbose;\n  parlay::sequence<vertexId> dup_seq;\n  edge_map(Graph const &G, Fa fa, Cond cond, bool dedup=false,\n\t   bool verbose=false) :\n    G(G), fa(fa), cond(cond), dedup(dedup), verbose(verbose) {\n    dup_seq = parlay::sequence<vertexId>::uninitialized(G.numVertices());\n  }\n\n  auto edge_map_sparse(vertex_subset_sparse const &vtx_subset) {\n    if (verbose) std::cout << \"edge map sparse: \" << vtx_subset.size() << std::endl;\n    auto nested_edges = parlay::map(vtx_subset, [&] (vertexId v) {\n\treturn parlay::delayed_tabulate(G[v].degree, [&, v] (size_t i) {\n\t    return std::pair(v, G[v].Neighbors[i]);});});\n    auto edges = delayed::flatten(nested_edges);\n    auto r = delayed::filter_map(edges,\n\t\t\t\t [&] (auto x) {return cond(x.second) && fa(x.first, x.second);},\n\t\t\t\t [] (auto x)  {return x.second;});\n    if (dedup) {\n      parlay::parallel_for(0,r.size(), [&] (size_t i) { dup_seq[r[i]] = i;});\n      auto flags = parlay::tabulate(r.size(), [&] (size_t i) {return i==dup_seq[r[i]];});\n      return vertex_subset_(parlay::pack(r, flags));\n    }\n    return vertex_subset_(std::move(r));\n  }\n\n  auto edge_map_dense(vertex_subset_dense const &vtx_subset) {\n    if (verbose) std::cout << \"edge map dense:  \" << vtx_subset.size() << std::endl;\n    auto r = parlay::tabulate(G.numVertices(), [&] (vertexId v) -> bool {\n        bool result = false;\n        if (cond(v)) {        \n\t  size_t block_size = 5000;\n\t  auto vtx = G[v];\n\t  auto d = vtx.degree;\n\t  auto ngh = vtx.Neighbors;\n\t  auto do_block = [&, vsub=vtx_subset.begin()] (size_t i) {\n            size_t begin = block_size * i;\n\t    size_t end = std::min<size_t>(begin + block_size, d);\n\t    for (size_t j = begin; j < end; j++) {\n\t      if (!cond(v)) return;\n\t      vertexId u = ngh[j];\n\t      if (vsub[u]) {\n\t\tbool x = fa(u,v);\n\t\tif (!result && x) result = true;\n\t      }}};\n\t  size_t num_blocks = vtx.degree/block_size + 1;\n\t  if (num_blocks == 1) do_block(0);\n\t  else parlay::parallel_for(0, num_blocks, do_block, 1);\n\t}\n\treturn result;});\n    return vertex_subset_(std::move(r));\n  }\n\n  auto operator() (vertex_subset_ const &vtx_subset) {\n    parlay::internal::timer t(\"edge_map\", verbose);\n    auto l = vtx_subset.size();\n    auto n = G.numVertices();\n    bool do_dense;\n    if (vtx_subset.is_sparse) {\n      auto out_degree = parlay::reduce(parlay::delayed_map(vtx_subset.sparse, [&] (size_t i) {\n\t\t\t   return G[i].degree;}));\n      if ((l + out_degree) > G.m/20) {\n\tparlay::sequence<bool> d_vtx_subset(n, false);\n\tparlay::parallel_for(0, l, [&] (size_t i) {\n          d_vtx_subset[vtx_subset.sparse[i]] = true;});\n\tt.next(\"convert\");\n\treturn edge_map_dense(d_vtx_subset);\n      } else return edge_map_sparse(vtx_subset.sparse);\n    } else {\n      if (l > n/20) return edge_map_dense(vtx_subset.dense);\n      else {\n\tauto s_vtx_subset = parlay::pack_index<vertexId>(vtx_subset.dense);\n\treturn edge_map_sparse(s_vtx_subset);\n      }\n    }\n  }\n};\n}\n"
  },
  {
    "path": "algorithms/bench/common/parallelDefs",
    "content": "ifeq (, $(shell which jemalloc-config))\nJEMALLOC =\nelse\nJEMALLOCLD = $(shell jemalloc-config --libdir)\nJEMALLOC = -L$(JEMALLOCLD) -ljemalloc \nendif\n\nCCFLAGS = -mcx16 -O3 -std=c++17 -DNDEBUG -I .\nCLFLAGS = -ldl $(JEMALLOC)\n\nOMPFLAGS = -DPARLAY_OPENMP -fopenmp\nCILKFLAGS = -DPARLAY_CILK -fcilkplus\nPBBFLAGS = -DHOMEGROWN -pthread\n\nifdef OPENMP\nCC = g++\nCFLAGS = $(OMPFLAGS) $(CCFLAGS)\nLFLAGS = $(OMPFLAGS) $(CLFLAGS)\n\nelse ifdef CILK\nCC = g++\nCFLAGS = $(CILKFLAGS) $(CCFLAGS)\nLFLAGS = $(CILKFLAGS) $(CLFLAGS)\n\nelse\nCC = g++\nCFLAGS = $(PBBFLAGS) $(CCFLAGS)\nLFLAGS = $(PBBFLAGS) $(CLFLAGS)\nendif\n\n"
  },
  {
    "path": "algorithms/bench/common/parallelDefsANN",
    "content": "ifeq (, $(shell which jemalloc-config))\r\nJEMALLOC =\r\nelse\r\nJEMALLOCLD = $(shell jemalloc-config --libdir)\r\nJEMALLOC = -L$(JEMALLOCLD) -ljemalloc \r\nendif\r\n\r\nCCFLAGS = -mcx16 -O3 -std=c++17 -march=native -DNDEBUG -I .\r\nCLFLAGS = -ldl $(JEMALLOC)\r\n\r\nOMPFLAGS = -DPARLAY_OPENMP -fopenmp\r\nCILKFLAGS = -DPARLAY_CILK -fcilkplus\r\nPBBFLAGS = -DHOMEGROWN -pthread\r\n\r\nifdef OPENMP\r\nCC = g++\r\nCFLAGS = $(OMPFLAGS) $(CCFLAGS)\r\nLFLAGS = $(OMPFLAGS) $(CLFLAGS)\r\n\r\nelse ifdef CILK\r\nCC = g++\r\nCFLAGS = $(CILKFLAGS) $(CCFLAGS)\r\nLFLAGS = $(CILKFLAGS) $(CLFLAGS)\r\n\r\nelse\r\nCC = g++\r\nCFLAGS = $(PBBFLAGS) $(CCFLAGS)\r\nLFLAGS = $(PBBFLAGS) $(CLFLAGS)\r\nendif\r\n"
  },
  {
    "path": "algorithms/bench/common/parallelDefs_OMP",
    "content": "ifeq (, $(shell which jemalloc-config))\nJEMALLOC =\nelse\nJEMALLOCLD = $(shell jemalloc-config --libdir)\nJEMALLOC = -L$(JEMALLOCLD) -ljemalloc \nendif\n\nCCFLAGS = -mcx16 -O3 -std=c++17\nCLFLAGS = -ldl $(JEMALLOC)\n\nOMPFLAGS = -DOPENMP -fopenmp\nCILKFLAGS = -DCILK -fcilkplus\nPBBFLAGS = -DPARLAY_OPENMP -fopenmp -pthread\n\nifdef OPENMP\nCC = g++\nCFLAGS = $(OMPFLAGS) $(CCFLAGS)\nLFLAGS = $(OMPFLAGS) $(LCFLAGS)\n\nelse ifdef CILK\nCC = g++\nCFLAGS = $(CILKFLAGS) $(CCFLAGS)\nLFLAGS = $(CILKFLAGS) $(CLFLAGS)\n\nelse\nCC = g++\nCFLAGS = $(PBBFLAGS) $(CCFLAGS)\nLFLAGS = $(PBBFLAGS) $(CLFLAGS)\nendif\n"
  },
  {
    "path": "algorithms/bench/common/parseCommandLine.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#ifndef _PARSE_COMMAND_LINE\n#define _PARSE_COMMAND_LINE\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <cstring>\nusing namespace std;\n\nstruct commandLine {\n  int argc;\n  char** argv;\n  string comLine;\n  commandLine(int _c, char** _v, string _cl) \n    : argc(_c), argv(_v), comLine(_cl) {}\n\n  commandLine(int _c, char** _v) \n    : argc(_c), argv(_v), comLine(\"bad arguments\") {}\n\n  void badArgument() {\n    cout << \"usage: \" << argv[0] << \" \" << comLine << endl;\n    abort();\n  }\n\n  // get an argument\n  // i is indexed from the last argument = 0, second to last indexed 1, ..\n  char* getArgument(int i) {\n    if (argc < 2+i) badArgument();\n    return argv[argc-1-i];\n  }\n\n  // looks for two filenames\n  pair<char*,char*> IOFileNames() {\n    if (argc < 3) badArgument();\n    return pair<char*,char*>(argv[argc-2],argv[argc-1]);\n  }\n\n  pair<int,char*> sizeAndFileName() {\n    if (argc < 3) badArgument();\n    return pair<int,char*>(std::atoi(argv[argc-2]),(char*) argv[argc-1]);\n  }\n\n  bool getOption(string option) {\n    for (int i = 1; i < argc; i++)\n      if ((string) argv[i] == option) return true;\n    return false;\n  }\n\n  char* getOptionValue(string option) {\n    for (int i = 1; i < argc-1; i++)\n      if ((string) argv[i] == option) return argv[i+1];\n    return NULL;\n  }\n\n  string getOptionValue(string option, string defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((string) argv[i] == option) return (string) argv[i+1];\n    return defaultValue;\n  }\n\n  int getOptionIntValue(string option, int defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((string) argv[i] == option) {\n\tint r = atoi(argv[i+1]);\n\tif (r < 1) badArgument();\n\treturn r;\n      }\n    return defaultValue;\n  }\n\n  long getOptionLongValue(string option, long defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((string) argv[i] == option) {\n\tlong r = atol(argv[i+1]);\n\tif (r < 1) badArgument();\n\treturn r;\n      }\n    return defaultValue;\n  }\n\n  double getOptionDoubleValue(string option, double defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((string) argv[i] == option) {\n\tdouble val;\n\tif (sscanf(argv[i+1], \"%lf\",  &val) == EOF) {\n\t  badArgument();\n\t}\n\treturn val;\n      }\n    return defaultValue;\n  }\n\n};\n \n#endif // _PARSE_COMMAND_LINE\n"
  },
  {
    "path": "algorithms/bench/common/parse_command_line.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <cstring>\n\nstruct commandLine {\n  int argc;\n  char** argv;\n  std::string comLine;\n  commandLine(int _c, char** _v, std::string _cl)\n    : argc(_c), argv(_v), comLine(_cl) {\n      if (getOption(\"-h\") || getOption(\"-help\"))\n\tbadArgument();\n    }\n\n  commandLine(int _c, char** _v)\n    : argc(_c), argv(_v), comLine(\"bad arguments\") { }\n\n  void badArgument() {\n    std::cout << \"usage: \" << argv[0] << \" \" << comLine << std::endl;\n    exit(0);\n  }\n\n  // get an argument\n  // i is indexed from the last argument = 0, second to last indexed 1, ..\n  char* getArgument(int i) {\n    if (argc < 2+i) badArgument();\n    return argv[argc-1-i];\n  }\n\n  // looks for two filenames\n  std::pair<char*,char*> IOFileNames() {\n    if (argc < 3) badArgument();\n    return std::pair<char*,char*>(argv[argc-2],argv[argc-1]);\n  }\n\n  std::pair<size_t,char*> sizeAndFileName() {\n    if (argc < 3) badArgument();\n    return std::pair<size_t,char*>(std::atoi(argv[argc-2]),(char*) argv[argc-1]);\n  }\n\n  bool getOption(std::string option) {\n    for (int i = 1; i < argc; i++)\n      if ((std::string) argv[i] == option) return true;\n    return false;\n  }\n\n  char* getOptionValue(std::string option) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) return argv[i+1];\n    return NULL;\n  }\n\n  std::string getOptionValue(std::string option, std::string defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) return (std::string) argv[i+1];\n    return defaultValue;\n  }\n\n  long getOptionLongValue(std::string option, long defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) {\n\tlong r = atol(argv[i+1]);\n\tif (r < 0) badArgument();\n\treturn r;\n      }\n    return defaultValue;\n  }\n\n  int getOptionIntValue(std::string option, int defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) {\n\tint r = atoi(argv[i+1]);\n\tif (r < 0) badArgument();\n\treturn r;\n      }\n    return defaultValue;\n  }\n\n  double getOptionDoubleValue(std::string option, double defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) {\n\tdouble val;\n\tif (sscanf(argv[i+1], \"%lf\",  &val) == EOF) {\n\t  badArgument();\n\t}\n\treturn val;\n      }\n    return defaultValue;\n  }\n\n};\n\n"
  },
  {
    "path": "algorithms/bench/common/runTests.py",
    "content": "import subprocess\nimport sys\nimport random\nimport os\n\ndef onPprocessors(command,p) :\n  if \"OPENMP\" in os.environ:\n    return \"OMP_NUM_THREADS=\"+repr(p)+\" \" + command\n    return command  \n  elif \"CILK\" in os.environ:\n    return \"CILK_NWORKERS=\"+repr(p)+\" \" + command\n  else:\n    return \"PARLAY_NUM_THREADS=\"+repr(p)+\" \" + command\n  \ndef shellGetOutput(str) :\n  process = subprocess.Popen(str,shell=True,stdout=subprocess.PIPE,\n                             stderr=subprocess.PIPE)\n  output, err = process.communicate()\n  \n  if (len(err) > 0):\n      raise NameError(str+\"\\n\"+output+err)\n  return output.decode(\"utf-8\")\n\ndef stripFloat(val) :\n  trunc = float(int(val*1000))/1000\n  return str(trunc).rstrip('0')    \n\ndef runSingle(runProgram, options, ifile, procs) :\n  comString = \"./\"+runProgram+\" \"+options+\" \"+ifile\n  if (procs > 0) :\n    comString = onPprocessors(comString,procs)\n  out = shellGetOutput(comString)\n  #print(out)\n  try:\n    times = [float(str[str.index(':')+2:]) for str in out.split('\\n') if str.startswith(\"Parlay time: \")]\n    return times\n  except (ValueError,IndexError):\n    raise NameError(comString+\"\\n\"+out)\n\ndef geomean(a) :\n  r = 1.0\n  for x in a :\n    r = r * x\n  return r**(1.0/len(a))\n\ndef runTest(runProgram, checkProgram, dataDir, test, rounds, procs, noOutput, keepData) :\n    random.seed()\n    outFile=\"/tmp/ofile%d_%d\" %(random.randint(0, 1000000), random.randint(0, 1000000)) \n    [weight, inputFileNames, runOptions, checkOptions] = test\n    if type(inputFileNames) is str :\n      inputFileNames = [inputFileNames]\n    shortInputNames = \" \".join(inputFileNames)\n    if len(dataDir)>0:\n      out = shellGetOutput(\"cd \" + dataDir + \"; make \" + shortInputNames)\n    longInputNames = \" \".join(dataDir + \"/\" + name for name in inputFileNames)\n    runOptions = runOptions + \" -r \" + repr(rounds)\n    if (noOutput == 0) :\n      runOptions = runOptions + \" -o \" + outFile\n    times = runSingle(runProgram, runOptions, longInputNames, procs)\n    if (noOutput == 0) :\n      checkString = (\"./\" + checkProgram + \" \" + checkOptions + \" \"\n                     + longInputNames + \" \" + outFile)\n      checkOut = shellGetOutput(checkString)\n      # Allow checker output comments. Comments are lines prefixed by '::'\n      nonCommentLines = [s for s in checkOut.split('\\n') if not s.startswith(':') and len(s)>0]\n      if (len(nonCommentLines) > 0) :\n        print(\"CheckOut:\", checkOut)\n        raise NameError(checkString+\"\\n\"+checkOut)\n      os.remove(outFile)\n    if len(dataDir)>0 and not(keepData):\n      out = shellGetOutput(\"rm \" + longInputNames)\n    ptimes = str([stripFloat(time)\n                  for time in times])[1:-1]\n    outputStr = \"\"\n    if (len(runOptions) > 0) :\n      outputStr = \" : \" + runOptions\n    print(shortInputNames + outputStr + \" : \"\n          + ptimes + \", geomean = \" + stripFloat(geomean(times)))\n    return [weight,times]\n    \ndef averageTime(times) :\n    return sum(times)/len(times)\n    \ndef timeAll(name, runProgram, checkProgram, dataDir, tests, rounds, procs, noOutput,\n            addToDatabase, problem, keepData) :\n  totalTime = 0\n  totalWeight = 0\n  try:\n    results = [runTest(runProgram, checkProgram, dataDir, test, rounds, procs,\n                       noOutput, keepData)\n               for test in tests]\n    meanOfMeans = geomean([geomean(times) for (w,times) in results])\n    meanOfMins = geomean([sorted(times)[0] for (w,times) in results])\n    print(name + \" : \" + repr(procs) +\" : \" +\n          \"geomean of mins = \" + stripFloat(meanOfMins) +\n          \", geomean of geomeans = \" + stripFloat(meanOfMeans))\n    if (addToDatabase) :\n      try:\n        dbAddResult(problem=problem, program=runProgram, results=results, numProcs=procs, mean=totalTimeMean/totalWeight,\n                    min=totalTimeMin/totalWeight, median=totalTimeMedian/totalWeight, tests=tests)\n      except:\n        print(\"Could not insert result in database. Error:\", sys.exc_info()[0])\n#        if (os.getlogin() == 'akyrola'):  raise\n    return 0\n  except NameError as x:\n    print(\"TEST TERMINATED ABNORMALLY:\\n[\"+str(x) + \"]\")\n    return 1\n  except KeyboardInterrupt:\n    return 1\n\n\ndef getOption(str) :\n  a = sys.argv\n  l = len(a)\n  for i in range(1, l) :\n    if (a[i] == str) :\n      return True\n  return False\n\ndef getArg(str, default) :\n  a = sys.argv\n  l = len(a)\n  for i in range(1, l) :\n    if (a[i] == str and  (i+1 != l)) :\n        return sys.argv[i+1]\n  return default\n\ndef getArgs() :\n  noOutput = getOption(\"-x\")\n  addToDatabase = getOption(\"-d\")\n  processors = int(getArg(\"-p\", 0))\n  rounds = int(getArg(\"-r\", 1))\n  keep = getOption(\"-k\")\n  return (noOutput, rounds, addToDatabase, processors, keep)\n\ndef timeAllArgs(runProgram, problem, checkProgram, dataDir, tests, keepInputData=False) :\n  keepData = keepInputData\n  (noOutput, rounds, addToDatabase, procs, keep) = getArgs()\n  keep = keepInputData or keep\n  name = os.path.basename(os.getcwd())\n  timeAll(name, runProgram, checkProgram, dataDir, tests, rounds, procs, noOutput, addToDatabase, problem, keep)\n\n#\n# Database insertions\n# - akyrola@cs.cmu.edu\n\nimport os\n\ndef dbInitConnection():\n    import MySQLdb\n    global cursor\n    # TODO: move to a config file\n    dbconn = MySQLdb.connect (host = \"multi6.aladdin.cs.cmu.edu\",\n                                                            user = \"pbbs\",\n                                                            passwd = \"pbbspasshuuhaa\",\n                                                            db = \"pbbsweb\")\n\n    cursor = dbconn.cursor ()\n    dbconn.autocommit(1)\n\n\n\ndef dbAddResult(problem, program, results, numProcs, mean, min, median, tests):\n    dbInitConnection()\n    contentHash = computeContentHash(tests)\n    program = shellGetOutput(\"pwd\").split('/')[-1].replace('\\r','').replace('\\n', '') + '/' + program\n    problemId = dbGetProblemId(problem, contentHash)\n    programId = dbGetProgramId(program, problemId)\n    hostId = getHostId()\n\n       \n    #username = os.getlogin()\n    # getlogin does not work with some terminals (see various posts on web)\n    # guyb replaced with the following\n    username = os.getenv('USER')\n    if (numProcs == 0): numProcs = detectCPUs()\n    # Insert run into db\n    cursor.execute(\"\"\" insert into pbbs_runs (problem_id,program_id,numprocs,mean_time,min_time,median_time,username,host_id) values(\n                                                %s,      %s,          %s,      %s,       %s,       %s,       %s,      %s)\n                       \"\"\", (problemId, programId, numProcs, mean, min, median, username, hostId))\n    cursor.execute(\" select last_insert_id()\")\n    runId = cursor.fetchone()[0]\n    \n    for i in range(0, len(results)):\n        (weight, times) = results[i]\n        test = tests[i]\n        [weight,inputFileNames,runOptions,checkOptions] = test\n        if type(inputFileNames) is list :\n          inputFileNames = \"+\".join(inputFileNames)\n        for time in times:\n            cursor.execute(\"\"\" insert into pbbs_subruns(run_id, inputfile, time, weight, params, check_params) values(\n                                                       %s,          %s      , %s ,   %s,       %s,     %s) \"\"\",\n                                                        (runId, inputFileNames, time, weight, runOptions, checkOptions))\n        \n    \ndef computeContentHash(tests):\n    hash = \"\"\n    for test in tests:\n        [weight,inputFileNames,runOptions,checkOptions] = test\n        if type(inputFileNames) is list :\n          inputFileNames = \"+\".join(inputFileNames)\n        hash += \";%f%s%s%s\" %(weight,inputFileNames.strip(), runOptions.strip(),checkOptions.strip())\n    hash = hash.replace(' ', '_')\n    return hash\n    \ndef dbGetProblemId(probname, contentHash):\n    cursor.execute(\"select id from pbbs_problems where name=%s and content_hash=%s\", (probname, contentHash))\n    row = cursor.fetchone()\n    if row == None:\n        # Insert into db\n        cursor.execute( \"insert into pbbs_problems (name,content_hash) values(%s,%s) \", (probname, contentHash))\n        cursor.execute(\" select last_insert_id()\")\n        row = cursor.fetchone()\n    return row[0]\n    \ndef dbGetProgramId(progname, problemId): \n    cursor.execute(\"select id from pbbs_programs where name=%s and problem_id=%s\", (progname, problemId))\n    row = cursor.fetchone()\n    if row == None:\n        # Insert into db\n        cursor.execute( \"insert into pbbs_programs (problem_id, name) values(%s, %s) \", (problemId, progname))\n        cursor.execute(\" select last_insert_id()\")\n        row = cursor.fetchone()\n    return row[0]\n    \nimport platform\ndef getHostId():\n    (procmodel, mhz) = detectCPUModel() \n    numprocs = detectCPUs()\n    \n    (sysname, nodename, release, version, machine) = os.uname()\n    \n    if (\"OPENMP\" in os.environ):\n       nodename = nodename + \"[OPENMP]\"\n    \n    cursor.execute(\"select id from pbbs_hosts where hostname=%s and procmodel=%s and version=%s and numprocs=%s\", (nodename, procmodel, version, numprocs))\n    row = cursor.fetchone()\n    if row == None:\n        cursor.execute(\"\"\" insert into pbbs_hosts(hostname,sysname,releasen,version,machine,numprocs,procmodel,mhz) values\n                                                  (%s,      %s,        %s,   %s,    %s,    %s,           %s,  %s) \"\"\",\n                                                    (nodename, sysname, release, version, machine, numprocs, procmodel, mhz))\n        cursor.execute(\" select last_insert_id()\")\n        row = cursor.fetchone()\n    return row[0]\n\ndef detectCPUModel():  \n    mhz = 0\n    model = platform.processor()\n    try:\n        if (platform.system() == \"Darwin\"):\n            model = shellGetOutput(\"system_profiler SPHardwareDataType |grep 'Processor Name'\")\n            mhz = shellGetOutput(\"system_profiler SPHardwareDataType |grep 'Processor Speed'\")\n        else:\n            model = shellGetOutput('grep \"model name\" /proc/cpuinfo').split('\\n')[0]\n            mhz = shellGetOutput('grep \"cpu MHz\" /proc/cpuinfo').split('\\n')[0]\n        model = model.split(':')[-1].strip()\n        mhz = mhz.split(':')[-1].strip()\n    except:\n        # Could not get processor model \n        print(\"Could not determine CPU model\", sys.exc_info()[0])\n    return (model, mhz)\n\ndef detectCPUs():\n    \"\"\"\n     Detects the number of CPUs on a system. Cribbed from pp.\n     \"\"\"\n    # Linux, Unix and MacOS:\n    if hasattr(os, \"sysconf\"):\n       if \"SC_NPROCESSORS_ONLN\" in os.sysconf_names:\n           # Linux & Unix:\n           ncpus = os.sysconf(\"SC_NPROCESSORS_ONLN\")\n           if isinstance(ncpus, int) and ncpus > 0:\n               return ncpus\n       else: # OSX:\n           return int(os.popen2(\"sysctl -n hw.ncpu\")[1].read())\n    # Windows:\n    if \"NUMBER_OF_PROCESSORS\" in os.environ:\n           ncpus = int(os.environ[\"NUMBER_OF_PROCESSORS\"]);\n           if ncpus > 0:\n               return ncpus\n    return 1 # Default    \n"
  },
  {
    "path": "algorithms/bench/common/runTestsANN.py",
    "content": "import subprocess\nimport sys\nimport random\nimport os\n\ndef addLineToFile(oFile, line):\n    with open(\"oFile\", \"a+\") as file_object:\n        # Move read cursor to the start of file.\n        file_object.seek(0)\n        # If file is not empty then append '\\n'\n        data = file_object.read(100)\n        if len(data) > 0 :\n            file_object.write(\"\\n\")\n        # Append text at the end of file\n        file_object.write(line)\n\ndef onPprocessors(command,p) :\n  if os.environ.has_key(\"OPENMP\"):\n    return \"OMP_NUM_THREADS=\"+repr(p)+\" \" + command\n    return command  \n  elif os.environ.has_key(\"CILK\"):\n    return \"CILK_NWORKERS=\"+repr(p)+\" \" + command\n  else:\n    return \"PARLAY_NUM_THREADS=\"+repr(p)+\" \" + command\n  \ndef shellGetOutput(str) :\n  process = subprocess.Popen(str,shell=True,stdout=subprocess.PIPE,\n                             stderr=subprocess.PIPE)\n  output, err = process.communicate()\n  \n  if (len(err) > 0):\n      raise NameError(str+\"\\n\"+output+err)\n  return output\n\ndef stripFloat(val) :\n  trunc = float(int(val*1000))/1000\n  return str(trunc).rstrip('0')    \n\ndef runSingle(runProgram, options, ifile, procs, oFile) :\n  comString = \"./\"+runProgram+\" \"+options+\" \"+ifile\n  if (procs > 0) :\n    comString = onPprocessors(comString,procs)\n  out = shellGetOutput(comString)\n  nonCommentLines = [s for s in out.split('\\n') if len(s)>0]\n  for i in nonCommentLines:\n    addLineToFile(oFile, i)\n  try:\n    times = [float(str[str.index(':')+2:]) for str in out.split('\\n') if str.startswith(\"Parlay time: \")]\n    return times\n  except (ValueError,IndexError):\n    raise NameError(comString+\"\\n\"+out)\n\ndef runTest(runProgram, checkProgram, dataDir, test, rounds, procs, noOutput, oFile) :\n    random.seed()\n    outFile=\"/tmp/ofile%d_%d\" %(random.randint(0, 1000000), random.randint(0, 1000000)) \n    [weight, gFileName, qFileName, iFileName, runOptions, checkOptions] = test\n    if type(gFileName) is str :\n      gFileName = [gFileName]\n    shortgFileName = \" \".join(gFileName)\n    if len(dataDir)>0:\n      out = shellGetOutput(\"cd \" + dataDir + \"; make \" + shortgFileName)\n    longgFileName = \" \".join(dataDir + \"/\" + name for name in gFileName)\n    if type(qFileName) is str :\n      qFileName = [qFileName]\n    shortqFileName = \" \".join(qFileName)\n    if len(dataDir)>0:\n      out = shellGetOutput(\"cd \" + dataDir + \"; make \" + shortqFileName)\n    longqFileName = \" \".join(dataDir + \"/\" + name for name in qFileName)\n    if type(iFileName) is str :\n      iFileName = [iFileName]\n    shortiFileName = \" \".join(iFileName)\n    if len(dataDir)>0:\n      out = shellGetOutput(\"cd \" + dataDir + \"; make \" + shortiFileName)\n    longiFileName = \" \".join(dataDir + \"/\" + name for name in iFileName)\n    runOptions = runOptions + \" -q \" + longqFileName\n    runOptions = runOptions + \" -r \" + repr(rounds)\n    if (noOutput == 0) :\n      runOptions = runOptions + \" -o \" + outFile\n    times = runSingle(runProgram, runOptions, longgFileName, procs, oFile)\n    if (noOutput == 0) :\n      checkString = (\"./\" + checkProgram + \" \" + checkOptions + \" \"\n                     + longiFileName + \" \" + outFile)\n      checkOut = shellGetOutput(checkString)\n      nonCommentLines = [s for s in checkOut.split('\\n') if len(s)>0]\n      for line in nonCommentLines:\n        print(line)\n        addLineToFile(oFile, line)\n      os.remove(outFile)\n    ptimes = str([stripFloat(time)\n                  for time in times])[1:-1]\n    outputStr = \"\"\n    if (len(runOptions) > 0) :\n      outputStr = \" : \" + runOptions\n    outStr = repr(weight) + outputStr + \" : \" + ptimes\n    print(outStr)\n    addLineToFile(oFile, outStr)\n    return [weight,times]\n    \ndef averageTime(times) :\n    return sum(times)/len(times)\n    \n\ndef timeAll(name, runProgram, checkProgram, dataDir, tests, rounds, procs, noOutput,\n            problem, oFile) :\n  totalTime = 0\n  totalWeight = 0\n  try:\n    results = [runTest(runProgram, checkProgram, dataDir, test, rounds, procs,\n                       noOutput, oFile)\n               for test in tests]\n    totalTimeMean = 0\n    totalTimeMin = 0\n    totalTimeMedian = 0\n    totalWeight = 0\n    j = 0\n    for (weight,times) in results:\n      l = len(times)\n      if (l == 0):\n        print(\"Warning, no timed results for\", tests[j])\n        continue\n      times = sorted(times)\n      totalTimeMean = totalTimeMean + weight*sum(times)/l\n      totalTimeMin = totalTimeMin + weight*times[0]\n      totalTimeMedian = totalTimeMedian + weight*times[(l-1)/2]\n      totalWeight = totalWeight + weight\n      j += 1\n    print(name + \" : \" + repr(procs) +\" : \" +\n          \"weighted time, min=\" + stripFloat(totalTimeMin/totalWeight) +\n          \" median=\" + stripFloat(totalTimeMedian/totalWeight) +\n          \" mean=\" + stripFloat(totalTimeMean/totalWeight))\n    # return 0\n  except NameError as x:\n    print(\"TEST TERMINATED ABNORMALLY:\\n[\"+str(x) + \"]\")\n    return 1\n  except KeyboardInterrupt:\n    return 1\n\n\ndef getOption(str) :\n  a = sys.argv\n  l = len(a)\n  for i in range(1, l) :\n    if (a[i] == str) :\n      return True\n  return False\n\ndef getArg(str, default) :\n  a = sys.argv\n  l = len(a)\n  for i in range(1, l) :\n    if (a[i] == str and  (i+1 != l)) :\n        return sys.argv[i+1]\n  return default\n\ndef getArgs() :\n  noOutput = getOption(\"-x\")\n  processors = int(getArg(\"-p\", 0))\n  rounds = int(getArg(\"-r\", 1))\n  return (noOutput, rounds, processors)\n\ndef timeAllArgs(runProgram, problem, checkProgram, dataDir, tests, oFile) :\n    (noOutput, rounds, procs) = getArgs()\n    name = os.path.basename(os.getcwd())\n    timeAll(name, runProgram, checkProgram, dataDir, tests, rounds, procs, noOutput, problem, oFile)\n\n"
  },
  {
    "path": "algorithms/bench/common/seqDefs",
    "content": "ifeq (, $(shell which jemalloc-config))\nJEMALLOC =\nelse\nJEMALLOCLD = $(shell jemalloc-config --libdir)\nJEMALLOC = -L$(JEMALLOCLD) -ljemalloc \nendif\n\nCCFLAGS = -mcx16 -DPARLAY_SEQUENTIAL -O3 -std=c++17 -DNDEBUG\nCLFLAGS = $(JEMALLOC)\n\nCC = g++\nCFLAGS = $(CCFLAGS)\nLFLAGS = $(CLFLAGS)\n"
  },
  {
    "path": "algorithms/bench/common/sequenceIO.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <cstring>\n#include \"IO.h\"\n#include \"../parlay/primitives.h\"\n#include \"../parlay/io.h\"\n\nnamespace parlay {\n  using chars = sequence<char>;\n};\n\nnamespace benchIO {\n  using namespace std;\n  using parlay::sequence;\n  using parlay::tabulate;\n  using parlay::make_slice;\n\n  typedef unsigned int uint;\n  typedef parlay::sequence<char> charSeq;\n  typedef pair<int,int> intPair;\n  typedef pair<unsigned int, unsigned int> uintPair;\n  typedef pair<unsigned int, int> uintIntPair;\n  typedef pair<long,long> longPair;\n  typedef pair<charSeq,long> stringIntPair;\n  typedef pair<double,double> doublePair;\n\n\n  enum elementType { none, intType, intPairT, doublePairT,\n\t\t     stringIntPairT, doubleT, stringT};\n  \n  //elementType dataType(long a) { return longT;}\n  elementType dataType(long a) { return intType;}\n  elementType dataType(int a) { return intType;}\n  elementType dataType(uint a) { return intType;}\n  elementType dataType(double a) { return doubleT;}\n  elementType dataType(charSeq a) { return stringT;}\n  elementType dataType(char* a) { return stringT;}\n  elementType dataType(intPair a) { return intPairT;}\n  elementType dataType(uintPair a) { return intPairT;}\n  elementType dataType(uintIntPair a) { return intPairT;}\n  elementType dataType(longPair a) { return intPairT;}\n  elementType dataType(stringIntPair a) { return stringIntPairT;}\n  elementType dataType(doublePair a) { return doublePairT;}\n\n  string seqHeader(elementType dt) {\n    switch (dt) {\n    case intType: return \"sequenceInt\";\n    case doubleT: return \"sequenceDouble\";\n    case stringT: return \"sequenceChar\";\n    case intPairT: return \"sequenceIntPair\";\n    case stringIntPairT: return \"sequenceStringIntPair\";\n    case doublePairT: return \"sequenceDoublePair\";\n    default: \n      cout << \"writeSeqToFile: type not supported\" << endl; \n      abort();\n    }\n  }\n\n  template <typename Range>\n  elementType elementTypeFromHeader(Range R) {\n    string s(R.begin(), R.end());\n    if (s == \"sequenceInt\") return intType;\n    else if (s == \"sequenceDouble\") return doubleT;\n    else if (s == \"sequenceChar\") return stringT;\n    else if (s == \"sequenceIntPair\") return intPairT;\n    else if (s == \"sequenceStringIntPair\") return stringIntPairT;\n    else if (s == \"sequenceDoublePair\") return doublePairT;\n    else return none;\n  }\n\n  template <typename Range>\n  elementType elementTypeFromString(Range R) {\n    string s(R.begin(), R.end());\n    if (s == \"double\") return doubleT;\n    else if (s == \"string\") return stringT;\n    else if (s == \"int\") return intType;\n    else return none;\n  }\n\n  long read_long(charSeq const &S) {\n    return chars_to_long(S);}\n\n  double read_double(charSeq const &S) {\n    return chars_to_double(S);}\n\n  using charseq_slice = parlay::slice<const charSeq*, const charSeq*>;\n  \n\n  // specialized parsing functions\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, double>::value, sequence<double>>::type\n  parseElements(Range const &S) {\n    return tabulate(S.size(), [&] (long i) -> double {return read_double(S[i]);});\n  }\n\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, int>::value, sequence<int>>::type\n  parseElements(Range const &S) {\n    return tabulate(S.size(), [&] (long i) -> int {return (int) read_long(S[i]);});\n  }\n\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, long>::value, sequence<long>>::type\n  parseElements(Range const &S) {\n    return tabulate(S.size(), [&] (long i) -> long {return (long) read_long(S[i]);});\n  }\n\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, uint>::value, sequence<uint>>::type\n  parseElements(Range const &S) {\n    return tabulate(S.size(), [&] (long i) -> uint {return (uint) read_long(S[i]);});\n  }\n\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, intPair>::value, sequence<intPair>>::type\n  parseElements(Range const &S) {\n    return tabulate((S.size())/2, [&] (long i) -> intPair {\n      return std::make_pair((int) read_long(S[2*i]), (int) read_long(S[2*i+1]));});\n  }\n\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, uintPair>::value, sequence<uintPair>>::type\n  parseElements(Range const &S) {\n    return tabulate((S.size())/2, [&] (long i) -> uintPair {\n      return std::make_pair((uint) read_long(S[2*i]), (uint) read_long(S[2*i+1]));});\n  }\n\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, doublePair>::value, sequence<doublePair>>::type\n  parseElements(Range const &S) {\n    return tabulate((S.size())/2, [&] (long i) -> doublePair {\n      return std::make_pair(read_double(S[2*i]), read_double(S[2*i+1]));});\n  }\n\n  template<typename T, typename Range>\n  inline typename std::enable_if<std::is_same<T, charSeq>::value, sequence<charSeq>>::type\n  parseElements(Range const &S) {\n    return parlay::to_sequence(S);\n  }\n\n  // sequence<stringIntPair> parseElements<stringIntPair>(Range S) {\n  //   return sequence<stringIntPair>(0);\n  // }  \n\n  template <typename T, typename CharRange>\n  void check_header(CharRange& S) {\n    T a;\n    string header(S[0].begin(), S[0].end());\n    string type_str = seqHeader(dataType(a));\n    if (header != type_str) {\n      cout << \"bad header: expected \" << type_str << \" got \" << header << endl;\n      abort();\n    }\n  }\n\n  // reads file, tokenizes and then dispatches to specialized parsing function\n  template <typename T>\n  sequence<T> readSequenceFromFile(char const *fileName) {\n    auto S = get_tokens(fileName);\n    check_header<T>(S[0]);\n    return parseElements<T>(S.cut(1,S.size()));\n  }\n  \n  template <class T>\n  int writeSequenceToFile(sequence<T> const &A, char const *fileName) {\n    elementType tp = dataType(A[0]);\n    return writeSeqToFile(seqHeader(tp), A, fileName);\n  }\n\n};\n"
  },
  {
    "path": "algorithms/bench/common/speculative_for.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include \"../parlay/parallel.h\"\n#include \"../parlay/primitives.h\"\n//#include \"atomics.h\"\n#include <limits>\n\nnamespace pbbs {\n\n  // idxT should be able to represent the range of iterations\n  // int OK for up to 2^31 iterations\n  // unsigned OK if freeze not used\n  template <class idxT>\n  struct reservation {\n    std::atomic<idxT> r;\n    static constexpr idxT max_idx = std::numeric_limits<idxT>::max();\n    reservation() : r(max_idx) {}\n    idxT get() const { return r.load();}\n    bool reserve(idxT i) { return parlay::write_min(&r, i, std::less<idxT>());}\n    bool reserved() const { return (r.load() < max_idx);}\n    void reset() {r = max_idx;}\n    void freeze() {r = -1;}\n    bool check(idxT i) const { return (r.load() == i);}\n    bool checkReset(idxT i) {\n      if (r==i) { r = max_idx; return 1;}\n      else return 0;\n    }\n  };\n\n  template <class idxT, class S>\n  long speculative_for(S step, idxT s, idxT e, long granularity,\n  \t\t     bool hasState=1, long maxTries=-1) {\n    if (maxTries < 0) maxTries = 100 + 200*granularity;\n    long maxRoundSize = (e-s)/granularity+1;\n    long currentRoundSize = maxRoundSize/4;\n    // integer types, do not need to be initialized\n    auto I = parlay::sequence<idxT>::uninitialized(maxRoundSize);\n    auto keep = parlay::sequence<bool>::uninitialized(maxRoundSize);\n    parlay::sequence<idxT> Ihold;  // initially empty\n    parlay::sequence<S> state;\n    if (hasState)\n      state = parlay::tabulate(maxRoundSize, [&] (size_t i) -> S {return step;});\n\n    long round = 0;\n    long numberDone = s; // number of iterations done\n    long numberKeep = 0; // number of iterations to carry to next round\n    long totalProcessed = 0; // number done including wasteds tries\n\n    while (numberDone < e) {\n      if (round++ > maxTries) \n\tthrow std::runtime_error(\"speculative_for: too many iterations, increase maxTries\");\n      long size = std::min(currentRoundSize, e - numberDone);\n\n      totalProcessed += size;\n      size_t loop_granularity = 0;\n\n      if (hasState) {\n        parlay::parallel_for (0, size, [&] (size_t i) {\n  \t  I[i] = (i < numberKeep) ? Ihold[i] : numberDone + i;\n  \t  keep[i] = state[i].reserve(I[i]);\n  \t}, loop_granularity);\n      } else {\n        parlay::parallel_for (0, size, [&] (size_t i) {\n  \t  I[i] = (i < numberKeep) ? Ihold[i] : numberDone + i;\n  \t  keep[i] = step.reserve(I[i]);\n  \t}, loop_granularity);\n      }\n\n      if (hasState) {\n        parlay::parallel_for (0, size, [&] (size_t i) {\n  \t  if (keep[i]) keep[i] = !state[i].commit(I[i]);}, loop_granularity);\n      } else {\n        parlay::parallel_for (0, size, [&] (size_t i) {\n  \t  if (keep[i]) keep[i] = !step.commit(I[i]);}, loop_granularity);\n      }\n\n      // keep iterations that failed for next round\n      Ihold = parlay::pack(I.head(size), keep.head(size));\n      numberKeep = Ihold.size();\n      numberDone += size - numberKeep;\n\n      //std::cout << size << \" : \" << numberKeep << \" : \"\n      //  << numberDone << \" : \" << currentRoundSize << std::endl;\n\n      // adjust round size based on number of failed attempts\n      if (float(numberKeep)/float(size) > .2)\n        currentRoundSize = std::max(currentRoundSize/2,\n  \t\t\t\t  std::max(maxRoundSize/64 + 1, numberKeep));\n      else if (float(numberKeep)/float(size) < .1)\n        currentRoundSize = std::min(currentRoundSize * 2, maxRoundSize);\n    }\n    return totalProcessed;\n  }\n} // namespace pbbs\n"
  },
  {
    "path": "algorithms/bench/common/time_loop.h",
    "content": "#include \"../parlay/internal/get_time.h\"\n\ntemplate<class F, class G, class H>\nvoid time_loop(int rounds, double delay, F initf, G runf, H endf) {\n  parlay::internal::timer t;\n  // run for delay seconds to \"warm things up\"\n  // will skip if delay is zero\n  while (t.total_time() < delay) {\n    initf(); runf(); endf();\n  } \n  for (int i=0; i < rounds; i++) {\n    initf();\n    t.start();\n    runf();\n    t.next(\"\");\n    endf();\n  }\n}\n"
  },
  {
    "path": "algorithms/bench/common/topology.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#ifndef _TOPOLOGY_INCLUDED\n#define _TOPOLOGY_INCLUDED\n\n#include <iostream>\n#include \"geometry.h\"\n\nusing namespace std;\n\n// *************************************************************\n//    TOPOLOGY\n// *************************************************************\n\ntemplate <typename point>\nstruct vertex;\n\n// an unoriented triangle with its three neighbors and 3 vertices\n//          vtx[1]\n//           o \n//           | \\ -> ngh[1]\n// ngh[2] <- |   o vtx[0]\n//           | / -> ngh[0]\n//           o\n//         vtx[2]\ntemplate <typename point>\nstruct triangle {\n  using tri_t = triangle<point>;\n  using vtx_t = vertex<point>;\n  tri_t *ngh [3];\n  vtx_t *vtx [3];\n  size_t id;\n  bool initialized;\n  char bad;  // used to mark badly shaped triangles\n  void setT(tri_t *t1, tri_t *t2, tri_t* t3) {\n    ngh[0] = t1; ngh[1] = t2; ngh[2] = t3; }\n  void setV(vtx_t *v1, vtx_t *v2, vtx_t *v3) {\n    vtx[0] = v1; vtx[1] = v2; vtx[2] = v3; }\n  int locate(tri_t *t) {\n    for (int i=0; i < 3; i++) {\n      //cout << t << \", \" << ngh[i] << endl;\n      if (ngh[i] == t) return i;\n    }\n    cout<<\"did not locate back pointer in triangulation\\n\";\n    abort(); // did not find\n  }\n  void update(tri_t *t, tri_t *tn) {\n    for (int i=0; i < 3; i++)\n      if (ngh[i] == t) {ngh[i] = tn; return;}\n    cout<<\"did not update\\n\";\n    abort(); // did not find\n  }\n};\n\n// a vertex pointing to an arbitrary triangle to which it belongs (if any)\ntemplate <typename point>\nstruct vertex {\n  using point_t = point;\n  using tri = triangle<point>;\n  point pt;\n  tri *t;\n  tri *badT;\n  int id;\n  int reserve;\n  size_t counter;\n  void print() {\n    cout << id << \" (\" << pt.x << \",\" << pt.y << \") \" << endl;\n  }\n  vertex(point p, size_t i) : pt(p), id(i), reserve(-1)\n\t\t\t    , badT(NULL)\n  {}\n  vertex() {}\n};\n\ninline int mod3(int i) {return (i>2) ? i-3 : i;}\n\n// a simplex is just an oriented triangle.  An integer (o)\n// is used to indicate which of 3 orientations it is in (0,1,2)\n// If boundary is set then it represents the edge through t.ngh[o],\n// which is a NULL pointer.\ntemplate <typename point>\nstruct simplex {\n  using vtx_t = vertex<point>;\n  using tri_t = triangle<point>;\n  tri_t *t;\n  int o;\n  bool boundary;\n  simplex(tri_t *tt, int oo) : t(tt), o(oo), boundary(0) {}\n  simplex(tri_t *tt, int oo, bool _b) : t(tt), o(oo), boundary(_b) {}\n  simplex(vtx_t *v1, vtx_t *v2, vtx_t *v3, tri_t *tt) {\n    t = tt;\n    t->ngh[0] = t->ngh[1] = t->ngh[2] = NULL;\n    t->vtx[0] = v1; v1->t = t;\n    t->vtx[1] = v2; v2->t = t;\n    t->vtx[2] = v3; v3->t = t;\n    o = 0;\n    boundary = 0;\n  }\n  simplex() : t(nullptr), o(0), boundary(false) {}\n\n  void print() {\n    if (t == NULL) cout << \"NULL simp\" << endl;\n    else {\n      cout << \"vtxs=\";\n      for (int i=0; i < 3; i++) \n\tif (t->vtx[mod3(i+o)] != NULL)\n\t  cout << t->vtx[mod3(i+o)]->id << \" (\" <<\n\t    t->vtx[mod3(i+o)]->pt.x << \",\" <<\n\t    t->vtx[mod3(i+o)]->pt.y << \") \";\n\telse cout << \"NULL \";\n      cout << endl;\n    }\n  }\n\n  simplex across() {\n    tri_t *to = t->ngh[o];\n    if (to != NULL) return simplex(to,to->locate(t));\n    else return simplex(t,o,1);\n  }\n\n  // depending on initial triangle this could be counterclockwise\n  simplex rotClockwise() { return simplex(t,mod3(o+1));}\n\n  bool valid() {return (!boundary);}\n  bool isTriangle() {return (!boundary);}\n  bool isBoundary() {return boundary;}\n  \n  vtx_t *firstVertex() {return t->vtx[o];}\n\n  bool inCirc(vtx_t *v) {\n    if (boundary || t == NULL) return 0;\n    return inCircle(t->vtx[0]->pt, t->vtx[1]->pt, \n\t\t    t->vtx[2]->pt, v->pt);\n  }\n\n  // the angle facing the across edge\n  double farAngle() {\n    return angle(t->vtx[mod3(o+1)]->pt,\n\t\t t->vtx[o]->pt,\n\t\t t->vtx[mod3(o+2)]->pt);\n  }\n\n  bool outside(vtx_t *v) {\n    if (boundary || t == NULL) return 0;\n    return counterClockwise(t->vtx[mod3(o+2)]->pt, v->pt, t->vtx[o]->pt);\n  }\n\n  // flips two triangles and adjusts neighboring triangles\n  void flip() { \n    simplex s = across();\n    int o1 = mod3(o+1);\n    int os1 = mod3(s.o+1);\n\n    tri_t *t1 = t->ngh[o1];\n    tri_t *t2 = s.t->ngh[os1];\n    vtx_t *v1 = t->vtx[o1];\n    vtx_t *v2 = s.t->vtx[os1];\n\n    t->vtx[o]->t = s.t;\n    t->vtx[o] = v2;\n    t->ngh[o] = t2;\n    if (t2 != NULL) t2->update(s.t,t);\n    t->ngh[o1] = s.t;\n\n    s.t->vtx[s.o]->t = t;\n    s.t->vtx[s.o] = v1;\n    s.t->ngh[s.o] = t1;\n    if (t1 != NULL) t1->update(t,s.t);\n    s.t->ngh[os1] = t;\n  }\n\n  // splits the triangle into three triangles with new vertex v in the middle\n  // updates all neighboring simplices\n  // ta0 and ta0 are pointers to the memory to use for the two new triangles\n  void split(vtx_t* v, tri_t* ta0, tri_t* ta1) {\n    v->t = t;\n    tri_t *t1 = t->ngh[0]; tri_t *t2 = t->ngh[1]; tri_t *t3 = t->ngh[2];\n    vtx_t *v1 = t->vtx[0]; vtx_t *v2 = t->vtx[1]; vtx_t *v3 = t->vtx[2];\n    t->ngh[1] = ta0;        t->ngh[2] = ta1;\n    t->vtx[1] = v;\n    ta0->setT(t2,ta1,t);  ta0->setV(v2,v,v1);\n    ta1->setT(t3,t,ta0);  ta1->setV(v3,v,v2);\n    if (t2 != NULL) t2->update(t,ta0);      \n    if (t3 != NULL) t3->update(t,ta1);\n    v2->t = ta0;\n  }\n\n  // splits one of the boundaries of a triangle to form two triangles\n  // the orientation dictates which edge to split (i.e., t.ngh[o])\n  // ta is a pointer to memory to use for the new triangle\n  void splitBoundary(vtx_t* v, tri_t* ta) {\n    int o1 = mod3(o+1);\n    int o2 = mod3(o+2);\n    if (t->ngh[o] != NULL) {\n      cout << \"simplex::splitBoundary: not boundary\" << endl; abort();}\n    v->t = t;\n    tri_t *t2 = t->ngh[o2];\n    vtx_t *v1 = t->vtx[o1]; vtx_t *v2 = t->vtx[o2];\n    t->ngh[o2] = ta;   t->vtx[o2] = v;\n    ta->setT(t2,NULL,t);  ta->setV(v2,v,v1);\n    if (t2 != NULL) t2->update(t,ta);      \n    v2->t = t;\n  }\n\n  // given a vtx v, extends a boundary edge (t.ngh[o]) with an extra \n  // triangle on that edge with apex v.  \n  // ta is used as the memory for the triangle\n  simplex extend(vtx_t* v, tri_t* ta) {\n    if (t->ngh[o] != NULL) {\n      cout << \"simplex::extend: not boundary\" << endl; abort();}\n    t->ngh[o] = ta;\n    ta->setV(t->vtx[o], t->vtx[mod3(o+2)], v);\n    ta->setT(NULL,t,NULL);\n    v->t = ta;\n    return simplex(ta,0);\n  }\n\n};\n\n// this might or might not be needed\n// void topologyFromTriangles(triangles<point2d> Tri, vtx** vr, tri** tr);\n\n#endif // _TOPOLOGY_INCLUDED\n"
  },
  {
    "path": "algorithms/bench/common/topology_from_triangles.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <iostream>\n#include <algorithm>\n#include \"../parlay/hash_table.h\"\n#include \"../parlay/primitives.h\"\n#include \"get_time.h\"\n#include \"atomics.h\"\n#include \"geometry.h\"\n#include \"topology.h\"\n\nusing parlay::parallel_for;\nusing parlay::hash64;\nusing parlay::sequence;\nusing parlay::tabulate;\nusing parlay::hashtable;\n\nusing std::pair;\nusing std::cout;\nusing std::endl;\nusing std::less;\n\nusing triang_t = triangle<point>;\nusing vertex_t = vertex<point>;\nusing simplex_t = simplex<point>;\nusing index_t = int;\nusing index_pair = pair<index_t,index_t>;\nusing edge = pair<index_pair, triang_t*>;\n\n// Hash table to store skinny triangles\nstruct hashEdges {\n  using kType = index_pair;\n  using eType = edge*;\n  eType empty() {return NULL;}\n  kType getKey(eType v) { return v->first;}\n  size_t hash(kType s) { return hash64(s.first)+3*(hash64(s.second)); }\n  int cmp(kType s1, kType s2) {\n    return ((s1.first > s2.first) ? 1 : \n\t    (s1.first < s2.first) ? -1 : \n\t    (s1.second > s2.second) ? 1 :\n\t    (s1.second < s2.second) ? -1 : 0);\n  }\n  bool cas(eType* p, eType o, eType n) {\n    return pbbs::atomic_compare_and_swap(p, o, n);\n  }\n  bool replaceQ(eType s, eType s2) {return 0;}\n};\n\nusing EdgeTable = hashtable<hashEdges>;\n\nEdgeTable makeEdgeTable(size_t m) {\n  return EdgeTable(m,hashEdges());}\n\nstd::pair<sequence<triang_t>,sequence<vertex_t>>\ntopology_from_triangles(triangles<point> &Tri, size_t extra_points = 0) {\n  size_t n = Tri.numPoints();\n  size_t m = Tri.numTriangles();\n\n  auto V = tabulate(n + extra_points, [&] (size_t i) {\n    return (i < n) ? vertex_t(Tri.P[i], i) : vertex_t();});\n\n  sequence<triang_t> Triangs(m + 2 * extra_points);\n  sequence<edge> E(m*3);\n  EdgeTable ET = makeEdgeTable(m*6);\n  parallel_for (0, m, [&] (size_t i) {\n    for (int j=0; j<3; j++) {\n      E[i*3 + j] = edge(index_pair(Tri.T[i][j], Tri.T[i][(j+1)%3]), &Triangs[i]);\n      ET.insert(&E[i*3+j]);\n      Triangs[i].vtx[(j+2)%3] = &V[Tri.T[i][j]];\n    }});\n\n  parallel_for (0, m, [&] (size_t i) {\n    Triangs[i].id = i;\n    Triangs[i].initialized = 1;\n    Triangs[i].bad = 0;\n    for (int j=0; j<3; j++) {\n      index_pair key = {Tri.T[i][(j+1)%3], Tri.T[i][j]};\n      edge *Ed = ET.find(key);\n      if (Ed != NULL) Triangs[i].ngh[j] = Ed->second;\n      else {\n\tTriangs[i].ngh[j] = NULL;\n\t//Triangs[i].vtx[j]->boundary = 1;\n\t//Triangs[i].vtx[(j+2)%3]->boundary = 1;\n      }\n    }\n  });\n  return std::pair(std::move(Triangs),std::move(V));\n}\n\n// Note that this is not currently a complete test of correctness\n// For example it would allow a set of disconnected triangles, or even no\n// triangles\nbool check_delaunay(sequence<triang_t> &Triangles, size_t boundary_size) {\n  size_t n = Triangles.size();\n  sequence<size_t> boundary_count(n, 0);\n  size_t insideOutError = n;\n  size_t inCircleError = n;\n  parallel_for (0, n, [&] (size_t i) {\n    if (Triangles[i].initialized) {\n      simplex_t t = simplex(&Triangles[i],0);\n      for (int j=0; j < 3; j++) {\n\tsimplex_t a = t.across();\n\tif (a.valid()) {\n\t  vertex_t* v = a.rotClockwise().firstVertex();\n\n          // Check that the neighbor is outside the triangle\n\t  if (!t.outside(v)) {\n\t    double vz = triAreaNormalized(t.t->vtx[(t.o+2)%3]->pt, \n\t\t\t\t\t  v->pt, t.t->vtx[t.o]->pt);\n\t    // allow for small error\n\t    if (vz < -1e-10) pbbs::write_min(&insideOutError, i, less<size_t>());\n\t  }\n\n          // Check that the neighbor is not in circumcircle of the triangle\n\t  if (t.inCirc(v)) {\n\t    double vz = inCircleNormalized(t.t->vtx[0]->pt, t.t->vtx[1]->pt, \n\t\t\t\t\t   t.t->vtx[2]->pt, v->pt);\n\t    // allow for small error\n\t    if (vz > 1e-10) pbbs::write_min(&inCircleError, i, less<size_t>());\n\t  }\n\t} else boundary_count[i]++;\n\tt = t.rotClockwise();\n      }\n    }\n  });\n  // if (boundary_size != reduce(boundary_count))\n  //   cout << \"Wrong boundary size: should be \" << boundary_size \n  // \t << \" is \" << reduce(boundary_count) << endl;\n\n  if (insideOutError < n) {\n    cout << \"delaunayCheck: neighbor inside triangle at triangle \" \n\t << inCircleError << endl;\n    return 1;\n  }\n  if (inCircleError < n) {\n    cout << \"In Circle Violation at triangle \" << inCircleError << endl;\n    return 1;\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "algorithms/bench/get_time.h",
    "content": "#pragma once\n\n#include <stdlib.h>\n#include <sys/time.h>\n#include <iomanip>\n#include <iostream>\n#include <string>\n\nnamespace cpam {\n\nstruct timer {\n  double total_time;\n  double last_time;\n  bool on;\n  std::string name;\n  struct timezone tzp;\n\n  timer(std::string name = \"PBBS time\", bool _start = true)\n  : total_time(0.0), on(false), name(name), tzp({0,0}) {\n    if (_start) start();\n  }\n\n  double get_time() {\n    timeval now;\n    gettimeofday(&now, &tzp);\n    return ((double) now.tv_sec) + ((double) now.tv_usec)/1000000.;\n  }\n\n  void start () {\n    on = 1;\n    last_time = get_time();\n  }\n\n  double stop () {\n    on = 0;\n    double d = (get_time()-last_time);\n    total_time += d;\n    return d;\n  }\n\n  void reset() {\n     total_time=0.0;\n     on=0;\n  }\n\n  double get_total() {\n    if (on) return total_time + get_time() - last_time;\n    else return total_time;\n  }\n\n  double get_next() {\n    if (!on) return 0.0;\n    double t = get_time();\n    double td = t - last_time;\n    total_time += td;\n    last_time = t;\n    return td;\n  }\n\n  void report(double time, std::string str) {\n    std::ios::fmtflags cout_settings = std::cout.flags();\n    std::cout.precision(4);\n    std::cout << std::fixed;\n    std::cout << name << \": \";\n    if (str.length() > 0)\n      std::cout << str << \": \";\n    std::cout << time << std::endl;\n    std::cout.flags(cout_settings);\n  }\n\n  void total() {\n    report(get_total(),\"total\");\n    total_time = 0.0;\n  }\n\n  void reportTotal(std::string str) {\n    report(get_total(), str);\n  }\n\n  void next(std::string str) {\n    if (on) report(get_next(), str);\n  }\n};\n\n}  // namespace cpam\n"
  },
  {
    "path": "algorithms/bench/neighborsTime.C",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <iostream>\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parse_command_line.h\"\n#include \"time_loop.h\"\n#include \"../utils/NSGDist.h\"\n#include \"../utils/euclidian_point.h\"\n#include \"../utils/point_range.h\"\n#include \"../utils/mips_point.h\"\n#include \"../utils/graph.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nusing namespace parlayANN;\n\n// *************************************************************\n//  TIMING\n// *************************************************************\n\nusing uint = unsigned int;\n\n\ntemplate<typename Point, typename PointRange, typename indexType>\nvoid timeNeighbors(Graph<indexType> &G,\n\t\t   PointRange &Query_Points, long k,\n\t\t   BuildParams &BP, char* outFile,\n\t\t   groundTruth<indexType> GT, char* res_file, bool graph_built, PointRange &Points)\n{\n\n\n    time_loop(1, 0,\n      [&] () {},\n      [&] () {\n        ANN<Point, PointRange, indexType>(G, k, BP, Query_Points, GT, res_file, graph_built, Points);\n      },\n      [&] () {});\n\n    if(outFile != NULL) {\n      G.save(outFile);\n    }\n\n\n}\n\nint main(int argc, char* argv[]) {\n    commandLine P(argc,argv,\n    \"[-a <alpha>] [-d <delta>] [-R <deg>]\"\n        \"[-L <bm>] [-k <k> ]  [-gt_path <g>] [-query_path <qF>]\"\n        \"[-graph_path <gF>] [-graph_outfile <oF>] [-res_path <rF>]\" \"[-num_passes <np>]\"\n        \"[-memory_flag <algoOpt>] [-mst_deg <q>] [-num_clusters <nc>] [-cluster_size <cs>]\"\n        \"[-data_type <tp>] [-dist_func <df>] [-base_path <b>] <inFile>\");\n\n  char* iFile = P.getOptionValue(\"-base_path\");\n  char* oFile = P.getOptionValue(\"-graph_outfile\");\n  char* gFile = P.getOptionValue(\"-graph_path\");\n  char* qFile = P.getOptionValue(\"-query_path\");\n  char* cFile = P.getOptionValue(\"-gt_path\");\n  char* rFile = P.getOptionValue(\"-res_path\");\n  char* vectype = P.getOptionValue(\"-data_type\");\n  long Q = P.getOptionIntValue(\"-Q\", 0);\n  long R = P.getOptionIntValue(\"-R\", 0);\n  if(R<0) P.badArgument();\n  long L = P.getOptionIntValue(\"-L\", 0);\n  if(L<0) P.badArgument();\n  long MST_deg = P.getOptionIntValue(\"-mst_deg\", 0);\n  if(MST_deg < 0) P.badArgument();\n  long num_clusters = P.getOptionIntValue(\"-num_clusters\", 0);\n  if(num_clusters<0) P.badArgument();\n  long cluster_size = P.getOptionIntValue(\"-cluster_size\", 0);\n  if(cluster_size<0) P.badArgument();\n  long k = P.getOptionIntValue(\"-k\", 0);\n  if (k > 1000 || k < 0) P.badArgument();\n  double alpha = P.getOptionDoubleValue(\"-alpha\", 1.0);\n  int num_passes = P.getOptionIntValue(\"-num_passes\", 1);\n  int two_pass = P.getOptionIntValue(\"-two_pass\", 0);\n  if(two_pass > 1 | two_pass < 0) P.badArgument();\n  if (two_pass == 1) num_passes = 2;\n  double delta = P.getOptionDoubleValue(\"-delta\", 0);\n  if(delta<0) P.badArgument();\n  char* dfc = P.getOptionValue(\"-dist_func\");\n  int quantize = P.getOptionIntValue(\"-quantize_bits\", 0);\n  int quantize_build = P.getOptionIntValue(\"-quantize_mode\", 0);\n  bool verbose = P.getOption(\"-verbose\");\n  bool graph_stats = P.getOption(\"-graph_stats\");\n  bool normalize = P.getOption(\"-normalize\");\n  double trim = P.getOptionDoubleValue(\"-trim\", 0.0); // not used\n  bool self = P.getOption(\"-self\");\n  int rerank_factor = P.getOptionIntValue(\"-rerank_factor\", 10);\n  bool range = P.getOption(\"-range\");\n  bool is_early_stop = P.getOption(\"-early_stop\");\n  char* sm = P.getOptionValue(\"-search_mode\");\n  double esr = P.getOptionDoubleValue(\"-early_stopping_radius\", 0);\n  double radius  = P.getOptionDoubleValue(\"-r\", 0.0);\n  double batch_factor = P.getOptionDoubleValue(\"-batch_factor\", .125);\n  \n  // this integer represents the number of random edges to start with for\n  // inserting in a single batch per round\n  int single_batch = P.getOptionIntValue(\"-single_batch\", 0);\n    \n  std::string df = std::string(dfc);\n  std::string tp = std::string(vectype);\n\n  std::string searchType = (sm == nullptr) ? \"\" : std::string(sm);\n  rangeQueryType rtype = Beam;\n\n  if (searchType == \"doubling\") {\n    rtype = Doubling;\n    std::cout << \"Using doubling range search\" << std::endl;\n  } else if (searchType == \"greedy\") {\n    rtype = Greedy;\n    std::cout << \"Using greedy range search\" << std::endl;\n  }\n  else if (searchType == \"beam\") {\n    rtype = Beam;\n    std::cout << \"Using beam range search\" << std::endl;\n  }\n  else rtype = None;\n  \n  BuildParams BP = BuildParams(R, L, alpha, num_passes,\n                               num_clusters, cluster_size, MST_deg, delta,\n                               verbose, quantize_build,\n                               self, single_batch,\n                               Q, trim,\n                               rerank_factor, batch_factor,\n                               is_early_stop, esr,\n                               rtype, radius, graph_stats);\n  long maxDeg = BP.max_degree();\n\n  if((tp != \"uint8\") && (tp != \"int8\") && (tp != \"float\")){\n    std::cout << \"Error: vector type not specified correctly, specify int8, uint8, or float\" << std::endl;\n    abort();\n  }\n\n  if(df != \"Euclidian\" && df != \"mips\"){\n    std::cout << \"Error: specify distance type Euclidian or mips\" << std::endl;\n    abort();\n  }\n\n  bool graph_built = (gFile != NULL);\n\n  groundTruth<uint> GT = groundTruth<uint>(cFile);\n  \n  if(tp == \"float\"){\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<float>> Points(iFile);\n      PointRange<Euclidian_Point<float>> Query_Points(qFile);\n      if (normalize) {\n        std::cout << \"normalizing data\" << std::endl;\n        for (int i=0; i < Points.size(); i++) \n          Points[i].normalize();\n        for (int i=0; i < Query_Points.size(); i++) \n          Query_Points[i].normalize();\n      }\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      if (quantize == 8) {\n        std::cout << \"quantizing data to 1 byte\" << std::endl;\n        using QT = uint8_t;\n        using QPoint = Euclidian_Point<QT>;\n        using PR = PointRange<QPoint>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeNeighbors<QPoint, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else if (quantize == 16) {\n        std::cout << \"quantizing data to 2 bytes\" << std::endl;\n        using Point = Euclidian_Point<uint16_t>;\n        using PR = PointRange<Point>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeNeighbors<Point, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else {\n        using Point = Euclidian_Point<float>;\n        using PR = PointRange<Point>;\n        timeNeighbors<Point, PR, uint>(G, Query_Points, k, BP, oFile, GT, rFile, graph_built, Points);\n      }\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<float>> Points(iFile);\n      PointRange<Mips_Point<float>> Query_Points(qFile);\n      if (normalize) {\n        std::cout << \"normalizing data\" << std::endl;\n        for (int i=0; i < Points.size(); i++) \n          Points[i].normalize();\n        for (int i=0; i < Query_Points.size(); i++) \n          Query_Points[i].normalize();\n      }\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      if (quantize == 8) {\n        std::cout << \"quantizing data to 1 byte\" << std::endl;\n        using QT = int8_t;\n        using Point = Quantized_Mips_Point<8>;\n        using PR = PointRange<Point>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeNeighbors<Point, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else if (quantize == 16) {\n        std::cout << \"quantizing data to 2 bytes\" << std::endl;\n        using QT = int16_t;\n        using Point = Quantized_Mips_Point<16>;\n        using PR = PointRange<Point>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeNeighbors<Point, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else {\n        using Point = Mips_Point<float>;\n        using PR = PointRange<Point>;\n        timeNeighbors<Point, PR, uint>(G, Query_Points, k, BP, oFile, GT, rFile, graph_built, Points);\n      }\n    }\n  } else if(tp == \"uint8\"){\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<uint8_t>> Points(iFile);\n      PointRange<Euclidian_Point<uint8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeNeighbors<Euclidian_Point<uint8_t>, PointRange<Euclidian_Point<uint8_t>>, uint>(G, Query_Points, k, BP, \n        oFile, GT, rFile, graph_built, Points);\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<uint8_t>> Points(iFile);\n      PointRange<Mips_Point<uint8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeNeighbors<Mips_Point<uint8_t>, PointRange<Mips_Point<uint8_t>>, uint>(G, Query_Points, k, BP, \n        oFile, GT, rFile, graph_built, Points);\n    }\n  } else if(tp == \"int8\"){\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<int8_t>> Points(iFile);\n      PointRange<Euclidian_Point<int8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeNeighbors<Euclidian_Point<int8_t>, PointRange<Euclidian_Point<int8_t>>, uint>(G, Query_Points, k, BP,\n        oFile, GT, rFile, graph_built, Points);\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<int8_t>> Points(iFile);\n      PointRange<Mips_Point<int8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeNeighbors<Mips_Point<int8_t>, PointRange<Mips_Point<int8_t>>, uint>(G, Query_Points, k, BP,\n        oFile, GT, rFile, graph_built, Points);\n    }\n  }\n  \n  return 0;\n}\n\n\n"
  },
  {
    "path": "algorithms/bench/parallelDefsANN",
    "content": "ifeq (, $(shell which jemalloc-config))\r\nJEMALLOC =\r\nelse\r\nJEMALLOCLD = $(shell jemalloc-config --libdir)\r\nJEMALLOC = -L$(JEMALLOCLD) -ljemalloc \r\nendif\r\n\r\nCCFLAGS = -mcx16 -O3 -std=c++17 -march=native -DNDEBUG -I .\r\nCLFLAGS = -ldl $(JEMALLOC)\r\n\r\nOMPFLAGS = -DPARLAY_OPENMP -fopenmp\r\nCILKFLAGS = -DPARLAY_CILK -fcilkplus\r\nPBBFLAGS = -DHOMEGROWN -pthread\r\n\r\nifdef OPENMP\r\nCC = g++\r\nCFLAGS = $(OMPFLAGS) $(CCFLAGS)\r\nLFLAGS = $(OMPFLAGS) $(CLFLAGS)\r\n\r\nelse ifdef CILK\r\nCC = g++\r\nCFLAGS = $(CILKFLAGS) $(CCFLAGS)\r\nLFLAGS = $(CILKFLAGS) $(CLFLAGS)\r\n\r\nelse\r\nCC = g++\r\nCFLAGS = $(PBBFLAGS) $(CCFLAGS)\r\nLFLAGS = $(PBBFLAGS) $(CLFLAGS)\r\nendif\r\n"
  },
  {
    "path": "algorithms/bench/parse_command_line.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <cstring>\n\nstruct commandLine {\n  int argc;\n  char** argv;\n  std::string comLine;\n  commandLine(int _c, char** _v, std::string _cl)\n    : argc(_c), argv(_v), comLine(_cl) {\n      if (getOption(\"-h\") || getOption(\"-help\"))\n\tbadArgument();\n    }\n\n  commandLine(int _c, char** _v)\n    : argc(_c), argv(_v), comLine(\"bad arguments\") { }\n\n  void badArgument() {\n    std::cout << \"usage: \" << argv[0] << \" \" << comLine << std::endl;\n    exit(0);\n  }\n\n  // get an argument\n  // i is indexed from the last argument = 0, second to last indexed 1, ..\n  char* getArgument(int i) {\n    if (argc < 2+i) badArgument();\n    return argv[argc-1-i];\n  }\n\n  // looks for two filenames\n  std::pair<char*,char*> IOFileNames() {\n    if (argc < 3) badArgument();\n    return std::pair<char*,char*>(argv[argc-2],argv[argc-1]);\n  }\n\n  std::pair<size_t,char*> sizeAndFileName() {\n    if (argc < 3) badArgument();\n    return std::pair<size_t,char*>(std::atoi(argv[argc-2]),(char*) argv[argc-1]);\n  }\n\n  bool getOption(std::string option) {\n    for (int i = 1; i < argc; i++)\n      if ((std::string) argv[i] == option) return true;\n    return false;\n  }\n\n  char* getOptionValue(std::string option) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) return argv[i+1];\n    return NULL;\n  }\n\n  std::string getOptionValue(std::string option, std::string defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) return (std::string) argv[i+1];\n    return defaultValue;\n  }\n\n  long getOptionLongValue(std::string option, long defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) {\n\tlong r = atol(argv[i+1]);\n\tif (r < 0) badArgument();\n\treturn r;\n      }\n    return defaultValue;\n  }\n\n  int getOptionIntValue(std::string option, int defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) {\n\tint r = atoi(argv[i+1]);\n\tif (r < 0) badArgument();\n\treturn r;\n      }\n    return defaultValue;\n  }\n\n  double getOptionDoubleValue(std::string option, double defaultValue) {\n    for (int i = 1; i < argc-1; i++)\n      if ((std::string) argv[i] == option) {\n\tdouble val;\n\tif (sscanf(argv[i+1], \"%lf\",  &val) == EOF) {\n\t  badArgument();\n\t}\n\treturn val;\n      }\n    return defaultValue;\n  }\n\n};\n\n"
  },
  {
    "path": "algorithms/bench/time_loop.h",
    "content": "#include \"get_time.h\"\n\n#ifndef TIMELOOP\n#define TIMELOOP\n\ntemplate<class F, class G, class H>\nvoid time_loop(int rounds, double delay, F initf, G runf, H endf) {\n  parlay::internal::timer t;\n  // run for delay seconds to \"warm things up\"\n  // will skip if delay is zero\n  while (t.total_time() < delay) {\n    initf(); runf(); endf();\n  } \n  for (int i=0; i < rounds; i++) {\n    initf();\n    t.start();\n    runf();\n    t.next(\"\");\n    endf();\n  }\n}\n\n#endif\n"
  },
  {
    "path": "algorithms/pyNNDescent/CMakeLists.txt",
    "content": "add_executable(neighbors-pynndescent ../bench/neighborsTime.C)\n  target_link_libraries(neighbors-pynndescent PRIVATE parlay)\n  target_precompile_headers(neighbors-pynndescent PRIVATE neighbors.h)\n\n"
  },
  {
    "path": "algorithms/pyNNDescent/Makefile",
    "content": "include ../bench/parallelDefsANN\n\nREQUIRE =  ../utils/beamSearch.h pynn_index.h ../utils/graph.h clusterPynn.h\nBENCH = neighbors\n\ninclude ../bench/MakeBench"
  },
  {
    "path": "algorithms/pyNNDescent/clusterPynn.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <math.h>\n\n#include <algorithm>\n#include <functional>\n#include <queue>\n#include <random>\n#include <set>\n\n#include \"../HCNNG/clusterEdge.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"../utils/union.h\"\n\nnamespace parlayANN {\n  \ntemplate<typename Point, typename PointRange, typename indexType>\nstruct clusterPID {\n  using distanceType = typename Point::distanceType;\n\tusing PR = PointRange;\n  using edge = std::pair<indexType , indexType >;\n  using pid = std::pair<indexType , distanceType>;\n\n  clusterPID() {}\n\n  parlay::sequence<parlay::sequence<pid>> intermediate_edges;\n\n  void naive_neighbors(PR &Points,\n                       parlay::sequence<size_t>& active_indices,\n                       long maxK) {\n    size_t n = active_indices.size();\n    parlay::parallel_for(0, n, [&](size_t i) {\n      auto less = [&](pid a, pid b) { return a.second < b.second; };\n      std::priority_queue<pid, std::vector<pid>, decltype(less)> Q(less);\n      size_t index = active_indices[i];\n      // tabulate all-pairs distances between the elements in the leaf\n      for (indexType  j = 0; j < n; j++) {\n        if (j != i) {\n          distanceType dist = Points[index].distance(Points[active_indices[j]]);\n          pid e = std::make_pair(active_indices[j], dist);\n          if (Q.size() >= maxK) {\n            distanceType topdist = Q.top().second;\n            if (dist < topdist) {\n              Q.pop();\n              Q.push(e);\n            }\n          } else {\n            Q.push(e);\n          }\n        }\n      }\n      size_t q = Q.size();\n      parlay::sequence<pid> sorted_edges(q);\n      for (indexType  j = 0; j < q; j++) {\n        sorted_edges[j] = Q.top();\n        Q.pop();\n      }\n      auto rev_edges = parlay::reverse(sorted_edges);\n      auto [new_best, changed] =\n          seq_union_bounded(intermediate_edges[index], rev_edges, maxK, less);\n      intermediate_edges[index] = new_best;\n    });\n  }\n\n\n  void random_clustering(PR &Points,\n                         parlay::sequence<size_t>& active_indices,\n                         parlay::random& rnd, long cluster_size,\n                         long K) {\n    if (active_indices.size() <= cluster_size)\n      naive_neighbors(Points, active_indices, K);\n    else {\n      auto [f, s] = select_two_random(active_indices, rnd);\n\n      auto left_rnd = rnd.fork(0);\n      auto right_rnd = rnd.fork(1);\n\n      if (Points[f]==Points[s]) {\n        parlay::sequence<size_t> closer_first;\n        parlay::sequence<size_t> closer_second;\n        for (indexType i = 0; i < active_indices.size(); i++) {\n          if (i < active_indices.size() / 2)\n            closer_first.push_back(active_indices[i]);\n          else\n            closer_second.push_back(active_indices[i]);\n        }\n        auto left_rnd = rnd.fork(0);\n        auto right_rnd = rnd.fork(1);\n        parlay::par_do(\n            [&]() {\n              random_clustering(Points, closer_first, left_rnd, cluster_size,\n                                K);\n            },\n            [&]() {\n              random_clustering(Points, closer_second, right_rnd, cluster_size,\n                                K);\n            });\n      } else {\n        // Split points based on which of the two points are closer.\n        auto closer_first =\n            parlay::filter(parlay::make_slice(active_indices), [&](size_t ind) {\n              distanceType dist_first = Points[ind].distance(Points[f]);\n              distanceType dist_second = Points[ind].distance(Points[s]);\n              return dist_first <= dist_second;\n            });\n\n        auto closer_second =\n            parlay::filter(parlay::make_slice(active_indices), [&](size_t ind) {\n              distanceType dist_first = Points[ind].distance(Points[f]);\n              distanceType dist_second = Points[ind].distance(Points[s]);\n              return dist_second < dist_first;\n            });\n\n\n        parlay::par_do(\n            [&]() {\n              random_clustering(Points, closer_first, left_rnd, cluster_size, \n                                K);\n            },\n            [&]() {\n              random_clustering(Points, closer_second, right_rnd, cluster_size,\n                                  K);\n        });\n\n      }\n    }\n  }\n\n  void random_clustering_wrapper(PR &Points,\n                                 long cluster_size, long K) {\n    std::random_device rd;\n    std::mt19937 rng(rd());\n    std::uniform_int_distribution<indexType> uni(0, Points.size());\n    parlay::random rnd(uni(rng));\n    auto active_indices =\n        parlay::tabulate(Points.size(), [&](size_t i) { return i; });\n    random_clustering(Points, active_indices, rnd, cluster_size, K);\n  }\n\n  void multiple_clustertrees(PR &Points,\n                             long cluster_size, long num_clusters,\n                             long K,\n                             parlay::sequence<parlay::sequence<pid>>& old_nbh) {\n    intermediate_edges = parlay::sequence<parlay::sequence<pid>>(Points.size());\n    for (long i = 0; i < num_clusters; i++) {\n      random_clustering_wrapper(Points, cluster_size, K);\n      std::cout << \"Cluster \" << i << std::endl; \n    }\n    parlay::parallel_for(0, Points.size(),\n                         [&](size_t i) { old_nbh[i] = intermediate_edges[i]; });\n  }\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/pyNNDescent/neighbors.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"../utils/NSGDist.h\"  \n#include \"../utils/types.h\"\n#include \"pynn_index.h\"\n#include \"../utils/beamSearch.h\"  \n#include \"../utils/stats.h\"\n#include \"../utils/parse_results.h\"\n#include \"../utils/check_nn_recall.h\"\n\nnamespace parlayANN {\n\ntemplate<typename Point, typename PointRange, typename indexType>\nvoid ANN(Graph<indexType> &G, long k, BuildParams &BP,\n         PointRange &Query_Points,\n         groundTruth<indexType> GT, char *res_file,\n         bool graph_built, PointRange &Points) {\n  parlay::internal::timer t(\"ANN\"); \n  {\n    using findex = pyNN_index<Point, PointRange, indexType>;\n    double idx_time;\n    long K = BP.R;\n    if(!graph_built){\n      findex I(K, BP.delta);\n      I.build_index(G, Points, BP.cluster_size, BP.num_clusters, BP.alpha);\n      idx_time = t.next_time();\n    }else {idx_time=0;}\n\n    std::string name = \"pyNNDescent\";\n    std::string params = \"K = \" + std::to_string(K);\n    auto [avg_deg, max_deg] = graph_stats_(G);\n    Graph_ G_(name, params, G.size(), avg_deg, max_deg, idx_time);\n    G_.print();\n    if(Query_Points.size() != 0)\n      search_and_parse(G_, G, Points, Query_Points, GT, res_file, k, BP.verbose);\n  };\n}\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/pyNNDescent/pynn_index.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"parlay/internal/get_time.h\"\n#include <random>\n#include <set>\n#include <queue>\n#include <math.h>\n#include \"clusterPynn.h\"\n\nnamespace parlayANN {\n\ntemplate<typename Point, typename PointRange, typename indexType>\nstruct pyNN_index{\n    using distanceType = typename Point::distanceType;\n\tusing GraphI = Graph<indexType>;\n\tusing PR = PointRange;\n    using edge = std::pair<indexType, indexType>;\n    using pid = std::pair<indexType,distanceType>;\n    using labelled_edge = std::pair<indexType, pid>;\n\n    long K;\n\tdouble delta;\n\n    static constexpr auto less = [] (edge a, edge b) {return a.second < b.second;};\n\n    pyNN_index(long md, double Delta) : K(md), delta(Delta) {}\n\n    parlay::sequence<parlay::sequence<pid>> old_neighbors;\n\n    void push_into_queue(std::priority_queue<edge, std::vector<edge>, decltype(less)> &Q, edge p){\n        if(Q.size() < 2*K){\n            Q.push(p);\n        } else{\n            indexType highest_p = Q.top().second;\n            if(p.second < highest_p){\n                Q.pop();\n                Q.push(p);\n            }\n        }\n    }\n\n    parlay::sequence<int> nn_descent(PR &Points, parlay::sequence<int> &changed){\n        auto new_changed = parlay::sequence<int>(Points.size(), 0);\n        auto rev = reverse_graph();\n        parlay::random_generator gen;\n        size_t n=Points.size();\n        std::uniform_int_distribution<indexType> dis(0, n-1);\n        int batch_size = 100000;\n        std::pair<indexType, parlay::sequence<indexType>> *begin;\n\t\tstd::pair<indexType, parlay::sequence<indexType>> *end = rev.begin();\n\t\tint counter = 0;\n\t\twhile(end != rev.end()){\n\t\t\tcounter++;\n\t\t\tbegin = end;\n\t\t\tint remaining = rev.end() - end;\n\t\t\tend += std::min(remaining, batch_size);\n\t\t\tnn_descent_chunk(Points, changed, new_changed, begin, end);\n\t\t}\n        return new_changed;\n    }\n\n    void nn_descent_chunk(PR &Points, parlay::sequence<int> &changed, \n\t\tparlay::sequence<int> &new_changed, std::pair<indexType, parlay::sequence<indexType>> *begin, \n\t\tstd::pair<indexType, parlay::sequence<indexType>> *end){\n        size_t stride = end - begin;\n\tauto less = [&] (pid a, pid b) {return a.second < b.second;};\n\tauto grouped_labelled = parlay::tabulate(stride, [&] (size_t i){\n            indexType index = (begin+i)->first;\n            std::set<indexType> to_filter;\n            to_filter.insert(index);\n            for(indexType j=0; j<old_neighbors[index].size(); j++){\n                to_filter.insert(old_neighbors[index][j].first);\n            }\n            auto f = [&] (indexType a) {return (to_filter.find(a) == to_filter.end());};\n            auto filtered_candidates = parlay::filter((begin+i)->second, f);\n\t    parlay::sequence<labelled_edge> edges;\n\t    edges.reserve(K*2);\n\t    for(indexType l=0; l<filtered_candidates.size(); l++){\n                indexType j=filtered_candidates[l];\n\t\tdistanceType j_max = old_neighbors[j][old_neighbors[j].size()-1].second;\n\t\tfor(indexType m=l+1; m<filtered_candidates.size(); m++){\n                    indexType k=filtered_candidates[m];\n\t\t    if (changed[j] || changed[k]) {\n              distanceType dist = Points[j].distance(Points[k]);\n\t\t      distanceType k_max = old_neighbors[k][old_neighbors[k].size()-1].second;\n\t\t      if(dist < j_max) edges.push_back(std::make_pair(j, std::make_pair(k, dist)));\n\t\t      if(dist < k_max) edges.push_back(std::make_pair(k, std::make_pair(j, dist)));\n\t\t    }\n\t\t}\n\t    }\n            for(indexType l=0; l<old_neighbors[index].size(); l++){\n                indexType j = old_neighbors[index][l].first;\n                for(const indexType& k : filtered_candidates){\n\t\t  if (changed[index] || changed[k]) {\n                    distanceType dist = Points[j].distance(Points[k]);\n                    distanceType j_max = old_neighbors[j][old_neighbors[j].size()-1].second;\n                    distanceType k_max = old_neighbors[k][old_neighbors[k].size()-1].second;\n                    if(dist < j_max) edges.push_back(std::make_pair(j, std::make_pair(k, dist)));\n                    if(dist < k_max) edges.push_back(std::make_pair(k, std::make_pair(j, dist)));\n\t\t  }\n                }\n            }\n\t\t\treturn edges;\n\t\t\t\t\t\t\t\t }, 1);\n\t\tauto candidates = parlay::group_by_key(parlay::flatten(grouped_labelled));\n        parlay::parallel_for(0, candidates.size(), [&] (size_t i){\n            auto less2 = [&] (pid a, pid b) {\n                if(a.second < b.second) return true;\n                else if(a.second == b.second){\n                    if(a.first < b.first) return true;\n                }\n                return false;\n            };\n            parlay::sort_inplace(candidates[i].second, less2);\n            indexType cur_index=std::numeric_limits<unsigned int>::max();\n            parlay::sequence<pid> filtered_candidates;\n            for(const pid& p : candidates[i].second){\n                if(p.first!=cur_index){\n                    filtered_candidates.push_back(p);\n                    cur_index = p.first;\n                }\n            }\n            indexType index = candidates[i].first;\n            auto less3 = [&] (pid a, pid b) {return a.second < b.second;};\n            auto [new_edges, change] = seq_union_bounded(old_neighbors[index], filtered_candidates, K, less3);\n            if(change){\n                new_changed[index]=1;\n                old_neighbors[index]=new_edges;\n            }\n        });\n    }\n\n    parlay::sequence<std::pair<indexType, parlay::sequence<indexType>>> reverse_graph(){\n        parlay::sequence<parlay::sequence<edge>> to_group = parlay::tabulate(old_neighbors.size(), [&] (size_t i){\n            size_t s = old_neighbors[i].size();\n            parlay::sequence<edge> e(s);\n            for(indexType j=0; j<s; j++){\n                e[j] = std::make_pair(old_neighbors[i][j].first, (int) i);\n            }\n            return e; \n        });\n        auto sorted_graph =  parlay::group_by_key(parlay::flatten(to_group));\n        parlay::parallel_for(0, sorted_graph.size(), [&] (size_t i){\n            auto shuffled = parlay::remove_duplicates(parlay::random_shuffle(sorted_graph[i].second, i));\n            indexType upper_bound = std::min((long) shuffled.size(), K);\n            auto truncated = parlay::tabulate(upper_bound, [&] (size_t j){\n                return shuffled[j];\n            });\n            sorted_graph[i].second = truncated;\n        });\n        return sorted_graph;\n    }\n\n    int nn_descent_wrapper(PR &Points){\n\t\tsize_t n = Points.size();\n\t\tparlay::sequence<int> changed = parlay::tabulate(n, [&] (size_t i) {return 1;});\n\t\tint rounds = 0;\n        int max_rounds = std::max(10, (int) log2(Points.dimension()));\n        if(Points.dimension()==256) max_rounds=20; //hack for ssnpp\n\t\twhile(parlay::reduce(changed) >= delta*n && rounds < max_rounds){\n\t\t\tauto new_changed = nn_descent(Points, changed);\n\t\t\tchanged = new_changed;\n\t\t\trounds++;\n            std::cout << parlay::reduce(new_changed) << \" elements changed\" << std::endl;\n\t\t\tstd::cout << \"Round \" << rounds << \" of \" <<  max_rounds << \" completed\" << std::endl; \n\t\t}\n\n\t\tstd::cout << \"descent converged in \" << rounds << \" rounds\";\n        if(rounds < max_rounds) std::cout << \" (Early termination)\";\n        std::cout << std::endl;\n\t\treturn rounds;\n\t}\n\n    void undirect_and_prune(GraphI &G, PR &Points, double alpha){\n        parlay::sequence<parlay::sequence<edge>> to_group = parlay::tabulate(old_neighbors.size(), [&] (size_t i){\n            size_t s = old_neighbors[i].size();\n            assert(s == K);\n            parlay::sequence<edge> e(s);\n            for(indexType j=0; j<s; j++){\n                e[j] = std::make_pair(old_neighbors[i][j].first, (int) i);\n            }\n            return e; \n        });\n        auto undirected_graph = parlay::group_by_key_ordered(parlay::flatten(to_group));\n        parlay::parallel_for(0, undirected_graph.size(), [&] (size_t i){\n            indexType index = undirected_graph[i].first;\n            auto filtered = parlay::remove_duplicates(undirected_graph[i].second);\n            auto undirected_pids = parlay::tabulate(filtered.size(), [&] (size_t j){\n                indexType indexU = filtered[j];\n                distanceType dist = Points[index].distance(Points[indexU]);\n                return std::make_pair(indexU, dist);\n            });\n            parlay::sort_inplace(undirected_pids, less);\n            auto less3 = [&] (pid a, pid b) {return a.second < b.second;};\n            auto merged_pids = seq_union(old_neighbors[index], undirected_pids, less3);\n            old_neighbors[index] = merged_pids;\n        });\n        parlay::parallel_for(0, G.size(), [&] (size_t i){\n            parlay::sequence<indexType> new_out = parlay::sequence<indexType>();\n\t\t\tfor(const pid& j : old_neighbors[i]){\n\t\t\t\tif(new_out.size() == K) break;\n\t\t\t\telse if(new_out.size() == 0) new_out.push_back(j.first);\n\t\t\t\telse{\n\t\t\t\t\tdistanceType dist_p = j.second;\n\t\t\t\t\tbool add = true;\n\t\t\t\t\tfor(const indexType& k : new_out){\n                        distanceType dist = Points[j.first].distance(Points[k]);\n\t\t\t\t\t\tif(dist_p > alpha*dist) {add = false; break;}\n\t\t\t\t\t}\n\t\t\t\t\tif(add) new_out.push_back(j.first);\n\t\t\t\t}\n\t\t\t}\n            G[i].update_neighbors(new_out);\n        });\n    }\n\n\n    void build_index(GraphI &G, PR &Points, long cluster_size, long num_clusters, double alpha){\n\t\tclusterPID<Point, PointRange, indexType> C;\n        old_neighbors = parlay::sequence<parlay::sequence<pid>>(G.size());\n\t\tC.multiple_clustertrees(Points, cluster_size, num_clusters, K, old_neighbors);\n\t\tnn_descent_wrapper(Points);\n\t\tundirect_and_prune(G, Points, alpha);\n\t}\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/pyNNDescent/scripts/nytimes",
    "content": "# bash\nBUILD_ARGS=\"-R 40 -cluster_size 100 -num_clusters 10 -alpha 1.2 -delta 0.05 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -verbose\"\nTYPE_ARGS=\"-data_type float -dist_func mips -normalize -file_type bin\"\n\nPATH=data/nytimes-256-angular\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_pynn_40_100\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n\n"
  },
  {
    "path": "algorithms/pyNNDescent/scripts/sift",
    "content": "# bash\nBUILD_ARGS=\"-R 40 -cluster_size 100 -num_clusters 10 -alpha 1.2 -delta 0.05 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 8 -verbose\"\nTYPE_ARGS=\"-data_type float -dist_func Euclidian -file_type bin\"\n\nPATH=data/sift-128-euclidean\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_pynn_40_100\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/tutorial.sh",
    "content": "#!/bin/bash\n\ncd vamana\nmake\necho \"Vamana:\"\n./neighbors -R 32 -L 64 -a 1.2 -graph_outfile ../../data/sift/sift_learn_32_64 -query_path ../../data/sift/sift_query.fvecs -gt_path ../../data/sift/sift-100K -res_path tutorial.csv -data_type float -file_type vec -dist_func Euclidian -base_path ../../data/sift/sift_learn.fvecs\n\necho \"\" \necho \"\" \n\ncd ../HCNNG\nmake\necho \"HCNNG:\"\n./neighbors -R 3 -L 10 -a 1000 -memory_flag 1 -graph_outfile ../../data/sift/sift_learn_3_10 -query_path ../../data/sift/sift_query.fvecs -gt_path ../../data/sift/sift-100K -res_path tutorial.csv -data_type float -file_type vec -dist_func Euclidian -base_path ../../data/sift/sift_learn.fvecs\n\necho \"\"\necho \"\"\n\ncd ../pyNNDescent\nmake\necho \"pyNNDescent:\"\n./neighbors -R 30 -L 100 -a 10 -d 1.2 -graph_outfile ../../data/sift/sift_learn_30 -query_path ../../data/sift/sift_query.fvecs -gt_path ../../data/sift/sift-100K -res_path tutorial.csv -data_type float -file_type vec -dist_func Euclidian -base_path ../../data/sift/sift_learn.fvecs"
  },
  {
    "path": "algorithms/utils/BUILD",
    "content": "# ANNS utilility.\n\npackage(default_visibility = [\"//algorithms:__subpackages__\"])\n\n\ncc_library(\n    name = \"csvfile\",\n    hdrs = [\"csvfile.h\"],\n)\n\ncc_library(\n    name = \"beamSearch\",\n    hdrs = [\"beamSearch.h\"],\n    deps = [\n        \"@parlaylib//parlay:io\",\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay:random\",\n        \":graph\",\n        \":stats\",\n        \":types\",\n    ],\n)\n\ncc_library(\n    name = \"check_range_recall\",\n    hdrs = [\"check_nn_recall.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \":beamSearch\",\n        \":csvfile\",\n        \":parse_results\",\n        \":stats\",\n        \":types\",\n    ],\n)\n\ncc_library(\n    name = \"euclidean_point\",\n    hdrs = [\"euclidian_point.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay/internal:file_map\",\n        \":parse_results\",\n        \":types\",\n    ],\n)\n\ncc_library(\n    name = \"graph\",\n    hdrs = [\"graph.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay/internal:file_map\",\n        \":parse_results\",\n        \":types\",\n    ],\n)\n\ncc_library(\n    name = \"mips_point\",\n    hdrs = [\"mips_point.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay/internal:file_map\",\n        \":types\",\n    ],\n)\n\ncc_library(\n    name = \"mmap\",\n    hdrs = [\"mmap.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay/internal:file_map\",\n    ],\n)\n\ncc_library(\n    name = \"parse_results\",\n    hdrs = [\"parse_results.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n    ],\n)\n\ncc_library(\n    name = \"check_nn_recall\",\n    hdrs = [\"check_nn_recall.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \":beamSearch\",\n        \":csvfile\",\n        \":parse_results\",\n        \":stats\",\n        \":types\",\n    ],\n)\n\ncc_library(\n    name = \"point_range\",\n    hdrs = [\"point_range.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay/internal:file_map\",\n        \":types\",\n    ],\n)\n\ncc_library(\n    name = \"stats\",\n    hdrs = [\"stats.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \":graph\",\n    ],\n)\n\ncc_library(\n    name = \"types\",\n    hdrs = [\"types.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \":mmap\",\n    ],\n)\n\n\ncc_library(\n    name = \"union\",\n    hdrs = [\"union.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n    ],\n)\n\ncc_library(\n    name = \"jl_point\",\n    hdrs = [\"jl_point.h\"],\n    deps = [\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay/internal:file_map\",\n        \":types\",\n        \":mips_point\",\n    ],\n)\n\n"
  },
  {
    "path": "algorithms/utils/NSGDist.h",
    "content": "//\n// Created by 付聪 on 2017/6/21.\n//\n\n#ifndef EFANNA2E_DISTANCE_H\n#define EFANNA2E_DISTANCE_H\n\n#include <math.h>\n#include <x86intrin.h>\n\n#include <algorithm>\n#include <iostream>\n#include <type_traits>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n\n\nnamespace efanna2e {\n\n// atomic_sum_counter<size_t> distance_calls;\n\nenum Metric { L2 = 0, INNER_PRODUCT = 1, FAST_L2 = 2, PQ = 3 };\nclass Distance {\n public:\n  virtual float compare(const float *a, const float *b,\n                        unsigned length) const = 0;\n  virtual ~Distance() {}\n};\n\nclass DistanceL2 : public Distance {\n public:\n  float compare(const float *a, const float *b, unsigned size) const {\n    float result = 0;\n\n#ifdef __GNUC__\n#ifdef __AVX__\n\n#define AVX_L2SQR(addr1, addr2, dest, tmp1, tmp2) \\\n  tmp1 = _mm256_loadu_ps(addr1);                  \\\n  tmp2 = _mm256_loadu_ps(addr2);                  \\\n  tmp1 = _mm256_sub_ps(tmp1, tmp2);               \\\n  tmp1 = _mm256_mul_ps(tmp1, tmp1);               \\\n  dest = _mm256_add_ps(dest, tmp1);\n\n    __m256 sum;\n    __m256 l0, l1;\n    __m256 r0, r1;\n    size_t qty16 = size >> 4;\n    size_t aligned_size = qty16 << 4;\n    const float *l = a;\n    const float *r = b;\n\n    float unpack[8] __attribute__((aligned(32))) = {0, 0, 0, 0, 0, 0, 0, 0};\n    sum = _mm256_loadu_ps(unpack);\n\n    for (unsigned i = 0; i < aligned_size; i += 16, l += 16, r += 16) {\n      AVX_L2SQR(l, r, sum, l0, r0);\n      AVX_L2SQR(l + 8, r + 8, sum, l1, r1);\n    }\n    _mm256_storeu_ps(unpack, sum);\n    result = unpack[0] + unpack[1] + unpack[2] + unpack[3] + unpack[4] + unpack[5] + unpack[6] + unpack[7];\n    for (unsigned i = aligned_size; i < size; ++i, ++l, ++r) {\n      float diff = *l - *r;\n      result += diff * diff;\n    }\n    \n    /*\n#else\n#ifdef __SSE2__\n#define SSE_L2SQR(addr1, addr2, dest, tmp1, tmp2) \\\n        tmp1 = _mm_load_ps(addr1);\\\n        tmp2 = _mm_load_ps(addr2);\\\n        tmp1 = _mm_sub_ps(tmp1, tmp2); \\\n        tmp1 = _mm_mul_ps(tmp1, tmp1); \\\n        dest = _mm_add_ps(dest, tmp1);\n\n__m128 sum;\n__m128 l0, l1, l2, l3;\n__m128 r0, r1, r2, r3;\nunsigned D = (size + 3) & ~3U;\nunsigned DR = D % 16;\nunsigned DD = D - DR;\nconst float *l = a;\nconst float *r = b;\nconst float *e_l = l + DD;\nconst float *e_r = r + DD;\nfloat unpack[4] __attribute__ ((aligned (16))) = {0, 0, 0, 0};\n\nsum = _mm_load_ps(unpack);\nswitch (DR) {\n    case 12:\n    SSE_L2SQR(e_l+8, e_r+8, sum, l2, r2);\n    case 8:\n    SSE_L2SQR(e_l+4, e_r+4, sum, l1, r1);\n    case 4:\n    SSE_L2SQR(e_l, e_r, sum, l0, r0);\n  default:\n    break;\n}\nfor (unsigned i = 0; i < DD; i += 16, l += 16, r += 16) {\n    SSE_L2SQR(l, r, sum, l0, r0);\n    SSE_L2SQR(l + 4, r + 4, sum, l1, r1);\n    SSE_L2SQR(l + 8, r + 8, sum, l2, r2);\n    SSE_L2SQR(l + 12, r + 12, sum, l3, r3);\n}\n_mm_storeu_ps(unpack, sum);\nresult += unpack[0] + unpack[1] + unpack[2] + unpack[3];\n*/\n// normal distance\n#else\n\n    float diff0, diff1, diff2, diff3;\n    const float *last = a + size;\n    const float *unroll_group = last - 3;\n\n    /* Process 4 items with each loop for efficiency. */\n    while (a < unroll_group) {\n      diff0 = a[0] - b[0];\n      diff1 = a[1] - b[1];\n      diff2 = a[2] - b[2];\n      diff3 = a[3] - b[3];\n      result += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;\n      a += 4;\n      b += 4;\n    }\n    /* Process last 0-3 pixels.  Not needed for standard vector lengths. */\n    while (a < last) {\n      diff0 = *a++ - *b++;\n      result += diff0 * diff0;\n    }\n// #endif\n#endif\n#endif\n\n    return result;\n  }\n};\n\nclass DistanceInnerProduct : public Distance {\n public:\n  float compare(const float *a, const float *b, unsigned size) const {\n    float result = 0;\n#ifdef __GNUC__\n#ifdef __AVX__\n#define AVX_DOT(addr1, addr2, dest, tmp1, tmp2) \\\n  tmp1 = _mm256_loadu_ps(addr1);                \\\n  tmp2 = _mm256_loadu_ps(addr2);                \\\n  tmp1 = _mm256_mul_ps(tmp1, tmp2);             \\\n  dest = _mm256_add_ps(dest, tmp1);\n\n    __m256 sum;\n    __m256 l0, l1;\n    __m256 r0, r1;\n    unsigned D = (size + 7) & ~7U;\n    unsigned DR = D % 16;\n    unsigned DD = D - DR;\n    const float *l = a;\n    const float *r = b;\n    const float *e_l = l + DD;\n    const float *e_r = r + DD;\n    float unpack[8] __attribute__((aligned(32))) = {0, 0, 0, 0, 0, 0, 0, 0};\n\n    sum = _mm256_loadu_ps(unpack);\n    if (DR) {\n      AVX_DOT(e_l, e_r, sum, l0, r0);\n    }\n\n    for (unsigned i = 0; i < DD; i += 16, l += 16, r += 16) {\n      AVX_DOT(l, r, sum, l0, r0);\n      AVX_DOT(l + 8, r + 8, sum, l1, r1);\n    }\n    _mm256_storeu_ps(unpack, sum);\n    result = unpack[0] + unpack[1] + unpack[2] + unpack[3] + unpack[4] +\n             unpack[5] + unpack[6] + unpack[7];\n/*\n#else\n#ifdef __SSE2__\n      #define SSE_DOT(addr1, addr2, dest, tmp1, tmp2) \\\n          tmp1 = _mm128_loadu_ps(addr1);\\\n          tmp2 = _mm128_loadu_ps(addr2);\\\n          tmp1 = _mm128_mul_ps(tmp1, tmp2); \\\n          dest = _mm128_add_ps(dest, tmp1);\n      __m128 sum;\n      __m128 l0, l1, l2, l3;\n      __m128 r0, r1, r2, r3;\n      unsigned D = (size + 3) & ~3U;\n      unsigned DR = D % 16;\n      unsigned DD = D - DR;\n      const float *l = a;\n      const float *r = b;\n      const float *e_l = l + DD;\n      const float *e_r = r + DD;\n      float unpack[4] __attribute__ ((aligned (16))) = {0, 0, 0, 0};\n\n      sum = _mm_load_ps(unpack);\n      switch (DR) {\n          case 12:\n          SSE_DOT(e_l+8, e_r+8, sum, l2, r2);\n          case 8:\n          SSE_DOT(e_l+4, e_r+4, sum, l1, r1);\n          case 4:\n          SSE_DOT(e_l, e_r, sum, l0, r0);\n        default:\n          break;\n      }\n      for (unsigned i = 0; i < DD; i += 16, l += 16, r += 16) {\n          SSE_DOT(l, r, sum, l0, r0);\n          SSE_DOT(l + 4, r + 4, sum, l1, r1);\n          SSE_DOT(l + 8, r + 8, sum, l2, r2);\n          SSE_DOT(l + 12, r + 12, sum, l3, r3);\n      }\n      _mm_storeu_ps(unpack, sum);\n      result += unpack[0] + unpack[1] + unpack[2] + unpack[3];\n*/\n#else\n\n    float dot0, dot1, dot2, dot3;\n    const float *last = a + size;\n    const float *unroll_group = last - 3;\n\n    /* Process 4 items with each loop for efficiency. */\n    while (a < unroll_group) {\n      dot0 = a[0] * b[0];\n      dot1 = a[1] * b[1];\n      dot2 = a[2] * b[2];\n      dot3 = a[3] * b[3];\n      result += dot0 + dot1 + dot2 + dot3;\n      a += 4;\n      b += 4;\n    }\n    /* Process last 0-3 pixels.  Not needed for standard vector lengths. */\n    while (a < last) {\n      result += *a++ * *b++;\n    }\n// #endif\n#endif\n#endif\n    return result;\n  }\n};\nclass DistanceFastL2 : public DistanceInnerProduct {\n public:\n  float norm(const float *a, unsigned size) const {\n    float result = 0;\n#ifdef __GNUC__\n#ifdef __AVX__\n#define AVX_L2NORM(addr, dest, tmp) \\\n  tmp = _mm256_loadu_ps(addr);      \\\n  tmp = _mm256_mul_ps(tmp, tmp);    \\\n  dest = _mm256_add_ps(dest, tmp);\n\n    __m256 sum;\n    __m256 l0, l1;\n    unsigned D = (size + 7) & ~7U;\n    unsigned DR = D % 16;\n    unsigned DD = D - DR;\n    const float *l = a;\n    const float *e_l = l + DD;\n    float unpack[8] __attribute__((aligned(32))) = {0, 0, 0, 0, 0, 0, 0, 0};\n\n    sum = _mm256_loadu_ps(unpack);\n    if (DR) {\n      AVX_L2NORM(e_l, sum, l0);\n    }\n    for (unsigned i = 0; i < DD; i += 16, l += 16) {\n      AVX_L2NORM(l, sum, l0);\n      AVX_L2NORM(l + 8, sum, l1);\n    }\n    _mm256_storeu_ps(unpack, sum);\n    result = unpack[0] + unpack[1] + unpack[2] + unpack[3] + unpack[4] +\n             unpack[5] + unpack[6] + unpack[7];\n/*\n#else\n#ifdef __SSE2__\n#define SSE_L2NORM(addr, dest, tmp) \\\n    tmp = _mm128_loadu_ps(addr); \\\n    tmp = _mm128_mul_ps(tmp, tmp); \\\n    dest = _mm128_add_ps(dest, tmp);\n\n    __m128 sum;\n    __m128 l0, l1, l2, l3;\n    unsigned D = (size + 3) & ~3U;\n    unsigned DR = D % 16;\n    unsigned DD = D - DR;\n    const float *l = a;\n    const float *e_l = l + DD;\n    float unpack[4] __attribute__ ((aligned (16))) = {0, 0, 0, 0};\n\n    sum = _mm_load_ps(unpack);\n    switch (DR) {\n        case 12:\n        SSE_L2NORM(e_l+8, sum, l2);\n        case 8:\n        SSE_L2NORM(e_l+4, sum, l1);\n        case 4:\n        SSE_L2NORM(e_l, sum, l0);\n      default:\n        break;\n    }\n    for (unsigned i = 0; i < DD; i += 16, l += 16) {\n        SSE_L2NORM(l, sum, l0);\n        SSE_L2NORM(l + 4, sum, l1);\n        SSE_L2NORM(l + 8, sum, l2);\n        SSE_L2NORM(l + 12, sum, l3);\n    }\n    _mm_storeu_ps(unpack, sum);\n    result += unpack[0] + unpack[1] + unpack[2] + unpack[3];\n*/\n#else\n    float dot0, dot1, dot2, dot3;\n    const float *last = a + size;\n    const float *unroll_group = last - 3;\n\n    /* Process 4 items with each loop for efficiency. */\n    while (a < unroll_group) {\n      dot0 = a[0] * a[0];\n      dot1 = a[1] * a[1];\n      dot2 = a[2] * a[2];\n      dot3 = a[3] * a[3];\n      result += dot0 + dot1 + dot2 + dot3;\n      a += 4;\n    }\n    /* Process last 0-3 pixels.  Not needed for standard vector lengths. */\n    while (a < last) {\n      result += (*a) * (*a);\n      a++;\n    }\n// #endif\n#endif\n#endif\n    return result;\n  }\n  using DistanceInnerProduct::compare;\n  float compare(const float *a, const float *b, float norm,\n                unsigned size) const {  // not implement\n    float result = -2 * DistanceInnerProduct::compare(a, b, size);\n    result += norm;\n    return result;\n  }\n};\n}  // namespace efanna2e\n\n\n\n#endif  // EFANNA2E_DISTANCE_H\n"
  },
  {
    "path": "algorithms/utils/beamSearch.h",
    "content": "#ifndef ALGORITHMS_ANN_BEAM_SEARCH_H_\n#define ALGORITHMS_ANN_BEAM_SEARCH_H_\n\n#include <algorithm>\n#include <functional>\n#include <limits>\n#include <random>\n#include <set>\n#include <unordered_set>\n#include <queue>\n\n#include \"parlay/io.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"types.h\"\n#include \"graph.h\"\n#include \"stats.h\"\n#include \"hashset.h\"\n\nnamespace parlayANN {\n\n  struct EarlyStopping {\n  template<typename PointInfo>\n  bool operator () (const PointInfo& frontier, \n                    const PointInfo& unvisited_frontier,\n                    const PointInfo& visited,\n                    const QueryParams& QP) { return false;}\n  };\n\n  \n  // main beam search\ntemplate<typename indexType, typename Point, typename PointRange,\n         typename QPoint, typename QPointRange, typename GT, typename ES = EarlyStopping>\nstd::pair<std::pair<parlay::sequence<std::pair<indexType, typename Point::distanceType>>,\n                    parlay::sequence<std::pair<indexType, typename Point::distanceType>>>,\n          size_t>\nfiltered_beam_search(const GT &G,\n                     const Point p,  const PointRange &Points,\n                     const QPoint qp, const QPointRange &Q_Points,\n                     const parlay::sequence<indexType> starting_points,\n                     const QueryParams &QP,\n                     bool use_filtering = false,\n                     ES early_stop = ES{}\n                     ) {\n  using dtype = typename Point::distanceType;\n  using id_dist = std::pair<indexType, dtype>;\n  int beamSize = QP.beamSize;\n  int max_degree = QP.degree_limit;\n\n  if (starting_points.size() == 0) {\n    std::cout << \"beam search expects at least one start point\" << std::endl;\n    abort();\n  } else if (starting_points.size() > beamSize) {\n    std::cout << \"beam search has more starting points than beam size\" << std::endl;\n    abort();\n  }\n\n  // compare two (node_id,distance) pairs, first by distance and then id if\n  // equal\n  using distanceType = typename Point::distanceType;\n  auto less = [&](id_dist a, id_dist b) {\n    return a.second < b.second || (a.second == b.second && a.first < b.first);\n  };\n\n  hashset<indexType> has_been_seen(2 * (10 + beamSize) * max_degree);\n  \n  // Frontier maintains the closest points found so far and its size\n  // is always at most beamSize.  Each entry is a (id,distance) pair.\n  // Initialized with starting points and kept sorted by distance.\n  std::vector<id_dist> frontier;\n  frontier.reserve(beamSize);\n  for (auto q : starting_points) {\n    frontier.push_back(id_dist(q, Points[q].distance(p)));\n    has_been_seen(q);\n  }\n  std::sort(frontier.begin(), frontier.end(), less);\n\n  // The subset of the frontier that has not been visited\n  // Use the first of these to pick next vertex to visit.\n  std::vector<id_dist> unvisited_frontier(beamSize);\n  for (int i=0; i < frontier.size(); i++)\n    unvisited_frontier[i] = frontier[i];\n\n  // maintains sorted set of visited vertices (id-distance pairs)\n  std::vector<id_dist> visited;\n  visited.reserve(2 * beamSize);\n\n  // counters\n  size_t dist_cmps = starting_points.size();\n  size_t full_dist_cmps = starting_points.size();\n  int remain = frontier.size();\n  int num_visited = 0;\n\n  // used as temporaries in the loop\n  std::vector<id_dist> new_frontier(2 * std::max<size_t>(beamSize,starting_points.size()) +\n                                    G.max_degree());\n  std::vector<id_dist> candidates;\n  candidates.reserve(G.max_degree() + beamSize);\n  std::vector<indexType> filtered;\n  filtered.reserve(G.max_degree());\n  std::vector<indexType> pruned;\n  pruned.reserve(G.max_degree());\n\n  dtype filter_threshold_sum = 0.0;\n  int filter_threshold_count = 0;\n  dtype filter_threshold;\n  indexType filter_id;\n  indexType filter_tail_mean = 0;\n\n  // offset into the unvisited_frontier vector (unvisited_frontier[offset] is the next to visit)\n  int offset = 0;\n\n  // The main loop.  Terminate beam search when the entire frontier\n  // has been visited or have reached max_visit.\n  while (remain > offset && num_visited < QP.limit) {\n    \n    // the next node to visit is the unvisited frontier node that is closest to p\n    id_dist current = unvisited_frontier[offset];\n    if (early_stop(frontier, unvisited_frontier, visited, QP))\n      break;\n    \n    G[current.first].prefetch();\n    // add to visited set\n    auto position = std::upper_bound(visited.begin(), visited.end(), current, less);\n    visited.insert(position, current);\n    num_visited++;\n    bool frontier_full = frontier.size() == beamSize;\n\n    // if using filtering based on lower quality distances measure, then maintain the average\n    // of low quality distance to the last point in the frontier (if frontier is full)\n    if (use_filtering && frontier_full) {\n      //constexpr int width = 5;\n      int width = frontier.size();\n      indexType id = frontier.back().first;\n      if (filter_threshold_count == 0 || filter_id != id) {\n        filter_tail_mean = 0.0;\n        for (int i = frontier.size() - width; i < frontier.size(); i ++) \n          filter_tail_mean += Q_Points[frontier[i].first].distance(qp);\n        filter_tail_mean /= width;\n        filter_id = id;\n      }\n      filter_threshold_sum += filter_tail_mean;\n      filter_threshold_count++;\n      filter_threshold = filter_threshold_sum / filter_threshold_count;\n    }\n\n    // keep neighbors that have not been visited (using approximate\n    // hash). Note that if a visited node is accidentally kept due to\n    // approximate hash it will be removed below by the union.\n    pruned.clear();\n    filtered.clear();\n    long num_elts = std::min<long>(G[current.first].size(), QP.degree_limit);\n    for (indexType i=0; i<num_elts; i++) {\n      auto a = G[current.first][i];\n      if (has_been_seen(a) || Points[a].same_as(p)) continue;  // skip if already seen\n      Q_Points[a].prefetch();\n      pruned.push_back(a);\n    }\n    dist_cmps += pruned.size();\n\n    // filter using low-quality distance\n    if (use_filtering && frontier_full) {\n      for (auto a : pruned) {\n        if (frontier_full && Q_Points[a].distance(qp) >= filter_threshold) continue;\n        filtered.push_back(a);\n        Points[a].prefetch();\n      }\n    } else std::swap(filtered, pruned);\n\n    // Further remove if distance is greater than current\n    // furthest distance in current frontier (if full).\n    distanceType cutoff = (frontier_full\n                           ? frontier[frontier.size() - 1].second\n                           : (distanceType)std::numeric_limits<distanceType>::max());\n    for (auto a : filtered) {\n      distanceType dist = Points[a].distance(p);\n      full_dist_cmps++;\n      // skip if frontier not full and distance too large\n      if (dist >= cutoff) continue;\n      candidates.push_back(std::pair{a, dist});\n    }\n    // If candidates insufficently full then skip rest of step until sufficiently full.\n    // This iproves performance for higher accuracies (e.g. beam sizes of 100+)\n    if (candidates.size() == 0 || \n        (QP.limit >= 2 * beamSize &&\n         //candidates.size() < beamSize/8 &&\n         candidates.size() < QP.batch_factor * beamSize &&\n         offset + 1 < remain)) {\n      offset++;\n      continue;\n    }\n    offset = 0;\n\n    // sort the candidates by distance from p,\n    // and remove any duplicates (to be robust for neighbor lists with duplicates)\n    std::sort(candidates.begin(), candidates.end(), less);\n    auto candidates_end = std::unique(candidates.begin(), candidates.end(),\n                                      [] (auto a, auto b) {return a.first == b.first;});\n\n    // union the frontier and candidates into new_frontier, both are sorted\n    auto new_frontier_size =\n      std::set_union(frontier.begin(), frontier.end(), candidates.begin(),\n                     candidates_end, new_frontier.begin(), less) -\n      new_frontier.begin();\n    candidates.clear();\n    \n    // trim to at most beam size\n    new_frontier_size = std::min<size_t>(beamSize, new_frontier_size);\n\n    // copy new_frontier back to the frontier\n    frontier.clear();\n    for (indexType i = 0; i < new_frontier_size; i++)\n      frontier.push_back(new_frontier[i]);\n\n    // get the unvisited frontier\n    remain = (std::set_difference(frontier.begin(),\n                                  frontier.begin() + std::min<long>(frontier.size(), QP.beamSize),\n                                  visited.begin(),\n                                  visited.end(),\n                                  unvisited_frontier.begin(), less) -\n              unvisited_frontier.begin());\n  }\n\n  return std::make_pair(std::make_pair(parlay::to_sequence(frontier),\n                                       parlay::to_sequence(visited)),\n                        full_dist_cmps);\n}\n\n  // alternative experimental version\n  // about equal performance\ntemplate<typename indexType, typename Point, typename PointRange,\n         typename QPoint, typename QPointRange, typename GT, typename ES = EarlyStopping>\nstd::pair<std::pair<parlay::sequence<std::pair<indexType, typename Point::distanceType>>,\n                    parlay::sequence<std::pair<indexType, typename Point::distanceType>>>,\n          size_t>\nfiltered_beam_search_new(const GT &G,\n                      const Point p,  const PointRange &Points,\n                      const QPoint qp, const QPointRange &Q_Points,\n                      const parlay::sequence<indexType> starting_points,\n                      const QueryParams &QP,\n                      bool use_filtering = false,\n                      ES early_stop = ES{}\n                      ) {\n  using dtype = typename Point::distanceType;\n  using id_dist = std::pair<indexType, dtype>;\n  int beamSize = QP.beamSize;\n  int max_degree = QP.degree_limit;\n\n  if (starting_points.size() == 0) {\n    std::cout << \"beam search expects at least one start point\" << std::endl;\n    abort();\n  } else if (starting_points.size() > beamSize) {\n    std::cout << \"beam search has more starting points than beam size\" << std::endl;\n    abort();\n  }\n\n  // compare two (node_id,distance) pairs, first by distance and then id if\n  // equal\n  using distanceType = typename Point::distanceType;\n  auto less = [&](id_dist a, id_dist b) {\n    return a.second < b.second || (a.second == b.second && a.first < b.first);\n  };\n\n  long set_size = 1.5 * (10 + beamSize) * max_degree;\n  hashset<indexType> has_been_seen(set_size);\n  \n  // Frontier maintains the closest points found so far and its size\n  // is always at most beamSize.  Each entry is a (id,distance) pair.\n  // Initialized with starting points and kept sorted by distance.\n  std::vector<id_dist> frontier;\n  frontier.reserve(2*beamSize);\n  for (auto q : starting_points) {\n    frontier.push_back(id_dist(q, Points[q].distance(p)));\n    has_been_seen(q);\n  }\n  std::sort(frontier.begin(), frontier.end(), less);\n  std::vector<id_dist> new_frontier;\n  \n  // maintains sorted set of visited vertices (id-distance pairs)\n  std::vector<id_dist> visited;\n  visited.reserve(2 * beamSize);\n\n  // counters\n  size_t dist_cmps = starting_points.size();\n  size_t full_dist_cmps = starting_points.size();\n  int num_visited = 0;\n\n  // used as temporaries in the loop\n  std::vector<id_dist> candidates;\n  candidates.reserve(G.max_degree() + beamSize);\n  std::vector<indexType> filtered;\n  filtered.reserve(G.max_degree());\n  std::vector<indexType> pruned;\n  pruned.reserve(G.max_degree());\n\n  // offset into the unvisited_frontier vector (unvisited_frontier[offset] is the next to visit)\n  int offset = 0;\n  std::priority_queue<dtype> topQ;\n  for (auto [v,d] : frontier)\n    topQ.push(d);\n  std::priority_queue<dtype> visitedQ;\n\n  float filter_threshold = 0.0;\n  int filter_threshold_cnt = 0;\n  float round_sum = 0.0;\n\n  // The main loop.  Terminate beam search when the entire frontier\n  // has been visited or have reached max_visit.\n  while (frontier.size() > 0 && num_visited < QP.limit) {\n    // the next node to visit is the unvisited frontier node that is closest to p\n    id_dist current = frontier[offset];\n    if (visitedQ.size() == beamSize && visitedQ.top() <= current.second) break;\n    visited.push_back(current);\n    visitedQ.push(current.second);\n    if (visitedQ.size() > beamSize)\n      visitedQ.pop();\n\n    //if (early_stop(frontier, unvisited_frontier, visited, QP))\n    //  break;\n    \n    G[current.first].prefetch();\n    num_visited++;\n    bool has_full_beam = (topQ.size() >= beamSize);\n\n    pruned.clear();\n    filtered.clear();\n    long num_elts = std::min<long>(G[current.first].size(), QP.degree_limit);\n    for (indexType i=0; i<num_elts; i++) {\n      auto a = G[current.first][i];\n      if (has_been_seen(a) || Points[a].same_as(p)) continue;  // skip if already seen\n      Q_Points[a].prefetch();\n      pruned.push_back(a);\n    }\n    dist_cmps += pruned.size();\n    \n    // filter using low-quality distance\n    if (use_filtering && has_full_beam) {\n      for (auto a : pruned) {\n        if (Q_Points[a].distance(qp) >= filter_threshold) continue;\n        filtered.push_back(a);\n        Points[a].prefetch();\n      }\n    } else std::swap(filtered, pruned);\n    \n    // Further remove if distance is greater than current\n    // furthest distance in current frontier (if full).\n    for (auto a : filtered) {\n      distanceType dist = Points[a].distance(p);\n      full_dist_cmps++;\n      // skip if frontier not full and distance too large\n      if (topQ.size() == beamSize && topQ.top() <= dist)\n        continue;\n      topQ.push(dist);\n      if (topQ.size() > beamSize) topQ.pop();\n      if (use_filtering)\n        round_sum += Q_Points[a].distance(qp);\n      candidates.push_back(std::pair{a, dist});\n    }\n\n    offset++;\n\n    // If candidates insufficently full then skip rest of step until sufficiently full.\n    // This iproves performance for higher accuracies (e.g. beam sizes of 100+)\n    if (offset != frontier.size() &&\n        (candidates.size() == 0 || \n         (QP.limit >= 2 * beamSize &&\n          candidates.size() < QP.batch_factor * beamSize)) &&\n        (visitedQ.size() != beamSize ||\n         visitedQ.top() > frontier[offset].second))\n      continue;\n\n    if (use_filtering) {\n      float round_average = round_sum/candidates.size();\n      // We use a rolling average to keep the filter_threshold smooth\n      // and always a bit bigger than distances we have seen recently\n      // so we don't filter out too many points.\n      if (filter_threshold_cnt == 0)\n        filter_threshold = round_average;\n      else filter_threshold = (filter_threshold * .85 + round_average * .15);\n      round_sum = 0;\n      filter_threshold_cnt++;\n    }\n    \n    // sort the candidates by distance from p,\n    std::sort(candidates.begin(), candidates.end(), less);\n    \n    // merge the frontier and candidates into new_frontier, both are sorted\n    long merge_size = frontier.size() - offset + candidates.size();\n        \n    new_frontier.resize(merge_size);\n    std::merge(frontier.begin()+offset, frontier.end(), candidates.begin(),\n               candidates.end(), new_frontier.begin(), less);\n    if (merge_size > beamSize) \n      new_frontier.resize(beamSize);\n    candidates.clear();\n    std::swap(frontier, new_frontier);\n    offset = 0;\n  }\n\n  // sort all visited points and take the first beamSize of them\n  std::sort(visited.begin(), visited.end(), less);\n  if (visited.size() > beamSize)\n    visited.resize(beamSize);\n\n  return std::make_pair(std::make_pair(parlay::to_sequence(visited),\n                                       parlay::to_sequence(visited)),\n                        full_dist_cmps);\n}\n\n  struct EStop {\n    template<typename PointInfo>\n    bool operator () (const PointInfo& frontier, \n                      const PointInfo& unvisited_frontier,\n                      const PointInfo& visited,\n                      const QueryParams& QP) { return false;}\n  };\n  \n// version without filtering\n  template<typename Point, typename PointRange, typename indexType> // = EarlyStopping>\nstd::pair<std::pair<parlay::sequence<std::pair<indexType, typename Point::distanceType>>,\n                    parlay::sequence<std::pair<indexType, typename Point::distanceType>>>, size_t>\nbeam_search(const Point p, const Graph<indexType> &G, const PointRange &Points,\n            const parlay::sequence<indexType> starting_points, const QueryParams &QP\n            ) {\n    return filtered_beam_search(G,p, Points, p, Points, starting_points, QP, false); //early_stop);\n}\n\n// backward compatibility (for hnsw)\ntemplate<typename indexType, typename Point, typename PointRange, class GT>\nstd::pair<std::pair<parlay::sequence<std::pair<indexType, typename Point::distanceType>>, parlay::sequence<std::pair<indexType, typename Point::distanceType>>>, size_t>\nbeam_search_impl(Point p, GT &G, PointRange &Points,\n                 parlay::sequence<indexType> starting_points, QueryParams &QP) {\n  return filtered_beam_search(G, p, Points, p, Points, starting_points, QP, false);\n}\n\n// pass single start point\ntemplate<typename Point, typename PointRange, typename indexType>\nstd::pair<std::pair<parlay::sequence<std::pair<indexType, typename Point::distanceType>>,\n                    parlay::sequence<std::pair<indexType, typename Point::distanceType>>>, indexType>\nbeam_search(const Point p, const Graph<indexType> &G, const PointRange &Points,\n            const indexType starting_point, const QueryParams &QP) {\n  parlay::sequence<indexType> start_points = {starting_point};\n  return beam_search(p, G, Points, start_points, QP);\n}\n\n// searches every element in q starting from a randomly selected point\ntemplate<typename PointRange, typename indexType>\nparlay::sequence<parlay::sequence<indexType>>\nbeamSearchRandom(const PointRange& Query_Points,\n                 const Graph<indexType> &G,\n                 const PointRange &Base_Points,\n                 stats<indexType> &QueryStats,\n                 const QueryParams &QP) {\n  using Point = typename PointRange::Point;\n  if (QP.k > QP.beamSize) {\n    std::cout << \"Error: beam search parameter Q = \" << QP.beamSize\n              << \" same size or smaller than k = \" << QP.k << std::endl;\n    abort();\n  }\n  // use a random shuffle to generate random starting points for each query\n  size_t n = G.size();\n\n  parlay::sequence<parlay::sequence<indexType>> all_neighbors(Query_Points.size());\n\n  parlay::random_generator gen;\n  std::uniform_int_distribution<long> dis(0, n - 1);\n  auto indices = parlay::tabulate(Query_Points.size(), [&](size_t i) {\n    auto r = gen[i];\n    return dis(r);\n  });\n\n  parlay::parallel_for(0, Query_Points.size(), [&](size_t i) {\n    parlay::sequence<indexType> neighbors = parlay::sequence<indexType>(QP.k);\n    indexType start = indices[i];\n    parlay::sequence<std::pair<indexType, typename Point::distanceType>> beamElts;\n    parlay::sequence<std::pair<indexType, typename Point::distanceType>> visitedElts;\n    auto [pairElts, dist_cmps] =\n      beam_search(Query_Points[i], G, Base_Points, start, QP);\n    beamElts = pairElts.first;\n    visitedElts = pairElts.second;\n    for (indexType j = 0; j < QP.k; j++) {\n      neighbors[j] = beamElts[j].first;\n    }\n    all_neighbors[i] = neighbors;\n    QueryStats.increment_visited(i, visitedElts.size());\n    QueryStats.increment_dist(i, dist_cmps);\n  });\n  return all_neighbors;\n}\n\ntemplate<typename PointRange, typename indexType>\nparlay::sequence<parlay::sequence<indexType>>\nsearchAll(PointRange& Query_Points,\n          Graph<indexType> &G, PointRange &Base_Points, stats<indexType> &QueryStats,\n          indexType starting_point, QueryParams &QP) {\n  parlay::sequence<indexType> start_points = {starting_point};\n  return searchAll<PointRange, indexType>(Query_Points, G, Base_Points, QueryStats, start_points, QP);\n}\n\ntemplate< typename PointRange, typename indexType>\nparlay::sequence<parlay::sequence<indexType>>\nsearchAll(PointRange &Query_Points,\n          Graph<indexType> &G, PointRange &Base_Points, stats<indexType> &QueryStats,\n          parlay::sequence<indexType> starting_points,\n          QueryParams &QP) {\n  if (QP.k > QP.beamSize) {\n    std::cout << \"Error: beam search parameter Q = \" << QP.beamSize\n              << \" same size or smaller than k = \" << QP.k << std::endl;\n    abort();\n  }\n  parlay::sequence<parlay::sequence<indexType>> all_neighbors(Query_Points.size());\n  parlay::parallel_for(0, Query_Points.size(), [&](size_t i) {\n    parlay::sequence<indexType> neighbors = parlay::sequence<indexType>(QP.k);\n    auto [pairElts, dist_cmps] = beam_search(Query_Points[i], G, Base_Points, starting_points, QP);\n    auto [beamElts, visitedElts] = pairElts;\n    for (indexType j = 0; j < QP.k; j++) {\n      neighbors[j] = beamElts[j].first;\n    }\n    all_neighbors[i] = neighbors;\n    QueryStats.increment_visited(i, visitedElts.size());\n    QueryStats.increment_dist(i, dist_cmps);\n  });\n\n  return all_neighbors;\n}\n\n// Returns a sequence of nearest neighbors each with their distance\ntemplate<typename Point, typename QPoint, typename QQPoint,\n         typename PointRange, typename QPointRange, typename QQPointRange,\n         typename indexType>\nparlay::sequence<std::pair<indexType, typename Point::distanceType>>\nbeam_search_rerank(const Point &p,\n                   const QPoint &qp,\n                   const QQPoint &qqp,\n                   const Graph<indexType> &G,\n                   const PointRange &Base_Points,\n                   const QPointRange &Q_Base_Points,\n                   const QQPointRange &QQ_Base_Points,\n                   stats<indexType> &QueryStats,\n                   const parlay::sequence<indexType> starting_points,\n                   const QueryParams &QP,\n                   bool stats = true) {\n  using dtype = typename Point::distanceType;\n  using id_dist = std::pair<indexType, dtype>;\n  auto QPP = QP;\n\n  bool use_rerank = (Base_Points.params.num_bytes() != Q_Base_Points.params.num_bytes());\n  bool use_filtering = (Q_Base_Points.params.num_bytes() != QQ_Base_Points.params.num_bytes());\n  std::pair<std::pair<parlay::sequence<id_dist>, parlay::sequence<id_dist>>, size_t> r;\n  r = filtered_beam_search(G,\n                            qp, Q_Base_Points,\n                            qqp, QQ_Base_Points,\n                            starting_points, QPP, use_filtering);\n  auto [pairElts, dist_cmps] = r;\n  auto [beamElts, visitedElts] = pairElts;\n  if (beamElts.size() < QP.k) {\n    std::cout << \"Error: for point id \" << p.id()\n              << \" beam search returned \" << beamElts.size()\n              << \" elements, which is less than k = \" << QP.k << std::endl;\n    abort();\n  }\n  \n  if (stats) {\n    QueryStats.increment_visited(p.id(), visitedElts.size());\n    QueryStats.increment_dist(p.id(), dist_cmps);\n  }\n\n  if (use_rerank) {\n    // recalculate distances with non-quantized points and sort\n    int num_check = std::min<int>(QP.k * QP.rerank_factor, beamElts.size());\n    std::vector<id_dist> pts;\n    for (int i=0; i < num_check; i++) {\n      int j = beamElts[i].first;\n      pts.push_back(id_dist(j, p.distance(Base_Points[j])));\n    }\n    auto less = [&] (id_dist a, id_dist b) {\n      return a.second < b.second || (a.second == b.second && a.first < b.first);\n    };\n    std::sort(pts.begin(), pts.end(), less);\n\n    // keep first k\n    parlay::sequence<id_dist> results;\n    for (int i= 0; i < QP.k; i++)\n      results.push_back(pts[i]);\n\n    return results;\n  } else {\n    //return beamElts;\n    parlay::sequence<id_dist> results;\n    for (int i= 0; i < QP.k; i++) {\n      int j = beamElts[i].first;\n      results.push_back(id_dist(j, p.distance(Base_Points[j])));\n    }\n    return results;\n  }\n}\n\ntemplate<typename PointRange, typename QPointRange, typename QQPointRange, typename indexType>\nparlay::sequence<parlay::sequence<indexType>>\nqsearchAll(const PointRange &Query_Points,\n           const QPointRange &Q_Query_Points,\n           const QQPointRange &QQ_Query_Points,\n           const Graph<indexType> &G,\n           const PointRange &Base_Points,\n           const QPointRange &Q_Base_Points,\n           const QQPointRange &QQ_Base_Points,\n           stats<indexType> &QueryStats,\n           const indexType starting_point,\n           const QueryParams &QP,\n           bool random = false) {\n  if (QP.k > QP.beamSize) {\n    std::cout << \"Error: beam search parameter Q = \" << QP.beamSize\n              << \" same size or smaller than k = \" << QP.k << std::endl;\n    abort();\n  }\n  parlay::sequence<parlay::sequence<indexType>> all_neighbors(Query_Points.size());\n  if (random) {\n    parlay::random_generator gen;\n    std::uniform_int_distribution<long> dis(0, G.size() - 1);\n    auto indices = parlay::tabulate(Query_Points.size(), [&](size_t i) -> indexType {\n      auto r = gen[i];\n      return dis(r);\n    });\n\n    parlay::parallel_for(0, Query_Points.size(), [&](size_t i) {\n      parlay::sequence<indexType> starting_points = {indices[i]};\n      auto ngh_dist = beam_search_rerank(Query_Points[i], Q_Query_Points[i], QQ_Query_Points[i],\n                                         G,\n                                         Base_Points, Q_Base_Points, QQ_Base_Points,\n                                         QueryStats, starting_points, QP);\n      all_neighbors[i] = parlay::map(ngh_dist, [] (auto& p) {return p.first;});\n    });\n  } else {\n    parlay::sequence<indexType> starting_points = {starting_point};\n    parlay::parallel_for(0, Query_Points.size(), [&](size_t i) {\n      auto ngh_dist = beam_search_rerank(Query_Points[i], Q_Query_Points[i], QQ_Query_Points[i],\n                                         G,\n                                         Base_Points, Q_Base_Points, QQ_Base_Points,\n                                         QueryStats, starting_points, QP);\n      all_neighbors[i] = parlay::map(ngh_dist, [] (auto& p) {return p.first;});\n    });\n  }\n\n  return all_neighbors;\n}\n\n\n\n} // end namespace\n\n#endif // ALGORITHMS_ANN_BEAM_SEARCH_H_\n"
  },
  {
    "path": "algorithms/utils/check_nn_recall.h",
    "content": "#ifndef ALGORITHMS_CHECK_NN_RECALL_H_\n#define ALGORITHMS_CHECK_NN_RECALL_H_\n\n#include <algorithm>\n#include <set>\n\n#include \"beamSearch.h\"\n#include \"csvfile.h\"\n#include \"parse_results.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"types.h\"\n#include \"stats.h\"\n\nnamespace parlayANN {\n\ntemplate<typename PointRange, typename QPointRange, typename QQPointRange, typename indexType>\nnn_result checkRecall(const Graph<indexType> &G,\n                      const PointRange &Base_Points,\n                      const PointRange &Query_Points,\n                      const QPointRange &Q_Base_Points,\n                      const QPointRange &Q_Query_Points,\n                      const QQPointRange &QQ_Base_Points,\n                      const QQPointRange &QQ_Query_Points,\n                      const groundTruth<indexType> &GT,\n                      const bool random,\n                      const long start_point,\n                      const long k,\n                      const QueryParams &QP,\n                      const bool verbose) {\n  using Point = typename PointRange::Point;\n\n  if (GT.size() > 0 && k > GT.dimension()) {\n    std::cout << k << \"@\" << k << \" too large for ground truth data of size \"\n              << GT.dimension() << std::endl;\n    abort();\n  }\n\n  parlay::sequence<parlay::sequence<indexType>> all_ngh;\n\n  parlay::internal::timer t;\n  float query_time;\n  stats<indexType> QueryStats(Query_Points.size());\n  QueryStats.clear();\n  // to help clear the cache between runs\n  auto volatile xx = parlay::random_permutation<long>(5000000);\n  t.next_time();\n  if (random) {\n    all_ngh = qsearchAll<PointRange, QPointRange, QQPointRange, indexType>(Query_Points, Q_Query_Points, QQ_Query_Points,\n                                                                           G,\n                                                                           Base_Points, Q_Base_Points, QQ_Base_Points,\n                                                                           QueryStats, start_point, QP, /*random=*/true);\n  } else {\n    all_ngh = qsearchAll<PointRange, QPointRange, QQPointRange, indexType>(Query_Points, Q_Query_Points, QQ_Query_Points,\n                                                                           G,\n                                                                           Base_Points, Q_Base_Points, QQ_Base_Points,\n                                                                           QueryStats, start_point, QP);\n  }\n  query_time = t.next_time();\n  \n  float recall = 0.0;\n  //TODO deprecate this after further testing\n  bool dists_present = true;\n  if (GT.size() > 0 && !dists_present) {\n    size_t n = Query_Points.size();\n    int numCorrect = 0;\n    for (indexType i = 0; i < n; i++) {\n      std::set<indexType> reported_nbhs;\n      if (all_ngh[i].size() != k) {\n        std::cout << \"bad number of neighbors reported: \" << all_ngh[i].size() << std::endl;\n        abort();\n      }\n      for (indexType l = 0; l < k; l++) reported_nbhs.insert((all_ngh[i])[l]);\n      if (reported_nbhs.size() != k) {\n        std::cout << \"duplicate entries in reported neighbors\" << std::endl;\n        abort();\n      }\n      for (indexType l = 0; l < k; l++) {\n        if (reported_nbhs.find((GT.coordinates(i,l))) !=\n            reported_nbhs.end()) {\n          numCorrect += 1;\n        }\n      }\n    }\n    recall = static_cast<float>(numCorrect) / static_cast<float>(k * n);\n  } else if (GT.size() > 0 && dists_present) {\n    size_t n = Query_Points.size();\n\n    int numCorrect = 0;\n    for (indexType i = 0; i < n; i++) {\n      parlay::sequence<int> results_with_ties;\n      for (indexType l = 0; l < k; l++)\n        results_with_ties.push_back(GT.coordinates(i,l));\n      Point qp = Query_Points[i];\n      float last_dist = qp.distance(Base_Points[GT.coordinates(i, k-1)]);\n      //float last_dist = GT.distances(i, k-1);\n      for (indexType l = k; l < GT.dimension(); l++) {\n        //if (GT.distances(i,l) == last_dist) {\n        if (qp.distance(Base_Points[GT.coordinates(i, l)]) == last_dist) {\n          results_with_ties.push_back(GT.coordinates(i,l));\n        }\n      }\n      std::set<int> reported_nbhs;\n      for (indexType l = 0; l < k; l++) reported_nbhs.insert((all_ngh[i])[l]);\n      for (indexType l = 0; l < results_with_ties.size(); l++) {\n        if (reported_nbhs.find(results_with_ties[l]) != reported_nbhs.end()) {\n          numCorrect += 1;\n        }\n      }\n    }\n    recall = static_cast<float>(numCorrect) / static_cast<float>(k * n);\n  }\n  float QPS = Query_Points.size() / query_time;\n  if (verbose)\n    std::cout << \"search: Q=\" << QP.beamSize << \", k=\" << QP.k\n              << \", limit=\" << QP.limit\n      //<< \", dlimit=\" << QP.degree_limit\n              << \", recall=\" << recall\n              << \", visited=\" << QueryStats.visited_stats()[0]\n              << \", comparisons=\" << QueryStats.dist_stats()[0]\n              << \", QPS=\" << QPS\n              << \", ctime=\" << 1/(QPS*QueryStats.dist_stats()[0]) * 1e9 << std::endl;\n\n  auto stats_ = {QueryStats.dist_stats(), QueryStats.visited_stats()};\n  parlay::sequence<indexType> stats = parlay::flatten(stats_);\n  nn_result N(recall, stats, QPS, k, QP.beamSize, Query_Points.size(), QP.limit, QP.degree_limit, k);\n  return N;\n}\n\nvoid write_to_csv(std::string csv_filename, parlay::sequence<float> buckets,\n                  parlay::sequence<nn_result> results, Graph_ G) {\n  csvfile csv(csv_filename);\n  csv << \"GRAPH\"\n      << \"Parameters\"\n      << \"Size\"\n      << \"Build time\"\n      << \"Avg degree\"\n      << \"Max degree\" << endrow;\n  csv << G.name << G.params << G.size << G.time << G.avg_deg << G.max_deg\n      << endrow;\n  csv << endrow;\n  csv << \"Num queries\"\n      << \"Target recall\"\n      << \"Actual recall\"\n      << \"QPS\"\n      << \"Average Cmps\"\n      << \"Tail Cmps\"\n      << \"Average Visited\"\n      << \"Tail Visited\"\n      << \"k\"\n      << \"Q\"\n      << endrow;\n  for (int i = 0; i < results.size(); i++) {\n    nn_result N = results[i];\n    csv << N.num_queries << buckets[i] << N.recall << N.QPS << N.avg_cmps\n        << N.tail_cmps << N.avg_visited << N.tail_visited << N.k << N.beamQ\n        << endrow;\n  }\n  csv << endrow;\n  csv << endrow;\n}\n\nparlay::sequence<long> calculate_limits(size_t upper_bound) {\n  parlay::sequence<long> L(6);\n  for (float i = 0; i < 6; i++) {\n    L[i] = (long)((4 + i) * ((float) upper_bound) * .1);\n    //std::cout << L[i - 1] << std::endl;\n  }\n  //auto limits = parlay::remove_duplicates(L);\n  return L; //limits;\n}\n\ntemplate<typename PointRange, typename indexType>\nvoid search_and_parse(Graph_ G_,\n                      Graph<indexType> &G,\n                      PointRange &Base_Points,\n                      PointRange &Query_Points,\n                      groundTruth<indexType> GT, char* res_file, long k,\n                      bool verbose = false,\n                      long fixed_beam_width = 0) {\n  search_and_parse(G_, G, Base_Points, Query_Points, Base_Points, Query_Points, Base_Points, Query_Points, GT, res_file, k, false, 0u, verbose, fixed_beam_width);\n}\n\ntemplate<typename PointRange, typename QPointRange, typename QQPointRange, typename indexType>\nvoid search_and_parse(Graph_ G_,\n                      Graph<indexType> &G,\n                      PointRange &Base_Points,\n                      PointRange &Query_Points,\n                      QPointRange &Q_Base_Points,\n                      QPointRange &Q_Query_Points,\n                      QQPointRange &QQ_Base_Points,\n                      QQPointRange &QQ_Query_Points,\n                      groundTruth<indexType> GT, char* res_file, long k,\n                      bool random = true,\n                      indexType start_point = 0,\n                      bool verbose = false,\n                      long fixed_beam_width = 0,\n                      double rerank_factor = 100,\n                      double batch_factor = .125) {\n  parlay::sequence<nn_result> results;\n  std::vector<long> beams;\n  std::vector<long> allr;\n\n  auto check = [&] (const long k, const QueryParams QP) {\n    return checkRecall(G,\n                       Base_Points, Query_Points,\n                       Q_Base_Points, Q_Query_Points,\n                       QQ_Base_Points, QQ_Query_Points,\n                       GT,\n                       random,\n                       start_point, k, QP, verbose);};\n\n  QueryParams QP(k, 0, G.size(), G.max_degree(), rerank_factor, batch_factor);\n  beams = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 28, 30, 32,\n    34, 36, 38, 40, 45, 50, 55, 60, 65, 70, 80, 90, 100, 120, 140, 160,\n    180, 200, 225, 250, 275, 300, 375, 500, 750, 1000};\n  if(k==0) allr = {10};\n  else allr = {k};\n\n  if (fixed_beam_width != 0) {\n    QP.k = allr[0];\n    QP.beamSize = fixed_beam_width;\n    for (int i = 0; i < 5; i++)\n      check(QP.k, QP);\n  } else {\n    for (long r : allr) {\n      results.clear();\n      QP.k = r;\n      for (float Q : beams){\n        QP.beamSize = Q;\n        if (Q >= r){\n          results.push_back(check(r, QP));\n        }\n      }\n\n      // check \"limited accuracy\"\n      parlay::sequence<long> limits = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,\n                                       22, 23, 24, 25, 26, 28, 30, 35};\n      //calculate_limits(results[0].avg_visited);\n      //parlay::sequence<long> degree_limits = calculate_limits(G.max_degree());\n      //degree_limits.push_back(G.max_degree());\n      QP = QueryParams(r, r, (long) G.size(), (long) G.max_degree(),\n                       rerank_factor, batch_factor);\n      for(long l : limits){\n        QP.limit = l;\n        QP.beamSize = std::max<long>(l, r);\n        //for(long dl : degree_limits){\n        QP.degree_limit = std::min<int>(G.max_degree(), 5 * l);\n        results.push_back(check(r, QP));\n      }\n      // check \"best accuracy\"\n      QP = QueryParams((long) 100, (long) 1000, (long) G.size(),\n                       (long) G.max_degree(), rerank_factor, batch_factor);\n      results.push_back(check(r, QP));\n\n      parlay::sequence<float> buckets =  {.1, .2, .3,  .4,  .5,  .6, .7, .75,  .8, .85,\n        .9, .93, .95, .97, .98, .99, .995, .999, .9995,\n        .9999, .99995, .99999};\n      auto [res, ret_buckets] = parse_result(results, buckets);\n      std::cout << std::endl;\n      if (res_file != NULL)\n        write_to_csv(std::string(res_file), ret_buckets, res, G_);\n    }\n  }\n}\n\n// template<typename Point, typename PointRange, typename indexType>\n// void search_and_parse(Graph_ G_,\n//                       Graph<indexType> &G,\n//                       PointRange &Base_Points,\n//                       PointRange &Query_Points,\n//                       groundTruth<indexType> GT, char* res_file, long k,\n//                       bool random=true, indexType start_point=0,\n//                       bool verbose=false) {\n//   search_and_parse<Point>(G_, G, Base_Points, Query_Points, Base_Points, Query_Points, GT,\n//                           res_file, k, random, start_point, verbose);\n// }\n\n} // end namespace\n\n#endif // ALGORITHMS_CHECK_NN_RECALL_H_\n"
  },
  {
    "path": "algorithms/utils/check_range_recall.h",
    "content": "#include <algorithm>\n#include <set>\n\n#include \"beamSearch.h\"\n#include \"doublingSearch.h\"\n#include \"rangeSearch.h\"\n#include \"csvfile.h\"\n#include \"parse_results.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"types.h\"\n#include \"stats.h\"\n\nnamespace parlayANN {\n\ntemplate<typename Point, typename PointRange, typename QPointRange, typename indexType>\nvoid checkRangeRecall(\n        Graph<indexType> &G,\n        PointRange &Base_Points, PointRange &Query_Points,\n        QPointRange &Q_Base_Points, QPointRange &Q_Query_Points,\n        RangeGroundTruth<indexType> GT, QueryParams QP,\n        long start_point,parlay::sequence<indexType> &active_indices) {\n\n  if(QP.range_query_type == Doubling) {\n    \n    parlay::internal::timer t;\n    float query_time;\n    stats<indexType> QueryStats(Query_Points.size());\n    parlay::sequence<indexType> start_points = {static_cast<indexType>(start_point)};\n    \n    auto [all_rr,timings] = DoubleBeamRangeSearch(G,\n                                                  Query_Points, Base_Points,\n                                                  Q_Query_Points, Q_Base_Points,\n                                                  QueryStats, start_points, QP, active_indices);\n    query_time = t.next_time();\n    auto [beam_search_time, other_time] = timings;\n    \n    float pointwise_recall = 0.0;\n    float reported_results = 0.0;\n    float total_results = 0.0;\n    float num_nonzero = 0.0;\n\n      //since distances are exact, just have to cross-check number of results\n      size_t n = Query_Points.size();\n      for (indexType i = 0; i < n; i++) {\n        float num_reported_results = all_rr[i].size();\n        float num_actual_results = GT[i].size();\n        reported_results += num_reported_results;\n        total_results += num_actual_results;\n        if(num_actual_results != 0) {pointwise_recall += num_reported_results/num_actual_results; num_nonzero++;}\n      }\n      \n      pointwise_recall /= num_nonzero;\n      float cumulative_recall = reported_results/total_results;\n    \n    float QPS = Query_Points.size() / query_time;\n    auto stats_ = {QueryStats.dist_stats(), QueryStats.visited_stats()};\n    std::cout << \"For \";\n    QP.print();\n    std::cout << \", Point Recall=\" << pointwise_recall\n              << \", Cum Recall=\" << cumulative_recall\n              << \", Comparisons=\" << QueryStats.dist_stats()[0]\n              << \", Visited=\" << QueryStats.visited_stats()[0]\n              << \", QPS=\" << QPS\n              << \", ctime=\" << (1e9 / (QPS * QueryStats.dist_stats()[0]))\n              << \", timings= [\" << beam_search_time<< \",\"<< other_time <<\"]\"\n              << std::endl;\n    \n  } else if (QP.range_query_type == Greedy || QP.range_query_type == Beam) {\n\n  float query_time;\n  stats<indexType> QueryStats(Query_Points.size());\n  parlay::sequence<indexType> start_points = {static_cast<indexType>(start_point)};\n  parlay::internal::timer t;  \n\n  auto [all_rr, timings] = RangeSearch<Point,PointRange,QPointRange,indexType>(G,\n                                                                    Query_Points, Base_Points,\n                                                                    Q_Query_Points, Q_Base_Points,\n                                                                    QueryStats, start_point, QP);\n  auto [beam_search_time, other_time] = timings;\n  query_time = t.next_time();\n\n  float pointwise_recall = 0.0;\n  float reported_results = 0.0;\n  float total_results = 0.0;\n  float num_nonzero = 0.0;\n\n  //since distances are exact, just have to cross-check number of results\n  size_t n = Query_Points.size();\n  for (indexType i = 0; i < n; i++) {\n    float num_reported_results = all_rr[i].size();\n    float num_actual_results = GT[i].size();\n    reported_results += num_reported_results;\n    total_results += num_actual_results;\n    if(num_actual_results != 0) {pointwise_recall += num_reported_results/num_actual_results; num_nonzero++;}\n  }\n    \n  pointwise_recall /= num_nonzero;\n  float cumulative_recall = reported_results/total_results;\n  \n  float QPS = Query_Points.size() / query_time;\n  auto stats_ = {QueryStats.dist_stats(), QueryStats.visited_stats()};\n  std::cout << \"For \";\n  QP.print();\n    std::cout << \", Point Recall=\" << pointwise_recall\n              << \", Cum Recall=\" << cumulative_recall\n              << \", Comparisons=\" << QueryStats.dist_stats()[0]\n              << \", Visited=\" << QueryStats.visited_stats()[0]\n              << \", QPS=\" << QPS\n              << \", ctime=\" << (1e9 / (QPS * QueryStats.dist_stats()[0]))\n              << \", timings= [\" << beam_search_time<< \",\"<< other_time <<\"]\"\n              << std::endl;\n  }\n  else {\n    std::cout << \"Error: No beam search type provided, -seach_mode should be one of [doubling, greedy, beam]\" << std::endl;\n  }\n}\n\n\ntemplate<typename Point, typename PointRange, typename QPointRange, typename indexType>\nvoid range_search_wrapper(Graph<indexType> &G,\n                          PointRange &Base_Points, PointRange &Query_Points,\n                          QPointRange &Q_Base_Points, QPointRange &Q_Query_Points, \n                          RangeGroundTruth<indexType> GT, indexType start_point=0,\n                          bool is_early_stopping = false, double esr = 0.0,\n                          rangeQueryType rtype = None, double rad = 0.0) {\n\n  std::vector<long> beams;\n\n  beams = {10, 20, 30, 40, 50, 100, 1000, 2000, 3000}; \n  \n  long es = 0;\n\n  parlay::sequence<indexType> all = parlay::tabulate(Query_Points.size(), [&] (indexType i){return i;});\n  parlay::sequence<double> cumulative_recall;\n  parlay::sequence<std::pair<double,double>> timings;\n  parlay::sequence<long> beam_size;\n\n\n\n  for(long b: beams){\n    if (is_early_stopping) \n      es = std::max((long)10, b/4);\n\n    QueryParams QP(b, b, G.size(), G.max_degree(),\n                   is_early_stopping, esr, es, rtype, rad);\n\n    \n    checkRangeRecall<Point>(G,\n                            Base_Points, Query_Points,\n                            Q_Base_Points, Q_Query_Points,\n                            GT, QP, start_point, all);\n\n  }\n  \n\n  \n}\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/csvfile.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n// source: https://gist.github.com/rudolfovich/f250900f1a833e715260a66c87369d15\n\n#pragma once\n#include <fstream>\n#include <iostream>\n#include <sstream>\n#include <string>\n\nnamespace parlayANN {\n\nclass csvfile;\n\ninline static csvfile& endrow(csvfile& file);\ninline static csvfile& flush(csvfile& file);\n\nclass csvfile {\n  std::ofstream fs_;\n  bool is_first_;\n  const std::string separator_;\n  const std::string escape_seq_;\n  const std::string special_chars_;\n\n public:\n  csvfile(const std::string filename, const std::string separator = \",\")\n      : fs_(),\n        is_first_(true),\n        separator_(separator),\n        escape_seq_(\"\\\"\"),\n        special_chars_(\"\\\"\") {\n    fs_.exceptions(std::ios::failbit | std::ios::badbit);\n    fs_.open(filename, std::ios::app);\n  }\n\n  ~csvfile() {\n    flush();\n    fs_.close();\n  }\n\n  void flush() { fs_.flush(); }\n\n  void endrow() {\n    fs_ << std::endl;\n    is_first_ = true;\n  }\n\n  csvfile& operator<<(csvfile& (*val)(csvfile&)) { return val(*this); }\n\n  csvfile& operator<<(const char* val) { return write(escape(val)); }\n\n  csvfile& operator<<(const std::string& val) { return write(escape(val)); }\n\n  template <typename T>\n  csvfile& operator<<(const T& val) {\n    return write(val);\n  }\n\n private:\n  template <typename T>\n  csvfile& write(const T& val) {\n    if (!is_first_) {\n      fs_ << separator_;\n    } else {\n      is_first_ = false;\n    }\n    fs_ << val;\n    return *this;\n  }\n\n  std::string escape(const std::string& val) {\n    std::ostringstream result;\n    result << '\"';\n    std::string::size_type to, from = 0u, len = val.length();\n    while (from < len && std::string::npos !=\n                             (to = val.find_first_of(special_chars_, from))) {\n      result << val.substr(from, to - from) << escape_seq_ << val[to];\n      from = to + 1;\n    }\n    result << val.substr(from) << '\"';\n    return result.str();\n  }\n};\n\ninline static csvfile& endrow(csvfile& file) {\n  file.endrow();\n  return file;\n}\n\ninline static csvfile& flush(csvfile& file) {\n  file.flush();\n  return file;\n}\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/doublingSearch.h",
    "content": "#include <algorithm>\n#include <functional>\n#include <random>\n#include <set>\n#include <unordered_set>\n#include <queue>\n\n#include \"parlay/io.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"parlay/worker_specific.h\"\n#include \"types.h\"\n#include \"graph.h\"\n#include \"stats.h\"\n#include \"beamSearch.h\"\n#include \"earlyStopping.h\"\n\nnamespace parlayANN{\n  template<typename PointRange,\n           typename QPointRange,\n           typename indexType>\nstd::pair<parlay::sequence<parlay::sequence<indexType>>,std::pair<double,double>>\nDoubleBeamRangeSearch(Graph<indexType> &G,\n                      PointRange &Query_Points, PointRange &Base_Points,\n                      QPointRange &Q_Query_Points, QPointRange &Q_Base_Points,\n                      stats<indexType> &QueryStats, \n                      parlay::sequence<indexType> starting_points,\n                      QueryParams &QP, parlay::sequence<indexType> active_indices) {\n  parlay::sequence<parlay::sequence<indexType>> all_neighbors(active_indices.size());\n  parlay::WorkerSpecific<double> first_round_time;\n  parlay::WorkerSpecific<double> second_round_time;\n  bool use_rerank = (Base_Points.params.num_bytes() != Q_Base_Points.params.num_bytes());\n  \n  parlay::parallel_for(0, active_indices.size(), [&](size_t i) {\n    parlay::sequence<indexType> neighbors;\n    parlay::internal::timer t_search_first(\"first round time\");\n    parlay::internal::timer t_search_other(\"after first round\");\n    t_search_first.stop();\n    t_search_other.stop();\n\n    t_search_first.start();\n    auto P = Query_Points[active_indices[i]];\n    auto Q_P = Q_Query_Points[active_indices[i]];\n    using dtype = typename decltype(Query_Points[0])::distanceType;\n    using id_dist = std::pair<indexType, dtype>;\n    QueryParams QP1(QP.beamSize, QP.beamSize,\n                    G.size(), G.max_degree(),\n                    QP.is_early_stop, Q_P.translate_distance(QP.early_stopping_radius),\n                    QP.early_stopping_count,\n                    QP.range_query_type, QP.radius);\n\n    auto [pairElts, dist_cmps] = filtered_beam_search(G, Q_P, Q_Base_Points, Q_P, Q_Base_Points,\n                                                      starting_points, QP1, false,\n                                                      early_stopping<std::vector<id_dist>>);\n    auto [beamElts, visitedElts] = pairElts;\n\n    QueryStats.increment_visited(i, visitedElts.size());\n    QueryStats.increment_dist(i, dist_cmps);\n    \n    // rerank and filter out results not within the radius\n      for (auto b : beamElts){\n        double dist;\n        if (use_rerank) {dist = P.distance(Base_Points[b.first]);}\n        else {dist = b.second;}\n        if (dist <= QP.radius) neighbors.push_back(b.first);\n      }\n\n    bool results_smaller_than_beam = false;\n    if (neighbors.size() < QP.beamSize)\n      results_smaller_than_beam = true;\n    \n    all_neighbors[i] = std::move(neighbors);\n\n    size_t initial_beam = QP.beamSize * 2;\n    // Initialize starting points\n    parlay::sequence<indexType> starting_points_idx;\n\n    for (auto s : beamElts) \n      starting_points_idx.push_back(s.first);\n    t_search_first.stop();\n\n    t_search_other.start();\n    while(!results_smaller_than_beam){\n      parlay::sequence<indexType> neighbors;\n\n      QueryParams QP2(initial_beam, initial_beam, 0.0, G.size(), G.max_degree());\n      auto [pairElts, dist_cmps] = beam_search(Q_P, G, Q_Base_Points, starting_points_idx, QP2);\n      auto [beamElts, visitedElts] = pairElts;\n\n      starting_points_idx.clear();\n      for (auto v : beamElts) \n        starting_points_idx.push_back(v.first);\n\n      // rerank and filter out results not within the radius\n      for (auto b : beamElts){\n        double dist;\n        if (use_rerank) {dist = P.distance(Base_Points[b.first]);}\n        else {dist = b.second;}\n        if (dist <= QP.radius) neighbors.push_back(b.first);\n      }\n\n      if (neighbors.size() < initial_beam)\n        results_smaller_than_beam = true;\n\n      all_neighbors[i] = neighbors;\n\n      QueryStats.increment_visited(i, visitedElts.size());\n      QueryStats.increment_dist(i, dist_cmps);\n      initial_beam *= 2;\n      neighbors.clear();\n\n    }\n    t_search_other.stop();\n    *first_round_time += t_search_first.total_time();\n    *second_round_time += t_search_other.total_time();\n    \n  });\n\n\n  double total_time_first = 0;\n  double total_time_second = 0;\n  for (auto x : first_round_time) total_time_first += x;\n  for (auto y: second_round_time) total_time_second += y;\n\n  return std::make_pair(all_neighbors,std::make_pair(total_time_first,total_time_second));\n}\n}\n"
  },
  {
    "path": "algorithms/utils/earlyStopping.h",
    "content": "#pragma once\n#include <algorithm>\n#include <functional>\n#include <random>\n#include <set>\n#include <unordered_set>\n#include <queue>\n\n#include \"parlay/io.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"beamSearch.h\"\n#include \"types.h\"\n#include \"graph.h\"\n#include \"stats.h\"\n\nnamespace parlayANN{\n  template<typename PointInfo>\n  bool early_stopping(const PointInfo& frontier, \n                      const PointInfo& unvisited_frontier,\n                      const PointInfo& visited,\n                      const QueryParams& QP){\n    bool has_visited_enough = (visited.size() >= QP.early_stopping_count);\n    bool early_stop = (QP.early_stopping_count > 0); \n    bool has_found_candidate = (frontier[0].second <= QP.radius);\n    bool within_early_stop_rad = (unvisited_frontier[0].second <= QP.early_stopping_radius);\n    return early_stop && has_visited_enough && !has_found_candidate && !within_early_stop_rad;\n    }\n}\n"
  },
  {
    "path": "algorithms/utils/euclidian_point.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <algorithm>\n#include <iostream>\n#include <bitset>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/internal/file_map.h\"\n\n#include \"types.h\"\n#include \"NSGDist.h\"\n// #include \"common/time_loop.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nnamespace parlayANN {\n\ninline float euclidian_distance_(const uint8_t *p, const uint8_t *q, unsigned d) {\n  int result = 0;\n  for (int i = 0; i < d; i++) {\n    result += ((int32_t)((int16_t)q[i] - (int16_t)p[i])) *\n      ((int32_t)((int16_t)q[i] - (int16_t)p[i]));\n  }\n  return (float)result;\n}\n\ninline float euclidian_distance(const uint8_t *p, const uint8_t *q, unsigned d) {\n  int32_t result = 0;\n  for (int i = 0; i < d; i++) {\n    int32_t qi = (int32_t) p[i];\n    int32_t pi = (int32_t) q[i];\n    result += (qi - pi) * (qi - pi);\n  }\n  return (float)result;\n}\n\ninline float euclidian_distance(const uint16_t *p, const uint16_t *q, unsigned d) {\n  int64_t result = 0;\n  for (int i = 0; i < d; i++) {\n    int32_t qi = (int32_t) p[i];\n    int32_t pi = (int32_t) q[i];\n    result += (qi - pi) * (qi - pi);\n  }\n  return (float) (result >> 8);\n}\n\ninline float euclidian_distance(const int8_t *p, const int8_t *q, unsigned d) {\n  int result = 0;\n  for (int i = 0; i < d; i++) {\n    result += ((int32_t)((int16_t)q[i] - (int16_t)p[i])) *\n      ((int32_t)((int16_t)q[i] - (int16_t)p[i]));\n  }\n  return (float)result;\n}\n\nfloat euclidian_distance(const float *p, const float *q, unsigned d) {\n  // efanna2e::DistanceL2 distfunc;\n  // return distfunc.compare(p, q, d);\n  float result = 0.0;\n  for (int i = 0; i < d; i++)\n    result += (q[i] - p[i]) * (q[i] - p[i]);\n  return (float)result;\n}\n\ntemplate<typename T_, long range=(1l << sizeof(T_)*8) - 1>\nstruct Euclidian_Point {\n  using distanceType = float;\n  using T = T_;\n  using byte = uint8_t;\n\n  struct parameters {\n    float slope;\n    int32_t offset;\n    int dims;\n    int num_bytes() const {return dims * sizeof(T);}\n    parameters() : slope(0), offset(0), dims(0) {}\n    parameters(int dims) : slope(1.0), offset(0), dims(dims) {}\n    parameters(float min_val, float max_val, int dims)\n      : slope(range / (max_val - min_val)),\n        offset((int32_t) round(min_val * slope)),\n        dims(dims) {}\n  };\n\n  static distanceType d_min() {return 0;}\n  static constexpr bool is_metric = true;\n  T operator[](long i) const {return *(values + i);}\n\n  float distance(const Euclidian_Point& x) const {\n    return euclidian_distance(this->values, x.values, params.dims);\n  }\n\n  float translate_distance(double r) const {\n    if constexpr (sizeof(T) == 2)\n                   return r * params.slope * params.slope / 256;\n    else return r * params.slope * params.slope;\n  }\n\n  void normalize() {\n    double norm = 0.0;\n    for (int j = 0; j < params.dims; j++)\n      norm += values[j] * values[j];\n    norm = std::sqrt(norm);\n    if (norm == 0) norm = 1.0;\n    for (int j = 0; j < params.dims; j++)\n      values[j] = values[j] / norm;\n  }\n\n  void prefetch() const {\n    int l = (params.dims * sizeof(T) - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n\n  long id() const {return id_;}\n\n  Euclidian_Point() : values(nullptr), id_(-1), params(0) {}\n\n  Euclidian_Point(byte* values, long id, parameters params)\n    : values((T*) values), id_(id), params(params) {}\n\n  // template <typename Point>\n  // Euclidian_Point(const Point& p, const parameters& params) : id_(-1), params(params) {\n  //   float slope = params.slope;\n  //   int32_t offset = params.offset;\n  //   float min_val = std::floor(offset / slope);\n  //   float max_val = std::ceil((range + offset) / slope);\n  //   values = new T[params.dims];\n  //   if (slope == 1 && offset == 0) {\n  //     for (int j = 0; j < params.dims; j++)\n  //       values[j] = (T) p[j];\n  //   } else {\n  //     for (int j = 0; j < params.dims; j++) {\n  //       auto x = p[j];\n  //       if (x < min_val || x > max_val) {\n  //         std::cout << x << \" is out of range: [\" << min_val << \",\" << max_val << \"]\" << std::endl;\n  //         abort();\n  //       }\n  //       int64_t r = (int64_t) (std::round(x * slope)) - offset;\n  //       if (r < 0 || r > range) {\n  //         std::cout << \"out of range: \" << r << \", \" << range << \", \" << x << \", \" << std::round(x * slope) - offset << \", \" << slope << \", \" << offset << std::endl;\n  //         abort();\n  //       }\n  //       values[j] = (T) r;\n  //     }\n  //   }\n  // }\n\n  bool operator==(const Euclidian_Point& q) const {\n    for (int i = 0; i < params.dims; i++) {\n      if (values[i] != q.values[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  bool same_as(const Euclidian_Point& q) const {\n    return values == q.values;\n  }\n\n  template <typename Point>\n  static void translate_point(byte* byte_values, const Point& p, const parameters& params) {\n    T* values = (T*) byte_values;\n    float slope = params.slope;\n    int32_t offset = params.offset;\n    if (slope == 1.0 && offset == 00) \n      for (int j = 0; j < params.dims; j++)\n        values[j] = p[j];\n    else {\n      //float min_val = std::floor(offset / slope);\n      //float max_val = std::ceil((range + offset) / slope);\n      for (int j = 0; j < params.dims; j++) {\n        auto x = p[j];\n        // if (x < min_val || x > max_val) {\n        //   std::cout << x << \" is out of range: [\" << min_val << \",\" << max_val << \"]\" << std::endl;\n        //   abort();\n        // }\n        int64_t r = (int64_t) (std::round(x * slope)) - offset;\n        if (r < 0) r = 0;\n        if (r > range) r = range;\n        // if (r < 0 || r > range) {\n        //   std::cout << \"out of range: \" << r << \", \" << range << \", \" << x << \", \" << std::round(x * slope) - offset << \", \" << slope << \", \" << offset << std::endl;\n        //   abort();\n        // }\n        values[j] = (T) r;\n      }\n    }\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    long n = pr.size();\n    int dims = pr.dimension();\n    using MT = float; // typename PR::Point::T;\n    parlay::sequence<MT> mins(n, 0.0);\n    parlay::sequence<MT> maxs(n, 0.0);\n    parlay::sequence<bool> ni(n, true);\n    parlay::parallel_for(0, n, [&] (long i) {\n      for (int j = 0; j < dims; j++) {\n        ni[i] = ni[i] && (pr[i][j] >= 0) && (pr[i][j] - (long) pr[i][j]) == 0;\n        mins[i]= std::min<MT>(mins[i], pr[i][j]);\n        maxs[i]= std::max<MT>(maxs[i], pr[i][j]);}});\n    float min_val = *parlay::min_element(mins);\n    float max_val = *parlay::max_element(maxs);\n    bool all_ints = *parlay::min_element(ni);\n    if (all_ints) {\n      if (sizeof(T) == 1 && max_val < 256) max_val = 255;\n      else if (sizeof(T) == 2 && max_val < 65536) max_val = 65536;\n      min_val = 0;\n    }\n    std::cout << \"scalar quantization: min value = \" << min_val\n              << \", max value = \" << max_val << std::endl;\n    return parameters(min_val, max_val, dims);\n  }\n\n  parameters params;\n\nprivate:\n  T* values;\n  long id_;\n};\n\ntemplate <int jl_dims>\nstruct Euclidean_JL_Sparse_Point {\n  using distanceType = float;\n  using Data = std::bitset<jl_dims>;\n  using byte = uint8_t;\n  constexpr static int nz = 6; // number of non_zeros per row\n  \n  struct parameters {\n    std::vector<int> JL_indices;\n    int source_dims;\n    int num_bytes() const {return sizeof(Data);}\n    parameters() : source_dims(0) {}\n    parameters(int dims) : source_dims(dims) {}\n    parameters(std::vector<int> const& JL_indices,\n               int source_dims)\n      : JL_indices(JL_indices), source_dims(source_dims) {\n      std::cout << \"JL sparse quantization, dims = \" << jl_dims << std::endl;\n    }\n  };\n  \n  static constexpr bool is_metric = false;\n  \n  int8_t operator [] (long j) const {\n    Data* pbits = (Data*) values;\n    return (*pbits)[j] ? 1 : -1;}\n\n  float distance(const Euclidean_JL_Sparse_Point &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    return (*pbits ^ *qbits).count();\n  }\n\n  void prefetch() const {\n    int l = (sizeof(Data) - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n    \n  bool same_as(const Euclidean_JL_Sparse_Point& q){\n    return &q == this;\n  }\n\n  long id() const {return id_;}\n\n  Euclidean_JL_Sparse_Point(byte* values, long id, const parameters& p)\n    : values(values), id_(id) {}\n\n  bool operator==(const Euclidean_JL_Sparse_Point &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    return *pbits == *qbits; }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  template <typename In_Point>\n  static void translate_point(byte* values, const In_Point& p, const parameters& params) {\n    Data* bits = new (values) Data;\n    const std::vector<int>& jli = params.JL_indices;\n    for (int i = 0; i < jl_dims; i++) {\n      double vv = 0.0;\n      for (int j = 0; j < nz/2; j++) \n        vv += (float) p[jli[i * nz + j]];\n      for (int j = nz/2; j < nz; j++) \n        vv -= (float) p[jli[i * nz + j]];\n      (*bits)[i] = (vv > 0);\n    }\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    int source_dims = pr.dimension();\n    std::vector<int> JL_indices(jl_dims * nz);\n    std::mt19937 rng;\n    std::uniform_int_distribution<std::mt19937::result_type> dist_i(0,source_dims);\n    for (int i = 0; i < jl_dims * nz; i++) {\n      JL_indices[i] = dist_i(rng);\n    }\n    return parameters(JL_indices, source_dims);\n  }\n\nprivate:\n  byte* values;\n  long id_;\n};\n\nstruct Euclidean_Bit_Point {\n  using distanceType = float;\n  using Data = std::bitset<64>;\n  using byte = uint8_t;\n  \n  struct parameters {\n    int dims;\n    long median;\n    int num_bytes() const {return ((dims - 1) / 64 + 1) * 8;}\n    parameters() : dims(0) {}\n    parameters(int dims, long median)\n      : dims(dims), median(median) {\n      std::cout << \"single-bit quantization with median: \" << median << std::endl;\n    }\n  };\n  \n  static constexpr bool is_metric = false;\n  \n  int8_t operator [] (long j) const {\n    Data* pbits = (Data*) values;\n    return pbits[j/64][j%64];\n  }\n\n  float distance(const Euclidean_Bit_Point &q) const {\n    int num_blocks = (params.dims - 1)/64 + 1;\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    int cnt = 0;\n    for (int i=0; i < num_blocks; i++)\n      cnt +=(*pbits ^ *qbits).count();\n    return cnt;\n  }\n\n  void prefetch() const {\n    int l = (params.num_bytes() - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n    \n  bool same_as(const Euclidean_Bit_Point& q){\n    return &q == this;\n  }\n\n  long id() const {return id_;}\n\n  Euclidean_Bit_Point(byte* values, long id, const parameters& params)\n    : values(values), id_(id), params(params) {}\n\n  bool operator==(const Euclidean_Bit_Point &q) const {\n    int num_blocks = (params.dims - 1)/64 + 1;\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    for (int i = 0; i < num_blocks; i++)\n      if (pbits[i] != qbits[i]) return false;\n    return true;\n  }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  template <typename In_Point>\n  static void translate_point(byte* values, const In_Point& p, const parameters& params) {\n    Data* pbits = (Data*) values;\n    for (int i = 0; i < params.dims; i++)\n      pbits[i/64][i%64] = p[i] > params.median;\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    long n = pr.size();\n    int dims = pr.dimension();\n    long len = n * dims;\n    parlay::sequence<typename PR::Point::T> vals(len);\n    parlay::parallel_for(0, n, [&] (long i) {\n      for (int j = 0; j < dims; j++) \n        vals[i * dims + j] = pr[i][j];\n    });\n    parlay::sort_inplace(vals);\n    long median = vals[n*dims/2];\n    return parameters(dims, median);\n  }\n\nprivate:\n  byte* values;\n  long id_;\n  parameters params;\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/graph.h",
    "content": "// This code is part of the Parlay Project\n// Copyright (c) 2024 Guy Blelloch, Magdalen Dobson and the Parlay team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n//#define Flexible\n#pragma once\n#ifdef Flexible\n#include \"simpleGraph.h\"\n#else\n\n#include <algorithm>\n#include <fcntl.h>\n#include <iostream>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/internal/file_map.h\"\n\n#include \"types.h\"\n\nnamespace parlayANN {\n  \ntemplate<typename indexType>\nstruct edgeRange{\n\n  size_t size() const {return edges[0];}\n\n  indexType id() const {return id_;}\n\n  edgeRange() : edges(parlay::make_slice<indexType*, indexType*>(nullptr, nullptr)) {}\n\n  edgeRange(indexType* start, indexType* end, indexType id)\n    : edges(parlay::make_slice<indexType*, indexType*>(start,end)), id_(id) {\n    maxDeg = edges.size() - 1;\n  }\n\n  indexType operator [] (indexType j) const {\n    if (j > edges[0]) {\n      std::cout << \"ERROR: index exceeds degree while accessing neighbors\" << std::endl;\n      abort();\n    } else return edges[j+1];\n  }\n\n  void append_neighbor(indexType nbh){\n    if (edges[0] == maxDeg) {\n      std::cout << \"ERROR in append_neighbor: cannot exceed max degree \"\n                << maxDeg << std::endl;\n      abort();\n    } else {\n      edges[edges[0]+1] = nbh;\n      edges[0] += 1;\n    }\n  }\n\n  template<typename rangeType>\n  void update_neighbors(const rangeType& r){\n    if (r.size() > maxDeg) {\n      std::cout << \"ERROR in update_neighbors: cannot exceed max degree \"\n                << maxDeg << std::endl;\n      abort();\n    }\n    edges[0] = r.size();\n    for (int i = 0; i < r.size(); i++) {\n      edges[i+1] = r[i];\n    }\n  }\n\n  template<typename rangeType>\n  void append_neighbors(const rangeType& r){\n    if (r.size() + edges[0] > maxDeg) {\n      std::cout << \"ERROR in append_neighbors for point \" << id_\n                << \": cannot exceed max degree \" << maxDeg << std::endl;\n      std::cout << edges[0] << std::endl;\n      std::cout << r.size() << std::endl;\n      abort();\n    }\n    for (int i = 0; i < r.size(); i++) {\n      edges[edges[0] + i + 1] = r[i];\n    }\n    edges[0] += r.size();\n  }\n\n  void clear_neighbors(){\n    edges[0] = 0;\n  }\n\n  void prefetch() const {\n    int l = ((edges[0] + 1) * sizeof(indexType))/64;\n    for (int i = 0; i < l; i++)\n      __builtin_prefetch((char*) edges.begin() + i *  64);\n  }\n\n  template<typename F>\n  void sort(F&& less){\n    std::sort(edges.begin() + 1, edges.begin() + 1 + edges[0], less);}\n\n  indexType* begin() const {return edges.begin() + 1;}\n\n  indexType* end() const {return edges.begin() + 1 + edges[0];}\n\nprivate:\n  parlay::slice<indexType*, indexType*> edges;\n  long maxDeg;\n  indexType id_;\n};\n\ntemplate<typename indexType_>\nstruct Graph{\n  using indexType = indexType_;\n  \n  long max_degree() const {return maxDeg;}\n  size_t size() const {return n;}\n\n  Graph(){}\n\n  void allocate_graph(long maxDeg, size_t n) {\n    long cnt = n * (maxDeg + 1);\n    long num_bytes = cnt * sizeof(indexType);\n    indexType* ptr = (indexType*) aligned_alloc(1l << 21, num_bytes);\n    madvise(ptr, num_bytes, MADV_HUGEPAGE);\n    parlay::parallel_for(0, cnt, [&] (long i) {ptr[i] = 0;});\n    graph = std::shared_ptr<indexType[]>(ptr, std::free);\n  }\n\n  Graph(long maxDeg, size_t n) : maxDeg(maxDeg), n(n) {\n    allocate_graph(maxDeg, n);\n  }\n\n  Graph(char* gFile){\n    std::ifstream reader(gFile);\n    if (!reader.is_open()) {\n      std::cout << \"graph file \" << gFile << \" not found\" << std::endl;\n      abort();\n    }\n\n    //read num points and max degree\n    indexType num_points;\n    indexType max_deg;\n    reader.read((char*)(&num_points), sizeof(indexType));\n    n = num_points;\n    reader.read((char*)(&max_deg), sizeof(indexType));\n    maxDeg = max_deg;\n    std::cout << \"Graph: detected \" << num_points\n              << \" points with max degree \" << max_deg << std::endl;\n\n    //read degrees and perform scan to find offsets\n    indexType* degrees_start = new indexType[n];\n    reader.read((char*) (degrees_start), sizeof(indexType) * n);\n    indexType* degrees_end = degrees_start + n;\n    parlay::slice<indexType*, indexType*> degrees0 =\n      parlay::make_slice(degrees_start, degrees_end);\n    auto degrees = parlay::tabulate(degrees0.size(), [&] (size_t i){\n      return static_cast<size_t>(degrees0[i]);});\n    auto [o, total] = parlay::scan(degrees);\n    auto offsets = o;\n    std::cout << \"Total edges read from file: \" << total << std::endl;\n    offsets.push_back(total);\n\n    allocate_graph(max_deg, n);\n\n    //write 1000000 vertices at a time\n    size_t BLOCK_SIZE = 1000000;\n    size_t index = 0;\n    size_t total_size_read = 0;\n    while(index < n){\n      size_t g_floor = index;\n      size_t g_ceiling = g_floor + BLOCK_SIZE <= n ? g_floor + BLOCK_SIZE : n;\n      size_t total_size_to_read = offsets[g_ceiling] - offsets[g_floor];\n      indexType* edges_start = new indexType[total_size_to_read];\n      reader.read((char*) (edges_start), sizeof(indexType) * total_size_to_read);\n      indexType* edges_end = edges_start + total_size_to_read;\n      parlay::slice<indexType*, indexType*> edges =\n        parlay::make_slice(edges_start, edges_end);\n      indexType* gr = graph.get();\n      parlay::parallel_for(g_floor, g_ceiling, [&] (size_t i){\n        gr[i * (maxDeg + 1)] = degrees[i];\n        for(size_t j = 0; j < degrees[i]; j++){\n          gr[i * (maxDeg + 1) + 1 + j] = edges[offsets[i] - total_size_read + j];\n        }\n      });\n      total_size_read += total_size_to_read;\n      index = g_ceiling;\n      delete[] edges_start;\n    }\n    delete[] degrees_start;\n  }\n\n  void save(char* oFile) {\n    std::cout << \"Writing graph with \" << n\n              << \" points and max degree \" << maxDeg\n              << \" to \" << oFile \n              << std::endl;\n    parlay::sequence<indexType> preamble =\n      {static_cast<indexType>(n), static_cast<indexType>(maxDeg)};\n    parlay::sequence<indexType> sizes = parlay::tabulate(n, [&] (size_t i){\n      return static_cast<indexType>((*this)[i].size());});\n    std::ofstream writer;\n    writer.open(oFile, std::ios::binary | std::ios::out);\n    writer.write((char*) preamble.begin(), 2 * sizeof(indexType));\n    writer.write((char*) sizes.begin(), sizes.size() * sizeof(indexType));\n    size_t BLOCK_SIZE = 1000000;\n    size_t index = 0;\n    while(index < n){\n      size_t floor = index;\n      size_t ceiling = index + BLOCK_SIZE <= n ? index + BLOCK_SIZE : n;\n      auto edge_data = parlay::tabulate(ceiling - floor, [&] (size_t i){\n        return parlay::tabulate(sizes[i + floor], [&] (size_t j){\n          return (*this)[i + floor][j];});\n      });\n      parlay::sequence<indexType> data = parlay::flatten(edge_data);\n      writer.write((char*)data.begin(), data.size() * sizeof(indexType));\n      index = ceiling;\n    }\n    writer.close();\n  }\n\n  edgeRange<indexType> operator [] (indexType i) const {\n    if (i > n) {\n      std::cout << \"ERROR: graph index out of range: \" << i << std::endl;\n      abort();\n    }\n    return edgeRange<indexType>(graph.get() + i * (maxDeg + 1),\n                                graph.get() + (i + 1) * (maxDeg + 1),\n                                i);\n  }\n\n  ~Graph(){}\n\nprivate:\n  size_t n;\n  long maxDeg;\n  std::shared_ptr<indexType[]> graph;\n};\n\n} // end namespace\n#endif // flexible\n"
  },
  {
    "path": "algorithms/utils/graph_reorder.h",
    "content": "#include <atomic>\n#include <limits>\n#include <optional>\n#include <tuple>\n#include <utility>\n\n#include <parlay/delayed.h>\n#include <parlay/parallel.h>\n#include <parlay/primitives.h>\n#include <parlay/random.h>\n#include <parlay/sequence.h>\n#include <parlay/utilities.h>\n#include <parlay/internal/get_time.h>\n\nusing vertex = int;\nusing w_type = float;\nusing edge_id = int;\n\nusing edge = std::pair<vertex,vertex>;\nusing w_edge = std::pair<edge,w_type>;\nstruct tagged_w_type {w_type w; edge_id i;};\nbool greater(tagged_w_type a, tagged_w_type b) {\n  return (a.w > b.w) ? true : ((a.w == b.w) ? a.i > b.i : false);}\nstruct vertex_info {\n  std::atomic<tagged_w_type> tw;\n  vertex size = 1;\n};\n\n// Uses recursive graph contraction to renumber a graph\n//   E : sequence of weighted edges (only needed in one direction)\n//   V : remaining vertices\n//   Sizes : keeps size of contracted vertices on the way\n//           down the recursion, and offsets on the way up\n//   W : used for temporary space to write priorities\n//   P : used for temporary space to write parent of contracted vertex\n//   m : original number of edges (not currently used)\n// Idea: each round of contraction identifies edges (u,v) that maximize\n//     w(u,v)/(|u||v|) on both u and v.  These edges are contracted.\n//     |u| is the number of vertices in the component and\n//     w(u,v) is the number of edges between components u and v.\nvoid recursive_reorder(parlay::sequence<w_edge>& E,\n                       parlay::sequence<vertex>& V,\n                       parlay::sequence<vertex_info>& W,\n                       parlay::sequence<vertex>& P,\n                       int i,\n                       long m) {\n  // std::cout << E.size() << \", \" << V.size() << std::endl;\n  \n  // Base case: need to scan if more than one component\n  if (i > 300 || E.size() == 0) { \n    auto vsizes = parlay::tabulate(V.size(), [&] (long i) {return W[V[i]].size;});\n    auto [offsets, sum] = parlay::scan(vsizes);\n    parlay::parallel_for(0, V.size(), [&] (long i) {W[V[i]].size = offsets[i];});\n    return;\n  }\n\n  // Write with max into W the priority (w(u,v)/(|u||v|)) to each endpoint\n  // Priorities are tagged with id to break ties\n  // Must firsrt clear W at all active vertices\n  float empty = std::numeric_limits<float>::lowest();\n  parlay::for_each(V, [&] (vertex& v) {\n      W[v].tw.store(tagged_w_type{empty, 0});});\n  parlay::parallel_for(0, E.size(), [&] (edge_id i) {\n      auto [u, v] = E[i].first;\n      auto w = tagged_w_type{E[i].second / (W[u].size * W[v].size), i};\n      parlay::write_min(&(W[v].tw), w, greater);\n      parlay::write_min(&(W[u].tw), w, greater);});\n\n  // Check for each active vertex u which edge (u,v) won on it.  If\n  // the edge also won on v, then it is matched, we contract v into u,\n  // and return the edge along with the old weight of u.\n  auto matches = parlay::map_maybe(V, [&] (vertex& u) {\n      long i = W[u].tw.load().i;\n      if (W[u].tw.load().w != empty && E[i].first.first == u) {\n        vertex v = E[i].first.second;\n        if (W[v].tw.load().i == i) {\n          vertex usize = W[u].size;\n          W[u].size += W[v].size;\n          P[v] = u;\n          return std::optional(std::tuple(u, v, usize));\n        }\n      }\n      return std::optional<std::tuple<vertex,vertex,vertex>>();});\n  \n  // Update edge endpoints and remove self edges\n  E = parlay::map_maybe(E, [&] (w_edge e) {\n        auto [u,v] = e.first;\n        vertex pu = P[u];\n        vertex pv = P[v];\n        if (pu > pv) std::swap(pu,pv); // keep oriented low to high\n        if (pu == pv) return std::optional<w_edge>();\n        return std::optional(w_edge(edge(pu, pv), e.second));});\n  \n  // Combine redundant edges\n  // For efficiency, only do every three steps\n  if (i % 4 == 3)\n    E = parlay::reduce_by_key(E);\n\n  // These are the remaining vertices after contraction\n  V = parlay::filter(V, [&] (vertex v) {return P[v] == v;});\n  \n  // recurse\n  recursive_reorder(E, V, W, P, i+1, m);\n\n  // update Sizes to give right offsets\n  parlay::for_each(matches, [&] (auto match) {\n        auto [u,v,usize] = match;\n        W[v].size = W[u].size + usize;});\n}\n\n// E is a sequence of edges, only needed in one direction\n// n is the number of vertices\nparlay::sequence<vertex> graph_reorder(parlay::sequence<edge>& E, long n) {\n  E = parlay::random_shuffle(E); // randomly permute the edges\n\n  // Initialize the five arguments\n  auto WE = parlay::map(E, [&] (edge e) { return w_edge(e,1); });\n  auto V = parlay::tabulate(n, [] (vertex i) {return i;});\n  parlay::sequence<vertex_info> W(n);\n  auto P = V;\n\n  // Call main routine\n  recursive_reorder(WE, V, W, P, 0, E.size());\n  return parlay::map(W, [] (auto& w) {return w.size;});\n}\n"
  },
  {
    "path": "algorithms/utils/hashset.h",
    "content": "#ifndef ALGORITHMS_ANN_HASHSET_H_\n#define ALGORITHMS_ANN_HASHSET_H_\n\n#include <vector>\n#include <cmath>\nnamespace parlayANN {\n\n// a hashset that enters integer keys and can give a false negative\n// grows as needed\n//   hashset x(n); : creates an empty hashset x of initial capacity n\n//   x(i) : returns true if i in set, otherwise adds i to set and returns false\n  template <typename K>\n  struct hashset {\n    static constexpr K empty = (K) -1;\n    int bits;\n    std::vector<K> entries;\n    size_t mask = 0;\n    long num_entries = 0;\n    size_t hash(K const& k) const noexcept {\n      return k * UINT64_C(0xbf58476d1ce4e5b9); }\n\n    bool operator () (K a) {\n      int loc = hash(a) & mask;\n      if (entries[loc] == a) return true;\n      if (num_entries > entries.size()/2) {\n        bits = bits + 1;\n        std::vector<K> new_entries(1ul << bits, empty);\n        mask = new_entries.size() - 1;\n        swap(entries, new_entries);\n        for (auto k : new_entries)\n          if (k != empty) {\n            int loc = hash(k) & mask;\n            while (entries[loc] != empty && entries[loc] != k)\n              loc = (loc + 1) & mask;\n            entries[loc] = k;\n            num_entries++;\n          }\n      }\n      if (entries[loc] != empty) {\n        loc = (loc + 1) & mask;\n        while (entries[loc] != -1 && entries[loc] != a)\n          loc = (loc + 1) & mask;\n        if (entries[loc] == a) return true;\n      }\n      entries[loc] = a;\n      num_entries++;\n      return false;\n    }\n  \n    hashset(long n) :\n      bits(std::ceil(std::log2(n))),\n      entries(std::vector<K>((1ul << bits), -1)),\n      mask(entries.size() - 1)\n    {}\n  };\n\n}\n#endif // ALGORITHMS_ANN_HASHSET_H_\n"
  },
  {
    "path": "algorithms/utils/jl_point.h",
    "content": "#pragma once\n\n#include <algorithm>\n#include <iostream>\n#include <bitset>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/internal/file_map.h\"\n#include \"mips_point.h\"\n#include \"types.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nnamespace parlayANN {\n\ntemplate <int jl_dims = 128>\nstruct Mips_JL_Point {\n  using T = int8_t;\n  using distanceType = float;\n  using Point = Quantized_Mips_Point<8>;\n  using Params = typename Point::parameters;\n  using byte = uint8_t;\n  \n  struct parameters {\n    std::vector<int8_t> JL_vects;\n    Params mips_params;\n    int dims;\n    int num_bytes() const {return mips_params.dims;}\n    parameters() : dims(0) {}\n    parameters(int dims) : dims(dims) {}\n    parameters(std::vector<int8_t> const& JL_vects, int dims, int d)\n      // vectors are normalized so few values will be greater than .3\n      : JL_vects(JL_vects), dims(dims), mips_params(.3, d) {}\n  };\n\n  static constexpr bool is_metric = false;\n  \n  T operator [] (long j) const {return pt[j];}\n\n  float distance(const Mips_JL_Point &q) const {\n    return pt.distance(q.pt);\n  }\n\n  void prefetch() const { pt.prefetch(); }\n\n  bool same_as(const Mips_JL_Point& q){\n    return pt.same_as(q.pt);\n  }\n\n  long id() const {return pt.id();}\n\n  Mips_JL_Point(byte* values, long id, const parameters& p) \n    : pt(Point(values, id, p.mips_params)), params(&p) {}\n\n  bool operator==(const Mips_JL_Point &q) const {\n    return pt == q.pt; }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  template <typename In_Point>\n  static void translate_point(byte* values, const In_Point& p, const parameters& params) {\n    int dims = params.dims;\n    const std::vector<int8_t>& jlv = params.JL_vects;\n    int d = params.mips_params.dims;\n    std::vector<float> v(d);\n    double nn = 0.0;\n    for (int i = 0; i < d; i++) {\n      double vv = 0.0;\n      for (int j = 0; j < dims; j++) {\n        vv += (float) p[j] * (float) jlv[i * dims + j];\n      }\n      v[i] = vv;\n      nn += vv * vv;\n    }\n    double norm = 1.0 / sqrt(nn);\n    for (int i = 0; i < d; i++) {\n      v[i] = v[i] * norm;\n    }\n    \n    Point::translate_point(values, v, params.mips_params);\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    int dims = pr.dimension();\n    std::vector<int8_t> JL_vects(jl_dims * dims);\n    std::mt19937 rng;\n    std::uniform_int_distribution<std::mt19937::result_type> dist(0,1);\n    for (int i = 0; i < jl_dims * dims; i++)\n      JL_vects[i] = (dist(rng) == 0) ? -1 : 1;\n    return parameters(std::move(JL_vects), dims, jl_dims);\n  }\n\nprivate:\n  Point pt;\n  const parameters* params;\n};\n\ntemplate <int jl_dims>\nstruct Mips_JL_Bit_Point {\n  using distanceType = float;\n  using Data = std::bitset<jl_dims>;\n  using byte = uint8_t;\n  \n  struct parameters {\n    std::vector<int8_t> JL_vects;\n    int source_dims;\n    int dims;\n    int num_bytes() const {return sizeof(Data);}\n    parameters() : source_dims(0) {}\n    parameters(int dims) : source_dims(dims) {}\n    parameters(std::vector<int8_t> const& JL_vects, int source_dims)\n      : JL_vects(JL_vects), source_dims(source_dims), dims(jl_dims) {\n      std::cout << \"JL dense quantization, dims = \" << jl_dims << std::endl;\n    }\n  };\n  \n  static constexpr bool is_metric  = false;\n  \n  int8_t operator [] (long j) const {\n    Data* pbits = (Data*) values;\n    return (*pbits)[j] ? 1 : -1;}\n\n  float distance(const Mips_JL_Bit_Point &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    return (*pbits ^ *qbits).count();\n  }\n\n  void prefetch() const {\n    int l = (sizeof(Data) - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n    \n  bool same_as(const Mips_JL_Bit_Point& q){\n    return &q == this;\n  }\n\n  long id() const {return id_;}\n\n  Mips_JL_Bit_Point(byte* values, long id, const parameters& p)\n    : values(values), id_(id) {}\n\n  bool operator==(const Mips_JL_Bit_Point &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    return *pbits == *qbits; }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  template <typename In_Point>\n  static void translate_point(byte* values, const In_Point& p, const parameters& params) {\n    Data* bits = new (values) Data;\n    const std::vector<int8_t>& jlv = params.JL_vects;\n    for (int i = 0; i < jl_dims; i++) {\n      double vv = 0.0;\n      for (int j = 0; j < params.source_dims; j++) {\n        vv += (float) p[j] * (float) jlv[i * params.source_dims + j];\n      }\n      (*bits)[i] = (vv > 0);\n    }\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    int source_dims = pr.dimension();\n    std::vector<int8_t> JL_vects(jl_dims * source_dims);\n    std::mt19937 rng;\n    std::uniform_int_distribution<std::mt19937::result_type> dist(0,1);\n    for (int i = 0; i < jl_dims * source_dims; i++)\n      JL_vects[i] = (dist(rng) == 0) ? -1 : 1;\n    return parameters(std::move(JL_vects), source_dims);\n  }\n\nprivate:\n  byte* values;\n  long id_;\n};\n\ntemplate <int jl_dims>\nstruct Mips_JL_Sparse_Point {\n  using distanceType = float;\n  using Data = std::bitset<jl_dims>;\n  using byte = uint8_t;\n  constexpr static int nz = 5; // number of non_zeros per row\n  \n  struct parameters {\n    std::vector<int8_t> JL_signs;\n    std::vector<int> JL_indices;\n    int source_dims;\n    int dims;\n    int num_bytes() const {return sizeof(Data);}\n    parameters() : source_dims(0) {}\n    parameters(int dims) : source_dims(dims) {}\n    parameters(std::vector<int8_t> const& JL_signs,\n               std::vector<int> const& JL_indices,\n               int source_dims)\n      : JL_signs(JL_signs), JL_indices(JL_indices), source_dims(source_dims), dims(jl_dims) {\n      std::cout << \"JL sparse quantization, dims = \" << jl_dims << std::endl;\n    }\n  };\n  \n  static constexpr bool is_metric = false;\n  \n  int8_t operator [] (long j) const {\n    Data* pbits = (Data*) values;\n    return (*pbits)[j] ? 1 : -1;}\n\n  float distance(const Mips_JL_Sparse_Point &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    return (*pbits ^ *qbits).count();\n  }\n\n  void prefetch() const {\n    int l = (sizeof(Data) - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n    \n  bool same_as(const Mips_JL_Sparse_Point& q){\n    return &q == this;\n  }\n\n  long id() const {return id_;}\n\n  Mips_JL_Sparse_Point(byte* values, long id, const parameters& p)\n    : values(values), id_(id) {}\n\n  bool operator==(const Mips_JL_Sparse_Point &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    return *pbits == *qbits; }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  template <typename In_Point>\n  static void translate_point(byte* values, const In_Point& p, const parameters& params) {\n    Data* bits = new (values) Data;\n    const std::vector<int8_t>& jls = params.JL_signs;\n    const std::vector<int>& jli = params.JL_indices;\n    for (int i = 0; i < jl_dims; i++) {\n      double vv = 0.0;\n      for (int j = 0; j < nz; j++) \n        vv += (float) p[jli[i * nz + j]] * jls[i * nz + j];\n      (*bits)[i] = (vv > 0);\n    }\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    int source_dims = pr.dimension();\n    std::vector<int8_t> JL_signs(jl_dims * nz);\n    std::vector<int> JL_indices(jl_dims * nz);\n    std::mt19937 rng;\n    std::uniform_int_distribution<std::mt19937::result_type> dist_s(0,1);\n    std::uniform_int_distribution<std::mt19937::result_type> dist_i(0,source_dims - 1);\n    for (int i = 0; i < jl_dims * nz; i++) {\n      JL_signs[i] = (dist_s(rng) == 0) ? -1 : 1;\n      JL_indices[i] = dist_i(rng);\n    }\n    return parameters(JL_signs, JL_indices, source_dims);\n  }\n\nprivate:\n  byte* values;\n  long id_;\n};\n\ntemplate <int jl_dims>\nstruct Mips_JL_Sparse_Point_Normalized {\n  using distanceType = float;\n  using Data = std::bitset<jl_dims>;\n  using byte = uint8_t;\n  constexpr static int nz = 5; // number of non_zeros per row\n  \n  struct parameters {\n    std::vector<int8_t> JL_signs;\n    std::vector<int> JL_indices;\n    int source_dims;\n    int dims;\n    int num_bytes() const {return sizeof(Data) + sizeof(float);}\n    parameters() : source_dims(0) {}\n    parameters(int dims) : source_dims(dims) {}\n    parameters(std::vector<int8_t> const& JL_signs,\n               std::vector<int> const& JL_indices,\n               int source_dims)\n      : JL_signs(JL_signs), JL_indices(JL_indices), source_dims(source_dims), dims(jl_dims) {\n      std::cout << \"JL sparse quantization, dims = \" << jl_dims << std::endl;\n    }\n  };\n  \n  static constexpr bool is_metric = false;\n  \n  int8_t operator [] (long j) const {\n    Data* pbits = (Data*) values;\n    return (*pbits)[j] ? 1 : -1;}\n\n  float distance(const Mips_JL_Sparse_Point_Normalized &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    float pr = *((float*) (values + sizeof(Data)));\n    float qr = *((float*) (q.values + sizeof(Data)));\n    return (*pbits ^ *qbits).count() * pr; // * qr;\n  }\n\n  void prefetch() const {\n    int l = (sizeof(Data) - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n    \n  bool same_as(const Mips_JL_Sparse_Point_Normalized& q){\n    return &q == this;\n  }\n\n  long id() const {return id_;}\n\n  Mips_JL_Sparse_Point_Normalized(byte* values, long id, const parameters& p)\n    : values(values), id_(id) {}\n\n  bool operator==(const Mips_JL_Sparse_Point_Normalized &q) const {\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    return *pbits == *qbits; }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  template <typename In_Point>\n  static void translate_point(byte* values, const In_Point& p, const parameters& params) {\n    Data* bits = new (values) Data;\n    float* radius = (float*) (values + sizeof(Data));\n    const std::vector<int8_t>& jls = params.JL_signs;\n    const std::vector<int>& jli = params.JL_indices;\n    double norm = 0.0;\n    for (int j = 0; j < params.source_dims; j++)\n      norm += p[j] * p[j];\n    *radius = std::sqrt(norm);\n    if (*radius > 0)\n      for (int i = 0; i < jl_dims; i++) {\n        double vv = 0.0;\n        for (int j = 0; j < nz; j++) \n          vv += (float) p[jli[i * nz + j]] * jls[i * nz + j];\n        (*bits)[i] = (vv > 0);\n    }\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    int source_dims = pr.dimension();\n    std::vector<int8_t> JL_signs(jl_dims * nz);\n    std::vector<int> JL_indices(jl_dims * nz);\n    std::mt19937 rng;\n    std::uniform_int_distribution<std::mt19937::result_type> dist_s(0,1);\n    std::uniform_int_distribution<std::mt19937::result_type> dist_i(0,source_dims);\n    for (int i = 0; i < jl_dims * nz; i++) {\n      JL_signs[i] = (dist_s(rng) == 0) ? -1 : 1;\n      JL_indices[i] = dist_i(rng);\n    }\n    return parameters(JL_signs, JL_indices, source_dims);\n  }\n\nprivate:\n  byte* values;\n  long id_;\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/mips_point.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <algorithm>\n#include <iostream>\n#include <bitset>\n#include <bit>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/internal/file_map.h\"\n#include \"types.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include \"NSGDist.h\"\n\nnamespace parlayANN {\n\n  inline float mips_distance(const uint8_t *p, const uint8_t *q, unsigned d) {\n    int result = 0;\n    for (int i = 0; i < d; i++) {\n      result += ((int32_t)q[i]) * ((int32_t)p[i]);\n    }\n    return -((float)result);\n  }\n\n  inline float mips_distance(const int8_t *p, const int8_t *q, unsigned d) {\n    int result = 0;\n    for (int i = 0; i < d; i++) {\n      result += ((int32_t)q[i]) * ((int32_t)p[i]);\n    }\n    return -((float)result);\n  }\n\n  inline float mips_distance(const float *p, const float *q, unsigned d) {\n    float result = 0;\n    for (int i = 0; i < d; i++) {\n      result += (q[i]) * (p[i]);\n    }\n    return -result;\n  }\n\ntemplate<typename T_>\nstruct Mips_Point {\n  using T = T_;\n  using distanceType = float;\n  using byte = uint8_t;\n  //template<typename C, typename range> friend struct Quantized_Mips_Point;\n\n  struct parameters {\n    int dims;\n    int num_bytes() const {return dims * sizeof(T);}\n    parameters() : dims(0) {}\n    parameters(int dims) : dims(dims) {}\n  };\n\n  static distanceType d_min() {return -std::numeric_limits<float>::max();}\n  static constexpr bool is_metric = false;\n  T operator [](long i) const {return *(values + i);}\n\n  float distance(const Mips_Point<T>& x) const {\n    return mips_distance(this->values, x.values, params.dims);\n  }\n\n  float translate_distance(float r) const {\n    return r;\n  }\n\n  void prefetch() const {\n    int l = (params.dims * sizeof(T) - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n\n  long id() const {return id_;}\n\n  Mips_Point() : values(nullptr), id_(-1), params(0) {}\n\n  Mips_Point(byte* values, long id, parameters params)\n    : values((T*) values), id_(id), params(params) {}\n\n  bool operator==(const Mips_Point<T>& q) const {\n    for (int i = 0; i < params.dims; i++) {\n      if (values[i] != q.values[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  bool same_as(const Mips_Point<T>& q) const {\n    return values == q.values;\n  }\n\n  void normalize() {\n    double norm = 0.0;\n    for (int j = 0; j < params.dims; j++)\n      norm += values[j] * values[j];\n    norm = std::sqrt(norm);\n    if (norm == 0) norm = 1.0;\n    float inv_norm = 1.0 / norm;\n    for (int j = 0; j < params.dims; j++)\n      values[j] = values[j] * inv_norm;\n  }\n\n  template <typename Point>\n  static void translate_point(byte* values, const Point& p, const parameters& params) {\n    for (int j = 0; j < params.dims; j++) ((T*) values)[j] = (T) p[j];\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    return parameters(pr.dimension());}\n\nprivate:\n  T* values;\n  long id_;\n  parameters params;\n};\n\n// template<typename T_, bool trim = false, int range = (1 << sizeof(T_)*8) - 1>\n// struct Quantized_Mips_Point{\n//   using T = T_;\n//   using distanceType = float;\n//   using byte = uint8_t;\n  \n//   struct parameters {\n//     float max_val;\n//     int dims;\n//     int num_bytes() const {return dims * sizeof(T);}\n//     parameters() : max_val(1), dims(0) {}\n//     parameters(int dims) : max_val(1), dims(dims) {}\n//     parameters(float max_val, int dims)\n//       : max_val(max_val), dims(dims) {}\n//   };\n\n//   static distanceType d_min() {return -std::numeric_limits<float>::max();}\n//   static bool is_metric() {return false;}\n  \n//   //T& operator [] (long j) const {if (j >= d) abort(); return *(values+j);}\n//   T operator [] (long i) const {return *(values + i);}\n\n//   float distance(int8_t* p, int8_t* q) const {\n//     int32_t result = 0;\n//     for (int i = 0; i < params.dims; i++){\n//       result += (int16_t) p[i] * (int16_t) q[i];\n//     }\n//     //return (float) (r * r - result);\n//     return (float) -result;\n//   }\n\n//   float distance(int16_t* p, int16_t* q) const {\n//     int64_t result = 0;\n//     for (int i = 0; i < params.dims; i++){\n//       result += (int32_t) p[i] * (int32_t) q[i];\n//     }\n//     return (float) -result;\n//   }\n\n//   float distance(const Quantized_Mips_Point &x) const {\n//     return distance(this->values, x.values);\n//   }\n\n//   void prefetch() const {\n//     int l = (params.dims * sizeof(T) - 1)/64 + 1;\n//     for (int i=0; i < l; i++)\n//       __builtin_prefetch(values + i * 64);\n//   }\n\n//   bool same_as(const Quantized_Mips_Point& q){\n//     return values == q.values;\n//   }\n\n//   long id() const {return id_;}\n\n//   Quantized_Mips_Point(byte* values, long id, parameters p)\n//     : values((T*) values), id_(id), params(p)\n//   {}\n\n//   bool operator==(const Quantized_Mips_Point &q) const {\n//     for (int i = 0; i < params.dims; i++) {\n//       if (values[i] != q.values[i]) {\n//         return false;\n//       }\n//     }\n//     return true;\n//   }\n\n//   void normalize() {\n//     std::cout << \"can't normalize quantized point\" << std::endl;\n//     abort();\n//   }\n\n//   template <typename Point>\n//   static void translate_point(byte* byte_values, const Point& p, const parameters& params) {\n//     T* values = (T*) byte_values;\n//     for (int j = 0; j < params.dims; j++) {\n//       float mv = params.max_val;\n//       float pj = p[j];\n//       if (pj < -mv) values[j] = - range/2 - 1;\n//       else if (pj > mv) values[j] = range/2;\n//       else {\n//         //if (pj < -mv || pj > mv) {\n//         //std::cout << pj << \" is out of range, should be in [\" << -mv << \":\" << mv << \"] \" << std::endl;\n//         //abort();\n//         //}\n//         int32_t x = std::round(pj * (range/2) / mv);\n//         values[j] = (T) x;\n//       }\n//     }\n//   }\n\n//   template <typename PR>\n//   static parameters generate_parameters(const PR& pr) {\n//     long n = pr.size();\n//     int dims = pr.dimension();\n//     long len = n * dims;\n//     parlay::sequence<typename PR::T> vals(len);\n//     parlay::parallel_for(0, n, [&] (long i) {\n//       for (int j = 0; j < dims; j++) \n//         vals[i * dims + j] = pr[i][j];\n//     });\n//     parlay::sort_inplace(vals);\n//     float min_val, max_val;\n//     if (trim) {\n//       float cutoff = .0001;\n//       min_val = vals[(long) (cutoff * len)];\n//       max_val = vals[(long) ((1.0-cutoff) * (len-1))];\n//     } else {\n//       min_val = vals[0];\n//       max_val = vals[len-1];\n//     }\n//     float bound = std::max(max_val, -min_val);\n\n//     // parlay::sequence<typename PR::T> mins(n);\n//     // parlay::sequence<typename PR::T> maxs(n);\n//     // parlay::parallel_for(0, n, [&] (long i) {\n//     //   mins[i] = 0.0;\n//     //   maxs[i] = 0.0;\n//     //   for (int j = 0; j < dims; j++) {\n//     //     mins[i]= std::min(mins[i], pr[i][j]);\n//     //     maxs[i]= std::max(maxs[i], pr[i][j]);}});\n//     // float min_val = *parlay::min_element(mins);\n//     // float max_val = *parlay::max_element(maxs);\n//     // float bound = std::max(max_val, -min_val);\n    \n    \n//     // if (sizeof(T) == 1) {\n//     //   auto x = parlay::flatten(parlay::tabulate(n, [&] (long i) {\n//     //     return parlay::tabulate(dims, [&] (long j) {\n//     //       return 128 + (int8_t) (std::round(pr[i][j] * (range/2) / bound));});}));\n//     //   auto y = parlay::histogram_by_index(x, 256);\n//     //   for (int i = 0; i < 256; i++)\n//     //     std::cout << i - 128 << \":\" << y[i] << \", \";\n//     //   std::cout << std::endl;\n//     // }\n//     std::cout << \"scalar quantization: min value = \" << min_val\n//               << \", max value = \" << max_val << std::endl;\n//     return parameters(bound, dims); // 1.7 for glove-100, 1.4 for nytimes, 1.5 for glove-25 but bad\n//   }\n\n// private:\n//   T* values;\n//   long id_;\n//   parameters params;\n// };\n\ntemplate<int bits, bool trim = false, int range = (1 << bits) - 1>\nstruct Quantized_Mips_Point{\n  using T = int16_t;\n  using distanceType = float; \n  using byte = uint8_t;\n  \n  struct parameters {\n    float max_val;\n    int dims;\n    float scale;\n    int num_bytes() const {return (dims * bits - 1) / 8 + 1;}\n    parameters() : max_val(1), dims(0) {}\n    parameters(int dims) : max_val(1), dims(dims) {}\n    parameters(float max_val, int dims)\n      : max_val(max_val), dims(dims), scale((range/2) / max_val) {}\n  };\n\n  static constexpr bool is_metric = false;\n  \n  int operator [] (long i) const {\n    if constexpr (bits <= 4) {\n      if (i & 1)\n        return ((int8_t) (values[i/2] & 240)) >> 4;\n      else\n        return ((int8_t) (values[i/2] << 4)) >> 4;\n    } else {\n      if constexpr (bits <= 8) {\n        return *(((int8_t*) values) + i);\n      } else {\n        return *(((int16_t*) values) + i);\n      }\n    }\n  }\n\n\n  distanceType distance_16(byte* p_, byte* q_) const {\n    int16_t* p = (int16_t*) p_;\n    int16_t* q = (int16_t*) q_;\n    int64_t result = 0;\n    for (int i = 0; i < params.dims; i++){\n      result += (int32_t) p[i] * (int32_t) q[i];\n    }\n    return (distanceType) -result;\n  }\n\n  distanceType distance_8(byte* p_, byte* q_) const {\n    int8_t* p = (int8_t*) p_;\n    int8_t* q = (int8_t*) q_;\n    int32_t result = 0;\n    for (int i = 0; i < params.dims; i++){\n      result += (int16_t) p[i] * (int16_t) q[i];\n    }\n    return (distanceType) -result;\n  }\n\n  distanceType distance_4(byte* p_, byte* q_) const {\n    int8_t* p = (int8_t*) p_;\n    int8_t* q = (int8_t*) q_;\n    int32_t result = 0;\n    int8_t mask = -16; // bit representation is 11110000, used as mask to extract high 4 bits\n    for (int i = 0; i < params.dims/2; i++) {\n      result += (int16_t) ((int8_t) (p[i] << 4)) * (int16_t) ((int8_t) (q[i] << 4));\n    }\n    for (int i = 0; i < params.dims/2; i++){\n      result += (int16_t) (p[i] & mask) * (int16_t) (q[i] & mask);\n    }\n    return (distanceType) -result;\n  }\n\n  distanceType distance(const Quantized_Mips_Point &x) const {\n    if constexpr (bits <= 4) {\n      return distance_4(this->values, x.values);\n    } else {\n      if constexpr (bits <= 8) {\n        return distance_8(this->values, x.values);\n      } else {\n        return distance_16(this->values, x.values);\n      }\n    }\n  }\n\n  float translate_distance(float r) const {\n    return r * params.scale * params.scale;\n  }\n  \n  void prefetch() const {\n    int l = (params.num_bytes() - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch(values + i * 64);\n  }\n\n  bool same_as(const Quantized_Mips_Point& q){\n    return values == q.values;\n  }\n\n  long id() const {return id_;}\n\n  Quantized_Mips_Point(byte* values, long id, parameters p)\n    : values(values), id_(id), params(p)\n  {}\n\n  bool operator==(const Quantized_Mips_Point &q) const {\n    for (int i = 0; i < params.dims; i++) {\n      if (values[i] != q.values[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  static void assign(byte* values, int i, int v) {\n    if constexpr (bits <= 4) {\n      byte* p = values + i/2;\n      if (i & 1) {\n        *p = (*p & 15) | (v << 4);\n      } else {\n        *p = (*p & 240) | v;\n      }\n    } else {\n      if constexpr (bits <= 8) {\n        ((int8_t*) values)[i] = (int8_t) v;\n      } else {\n        ((int16_t*) values)[i] = (int16_t) v;\n      }\n    }\n  }\n  \n  template <typename Point>\n  static void translate_point(byte* byte_values, const Point& p, const parameters& params) {\n    for (int j = 0; j < params.dims; j++) {\n      float mv = params.max_val;\n      float scale = params.scale; //(range/2) / mv;\n      float pj = p[j];\n      // cap if underflow or overflow\n      if (pj < -mv) assign(byte_values, j, - range/2); // - 1);\n      else if (pj > mv) assign(byte_values, j, range/2);\n      else {\n        int32_t v = std::round(pj * scale); \n        assign(byte_values, j, v);\n      }\n    }\n  }\n\n  \n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    long n = pr.size();\n    int dims = pr.dimension();\n    float min_val, max_val;\n    auto min_per_point = parlay::delayed_tabulate(n, [&](size_t i) {\n      float min = 0.0;\n      auto p = pr[i];\n      for (int j = 0; j < dims; j++) min = std::min<float>(min, p[j]);\n      return min;\n    });\n    auto max_per_point = parlay::delayed_tabulate(n, [&](size_t i) {\n      float max = 0.0;\n      auto p = pr[i];\n      for (int j = 0; j < dims; j++) max = std::max<float>(max, p[j]);\n      return max;\n    });\n    if (trim) {\n      double cutoff = .0001;\n      size_t min_rank = cutoff * n;\n      size_t max_rank = (1.0 - cutoff) * (n - 1);\n\n      min_val = parlay::kth_smallest_copy(min_per_point, min_rank);\n      max_val = parlay::kth_smallest_copy(max_per_point, max_rank);\n      std::cout << \"mips scalar quantization to \" << bits\n                << \" bits. trimmed to: min = \" << min_val\n                << \", max = \" << max_val << std::endl;\n    } else {\n      min_val = parlay::reduce(min_per_point, parlay::minm<float>());\n      max_val = parlay::reduce(max_per_point, parlay::maxm<float>());\n      std::cout << \"mips scalar quantization to \" << bits\n                << \" bits: min value = \" << min_val\n                << \", max value = \" << max_val << std::endl;\n    }\n    float bound = std::max(max_val, -min_val);\n\n    // parlay::sequence<typename PR::T> mins(n);\n    // parlay::sequence<typename PR::T> maxs(n);\n    // parlay::parallel_for(0, n, [&] (long i) {\n    //   mins[i] = 0.0;\n    //   maxs[i] = 0.0;\n    //   for (int j = 0; j < dims; j++) {\n    //     mins[i]= std::min(mins[i], pr[i][j]);\n    //     maxs[i]= std::max(maxs[i], pr[i][j]);}});\n    // float min_val = *parlay::min_element(mins);\n    // float max_val = *parlay::max_element(maxs);\n    // float bound = std::max(max_val, -min_val);\n    \n    \n    // if (sizeof(T) == 1) {\n    //   auto x = parlay::flatten(parlay::tabulate(n, [&] (long i) {\n    //     return parlay::tabulate(dims, [&] (long j) {\n    //       return 128 + (int8_t) (std::round(pr[i][j] * (range/2) / bound));});}));\n    //   auto y = parlay::histogram_by_index(x, 256);\n    //   for (int i = 0; i < 256; i++)\n    //     std::cout << i - 128 << \":\" << y[i] << \", \";\n    //   std::cout << std::endl;\n    // }\n    return parameters(bound, dims); // 1.7 for glove-100, 1.4 for nytimes, 1.5 for glove-25 but bad\n  }\n\nprivate:\n  byte* values;\n  long id_;\n  parameters params;\n};\n\n\nstruct Mips_2Bit_Point {\n  using distanceType = float;\n  using byte = uint8_t;\n  using word = std::bitset<64>;\n  //using word = uint64_t; \n  using T = int8_t;\n\n  static int pop_count(word x) {\n    return x.count();\n    //return __builtin_popcountl(x);\n  }\n\n  static void set_bit(word& x, int i, bool v) {\n    x[i] = v;\n    //x = (~(1ul << i) & x) | ((uint64_t) v << i);\n  }\n  \n  struct parameters {\n    float cut;\n    int dims;\n    int num_bytes() const {return ((dims - 1) / 64 + 1) * 8 * 2;}\n    parameters() : cut(.25), dims(0) {}\n    parameters(int dims) : cut(.25), dims(dims) {}\n    parameters(float cut, int dims)\n      : cut(cut), dims(dims) {\n      std::cout << \"3-value quantization with cut = \" << cut << std::endl;\n    }\n  };\n\n  static constexpr bool is_metric = false;\n  \n  int operator [] (long i) const {\n    abort();\n  }\n\n  float distance_8(byte* p_, byte* q_) const {\n    word* p = (word*) p_;\n    word* q = (word*) q_;\n    int num_blocks = params.num_bytes() / 16;\n    int16_t total = 0;\n    for (int i = 0; i < num_blocks; i++) {\n      word not_equal = p[2 * i] ^ q[2 * i];\n      word not_zero = p[2 * i + 1] & q[2 * i + 1];\n      int16_t num_neg = pop_count(not_equal & not_zero);\n      int16_t num_not_zero = pop_count(not_zero);\n      total += (2 * num_neg) - num_not_zero;\n    }\n    return total;\n  }\n\n  float distance(const Mips_2Bit_Point &x) const {\n    return distance_8(this->values, x.values);\n  }\n  \n  void prefetch() const {\n    int l = (params.num_bytes() - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch(values + i * 64);\n  }\n\n  bool same_as(const Mips_2Bit_Point& q){\n    return values == q.values;\n  }\n\n  long id() const {return id_;}\n\n  Mips_2Bit_Point(byte* values, long id, parameters p)\n    : values(values), id_(id), params(p)\n  {}\n\n  bool operator==(const Mips_2Bit_Point &q) const {\n    for (int i = 0; i < params.num_bytes(); i++) {\n      if (values[i] != q.values[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n  \n  template <typename Point>\n  static void translate_point(byte* byte_values, const Point& p, const parameters& params) {\n    // two words per block, one for -1, +1, the other to mark if non-zero\n    int num_blocks = params.num_bytes() / 16;\n    word* words = (word*) byte_values;\n    float cv = params.cut;\n    for (int i = 0; i < num_blocks; i++) {\n      for (int j = 0; j < 64; j++) {\n        if (j + i * 64 >= params.dims) {\n          set_bit(words[2 * i + 1], j, false);\n          return;\n        }\n        set_bit(words[2 * i + 1], j, true);\n        float pj = p[j + i * 64];\n        if (pj < -cv) set_bit(words[2 * i], j, false);\n        else if (pj > cv) set_bit(words[2 * i], j, true);\n        else set_bit(words[2 * i + 1], j, false);\n      }\n    }\n  }\n  \n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    long n = pr.size();\n    int dims = pr.dimension();\n    long len = n * dims;\n    using MT = float;\n    parlay::sequence<MT> vals(len);\n    parlay::parallel_for(0, n, [&] (long i) {\n      for (int j = 0; j < dims; j++) \n        vals[i * dims + j] = pr[i][j];\n    });\n    parlay::sort_inplace(vals);\n    float cutoff = .3;\n    float min_cut = vals[(long) (cutoff * len)];\n    float max_cut = vals[(long) ((1.0-cutoff) * (len-1))];\n    float cut = std::max(max_cut, -min_cut);\n    return parameters(cut, dims); \n  }\n\nprivate:\n  byte* values;\n  long id_;\n  parameters params;\n};\n\nstruct Mips_Bit_Point {\n  using distanceType = float;\n  using Data = std::bitset<64>;\n  using byte = uint8_t;\n  \n  struct parameters {\n    int dims;\n    int num_bytes() const {return ((dims - 1) / 64 + 1) * 8;}\n    parameters() : dims(0) {}\n    parameters(int dims)\n      : dims(dims) {\n      std::cout << \"single-bit quantization\" << std::endl;\n    }\n  };\n  \n  static constexpr bool is_metric = false;\n  \n  int8_t operator [] (long j) const {\n    Data* pbits = (Data*) values;\n    return pbits[j/64][j%64] ? 1 : -1;\n  }\n\n  float distance(const Mips_Bit_Point &q) const {\n    int num_blocks = (params.dims - 1)/64 + 1;\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    int cnt = 0;\n    for (int i=0; i < num_blocks; i++)\n      cnt +=(*pbits ^ *qbits).count();\n    return cnt;\n  }\n\n  void prefetch() const {\n    int l = (params.num_bytes() - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch((char*) values + i* 64);\n  }\n    \n  bool same_as(const Mips_Bit_Point& q){\n    return &q == this;\n  }\n\n  long id() const {return id_;}\n\n  Mips_Bit_Point(byte* values, long id, const parameters& params)\n    : values(values), id_(id), params(params) {}\n\n  bool operator==(const Mips_Bit_Point &q) const {\n    int num_blocks = (params.dims - 1)/64 + 1;\n    Data* pbits = (Data*) values;\n    Data* qbits = (Data*) q.values;\n    for (int i = 0; i < num_blocks; i++)\n      if (pbits[i] != qbits[i]) return false;\n    return true;\n  }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n\n  template <typename In_Point>\n  static void translate_point(byte* values, const In_Point& p, const parameters& params) {\n    Data* pbits = (Data*) values;\n    for (int i = 0; i < params.dims; i++)\n      pbits[i/64][i%64] = (p[i] > 0);\n  }\n\n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    return parameters(pr.dimension());\n  }\n\nprivate:\n  byte* values;\n  long id_;\n  parameters params;\n};\n\n\n\nstruct Mips_4Bit_Point {\n  using distanceType = float;\n  using byte = uint8_t;\n  using word = std::bitset<64>;\n  //using word = uint64_t; \n  using T = int8_t;\n\n  static int pop_count(word x) {\n    return x.count();\n  }\n\n  static void set_bit(word& x, int i, bool v) {\n    x[i] = v;\n  }\n  \n  struct parameters {\n    float cut;\n    int dims;\n    int num_bytes() const {return ((dims - 1) / 64 + 1) * 8 * 4;}\n    parameters() : cut(.25), dims(0) {}\n    parameters(int dims) : cut(.25), dims(dims) {}\n    parameters(float cut, int dims)\n      : cut(cut), dims(dims) {\n      std::cout << \"3-value quantization with cut = \" << cut << std::endl;\n    }\n  };\n\n  static constexpr bool is_metric = false;\n  \n  int operator [] (long i) const {\n    abort();\n  }\n\n  static int16_t triple(word a, word b, word plus, word minus) {\n    word x = a & b;\n    return pop_count(x & plus) - pop_count(x & minus);\n  }\n      \n  float distance(byte* p_, byte* q_) const {\n    word* p = (word*) p_;\n    word* q = (word*) q_;\n    int num_blocks = params.num_bytes() / 16;\n    int16_t total = 0;\n    for (int i = 0; i < num_blocks; i++) {\n      word minus = p[2 * i] ^ q[2 * i];\n      word plus = ~minus;\n      auto triple = [=] (word a, word b) -> int16_t {\n        word x = a & b;\n        return pop_count(x & plus) - pop_count(x & minus);\n      };\n      total += triple(p[2 * i + 1], q[2 * i + 1]);\n      total += triple(p[2 * i + 1], q[2 * i + 2]) * 2;\n      total += triple(p[2 * i + 1], q[2 * i + 3]) * 4;\n      total += triple(p[2 * i + 2], q[2 * i + 1]) * 2;\n      total += triple(p[2 * i + 2], q[2 * i + 2]) * 4;\n      total += triple(p[2 * i + 2], q[2 * i + 3]) * 8;\n      total += triple(p[2 * i + 3], q[2 * i + 1]) * 4;\n      total += triple(p[2 * i + 3], q[2 * i + 2]) * 8;\n      total += triple(p[2 * i + 3], q[2 * i + 3]) * 16;\n    }\n    return total;\n  }\n\n  float distance(const Mips_4Bit_Point &x) const {\n    return distance(this->values, x.values);\n  }\n  \n  void prefetch() const {\n    int l = (params.num_bytes() - 1)/64 + 1;\n    for (int i=0; i < l; i++)\n      __builtin_prefetch(values + i * 64);\n  }\n\n  bool same_as(const Mips_4Bit_Point& q){\n    return values == q.values;\n  }\n\n  long id() const {return id_;}\n\n  Mips_4Bit_Point(byte* values, long id, parameters p)\n    : values(values), id_(id), params(p)\n  {}\n\n  bool operator==(const Mips_4Bit_Point &q) const {\n    for (int i = 0; i < params.num_bytes(); i++) {\n      if (values[i] != q.values[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  void normalize() {\n    std::cout << \"can't normalize quantized point\" << std::endl;\n    abort();\n  }\n  \n  template <typename Point>\n  static void translate_point(byte* byte_values, const Point& p, const parameters& params) {\n    // two words per block, one for -1, +1, the other to mark if non-zero\n    int num_blocks = params.num_bytes() / 16;\n    word* words = (word*) byte_values;\n    float cv = params.cut;\n    for (int i = 0; i < num_blocks; i++) {\n      for (int j = 0; j < 64; j++) {\n        if (j + i * 64 >= params.dims) {\n          set_bit(words[2 * i + 1], j, false);\n          return;\n        }\n        set_bit(words[2 * i + 1], j, true);\n        float pj = p[j + i * 64];\n        if (pj < -cv) set_bit(words[2 * i], j, false);\n        else if (pj > cv) set_bit(words[2 * i], j, true);\n        else set_bit(words[2 * i + 1], j, false);\n      }\n    }\n  }\n  \n  template <typename PR>\n  static parameters generate_parameters(const PR& pr) {\n    long n = pr.size();\n    int dims = pr.dimension();\n    long len = n * dims;\n    using MT = float;\n    parlay::sequence<MT> vals(len);\n    parlay::parallel_for(0, n, [&] (long i) {\n      for (int j = 0; j < dims; j++) \n        vals[i * dims + j] = pr[i][j];\n    });\n    parlay::sort_inplace(vals);\n    float cutoff = .3;\n    float min_cut = vals[(long) (cutoff * len)];\n    float max_cut = vals[(long) ((1.0-cutoff) * (len-1))];\n    float cut = std::max(max_cut, -min_cut);\n    return parameters(cut, dims); \n  }\n\nprivate:\n  byte* values;\n  long id_;\n  parameters params;\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/mmap.h",
    "content": "#ifndef ALGORITHMS_ANN_MMAP_H_\n#define ALGORITHMS_ANN_MMAP_H_\n\n#include <algorithm>\n#include <iostream>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/internal/file_map.h\"\n\nnamespace parlayANN {\n\n// returns a pointer and a length\ninline std::pair<char*, size_t> mmapStringFromFile(const char* filename) {\n  struct stat sb;\n  int fd = open(filename, O_RDONLY);\n  if (fd == -1) {\n    perror(\"open\");\n    exit(-1);\n  }\n  if (fstat(fd, &sb) == -1) {\n    perror(\"fstat\");\n    exit(-1);\n  }\n  if (!S_ISREG(sb.st_mode)) {\n    perror(\"not a file\\n\");\n    exit(-1);\n  }\n  char* p =\n      static_cast<char*>(mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));\n  if (p == MAP_FAILED) {\n    perror(\"mmap\");\n    exit(-1);\n  }\n  if (close(fd) == -1) {\n    perror(\"close\");\n    exit(-1);\n  }\n  size_t n = sb.st_size;\n  return std::make_pair(p, n);\n}\n\n} // end namespace\n\n#endif // ANN_MMAP_H_\n"
  },
  {
    "path": "algorithms/utils/parse_results.h",
    "content": "#ifndef ALGORITHMS_UTILS_PARSE_RESULTS_H_\n#define ALGORITHMS_UTILS_PARSE_RESULTS_H_\n\n#include <algorithm>\n#include <set>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n\nnamespace parlayANN {\n  \nstruct Graph_ {\n  std::string name;\n  std::string params;\n  long size;\n  double avg_deg;\n  int max_deg;\n  double time;\n\n  Graph_(std::string n, std::string p, long s, double ad, int md, double t)\n      : name(n), params(p), size(s), avg_deg(ad), max_deg(md), time(t) {}\n\n  void print() {\n    std::cout << name << \" graph built with \" << size\n              << \" points and parameters \" << params << std::endl;\n    std::cout << \"Graph has average degree \" << avg_deg\n              << \" and maximum degree \" << max_deg << std::endl;\n    std::cout << \"Graph built in \" << time << \" seconds\" << std::endl;\n  }\n};\n\nstruct LSH {\n  std::string name;\n  std::string params;\n  long size;\n  double time;\n\n  LSH(std::string n, std::string p, long s, double t)\n      : name(n), params(p), size(s), time(t) {}\n\n  void print() {\n    std::cout << name << \" LSH tables built with \" << size\n              << \" points and parameters \" << params << std::endl;\n    std::cout << \"Tables built in \" << time << \" seconds\" << std::endl;\n  }\n};\n\nstruct range_result {\n  int num_queries;\n  int num_nonzero_queries;\n\n  double recall;\n  double alt_recall;\n\n  size_t avg_cmps;\n  size_t tail_cmps;\n\n  size_t avg_visited;\n  size_t tail_visited;\n\n  float QPS;\n\n  int k;\n  int beamQ;\n  double slack;\n\n  range_result(int nq, int nnq, double r, double r2,\n               parlay::sequence<size_t> stats, float qps, int K, int Q, float c,\n               float s)\n      : num_queries(nq),\n        num_nonzero_queries(nnq),\n        recall(r),\n        alt_recall(r2),\n        QPS(qps),\n        k(K),\n        beamQ(Q),\n        slack(s) {\n    if (stats.size() != 4) abort();\n\n    avg_cmps = stats[0];\n    tail_cmps = stats[1];\n    avg_visited = stats[2];\n    tail_visited = stats[3];\n  }\n\n  void print() {\n    std::cout << \"k = \" << k << \", Q = \" << beamQ\n              << \", slack = \" << slack << \", throughput = \" << QPS << \"/second\"\n              << std::endl;\n    std::cout << std::endl;\n    std::cout << \"Num nonzero queries: \" << num_nonzero_queries << std::endl;\n    std::cout << \"Nonzero recall: \" << recall << std::endl;\n    std::cout << \"Alternate recall: \" << alt_recall;\n    std::cout << std::endl;\n    std::cout << \"Average dist cmps: \" << avg_cmps\n              << \", 99th percentile dist cmps: \" << tail_cmps << std::endl;\n    std::cout << \"Average num visited: \" << avg_visited\n              << \", 99th percentile num visited: \" << tail_visited << std::endl;\n  }\n};\n\nstruct nn_result {\n  double recall;\n\n  uint avg_cmps;\n  uint tail_cmps;\n\n  uint avg_visited;\n  uint tail_visited;\n\n  float QPS;\n\n  int k;\n  int beamQ;\n  int limit;\n  int degree_limit;\n  int gtn;\n\n  long num_queries;\n\n  nn_result(double r, parlay::sequence<uint> stats, float qps, int K, int Q,\n            long q, int limit, int degree_limit, int gtn)\n      : recall(r),\n        QPS(qps),\n        k(K),\n        beamQ(Q),\n        limit(limit),\n        degree_limit(degree_limit),\n        gtn(gtn),\n        num_queries(q) {\n    if (stats.size() != 4) abort();\n\n    avg_cmps = stats[0];\n    tail_cmps = stats[1];\n    avg_visited = stats[2];\n    tail_visited = stats[3];\n  }\n\n  void print() {\n    std::cout << \"For \" << gtn << \"@\" << gtn << \" recall = \" << recall\n              << \", QPS = \" << QPS << \", Q = \" << beamQ;\n    std::cout << \", visited limit = \" << limit << \", degree limit: \" << degree_limit;\n    std::cout << \", average visited = \" << avg_visited << \", average cmps = \" << avg_cmps << std::endl;\n  }\n\n  void print_verbose() {\n    std::cout << \"Over \" << num_queries << \" queries\" << std::endl;\n    std::cout << \"k = \" << k << \", Q = \" << beamQ \n              << \", throughput = \" << QPS << \"/second\" << std::endl;\n    std::cout << \"Recall: \" << recall << std::endl;\n    std::cout << \"Average dist cmps: \" << avg_cmps\n              << \", 99th percentile dist cmps: \" << tail_cmps << std::endl;\n    std::cout << \"Average num visited: \" << avg_visited\n              << \", 99th percentile num visited: \" << tail_visited << std::endl;\n  }\n};\n\nstruct lsh_result {\n  double recall;\n\n  size_t avg_cmps;\n  size_t tail_cmps;\n\n  float QPS;\n\n  int k;\n  int num_tables;\n  long num_queries;\n\n  lsh_result(double r, parlay::sequence<size_t> stats, float qps, int K, int n,\n             long q)\n      : recall(r), QPS(qps), k(K), num_tables(n), num_queries(q) {\n    if (stats.size() != 2) abort();\n    avg_cmps = stats[0];\n    tail_cmps = stats[1];\n  }\n\n  void print() {\n    std::cout << \"Over \" << num_queries << \" queries\" << std::endl;\n    std::cout << \"k = \" << k << \", tables = \" << num_tables\n              << \", throughput = \" << QPS << \"/second\" << std::endl;\n    std::cout << \"Recall: \" << recall << std::endl;\n    std::cout << \"Average dist cmps: \" << avg_cmps\n              << \", 99th percentile dist cmps: \" << tail_cmps << std::endl;\n  }\n};\n\ntemplate <typename res>\nauto parse_result(parlay::sequence<res> results,\n                  parlay::sequence<float> buckets) {\n  parlay::sequence<float> ret_buckets;\n  parlay::sequence<res> retval;\n  for (int i = 0; i < buckets.size(); i++) {\n    float b = buckets[i];\n    auto pred = [&](res R) { return R.recall >= b; };\n    parlay::sequence<res> candidates;\n    auto temp_candidates = parlay::filter(results, pred);\n    if ((i == buckets.size() - 1) || (temp_candidates.size() == 0)) {\n      candidates = temp_candidates;\n    } else {\n      float c = buckets[i + 1];\n      auto pred2 = [&](res R) { return R.recall <= c; };\n      candidates = parlay::filter(temp_candidates, pred2);\n    }\n    if (candidates.size() != 0) {\n      auto less = [&](res R, res S) { return R.QPS < S.QPS; };\n      res M = *(parlay::max_element(candidates, less));\n      M.print();\n      retval.push_back(M);\n      ret_buckets.push_back(b);\n    }\n  }\n  return std::make_pair(retval, ret_buckets);\n}\n\n} // end namespace\n\n#endif  // ALGORITHMS_UTILS_PARSE_RESULTS_H_\n\n"
  },
  {
    "path": "algorithms/utils/point_range.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <sys/mman.h>\n#include <algorithm>\n#include <iostream>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/internal/file_map.h\"\n#include \"types.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nnamespace parlayANN {\n\ntemplate<class Point_>\nstruct PointRange{\n  //using T = T_;\n  using Point = Point_;\n  using parameters = typename Point::parameters;\n  using byte = uint8_t;\n\n  long dimension() const {return params.dims;}\n  //long aligned_dimension() const {return aligned_dims;}\n\n  PointRange() : values(std::shared_ptr<byte[]>(nullptr, std::free)), n(0) {}\n\n  template <typename PR>\n  PointRange(const PR& pr, const parameters& p) : params(p)  {\n    n = pr.size();\n    int num_bytes = p.num_bytes();\n    aligned_bytes = (num_bytes <= 32) ? 32 : 64 * ((num_bytes - 1)/64 + 1);\n    long total_bytes = n * aligned_bytes;\n    byte* ptr = (byte*) aligned_alloc(1l << 21, total_bytes);\n    madvise(ptr, total_bytes, MADV_HUGEPAGE);\n    values = std::shared_ptr<byte[]>(ptr, std::free);\n    byte* vptr = values.get();\n    parlay::parallel_for(0, n, [&] (long i) {\n      Point::translate_point(vptr + i * aligned_bytes, pr[i], params);});\n  }\n\n  template <typename PR>\n  PointRange (PR& pr) : PointRange(pr, Point::generate_parameters(pr)) { }\n\n  template <typename PR>\n  PointRange (PR& pr, int dims) : PointRange(pr, Point::generate_parameters(dims)) { }\n\n  PointRange(char* filename) : values(std::shared_ptr<byte[]>(nullptr, std::free)){\n      if(filename == NULL) {\n        n = 0;\n        return;\n      }\n      std::ifstream reader(filename);\n      if (!reader.is_open()) {\n        std::cout << \"Data file \" << filename << \" not found\" << std::endl;\n        std::abort();\n      }\n\n      //read num points and max degree\n      unsigned int num_points;\n      unsigned int d;\n      reader.read((char*)(&num_points), sizeof(unsigned int));\n      n = num_points;\n      reader.read((char*)(&d), sizeof(unsigned int));\n      params = parameters(d);\n      std::cout << \"Data: detected \" << num_points << \" points with dimension \" << d << std::endl;\n      int num_bytes = params.num_bytes();\n      aligned_bytes =  64 * ((num_bytes - 1)/64 + 1);\n      if (aligned_bytes != num_bytes)\n        std::cout << \"Aligning bytes to \" << aligned_bytes << std::endl;\n      long total_bytes = n * aligned_bytes;\n      byte* ptr = (byte*) aligned_alloc(1l << 21, total_bytes);\n      madvise(ptr, total_bytes, MADV_HUGEPAGE);\n      values = std::shared_ptr<byte[]>(ptr, std::free);\n      size_t BLOCK_SIZE = 1000000;\n      size_t index = 0;\n      while(index < n) {\n          size_t floor = index;\n          size_t ceiling = index+BLOCK_SIZE <= n ? index+BLOCK_SIZE : n;\n          long m = ceiling - floor;\n          byte* data_start = new byte[m * num_bytes];\n          reader.read((char*)(data_start), m * num_bytes);\n          parlay::parallel_for(floor, ceiling, [&] (size_t i) {\n            std::memmove(values.get() + i * aligned_bytes,\n                         data_start + (i - floor) * num_bytes,\n                         num_bytes);\n          });\n          delete[] data_start;\n          index = ceiling;\n      }\n  }\n\n  size_t size() const { return n; }\n\n  unsigned int get_dims() const { return params.dims; }\n  \n  Point operator [] (long i) const {\n    if (i > n) {\n      std::cout << \"ERROR: point index out of range: \" << i << \" from range \" << n << \", \" << std::endl;\n      abort();\n    }\n    return Point(values.get()+i*aligned_bytes, i, params);\n  }\n\n  byte* location(long i) const {\n    return values.get() + i * aligned_bytes;\n  }\n  \n  parameters params;\n\nprivate:\n  std::shared_ptr<byte[]> values;\n  long aligned_bytes;\n  size_t n;\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/rangeSearch.h",
    "content": "#include <algorithm>\n#include <functional>\n#include <random>\n#include <set>\n#include <unordered_set>  \n#include <queue>\n\n#include \"parlay/io.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n#include \"beamSearch.h\"\n#include \"earlyStopping.h\"\n#include \"types.h\"\n#include \"graph.h\"\n#include \"stats.h\"\n#include \"filtered_hashset.h\"\n\nnamespace parlayANN {\n\n  template<typename Point, typename PointRange, typename indexType>\n  std::pair<std::vector<indexType>, long>\ngreedy_search(Point p, Graph<indexType> &G, PointRange &Points,\n              std::vector<std::pair<indexType, typename Point::distanceType>> &starting_points,\n              double radius) {\n  std::vector<indexType> result;\n  hashset<indexType> has_been_seen(2 * starting_points.size() * 64);\n  long distance_comparisons = 0;\n\n  for (auto [v,d] : starting_points) {\n    if (has_been_seen(v) || d > radius) continue;\n    result.push_back(v);\n  }\n\n  // now do a BFS over all vertices with distance less than radius\n  long position = 0;\n  std::vector<indexType> unseen;\n  while (position < result.size()) {\n    indexType next = result[position++];\n    unseen.clear();\n    for (long i = 0; i < G[next].size(); i++) {\n      auto v = G[next][i];\n      if (has_been_seen(v)) continue;\n      unseen.push_back(v);\n      Points[v].prefetch();\n    }\n    for (auto v : unseen) {\n      distance_comparisons++;\n      if (Points[v].distance(p) <= radius)\n        result.push_back(v);\n    }\n  }\n\n  return std::pair(std::move(result), distance_comparisons);\n}\n\n  // Does a priority-first search up to the radius given\n  template<typename Point, typename PointRange, typename indexType>\n  std::pair<std::vector<indexType>, long>\ngreedy_search_pq(Point p, Graph<indexType> &G, PointRange &Points,\n                 std::vector<std::pair<indexType, typename Point::distanceType>> &starting_points,\n                 double radius) {\n\n  std::vector<indexType> result;\n  hashset<indexType> has_been_seen(2 * starting_points.size() * 64);\n  \n  long distance_comparisons = 0;\n  using did = std::pair<typename Point::distanceType, indexType>;\n  auto cmp = [] (did a, did b) {return a.first > b.first;};\n  std::priority_queue<did, std::vector<did>, decltype(cmp)> pq(cmp);\n\n  for (auto [v,d] : starting_points) {\n    if (has_been_seen(v)) continue;\n    if (d > radius ) continue;\n    pq.push(std::pair(d,v));\n  }\n\n  long position = 0;\n  std::vector<indexType> unseen;\n  while (pq.top().first <= radius) {\n    auto nxt = pq.top().second;\n    pq.pop();\n    result.push_back(nxt);\n    unseen.clear();\n    for (long i = 0; i < G[nxt].size(); i++) {\n      auto v = G[nxt][i];\n      if (has_been_seen(v)) continue;\n      unseen.push_back(v);\n      Points[v].prefetch();\n    }\n    for (auto v : unseen) {\n      distance_comparisons++;\n      pq.push(std::pair(Points[v].distance(p), v));\n    }\n  }\n\n  return std::pair(std::move(result), distance_comparisons);\n}\n\n  //a variant specialized for range searching\ntemplate<typename Point, typename PointRange, typename indexType>\nstd::pair<std::vector<indexType>, size_t>\ngreedy_search_old(Point p, Graph<indexType> &G, PointRange &Points,\n                  parlay::sequence<std::pair<indexType, typename Point::distanceType>> &starting_points,\n                  double radius,\n                  parlay::sequence<std::pair<indexType, typename Point::distanceType>> &already_visited) {\n  // compare two (node_id,distance) pairs, first by distance and then id if\n  // equal\n  using distanceType = typename Point::distanceType;\n  auto less = [&](std::pair<indexType, distanceType> a, std::pair<indexType, distanceType> b) {\n    return a.second < b.second || (a.second == b.second && a.first < b.first);\n  };\n\n  //need to use an unordered map for a dynamically sized hash table\n  std::unordered_set<indexType> has_been_seen;\n\n  //Insert everything from visited list into has_been_seen\n  for(auto v : already_visited){\n    if(!has_been_seen.count(v.first) > 0) has_been_seen.insert(v.first);\n  }\n\n  // Frontier maintains the points within radius found so far \n  // Each entry is a (id,distance) pair.\n  // Initialized with starting points \n  std::queue<indexType> frontier;\n  for (auto q : starting_points){\n    if (!has_been_seen.count(q.first) > 0) has_been_seen.insert(q.first);\n    frontier.push(q.first);\n  }\n  \n\n  // maintains set of visited vertices (id-distance pairs)\n  std::vector<indexType> visited;\n\n  // counters\n  size_t dist_cmps = starting_points.size();\n  int remain = 1;\n  int num_visited = 0;\n  double total;\n\n  // used as temporaries in the loop\n  std::vector<indexType> keep;\n  keep.reserve(G.max_degree());\n\n  // The main loop.  Terminate beam search when the entire frontier\n  // has been visited or have reached max_visit.\n  while (frontier.size() > 0) {\n    // the next node to visit is the unvisited frontier node that is closest to\n    // p\n    indexType current = frontier.front();\n    frontier.pop();\n    G[current].prefetch();\n    // add to visited set\n    visited.push_back(current);\n    num_visited++;\n\n    // keep neighbors that have not been visited (using approximate\n    // hash). Note that if a visited node is accidentally kept due to\n    // approximate hash it will be removed below by the union or will\n    // not bump anyone else.\n    keep.clear();\n    for (indexType i=0; i<G[current].size(); i++) {\n      auto a = G[current][i];\n      //TODO this is a bug when searching for a point not in the graph???\n      if (a == p.id() || has_been_seen.count(a) > 0) continue;  // skip if already seen\n      keep.push_back(a);\n      Points[a].prefetch();\n      has_been_seen.insert(a);\n    }\n\n    for (auto a : keep) {\n      distanceType dist = Points[a].distance(p);\n      dist_cmps++;\n      // filter out if not within radius\n      if (dist > radius) continue;\n      frontier.push(a);\n    }\n  }\n\n  return std::make_pair(visited, dist_cmps);    \n}\n\n  template<typename Point, typename PointRange, typename QPointRange, typename indexType>\n  std::pair<parlay::sequence<std::vector<indexType>>,std::pair<double,double>> \nRangeSearch(Graph<indexType> &G,\n            PointRange &Query_Points, PointRange &Base_Points,\n            QPointRange& Q_Query_Points, QPointRange &Q_Base_Points,\n            stats<indexType> &QueryStats,\n            indexType starting_point,\n            QueryParams &QP) {\n\n  parlay::sequence<indexType> starting_points = {starting_point};\n  parlay::sequence<std::vector<indexType>> all_neighbors(Query_Points.size());\n  parlay::WorkerSpecific<double> beam_time;\n  parlay::WorkerSpecific<double> other_time;\n  bool use_rerank = (Base_Points.params.num_bytes() != Q_Base_Points.params.num_bytes());\n  parlay::parallel_for(0, Query_Points.size(), [&](size_t i) {\n    parlay::internal::timer t_search_beam(\"beam search time\");\n    parlay::internal::timer t_search_other(\"other time\");\n    t_search_beam.stop();\n    t_search_other.stop();\n    std::vector<indexType> neighbors;\n    std::vector<std::pair<indexType, typename Point::distanceType>> neighbors_with_distance;\n    t_search_beam.start();\n    using dtype = typename Point::distanceType;\n    using id_dist = std::pair<indexType, dtype>;\n    QueryParams QP1(QP.beamSize, QP.beamSize, G.size(), G.max_degree(),\n                    QP.is_early_stop, Q_Query_Points[i].translate_distance(QP.early_stopping_radius),\n                    QP.early_stopping_count,\n                    QP.range_query_type, Q_Query_Points[i].translate_distance(QP.radius));\n\n    auto [pairElts, dist_cmps_beam] =\n      filtered_beam_search(G, Q_Query_Points[i], Q_Base_Points,\n                           Q_Query_Points[i], Q_Base_Points,\n                           starting_points, QP1, false,\n                           early_stopping<std::vector<id_dist>>);\n    t_search_beam.stop();\n    auto [beamElts, visitedElts] = pairElts;\n    for (auto b : beamElts) {\n      double dist;\n      if (use_rerank) {\n        dist = Query_Points[i].distance(Base_Points[b.first]);\n      } else {\n        dist = b.second;\n      }\n      if (dist <= QP.radius) {\n        neighbors.push_back(b.first);\n        neighbors_with_distance.push_back(b);\n      }\n    }\n    if (neighbors.size() < QP.beamSize || QP.range_query_type == Beam){\n      all_neighbors[i] = std::move(neighbors);\n    } else{\n      // if using quantization then use slightly larger radius\n      t_search_other.start();\n      double pad_factor = (QP1.radius > 0) ? 1.05 : .975;\n      double radius = use_rerank ? pad_factor * QP1.radius : QP1.radius;\n      auto [in_range, dist_cmps_greedy] =\n        greedy_search(Q_Query_Points[i], G, Q_Base_Points,\n                      neighbors_with_distance, radius);\n\n      std::vector<indexType> ans;\n\n      //#define EndWithBeam\n#ifdef EndWithBeam\n      int beamSize = in_range.size() * 1.1;\n      QueryParams QP2(beamSize, beamSize, G.size(), G.max_degree());\n      auto [pairElts, dist_cmps2] = beam_search(Q_Query_Points[i], G, Q_Base_Points, in_range, QP2);\n      for (auto r : pairElts.first) \n        if (Query_Points[i].distance(Base_Points[r.first]) <= QP.radius)\n          ans.push_back(r.first);\n#else\n      for (auto r : in_range)\n        if (!use_rerank || Query_Points[i].distance(Base_Points[r]) <= QP.radius)\n          ans.push_back(r);\n#endif\n      all_neighbors[i] = std::move(ans);\n      QueryStats.increment_visited(i, in_range.size());\n      QueryStats.increment_dist(i, dist_cmps_greedy);\n      t_search_other.stop();\n    }\n    \n\n    *beam_time += t_search_beam.total_time();\n    *other_time += t_search_other.total_time();\n    QueryStats.increment_visited(i, visitedElts.size());\n    QueryStats.increment_dist(i, dist_cmps_beam);\n    \n  });\n\n  double total_time_beam = 0;\n  double total_time_other = 0;\n  for (auto x : beam_time) total_time_beam += x;\n  for (auto y: other_time) total_time_other += y;\n  return std::make_pair(all_neighbors,std::make_pair(total_time_beam,total_time_other));\n}\n\n}\n"
  },
  {
    "path": "algorithms/utils/simpleGraph.h",
    "content": "// This code is part of the Parlay Project\n// Copyright (c) 2024 Guy Blelloch, Magdalen Dobson and the Parlay team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <algorithm>\n#include <fcntl.h>\n#include <iostream>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/internal/file_map.h\"\n#include \"../../parlaylib/examples/helper/graph_utils.h\"\n\n#include \"types.h\"\n\nnamespace parlayANN {\n  \ntemplate<typename indexType>\nstruct edgeRange{\n  using graphUtils = graph_utils<indexType>;\n  using Edges = typename graph_utils<indexType>::vertices;\n  \n  size_t size() const {return (*edges).size();}\n  indexType id() const {return id_;}\n\n  edgeRange() : edges(nullptr), id_(0) {}\n\n  edgeRange(Edges* ngh, indexType maxDeg, indexType i)\n    : edges(ngh), maxDeg(maxDeg), id_(i) {}\n\n  indexType operator [] (indexType j) const {\n    if (j > size()) {\n      std::cout << \"ERROR: index exceeds degree while accessing neighbors\" << std::endl;\n      abort();\n    } else return (*edges)[j];\n  }\n\n  void append_neighbor(indexType nbh){\n    (*edges).push_back(nbh);\n  }\n\n  template<typename rangeType>\n  void update_neighbors(const rangeType& r){\n    (*edges).clear();\n    for (int i = 0; i < r.size(); i++) \n      (*edges).push_back(r[i]);\n  }\n\n  template<typename rangeType>\n  void append_neighbors(const rangeType& r){\n    for (int i = 0; i < r.size(); i++) \n      (*edges).push_back(r[i]);\n  }\n\n  void clear_neighbors(){\n    (*edges).clear();\n  }\n\n  void prefetch() const {\n    int l = (size() * sizeof(indexType))/64;\n    for (int i = 0; i < l; i++)\n      __builtin_prefetch(((char*) (*edges).data()) + i *  64);\n  }\n\n  template<typename F>\n  void sort(F&& less){\n    std::sort((*edges).begin(), (*edges).end(), less);}\n\n  indexType* begin() const {return (*edges).data();}\n\n  indexType* end() const {return (*edges).data() + size();}\n\nprivate:\n  Edges* edges;\n  long maxDeg;\n  indexType id_;\n};\n\ntemplate<typename indexType_>\nstruct Graph{\n  using indexType = indexType_;\n  using graphUtils = graph_utils<indexType>;\n  using Edges = typename graph_utils<indexType>::vertices;\n  using gtype = typename graphUtils::graph;\n  \n  long max_degree() const {return maxDeg;}\n  size_t size() const {return graph.size();}\n\n  Graph(){}\n  Graph(long maxDeg, size_t n) : maxDeg(maxDeg), graph(gtype(n)) {}\n\n  Graph(char* gFile){\n    std::string fname = gFile;\n    graph = graphUtils::read_graph_from_file(fname);\n    maxDeg = reduce(map(graph, parlay::size_of()), parlay::maximum<size_t>());\n  }\n  \n  void save(char* oFile) {\n    std::cout << \"Writing graph with \" << graph.size()\n              << \" points and max degree \" << maxDeg\n              << \" to \" << oFile \n              << std::endl;\n    std::string fname = oFile;\n    graphUtils::write_graph_to_file(graph, fname);\n  }\n\n  edgeRange<indexType> operator [] (indexType i) const {\n    if (i > graph.size()) {\n      std::cout << \"ERROR: graph index out of range: \" << i << std::endl;\n      abort();\n    }\n    return edgeRange<indexType>((Edges*) &graph[i], maxDeg, i);\n  }\n  ~Graph(){}\n\nprivate:\n  indexType maxDeg;\n  gtype graph;\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/stats.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <algorithm>\n#include <queue>\n#include <set>\n\n#include \"graph.h\"\n\n#include \"parlay/io.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"../../parlaylib/examples/BFS.h\"\n#include \"graph_reorder.h\"\n#include \"../../parlaylib/examples/helper/graph_utils.h\"\n#include \"parlay/internal/get_time.h\"\n\nnamespace parlayANN {\n\ninline std::pair<double, int> graph_stats_(Graph<unsigned int> &G) {\n  auto od = parlay::delayed_seq<size_t>(\n      G.size(), [&](size_t i) { return G[i].size(); });\n  size_t j = parlay::max_element(od) - od.begin();\n  int maxDegree = od[j];\n  size_t sum1 = parlay::reduce(od);\n  double avg_deg = sum1 / ((double)G.size());\n  return std::make_pair(avg_deg, maxDegree);\n}\n\n  template <typename indexType>\nvoid print_graph_statistics(Graph<indexType> &G, indexType start) {\n  long n = G.size();\n\n  // convert to right format for transpose\n  auto GG = parlay::tabulate(n, [&] (indexType i) {\n                                  parlay::sequence<indexType> out;\n                                  for (int x : G[i]) out.push_back(x);\n                                  return out;\n                                });\n  // generate some statistics for the graph\n  auto GTrans = graph_utils<indexType>::transpose(GG);\n  auto inDegrees = parlay::map(GTrans, parlay::size_of());\n  auto lowInDegrees = parlay::filter(parlay::iota(n), [&] (long i) {return inDegrees[i] < 4;});\n  auto maxInDegree = *parlay::max_element(inDegrees);\n  auto outDegrees = parlay::map(GG, parlay::size_of());\n  auto maxOutDegree = *parlay::max_element(outDegrees);\n  // for (auto u : lowDegrees) \n  //   for (auto v : G[u])\n  //     G[v].append_neighbor(u);\n\n  using etype = std::pair<int,int>;\n\n  int psize = 20;\n  auto Gprefix = parlay::map(GG, [&] (auto& ngh) {\n                                   if (ngh.size() > psize)\n                                     return parlay::tabulate(psize, [&] (int i) {return ngh[i];});\n                                   else return ngh;});\n    \n  auto E = parlay::flatten(parlay::tabulate(G.size(), [&] (int u) {\n                   return parlay::map_maybe(Gprefix[u], [=] (int v) {\n                                                    if (u < v)\n                                                      return std::optional<etype>(etype(u,v));\n                                                    else return std::optional<etype>();});}));\n\n  parlay::internal::timer t;\n  auto result = graph_reorder(E, n);\n  t.next(\"reorder time\");\n  //auto result = parlay::iota<int>(n);\n\n  //auto ldiff = [] (int u, int v) {return std::log2(std::abs(u -v));};\n  auto ldiff = [] (int u, int v) {\n                 float diff = std::abs(u - v);\n                 if (diff < 128) return 1;\n                 if (diff < 16384) return 2;\n                 else return 3;\n               };\n  auto vcost = [&] (int i) {\n                 auto nghn = parlay::sort(parlay::map(GG[i], [&] (int v) {return result[v];}));\n                 return ldiff(result[i],nghn[0]) +\n                   parlay::reduce(parlay::tabulate(nghn.size()-1, [&] (int i) {return ldiff(nghn[i+1], nghn[i]);}));};\n  float total_cost = parlay::reduce(parlay::tabulate(n, vcost));\n  \n  std::cout << \"Bytes per edge: \" << total_cost/parlay::reduce(parlay::map(GG, parlay::size_of())) << std::endl;\n\n  auto layers = BFS(start, G);\n  auto sizes = parlay::map(layers, parlay::size_of());\n  auto visited = flatten(layers);\n  \n  std::cout << \"Graph statistics:\" << std::endl;\n  //std::cout << \"  average degree = \" << float(parlay::reduce(GG, parlay::size_of()))/n << std::endl;\n  //std::cout << \"  max out-degree = \" << *minDegreeLoc << std::endl;\n  std::cout << \"  max in-degree = \" << maxInDegree << std::endl;\n  std::cout << \"  number with low (< 4) in-degree = \" << lowInDegrees.size() << std::endl;\n  std::cout << \"  unreachable from source = \" << (n - visited.size()) << \"/\" << n << std::endl;\n  std::cout << \"  radius from source = \" << layers.size() << std::endl;\n  std::cout << \"  BFS level sizes = \" << parlay::to_chars(sizes) << std::endl;\n  }\n  \ntemplate<typename indexType>\nstruct stats{\n\n  stats() {}\n  \n  stats(size_t n){\n    visited = parlay::sequence<indexType>(n, 0);\n    distances = parlay::sequence<indexType>(n, 0);\n  }\n\n  parlay::sequence<indexType> visited;\n  parlay::sequence<indexType> distances;\n\n  void increment_dist(indexType i, indexType j){\n    distances[i]+=j;}\n  void increment_visited(indexType i, indexType j){\n    visited[i]+=j;}\n\n  parlay::sequence<indexType> visited_stats(){return statistics(this->visited);}\n  parlay::sequence<indexType> dist_stats(){return statistics(this->distances);}\n\n  void clear(){\n    size_t n = visited.size();\n    visited = parlay::sequence<indexType>(n, 0);\n    distances = parlay::sequence<indexType>(n, 0);\n  }\n\n  static parlay::sequence<indexType> statistics(parlay::sequence<indexType> s){\n    auto sl = parlay::map(s, [] (long x) { return x;});\n    indexType avg = (indexType) (parlay::reduce(sl) / s.size());\n    indexType tail = parlay::sort(s)[.99 * ((float)s.size())];\n    auto result = {avg, tail};\n    return result;\n  }\n\n};\n\n\n\n// template <typename T>\n// auto query_stats(parlay::sequence<Tvec_point<T> *> &q) {\n//   parlay::sequence<size_t> vs = visited_stats(q);\n//   parlay::sequence<size_t> ds = distance_stats(q);\n//   auto result = {ds, vs};\n//   return parlay::flatten(result);\n// }\n\n// template <typename T>\n// auto range_query_stats(parlay::sequence<Tvec_point<T> *> &q) {\n//   auto pred = [&](Tvec_point<T> *p) { return (p->ngh.size() == 0); };\n//   auto pred1 = [&](Tvec_point<T> *p) { return !pred(p); };\n//   auto zero_queries = parlay::filter(q, pred);\n//   auto nonzero_queries = parlay::filter(q, pred1);\n//   parlay::sequence<int> vn = visited_stats(nonzero_queries);\n//   parlay::sequence<int> dn = distance_stats(nonzero_queries);\n//   parlay::sequence<int> rn = rounds_stats(nonzero_queries);\n//   parlay::sequence<int> vz = visited_stats(zero_queries);\n//   parlay::sequence<int> dz = distance_stats(zero_queries);\n//   parlay::sequence<int> rz = rounds_stats(zero_queries);\n//   auto result = {rn, dn, vn, rz, dz, vz};\n//   return parlay::flatten(result);\n// }\n\n// template <typename T>\n// parlay::sequence<size_t> visited_stats(parlay::sequence<Tvec_point<T> *> &q) {\n//   auto visited_stats =\n//       parlay::tabulate(q.size(), [&](size_t i) { return q[i]->visited; });\n//   parlay::sort_inplace(visited_stats);\n//   size_t avg_visited = (int)parlay::reduce(visited_stats) / ((double)q.size());\n//   size_t tail_index = .99 * ((float)q.size());\n//   size_t tail_visited = visited_stats[tail_index];\n//   auto result = {avg_visited, tail_visited};\n//   return result;\n// }\n\n// template <typename T>\n// parlay::sequence<size_t> distance_stats(parlay::sequence<Tvec_point<T> *> &q) {\n//   auto dist_stats =\n//       parlay::tabulate(q.size(), [&](size_t i) { return q[i]->dist_calls; });\n//   parlay::sort_inplace(dist_stats);\n//   size_t avg_dist = (size_t)parlay::reduce(dist_stats) / ((double)q.size());\n//   size_t tail_index = .99 * ((float)q.size());\n//   size_t tail_dist = dist_stats[tail_index];\n//   auto result = {avg_dist, tail_dist};\n//   return result;\n// }\n\n// template <typename T>\n// parlay::sequence<size_t> rounds_stats(parlay::sequence<Tvec_point<T> *> &q) {\n//   auto exp_stats =\n//       parlay::tabulate(q.size(), [&](size_t i) { return q[i]->rounds; });\n//   parlay::sort_inplace(exp_stats);\n//   size_t avg_exps = (size_t)parlay::reduce(exp_stats) / ((double)q.size());\n//   size_t tail_index = .99 * ((float)q.size());\n//   size_t tail_exps = exp_stats[tail_index];\n//   auto result = {avg_exps, tail_exps, exp_stats[exp_stats.size() - 1]};\n//   return result;\n// }\n\n// void range_gt_stats(parlay::sequence<ivec_point> groundTruth) {\n//   auto sizes = parlay::tabulate(groundTruth.size(), [&](size_t i) {\n//     return groundTruth[i].coordinates.size();\n//   });\n//   parlay::sort_inplace(sizes);\n//   size_t first_nonzero_index = 0;\n//   for (size_t i = 0; i < sizes.size(); i++) {\n//     if (sizes[i] != 0) {\n//       first_nonzero_index = i;\n//       break;\n//     }\n//   }\n//   auto nonzero_sizes = (sizes).cut(first_nonzero_index, sizes.size());\n//   auto sizes_sum = parlay::reduce(nonzero_sizes);\n//   float avg =\n//       static_cast<float>(sizes_sum) / static_cast<float>(nonzero_sizes.size());\n//   std::cout << \"Among nonzero entries, the average number of matches is \" << avg\n//             << std::endl;\n//   std::cout << \"25th percentile: \" << nonzero_sizes[.25 * nonzero_sizes.size()]\n//             << std::endl;\n//   std::cout << \"75th percentile: \" << nonzero_sizes[.75 * nonzero_sizes.size()]\n//             << std::endl;\n//   std::cout << \"99th percentile: \" << nonzero_sizes[.99 * nonzero_sizes.size()]\n//             << std::endl;\n//   std::cout << \"Max: \" << nonzero_sizes[nonzero_sizes.size() - 1] << std::endl;\n// }\n\n// template <typename T>\n// int connected_components(parlay::sequence<Tvec_point<T> *> &v) {\n//   parlay::sequence<bool> visited(v.size(), false);\n//   int cc = 0;\n//   for (int i = 0; i < v.size(); i++) {\n//     if (!visited[i]) {\n//       BFS(i, v, visited);\n//       cc++;\n//     }\n//   }\n//   return cc;\n// }\n\n// template <typename T>\n// void BFS(int start, parlay::sequence<Tvec_point<T> *> &v,\n//          parlay::sequence<bool> &visited) {\n//   std::queue<int> frontier;\n//   frontier.push(start);\n//   while (frontier.size() != 0) {\n//     int c = frontier.front();\n//     frontier.pop();\n//     visited[c] = true;\n//     for (int l = 0; l < size_of(v[c]->out_nbh); l++) {\n//       int j = v[c]->out_nbh[l];\n//       if (!visited[j]) frontier.push(j);\n//     }\n//   }\n// }\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/utils/types.h",
    "content": "#ifndef ALGORITHMS_ANN_TYPES_H_\n#define ALGORITHMS_ANN_TYPES_H_\n\n// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#ifndef TYPES\n#define TYPES\n\n#include <algorithm>\n#include <fstream>\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"mmap.h\"\n\nnamespace parlayANN {\n\ntemplate<typename T>\nstruct groundTruth{\n  parlay::slice<T*, T*> coords;\n  parlay::slice<float*, float*> dists;\n  long dim;\n  size_t n;\n\n  groundTruth() : coords(parlay::make_slice<T*, T*>(nullptr, nullptr)),\n                  dists(parlay::make_slice<float*, float*>(nullptr, nullptr)){}\n\n  groundTruth(char* gtFile) : coords(parlay::make_slice<T*, T*>(nullptr, nullptr)),\n                              dists(parlay::make_slice<float*, float*>(nullptr, nullptr)){\n    if(gtFile == NULL){\n      n = 0;\n      dim = 0;\n    } else{\n      auto [fileptr, length] = mmapStringFromFile(gtFile);\n\n      int num_vectors = *((T*) fileptr);\n      int d = *((T*) (fileptr + 4));\n\n      \n      std::cout << \"Ground truth: detected \" << num_vectors << \" points with num results \" << d << std::endl;\n\n      T* start_coords = (T*)(fileptr+8);\n      T* end_coords = start_coords + d*num_vectors;\n\n      float* start_dists = (float*)(end_coords);\n      float* end_dists = start_dists + d*num_vectors;\n\n      n = num_vectors;\n      dim = d;\n      coords = parlay::make_slice(start_coords, end_coords);\n      dists = parlay::make_slice(start_dists, end_dists);\n    }\n  }\n\n  groundTruth(parlay::sequence<parlay::sequence<T>> gt) : coords(parlay::make_slice<T*, T*>(nullptr, nullptr)),\n                                                          dists(parlay::make_slice<float*, float*>(nullptr, nullptr)){\n    n = gt.size();\n    dim = gt[0].size();\n    auto flat_gt = parlay::flatten(gt);\n    coords = parlay::make_slice(flat_gt.begin(), flat_gt.end());\n    parlay::sequence<float> dummy_ds = parlay::sequence<float>(dim * n, 0.0);\n    dists = parlay::make_slice(dummy_ds.begin(), dummy_ds.end());\n  }\n\n  //saves in binary format\n  //assumes gt is not so big that it needs block saving\n  void save(char* save_path) {\n    std::cout << \"Writing groundtruth for \" << n << \" points and num results \" << dim\n              << std::endl;\n    parlay::sequence<T> preamble = {static_cast<T>(n), static_cast<T>(dim)};\n    std::ofstream writer;\n    writer.open(save_path, std::ios::binary | std::ios::out);\n    writer.write((char*)preamble.begin(), 2 * sizeof(T));\n    writer.write((char*)coords.begin(), dim*n*sizeof(T));\n    writer.write((char*)dists.begin(), dim*n*sizeof(float));\n    writer.close();\n  }\n\n  T coordinates(long i, long j) const {return *(coords.begin() + i * dim + j);}\n\n  float distances(long i, long j) const {return *(dists.begin() + i * dim + j);}\n\n  size_t size() const {return n;}\n\n  long dimension() const {return dim;}\n\n};\n\ntemplate<typename T>\nstruct RangeGroundTruth{\n  T* coords;\n  parlay::sequence<T> offsets;\n  parlay::slice<T*, T*> sizes;\n  size_t n;\n  size_t num_matches;\n\n  RangeGroundTruth() : sizes(parlay::make_slice<T*, T*>(nullptr, nullptr)){}\n\n  RangeGroundTruth(char* gtFile) : sizes(parlay::make_slice<T*, T*>(nullptr, nullptr)){\n    if(gtFile == NULL){\n      n = 0;\n      num_matches = 0;\n    } else{\n      auto [fileptr, length] = mmapStringFromFile(gtFile);\n\n      n = *((T*) fileptr);\n      num_matches = *((T*) (fileptr + sizeof(T)));\n\n      T* sizes_begin = (T*)(fileptr + 2 * sizeof(T)) ;\n      T* sizes_end = sizes_begin+n;\n      sizes = parlay::make_slice(sizes_begin, sizes_end);\n\n      auto [offsets0, total] = parlay::scan(sizes);\n      offsets0.push_back(total);\n      offsets = offsets0;\n\n      std::cout << \"Detected \" << n << \" points with num matches \" << num_matches << std::endl;\n\n      coords = sizes_end;\n    }\n  }\n\n  parlay::slice<T*, T*> operator[] (long i){\n    T* begin = coords + offsets[i];\n    T* end = coords + offsets[i + 1];\n    return parlay::make_slice(begin, end);\n  }\n\n  size_t size(){return n;}\n  size_t matches(){return num_matches;}\n};\n\nenum rangeQueryType {None, Greedy, Doubling, Beam};\n  \nstruct BuildParams{\n  long R; //vamana and pynnDescent\n  long L; //vamana\n  double m_l = 0; // HNSW\n  double alpha; //vamana and pyNNDescent\n  int num_passes; //vamana\n\n  long num_clusters; // HCNNG and pyNNDescent\n  long cluster_size; //HCNNG and pyNNDescent\n  long MST_deg; //HCNNG\n\n  double delta; //pyNNDescent\n  bool verbose = false;\n  bool graph_stats = false;\n\n  int quantize = 0; // use quantization for build and query (0 = none, 1 = one-level, 2 = two-level)\n  bool self;\n  int single_batch; //vamana\n  long Q = 0; //beam width to pass onto query (0 indicates none specified)\n  double trim = 0.0; // for quantization\n  double rerank_factor = 100; // for reranking, k * factor = to rerank\n  double batch_factor = 1.0;\n  bool is_early_stop = false;\n  double early_stopping_radius; // for radius search\n  rangeQueryType range_query_type = None;\n  double radius; // for radius search\n\n  std::string alg_type;\n\n\n  BuildParams(long R, long L, double a, int num_passes,\n              long nc, long cs, long mst, double de,\n              bool verbose = false, int quantize = 0, \n              bool self = false, int single_batch = 0,\n              long Q = 0, double trim = 0.0,\n              double rerank_factor = 100, double batch_factor = 1.0,\n              bool is_early_stop = false, double early_stopping_radius = 0.0, \n              rangeQueryType range_query_type = None, double radius = 0.0,\n              bool graph_stats = false) \n    : R(R), L(L), alpha(a), num_passes(num_passes), num_clusters(nc),\n      cluster_size(cs), MST_deg(mst), delta(de),\n      verbose(verbose), graph_stats(graph_stats), quantize(quantize),\n      self(self), single_batch(single_batch),\n      Q(Q), trim(trim),\n      rerank_factor(rerank_factor), batch_factor(batch_factor),\n      is_early_stop(is_early_stop), early_stopping_radius(early_stopping_radius),\n      range_query_type(range_query_type), radius(radius) {\n    if(R != 0 && L != 0 && alpha != 0){alg_type = m_l>0? \"HNSW\": \"Vamana\";}\n    else if(num_clusters != 0 && cluster_size != 0 && MST_deg != 0){alg_type = \"HCNNG\";}\n    else if(R != 0 && alpha != 0 && num_clusters != 0 && cluster_size != 0 && delta != 0){alg_type = \"pyNNDescent\";}\n  }\n\n  BuildParams() {}\n\n  BuildParams(long R, long L, double a, int num_passes, bool verbose = false)\n    : R(R), L(L), alpha(a), num_passes(num_passes), verbose(verbose), single_batch(0)\n  {alg_type = \"Vamana\";}\n\n  BuildParams(long R, long L, double m_l, double a)\n    : R(R), L(L), m_l(m_l), alpha(a), verbose(false)\n  {alg_type = \"HNSW\";}\n\n  BuildParams(long nc, long cs, long mst)\n    : num_clusters(nc), cluster_size(cs), MST_deg(mst), verbose(false)\n  {alg_type = \"HCNNG\";}\n\n  BuildParams(long R, double a, long nc, long cs, double de)\n    : R(R), alpha(a), num_clusters(nc), cluster_size(cs), delta(de), verbose(false)\n  {alg_type = \"pyNNDescent\";}\n\n  long max_degree(){\n    if(alg_type == \"HCNNG\") return num_clusters*MST_deg;\n    else if(alg_type == \"HNSW\")  return R*2;\n    else return R;\n  }\n};\n\n\nstruct QueryParams{\n  long k;\n  long beamSize;\n  long limit;\n  long degree_limit;\n  double rerank_factor = 100;\n  double batch_factor = .125;\n  bool is_early_stop = false;\n  double early_stopping_radius;\n  double early_stopping_count;\n  rangeQueryType range_query_type = None;\n  double radius;\n\n  float pad = 1.0;\n\n  QueryParams(long k, long Q, long limit, long dg,\n              double rerank_factor = 100,\n              double batch_factor = .125)\n    : k(k), beamSize(Q), limit(limit), degree_limit(dg),\n      rerank_factor(rerank_factor), batch_factor(batch_factor) {}\n\n  QueryParams(long k, long Q, long limit, long dg,\n              long es, double esr, long esc,\n              rangeQueryType range_query_type, double radius)\n    : k(k), beamSize(Q), limit(limit), degree_limit(dg),\n      is_early_stop(es), early_stopping_radius(esr), early_stopping_count(esc),\n      range_query_type(range_query_type), radius(radius) {}\n\n  QueryParams() {}\n\n  void print(){\n    std::cout << \"Beam: \" << beamSize;\n  }\n\n\n};\n\ntemplate<typename T, typename Point>\nclass Desc_HNSW{\npublic:\n  typedef T type_elem;\n  typedef Point type_point;\n  static auto distance(const type_point &u, const type_point &v, uint32_t dim)\n  {\n    (void)dim;\n    return u.distance(v);\n  }\n\n  static auto get_id(const type_point &u)\n  {\n    return u.id();\n  }\n};\n\n#endif\n} // end namespace\n\n#endif // ALGORITHMS_ANN_TYPES_H_\n"
  },
  {
    "path": "algorithms/utils/union.h",
    "content": "#ifndef ALGORITHMS_ANN_UNION_\n#define ALGORITHMS_ANN_UNION_\n\n#include <set>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n\nnamespace parlayANN {\n\n// takes in two sorted sequences and returns a sorted union\n// of length at most K, with a bool denoting whether P has changed\ntemplate <typename F, typename T>\nstd::pair<parlay::sequence<T>, bool> seq_union_bounded(\n    parlay::sequence<T>& P, parlay::sequence<T>& Q, int K, F&& less) {\n  T* first1 = P.begin();\n  T* last1 = P.end();\n  T* first2 = Q.begin();\n  T* last2 = Q.end();\n  bool changed = false;\n  parlay::sequence<T> result = parlay::sequence<T>();\n  result.reserve(K);\n  int count = 0;\n  while (true && count < K) {\n    if (first1 == last1) {\n      while (first2 != last2 && count < K) {\n        changed = true;\n        result.push_back(*first2);\n        count++;\n        ++first2;\n      }\n      return std::make_pair(result, changed);\n    } else if (first2 == last2) {\n      while (first1 != last1 && count < K) {\n        result.push_back(*first1);\n        count++;\n        ++first1;\n      }\n      return std::make_pair(result, changed);\n    }\n    if (less(*first1, *first2)) {\n      result.push_back(*first1);\n      count++;\n      ++first1;\n    } else if (less(*first2, *first1)) {\n      result.push_back(*first2);\n      changed = true;\n      count++;\n      ++first2;\n    } else {\n      if (first1->first == first2->first) {\n        result.push_back(*first1);\n        count++;\n        ++first1;\n        ++first2;\n      } else {\n        result.push_back(*first1);\n        count++;\n        if (count == K)\n          break;\n        else {\n          result.push_back(*first2);\n          changed = true;\n          count++;\n          ++first1;\n          ++first2;\n        }\n      }\n    }\n  }\n  return std::make_pair(result, changed);\n}\n\n// takes in two sorted sequences and returns a sorted union\ntemplate <typename F, typename T>\nparlay::sequence<T> seq_union(parlay::sequence<T>& P, parlay::sequence<T>& Q, F&& less) {\n  T* first1 = P.begin();\n  T* last1 = P.end();\n  T* first2 = Q.begin();\n  T* last2 = Q.end();\n  parlay::sequence<T> result = parlay::sequence<T>();\n  result.reserve(P.size() + Q.size());\n  while (true) {\n    if (first1 == last1) {\n      while (first2 != last2) {\n        result.push_back(*first2);\n        ++first2;\n      }\n      return result;\n    } else if (first2 == last2) {\n      while (first1 != last1) {\n        result.push_back(*first1);\n        ++first1;\n      }\n      return result;\n    }\n    if (less(*first1, *first2)) {\n      result.push_back(*first1);\n      ++first1;\n    } else if (less(*first2, *first1)) {\n      result.push_back(*first2);\n      ++first2;\n    } else {\n      if (first1->first == first2->first) {\n        result.push_back(*first1);\n        ++first1;\n        ++first2;\n      } else {\n        result.push_back(*first1);\n        result.push_back(*first2);\n        ++first1;\n        ++first2;\n      }\n    }\n  }\n  return result;\n}\n\n} // end namespace\n\n#endif // ALGORITHMS_ANN_UNION_\n"
  },
  {
    "path": "algorithms/vamana/BUILD",
    "content": "# Vamana algorithm.\n\ncc_library(\n    name = \"index\",\n    hdrs = [\"index.h\"],\n    deps = [\n        \"@parlaylib//parlay:delayed\",\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay:random\",\n        \"//algorithms/utils:graph\",\n        \"//algorithms/utils:beamSearch\",\n        \"//algorithms/utils:types\",\n        \"//algorithms/utils:point_range\",\n    ],\n)\n\ncc_test(\n    name = \"index_test\",\n    size = \"small\",\n    srcs = [\"index_test.cc\"],\n    deps = [\n        \"@googletest//:gtest_main\",\n        \":index\",\n        \"//algorithms/utils:point_range\",\n        \"//algorithms/utils:types\",\n        \"//algorithms/utils:mmap\",\n        \"//algorithms/utils:graph\",\n        \"//algorithms/utils:beamSearch\",\n        \"//algorithms/utils:stats\",\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay:random\",\n        \"@parlaylib//parlay:delayed\",\n    ],\n)\n\ncc_library(\n    name = \"neighbors\",\n    hdrs = [\"neighbors.h\"],\n    deps = [\n        \":index\",\n        \"@parlaylib//parlay:parallel\",\n        \"@parlaylib//parlay:primitives\",\n        \"@parlaylib//parlay:random\",\n        \"//algorithms/utils:beamSearch\",\n        \"//algorithms/utils:check_nn_recall\",\n        \"//algorithms/utils:csvfile\",\n        \"//algorithms/utils:parse_results\",\n        \"//algorithms/utils:graph\",\n        \"//algorithms/utils:jl_point\",\n        \"//algorithms/utils:stats\",\n        \"//algorithms/utils:types\",\n        \"//algorithms/utils:point_range\",\n        \"//algorithms/utils:euclidean_point\",\n        \"//algorithms/utils:mips_point\",\n    ],\n)\n\ncc_test(\n    name = \"neighbors_test\",\n    size = \"small\",\n    srcs = [\"neighbors_test.cc\"],\n    deps = [\n        \"@googletest//:gtest_main\",\n        \":neighbors\",\n    ],\n)\n"
  },
  {
    "path": "algorithms/vamana/CMakeLists.txt",
    "content": "function(add_neighbors NAME DEFS)\n  add_executable(${NAME} ../bench/neighborsTime.C)\n  target_link_libraries(${NAME} PRIVATE parlay)\n  target_compile_definitions(${NAME} PRIVATE ${DEFS})\n  target_precompile_headers(${NAME} PRIVATE neighbors.h)\n  target_include_directories(${NAME} PRIVATE ${PARLAY_INCLUDE_DIR})\nendfunction()\n\nadd_neighbors(neighbors \"\")\n\n\n"
  },
  {
    "path": "algorithms/vamana/Makefile",
    "content": "include ../bench/parallelDefsANN\n\nREQUIRE = ../utils/beamSearch.h index.h  ../utils/check_nn_recall.h ../utils/NSGDist.h ../utils/parse_results.h ../utils/graph.h ../utils/point_range.h ../utils/euclidian_point.h ../utils/mips_point.h ../utils/jl_point.h ../utils/hashset.h ../utils/types.h\nBENCH = neighbors\n\ninclude ../bench/MakeBench\n"
  },
  {
    "path": "algorithms/vamana/index.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <math.h>\n\n#include <algorithm>\n#include <random>\n#include <set>\n\n#include \"../utils/point_range.h\"\n#include \"../utils/graph.h\"\n#include \"../utils/types.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/delayed.h\"\n#include \"parlay/random.h\"\n#include \"../utils/beamSearch.h\"\n\nnamespace parlayANN {\n\ntemplate<typename PointRange, typename QPointRange, typename indexType>\nstruct knn_index {\n  using Point = typename PointRange::Point;\n  using QPoint = typename QPointRange::Point;\n  using distanceType = typename Point::distanceType;\n  using pid = std::pair<indexType, distanceType>;\n  using PR = PointRange;\n  using QPR = QPointRange;\n  using GraphI = Graph<indexType>;\n\n  BuildParams BP;\n  std::set<indexType> delete_set;\n  indexType start_point;\n\n  knn_index(BuildParams &BP) : BP(BP) {}\n\n  indexType get_start() { return start_point; }\n\n  //robustPrune routine as found in DiskANN paper, with the exception\n  //that the new candidate set is added to the field new_nbhs instead\n  //of directly replacing the out_nbh of p\n  std::pair<parlay::sequence<indexType>, long>\n  robustPrune(indexType p, parlay::sequence<pid>& cand,\n              GraphI &G, PR &Points, double alpha, bool add = true) {\n    // add out neighbors of p to the candidate set.\n    size_t out_size = G[p].size();\n    std::vector<pid> candidates;\n    long distance_comps = 0;\n    for (auto x : cand) candidates.push_back(x);\n\n    if(add){\n      for (size_t i=0; i<out_size; i++) {\n        distance_comps++;\n        candidates.push_back(std::make_pair(G[p][i], Points[G[p][i]].distance(Points[p])));\n      }\n    }\n\n    // Sort the candidate set according to distance from p\n    auto less = [&](std::pair<indexType, distanceType> a, std::pair<indexType, distanceType> b) {\n      return a.second < b.second || (a.second == b.second && a.first < b.first);\n    };\n    std::sort(candidates.begin(), candidates.end(), less);\n\n    // remove any duplicates\n    auto new_end =std::unique(candidates.begin(), candidates.end(),\n\t\t\t      [&] (auto x, auto y) {return x.first == y.first;});\n    candidates = std::vector(candidates.begin(), new_end);\n\n    std::vector<indexType> new_nbhs;\n    new_nbhs.reserve(BP.R);\n\n    size_t candidate_idx = 0;\n\n    while (new_nbhs.size() < BP.R && candidate_idx < candidates.size()) {\n      // Don't need to do modifications.\n      int p_star = candidates[candidate_idx].first;\n      candidate_idx++;\n      if (p_star == p || p_star == -1) {\n        continue;\n      }\n\n      new_nbhs.push_back(p_star);\n\n      for (size_t i = candidate_idx; i < candidates.size(); i++) {\n        int p_prime = candidates[i].first;\n        if (p_prime != -1) {\n          distance_comps++;\n          distanceType dist_starprime = Points[p_star].distance(Points[p_prime]);\n          distanceType dist_pprime = candidates[i].second;\n          if (alpha * dist_starprime <= dist_pprime) {\n            candidates[i].first = -1;\n          }\n        }\n      }\n    }\n\n    auto new_neighbors_seq = parlay::to_sequence(new_nbhs);\n    return std::pair(new_neighbors_seq, distance_comps);\n  }\n\n  //wrapper to allow calling robustPrune on a sequence of candidates\n  //that do not come with precomputed distances\n  std::pair<parlay::sequence<indexType>, long>\n  robustPrune(indexType p, parlay::sequence<indexType> candidates,\n              GraphI &G, PR &Points, double alpha, bool add = true){\n\n    parlay::sequence<pid> cc;\n    long distance_comps = 0;\n    cc.reserve(candidates.size()); // + size_of(p->out_nbh));\n    for (size_t i=0; i<candidates.size(); ++i) {\n      distance_comps++;\n      cc.push_back(std::make_pair(candidates[i], Points[candidates[i]].distance(Points[p])));\n    }\n    auto [ngh_seq, dc] = robustPrune(p, cc, G, Points, alpha, add);\n    return std::pair(ngh_seq, dc + distance_comps);\n  }\n\n  // add ngh to candidates without adding any repeats\n  template<typename rangeType1, typename rangeType2>\n  void add_neighbors_without_repeats(const rangeType1 &ngh, rangeType2& candidates) {\n    std::unordered_set<indexType> a;\n    for (auto c : candidates) a.insert(c);\n    for (int i=0; i < ngh.size(); i++)\n      if (a.count(ngh[i]) == 0) candidates.push_back(ngh[i]);\n  }\n\n  void set_start(){start_point = 0;}\n\n  void build_index(GraphI &G, PR &Points, QPR &QPoints,\n                   stats<indexType> &BuildStats, bool sort_neighbors = true){\n    std::cout << \"Building graph...\" << std::endl;\n    set_start();\n    parlay::sequence<indexType> inserts = parlay::tabulate(Points.size(), [&] (size_t i){\n      return static_cast<indexType>(i);});\n    if (BP.single_batch != 0) {\n      int degree = BP.single_batch;\n      std::cout << \"Using single batch per round with \" << degree << \" random start edges\" << std::endl;\n      parlay::random_generator gen;\n      std::uniform_int_distribution<long> dis(0, G.size());\n      parlay::parallel_for(0, G.size(), [&] (long i) {\n        std::vector<indexType> outEdges(degree);\n        for (int j = 0; j < degree; j++) {\n          auto r = gen[i*degree + j];\n          outEdges[j] = dis(r);\n        }\n        G[i].update_neighbors(outEdges);\n      });\n    }\n\n    // last pass uses alpha\n    std::cout << \"number of passes = \" << BP.num_passes << std::endl;\n    for (int i=0; i < BP.num_passes; i++) {\n      if (i == BP.num_passes - 1)\n        batch_insert(inserts, G, Points, QPoints, BuildStats, BP.alpha, true, 2, .02);\n      else\n        batch_insert(inserts, G, Points, QPoints, BuildStats, 1.0, true, 2, .02);\n    }\n\n    if (sort_neighbors) {\n      parlay::parallel_for (0, G.size(), [&] (long i) {\n        auto less = [&] (indexType j, indexType k) {\n          return Points[i].distance(Points[j]) < Points[i].distance(Points[k]);};\n        G[i].sort(less);});\n    }\n  }\n\n  void batch_insert(parlay::sequence<indexType> &inserts,\n                    GraphI &G, PR &Points, QPR &QPoints,\n                    stats<indexType> &BuildStats, double alpha,\n                    bool random_order = false, double base = 2,\n                    double max_fraction = .02, bool print=true) {\n    for(int p : inserts){\n      if(p < 0 || p > (int) G.size()){\n        std::cout << \"ERROR: invalid point \"\n                  << p << \" given to batch_insert\" << std::endl;\n        abort();\n      }\n    }\n    size_t n = G.size();\n    size_t m = inserts.size();\n    size_t inc = 0;\n    size_t count = 0;\n    float frac = 0.0;\n    float progress_inc = .1;\n    size_t max_batch_size = std::min(static_cast<size_t>(max_fraction * static_cast<float>(n)),\n                                     1000000ul);\n    //fix bug where max batch size could be set to zero\n    if(max_batch_size == 0) max_batch_size = n;\n    parlay::sequence<int> rperm;\n    if (random_order) \n      rperm = parlay::random_permutation<int>(static_cast<int>(m));\n    else\n      rperm = parlay::tabulate(m, [&](int i) { return i; });\n    auto shuffled_inserts =\n      parlay::tabulate(m, [&](size_t i) { return inserts[rperm[i]]; });\n    parlay::internal::timer t_beam(\"beam search time\");\n    parlay::internal::timer t_bidirect(\"bidirect time\");\n    parlay::internal::timer t_prune(\"prune time\");\n    t_beam.stop();\n    t_bidirect.stop();\n    t_prune.stop();\n    while (count < m) {\n      size_t floor;\n      size_t ceiling;\n      if (pow(base, inc) <= max_batch_size) {\n        floor = static_cast<size_t>(pow(base, inc)) - 1;\n        ceiling = std::min(static_cast<size_t>(pow(base, inc + 1)) - 1, m);\n        count = std::min(static_cast<size_t>(pow(base, inc + 1)) - 1, m);\n      } else {\n        floor = count;\n        ceiling = std::min(count + static_cast<size_t>(max_batch_size), m);\n        count += static_cast<size_t>(max_batch_size);\n      }\n\n      if (BP.single_batch != 0) {\n        floor = 0;\n        ceiling = m;\n        count = m;\n      }\n\n      parlay::sequence<parlay::sequence<indexType>> new_out_(ceiling-floor);\n      // search for each node starting from the start_point, then call\n      // robustPrune with the visited list as its candidate set\n      t_beam.start();\n\n      parlay::parallel_for(floor, ceiling, [&](size_t i) {\n        size_t index = shuffled_inserts[i];\n        indexType sp = BP.single_batch ? i : start_point;\n        parlay::sequence<indexType> starting_points = {sp};\n        QueryParams QP(0, BP.L, (long) Points.size(), (long) G.max_degree(),\n                       BP.rerank_factor, BP.batch_factor);\n        bool use_filtering = (Points.params.num_bytes() != QPoints.params.num_bytes());\n        auto r = filtered_beam_search(G,\n                                      Points[index],\n                                      Points,\n                                      QPoints[index],\n                                      QPoints,\n                                      starting_points,\n                                      QP,\n                                      use_filtering);\n        auto visited = r.first.second;\n        BuildStats.increment_dist(index, r.second);\n        BuildStats.increment_visited(index, visited.size());\n\n        long rp_distance_comps;\n        std::tie(new_out_[i-floor], rp_distance_comps) = robustPrune(index, visited, G, Points, alpha);\n        BuildStats.increment_dist(index, rp_distance_comps);\n      });\n\n      parlay::parallel_for(floor, ceiling, [&](size_t i) {\n        G[shuffled_inserts[i]].update_neighbors(new_out_[i-floor]);\n      });\n\n      t_beam.stop();\n\n      // make each edge bidirectional by first adding each new edge\n      //(i,j) to a sequence, then semisorting the sequence by key values\n      t_bidirect.start();\n\n      auto flattened = parlay::delayed::flatten(parlay::tabulate(ceiling - floor, [&](size_t i) {\n        indexType index = shuffled_inserts[i + floor];\n        return parlay::delayed::map(new_out_[i], [=] (indexType ngh) {\n          return std::pair(ngh, index);});}));\n      auto grouped_by = parlay::group_by_key(parlay::delayed::to_sequence(flattened));\n\n      t_bidirect.stop();\n      t_prune.start();\n      // finally, add the bidirectional edges; if they do not make\n      // the vertex exceed the degree bound, just add them to out_nbhs;\n      // otherwise, use robustPrune on the vertex with user-specified alpha\n      parlay::parallel_for(0, grouped_by.size(), [&](size_t j) {\n        auto &[index, candidates] = grouped_by[j];\n\tsize_t newsize = candidates.size() + G[index].size();\n        if (newsize <= BP.R) {\n\t  add_neighbors_without_repeats(G[index], candidates);\n\t  G[index].update_neighbors(candidates);\n        } else {\n          auto [new_out_2_, distance_comps] = robustPrune(index, std::move(candidates), G, Points, alpha);\n\t  G[index].update_neighbors(new_out_2_);\n          BuildStats.increment_dist(index, distance_comps);\n        }\n      });\n      t_prune.stop();\n\n      if (print && BP.single_batch == 0) {\n        auto ind = frac * n;\n        if (floor <= ind && ceiling > ind) {\n          frac += progress_inc;\n          std::cout << \"Pass \" << 100 * frac << \"% complete\"\n                    << std::endl;\n        }\n      }\n      inc += 1;\n    }\n    t_beam.total();\n    t_bidirect.total();\n    t_prune.total();\n  }\n\n};\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/vamana/index_test.cc",
    "content": "#include \"algorithms/vamana/index.h\"\n\n#include <gtest/gtest.h>\n\nTEST(PlaceHolderTest, BuildPlaceHolder) { EXPECT_EQ(7 * 6, 42); }\n"
  },
  {
    "path": "algorithms/vamana/neighbors.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <algorithm>\n\n#include \"../utils/beamSearch.h\"\n#include \"../utils/check_nn_recall.h\"\n#include \"../utils/parse_results.h\"\n#include \"../utils/mips_point.h\"\n#include \"../utils/euclidian_point.h\"\n#include \"../utils/jl_point.h\"\n#include \"../utils/stats.h\"\n#include \"../utils/types.h\"\n#include \"../utils/graph.h\"\n#include \"index.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n\nnamespace parlayANN {\n\ntemplate<typename PointRange, typename QPointRange, typename QQPointRange, typename indexType>\nvoid ANN_Quantized(Graph<indexType> &G, long k, BuildParams &BP,\n                   PointRange &Query_Points, QPointRange &Q_Query_Points, QQPointRange &QQ_Query_Points,\n                   groundTruth<indexType> GT, char *res_file,\n                   bool graph_built,\n                   PointRange &Points, QPointRange &Q_Points, QQPointRange &QQ_Points) {\n  parlay::internal::timer t(\"ANN\");\n\n  bool verbose = BP.verbose;\n  using findex = knn_index<QPointRange, QQPointRange, indexType>;\n  findex I(BP);\n  indexType start_point;\n  double idx_time;\n  stats<unsigned int> BuildStats(G.size());\n  if(graph_built){\n    idx_time = 0;\n    start_point = 0;\n  } else{\n    I.build_index(G, Q_Points, QQ_Points, BuildStats);\n    start_point = 0; // I.get_start();\n    idx_time = t.next_time();\n  }\n  if (BP.graph_stats)\n    print_graph_statistics(G, start_point);\n    \n  std::string name = \"Vamana\";\n  std::string params =\n    \"R = \" + std::to_string(BP.R) + \", L = \" + std::to_string(BP.L);\n  auto [avg_deg, max_deg] = graph_stats_(G);\n  auto vv = BuildStats.visited_stats();\n  std::cout << \"Average visited: \" << vv[0] << \", Tail visited: \" << vv[1]\n            << std::endl;\n  Graph_ G_(name, params, G.size(), avg_deg, max_deg, idx_time);\n  G_.print();\n  \n  long build_num_distances = parlay::reduce(parlay::map(BuildStats.distances,\n                                                        [] (auto x) {return (long) x;}));\n\n  if(Query_Points.size() != 0) {\n    search_and_parse(G_, G,\n                     Points, Query_Points,\n                     Q_Points, Q_Query_Points,\n                     QQ_Points, QQ_Query_Points,\n                     GT,\n                     res_file, k, false, start_point,\n                     verbose, BP.Q, BP.rerank_factor, BP.batch_factor);\n  }\n}\n\ntemplate<typename Point, typename PointRange_, typename indexType>\nvoid ANN(Graph<indexType> &G, long k, BuildParams &BP,\n         PointRange_ &Query_Points,\n         groundTruth<indexType> GT, char *res_file,\n         bool graph_built, PointRange_ &Points) {\n  if (BP.quantize != 0) {\n    std::cout << \"quantizing build and first pass of search to 1 byte\" << std::endl;\n    if constexpr (Point::is_metric) {\n      using QT = uint8_t;\n      using QPoint = Euclidian_Point<QT>;\n      using QPR = PointRange<QPoint>;\n      QPR Q_Points(Points);  // quantized to one byte\n      QPR Q_Query_Points(Query_Points, Q_Points.params);\n      if (BP.quantize == 1) {\n         ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, Q_Query_Points,\n                       GT, res_file, graph_built, Points, Q_Points, Q_Points);\n      }\n      // } else if (BP.quantize == 2) {\n      //   using QQPoint = Euclidean_Bit_Point;\n      //   using QQPR = PointRange<QQPoint>;\n      //   QQPR QQ_Points(Points);\n      //   QQPR QQ_Query_Points(Query_Points, QQ_Points.params);\n      //   ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, QQ_Query_Points,\n      //                 GT, res_file, graph_built, Points, Q_Points, QQ_Points);\n      else if (BP.quantize == 3) {\n        using QQPoint = Euclidean_JL_Sparse_Point<1024>;\n        using QQPR = PointRange<QQPoint>;\n        QQPR QQ_Points(Points);\n        QQPR QQ_Query_Points(Query_Points, QQ_Points.params);\n        ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, QQ_Query_Points,\n                      GT, res_file, graph_built, Points, Q_Points, QQ_Points);\n      }\n    } else {\n      std::cout << \"hello\" << std::endl;\n      using QT = int8_t;\n      //using QPoint = Euclidian_Point<uint8_t>;\n      using QPoint = Quantized_Mips_Point<8,true,255>;\n      using QPR = PointRange<QPoint>;\n      QPR Q_Points(Points);\n      QPR Q_Query_Points(Query_Points, Q_Points.params);\n      if (BP.quantize == 1) {\n        ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, Q_Query_Points,\n                      GT, res_file, graph_built, Points, Q_Points, Q_Points);\n      // } else if (BP.quantize == 2) {\n      //   using QQPoint = Mips_Bit_Point;\n      //   using QQPR = PointRange<QQPoint>;\n      //   QQPR QQ_Points(Points);\n      //   QQPR QQ_Query_Points(Query_Points, QQ_Points.params);\n      //   ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, QQ_Query_Points,\n      //                 GT, res_file, graph_built, Points, Q_Points, QQ_Points);\n      // } else if (BP.quantize == 3) {\n      //   using QQPoint = Mips_2Bit_Point;\n      //   using QQPR = PointRange<QQPoint>;\n      //   QQPR QQ_Points(Points);\n      //   QQPR QQ_Query_Points(Query_Points, QQ_Points.params);\n      //   ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, QQ_Query_Points,\n      //                 GT, res_file, graph_built, Points, Q_Points, QQ_Points);\n      // } else if (BP.quantize == 4) {\n      //   using QQPoint = Mips_JL_Sparse_Point<512>;\n      //   //using QQPoint = Mips_JL_Bit_Point<512>;\n      //   using QQPR = PointRange<QQPoint>;\n      //   QQPR QQ_Points(Points);\n      //   QQPR QQ_Query_Points(Query_Points, QQ_Points.params);\n      //   ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, QQ_Query_Points,\n      //                 GT, res_file, graph_built, Points, Q_Points, QQ_Points);\n      } else if (BP.quantize == 5) {\n        using QQPoint = Mips_JL_Sparse_Point<1024>;\n        using QQPR = PointRange<QQPoint>;\n        QQPR QQ_Points(Points);\n        QQPR QQ_Query_Points(Query_Points, QQ_Points.params);\n        ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, QQ_Query_Points,\n                      GT, res_file, graph_built, Points, Q_Points, QQ_Points);\n      // } else if (BP.quantize == 6) {\n      //   using QQPoint = Mips_JL_Sparse_Point_Normalized<1024>;\n      //   using QQPR = PointRange<QQPoint>;\n      //   QQPR QQ_Points(Points);\n      //   QQPR QQ_Query_Points(Query_Points, QQ_Points.params);\n      //   ANN_Quantized(G, k, BP, Query_Points, Q_Query_Points, QQ_Query_Points,\n      //                 GT, res_file, graph_built, Points, Q_Points, QQ_Points);\n      }\n    }\n  } else {\n    ANN_Quantized(G, k, BP, Query_Points, Query_Points, Query_Points,\n                  GT, res_file, graph_built, Points, Points, Points);\n  }\n}\n\n} // end namespace\n"
  },
  {
    "path": "algorithms/vamana/neighbors.sh",
    "content": "#!/bin/bash\ncd ~/ParlayANN/algorithms/vamana\nmake \n\nP=/ssd1/data/bigann\n# ./neighbors -R 64 -L 128 -alpha 1.2 -data_type uint8 -dist_func Euclidian -base_path $P/base.1B.u8bin.crop_nb_1000000\n\n# # PARLAY_NUM_THREADS=1 \n# ./neighbors -R 64 -L 128 -alpha 1.2 -two_pass 0 -data_type uint8 -dist_func Euclidian -query_path $P/query.public.10K.u8bin -gt_path $P/bigann-1M -res_path test.csv -base_path $P/base.1B.u8bin.crop_nb_1000000\n# # ./neighbors -R 64 -L 128 -alpha 1.2 -data_type uint8 -dist_func Euclidian -graph_path $P/graph-10M -query_path $P/query.public.10K.u8bin -gt_path $P/bigann-10M -res_path test.csv -base_path $P/base.1B.u8bin.crop_nb_10000000\n\n# Q=/ssd1/data/text2image1B\n# ./neighbors -R 64 -L 128 -alpha 1.0 -data_type float -two_pass 0 -dist_func mips -query_path $Q/query.public.10K.fbin -gt_path $Q/text2image-10K-1M -res_path test.csv -base_path $Q/base.1B.fbin.crop_nb_1000000\n\n# V=/ssd1/data/MSSPACEV1B\n# ./neighbors -R 64 -L 128 -alpha 1.2 -two_pass 0 -data_type int8 -dist_func Euclidian -query_path $V/query.i8bin -gt_path $V/msspacev-1M -res_path test.csv -base_path $V/spacev1b_base.i8bin.crop_nb_1000000\n\n\n# ./neighbors -R 64 -L 128 -a 1.2 -data_type uint8 -dist_func Euclidian -query_path /ssd1/data/bigann/query.public.10K.u8bin -gt_path /ssd1/data/bigann/bigann-1M -res_path test.csv -base_path /ssd1/data/bigann/base.1B.u8bin.crop_nb_1000000\n\n# P=/ssd1/data/FB_ssnpp\n# make\n# ./neighbors -R 128 -L 256 -alpha 1.0 -data_type uint8 -two_pass 0 -dist_func Euclidian -k 10 -query_path $P/ssnpp_nonzero.u8bin -gt_path $P/ssnpp-nonzero-1M -res_path test.csv -base_path $P/FB_ssnpp_database.u8bin.crop_nb_1000000\n\nT=/ssd1/data/gist\n./neighbors -R 64 -L 128 -alpha 1.2 -two_pass 0 -data_type float -dist_func Euclidian -query_path $T/gist_query.fbin -gt_path $T/gist-1M -res_path test.csv -base_path $T/gist_base.fbin\n"
  },
  {
    "path": "algorithms/vamana/neighbors_test.cc",
    "content": "#include \"algorithms/vamana/neighbors.h\"\n\n#include <gtest/gtest.h>\n\nTEST(PlaceHolderTest, BuildPlaceHolder) { EXPECT_EQ(7 * 6, 42); }\n"
  },
  {
    "path": "algorithms/vamana/scripts/OpenAIArXiv",
    "content": "# bash\nNAME=OpenAIArXiv\nBUILD_ARGS=\"-R 100 -L 200 -alpha 1.05 -num_passes 2 -quantize_mode 3 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 3 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func Euclidian -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.bin\nQUERY_FILE=$PATH/query.bin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_100_1.05_m3\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/deep10M",
    "content": "# bash\nNAME=deep10M\nBUILD_ARGS=\"-R 64 -L 128 -alpha 1.05 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 1 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func Euclidian -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_64_1.05\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/fashion",
    "content": "# bash\nNAME=fashion-mnist-784-euclidean\nBUILD_ARGS=\"-R 40 -L 80 -alpha 1.1 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 8 -verbose\"\nTYPE_ARGS=\"-data_type float -dist_func Euclidian -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/\"graph_40_1.1\"\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/gist",
    "content": "# bash\nNAME=gist\nBUILD_ARGS=\"-R 100 -L 200 -alpha 1.1 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 3 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func Euclidian -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_100_1.1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/glove100",
    "content": "# bash\nNAME=glove-100-angular\nBUILD_ARGS=\"-R 100 -L 200 -alpha 1 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 1 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func mips -normalize -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_100_1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n\n"
  },
  {
    "path": "algorithms/vamana/scripts/glove25",
    "content": "# bash\nNAME=glove-25-angular\nBUILD_ARGS=\"-R 100 -L 200 -alpha 1 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 1 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func mips -normalize -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_100_1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n\n"
  },
  {
    "path": "algorithms/vamana/scripts/msmarco_websearch",
    "content": "# bash\nBUILD_ARGS=\"-R 64 -L 128 -alpha 1 -num_passes 1 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 5 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func mips -file_type bin\"\n\nPATH=data/msmarco_websearch\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.bin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_64_1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n\n"
  },
  {
    "path": "algorithms/vamana/scripts/nytimes",
    "content": "# bash\nBUILD_ARGS=\"-R 130 -L 260 -alpha .85 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 5 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func mips -normalize -file_type bin\"\n\nPATH=data/nytimes-256-angular\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_130_85\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n\n"
  },
  {
    "path": "algorithms/vamana/scripts/sift",
    "content": "# bash\nBUILD_ARGS=\"-R 64 -L 128 -alpha 1.15 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 8 -verbose\"\nTYPE_ARGS=\"-data_type float -dist_func Euclidian -file_type bin\"\n\nPATH=data/sift-128-euclidean\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/\"graph_64_1.15\"\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/sift100",
    "content": "# bash\nBUILD_ARGS=\"-R 64 -L 128 -alpha 1.15 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 8 -verbose\"\nTYPE_ARGS=\"-data_type uint8 -dist_func Euclidian -file_type bin\"\n\nPATH=data/sift100\nDATA_FILE=$PATH/base.uint8\nQUERY_FILE=$PATH/query.uint8\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/\"graph_64_1.15\"\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/space_1",
    "content": "# bash\nNAME=space_1\nBUILD_ARGS=\"-R 64 -L 128 -alpha 1.1 -num_passes 2 -verbose\"\nQUERY_ARGS=\"-verbose\"\nTYPE_ARGS=\"-data_type int8 -dist_func Euclidian -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.i8bin\nQUERY_FILE=$PATH/query.i8bin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_64_1.1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/space_10",
    "content": "# bash\nNAME=space_10\nBUILD_ARGS=\"-R 64 -L 128 -alpha 1.1 -num_passes 2 -verbose\"\nQUERY_ARGS=\"-verbose\"\nTYPE_ARGS=\"-data_type int8 -dist_func Euclidian -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.i8bin\nQUERY_FILE=$PATH/query.i8bin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_64_1.1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/t2i_1",
    "content": "# bash\nNAME=t2i_1\nBUILD_ARGS=\"-R 100 -L 200 -alpha 1 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 1 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func mips -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_100_1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/t2i_10",
    "content": "# bash\nNAME=t2i_10\nBUILD_ARGS=\"-R 100 -L 200 -alpha 1 -num_passes 2 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 1 -verbose -rerank_factor 2\"\nTYPE_ARGS=\"-data_type float -dist_func mips -file_type bin\"\n\nPATH=data/$NAME\nDATA_FILE=$PATH/base.fbin\nQUERY_FILE=$PATH/query.fbin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_100_1\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n"
  },
  {
    "path": "algorithms/vamana/scripts/wikipedia_cohere",
    "content": "# bash\nBUILD_ARGS=\"-R 64 -L 128 -alpha .98 -num_passes 1 -quantize_bits 8 -verbose\"\nQUERY_ARGS=\"-quantize_bits 16 -quantize_mode 5 -verbose -rerank_factor 6\"\nTYPE_ARGS=\"-data_type float -dist_func mips -file_type bin\"\n\nPATH=data/wikipedia_cohere\nDATA_FILE=$PATH/base.bin\nQUERY_FILE=$PATH/query.bin\nGROUNDTRUTH_FILE=$PATH/groundtruth\nGRAPH_FILE=$PATH/graphs/graph_64_98\n\n# build\necho ./neighbors $BUILD_ARGS $TYPE_ARGS -base_path $DATA_FILE -graph_outfile $GRAPH_FILE\n\n# query \necho ./neighbors $QUERY_ARGS $TYPE_ARGS -base_path $DATA_FILE -query_path $QUERY_FILE -gt_path $GROUNDTRUTH_FILE -graph_path $GRAPH_FILE\n\n"
  },
  {
    "path": "algorithms/vamanaRange/CMakeLists.txt",
    "content": "# TODO: fix build\n#add_executable(neighbors-vamanaRange ../bench/neighborsTime.C)\n#  target_link_libraries(neighbors-vamanaRange PRIVATE parlay)\n#  target_precompile_headers(neighbors-vamanaRange PRIVATE neighbors.h)\n\n"
  },
  {
    "path": "algorithms/vamanaRange/Makefile",
    "content": "include ../bench/parallelDefsANN\n\nREQUIRE = ../utils/beamSearch.h index.h  ../utils/check_nn_recall.h ../utils/NSGDist.h ../utils/parse_results.h ../utils/graph.h ../utils/point_range.h\nBENCH = neighbors\n\ninclude ../bench/MakeBench\n"
  },
  {
    "path": "algorithms/vamanaRange/index.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <math.h>\n\n#include <algorithm>\n#include <random>\n#include <set>\n\n#include \"../utils/NSGDist.h\"\n#include \"../utils/point_range.h\"\n#include \"../utils/graph.h\"\n#include \"../utils/types.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/delayed.h\"\n#include \"parlay/random.h\"\n#include \"../utils/beamSearch.h\"\n\n\ntemplate<typename Point, typename PointRange, typename indexType>\nstruct knn_index {\n  using distanceType = typename Point::distanceType;\n  using pid = std::pair<indexType, distanceType>;\n  using PR = PointRange;\n  using GraphI = Graph<indexType>;\n  \n\n  BuildParams BP;\n  std::set<indexType> delete_set; \n  indexType start_point;\n\n  knn_index(BuildParams &BP) : BP(BP) {}\n\n  indexType get_start() { return start_point; }\n\n  //robustPrune routine as found in DiskANN paper, with the exception\n  //that the new candidate set is added to the field new_nbhs instead\n  //of directly replacing the out_nbh of p\n  std::pair<parlay::sequence<indexType>, long>\n  robustPrune(indexType p, parlay::sequence<pid>& cand,\n              GraphI &G, PR &Points, double alpha, bool add = true) {\n    // add out neighbors of p to the candidate set.\n    size_t out_size = G[p].size();\n    std::vector<pid> candidates;\n    long distance_comps = 0;\n    for (auto x : cand) candidates.push_back(x);\n\n    if(add){\n      for (size_t i=0; i<out_size; i++) {\n        distance_comps++;\n        candidates.push_back(std::make_pair(G[p][i], Points[G[p][i]].distance(Points[p])));\n      }\n    }\n\n    // Sort the candidate set according to distance from p\n    auto less = [&](pid a, pid b) { return a.second < b.second; };\n    std::sort(candidates.begin(), candidates.end(), less);\n\n    // remove any duplicates\n    auto new_end =std::unique(candidates.begin(), candidates.end(),\n\t\t\t      [&] (auto x, auto y) {return x.first == y.first;});\n    candidates = std::vector(candidates.begin(), new_end);\n    \n    std::vector<indexType> new_nbhs;\n    new_nbhs.reserve(BP.R);\n\n    size_t candidate_idx = 0;\n\n    while (new_nbhs.size() < BP.R && candidate_idx < candidates.size()) {\n      // Don't need to do modifications.\n      int p_star = candidates[candidate_idx].first;\n      candidate_idx++;\n      if (p_star == p || p_star == -1) {\n        continue;\n      }\n\n      new_nbhs.push_back(p_star);\n\n      for (size_t i = candidate_idx; i < candidates.size(); i++) {\n        int p_prime = candidates[i].first;\n        if (p_prime != -1) {\n          distance_comps++;\n          distanceType dist_starprime = Points[p_star].distance(Points[p_prime]);\n          distanceType dist_pprime = candidates[i].second;\n          if (alpha * dist_starprime <= dist_pprime) {\n            candidates[i].first = -1;\n          }\n        }\n      }\n    }\n\n    auto new_neighbors_seq = parlay::to_sequence(new_nbhs);\n    return std::pair(new_neighbors_seq, distance_comps);\n  }\n\n  //wrapper to allow calling robustPrune on a sequence of candidates \n  //that do not come with precomputed distances\n  std::pair<parlay::sequence<indexType>, long>\n  robustPrune(indexType p, parlay::sequence<indexType> candidates,\n              GraphI &G, PR &Points, double alpha, bool add = true){\n\n    parlay::sequence<pid> cc;\n    long distance_comps = 0;\n    cc.reserve(candidates.size()); // + size_of(p->out_nbh));\n    for (size_t i=0; i<candidates.size(); ++i) {\n      distance_comps++;\n      cc.push_back(std::make_pair(candidates[i], Points[candidates[i]].distance(Points[p])));\n    }\n    auto [ngh_seq, dc] = robustPrune(p, cc, G, Points, alpha, add);\n    return std::pair(ngh_seq, dc + distance_comps);\n  }\n\n  // add ngh to candidates without adding any repeats\n  template<typename rangeType1, typename rangeType2>\n  void add_neighbors_without_repeats(const rangeType1 &ngh, rangeType2& candidates) {\n    std::unordered_set<indexType> a;\n    for (auto c : candidates) a.insert(c);\n    for (int i=0; i < ngh.size(); i++) \n      if (a.count(ngh[i]) == 0) candidates.push_back(ngh[i]);\n  }\n\n  void set_start(){start_point = 0;}\n\n  void build_index(GraphI &G, PR &Points, stats<indexType> &BuildStats, bool sort_neighbors = true){\n    std::cout << \"Building graph...\" << std::endl;\n    set_start();\n    parlay::sequence<indexType> inserts = parlay::tabulate(Points.size(), [&] (size_t i){\n\t\t\t\t\t    return static_cast<indexType>(i);});\n\n    if(BP.two_pass) batch_insert(inserts, G, Points, BuildStats, 1.0, true, 2, .02);\n    batch_insert(inserts, G, Points, BuildStats, BP.alpha, true, 2, .02);\n    if (sort_neighbors) {\n      parlay::parallel_for (0, G.size(), [&] (long i) {\n        auto less = [&] (indexType j, indexType k) {\n                      return Points[i].distance(Points[j]) < Points[i].distance(Points[k]);};\n        G[i].sort(less);});\n    }\n  }\n\n  void batch_insert(parlay::sequence<indexType> &inserts,\n                     GraphI &G, PR &Points, stats<indexType> &BuildStats, double alpha,\n                    bool random_order = false, double base = 2,\n                    double max_fraction = .02, bool print=true) {\n    for(int p : inserts){\n      if(p < 0 || p > (int) G.size()){\n        std::cout << \"ERROR: invalid point \"\n                  << p << \" given to batch_insert\" << std::endl;\n        abort();\n      }\n    }\n    size_t n = G.size();\n    size_t m = inserts.size();\n    size_t inc = 0;\n    size_t count = 0;\n    float frac = 0.0;\n    float progress_inc = .1;\n    size_t max_batch_size = std::min(\n        static_cast<size_t>(max_fraction * static_cast<float>(n)), 1000000ul);\n    //fix bug where max batch size could be set to zero \n    if(max_batch_size == 0) max_batch_size = n;\n    parlay::sequence<int> rperm;\n    if (random_order)\n      rperm = parlay::random_permutation<int>(static_cast<int>(m));\n    else\n      rperm = parlay::tabulate(m, [&](int i) { return i; });\n    auto shuffled_inserts =\n        parlay::tabulate(m, [&](size_t i) { return inserts[rperm[i]]; });\n    parlay::internal::timer t_beam(\"beam search time\");\n    parlay::internal::timer t_bidirect(\"bidirect time\");\n    parlay::internal::timer t_prune(\"prune time\");\n    t_beam.stop();\n    t_bidirect.stop();\n    t_prune.stop();\n    while (count < m) {\n      size_t floor;\n      size_t ceiling;\n      if (pow(base, inc) <= max_batch_size) {\n        floor = static_cast<size_t>(pow(base, inc)) - 1;\n        ceiling = std::min(static_cast<size_t>(pow(base, inc + 1)) - 1, m);\n        count = std::min(static_cast<size_t>(pow(base, inc + 1)) - 1, m);\n      } else {\n        floor = count;\n        ceiling = std::min(count + static_cast<size_t>(max_batch_size), m);\n        count += static_cast<size_t>(max_batch_size);\n      }\n      parlay::sequence<parlay::sequence<indexType>> new_out_(ceiling-floor);\n      // search for each node starting from the start_point, then call\n      // robustPrune with the visited list as its candidate set\n      t_beam.start();\n      parlay::parallel_for(floor, ceiling, [&](size_t i) {\n        size_t index = shuffled_inserts[i];\n        QueryParams QP((long) 0, BP.L, (double) 0.0, (long) Points.size(), (long) G.max_degree());\n        auto [beam_visited, bs_distance_comps] =\n          beam_search<Point, PointRange, indexType>(Points[index], G, Points, start_point, QP);\n        auto [beam, visited] = beam_visited;\n        BuildStats.increment_dist(index, bs_distance_comps);\n        BuildStats.increment_visited(index, visited.size());\n\n        long rp_distance_comps;\n        std::tie(new_out_[i-floor], rp_distance_comps) = robustPrune(index, visited, G, Points, alpha);\n        BuildStats.increment_dist(index, rp_distance_comps);\n      });\n      t_beam.stop();\n\n      // make each edge bidirectional by first adding each new edge\n      //(i,j) to a sequence, then semisorting the sequence by key values\n      t_bidirect.start();\n\n      auto flattened = parlay::delayed::flatten(parlay::tabulate(ceiling - floor, [&](size_t i) {\n        indexType index = shuffled_inserts[i + floor];\n        return parlay::delayed::map(new_out_[i], [=] (indexType ngh) {\n                                      return std::pair(ngh, index);});}));\n      auto grouped_by = parlay::group_by_key_ordered(parlay::delayed::to_sequence(flattened));\n      \n      parlay::parallel_for(floor, ceiling, [&](size_t i) {\n         G[shuffled_inserts[i]].update_neighbors(new_out_[i-floor]);\n      });\n\n      t_bidirect.stop();\n      t_prune.start();\n      // finally, add the bidirectional edges; if they do not make\n      // the vertex exceed the degree bound, just add them to out_nbhs;\n      // otherwise, use robustPrune on the vertex with user-specified alpha\n      parlay::parallel_for(0, grouped_by.size(), [&](size_t j) {\n        auto &[index, candidates] = grouped_by[j];\n\tsize_t newsize = candidates.size() + G[index].size();\n        if (newsize <= BP.R) {\n\t  add_neighbors_without_repeats(G[index], candidates);\n\t  G[index].update_neighbors(candidates);\n        } else {\n          auto [new_out_2_, distance_comps] = robustPrune(index, std::move(candidates), G, Points, alpha);\n\t  G[index].update_neighbors(new_out_2_);    \n          BuildStats.increment_dist(index, distance_comps);\n        }\n      });\n      t_prune.stop();\n      if (print) {\n        auto ind = frac * n;\n        if (floor <= ind && ceiling > ind) {\n          frac += progress_inc;\n          std::cout << \"Pass \" << 100 * frac << \"% complete\"\n                    << std::endl;\n        }\n      }\n      inc += 1;\n    }\n    t_beam.total();\n    t_bidirect.total();\n    t_prune.total();\n  }\n\n};\n"
  },
  {
    "path": "algorithms/vamanaRange/neighbors.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <algorithm>\n\n#include \"../utils/NSGDist.h\"\n#include \"../utils/beamSearch.h\"\n#include \"../utils/check_nn_recall.h\"\n#include \"../utils/parse_results.h\"\n#include \"../utils/mips_point.h\"\n#include \"../utils/euclidian_point.h\"\n#include \"../utils/stats.h\"\n#include \"../utils/types.h\"\n#include \"../utils/graph.h\"\n#include \"index.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n\n\ntemplate<typename Point, typename PointRange_, typename indexType>\nvoid ANN(Graph<indexType> &G, long k, BuildParams &BP,\n         PointRange_ &Query_Points,\n         groundTruth<indexType> GT, char *res_file,\n         bool graph_built, PointRange_ &Points) {\n\n  indexType start_point;\n  double idx_time;\n  stats<unsigned int> BuildStats(G.size());\n  parlay::internal::timer t(\"ANN\");\n  if(graph_built){\n    idx_time = 0;\n  } else {\n    if (sizeof(typename PointRange_::T) >= 4) {\n      if (Points[0].is_metric()) {\n        using QT = uint8_t;\n        using QPoint = Euclidian_Point<QT>;\n        using QPR = PointRange<QT, QPoint>;\n        QPR pr(Points);\n        using findex = knn_index<QPoint, QPR, indexType>;\n        findex I(BP);\n        I.build_index(G, pr, BuildStats, false);\n        start_point = I.get_start();\n      } else {\n        using QT = uint8_t;\n        using QPoint = Quantized_Mips_Point<QT>;\n        using QPR = PointRange<QT, QPoint>;\n        QPR pr(Points);\n        using findex = knn_index<QPoint, QPR, indexType>;\n        findex I(BP);\n        I.build_index(G, pr, BuildStats, false);\n        start_point = I.get_start();\n      }\n    } else {\n      using findex = knn_index<Point, PointRange_, indexType>;\n      findex I(BP);\n      I.build_index(G, Points, BuildStats, false);\n      start_point = I.get_start();\n    }\n    idx_time = t.next_time();\n  }\n\n  std::string name = \"Vamana\";\n  std::string params =\n      \"R = \" + std::to_string(BP.R) + \", L = \" + std::to_string(BP.L);\n  auto [avg_deg, max_deg] = graph_stats_(G);\n  auto vv = BuildStats.visited_stats();\n\n  long build_num_distances = parlay::reduce(parlay::map(BuildStats.distances, [] (auto x) {return (long) x;}));\n\n  Graph_ G_(name, params, G.size(), avg_deg, max_deg, idx_time);\n  G_.print();\n\n  parlay::internal::timer t_range(\"range search time\");\n  double radius = BP.radius;\n  double radius_2 = BP.radius_2;\n  std::cout << \"radius = \" << radius << \" radius_2 = \" << radius_2 << std::endl;\n  QueryParams QP;\n  QP.limit = (long) G.size();\n  QP.degree_limit = (long) G.max_degree();\n  QP.cut = 1.535;\n  QP.k = 0;\n  QP.beamSize = 45;\n  long n = Points.size();\n  parlay::sequence<long> counts(n);\n  parlay::sequence<long> distance_comps(n);\n  parlay::parallel_for(0, G.size(), [&] (long i) {\n    parlay::sequence<indexType> pts;\n    pts.push_back(Points[i].id()); //Points[i].id());\n    auto [r, dc] = range_search(Points[i], G, Points, pts, radius, radius_2, QP, true);\n    counts[i] = r.size();\n    distance_comps[i] = dc;});\n  t_range.total();\n  long range_num_distances = parlay::reduce(distance_comps);\n\n  std::cout << \"edges within range: \" << parlay::reduce(counts) << std::endl;\n  std::cout << \"distance comparisons during build = \" << build_num_distances << std::endl;\n  std::cout << \"distance comparisons during range = \" << range_num_distances << std::endl;\n\n  // brute force\n  if (false) {\n    parlay::sequence<parlay::sequence<indexType>> in_radius(G.size());\n    parlay::parallel_for(0, G.size(), [&] (long i) {\n      if (i % 10000 == 0) std::cout << \".\" << std::flush;\n      parlay::sequence<indexType> pts;\n      long cnt = 0;\n      for (long j=0; j < i; j++) \n        if (Points[i].distance(Points[j]) <= radius) {\n          in_radius[i].push_back(j);\n          //in_radius[j].push_back(i);\n        }\n                                      }, 1);\n    parlay::parallel_for (0, G.size(), [&] (long i) {\n                                         //std::sort(in_radius[i].begin(), in_radius[i].end());\n                                         counts[i] = in_radius[i].size();\n                                       });\n    \n    std::cout << \"gt count: \" << parlay::reduce(counts) * 2 << std::endl;\n  }\n}\n\n"
  },
  {
    "path": "build/_deps/parlaylib-subbuild/CMakeLists.txt",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\ncmake_minimum_required(VERSION 3.28.0)\n\n# Reject any attempt to use a toolchain file. We must not use one because\n# we could be downloading it here. If the CMAKE_TOOLCHAIN_FILE environment\n# variable is set, the cache variable will have been initialized from it.\nunset(CMAKE_TOOLCHAIN_FILE CACHE)\nunset(ENV{CMAKE_TOOLCHAIN_FILE})\n\n# We name the project and the target for the ExternalProject_Add() call\n# to something that will highlight to the user what we are working on if\n# something goes wrong and an error message is produced.\n\nproject(parlaylib-populate NONE)\n\n\n# Pass through things we've already detected in the main project to avoid\n# paying the cost of redetecting them again in ExternalProject_Add()\nset(GIT_EXECUTABLE [==[/usr/bin/git]==])\nset(GIT_VERSION_STRING [==[2.25.1]==])\nset_property(GLOBAL PROPERTY _CMAKE_FindGit_GIT_EXECUTABLE_VERSION\n  [==[/usr/bin/git;2.25.1]==]\n)\n\n\ninclude(ExternalProject)\nExternalProject_Add(parlaylib-populate\n                     \"UPDATE_DISCONNECTED\" \"False\" \"GIT_REPOSITORY\" \"https://github.com/cmuparlay/parlaylib.git\" \"EXTERNALPROJECT_INTERNAL_ARGUMENT_SEPARATOR\" \"GIT_TAG\" \"master\"\n                    SOURCE_DIR          \"/usr0/home/guyb/cvs/ParlayANN/build/_deps/parlaylib-src\"\n                    BINARY_DIR          \"/usr0/home/guyb/cvs/ParlayANN/build/_deps/parlaylib-build\"\n                    CONFIGURE_COMMAND   \"\"\n                    BUILD_COMMAND       \"\"\n                    INSTALL_COMMAND     \"\"\n                    TEST_COMMAND        \"\"\n                    USES_TERMINAL_DOWNLOAD  YES\n                    USES_TERMINAL_UPDATE    YES\n                    USES_TERMINAL_PATCH     YES\n)\n\n\n"
  },
  {
    "path": "data_tools/Makefile",
    "content": "include ../algorithms/bench/parallelDefsANN\n\nvec_to_bin : vec_to_bin.cpp\n\t$(CC) $(CFLAGS) -o vec_to_bin vec_to_bin.cpp $(LFLAGS) \n\ncompute_groundtruth : compute_groundtruth.cpp\n\t$(CC) $(CFLAGS) -o compute_groundtruth compute_groundtruth.cpp $(LFLAGS) \n\ncompute_range_groundtruth : compute_range_groundtruth.cpp\n\t$(CC) $(CFLAGS) -o compute_range_groundtruth compute_range_groundtruth.cpp $(LFLAGS) \n\ncrop : crop.cpp\n\t$(CC) $(CFLAGS) -o crop crop.cpp $(LFLAGS) \n\nrandom_sample : random_sample.cpp\n\t$(CC) $(CFLAGS) -o random_sample random_sample.cpp $(LFLAGS) "
  },
  {
    "path": "data_tools/compute_groundtruth.cpp",
    "content": "/*\n  Example usage:\n    ./compute_groundtruth -base_path ~/data/sift/sift-1M \\\n    -query_path ~/data/sift/query-10K -data_type uint8 \\\n    -dist_func Euclidian -k 100 -gt_path ~/data/sift/GT/sift-1M.gt\n*/\n\n#include <iostream>\n#include <algorithm>\n#include <cstdint>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/io.h\"\n#include \"utils/euclidian_point.h\"\n#include \"utils/mips_point.h\"\n#include \"utils/point_range.h\"\n#include \"../algorithms/bench/parse_command_line.h\"\n\nusing pid = std::pair<int, float>;\nusing namespace parlayANN;\n\ntemplate<typename PointRange>\nparlay::sequence<parlay::sequence<pid>> compute_groundtruth(PointRange &B, \n  PointRange &Q, int k){\n    unsigned d = B.dimension();\n    size_t q = Q.size();\n    size_t b = B.size();\n    auto answers = parlay::tabulate(q, [&] (size_t i){  \n        float topdist = B[0].d_min();   \n        int toppos;\n        parlay::sequence<pid> topk;\n        for(size_t j=0; j<b; j++){\n            // float dist = D->distance((Q[i].coordinates).begin(), (B[j].coordinates).begin(), d);\n            float dist = Q[i].distance(B[j]);\n            if(topk.size() < k){\n                if(dist > topdist){\n                    topdist = dist;   \n                    toppos = topk.size();\n                }\n                topk.push_back(std::make_pair((int) j, dist));\n            }\n            else if(dist < topdist){\n                float new_topdist=B[0].d_min();  \n                int new_toppos=0;\n                topk[toppos] = std::make_pair((int) j, dist);\n                for(size_t l=0; l<topk.size(); l++){\n                    if(topk[l].second > new_topdist){\n                        new_topdist = topk[l].second;\n                        new_toppos = (int) l;\n                    }\n                }\n                topdist = new_topdist;\n                toppos = new_toppos;\n            }\n        }\n        return topk;\n    });\n    std::cout << \"Done computing groundtruth\" << std::endl;\n    return answers;\n}\n\n// ibin is the same as the binary groundtruth format used in the\n// big-ann-benchmarks (see: https://big-ann-benchmarks.com/neurips21.html)\nvoid write_ibin(parlay::sequence<parlay::sequence<pid>> &result, const std::string outFile, int k){\n    std::cout << \"Writing file with dimension \" << result[0].size() << std::endl;\n    std::cout << \"File contains groundtruth for \" << result.size() << \" query points\" << std::endl;\n\n    auto less = [&] (pid a, pid b) {return a.second < b.second;};\n    parlay::sequence<int> preamble = {static_cast<int>(result.size()), static_cast<int>(result[0].size())};\n    size_t n = result.size();\n    parlay::parallel_for(0, result.size(), [&] (size_t i){\n      parlay::sort_inplace(result[i], less);\n    });\n    auto ids = parlay::tabulate(result.size(), [&] (size_t i){\n        parlay::sequence<int> data;\n        for(int j=0; j<k; j++){\n          data.push_back(static_cast<int>(result[i][j].first));\n        }\n        return data;\n    });\n    auto distances = parlay::tabulate(result.size(), [&] (size_t i){\n        parlay::sequence<float> data;\n        for(int j=0; j<k; j++){\n          data.push_back(static_cast<float>(result[i][j].second));\n        }\n        return data;\n    });\n    parlay::sequence<int> flat_ids = parlay::flatten(ids);\n    parlay::sequence<float> flat_dists = parlay::flatten(distances);\n\n    auto pr = preamble.begin();\n    auto id_data = flat_ids.begin();\n    auto dist_data = flat_dists.begin();\n    std::ofstream writer;\n    writer.open(outFile, std::ios::binary | std::ios::out);\n    writer.write((char *) pr, 2*sizeof(int));\n    writer.write((char *) id_data, n * k * sizeof(int));\n    writer.write((char *) dist_data, n * k * sizeof(float));\n    writer.close();\n}\n\nint main(int argc, char* argv[]) {\n  commandLine P(argc,argv,\n  \"[-base_path <b>] [-query_path <q>] \"\n      \"[-data_type <d>] [-k <k> ] [-dist_func <d>] [-gt_path <outfile>]\");\n\n  char* gFile = P.getOptionValue(\"-gt_path\");\n  char* qFile = P.getOptionValue(\"-query_path\");\n  char* bFile = P.getOptionValue(\"-base_path\");\n  char* vectype = P.getOptionValue(\"-data_type\");\n  char* dfc = P.getOptionValue(\"-dist_func\");\n  int k = P.getOptionIntValue(\"-k\", 100);\n\n  std::string df = std::string(dfc);\n  if(df != \"Euclidian\" && df != \"mips\"){\n    std::cout << \"Error: invalid distance type: specify Euclidian or mips\" << std::endl;\n    abort();\n  }\n\n  std::string tp = std::string(vectype);\n  if((tp != \"uint8\") && (tp != \"int8\") && (tp != \"float\")){\n    std::cout << \"Error: data type not specified correctly, specify int8, uint8, or float\" << std::endl;\n    abort();\n  }\n\n  std::cout << \"Computing the \" << k << \" nearest neighbors\" << std::endl;\n\n  int maxDeg = 0;\n\n  parlay::sequence<parlay::sequence<pid>> answers;\n  std::string base = std::string(bFile);\n  std::string query = std::string(qFile);\n\n\n  if(tp == \"float\"){\n    std::cout << \"Detected float coordinates\" << std::endl;\n    if(df == \"Euclidian\"){\n      auto B = PointRange<Euclidian_Point<float>>(bFile);\n      auto Q = PointRange<Euclidian_Point<float>>(qFile);\n      answers = compute_groundtruth<PointRange<Euclidian_Point<float>>>(B, Q, k);\n    } else if(df == \"mips\"){\n      auto B = PointRange<Mips_Point<float>>(bFile);\n      auto Q = PointRange<Mips_Point<float>>(qFile);\n      answers = compute_groundtruth<PointRange<Mips_Point<float>>>(B, Q, k);\n    }\n  }else if(tp == \"uint8\"){\n    std::cout << \"Detected uint8 coordinates\" << std::endl;\n    if(df == \"Euclidian\"){\n      auto B = PointRange<Euclidian_Point<uint8_t>>(bFile);\n      auto Q = PointRange<Euclidian_Point<uint8_t>>(qFile);\n      answers = compute_groundtruth<PointRange<Euclidian_Point<uint8_t>>>(B, Q, k);\n    } else if(df == \"mips\"){\n      auto B = PointRange<Mips_Point<uint8_t>>(bFile);\n      auto Q = PointRange<Mips_Point<uint8_t>>(qFile);\n      answers = compute_groundtruth<PointRange<Mips_Point<uint8_t>>>(B, Q, k);\n    }\n  } else if(tp == \"int8\"){\n    std::cout << \"Detected int8 coordinates\" << std::endl;\n    if(df == \"Euclidian\"){\n      auto B = PointRange<Euclidian_Point<int8_t>>(bFile);\n      auto Q = PointRange<Euclidian_Point<int8_t>>(qFile);\n      answers = compute_groundtruth<PointRange<Euclidian_Point<int8_t>>>(B, Q, k);\n    } else if(df == \"mips\"){\n      auto B = PointRange<Mips_Point<int8_t>>(bFile);\n      auto Q = PointRange<Mips_Point<int8_t>>(qFile);\n      answers = compute_groundtruth<PointRange<Mips_Point<int8_t>>>(B, Q, k);\n    }\n  }\n  write_ibin(answers, std::string(gFile), k);\n\n  return 0;\n}\n"
  },
  {
    "path": "data_tools/compute_range_groundtruth.cpp",
    "content": "#include <iostream>\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/io.h\"\n// #include \"utils/types.h\"\n#include \"utils/euclidian_point.h\"\n#include \"utils/mips_point.h\"\n#include \"utils/point_range.h\"\n#include \"../algorithms/bench/parse_command_line.h\"\n\nusing namespace parlayANN;\n\ntemplate<typename PointRange>\nparlay::sequence<parlay::sequence<int>> compute_range_groundtruth(PointRange &B, \n  PointRange &Q, float r){\n    unsigned d = B.dimension();\n    size_t q = Q.size();\n    size_t b = B.size();\n    auto answers = parlay::tabulate(q, [&] (size_t i){  \n        parlay::sequence<int> results;\n        for(size_t j=0; j<b; j++){\n            float dist = Q[i].distance(B[j]);\n            if(dist <= r) results.push_back(j);\n        }\n        return results;\n    });\n    std::cout << \"Done computing groundtruth\" << std::endl;\n    return answers;\n}\n\ntemplate<typename PointRange, typename T>\nvoid write_nonzero_elts(parlay::sequence<parlay::sequence<int>> &result, PointRange Query_Points, const std::string outFile){\n  size_t n = result.size();\n    parlay::sequence<int> sizes = parlay::tabulate(n, [&] (size_t i){\n        if(result[i].size() > 0) return 1;\n        return 0;\n    });\n    size_t num_nonzero = parlay::reduce(sizes);\n\n    std::cout << \"Number of nonzero elements: \" << num_nonzero << std::endl;\n    int d = Query_Points.dimension();\n    parlay::sequence<int> preamble = {static_cast<int>(num_nonzero), static_cast<int>(d)};\n    parlay::sequence<T> data(num_nonzero*d);\n    parlay::sequence<parlay::sequence<int>> to_flatten = parlay::tabulate(n, [&] (size_t i){\n      parlay::sequence<int> ret;\n      if(result[i].size() > 0) ret.push_back(i);\n      return ret;\n    });\n    parlay::sequence<int> indices = parlay::flatten(to_flatten);\n    if(indices.size() != num_nonzero) abort();\n    parlay::parallel_for(0, indices.size(), [&] (size_t i){\n      for(int j=0; j<d; j++) data[d*i+j] = Query_Points[indices[i]][j];\n    });\n\n    std::ofstream writer;\n    writer.open(outFile, std::ios::binary | std::ios::out);\n    writer.write((char *) (preamble.begin()), 2*sizeof(int));\n    writer.write((char *) (data.begin()), num_nonzero * sizeof(T) * d);\n    writer.close();\n\n\n}\n\nvoid write_rangeres(parlay::sequence<parlay::sequence<int>> &result, const std::string outFile){\n    std::cout << \"File contains range groundtruth for \" << result.size() << \" data points\" << std::endl;\n\n    \n    size_t n = result.size();\n    parlay::sequence<int> sizes = parlay::tabulate(n, [&] (size_t i){\n        return static_cast<int>(result[i].size());\n    });\n    size_t num_matches = parlay::reduce(sizes);\n\n    std::cout << \"Number of nonzero matches: \" << num_matches << std::endl;\n    parlay::sequence<int> preamble = {static_cast<int>(n), static_cast<int>(num_matches)};\n\n    auto flat_ids = parlay::flatten(result);\n\n    auto pr = preamble.begin();\n    auto size_data = sizes.begin();\n    auto id_data = flat_ids.begin();\n    std::ofstream writer;\n    writer.open(outFile, std::ios::binary | std::ios::out);\n    writer.write((char *) pr, 2*sizeof(int));\n    writer.write((char *) size_data, n * sizeof(int));\n    writer.write((char *) id_data, num_matches * sizeof(int));\n    writer.close();\n}\n\n\nint main(int argc, char* argv[]) {\n  commandLine P(argc,argv,\n  \"[-base_path <b>] [-query_path <q>] \"\n      \"[-data_type <d>] [-r <r> ] [-dist_func <d>] [-gt_path <outfile>]\");\n\n  char* gFile = P.getOptionValue(\"-gt_path\");\n  char* qFile = P.getOptionValue(\"-query_path\");\n  char* bFile = P.getOptionValue(\"-base_path\");\n  char* vectype = P.getOptionValue(\"-data_type\");\n  char* dfc = P.getOptionValue(\"-dist_func\");\n  float r = P.getOptionDoubleValue(\"-r\", 0);\n\n  std::string df = std::string(dfc);\n  if(df != \"Euclidian\" && df != \"mips\"){\n    std::cout << \"Error: invalid distance type: specify Euclidian or mips\" << std::endl;\n    abort();\n  }\n\n  std::string tp = std::string(vectype);\n  if((tp != \"uint8\") && (tp != \"int8\") && (tp != \"float\")){\n    std::cout << \"Error: data type not specified correctly, specify int8, uint8, or float\" << std::endl;\n    abort();\n  }\n\n  std::cout << \"Computing the groundtruth for radius \" << r << std::endl;\n\n  parlay::sequence<parlay::sequence<int>> answers;\n\n  if(tp == \"float\"){\n    std::cout << \"Detected float coordinates\" << std::endl;\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<float>> B = PointRange<Euclidian_Point<float>>(bFile);\n      PointRange<Euclidian_Point<float>> Q = PointRange<Euclidian_Point<float>>(qFile);\n      answers = compute_range_groundtruth<PointRange<Euclidian_Point<float>>>(B, Q, r);\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<float>> B = PointRange<Mips_Point<float>>(bFile);\n      PointRange<Mips_Point<float>> Q = PointRange<Mips_Point<float>>(qFile);\n      answers = compute_range_groundtruth<PointRange<Mips_Point<float>>>(B, Q, r);\n    }\n  }else if(tp == \"uint8\"){\n    std::cout << \"Detected uint8 coordinates\" << std::endl;\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<uint8_t>> B = PointRange<Euclidian_Point<uint8_t>>(bFile);\n      PointRange<Euclidian_Point<uint8_t>> Q = PointRange<Euclidian_Point<uint8_t>>(qFile);\n      answers = compute_range_groundtruth<PointRange<Euclidian_Point<uint8_t>>>(B, Q, r);\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<uint8_t>> B = PointRange<Mips_Point<uint8_t>>(bFile);\n      PointRange<Mips_Point<uint8_t>> Q = PointRange<Mips_Point<uint8_t>>(qFile);\n      answers = compute_range_groundtruth<PointRange<Mips_Point<uint8_t>>>(B, Q, r);\n    }\n  }else if(tp == \"int8\"){\n    std::cout << \"Detected int8 coordinates\" << std::endl;\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<int8_t>> B = PointRange<Euclidian_Point<int8_t>>(bFile);\n      PointRange<Euclidian_Point<int8_t>> Q = PointRange<Euclidian_Point<int8_t>>(qFile);\n      answers = compute_range_groundtruth<PointRange<Euclidian_Point<int8_t>>>(B, Q, r);\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<int8_t>> B = PointRange<Mips_Point<int8_t>>(bFile);\n      PointRange<Mips_Point<int8_t>> Q = PointRange<Mips_Point<int8_t>>(qFile);\n      answers = compute_range_groundtruth<PointRange<Mips_Point<int8_t>>>(B, Q, r);\n    }\n  }\n  write_rangeres(answers, std::string(gFile));\n  \n\n  return 0;\n}\n\n"
  },
  {
    "path": "data_tools/crop.cpp",
    "content": "#include <iostream>\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/io.h\"\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n// using namespace benchIO;\n// *************************************************************\n// Parsing code (should move to common?)\n// *************************************************************\n\n// returns a pointer and a length\nstd::pair<char*, size_t> mmapStringFromFile(const char* filename) {\n  struct stat sb;\n  int fd = open(filename, O_RDONLY);\n  if (fd == -1) {\n    perror(\"open\");\n    exit(-1);\n  }\n  if (fstat(fd, &sb) == -1) {\n    perror(\"fstat\");\n    exit(-1);\n  }\n  if (!S_ISREG(sb.st_mode)) {\n    perror(\"not a file\\n\");\n    exit(-1);\n  }\n  char* p =\n      static_cast<char*>(mmap(0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));\n  if (p == MAP_FAILED) {\n    perror(\"mmap\");\n    exit(-1);\n  }\n  if (close(fd) == -1) {\n    perror(\"close\");\n    exit(-1);\n  }\n  size_t n = sb.st_size;\n  return std::make_pair(p, n);\n}\n\ntemplate<typename T>\nvoid crop_file(char* iFile, int n, char* oFile){\n  auto [fileptr, length] = mmapStringFromFile(iFile);\n\n  int dim = *((int*) (fileptr+4));\n  std::cout << \"Writing \" << n << \" points with dimension \" << dim << std::endl;\n  parlay::sequence<int> preamble = {n, dim};\n\n  T* data = (T*)(fileptr+8);\n  std::ofstream writer;\n  writer.open(oFile, std::ios::binary | std::ios::out);\n\n  size_t bytes_to_write = n;\n  bytes_to_write *= dim;\n  bytes_to_write *= sizeof(T);\n\n  writer.write((char *)(preamble.begin()), 2*sizeof(int));\n  writer.write((char *) data, bytes_to_write);\n  writer.close();\n}\n\nint main(int argc, char* argv[]) {\n  if (argc != 5) {\n    std::cout << \"usage: crop <base> <num_points_to_crop> <tp> <oF>\" << std::endl;\n    return 1;\n  }\n  \n\n  int n = atoi(argv[2]);\n\n  std::string tp = std::string(argv[3]);\n\n  if(tp == \"float\") crop_file<float>(argv[1], n, argv[4]);\n  else if(tp == \"uint8\") crop_file<uint8_t>(argv[1], n, argv[4]);\n  else if(tp == \"int8\") crop_file<int8_t>(argv[1], n, argv[4]);\n  else{\n    std::cout << \"Invalid type, specify float, uint8, or int8\" << std::endl;\n  }\n\n  return 0;\n}"
  },
  {
    "path": "data_tools/random_sample.cpp",
    "content": "#include <iostream>\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/io.h\"\n#include \"parlay/random.h\"\n#include \"utils/mmap.h\"\n\n#include <random>\n\nusing namespace parlayANN;\n\ntemplate<typename T>\nvoid random_sample(char* iFile, int n, char* oFile){\n    auto [fileptr, length] = mmapStringFromFile(iFile);\n\n    int fsize = *((int*) fileptr);\n    int dim = *((int*) (fileptr+4));\n    std::cout << \"Writing \" << n << \" points with dimension \" << dim << std::endl;\n    parlay::sequence<int> preamble = {n, dim};\n\n    parlay::random_generator gen;\n    std::uniform_int_distribution<long> dis(0, fsize - 1);\n    auto indices = parlay::tabulate(n, [&](size_t i) {\n        auto r = gen[i];\n        return dis(r);\n    });\n\n    T* start = (T*)(fileptr + 8);\n\n    auto to_flatten = parlay::tabulate(n, [&] (size_t i){\n        parlay::sequence<T> data;\n        for(int j=0; j<dim; j++){\n            data.push_back(*(start + dim*indices[i] + j));\n        }\n        return data;\n    });\n\n    auto data = parlay::flatten(to_flatten);\n\n    std::ofstream writer;\n    writer.open(oFile, std::ios::binary | std::ios::out);\n    writer.write((char *)(preamble.begin()), 2*sizeof(int));\n    writer.write((char *)(data.begin()), dim*n*sizeof(T));\n    writer.close();\n}\n\nint main(int argc, char* argv[]) {\n  if (argc != 5) {\n    std::cout << \"usage: random_sample <base> <num_points_to_crop> <tp> <oF>\" << std::endl;\n    return 1;\n  }\n  \n\n  int n = atoi(argv[2]);\n\n  std::string tp = std::string(argv[3]);\n\n  if(tp == \"float\") random_sample<float>(argv[1], n, argv[4]);\n  else if(tp == \"uint8\") random_sample<uint8_t>(argv[1], n, argv[4]);\n  else if(tp == \"int8\") random_sample<int8_t>(argv[1], n, argv[4]);\n  else{\n    std::cout << \"Invalid type, specify float, uint8, or int8\" << std::endl;\n  }\n\n  return 0;\n}"
  },
  {
    "path": "data_tools/vec_to_bin.cpp",
    "content": "#include <iostream>\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/io.h\"\n\n// convert from .bvec file to .u8bin file\n\n\nauto convert_onebyte(const char* infile, const char* outfile) {\n  auto str = parlay::chars_from_file(infile);\n  int dims = *((int *) str.data());\n  int n = str.size()/(dims+4);\n  std::cout << \"n = \" << n << \" d = \" << dims << std::endl;\n  auto vects = parlay::tabulate(n, [&] (size_t i) {\n\t\t     return parlay::to_sequence(str.cut(4 + i * (4 + dims), (i+1) * (4 + dims)));});\n  parlay::sequence<char> head(8);\n  *((int *) head.data()) = n;\n  *(((int *) head.data()) + 1) = dims;\n  auto strout = parlay::append(head, parlay::flatten(vects));\n  parlay::chars_to_file(strout, outfile);\n}\n\nauto convert_fourbyte(const char* infile, const char* outfile) {\n  auto str = parlay::chars_from_file(infile);\n  int dims = *((int *) str.data());\n  int n = str.size()/(4*dims+4);\n  std::cout << \"n = \" << n << \" d = \" << dims << std::endl;\n  auto vects = parlay::tabulate(n, [&] (size_t i) {\n\t\t     return parlay::to_sequence(str.cut(4 + i * (4 + 4*dims), (i+1) * (4 + 4*dims)));});\n  parlay::sequence<char> head(8);\n  *((int *) head.data()) = n;\n  *(((int *) head.data()) + 1) = dims;\n  auto strout = parlay::append(head, parlay::flatten(vects));\n  parlay::chars_to_file(strout, outfile);\n}\n\nint main(int argc, char* argv[]) {\n  if (argc != 4) {\n    std::cout << \"usage: vec_to_bin type <infile> <outfile>\" << std::endl;\n    return 1;\n  }\n  std::string tp = std::string(argv[1]);\n  if(tp == \"uint8\") convert_onebyte(argv[2], argv[3]);\n  else if(tp == \"float\" | tp == \"int\") convert_fourbyte(argv[2], argv[3]);\n  else{\n    std::cout << \"invalid type: specify uint8, float, or int\" << std::endl;\n    abort();\n  }\n  return 0;\n}\n"
  },
  {
    "path": "docs/README.md",
    "content": "# ParlayANN\n\nParlayANN is a library of approximate nearest neighbor search algorithms, along with a set of useful tools for designing such algorithms. It is written in C++ and uses parallel primitives from [ParlayLib](https://cmuparlay.github.io/parlaylib/). Currently it includes implementations of the ANNS algorithms [DiskANN](https://github.com/microsoft/DiskANN), [HNSW](https://github.com/nmslib/hnswlib), [HCNNG](https://github.com/jalvarm/hcnng), and [pyNNDescent](https://pynndescent.readthedocs.io/en/latest/).\n\nTo install, [clone the repo](https://github.com/cmuparlay/ParlayANN/tree/main) and then initiate the ParlayLib submodule:\n\n```bash\ngit submodule init\ngit submodule update\n```\n\nSee the following documentation for help getting started:\n- [Quickstart](https://cmuparlay.github.io/ParlayANN/quickstart)\n- [Algorithms](https://cmuparlay.github.io/ParlayANN/algorithms)\n- [Data Tools](https://cmuparlay.github.io/ParlayANN/data_tools)\n\nThis repository was built for our paper [Scaling Graph-Based ANNS Algorithms to Billion-Size Datasets: A Comparative Analsyis](https://arxiv.org/abs/2305.04359). If you use this repository for your own work, please cite us:\n\n```bibtex\n@inproceedings{ANNScaling,\n  author = {Manohar, Magdalen Dobson and Shen, Zheqi and Blelloch, Guy and Dhulipala, Laxman and Gu, Yan and Simhadri, Harsha Vardhan and Sun, Yihan},\n  title = {ParlayANN: Scalable and Deterministic Parallel Graph-Based Approximate Nearest Neighbor Search Algorithms},\n  year = {2024},\n  isbn = {9798400704352},\n  publisher = {Association for Computing Machinery},\n  address = {New York, NY, USA},\n  url = {https://doi.org/10.1145/3627535.3638475},\n  doi = {10.1145/3627535.3638475},\n  booktitle = {Proceedings of the 29th ACM SIGPLAN Annual Symposium on Principles and Practice of Parallel Programming},\n  pages = {270–285},\n  numpages = {16},\n  keywords = {nearest neighbor search, vector search, parallel algorithms},\n  location = {Edinburgh, United Kingdom},\n  series = {PPoPP '24}\n}\n```\n\nThe range search algorithms are from our paper [Range Retrieval with Graph-Based Indices](https://arxiv.org/abs/2502.13245). If you use this repository for your own work, please cite us:\n\n```bibtex\n@misc{manohar2025range,\n      title={Range Retrieval with Graph-Based Indices}, \n      author={Magdalen Dobson Manohar and Taekseung Kim and Guy E. Blelloch},\n      year={2025},\n      eprint={2502.13245},\n      archivePrefix={arXiv},\n      primaryClass={cs.IR},\n      url={https://arxiv.org/abs/2502.13245}, \n}\n```"
  },
  {
    "path": "docs/algorithms.md",
    "content": "# Algorithms\n\nThe algorithms in this folder share a common main file and thus a common commandline interface. The commandline interface allows the user to build an ANNS graph and write it to an outfile, load a graph and search it, or build and search in one shot. It contains several \"generic\" parameters that can be repurposed for a new benchmark. In the following examples, we provide instructions for building indices using bash. The instructions assume that the user has downloaded, converted, and built groundtruth for the 100K slice of the BIGANN dataset, as shown in the quickstart instructions. If you want to use range searching, we also provide instructions for computing range groundtruth in `data_tools.md`.\n\n### Universal Parameters\n\n#### Parameters for building:\n1. **-graph_outfile** (optional): if graph is not already built, path the graph is written to. This is optional; if not provided, the graph will be built and will print timing and statistics before terminating.\n2. **-data_type**: type of the base and query vectors. Currently \"float\", \"int8\", and \"uint8\" are supported.\n3. **-dist_func**: the distance function to use when calculating nearest neighbors. Currently Euclidian distance (\"euclidian\") and maximum inner product search (\"mips\") are supported.\n4. **-base_path**: path to the base file. We only work with files in the .bin format; for your convenience, a converter from the popular .vecs format has been provided in the data tools folder.\n\n#### Parameters for searching:\n\n1. **-gt_path**: path to the ground truth, in .ibin format.\n2. **-graph_path** (optional): path to the ANNS graph in the case of using an already built graph.\n3. **-query_path**: path to the queries in .bin format.\n4. **-res_path** (optional): path where a CSV file of results can be written (it is written to in append form, so it can be used to collect results of multiple runs).\n5. **-k** (`long`): the number of nearest neighbors to search for.\n\n\n### Algorithms\n\nNext we provide some descriptions and example commandline arguments for each algorithm in the implementation.\n\n## Vamana (DiskANN)\n\nVamana, also known as DiskANN, is an algorithm introduced in [DiskANN: Fast Accurate Billion-point Nearest\nNeighbor Search on a Single Node](https://proceedings.neurips.cc/paper_files/paper/2019/file/09853c7fb1d3f8ee67a61b6bf4a7f8e6-Paper.pdf) by Subramanya et al., with original code in the [DiskANN repo](https://github.com/microsoft/DiskANN). It builds a graph incrementally, and its insert procedure does a variant on greedy search or beam search with a frontier size $L$ on the existing graph and uses the nodes visited during the search as edge candidates. The visited nodes are pruned to a list of size $R$ by pruning out points that are likely to become long edges of triangles, with a parameter $a$ that is used to control how aggressive the prune step is. \n\n1. **R** (`long`): the degree bound.\n2. **L** (`long`): the beam width to use when building the graph.\n3. **alpha** (`double`): the pruning parameter.\n4. **two_pass** (`bool`): optional argument that allows the user to build the graph with two passes or just one (two passes approximately doubles the build time, but provides higher accuracy).\n\nTo build a Vamana graph on BIGANN-100K and save it to memory, use the following commandline:\n\n```bash\ncd vamana\nmake\n./neighbors -R 32 -L 64 -alpha 1.2 -graph_outfile ../../data/sift/sift_learn_32_64 -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\nTo load an already built graph and query it, use the following:\n```bash\ncd vamana\nmake\n./neighbors -R 32 -L 64 -alpha 1.2 -graph_path ../../data/sift/sift_learn_32_64 -query_path ../../data/sift/sift_query.fbin -gt_path ../../data/sift/sift-100K -res_path ../../data/vamana_res.csv -data_type float  -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\nTo build, query, and save to memory, use the following:\n```bash\ncd vamana\nmake\n./neighbors -R 32 -L 64 -alpha 1.2 -graph_outfile ../../data/sift/sift_learn_32_64 -query_path ../../data/sift/sift_query.fbin -gt_path ../../data/sift/sift-100K -res_path ../../data/vamana_res.csv -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\nTo execute range search using Vamana, use the following commandline. Note that range searching currently does not support exporting data to a CSV file: \n\n```bash\ncd ../rangeSearch/vamanaRange\nmake\n./range -R 32 -L 64 -alpha 1.2 -graph_outfile ../../data/sift/sift_learn_32_64 -query_path ../../data/sift/sift_query.fbin -gt_path ../../data/sift/sift-100K-range -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\n## HNSW\n\nHNSW is an algorithm proposed in [Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs](https://dl.acm.org/doi/10.1109/TPAMI.2018.2889473) by Yu et al., of which an implementation is available at [hnswlib](https://github.com/nmslib/hnswlib) and is maintained by the paper authors. The HNSW incrementally builds a hierarchical structure consisting of multiple layers, where each layer is a proximity graphs with the Navigable Small World (NSW) property. The lower layers are always the supersets of the upper ones, and the bottom layer contains all the base points. In the process of constructions, each point is randomly assigned with a height in a logarithmic distribution and repeatedly inserted into all the layers below. As the two points incident to an edge in higher layers has longer distance, the hierarchical structures allows to quickly approach the query point at high layers first and then do fine-grained search at low layers.\nIts parameters are as follows:\n\n1. **m** (`long`): the degree bound. Typically between 16 and 64. The graph at the bottom layer (layer0) has the degree bound of $2m$ while graphs at upper layers have degree bound of $m$.\n2. **efc** (`long`): the beam width to use when building the graph. Should be set at least $2.5m$, and up to 500.\n3. **alpha** (`double`): the pruning parameter. Should be set between 1.0 and 1.15 for similarity measures that are not metrics (e.g. maximum inner product), and between 0.8 and 1.0 for metric spaces. \n4. **ml** (`double`): optional argument to control the number of layers (height). Increasing $ml$ results in more layers which increases the build time but potentially improve the query performance; however, improper settings of $ml$ (too high or too low) can incur much work of query thus impacting the query performance. It should be set around $1/log~m$.\n\nA commandline with suggested parameters for HNSW for the BIGANN-100K dataset is as follows:\n```bash\ncd HNSW\nmake\n./neighbors -m 20 -efc 50 -alpha 0.9 -ml 0.34 -graph_outfile ../../data/sift/sift_learn_20_50_034 -query_path ../../data/sift/sift_query.fbin -gt_path ../../data/sift/sift-100K -res_path ../../data/hnsw_res.csv -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\n## HCNNG\n\nHCNNG is an algorithm taken from [Hierarchical Clustering-Based Graphs for Large Scale Approximate Nearest Neighbor Search](https://www.researchgate.net/publication/334477189_Hierarchical_Clustering-Based_Graphs_for_Large_Scale_Approximate_Nearest_Neighbor_Search) by Munoz et al. and original implemented in [this repository](https://github.com/jalvarm/hcnng). Roughly, it builds a tree by recursively partitioning the data using random partitions until it reaches a leaf size of at most 1000 points, and then builds a bounded-degree MST with the points in each leaf. The edges from the MST are used as the edges in the graph. The algorithm repeats this process a total of $L$ times and merges the edges into the graph on each iteration. Its parameters are as follows:\n\n1. **mst_deg** (`long`): the degree bound of the graph built by each individual cluster tree.\n2. **num_clusters** (`long`): the number of cluster trees.\n3. **cluster_size** (`long`): the leaf size of each cluster tree.\n\nA commandline with suggested parameters for HCNNG for the BIGANN-100K dataset is as follows:\n\n```bash\ncd HCNNG\nmake\n./neighbors -cluster_size 1000 -mst_deg 3 -num_clusters 30  -graph_outfile ../../data/sift/sift_learn_3_10 -query_path ../../data/sift/sift_query.fbin -gt_path ../../data/sift/sift-100K -res_path ../../data/hcnng_res.csv -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\n## pyNNDescent\n\n[pyNNDescent](https://pynndescent.readthedocs.io/en/latest/) is an ANNS algorithm by Leland McInnes. It works based on the principle that in a k-nearest neighbor graph, a neighbor of a neighbor is likely to be a neighbor. It finds an approximate nearest neighbor graph by building some number of random clustering trees and calculating exhaustive nearest neighbors at the leaves. Then, it proceeds in rounds, connecting each vertex to the neighbors of each neighbors and keeping the $R$ closest neighbors on each round. After terminating, it prunes out long edges of triangles; in our version, we add a pruning parameter $d$ to control for a denser graph if desired.\n\n1. **R** (`long`): the graph degree bound.\n2. **num_clusters** (`long`): the number of cluster trees to use when initializing the graph.\n3. **cluster_size** (`long`): the leaf size of the cluster trees.\n4. **alpha** (`double`): the pruning parameter for the final pruning step.\n5. **delta** (`double`): the early stopping parameter for the nnDescent process.\n\n\n```bash\ncd pyNNDescent\nmake\n./neighbors -R 40 -cluster_size 100 -num_clusters 10 -alpha 1.2 -delta 0.05 -graph_outfile ../../data/sift/sift_learn_30 -query_path ../../data/sift/sift_query.fbin -gt_path ../../data/sift/sift-100K -res_path ../../data/pynn_res.csv -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\n## Searching\n\nEach graph is searched using a version of the greedy search/beam search algorithm described in [DiskANN: Fast Accurate Billion-point Nearest Neighbor Search on a Single Node](https://proceedings.neurips.cc/paper_files/paper/2019/file/09853c7fb1d3f8ee67a61b6bf4a7f8e6-Paper.pdf). It also incorporates the optimization suggested in [Pruned Bi-Directed K-Nearest Neighbor Graph for Proximity Search](https://link.springer.com/chapter/10.1007/978-3-319-46759-7_2) of pruning the search frontier when it includes points that are far away from the current $k$-nearest neighbor. Instead of taking in parameters specified by the user, the search routine tries a wide variety of parameter choices and reports those that maximize QPS for a given recall value. The search parameters (see `types.h` in the utils folder) can be tuned if you are developing your own algorithm and are as follows:\n\n1. **Q** (`long`): the beam width. Must be set at least $k$. Controls the number of candidate neighbors retained at any point in the search and is for the most part the chief determinant of accuracy and speed of the search. \n2. **k** (`long`): number of nearest neighbors to search for. \n3. **cut** (`double`): controls pruning the frontier of points that are far away from the current $k$ nearest neighbors. Used only for distance functions that are true metrics (as opposed to similarities that may not obey the triangle inequality, etc.)\n4. **visited limit** (`long`): controls the maximum number of vertices visited during the beam search. Used for low accuracy searches; set to the number of vertices in the graph if you don't want any limit.\n5. **degree limit** (`long`): controls the maximum number of out-neighbors read when visiting a vertex. Also useful for low accuracy searches. Note that if the out-neighbors are not sorted in order of distance, it does not make sense to use this parameter. \n\n\n"
  },
  {
    "path": "docs/data_tools.md",
    "content": "# Data Tools\n\nParlayANN provides various useful tools for manipulating and reading datasets in common formats. For all of the examples below, it is assumed that the BIGANN dataset is downloaded and stored in ParlayANN/data/sift. You can do this using the following commandline:\n\n```bash\nmkdir -p data && cd data\nwget ftp://ftp.irisa.fr/local/texmex/corpus/sift.tar.gz\ntar -xf sift.tar.gz\n```\n\nYou then need to convert two of the datasets from the .fvecs format to the binary format as follows:\n\n```bash\nmake vec_to_bin\n./vec_to_bin float ../data/sift/sift_learn.fvecs ../data/sift/sift_learn.fbin\n./vec_to_bin float ../data/sift/sift_query.fvecs ../data/sift/sift_query.fbin\n```\n\n## Compute Groundtruth\n\nParlayANN supports computing the exact groundtruth for k-nearest neighbors for bin files files. The commandline for computing the groundtruth takes the following parameters:\n1. **-base_path**: pointer to the base file, which ground truth will be calculate with respect to.\n2. **-query_path**: pointer to the query file, for which the ground truth will be calculated.\n3. **-data_type**: type of the query and base files. Current options are \"uint8\", \"int8\", and \"float\".\n4. **-k**: the number of nearest neighbors to calculate. Default is 100.\n5. **-dist_func**: the distance function to use when computing the ground truth. Current options are \"euclidian\" for Euclidian distance and \"mips\" for maximum inner product.\n6. **-gt_path**: the path where the new groundtruth file will be written\n\nThe following is an example of how to compute the groundtruth for a 100K slice of the BIGANN dataset:\n\n```bash\nmake compute_groundtruth\n./compute_groundtruth -base_path ../data/sift/sift_learn.fbin -query_path ../data/sift/sift_query.fbin -data_type float -k 100 -dist_func Euclidian -gt_path ../data/sift/sift-100K\n```\n\n## Compute Range Groundtruth\n\nWe also support computing groundtruth for range search, i.e. finding all points in a given radius. The commandline takes the following parameters:\n1. **-base_path**: pointer to the base file, which ground truth will be calculate with respect to.\n2. **-query_path**: pointer to the query file, for which the ground truth will be calculated.\n3. **-data_type**: type of the query and base files. Current options are \"uint8\", \"int8\", and \"float\".\n4. **-rad**: the radius for which to calculate the groundtruth.\n5. **-dist_func**: the distance function to use when computing the ground truth. Current options are \"euclidian\" for Euclidian distance and \"mips\" for maximum inner product.\n6. **-gt_path**: the path where the new groundtruth file will be written\n\nAn example commandline is as follows:\n\n```bash\nmake compute_range_groundtruth\n./compute_groundtruth -base_path ../data/sift/sift_learn.fbin -query_path ../data/sift/sift_query.fbin -data_type float -rad 5000 -dist_func Euclidian -gt_path ../data/sift/sift-100K-range\n```\n\nThe range groundtruth is written in binary format in integers. It consists of first the number of datapoints, followed by the total number of range results for the whole dataset, followed by the number of results for each individual point, followed by the result ids. \n\n## File Conversion\n\nParlayANN supports converting a .vecs file to a .bin file for vectors with `float`, `uint8`, and `int` coordinates. An example commandline:\n\n```bash\nmake vec_to_bin\n./vec_to_bin float ../data/sift/sift_learn.fvecs ../data/sift/sift_learn.fbin\n```\n\n## Cropping\n\nCrop a file to the desired size:\n\n```bash\nmake crop\n./crop ../data/sift/sift_learn.fbin 50000 float ../data/sift/sift_50K.fbin\n```\n\n## Random Sampling\n\nTake a random sample of desired size from a file:\n\n```bash\nmake random_sample\n./random_sample ../data/sift/sift_learn.fbin 50000 float ../data/sift/sift_50K_random.fbin\n```\n\n\n"
  },
  {
    "path": "docs/quickstart.md",
    "content": "# Quickstart\n\nThe following is a crash course in quickly building and querying an index using ParlayANN.\n\nFirst, download a 100K slice of the BIGANN dataset.\n\n```bash\nmkdir -p data && cd data\nwget ftp://ftp.irisa.fr/local/texmex/corpus/sift.tar.gz\ntar -xf sift.tar.gz\n```\n\nNext, convert it from the .fvecs format to binary format:\n\n```bash\ncd ../data_tools\nmake vec_to_bin\n./vec_to_bin float ../data/sift/sift_learn.fvecs ../data/sift/sift_learn.fbin\n./vec_to_bin float ../data/sift/sift_query.fvecs ../data/sift/sift_query.fbin\n```\n\nNext, calculate its ground truth up to $k=100$. See the README in the data_tools folder for an explanation of each parameter.\n\n```bash\ncd ../data_tools\nmake compute_groundtruth\n./compute_groundtruth -base_path ../data/sift/sift_learn.fbin -query_path ../data/sift/sift_query.fbin -data_type float -k 100 -dist_func Euclidian -gt_path ../data/sift/sift-100K\n```\n\nTo build an index using Vamana and write it to an outfile, use the following commandline:\n```bash\ncd ../algorithms/vamana\nmake\n./neighbors -R 32 -L 64 -alpha 1.2 two_pass 0 -graph_outfile ../../data/sift/sift_learn_32_64 -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\nYou should see the following output; timing will vary based on your machine and these times are taken from a machine with 72 cores:\n\n```bash\nDetected 100000 points with dimension 128\nBuilding graph...\nPass 10% complete\nPass 20% complete\nPass 30% complete\nPass 40% complete\nPass 50% complete\nPass 60% complete\nPass 70% complete\nPass 80% complete\nPass 90% complete\nPass 100% complete\nbeam search time: total: 0.3436\nbidirect time: total: 0.0557\nprune time: total: 0.3751\nAverage visited: 68, Tail visited: 76\nVamana graph built with 100000 points and parameters R = 32, L = 64\nGraph has average degree 26.94 and maximum degree 32\nGraph built in 0.8123 seconds\nParlay time: 0.8138\nWriting graph with 100000 points and max degree 32\n```\n\nTo load and then query the index, use the following command:\n```bash\ncd ../algorithms/vamana\nmake\n./neighbors -R 32 -L 64 -a 1.2 -graph_path ../../data/sift/sift_learn_32_64 -query_path ../../data/sift/sift_query.fbin -gt_path ../../data/sift/sift-100K -data_type float -dist_func Euclidian -base_path ../../data/sift/sift_learn.fbin\n```\n\nYou should see an output similar to the following; timings were taken using a machine with 72 cores.\n\n```bash\nDetected 10000 points with num results 100\nDetected 100000 points with dimension 128\nDetected 10000 points with dimension 128\nDetected 100000 points with max degree 32\nAverage visited: 0, Tail visited: 0\nVamana graph built with 100000 points and parameters R = 32, L = 64\nGraph has average degree 26.9416 and maximum degree 32\nGraph built in 0 seconds\nFor 10@10 recall = 0.11027, QPS = 5.05561e+06, Q = 10, cut = 1.35, visited limit = 6, degree limit: 16, average visited = 6, average cmps = 89\nFor 10@10 recall = 0.2032, QPS = 4.40141e+06, Q = 10, cut = 1.35, visited limit = 8, degree limit: 16, average visited = 8, average cmps = 115\nFor 10@10 recall = 0.36414, QPS = 2.58598e+06, Q = 10, cut = 1.35, visited limit = 10, degree limit: 19, average visited = 10, average cmps = 169\nFor 10@10 recall = 0.45495, QPS = 2.68528e+06, Q = 10, cut = 1.35, visited limit = 10, degree limit: 22, average visited = 10, average cmps = 195\nFor 10@10 recall = 0.59687, QPS = 2.35627e+06, Q = 10, cut = 1.35, visited limit = 10, degree limit: 25, average visited = 10, average cmps = 219\nFor 10@10 recall = 0.61139, QPS = 2.07857e+06, Q = 13, cut = 1.35, visited limit = 13, degree limit: 22, average visited = 13, average cmps = 245\nFor 10@10 recall = 0.7209, QPS = 1.86532e+06, Q = 11, cut = 1.35, visited limit = 11, degree limit: 28, average visited = 11, average cmps = 261\nFor 10@10 recall = 0.75036, QPS = 1.90006e+06, Q = 13, cut = 1.35, visited limit = 13, degree limit: 25, average visited = 13, average cmps = 274\nFor 10@10 recall = 0.81667, QPS = 1.80538e+06, Q = 15, cut = 1.35, visited limit = 15, degree limit: 25, average visited = 15, average cmps = 310\nFor 10@10 recall = 0.86956, QPS = 1.55304e+06, Q = 15, cut = 1.35, visited limit = 15, degree limit: 28, average visited = 15, average cmps = 339\nFor 10@10 recall = 0.92219, QPS = 1.4652e+06, Q = 15, cut = 1.35, visited limit = 15, degree limit: 32, average visited = 15, average cmps = 372\nFor 10@10 recall = 0.95779, QPS = 1.15009e+06, Q = 12, cut = 1.35, visited limit = 100000, degree limit: 32, average visited = 18, average cmps = 436\nFor 10@10 recall = 0.97133, QPS = 955658, Q = 17, cut = 1.35, visited limit = 100000, degree limit: 32, average visited = 22, average cmps = 529\nFor 10@10 recall = 0.98078, QPS = 775014, Q = 24, cut = 1.35, visited limit = 100000, degree limit: 32, average visited = 29, average cmps = 656\nFor 10@10 recall = 0.99151, QPS = 473530, Q = 45, cut = 1.35, visited limit = 100000, degree limit: 32, average visited = 49, average cmps = 1026\nFor 10@10 recall = 0.99509, QPS = 351296, Q = 70, cut = 1.35, visited limit = 100000, degree limit: 32, average visited = 71, average cmps = 1356\nFor 10@10 recall = 0.99912, QPS = 186532, Q = 180, cut = 1.35, visited limit = 100000, degree limit: 32, average visited = 145, average cmps = 2279\nFor 10@10 recall = 0.9995, QPS = 151930, Q = 250, cut = 1.35, visited limit = 100000, degree limit: 32, average visited = 174, average cmps = 2546\nFor 10@10 recall = 0.99995, QPS = 13560.6, Q = 1000, cut = 10, visited limit = 100000, degree limit: 32, average visited = 1003, average cmps = 7885\nFor 10@10 recall = 0.99995, QPS = 13560.6, Q = 1000, cut = 10, visited limit = 100000, degree limit: 32, average visited = 1003, average cmps = 7885\n```\n\n"
  },
  {
    "path": "docs/rangesearch.md",
    "content": "# Range Search \n\nRange search is defined as finding every point within a specified radius of a query point with respect to some dataset. This repository contains the algorithms introduced in the paper [Range Retrieval with Graph-Based Indices](https://arxiv.org/abs/2502.13245).\n\n## Sample commandline and parameters\n\nRange groundtruth file should be computed before running these commands. These tools are provided in data_tools library. For further explanation, see [Data Tools](https://cmuparlay.github.io/ParlayANN/data_tools)\n\nAn example commandline for generating range ground truth is shown below. This example is also explained in the [Quickstart](https://cmuparlay.github.io/ParlayANN/quickstart) guide. In this case, the **SIFT dataset** refers to the BIGANN dataset, as described in the [Quickstart](https://cmuparlay.github.io/ParlayANN/quickstart).\n\n\n```\ncd ../data_tools\nmake compute_range_groundtruth\n./compute_range_groundtruth -base_path ../data/sift/sift_learn.fbin -query_path ../data/sift/sift_query.fbin -data_type float -k 100 -dist_func Euclidian -gt_path ../data/sift/sift-100K\n```\n\nTo run a range search on sift run:\n```\ncd rangeSearch/vamanaRange\nR=../../data/sift\nmake\n./range  -alpha 1.15 -R 64 -L 128 -r 10000 -base_path $R/sift_learn.fbin -data_type uint8 -dist_func Euclidian -query_path $R/sift_query.fbin  -gt_path $R/range_gt_1M_10000 -search_mode beamSearch -early_stop -graph_path $R/graph1M  -early_stopping_radius 30000\n```\n\nAll other parameters are same as in  [Algorithms](https://cmuparlay.github.io/ParlayANN/algorithms). Here we add descriptions for parameters that are new.\n\n1. **-r**(`double`): Range search radius\n2. **-search_mode**(`string`): The search mode to use can be specified. Possible options are ['doubling', 'greedy', 'beam'], corresponding to the three algorithms introduced in our paper. The default option is beam search.\n3. **-early_stop**(optional): Flag for early stopping. With this flag on, range search would stop early based on early stopping radius.\n4. **-early_stopping_radius**(`double`): Radius for early stopping. Typically larger than the range search radius.\n"
  },
  {
    "path": "python/__init__.py",
    "content": "# // This code is part of the Problem Based Benchmark Suite (PBBS)\n# // Copyright (c) 2011 Guy Blelloch and the PBBS team\n# //\n# // Permission is hereby granted, free of charge, to any person obtaining a\n# // copy of this software and associated documentation files (the\n# // \"Software\"), to deal in the Software without restriction, including\n# // without limitation the rights (to use, copy, modify, merge, publish,\n# // distribute, sublicense, and/or sell copies of the Software, and to\n# // permit persons to whom the Software is furnished to do so, subject to\n# // the following conditions:\n# //\n# // The above copyright notice and this permission notice shall be included\n# // in all copies or substantial portions of the Software.\n# //\n# // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\"\"\"\n# Documentation Overview\n`ParlayANNpy` is mostly structured around 2 distinct processes: [Index Builder Functions](#index-builders) and [Search Classes](#search-classes)\nThis code is adapted from the DiskANN pybindings and heavily draws on their implementation.\n\nIt also includes a few nascent [utilities](#utilities).\n\nAnd lastly, it makes substantial use of type hints, with various shorthand [type aliases](#parameter-and-response-type-aliases) documented. \nWhen reading the `ParlayANNpy` code we refer to the type aliases.\n\n## Index Builders\n- `build_vamana_index` - Builds an in-memory Vamana index\n\n## Search Classes\n- `VamanaIndex` - fully in memory and static\n\n## Parameter Defaults\n- `ParlayANNpy.defaults` - Default values exported from the C++ extension for Python users\n\n## Parameter and Response Type Aliases\n- `DistanceMetric` - What distance metrics does `ParlayANNpy` support?\n- `VectorDType` - What vector datatypes does `ParlayANNpy` support?\n- `QueryResponse` - What can I expect as a response to my search?\n- `QueryResponseBatch` - What can I expect as a response to my batch search?\n- `VectorIdentifier` - What types do `ParlayANNpy` support as vector identifiers?\n- `VectorIdentifierBatch` - A batch of identifiers of the exact same type. The type can change, but they must **all** change.\n- `VectorLike` - How does a vector look to `ParlayANNpy`, to be inserted or searched with.\n- `VectorLikeBatch` - A batch of those vectors, to be inserted or searched with.\n- `Metadata` - DiskANN vector binary file metadata (num_points, vector_dim)\n\n## Utilities\n- `vectors_to_file` - Turns a 2 dimensional `numpy.typing.NDArray[VectorDType]` with shape `(number_of_points, vector_dim)` into a ParlayANN vector bin file.\n- `vectors_from_file` - Reads a ParlayANN vector bin file representing stored vectors into a numpy ndarray.\n- `vectors_metadata_from_file` - Reads metadata stored in a ParlayANN vector bin file without reading the entire file\n- `valid_dtype` - Checks if a given vector dtype is supported by `ParlayANNpy`\n\"\"\"\n\nfrom typing import Any, Literal, NamedTuple, Type, Union\n\nimport numpy as np\nfrom numpy import typing as npt\n\nDistanceMetric = Literal[\"Euclidian\", \"mips\"]\n\"\"\" Type alias for one of {\"l2\", \"mips\",} \"\"\"\nVectorDType = Union[Type[np.float32], Type[np.int8], Type[np.uint8]]\n\"\"\" Type alias for one of {`numpy.float32`, `numpy.int8`, `numpy.uint8`} \"\"\"\nVectorLike = npt.NDArray[VectorDType]\n\"\"\" Type alias for something that can be treated as a vector \"\"\"\nVectorLikeBatch = npt.NDArray[VectorDType]\n\"\"\" Type alias for a batch of VectorLikes \"\"\"\nVectorIdentifier = np.uint32\n\"\"\" \nType alias for a vector identifier, whether it be an implicit array index identifier from StaticMemoryIndex or \nStaticDiskIndex, or an explicit tag identifier from DynamicMemoryIndex \n\"\"\"\nVectorIdentifierBatch = npt.NDArray[np.uint32]\n\"\"\" Type alias for a batch of VectorIdentifiers \"\"\"\n\n\nclass QueryResponse(NamedTuple):\n    \"\"\"\n    Tuple with two values, identifiers and distances. Both are 1d arrays, positionally correspond, and will contain the\n    nearest neighbors from [0..k_neighbors)\n    \"\"\"\n\n    identifiers: npt.NDArray[VectorIdentifier]\n    \"\"\" A `numpy.typing.NDArray[VectorIdentifier]` array of vector identifiers, 1 dimensional \"\"\"\n    distances: npt.NDArray[np.float32]\n    \"\"\"\n    A `numpy.typing.NDAarray[numpy.float32]` of distances as calculated by the distance metric function,  1 dimensional\n    \"\"\"\n\n\nclass QueryResponseBatch(NamedTuple):\n    \"\"\"\n    Tuple with two values, identifiers and distances. Both are 2d arrays, with dimensionality determined by the\n    rows corresponding to the number of queries made, and the columns corresponding to the k neighbors\n    requested. The two 2d arrays have an implicit, position-based relationship\n    \"\"\"\n\n    identifiers: npt.NDArray[VectorIdentifier]\n    \"\"\" \n    A `numpy.typing.NDArray[VectorIdentifier]` array of vector identifiers, 2 dimensional. The row corresponds to index \n    of the query, and the column corresponds to the k neighbors requested \n    \"\"\"\n    distances: np.ndarray[np.float32]\n    \"\"\"  \n    A `numpy.typing.NDAarray[numpy.float32]` of distances as calculated by the distance metric function, 2 dimensional. \n    The row corresponds to the index of the query, and the column corresponds to the distance of the query to the \n    *k-th* neighbor \n    \"\"\"\n\n\nfrom . import defaults\nfrom ._builder import build_vamana_index\nfrom ._builder import build_hcnng_index\nfrom ._common import valid_dtype\n# TODO implement searching once index build works\n# from ._dynamic_memory_index import DynamicMemoryIndex\nfrom ._files import (\n    Metadata,\n    vectors_from_file,\n    vectors_metadata_from_file,\n    vectors_to_file,\n)\n# from ._static_disk_index import StaticDiskIndex\n# from ._static_memory_index import StaticMemoryIndex\n\n__all__ = [\n    \"build_vamana_index\",\n    \"build_hcnng_index\",\n    # \"StaticDiskIndex\", //TODO add back search index\n    \"defaults\",\n    \"DistanceMetric\",\n    \"VectorDType\",\n    \"QueryResponse\",\n    \"QueryResponseBatch\",\n    \"VectorIdentifier\",\n    \"VectorIdentifierBatch\",\n    \"VectorLike\",\n    \"VectorLikeBatch\",\n    \"Metadata\",\n    \"vectors_metadata_from_file\",\n    \"vectors_to_file\",\n    \"vectors_from_file\",\n    \"valid_dtype\",\n]"
  },
  {
    "path": "python/_builder.py",
    "content": "# // This code is part of the Problem Based Benchmark Suite (PBBS)\n# // Copyright (c) 2011 Guy Blelloch and the PBBS team\n# //\n# // Permission is hereby granted, free of charge, to any person obtaining a\n# // copy of this software and associated documentation files (the\n# // \"Software\"), to deal in the Software without restriction, including\n# // without limitation the rights (to use, copy, modify, merge, publish,\n# // distribute, sublicense, and/or sell copies of the Software, and to\n# // permit persons to whom the Software is furnished to do so, subject to\n# // the following conditions:\n# //\n# // The above copyright notice and this permission notice shall be included\n# // in all copies or substantial portions of the Software.\n# //\n# // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport os\nimport shutil\nfrom pathlib import Path\nfrom typing import Optional, Tuple, Union\n\nimport numpy as np\n\nfrom . import VectorDType, VectorIdentifierBatch, VectorLikeBatch\nfrom . import _ParlayANNpy as _parlayann\nfrom ._common import (\n    _assert,\n    _assert_is_nonnegative_uint32,\n    _assert_is_positive_uint32,\n    _castable_dtype_or_raise,\n    _valid_metric,\n    _write_index_metadata,\n    valid_dtype,\n)\nfrom ._ParlayANNpy import defaults\nfrom ._files import tags_to_file, vectors_metadata_from_file, vectors_to_file\n\n\ndef _valid_path_and_dtype(\n    data: Union[str, VectorLikeBatch],\n    vector_dtype: VectorDType,\n    index_path: str,\n    index_prefix: str,\n) -> Tuple[str, VectorDType]:\n    if isinstance(data, str):\n        vector_bin_path = data\n        _assert(\n            Path(data).exists() and Path(data).is_file(),\n            \"if data is of type `str`, it must both exist and be a file\",\n        )\n        vector_dtype_actual = valid_dtype(vector_dtype)\n    else:\n        vector_bin_path = os.path.join(index_path, f\"{index_prefix}_vectors.bin\")\n        if Path(vector_bin_path).exists():\n            raise ValueError(\n                f\"The path {vector_bin_path} already exists. Remove it and try again.\"\n            )\n        vector_dtype_actual = valid_dtype(data.dtype)\n        vectors_to_file(vector_file=vector_bin_path, vectors=data)\n\n    return vector_bin_path, vector_dtype_actual\n\n\n\ndef build_memory_index(\n    data: Union[str, VectorLikeBatch],\n    distance_metric: str,\n    index_directory: str,\n    beam_width: int,\n    graph_degree: int,\n    alpha: float = defaults.ALPHA,\n    vector_dtype: Optional[VectorDType] = None,\n    index_prefix: str = \"ann\",\n) -> None:\n    _assert(\n        (isinstance(data, str) and vector_dtype is not None)\n        or isinstance(data, np.ndarray),\n        \"vector_dtype is required if data is a str representing a path to the vector bin file\",\n    )\n    dap_metric = _valid_metric(distance_metric)\n    _assert_is_positive_uint32(complexity, \"complexity\")\n    _assert_is_positive_uint32(graph_degree, \"graph_degree\")\n    _assert(\n        alpha >= 1,\n        \"alpha must be >= 1, and realistically should be kept between [1.0, 2.0)\",\n    )\n    _assert_is_nonnegative_uint32(num_threads, \"num_threads\")\n    _assert_is_nonnegative_uint32(num_pq_bytes, \"num_pq_bytes\")\n    _assert_is_nonnegative_uint32(filter_complexity, \"filter_complexity\")\n    _assert(index_prefix != \"\", \"index_prefix cannot be an empty string\")\n\n    index_path = Path(index_directory)\n    _assert(\n        index_path.exists() and index_path.is_dir(),\n        \"index_directory must both exist and be a directory\",\n    )\n\n    vector_bin_path, vector_dtype_actual = _valid_path_and_dtype(\n        data, vector_dtype, index_directory, index_prefix\n    )\n\n    num_points, dimensions = vectors_metadata_from_file(vector_bin_path)\n\n    if vector_dtype_actual == np.uint8:\n        _builder = _parlayann.build_memory_uint8_index\n    elif vector_dtype_actual == np.int8:\n        _builder = _parlayann.build_memory_int8_index\n    else:\n        _builder = _parlayann.build_memory_float_index\n\n    index_prefix_path = os.path.join(index_directory, index_prefix)\n\n    _builder(\n        distance_metric=dap_metric,\n        data_file_path=vector_bin_path,\n        index_output_path=index_prefix_path,\n        beam_width=beam_width,\n        graph_degree=graph_degree,\n        alpha=alpha,\n    )\n\n    _write_index_metadata(\n        index_prefix_path, vector_dtype_actual, dap_metric, num_points, dimensions\n    )"
  },
  {
    "path": "python/_builder.pyi",
    "content": "# // This code is part of the Problem Based Benchmark Suite (PBBS)\n# // Copyright (c) 2011 Guy Blelloch and the PBBS team\n# //\n# // Permission is hereby granted, free of charge, to any person obtaining a\n# // copy of this software and associated documentation files (the\n# // \"Software\"), to deal in the Software without restriction, including\n# // without limitation the rights (to use, copy, modify, merge, publish,\n# // distribute, sublicense, and/or sell copies of the Software, and to\n# // permit persons to whom the Software is furnished to do so, subject to\n# // the following conditions:\n# //\n# // The above copyright notice and this permission notice shall be included\n# // in all copies or substantial portions of the Software.\n# //\n# // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nfrom typing import BinaryIO, Optional, overload\n\nimport numpy as np\n\nfrom . import VectorDType, VectorIdentifierBatch, VectorLikeBatch\n\ndef numpy_to_diskann_file(vectors: np.ndarray, file_handler: BinaryIO): ...\ndef build_vamana_index(\n    data: str,\n    distance_metric: str,\n    index_directory: str,\n    beam_width: int,\n    graph_degree: int,\n    alpha: float,\n    two_pass: bool,\n    vector_dtype: VectorDType,\n    index_prefix: str,\n) -> None: ...\ndef build_hcnng_index(\n    data: str,\n    distance_metric: str,\n    index_directory: str,\n    mst_deg: int,\n    num_clusters: int,\n    cluster_size: float,\n    vector_dtype: VectorDType,\n    index_prefix: str,\n) -> None: ...\ndef build_pynndescent_index(\n    data: str,\n    distance_metric: str,\n    index_directory: str,\n    max_deg: int,\n    num_clusters: int,\n    cluster_size: float,\n    alpha: float,\n    delta: float,\n    vector_dtype: VectorDType,\n    index_prefix: str,\n) -> None: ...\n"
  },
  {
    "path": "python/_common.py",
    "content": "# // This code is part of the Problem Based Benchmark Suite (PBBS)\n# // Copyright (c) 2011 Guy Blelloch and the PBBS team\n# //\n# // Permission is hereby granted, free of charge, to any person obtaining a\n# // copy of this software and associated documentation files (the\n# // \"Software\"), to deal in the Software without restriction, including\n# // without limitation the rights (to use, copy, modify, merge, publish,\n# // distribute, sublicense, and/or sell copies of the Software, and to\n# // permit persons to whom the Software is furnished to do so, subject to\n# // the following conditions:\n# //\n# // The above copyright notice and this permission notice shall be included\n# // in all copies or substantial portions of the Software.\n# //\n# // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport os\nimport warnings\nfrom enum import Enum\nfrom pathlib import Path\nfrom typing import Literal, NamedTuple, Optional, Tuple, Type, Union\n\nimport numpy as np\n\nfrom . import (\n    DistanceMetric,\n    VectorDType,\n    VectorIdentifierBatch,\n    VectorLike,\n    VectorLikeBatch,\n)\nfrom . import _ParlayANNpy as _parlayann\n\n__ALL__ = [\"valid_dtype\"]\n\n_VALID_DTYPES = [np.float32, np.int8, np.uint8]\n\n\ndef valid_dtype(dtype: Type) -> VectorDType:\n    \"\"\"\n    Utility method to determine whether the provided dtype is supported by `diskannpy`, and if so, the canonical\n    dtype we will use internally (e.g. np.single -> np.float32)\n    \"\"\"\n    _assert_dtype(dtype)\n    if dtype == np.uint8:\n        return np.uint8\n    if dtype == np.int8:\n        return np.int8\n    if dtype == np.float32:\n        return np.float32\n\n\ndef _assert(statement_eval: bool, message: str):\n    if not statement_eval:\n        raise ValueError(message)\n\n\ndef _valid_metric(metric: str) -> str:\n    if not isinstance(metric, str):\n        raise ValueError(\"distance_metric must be a string\")\n    if metric.lower() == \"Euclidian\":\n        return \"Euclidian\"\n    elif metric.lower() == \"mips\":\n        return \"mips\"\n    else:\n        raise ValueError(\"distance_metric must be one of 'l2', 'mips', or 'cosine'\")\n\n\ndef _assert_dtype(dtype: Type):\n    _assert(\n        any(np.can_cast(dtype, _dtype) for _dtype in _VALID_DTYPES),\n        f\"Vector dtype must be of one of type {{(np.single, np.float32), (np.byte, np.int8), (np.ubyte, np.uint8)}}\",\n    )\n\n\ndef _castable_dtype_or_raise(\n    data: Union[VectorLike, VectorLikeBatch, VectorIdentifierBatch], expected: np.dtype\n) -> np.ndarray:\n    if isinstance(data, np.ndarray) and np.can_cast(data.dtype, expected):\n        return data.astype(expected, casting=\"safe\")\n    else:\n        raise TypeError(\n            f\"expecting a numpy ndarray of dtype {expected}, not a {type(data)}\"\n        )\n\n\ndef _assert_2d(vectors: np.ndarray, name: str):\n    _assert(len(vectors.shape) == 2, f\"{name} must be 2d numpy array\")\n\n\n__MAX_UINT32_VAL = 4_294_967_295\n\n\ndef _assert_is_positive_uint32(test_value: int, parameter: str):\n    _assert(\n        test_value is not None and 0 < test_value < __MAX_UINT32_VAL,\n        f\"{parameter} must be a positive integer in the uint32 range\",\n    )\n\n\ndef _assert_is_nonnegative_uint32(test_value: int, parameter: str):\n    _assert(\n        test_value is not None and -1 < test_value < __MAX_UINT32_VAL,\n        f\"{parameter} must be a non-negative integer in the uint32 range\",\n    )\n\n\ndef _assert_is_nonnegative_uint64(test_value: int, parameter: str):\n    _assert(\n        -1 < test_value,\n        f\"{parameter} must be a non-negative integer in the uint64 range\",\n    )\n\n\ndef _assert_existing_directory(path: str, parameter: str):\n    _path = Path(path)\n    _assert(\n        _path.exists() and _path.is_dir(), f\"{parameter} must be an existing directory\"\n    )\n\n\ndef _assert_existing_file(path: str, parameter: str):\n    _path = Path(path)\n    _assert(_path.exists() and _path.is_file(), f\"{parameter} must be an existing file\")\n\n\nclass _DataType(Enum):\n    FLOAT32 = 0\n    INT8 = 1\n    UINT8 = 2\n\n    @classmethod\n    def from_type(cls, vector_dtype: VectorDType) -> \"DataType\":\n        if vector_dtype == np.float32:\n            return cls.FLOAT32\n        if vector_dtype == np.int8:\n            return cls.INT8\n        if vector_dtype == np.uint8:\n            return cls.UINT8\n\n    def to_type(self) -> VectorDType:\n        if self is _DataType.FLOAT32:\n            return np.float32\n        if self is _DataType.INT8:\n            return np.int8\n        if self is _DataType.UINT8:\n            return np.uint8\n\n\n\n\n\ndef _build_metadata_path(index_path_and_prefix: str) -> str:\n    return index_path_and_prefix + \"_metadata.bin\"\n\n\ndef _write_index_metadata(\n    index_path_and_prefix: str,\n    dtype: VectorDType,\n    metric: str,\n    num_points: int,\n    dimensions: int,\n):\n    np.array(\n        [\n            _DataType.from_type(dtype).value,\n            metric,\n            num_points,\n            dimensions,\n        ],\n        dtype=np.uint64,\n    ).tofile(_build_metadata_path(index_path_and_prefix))\n\n\ndef _read_index_metadata(\n    index_path_and_prefix: str,\n) -> Optional[Tuple[VectorDType, str, np.uint64, np.uint64]]:\n    path = _build_metadata_path(index_path_and_prefix)\n    if not Path(path).exists():\n        return None\n    else:\n        metadata = np.fromfile(path, dtype=np.uint64, count=-1)\n        return (\n            _DataType(int(metadata[0])).to_type(),\n            int(metadata[1]).to_str(),\n            metadata[2],\n            metadata[3],\n        )\n\n\ndef _ensure_index_metadata(\n    index_path_and_prefix: str,\n    vector_dtype: Optional[VectorDType],\n    distance_metric: Optional[str],\n    max_vectors: int,\n    dimensions: Optional[int],\n) -> Tuple[VectorDType, str, np.uint64, np.uint64]:\n    possible_metadata = _read_index_metadata(index_path_and_prefix)\n    if possible_metadata is None:\n        _assert(\n            all([vector_dtype, distance_metric, dimensions]),\n            \"distance_metric, vector_dtype, and dimensions must provided if a corresponding metadata file has not \"\n            \"been built for this index, such as when an index was built via the CLI tools or prior to the addition \"\n            \"of a metadata file\",\n        )\n        _assert_dtype(vector_dtype)\n        _assert_is_positive_uint32(max_vectors, \"max_vectors\")\n        _assert_is_positive_uint32(dimensions, \"dimensions\")\n        return vector_dtype, distance_metric, max_vectors, dimensions  # type: ignore\n    else:\n        vector_dtype, distance_metric, num_vectors, dimensions = possible_metadata\n        if max_vectors is not None and num_vectors > max_vectors:\n            warnings.warn(\n                \"The number of vectors in the saved index exceeds the max_vectors parameter. \"\n                \"max_vectors is being adjusted to accommodate the dataset, but any insertions will fail.\"\n            )\n            max_vectors = num_vectors\n        if num_vectors == max_vectors:\n            warnings.warn(\n                \"The number of vectors in the saved index equals max_vectors parameter. Any insertions will fail.\"\n            )\n        return possible_metadata\n\n\ndef _valid_index_prefix(index_directory: str, index_prefix: str) -> str:\n    _assert(\n        index_directory is not None and index_directory != \"\",\n        \"index_directory cannot be None or empty\",\n    )\n    _assert_existing_directory(index_directory, \"index_directory\")\n    _assert(index_prefix != \"\", \"index_prefix cannot be an empty string\")\n    return os.path.join(index_directory, index_prefix)"
  },
  {
    "path": "python/_files.py",
    "content": "# // This code is part of the Problem Based Benchmark Suite (PBBS)\n# // Copyright (c) 2011 Guy Blelloch and the PBBS team\n# //\n# // Permission is hereby granted, free of charge, to any person obtaining a\n# // copy of this software and associated documentation files (the\n# // \"Software\"), to deal in the Software without restriction, including\n# // without limitation the rights (to use, copy, modify, merge, publish,\n# // distribute, sublicense, and/or sell copies of the Software, and to\n# // permit persons to whom the Software is furnished to do so, subject to\n# // the following conditions:\n# //\n# // The above copyright notice and this permission notice shall be included\n# // in all copies or substantial portions of the Software.\n# //\n# // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nimport warnings\nfrom typing import BinaryIO, NamedTuple\n\nimport numpy as np\nimport numpy.typing as npt\n\nfrom . import VectorDType, VectorIdentifierBatch, VectorLikeBatch\nfrom ._common import _assert, _assert_2d, _assert_dtype, _assert_existing_file\n\n\nclass Metadata(NamedTuple):\n    \"\"\"DiskANN binary vector files contain a small stanza containing some metadata about them.\"\"\"\n\n    num_vectors: int\n    \"\"\" The number of vectors in the file. \"\"\"\n    dimensions: int\n    \"\"\" The dimensionality of the vectors in the file. \"\"\"\n\n\ndef vectors_metadata_from_file(vector_file: str) -> Metadata:\n    \"\"\"\n    Read the metadata from a DiskANN binary vector file.\n    ### Parameters\n    - **vector_file**: The path to the vector file to read the metadata from.\n\n    ### Returns\n    `diskannpy.Metadata`\n    \"\"\"\n    _assert_existing_file(vector_file, \"vector_file\")\n    points, dims = np.fromfile(file=vector_file, dtype=np.int32, count=2)\n    return Metadata(points, dims)\n\n\ndef _write_bin(data: np.ndarray, file_handler: BinaryIO):\n    if len(data.shape) == 1:\n        _ = file_handler.write(np.array([data.shape[0], 1], dtype=np.int32).tobytes())\n    else:\n        _ = file_handler.write(np.array(data.shape, dtype=np.int32).tobytes())\n    _ = file_handler.write(data.tobytes())\n\n\ndef vectors_to_file(vector_file: str, vectors: VectorLikeBatch) -> None:\n    \"\"\"\n    Utility function that writes a DiskANN binary vector formatted file to the location of your choosing.\n\n    ### Parameters\n    - **vector_file**: The path to the vector file to write the vectors to.\n    - **vectors**: A 2d array of dtype `numpy.float32`, `numpy.uint8`, or `numpy.int8`\n    \"\"\"\n    _assert_dtype(vectors.dtype)\n    _assert_2d(vectors, \"vectors\")\n    with open(vector_file, \"wb\") as fh:\n        _write_bin(vectors, fh)\n\n\ndef vectors_from_file(vector_file: str, dtype: VectorDType) -> npt.NDArray[VectorDType]:\n    \"\"\"\n    Read vectors from a DiskANN binary vector file.\n\n    ### Parameters\n    - **vector_file**: The path to the vector file to read the vectors from.\n    - **dtype**: The data type of the vectors in the file. Ensure you match the data types exactly\n\n    ### Returns\n    `numpy.typing.NDArray[dtype]`\n    \"\"\"\n    points, dims = vectors_metadata_from_file(vector_file)\n    return np.fromfile(file=vector_file, dtype=dtype, offset=8).reshape(points, dims)\n\n\n\n\n\n"
  },
  {
    "path": "python/big_env.yml",
    "content": "name: bigann\nchannels:\n  - conda-forge\n  - defaults\ndependencies:\n  - _libgcc_mutex=0.1=conda_forge\n  - _openmp_mutex=4.5=2_kmp_llvm\n  - asttokens=2.0.5=pyhd3eb1b0_0\n  - backcall=0.2.0=pyhd3eb1b0_0\n  - bzip2=1.0.8=h7b6447c_0\n  - ca-certificates=2023.7.22=hbcca054_0\n  - colorama=0.4.6=pyhd8ed1ab_0\n  - comm=0.1.2=py310h06a4308_0\n  - debugpy=1.6.7=py310h6a678d5_0\n  - decorator=5.1.1=pyhd3eb1b0_0\n  - executing=0.8.3=pyhd3eb1b0_0\n  - faiss=1.7.4=py310h9ed8947_0_cpu\n  - gmp=6.2.1=h58526e2_0\n  - gmpy2=2.1.2=py310h3ec546c_1\n  - ipykernel=6.19.2=py310h2f386ee_0\n  - ipython=8.12.0=py310h06a4308_0\n  - jedi=0.18.1=py310h06a4308_1\n  - jupyter_client=8.1.0=py310h06a4308_0\n  - jupyter_core=5.3.0=py310h06a4308_0\n  - ld_impl_linux-64=2.38=h1181459_1\n  - libblas=3.9.0=17_linux64_openblas\n  - libcblas=3.9.0=17_linux64_openblas\n  - libfaiss=1.7.4=hf47d654_0_cpu\n  - libfaiss-avx2=1.7.4=h1234567_0_cpu\n  - libffi=3.4.4=h6a678d5_0\n  - libgcc-ng=13.1.0=he5830b7_0\n  - libgfortran-ng=13.1.0=h69a702a_0\n  - libgfortran5=13.1.0=h15d22d2_0\n  - liblapack=3.9.0=17_linux64_openblas\n  - libopenblas=0.3.23=pthreads_h80387f5_0\n  - libsodium=1.0.18=h7b6447c_0\n  - libstdcxx-ng=13.1.0=hfd8a6a1_0\n  - libuuid=1.41.5=h5eee18b_0\n  - llvm-openmp=14.0.6=h9e868ea_0\n  - matplotlib-inline=0.1.6=py310h06a4308_0\n  - mpc=1.3.1=hfe3b2da_0\n  - mpfr=4.2.0=hb012696_0\n  - ncurses=6.4=h6a678d5_0\n  - nest-asyncio=1.5.6=py310h06a4308_0\n  - openssl=3.1.2=hd590300_0\n  - parso=0.8.3=pyhd3eb1b0_0\n  - pexpect=4.8.0=pyhd3eb1b0_3\n  - pickleshare=0.7.5=pyhd3eb1b0_1003\n  - pip=23.2.1=py310h06a4308_0\n  - platformdirs=2.5.2=py310h06a4308_0\n  - prompt-toolkit=3.0.36=py310h06a4308_0\n  - ptyprocess=0.7.0=pyhd3eb1b0_2\n  - pure_eval=0.2.2=pyhd3eb1b0_0\n  - pybind11=2.11.1=py310hd41b1e2_0\n  - pybind11-global=2.11.1=py310hd41b1e2_0\n  - pygments=2.15.1=py310h06a4308_1\n  - python=3.10.12=h955ad1f_0\n  - python-dateutil=2.8.2=pyhd3eb1b0_0\n  - python_abi=3.10=2_cp310\n  - pyzmq=25.1.0=py310h6a678d5_0\n  - readline=8.2=h5eee18b_0\n  - setuptools=68.0.0=py310h06a4308_0\n  - six=1.16.0=pyhd3eb1b0_1\n  - sqlite=3.41.2=h5eee18b_0\n  - stack_data=0.2.0=pyhd3eb1b0_0\n  - tk=8.6.12=h1ccaba5_0\n  - tornado=6.3.2=py310h5eee18b_0\n  - tqdm=4.66.1=pyhd8ed1ab_0\n  - traitlets=5.7.1=py310h06a4308_0\n  - wcwidth=0.2.5=pyhd3eb1b0_0\n  - wheel=0.38.4=py310h06a4308_0\n  - xz=5.4.2=h5eee18b_0\n  - zeromq=4.3.4=h2531618_0\n  - zlib=1.2.13=h5eee18b_0\n  - pip:\n      - ansicolors==1.1.8\n      - certifi==2023.7.22\n      - charset-normalizer==3.2.0\n      - cycler==0.11.0\n      - docker==6.1.2\n      - h5py==3.8.0\n      - idna==3.4\n      - jinja2==3.1.2\n      - joblib==1.3.1\n      - kiwisolver==1.4.4\n      - markupsafe==2.1.3\n      - matplotlib==3.3.4\n      - numpy==1.24.2\n      - packaging==23.1\n      - pandas==2.0.0\n      - pillow==10.0.0\n      - psutil==5.9.4\n      - pyparsing==3.1.1\n      - pytz==2023.3\n      - pyyaml==6.0\n      - requests==2.31.0\n      - scikit-learn==1.3.0\n      - scipy==1.10.1\n      - threadpoolctl==3.2.0\n      - tzdata==2023.3\n      - urllib3==2.0.4\n      - websocket-client==1.6.1\nprefix: /home/ben/miniconda3/envs/bigann\n"
  },
  {
    "path": "python/builder.cpp",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include \"../algorithms/vamana/index.h\"\n#include \"../algorithms/HCNNG/hcnng_index.h\"\n#include \"../algorithms/pyNNDescent/pynn_index.h\"\n#include \"../algorithms/HNSW/HNSW.hpp\"\n#include \"../algorithms/utils/types.h\"\n#include \"../algorithms/utils/point_range.h\"\n#include \"../algorithms/utils/graph.h\"\n#include \"../algorithms/utils/euclidian_point.h\"\n#include \"../algorithms/utils/mips_point.h\"\n#include \"../algorithms/utils/stats.h\"\n\nusing namespace parlayANN;\n\ntemplate <typename T, typename Point>\nvoid build_vamana_index(std::string metric, std::string &vector_bin_path,\n                        std::string &index_output_path, uint32_t graph_degree, uint32_t beam_width,\n                        float alpha, bool two_pass)\n{\n  //use file parsers to create Point object\n\n  using Range = PointRange<Point>;\n  Range* Points = new Range(vector_bin_path.data());\n  if (!Point::is_metric()) { // normalize if not a metric\n    std::cout << \"normalizing\" << std::endl;\n    for (int i=0; i < Points->size(); i++) \n      (*Points)[i].normalize();\n    if (Points->dimension() <= 200) {\n      if (Points->dimension() < 100) {\n        std::cout << \"Setting alpha to 1.0 because dimensionality is \" << Points->dimension() << \" (< 100)\" << std::endl;\n        alpha = 1.0;\n      } else {\n        std::cout << \"Setting alpha to 0.98 because dimensionality is \" << Points->dimension() << \" (>= 100 & <= 200)\" << std::endl;\n        alpha = .98;\n        };\n    }\n  }\n\n  //instantiate build params and stats objects\n  BuildParams BP(graph_degree, beam_width, alpha, two_pass ? 2 : 1);\n  stats<unsigned int> BuildStats(Points->size());\n\n  if (sizeof(typename Range::Point::T) > 1) {\n    if (Point::is_metric()) {\n      using QuantT = uint8_t;\n      using QuantPoint = Euclidian_Point<QuantT>;\n      using QuantRange = PointRange<QuantPoint>;\n      QuantRange Quant_Points(*Points);  // quantized to one byte\n      delete Points; // remove original points\n      Graph<unsigned int> G = Graph<unsigned int>(graph_degree, Points->size());\n\n      //call the build function\n      using index = knn_index<QuantRange, QuantRange, unsigned int>;\n      index I(BP);\n      I.build_index(G, Quant_Points, Quant_Points, BuildStats);\n      G.save(index_output_path.data());\n    } else {\n      using QuantT = int8_t;\n      using QuantPoint = Quantized_Mips_Point<8, true>;\n      using QuantRange = PointRange<QuantPoint>;\n      QuantRange Quant_Points(*Points);  // quantized to one byte\n      delete Points;  // remove original points\n      Graph<unsigned int> G = Graph<unsigned int>(graph_degree, Points->size());\n\n      //call the build function\n      using index = knn_index<QuantRange, QuantRange, unsigned int>;\n      index I(BP);\n      I.build_index(G, Quant_Points, Quant_Points, BuildStats);\n      G.save(index_output_path.data());\n    }\n  } else {\n    Graph<unsigned int> G = Graph<unsigned int>(graph_degree, Points->size());\n    using index = knn_index<PointRange<Point>, PointRange<Point>, unsigned int>;\n    index I(BP);\n    I.build_index(G, *Points, *Points, BuildStats);\n    G.save(index_output_path.data());\n  } \n}\n\ntemplate void build_vamana_index<float, Euclidian_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        float, bool);                            \ntemplate void build_vamana_index<float, Mips_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        float, bool);\n\ntemplate void build_vamana_index<int8_t, Euclidian_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         float, bool);\ntemplate void build_vamana_index<int8_t, Mips_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         float, bool);\n\ntemplate void build_vamana_index<uint8_t, Euclidian_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          float, bool);\ntemplate void build_vamana_index<uint8_t, Mips_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          float, bool);\n\n\n\ntemplate <typename T, typename Point>\nvoid build_hcnng_index(std::string metric, std::string &vector_bin_path,\n                         std::string &index_output_path, uint32_t mst_deg, uint32_t num_clusters,\n                        uint32_t cluster_size)\n{\n    \n    //instantiate build params object\n    BuildParams BP(num_clusters, cluster_size, mst_deg);\n    uint32_t graph_degree = BP.max_degree();\n\n    //use file parsers to create Point object\n\n    PointRange<Point> Points(vector_bin_path.data());\n    //use max degree info to create Graph object\n    Graph<unsigned int> G = Graph<unsigned int>(graph_degree, Points.size());\n\n    //call the build function\n    using index = hcnng_index<Point, PointRange<Point>, unsigned int>;\n    index I;\n    stats<unsigned int> BuildStats(G.size());\n    I.build_index(G, Points, BP.num_clusters, BP.cluster_size, BP.MST_deg);\n\n    //save the graph object\n    G.save(index_output_path.data());\n\n    \n}\n\ntemplate void build_hcnng_index<float, Euclidian_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        uint32_t);                            \ntemplate void build_hcnng_index<float, Mips_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        uint32_t);\n\ntemplate void build_hcnng_index<int8_t, Euclidian_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         uint32_t);\ntemplate void build_hcnng_index<int8_t, Mips_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         uint32_t);\n\ntemplate void build_hcnng_index<uint8_t, Euclidian_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          uint32_t);\ntemplate void build_hcnng_index<uint8_t, Mips_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          uint32_t);\n\n\ntemplate <typename T, typename Point>\nvoid build_pynndescent_index(std::string metric, std::string &vector_bin_path,\n                         std::string &index_output_path, uint32_t max_deg, uint32_t num_clusters,\n                        uint32_t cluster_size, double alpha, double delta)\n{\n    \n    //instantiate build params object\n    BuildParams BP(max_deg, alpha, num_clusters, cluster_size, delta);\n    uint32_t graph_degree = BP.max_degree();\n\n    //use file parsers to create Point object\n\n    PointRange<Point> Points(vector_bin_path.data());\n    //use max degree info to create Graph object\n    Graph<unsigned int> G = Graph<unsigned int>(graph_degree, Points.size());\n\n    //call the build function\n    using index = pyNN_index<Point, PointRange<Point>, unsigned int>;\n    index I(BP.R, BP.delta);\n    stats<unsigned int> BuildStats(G.size());\n    I.build_index(G, Points, BP.cluster_size, BP.num_clusters, BP.alpha);\n\n    //save the graph object\n    G.save(index_output_path.data());\n\n    \n}\n\ntemplate void build_pynndescent_index<float, Euclidian_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        uint32_t, double, double);                            \ntemplate void build_pynndescent_index<float, Mips_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        uint32_t, double, double);\n\ntemplate void build_pynndescent_index<int8_t, Euclidian_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         uint32_t, double, double);\ntemplate void build_pynndescent_index<int8_t, Mips_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         uint32_t, double, double);\n\ntemplate void build_pynndescent_index<uint8_t, Euclidian_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          uint32_t, double, double);\ntemplate void build_pynndescent_index<uint8_t, Mips_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          uint32_t, double, double);\n\n\ntemplate <typename T, typename Point>\nvoid build_hnsw_index(std::string metric, std::string &vector_bin_path,\n                         std::string &index_output_path, uint32_t graph_degree, uint32_t efc,\n                        float m_l, float alpha)\n{\n    //instantiate build params object\n    //BuildParams BP(graph_degree, efc, alpha);\n\n    //use file parsers to create Point object\n    PointRange<Point> Points(vector_bin_path.data());\n    /*\n    //use max degree info to create Graph object\n    Graph<unsigned int> G = Graph<unsigned int>(graph_degree, Points.size());\n\n    //call the build function\n    using index = hnsw_index<Point, PointRange<T, Point>, unsigned int>;\n    index I(BP);\n    stats<unsigned int> BuildStats(G.size());\n    I.build_index(G, Points, BuildStats);\n\n    //save the graph object\n    G.save(index_output_path.data());\n    */\n    using desc = Desc_HNSW<T, Point>;\n    // using elem_t = typename desc::type_elem;\n\n    // point_converter_default<elem_t> to_point;\n    // auto [ps,dim] = load_point(vector_bin_path, to_point, cnt_points);\n    auto ps = parlay::delayed_seq<Point>(\n      Points.size(),\n      [&](size_t i){return Points[i];}\n    );\n    const auto dim = Points.get_dims();\n    auto G = ANN::HNSW<desc>(ps.begin(), ps.end(), dim, m_l, graph_degree, efc, alpha);\n    G.save(index_output_path);\n}\n\ntemplate void build_hnsw_index<float, Euclidian_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        float, float);\ntemplate void build_hnsw_index<float, Mips_Point<float>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                        float, float);\n\ntemplate void build_hnsw_index<int8_t, Euclidian_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         float, float);\ntemplate void build_hnsw_index<int8_t, Mips_Point<int8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                         float, float);\n\ntemplate void build_hnsw_index<uint8_t, Euclidian_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          float, float);\ntemplate void build_hnsw_index<uint8_t, Mips_Point<uint8_t>>(std::string , std::string &, std::string &, uint32_t, uint32_t,\n                                          float, float);\n"
  },
  {
    "path": "python/compile.sh",
    "content": "# g++ -DSTATS -DHOMEGROWN -pthread -mcx16 -O3 -Wall -shared -std=c++17 -march=native -DNDEBUG -I . -fPIC $(python3 -m pybind11 --includes) vamana_index.cpp -o vamana_index$(python3-config --extension-suffix) -DHOMEGROWN -pthread -ldl -L/usr/local/lib -ljemalloc \n\ng++ -DSTATS -DHOMEGROWN -pthread -mcx16 -O3 -shared -std=c++17 -march=native -DNDEBUG -I . -fPIC $(python -m pybind11 --includes) module.cpp -o _ParlayANNpy$(python -c \"import sysconfig; print(sysconfig.get_config_var('EXT_SUFFIX'))\") -DHOMEGROWN -pthread -ldl -L/usr/local/lib #-ljemalloc \n"
  },
  {
    "path": "python/defaults.py",
    "content": "# // This code is part of the Problem Based Benchmark Suite (PBBS)\n# // Copyright (c) 2011 Guy Blelloch and the PBBS team\n# //\n# // Permission is hereby granted, free of charge, to any person obtaining a\n# // copy of this software and associated documentation files (the\n# // \"Software\"), to deal in the Software without restriction, including\n# // without limitation the rights (to use, copy, modify, merge, publish,\n# // distribute, sublicense, and/or sell copies of the Software, and to\n# // permit persons to whom the Software is furnished to do so, subject to\n# // the following conditions:\n# //\n# // The above copyright notice and this permission notice shall be included\n# // in all copies or substantial portions of the Software.\n# //\n# // THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n# // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n# // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n# // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n# // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n# // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n# // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\"\"\"\n# Parameter Defaults\nThese parameter defaults are re-exported from the C++ extension module, and used to keep the pythonic wrapper in sync with the C++.\n\"\"\"\nfrom ._ParlayANNpy import defaults as _defaults\n\nALPHA = _defaults.ALPHA\n\"\"\" \nNote that, as ALPHA is a `float32` (single precision float) in C++, when converted into Python it becomes a \n`float64` (double precision float). The actual value is 1.2f. The alpha parameter (>=1) is used to control the nature \nand number of points that are added to the graph. A higher alpha value (e.g., 1.4) will result in fewer hops (and IOs) \nto convergence, but probably more distance comparisons compared to a lower alpha value.\n\"\"\"\nGRAPH_DEGREE = _defaults.GRAPH_DEGREE\n\"\"\" \nGraph degree (a.k.a. `R`) is the maximum degree allowed for a node in the index's graph structure. This degree will be \npruned throughout the course of the index build, but it will never grow beyond this value. Higher R values require \nlonger index build times, but may result in an index showing excellent recall and latency characteristics. \n\"\"\"\nBEAMWIDTH = _defaults.BEAMWIDTH\n\"\"\" \nComplexity (a.k.a `L`) references the size of the list we store candidate approximate neighbors in while doing build\nor search tasks. It's used during index build as part of the index optimization processes. It's used in index search \nclasses both to help mitigate poor latencies during cold start, as well as on subsequent queries to conduct the search. \nLarge values will likely increase latency but also may improve recall, and tuning these values for your particular \nindex is certainly a reasonable choice.\n\"\"\""
  },
  {
    "path": "python/graph_index.cpp",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n#include \"../algorithms/vamana/index.h\"\n#include \"../algorithms/utils/types.h\"\n#include \"../algorithms/utils/point_range.h\"\n#include \"../algorithms/utils/graph.h\"\n#include \"../algorithms/utils/euclidian_point.h\"\n#include \"../algorithms/utils/mips_point.h\"\n#include \"../algorithms/utils/jl_point.h\"\n#include \"../algorithms/utils/stats.h\"\n#include \"../algorithms/utils/beamSearch.h\"\n#include \"../algorithms/HNSW/HNSW.hpp\"\n#include \"pybind11/numpy.h\"\n\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n\n#include <cstdio>\n#include <utility>\n#include <optional>\n\nusing namespace parlayANN;\n\nnamespace py = pybind11;\nusing NeighborsAndDistances = std::pair<py::array_t<unsigned int>, py::array_t<float>>;\n\ntemplate<typename T, typename Point>\nstruct GraphIndex{\n  Graph<unsigned int> G;\n  PointRange<Point> Points;\n\n  // using EPoint = Euclidian_Point<uint16_t>;\n  // using ERange = PointRange<EPoint>;\n  // ERange E_Points;\n  \n  // euclidean quantized points\n  using EQuantPoint = Euclidian_Point<uint8_t>;\n  using EQuantRange = PointRange<EQuantPoint>;\n  EQuantRange EQuant_Points;\n\n  // euclidean low-quality quantized points\n  using EQQuantPoint = Euclidean_JL_Sparse_Point<1024>;\n  using EQQuantRange = PointRange<EQQuantPoint>;\n  EQQuantRange EQQuant_Points;\n\n  // mips or angular quantized points\n  using MQuantT = int8_t;\n  using MQuantPoint = Quantized_Mips_Point<8,true>;\n  using MQuantRange = PointRange<MQuantPoint>;\n  MQuantRange MQuant_Points;\n\n  using MQQuantPoint = Mips_2Bit_Point;\n  //using MQQuantPoint = Mips_JL_Sparse_Point<512>;\n  using MQQuantRange = PointRange<MQQuantPoint>;\n  MQQuantRange MQQuant_Points;\n  \n  bool use_quantization;\n\n  std::optional<ANN::HNSW<Desc_HNSW<T, Point>>> HNSW_index;\n\n  GraphIndex(std::string &data_path, std::string &index_path, bool is_hnsw=false)\n    : use_quantization(false) {\n    Points = PointRange<Point>(data_path.data());\n    \n    if (sizeof(T) > 1) {\n      use_quantization = true;\n      if (Point::is_metric()) {\n        //E_Points = ERange(Points);\n        EQuant_Points = EQuantRange(Points);\n        if (Points.dimension() > 800)\n          EQQuant_Points = EQQuantRange(Points);\n      } else {\n        for (int i=0; i < Points.size(); i++) \n          Points[i].normalize();\n        MQuant_Points = MQuantRange(Points);\n        // only double quantize for high dimensionality\n        if (Points.dimension() > 200)\n          MQQuant_Points = MQQuantRange(Points);\n      }\n    }\n\n    if(is_hnsw) {\n      HNSW_index = ANN::HNSW<Desc_HNSW<T, Point>>(\n                                                  index_path,\n                                                  [&](unsigned int i/*indexType*/){\n                                                    return Points[i];\n                                                  }\n                                                  );\n    }\n    else {\n      G = Graph<unsigned int>(index_path.data());\n      if (G.size() != Points.size()) {\n        std::cout << \"graph size and point size do not match\" << std::endl;\n        abort();\n      }\n    }\n  }\n\n  auto search_dispatch(Point &q, QueryParams &QP, bool quant)\n  {\n    // if(HNSW_index) {\n    //   using indexType = unsigned int; // be consistent with the type of G\n    //   using std::pair;\n    //   using seq_t = parlay::sequence<pair<indexType, typename Point::distanceType>>;\n\n    //   indexType dist_cmps = 0;\n    //   search_control ctrl{};\n    //   if(QP.limit>0) {\n    //     ctrl.limit_eval = QP.limit;\n    //   }\n    //   ctrl.count_cmps = &dist_cmps;\n\n    //   seq_t frontier = HNSW_index->search(q, QP.k, QP.beamSize, ctrl);\n    //   return pair(pair(std::move(frontier), seq_t{}), dist_cmps);\n    // }\n    //    else {\n    using indexType = unsigned int;\n    parlay::sequence<indexType> starts(1, 0);\n    stats<indexType> Qstats(1);\n    if (quant && use_quantization) {\n      int dim = Points.params.dims;\n      if (Point::is_metric()) {\n        typename EQuantPoint::T buffer[dim];\n        if (EQuant_Points.params.slope == 1) {\n          for (int i=0; i < dim; i++)\n            buffer[i] = q[i];\n          EQuantPoint quant_q(buffer, 0, EQuant_Points.params);\n          return beam_search(quant_q, G, EQuant_Points, starts, QP).first.first;\n        } else {\n          // uint8_t buffer_1[dim*2];\n          // EPoint::translate_point(buffer_1, q, E_Points.params);\n          // EPoint e_q(buffer_1, 0, E_Points.params);\n          EQuantPoint::translate_point(buffer, q, EQuant_Points.params);\n          EQuantPoint quant_q(buffer, 0, EQuant_Points.params);\n          if (Points.dimension() > 800) {\n            uint8_t buffer_2[dim];\n            EQQuantPoint::translate_point(buffer_2, q, EQQuant_Points.params);\n            EQQuantPoint quant_qq(buffer_2, 0, EQQuant_Points.params);\n            return beam_search_rerank(q, quant_q, quant_qq, G,\n                                      Points, EQuant_Points, EQQuant_Points,\n                                      Qstats, starts, QP, false);\n          } else // don't use second level quantization\n            return beam_search_rerank(q, quant_q, quant_q, G,\n                                      Points, EQuant_Points, EQuant_Points,\n                                      Qstats, starts, QP, false);\n        }\n      } else {\n        //typename MQuantPoint::T buffer[dim];\n        uint8_t buffer[dim];\n        q.normalize();\n        MQuantPoint::translate_point(buffer, q, MQuant_Points.params);\n        MQuantPoint quant_q(buffer, 0, MQuant_Points.params);\n        if (Points.dimension() > 200) {\n          uint8_t buffer_2[dim];\n          MQQuantPoint::translate_point(buffer_2, q, MQQuant_Points.params);\n          MQQuantPoint quant_qq(buffer_2, 0, MQQuant_Points.params);\n          return beam_search_rerank(q, quant_q, quant_qq, G,\n                                    Points, MQuant_Points, MQQuant_Points,\n                                    Qstats, starts, QP, false);\n        } else {\n          return beam_search_rerank(q, quant_q, quant_q, G,\n                                    Points, MQuant_Points, MQuant_Points,\n                                    Qstats, starts, QP, false);\n        }\n      }\n    } else {\n      return beam_search(q, G, Points, starts, QP).first.first;\n    }\n  }\n\n  NeighborsAndDistances batch_search(py::array_t<T, py::array::c_style | py::array::forcecast> &queries,\n                                     //uint64_t num_queries_,\n                                     uint64_t knn,\n                                     uint64_t beam_width,\n                                     bool quant = false,\n                                     int64_t visit_limit = -1) {\n    QueryParams QP(knn, beam_width, 1.35, visit_limit, std::min<int>(G.max_degree(), 3*visit_limit));\n\n    uint64_t num_queries = queries.shape(0);\n    py::array_t<unsigned int> ids({num_queries, knn});\n    py::array_t<float> dists({num_queries, knn});\n\n    parlay::parallel_for(0, num_queries, [&] (size_t i){\n      std::vector<T> v(Points.dimension());\n      for (int j=0; j < v.size(); j++)\n        v[j] = queries.data(i)[j];\n      Point q = Point((uint8_t*) v.data(), 0, Points.params);\n      auto frontier = search_dispatch(q, QP, quant);\n      for(int j=0; j<knn; j++){\n        ids.mutable_data(i)[j] = frontier[j].first;\n        dists.mutable_data(i)[j] = frontier[j].second;\n      }\n    });\n    return std::make_pair(std::move(ids), std::move(dists));\n  }\n\n  py::array_t<unsigned int>\n  single_search(py::array_t<T>& q, uint64_t knn,\n                uint64_t beam_width, bool quant,\n                int64_t visit_limit) {\n    QueryParams QP(knn, beam_width, 1.35, visit_limit, std::min<int>(G.max_degree(), 3*visit_limit));\n    int dims = Points.dimension();\n\n    py::array_t<unsigned int> ids({(long) knn});\n    auto pp = q.mutable_unchecked();\n    T v[dims];\n    for (int j=0; j < dims; j++)\n      v[j] = pp(j); //q.data()[j];\n    Point p = Point((uint8_t*) v, 0, Points.params);\n    auto frontier = search_dispatch(p, QP, quant);\n    for(int j=0; j<knn; j++) \n      ids.mutable_data()[j] = frontier[j].first;\n    return std::move(ids);\n  }\n\n  NeighborsAndDistances batch_search_from_string(std::string &queries,\n                                                 //uint64_t num_queries_,\n                                                 uint64_t knn,\n                                                 uint64_t beam_width, bool quant = false,\n                                                 int64_t visit_limit = -1) {\n    QueryParams QP(knn, beam_width, 1.35, visit_limit, std::min<int>(G.max_degree(), 3*visit_limit));\n    PointRange<Point> QueryPoints(queries.data());\n    uint64_t num_queries = QueryPoints.size();\n    py::array_t<unsigned int> ids({num_queries, knn});\n    py::array_t<float> dists({num_queries, knn});\n    parlay::parallel_for(0, num_queries, [&] (size_t i){\n      auto p = QueryPoints[i];\n      auto frontier = search_dispatch(p, QP, quant);\n      for(int j=0; j<knn; j++){\n        ids.mutable_data(i)[j] = frontier[j].first;\n        dists.mutable_data(i)[j] = frontier[j].second;\n      }\n    });\n\n    return std::make_pair(std::move(ids), std::move(dists));\n  }\n\n  void check_recall(std::string &queries_file,\n                    std::string &graph_file,\n                    py::array_t<unsigned int, py::array::c_style | py::array::forcecast> &neighbors,\n                    int k) {\n    bool resolve_eq_distances = true;\n    groundTruth<unsigned int> GT = groundTruth<unsigned int>(graph_file.data());\n    PointRange<Point> QueryPoints(queries_file.data());\n\n    size_t n = GT.size();\n    long m = Points.size();\n\n    float last_dist;\n    \n    int numCorrect = 0;\n    for (unsigned int i = 0; i < n; i++) {\n      parlay::sequence<int> results;\n      int cnt = 0;\n      for (unsigned int l = 0; l < k; l++)\n        results.push_back(GT.coordinates(i,l));\n      if (resolve_eq_distances) {\n        last_dist = QueryPoints[i].distance(Points[GT.coordinates(i, k-1)]);\n        for (unsigned int l = k; l < GT.dimension(); l++) {\n          auto p = Points[GT.coordinates(i, l)];\n          if (QueryPoints[i].distance(p) == last_dist) {\n            cnt++;\n            results.push_back(GT.coordinates(i,l));\n          }\n        }\n      }\n      std::set<int> reported_nbhs;\n      for (unsigned int l = 0; l < k; l++) {\n        long ngh = neighbors.mutable_data(i)[l];\n        if (ngh < 0 || ngh >= m) {\n          std::cout << \"neighbor reported by query out of range: \" << ngh << std::endl;\n          std::abort();\n        }\n        reported_nbhs.insert(ngh);\n      }\n      for (unsigned int l = 0; l < results.size(); l++) {\n        if (reported_nbhs.find(results[l]) != reported_nbhs.end()) {\n          numCorrect += 1;\n        }\n      }\n    }\n    float recall = static_cast<double>(numCorrect) / static_cast<double>(k * n);\n    std::cout << \"Recall: \" << std::setprecision(6) << recall <<std::endl;\n  }\n\n  // void check_recall(std::string &graph_file, py::array_t<unsigned int, py::array::c_style | py::array::forcecast> &neighbors, int k){\n  //   groundTruth<unsigned int> GT = groundTruth<unsigned int>(graph_file.data());\n\n  //   size_t n = GT.size();\n\n  //   int numCorrect = 0;\n  //   for (unsigned int i = 0; i < n; i++) {\n  //     parlay::sequence<int> results_with_ties;\n  //     for (unsigned int l = 0; l < k; l++)\n  //       results_with_ties.push_back(GT.coordinates(i,l));\n  //     std::cout << i << std::endl;\n  //     float last_dist = GT.distances(i, k-1);\n  //     for (unsigned int l = k; l < GT.dimension(); l++) {\n  //       if (GT.distances(i,l) == last_dist) {\n  //         results_with_ties.push_back(GT.coordinates(i,l));\n  //       }\n  //     }\n  //     std::cout << \"aa\" << std::endl;\n  //     std::set<int> reported_nbhs;\n  //     for (unsigned int l = 0; l < k; l++) reported_nbhs.insert(neighbors.mutable_data(i)[l]);\n  //     for (unsigned int l = 0; l < results_with_ties.size(); l++) {\n  //       if (reported_nbhs.find(results_with_ties[l]) != reported_nbhs.end()) {\n  //         numCorrect += 1;\n  //       }\n  //     }\n  //   }\n  //   float recall = static_cast<float>(numCorrect) / static_cast<float>(k * n);\n  //   std::cout << \"Recall: \" << recall << std::endl;\n  // }\n\n};\n"
  },
  {
    "path": "python/module.cpp",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <string>\n\n#include <pybind11/pybind11.h>\n#include <pybind11/stl.h>\n#include \"pybind11/numpy.h\"\n\n#include \"builder.cpp\"\n#include \"graph_index.cpp\"\n\nusing namespace parlayANN;\n\nPYBIND11_MAKE_OPAQUE(std::vector<uint32_t>);\nPYBIND11_MAKE_OPAQUE(std::vector<float>);\nPYBIND11_MAKE_OPAQUE(std::vector<int8_t>);\nPYBIND11_MAKE_OPAQUE(std::vector<uint8_t>);\n\nnamespace py = pybind11;\nusing namespace pybind11::literals;\n\n// using NeighborsAndDistances = std::pair<py::array_t<unsigned int, py::array::c_style | py::array::forcecast>, py::array_t<float, py::array::c_style | py::array::forcecast>>;\n\nstruct Variant\n{\n    std::string builder_name;\n    std::string index_name;\n};\n\nconst Variant FloatEuclidianVariant{\"build_vamana_float_euclidian_index\", \"FloatEuclidianIndex\"};\nconst Variant FloatMipsVariant{\"build_vamana_float_mips_index\", \"FloatMipsIndex\"};\n\nconst Variant UInt8EuclidianVariant{\"build_vamana_uint8_euclidian_index\", \"UInt8EuclidianIndex\"};\nconst Variant UInt8MipsVariant{\"build_vamana_uint8_mips_index\", \"UInt8MipsIndex\"};\n\nconst Variant Int8EuclidianVariant{\"build_vamana_int8_euclidian_index\", \"Int8EuclidianIndex\"};\nconst Variant Int8MipsVariant{\"build_vamana_int8_mips_index\", \"Int8MipsIndex\"};\n\ntemplate <typename T, typename Point> inline void add_variant(py::module_ &m, const Variant &variant)\n{\n\n    m.def(variant.builder_name.c_str(), build_vamana_index<T, Point>, \"distance_metric\"_a,\n          \"data_file_path\"_a, \"index_output_path\"_a, \"graph_degree\"_a, \"beam_width\"_a, \"alpha\"_a, \"two_pass\"_a);\n\n    py::class_<GraphIndex<T, Point>>(m, variant.index_name.c_str())\n      .def(py::init<std::string &, std::string &, bool>(),\n           \"index_path\"_a, \"data_path\"_a, \"hnsw\"_a=false)\n      //do we want to add options like visited limit, or leave those as defaults?\n      .def(\"batch_search\", &GraphIndex<T, Point>::batch_search, \"queries\"_a, \"knn\"_a,\n           \"beam_width\"_a, \"quant\"_a, \"visit_limit\"_a)\n      .def(\"single_search\", &GraphIndex<T, Point>::single_search, \"q\"_a, \"knn\"_a,\n           \"beam_width\"_a, \"quant\"_a, \"visit_limit\"_a)\n      .def(\"batch_search_from_string\", &GraphIndex<T, Point>::batch_search_from_string, \"queries\"_a, \"knn\"_a,\n           \"beam_width\"_a, \"quant\"_a, \"visit_limit\"_a)\n      .def(\"check_recall\", &GraphIndex<T, Point>::check_recall, \"queries_file\"_a, \"graph_file\"_a, \"neighbors\"_a, \"k\"_a);\n}\n\nconst Variant FloatEuclidianHCNNGVariant{\"build_hcnng_float_euclidian_index\", \"FloatEuclidianIndex\"};\nconst Variant FloatMipsHCNNGVariant{\"build_hcnng_float_mips_index\", \"FloatMipsIndex\"};\n\nconst Variant UInt8EuclidianHCNNGVariant{\"build_hcnng_uint8_euclidian_index\", \"UInt8EuclidianIndex\"};\nconst Variant UInt8MipsHCNNGVariant{\"build_hcnng_uint8_mips_index\", \"UInt8MipsIndex\"};\n\nconst Variant Int8EuclidianHCNNGVariant{\"build_hcnng_int8_euclidian_index\", \"Int8EuclidianIndex\"};\nconst Variant Int8MipsHCNNGVariant{\"build_hcnng_int8_mips_index\", \"Int8MipsIndex\"};\n\ntemplate <typename T, typename Point> inline void add_hcnng_variant(py::module_ &m, const Variant &variant)\n{\n\n    m.def(variant.builder_name.c_str(), build_hcnng_index<T, Point>, \"distance_metric\"_a,\n          \"data_file_path\"_a, \"index_output_path\"_a, \"mst_deg\"_a, \"num_clusters\"_a, \"cluster_size\"_a);\n\n   \n}\n\nconst Variant FloatEuclidianpyNNVariant{\"build_pynndescent_float_euclidian_index\", \"FloatEuclidianIndex\"};\nconst Variant FloatMipspyNNVariant{\"build_pynndescent_float_mips_index\", \"FloatMipsIndex\"};\n\nconst Variant UInt8EuclidianpyNNVariant{\"build_pynndescent_uint8_euclidian_index\", \"UInt8EuclidianIndex\"};\nconst Variant UInt8MipspyNNVariant{\"build_pynndescent_uint8_mips_index\", \"UInt8MipsIndex\"};\n\nconst Variant Int8EuclidianpyNNVariant{\"build_pynndescent_int8_euclidian_index\", \"Int8EuclidianIndex\"};\nconst Variant Int8MipspyNNVariant{\"build_pynndescent_int8_mips_index\", \"Int8MipsIndex\"};\n\ntemplate <typename T, typename Point> inline void add_pynndescent_variant(py::module_ &m, const Variant &variant)\n{\n\n    m.def(variant.builder_name.c_str(), build_pynndescent_index<T, Point>, \"distance_metric\"_a,\n          \"data_file_path\"_a, \"index_output_path\"_a, \"max_deg\"_a, \"num_clusters\"_a, \"cluster_size\"_a, \n          \"alpha\"_a, \"delta\"_a);\n\n   \n}\n\nconst Variant FloatEuclidianHNSWVariant{\"build_hnsw_float_euclidian_index\", \"FloatEuclidianIndex\"};\nconst Variant FloatMipsHNSWVariant{\"build_hnsw_float_mips_index\", \"FloatMipsIndex\"};\n\nconst Variant UInt8EuclidianHNSWVariant{\"build_hnsw_uint8_euclidian_index\", \"UInt8EuclidianIndex\"};\nconst Variant UInt8MipsHNSWVariant{\"build_hnsw_uint8_mips_index\", \"UInt8MipsIndex\"};\n\nconst Variant Int8EuclidianHNSWVariant{\"build_hnsw_int8_euclidian_index\", \"Int8EuclidianIndex\"};\nconst Variant Int8MipsHNSWVariant{\"build_hnsw_int8_mips_index\", \"Int8MipsIndex\"};\n\ntemplate <typename T, typename Point> inline void add_hnsw_variant(py::module_ &m, const Variant &variant)\n{\n\n    m.def(variant.builder_name.c_str(), build_hnsw_index<T, Point>, \"distance_metric\"_a,\n          \"data_file_path\"_a, \"index_output_path\"_a, \"graph_degree\"_a, \"efc\"_a, \"m_l\"_a, \"alpha\"_a);\n}\n\n\nPYBIND11_MODULE(_ParlayANNpy, m)\n{\n    m.doc() = \"ParlayANN Python Bindings\";\n#ifdef VERSION_INFO\n    m.attr(\"__version__\") = VERSION_INFO;\n#else\n    m.attr(\"__version__\") = \"dev\";\n#endif\n\n    // let's re-export our defaults\n    py::module_ default_values = m.def_submodule(\n        \"defaults\");\n\n    default_values.attr(\"METRIC\") = \"Euclidian\";\n    default_values.attr(\"ALPHA\") = 1.2;\n    default_values.attr(\"GRAPH_DEGREE\") = 64;\n    default_values.attr(\"BEAMWIDTH\") = 128;\n\n    add_variant<float, Euclidian_Point<float>>(m, FloatEuclidianVariant);\n    add_variant<float, Mips_Point<float>>(m, FloatMipsVariant);\n    add_variant<uint8_t, Euclidian_Point<uint8_t>>(m, UInt8EuclidianVariant);\n    add_variant<uint8_t, Mips_Point<uint8_t>>(m, UInt8MipsVariant);\n    add_variant<int8_t, Euclidian_Point<int8_t>>(m, Int8EuclidianVariant);\n    add_variant<int8_t, Mips_Point<int8_t>>(m, Int8MipsVariant);\n\n    add_hcnng_variant<float, Euclidian_Point<float>>(m, FloatEuclidianHCNNGVariant);\n    add_hcnng_variant<float, Mips_Point<float>>(m, FloatMipsHCNNGVariant);\n    add_hcnng_variant<uint8_t, Euclidian_Point<uint8_t>>(m, UInt8EuclidianHCNNGVariant);\n    add_hcnng_variant<uint8_t, Mips_Point<uint8_t>>(m, UInt8MipsHCNNGVariant);\n    add_hcnng_variant<int8_t, Euclidian_Point<int8_t>>(m, Int8EuclidianHCNNGVariant);\n    add_hcnng_variant<int8_t, Mips_Point<int8_t>>(m, Int8MipsHCNNGVariant);\n\n    add_pynndescent_variant<float, Euclidian_Point<float>>(m, FloatEuclidianpyNNVariant);\n    add_pynndescent_variant<float, Mips_Point<float>>(m, FloatMipspyNNVariant);\n    add_pynndescent_variant<uint8_t, Euclidian_Point<uint8_t>>(m, UInt8EuclidianpyNNVariant);\n    add_pynndescent_variant<uint8_t, Mips_Point<uint8_t>>(m, UInt8MipspyNNVariant);\n    add_pynndescent_variant<int8_t, Euclidian_Point<int8_t>>(m, Int8EuclidianpyNNVariant);\n    add_pynndescent_variant<int8_t, Mips_Point<int8_t>>(m, Int8MipspyNNVariant);\n\n    add_hnsw_variant<float, Euclidian_Point<float>>(m, FloatEuclidianHNSWVariant);\n    add_hnsw_variant<float, Mips_Point<float>>(m, FloatMipsHNSWVariant);\n    add_hnsw_variant<uint8_t, Euclidian_Point<uint8_t>>(m, UInt8EuclidianHNSWVariant);\n    add_hnsw_variant<uint8_t, Mips_Point<uint8_t>>(m, UInt8MipsHNSWVariant);\n    add_hnsw_variant<int8_t, Euclidian_Point<int8_t>>(m, Int8EuclidianHNSWVariant);\n    add_hnsw_variant<int8_t, Mips_Point<int8_t>>(m, Int8MipsHNSWVariant);\n}\n"
  },
  {
    "path": "python/scripts/fashion_test.py",
    "content": "import _ParlayANNpy as pann\nimport wrapper as wp\nimport time\n\nNAME = \"fashion-mnist-784-euclidean\"\nDATA_DIR = \"data/\" + NAME + \"/\"\nmetric = \"Euclidian\"\n\nwp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME, 32, 64, 1.15, True)\n\nIndex = wp.load_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME)\n\nfor (Q,limit) in [(10,10), (10,13), (10,16)] :\n    for x in range(5) :\n        start = time.time()\n        neighbors, distances = Index.batch_search_from_string(DATA_DIR + NAME + \"_query.fbin\", 10, Q, True, limit)\n        end = time.time()\n        print(\"QPS: \", neighbors.shape[0]/(end - start))\n        \n    Index.check_recall(DATA_DIR + NAME + \"_query.fbin\", DATA_DIR + NAME + \"_groundtruth\", neighbors, 10)\n"
  },
  {
    "path": "python/scripts/gist_test.py",
    "content": "import _ParlayANNpy as pann\nimport wrapper as wp\nimport time\n\nNAME = \"gist\"\nDATA_DIR = \"data/\" + NAME + \"/\"\nmetric = \"Euclidian\"\n\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME, 100, 200, 1.1, True)\n\nIndex = wp.load_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME)\n\nfor Q in [19, 36, 65] :\n    for x in range(5) :\n        start = time.time()\n        neighbors, distances = Index.batch_search_from_string(DATA_DIR + NAME + \"_query.fbin\", 10, Q, True, 1000)\n        end = time.time()\n        print(\"QPS: \", neighbors.shape[0]/(end - start))\n        \n    Index.check_recall(DATA_DIR + NAME + \"_query.fbin\", DATA_DIR + NAME + \"-1M\", neighbors, 10)\n"
  },
  {
    "path": "python/scripts/glove100_test.py",
    "content": "import _ParlayANNpy as pann\nimport wrapper as wp\nimport time\n\nNAME = \"glove-100-angular\"\nDATA_DIR = \"data/\" + NAME + \"/\"\nmetric = \"mips\"\n\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME, 150, 300, 1, True)\n\nIndex = wp.load_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME)\n\nfor Q in [30, 80, 160] :\n    for x in range(5) :\n        start = time.time()\n        neighbors, distances = Index.batch_search_from_string(DATA_DIR + NAME + \"_query.fbin\", 10, Q, True, 1000)\n        end = time.time()\n        print(\"QPS: \", neighbors.shape[0]/(end - start))\n        \n    Index.check_recall(DATA_DIR + NAME + \"_query.fbin\", DATA_DIR + NAME + \"_groundtruth\", neighbors, 10)\n"
  },
  {
    "path": "python/scripts/glove25_test.py",
    "content": "import _ParlayANNpy as pann\nimport wrapper as wp\nimport time\n\nNAME = \"glove-25-angular\"\nDATA_DIR = \"data/\" + NAME + \"/\"\nmetric = \"mips\"\n\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME, 150, 300, 1, True)\n\nIndex = wp.load_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME)\n\nfor Q in [11, 20, 32] :\n    for x in range(5) :\n        start = time.time()\n        neighbors, distances = Index.batch_search_from_string(DATA_DIR + NAME + \"_query.fbin\", 10, Q, True, 1000)\n        end = time.time()\n        print(\"QPS: \", neighbors.shape[0]/(end - start))\n        \n    Index.check_recall(DATA_DIR + NAME + \"_query.fbin\", DATA_DIR + NAME + \"_groundtruth\", neighbors, 10)\n"
  },
  {
    "path": "python/scripts/nyt_test.py",
    "content": "import _ParlayANNpy as pann\nimport wrapper as wp\nimport time\n\nNAME = \"nytimes-256-angular\"\nDATA_DIR = \"data/\" + NAME + \"/\"\nmetric = \"mips\"\n\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME, 150, 300, .85, True)\n\nIndex = wp.load_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME)\n\nfor Q in [17, 60] :\n    for x in range(5) :\n        start = time.time()\n        neighbors, distances = Index.batch_search_from_string(DATA_DIR + NAME + \"_query.fbin\", 10, Q, True, 1000)\n        end = time.time()\n        print(\"QPS: \", neighbors.shape[0]/(end - start))\n        \n    Index.check_recall(DATA_DIR + NAME + \"_query.fbin\", DATA_DIR + NAME + \"_groundtruth\", neighbors, 10)\n"
  },
  {
    "path": "python/scripts/sift_test.py",
    "content": "import _ParlayANNpy as pann\nimport wrapper as wp\nimport time\n\nNAME = \"sift-128-euclidean\"\nDATA_DIR = \"data/\" + NAME + \"/\"\nmetric = \"Euclidian\"\n\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME + \"_64\", 64, 128, 1.15, True)\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME + \"_40\", 40, 80, 1.15, True)\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME + \"_32\", 32, 64, 1.15, True)\n\nIndex = wp.load_index(metric, \"float\", DATA_DIR + NAME + \"_base.fbin\", DATA_DIR + \"outputs/\" + NAME + \"_64\")\n\nfor Q in [14, 23] :\n    for x in range(5) :\n        start = time.time()\n        neighbors, distances = Index.batch_search_from_string(DATA_DIR + NAME + \"_query.fbin\", 10, Q, True, 1000)\n        end = time.time()\n        print(neighbors.size)\n        print(\"QPS: \", f'{neighbors.shape[0]/(end - start):.6f})\n        \n    Index.check_recall(DATA_DIR + NAME + \"_query.fbin\", DATA_DIR + NAME + \"_groundtruth\", neighbors, 10)\n"
  },
  {
    "path": "python/sift_test.py",
    "content": "import _ParlayANNpy as pann\nimport wrapper as wp\nimport time\n\nNAME = \"sift-128-euclidean\"\nDATA_DIR = \"data/\" + NAME + \"/\"\nmetric = \"Euclidian\"\n\n# wp.build_vamana_index(metric, \"float\", DATA_DIR + \"base.fbin\", DATA_DIR + \"graphs/graph_\" + \"64_1.1x\", 64, 128, 1.1, True)\n\nIndex = wp.load_index(metric, \"float\", DATA_DIR + \"base.fbin\", DATA_DIR + \"graphs/graph_\" + \"64_1.1x\")\n\nfor Q in [16, 25] :\n    for x in range(3) :\n        start = time.time()\n        neighbors, distances = Index.batch_search_from_string(DATA_DIR + \"query.fbin\", 10, Q, True, 1000)\n        end = time.time()\n        print(\"QPS: \", neighbors.shape[0]/(end - start))\n        \n    Index.check_recall(DATA_DIR + \"query.fbin\", DATA_DIR + \"groundtruth\", neighbors, 10)\n"
  },
  {
    "path": "python/test.py",
    "content": "import wrapper as wp\n\n\nFERN_DATA_DIR = \"/ssd1/anndata/bigann/\"\nAWARE_DATA_DIR = \"/ssd1/data/bigann/\"\n\nDATA_DIR = FERN_DATA_DIR\n\nprint(\"Testing pynndescent...\")\n\nwp.build_pynndescent_index(\"Euclidian\", \"uint8\", DATA_DIR + \"base.1B.u8bin.crop_nb_1000000\", DATA_DIR + \"outputs/pynn\", 40, 10, 100, 1.2, .05)\n\nIndex = wp.load_index(\"Euclidian\", \"uint8\", DATA_DIR + \"base.1B.u8bin.crop_nb_1000000\", DATA_DIR + \"outputs/pynn\")\nneighbors, distances = Index.batch_search_from_string(DATA_DIR + \"query.public.10K.u8bin\", 10, 10, True, 10000)\n\nIndex.check_recall(DATA_DIR + \"query.public.10K.u8bin\", DATA_DIR + \"bigann-1M\", neighbors, 10)\n\nprint(\"\\nTesting vamana...\")\n\nwp.build_vamana_index(\"Euclidian\", \"uint8\", DATA_DIR + \"base.1B.u8bin.crop_nb_1000000\", DATA_DIR + \"outputs/vamana\", 40, 100, 1.2, False)\n\nIndex = wp.load_index(\"Euclidian\", \"uint8\", DATA_DIR + \"base.1B.u8bin.crop_nb_1000000\", DATA_DIR + \"outputs/vamana\")\nneighbors, distances = Index.batch_search_from_string(DATA_DIR + \"query.public.10K.u8bin\", 10, 10, True, 10000)\n\nIndex.check_recall(DATA_DIR + \"query.public.10K.u8bin\", DATA_DIR + \"bigann-1M\", neighbors, 10)\n\nprint(\"\\nTesting hcnng...\")\n\nwp.build_hcnng_index(\"Euclidian\", \"uint8\", DATA_DIR + \"base.1B.u8bin.crop_nb_1000000\", DATA_DIR + \"outputs/hcnng\", 40, 20, 1000)\n\nIndex = wp.load_index(\"Euclidian\", \"uint8\", DATA_DIR + \"base.1B.u8bin.crop_nb_1000000\", DATA_DIR + \"outputs/hcnng\")\nneighbors, distances = Index.batch_search_from_string(DATA_DIR + \"query.public.10K.u8bin\", 10, 10, True, 10000)\n\nIndex.check_recall(DATA_DIR + \"query.public.10K.u8bin\", DATA_DIR + \"bigann-1M\", neighbors, 10)\n\n# HNSW is currently broken, as far as I can tell"
  },
  {
    "path": "python/wrapper.py",
    "content": "from _ParlayANNpy import *\n\ndef build_vamana_index(metric, dtype, data_dir, index_dir, R, L, alpha, two_pass):\n    if metric == 'Euclidian':\n        if dtype == 'uint8':\n            return build_vamana_uint8_euclidian_index(metric, data_dir, index_dir, R, L, alpha, two_pass)\n        elif dtype == 'int8':\n            return build_vamana_int8_euclidian_index(metric, data_dir, index_dir, R, L, alpha, two_pass)\n        elif dtype == 'float':\n            return build_vamana_float_euclidian_index(metric, data_dir, index_dir, R, L, alpha, two_pass)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    elif metric == 'mips':\n        if dtype == 'uint8':\n            return build_vamana_uint8_mips_index(metric, data_dir, index_dir, R, L, alpha, two_pass)\n        elif dtype == 'int8':\n            return build_vamana_int8_mips_index(metric, data_dir, index_dir, R, L, alpha, two_pass)\n        elif dtype == 'float':\n            return build_vamana_float_mips_index(metric, data_dir, index_dir, R, L, alpha, two_pass)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    else:\n        raise Exception('Invalid metric ' + metric)\n    \n\n\ndef build_hcnng_index(metric, dtype, data_dir, index_dir, mst_deg, num_clusters, cluster_size):\n    if metric == 'Euclidian':\n        if dtype == 'uint8':\n            build_hcnng_uint8_euclidian_index(metric, data_dir, index_dir, mst_deg, num_clusters, cluster_size)\n        elif dtype == 'int8':\n            build_hcnng_int8_euclidian_index(metric, data_dir, index_dir, mst_deg, num_clusters, cluster_size)\n        elif dtype == 'float':\n            build_hcnng_float_euclidian_index(metric, data_dir, index_dir, mst_deg, num_clusters, cluster_size)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    elif metric == 'mips':\n        if dtype == 'uint8':\n            build_hcnng_uint8_mips_index(metric, data_dir, index_dir, mst_deg, num_clusters, cluster_size)\n        elif dtype == 'int8':\n            build_hcnng_int8_mips_index(metric, data_dir, index_dir, mst_deg, num_clusters, cluster_size)\n        elif dtype == 'float':\n            build_hcnng_float_mips_index(metric, data_dir, index_dir, mst_deg, num_clusters, cluster_size)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    else:\n        raise Exception('Invalid metric ' + metric)\n\n\ndef build_pynndescent_index(metric, dtype, data_dir, index_dir, max_deg, num_clusters, cluster_size, alpha, delta):\n    if metric == 'Euclidian':\n        if dtype == 'uint8':\n            build_pynndescent_uint8_euclidian_index(metric, data_dir, index_dir, max_deg, num_clusters, cluster_size, alpha, delta)\n        elif dtype == 'int8':\n            build_pynndescent_int8_euclidian_index(metric, data_dir, index_dir, max_deg, num_clusters, cluster_size, alpha, delta)\n        elif dtype == 'float':\n            build_pynndescent_float_euclidian_index(metric, data_dir, index_dir, max_deg, num_clusters, cluster_size, alpha, delta)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    elif metric == 'mips':\n        if dtype == 'uint8':\n            build_pynndescent_uint8_mips_index(metric, data_dir, index_dir, max_deg, num_clusters, cluster_size, alpha, delta)\n        elif dtype == 'int8':\n            build_pynndescent_int8_mips_index(metric, data_dir, index_dir, max_deg, num_clusters, cluster_size, alpha, delta)\n        elif dtype == 'float':\n            build_pynndescent_float_mips_index(metric, data_dir, index_dir, max_deg, num_clusters, cluster_size, alpha, delta)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    else:\n        raise Exception('Invalid metric ' + metric)\n\n\ndef build_hnsw_index(metric, dtype, data_dir, index_dir, R, efc, m_l, alpha):\n    if metric == 'Euclidian':\n        if dtype == 'uint8':\n            build_hnsw_uint8_euclidian_index(metric, data_dir, index_dir, R, efc, m_l, alpha)\n        elif dtype == 'int8':\n            build_hnsw_int8_euclidian_index(metric, data_dir, index_dir, R, efc, m_l, alpha)\n        elif dtype == 'float':\n            build_hnsw_float_euclidian_index(metric, data_dir, index_dir, R, efc, m_l, alpha)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    elif metric == 'mips':\n        if dtype == 'uint8':\n            build_hnsw_uint8_mips_index(metric, data_dir, index_dir, R, efc, m_l, alpha)\n        elif dtype == 'int8':\n            build_hnsw_int8_mips_index(metric, data_dir, index_dir, R, efc, m_l, alpha)\n        elif dtype == 'float':\n            build_hnsw_float_mips_index(metric, data_dir, index_dir, R, efc, m_l, alpha)\n        else:\n            raise Exception('Invalid data type ' + dtype)\n    else:\n        raise Exception('Invalid metric ' + metric)\n\n        \ndef load_index(metric, dtype, data_dir, index_dir, hnsw=False):\n    if metric == 'Euclidian':\n        if dtype == 'uint8':\n            return UInt8EuclidianIndex(data_dir, index_dir, hnsw)\n        elif dtype == 'int8':\n            return Int8EuclidianIndex(data_dir, index_dir, hnsw)\n        elif dtype == 'float':\n            return FloatEuclidianIndex(data_dir, index_dir, hnsw)\n        else:\n            raise Exception('Invalid data type')\n    elif metric == 'mips':\n        if dtype == 'uint8':\n            return UInt8MipsIndex(data_dir, index_dir, hnsw)\n        elif dtype == 'int8':\n            return Int8MipsIndex(data_dir, index_dir, hnsw)\n        elif dtype == 'float':\n            return FloatMipsIndex(data_dir, index_dir, hnsw)\n        else:\n            raise Exception('Invalid data type')\n    else:\n        raise Exception('Invalid metric')\n"
  },
  {
    "path": "rangeSearch/bench/.gitignore",
    "content": "neighborsCheck\nneighborsCheck.o\n"
  },
  {
    "path": "rangeSearch/bench/IO.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#pragma once\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <string>\n#include <cstring>\n#include \"parlay/primitives.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/io.h\"\n#include \"parlay/internal/get_time.h\"\n\nnamespace benchIO {\n  using namespace std;\n  using parlay::sequence;\n  using parlay::tabulate;\n  using parlay::make_slice;\n\n  auto is_space = [] (char c) {\n    switch (c)  {\n    case '\\r': \n    case '\\t': \n    case '\\n': \n    case 0:\n    case ' ' : return true;\n    default : return false;\n    }\n  };\n\n  // parallel code for converting a string to word pointers\n  // side effects string by setting to null after each word\n  template <class Seq>\n    parlay::sequence<char*> stringToWords(Seq &Str) {\n    size_t n = Str.size();\n    \n    parlay::parallel_for(0, n, [&] (long i) {\n\tif (is_space(Str[i])) Str[i] = 0;}); \n\n    // mark start of words\n    auto FL = parlay::tabulate(n, [&] (long i) -> bool {\n\treturn (i==0) ? Str[0] : Str[i] && !Str[i-1];});\n    \n    // offset for each start of word\n    auto Offsets = parlay::pack_index<long>(FL);\n\n    // pointer to each start of word\n    auto SA = parlay::tabulate(Offsets.size(), [&] (long j) -> char* {\n\treturn Str.begin() + Offsets[j];});\n    \n    return SA;\n  }\n\n  //using this as a typename so we can replace with parlay::chars easily if desired\n  using charstring = typename parlay::sequence<char>;\n\n  inline int xToStringLen(charstring const &a) { return a.size();}\n  inline void xToString(char* s, charstring const &a) {\n    for (int i=0; i < a.size(); i++) s[i] = a[i];}\n\n  inline int xToStringLen(long a) { return 21;}\n  inline void xToString(char* s, long a) { sprintf(s,\"%ld\",a);}\n\n  inline int xToStringLen(unsigned long a) { return 21;}\n  inline void xToString(char* s, unsigned long a) { sprintf(s,\"%lu\",a);}\n\n  inline uint xToStringLen(uint a) { return 12;}\n  inline void xToString(char* s, uint a) { sprintf(s,\"%u\",a);}\n\n  inline int xToStringLen(int a) { return 12;}\n  inline void xToString(char* s, int a) { sprintf(s,\"%d\",a);}\n\n  inline int xToStringLen(double a) { return 18;}\n  inline void xToString(char* s, double a) { sprintf(s,\"%.11le\", a);}\n\n  inline int xToStringLen(char* a) { return strlen(a)+1;}\n  inline void xToString(char* s, char* a) { sprintf(s,\"%s\",a);}\n\n  template <class A, class B>\n  inline int xToStringLen(pair<A,B> a) { \n    return xToStringLen(a.first) + xToStringLen(a.second) + 1;\n  }\n\n  template <class A, class B>\n  inline void xToString(char* s, pair<A,B> a) { \n    int l = xToStringLen(a.first);\n    xToString(s, a.first);\n    s[l] = ' ';\n    xToString(s+l+1, a.second);\n  }\n\n  template <class Seq>\n  charstring seqToString(Seq const &A) {\n    size_t n = A.size();\n    auto L = parlay::tabulate(n, [&] (size_t i) -> long {\n\ttypename Seq::value_type x = A[i];\n\treturn xToStringLen(x)+1;});\n    size_t m;\n    std::tie(L,m) = parlay::scan(std::move(L));\n\n    charstring B(m+1, (char) 0);\n    char* Bs = B.begin();\n\n    parlay::parallel_for(0, n-1, [&] (long i) {\n      xToString(Bs + L[i], A[i]);\n      Bs[L[i+1] - 1] = '\\n';\n      });\n    xToString(Bs + L[n-1], A[n-1]);\n    Bs[m] = Bs[m-1] = '\\n';\n    \n    charstring C = parlay::filter(B, [&] (char c) {return c != 0;}); \n    C[C.size()-1] = 0;\n    return C;\n  }\n\n  template <class T>\n  void writeSeqToStream(ofstream& os, parlay::sequence<T> const &A) {\n    size_t bsize = 10000000;\n    size_t offset = 0;\n    size_t n = A.size();\n    while (offset < n) {\n      // Generates a string for a sequence of size at most bsize\n      // and then wrties it to the output stream\n      charstring S = seqToString(A.cut(offset, min(offset + bsize, n)));\n      os.write(S.begin(), S.size()-1);\n      offset += bsize;\n    }\n  }\n\n  template <class T>\n  int writeSeqToFile(string header,\n\t\t     parlay::sequence<T> const &A,\n\t\t     char const *fileName) {\n    auto a = A[0];\n    //xToStringLena(a);\n    ofstream file (fileName, ios::out | ios::binary);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      return 1;\n    }\n    file << header << endl;\n    writeSeqToStream(file, A);\n    file.close();\n    return 0;\n  }\n\n  template <class T1, class T2>\n  int write2SeqToFile(string header,\n\t\t      parlay::sequence<T1> const &A,\n\t\t      parlay::sequence<T2> const &B,\n\t\t      char const *fileName) {\n    ofstream file (fileName, ios::out | ios::binary);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      return 1;\n    }\n    file << header << endl;\n    writeSeqToStream(file, A);\n    writeSeqToStream(file, B);\n    file.close();\n    return 0;\n  }\n\n  charstring readStringFromFile(char const *fileName) {\n    ifstream file (fileName, ios::in | ios::binary | ios::ate);\n    if (!file.is_open()) {\n      std::cout << \"Unable to open file: \" << fileName << std::endl;\n      abort();\n    }\n    long end = file.tellg();\n    file.seekg (0, ios::beg);\n    long n = end - file.tellg();\n    charstring bytes(n, (char) 0);\n    file.read (bytes.begin(), n);\n    file.close();\n    return bytes;\n  }\n\n  string intHeaderIO = \"sequenceInt\";\n\n  template <class T>\n  int writeIntSeqToFile(parlay::sequence<T> const &A, char const *fileName) {\n    return writeSeqToFile(intHeaderIO, A, fileName);\n  }\n\n  sequence<sequence<char>> get_tokens(char const *fileName) {\n    // parlay::internal::timer t(\"get_tokens\");\n    // auto S = parlay::chars_from_file(fileName);\n    auto S = parlay::file_map(fileName);\n    // t.next(\"file map\");\n    auto r =  parlay::tokens(S, benchIO::is_space);\n    // t.next(\"tokens\");\n    return r;\n  }\n\n  template <class T>\n  parlay::sequence<T> readIntSeqFromFile(char const *fileName) {\n    auto W = get_tokens(fileName);\n    string header(W[0].begin(),W[0].end());\n    if (header != intHeaderIO) {\n      cout << \"readIntSeqFromFile: bad input\" << endl;\n      abort();\n    }\n    long n = W.size()-1;\n    auto A = parlay::tabulate(n, [&] (long i) -> T {\n\treturn parlay::chars_to_long(W[i+1]);});\n    return A;\n  }\n};\n\n"
  },
  {
    "path": "rangeSearch/bench/MakeBench",
    "content": "# ********************\n# GENERIC MAKEFILE FOR MOST BENCHMARKS THAT #include <name>.h\n# USES FOLLOWING DEFINITIONS\n#    BENCH : the name of the benchmark\n#    REQUIRE : dependences\n#    CC : the compiler\n#    CFLAGS : compiler flags\n#    LFLAGS : compiler link flags\n# ********************\n\nTIME = ../bench/$(BENCH)Time.C\nINCLUDE = \n\nall : $(BENCH) \n\n$(BENCH) : $(TIME) $(BENCH).h $(REQUIRE)\n\t$(CC) -DSTATS $(CFLAGS) $(INCLUDE) -include $(BENCH).h -o $(BENCH) $(TIME) $(LFLAGS)\n\nclean :\n\trm -f $(BENCH)\n\ncleanall : clean\n\trm -f testInputs*; cd ../bench; make -s clean\n"
  },
  {
    "path": "rangeSearch/bench/Makefile",
    "content": "include parallelDefsANN\r\nBNCHMRK = range\r\n\r\nCHECKFILES = $(BNCHMRK)Check.o\r\n\r\nCOMMON =\r\n\r\nINCLUDE = -Icommon\r\n\r\n%.o : %.C $(COMMON)\r\n\t$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@\r\n\r\n# $(BNCHMRK)Check : $(CHECKFILES)\r\n# \t$(CC) $(LFLAGS) -o $@ $(CHECKFILES)\r\n\r\nclean :\r\n\trm -f $(BNCHMRK)Check *.o *.pyc\r\n"
  },
  {
    "path": "rangeSearch/bench/get_time.h",
    "content": "#pragma once\n\n#include <stdlib.h>\n#include <sys/time.h>\n#include <iomanip>\n#include <iostream>\n#include <string>\n\nnamespace cpam {\n\nstruct timer {\n  double total_time;\n  double last_time;\n  bool on;\n  std::string name;\n  struct timezone tzp;\n\n  timer(std::string name = \"PBBS time\", bool _start = true)\n  : total_time(0.0), on(false), name(name), tzp({0,0}) {\n    if (_start) start();\n  }\n\n  double get_time() {\n    timeval now;\n    gettimeofday(&now, &tzp);\n    return ((double) now.tv_sec) + ((double) now.tv_usec)/1000000.;\n  }\n\n  void start () {\n    on = 1;\n    last_time = get_time();\n  }\n\n  double stop () {\n    on = 0;\n    double d = (get_time()-last_time);\n    total_time += d;\n    return d;\n  }\n\n  void reset() {\n     total_time=0.0;\n     on=0;\n  }\n\n  double get_total() {\n    if (on) return total_time + get_time() - last_time;\n    else return total_time;\n  }\n\n  double get_next() {\n    if (!on) return 0.0;\n    double t = get_time();\n    double td = t - last_time;\n    total_time += td;\n    last_time = t;\n    return td;\n  }\n\n  void report(double time, std::string str) {\n    std::ios::fmtflags cout_settings = std::cout.flags();\n    std::cout.precision(4);\n    std::cout << std::fixed;\n    std::cout << name << \": \";\n    if (str.length() > 0)\n      std::cout << str << \": \";\n    std::cout << time << std::endl;\n    std::cout.flags(cout_settings);\n  }\n\n  void total() {\n    report(get_total(),\"total\");\n    total_time = 0.0;\n  }\n\n  void reportTotal(std::string str) {\n    report(get_total(), str);\n  }\n\n  void next(std::string str) {\n    if (on) report(get_next(), str);\n  }\n};\n\n}  // namespace cpam\n"
  },
  {
    "path": "rangeSearch/bench/parallelDefsANN",
    "content": "ifeq (, $(shell which jemalloc-config))\r\nJEMALLOC =\r\nelse\r\nJEMALLOCLD = $(shell jemalloc-config --libdir)\r\nJEMALLOC = -L$(JEMALLOCLD) -ljemalloc \r\nendif\r\n\r\nCCFLAGS = -mcx16 -O3 -std=c++17 -march=native -DNDEBUG -I .\r\nCLFLAGS = -ldl $(JEMALLOC) \r\n\r\nOMPFLAGS = -DPARLAY_OPENMP -fopenmp\r\nCILKFLAGS = -DPARLAY_CILK -fcilkplus\r\nPBBFLAGS = -DHOMEGROWN -pthread\r\n\r\nifdef OPENMP\r\nCC = g++\r\nCFLAGS = $(OMPFLAGS) $(CCFLAGS)\r\nLFLAGS = $(OMPFLAGS) $(CLFLAGS)\r\n\r\nelse ifdef CILK\r\nCC = g++\r\nCFLAGS = $(CILKFLAGS) $(CCFLAGS)\r\nLFLAGS = $(CILKFLAGS) $(CLFLAGS)\r\n\r\nelse\r\nCC = g++\r\nCFLAGS = $(PBBFLAGS) $(CCFLAGS)\r\nLFLAGS = $(PBBFLAGS) $(CLFLAGS)\r\nendif\r\n"
  },
  {
    "path": "rangeSearch/bench/rangeTime.C",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <iostream>\n#include <algorithm>\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"../../algorithms/bench/parse_command_line.h\"\n#include \"../../algorithms/bench/time_loop.h\"\n#include \"../utils/NSGDist.h\"\n#include \"../utils/euclidian_point.h\"\n#include \"../utils/point_range.h\"\n#include \"../utils/mips_point.h\"\n#include \"../utils/graph.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\nusing namespace parlayANN;\n\n// *************************************************************\n//  TIMING\n// *************************************************************\n\nusing uint = unsigned int;\n\ntemplate<typename Point, typename PointRange, typename indexType>\nvoid timeRange(Graph<indexType> &G,\n               PointRange &Query_Points, long k,\n               BuildParams &BP, char* outFile,\n               RangeGroundTruth<indexType> GT, char* res_file, bool graph_built,\n               PointRange &Points)\n{\n  RNG<Point, PointRange, indexType>(G, BP, Query_Points, GT, res_file,\n                                    graph_built, Points);\n  if(outFile != NULL) G.save(outFile);\n}\n\nint main(int argc, char* argv[]) {\n    commandLine P(argc,argv,\n    \"[-a <alpha>] [-d <delta>] [-R <deg>]\"\n        \"[-L <bm>] [-k <k> ]  [-gt_path <g>] [-query_path <qF>]\"\n        \"[-graph_path <gF>] [-graph_outfile <oF>] [-res_path <rF>]\" \"[-num_passes <np>]\"\n        \"[-memory_flag <algoOpt>] [-mst_deg <q>] [-num_clusters <nc>] [-cluster_size <cs>]\"\n        \"[-data_type <tp>] [-dist_func <df>] [-base_path <b>] <inFile>\");\n\n  char* iFile = P.getOptionValue(\"-base_path\");\n  char* oFile = P.getOptionValue(\"-graph_outfile\");\n  char* gFile = P.getOptionValue(\"-graph_path\");\n  char* qFile = P.getOptionValue(\"-query_path\");\n  char* cFile = P.getOptionValue(\"-gt_path\");\n  char* rFile = P.getOptionValue(\"-res_path\");\n  char* vectype = P.getOptionValue(\"-data_type\");\n  long Q = P.getOptionIntValue(\"-Q\", 0);\n  long R = P.getOptionIntValue(\"-R\", 0);\n  if(R<0) P.badArgument();\n  long L = P.getOptionIntValue(\"-L\", 0);\n  if(L<0) P.badArgument();\n  long MST_deg = P.getOptionIntValue(\"-mst_deg\", 0);\n  if(MST_deg < 0) P.badArgument();\n  long num_clusters = P.getOptionIntValue(\"-num_clusters\", 0);\n  if(num_clusters<0) P.badArgument();\n  long cluster_size = P.getOptionIntValue(\"-cluster_size\", 0);\n  if(cluster_size<0) P.badArgument();\n  double radius  = P.getOptionDoubleValue(\"-radius\", 0.0);\n  long k = P.getOptionIntValue(\"-k\", 0);\n  if (k > 1000 || k < 0) P.badArgument();\n  double alpha = P.getOptionDoubleValue(\"-alpha\", 1.0);\n  int num_passes = P.getOptionIntValue(\"-num_passes\", 1);\n  int two_pass = P.getOptionIntValue(\"-two_pass\", 0);\n  if(two_pass > 1 | two_pass < 0) P.badArgument();\n  if (two_pass == 1) num_passes = 2;\n  double delta = P.getOptionDoubleValue(\"-delta\", 0);\n  if(delta<0) P.badArgument();\n  char* dfc = P.getOptionValue(\"-dist_func\");\n  int quantize = P.getOptionIntValue(\"-quantize_bits\", 0);\n  int quantize_build = P.getOptionIntValue(\"-quantize_mode\", 0);\n  bool verbose = P.getOption(\"-verbose\");\n  bool normalize = P.getOption(\"-normalize\");\n  double trim = P.getOptionDoubleValue(\"-trim\", 0.0); // not used\n  bool self = P.getOption(\"-self\");\n  int rerank_factor = P.getOptionIntValue(\"-rerank_factor\", 100);\n  bool range = P.getOption(\"-range\");\n  bool is_early_stop = P.getOption(\"-early_stop\");\n  char* sm = P.getOptionValue(\"-search_mode\");\n  double esr = P.getOptionDoubleValue(\"-early_stopping_radius\", 0);\n  double rad  = P.getOptionDoubleValue(\"-r\", 0.0);\n  double batch_factor = P.getOptionDoubleValue(\"-batch_factor\", .125);\n    \n  // this integer represents the number of random edges to start with for\n  // inserting in a single batch per round\n  int single_batch = P.getOptionIntValue(\"-single_batch\", 0);\n    \n  std::string df = std::string(dfc);\n  std::string tp = std::string(vectype);\n\n  std::string searchType = std::string(sm);\n  rangeQueryType rtype = Beam;\n\n  if (searchType == \"doubling\") {\n    rtype = Doubling;\n    std::cout << \"Using doubling range search\" << std::endl;\n  } else if (searchType == \"greedy\") {\n    rtype = Greedy;\n    std::cout << \"Using greedy range search\" << std::endl;\n  }\n  else if (searchType == \"beam\") {\n    rtype = Beam;\n    std::cout << \"Using beam range search\" << std::endl;\n  }\n  else rtype = None;\n\n  BuildParams BP = BuildParams(R, L, alpha, num_passes, num_clusters, cluster_size, MST_deg, delta,\n                               verbose, quantize_build,\n                               self, single_batch,\n                               Q, trim,\n                               rerank_factor, batch_factor,\n                               is_early_stop, esr,\n                               rtype, rad);\n  long maxDeg = BP.max_degree();\n\n  if((tp != \"uint8\") && (tp != \"int8\") && (tp != \"float\")){\n    std::cout << \"Error: vector type not specified correctly, specify int8, uint8, or float\" << std::endl;\n    abort();\n  }\n\n  if(df != \"Euclidian\" && df != \"mips\"){\n    std::cout << \"Error: specify distance type Euclidian or mips\" << std::endl;\n    abort();\n  }\n\n  bool graph_built = (gFile != NULL);\n\n  RangeGroundTruth<uint> GT = RangeGroundTruth<uint>(cFile);\n  \n  if(tp == \"float\"){\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<float>> Points(iFile);\n      PointRange<Euclidian_Point<float>> Query_Points(qFile);\n      if (normalize) {\n        std::cout << \"normalizing data\" << std::endl;\n        for (int i=0; i < Points.size(); i++) \n          Points[i].normalize();\n        for (int i=0; i < Query_Points.size(); i++) \n          Query_Points[i].normalize();\n      }\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      if (quantize == 8) {\n        std::cout << \"quantizing data to 1 byte\" << std::endl;\n        using QT = uint8_t;\n        using QPoint = Euclidian_Point<QT>;\n        using PR = PointRange<QPoint>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeRange<QPoint, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else if (quantize == 16) {\n        std::cout << \"quantizing data to 2 bytes\" << std::endl;\n        using Point = Euclidian_Point<uint16_t>;\n        using PR = PointRange<Point>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeRange<Point, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else {\n        using Point = Euclidian_Point<float>;\n        using PR = PointRange<Point>;\n        timeRange<Point, PR, uint>(G, Query_Points, k, BP, oFile, GT, rFile, graph_built, Points);\n      }\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<float>> Points(iFile);\n      PointRange<Mips_Point<float>> Query_Points(qFile);\n      if (normalize) {\n        std::cout << \"normalizing data\" << std::endl;\n        for (int i=0; i < Points.size(); i++) \n          Points[i].normalize();\n        for (int i=0; i < Query_Points.size(); i++) \n          Query_Points[i].normalize();\n      }\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      if (quantize == 8) {\n        std::cout << \"quantizing data to 1 byte\" << std::endl;\n        using QT = int8_t;\n        using Point = Quantized_Mips_Point<8>;\n        using PR = PointRange<Point>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeRange<Point, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else if (quantize == 16) {\n        std::cout << \"quantizing data to 2 bytes\" << std::endl;\n        using QT = int16_t;\n        using Point = Quantized_Mips_Point<16>;\n        using PR = PointRange<Point>;\n        PR Points_(Points);\n        PR Query_Points_(Query_Points, Points_.params);\n        timeRange<Point, PR, uint>(G, Query_Points_, k, BP, oFile, GT, rFile, graph_built, Points_);\n      } else {\n        using Point = Mips_Point<float>;\n        using PR = PointRange<Point>;\n        timeRange<Point, PR, uint>(G, Query_Points, k, BP, oFile, GT, rFile, graph_built, Points);\n      }\n    }\n  } else if(tp == \"uint8\"){\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<uint8_t>> Points(iFile);\n      PointRange<Euclidian_Point<uint8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeRange<Euclidian_Point<uint8_t>, PointRange<Euclidian_Point<uint8_t>>, uint>(G, Query_Points, k, BP, \n        oFile, GT, rFile, graph_built, Points);\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<uint8_t>> Points(iFile);\n      PointRange<Mips_Point<uint8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeRange<Mips_Point<uint8_t>, PointRange<Mips_Point<uint8_t>>, uint>(G, Query_Points, k, BP, \n        oFile, GT, rFile, graph_built, Points);\n    }\n  } else if(tp == \"int8\"){\n    if(df == \"Euclidian\"){\n      PointRange<Euclidian_Point<int8_t>> Points(iFile);\n      PointRange<Euclidian_Point<int8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeRange<Euclidian_Point<int8_t>, PointRange<Euclidian_Point<int8_t>>, uint>(G, Query_Points, k, BP,\n        oFile, GT, rFile, graph_built, Points);\n    } else if(df == \"mips\"){\n      PointRange<Mips_Point<int8_t>> Points(iFile);\n      PointRange<Mips_Point<int8_t>> Query_Points(qFile);\n      Graph<unsigned int> G; \n      if(gFile == NULL) G = Graph<unsigned int>(maxDeg, Points.size());\n      else G = Graph<unsigned int>(gFile);\n      timeRange<Mips_Point<int8_t>, PointRange<Mips_Point<int8_t>>, uint>(G, Query_Points, k, BP,\n        oFile, GT, rFile, graph_built, Points);\n    }\n  }\n  \n  return 0;\n}\n\n\n"
  },
  {
    "path": "rangeSearch/vamanaRange/Makefile",
    "content": "include ../bench/parallelDefsANN\n\nREQUIRE = ../utils/beamSearch.h ../utils/doublingSearch.h ../../algorithms/vamana/index.h  ../utils/check_nn_recall.h ../utils/NSGDist.h ../utils/parse_results.h ../utils/graph.h ../utils/point_range.h ../utils/check_range_recall.h ../utils/earlyStopping.h ../utils/rangeSearch.h ../utils/types.h ../utils/stats.h\nBENCH = range\n\ninclude ../bench/MakeBench\n"
  },
  {
    "path": "rangeSearch/vamanaRange/range.h",
    "content": "// This code is part of the Problem Based Benchmark Suite (PBBS)\n// Copyright (c) 2011 Guy Blelloch and the PBBS team\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights (to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n#include <algorithm>\n\n#include \"../utils/NSGDist.h\"\n#include \"../utils/check_range_recall.h\"\n#include \"../utils/beamSearch.h\"\n#include \"../utils/check_nn_recall.h\"\n#include \"../utils/parse_results.h\"\n#include \"../utils/stats.h\"\n#include \"../utils/types.h\"\n#include \"../utils/graph.h\"\n#include \"../utils/mips_point.h\"\n#include \"../utils/euclidian_point.h\"\n#include \"../../algorithms/vamana/index.h\"\n#include \"parlay/parallel.h\"\n#include \"parlay/primitives.h\"\n#include \"parlay/random.h\"\n\nnamespace parlayANN{\n\ntemplate<typename Point, typename PointRange_,  typename indexType>\nvoid RNG(Graph<indexType> &G, BuildParams &BP,\n         PointRange_ &Query_Points,\n         RangeGroundTruth<indexType> GT,\n         char* res_file, bool graph_built, PointRange_ &Points) {\n  parlay::internal::timer t(\"ANN\");\n  using findex = knn_index<PointRange_, PointRange_, indexType>;\n  findex I(BP);\n  double idx_time;\n  indexType start_point;\n\n  stats<unsigned int> BuildStats(G.size());\n  if(graph_built){\n    idx_time = 0;\n    start_point = 1;\n  } else{\n    I.build_index(G, Points, Points, BuildStats);\n    start_point = 1; //  I.get_start();\n    idx_time = t.next_time();\n  }\n\n  \n  std::string name = \"Vamana\";\n  std::string params =\n      \"R = \" + std::to_string(BP.R) + \", L = \" + std::to_string(BP.L);\n  auto [avg_deg, max_deg] = graph_stats_(G);\n  auto vv = BuildStats.visited_stats();\n  std::cout << \"Average visited: \" << vv[0] << \", Tail visited: \" << vv[1]\n            << std::endl;\n  Graph_ G_(name, params, G.size(), avg_deg, max_deg, idx_time);\n  G_.print();\n  double esr = BP.early_stopping_radius;\n  double rad = BP.radius;\n  if(Query_Points.size() != 0) {\n    if (BP.quantize != 0) {\n      std::cout << \"quantizing build and first pass of search to 1 byte\" << std::endl;\n      if (Point::is_metric()) {\n        using QT = uint8_t;\n        using QPoint = Euclidian_Point<QT>;\n        using QPR = PointRange<QPoint>;\n        QPR Q_Points(Points);  // quantized to one byte\n        QPR Q_Query_Points(Query_Points, Q_Points.params);\n        range_search_wrapper<Point>(G,\n                                    Points, Query_Points,\n                                    Q_Points, Q_Query_Points,\n                                    GT, start_point,\n                                    BP.is_early_stop, esr, BP.range_query_type, rad);\n      } else {\n        using QPoint = Quantized_Mips_Point<8,true,255>;\n        using QPR = PointRange<QPoint>;\n        QPR Q_Points(Points);\n        QPR Q_Query_Points(Query_Points, Q_Points.params);\n        range_search_wrapper<Point>(G,\n                                    Points, Query_Points,\n                                    Q_Points, Q_Query_Points,\n                                    GT, start_point,\n                                    BP.is_early_stop, esr, BP.range_query_type, rad);\n      }\n    } else {\n      range_search_wrapper<Point>(G,\n                                  Points, Query_Points,\n                                  Points, Query_Points,\n                                  GT, start_point,\n                                  BP.is_early_stop, esr, BP.range_query_type, rad);\n    }\n  }\n}\n}\n"
  }
]