From 4f6ddfed2e6149b9874c26385afc42cfcb33e60e Mon Sep 17 00:00:00 2001 From: Jonas Andreas Sibbesen Date: Mon, 26 Feb 2018 13:13:31 +0100 Subject: [PATCH] BayesTyper (v1.2) --- .gitignore | 2 + CMakeLists.txt | 8 +- external/kmc_api/CMakeLists.txt | 7 + external/libbf/.clang-format | 41 + external/libbf/.gitignore | 6 + external/libbf/CMakeLists.txt | 20 + external/libbf/COPYING | 28 + external/libbf/README.md | 168 ++ external/libbf/aux/macports/README | 9 + .../libbf/aux/macports/devel/boost/Portfile | 314 +++ .../devel/boost/files/patch-bootstrap.sh.diff | 11 + .../patch-libs-mpi-build-Jamfile.v2.diff | 26 + ...ython-src-converter-builtin_converters.cpp | 12 + ...patch-tools-build-v2-tools-darwin.jam.diff | 11 + ...tch-tools-build-v2-tools-python-2.jam.diff | 16 + ...patch-tools-build-v2-tools-python.jam.diff | 11 + ...h-tools_build_v2_engine_src_build.jam.diff | 11 + ...ch-tools_build_v2_engine_src_build.sh.diff | 11 + external/libbf/bf/all.hpp | 10 + external/libbf/bf/bitvector.hpp | 313 +++ external/libbf/bf/bloom_filter.hpp | 52 + external/libbf/bf/bloom_filter/a2.hpp | 57 + external/libbf/bf/bloom_filter/basic.hpp | 104 + external/libbf/bf/bloom_filter/bitwise.hpp | 38 + external/libbf/bf/bloom_filter/counting.hpp | 135 ++ external/libbf/bf/bloom_filter/stable.hpp | 38 + external/libbf/bf/counter_vector.hpp | 103 + external/libbf/bf/h3.hpp | 68 + external/libbf/bf/hash.hpp | 76 + external/libbf/bf/object.hpp | 34 + external/libbf/bf/wrap.hpp | 46 + external/libbf/cmake/cmake_uninstall.cmake.in | 35 + external/libbf/configure | 146 ++ external/libbf/doc/Doxyfile | 1870 +++++++++++++++++ external/libbf/doc/documentation.dox | 14 + external/libbf/doc/figs/architecture.png | Bin 0 -> 54755 bytes external/libbf/doc/figs/bf-basic-part.png | Bin 0 -> 11542 bytes external/libbf/doc/figs/bf-basic.png | Bin 0 -> 10390 bytes external/libbf/doc/figs/bf-bitwise.png | Bin 0 -> 17182 bytes external/libbf/doc/figs/bf-counting.png | Bin 0 -> 10946 bytes external/libbf/doc/figs/bf-scalable.png | Bin 0 -> 15642 bytes external/libbf/doc/figs/shrinking.png | Bin 0 -> 16885 bytes external/libbf/doc/figs/sliding-window.png | Bin 0 -> 9878 bytes external/libbf/doc/figs/spectral-rm-bug.png | Bin 0 -> 20304 bytes external/libbf/src/bitvector.cpp | 421 ++++ external/libbf/src/bloom_filter/a2.cpp | 45 + external/libbf/src/bloom_filter/basic.cpp | 96 + external/libbf/src/bloom_filter/bitwise.cpp | 54 + external/libbf/src/bloom_filter/counting.cpp | 174 ++ external/libbf/src/bloom_filter/stable.cpp | 38 + external/libbf/src/counter_vector.cpp | 105 + external/libbf/src/hash.cpp | 57 + external/libbf/test/CMakeLists.txt | 6 + external/libbf/test/bf/CMakeLists.txt | 8 + external/libbf/test/bf/bf.cc | 219 ++ external/libbf/test/bf/configuration.cc | 43 + external/libbf/test/bf/configuration.h | 14 + external/libbf/test/bf/util/configuration.h | 428 ++++ external/libbf/test/bf/util/error.h | 45 + external/libbf/test/bf/util/trial.h | 172 ++ external/libbf/test/test.hpp | 29 + external/libbf/test/tests.cpp | 324 +++ external/libbf/test/unit_test.hpp | 562 +++++ external/libbf/test/unit_test_impl.hpp | 621 ++++++ external/nthash.hpp | 469 +++++ include/bayesTyper/BitsetCompare.hpp | 2 +- include/bayesTyper/ChromosomePloidy.hpp | 2 +- include/bayesTyper/Combinator.hpp | 2 +- include/bayesTyper/CountAllocation.hpp | 2 +- include/bayesTyper/CountDistribution.hpp | 2 +- include/bayesTyper/DiscreteSampler.hpp | 2 +- include/bayesTyper/FrequencyDistribution.hpp | 2 +- include/bayesTyper/GenotypeWriter.hpp | 10 +- include/bayesTyper/Genotypes.hpp | 12 +- .../HaplotypeFrequencyDistribution.hpp | 52 +- include/bayesTyper/HybridHash.hpp | 2 +- include/bayesTyper/HybridHash.tpp | 15 +- include/bayesTyper/InferenceEngine.hpp | 8 +- include/bayesTyper/Kmer.hpp | 3 +- include/bayesTyper/Kmer.tpp | 9 +- include/bayesTyper/KmerCounter.hpp | 34 +- include/bayesTyper/KmerCounts.hpp | 7 +- include/bayesTyper/KmerFactory.hpp | 4 +- include/bayesTyper/KmerHash.hpp | 2 +- include/bayesTyper/KmerStats.hpp | 2 +- include/bayesTyper/LinearMap.hpp | 2 +- include/bayesTyper/LinearMap.tpp | 2 +- .../NegativeBinomialDistribution.hpp | 2 +- include/bayesTyper/OptionsContainer.hpp | 2 +- include/bayesTyper/OptionsContainer.tpp | 2 +- include/bayesTyper/PerfectSet.hpp | 2 +- include/bayesTyper/PerfectSet.tpp | 2 +- include/bayesTyper/ProducerConsumerQueue.hpp | 2 +- include/bayesTyper/ProducerConsumerQueue.tpp | 2 +- include/bayesTyper/Regions.hpp | 2 +- include/bayesTyper/Sample.hpp | 2 +- include/bayesTyper/Sequence.hpp | 2 +- include/bayesTyper/SparsityEstimator.hpp | 2 +- include/bayesTyper/Utils.hpp | 9 +- include/bayesTyper/VariantCluster.hpp | 18 +- .../bayesTyper/VariantClusterGenotyper.hpp | 20 +- include/bayesTyper/VariantClusterGraph.hpp | 93 +- .../bayesTyper/VariantClusterGraphPath.hpp | 45 +- .../bayesTyper/VariantClusterGraphVertex.hpp | 4 +- include/bayesTyper/VariantClusterGroup.hpp | 24 +- .../bayesTyper/VariantClusterHaplotypes.hpp | 16 +- include/bayesTyper/VariantFileParser.hpp | 36 +- include/bayesTyper/VariantInfo.hpp | 2 +- include/bayesTyperTools/addAttributes.hpp | 4 +- include/bayesTyperTools/annotate.hpp | 2 +- .../{writeIndels.hpp => bloomMaker.hpp} | 37 +- include/bayesTyperTools/combine.hpp | 2 +- include/bayesTyperTools/convertAlleleId.hpp | 2 +- include/bayesTyperTools/filter.hpp | 2 +- include/bayesTyperTools/getSummary.hpp | 2 +- include/bayesTyperTools/merge.hpp | 2 +- include/bayesTyperTools/split.hpp | 4 +- include/kmerBloom/KmerBloom.hpp | 111 + include/vcf++/Allele.hpp | 2 +- include/vcf++/Attribute.hpp | 2 +- include/vcf++/AttributeFilter.hpp | 2 +- include/vcf++/AttributeSet.hpp | 2 +- include/vcf++/Auxiliaries.hpp | 4 +- include/vcf++/CompareOperators.hpp | 2 +- include/vcf++/Contig.hpp | 2 +- include/vcf++/FastaReader.hpp | 2 +- include/vcf++/FastaRecord.hpp | 2 +- include/vcf++/JoiningString.hpp | 2 +- include/vcf++/ReductionOperator.hpp | 2 +- include/vcf++/Regions.hpp | 2 +- include/vcf++/Sample.hpp | 2 +- include/vcf++/SampleAlleleAttributeFilter.hpp | 2 +- include/vcf++/Stats.hpp | 2 +- include/vcf++/Trio.hpp | 2 +- include/vcf++/Utils.hpp | 2 +- include/vcf++/Variant.hpp | 2 +- include/vcf++/VcfFile.hpp | 2 +- include/vcf++/VcfMetaData.hpp | 2 +- src/bayesTyper/CMakeLists.txt | 9 +- src/bayesTyper/ChromosomePloidy.cpp | 2 +- src/bayesTyper/Combinator.cpp | 2 +- src/bayesTyper/CountAllocation.cpp | 2 +- src/bayesTyper/CountDistribution.cpp | 2 +- src/bayesTyper/DiscreteSampler.cpp | 2 +- src/bayesTyper/FrequencyDistribution.cpp | 2 +- src/bayesTyper/GenotypeWriter.cpp | 54 +- .../HaplotypeFrequencyDistribution.cpp | 84 +- src/bayesTyper/InferenceEngine.cpp | 39 +- src/bayesTyper/KmerCounter.cpp | 331 +-- src/bayesTyper/KmerCounts.cpp | 17 +- src/bayesTyper/KmerFactory.cpp | 63 +- src/bayesTyper/KmerHash.cpp | 51 +- src/bayesTyper/KmerStats.cpp | 4 +- .../NegativeBinomialDistribution.cpp | 2 +- src/bayesTyper/Regions.cpp | 2 +- src/bayesTyper/SparsityEstimator.cpp | 2 +- src/bayesTyper/VariantCluster.cpp | 2 +- src/bayesTyper/VariantClusterGenotyper.cpp | 104 +- src/bayesTyper/VariantClusterGraph.cpp | 1208 +++++------ src/bayesTyper/VariantClusterGraphPath.cpp | 225 +- src/bayesTyper/VariantClusterGroup.cpp | 128 +- src/bayesTyper/VariantClusterHaplotypes.cpp | 100 +- src/bayesTyper/VariantFileParser.cpp | 240 ++- src/bayesTyper/main.cpp | 40 +- src/bayesTyperTools/CMakeLists.txt | 7 +- src/bayesTyperTools/addAttributes.cpp | 155 +- src/bayesTyperTools/annotate.cpp | 2 +- src/bayesTyperTools/bloomMaker.cpp | 257 +++ src/bayesTyperTools/combine.cpp | 35 +- src/bayesTyperTools/convertAlleleId.cpp | 15 +- src/bayesTyperTools/filter.cpp | 31 +- src/bayesTyperTools/getSummary.cpp | 24 +- src/bayesTyperTools/main.cpp | 198 +- src/bayesTyperTools/merge.cpp | 7 +- src/bayesTyperTools/scripts/CMakeLists.txt | 16 +- .../scripts/addEditDistanceAndCondordance.cpp | 327 ++- .../scripts/addGenotypeQuality.cpp | 113 - .../scripts/addMaxGenotypePosterior.cpp | 2 +- .../assessHaplotypeTransmissionSupport.cpp | 48 +- .../scripts/collapseSummaryTable.cpp | 2 +- .../scripts/convertNestedGenotypes.cpp | 2 +- .../scripts/convertSeqToAlleleId.cpp | 137 +- .../scripts/filterStructuralVariants.cpp | 4 +- .../scripts/generateDiplotypes.cpp | 4 +- .../scripts/getGenomicIntervals.cpp | 2 +- src/bayesTyperTools/scripts/getKmerStats.cpp | 4 +- .../scripts/transferAlleleCallsetOrigin.cpp | 176 ++ .../scripts/transferAlleleFreqFields.cpp | 207 -- src/bayesTyperTools/scripts/writeIndels.cpp | 116 + src/bayesTyperTools/split.cpp | 75 +- src/bayesTyperTools/writeIndels.cpp | 110 - src/kmerBloom/CMakeLists.txt | 8 + src/kmerBloom/KmerBloom.cpp | 326 +++ src/vcf++/Allele.cpp | 2 +- src/vcf++/Attribute.cpp | 2 +- src/vcf++/AttributeFilter.cpp | 2 +- src/vcf++/AttributeSet.cpp | 2 +- src/vcf++/Auxiliaries.cpp | 8 +- src/vcf++/Contig.cpp | 2 +- src/vcf++/FastaReader.cpp | 2 +- src/vcf++/FastaRecord.cpp | 2 +- src/vcf++/JoiningString.cpp | 2 +- src/vcf++/Regions.cpp | 2 +- src/vcf++/Sample.cpp | 2 +- src/vcf++/SampleAlleleAttributeFilter.cpp | 2 +- src/vcf++/Stats.cpp | 2 +- src/vcf++/Trio.cpp | 2 +- src/vcf++/Utils.cpp | 2 +- src/vcf++/Variant.cpp | 2 +- src/vcf++/VcfFile.cpp | 2 +- src/vcf++/VcfMetaData.cpp | 2 +- 211 files changed, 10754 insertions(+), 2873 deletions(-) create mode 100644 .gitignore create mode 100755 external/kmc_api/CMakeLists.txt create mode 100644 external/libbf/.clang-format create mode 100644 external/libbf/.gitignore create mode 100644 external/libbf/CMakeLists.txt create mode 100644 external/libbf/COPYING create mode 100644 external/libbf/README.md create mode 100644 external/libbf/aux/macports/README create mode 100644 external/libbf/aux/macports/devel/boost/Portfile create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-bootstrap.sh.diff create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-libs-mpi-build-Jamfile.v2.diff create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-libs-python-src-converter-builtin_converters.cpp create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-darwin.jam.diff create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python-2.jam.diff create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python.jam.diff create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.jam.diff create mode 100644 external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.sh.diff create mode 100644 external/libbf/bf/all.hpp create mode 100644 external/libbf/bf/bitvector.hpp create mode 100644 external/libbf/bf/bloom_filter.hpp create mode 100644 external/libbf/bf/bloom_filter/a2.hpp create mode 100644 external/libbf/bf/bloom_filter/basic.hpp create mode 100644 external/libbf/bf/bloom_filter/bitwise.hpp create mode 100644 external/libbf/bf/bloom_filter/counting.hpp create mode 100644 external/libbf/bf/bloom_filter/stable.hpp create mode 100644 external/libbf/bf/counter_vector.hpp create mode 100644 external/libbf/bf/h3.hpp create mode 100644 external/libbf/bf/hash.hpp create mode 100644 external/libbf/bf/object.hpp create mode 100644 external/libbf/bf/wrap.hpp create mode 100644 external/libbf/cmake/cmake_uninstall.cmake.in create mode 100755 external/libbf/configure create mode 100644 external/libbf/doc/Doxyfile create mode 100644 external/libbf/doc/documentation.dox create mode 100644 external/libbf/doc/figs/architecture.png create mode 100644 external/libbf/doc/figs/bf-basic-part.png create mode 100644 external/libbf/doc/figs/bf-basic.png create mode 100644 external/libbf/doc/figs/bf-bitwise.png create mode 100644 external/libbf/doc/figs/bf-counting.png create mode 100644 external/libbf/doc/figs/bf-scalable.png create mode 100644 external/libbf/doc/figs/shrinking.png create mode 100644 external/libbf/doc/figs/sliding-window.png create mode 100644 external/libbf/doc/figs/spectral-rm-bug.png create mode 100644 external/libbf/src/bitvector.cpp create mode 100644 external/libbf/src/bloom_filter/a2.cpp create mode 100644 external/libbf/src/bloom_filter/basic.cpp create mode 100644 external/libbf/src/bloom_filter/bitwise.cpp create mode 100644 external/libbf/src/bloom_filter/counting.cpp create mode 100644 external/libbf/src/bloom_filter/stable.cpp create mode 100644 external/libbf/src/counter_vector.cpp create mode 100644 external/libbf/src/hash.cpp create mode 100644 external/libbf/test/CMakeLists.txt create mode 100644 external/libbf/test/bf/CMakeLists.txt create mode 100644 external/libbf/test/bf/bf.cc create mode 100644 external/libbf/test/bf/configuration.cc create mode 100644 external/libbf/test/bf/configuration.h create mode 100644 external/libbf/test/bf/util/configuration.h create mode 100644 external/libbf/test/bf/util/error.h create mode 100644 external/libbf/test/bf/util/trial.h create mode 100644 external/libbf/test/test.hpp create mode 100644 external/libbf/test/tests.cpp create mode 100644 external/libbf/test/unit_test.hpp create mode 100644 external/libbf/test/unit_test_impl.hpp create mode 100644 external/nthash.hpp rename include/bayesTyperTools/{writeIndels.hpp => bloomMaker.hpp} (60%) create mode 100644 include/kmerBloom/KmerBloom.hpp create mode 100644 src/bayesTyperTools/bloomMaker.cpp delete mode 100644 src/bayesTyperTools/scripts/addGenotypeQuality.cpp create mode 100644 src/bayesTyperTools/scripts/transferAlleleCallsetOrigin.cpp delete mode 100644 src/bayesTyperTools/scripts/transferAlleleFreqFields.cpp create mode 100644 src/bayesTyperTools/scripts/writeIndels.cpp delete mode 100644 src/bayesTyperTools/writeIndels.cpp create mode 100755 src/kmerBloom/CMakeLists.txt create mode 100644 src/kmerBloom/KmerBloom.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b85dcb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore Mac specific files +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt index 1273ed3..4f40c5e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,11 +14,15 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) -SET(CMAKE_CXX_FLAGS "--std=c++11 -g -Wall -O3 -DBT_VERSION='\"v1.1 ${GIT_LAST_COMMIT_HASH}\"' -lpthread") +SET(CMAKE_CXX_FLAGS "--std=c++11 -g -Wall -O3 -DBT_VERSION='\"v1.2 ${GIT_LAST_COMMIT_HASH}\"' -lpthread") FIND_PACKAGE(Boost COMPONENTS program_options system filesystem iostreams REQUIRED) message(STATUS ${Boost_LIBRARIES}) + +add_subdirectory(${CMAKE_SOURCE_DIR}/external/kmc_api) +add_subdirectory(${CMAKE_SOURCE_DIR}/external/libbf) add_subdirectory(${CMAKE_SOURCE_DIR}/src/vcf++) +add_subdirectory(${CMAKE_SOURCE_DIR}/src/kmerBloom) add_subdirectory(${CMAKE_SOURCE_DIR}/src/bayesTyper) add_subdirectory(${CMAKE_SOURCE_DIR}/src/bayesTyperTools) -add_subdirectory(${CMAKE_SOURCE_DIR}/src/bayesTyperTools/scripts) +add_subdirectory(${CMAKE_SOURCE_DIR}/src/bayesTyperTools/scripts) \ No newline at end of file diff --git a/external/kmc_api/CMakeLists.txt b/external/kmc_api/CMakeLists.txt new file mode 100755 index 0000000..749e26b --- /dev/null +++ b/external/kmc_api/CMakeLists.txt @@ -0,0 +1,7 @@ +project(kmc) + +SET(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib) + +include_directories(${CMAKE_SOURCE_DIR}/external/kmc_api) + +add_library(${PROJECT_NAME} kmc_file.cpp kmer_api.cpp mmer.cpp) diff --git a/external/libbf/.clang-format b/external/libbf/.clang-format new file mode 100644 index 0000000..105c50d --- /dev/null +++ b/external/libbf/.clang-format @@ -0,0 +1,41 @@ +--- +AccessModifierOffset: -2 +AlignEscapedNewlinesLeft: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackParameters: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +IndentCaseLabels: true +IndentWidth: 2 +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None + +# Force pointers to the type +DerivePointerAlignment: false +PointerAlignment: Left + +# Put space after = and after control statements +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements + +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +Standard: Cpp11 +UseTab: Never +BreakConstructorInitializersBeforeComma: false +... diff --git a/external/libbf/.gitignore b/external/libbf/.gitignore new file mode 100644 index 0000000..248ea02 --- /dev/null +++ b/external/libbf/.gitignore @@ -0,0 +1,6 @@ +.*swp +.*swo +.DS_Store +build +doc/gh-pages +Makefile diff --git a/external/libbf/CMakeLists.txt b/external/libbf/CMakeLists.txt new file mode 100644 index 0000000..eb5f2fe --- /dev/null +++ b/external/libbf/CMakeLists.txt @@ -0,0 +1,20 @@ +# -- Project Setup ------------------------------------------------------------ + +project(libbf) + +SET(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/lib) + +include_directories(${CMAKE_SOURCE_DIR}/external/libbf) + +set(libbf_sources + src/bitvector.cpp + src/counter_vector.cpp + src/hash.cpp + src/bloom_filter/a2.cpp + src/bloom_filter/basic.cpp + src/bloom_filter/bitwise.cpp + src/bloom_filter/counting.cpp + src/bloom_filter/stable.cpp +) + +add_library(libbf ${libbf_sources}) diff --git a/external/libbf/COPYING b/external/libbf/COPYING new file mode 100644 index 0000000..2b7643d --- /dev/null +++ b/external/libbf/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2016, Matthias Vallentin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/external/libbf/README.md b/external/libbf/README.md new file mode 100644 index 0000000..2c5e7f1 --- /dev/null +++ b/external/libbf/README.md @@ -0,0 +1,168 @@ +**libbf** is a C++11 library which implements [various Bloom +filters][blog-post], including: + +- Basic +- Counting +- Spectral MI +- Spectral RM +- Bitwise +- A^2 +- Stable + +[blog-post]: http://matthias.vallentin.net/blog/2011/06/a-garden-variety-of-bloom-filters/ + +Synopsis +======== + + #include + #include + + int main() + { + bf::basic_bloom_filter b(0.8, 100); + + // Add two elements. + b.add("foo"); + b.add(42); + + // Test set membership + std::cout << b.lookup("foo") << std::endl; // 1 + std::cout << b.lookup("bar") << std::endl; // 0 + std::cout << b.lookup(42) << std::endl; // 1 + + // Remove all elements. + b.clear(); + std::cout << b.lookup("foo") << std::endl; // 0 + std::cout << b.lookup(42) << std::endl; // 0 + + return 0; + } + +Requirements +============ + +- A C++11 compiler (GCC >= 4.7 or Clang >= 3.2) +- CMake (>= 2.8) + +Installation +============ + +The build process uses CMake, wrapped in autotools-like scripts. The configure +script honors the `CXX` environment variable to select a specific C++compiler. +For example, the following steps compile libbf with Clang and install it under +`PREFIX`: + + CXX=clang++ ./configure --prefix=PREFIX + make + make test + make install + +Documentation +============= + +The most recent version of the Doxygen API documentation exists at +. Alternatively, you can build the +documentation locally via `make doc` and then browse to +`doc/gh-pages/api/index.html`. + +Usage +===== + +After having installed libbf, you can use it in your application by including +the header file `bf.h` and linking against the library. All data structures +reside in the namespace `bf` and the following examples assume: + + using namespace bf; + +Each Bloom filter inherits from the abstract base class `bloom_filter`, which +provides addition and lookup via the virtual functions `add` and `lookup`. +These functions take an *object* as argument, which serves a light-weight view +over sequential data for hashing. + +For example, if you can create a basic Bloom filter with a desired +false-positive probability and capacity as follows: + + // Construction. + bloom_filter* bf = new basic_bloom_filter(0.8, 100); + + // Addition. + bf->add("foo"); + bf->add(42); + + // Lookup. + assert(bf->lookup("foo") == 1); + assert(bf->lookup(42) == 1); + + // Remove all elements from the Bloom filter. + bf->clear(); + +In this case, libbf computes the optimal number of hash functions needed to +achieve the desired false-positive rate which holds until the capacity has been +reached (80% and 100 distinct elements, in the above example). Alternatively, +you can construct a basic Bloom filter by specifying the number of hash +functions and the number of cells in the underlying bit vector: + + bloom_filter* bf = new basic_bloom_filter(make_hasher(3), 1024); + +Since not all Bloom filter implementations come with closed-form solutions +based on false-positive probabilities, most constructors use this latter form +of explicit resource provisioning. + +In the above example, the free function `make_hasher` constructs a *hasher*-an +abstraction for hashing objects *k* times. There exist currently two different +hasher, a `default_hasher` and a +[`double_hasher`](http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/rsa.pdf). The +latter uses a linear combination of two pairwise-independent, universal hash +functions to produce the *k* digests, whereas the former merely hashes the +object *k* times. + +Evaluation +---------- + +libbf also ships with a small Bloom filter tool `bf` in the test directory. +This program supports evaluation the accuracy of the different Bloom filter +flavors with respect to their false-positive and false-negative rates. Have a +look at the console help (`-h` or `--help`) for detailed usage instructions. + +The tool operates in two phases: + +1. Read input from a file and insert it into a Bloom filter +2. Query the Bloom filter and compare the result to the ground truth + +For example, consider the following input file: + + foo + bar + baz + baz + foo + +From this input file, you can generate the real ground truth file as follows: + + sort input.txt | uniq -c | tee query.txt + 1 bar + 2 baz + 2 foo + +The tool `bf` will compute false-positive and false-negative counts for each +element, based on the ground truth given. In the case of a simple counting +Bloom filter, an invocation may look like this: + + bf -t counting -m 2 -k 3 -i input.txt -q query.txt | column -t + +Yielding the following output: + + TN TP FP FN G C E + 0 1 0 0 1 1 bar + 0 1 0 1 2 1 baz + 0 1 0 2 2 1 foo + +The column headings denote true negatives (`TN`), true positives (`TP`), false +positives (`FP`), false negatives (`FN`), ground truth count (`G`), actual +count (`C`), and the queried element. The counts are cumulative to support +incremental evaluation. + +License +======== + +libbf comes with a BSD-style license (see [COPYING](COPYING) for details). diff --git a/external/libbf/aux/macports/README b/external/libbf/aux/macports/README new file mode 100644 index 0000000..93a117a --- /dev/null +++ b/external/libbf/aux/macports/README @@ -0,0 +1,9 @@ +Add the line + + file:///path/to/this/directory + +to /opt/local/etc/macports/sources.conf *before* the rsync source and run + + sudo portindex + +in the same directory where this README is located. diff --git a/external/libbf/aux/macports/devel/boost/Portfile b/external/libbf/aux/macports/devel/boost/Portfile new file mode 100644 index 0000000..4a5a1b7 --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/Portfile @@ -0,0 +1,314 @@ +# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=tcl:et:sw=4:ts=4:sts=4 +# $Id: Portfile 78181 2011-04-27 16:35:03Z adfernandes@macports.org $ + +PortSystem 1.0 + +name boost +version 1.46.1 +license Boost-1.0 +categories devel +platforms darwin +maintainers adfernandes +description Collection of portable C++ source libraries + +long_description \ + Boost provides free portable peer-reviewed C++ \ + libraries. The emphasis is on portable libraries \ + which work well with the C++ Standard Library. + +homepage http://www.boost.org +master_sites sourceforge +set distver [join [split ${version} .] _] +distname ${name}_${distver} +use_bzip2 yes + +checksums md5 7375679575f4c8db605d426fc721d506 \ + sha1 3ca6e173ec805e5126868d8a03618e587aa26aef \ + rmd160 bb43b39f7c4b683b80bafa3042b95e65a242d9c3 + +depends_lib port:zlib \ + port:expat \ + port:bzip2 \ + port:icu + +patchfiles patch-tools_build_v2_engine_src_build.sh.diff \ + patch-tools_build_v2_engine_src_build.jam.diff \ + patch-bootstrap.sh.diff + +post-patch { + reinplace "s|%%CONFIGURE.CC%%|${configure.cc}|g" ${worksrcpath}/tools/build/v2/engine/src/build.sh + reinplace "s|%%CONFIGURE.CC%%|${configure.cc}|g" ${worksrcpath}/tools/build/v2/engine/src/build.jam +} + +proc write_jam s { + global worksrcpath + set config [open ${worksrcpath}/user-config.jam a] + puts ${config} ${s} + close ${config} +} + +proc user_config_jam {toolset version compiler flags} { + set config "using ${toolset} : ${version} : ${compiler} : ${flags} ;" + write_jam config +} + +configure.cmd ./bootstrap.sh +configure.args --without-libraries=python \ + --without-libraries=mpi \ + --with-icu=${prefix} + +configure.universal_args + +post-configure { + reinplace -E "s|-install_name \"|&${prefix}/lib/|" \ + ${worksrcpath}/tools/build/v2/tools/darwin.jam + + # Modified from 'portconfigure.tcl': + # set pre-compiler filter to use (ccache/distcc), if any. + if {[tbool configure.ccache] && [tbool configure.distcc]} { + set filter "ccache " + ui_msg "Warning: boost does not support distcc with ccache" + } elseif {[tbool configure.ccache]} { + set filter "ccache " + } elseif {[tbool configure.distcc]} { + set filter "distcc " + } else { + set filter "" + } + + user_config_jam darwin "" ${filter}${configure.cxx} "" +} + +# Although bjam can supposedly use parallel builds, it has random failures. See #28878 and #23531. +# To re-enable it, comment out the below and add '-j${build.jobs}' to 'build.args', also below. +# +use_parallel_build no + +build.cmd ${worksrcpath}/bjam +build.target +build.args -d2 \ + --layout=tagged \ + --debug-configuration \ + --user-config=user-config.jam \ + -sBZIP2_INCLUDE=${prefix}/include \ + -sBZIP2_LIBPATH=${prefix}/lib \ + -sEXPAT_INCLUDE=${prefix}/include \ + -sEXPAT_LIBPATH=${prefix}/lib \ + -sZLIB_INCLUDE=${prefix}/include \ + -sZLIB_LIBPATH=${prefix}/lib \ + variant=release \ + threading=single,multi \ + link=static,shared + +destroot.cmd ${worksrcpath}/bjam +destroot.post_args +# +pre-destroot { + eval destroot.args ${build.args} --prefix=${destroot}${prefix} + system "find ${worksrcpath} -type f -name '*.gch' -exec rm {} \\;" +} + +post-destroot { + set docdir ${prefix}/share/doc/${name} + xinstall -d ${destroot}${docdir} + set l [expr [string length ${worksrcpath}] + 1] + fs-traverse f [glob -directory ${worksrcpath} *] { + set dest ${destroot}${docdir}/[string range ${f} ${l} end] + if {[file isdirectory ${f}]} { + if {[file tail ${f}] eq "example"} { + copy ${f} ${dest} + continue + } + xinstall -d ${dest} + } elseif {[lsearch -exact {css htm html png svg} [string range [file extension ${f}] 1 end]] != -1} { + xinstall -m 644 ${f} ${dest} + } + } +} + +set pythons_suffixes {24 25 26 27 31 32} + +set pythons_ports {} +foreach s ${pythons_suffixes} { + lappend pythons_ports python${s} +} + +proc python_dir {} { + global pythons_suffixes + foreach s ${pythons_suffixes} { + if {[variant_isset python${s}]} { + set p python[string index ${s} 0].[string index ${s} 1] + return [file normalize [exec ${p} -c "import sys; print(sys.prefix)"]/lib/${p}/site-packages] + } + } + error "Python support not enabled." +} + +foreach s ${pythons_suffixes} { + set p python${s} + set v [string index ${s} 0].[string index ${s} 1] + set i [lsearch -exact ${pythons_ports} ${p}] + set c [lreplace ${pythons_ports} ${i} ${i}] + eval [subst { + variant ${p} description "Build Boost.Python for Python ${v}" conflicts ${c} { + + # There is a conflict with python and debug support, so we should really change the 'variant' line above + # to end with "conflicts ${c} debug" above. However, we leave it enabled for those who want to try it. + # The issue has been reported to both the MacPorts team and the boost team, as per: + # and + + depends_lib-append port:${p} + configure.args-delete --without-libraries=python + configure.args-append --with-python=${prefix}/bin/python${v} + + patchfiles-append patch-tools-build-v2-tools-python.jam.diff \ + patch-tools-build-v2-tools-python-2.jam.diff + + # As per https://trac.macports.org/ticket/29097 and https://svn.boost.org/trac/boost/ticket/4994, + # apply https://svn.boost.org/trac/boost/changeset/71050 to boost-1.46.1 + # + patchfiles-append patch-libs-python-src-converter-builtin_converters.cpp + + post-patch { + reinplace s|@PREFIX@|${prefix}| ${worksrcpath}/tools/build/v2/tools/python.jam + } + + } + }] +} + +variant debug description {Builds debug versions of the libraries as well} { + build.args-delete variant=release + build.args-append variant=debug,release +} + +variant no_static description {Disable building static libraries} { + build.args-delete link=shared,static + build.args-append link=shared +} + +variant no_single description {Disable building single-threaded libraries} { + build.args-delete threading=single,multi + build.args-append threading=multi +} + +variant std0x conflicts openmpi universal description {Build with C++0x support} { + configure.compiler macports-gcc-4.6 + depends_lib-append port:gcc46 + + post-configure { + user_config_jam darwin "" ${configure.cxx} "-std=gnu++0x" + } + + patchfiles-append patch-tools-build-v2-tools-darwin.jam.diff +} + +variant openmpi description {Build Boost.MPI} { + + # There is a conflict with python and debug support, so we should really change the 'variant' line above + # to end with "conflicts debug" above. However, we leave it enabled for those who want to try it. + # The issue has been reported to both the MacPorts team and the boost team, as per: + # and + + depends_lib-append port:openmpi + configure.args-delete --without-libraries=mpi + + post-configure { + user_config_jam mpi ${prefix}/bin/openmpic++ "" \ + ${prefix}/bin/openmpirun + } + + if {![catch python_dir]} { + + patchfiles-append patch-libs-mpi-build-Jamfile.v2.diff + + post-destroot { + set site_packages [python_dir] + xinstall -d ${destroot}${site_packages}/boost + xinstall -m 644 ${worksrcpath}/libs/mpi/build/__init__.py \ + ${destroot}${site_packages}/boost + + set l ${site_packages}/boost/mpi.so + move ${destroot}${prefix}/lib/mpi.so ${destroot}${l} + system "install_name_tool -id ${l} ${destroot}${l}" + } + + } + +} + +variant regex_match_extra description \ + "Enable access to extended capture information of submatches in Boost.Regex" { + notes \ + You enabled +regex_match_extra variant, see the following page for an \ + exhaustive list of the consequences of this feature:\n\t \ + http://www.boost.org/doc/libs/${distver}/libs/regex/doc/html/boost_regex/ref/sub_match.html + + post-patch { + reinplace {/#define BOOST_REGEX_MATCH_EXTRA/s:^// ::} \ + ${worksrcpath}/boost/regex/user.hpp + } +} + +if {! ([variant_isset universal] || [variant_isset std0x])} { + # Honour 'build_arch', if not universal as per #28327 + if {[lsearch ${build_arch} ppc*] != -1} { + build.args-append architecture=power + if {${os.arch} != "powerpc"} { + build.args-append --disable-long-double + } + } else { + if {[lsearch ${build_arch} *86*] != -1} { + build.args-append architecture=x86 + } else { + error "Current value of 'build_arch' is not supported." + } + } + if {[lsearch ${build_arch} *64] != -1} { + build.args-append address-model=64 + } else { + build.args-append address-model=32 + } +} + +variant universal { + build.args-append pch=off + + if {[lsearch ${universal_archs} ppc*] != -1} { + if {[lsearch ${universal_archs} *86*] != -1} { + build.args-append architecture=combined + } else { + build.args-append architecture=power + } + + if {${os.arch} != "powerpc"} { + build.args-append --disable-long-double + } + } else { + build.args-append architecture=x86 + } + + if {[lsearch ${universal_archs} *64] != -1} { + if {[lsearch ${universal_archs} i386] != -1 || [lsearch ${universal_archs} ppc] != -1} { + build.args-append address-model=32_64 + } else { + build.args-append address-model=64 + } + } else { + build.args-append address-model=32 + } +} + +platform powerpc { + build.args-append --disable-long-double +} + +platform darwin 8 powerpc { + if {[variant_isset universal]} { + build.args-append macosx-version=10.4 + } +} + +livecheck.type regex +livecheck.url ${homepage} +livecheck.regex "Version (\\d+\\.\\d+\\.\\d+)" diff --git a/external/libbf/aux/macports/devel/boost/files/patch-bootstrap.sh.diff b/external/libbf/aux/macports/devel/boost/files/patch-bootstrap.sh.diff new file mode 100644 index 0000000..2bd5fe1 --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-bootstrap.sh.diff @@ -0,0 +1,11 @@ +--- bootstrap.sh.orig 2011-04-07 14:57:52.000000000 -0400 ++++ bootstrap.sh 2011-04-07 15:02:12.000000000 -0400 +@@ -279,7 +279,7 @@ + + if test "x$PYTHON_ROOT" = x; then + echo -n "Detecting Python root... " +- PYTHON_ROOT=`$PYTHON -c "import sys; print sys.prefix"` ++ PYTHON_ROOT=`$PYTHON -c "import sys; print (sys.prefix)"` + echo $PYTHON_ROOT + fi + fi diff --git a/external/libbf/aux/macports/devel/boost/files/patch-libs-mpi-build-Jamfile.v2.diff b/external/libbf/aux/macports/devel/boost/files/patch-libs-mpi-build-Jamfile.v2.diff new file mode 100644 index 0000000..2fbc640 --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-libs-mpi-build-Jamfile.v2.diff @@ -0,0 +1,26 @@ +--- libs/mpi/build/Jamfile.v2.orig 2010-10-18 02:22:09.000000000 -0400 ++++ libs/mpi/build/Jamfile.v2 2010-11-23 13:28:09.000000000 -0500 +@@ -68,6 +68,7 @@ + shared:BOOST_MPI_DYN_LINK=1 + : # Default build + shared ++ multi + : # Usage requirements + ../../serialization/build//boost_serialization + /mpi//mpi [ mpi.extra-requirements ] +@@ -90,6 +91,7 @@ + BOOST_MPI_PYTHON_SOURCE=1 + : # Default build + shared ++ multi + : # Usage requirements + /mpi//mpi [ mpi.extra-requirements ] + ; +@@ -118,6 +120,7 @@ + shared:BOOST_MPI_PYTHON_DYN_LINK=1 + shared:BOOST_PYTHON_DYN_LINK=1 + shared shared ++ multi + ; + } + } diff --git a/external/libbf/aux/macports/devel/boost/files/patch-libs-python-src-converter-builtin_converters.cpp b/external/libbf/aux/macports/devel/boost/files/patch-libs-python-src-converter-builtin_converters.cpp new file mode 100644 index 0000000..1356295 --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-libs-python-src-converter-builtin_converters.cpp @@ -0,0 +1,12 @@ +--- libs/python/src/converter/builtin_converters.cpp (revision 56305) ++++ libs/python/src/converter/builtin_converters.cpp (revision 71050) +@@ -432,5 +432,8 @@ + { + int err = PyUnicode_AsWideChar( +- (PyUnicodeObject *)intermediate ++#if PY_VERSION_HEX < 0x03020000 ++ (PyUnicodeObject *) ++#endif ++ intermediate + , &result[0] + , result.size()); diff --git a/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-darwin.jam.diff b/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-darwin.jam.diff new file mode 100644 index 0000000..cc7897d --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-darwin.jam.diff @@ -0,0 +1,11 @@ +--- tools/build/v2/tools/darwin.jam.old 2011-05-07 13:10:50.000000000 -0700 ++++ tools/build/v2/tools/darwin.jam 2011-05-07 13:10:55.000000000 -0700 +@@ -482,7 +482,7 @@ + flags darwin.compile OPTIONS shared : -dynamic ; + + # Misc options. +-flags darwin.compile OPTIONS : -no-cpp-precomp -gdwarf-2 ; ++flags darwin.compile OPTIONS : -gdwarf-2 ; + + # Add the framework names to use. + flags darwin.link FRAMEWORK ; diff --git a/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python-2.jam.diff b/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python-2.jam.diff new file mode 100644 index 0000000..a58a678 --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python-2.jam.diff @@ -0,0 +1,16 @@ +--- tools/build/v2/tools/python.jam.orig 2009-10-06 15:56:18.000000000 +0200 ++++ tools/build/v2/tools/python.jam 2009-10-06 15:53:48.000000000 +0200 +@@ -546,6 +546,13 @@ + libraries ?= $(default-library-path) ; + includes ?= $(default-include-path) ; + } ++ else if $(target-os) = darwin ++ { ++ includes ?= $(prefix)/Headers ; ++ ++ local lib = $(exec-prefix)/lib ; ++ libraries ?= $(lib)/python$(version)/config $(lib) ; ++ } + else + { + includes ?= $(prefix)/include/python$(version) ; diff --git a/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python.jam.diff b/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python.jam.diff new file mode 100644 index 0000000..cd8752d --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-tools-build-v2-tools-python.jam.diff @@ -0,0 +1,11 @@ +--- tools/build/v2/tools/python.jam.orig 2009-10-06 15:56:18.000000000 +0200 ++++ tools/build/v2/tools/python.jam 2009-10-06 15:53:48.000000000 +0200 +@@ -427,7 +427,7 @@ + version ?= $(.version-countdown) ; + + local prefix +- = [ GLOB /System/Library/Frameworks /Library/Frameworks ++ = [ GLOB @PREFIX@/Library/Frameworks + : Python.framework ] ; + + return $(prefix)/Versions/$(version)/bin/python ; diff --git a/external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.jam.diff b/external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.jam.diff new file mode 100644 index 0000000..70002f3 --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.jam.diff @@ -0,0 +1,11 @@ +--- tools/build/v2/engine/src/build.jam.orig 2011-03-21 22:43:37.000000000 -0500 ++++ tools/build/v2/engine/src/build.jam 2011-03-21 22:45:23.000000000 -0500 +@@ -194,7 +194,7 @@ + -I$(--python-include) -I$(--extra-include) + : -L$(--python-lib[1]) -l$(--python-lib[2]) ; + ## MacOSX Darwin, using GCC 2.9.x, 3.x +-toolset darwin cc : "-o " : -D ++toolset darwin %%CONFIGURE.CC%% : "-o " : -D + : + [ opt --release : -Wl,-x -O3 -finline-functions ] + [ opt --debug : -g -O0 -fno-inline -pg ] diff --git a/external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.sh.diff b/external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.sh.diff new file mode 100644 index 0000000..88afd3c --- /dev/null +++ b/external/libbf/aux/macports/devel/boost/files/patch-tools_build_v2_engine_src_build.sh.diff @@ -0,0 +1,11 @@ +--- tools/build/v2/engine/src/build.sh.orig 2011-03-21 22:16:09.000000000 -0500 ++++ tools/build/v2/engine/src/build.sh 2011-03-21 22:16:59.000000000 -0500 +@@ -133,7 +133,7 @@ + ;; + + darwin) +- BOOST_JAM_CC=cc ++ BOOST_JAM_CC=%%CONFIGURE.CC%% + ;; + + intel-linux) diff --git a/external/libbf/bf/all.hpp b/external/libbf/bf/all.hpp new file mode 100644 index 0000000..69376aa --- /dev/null +++ b/external/libbf/bf/all.hpp @@ -0,0 +1,10 @@ +#ifndef BF_ALL_HPP +#define BF_ALL_HPP + +#include "bf/bloom_filter/a2.hpp" +#include "bf/bloom_filter/basic.hpp" +#include "bf/bloom_filter/bitwise.hpp" +#include "bf/bloom_filter/counting.hpp" +#include "bf/bloom_filter/stable.hpp" + +#endif diff --git a/external/libbf/bf/bitvector.hpp b/external/libbf/bf/bitvector.hpp new file mode 100644 index 0000000..bf83c8f --- /dev/null +++ b/external/libbf/bf/bitvector.hpp @@ -0,0 +1,313 @@ +#ifndef BF_BITVECTOR_HPP +#define BF_BITVECTOR_HPP + +#include +#include +#include +#include + +namespace bf { + +/// A vector of bits. +class bitvector +{ + friend std::string to_string(bitvector const&, bool, size_t); + +public: + typedef size_t block_type; + typedef size_t size_type; + static size_type constexpr npos = static_cast(-1); + static block_type constexpr bits_per_block = + std::numeric_limits::digits; + +public: + /// An lvalue proxy for single bits. + class reference + { + friend class bitvector; + void operator&() = delete; + + /// Constructs a bit from a block. + /// @param block The block to look at. + /// @param i The bit position within *block*. + reference(block_type& block, block_type i); + + public: + reference& flip(); + operator bool() const; + bool operator~() const; + reference& operator=(bool x); + reference& operator=(reference const& other); + reference& operator|=(bool x); + reference& operator&=(bool x); + reference& operator^=(bool x); + reference& operator-=(bool x); + + private: + block_type& block_; + block_type const mask_; + }; + + /// Unlike the reference type, a const_reference does not need lvalue + /// semantics and can thus represent simply a boolean (bit) value. + typedef bool const_reference; + + /// Constructs an empty bit vector. + bitvector(); + + /// Constructs a bit vector of a given size. + /// @param size The number of bits. + /// @param value The value for each bit. + explicit bitvector(size_type size, bool value = false); + + /// Constructs a bit vector from a sequence of blocks. + template + bitvector(InputIterator first, InputIterator last) + { + bits_.insert(bits_.end(), first, last); + num_bits_ = bits_.size() * bits_per_block; + } + + /// Copy-constructs a bit vector. + /// @param other The bit vector to copy. + bitvector(bitvector const& other); + + /// Move-constructs a bit vector. + /// @param other The bit vector to move. + bitvector(bitvector&& other); + + /// Assigns another bit vector to this instance. + /// @param other The RHS of the assignment. + bitvector& operator=(bitvector other); + + /// Swaps two bit vectors. + friend void swap(bitvector& x, bitvector& y); + + // + // Bitwise operations + // + bitvector operator~() const; + bitvector operator<<(size_type n) const; + bitvector operator>>(size_type n) const; + bitvector& operator<<=(size_type n); + bitvector& operator>>=(size_type n); + bitvector& operator&=(bitvector const& other); + bitvector& operator|=(bitvector const& other); + bitvector& operator^=(bitvector const& other); + bitvector& operator-=(bitvector const& other); + friend bitvector operator&(bitvector const& x, bitvector const& y); + friend bitvector operator|(bitvector const& x, bitvector const& y); + friend bitvector operator^(bitvector const& x, bitvector const& y); + friend bitvector operator-(bitvector const& x, bitvector const& y); + + // + // Relational operators + // + friend bool operator==(bitvector const& x, bitvector const& y); + friend bool operator!=(bitvector const& x, bitvector const& y); + friend bool operator<(bitvector const& x, bitvector const& y); + + // + // Basic operations + // + /// Appends the bits in a sequence of values. + /// @tparam Iterator A forward iterator. + /// @param first An iterator pointing to the first element of the sequence. + /// @param last An iterator pointing to one past the last element of the + /// sequence. + template < + typename Iterator, + typename std::enable_if< + std::is_same< + typename std::iterator_traits::iterator_category, + std::forward_iterator_tag + >::value + >::type = 0 + > + void append(Iterator first, Iterator last) + { + if (first == last) + return; + + auto excess = extra_bits(); + auto delta = std::distance(first, last); + bits_.reserve(blocks() + delta); + if (excess == 0) + { + bits_.back() |= (*first << excess); + do + { + auto b = *first++ >> (bits_per_block - excess); + bits_.push_back(b | (first == last ? 0 : *first << excess)); + } while (first != last); + } + else + { + bits_.insert(bits_.end(), first, last); + } + + num_bits_ += bits_per_block * delta; + } + + /// Appends the bits in a given block. + /// @param block The block containing bits to append. + void append(block_type block); + + /// Appends a single bit to the end of the bit vector. + /// @param bit The value of the bit. + void push_back(bool bit); + + /// Clears all bits in the bitvector. + void clear() noexcept; + + /// Resizes the bit vector to a new number of bits. + /// @param n The new number of bits of the bit vector. + /// @param value The bit value of new values, if the vector expands. + void resize(size_type n, bool value = false); + + /// Sets a bit at a specific position to a given value. + /// @param i The bit position. + /// @param bit The value assigned to position *i*. + /// @return A reference to the bit vector instance. + bitvector& set(size_type i, bool bit = true); + + /// Sets all bits to 1. + /// @return A reference to the bit vector instance. + bitvector& set(); + + /// Resets a bit at a specific position, i.e., sets it to 0. + /// @param i The bit position. + /// @return A reference to the bit vector instance. + bitvector& reset(size_type i); + + /// Sets all bits to 0. + /// @return A reference to the bit vector instance. + bitvector& reset(); + + /// Toggles/flips a bit at a specific position. + /// @param i The bit position. + /// @return A reference to the bit vector instance. + bitvector& flip(size_type i); + + /// Computes the complement + /// @return A reference to the bit vector instance. + bitvector& flip(); + + /// Retrieves a single bit. + /// @param i The bit position. + /// @return A mutable reference to the bit at position *i*. + reference operator[](size_type i); + + /// Retrieves a single bit. + /// @param i The bit position. + /// @return A const-reference to the bit at position *i*. + const_reference operator[](size_type i) const; + + /// Counts the number of 1-bits in the bit vector. Also known as *population + /// count* or *Hamming weight*. + /// @return The number of bits set to 1. + size_type count() const; + + /// Retrieves the number of blocks of the underlying storage. + /// @param The number of blocks that represent `size()` bits. + size_type blocks() const; + + /// Retrieves the number of bits the bitvector consist of. + /// @return The length of the bit vector in bits. + size_type size() const; + + /// Checks whether the bit vector is empty. + /// @return `true` iff the bitvector has zero length. + bool empty() const; + + /// Finds the bit position of of the first 1-bit. + /// + /// @return The position of the first bit that equals to one or `npos` if no + /// such bit exists. + size_type find_first() const; + + /// Finds the next 1-bit from a given starting position. + /// + /// @param i The index where to start looking. + /// + /// @return The position of the first bit that equals to 1 after position + /// *i* or `npos` if no such bit exists. + size_type find_next(size_type i) const; + +private: + /// Computes the block index for a given bit position. + static size_type constexpr block_index(size_type i) + { + return i / bits_per_block; + } + + /// Computes the bit index within a given block for a given bit position. + static block_type constexpr bit_index(size_type i) + { + return i % bits_per_block; + } + + /// Computes the bitmask block to extract a bit a given bit position. + static block_type constexpr bit_mask(size_type i) + { + return block_type(1) << bit_index(i); + } + + /// Computes the number of blocks needed to represent a given number of + /// bits. + /// @param bits the number of bits. + /// @return The number of blocks to represent *bits* number of bits. + static size_type constexpr bits_to_blocks(size_type bits) + { + return bits / bits_per_block + + static_cast(bits % bits_per_block != 0); + } + + /// Computes the bit position first 1-bit in a given block. + /// @param block The block to inspect. + /// @return The bit position where *block* has its first bit set to 1. + static size_type lowest_bit(block_type block); + + /// Computes the number of excess/unused bits in the bit vector. + block_type extra_bits() const; + + // If the number of bits in the vector are not not a multiple of + // bitvector::bits_per_block, then the last block exhibits unused bits which + // this function resets. + void zero_unused_bits(); + + /// Looks for the first 1-bit starting at a given position. + /// + /// @param i The block index to start looking. + /// + /// @return The block index of the first 1-bit starting from *i* or + /// `bitvector::npos` if no 1-bit exists. + size_type find_from(size_type i) const; + + std::vector bits_; + size_type num_bits_; +}; + +/// Converts a bitvector to a `std::string`. +/// +/// @param b The bitvector to convert. +/// +/// @param msb_to_lsb The order of display. If `true`, display bits from MSB to +/// LSB and in the reverse order otherwise. +/// +/// @param all Indicates whether to include also the unused bits of the last +/// block if the number of `b.size()` is not a multiple of +/// `bitvector::bits_per_block`. +/// +/// @param cut_off Specifies a maximum size on the output. If 0, no cutting +/// occurs. +/// +/// @return A `std::string` representation of *b*. +std::string to_string(bitvector const& b, + bool msb_to_lsb = true, + bool all = false, + size_t cut_off = 0); + +} // namespace bf + +#endif diff --git a/external/libbf/bf/bloom_filter.hpp b/external/libbf/bf/bloom_filter.hpp new file mode 100644 index 0000000..9ec4392 --- /dev/null +++ b/external/libbf/bf/bloom_filter.hpp @@ -0,0 +1,52 @@ +#ifndef BF_BLOOM_FILTER_HPP +#define BF_BLOOM_FILTER_HPP + +#include + +namespace bf { + +/// The abstract Bloom filter interface. +class bloom_filter +{ + bloom_filter(bloom_filter const&) = delete; + bloom_filter& operator=(bloom_filter const&) = delete; + +public: + bloom_filter() = default; + virtual ~bloom_filter() = default; + + /// Adds an element to the Bloom filter. + /// @tparam T The type of the element to insert. + /// @param x An instance of type `T`. + template + void add(T const& x) + { + add(wrap(x)); + } + + /// Adds an element to the Bloom filter. + /// @param o A wrapped object. + virtual void add(object const& o) = 0; + + /// Retrieves the count of an element. + /// @tparam T The type of the element to query. + /// @param x An instance of type `T`. + /// @return A frequency estimate for *x*. + template + size_t lookup(T const& x) const + { + return lookup(wrap(x)); + } + + /// Retrieves the count of an element. + /// @param o A wrapped object. + /// @return A frequency estimate for *o*. + virtual size_t lookup(object const& o) const = 0; + + /// Removes all items from the Bloom filter. + virtual void clear() = 0; +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/bloom_filter/a2.hpp b/external/libbf/bf/bloom_filter/a2.hpp new file mode 100644 index 0000000..dd1ed70 --- /dev/null +++ b/external/libbf/bf/bloom_filter/a2.hpp @@ -0,0 +1,57 @@ +#ifndef BF_BLOOM_FILTER_A2_HPP +#define BF_BLOOM_FILTER_A2_HPP + +#include + +namespace bf { + +class a2_bloom_filter : public bloom_filter +{ +public: + /// Computes the optimal value number of hash functions based on a desired + /// false-positive rate. + /// + /// @param fp The desired false-positive rate. + /// + /// @return The optimal number of hash functions for *fp*. + static size_t k(double fp); + + /// @param fp The desired false-positive rate. + /// + /// @param cells The number of cells (bits) to use. + /// + /// @return The optimal capacity for *fp* and *m*. + static size_t capacity(double fp, size_t cells); + + /// Constructs an @f$A^2$@f Bloom filter. + /// + /// @param k The number of hash functions to use in each Bloom filter. + /// + /// @param cells The number cells to use for both Bloom filters, i.e., each + /// Bloom filter uses `cells / 2` cells. + /// + /// @param seed1 The initial seed for the first Bloom filter. + /// + /// @param seed2 The initial seed for the second Bloom filter. + /// + /// @pre `cells % 2 == 0` + a2_bloom_filter(size_t k, size_t cells, size_t capacity, + size_t seed1 = 0, size_t seed2 = 0); + + using bloom_filter::add; + using bloom_filter::lookup; + + virtual void add(object const& o) override; + virtual size_t lookup(object const& o) const override; + virtual void clear() override; + +private: + basic_bloom_filter first_; + basic_bloom_filter second_; + size_t items_ = 0; ///< Number of items in the active Bloom filter. + size_t capacity_; ///< Maximum number of items in the active Bloom filter. +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/bloom_filter/basic.hpp b/external/libbf/bf/bloom_filter/basic.hpp new file mode 100644 index 0000000..ee49e96 --- /dev/null +++ b/external/libbf/bf/bloom_filter/basic.hpp @@ -0,0 +1,104 @@ +#ifndef BF_BLOOM_FILTER_BASIC_HPP +#define BF_BLOOM_FILTER_BASIC_HPP + +#include +#include +#include +#include + +namespace bf { + +/// The basic Bloom filter. +/// +/// @note This Bloom filter does not use partitioning because it results in +/// slightly worse performance because partitioned Bloom filters tend to have +/// more 1s than non-partitioned filters. +class basic_bloom_filter : public bloom_filter +{ +public: + /// Computes the number of cells based on a false-positive rate and capacity. + /// + /// @param fp The desired false-positive rate + /// + /// @param capacity The maximum number of items. + /// + /// @return The number of cells to use that guarantee *fp* for *capacity* + /// elements. + static size_t m(double fp, size_t capacity); + + /// Computes @f$k^*@f$, the optimal number of hash functions for a given + /// Bloom filter size (in terms of cells) and capacity. + /// + /// @param cells The number of cells in the Bloom filter (aka. *m*) + /// + /// @param capacity The maximum number of elements that can guarantee *fp*. + /// + /// @return The optimal number of hash functions for *cells* and *capacity*. + static size_t k(size_t cells, size_t capacity); + + /// Constructs a basic Bloom filter. + /// @param hasher The hasher to use. + /// @param cells The number of cells in the bit vector. + /// @param partition Whether to partition the bit vector per hash function. + basic_bloom_filter(hasher h, size_t cells, bool partition = false); + + /// Constructs a basic Bloom filter by given a desired false-positive + /// probability and an expected number of elements. The implementation + /// computes the optimal number of hash function and required space. + /// + /// @param fp The desired false-positive probability. + /// + /// @param capacity The desired false-positive probability. + /// + /// @param seed The initial seed used to construct the hash functions. + /// + /// @param double_hashing Flag indicating whether to use default or double + /// hashing. + /// + /// @param partition Whether to partition the bit vector per hash function. + basic_bloom_filter(double fp, size_t capacity, size_t seed = 0, + bool double_hashing = true, bool partition = true); + + /// Constructs a basic Bloom filter given a hasher and a bitvector. + /// + /// @param hasher The hasher to use. + /// @param bitvector the underlying bitvector of the bf. + basic_bloom_filter(hasher h, bitvector b, bool partition = true); + + basic_bloom_filter(basic_bloom_filter&&); + + using bloom_filter::add; + using bloom_filter::lookup; + + virtual void add(object const& o) override; + virtual size_t lookup(object const& o) const override; + virtual void clear() override; + + /// Removes an object from the Bloom filter. + /// May introduce false negatives because the bitvector indices of the object + /// to remove may be shared with other objects. + /// + /// @param o The object to remove. + void remove(object const& o); + + /// Swaps two basic Bloom filters. + /// @param other The other basic Bloom filter. + void swap(basic_bloom_filter& other); + + /// Returns the underlying storage of the Bloom filter. + bitvector const& storage() const; + + /// Returns the hasher of the Bloom filter. + hasher const& hasher_function() const; + + bool partition() const; + +private: + hasher hasher_; + bitvector bits_; + bool partition_; +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/bloom_filter/bitwise.hpp b/external/libbf/bf/bloom_filter/bitwise.hpp new file mode 100644 index 0000000..7b22a69 --- /dev/null +++ b/external/libbf/bf/bloom_filter/bitwise.hpp @@ -0,0 +1,38 @@ +#ifndef BF_BLOOM_FILTER_BITWISE_HPP +#define BF_BLOOM_FILTER_BITWISE_HPP + +#include + +namespace bf { + +/// The bitwise Bloom filter. +class bitwise_bloom_filter : public bloom_filter +{ +public: + /// Constructs a bitwise Bloom filter. + /// @param k The number of hash functions in the first level. + /// @param cells0 The number of cells in the the first level. + /// @param seed0 The seed for the first level. + bitwise_bloom_filter(size_t k, size_t cells, size_t seed = 0); + + using bloom_filter::add; + using bloom_filter::lookup; + + virtual void add(object const& o) override; + virtual size_t lookup(object const& o) const override; + virtual void clear() override; + +private: + /// Appends a new level. + /// @post `levels_.size() += 1` + void grow(); + + size_t k_; + size_t cells_; + size_t seed_; + std::vector levels_; +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/bloom_filter/counting.hpp b/external/libbf/bf/bloom_filter/counting.hpp new file mode 100644 index 0000000..4324f3b --- /dev/null +++ b/external/libbf/bf/bloom_filter/counting.hpp @@ -0,0 +1,135 @@ +#ifndef BF_BLOOM_FILTER_COUNTING_HPP +#define BF_BLOOM_FILTER_COUNTING_HPP + +#include +#include +#include + +namespace bf { + +class spectral_mi_bloom_filter; +class spectral_rm_bloom_filter; + +/// The counting Bloom filter. +class counting_bloom_filter : public bloom_filter +{ + friend spectral_mi_bloom_filter; + friend spectral_rm_bloom_filter; + +public: + /// Constructs a counting Bloom filter. + /// @param h The hasher. + /// @param cells The number of cells. + /// @param width The number of bits per cell. + /// @param partition Whether to partition the bit vector per hash function. + counting_bloom_filter(hasher h, size_t cells, size_t width, + bool partition = false); + + /// Move-constructs a counting Bloom filter. + counting_bloom_filter(counting_bloom_filter&&) = default; + + using bloom_filter::add; + using bloom_filter::lookup; + + virtual void add(object const& o) override; + virtual size_t lookup(object const& o) const override; + virtual void clear() override; + + /// Removes an element. + /// @param o The object whose cells to decrement by 1. + void remove(object const& o); + + template + void remove(T const& x) + { + remove(wrap(x)); + } + +protected: + /// Maps an object to the indices in the underlying counter vector. + /// @param o The object to map. + /// @return The indices corresponding to the digests of *o*. + std::vector find_indices(object const& o) const; + + /// Finds the minimum value in a list of arbitrary indices. + /// @param indices The indices over which to compute the minimum. + /// @return The minimum counter value over *indices*. + size_t find_minimum(std::vector const& indices) const; + + /// Finds one or more minimum indices for a list of arbitrary indices. + /// @param indices The indices over which to compute the minimum. + /// @return The indices corresponding to the minima in the counter vector. + std::vector find_minima(std::vector const& indices) const; + + /// Increments a given set of indices in the underlying counter vector. + /// @param indices The indices to increment. + /// @return `true` iff no counter overflowed. + bool increment(std::vector const& indices, size_t value = 1); + + /// Decrements a given set of indices in the underlying counter vector. + /// @param indices The indices to decrement. + /// @return `true` iff no counter underflowed. + bool decrement(std::vector const& indices, size_t value = 1); + + /// Retrieves the counter for given cell index. + /// @param index The index of the counter vector. + /// @pre `index < cells.size()` + size_t count(size_t index) const; + + hasher hasher_; + counter_vector cells_; + bool partition_; +}; + +/// A spectral Bloom filter with minimum increase (MI) policy. +class spectral_mi_bloom_filter : public counting_bloom_filter +{ +public: + /// Constructs a spectral MI Bloom filter. + /// @param h The hasher. + /// @param cells The number of cells. + /// @param width The number of bits per cell. + /// @param partition Whether to partition the bit vector per hash function. + spectral_mi_bloom_filter(hasher h, size_t cells, size_t width, + bool partition = false); + + using bloom_filter::add; + using bloom_filter::lookup; + using counting_bloom_filter::remove; + virtual void add(object const& o) override; +}; + +/// A spectral Bloom filter with recurring minimum (RM) policy. +class spectral_rm_bloom_filter : public bloom_filter +{ +public: + /// Constructs a spectral RM Bloom filter. + /// @param h1 The first hasher. + /// @param cells1 The number of cells in the first Bloom filter. + /// @param width1 The number of bits per cell in the first Bloom filter. + /// @param h2 The second hasher. + /// @param cells2 The number of cells in the second Bloom filter. + /// @param width2 The number of bits per cell in the second Bloom filter. + /// @param partition Whether to partition the bit vector per hash function. + spectral_rm_bloom_filter(hasher h1, size_t cells1, size_t width1, + hasher h2, size_t cells2, size_t width2, + bool partition = false); + + using bloom_filter::add; + using bloom_filter::lookup; + virtual void add(object const& o) override; + virtual size_t lookup(object const& o) const override; + virtual void clear() override; + + /// Removes an element. + /// @param o The object whose cells to decrement by 1. + void remove(object const& o); + +private: + counting_bloom_filter first_; + counting_bloom_filter second_; +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/bloom_filter/stable.hpp b/external/libbf/bf/bloom_filter/stable.hpp new file mode 100644 index 0000000..f67760b --- /dev/null +++ b/external/libbf/bf/bloom_filter/stable.hpp @@ -0,0 +1,38 @@ +#ifndef BF_BLOOM_FILTER_STABLE_HPP +#define BF_BLOOM_FILTER_STABLE_HPP + +#include +#include + +namespace bf { + +/// A stable Bloom filter. +class stable_bloom_filter : public counting_bloom_filter +{ +public: + /// Constructs a stable Bloom filter. + /// @param h The hasher. + /// @param cells The number of cells. + /// @param width The number of bits per cell. + /// @param d The number of cells to decrement before adding an element. + /// @pre `cells <= d` + stable_bloom_filter(hasher h, size_t cells, size_t width, size_t d); + + /// Adds an item to the stable Bloom filter. + /// This invovles first decrementing *k* positions uniformly at random and + /// then setting the counter of *o* to all 1s. + /// @param o The object to add. + virtual void add(object const& o) override; + + using bloom_filter::add; + using bloom_filter::lookup; + +private: + size_t d_; + std::mt19937 generator_; + std::uniform_int_distribution<> unif_; +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/counter_vector.hpp b/external/libbf/bf/counter_vector.hpp new file mode 100644 index 0000000..5d22a46 --- /dev/null +++ b/external/libbf/bf/counter_vector.hpp @@ -0,0 +1,103 @@ +#ifndef BF_COUNTER_VECTOR_HPP +#define BF_COUNTER_VECTOR_HPP + +#include +#include +#include + +namespace bf { + +/// The *fixed width* storage policy implements a bit vector where each +/// cell represents a counter having a fixed number of bits. +class counter_vector +{ + /// Generates a string representation of a counter vector. + /// The arguments have the same meaning as in bf::bitvector. + friend std::string to_string(counter_vector const& v, bool all = false, + size_t cut_off = 0) + { + return to_string(v.bits_, false, all, cut_off); + } + + friend counter_vector operator|(counter_vector const& x, + counter_vector const& y); + +public: + /// Construct a counter vector of size @f$O(mw)@f$ where *m is the number of + /// cells and *w the number of bits per cell. + /// + /// @param cells The number of cells. + /// + /// @param width The number of bits per cell. + /// + /// @pre `cells > 0 && width > 0` + counter_vector(size_t cells, size_t width); + + /// Merges this counter vector with another counter vector. + /// @param other The other counter vector. + /// @return A reference to `*this`. + /// @pre `size() == other.size() && width() == other.width()` + counter_vector& operator|=(counter_vector const& other); + + /// Increments a cell counter by a given value. If the value is larger + /// than or equal to max(), all bits are set to 1. + /// + /// @param cell The cell index. + /// + /// @param value The value that is added to the current cell value. + /// + /// @return `true` if the increment succeeded, `false` if all bits in + /// the cell were already 1. + /// + /// @pre `cell < size()` + bool increment(size_t cell, size_t value = 1); + + /// Decrements a cell counter. + /// + /// @param cell The cell index. + /// + /// @return `true` if decrementing succeeded, `false` if all bits in the + /// cell were already 0. + /// + /// @pre `cell < size()` + bool decrement(size_t cell, size_t value = 1); + + /// Retrieves the counter of a cell. + /// + /// @param cell The cell index. + /// + /// @return The counter associated with *cell*. + /// + /// @pre `cell < size()` + size_t count(size_t cell) const; + + /// Sets a cell to a given value. + /// @param cell The cell whose value changes. + /// @param value The new value of the cell. + /// @pre `cell < size()` + void set(size_t cell, size_t value); + + /// Sets all counter values to 0. + void clear(); + + /// Retrieves the number of cells. + /// @return The number of cells in the counter vector. + size_t size() const; + + /// Retrieves the maximum possible counter value. + /// @return The maximum counter value constrained by the cell width. + size_t max() const; + + /// Retrieves the counter width. + /// @return The number of bits per cell. + size_t width() const; + +private: + bitvector bits_; + size_t width_; +}; + + +} // namespace bf + +#endif diff --git a/external/libbf/bf/h3.hpp b/external/libbf/bf/h3.hpp new file mode 100644 index 0000000..d524233 --- /dev/null +++ b/external/libbf/bf/h3.hpp @@ -0,0 +1,68 @@ +#ifndef BF_H3_HPP +#define BF_H3_HPP + +#include +#include + +namespace bf { + +/// An implementation of the H3 hash function family. +template +class h3 +{ + static size_t const bits_per_byte = + std::numeric_limits::digits; + +public: + constexpr static size_t byte_range = + std::numeric_limits::max() + 1; + + h3(T seed = 0) + { + T bits[N * bits_per_byte]; + std::minstd_rand0 prng(seed); + for (size_t bit = 0; bit < N * bits_per_byte; ++bit) + { + bits[bit] = 0; + for (size_t i = 0; i < sizeof(T)/2; i++) + bits[bit] = (bits[bit] << 16) | (prng() & 0xFFFF); + } + + for (size_t byte = 0; byte < N; ++byte) + for (size_t val = 0; val < byte_range; ++val) + { + bytes_[byte][val] = 0; + for (size_t bit = 0; bit < bits_per_byte; ++bit) + if (val & (1 << bit)) + bytes_[byte][val] ^= bits[byte * bits_per_byte + bit]; + } + } + + T operator()(void const* data, size_t size, size_t offset = 0) const + { + auto *p = static_cast(data); + T result = 0; + // Duff's Device. + auto n = (size + 7) / 8; + switch (size % 8) + { + case 0: do { result ^= bytes_[offset++][*p++]; + case 7: result ^= bytes_[offset++][*p++]; + case 6: result ^= bytes_[offset++][*p++]; + case 5: result ^= bytes_[offset++][*p++]; + case 4: result ^= bytes_[offset++][*p++]; + case 3: result ^= bytes_[offset++][*p++]; + case 2: result ^= bytes_[offset++][*p++]; + case 1: result ^= bytes_[offset++][*p++]; + } while ( --n > 0 ); + } + return result; + } + +private: + T bytes_[N][byte_range]; +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/hash.hpp b/external/libbf/bf/hash.hpp new file mode 100644 index 0000000..ec0db51 --- /dev/null +++ b/external/libbf/bf/hash.hpp @@ -0,0 +1,76 @@ +#ifndef BF_HASH_POLICY_HPP +#define BF_HASH_POLICY_HPP + +#include +#include +#include + +namespace bf { + +/// The hash digest type. +typedef size_t digest; + +/// The hash function type. +typedef std::function hash_function; + +/// A function that hashes an object *k* times. +typedef std::function(object const&)> hasher; + +class default_hash_function +{ +public: + constexpr static size_t max_obj_size = 36; + + default_hash_function(size_t seed); + + size_t operator()(object const& o) const; + +private: + h3 h3_; +}; + +/// A hasher which hashes an object *k* times. +class default_hasher +{ +public: + default_hasher(std::vector fns); + + std::vector operator()(object const& o) const; + +private: + std::vector fns_; +}; + +/// A hasher which hashes an object two times and generates *k* digests through +/// a linear combinations of the two digests. +class double_hasher +{ +public: + double_hasher(size_t k, hash_function h1, hash_function h2); + + std::vector operator()(object const& o) const; + +private: + size_t k_; + hash_function h1_; + hash_function h2_; +}; + +/// Creates a default or double hasher with the default hash function, using +/// seeds from a linear congruential PRNG. +/// +/// @param k The number of hash functions to use. +/// +/// @param seed The initial seed of the PRNG. +/// +/// @param double_hashing If `true`, the function constructs a ::double_hasher +/// and a ::default_hasher otherwise. +/// +/// @return A ::hasher with the *k* hash functions. +/// +/// @pre `k > 0` +hasher make_hasher(size_t k, size_t seed = 0, bool double_hashing = false); + +} // namespace bf + +#endif diff --git a/external/libbf/bf/object.hpp b/external/libbf/bf/object.hpp new file mode 100644 index 0000000..3dbb712 --- /dev/null +++ b/external/libbf/bf/object.hpp @@ -0,0 +1,34 @@ +#ifndef BF_OBJECT_HPP +#define BF_OBJECT_HPP + +#include + +namespace bf { + +/// Wraps sequential data to be used in hashing. +class object +{ +public: + object(void const* data, size_t size) + : data_(data), size_(size) + { + } + + void const* data() const + { + return data_; + } + + size_t size() const + { + return size_; + } + +private: + void const* data_ = nullptr; + size_t size_ = 0; +}; + +} // namespace bf + +#endif diff --git a/external/libbf/bf/wrap.hpp b/external/libbf/bf/wrap.hpp new file mode 100644 index 0000000..af7e1a7 --- /dev/null +++ b/external/libbf/bf/wrap.hpp @@ -0,0 +1,46 @@ +#ifndef BF_WRAP_HPP +#define BF_WRAP_HPP + +#include +#include +#include +#include + +namespace bf { + +template < + typename T, + typename = typename std::enable_if::value>::type +> +object wrap(T const& x) +{ + return {&x, sizeof(T)}; +} + +template < + typename T, + size_t N, + typename = typename std::enable_if::value>::type +> +object wrap(T const (&str)[N]) +{ + return {&str, N * sizeof(T)}; +} + +template < + typename T, + typename = typename std::enable_if::value>::type +> +object wrap(std::vector const& s) +{ + return {s.data(), s.size()}; +} + +inline object wrap(std::string const& str) +{ + return {str.data(), str.size()}; +} + +} // namespace bf + +#endif diff --git a/external/libbf/cmake/cmake_uninstall.cmake.in b/external/libbf/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000..bed4da6 --- /dev/null +++ b/external/libbf/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,35 @@ +function(uninstall_manifest manifestPath) + file(READ "${manifestPath}" files) + string(REGEX REPLACE "\n" ";" files "${files}") + foreach (file ${files}) + set(fileName $ENV{DESTDIR}${file}) + + if (EXISTS "${fileName}" OR IS_SYMLINK "${fileName}") + message(STATUS "Uninstalling: ${fileName}") + + execute_process( + COMMAND "@CMAKE_COMMAND@" -E remove "${fileName}" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval + ) + + if (NOT ${rm_retval} EQUAL 0) + message(FATAL_ERROR "Problem when removing: ${fileName}") + endif () + else () + message(STATUS "Does not exist: ${fileName}") + endif () + + endforeach () +endfunction(uninstall_manifest) + +file(GLOB install_manifests @CMAKE_CURRENT_BINARY_DIR@/install_manifest*.txt) + +if (install_manifests) + foreach (manifest ${install_manifests}) + uninstall_manifest(${manifest}) + endforeach () +else () + message(FATAL_ERROR "Cannot find any install manifests in: " + "\"@CMAKE_CURRENT_BINARY_DIR@/install_manifest*.txt\"") +endif () diff --git a/external/libbf/configure b/external/libbf/configure new file mode 100755 index 0000000..e898ac5 --- /dev/null +++ b/external/libbf/configure @@ -0,0 +1,146 @@ +#!/bin/sh +# Convenience wrapper for easily viewing/setting options that +# the project's CMake scripts will recognize + +command="$0 $*" +sourcedir="$( cd "$( dirname "$0" )" && pwd )" +type cmake > /dev/null 2>&1 || { + echo "\ +This package requires CMake, please install it first, then you may +use this configure script to access CMake equivalent functionality.\ +" >&2; + exit 1; +} + +usage="\ +Usage: $0 [OPTION]... [VAR=VALUE]... + + Build Options: + --builddir=DIR place build files in directory [build] + --generator=GENERATOR CMake generator to use (see cmake --help) + + Installation Directories: + --prefix=PREFIX installation directory [/usr/local] + + Optional Features: + --enable-debug compile in debugging mode + --enable-perftools use Google perftools + + Required Packages in Non-Standard Locations: + --with-boost=PATH path to Boost install root + + Influential Environment Variables (only on first invocation + per build directory): + CXX C++ compiler command + CXXFLAGS C++ compiler flags +" + +# Appends a CMake cache entry definition to the CMakeCacheEntries variable. +# $1 is the cache entry variable name. +# $2 is the cache entry variable type. +# $3 is the cache entry variable value. +append_cache_entry () { + CMakeCacheEntries="$CMakeCacheEntries -D $1:$2=$3" +} + +# Set defaults +builddir=build +CMakeCacheEntries="" +append_cache_entry CMAKE_INSTALL_PREFIX PATH /usr/local +append_cache_entry ENABLE_DEBUG BOOL false + +# parse arguments +while [ $# -ne 0 ]; do + case "$1" in + -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + case "$1" in + --help|-h) + echo "${usage}" 1>&2 + exit 1 + ;; + --builddir=*) + builddir=$optarg + ;; + --generator=*) + CMakeGenerator="$optarg" + ;; + --prefix=*) + append_cache_entry CMAKE_INSTALL_PREFIX PATH $optarg + ;; + --enable-debug) + append_cache_entry ENABLE_DEBUG BOOL true + ;; + --with-boost=*) + append_cache_entry BOOST_ROOT PATH $optarg + ;; + *) + echo "Invalid option '$1'. Try $0 --help to see available options." + exit 1 + ;; + esac + shift +done + +if [ -d $builddir ]; then + if [ -f $builddir/CMakeCache.txt ]; then + rm -f $builddir/CMakeCache.txt + fi +else + mkdir -p $builddir +fi + +echo "Build Directory : $builddir" +echo "Source Directory: $sourcedir" +cd $builddir + +if [ -n "$CMakeGenerator" ]; then + cmake -G "$CMakeGenerator" $CMakeCacheEntries $sourcedir +else + cmake $CMakeCacheEntries $sourcedir +fi + +echo "# This is the command used to configure this build" > config.status +if [ -n "$CC" ]; then + printf "CC=%s" $CC >> config.status + printf ' ' >> config.status +fi +if [ -n "$CXX" ]; then + printf "CXX=%s" $CXX >> config.status + printf ' ' >> config.status +fi +echo $command >> config.status +chmod u+x config.status + +cd .. + +printf "DIRS := %s\n\n" $builddir > $sourcedir/Makefile +makefile=$(cat <<'EOT' +all: + @for i in $(DIRS); do $(MAKE) -C $$i $@; done + +doc: + $(MAKE) -C $@ + +test: + @for i in $(DIRS); do $(MAKE) -C $$i $@; done + +install: + @for i in $(DIRS); do $(MAKE) -C $$i $@; done + +uninstall: + @for i in $(DIRS); do $(MAKE) -C $$i $@; done + +clean: + @for i in $(DIRS); do $(MAKE) -C $$i $@; done + +distclean: + rm -rf $(DIRS) Makefile + +.PHONY: all doc test install uninstall clean distclean +EOT +) + +echo "$makefile" >> $sourcedir/Makefile diff --git a/external/libbf/doc/Doxyfile b/external/libbf/doc/Doxyfile new file mode 100644 index 0000000..408fdff --- /dev/null +++ b/external/libbf/doc/Doxyfile @@ -0,0 +1,1870 @@ +# Doxyfile 1.8.3.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = libbf + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.1 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. Note that you specify absolute paths here, but also +# relative paths, which will be relative from the directory where doxygen is +# started. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 1 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, +# and language is one of the parsers supported by doxygen: IDL, Java, +# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, +# C++. For instance to make doxygen treat .inc files as Fortran files (default +# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note +# that for custom extensions you also need to set FILE_PATTERNS otherwise the +# files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented classes, +# or namespaces to their corresponding documentation. Such a link can be +# prevented in individual cases by by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES (the +# default) will make doxygen replace the get and set methods by a property in +# the documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = YES + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if section-label ... \endif +# and \cond section-label ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. Do not use +# file names with spaces, bibtex cannot handle them. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../doc \ + ../src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = figs + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MD_FILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page (index.html). +# This can be useful if you have a project on for instance GitHub and want reuse +# the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = gh-pages/api + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If left blank doxygen will +# generate a default style sheet. Note that it is recommended to use +# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this +# tag will in the future become obsolete. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional +# user-defined cascading style sheet that is included after the standard +# style sheets created by doxygen. Using this option one can overrule +# certain style aspects. This is preferred over using HTML_STYLESHEET +# since it does not replace the standard style sheet and is therefor more +# robust against future updates. Doxygen will copy the style sheet file to +# the output directory. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely +# identify the documentation publisher. This should be a reverse domain-name +# style string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = YES + +# When MathJax is enabled you can set the default output format to be used for +# thA MathJax output. Supported types are HTML-CSS, NativeMML (i.e. MathML) and +# SVG. The default value is HTML-CSS, which is slower, but has the best +# compatibility. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. +# There are two flavours of web server based search depending on the +# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for +# searching and an index file used by the script. When EXTERNAL_SEARCH is +# enabled the indexing and searching needs to be provided by external tools. +# See the manual for details. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain +# the search results. Doxygen ships with an example indexer (doxyindexer) and +# search engine (doxysearch.cgi) which are based on the open source search engine +# library Xapian. See the manual for configuration details. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will returned the search results when EXTERNAL_SEARCH is enabled. +# Doxygen ships with an example search engine (doxysearch) which is based on +# the open source search engine library Xapian. See the manual for configuration +# details. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH AND EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id +# of to a relative location where the documentation can be found. +# The format is: EXTRA_SEARCH_MAPPINGS = id1=loc1 id2=loc2 ... + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = libbf + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = YES + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/external/libbf/doc/documentation.dox b/external/libbf/doc/documentation.dox new file mode 100644 index 0000000..8c19548 --- /dev/null +++ b/external/libbf/doc/documentation.dox @@ -0,0 +1,14 @@ +/** + +\mainpage libbf + +**libbf** is a C++11 Bloom filter library. + +\section Usage + +Please refer to [the project page](https://github.com/mavam/libbf) for usage +examples. + +This manual only documents the API. + +*/ diff --git a/external/libbf/doc/figs/architecture.png b/external/libbf/doc/figs/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..8bea2ccc4cd7012a31dc6721e4a11d8e4003f672 GIT binary patch literal 54755 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

RddFW@tl+{O6< zyy+0FTb_aGw!^2i6@Ois$$~4z&ty@oKHPsBh1wcxGO-c!G1k8D-T0xmN(oH8*8*vI zM>RNv3zrRyjEq|NZTO{Z08VT=WdZt>kEQzG3BAZWOTIE4?y8razvl<=Zqop|6oPYz zKmzrT_~MTxi4xY*-cSE3T~5K0j)wm7W21U|^VK zFO4n)59@5`3NQ`HcOvi`dy24{>6?zU)XvRiWN0J4uR6kC)cza6?sX#GOZLED1Qj!H z*>)+Etba%V6cLi3GT7;efJr#t{T{iM{;|A^Cn#hG2M3>iN)egwr0CQn1_fwG3gGPg z;T4rso6V-^DKQ+Izq@r4?U3w3tYiaZ(xSUb2Tj=Nj|8OgczLeo;2aI+vWLm z|0~7a5sLOV+Y8b3$%uk!(~xje~d(2wx9ZH zJY9tL0_}0=zRDaO8BuWCC3e}0c~y{=nPc{S4vqUR<5y!zc{9@b`nqzJZh#Tfsh@%i z!)XtOtao}W*g)+9Szt+OGuwhc`24lautwh-9J&__s+vpb?-pfuHR@W@4ZutrX(Vp1 z>Ubj8+hvb74|7vl-<|i$wC_?!BtfYd1UV>BVp4UM_qd40jICd-%ONf`=ey@dsK1m1ux{b+S%Al^q$-{yYD1U%ov2L{ADXfidC zC>_tQzGL8T?SX!&a#RwDJ9;QaGP4n~>&@Q?0;|@lTf{p`SbG_5&`;2QQaJNgYt50# zm<0EcuZ)by>aR$BJUg^9LqBJO@FfJZX9X50x zG4Gi~B4ZEff}e%Sc5`{<V+E0g5^a zs(s874ix0l2md;4oG#%Vo_zT5p_sqpb5wKSK!9lP z=3wRxu#kDDSfTmOMKK9N1j?+#K{frhCV(&nHnZWtu?!O^4o;Q%T&0tf__SQMvFdVu zd%`%!#eo_!3aoR-yOQD;HN2@wno*rf!(3)}zEqO+>Dm_ny26+z_|pcT5fgv{ED6Gk zl@vI^vF2Mj2m-Y)Vu1f;A-RW%*GD$s^+%uY3F`N<+cRubRASd?2epQNwF6-7){RrE zsT1#=tagwUiKH^M_&{P$~Dw)2#eQSbxpRnw!oEJ z2Y5!@LcE^+UW|rG!VYi@CiOM?=_QZ!0@i|F!~+!y&hx*JTc&nI`+WFmJ1$D0;oAdw zd)Nx#snsegBaK`lYF5)D`tR)2i3bR|9p9QuhIu79@;agO;A#_zu~AZgK|t`?fCWvX z^28oJUER>kkNsNa=dn-u_Mn*V?g#x} z`nJp1xlp?o+kPs9h4I&EunAdS&>#@`=di_y=mI)Ou(ua4r4VJlF^ZA;#o*fX*T3D z4^99T+6zH?N&&spoRnn+&2x7s&}+vep~VqbG1weAKRD;qTVu~-#^-RLP=Ru)k_g~9 zJkTWW1BB6SAuQ>8SoQanz>tD9Yc|Yj9`ry}exaT}X$#k+q=v!>;DQ6kpD0pDNqD6j|5axq*(wONEsDe`OK;D>0;u**v+!JH(87e{g&1xoWC>}dsNQzb2r zI#1Y|SpToC)d8q@sLDPunSz~gP}dP>!0$qDC|-~pkxkd-AF5UHivdOg^ZOGia&!bJ z4-8yZQ%q<2H<08*)`1Lywpr;Ib#toKg_mb;o`YWTh){{F5rNM-P4{#@JX816*)aCl z%nRVZ<&^K05Pe>~2MwXtg~XYq%YquShz{M}&@~SMZ~*hrX|@`BsCvGEBGo4AB)0Lo zU5C&2Ep+;Ll$~0(3Eme{CpmC=?yA?)#`s$oeG>JB)L%$o=V9m|msMls^!}i2{#gZh z_7s%9y7L_&%SHWY1#M$vGxT5wLcL*bv!wGD052zOa`UPn+5CEM8-sd^>H{{iPo`UG z0R|!AQMUSW8qJN+#m=ZaALDXbb)I4dEXzyi;pw9Ib>HP|k_2*FmGDLj5%`TLjj<`y z%lV?BLiZvP@_VZP5tW2Hs6wr=lasflA-H-KHKr(^Sk4-s~>s zS69K{v)T35c(h9N&2<6GnJ^Hmion6PE837%h#|Uz!X0yqGNmTzn^N zBWy&llN>e>g{0hlp2d8AoP6JnNp8gsD&f`v4z#RP#~aQMr2|ZGwO17M*iis*l-pd2d%p zqX8HXG!q*@87cbW4NC4$U$2UwXUVY;G*#lhk)XFqhzz`5pwXoq#Yu;TGhM zj-T&0RdF)nL04vVS*m+R$YhHY*rW`4~y)VV57ha6G@T=cvJd-KUnu{qu2Sk zZjJ`!;nHbE0rFuQup$Rj+o$mB;0f_h=lsBP-j8DMsNBTU(z>sqQ3YmmoiHA3uT52d zRP)fT26$^>+yYYH=4hUA``uzD(mj~;;rpaZqgp$V8A@q@1}%fO2F3VL*|d{t_A~V4 zO^h?-UCI1;Mw;F3*q!eH89CZGOB}u9d7BfY(xn-wl0FqOuf%mRbMk|<AwAov95!Ut)zw3Z(dx9Y?!ldaW{XZsEBR3FE^b~Z z+yH}sqt+zk?6#~8GGTFOctNK@c^rt)*pc4iQ4Yd;0)qKmdIm*2Hj0;j@C`})I zyAJeLA&_*r1+|FT8en19hd_vR0T_4~!*dz^(G6yc@)?YA+518m^98{<{#8fgMK}rA zGP|9hqk)dF7i^iwrsVHzq*(~kFOl&5l^?$qM}=H&;6a14I+>egtKBB(9-LU;h?lpz z1f^?LVc~nB@VvhTq)ZhsTKA$aj;HrVJPqSMJOVu9Ooi5RQ)mTHTW;^i!Nb0O{TfRB z@VmVi)r;wJ-PX#qDvlnc1hWUgM{uH0mgEsALyI)qnq~4Y z!{V}^!{y0HfTi|2!c>vKl#7)ro({6Gy?o5HpR0fEl>0#&Bv88IC!XgmEVOa zl3pGqWCN1IO!Z!C8IO>=U$s&j>P2t^^0cOr?{t{ZuB4~nzIDKhW{AXJQ=i(3bKcSu zP$0kKV$m)B*4J(I>F~K5{o0vGKB7uL=D7 z7#)`12turvu~Q>IIj4Ftx0pz?P(Y1n6jJyy{R;qC_ozcs-&@c62!bObeyp^#Q3whc ze0DtdXl%~S$zd>`XIKUnb$sebH^F0(zRK+U-)0Dwh2*}UvH1X+(dTLc=Bf9OIttHb zKLHjB5tlw61qc%Wfl;gu?jMbq_^(}ci`p7cn0fcpfk9#RPLr`2??6Pm3Wur1(ZjZ( zVwJ?(A<2Ng1_gx@iA>D%QQY5K79Bx9T|*74ujbnkOYn>%ASARXtS|TED&g-1mIbUT z+UF<7fMhl1=TCF`P{JGV7Cqnsm$YNG-=CzcyX=QqTwBwI7;Nc%s`*v)5|@@)-X`yn zm7{h7DNcf214wI--o1ft{gv?poWU5hkwZ$PYXq>j&mJJ^il=L~5@yHYZ z2n!NxmD>xzsPq8cc~6uSX-p1%sqXMI15JNO;^xnslwxOUN-o41tY(S#*X{Mkvmy~jr*#``9`5cu51hMkN zcjk&GAhd4C5V=k@2M+X9#LjC_+pHG=dVa;<$mR6+h}DHck5oJCRY}$K*)@8@jkK8%EnxS`I5Ya^85@+NZSQBB$JX_k zYdVeQf#D)Q$tIA6p`oEcd4?DjekF8n#TfYTn`rI)_S1jG)E-yA3CuYwUBoqKy#N|L zUZp2?^rvOcBjr{?(mX7=4A*?V@(btkTGCg;zUvSWa~g5oph1}`*_`2Dz{>gcIikiHgO1tZyB0Erb0$wJPH z+kmA@?vqxS6(eIduQqUar%N9F(*@MguQ8P zesb6dzWYhzqkJHtN}P1^LS+G;yh7cDXQ0$RAno5Fql~9uuvlbab>4U$Cv?`<9-VNP z0VPp(gk-9%5toY^E@ozojhH{ud3$yMlmwI6V3@1l_D2%QW7sA3Z?zo0z)pupL)(&` zOLZ&3{T)GX@yOC#%%?j+eRL+4uVuXcZ7W`qku>-*cv08z+Chm9GqlT10!Wq$67Sxi z3?&Id^XNg4yOc%x37wU__@HXuJcm27_kBQk^ z(OOK&bK9%n`9Y@ybxw``bQs9FjdnWgUlyS2o;Jj~u=U~$*}zy}AgON~eukmpM&TQ) zfde@jF~VteJLjp#VyOx<$n)Sq8K~i|(p?qRc&WZ@abV!VypcAo8>~pD75a10;mvRnC-Z4;a=)Fravq}zzfWSP1o0Emd^KC z|Gku76OdQ@hc^ioR~PCn(lmRo%$oj_X|%ijeZ5i!VSjGBSt%*CsSNo9dI{QwppGmA z$j45CY-jm$@IthJZq0+xr}Ma7fsi!#jFp#B{HWso4+3{Q0a!^)IK6vADCFJXd01Cr5)|blE`}_NVQKkVt z8st0T8d)Q0GRoT_MsGlnrY`=44h{M*WoM$jvRBgRQI&-;;v(XyX@SZQF~jq^i@49r zvfk|TE4IV5ra#I-XYntj6C%IGD^Y4XoLCEZKz4q5i#sgYu=GcyR!k8c^rx>o^pVi~ zJzzDG3y!64rtg3OF7|ZQJ+|U&Kz=b^J+-W(Yx^A`-lr4f*wdnuOcbO^=un!AI37k# zSm4wsYc@$?o$(uSP59Es`?mydP{$BNBkiiohJano- zoGc3ZT3S!*-Q0|M9qzR{@R|82&ZhiimA!2tYms zxBGwRrPrRk@jsxQt{|%0pzAT0+Sqo>)0y<8otJCd?x$5_HQcZMYIK^1tG`A4)lyJ1 z8TnsZ{PGS>8ZR`*yI)N?ptpPFYiic!Mcq8NZACiLUhR=w8Pf$|9|wFM!5J(B86+r( zfLsrx_}<_j_muBJIa-aKEam@xa?0`FWrEEsQoR5D?=Ro=VZs>Ci-S0x-^sotr2pq~ z+fv@Nd{x_2M!p3p?ndbGFD0b^^X^n6;DKv!yY!^lBN7Z9q8Wa4YPRhdKHFa71)8>^ckxEHBVWNpH2kuA%9hLj8xfC012oD+_JE~{MRAZcJ?<003 zzJIyJQjSGt_KVi@gLJxgwg+`Ie5af^y~Xmk=%C3Q4ssM@=XS%{_d_)(_1yWt46*nd zDZ5O|dYHpdIFiyq=Z#M~Ypuuit?2`<;t_A~$x5W?9Fj$qaZ=MdW>zhf*X;G(9+!Q9 zm}Y1VTW{8xcY@^sj(cOQuswJr?y3LzbxIF!S+LMF|G`$z>UFLe-j0n+4|3W6T`F3x zLrS#Q2*o2`Ox}v>;}z;(6Q&!d>G6!It}pBHNraimFMr|~OGgxqvaurf9Mx+)ntn~W z$$s#Jv{>7QQ}S0<@OB#cl)XJ+1cN zM!FqxN`CrXo$lV@fkNirl#s-Aaj5omHk5hP$l42-iLo!Ma)HuY-~Y3Y$`OqJ(~oH` z=I+2Syb+s1P33sS`bH`-O6q`{|J+1|H%#FblHfRV4pF2*vJw2ur;>k2l{WjL3X`LL ztsmg}9eZ2zO%!d1WJ%X#VaN1VO`H2K!W4A5ke#uWwAjjjr9Zh4M6OW>y)IueAbd;m z)_*DO@14St2|)w`g-$Q>t~7)bQ$Yp^>g6Z&_AW3H0bDB#>p=|}_S!%Uw(}!@gk=1h zbcRiIht*Z#?PqtwW_xGMoAhNLIX`r3e1}u#BQxX7_E(S*l_*yyzOeRSv40{G2!-(n ztj-8ERM(;d3xATioXM)y*ngLM%<2AyCqF%GHDa(4B9aa_4Y5)>=^^n_=-qo=ksX$5 z!5{XcMpJlWaM3uJIk^c+oKYgN&W&*)T2cd53rvhnt zq;pbOkjEhWCB%+2YSDn{p%sEuMmE;-$iQPM^9Ao3^sR3E?9q05VeWetJ$5_S(iSk{qI?9~@wVa+>zxp8Oa~w}eW7Z#f?? zZ{MRmz)0cWEdT=}q+suVFI~lvnVD`HoaOe3jdjV|SS62?iF78qgbu>i6CkI$uv2UM z4l$cSxQv;YBLi0lA>wO(9A;D`xnW#rGB%Fns{HiLSH|qXN{&q@Axr#i48w0OMpoZ# z^LFfN-Gz8GNHP=kDhagmMgMIV;C2b=eGlfJSx4S!6NP*!sh{kT?E93icfS&!#CNzT%>-3bN$HsSDl=mx zw%cFX1cnPq1QsRhA1GD-;H!GzjCp&2A{gQE!!wXH^*m}txLRI3B8;IYwY>y}?pdjx z%bHQ1AgSpvqdKOR&^Fea`=FD^gyU1WaE(xBy2Cd~)i`n3i}@9flrK0OSpO3`FSjQj zC_JMNb|Ave(W$gcakBJ8MRhM{TQJl==;qaY5D>?%x9SLvBwAWO778hvY{i}(H($E9 zQLJ&^aVT!}Qx;Q&b-1R`>kTmFsB!o->FCgXXG8lfB{uBPu4JguX15=$KD8a2uro(# zl0n-GjQIE+F+p0|JUPhkwME6{2*=H*<HMr3pN4Pzk)c@L%-)<6hUjkOuDeJR zmwdqSA@9P=ASmZbqi#f;u*e^4)J>sNwl_4&Xtm=-yLY@76nA{FkD^djW-{b`5W z=Q%9p)-#6fY1rvUpDa<_RNRohuzDTQB)WOqJc0~7xt$1szPH<6F9)Ll9%6FP7a7X# z|D~B_dXvNjJ85C*@Mav!TQ1P1H*A=Gtx!Cv`@!8spf2xWPu9FnEFgg~+gEG%RLnUO z{yp)BLv^2Q(wGH}Ui!V?>u=kQ(- zF$$0O!R6$GfHEDmB2Q$D{j{U|l)*u+xYELTkvk6b^OyH;5WD%hyu`7}Ev*so>-Ixa zOl*qKtI{=#xbau*nzCL~RbCh)EVAZ0DsdB*l{bL-%Wk%k^|Dz%l&LO{*q|de4z#~G z(msqUV z);q6J(7$cYez-iQ!A8l592haD&~zvC!~VM!rZBJ+?ayy(5@;pH6bbhyxX>i|--4%V zK(Pu2t5D=-T{2`)I+O898~?NRwrrs@X z?ZHu}P}aO>$4$WuXfW}T{j;Wil=b||%e;Gcsk%tsI4CK1zc+h#x3R;G+@bA}>Lt=T z#e|{-Eqhn)(r$TEWl)`32hgQmFN<&Lg z-T2~%(<0|yKGqET+kMaAyj4jxHfAsz3qNNQ<1HgvOyJh$?z^;&T!s5=G@HQMr+DIB z5tIJ1VTP@eTb;mUgW|$jBo0xkDiaBQhb3{#a9D z^)yX~tAFI8%6B#G4OF%Uc;n53k=W}CgFhA#zsh9Kvctg_SczUU8% z?D+``pRhmeJoqiLtYWmn>uLO~Dhuq7&8X*XI!D3N z?YeD`po*}O&w9@LcE2nXIFG zKv4oo`-h*J6gM~w!ae28*V*@IbT=CA|*0FWHKYLD{X~ZrzCTyRGj%Ss}yRVRW z+-9mcX$Og3=<5e56VGjf12`||a zsj|)z1I)!HMt4Z=Xu`EnJ1L{@#y5H|cllV-nc9hA&&OCJTQ(KsF59=mIbZ#{W91^m z&Zmq!Q4JX^J6VVqNo0w6?dt%GyB1IHvz_oMIw6H*VK*r5H6|KQ>&eUIflVY`Y%eg!^{=Q7PFfLPXfG5Kwp}pXj`G3zB&8*OJ^* z6Ufv;_`EZ^FV)5LWBZJJuP(UfqJ?k7C}eSqzAfH;-=eWu zZX%}pr404UFz2Sy(Z~3Uqmx=a?tP5B)Y?}U$mSy3l`Y{{f5pqQLW?x_8fF4l1J(c1 z`aVf}gq?WlZ5A&T;i=YBV5JDg7SK;ynij(ucnEf%tOf3&)gzpg&qrdYsEDt&!42=u zh!zq^ZWWNq?dDJ?kjNLJ6S@qXhgO~z1x^%h}w+!47!Lv=oo)v(_mx#kKXAI0WLTfK(()@|7*8y--~R$j(8=h z7oMW1XgUi*=J~iZAgIA}e*`m-=cw>|S~03TGwO?dCJH`Rrsn;dN>A)77!7zGtTc`4 z33lt4sB$gwxwpNr2h4ChEl_pGk-fgwyWYmyJ2>CU+TRnI{Ky?x6-1BqWRhV#T1t*w zIx)@@FHjrkg-5D()P){oGwp(vb~Yf5EDApt@AKSh&_OedBn-`#SHeP87BnUK9dtqU zEiK>Ev{q$*Dq--F<}a5iAGTWEg6ZfV^gPP)@(C1dm=6@dH9Lsye`mMfVed=t;##B<$jcALKSm{ns}ztC=}yY#u8O!e z)*n;XSUMWl4nDi?d=T9FT}8Y@7%bl5C5c zE+CUITGl=J8uAcykD%!(llA6&3LWHXHs?Ot3V7NbNN0Hs8np}-q&WNLQ)B|!lCx*} zi{yd_T%y}pvp*UxmpV79he9U$p_g2D+rbTB`CK?uy0YK$#}(w0*mzc~^5P75#-zGPI;aWxDtmN^p1xYfo?QBr)FNrl=|Z zLyZLg18Rd_(UVdji{-zaC%Qg1<&inxh~1lP;l<7FVlP6%3^%765A$da1hH-fn**M#jR z8;QkP>Db0laR`L;vc3r^bbZKgH?~?%u`5qW{;x8plJN~ANwIzg;l`xZrj)DO*>tGI# z91r6ja&xEg5ToU-#=(Z5+wtSDq+y!lpHHc@EfWrAi|N%0cLQF60D=wK2kq39DmNNg%(~zP=7wm3aAuNv1AZ z?=agh{Q&h<|HQg7rplvl=PDXhNW6>Ih!BE@9P|S9vht)%L4khQ#kU&X$wn3ZYbs7z zvb4GPhj)GiRZtpVQfsL}`pF^q0O@&d^)c4lJnQl3+%pneLk`tqFegM}r7%MlHa)MK zZ`lwkUFk^cvmpxqyxD3h;Ne$S@fwU?XwsOvG@M?sivRuCF?!+hU}DJic7lY3N%W0L z7*sZaxW^3*SAD+uuhGEIzc0eTVY5GFnm6j~f^!CsSQ7Y&wW83f#oJ8WWBNO4jdk;r zzC%Di<70qMkqG|V?!K=yTjo~F2E!-;3mN4G6SR1X=f8A3;W(NL@!5~PE0fn5s0}p& zK4kBtl@?VP*3Qj`BtfvRjnjR%i#0n)C)qSbHNG!gZD&bn1pP-OQ?Wdp*5n1VjTd0Z z8h$-S^4@J1C3-VPOwTs@;fMd^!&Wh4P;FilLS~Y0;0=FhKQRg=#=uy`F-#7f&{+pf@Cz5_| z_IfN$ond18b%`k)y23JNb9Pt51~g?3uhMFQC)kCEL{Pzv>%4N-mGSS@#vjV`F;G#p z-z)Vbe&Stn=2&%HwZ^(SGx@;(CUI`*AF(f8H(3lzBd_7}B6`{WeRTaF2?HZEGGdv| z;~O@5M-#b@ZWN4uu4}`Ncal@yx=JW!f^m z9OM7kJZP2qJIYp%j(-+ov<%INtslipu@u@GC>Z94SE$+2G}{4~7GcHtLHsA-;z)_h^$uWp(ZvBj1`nuFie zQiq3SG9{U$?<{kZDwG)P*4@K#6MTE*oEAcNzL3; zL6tG)#{3HBr^bl@qbkQ!GGU14Yodyhkm21)WA%UV?W%QgV;Z7ka2LxSY>t!CYU_qd zH4*j+ti8F}9#{tgjx?(}Y9wK7vo;?mN%Tv`W+M9cyxbfUk}BL$q^@VhqI`s^7+7D| zu^;7v0k3|dct@h2b+-6P*WSQ})=}9qOdmCO;pL!TPu9%pjdY>v8x_8%Y-Lo;oAO(c zS^ZXHL$x-i7vL9eS7_YT+4bB+%QE`+s04ZL(7wec3w>GDV&JP(3t9u#(xck6rI4z9 zUQoCAqx)?9hKTEY^xxuzNms^>caMY{jvjrF0!#(+rOZ{7$$)@#CI4oPhx1HL7&~Ej z=QEFqA$jvWBRNTZr8a5ARsGf0$fLv8@R|gkzC8h9hp%(ln1<}=fq{@92S-O(DK(e( znyBnZN%WxWmvW{-_Z&3;ZdvAPKJV)2u25>MJwc@|{2l+=H)}#QV4aM*SD?}mv&O}lw-!YatquAetzL=$l!{rv2ZvkX{7n_XSbz1p~S|_SPsizD&7w9 z>UBbrQO8J?AiloJ8=UH}k<|sd>gphmWmGA)6Y`v#0l3nIBgJgkAsN0Uc*C^zbu{zM zrU4ngL%*uF?YGqd7Q^M|L-a=S?fOn`Dr5^O#J=|=rY+gOF%mP$st3WfYt(}H(rIA1AJXG859Psqt8<+5HyI5%*(Eyy3 zafM_5MG-;hV>j}DWS`ii)p71KvTM5rtb83jj0!(Q(@e`!^?c^XKk9$aVjH8Au#n`t zFy~u5umQ26EHu4YFK|4$ZC%)vFcLI*FVhc7z@zhmIWP2jWbkSw)q)EqV1VqHq9WZbD z6&w+^qN4(I)5_-CTbPd%o~V+|d+j>n%uG*yM$mS=bF6mSuWUIGpofvZR?|KX>-of> z$cOZ^S=1=z@;m(B=EAqd8p?H_%0~8G(o;+%{n~NNGASpikME9qTxz1?(M?($y)8y? zoPv{ef^!gqzV`jObHs2EBWfgn*8;8j)0*XPb+F;Rsw7eTj(j)KO?<*PiLEWmlmK}4 z>lxolSXMbbtREMU^I{lubM&W_{%hBoI9c4EZ}S(pQILLxG=K2hD_u3ho33*oSiEjB zpitfVwcq;XN>@qs`J<&$$^{GcdO&5{^@gsBX2Ed_&avnc&h(E=_-pws<{{}{Umf1) zc}7;$^O-gGeK%_HFEczeBYdnkd*Jat`pDfWuUTk*_3k(80zY&^TWoV){T31_$(!D3 zgN6V7sR*t@!@gH-H~dyyu^kcaoT*nH5%U?JehoE4SGpLl_E98+#@iOY+?Fr@xr+ zu`b)lsvyp6MINGYTYYjQ;q}1~bJE-Hq6~JR=DY?`{Px89K;L#Qnb^U z{l4>>XvmVKct+kammpzgKO8>PH3)U>a!if_^o$#CYuB44dLI{wq zU1~16e<>$>vRaP)@rilhB7eBZHq9+sxV{@}>pz9JK#)qFK>0D?d7?M^kh{V}-QA7F zcvS^8KfYzcGqHtPuhLT@u~1F4jiVbYSEu>o-T^HPn^vSNTb&0hIMOk+_)Gh1E~t9` zzgd4OqkNS#lf#w+-zqpwdZS#Au?muckbNox*`cnWIQtT*j z)TP70U`A6=#u-u6uqtY0V&vml=u-OlCBl`QwUD;n;!i+CkdNv+H@)HeL`tGQ7inmT zMiyv`jQdxs`6oTs8mpT)3PQu{nY!A{o=k_IE7Kn_sKVRTBojth${0P z)n7Y_9M(7@Cf$kk3F2wbP7Mn38LD-Nb*@dK%Z>umo+yu$u1zD@BBwS?=tZ1;2cJ3i z^{3h}bJb1Nw#4ekR8F{llz1-_?IM{AK5~l7aPqcCglVAx_gD75Mx- z){r6fqVimm536d9=f4EdAN>WgA$|p{HrjuA{I8^$X#-;3T_Ef1{kwu1IiAQ)M)B*j z;L55WJYyhWL^#GbO?_%ae%kCFKSKxr5LnZJ<}$Y64!d!0D@JM?)PO`SvFycAw->ri)i&ettJVfsImj3yP( z!~ibmf1HyvanxZ?zd{G_p&ab!9P(9jyXT5<&U+1&`{{qL6=H1JTj-Zn5zeVF9QM0R zfPbQd5&Q4n;a26QPo;Xb%$`8@VxUYr=U0OVCkcx@8ltd%CY*6p9|gB_+XH>W zDW=^4PkJ?&Mg2WSK88(d5>Arv5%;7pgH=QCPN!Pdvc6=vaHKvpK~9Ui-YYH^joFE6 z(cw7A#;!w<<=vC))?}UIG5HjyCjsNG9(xAKyqsYm-~_o5FwMf^v(g>)R>h#M7}wFn zAD);5eVgbJe&1YZ2~m%3bthK5b{e2D`49WVBi+eRC3{aR0x$8yt_sC z1hPx^sBJx~OaU_Uydmd;zKk-qQE+fL`#wYsQ)LiW!ZA&PO6yMSJ@;Mzf+qKaEW#-hjqQn zXgfxuw9Zx-#X|OzjTj%2ZG5JGWf6yvpm`pt>w>3&UD{OLzS$S5gpwdGZA|b+wfCK& z>iw?`mg!$eo;_TJ{6Vhjjl=opQxv4QJu4`z<}f%G{=zCm`mb+(F)d| zF2)RFLXlI5Ypdmyt)FSeuodL`FU|+-h|<0?mknO9Hhp3JYKV4RNzD;eI>O_xuGAp? zp)%YS_ETKxYZ~}FHOOZKGASOK%(F%!L>3~x4iD_Ej;8Z4Jp6t8XBb%LfKcDZTx`6-Ij-IGpVWs(-z;R))yC~Bm z`!Sa&HmkiNOv)Eric~l)q$h|0qcwDen4R zcK-6%aeG-5Z^}I@pu(nwV3(_%Cm#;NrE@64UO1v#7-W{5*2jP2KEE8N4M! z_!=;fWHZ+i$-2G(e*kMwC3dNZj){Wro_m5SP$ru=@M<`TZ9^5|4G!`C$W%1%r>3y7 zTPzE{nBtiB_@RrDCQ`7MNJbDUN~>U8e*K;Q8Mx~(qN@Qo^q|%^-}}u4aW@eNu(@Pg z|4;%<)*sCdyhc`*mSI+~ajF+M?ko4o98V>VR5;#fO789WR5xk4 z?T?amG8pueriGO=!2Dw$nFDs1Ow2?%L7~Vsb8Vz3vkBOU=`Ctw^_1^!& z6+9`Bk*w3=-OFd$=eb5xat5NZuG}2;K)R^2l&6(HA9r zi|sTv>k5mtc@!%mZh}GfD-hLAffxuZK>?X+sZF~MtRg5(rpm!a#;*Xn=#uFvB<2Hm!(wKjRtoFyjkP zqVS18{?CBRF8JX7=RURxjwa}zQF@9GhEY&J_ZBrWd3WcDt$QAYNhFfdmZ*1;1PE)c z#TqHSBYKvHJ`1Fvm+_dpeWB{dx8q*mjyO2%{j4>b4{{4fWeY^JTS6_w z^kJ%(gY~9;zLb>6J($bZb1qs}xp4Kx$77joX+OMj*;1X`mt9&ibfkC9r8KW|EqNQ{ zzI(je;HCFsUV_%$muLT{zP^4I^d7-k2*2~R*U?l_!aHZZ+P|jv)~??$OzkmS)ovb% zDeF0mKL9he=tFZRGez@Rhk*W#jv}gLY>G=u&b!7;&p?po|4NB!Lfiw=gn0n^5-?3r jv=y%J4Wb18PllKNRcE)^K`d&t-T^O)8_ktV4!QU@Hq^oI literal 0 HcmV?d00001 diff --git a/external/libbf/doc/figs/bf-basic-part.png b/external/libbf/doc/figs/bf-basic-part.png new file mode 100644 index 0000000000000000000000000000000000000000..d503188db9ad6fe92f57a5b54d337f7294b51b56 GIT binary patch literal 11542 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 literal 0 HcmV?d00001 diff --git a/external/libbf/doc/figs/bf-counting.png b/external/libbf/doc/figs/bf-counting.png new file mode 100644 index 0000000000000000000000000000000000000000..5c35a0d918abfe78348d85bf72375a8796f6060d GIT binary patch literal 10946 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 T@Y3&@89-cEMyOgq&;Ne_p!Iu& literal 0 HcmV?d00001 diff --git a/external/libbf/doc/figs/bf-scalable.png b/external/libbf/doc/figs/bf-scalable.png new file mode 100644 index 0000000000000000000000000000000000000000..c9a1bbebc67f981e65717f4a8ff453a79e5fadce GIT binary patch literal 15642 zcmXwfb9~&-_jT09wr!(HlSYkgH#VEbb{dA8r!yQY@9sX&-eHIvHM!g%$@h0 zGxyFtXTp^fB#{va5Fj8Rkfo)>R3IQA6~Ui7;XZ@k8=Rw`As}$otwcqYq(w!^lpO8N zt!&L8AlxD6wLSRN*B3VrCmtN-7x^b|nRHD7oaQt;>4(=Cc^m~I(9&Y`rbyJPDk3FG zNWVp_(DGEI4jxJlJ$$9Ys5MOcVwzvO+ZHtJM z#&ZvNzA8+xye>KTf3Ek3OY&#xXJpV_Tv;J{tvWx1^LI__x0uRH?2A0hNulYY)BbiN;3LLZ+<;1zAr(6CA5!e8EYKWY)-V?weQF9%>A>U005 zv^5(2uF|%T2*CF~45>Q5+S=p}G9y#SR`UAsr!lfnRDikSy+@}rUV@t~WKd*bTdhM6 zNr-cZC)3*Q0ryR+y{nTt%F4(yMlor7)3frk3g~<#3Z-aKQet(fR|{_dHP6@<*$BxC z>yCY6bK;sc&CH%OsOtzW*xo<>m=qsI6=L4mjm3QFAn?19d7F*@p-2e?CmNEsFjrXc zixL_1Sb!kgV_7O>r$3aFv7tT|98|U;5mYQ(&|vW2KOd~TZyz~$ZzPAXu`ibdPXV}0 zgm06Xe&)NOhp#^@Ag)5LH=Pmq2U+ahT$C>$hd?JzBT z!rsTma$WwgU%3F>Thz#McRf&q-9L3%lG8(!>~{;gY#DG>(LLpOOAfqFhpu$wLPAdUl8ivQ2cV&aX4g?DK@teV;syG2BP>B${6Yhf`Ypjy{w0rt zNDw9%f;jB{^a~y<07D7VT3EpHvt17KoxdO(gnpm{+oz(x{8}(Ve?P-~*9wD+CqoHC z#Rw=M6AweO`W_O;YYNRPN)ktkg&-~puY`~UX(pPQW1~dvjMxh8A{?9h{RsOGB|XSj z_!N#R5a(~y*7q=qJv7r`mL4Rd&|jjD

PvVQPK&VkCKZ4$#xVN5XgH`V>WIw_(^+ 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 Jl_G|L{}1lfftLUP literal 0 HcmV?d00001 diff --git a/external/libbf/doc/figs/shrinking.png b/external/libbf/doc/figs/shrinking.png new file mode 100644 index 0000000000000000000000000000000000000000..12641b1b4eaa621c84bb8b10af217c965f5a8b74 GIT binary patch literal 16885 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 IM6N<$f@0wJr~m)} literal 0 HcmV?d00001 diff --git a/external/libbf/doc/figs/spectral-rm-bug.png b/external/libbf/doc/figs/spectral-rm-bug.png new file mode 100644 index 0000000000000000000000000000000000000000..e5f54ded7e61723c7b6870402187b77674836423 GIT binary patch literal 20304 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 h{PO?%$hUJUd%I= + +#include + +namespace bf { + +typedef bitvector::size_type size_type; +typedef bitvector::block_type block_type; + +namespace { + +uint8_t count_table[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, + 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, + 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, + 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, + 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, + 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, + 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, + 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; + +} // namespace + +bitvector::reference::reference(block_type& block, block_type i) + : block_(block), mask_(block_type(1) << i) { + assert(i < bits_per_block); +} + +bitvector::reference& bitvector::reference::flip() { + block_ ^= mask_; + return *this; +} + +bitvector::reference::operator bool() const { + return (block_ & mask_) != 0; +} + +bool bitvector::reference::operator~() const { + return (block_ & mask_) == 0; +} + +bitvector::reference& bitvector::reference::operator=(bool x) { + x ? block_ |= mask_ : block_ &= ~mask_; + return *this; +} + +bitvector::reference& bitvector::reference::operator=(reference const& other) { + other ? block_ |= mask_ : block_ &= ~mask_; + return *this; +} + +bitvector::reference& bitvector::reference::operator|=(bool x) { + if (x) + block_ |= mask_; + return *this; +} + +bitvector::reference& bitvector::reference::operator&=(bool x) { + if (!x) + block_ &= ~mask_; + return *this; +} + +bitvector::reference& bitvector::reference::operator^=(bool x) { + if (x) + block_ ^= mask_; + return *this; +} + +bitvector::reference& bitvector::reference::operator-=(bool x) { + if (x) + block_ &= ~mask_; + return *this; +} + +bitvector::bitvector() : num_bits_(0) { +} + +bitvector::bitvector(size_type size, bool value) + : bits_(bits_to_blocks(size), value ? ~block_type(0) : 0), num_bits_(size) { +} + +bitvector::bitvector(bitvector const& other) + : bits_(other.bits_), num_bits_(other.num_bits_) { +} + +bitvector::bitvector(bitvector&& other) + : bits_(std::move(other.bits_)), num_bits_(other.num_bits_) { + other.num_bits_ = 0; +} + +bitvector bitvector::operator~() const { + bitvector b(*this); + b.flip(); + return b; +} + +bitvector& bitvector::operator=(bitvector other) { + swap(*this, other); + return *this; +} + +void swap(bitvector& x, bitvector& y) { + using std::swap; + swap(x.bits_, y.bits_); + swap(x.num_bits_, y.num_bits_); +} + +bitvector bitvector::operator<<(size_type n) const { + bitvector b(*this); + return b <<= n; +} + +bitvector bitvector::operator>>(size_type n) const { + bitvector b(*this); + return b >>= n; +} + +bitvector& bitvector::operator<<=(size_type n) { + if (n >= num_bits_) + return reset(); + + if (n > 0) { + auto last = blocks() - 1; + auto div = n / bits_per_block; + auto r = bit_index(n); + auto b = &bits_[0]; + assert(blocks() >= 1); + assert(div <= last); + + if (r != 0) { + for (size_type i = last - div; i > 0; --i) + b[i + div] = (b[i] << r) | (b[i - 1] >> (bits_per_block - r)); + b[div] = b[0] << r; + } else { + for (size_type i = last - div; i > 0; --i) + b[i + div] = b[i]; + b[div] = b[0]; + } + + std::fill_n(b, div, block_type(0)); + zero_unused_bits(); + } + + return *this; +} + +bitvector& bitvector::operator>>=(size_type n) { + if (n >= num_bits_) + return reset(); + + if (n > 0) { + auto last = blocks() - 1; + auto div = n / bits_per_block; + auto r = bit_index(n); + auto b = &bits_[0]; + assert(blocks() >= 1); + assert(div <= last); + + if (r != 0) { + for (size_type i = last - div; i > 0; --i) + b[i - div] = (b[i] >> r) | (b[i + 1] << (bits_per_block - r)); + b[last - div] = b[last] >> r; + } else { + for (size_type i = div; i <= last; ++i) + b[i - div] = b[i]; + } + + std::fill_n(b + (blocks() - div), div, block_type(0)); + } + + return *this; +} + +bitvector& bitvector::operator&=(bitvector const& other) { + assert(size() >= other.size()); + for (size_type i = 0; i < blocks(); ++i) + bits_[i] &= other.bits_[i]; + return *this; +} + +bitvector& bitvector::operator|=(bitvector const& other) { + assert(size() >= other.size()); + for (size_type i = 0; i < blocks(); ++i) + bits_[i] |= other.bits_[i]; + return *this; +} + +bitvector& bitvector::operator^=(bitvector const& other) { + assert(size() >= other.size()); + for (size_type i = 0; i < blocks(); ++i) + bits_[i] ^= other.bits_[i]; + return *this; +} + +bitvector& bitvector::operator-=(bitvector const& other) { + assert(size() >= other.size()); + for (size_type i = 0; i < blocks(); ++i) + bits_[i] &= ~other.bits_[i]; + return *this; +} + +bitvector operator&(bitvector const& x, bitvector const& y) { + bitvector b(x); + return b &= y; +} + +bitvector operator|(bitvector const& x, bitvector const& y) { + bitvector b(x); + return b |= y; +} + +bitvector operator^(bitvector const& x, bitvector const& y) { + bitvector b(x); + return b ^= y; +} + +bitvector operator-(bitvector const& x, bitvector const& y) { + bitvector b(x); + return b -= y; +} + +bool operator==(bitvector const& x, bitvector const& y) { + return x.num_bits_ == y.num_bits_ && x.bits_ == y.bits_; +} + +bool operator!=(bitvector const& x, bitvector const& y) { + return !(x == y); +} + +bool operator<(bitvector const& x, bitvector const& y) { + assert(x.size() == y.size()); + for (size_type r = x.blocks(); r > 0; --r) { + auto i = r - 1; + if (x.bits_[i] < y.bits_[i]) + return true; + else if (x.bits_[i] > y.bits_[i]) + return false; + } + return false; +} + +void bitvector::resize(size_type n, bool value) { + auto old = blocks(); + auto required = bits_to_blocks(n); + auto block_value = value ? ~block_type(0) : block_type(0); + + if (required != old) + bits_.resize(required, block_value); + + if (value && (n > num_bits_) && extra_bits()) + bits_[old - 1] |= (block_value << extra_bits()); + + num_bits_ = n; + zero_unused_bits(); +} + +void bitvector::clear() noexcept { + bits_.clear(); + num_bits_ = 0; +} + +void bitvector::push_back(bool bit) { + auto s = size(); + resize(s + 1); + set(s, bit); +} + +void bitvector::append(block_type block) { + auto excess = extra_bits(); + if (excess) { + assert(!bits_.empty()); + bits_.push_back(block >> (bits_per_block - excess)); + bits_[bits_.size() - 2] |= (block << excess); + } else { + bits_.push_back(block); + } + num_bits_ += bits_per_block; +} + +bitvector& bitvector::set(size_type i, bool bit) { + assert(i < num_bits_); + + if (bit) + bits_[block_index(i)] |= bit_mask(i); + else + reset(i); + + return *this; +} + +bitvector& bitvector::set() { + std::fill(bits_.begin(), bits_.end(), ~block_type(0)); + zero_unused_bits(); + return *this; +} + +bitvector& bitvector::reset(size_type i) { + assert(i < num_bits_); + bits_[block_index(i)] &= ~bit_mask(i); + return *this; +} + +bitvector& bitvector::reset() { + std::fill(bits_.begin(), bits_.end(), block_type(0)); + return *this; +} + +bitvector& bitvector::flip(size_type i) { + assert(i < num_bits_); + bits_[block_index(i)] ^= bit_mask(i); + return *this; +} + +bitvector& bitvector::flip() { + for (size_type i = 0; i < blocks(); ++i) + bits_[i] = ~bits_[i]; + zero_unused_bits(); + return *this; +} + +bool bitvector::operator[](size_type i) const { + assert(i < num_bits_); + return (bits_[block_index(i)] & bit_mask(i)) != 0; +} + +bitvector::reference bitvector::operator[](size_type i) { + assert(i < num_bits_); + return {bits_[block_index(i)], bit_index(i)}; +} + +size_type bitvector::count() const { + auto first = bits_.begin(); + size_t n = 0; + auto length = blocks(); + while (length) { + auto block = *first; + while (block) { + // TODO: use __popcnt if available. + n += count_table[block & ((1u << 8) - 1)]; + block >>= 8; + } + ++first; + --length; + } + return n; +} + +size_type bitvector::blocks() const { + return bits_.size(); +} + +size_type bitvector::size() const { + return num_bits_; +} + +bool bitvector::empty() const { + return bits_.empty(); +} + +size_type bitvector::find_first() const { + return find_from(0); +} + +size_type bitvector::find_next(size_type i) const { + if (i >= (size() - 1) || size() == 0) + return npos; + ++i; + auto bi = block_index(i); + auto block = bits_[bi] & (~block_type(0) << bit_index(i)); + return block ? bi * bits_per_block + lowest_bit(block) : find_from(bi + 1); +} + +size_type bitvector::lowest_bit(block_type block) { + auto x = block - (block & (block - 1)); // Extract right-most 1-bit. + size_type log = 0; + while (x >>= 1) + ++log; + return log; +} + +block_type bitvector::extra_bits() const { + return bit_index(size()); +} + +void bitvector::zero_unused_bits() { + if (extra_bits()) + bits_.back() &= ~(~block_type(0) << extra_bits()); +} + +size_type bitvector::find_from(size_type i) const { + while (i < blocks() && bits_[i] == 0) + ++i; + if (i >= blocks()) + return npos; + return i * bits_per_block + lowest_bit(bits_[i]); +} + +std::string to_string(bitvector const& b, bool msb_to_lsb, bool all, + size_t cut_off) { + std::string str; + auto str_size = all ? bitvector::bits_per_block * b.blocks() : b.size(); + if (cut_off == 0 || str_size <= cut_off) { + str.assign(str_size, '0'); + } else { + str.assign(cut_off + 2, '0'); + str[cut_off + 0] = '.'; + str[cut_off + 1] = '.'; + str_size = cut_off; + } + + for (bitvector::size_type i = 0; i < std::min(str_size, b.size()); ++i) + if (b[i]) + str[msb_to_lsb ? str_size - i - 1 : i] = '1'; + + return str; +} + +} // namespace bf diff --git a/external/libbf/src/bloom_filter/a2.cpp b/external/libbf/src/bloom_filter/a2.cpp new file mode 100644 index 0000000..e11225a --- /dev/null +++ b/external/libbf/src/bloom_filter/a2.cpp @@ -0,0 +1,45 @@ +#include + +#include + +namespace bf { + +size_t a2_bloom_filter::k(double fp) { + return std::floor(-std::log(1 - std::sqrt(1 - fp)) / std::log(2)); +} + +size_t a2_bloom_filter::capacity(double fp, size_t cells) { + return std::floor(cells / (2 * k(fp)) * std::log(2)); +} + +a2_bloom_filter::a2_bloom_filter(size_t k, size_t cells, size_t capacity, + size_t seed1, size_t seed2) + : first_(make_hasher(k, seed1), cells / 2), + second_(make_hasher(k, seed2), cells / 2), + capacity_(capacity) { + assert(cells % 2 == 0); +} + +void a2_bloom_filter::add(object const& o) { + if (first_.lookup(o)) + return; + first_.add(o); // FIXME: do not hash object twice for better performance. + if (++items_ <= capacity_) + return; + items_ = 1; + second_.clear(); + first_.swap(second_); + first_.add(o); +} + +size_t a2_bloom_filter::lookup(object const& o) const { + auto r1 = first_.lookup(o); + return r1 > 0 ? r1 : second_.lookup(o); +} + +void a2_bloom_filter::clear() { + first_.clear(); + second_.clear(); +} + +} // namespace bf diff --git a/external/libbf/src/bloom_filter/basic.cpp b/external/libbf/src/bloom_filter/basic.cpp new file mode 100644 index 0000000..f0551b1 --- /dev/null +++ b/external/libbf/src/bloom_filter/basic.cpp @@ -0,0 +1,96 @@ +#include + +#include +#include + +namespace bf { + +size_t basic_bloom_filter::m(double fp, size_t capacity) { + auto ln2 = std::log(2); + return std::ceil(-(capacity * std::log(fp) / ln2 / ln2)); +} + +size_t basic_bloom_filter::k(size_t cells, size_t capacity) { + auto frac = static_cast(cells) / static_cast(capacity); + return std::ceil(frac * std::log(2)); +} + +basic_bloom_filter::basic_bloom_filter(hasher h, size_t cells, bool partition) + : hasher_(std::move(h)), bits_(cells), partition_(partition) { +} + +basic_bloom_filter::basic_bloom_filter(double fp, size_t capacity, size_t seed, + bool double_hashing, bool partition) + : partition_(partition) { + auto required_cells = m(fp, capacity); + auto optimal_k = k(required_cells, capacity); + bits_.resize(required_cells); + hasher_ = make_hasher(optimal_k, seed, double_hashing); +} + +basic_bloom_filter::basic_bloom_filter(hasher h, bitvector b, bool partition) + : hasher_(std::move(h)), bits_(std::move(b)), partition_(partition) { +} + +basic_bloom_filter::basic_bloom_filter(basic_bloom_filter&& other) + : hasher_(std::move(other.hasher_)), bits_(std::move(other.bits_)) { +} + +void basic_bloom_filter::add(object const& o) { + auto digests = hasher_(o); + assert(bits_.size() % digests.size() == 0); + if (partition_) { + auto parts = bits_.size() / digests.size(); + for (size_t i = 0; i < digests.size(); ++i) + bits_.set(i * parts + (digests[i] % parts)); + } else { + for (auto d : digests) + bits_.set(d % bits_.size()); + } +} + +size_t basic_bloom_filter::lookup(object const& o) const { + auto digests = hasher_(o); + assert(bits_.size() % digests.size() == 0); + if (partition_) { + auto parts = bits_.size() / digests.size(); + for (size_t i = 0; i < digests.size(); ++i) + if (!bits_[i * parts + (digests[i] % parts)]) + return 0; + } else { + for (auto d : digests) + if (!bits_[d % bits_.size()]) + return 0; + } + + return 1; +} + +void basic_bloom_filter::clear() { + bits_.reset(); +} + +void basic_bloom_filter::remove(object const& o) { + for (auto d : hasher_(o)) + bits_.reset(d % bits_.size()); +} + +void basic_bloom_filter::swap(basic_bloom_filter& other) { + using std::swap; + swap(hasher_, other.hasher_); + swap(bits_, other.bits_); +} + +bitvector const& basic_bloom_filter::storage() const { + return bits_; +} +hasher const& basic_bloom_filter::hasher_function() const { + return hasher_; +} + +bool basic_bloom_filter::partition() const { + + return partition_; +} + +} // namespace bf diff --git a/external/libbf/src/bloom_filter/bitwise.cpp b/external/libbf/src/bloom_filter/bitwise.cpp new file mode 100644 index 0000000..736a447 --- /dev/null +++ b/external/libbf/src/bloom_filter/bitwise.cpp @@ -0,0 +1,54 @@ +#include + +namespace bf { + +bitwise_bloom_filter::bitwise_bloom_filter(size_t k, size_t cells, size_t seed) + : k_(k), cells_(cells), seed_(seed) { + grow(); +} + +void bitwise_bloom_filter::add(object const& o) { + size_t l = 0; + // FIXME: do not hash element more than once for better performance. + while (l < levels_.size()) + if (levels_[l].lookup(o)) { + levels_[l++].remove(o); + } else { + levels_[l].add(o); + return; + } + + grow(); + levels_.back().add(o); +} + +size_t bitwise_bloom_filter::lookup(object const& o) const { + size_t result = 0; + for (size_t l = 0; l < levels_.size(); ++l) + result += levels_[l].lookup(o) << l; + return result; +} + +void bitwise_bloom_filter::clear() { + levels_.clear(); + grow(); +} + +void bitwise_bloom_filter::grow() { + auto l = levels_.size(); + + // TODO: come up with a reasonable growth scheme. + static size_t const min_size = 128; + auto cells = l == 0 ? min_size : cells_ / (2 * l); + if (cells < min_size) + cells = min_size; + + size_t seed = seed_; + std::minstd_rand0 prng(seed); + for (size_t i = 0; i < l; ++i) + seed = prng(); + + levels_.emplace_back(make_hasher(k_, seed), cells); +} + +} // namespace bf diff --git a/external/libbf/src/bloom_filter/counting.cpp b/external/libbf/src/bloom_filter/counting.cpp new file mode 100644 index 0000000..8ee6537 --- /dev/null +++ b/external/libbf/src/bloom_filter/counting.cpp @@ -0,0 +1,174 @@ +#include + +#include +#include + +namespace bf { + +counting_bloom_filter::counting_bloom_filter(hasher h, size_t cells, + size_t width, bool partition) + : hasher_(std::move(h)), cells_(cells, width), partition_(partition) { +} + +void counting_bloom_filter::add(object const& o) { + increment(find_indices(o)); +} + +size_t counting_bloom_filter::lookup(object const& o) const { + auto min = cells_.max(); + for (auto i : find_indices(o)) { + auto cnt = cells_.count(i); + if (cnt < min) + return min = cnt; + } + return min; +} + +void counting_bloom_filter::clear() { + cells_.clear(); +} + +void counting_bloom_filter::remove(object const& o) { + decrement(find_indices(o)); +} + +std::vector counting_bloom_filter::find_indices(object const& o) const { + auto digests = hasher_(o); + std::vector indices(digests.size()); + if (partition_) { + assert(cells_.size() % digests.size() == 0); + auto const parts = cells_.size() / digests.size(); + for (size_t i = 0; i < indices.size(); ++i) + indices[i] = (i * parts) + digests[i] % parts; + } else { + for (size_t i = 0; i < indices.size(); ++i) + indices[i] = digests[i] % cells_.size(); + } + std::sort(indices.begin(), indices.end()); + indices.erase(std::unique(indices.begin(), indices.end()), indices.end()); + return indices; +}; + +size_t +counting_bloom_filter::find_minimum(std::vector const& indices) const { + auto min = cells_.max(); + for (auto i : indices) { + auto cnt = cells_.count(i); + if (cnt < min) + min = cnt; + } + return min; +} + +std::vector +counting_bloom_filter::find_minima(std::vector const& indices) const { + auto min = cells_.max(); + std::vector positions; + for (auto i : indices) { + auto cnt = cells_.count(i); + if (cnt == min) { + positions.push_back(i); + } else if (cnt < min) { + min = cnt; + positions.clear(); + positions.push_back(i); + } + } + return positions; +} + +bool counting_bloom_filter::increment(std::vector const& indices, + size_t value) { + auto status = true; + for (auto i : indices) + if (!cells_.increment(i, value)) + status = false; + return status; +} + +bool counting_bloom_filter::decrement(std::vector const& indices, + size_t value) { + auto status = true; + for (auto i : indices) + if (!cells_.decrement(i, value)) + status = false; + return status; +} + +size_t counting_bloom_filter::count(size_t index) const { + return cells_.count(index); +} + +spectral_mi_bloom_filter::spectral_mi_bloom_filter(hasher h, size_t cells, + size_t width, bool partition) + : counting_bloom_filter(std::move(h), cells, width, partition) { +} + +void spectral_mi_bloom_filter::add(object const& o) { + increment(find_minima(find_indices(o))); +} + +spectral_rm_bloom_filter::spectral_rm_bloom_filter(hasher h1, size_t cells1, + size_t width1, hasher h2, + size_t cells2, size_t width2, + bool partition) + : first_(std::move(h1), cells1, width1, partition), + second_(std::move(h2), cells2, width2, partition) { +} + +// "When adding an item x, increase the counters of x in the primary SBF. Then +// check if x has a recurring minimum. If so, continue normally. Otherwise (if +// x has a single minimum), look for x in the secondary SBF. If found, increase +// its counters, otherwise add x to the secondary SBF, with an initial value +// that equals its minimal value from the primary SBF." +void spectral_rm_bloom_filter::add(object const& o) { + auto indices1 = first_.find_indices(o); + first_.increment(indices1); + auto mins1 = first_.find_minima(indices1); + if (mins1.size() > 1) + return; + + auto indices2 = second_.find_indices(o); + auto min1 = first_.count(mins1[0]); + auto min2 = second_.find_minimum(indices2); + + // Note: it's unclear to me whether "increase its counters" means increase + // only the minima or all indices. I opted for the latter (same during + // deletion). + second_.increment(indices2, min2 > 0 ? 1 : min1); +} + +// "When performing lookup for x, check if x has a recurring minimum in the +// primary SBF. If so return the minimum. Otherwise, perform lookup for x in +// secondary SBF. If [the] returned value is greater than 0, return it. +// Otherwise, return minimum from primary SBF." +size_t spectral_rm_bloom_filter::lookup(object const& o) const { + auto mins1 = first_.find_minima(first_.find_indices(o)); + auto min1 = first_.count(mins1[0]); + if (mins1.size() > 1) + return min1; + auto min2 = second_.find_minimum(second_.find_indices(o)); + return min2 > 0 ? min2 : min1; +} + +void spectral_rm_bloom_filter::clear() { + first_.clear(); + second_.clear(); +} + +// "First decrease its counters in the primary SBF, then if it has a single +// minimum (or if it exists in Bf) decrease its counters in the secondary SBF, +// unless at least one of them is 0." +void spectral_rm_bloom_filter::remove(object const& o) { + auto indices1 = first_.find_indices(o); + first_.decrement(indices1); + auto mins1 = first_.find_minima(indices1); + if (mins1.size() > 1) + return; + + auto indices2 = second_.find_indices(o); + if (second_.find_minimum(indices2) > 0) + second_.decrement(indices2); +} + +} // namespace bf diff --git a/external/libbf/src/bloom_filter/stable.cpp b/external/libbf/src/bloom_filter/stable.cpp new file mode 100644 index 0000000..e3ff53f --- /dev/null +++ b/external/libbf/src/bloom_filter/stable.cpp @@ -0,0 +1,38 @@ +#include + +#include + +namespace bf { + +stable_bloom_filter::stable_bloom_filter(hasher h, size_t cells, size_t width, + size_t d) + : counting_bloom_filter(std::move(h), cells, width), + d_(d), + unif_(0, cells - 1) { + assert(d <= cells); +} + +void stable_bloom_filter::add(object const& o) { + // Decrement d distinct cells uniformly at random. + std::vector indices; + for (size_t d = 0; d < d_; ++d) { + bool unique; + do { + size_t u = unif_(generator_); + unique = true; + for (auto i : indices) + if (i == u) { + unique = false; + break; + } + if (unique) { + indices.push_back(u); + cells_.decrement(u); + } + } while (!unique); + } + + increment(find_indices(o), cells_.max()); +} + +} // namespace bf diff --git a/external/libbf/src/counter_vector.cpp b/external/libbf/src/counter_vector.cpp new file mode 100644 index 0000000..ce6aa48 --- /dev/null +++ b/external/libbf/src/counter_vector.cpp @@ -0,0 +1,105 @@ +#include + +#include + +namespace bf { + +counter_vector::counter_vector(size_t cells, size_t width) + : bits_(cells * width), width_(width) { + assert(cells > 0); + assert(width > 0); +} + +counter_vector& counter_vector::operator|=(counter_vector const& other) { + assert(size() == other.size()); + assert(width() == other.width()); + for (size_t cell = 0; cell < size(); ++cell) { + bool carry = false; + size_t lsb = cell * width_; + for (size_t i = 0; i < width_; ++i) { + bool b1 = bits_[lsb + i]; + bool b2 = other.bits_[lsb + i]; + bits_[lsb + i] = b1 ^ b2 ^ carry; + carry = (b1 && b2) || (carry && (b1 != b2)); + } + if (carry) + for (size_t i = 0; i < width_; ++i) + bits_.set(lsb + i); + } + return *this; +} + +counter_vector operator|(counter_vector const& x, counter_vector const& y) { + counter_vector cv(x); + return cv |= y; +} + +bool counter_vector::increment(size_t cell, size_t value) { + assert(cell < size()); + assert(value != 0); + size_t lsb = cell * width_; + bool carry = false; + for (size_t i = 0; i < width_; ++i) { + bool b1 = bits_[lsb + i]; + bool b2 = value & (1 << i); + bits_[lsb + i] = b1 ^ b2 ^ carry; + carry = (b1 && b2) || (carry && (b1 != b2)); + } + if (carry) + for (size_t i = 0; i < width_; ++i) + bits_[lsb + i] = true; + return !carry; +} + +bool counter_vector::decrement(size_t cell, size_t value) { + assert(cell < size()); + assert(value != 0); + value = ~value + 1; // A - B := A + ~B + 1 + bool carry = false; + size_t lsb = cell * width_; + for (size_t i = 0; i < width_; ++i) { + bool b1 = bits_[lsb + i]; + bool b2 = value & (1 << i); + bits_[lsb + i] = b1 ^ b2 ^ carry; + carry = (b1 && b2) || (carry && (b1 != b2)); + } + return carry; +} + +size_t counter_vector::count(size_t cell) const { + assert(cell < size()); + size_t cnt = 0, order = 1; + size_t lsb = cell * width_; + for (auto i = lsb; i < lsb + width_; ++i, order <<= 1) + if (bits_[i]) + cnt |= order; + return cnt; +} + +void counter_vector::set(size_t cell, size_t value) { + assert(cell < size()); + assert(value <= max()); + bitvector bits(width_, value); + auto lsb = cell * width_; + for (size_t i = 0; i < width_; ++i) + bits_[lsb + i] = bits[i]; +} + +void counter_vector::clear() { + bits_.reset(); +} + +size_t counter_vector::size() const { + return bits_.size() / width_; +} + +size_t counter_vector::max() const { + using limits = std::numeric_limits; + return limits::max() >> (limits::digits - width()); +} + +size_t counter_vector::width() const { + return width_; +} + +} // namespace bf diff --git a/external/libbf/src/hash.cpp b/external/libbf/src/hash.cpp new file mode 100644 index 0000000..00f1e03 --- /dev/null +++ b/external/libbf/src/hash.cpp @@ -0,0 +1,57 @@ +#include + +#include + +namespace bf { + +default_hash_function::default_hash_function(size_t seed) : h3_(seed) { +} + +size_t default_hash_function::operator()(object const& o) const { + // FIXME: fall back to a generic universal hash function (e.g., HMAC/MD5) for + // too large objects. + if (o.size() > max_obj_size) + throw std::runtime_error("object too large"); + return o.size() == 0 ? 0 : h3_(o.data(), o.size()); +} + +default_hasher::default_hasher(std::vector fns) + : fns_(std::move(fns)) { +} + +std::vector default_hasher::operator()(object const& o) const { + std::vector d(fns_.size()); + for (size_t i = 0; i < fns_.size(); ++i) + d[i] = fns_[i](o); + return d; +} + +double_hasher::double_hasher(size_t k, hash_function h1, hash_function h2) + : k_(k), h1_(std::move(h1)), h2_(std::move(h2)) { +} + +std::vector double_hasher::operator()(object const& o) const { + auto d1 = h1_(o); + auto d2 = h2_(o); + std::vector d(k_); + for (size_t i = 0; i < d.size(); ++i) + d[i] = d1 + i * d2; + return d; +} + +hasher make_hasher(size_t k, size_t seed, bool double_hashing) { + assert(k > 0); + std::minstd_rand0 prng(seed); + if (double_hashing) { + auto h1 = default_hash_function(prng()); + auto h2 = default_hash_function(prng()); + return double_hasher(k, std::move(h1), std::move(h2)); + } else { + std::vector fns(k); + for (size_t i = 0; i < k; ++i) + fns[i] = default_hash_function(prng()); + return default_hasher(std::move(fns)); + } +} + +} // namespace bf diff --git a/external/libbf/test/CMakeLists.txt b/external/libbf/test/CMakeLists.txt new file mode 100644 index 0000000..b84f7c7 --- /dev/null +++ b/external/libbf/test/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(bf) + +enable_testing() +add_executable(bf-test tests.cpp) +target_link_libraries(bf-test libbf ${CMAKE_THREAD_LIBS_INIT}) +add_test(unit ${CMAKE_CURRENT_BINARY_DIR}/bf-test) diff --git a/external/libbf/test/bf/CMakeLists.txt b/external/libbf/test/bf/CMakeLists.txt new file mode 100644 index 0000000..4ac31c7 --- /dev/null +++ b/external/libbf/test/bf/CMakeLists.txt @@ -0,0 +1,8 @@ +include_directories(BEFORE .) +set(bf_sources + bf.cc + configuration.cc + ) + +add_executable(bf ${bf_sources}) +target_link_libraries(bf libbf) diff --git a/external/libbf/test/bf/bf.cc b/external/libbf/test/bf/bf.cc new file mode 100644 index 0000000..20e6d21 --- /dev/null +++ b/external/libbf/test/bf/bf.cc @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include + +#include "configuration.h" + +#include "bf/all.hpp" + +using namespace util; +using namespace bf; + +trial run(config const& cfg) { + auto numeric = cfg.check("numeric"); + auto k = *cfg.as("hash-functions"); + auto cells = *cfg.as("cells"); + auto seed = *cfg.as("seed"); + auto fpr = *cfg.as("fp-rate"); + auto capacity = *cfg.as("capacity"); + auto width = *cfg.as("width"); + auto part = cfg.check("partition"); + auto double_hashing = cfg.check("double-hashing"); + auto d = *cfg.as("evict"); + + auto k2 = *cfg.as("hash-functions-2nd"); + auto cells2 = *cfg.as("cells-2nd"); + auto seed2 = *cfg.as("seed-2nd"); + auto width2 = *cfg.as("width-2nd"); + auto double_hashing2 = cfg.check("double-hashing-2nd"); + + auto const& type = *cfg.as("type"); + std::unique_ptr bf; + + if (type == "basic") { + if (fpr == 0 || capacity == 0) { + if (cells == 0) + return error{"need non-zero cells"}; + if (k == 0) + return error{"need non-zero k"}; + + auto h = make_hasher(k, seed, double_hashing); + bf.reset(new basic_bloom_filter(std::move(h), cells, part)); + } else { + assert(fpr != 0 && capacity != 0); + bf.reset(new basic_bloom_filter(fpr, capacity, seed, part)); + } + } else if (type == "counting") { + if (cells == 0) + return error{"need non-zero cells"}; + if (width == 0) + return error{"need non-zero cell width"}; + if (k == 0) + return error{"need non-zero k"}; + + auto h = make_hasher(k, seed, double_hashing); + bf.reset(new counting_bloom_filter(std::move(h), cells, width, part)); + } else if (type == "spectral-mi") { + if (cells == 0) + return error{"need non-zero cells"}; + if (width == 0) + return error{"need non-zero cell width"}; + if (k == 0) + return error{"need non-zero k"}; + + auto h = make_hasher(k, seed, double_hashing); + bf.reset(new spectral_mi_bloom_filter(std::move(h), cells, width, part)); + } else if (type == "spectral-rm") { + if (cells == 0) + return error{"need non-zero cells"}; + if (cells2 == 0) + return error{"need non-zero cells for 2nd bloom filter"}; + + if (width == 0) + return error{"need non-zero cell width"}; + if (width2 == 0) + return error{"need non-zero cell width for 2nd bloom filter"}; + + if (k == 0) + return error{"need non-zero k"}; + if (k2 == 0) + return error{"need non-zero k for second bloom filter"}; + + auto h1 = make_hasher(k, seed, double_hashing); + auto h2 = make_hasher(k2, seed2, double_hashing2); + bf.reset(new spectral_rm_bloom_filter(std::move(h1), cells, width, + std::move(h2), cells2, width2, part)); + } else if (type == "bitwise") { + if (cells == 0) + return error{"need non-zero cells"}; + if (k == 0) + return error{"need non-zero k"}; + + bf.reset(new bitwise_bloom_filter(k, cells, seed)); + } else if (type == "a2") { + if (cells == 0) + return error{"need non-zero cells"}; + if (capacity == 0) + return error{"need non-zero capacity"}; + if (k == 0) + return error{"need non-zero k"}; + + bf.reset(new a2_bloom_filter(k, cells, capacity, seed, seed2)); + } else if (type == "stable") { + if (cells == 0) + return error{"need non-zero cells"}; + if (k == 0) + return error{"need non-zero k"}; + + auto h = make_hasher(k, seed, double_hashing); + bf.reset(new stable_bloom_filter(std::move(h), cells, seed, d)); + } else { + return error{"invalid bloom filter type"}; + } + + std::string line; + auto input_file = *cfg.as("input"); + std::ifstream in{input_file}; + if (!in) + return error{"cannot read " + input_file}; + + in >> std::noskipws; + + while (std::getline(in, line)) { + if (line.empty()) + continue; + + auto p = line.data(); + while (*p) + if (*p == ' ' || *p == '\t') + return error{"whitespace in input not supported"}; + else + ++p; + + if (numeric) + bf->add(std::strtod(line.c_str(), nullptr)); + else + bf->add(line); + } + + size_t tn = 0, tp = 0, fp = 0, fn = 0; + size_t ground_truth; + std::string element; + auto query_file = *cfg.as("query"); + std::ifstream query{query_file}; + if (!query) + return error{"cannot read " + query_file}; + + std::cout << "TN TP FP FN G C E" << std::endl; + while (query >> ground_truth >> element) // uniq -c + { + size_t count; + if (numeric) + count = bf->lookup(std::strtod(element.c_str(), nullptr)); + else + count = bf->lookup(element); + + if (!query) + return error{"failed to parse element"}; + + if (count == 0 && ground_truth == 0) + ++tn; + else if (count == ground_truth) + ++tp; + else if (count > ground_truth) + ++fp; + else + ++fn; + + std::cout << tn << ' ' << tp << ' ' << fp << ' ' << fn << ' ' + << ground_truth << ' ' << count << ' '; + + if (numeric) + std::cout << std::strtod(element.c_str(), nullptr); + else + std::cout << element; + + std::cout << std::endl; + } + + return nil; +} + +int main(int argc, char* argv[]) { + auto cfg = config::parse(argc, argv); + if (!cfg) { + std::cerr << cfg.failure().msg() << ", try -h or --help" << std::endl; + return 1; + } + + if (argc < 2 || cfg->check("help") || cfg->check("advanced")) { + cfg->usage(std::cerr, cfg->check("advanced")); + return 0; + } + + if (!cfg->check("type")) { + std::cerr << "missing bloom filter type" << std::endl; + return 1; + } + + if (!cfg->check("input")) { + std::cerr << "missing input file" << std::endl; + return 1; + } + + if (!cfg->check("query")) { + std::cerr << "missing query file" << std::endl; + return 1; + } + + auto t = run(*cfg); + if (!t) { + std::cerr << t.failure().msg() << std::endl; + return 1; + } + + return 0; +} diff --git a/external/libbf/test/bf/configuration.cc b/external/libbf/test/bf/configuration.cc new file mode 100644 index 0000000..0e2b662 --- /dev/null +++ b/external/libbf/test/bf/configuration.cc @@ -0,0 +1,43 @@ +#include "configuration.h" + +#include + +std::string config::banner() const { + std::stringstream ss; + ss << " __ ____\n" + " / /_ / __/\n" + " / __ \\/ /_\n" + " / /_/ / __/\n" + "/_.___/_/\n"; + + return ss.str(); +} + +void config::initialize() { + auto& general = create_block("general options"); + general.add('i', "input", "input file").single(); + general.add('q', "query", "query file").single(); + general.add('h', "help", "display this help"); + general.add('n', "numeric", "interpret input as numeric values"); + + auto& bloomfilter = create_block("bloom filter options"); + bloomfilter + .add('t', "type", "basic|counting|spectral-mi|spectral-rm|bitwise|stable") + .single(); + bloomfilter.add('f', "fp-rate", "desired false-positive rate").init(0); + bloomfilter.add('c', "capacity", "max number of expected elements").init(0); + bloomfilter.add('m', "cells", "number of cells").init(0); + bloomfilter.add('w', "width", "bits per cells").init(1); + bloomfilter.add('p', "partition", "enable partitioning"); + bloomfilter.add('e', "evict", "number of cells to evict (stable)").init(0); + bloomfilter.add('k', "hash-functions", "number of hash functions").init(0); + bloomfilter.add('d', "double-hashing", "use double-hashing"); + bloomfilter.add('s', "seed", "specify a custom seed").init(0); + + auto& second = create_block("second bloom filter options"); + second.add('M', "cells-2nd", "number of cells").init(0); + second.add('W', "width-2nd", "bits per cells").init(1); + second.add('K', "hash-functions-2nd", "number of hash functions").init(0); + second.add('D', "double-hashing-2nd", "use double-hashing"); + second.add('S', "seed-2nd", "specify a custom seed").init(0); +} diff --git a/external/libbf/test/bf/configuration.h b/external/libbf/test/bf/configuration.h new file mode 100644 index 0000000..98b97c2 --- /dev/null +++ b/external/libbf/test/bf/configuration.h @@ -0,0 +1,14 @@ +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +#include "util/configuration.h" + +class config : public util::configuration { +public: + config() = default; + + void initialize(); + std::string banner() const; +}; + +#endif diff --git a/external/libbf/test/bf/util/configuration.h b/external/libbf/test/bf/util/configuration.h new file mode 100644 index 0000000..e0c2d05 --- /dev/null +++ b/external/libbf/test/bf/util/configuration.h @@ -0,0 +1,428 @@ +#ifndef UTIL_CONFIGURATION_H +#define UTIL_CONFIGURATION_H + +#include "util/trial.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace util { + +/// A command line parser and program option utility. +template +class configuration { +public: + struct error : util::error { + using util::error::error; + + error(std::string msg, char c) : util::error{msg + " (-" + c + ')'} { + } + error(std::string msg, std::string opt) + : util::error{msg + " (--" + opt + ')'} { + } + }; + + /// Initializes the configuration from a configuration file. + /// @param filename The name of the configuration file. + /// @returns An engaged trial on success. + static trial parse(std::string const& /* filename */) { + return error{"function not yet implemented"}; + } + + /// Initializes the configuration from command line parameters. + /// @argc The argc parameter from main. + /// @param argv The argv parameter from main. + static trial parse(int argc, char* argv[]) { + Derived cfg; + + // Although we don't like to use exceptions, for the "configuration DSL" we + // prefer a monadic style to declare our program and hence have to fall + // back to exceptions. + try { + cfg.initialize(); + } catch (std::logic_error const& e) { + return error{e.what()}; + } + + for (int i = 1; i < argc; ++i) { + std::vector values; + + std::string arg{argv[i]}; + auto val = cfg.optionize(arg); + if (!val) + return val.failure(); + else if (!val->empty()) + values.emplace_back(*val); + + auto o = cfg.find_option(arg); + if (o) + o->defaulted_ = false; + else + return error{"unknown option", arg}; + + // Consume everything until the next option. + while (i + 1 < argc) { + std::string next{argv[i + 1]}; + if (cfg.optionize(next)) + break; + + values.emplace_back(std::move(next)); + ++i; + } + + if (values.size() > o->max_vals_) + return error{"too many values", arg}; + + if (o->max_vals_ == 1 && values.size() != 1) + return error{"option value required", arg}; + + if (!values.empty()) + o->values_ = std::move(values); + } + + if (!cfg.verify()) + return error{"configuration verification failed"}; + + return {std::move(cfg)}; + } + + /// Checks whether the given option is set. + /// @param opt Name of the option to check. + /// @returns `true` if *opt* is set. + bool check(std::string const& opt) const { + auto o = find_option(opt); + return o && !o->defaulted_; + } + + /// Returns the value of the given option. + /// @param opt The name of the option. + /// @returns The option value. + trial get(std::string const& opt) const { + auto o = find_option(opt); + if (!o) + return error{"option does not exist"}; + if (o->values_.empty()) + return error{"option has no value"}; + if (o->max_vals_ > 1) + return error{"cannot get multi-value option"}; + + assert(o->values_.size() == 1); + return o->values_.front(); + } + + /// Retrieves an option as a specific type. + /// @tparam T The type to convert the option to. + /// @param opt The name of the option. + /// @returns The converted option value. + template + trial as(std::string const& opt) const { + auto o = find_option(opt); + if (!o) + return error{"unknown option", opt}; + + return dispatch(*o, std::is_same>()); + } + + /// Prints the usage into a given output stream. + /// @param sink The output stream to receive the configuration. + /// @param show_all Whether to also print invisible options. + void usage(std::ostream& sink, bool show_all = false) { + sink << derived()->banner() << "\n"; + + for (auto& b : blocks_) { + if (!show_all && !b.visible_) + continue; + + sink << "\n " << b.name_ << ":\n"; + + auto has_shortcut = + std::any_of(b.options_.begin(), b.options_.end(), + [](option const& o) { return o.shortcut_ != '\0'; }); + + auto max = std::max_element(b.options_.begin(), b.options_.end(), + [](option const& o1, option const& o2) { + return o1.name_.size() < o2.name_.size(); + }); + + auto max_len = max->name_.size(); + for (auto& opt : b.options_) { + sink << " --" << opt.name_; + sink << std::string(max_len - opt.name_.size(), ' '); + if (has_shortcut) + sink << (opt.shortcut_ ? std::string(" | -") + opt.shortcut_ : + " "); + + sink << " " << opt.description_ << "\n"; + } + } + + sink << std::endl; + } + +protected: + class option { + friend configuration; + + public: + option(std::string name, std::string desc, char shortcut = '\0') + : name_{std::move(name)}, + description_{std::move(desc)}, + shortcut_{shortcut} { + } + + template + option& init(T const& x) { + std::ostringstream ss; + ss << x; + values_.push_back(ss.str()); + max_vals_ = (values_.size() == 1) ? 1 : -1; + return *this; + } + + template + option& init(T const& head, Args... tail) { + init(head); + init(tail...); + return *this; + } + + option& multi(size_t n = -1) { + max_vals_ = n; + return *this; + } + + option& single() { + return multi(1); + } + + private: + std::string name_; + std::vector values_; + std::string description_; + size_t max_vals_ = 0; + bool defaulted_ = true; + char shortcut_ = '\0'; + }; + + /// A proxy class to add options to the configuration. + class block { + friend class configuration; + block(block const&) = delete; + block& operator=(block other) = delete; + + public: + /// Separates hierarchical options. + static constexpr char const* separator = "."; + + /// Move-constructs a block. + /// @param other The block to move. + block(block&& other) + : visible_{other.visible_}, + name_{std::move(other.name_)}, + prefix_{std::move(other.prefix_)}, + options_{std::move(other.options_)}, + config_{other.config_} { + other.visible_ = true; + other.config_ = nullptr; + } + + /// Adds a new option. + /// @param name The option name. + /// @param desc The option description. + option& add(std::string const& name, std::string desc) { + std::string fqn = qualify(name); + if (config_->find_option(fqn)) + throw std::logic_error{"duplicate option"}; + options_.emplace_back(std::move(fqn), std::move(desc)); + return options_.back(); + } + + /// Adds a new option with shortcut. + /// @param shortcut The shortcut of the option (single character). + /// @param name The option name. + /// @param desc The option description. + option& add(char shortcut, std::string const& name, std::string desc) { + if (config_->shortcuts_.count({shortcut})) + throw std::logic_error{"duplicate shortcut"}; + std::string fqn = qualify(name); + config_->shortcuts_.insert({{shortcut}, fqn}); + if (config_->find_option(fqn)) + throw std::logic_error{"duplicate option"}; + options_.emplace_back(std::move(fqn), std::move(desc), shortcut); + return options_.back(); + } + + /// Sets the visibility of this block when displaying the usage. + bool visible() const { + return visible_; + } + + /// Sets the visibility of this block when displaying the usage. + void visible(bool flag) { + visible_ = flag; + } + + private: + block(std::string name, std::string prefix, configuration* config) + : name_{std::move(name)}, prefix_{std::move(prefix)}, config_{config} { + } + + std::string qualify(std::string const& name) const { + return prefix_.empty() ? name : prefix_ + separator + name; + } + + bool visible_ = true; + std::string name_; + std::string prefix_; + std::vector