├── .github └── workflows │ └── build_wheels.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── compare.png ├── example.png ├── example.py ├── executable ├── CMakeLists.txt └── main.cpp ├── include └── dbscan │ ├── IO.h │ ├── algo.h │ ├── bruteforce.h │ ├── capi.h │ ├── cell.h │ ├── coreBccp.h │ ├── geometryIO.h │ ├── grid.h │ ├── kdNode.h │ ├── kdTree.h │ ├── pbbs │ ├── gettime.h │ ├── ndHash.h │ ├── parallel.h │ ├── parseCommandLine.h │ ├── quickSort.h │ ├── sampleSort.h │ ├── scheduler.h │ ├── sequence.h │ ├── transpose.h │ ├── unionFind.h │ ├── utils.h │ └── work_stealing_job.h │ ├── point.h │ └── shared.h ├── pyproject.toml ├── pythonmodule └── __init__.py ├── setup.py ├── src ├── Caller.cpp ├── Caller.h ├── capi.cpp ├── dbscan └── dbscanmodule.cpp └── test ├── CMakeLists.txt ├── CMakeLists.txt.in ├── dbscan_test.cpp ├── grid_test.cpp └── test_python_module.py /.github/workflows/build_wheels.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/pypa/cibuildwheel/blob/main/examples/github-deploy.yml 2 | # except no Windows 3 | name: build 4 | 5 | # Build on every branch push, tag push, and pull request change: 6 | on: [push, pull_request] 7 | # Alternatively, to publish when a (published) GitHub Release is created, use the following: 8 | # on: 9 | # push: 10 | # pull_request: 11 | # release: 12 | # types: 13 | # - published 14 | 15 | jobs: 16 | build_and_test: 17 | name: Build executable and run test 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Build 24 | run: | 25 | mkdir build 26 | cd build 27 | cmake .. 28 | make -j 29 | 30 | - name: Test 31 | run: | 32 | cd build 33 | ctest --no-tests=error --output-on-failure 34 | 35 | - uses: actions/upload-artifact@v3 36 | with: 37 | name: executable 38 | path: ./build/executable/dbscan 39 | 40 | generate_matrix: 41 | name: Generate Matrix 42 | runs-on: ubuntu-latest 43 | 44 | outputs: 45 | OSES: ${{ steps.development.outputs.OSES || steps.production.outputs.OSES }} 46 | CIBW_ARCHS_LINUX: ${{ steps.development.outputs.CIBW_ARCHS_LINUX || steps.production.outputs.CIBW_ARCHS_LINUX }} 47 | 48 | steps: 49 | - name: Development 50 | id: development 51 | if: "!startsWith(github.ref, 'refs/tags/v')" 52 | run: | 53 | echo 'OSES=["ubuntu-latest"]' >> $GITHUB_OUTPUT 54 | echo 'CIBW_ARCHS_LINUX="auto"' >> $GITHUB_OUTPUT 55 | 56 | - name: Production 57 | id: production 58 | if: startsWith(github.ref, 'refs/tags/v') 59 | run: | 60 | echo 'OSES=["ubuntu-latest", "macos-latest", "windows-latest"]' >> $GITHUB_OUTPUT 61 | echo 'CIBW_ARCHS_LINUX="auto aarch64"' >> $GITHUB_OUTPUT 62 | 63 | build_wheels: 64 | needs: [generate_matrix] 65 | name: Build wheels on ${{ matrix.os }} 66 | runs-on: ${{ matrix.os }} 67 | if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') 68 | strategy: 69 | matrix: 70 | os: ${{ fromJson(needs.generate_matrix.outputs.OSES) }} 71 | 72 | steps: 73 | - uses: actions/checkout@v3 74 | with: 75 | fetch-depth: 0 76 | 77 | - name: Set up QEMU 78 | if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/v') 79 | uses: docker/setup-qemu-action@v2 80 | with: 81 | platforms: all 82 | 83 | - name: Build wheels 84 | uses: pypa/cibuildwheel@v2.11.2 85 | env: 86 | CIBW_ARCHS_MACOS: "x86_64 arm64" 87 | CIBW_ARCHS_LINUX: ${{ fromJson(needs.generate_matrix.outputs.CIBW_ARCHS_LINUX) }} 88 | CIBW_TEST_REQUIRES: pytest 89 | CIBW_TEST_EXTRAS: "py36" 90 | CIBW_TEST_COMMAND: "pytest {package}/test" 91 | 92 | - uses: actions/upload-artifact@v3 93 | with: 94 | name: wheels 95 | path: ./wheelhouse/*.whl 96 | 97 | build_sdist: 98 | name: Build source distribution 99 | runs-on: ubuntu-latest 100 | if: startsWith(github.ref, 'refs/tags/v') 101 | steps: 102 | - uses: actions/checkout@v3 103 | with: 104 | fetch-depth: 0 105 | 106 | - name: Build sdist 107 | run: pipx run build --sdist 108 | 109 | - uses: actions/upload-artifact@v3 110 | with: 111 | name: wheels 112 | path: dist/*.tar.gz 113 | 114 | upload_pypi: 115 | needs: [build_wheels, build_sdist] 116 | runs-on: ubuntu-latest 117 | # upload to PyPI on every tag starting with 'v' 118 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 119 | # alternatively, to publish when a GitHub Release is created, use the following rule: 120 | # if: github.event_name == 'release' && github.event.action == 'published' 121 | steps: 122 | - uses: actions/download-artifact@v3 123 | with: 124 | # unpacks default artifact into dist/ 125 | # if `name: artifact` is omitted, the action will create extra parent dir 126 | name: wheels 127 | path: dist 128 | 129 | - uses: pypa/gh-action-pypi-publish@v1.5.0 130 | with: 131 | user: __token__ 132 | password: ${{ secrets.PYPI_API_TOKEN }} 133 | # To test: repository_url: https://test.pypi.org/legacy/ 134 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Temporary files 35 | *.*~ 36 | 37 | # Build files 38 | build/ 39 | dbscan/build/ 40 | .DS_Store 41 | dbscan.egg-info/ 42 | __pycache__ 43 | pythonmodule/_version.py 44 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(dbscan) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | set(CMAKE_CXX_FLAGS "-std=c++17 -pthread -O3 -fPIC") 9 | 10 | add_subdirectory(executable) 11 | 12 | enable_testing() 13 | add_subdirectory(test) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Yiqiu Wang, Yan Gu, Julian Shun 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include include * 2 | global-exclude *.py[co] .DS_Store 3 | exclude src/dbscan 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Theoretically-Efficient and Practical Parallel DBSCAN 2 | 3 | [![arXiv](https://img.shields.io/badge/arXiv-1912.06255-b31b1b.svg)](https://arxiv.org/abs/1912.06255) 4 | [![build](https://github.com/wangyiqiu/dbscan-python/actions/workflows/build_wheels.yml/badge.svg)](https://github.com/wangyiqiu/dbscan-python/actions/workflows/build_wheels.yml) 5 | 6 | ## Overview 7 | 8 | This repository hosts fast parallel DBSCAN clustering code for low dimensional Euclidean space. The code automatically uses the available threads on a parallel shared-memory machine to speedup DBSCAN clustering. It stems from a paper presented in SIGMOD'20: [Theoretically Efficient and Practical Parallel DBSCAN](https://dl.acm.org/doi/10.1145/3318464.3380582). 9 | 10 | Our software on 1 thread is on par with all serial state-of-the-art DBSCAN packages, and provides additional speedup via multi-threading. Below, we show a simple benchmark comparing our code with the DBSCAN implementation of Sklearn, tested on a 6-core computer with 2-way hyperthreading using a 2-dimensional data set with 50000 data points, where both implementation uses all available threads. Our implementation is more than **32x** faster. We also show a visualization of the clustering result on a smaller data set. 11 | 12 | Data sets with dimensionality 2 - 20 are supported by default, which can be modified by modifying ``DBSCAN_MIN_DIMS`` and ``DBSCAN_MAX_DIMS`` in the [source code](https://github.com/wangyiqiu/dbscan-python/blob/master/include/dbscan/capi.h). 13 | 14 |

15 | timing 16 | example 17 |

18 | 19 | ## Tutorial 20 | 21 | ### Option 1: Use the Python binding 22 | 23 | There are two ways to install it: 24 | 25 | * Install it using PyPI: ``pip3 install --user dbscan`` (you can find the wheels [here](https://pypi.org/project/dbscan/#files)). 26 | * To build from scratch for testing: ``pip3 install -e .`` from the project root directory. 27 | 28 | An example for using the Python module is provided in ``example.py``. It generates the clustering example above. 29 | 30 | #### Python API 31 | 32 | ``` 33 | from dbscan import DBSCAN 34 | labels, core_samples_mask = DBSCAN(X, eps=0.3, min_samples=10) 35 | ``` 36 | 37 | #### Input 38 | 39 | * ``X``: A 2-D Numpy array containing the input data points. The first dimension of ``X`` is the number of data points ``n``, and the second dimension is the data set dimensionality (the maximum supported dimensionality is 20). 40 | * ``eps``: The epsilon parameter (default 0.5). 41 | * ``min_samples``: The minPts parameter (default 5). 42 | 43 | #### Output 44 | 45 | * ``labels``: A length ``n`` Numpy array (``dtype=np.int32``) containing cluster IDs of the data points, in the same ordering as the input data. Noise points are given a pseudo-ID of ``-1``. 46 | * ``core_samples_mask``: A length ``n`` Numpy array (``dtype=np.bool``) masking the core points, in the same ordering as the input data. 47 | 48 | We provide a complete example below that generates a toy data set, computes the DBSCAN clustering, and visualizes the result as shown in the plot above. 49 | 50 | ``` 51 | import numpy as np 52 | from sklearn.datasets import make_blobs 53 | from sklearn.preprocessing import StandardScaler 54 | 55 | # ############################################################################# 56 | # Generate sample data 57 | centers = [[1, 1], [-1, -1], [1, -1]] 58 | X, labels_true = make_blobs(n_samples=750, centers=centers, cluster_std=0.4, 59 | random_state=0) 60 | X = StandardScaler().fit_transform(X) 61 | 62 | # ############################################################################# 63 | # Compute DBSCAN 64 | 65 | # direct call of the C API: 66 | from dbscan import DBSCAN 67 | labels, core_samples_mask = DBSCAN(X, eps=0.3, min_samples=10) 68 | 69 | # OR calling our sklearn API: 70 | # from dbscan import sklDBSCAN as DBSCAN 71 | # db = DBSCAN(eps=0.3, min_samples=10).fit(X) 72 | # core_samples_mask = np.zeros_like(db.labels_, dtype=bool) 73 | # core_samples_mask[db.core_sample_indices_] = True 74 | # labels = db.labels_ 75 | 76 | # ############################################################################# 77 | # Plot result 78 | import matplotlib.pyplot as plt 79 | 80 | n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0) 81 | n_noise_ = list(labels).count(-1) 82 | # Black removed and is used for noise instead. 83 | unique_labels = set(labels) 84 | colors = [plt.cm.Spectral(each) 85 | for each in np.linspace(0, 1, len(unique_labels))] 86 | for k, col in zip(unique_labels, colors): 87 | if k == -1: 88 | # Black used for noise. 89 | col = [0, 0, 0, 1] 90 | 91 | class_member_mask = (labels == k) 92 | 93 | xy = X[class_member_mask & core_samples_mask] 94 | plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col), 95 | markeredgecolor='k', markersize=14) 96 | 97 | xy = X[class_member_mask & ~core_samples_mask] 98 | plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col), 99 | markeredgecolor='k', markersize=6) 100 | 101 | plt.title('Estimated number of clusters: %d' % n_clusters_) 102 | plt.show() 103 | ``` 104 | 105 | ### Option 2: Use the binary executable 106 | 107 | Compile and run the program: 108 | 109 | ``` 110 | mkdir build 111 | cd build 112 | cmake .. 113 | cd executable 114 | make -j # this will take a while 115 | ./dbscan -eps 0.1 -minpts 10 -o clusters.txt 116 | ``` 117 | 118 | The `` can be any CSV-like point data file, where each line contains a data point -- see an example [here](https://github.com/wangyiqiu/hdbscan/blob/main/example-data.csv). The data file can be either with or without header. The cluster output `clusters.txt` will contain a cluster ID on each line (other than the first-line header), giving a cluster assignment in the same ordering as the input file. A noise point will have a cluster ID of `-1`. 119 | 120 | ### Option 3: Include directly in your own C++ program 121 | 122 | Create your own caller header and source file by instantiating the DBSCAN template function in "dbscan/algo.h". 123 | 124 | dbscan.h: 125 | ```c++ 126 | template 127 | int DBSCAN(int n, double* PF, double epsilon, int minPts, bool* coreFlagOut, int* coreFlag, int* cluster); 128 | 129 | // equivalent to 130 | // int DBSCAN(intT n, floatT PF[n][dim], double epsilon, intT minPts, bool coreFlagOut[n], intT coreFlag[n], intT cluster[n]) 131 | // if C++ syntax was a little more flexible 132 | 133 | template<> 134 | int DBSCAN<3>(int n, double* PF, double epsilon, int minPts, bool* coreFlagOut, int* coreFlag, int* cluster); 135 | ``` 136 | 137 | dbscan.cpp: 138 | ```c++ 139 | #include "dbscan/algo.h" 140 | #include "dbscan.h" 141 | ``` 142 | 143 | Calling the instantiated function: 144 | ```c++ 145 | int n = ...; // number of data points 146 | double data[n][3] = ...; // data points 147 | int labels[n]; // label ids get saved here 148 | bool core_samples[n]; // a flag determining whether or not the sample is a core sample is saved here 149 | { 150 | int ignore[n]; 151 | DBSCAN<3>(n, (void*)data, 70, 100, core_samples, ignore, labels); 152 | } 153 | ``` 154 | 155 | Doing this will only compile the function for the number of dimensions that you want, which saves on compilation time. 156 | 157 | You can also include the "dbscan/capi.h" and define your own ``DBSCAN_MIN_DIMS`` and ``DBSCAN_MAX_DIMS`` macros the same way the Python extension uses it. The function exported has the following signature. 158 | ```c++ 159 | extern "C" int DBSCAN(int dim, int n, double* PF, double epsilon, int minPts, bool* coreFlag, int* cluster); 160 | ``` 161 | 162 | Right now, the only two files that are guaranteed to remain in the C/C++ API are "dbscan/algo.h" and "dbscan/capi.h" and the functions named DBSCAN within. 163 | 164 | ## Citation 165 | 166 | If you use our work in a publication, we would appreciate citations: 167 | 168 | @inproceedings{wang2020theoretically, 169 | author = {Wang, Yiqiu and Gu, Yan and Shun, Julian}, 170 | title = {Theoretically-Efficient and Practical Parallel DBSCAN}, 171 | year = {2020}, 172 | isbn = {9781450367356}, 173 | publisher = {Association for Computing Machinery}, 174 | address = {New York, NY, USA}, 175 | url = {https://doi.org/10.1145/3318464.3380582}, 176 | doi = {10.1145/3318464.3380582}, 177 | booktitle = {Proceedings of the 2020 ACM SIGMOD International Conference on Management of Data}, 178 | pages = {2555–2571}, 179 | numpages = {17}, 180 | keywords = {parallel algorithms, spatial clustering, DBScan}, 181 | location = {Portland, OR, USA}, 182 | series = {SIGMOD ’20} 183 | } 184 | -------------------------------------------------------------------------------- /compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangyiqiu/dbscan-python/42e1e719c2aa363c8991a0ada8e52a32b8702b15/compare.png -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangyiqiu/dbscan-python/42e1e719c2aa363c8991a0ada8e52a32b8702b15/example.png -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from sklearn.datasets import make_blobs 4 | from sklearn.preprocessing import StandardScaler 5 | 6 | # ############################################################################# 7 | # Generate sample data 8 | centers = [[1, 1], [-1, -1], [1, -1]] 9 | X, labels_true = make_blobs(n_samples=750, centers=centers, cluster_std=0.4, 10 | random_state=0) 11 | X = StandardScaler().fit_transform(X) 12 | 13 | # ############################################################################# 14 | # Compute DBSCAN 15 | 16 | # direct call of the C API: 17 | from dbscan import DBSCAN 18 | labels, core_samples_mask = DBSCAN(X, eps=0.3, min_samples=10) 19 | print(labels.dtype) 20 | print(core_samples_mask.dtype) 21 | 22 | # OR calling our sklearn API: 23 | # from dbscan import sklDBSCAN as DBSCAN 24 | # db = DBSCAN(eps=0.3, min_samples=10).fit(X) 25 | # core_samples_mask = np.zeros_like(db.labels_, dtype=bool) 26 | # core_samples_mask[db.core_sample_indices_] = True 27 | # labels = db.labels_ 28 | 29 | # ############################################################################# 30 | # Metrics 31 | 32 | from sklearn import metrics 33 | 34 | # Number of clusters in labels, ignoring noise if present. 35 | n_clusters_ = len(set(labels)) - (1 if -1 in labels else 0) 36 | n_noise_ = list(labels).count(-1) 37 | 38 | print('Estimated number of clusters: %d' % n_clusters_) 39 | print('Estimated number of noise points: %d' % n_noise_) 40 | print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels_true, labels)) 41 | print("Completeness: %0.3f" % metrics.completeness_score(labels_true, labels)) 42 | print("V-measure: %0.3f" % metrics.v_measure_score(labels_true, labels)) 43 | print("Adjusted Rand Index: %0.3f" 44 | % metrics.adjusted_rand_score(labels_true, labels)) 45 | print("Adjusted Mutual Information: %0.3f" 46 | % metrics.adjusted_mutual_info_score(labels_true, labels)) 47 | print("Silhouette Coefficient: %0.3f" 48 | % metrics.silhouette_score(X, labels)) 49 | 50 | # ############################################################################# 51 | # Plot result 52 | import matplotlib.pyplot as plt 53 | 54 | # Black removed and is used for noise instead. 55 | unique_labels = set(labels) 56 | colors = [plt.cm.Spectral(each) 57 | for each in np.linspace(0, 1, len(unique_labels))] 58 | for k, col in zip(unique_labels, colors): 59 | if k == -1: 60 | # Black used for noise. 61 | col = [0, 0, 0, 1] 62 | 63 | class_member_mask = (labels == k) 64 | 65 | xy = X[class_member_mask & core_samples_mask] 66 | plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col), 67 | markeredgecolor='k', markersize=14) 68 | 69 | xy = X[class_member_mask & ~core_samples_mask] 70 | plt.plot(xy[:, 0], xy[:, 1], 'o', markerfacecolor=tuple(col), 71 | markeredgecolor='k', markersize=6) 72 | 73 | plt.title('Estimated number of clusters: %d' % n_clusters_) 74 | plt.show() 75 | -------------------------------------------------------------------------------- /executable/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(dbscan) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | set(CMAKE_CXX_FLAGS "-std=c++17 -pthread -O3 -fPIC") 9 | 10 | add_executable(dbscan main.cpp ../src/capi.cpp) 11 | target_include_directories(dbscan PRIVATE ../src/) 12 | -------------------------------------------------------------------------------- /executable/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "dbscan/capi.h" 4 | #include "dbscan/point.h" 5 | #include "dbscan/geometryIO.h" 6 | #include "dbscan/pbbs/parallel.h" 7 | #include "dbscan/pbbs/parseCommandLine.h" 8 | 9 | 10 | int main(int argc, char* argv[]) { 11 | parlay::internal::start_scheduler(); 12 | 13 | commandLine P(argc,argv,"[-o ] [-eps ] [-minpts ] "); 14 | char* iFile = P.getArgument(0); 15 | char* oFile = P.getOptionValue("-o"); 16 | size_t rounds = P.getOptionIntValue("-r",1); 17 | double p_epsilon = P.getOptionDoubleValue("-eps",1); 18 | size_t p_minpts = P.getOptionIntValue("-minpts",1); 19 | double p_rho = P.getOptionDoubleValue("-rho",-1); 20 | 21 | int dim = readHeader(iFile); 22 | _seq PIn = readDoubleFromFile(iFile, dim); 23 | 24 | bool* coreFlag = new bool[PIn.n / dim]; 25 | int* cluster = new int[PIn.n / dim]; 26 | double* data = PIn.A; 27 | 28 | if (DBSCAN(dim, PIn.n / dim, data, p_epsilon, p_minpts, coreFlag, cluster)) 29 | cout << "Error: dimension >20 is not supported." << endl; 30 | 31 | if (oFile != NULL) { 32 | writeArrayToFile("cluster-id", cluster, PIn.n / dim, oFile); 33 | } 34 | 35 | PIn.del(); 36 | delete[] coreFlag; 37 | delete[] cluster; 38 | 39 | parlay::internal::stop_scheduler(); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /include/dbscan/IO.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2011 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "pbbs/parallel.h" 30 | #include "pbbs/sequence.h" 31 | 32 | namespace benchIO { 33 | using namespace std; 34 | 35 | // A structure that keeps a sequence of strings all allocated from 36 | // the same block of memory 37 | struct words { 38 | long n; // total number of characters 39 | char* Chars; // array storing all strings 40 | long m; // number of substrings 41 | char** Strings; // pointers to strings (all should be null terminated) 42 | words() {} 43 | words(char* C, long nn, char** S, long mm) 44 | : Chars(C), n(nn), Strings(S), m(mm) {} 45 | void del() {free(Chars); free(Strings);} 46 | }; 47 | 48 | inline bool isSpace(char c) { 49 | switch (c) { 50 | case '\r': 51 | case '\t': 52 | case '\n': 53 | case ',': 54 | case 0: 55 | case ' ' : return true; 56 | default : return false; 57 | } 58 | } 59 | 60 | inline bool isComma(char c) { 61 | switch (c) { 62 | case '\r': 63 | case '\t': 64 | case '\n': 65 | case ',': 66 | case 0: 67 | case ' ' : return true; 68 | default : return false; 69 | } 70 | } 71 | 72 | struct toLong { long operator() (bool v) {return (long) v;} }; 73 | 74 | // parallel code for converting a string to words 75 | words stringToWords(char *Str, long n) { 76 | par_for(long i=0; i Off = sequence::packIndex(FL, n); 86 | long m = Off.n; 87 | long *offsets = Off.A; 88 | 89 | // pointer to each start of word 90 | char **SA = newA(char*, m); 91 | par_for(long j=0; j Off = sequence::packIndex(FL, n); 124 | long m = Off.n; 125 | long *offsets = Off.A; 126 | 127 | // pointer to each start of word 128 | char **SA = newA(char*, m); 129 | par_for(long j=0; j 167 | inline int xToStringLen(pair a) { 168 | return xToStringLen(a.first) + xToStringLen(a.second) + 1; 169 | } 170 | template 171 | inline void xToString(char* s, pair a) { 172 | int l = xToStringLen(a.first); 173 | xToString(s,a.first); 174 | s[l] = ' '; 175 | xToString(s+l+1,a.second); 176 | } 177 | 178 | struct notZero { bool operator() (char A) {return A > 0;}}; 179 | 180 | template 181 | _seq arrayToString(T* A, long n) { 182 | long* L = newA(long,n); 183 | {par_for(long i=0; i(),(long) 0); 185 | char* B = newA(char,m); 186 | par_for(long j=0; j(C,mm); 200 | } 201 | 202 | template 203 | void writeArrayToStream(ofstream& os, T* A, long n) { 204 | long BSIZE = 1000000; 205 | long offset = 0; 206 | while (offset < n) { 207 | // Generates a string for a sequence of size at most BSIZE 208 | // and then wrties it to the output stream 209 | _seq S = arrayToString(A+offset,min(BSIZE,n-offset)); 210 | os.write(S.A, S.n); 211 | S.del(); 212 | offset += BSIZE; 213 | } 214 | } 215 | 216 | template 217 | int writeArrayToFile(string header, T* A, long n, char* fileName) { 218 | ofstream file (fileName, ios::out | ios::binary); 219 | if (!file.is_open()) { 220 | std::cout << "Unable to open file: " << fileName << std::endl; 221 | return 1; 222 | } 223 | file << header << endl; 224 | writeArrayToStream(file, A, n); 225 | file.close(); 226 | return 0; 227 | } 228 | 229 | _seq readStringFromFile(char *fileName) { 230 | ifstream file (fileName, ios::in | ios::binary | ios::ate); 231 | if (!file.is_open()) { 232 | std::cout << "Unable to open file: " << fileName << std::endl; 233 | abort(); 234 | } 235 | long end = file.tellg(); 236 | file.seekg (0, ios::beg); 237 | long n = end - file.tellg(); 238 | // initializes in parallel 239 | //char* bytes = newArray(n+1, (char) 0); 240 | char* bytes = newA(char, n+1); 241 | par_for(long i=0; i < n+1; i++) bytes[i] = 0; 242 | file.read (bytes,n); 243 | file.close(); 244 | return _seq(bytes,n); 245 | } 246 | 247 | string intHeaderIO = "sequenceInt"; 248 | 249 | template 250 | intT writeIntArrayToFile(intT* A, long n, char* fileName) { 251 | return writeArrayToFile(intHeaderIO, A, n, fileName); 252 | } 253 | 254 | template 255 | intT writeIntArrayToFile(long* A, long n, char* fileName) { 256 | return writeArrayToFile(intHeaderIO, A, n, fileName); 257 | } 258 | 259 | template 260 | _seq readIntArrayFromFile(char *fileName) { 261 | _seq S = readStringFromFile(fileName); 262 | words W = stringToWords(S.A, S.n); 263 | string header = (string) W.Strings[0]; 264 | if (header != intHeaderIO) { 265 | cout << "readIntArrayFromFile: bad input" << endl; 266 | abort(); 267 | } 268 | long n = W.m-1; 269 | intT* A = new intT[n]; 270 | par_for(long i=0; i(A,n); 274 | } 275 | }; 276 | -------------------------------------------------------------------------------- /include/dbscan/algo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "dbscan/point.h" 5 | #include "dbscan/shared.h" 6 | #include "dbscan/grid.h" 7 | #include "dbscan/coreBccp.h" 8 | // #include "dbscan/pbbs/gettime.h" 9 | #include "dbscan/pbbs/parallel.h" 10 | #include "dbscan/pbbs/sampleSort.h" 11 | #include "dbscan/pbbs/unionFind.h" 12 | 13 | // #define VERBOSE 14 | 15 | template 16 | int DBSCAN(intT n, floatT* PF, double epsilon, intT minPts, bool* coreFlagOut, intT* coreFlag, intT* cluster) { 17 | typedef point pointT; 18 | typedef grid gridT; 19 | typedef cell cellT; 20 | 21 | point* PRead = (point*)PF; 22 | 23 | #ifdef VERBOSE 24 | cout << "Input: " << n << " points, dimension " << dim << endl; 25 | printScheduler(); 26 | timing tt; tt.start(); 27 | timing t0; t0.start(); 28 | #endif 29 | 30 | floatT epsSqr = epsilon*epsilon; 31 | pointT pMin = pMinParallel(PRead, n); 32 | 33 | auto P = newA(pointT, n); 34 | // parallel_for(0, n, [&](intT i){P[i] = PRead[i];}); 35 | auto G = new gridT(n+1, pMin, epsilon/sqrt(dim)); 36 | auto I = newA(intT, n); 37 | G->insertParallel(PRead, P, n, I); 38 | #ifdef VERBOSE 39 | cout << "num-cell = " << G->numCell() << endl; 40 | cout << "compute-grid = " << t0.next() << endl; 41 | #endif 42 | //mark core 43 | parallel_for(0, n, [&](intT i) {coreFlag[i] = -1;}); 44 | 45 | auto isCore = [&](pointT *p) { 46 | coreFlag[p-P] = 1; 47 | return false; 48 | }; 49 | 50 | parallel_for(0, G->numCell(), [&](intT i) { 51 | cellT* c = G->getCell(i); 52 | if (c->size() >= minPts) c->pointMap(isCore); 53 | }); 54 | 55 | parallel_for(0, n, [&](intT i) { 56 | if (coreFlag[i] < 0) { 57 | intT count = 0; 58 | auto isCore = [&] (pointT *p) { 59 | if(count >= minPts) return true; 60 | if(p->distSqr(P[i]) <= epsSqr) {//todo sqrt opt 61 | count ++;} 62 | return false;}; 63 | G->nghPointMap(P[i].coordinate(), isCore); 64 | if (count >= minPts) coreFlag[i] = 1; 65 | else coreFlag[i] = 0; 66 | } 67 | }); 68 | #ifdef VERBOSE 69 | cout << "mark-core-time = " << t0.next() << endl; 70 | #endif 71 | //cluster core 72 | auto ccFlag = newA(intT, G->numCell()); 73 | parallel_for(0, G->numCell(), [&](intT i) { 74 | auto ci = G->getCell(i); 75 | ccFlag[i] = 0; 76 | auto hasCore = [&](pointT *p) { 77 | if (coreFlag[p-P]) { 78 | ccFlag[i] = 1; 79 | return true; 80 | } 81 | return false; 82 | }; 83 | ci->pointMap(hasCore); 84 | }); 85 | 86 | typedef kdTree treeT; 87 | auto trees = newA(treeT*, G->numCell()); 88 | parallel_for(0, G->numCell(), [&](intT i) {trees[i] = NULL;}); 89 | 90 | // auto degCmp = [&](intT i, intT j) { 91 | // return G->getCell(i)->size() < G->getCell(j)->size(); 92 | // }; 93 | // auto ordering = newA(intT, G->numCell()); 94 | // par_for(intT i=0; inumCell(); ++i) ordering[i] = i; 95 | //sampleSort(ordering, G->numCell(), degCmp); 96 | 97 | auto uf = unionFind(G->numCell()); 98 | 99 | parallel_for(0, G->numCell(), [&](intT i) { 100 | if (ccFlag[i]) { 101 | auto procTj = [&](cellT* cj) { 102 | intT j = cj - G->getCell(0); 103 | if (j < i && ccFlag[j] && 104 | uf.find(i) != uf.find(j)) { 105 | if(hasEdge(i, j, coreFlag, P, epsilon, G->getCell(0), trees)) { 106 | uf.link(i, j); 107 | } 108 | } 109 | return false; 110 | }; 111 | //G->nghCellMap(G->getCell(ordering[i]), procTj); 112 | G->nghCellMap(G->getCell(i), procTj); 113 | } 114 | }); 115 | 116 | parallel_for(0, G->numCell(), [&](intT i) { 117 | if (trees[i]) delete trees[i]; 118 | }); 119 | 120 | parallel_for(0, n, [&](intT i) {cluster[i] = -1;}); 121 | 122 | parallel_for(0, G->numCell(), [&](intT i) { 123 | auto cid = G->getCell(uf.find(i))->getItem() - P;//id of first point 124 | auto clusterCore = [&](pointT* p){ 125 | if (coreFlag[p - P]) 126 | cluster[p - P] = cid; 127 | return false; 128 | }; 129 | G->getCell(i)->pointMap(clusterCore); 130 | }); 131 | #ifdef VERBOSE 132 | cout << "cluster-core-time = " << t0.next() << endl; 133 | #endif 134 | //cluster border to closest core point 135 | parallel_for(0, n, [&](intT i) { 136 | if (!coreFlag[i]) { 137 | intT cid = -1; 138 | floatT cDistSqr = floatMax(); 139 | auto closestCore = [&] (pointT* p) { 140 | if (coreFlag[p-P]) { 141 | auto dist = p->distSqr(P[i]); 142 | if (dist <= epsSqr && dist < cDistSqr) { 143 | cDistSqr = dist; 144 | cid = cluster[p-P];} 145 | } 146 | return false;}; 147 | G->nghPointMap(P[i].coordinate(), closestCore); 148 | cluster[i] = cid; 149 | } 150 | }); 151 | #ifdef VERBOSE 152 | cout << "cluster-border-time = " << t0.next() << endl; 153 | cout << ">> total-clustering-time = " << tt.next() << endl; 154 | #endif 155 | uf.del(); 156 | free(ccFlag); 157 | free(trees); 158 | delete G; 159 | 160 | //improving cluster representation 161 | auto cluster2 = newA(intT, n); 162 | auto flag = newA(intT, n+1); 163 | parallel_for(0, n, [&](intT i){cluster2[i] = cluster[i];}); 164 | sampleSort(cluster, n, std::less()); 165 | 166 | flag[0] = 1; 167 | parallel_for(1, n, [&](intT i){ 168 | if (cluster[i] != cluster[i-1]) 169 | flag[i] = 1; 170 | else 171 | flag[i] = 0; 172 | }); 173 | flag[n] = sequence::prefixSum(flag, 0, n); 174 | 175 | // typedef pair eType; 176 | struct myPair { 177 | intT first; 178 | intT second; 179 | myPair(intT _first, intT _second): first(_first), second(_second) {} 180 | myPair(): first(-1), second(-1) {} 181 | inline bool operator==(myPair a) { 182 | if(a.first==first && a.second== second) 183 | return true; 184 | else 185 | return false; 186 | } 187 | }; 188 | 189 | typedef Table,intT> tableT; 190 | auto T = new tableT(n, hashSimplePair()); 191 | parallel_for(0, n, [&] (intT i) { 192 | if (flag[i] != flag[i+1]) { 193 | // T->insert(make_pair(cluster[i], flag[i])); 194 | T->insert(myPair(cluster[i], flag[i])); 195 | } 196 | }); 197 | 198 | if(T->find(-1).second < 0) { 199 | parallel_for(0, n, [&](intT i){ 200 | cluster2[i] = T->find(cluster2[i]).second; 201 | }); 202 | } else { 203 | parallel_for(0, n, [&](intT i){ 204 | if (cluster2[i] > 0) 205 | cluster2[i] = T->find(cluster2[i]).second-1; 206 | }); 207 | } 208 | 209 | //restoring order 210 | parallel_for(0, n, [&](intT i){ 211 | cluster[I[i]] = cluster2[i]; 212 | }); 213 | parallel_for(0, n, [&](intT i){ 214 | coreFlagOut[I[i]] = coreFlag[i]; 215 | }); 216 | 217 | free(I); 218 | free(cluster2); 219 | free(flag); 220 | T->del(); // Required to clean-up T's internals 221 | delete T; 222 | free(P); 223 | #ifdef VERBOSE 224 | cout << "output-time = " << tt.stop() << endl; 225 | #endif 226 | return 0; 227 | } 228 | -------------------------------------------------------------------------------- /include/dbscan/bruteforce.h: -------------------------------------------------------------------------------- 1 | // This code is part of the project "Theoretically Efficient and Practical 2 | // Parallel DBSCAN" 3 | // Copyright (c) 2020 Yiqiu Wang, Yan Gu, Julian Shun 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a 6 | // copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights (to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included 14 | // in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | #pragma once 25 | 26 | template 27 | intT* coreBF(pointT* P, intT n, floatT epsilon, intT minPts) { 28 | intT* coreFlag = newA(intT, n); 29 | 30 | parallel_for (0, n, [&](intT i) { 31 | coreFlag[i] = 0; 32 | for (intT j=0; j= minPts) { 41 | coreFlag[i] = 1; 42 | numCore ++; 43 | } else coreFlag[i] = 0; 44 | //cout << coreFlag[i] << " "; 45 | } 46 | //cout << endl << endl; 47 | cout << "bf-num-core = " << numCore << endl; 48 | return coreFlag; 49 | } 50 | 51 | 52 | template 53 | intT* clusterCoreBF(pointT* P, intT n, floatT epsilon, intT minPts, intT* coreFlag) { 54 | auto uf = unionFind(n); 55 | parallel_for (0, n, [&](intT i) { 56 | for (intT j=i+1; j 74 | void clusterBorderBF(pointT* P, intT n, floatT epsilon, intT minPts, intT* coreFlag, intT* clusterb) { 75 | floatT thresh = epsilon*epsilon; 76 | parallel_for(0, n, [&](intT i) { 77 | if (!coreFlag[i]) { 78 | intT cid = -1; 79 | floatT cDistSqr = floatMax(); 80 | for(intT j=0; j 1); 21 | static_assert(DBSCAN_MAX_DIMS >= DBSCAN_MIN_DIMS); 22 | 23 | // intT* DBSCAN(int dim, intT n, floatT* PF, floatT epsilon, intT minPts, bool* coreFlag, intT* cluster); 24 | 25 | // replace names from "dbscan/pbbs/parallel.h" with actual types 26 | 27 | extern "C" int DBSCAN(int dim, int n, double* PF, double epsilon, int minPts, bool* coreFlag, int* cluster); 28 | 29 | #else 30 | 31 | #if DBSCAN_MIN_DIMS <= 1 32 | #error DBSCAN_MIN_DIMS <= 1 33 | #endif 34 | 35 | #if DBSCAN_MAX_DIMS < DBSCAN_MIN_DIMS 36 | #error DBSCAN_MAX_DIMS < DBSCAN_MIN_DIMS 37 | #endif 38 | 39 | int DBSCAN(int dim, int n, double* PF, double epsilon, int minPts, bool* coreFlag, int* cluster); 40 | 41 | #ifndef __STDC_NO_VLA__ 42 | inline int DBSCAN_vla(int dim, int n, double PF[n][dim], double epsilon, int minPts, bool coreFlag[n], int cluster[n]) { 43 | return DBSCAN(dim, n, (void*)PF, epsilon, minPts, coreFlag, cluster); 44 | } 45 | #endif 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /include/dbscan/cell.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Yiqiu Wang 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights (to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #pragma once 23 | 24 | #include "shared.h" 25 | #include "point.h" 26 | #include "pbbs/ndHash.h" 27 | 28 | #ifdef USEJEMALLOC 29 | #include 30 | #define jeNewA(__E,__n) (__E*) je_custom_prefix_malloc((__n)*sizeof(__E)) 31 | #define jeFree(__E) je_custom_prefix_free(__E) 32 | #endif 33 | 34 | template struct grid; 35 | template struct cellHash; 36 | 37 | /** 38 | * A cell class that represents each box in the grid. 39 | */ 40 | template 41 | struct cell { 42 | typedef double floatT; 43 | typedef point geoPointT; 44 | typedef cell cellT; 45 | typedef Table,intT> tableT; 46 | typedef grid gridT; 47 | 48 | static const intT defaultCapacity = 10; 49 | static const intT resizeFactor = 2; 50 | 51 | objT *P=NULL; 52 | geoPointT coordP; 53 | intT numPoints; 54 | 55 | /** 56 | * Constructor 1, that initializes an empty cell. 57 | */ 58 | cell(): numPoints(0) {}; 59 | 60 | /** 61 | * Constructor 2, that initializes cell with external point array. 62 | * @param PP external point array. 63 | * @param nn size of PP. 64 | */ 65 | cell(objT* PP, intT nn): P(PP), numPoints(nn) {}; 66 | 67 | /** 68 | * Constructor 3, that only initializes the coordinate of the cell base on an external point. 69 | * @param coordPP external point. 70 | */ 71 | cell(geoPointT coordPP): coordP(coordPP), numPoints(0) {} 72 | 73 | cell(floatT* coordIn): numPoints(0) { 74 | for(intT i=0; i 121 | void pointMap(func& f) { 122 | for(intT i=0; i 134 | struct cellHash { 135 | typedef double floatT; 136 | typedef cell cellT; 137 | typedef hashFloatToCell hashFunc; 138 | typedef cellT* eType; 139 | typedef cellT* kType; 140 | 141 | hashFunc* hashF; 142 | cellT* e; 143 | 144 | cellHash(hashFunc* hashFF):hashF(hashFF) { 145 | e = new cellT();} 146 | 147 | cellHash(const cellHash& rhs) { 148 | hashF = rhs.hashF; 149 | e = new cellT(); 150 | *e = *rhs.e; 151 | } 152 | 153 | ~cellHash() { 154 | delete e; 155 | } 156 | 157 | eType empty() {return e;} 158 | 159 | kType getKey(eType v) {return v;} 160 | 161 | uintT hash(kType c) { 162 | return hashF->hash(c->coordinate()); 163 | } 164 | 165 | int cmp(kType c1, kType c2) { 166 | if (c1->isEmpty() || c2->isEmpty()) return 1; 167 | return hashF->compareCell(c1->coordinate(), c2->coordinate()); 168 | } 169 | 170 | inline int diffCell(floatT* c1, floatT* c2) {return hashF->compareCell(c1, c2);} 171 | 172 | bool replaceQ(eType c1, eType c2) {return 0;} 173 | 174 | bool cas(eType* p, eType o, eType n) { 175 | return std::atomic_compare_exchange_strong_explicit( 176 | reinterpret_cast*>(p), &o, n, std::memory_order_relaxed, std::memory_order_relaxed); 177 | } 178 | }; 179 | -------------------------------------------------------------------------------- /include/dbscan/coreBccp.h: -------------------------------------------------------------------------------- 1 | // This code is part of the project "Theoretically Efficient and Practical 2 | // Parallel DBSCAN" 3 | // Copyright (c) 2020 Yiqiu Wang, Yan Gu, Julian Shun 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a 6 | // copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights (to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included 14 | // in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | #ifndef BCCP_CORE_H 25 | #define BCCP_CORE_H 26 | 27 | #include "kdTree.h" 28 | #include "kdNode.h" 29 | #include "pbbs/parallel.h" 30 | #include "pbbs/utils.h" 31 | 32 | template 33 | inline void compBcpCoreHSerial(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, objT* P) { 34 | if (n1->nodeDistance(n2) > *r) return; 35 | 36 | if (n1->isLeaf() && n2->isLeaf()) {//basecase 37 | for (intT i=0; isize(); ++i) { 38 | for (intT j=0; jsize(); ++j) { 39 | auto pi = n1->getItem(i); 40 | auto pj = n2->getItem(j); 41 | if (coreFlag[pi - P] && coreFlag[pj - P]) { 42 | floatT dist = pi->dist(*pj); 43 | r[0] = min(r[0], dist); 44 | } 45 | } 46 | } 47 | } else {//recursive, todo consider call order, might help 48 | if (n1->isLeaf()) { 49 | if (n1->nodeDistance(n2->L()) < n1->nodeDistance(n2->R())) { 50 | compBcpCoreHSerial(n1, n2->L(), r, coreFlag, P); 51 | compBcpCoreHSerial(n1, n2->R(), r, coreFlag, P); 52 | } else { 53 | compBcpCoreHSerial(n1, n2->R(), r, coreFlag, P); 54 | compBcpCoreHSerial(n1, n2->L(), r, coreFlag, P); 55 | } 56 | } else if (n2->isLeaf()) { 57 | if (n2->nodeDistance(n1->L()) < n2->nodeDistance(n1->R())) { 58 | compBcpCoreHSerial(n2, n1->L(), r, coreFlag, P); 59 | compBcpCoreHSerial(n2, n1->R(), r, coreFlag, P); 60 | } else { 61 | compBcpCoreHSerial(n2, n1->R(), r, coreFlag, P); 62 | compBcpCoreHSerial(n2, n1->L(), r, coreFlag, P); 63 | } 64 | } else { 65 | pair ordering[4]; 66 | ordering[0] = make_pair(n2->L(), n1->L()); 67 | ordering[1] = make_pair(n2->R(), n1->L()); 68 | ordering[2] = make_pair(n2->L(), n1->R()); 69 | ordering[3] = make_pair(n2->R(), n1->R()); 70 | auto bbd = [&](pair p1, pair p2) { 71 | return p1.first->nodeDistance(p1.second) < p2.first->nodeDistance(p2.second);}; 72 | quickSortSerial(ordering, 4, bbd); 73 | for (intT o=0; o<4; ++o) { 74 | compBcpCoreHSerial(ordering[o].first, ordering[o].second, r, coreFlag, P);} 75 | } 76 | } 77 | } 78 | 79 | template 80 | inline void compBcpCoreHBase(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, objT* P) { 81 | if (n1->nodeDistance(n2) > *r) return; 82 | 83 | if (n1->isLeaf() && n2->isLeaf()) {//basecase 84 | for (intT i=0; isize(); ++i) { 85 | for (intT j=0; jsize(); ++j) { 86 | auto pi = n1->getItem(i); 87 | auto pj = n2->getItem(j); 88 | if (coreFlag[pi - P] && coreFlag[pj - P]) { 89 | floatT dist = pi->dist(*pj); 90 | utils::writeMin(r, dist); 91 | } 92 | } 93 | } 94 | } else {//recursive, todo consider call order, might help 95 | if (n1->isLeaf()) { 96 | if (n1->nodeDistance(n2->L()) < n1->nodeDistance(n2->R())) { 97 | compBcpCoreH(n1, n2->L(), r, coreFlag, P); 98 | compBcpCoreH(n1, n2->R(), r, coreFlag, P); 99 | } else { 100 | compBcpCoreH(n1, n2->R(), r, coreFlag, P); 101 | compBcpCoreH(n1, n2->L(), r, coreFlag, P); 102 | } 103 | } else if (n2->isLeaf()) { 104 | if (n2->nodeDistance(n1->L()) < n2->nodeDistance(n1->R())) { 105 | compBcpCoreH(n2, n1->L(), r, coreFlag, P); 106 | compBcpCoreH(n2, n1->R(), r, coreFlag, P); 107 | } else { 108 | compBcpCoreH(n2, n1->R(), r, coreFlag, P); 109 | compBcpCoreH(n2, n1->L(), r, coreFlag, P); 110 | } 111 | } else { 112 | pair ordering[4]; 113 | ordering[0] = make_pair(n2->L(), n1->L()); 114 | ordering[1] = make_pair(n2->R(), n1->L()); 115 | ordering[2] = make_pair(n2->L(), n1->R()); 116 | ordering[3] = make_pair(n2->R(), n1->R()); 117 | auto bbd = [&](pair p1, pair p2) { 118 | return p1.first->nodeDistance(p1.second) < p2.first->nodeDistance(p2.second);}; 119 | quickSortSerial(ordering, 4, bbd); 120 | for (intT o=0; o<4; ++o) { 121 | compBcpCoreH(ordering[o].first, ordering[o].second, r, coreFlag, P);} 122 | } 123 | } 124 | } 125 | 126 | template 127 | inline void compBcpCoreH(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, objT* P) { 128 | if (n1->nodeDistance(n2) > *r) return; 129 | 130 | if ((n1->isLeaf() && n2->isLeaf()) || (n1->size()+n2->size() < 2000)) { 131 | return compBcpCoreHBase(n1, n2, r, coreFlag, P); 132 | } else {//recursive, todo consider call order, might help 133 | if (n1->isLeaf()) { 134 | if (n1->nodeDistance(n2->L()) < n1->nodeDistance(n2->R())) { 135 | par_do([&](){compBcpCoreH(n1, n2->L(), r, coreFlag, P);}, 136 | [&](){compBcpCoreH(n1, n2->R(), r, coreFlag, P);}); 137 | } else { 138 | par_do([&](){compBcpCoreH(n1, n2->R(), r, coreFlag, P);}, 139 | [&](){compBcpCoreH(n1, n2->L(), r, coreFlag, P);}); 140 | } 141 | } else if (n2->isLeaf()) { 142 | if (n2->nodeDistance(n1->L()) < n2->nodeDistance(n1->R())) { 143 | par_do([&](){compBcpCoreH(n2, n1->L(), r, coreFlag, P);}, 144 | [&](){compBcpCoreH(n2, n1->R(), r, coreFlag, P);}); 145 | } else { 146 | par_do([&](){compBcpCoreH(n2, n1->R(), r, coreFlag, P);}, 147 | [&](){compBcpCoreH(n2, n1->L(), r, coreFlag, P);}); 148 | } 149 | } else { 150 | pair ordering[4]; 151 | ordering[0] = make_pair(n2->L(), n1->L()); 152 | ordering[1] = make_pair(n2->R(), n1->L()); 153 | ordering[2] = make_pair(n2->L(), n1->R()); 154 | ordering[3] = make_pair(n2->R(), n1->R()); 155 | auto bbd = [&](pair p1, pair p2) { 156 | return p1.first->nodeDistance(p1.second) < p2.first->nodeDistance(p2.second);}; 157 | quickSortSerial(ordering, 4, bbd); 158 | parallel_for (0, 4, [&](intT o) { 159 | compBcpCoreH(ordering[o].first, ordering[o].second, r, coreFlag, P);}, 1); 160 | } 161 | } 162 | } 163 | 164 | template 165 | inline bool hasEdge(intT n1, intT n2, intT* coreFlag, objT* P, floatT epsilon, cellT* cells, treeT** trees) { 166 | 167 | if (cells[n1].size() + cells[n2].size() <= 32) { 168 | floatT thresh = epsilon*epsilon; 169 | for (intT i=0; idistSqr(*pj) <= thresh) return true;} 175 | } 176 | } 177 | return false; 178 | } 179 | 180 | if (!trees[n1]) 181 | trees[n1] = new treeT(cells[n1].getItem(), cells[n1].size(), false);//todo allocation, parallel 182 | if (!trees[n2]) 183 | trees[n2] = new treeT(cells[n2].getItem(), cells[n2].size(), false);//todo allocation, parallel 184 | floatT r = floatMax(); 185 | compBcpCoreH(trees[n1]->rootNode(), trees[n2]->rootNode(), &r, coreFlag, P); 186 | return r <= epsilon; 187 | } 188 | 189 | #endif 190 | -------------------------------------------------------------------------------- /include/dbscan/geometryIO.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2011 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #pragma once 24 | 25 | #include 26 | #include "IO.h" 27 | #include "pbbs/parallel.h" 28 | #include "point.h" 29 | 30 | using namespace benchIO; 31 | 32 | namespace benchIO { 33 | using namespace std; 34 | 35 | void parseDouble(char** Str, double* a, intT n) { 36 | par_for (long i=0; i readDoubleFromFile(char* fname, int d) { 120 | _seq S = readStringFromFile(fname); 121 | words W = stringToWords(S.A, S.n); 122 | 123 | long n; 124 | double *P; 125 | 126 | if (isGenericHeader(W.Strings[0])) { 127 | n = W.m - 1; 128 | P = newA(double, n); 129 | parseDouble(W.Strings + 1, P, n); 130 | } else { 131 | n = W.m; 132 | P = newA(double, n); 133 | parseDouble(W.Strings, P, n); 134 | } 135 | 136 | W.del(); 137 | return _seq(P, n); 138 | } 139 | 140 | }; 141 | -------------------------------------------------------------------------------- /include/dbscan/grid.h: -------------------------------------------------------------------------------- 1 | // This code is part of the project "Theoretically Efficient and Practical 2 | // Parallel DBSCAN" 3 | // Copyright (c) 2020 Yiqiu Wang, Yan Gu, Julian Shun 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a 6 | // copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights (to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included 14 | // in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | #pragma once 25 | 26 | #include "cell.h" 27 | #include "point.h" 28 | #include "shared.h" 29 | #include "kdTree.h" 30 | #include "kdNode.h" 31 | #include "pbbs/sequence.h" 32 | #include "pbbs/ndHash.h" 33 | #include "pbbs/sampleSort.h" 34 | #include "pbbs/quickSort.h" 35 | #include "pbbs/parallel.h" 36 | 37 | //a less comparator based on grid 38 | template 39 | inline bool pointGridCmp(pointT p1, pointT p2, geoPointT pMin, floatT r) { 40 | for(int i=0; i xx2) return false; 45 | else return true;} 46 | } 47 | return false; 48 | } 49 | 50 | /** 51 | * A grid class, that puts dim-dimensional axis-aligned box cells on a point set. 52 | */ 53 | template 54 | struct grid { 55 | typedef grid gridT; 56 | typedef double floatT; 57 | typedef point geoPointT; 58 | typedef cell cellT; 59 | typedef hashFloatToCell cellHashT; 60 | typedef Table,intT> tableT; 61 | typedef Table,intT> objTableT; 62 | typedef kdTree treeT; 63 | // #ifdef USEJEMALLOC 64 | // typedef vector> cellBuf; 65 | // #else 66 | typedef vector cellBuf; 67 | //#endif 68 | 69 | static const bool noRandom = true; 70 | 71 | floatT r; 72 | geoPointT pMin; 73 | cellT* cells; 74 | intT numCells, cellCapacity; 75 | cellHashT* myHash=NULL;// generic hash function 76 | tableT* table=NULL; 77 | treeT* tree=NULL; 78 | intT totalPoints; 79 | cellBuf **nbrCache; 80 | 81 | /** 82 | * Grid constructor. 83 | * @param cellMax projected maximum number of points inserted. 84 | * @param pMinn global coordinate minimum. 85 | * @param r box cell size. 86 | */ 87 | grid(intT cellMax, geoPointT pMinn, floatT rr): 88 | r(rr), pMin(pMinn), cellCapacity(cellMax), totalPoints(0) { 89 | 90 | cells = newA(cellT, cellCapacity); 91 | nbrCache = newA(cellBuf*, cellCapacity); 92 | parallel_for(0, cellCapacity, [&](intT i) { 93 | nbrCache[i] = NULL; 94 | cells[i].init(); 95 | }); 96 | numCells = 0; 97 | 98 | myHash = new cellHashT(pMinn, r); 99 | table = new tableT(cellMax*2, cellHash(myHash));//todo load 100 | } 101 | 102 | ~grid() { 103 | free(cells); 104 | parallel_for(0, numCells, [&](intT i) { 105 | if(nbrCache[i]) delete nbrCache[i]; 106 | }); 107 | free(nbrCache); 108 | if(myHash) delete myHash; 109 | if(table) { 110 | table->del(); 111 | delete table;} 112 | if(tree) delete tree; 113 | } 114 | 115 | inline cellT* getCell(floatT* coord) { 116 | cellT bait = cellT(geoPointT(coord)); 117 | cellT* found = table->find(&bait); 118 | return found;} 119 | 120 | inline cellT* getCell(intT i) { 121 | return &cells[i];} 122 | 123 | inline intT numCell() {return numCells;} 124 | 125 | inline intT size() { 126 | return totalPoints; 127 | } 128 | 129 | template 130 | inline void nghPointMap(floatT* center, func& f) { 131 | auto bait = getCell(center);//center must be there 132 | if (!bait) { 133 | cout << "error, nghPointMap mapped to a non-existent point, abort" << endl; 134 | abort();} 135 | auto fStop = [&](){return false;}; 136 | auto fWrap = [&](cellT* nbr) { 137 | if (!nbr->isEmpty() 138 | && nbr->actualSize()>0) { 139 | for(intT jj=0;jjsize();++jj) { 140 | if(f(nbr->getItem(jj))) return true; 141 | } 142 | } 143 | return false;};//todo, optimize 144 | if (nbrCache[bait-cells]) { 145 | auto accum = nbrCache[bait-cells]; 146 | for (auto accum_i : *accum) { 147 | if(fWrap(accum_i)) break; 148 | } 149 | } else { 150 | floatT hop = sqrt(dim + 3) * 1.0000001; 151 | nbrCache[bait-cells] = tree->rangeNeighbor(bait, r * hop, fStop, fWrap, true, nbrCache[bait-cells]); 152 | } 153 | } 154 | 155 | template 156 | inline void nghCellMap(cellT* bait, func& f) { 157 | auto fStop = [&](){return false;}; 158 | auto fWrap = [&](cellT* cell){ 159 | if(!cell->isEmpty()) 160 | return f(cell); 161 | return false; 162 | }; 163 | if (nbrCache[bait-cells]) { 164 | auto accum = nbrCache[bait-cells]; 165 | for (auto accum_i : *accum) { 166 | if(fWrap(accum_i)) break; 167 | } 168 | } else { 169 | floatT hop = sqrt(dim + 3) * 1.0000001; 170 | nbrCache[bait-cells] = tree->rangeNeighbor(bait, r * hop, fStop, fWrap, true, nbrCache[bait-cells]); 171 | } 172 | } 173 | 174 | void insertParallel(objT* P, objT* PP, intT nn, intT* I, intT* flag=NULL) { 175 | if (nn==0) return; 176 | 177 | bool freeFlag=false; 178 | if (!flag) { 179 | flag=newA(intT, nn+1);//todo size 180 | freeFlag=true;} 181 | 182 | parallel_for(0, nn, [&](intT i){I[i] = i;}); 183 | auto ipLess = [&] (intT a, intT b) { 184 | return pointGridCmp(P[a], P[b], pMin, r);}; 185 | sampleSort(I, nn, ipLess); 186 | parallel_for(0, nn, [&](intT i){PP[i] = P[I[i]];}); 187 | 188 | flag[0] = 1; 189 | parallel_for(1, nn, [&](intT i) { 190 | if (table->hashStruct.diffCell(PP[i].coordinate(), PP[i-1].coordinate())) { 191 | flag[i] = 1; 192 | } else { 193 | flag[i] = 0;} 194 | }); 195 | 196 | numCells = sequence::prefixSum(flag, 0, nn); 197 | flag[nn] = numCells; 198 | 199 | if (numCells > cellCapacity) { 200 | cout << "error, grid insert exceeded cell capacity, abort()" << endl;abort();} 201 | 202 | parallel_for(0, nn, [&](intT i) { 203 | if (flag[i] != flag[i+1]) { 204 | auto c = &cells[flag[i]]; 205 | c->P = &PP[i]; 206 | c->computeCoord(pMin, r); 207 | table->insert(c); 208 | } 209 | }); 210 | parallel_for(0, numCells-1, [&](intT i) { 211 | cells[i].numPoints = cells[i+1].P - cells[i].P; 212 | }); 213 | cells[numCells-1].numPoints = &PP[nn] - cells[numCells-1].P; 214 | 215 | tree = new treeT(&cells[0], numCells, true); 216 | if(freeFlag) free(flag); 217 | } 218 | 219 | }; 220 | -------------------------------------------------------------------------------- /include/dbscan/kdNode.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Yiqiu Wang 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights (to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #pragma once 23 | 24 | template 25 | class kdNode { 26 | typedef double floatT; 27 | typedef point pointT; 28 | typedef kdNode nodeT; 29 | static const int spatialMedian = 1;//toggle spatial or object median 30 | static const int boxInclude = 0; 31 | static const int boxOverlap = 1; 32 | static const int boxExclude = 2; 33 | int k; 34 | pointT pMin, pMax; 35 | objT **items; 36 | intT n; 37 | nodeT* left; 38 | nodeT* right; 39 | nodeT* sib; 40 | 41 | inline void boundingBoxSerial() { 42 | pMin = pointT(items[0]->coordinate()); 43 | pMax = pointT(items[0]->coordinate()); 44 | for(intT i=0; icoordinate()); 46 | pMax.maxCoords(items[i]->coordinate()); 47 | }} 48 | 49 | inline void boundingBoxParallel() { 50 | // intT P = getWorkers()*8; 51 | static const intT P = 36 * 8; 52 | intT blockSize = (n+P-1)/P; 53 | pointT localMin[P]; 54 | pointT localMax[P]; 55 | for (intT i=0; icoordinate()); 57 | localMax[i] = pointT(items[0]->coordinate());} 58 | parallel_for(0, P, 59 | [&](intT p) { 60 | intT s = p*blockSize; 61 | intT e = min((intT)(p+1)*blockSize,n); 62 | for (intT j=s; jcoordinate()); 64 | localMax[p].maxCoords(items[j]->coordinate());} 65 | }); 66 | pMin = pointT(items[0]->coordinate()); 67 | pMax = pointT(items[0]->coordinate()); 68 | for(intT p=0; pcoordinate(k)>=xM) { 80 | while (items[rPt]->coordinate(k)>=xM && lPt < rPt) { 81 | rPt--; 82 | } 83 | if (lPt < rPt) { 84 | swap(items[lPt], items[rPt]); 85 | rPt--; } 86 | else { break;} 87 | } 88 | lPt++; 89 | } 90 | if (items[lPt]->coordinate(k) < xM) lPt++; 91 | return lPt; 92 | } 93 | 94 | inline intT splitItemParallel(floatT xM, objT **scratch, intT* flags) { 95 | if (n < 2) { 96 | cout << "error, kdTree splitting singleton, abort" << endl;abort();} 97 | parallel_for(0, n, 98 | [&](intT i) { 99 | if (items[i]->coordinate(k)pMax2[i]) exclude = true; 122 | if (pMax1[i]pMin2[i]) include = false; 123 | } 124 | if (exclude) return boxExclude; 125 | else if (include) return boxInclude; 126 | else return boxOverlap; 127 | } 128 | 129 | inline bool itemInBox(pointT pMin1, pointT pMax1, objT* item) { 130 | for(int i=0; icoordinate(i) || pMin1[i]>item->coordinate(i)) return false; 132 | } 133 | return true;} 134 | 135 | intT findWidest() { 136 | floatT xM = -1; 137 | for (int kk=0; kkxM) { 139 | xM = pMax[kk]-pMin[kk]; 140 | k = kk;}} 141 | return k; 142 | } 143 | 144 | void constructSerial(nodeT *space, intT leafSize) { 145 | boundingBoxSerial(); 146 | sib = NULL; 147 | if (n <= leafSize) { 148 | left = NULL; right = NULL; 149 | } else { 150 | if (!space[0].isEmpty() || !space[1].isEmpty()) { 151 | cout << "error, kdNode overwrite, abort" << endl;abort();} 152 | intT k = findWidest(); 153 | floatT xM = (pMax[k]+pMin[k])/2; 154 | 155 | intT median; 156 | if (spatialMedian) { 157 | median = splitItemSerial(xM); 158 | } else { 159 | auto splitK = [&](objT* a, objT* b) { 160 | return a->coordinate(k) < b->coordinate(k);}; 161 | median = ceil(n/2.0); 162 | nth_element(items, items+median, items + n, splitK); 163 | } 164 | 165 | if (median == 0 || median == n) {median = ceil(n/2.0);} 166 | space[0] = nodeT(items, median, space+1, leafSize); 167 | space[2*median-1] = nodeT(items+median, n-median, space+2*median, leafSize); 168 | left = space; 169 | right = space+2*median-1; 170 | left->sib = right; 171 | right->sib = left; 172 | } 173 | } 174 | 175 | void constructParallel(nodeT *space, objT** scratch, intT* flags, intT leafSize) { 176 | boundingBoxParallel(); 177 | sib = NULL; 178 | if (n <= leafSize) { 179 | left = NULL; right = NULL; 180 | } else { 181 | if (!space[0].isEmpty() || !space[1].isEmpty()) { 182 | cout << "error, kdNode overwrite, abort" << endl;abort();} 183 | intT k = findWidest(); 184 | floatT xM = (pMax[k]+pMin[k])/2; 185 | 186 | intT median; 187 | if (spatialMedian) { 188 | median = splitItemParallel(xM, scratch, flags); 189 | } else { 190 | auto splitK = [&](objT* a, objT* b) { 191 | return a->coordinate(k) < b->coordinate(k);}; 192 | median = ceil(n/2.0); 193 | nth_element(items, items+median, items + n, splitK);//todo 194 | } 195 | 196 | if (median == 0 || median == n) {median = (n/2.0);} 197 | par_do([&](){space[0] = nodeT(items, median, space+1, scratch, flags, leafSize);}, 198 | [&](){space[2*median-1] = nodeT(items+median, n-median, space+2*median, scratch+median, flags+median, leafSize);}); 199 | left = space; 200 | right = space+2*median-1; 201 | left->sib = right; 202 | right->sib = left; 203 | } 204 | } 205 | 206 | public: 207 | inline nodeT* L() {return left;} 208 | inline nodeT* R() {return right;} 209 | inline nodeT* siblin() {return sib;}//todo 210 | inline pair getBox() {return make_pair(pMin, pMax);} 211 | inline objT** getItems() {return items;} 212 | inline objT* getItem(intT i) {return items[i];} 213 | inline intT size() {return n;} 214 | inline objT* operator[](intT i) {return items[i];} 215 | inline pointT getMax() {return pMax;} 216 | inline pointT getMin() {return pMin;} 217 | inline floatT getMax(intT i) {return pMax[i];} 218 | inline floatT getMin(intT i) {return pMin[i];} 219 | 220 | kdNode(objT** itemss, intT nn, nodeT *space, objT** scratch, intT* flags, intT leafSize=16): items(itemss), n(nn) { 221 | if (n>2000) constructParallel(space, scratch, flags, leafSize); 222 | else constructSerial(space, leafSize);} 223 | kdNode(objT** itemss, intT nn, nodeT *space, intT leafSize=16): items(itemss), n(nn) { 224 | constructSerial(space, leafSize);} 225 | 226 | void setEmpty() {n=-1;} 227 | bool isEmpty() {return n<0;} 228 | bool isLeaf() {return !left;}//check 229 | 230 | floatT nodeDiag() {//todo change name 231 | floatT result = 0; 232 | for (int d = 0; d < dim; ++ d) { 233 | floatT tmp = pMax[d] - pMin[d]; 234 | result += tmp * tmp; 235 | } 236 | return sqrt(result); 237 | } 238 | 239 | // return maximum span of node bounding box among all dimensions 240 | inline floatT lMax() { 241 | floatT myMax = 0; 242 | for (int d=0; d myMax) { 245 | myMax = thisMax;} 246 | } 247 | return myMax; 248 | } 249 | 250 | //return the bb distance with and n2 251 | inline floatT nodeDistance(nodeT* n2) { 252 | for (int d = 0; d < dim; ++ d) { 253 | if (pMin[d] > n2->pMax[d] || n2->pMin[d] > pMax[d]) { 254 | // disjoint at dim d, and intersect on dim < d 255 | floatT rsqr = 0; 256 | for (int dd = d; dd < dim; ++ dd) { 257 | floatT tmp = max(pMin[dd]-n2->pMax[dd], n2->pMin[dd]-pMax[dd]); 258 | tmp = max(tmp, (floatT)0); 259 | rsqr += tmp*tmp; 260 | } 261 | return sqrt(rsqr); 262 | } 263 | } 264 | return 0; // intersect 265 | } 266 | 267 | //return the far bb distance between n1 and n2 268 | inline floatT nodeFarDistance(nodeT* n2) { 269 | floatT result = 0; 270 | for (int d = 0; d < dim; ++ d) { 271 | floatT tmp = max(pMax[d],n2->pMax[d]) - min(pMin[d],n2->pMin[d]); 272 | result += tmp *tmp; 273 | } 274 | return sqrt(result); 275 | } 276 | 277 | //whether well separated with v 278 | inline bool wellSeparated(nodeT *v, floatT s=2) { 279 | floatT circleDiam_u = 0; 280 | floatT circleDiam_v = 0; 281 | floatT circleDistance = 0; 282 | for (int d = 0; d < dim; ++ d) { 283 | floatT uTmpDiff = pMax[d] - pMin[d]; 284 | floatT vTmpDiff = v->pMax[d] - v->pMin[d]; 285 | floatT uTmpAvg = (pMax[d] + pMin[d])/2; 286 | floatT vTmpAvg = (v->pMax[d] + v->pMin[d])/2; 287 | circleDistance += (uTmpAvg - vTmpAvg) * (uTmpAvg - vTmpAvg); 288 | circleDiam_u += uTmpDiff * uTmpDiff; 289 | circleDiam_v += vTmpDiff * vTmpDiff; 290 | } 291 | circleDiam_u = sqrt(circleDiam_u); 292 | circleDiam_v = sqrt(circleDiam_v); 293 | floatT myRadius = max(circleDiam_u, circleDiam_v)/2; 294 | circleDistance = sqrt(circleDistance) - circleDiam_u/2 - circleDiam_v/2; 295 | return circleDistance >= (s * myRadius); 296 | } 297 | 298 | //vecT need to be vector 299 | template 300 | void rangeNeighbor(pointT queryPt, floatT r, pointT pMin1, pointT pMax1, vecT* accum) { 301 | int relation = boxCompare(pMin1, pMax1, pMin, pMax); 302 | if (relation == boxInclude) { 303 | for(intT i=0; igetCoordObj()->dist(queryPt) <= r) 305 | accum->push_back(items[i]); 306 | } 307 | } else if (relation == boxOverlap) { 308 | if (isLeaf()) { 309 | for(intT i=0; igetCoordObj()->dist(queryPt) <= r && 311 | itemInBox(pMin1, pMax1, items[i])) accum->push_back(items[i]); 312 | } 313 | } else { 314 | left->rangeNeighbor(queryPt, r, pMin1, pMax1, accum); 315 | right->rangeNeighbor(queryPt, r, pMin1, pMax1, accum);} 316 | } 317 | } 318 | 319 | template 320 | void rangeNeighbor(pointT queryPt, floatT r, pointT pMin1, pointT pMax1, func term, func2 doTerm) { 321 | if (term()) return; 322 | int relation = boxCompare(pMin1, pMax1, pMin, pMax); 323 | if (relation == boxInclude) { 324 | for(intT i=0; igetCoordObj()->dist(queryPt) <= r && 326 | doTerm(items[i])) break; 327 | } 328 | } else if (relation == boxOverlap) { 329 | if (isLeaf()) { 330 | for(intT i=0; igetCoordObj()->dist(queryPt) <= r && 332 | doTerm(items[i])) break; 333 | } 334 | } else { 335 | left->rangeNeighbor(queryPt, r, pMin1, pMax1, term, doTerm); 336 | right->rangeNeighbor(queryPt, r, pMin1, pMax1, term, doTerm);} 337 | } 338 | } 339 | 340 | struct bcp { 341 | objT* u; 342 | objT* v; 343 | floatT dist; 344 | bcp(objT* uu, objT* vv, floatT distt): u(uu), v(vv), dist(distt) {} 345 | bcp(): u(NULL), v(NULL), dist(floatMax()) {} 346 | void update(objT* uu, objT* vv) { 347 | auto distt = uu->dist(*vv); 348 | if (distt < dist) { 349 | u = uu; v = vv; dist = distt;} 350 | } 351 | void update(objT* uu, objT* vv, floatT distt) { 352 | if (distt < dist) { 353 | u = uu; v = vv; dist = distt;} 354 | } 355 | }; 356 | 357 | inline void compBcpH(nodeT* n2, bcp* r) { 358 | if (nodeDistance(n2) > r->dist) return; 359 | 360 | if (isLeaf() && n2->isLeaf()) {//basecase 361 | for (intT i=0; isize(); ++j) { 363 | r->update(items[i], n2->items[j]);} 364 | } 365 | } else {//recursive, todo consider call order, might help 366 | if (isLeaf()) { 367 | if (nodeDistance(n2->left) < nodeDistance(n2->right)) { 368 | compBcpH(n2->left, r); 369 | compBcpH(n2->right, r); 370 | } else { 371 | compBcpH(n2->right, r); 372 | compBcpH(n2->left, r); 373 | } 374 | } else if (n2->isLeaf()) { 375 | if (n2->nodeDistance(left) < n2->nodeDistance(right)) { 376 | n2->compBcpH(left, r); 377 | n2->compBcpH(right, r); 378 | } else { 379 | n2->compBcpH(right, r); 380 | n2->compBcpH(left, r); 381 | } 382 | } else { 383 | pair ordering[4]; 384 | ordering[0] = make_pair(n2->left, left); 385 | ordering[1] = make_pair(n2->right, left); 386 | ordering[2] = make_pair(n2->left, right); 387 | ordering[3] = make_pair(n2->right, right); 388 | auto bbd = [&](pair p1, pair p2) { 389 | return p1.first->nodeDistance(p1.second) < p2.first->nodeDistance(p2.second);}; 390 | quickSortSerial(ordering, 4, bbd); 391 | for (intT o=0; o<4; ++o) { 392 | ordering[o].first->compBcpH(ordering[o].second, r);} 393 | } 394 | } 395 | } 396 | 397 | inline bcp compBcp(nodeT* n2) { 398 | auto r = bcp(); 399 | compBcpH(n2, &r); 400 | // for (intT i=0; isize(); ++j) { 402 | // r.update(items[i], n2->items[j]);} 403 | // } 404 | return r; 405 | } 406 | 407 | }; 408 | -------------------------------------------------------------------------------- /include/dbscan/kdTree.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Yiqiu Wang 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights (to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #pragma once 23 | 24 | #include 25 | #include 26 | #include "pbbs/parallel.h" 27 | #include "pbbs/utils.h" 28 | #include "pbbs/sequence.h" 29 | #include "pbbs/quickSort.h" 30 | #include "point.h" 31 | #include "kdNode.h" 32 | 33 | // ************************************************************* 34 | // A generic parallel Euclidean kdTree 35 | // ************************************************************* 36 | // objT needs to be templatized with int dim, 37 | // and supports coordinate() that returns floatT[dim], 38 | // and coordinate(i) that returns floatT* A[i] 39 | // and dist(objT p2) that computes euclidean distance with p2 40 | // and objT need an empty constructor for empty value 41 | 42 | template 43 | class kdTree { 44 | typedef double floatT; 45 | typedef point pointT; 46 | typedef kdNode nodeT; 47 | objT **items; 48 | nodeT *root; 49 | intT n; 50 | 51 | public: 52 | nodeT* rootNode() {return root;} 53 | intT size() {return n;} 54 | 55 | kdTree(objT* P, intT nn, bool parallel=true, bool noCoarsen=false): n(nn) { 56 | items = newA(objT*, n); 57 | parallel_for(0, n, [&](intT i) { 58 | items[i]=&P[i]; 59 | }); 60 | root = newA(nodeT, 2*n-1); 61 | parallel_for(0, 2*n-1, [&](intT i) { 62 | root[i].setEmpty(); 63 | }); 64 | if (parallel) { 65 | objT** scratch = newA(objT*, n); 66 | intT* flags = newA(intT, n); 67 | root[0] = nodeT(items, n, root+1, scratch, flags, noCoarsen ? 1 : 16); 68 | free(scratch); 69 | free(flags); 70 | } else { 71 | root[0] = nodeT(items, n, root+1, noCoarsen ? 1 : 16);} 72 | } 73 | ~kdTree() { 74 | free(items); 75 | free(root);} 76 | 77 | template 78 | vecT* rangeNeighbor(objT* query, floatT r, func term, func2 doTerm, bool cache=false, vecT* accum=NULL) { 79 | pointT pMin1 = pointT(); 80 | pointT pMax1 = pointT(); 81 | pointT queryPt = pointT(); 82 | floatT* center = query->coordinate(); 83 | for (int i=0; irangeNeighbor(queryPt, r, pMin1, pMax1, accum); 90 | for (auto accum_i : *accum) { 91 | if(doTerm(accum_i)) break; 92 | } 93 | return accum; 94 | } else { 95 | root->rangeNeighbor(queryPt, r, pMin1, pMax1, term, doTerm); 96 | return NULL; 97 | } 98 | } 99 | 100 | objT** kNN(objT* q, intT k, objT** R=NULL) { 101 | return rootNode()->kNN(q, k, R); 102 | } 103 | 104 | }; 105 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/gettime.h: -------------------------------------------------------------------------------- 1 | #ifndef GETTIME_H 2 | #define GETTIME_H 3 | 4 | /* 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct timing { 11 | double totalTime; 12 | double lastTime; 13 | double totalWeight; 14 | bool on; 15 | struct timezone tzp; 16 | timing() { 17 | struct timezone tz = {0, 0}; 18 | totalTime=0.0; 19 | totalWeight=0.0; 20 | on=0; tzp = tz;} 21 | void clear() { totalTime=0.0; totalWeight=0.0; on=0;} 22 | double getTime() { 23 | timeval now; 24 | gettimeofday(&now, &tzp); 25 | return ((double) now.tv_sec) + ((double) now.tv_usec)/1000000.; 26 | } 27 | void start () { 28 | on = 1; 29 | lastTime = getTime(); 30 | } 31 | double stop () { 32 | on = 0; 33 | double d = (getTime()-lastTime); 34 | totalTime += d; 35 | return d; 36 | } 37 | double stop (double weight) { 38 | on = 0; 39 | totalWeight += weight; 40 | double d = (getTime()-lastTime); 41 | totalTime += weight*d; 42 | return d; 43 | } 44 | 45 | double total() { 46 | if (on) return totalTime + getTime() - lastTime; 47 | else return totalTime; 48 | } 49 | 50 | double next() { 51 | if (!on) return 0.0; 52 | double t = getTime(); 53 | double td = t - lastTime; 54 | totalTime += td; 55 | lastTime = t; 56 | return td; 57 | } 58 | 59 | void reportT(double time) { 60 | std::cout << "PBBS-time: " << std::setprecision(3) << time << std::endl;; 61 | } 62 | 63 | void reportTime(double time) { 64 | reportT(time); 65 | } 66 | 67 | void reportStop(double weight, std::string str) { 68 | std::cout << str << " :" << weight << ": "; 69 | reportTime(stop(weight)); 70 | } 71 | 72 | void reportTotal() { 73 | double to = (totalWeight > 0.0) ? total()/totalWeight : total(); 74 | reportTime(to); 75 | totalTime = 0.0; 76 | totalWeight = 0.0; 77 | } 78 | 79 | void reportTotal(std::string str) { 80 | std::cout << str << " : "; 81 | reportTotal();} 82 | 83 | void reportNext() {reportTime(next());} 84 | 85 | void reportNext(std::string str) {std::cout << str << " : "; reportNext();} 86 | }; 87 | 88 | // static timer _tm; 89 | // #define timeStatement(_A,_string) _tm.start(); _A; _tm.reportNext(_string); 90 | // #define startTime() _tm.start(); 91 | // #define stopTime(_weight,_str) _tm.reportStop(_weight,_str); 92 | // #define reportTime(_str) _tm.reportTotal(_str); 93 | // #define nextTime(_string) _tm.reportNext(_string); 94 | // #define nextTimeN() _tm.reportT(_tm.next()); 95 | 96 | */ 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/ndHash.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2010 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #ifndef ND_HASH_H 24 | #define ND_HASH_H 25 | 26 | #include "parallel.h" 27 | #include "utils.h" 28 | #include "sequence.h" 29 | #include 30 | using namespace std; 31 | 32 | extern intT g_dim; 33 | 34 | template 35 | class Table { 36 | public: 37 | // typedef typename std::atomic eType; 38 | typedef typename HASH::eType eType; 39 | typedef typename HASH::kType kType; 40 | intT m; 41 | intT mask; 42 | eType empty; 43 | HASH hashStruct; 44 | eType* TA; 45 | intT* compactL; 46 | float load; 47 | 48 | // needs to be in separate routine due to Cilk bugs 49 | static void clearA(eType* A, intT n, eType v) { 50 | parallel_for(0, n, [&](intT i) {A[i] = v;}); 51 | } 52 | 53 | /* 54 | // needs to be in separate routine due to Cilk bugs 55 | void clear() { 56 | parallel_for(0, m, [&](intT i) {TA[i] = empty;}); 57 | } 58 | */ 59 | 60 | struct notEmptyF { 61 | eType e; notEmptyF(eType _e) : e(_e) {} 62 | int operator() (eType a) {return e != a;}}; 63 | 64 | uintT hashToRange(intT h) {return h & mask;} 65 | intT firstIndex(kType v) {return hashToRange(hashStruct.hash(v));} 66 | intT incrementIndex(intT h) {return hashToRange(h+1);} 67 | intT decrementIndex(intT h) {return hashToRange(h-1);} 68 | bool lessIndex(intT a, intT b) {return 2 * hashToRange(a - b) > m;} 69 | 70 | /* 71 | // Size is the maximum number of values the hash table will hold. 72 | // Overfilling the table could put it into an infinite loop. 73 | Table(intT size, HASH hashF, float _load) : 74 | m((intT)1 << utils::log2Up(100+(intT)(_load*(float)size))), 75 | mask(m-1), 76 | empty(hashF.empty()), 77 | hashStruct(hashF), 78 | TA(newA(eType,m)), 79 | compactL(NULL), 80 | load(_load) 81 | { clearA(TA,m,empty); 82 | } 83 | */ 84 | 85 | Table(intT size, HASH hashF) : 86 | m((intT)1 << utils::log2Up(100+(intT)(2.0*(float)size))), 87 | mask(m-1), 88 | empty(hashF.empty()), 89 | hashStruct(hashF), 90 | TA(newA(eType,m)), 91 | compactL(NULL), 92 | load(2.0) 93 | { clearA(TA,m,empty); 94 | } 95 | 96 | /* 97 | // Constructor that takes an array for the hash table space. The 98 | // passed size must be a power of 2 and will not be rounded. Make 99 | // sure to not call del() if you are passing a pointer to the middle 100 | // of an array. 101 | Table(intT size, eType* _TA, HASH hashF) : 102 | m(size), 103 | mask(m-1), 104 | empty(hashF.empty()), 105 | hashStruct(hashF), 106 | TA(newA(eType,m)), 107 | compactL(NULL), 108 | load(1.0) 109 | { clearA(TA,m,empty); 110 | } 111 | 112 | void setActive(intT mm) { 113 | m = (intT)1 << utils::log2Up(100+(intT)(load*(float)mm)); 114 | mask = m-1; 115 | } 116 | */ 117 | 118 | // Deletes the allocated arrays 119 | void del() { 120 | free(TA); 121 | if (compactL != NULL) free(compactL); 122 | } 123 | 124 | //for equal keys, first one to arrive at location wins, linear probing 125 | bool insert(eType v) { 126 | kType vkey = hashStruct.getKey(v); 127 | intT h = firstIndex(vkey); 128 | while (1) { 129 | eType c; 130 | c = TA[h]; 131 | // intT cmp; 132 | // if(c==empty && utils::CAS(&TA[h],c,v)) return 1; 133 | if(c==empty && hashStruct.cas(&TA[h],c,v)) return 1; 134 | else if(0 == hashStruct.cmp(vkey,hashStruct.getKey(c))) { 135 | if(!hashStruct.replaceQ(v,c)) 136 | return 0; 137 | // else if (utils::CAS(&TA[h],c,v)) 138 | else if (hashStruct.cas(&TA[h],c,v)) 139 | return 1; 140 | } 141 | // move to next bucket 142 | h = incrementIndex(h); 143 | } 144 | return 0; // should never get here 145 | } 146 | 147 | /* 148 | //for equal keys, first one to arrive at location wins, linear probing 149 | bool insertWithDuplicates(eType v) { 150 | kType vkey = hashStruct.getKey(v); 151 | intT h = firstIndex(vkey); 152 | while (1) { 153 | eType c; 154 | c = TA[h]; 155 | if(c==empty && utils::CAS(&TA[h],c,v)) return 1; 156 | // move to next bucket 157 | h = incrementIndex(h); 158 | } 159 | return 0; // should never get here 160 | } 161 | 162 | // needs to be more thoroughly tested 163 | // currently always returns true 164 | bool deleteVal(kType v) { 165 | intT i = firstIndex(v); 166 | int cmp = 1; 167 | 168 | // find first element less than or equal to v in priority order 169 | intT j = i; 170 | eType c = TA[j]; 171 | 172 | if (c == empty) return true; 173 | 174 | // find first location with priority less or equal to v's priority 175 | while(c != empty && (cmp = hashStruct.cmp(v,hashStruct.getKey(c))) != 0) { 176 | j = incrementIndex(j); 177 | c = TA[j]; 178 | } 179 | cmp=(c==empty)?1:hashStruct.cmp(v,hashStruct.getKey(c)); 180 | while (1) { 181 | // Invariants: 182 | // v is the key that needs to be deleted 183 | // j is our current index into TA 184 | // if v appears in TA, then at least one copy must appear at or before j 185 | // c = TA[j] at some previous time (could now be changed) 186 | // i = h(v) 187 | // cmp = compare v to key of c (1 if greater, 0 equal, -1 less) 188 | 189 | if (cmp != 0){//why doesn't the following work as the condition??? 190 | //c==empty || hashStruct.cmp(v,hashStruct.getKey(c)) != 0) { 191 | // v does not match key of c, need to move down one and exit if 192 | // moving before h(v) 193 | if (j == i) return true; 194 | j = decrementIndex(j); 195 | c = TA[j]; 196 | cmp = (c == empty) ? 1 : hashStruct.cmp(v, hashStruct.getKey(c)); 197 | } else { // found v at location j (at least at some prior time) 198 | 199 | // Find next available element to fill location j. 200 | // This is a little tricky since we need to skip over elements for 201 | // which the hash index is greater than j, and need to account for 202 | // things being moved downwards by others as we search. 203 | // Makes use of the fact that values in a cell can only decrease 204 | // during a delete phase as elements are moved from the right to left. 205 | intT jj = incrementIndex(j); 206 | eType x = TA[jj]; 207 | while (x != empty && lessIndex(j, firstIndex(hashStruct.getKey(x)))) { 208 | jj = incrementIndex(jj); 209 | x = TA[jj]; 210 | } 211 | intT jjj = decrementIndex(jj); 212 | while (jjj != j) { 213 | eType y = TA[jjj]; 214 | if (y == empty || !lessIndex(j, firstIndex(hashStruct.getKey(y)))) { 215 | x = y; 216 | jj = jjj; 217 | } 218 | jjj = decrementIndex(jjj); 219 | } 220 | 221 | // try to copy the the replacement element into j 222 | if (utils::CAS(&TA[j],c,x)) { 223 | // swap was successful 224 | // if the replacement element was empty, we are done 225 | if (x == empty) return true; 226 | 227 | // Otherwise there are now two copies of the replacement element x 228 | // delete one copy (probably the original) by starting to look at jj. 229 | // Note that others can come along in the meantime and delete 230 | // one or both of them, but that is fine. 231 | v = hashStruct.getKey(x); 232 | j = jj; 233 | i = firstIndex(v); 234 | } 235 | c = TA[j]; 236 | cmp = (c == empty) ? 1 : hashStruct.cmp(v, hashStruct.getKey(c)); 237 | } 238 | } 239 | } 240 | */ 241 | 242 | // Returns the value if an equal value is found in the table 243 | // otherwise returns the "empty" element. 244 | // due to prioritization, can quit early if v is greater than cell 245 | eType find(kType v) { 246 | intT h = firstIndex(v); 247 | eType c = TA[h]; 248 | while (1) { 249 | if (c == empty) return empty; 250 | else if (!hashStruct.cmp(v,hashStruct.getKey(c))) 251 | return c; 252 | h = incrementIndex(h); 253 | c = TA[h]; 254 | } 255 | } 256 | 257 | /* 258 | eType findWithDuplicates(eType v) { 259 | kType vKey = hashStruct.getKey(v); 260 | intT h = firstIndex(vKey); 261 | eType c = TA[h]; 262 | while (1) { 263 | if (c == empty) { 264 | return empty; 265 | } else if (!hashStruct.cmp(vKey,hashStruct.getKey(c))) { 266 | intT* vCoordinates = (intT *)v.first; 267 | intT* cCoordinates = (intT *)c.first; 268 | long i = 0; 269 | for(;i(TA,m,utils::addF(),notEmptyF(empty)); 287 | } 288 | 289 | /* 290 | // returns all the current entries compacted into a sequence 291 | _seq entries() { 292 | bool *FL = newA(bool,m); 293 | parallel_for (0, m, [&](intT i) { 294 | FL[i] = (TA[i] != empty);}); 295 | _seq R = sequence::pack(TA,FL,m); 296 | free(FL); 297 | return R; 298 | } 299 | 300 | // prints the current entries along with the index they are stored at 301 | void print() { 302 | cout << "vals = "; 303 | for (intT i=0; i < m; i++) 304 | if (TA[i] != empty) 305 | cout << i << ":" << TA[i] << ","; 306 | cout << endl; 307 | } 308 | 309 | */ 310 | }; 311 | 312 | /* 313 | template 314 | _seq removeDuplicates(_seq S, intT m, HASH hashF) { 315 | Table T(m,hashF,1.0); 316 | ET* A = S.A; 317 | parallel_for(0, S.n, [&](intT i) { T.insert(A[i]);}); 318 | _seq R = T.entries(); 319 | T.del(); 320 | return R; 321 | } 322 | 323 | template 324 | _seq removeDuplicates(_seq S, HASH hashF) { 325 | return removeDuplicates(S, S.n, hashF); 326 | } 327 | 328 | template 329 | struct hashInt { 330 | typedef intT eType; 331 | typedef intT kType; 332 | eType empty() {return -1;} 333 | kType getKey(eType v) {return v;} 334 | intT hash(kType v) {return utils::hash(v);} 335 | int cmp(kType v, kType b) {return (v > b) ? 1 : ((v == b) ? 0 : -1);} 336 | bool replaceQ(eType v, eType b) {return 0;} 337 | }; 338 | 339 | //typedef Table IntTable; 340 | //static IntTable makeIntTable(int m) {return IntTable(m,hashInt());} 341 | template 342 | static Table,intT > makeIntTable(intT m, float load) { 343 | return Table,intT >(m,hashInt(),load);} 344 | 345 | struct hashStr { 346 | typedef char* eType; 347 | typedef char* kType; 348 | 349 | eType empty() {return NULL;} 350 | kType getKey(eType v) { 351 | return v;} 352 | 353 | uintT hash(kType s) { 354 | uintT hash = 0; 355 | while (*s) hash = *s++ + (hash << 6) + (hash << 16) - hash; 356 | return hash; 357 | } 358 | 359 | int cmp(kType s, kType s2) { 360 | while (*s && *s==*s2) {s++; s2++;}; 361 | return (*s > *s2) ? 1 : ((*s == *s2) ? 0 : -1); 362 | } 363 | 364 | bool replaceQ(eType s, eType s2) {return 0;} 365 | }; 366 | 367 | template 368 | static Table makeStrTable(intT m, float load) { 369 | return Table(m,hashStr(),load);} 370 | 371 | template 372 | struct hashPair { 373 | KEYHASH keyHash; 374 | typedef typename KEYHASH::kType kType; 375 | typedef pair* eType; 376 | eType empty() {return NULL;} 377 | 378 | hashPair(KEYHASH _k) : keyHash(_k) {} 379 | 380 | kType getKey(eType v) { return v->first; } 381 | 382 | uintT hash(kType s) { return keyHash.hash(s);} 383 | int cmp(kType s, kType s2) { return keyHash.cmp(s, s2);} 384 | 385 | bool replaceQ(eType s, eType s2) { 386 | return 0;}//s->second > s2->second;} 387 | }; 388 | 389 | static _seq*> removeDuplicates(_seq*> S) { 390 | return removeDuplicates(S,hashPair(hashStr()));} 391 | 392 | */ 393 | 394 | template 395 | struct hashSimplePair { 396 | // typedef pair eType; 397 | typedef myPair eType; 398 | typedef intT kType; 399 | // eType empty() {return pair(-1,-1);} 400 | eType empty() {return myPair();} 401 | kType getKey(eType v) { return v.first; } 402 | uintT hash(intT s) { return utils::hash(s);} 403 | int cmp(intT v, intT b) {return (v > b) ? 1 : ((v == b) ? 0 : -1);} 404 | bool replaceQ(eType s, eType s2) {return 0;}//return s.second > s2.second;} 405 | bool cas(eType* p, eType o, eType n) { 406 | return std::atomic_compare_exchange_strong_explicit( 407 | reinterpret_cast*>(p), &o, n, std::memory_order_relaxed, std::memory_order_relaxed); 408 | } 409 | }; 410 | 411 | /* 412 | // static _seq > removeDuplicates(_seq > A) { 413 | // return removeDuplicates(A,hashSimplePair()); 414 | // } 415 | 416 | template 417 | static _seq removeDuplicates(_seq A) { 418 | return removeDuplicates(A,hashSimplePair()); 419 | } 420 | */ 421 | 422 | #endif 423 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/parallel.h: -------------------------------------------------------------------------------- 1 | #ifndef PARALLEL_H 2 | #define PARALLEL_H 3 | 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | typedef int intT; 9 | typedef unsigned int uintT; 10 | typedef double floatT; 11 | static intT intMax() {return numeric_limits::max();} 12 | static floatT floatMax() {return numeric_limits::max();} 13 | 14 | #define HOMEGROWN 15 | 16 | #if defined(OPENCILK) 17 | 18 | #include 19 | #include 20 | #define parallel_main main 21 | #define par_for cilk_for 22 | #define par_for_1 _Pragma("cilk_grainsize = 1") cilk_for 23 | #define par_for_256 _Pragma("cilk_grainsize = 256") cilk_for 24 | 25 | extern "C" int __cilkrts_internal_worker_id(void); 26 | 27 | static int getWorkers() { 28 | return __cilkrts_get_nworkers();} 29 | static int getWorkerId() {return __cilkrts_internal_worker_id();} 30 | static void setWorkers(int n) { } 31 | static void printScheduler() { 32 | cout << "scheduler = OpenCilk" << endl; 33 | cout << "num-threads = " << getWorkers() << endl; 34 | } 35 | 36 | //new syntax: 37 | 38 | inline size_t num_workers() { 39 | return __cilkrts_get_nworkers();} 40 | 41 | inline size_t worker_id() { 42 | return __cilkrts_internal_worker_id();} 43 | 44 | template 45 | inline void parallel_for(size_t start, size_t end, F f, 46 | size_t granularity=0, 47 | bool conservative=false) { 48 | if (end > start) { 49 | if (granularity == 1) { 50 | _Pragma("cilk_grainsize = 1") cilk_for(size_t i=start; i 60 | inline void par_do(Lf left, Rf right, bool conservative=false) { 61 | cilk_spawn left(); 62 | right(); 63 | cilk_sync; 64 | } 65 | 66 | #elif defined(CILK) 67 | 68 | #include 69 | #include 70 | #define parallel_main main 71 | #define par_for cilk_for 72 | #define par_for_1 _Pragma("cilk_grainsize = 1") cilk_for 73 | #define par_for_256 _Pragma("cilk_grainsize = 256") cilk_for 74 | 75 | static int getWorkers() { 76 | return __cilkrts_get_nworkers();} 77 | static int getWorkerId() {return __cilkrts_get_worker_number();} 78 | static void setWorkers(int n) { } 79 | static void printScheduler() { 80 | cout << "scheduler = CilkPlus" << endl; 81 | cout << "num-threads = " << getWorkers() << endl; 82 | } 83 | 84 | //new syntax: 85 | 86 | inline size_t num_workers() { 87 | return __cilkrts_get_nworkers();} 88 | 89 | inline size_t worker_id() { 90 | return __cilkrts_get_worker_number();} 91 | 92 | template 93 | inline void parallel_for(size_t start, size_t end, F f, 94 | size_t granularity=0, 95 | bool conservative=false) { 96 | if (end > start) { 97 | if (granularity == 1) { 98 | _Pragma("cilk_grainsize = 1") cilk_for(size_t i=start; i 108 | inline void par_do(Lf left, Rf right, bool conservative=false) { 109 | cilk_spawn left(); 110 | right(); 111 | cilk_sync; 112 | } 113 | 114 | #elif defined(HOMEGROWN) 115 | 116 | #include "scheduler.h" 117 | 118 | namespace parlay { 119 | namespace internal { 120 | // Use a "Meyer singleton" to provide thread-safe 121 | // initialisation and destruction of the scheduler 122 | // 123 | // The declaration of get_default_scheduler must be 124 | // extern inline to ensure that there is only ever one 125 | // copy of the scheduler. This is guaranteed by the C++ 126 | // standard: 7.1.2/4 A static local variable in an 127 | // extern inline function always refers to the same 128 | // object. 129 | extern inline fork_join_scheduler& get_default_scheduler() { 130 | static fork_join_scheduler fj; 131 | return fj; 132 | } 133 | 134 | extern inline void start_scheduler() { 135 | get_default_scheduler().start(); 136 | } 137 | 138 | extern inline void stop_scheduler() { 139 | get_default_scheduler().stop(); 140 | } 141 | } 142 | 143 | inline size_t num_workers() { 144 | return internal::get_default_scheduler().num_workers(); 145 | } 146 | 147 | inline size_t worker_id() { 148 | return internal::get_default_scheduler().worker_id(); 149 | } 150 | 151 | template 152 | inline void parallel_for(size_t start, size_t end, F f, 153 | size_t granularity=0, 154 | bool conservative=false) { 155 | if (end > start) 156 | internal::get_default_scheduler().parfor(start, end, f, granularity, conservative); 157 | } 158 | 159 | template 160 | inline void par_do(Lf left, Rf right, bool conservative=false) { 161 | return internal::get_default_scheduler().pardo(left, right, conservative); 162 | } 163 | } 164 | 165 | using namespace parlay; 166 | 167 | #define cilk_spawn 168 | #define cilk_sync 169 | #define parallel_main main 170 | #define par_for for 171 | #define par_for_1 for 172 | #define par_for_256 for 173 | 174 | static int getWorkers() {return (int)num_workers();} 175 | static int getWorkerId() {return (int)worker_id();} 176 | static void setWorkers(int n) { } 177 | static void printScheduler() { 178 | cout << "scheduler = Parlay-HomeGrown" << endl; 179 | cout << "num-threads = " << getWorkers() << endl;} 180 | 181 | #else 182 | 183 | #define cilk_spawn 184 | #define cilk_sync 185 | #define parallel_main main 186 | #define par_for for 187 | #define par_for_1 for 188 | #define par_for_256 for 189 | 190 | static void printScheduler() { 191 | cout << "scheduler = sequential" << endl;} 192 | static int getWorkers() {return 1;} 193 | static int getWorkerId() {return 0;} 194 | static void setWorkers(int n) { } 195 | 196 | //new syntax: 197 | 198 | inline size_t num_workers() {return 1;} 199 | inline size_t worker_id() {return 0;} 200 | 201 | template 202 | inline void parallel_for(size_t start, size_t end, F f, 203 | size_t granularity=0, 204 | bool conservative=false) { 205 | if (end > start) { 206 | for(size_t i=start; i 210 | inline void par_do(Lf left, Rf right, bool conservative=false) { 211 | left(); 212 | right(); 213 | } 214 | 215 | #endif 216 | 217 | #define nblocks(_n,_bsize) (1 + ((_n)-1)/(_bsize)) 218 | 219 | template 220 | inline void blocked_for(intT _s, intT _e, intT _bsize, F f) { 221 | if (_e > _s) { 222 | intT _ss = _s; 223 | intT _ee = _e; 224 | intT _n = _ee-_ss; 225 | intT _l = nblocks(_n,_bsize); 226 | auto body = [&](intT _i) { 227 | intT _s = _ss + _i * (_bsize); 228 | intT _e = min(_s + (_bsize), _ee); 229 | f(_s, _e, _i); 230 | }; 231 | parallel_for(0, _l, body); 232 | } 233 | } 234 | 235 | template 236 | inline void granular_for(intT _s, intT _e, intT _thresh, F f, intT granularity=0) { 237 | if (_e - _s > _thresh) 238 | parallel_for(_s, _e, f, granularity); 239 | else 240 | for(intT i=_s; i<_e; ++i) f(i); 241 | } 242 | 243 | #endif 244 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/parseCommandLine.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2011 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #ifndef _PARSE_COMMAND_LINE 24 | #define _PARSE_COMMAND_LINE 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | using namespace std; 31 | 32 | struct commandLine { 33 | int argc; 34 | char** argv; 35 | string comLine; 36 | commandLine(int _c, char** _v, string _cl) 37 | : argc(_c), argv(_v), comLine(_cl) {} 38 | 39 | commandLine(int _c, char** _v) 40 | : argc(_c), argv(_v), comLine("bad arguments") {} 41 | 42 | void badArgument() { 43 | cout << "usage: " << argv[0] << " " << comLine << endl; 44 | abort(); 45 | } 46 | 47 | // get an argument 48 | // i is indexed from the last argument = 0, second to last indexed 1, .. 49 | char* getArgument(int i) { 50 | if (argc < 2+i) badArgument(); 51 | return argv[argc-1-i]; 52 | } 53 | 54 | // looks for two filenames 55 | pair IOFileNames() { 56 | if (argc < 3) badArgument(); 57 | return pair(argv[argc-2],argv[argc-1]); 58 | } 59 | 60 | pair sizeAndFileName() { 61 | if (argc < 3) badArgument(); 62 | return pair(std::atoi(argv[argc-2]),(char*) argv[argc-1]); 63 | } 64 | 65 | bool getOption(string option) { 66 | for (int i = 1; i < argc; i++) 67 | if ((string) argv[i] == option) return true; 68 | return false; 69 | } 70 | 71 | char* getOptionValue(string option) { 72 | for (int i = 1; i < argc-1; i++) 73 | if ((string) argv[i] == option) return argv[i+1]; 74 | return NULL; 75 | } 76 | 77 | string getOptionValue(string option, string defaultValue) { 78 | for (int i = 1; i < argc-1; i++) 79 | if ((string) argv[i] == option) return (string) argv[i+1]; 80 | return defaultValue; 81 | } 82 | 83 | int getOptionIntValue(string option, int defaultValue) { 84 | for (int i = 1; i < argc-1; i++) 85 | if ((string) argv[i] == option) { 86 | int r = atoi(argv[i+1]); 87 | if (r < 1) badArgument(); 88 | return r; 89 | } 90 | return defaultValue; 91 | } 92 | 93 | long getOptionLongValue(string option, long defaultValue) { 94 | for (int i = 1; i < argc-1; i++) 95 | if ((string) argv[i] == option) { 96 | long r = atol(argv[i+1]); 97 | if (r < 1) badArgument(); 98 | return r; 99 | } 100 | return defaultValue; 101 | } 102 | 103 | double getOptionDoubleValue(string option, double defaultValue) { 104 | for (int i = 1; i < argc-1; i++) 105 | if ((string) argv[i] == option) { 106 | double val; 107 | if (sscanf(argv[i+1], "%lf", &val) == EOF) { 108 | badArgument(); 109 | } 110 | return val; 111 | } 112 | return defaultValue; 113 | } 114 | 115 | }; 116 | 117 | #endif // _PARSE_COMMAND_LINE 118 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/quickSort.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2010 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #ifndef QUICK_SORT_H 24 | #define QUICK_SORT_H 25 | #include 26 | #include "parallel.h" 27 | 28 | template 29 | void insertionSort(E* A, intT n, BinPred f) { 30 | for (intT i=0; i < n; i++) { 31 | E v = A[i]; 32 | E* B = A + i; 33 | while (--B >= A && f(v,*B)) *(B+1) = *B; 34 | *(B+1) = v; 35 | } 36 | } 37 | 38 | template 39 | E medianOfThree(E a, E b, E c, BinPred f) { 40 | return f(a,b) ? (f(b,c) ? b : (f(a,c) ? c : a)) 41 | : (f(a,c) ? a : (f(b,c) ? c : b)); 42 | } 43 | 44 | 45 | template 46 | std::pair split(E* A, intT n, BinPred f) { 47 | E p = medianOfThree(A[n/4],A[n/2],A[(3*n)/4],f); 48 | E* L = A; // below L are less than pivot 49 | E* M = A; // between L and M are equal to pivot 50 | E* R = A+n-1; // above R are greater than pivot 51 | while (1) { 52 | while (!f(p,*M)) { 53 | if (f(*M,p)) std::swap(*M,*(L++)); 54 | if (M >= R) break; 55 | M++; 56 | } 57 | while (f(p,*R)) R--; 58 | if (M >= R) break; 59 | std::swap(*M,*R--); 60 | if (f(*M,p)) std::swap(*M,*(L++)); 61 | M++; 62 | } 63 | return std::pair(L,M); 64 | } 65 | template 66 | void quickSortSerial(E* A, intT n, BinPred f) { 67 | while (n > 20) { 68 | std::pair X = split(A,n,f); 69 | quickSortSerial(X.second, A+n-X.second, f); 70 | n = X.first - A; 71 | } 72 | insertionSort(A,n,f); 73 | } 74 | 75 | template 76 | void quickSort(E* A, intT n, BinPred f) { 77 | if (n < (1 << 8)) quickSortSerial(A, n, f); 78 | else { 79 | std::pair X = split(A,n,f); 80 | par_do([&](){quickSort(A, X.first - A, f);}, 81 | [&](){quickSort(X.second, A+n-X.second, f);}); 82 | } 83 | } 84 | 85 | #define compSort(__A, __n, __f) (quickSort(__A, __n, __f)) 86 | 87 | #endif // _A_QSORT_INCLUDED 88 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/sampleSort.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2010 Guy Blelloch and Harsha Vardhan Simhadri and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | // This file is basically the cache-oblivious sorting algorithm from: 24 | // 25 | // Low depth cache-oblivious algorithms. 26 | // Guy E. Blelloch, Phillip B. Gibbons and Harsha Vardhan Simhadri. 27 | // Proc. ACM symposium on Parallelism in algorithms and architectures (SPAA), 2010 28 | 29 | // intT is either "int" or "long" (needs to be "long" if n >= 2^31) 30 | 31 | #ifndef SAMPLE_SORT_H 32 | #define SAMPLE_SORT_H 33 | 34 | #include "parallel.h" 35 | #include "sequence.h" 36 | #include "math.h" 37 | #include "quickSort.h" 38 | #include "transpose.h" 39 | #include "utils.h" 40 | 41 | template 42 | void mergeSeq (E* sA, E* sB, intT* sC, long lA, long lB, BinPred f) { 43 | if (lA==0 || lB==0) return; 44 | E *eA = sA+lA; 45 | E *eB = sB+lB; 46 | for (long i=0; i <= lB; i++) sC[i] = 0; 47 | while(1) { 48 | while (f(*sA, *sB)) {(*sC)++; if (++sA == eA) return;} 49 | sB++; sC++; 50 | if (sB == eB) break; 51 | if (!(f(*(sB-1),*sB))) { 52 | while (!f(*sB, *sA)) {(*sC)++; if (++sA == eA) return;} 53 | sB++; sC++; 54 | if (sB == eB) break; 55 | } 56 | } 57 | *sC = eA-sA; 58 | } 59 | 60 | inline unsigned long hashVal(unsigned long a) { 61 | // 982.. is a largish prime 62 | return (((unsigned long) 982451653 * a) + (unsigned long) 12345); 63 | } 64 | 65 | // the following parameters can be tuned 66 | #define PBBS_QUICKSORT_THRESHOLD 1000 67 | #define PBBS_BLOCK_QUOTIENT 2 68 | #define PBBS_BUCKET_QUOTIENT 2 69 | #define PBBS_OVER_SAMPLE 10 70 | 71 | template 72 | void sampleSort (E* A, intT n, BinPred f) { 73 | if (n < PBBS_QUICKSORT_THRESHOLD) quickSort(A, n, f); 74 | else { 75 | long sqrt = (long) ceil(pow(n,0.5)); 76 | long numBlocks = (long) (sqrt/PBBS_BLOCK_QUOTIENT) + 1; 77 | long blockSize = ((n-1)/numBlocks) + 1; 78 | int numBuckets = (int) ((sqrt/PBBS_BUCKET_QUOTIENT) + 1); 79 | long sampleSetSize = numBuckets * PBBS_OVER_SAMPLE; 80 | 81 | E* sampleSet = newA(E,sampleSetSize); 82 | 83 | // generate "random" samples with oversampling 84 | parallel_for(0, sampleSetSize, 85 | [&](intT j) {sampleSet[j] = A[hashVal(j)%n];}); 86 | 87 | // sort the samples 88 | quickSort(sampleSet, sampleSetSize, f); 89 | 90 | // subselect samples at even stride 91 | E* pivots = newA(E,numBuckets-1); 92 | parallel_for(0, numBuckets-1, 93 | [&](intT k){pivots[k] = sampleSet[PBBS_OVER_SAMPLE*k];}); 94 | free(sampleSet); 95 | 96 | // sort each block and merge with samples to get counts for each bucket 97 | intT *counts = newA(intT, numBlocks*numBuckets); 98 | parallel_for(0, numBlocks, 99 | [&](intT i) { 100 | long offset = i * blockSize; 101 | long size = (i < numBlocks - 1) ? blockSize : n - offset; 102 | quickSort(A+offset, size, f); 103 | mergeSeq(A + offset, pivots, counts + i*numBuckets, size, numBuckets-1, f); 104 | }); 105 | 106 | E *B = newA(E, numBlocks*blockSize); 107 | intT *sourceOffsets = newA(intT, numBlocks*numBuckets); 108 | intT *destOffsets = newA(intT, numBlocks*numBuckets); 109 | 110 | // transpose from blocks-major to bucket-major 111 | sequence::scan(counts, sourceOffsets, numBlocks*numBuckets, plus(),(intT)0); 112 | transpose(counts, destOffsets).trans(numBlocks, numBuckets); 113 | sequence::scan(destOffsets, destOffsets, 114 | numBlocks*numBuckets, plus(),(intT)0); 115 | blockTrans(A, B, sourceOffsets, 116 | destOffsets, counts).trans(numBlocks, numBuckets); 117 | free(sourceOffsets); 118 | free(counts); 119 | 120 | // sort within each bucket 121 | parallel_for(0, numBuckets, 122 | [&](intT i) { 123 | long start = destOffsets[i*numBlocks]; 124 | long end = (i < numBuckets -1) ? destOffsets[(i+1)*numBlocks] : n; 125 | 126 | // middle buckets need not be sorted if two consecutive pivots are equal 127 | if (i == 0 || i == numBuckets - 1 || f(pivots[i-1],pivots[i])) 128 | quickSort(B+start, end - start, f); 129 | 130 | // copy back to A 131 | for (long j = start; j < end; j++) 132 | A[j] = B[j]; 133 | }); 134 | free(pivots); 135 | free(destOffsets); 136 | free(B); 137 | } 138 | } 139 | 140 | #undef compSort 141 | #define compSort(__A, __n, __f) (sampleSort(__A, __n, __f)) 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/scheduler.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-present Guy Blelloch and other contributors 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // EXAMPLE USE 1: 22 | // 23 | // fork_join_scheduler fj; 24 | // 25 | // long fib(long i) { 26 | // if (i <= 1) return 1; 27 | // long l,r; 28 | // fj.pardo([&] () { l = fib(i-1);}, 29 | // [&] () { r = fib(i-2);}); 30 | // return l + r; 31 | // } 32 | // 33 | // fib(40); 34 | // 35 | // EXAMPLE USE 2: 36 | // 37 | // void init(long* x, size_t n) { 38 | // parfor(0, n, [&] (int i) {a[i] = i;}); 39 | // } 40 | // 41 | 42 | #ifndef PARLAY_SCHEDULER_H_ 43 | #define PARLAY_SCHEDULER_H_ 44 | 45 | #include 46 | #include 47 | 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include // IWYU pragma: keep 58 | #include 59 | 60 | #include "work_stealing_job.h" 61 | 62 | namespace parlay { 63 | 64 | // Deque from Arora, Blumofe, and Plaxton (SPAA, 1998). 65 | template 66 | struct Deque { 67 | using qidx = unsigned int; 68 | using tag_t = unsigned int; 69 | 70 | // use std::atomic for atomic access. 71 | // Note: Explicit alignment specifier required 72 | // to ensure that Clang inlines atomic loads. 73 | struct alignas(int64_t) age_t { 74 | tag_t tag; 75 | qidx top; 76 | }; 77 | 78 | // align to avoid false sharing 79 | struct alignas(64) padded_job { 80 | std::atomic job; 81 | }; 82 | 83 | static constexpr int q_size = 200; 84 | std::atomic bot; 85 | std::atomic age; 86 | std::array deq; 87 | 88 | Deque() : bot(0), age(age_t{0, 0}) {} 89 | 90 | void push_bottom(Job* job) { 91 | auto local_bot = bot.load(std::memory_order_relaxed); // atomic load 92 | deq[local_bot].job.store(job, std::memory_order_relaxed); // shared store 93 | local_bot += 1; 94 | if (local_bot == q_size) { 95 | throw std::runtime_error("internal error: scheduler queue overflow"); 96 | } 97 | bot.store(local_bot, std::memory_order_relaxed); // shared store 98 | std::atomic_thread_fence(std::memory_order_seq_cst); 99 | } 100 | 101 | Job* pop_top() { 102 | Job* result = nullptr; 103 | auto old_age = age.load(std::memory_order_relaxed); // atomic load 104 | auto local_bot = bot.load(std::memory_order_relaxed); // atomic load 105 | if (local_bot > old_age.top) { 106 | auto job = 107 | deq[old_age.top].job.load(std::memory_order_relaxed); // atomic load 108 | auto new_age = old_age; 109 | new_age.top = new_age.top + 1; 110 | if (age.compare_exchange_strong(old_age, new_age)) 111 | result = job; 112 | else 113 | result = nullptr; 114 | } 115 | return result; 116 | } 117 | 118 | Job* pop_bottom() { 119 | Job* result = nullptr; 120 | auto local_bot = bot.load(std::memory_order_relaxed); // atomic load 121 | if (local_bot != 0) { 122 | local_bot--; 123 | bot.store(local_bot, std::memory_order_relaxed); // shared store 124 | std::atomic_thread_fence(std::memory_order_seq_cst); 125 | auto job = 126 | deq[local_bot].job.load(std::memory_order_relaxed); // atomic load 127 | auto old_age = age.load(std::memory_order_relaxed); // atomic load 128 | if (local_bot > old_age.top) 129 | result = job; 130 | else { 131 | bot.store(0, std::memory_order_relaxed); // shared store 132 | auto new_age = age_t{old_age.tag + 1, 0}; 133 | if ((local_bot == old_age.top) && 134 | age.compare_exchange_strong(old_age, new_age)) 135 | result = job; 136 | else { 137 | age.store(new_age, std::memory_order_relaxed); // shared store 138 | result = nullptr; 139 | } 140 | std::atomic_thread_fence(std::memory_order_seq_cst); 141 | } 142 | } 143 | return result; 144 | } 145 | }; 146 | 147 | template 148 | struct scheduler { 149 | public: 150 | // see comments under wait(..) 151 | static bool const conservative = false; 152 | unsigned int num_threads; 153 | 154 | static thread_local unsigned int thread_id; 155 | 156 | scheduler() 157 | : num_threads(init_num_workers()), 158 | num_deques(2 * num_threads), 159 | deques(num_deques), 160 | attempts(num_deques), 161 | spawned_threads(), 162 | finished_flag(false) { 163 | // Stopping condition 164 | auto finished = [this]() { 165 | return finished_flag.load(std::memory_order_relaxed); 166 | }; 167 | 168 | // Spawn num_threads many threads on startup 169 | thread_id = 0; // thread-local write 170 | for (unsigned int i = 1; i < num_threads; i++) { 171 | spawned_threads.emplace_back([&, i, finished]() { 172 | thread_id = i; // thread-local write 173 | start(finished); 174 | }); 175 | } 176 | } 177 | 178 | ~scheduler() { 179 | finished_flag.store(true, std::memory_order_relaxed); 180 | for (unsigned int i = 1; i < num_threads; i++) { 181 | spawned_threads[i - 1].join(); 182 | } 183 | } 184 | 185 | // Push onto local stack. 186 | void spawn(Job* job) { 187 | int id = worker_id(); 188 | deques[id].push_bottom(job); 189 | } 190 | 191 | // Wait for condition: finished(). 192 | template 193 | void wait(F finished, bool conservative = false) { 194 | // Conservative avoids deadlock if scheduler is used in conjunction 195 | // with user locks enclosing a wait. 196 | if (conservative) { 197 | while (!finished()) std::this_thread::yield(); 198 | } 199 | // If not conservative, schedule within the wait. 200 | // Can deadlock if a stolen job uses same lock as encloses the wait. 201 | else 202 | start(finished); 203 | } 204 | 205 | // All scheduler threads quit after this is called. 206 | void finish() { finished_flag.store(true, std::memory_order_relaxed); } 207 | 208 | // Pop from local stack. 209 | Job* try_pop() { 210 | auto id = worker_id(); 211 | return deques[id].pop_bottom(); 212 | } 213 | 214 | #ifdef _MSC_VER 215 | #pragma warning(push) 216 | #pragma warning(disable : 4996) // 'getenv': This function or variable may be unsafe. 217 | #endif 218 | 219 | // Determine the number of workers to spawn 220 | unsigned int init_num_workers() { 221 | if (const auto env_p = std::getenv("PARLAY_NUM_THREADS")) { 222 | return std::stoi(env_p); 223 | } else { 224 | return std::thread::hardware_concurrency(); 225 | } 226 | } 227 | 228 | #ifdef _MSC_VER 229 | #pragma warning(pop) 230 | #endif 231 | 232 | unsigned int num_workers() { return num_threads; } 233 | unsigned int worker_id() { return thread_id; } 234 | void set_num_workers(unsigned int) { 235 | std::cout << "Unsupported" << std::endl; 236 | exit(-1); 237 | } 238 | 239 | private: 240 | // Align to avoid false sharing. 241 | struct alignas(128) attempt { 242 | size_t val; 243 | }; 244 | 245 | int num_deques; 246 | std::vector> deques; 247 | std::vector attempts; 248 | std::vector spawned_threads; 249 | std::atomic finished_flag; 250 | 251 | // Start an individual scheduler task. Runs until finished(). 252 | template 253 | void start(F finished) { 254 | while (true) { 255 | Job* job = get_job(finished); 256 | if (!job) return; 257 | (*job)(); 258 | } 259 | } 260 | 261 | Job* try_steal(size_t id) { 262 | // use hashing to get "random" target 263 | size_t target = (hash(id) + hash(attempts[id].val)) % num_deques; 264 | attempts[id].val++; 265 | return deques[target].pop_top(); 266 | } 267 | 268 | // Find a job, first trying local stack, then random steals. 269 | template 270 | Job* get_job(F finished) { 271 | if (finished()) return nullptr; 272 | Job* job = try_pop(); 273 | if (job) return job; 274 | size_t id = worker_id(); 275 | while (true) { 276 | // By coupon collector's problem, this should touch all. 277 | for (int i = 0; i <= num_deques * 100; i++) { 278 | if (finished()) return nullptr; 279 | job = try_steal(id); 280 | if (job) return job; 281 | } 282 | // If haven't found anything, take a breather. 283 | std::this_thread::sleep_for(std::chrono::nanoseconds(num_deques * 100)); 284 | } 285 | } 286 | 287 | size_t hash(uint64_t x) { 288 | x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; 289 | x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL; 290 | x = x ^ (x >> 31); 291 | return static_cast(x); 292 | } 293 | }; 294 | 295 | template 296 | thread_local unsigned int scheduler::thread_id = 0; 297 | 298 | class fork_join_scheduler { 299 | using Job = WorkStealingJob; 300 | 301 | // Underlying scheduler object 302 | std::unique_ptr> sched; 303 | 304 | public: 305 | fork_join_scheduler() {} 306 | 307 | void start() { 308 | sched = std::make_unique>(); 309 | } 310 | 311 | void stop() { 312 | auto sched_pt = sched.release(); 313 | delete sched_pt; 314 | } 315 | 316 | unsigned int num_workers() { return sched->num_workers(); } 317 | unsigned int worker_id() { return sched->worker_id(); } 318 | void set_num_workers(int n) { sched->set_num_workers(n); } 319 | 320 | // Fork two thunks and wait until they both finish. 321 | template 322 | void pardo(L left, R right, bool conservative = false) { 323 | auto right_job = make_job(right); 324 | sched->spawn(&right_job); 325 | left(); 326 | if (sched->try_pop() != nullptr) 327 | right(); 328 | else { 329 | auto finished = [&]() { return right_job.finished(); }; 330 | sched->wait(finished, conservative); 331 | } 332 | } 333 | 334 | #ifdef _MSC_VER 335 | #pragma warning(push) 336 | #pragma warning(disable: 4267) // conversion from 'size_t' to *, possible loss of data 337 | #endif 338 | 339 | template 340 | size_t get_granularity(size_t start, size_t end, F f) { 341 | size_t done = 0; 342 | size_t sz = 1; 343 | int ticks = 0; 344 | do { 345 | sz = std::min(sz, end - (start + done)); 346 | auto tstart = std::chrono::high_resolution_clock::now(); 347 | for (size_t i = 0; i < sz; i++) f(start + done + i); 348 | auto tstop = std::chrono::high_resolution_clock::now(); 349 | ticks = static_cast((tstop - tstart).count()); 350 | done += sz; 351 | sz *= 2; 352 | } while (ticks < 1000 && done < (end - start)); 353 | return done; 354 | } 355 | 356 | template 357 | void parfor(size_t start, size_t end, F f, size_t granularity = 0, 358 | bool conservative = false) { 359 | if (end <= start) return; 360 | if (granularity == 0) { 361 | size_t done = get_granularity(start, end, f); 362 | granularity = std::max(done, (end - start) / (128 * sched->num_threads)); 363 | parfor_(start + done, end, f, granularity, conservative); 364 | } else 365 | parfor_(start, end, f, granularity, conservative); 366 | } 367 | 368 | private: 369 | template 370 | void parfor_(size_t start, size_t end, F f, size_t granularity, 371 | bool conservative) { 372 | if ((end - start) <= granularity) 373 | for (size_t i = start; i < end; i++) f(i); 374 | else { 375 | size_t n = end - start; 376 | // Not in middle to avoid clashes on set-associative 377 | // caches on powers of 2. 378 | size_t mid = (start + (9 * (n + 1)) / 16); 379 | pardo([&]() { parfor_(start, mid, f, granularity, conservative); }, 380 | [&]() { parfor_(mid, end, f, granularity, conservative); }, 381 | conservative); 382 | } 383 | } 384 | 385 | #ifdef _MSC_VER 386 | #pragma warning(pop) 387 | #endif 388 | 389 | }; 390 | 391 | } // namespace parlay 392 | 393 | #endif // PARLAY_SCHEDULER_H_ 394 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/sequence.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2011 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #ifndef SEQUENCE_H 24 | #define SEQUENCE_H 25 | 26 | #include 27 | #include "parallel.h" 28 | #include "utils.h" 29 | 30 | // For fast popcount 31 | // #include 32 | // #include 33 | 34 | using namespace std; 35 | 36 | #define _BSIZE 2048 37 | #define _SCAN_LOG_BSIZE 10 38 | #define _SCAN_BSIZE (1 << _SCAN_LOG_BSIZE) 39 | 40 | template 41 | struct _seq { 42 | T* A; 43 | long n; 44 | _seq() {A = NULL; n=0;} 45 | _seq(T* _A, long _n) : A(_A), n(_n) {} 46 | void del() {free(A);} 47 | }; 48 | 49 | template 50 | void brokenCompiler__(intT n, E* x, E v) { 51 | parallel_for(0, n, [&](intT i) {x[i] = v;}); 52 | } 53 | 54 | template 55 | static E* newArray(intT n, E v) { 56 | E* x = (E*) malloc(n*sizeof(E)); 57 | brokenCompiler__(n, x, v); 58 | return x; 59 | } 60 | 61 | namespace sequence { 62 | 63 | template 64 | struct boolGetA { 65 | bool* A; 66 | boolGetA(bool* AA) : A(AA) {} 67 | intT operator() (intT i) {return (intT) A[i];} 68 | }; 69 | 70 | template 71 | struct getA { 72 | ET* A; 73 | getA(ET* AA) : A(AA) {} 74 | ET operator() (intT i) {return A[i];} 75 | }; 76 | 77 | template 78 | struct getAF { 79 | IT* A; 80 | F f; 81 | getAF(IT* AA, F ff) : A(AA), f(ff) {} 82 | OT operator () (intT i) {return f(A[i]);} 83 | }; 84 | 85 | template 86 | OT reduceSerial(intT s, intT e, F f, G g) { 87 | OT r = g(s); 88 | for (intT j=s+1; j < e; j++) r = f(r,g(j)); 89 | return r; 90 | } 91 | 92 | template 93 | OT reduce(intT s, intT e, F f, G g) { 94 | intT l = nblocks(e-s, _SCAN_BSIZE); 95 | if (l <= 1) return reduceSerial(s, e, f , g); 96 | OT *Sums = newA(OT,l); 97 | // blocked_for (i, s, e, _SCAN_BSIZE, 98 | // Sums[i] = reduceSerial(s, e, f, g);); 99 | auto body = [&](intT _s, intT _e, intT i) { 100 | Sums[i] = reduceSerial(_s, _e, f, g);}; 101 | blocked_for(s, e, _SCAN_BSIZE, body); 102 | OT r = reduce((intT) 0, l, f, getA(Sums)); 103 | free(Sums); 104 | return r; 105 | } 106 | 107 | template 108 | OT reduce(OT* A, intT n, F f) { 109 | return reduce((intT)0,n,f,getA(A)); 110 | } 111 | 112 | template 113 | OT reduce(OT* A, intT s, intT e, F f) { 114 | return reduce(s,e,f,getA(A)); 115 | } 116 | 117 | template 118 | OT plusReduce(OT* A, intT n) { 119 | return reduce((intT)0,n,utils::addF(),getA(A)); 120 | } 121 | 122 | template 123 | intT sum(bool *In, intT n) { 124 | return reduce((intT) 0, n, utils::addF(), boolGetA(In)); 125 | } 126 | 127 | // g is the map function (applied to each element) 128 | // f is the reduce function 129 | // need to specify OT since it is not an argument 130 | template 131 | OT mapReduce(IT* A, intT n, F f, G g) { 132 | return reduce((intT) 0,n,f,getAF(A,g)); 133 | } 134 | 135 | template 136 | intT maxIndexSerial(intT s, intT e, F f, G g) { 137 | ET r = g(s); 138 | intT k = s; 139 | for (intT j=s+1; j < e; j++) { 140 | ET v = g(j); 141 | if (f(v,r)) { r = v; k = j;} 142 | } 143 | return k; 144 | } 145 | 146 | template 147 | intT maxIndex(intT s, intT e, F f, G g) { 148 | intT l = nblocks(e-s, _SCAN_BSIZE); 149 | if (l <= 2) return maxIndexSerial(s, e, f , g); 150 | else { 151 | intT *Idx = newA(intT,l); 152 | // blocked_for (i, s, e, _SCAN_BSIZE, 153 | // Idx[i] = maxIndexSerial(s, e, f, g);); 154 | auto body = [&](intT _s, intT _e, intT i) { 155 | Idx[i] = maxIndexSerial(_s, _e, f, g); 156 | }; 157 | blocked_for (s, e, _SCAN_BSIZE, body); 158 | intT k = Idx[0]; 159 | for (intT j=1; j < l; j++) 160 | if (f(g(Idx[j]),g(k))) k = Idx[j]; 161 | free(Idx); 162 | return k; 163 | } 164 | } 165 | 166 | template 167 | intT maxIndex(ET* A, intT n, F f) { 168 | return maxIndex((intT) 0, n, f, getA(A)); 169 | } 170 | 171 | template 172 | intT maxIndexSerial(intT s, intT e, G g) { 173 | ET r = g(s); 174 | intT k = s; 175 | for (intT j=s+1; j < e; j++) { 176 | ET v = g(j); 177 | if (v >= r) { r = v; k = j;} 178 | } 179 | return k; 180 | } 181 | 182 | template 183 | intT maxIndex(intT s, intT e, G g) { 184 | intT l = nblocks(e-s, _SCAN_BSIZE); 185 | if (l <= 2) return maxIndexSerial(s, e, g); 186 | else { 187 | intT *Idx = newA(intT,l); 188 | // blocked_for (i, s, e, _SCAN_BSIZE, 189 | // Idx[i] = maxIndexSerial(s, e, g);); 190 | auto body = [&](intT _s, intT _e, intT i) { 191 | Idx[i] = maxIndexSerial(_s, _e, g); 192 | }; 193 | blocked_for(s, e, _SCAN_BSIZE, body); 194 | intT k = Idx[0]; 195 | for (intT j=1; j < l; j++) 196 | if (g(Idx[j])>= g(k)) k = Idx[j]; 197 | free(Idx); 198 | return k; 199 | } 200 | } 201 | 202 | template 203 | intT minIndexSerial(intT s, intT e, G g) { 204 | ET r = g(s); 205 | intT k = s; 206 | for (intT j=s+1; j < e; j++) { 207 | ET v = g(j); 208 | if (v < r) { r = v; k = j;} 209 | } 210 | return k; 211 | } 212 | 213 | template 214 | intT minIndex(intT s, intT e, G g) { 215 | intT l = nblocks(e-s, _SCAN_BSIZE); 216 | if (l <= 2) return minIndexSerial(s, e, g); 217 | else { 218 | intT *Idx = newA(intT,l); 219 | // blocked_for (i, s, e, _SCAN_BSIZE, 220 | // Idx[i] = minIndexSerial(s, e, g);); 221 | auto body = [&] (intT _s, intT _e, intT i) { 222 | Idx[i] = minIndexSerial(_s, _e, g);}; 223 | blocked_for(s, e, _SCAN_BSIZE, body); 224 | intT k = Idx[0]; 225 | for (intT j=1; j < l; j++) 226 | if (g(Idx[j])< g(k)) k = Idx[j]; 227 | free(Idx); 228 | return k; 229 | } 230 | } 231 | 232 | template 233 | ET scanSerial(ET* Out, intT s, intT e, F f, G g, ET zero, bool inclusive, bool back) { 234 | ET r = zero; 235 | 236 | if (inclusive) { 237 | if (back) for (long i = e-1; i >= s; i--) Out[i] = r = f(r,g(i)); 238 | else for (intT i = s; i < e; i++) Out[i] = r = f(r,g(i)); 239 | } else { 240 | if (back) 241 | for (long i = e-1; i >= s; i--) { 242 | ET t = g(i); 243 | Out[i] = r; 244 | r = f(r,t); 245 | } 246 | else 247 | for (intT i = s; i < e; i++) { 248 | ET t = g(i); 249 | Out[i] = r; 250 | r = f(r,t); 251 | } 252 | } 253 | return r; 254 | } 255 | 256 | template 257 | ET scanSerial(ET *In, ET* Out, intT n, F f, ET zero) { 258 | return scanSerial(Out, (intT) 0, n, f, getA(In), zero, false, false); 259 | } 260 | 261 | // back indicates it runs in reverse direction 262 | template 263 | ET scan(ET* Out, intT s, intT e, F f, G g, ET zero, bool inclusive, bool back) { 264 | intT n = e-s; 265 | intT l = nblocks(n,_SCAN_BSIZE); 266 | if (l <= 2) return scanSerial(Out, s, e, f, g, zero, inclusive, back); 267 | ET *Sums = newA(ET,nblocks(n,_SCAN_BSIZE)); 268 | auto body1 = [&](intT _s, intT _e, intT i) { 269 | Sums[i] = reduceSerial(_s, _e, f, g);}; 270 | blocked_for(s, e, _SCAN_BSIZE, body1); 271 | // blocked_for (i, s, e, _SCAN_BSIZE, 272 | // Sums[i] = reduceSerial(s, e, f, g);); 273 | ET total = scan(Sums, (intT) 0, l, f, getA(Sums), zero, false, back); 274 | auto body2 = [&](intT _s, intT _e, intT i) { 275 | scanSerial(Out, _s, _e, f, g, Sums[i], inclusive, back);}; 276 | blocked_for(s, e, _SCAN_BSIZE, body2); 277 | // blocked_for (i, s, e, _SCAN_BSIZE, 278 | // scanSerial(Out, s, e, f, g, Sums[i], inclusive, back);); 279 | free(Sums); 280 | return total; 281 | } 282 | 283 | template 284 | ET scan(ET *In, ET* Out, intT n, F f, ET zero) { 285 | return scan(Out, (intT) 0, n, f, getA(In), zero, false, false);} 286 | 287 | template 288 | ET scanBack(ET *In, ET* Out, intT n, F f, ET zero) { 289 | return scan(Out, (intT) 0, n, f, getA(In), zero, false, true);} 290 | 291 | template 292 | ET scanI(ET *In, ET* Out, intT n, F f, ET zero) { 293 | return scan(Out, (intT) 0, n, f, getA(In), zero, true, false);} 294 | 295 | template 296 | ET scanIBack(ET *In, ET* Out, intT n, F f, ET zero) { 297 | return scan(Out, (intT) 0, n, f, getA(In), zero, true, true);} 298 | 299 | template 300 | ET plusScan(ET *In, ET* Out, intT n) { 301 | return scan(Out, (intT) 0, n, utils::addF(), getA(In), 302 | (ET) 0, false, false);} 303 | 304 | template 305 | intT enumerate(bool *In, intT* Out, intT n) { 306 | return scan(Out, (intT) 0, n, utils::addF(), boolGetA(In), 307 | (intT) 0, false, false);} 308 | 309 | 310 | #define _F_BSIZE (2*_SCAN_BSIZE) 311 | 312 | // sums a sequence of n boolean flags 313 | // an optimized version that sums blocks of 4 booleans by treating 314 | // them as an integer 315 | // Only optimized when n is a multiple of 512 and Fl is 4byte aligned 316 | template 317 | intT sumFlagsSerial(bool *Fl, intT n) { 318 | intT r = 0; 319 | if (n >= 128 && (n & 511) == 0 && ((long) Fl & 3) == 0) { 320 | int* IFl = (int*) Fl; 321 | for (int k = 0; k < (n >> 9); k++) { 322 | int rr = 0; 323 | for (int j=0; j < 128; j++) rr += IFl[j]; 324 | r += (rr&255) + ((rr>>8)&255) + ((rr>>16)&255) + ((rr>>24)&255); 325 | IFl += 128; 326 | } 327 | } else for (intT j=0; j < n; j++) r += Fl[j]; 328 | return r; 329 | } 330 | 331 | template 332 | inline bool checkBit(long* Fl, intT i) { 333 | return Fl[i/64] & ((long)1 << (i % 64)); 334 | } 335 | 336 | template 337 | intT sumBitFlagsSerial(long* Fl, intT s, intT e) { 338 | intT res = 0; 339 | while (s % 64 && s < e) { 340 | if (checkBit(Fl,s)) ++res; 341 | s++; 342 | } 343 | if (s == e) 344 | return res; 345 | while (e%64) { 346 | if (checkBit(Fl,e-1)) ++res; 347 | e--; 348 | } 349 | // Do the rest with popcount 350 | intT be = e / 64; 351 | intT bs = s / 64; 352 | for (intT i = bs; i < be; ++i) { 353 | res += _popcnt64(Fl[i]); 354 | } 355 | return res; 356 | } 357 | 358 | 359 | template 360 | _seq packSerial(ET* Out, bool* Fl, intT s, intT e, F f) { 361 | if (Out == NULL) { 362 | intT m = sumFlagsSerial(Fl+s, e-s); 363 | Out = newA(ET,m); 364 | } 365 | intT k = 0; 366 | for (intT i=s; i < e; i++) if (Fl[i]) Out[k++] = f(i); 367 | return _seq(Out,k); 368 | } 369 | 370 | template 371 | void packSerial01(ET* Out0, ET* Out1, long* Fl, intT s, intT e, F f) { 372 | if (Out0 == NULL) { 373 | intT m = e - s - sumBitFlagsSerial(Fl, e, s); 374 | Out0 = newA(ET,m); 375 | } 376 | if (Out1 == NULL) { 377 | intT m = sumBitFlagsSerial(Fl, e, s); 378 | Out1 = newA(ET,m); 379 | } 380 | intT k0 = 0; 381 | intT k1 = 0; 382 | for (intT i = s; i < e; ++i) { 383 | if (checkBit(Fl,i)) 384 | Out1[k1++] = f(i); 385 | else 386 | Out0[k0++] = f(i); 387 | } 388 | } 389 | 390 | template 391 | void packSerial0(ET* Out, long* Fl, intT s, intT e, F f) { 392 | if (Out == NULL) { 393 | intT m = e - s - sumBitFlagsSerial(Fl, e, s); 394 | Out = newA(ET,m); 395 | } 396 | intT k = 0; 397 | for (intT i=s; i < e; i++) { 398 | if (!checkBit(Fl, i)) { 399 | Out[k++] = f(i); 400 | } 401 | } 402 | } 403 | 404 | template 405 | void packSerial1(ET* Out, long* Fl, intT s, intT e, F f) { 406 | if (Out == NULL) { 407 | intT m = sumBitFlagsSerial(Fl, e, s); 408 | Out = newA(ET,m); 409 | } 410 | intT k = 0; 411 | for (intT i=s; i < e; i++) if (checkBit(Fl, i)) Out[k++] = f(i); 412 | } 413 | 414 | template 415 | T prefixSumSerial(T* data, intT s, intT e) { 416 | T res = 0; 417 | for (intT i = s; i < e; ++i) { 418 | res += data[i]; 419 | data[i] = res - data[i]; 420 | } 421 | return res; 422 | } 423 | 424 | template 425 | void addSerial(T* data, intT s, intT e, T val) { 426 | for (intT i = s; i < e; ++i) 427 | data[i] += val; 428 | } 429 | 430 | template 431 | T prefixSum(T* data, intT s, intT e) { 432 | intT l = nblocks(e-s, _SCAN_BSIZE); 433 | if (l <= 1) return prefixSumSerial(data, s, e); 434 | T* sums = newA(T, l); 435 | auto body1 = [&](intT _s, intT _e, intT i) { 436 | sums[i] = prefixSumSerial(data, _s, _e);}; 437 | blocked_for(s, e, _SCAN_BSIZE, body1); 438 | // blocked_for (i, s, e, _SCAN_BSIZE, 439 | // sums[i] = prefixSumSerial(data, s, e);); 440 | T res = prefixSumSerial(sums, 0, l); 441 | auto body2 = [&](intT _s, intT _e, intT i) { 442 | addSerial(data, _s, _e, sums[i]);}; 443 | blocked_for(s, e, _SCAN_BSIZE, body2); 444 | // blocked_for (i, s, e, _SCAN_BSIZE, 445 | // addSerial(data, s, e, sums[i]);); 446 | free(sums); 447 | return res; 448 | } 449 | 450 | template 451 | _seq pack(ET* Out, bool* Fl, intT s, intT e, F f) { 452 | intT l = nblocks(e-s, _F_BSIZE); 453 | if (l <= 1) return packSerial(Out, Fl, s, e, f); 454 | intT *Sums = newA(intT,l); 455 | auto body1 = [&](intT _s, intT _e, intT i) { 456 | Sums[i] = sumFlagsSerial(Fl+_s, _e-_s);}; 457 | blocked_for(s, e, _F_BSIZE, body1); 458 | //blocked_for (i, s, e, _F_BSIZE, Sums[i] = sumFlagsSerial(Fl+s, e-s);); 459 | intT m = plusScan(Sums, Sums, l); 460 | if (Out == NULL) Out = newA(ET,m); 461 | auto body2 = [&](intT _s, intT _e, intT i) { 462 | packSerial(Out+Sums[i], Fl, _s, _e, f);}; 463 | blocked_for(s, e, _F_BSIZE, body2); 464 | //blocked_for (i, s, e, _F_BSIZE, packSerial(Out+Sums[i], Fl, s, e, f);); 465 | free(Sums); 466 | return _seq(Out,m); 467 | } 468 | 469 | template 470 | void splitSerial(ET* OutFalse, ET* OutTrue, bool* Fl, intT s, intT e, F f) { 471 | intT kT = 0; 472 | intT kF = 0; 473 | for (intT i=s; i < e; i++) 474 | if (Fl[i]) OutTrue[kT++] = f(i); 475 | else OutFalse[kF++] = f(i); 476 | } 477 | 478 | // Given a boolean array, splits so false (0) elements are at the bottom 479 | // and true (1) elements are at the top of the output (of lenght e-s). 480 | // As usual s is a start index, e is an end index and 481 | // f is a function of type [s,e-1) -> ET 482 | template 483 | int split(ET* Out, bool* Fl, intT s, intT e, F f) { 484 | intT l = nblocks(e-s, _F_BSIZE); 485 | intT *SumsTrue = newA(intT,l); 486 | auto body1 = [&](intT _s, intT _e, intT i) { 487 | SumsTrue[i] = sumFlagsSerial(Fl+_s, _e-_s);}; 488 | blocked_for(s, e, _F_BSIZE, body1); 489 | //blocked_for (i, s, e, _F_BSIZE, SumsTrue[i] = sumFlagsSerial(Fl+s, e-s);); 490 | intT numTrue = plusScan(SumsTrue, SumsTrue, l); 491 | intT numFalse = (e - s) - numTrue; 492 | ET* OutTrue = Out + numFalse; 493 | auto body2 = [&](intT _s, intT _e, intT i) { 494 | splitSerial(Out + _F_BSIZE*i - SumsTrue[i], 495 | OutTrue + SumsTrue[i], 496 | Fl, _s, _e, f);}; 497 | blocked_for(s, e, _F_BSIZE, body2); 498 | // blocked_for(i, s, e, _F_BSIZE, 499 | // splitSerial(Out + _F_BSIZE*i - SumsTrue[i], 500 | // OutTrue + SumsTrue[i], 501 | // Fl, s, e, f);); 502 | free(SumsTrue); 503 | return numFalse; 504 | } 505 | 506 | template 507 | pair<_seq,_seq > pack2(ET* Out, bool* Fl1, bool* Fl2, 508 | intT s, intT e, F f) { 509 | intT l = nblocks(e-s, _F_BSIZE); 510 | intT *Sums1 = newA(intT,l); 511 | intT *Sums2 = newA(intT,l); 512 | auto body1 = [&](intT _s, intT _e, intT i) { 513 | Sums1[i] = sumFlagsSerial(Fl1+_s, _e-_s); 514 | Sums2[i] = sumFlagsSerial(Fl2+_s, _e-_s);}; 515 | blocked_for(s, e, _F_BSIZE, body1); 516 | // blocked_for (i, s, e, _F_BSIZE, 517 | // Sums1[i] = sumFlagsSerial(Fl1+s, e-s); 518 | // Sums2[i] = sumFlagsSerial(Fl2+s, e-s);); 519 | intT m1 = plusScan(Sums1, Sums1, l); 520 | intT m2 = plusScan(Sums2, Sums2, l); 521 | ET* Out1; 522 | ET* Out2; 523 | if (Out == NULL) { 524 | Out1 = newA(ET,m1); 525 | Out2 = newA(ET,m2); 526 | } else { 527 | Out1 = Out; 528 | Out2 = Out+m1; 529 | } 530 | auto body2 = [&](intT _s, intT _e, intT i) { 531 | packSerial(Out1+Sums1[i], Fl1, _s, _e, f); 532 | packSerial(Out2+Sums2[i], Fl2, _s, _e, f);}; 533 | blocked_for(s, e, _F_BSIZE, body2); 534 | // blocked_for(i, s, e, _F_BSIZE, 535 | // packSerial(Out1+Sums1[i], Fl1, s, e, f); 536 | // packSerial(Out2+Sums2[i], Fl2, s, e, f);); 537 | free(Sums1); free(Sums2); 538 | return pair<_seq,_seq >(_seq(Out1,m1),_seq(Out2,m2)); 539 | } 540 | // Custom pack2 to be used with bitvector as flags (used for example for wavelet trees) 541 | template 542 | pair<_seq,_seq > pack2(ET* Out, long* Fl, intT s, intT e, F f) { 543 | // If interval empty 544 | if (s >= e) 545 | return pair<_seq,_seq >(_seq(Out,0),_seq(Out,0)); 546 | intT l = nblocks(e-s, _F_BSIZE); 547 | intT *Sums1 = newA(intT,l); 548 | intT *Sums2 = newA(intT,l); 549 | auto body1 = [&](intT _s, intT _e, intT i) { 550 | Sums2[i] = sumBitFlagsSerial(Fl, _s, _e); // count ones 551 | Sums1[i] = (_e-_s-Sums2[i]);}; 552 | blocked_for(s, e, _F_BSIZE, body1); 553 | // blocked_for (i, s, e, _F_BSIZE, 554 | // Sums2[i] = sumBitFlagsSerial(Fl, s, e); // count ones 555 | // Sums1[i] = (e-s-Sums2[i]);); // calculate zeros 556 | intT m1 = plusScan(Sums1, Sums1, l); 557 | intT m2 = plusScan(Sums2, Sums2, l); 558 | ET* Out1; 559 | ET* Out2; 560 | if (Out == NULL) { 561 | Out1 = newA(ET,m1); 562 | Out2 = newA(ET,m2); 563 | } else { 564 | Out1 = Out; 565 | Out2 = Out+m1; 566 | } 567 | auto body2 = [&](intT _s, intT _e, intT i) { 568 | packSerial01(Out1+Sums1[i], Out2+Sums2[i], Fl, _s, _e, f);}; 569 | blocked_for(s, e, _F_BSIZE, body2); 570 | // blocked_for(i, s, e, _F_BSIZE, 571 | // packSerial01(Out1+Sums1[i], Out2+Sums2[i], Fl, s, e, f);); 572 | //packSerial0(Out1+Sums1[i], Fl, s, e, f); 573 | //packSerial1(Out2+Sums2[i], Fl, s, e, f);); 574 | free(Sums1); free(Sums2); 575 | return pair<_seq,_seq >(_seq(Out1,m1),_seq(Out2,m2)); 576 | } 577 | 578 | template 579 | intT pack(ET* In, ET* Out, bool* Fl, intT n) { 580 | return pack(Out, Fl, (intT) 0, n, getA(In)).n; 581 | } 582 | 583 | template 584 | intT split(ET* In, ET* Out, bool* Fl, intT n) { 585 | return split(Out, Fl, (intT) 0, n, getA(In)); 586 | } 587 | 588 | template 589 | pair pack2(ET* In, ET* Out, bool* Fl1, bool* Fl2, intT n) { 590 | pair<_seq,_seq > r; 591 | r = pack2(Out, Fl1, Fl2, (intT) 0, n, getA(In)); 592 | return pair(r.first.n,r.second.n); 593 | } 594 | 595 | // Custom pack which takes an input and one flag array and puts all elements where 0 is set on the left side and alle the other elements on to the right 596 | template 597 | intT pack2Bit(ET* In, ET* Out, long* Flags, intT s, intT e) { 598 | pair<_seq,_seq > r; 599 | r = pack2(Out, Flags, s, e, getA(In)); 600 | return r.first.n; 601 | } 602 | 603 | template 604 | _seq pack(ET* In, bool* Fl, intT n) { 605 | return pack((ET*) NULL, Fl, (intT) 0, n, getA(In)); 606 | } 607 | 608 | template 609 | _seq packMap(bool* Fl, intT n, F& f) { 610 | return pack((OT*) NULL, Fl, (intT) 0, n, f); 611 | } 612 | 613 | template 614 | intT packIndex(intT* Out, bool* Fl, intT n) { 615 | return pack(Out, Fl, (intT) 0, n, utils::identityF()).n; 616 | } 617 | 618 | template 619 | _seq packIndex(bool* Fl, intT n) { 620 | return pack((intT *) NULL, Fl, (intT) 0, n, utils::identityF()); 621 | } 622 | 623 | template 624 | intT filterSerial(ET* In, ET* Out, intT n, PRED p) { 625 | intT k = 0; 626 | for (intT i = 0; i < n; i++) 627 | if (p(In[i])) Out[k++] = In[i]; 628 | return k; 629 | } 630 | 631 | template 632 | intT filter(ET* In, ET* Out, intT n, PRED p) { 633 | if (n < _F_BSIZE) 634 | return filterSerial(In, Out, n, p); 635 | bool *Fl = newA(bool,n); 636 | parallel_for (0, n, [&](intT i) {Fl[i] = (bool) p(In[i]);}); 637 | intT m = pack(In, Out, Fl, n); 638 | free(Fl); 639 | return m; 640 | } 641 | 642 | // Avoids reallocating the bool array 643 | template 644 | intT filter(ET* In, ET* Out, bool* Fl, intT n, PRED p) { 645 | if (n < _F_BSIZE) 646 | return filterSerial(In, Out, n, p); 647 | parallel_for (0, n, [&](intT i) {Fl[i] = (bool) p(In[i]);}); 648 | intT m = pack(In, Out, Fl, n); 649 | return m; 650 | } 651 | 652 | template 653 | _seq filter(ET* In, intT n, PRED p) { 654 | bool *Fl = newA(bool,n); 655 | parallel_for (0, n, [&](intT i) {Fl[i] = (bool) p(In[i]);}); 656 | _seq R = pack(In, Fl, n); 657 | free(Fl); 658 | return R; 659 | } 660 | 661 | // Faster for a small number in output (about 40% or less) 662 | // Destroys the input. Does not need a bool array. 663 | template 664 | intT filterf(ET* In, ET* Out, intT n, PRED p) { 665 | intT b = _F_BSIZE; 666 | if (n < b) 667 | return filterSerial(In, Out, n, p); 668 | intT l = nblocks(n, b); 669 | b = nblocks(n, l); 670 | intT *Sums = newA(intT,l + 1); 671 | parallel_for (0, l, [&](intT i) { 672 | intT s = i * b; 673 | intT e = min(s + b, n); 674 | intT k = s; 675 | for (intT j = s; j < e; j++) 676 | if (p(In[j])) In[k++] = In[j]; 677 | Sums[i] = k - s; 678 | }); 679 | intT m = plusScan(Sums, Sums, l); 680 | Sums[l] = m; 681 | parallel_for (0, l, 682 | [&](intT i) { 683 | ET* I = In + i*b; 684 | ET* O = Out + Sums[i]; 685 | for (intT j = 0; j < Sums[i+1]-Sums[i]; j++) { 686 | O[j] = I[j]; 687 | } 688 | }); 689 | free(Sums); 690 | return m; 691 | } 692 | 693 | } 694 | #endif 695 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/transpose.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2010 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #ifndef TRANSPOSE_H 24 | #define TRANSPOSE_H 25 | 26 | #include "parallel.h" 27 | 28 | #define _TRANS_THRESHHOLD 64 29 | 30 | template 31 | struct transpose { 32 | E *A, *B; 33 | transpose(E *AA, E *BB) : A(AA), B(BB) {} 34 | 35 | void transR(intT rStart, intT rCount, intT rLength, 36 | intT cStart, intT cCount, intT cLength) { 37 | //cout << "cc,rc: " << cCount << "," << rCount << endl; 38 | if (cCount < _TRANS_THRESHHOLD && rCount < _TRANS_THRESHHOLD) { 39 | for (intT i=rStart; i < rStart + rCount; i++) 40 | for (intT j=cStart; j < cStart + cCount; j++) 41 | B[j*cLength + i] = A[i*rLength + j]; 42 | } else if (cCount > rCount) { 43 | intT l1 = cCount/2; 44 | intT l2 = cCount - cCount/2; 45 | par_do([&](){this->transR(rStart,rCount,rLength,cStart,l1,cLength);}, 46 | [&](){transR(rStart,rCount,rLength,cStart + l1,l2,cLength);}); 47 | } else { 48 | intT l1 = rCount/2; 49 | intT l2 = rCount - rCount/2; 50 | par_do([&](){this->transR(rStart,l1,rLength,cStart,cCount,cLength);}, 51 | [&](){transR(rStart + l1,l2,rLength,cStart,cCount,cLength);}); 52 | } 53 | } 54 | 55 | void trans(intT rCount, intT cCount) { 56 | transR(0,rCount,cCount,0,cCount,rCount); 57 | } 58 | }; 59 | 60 | template 61 | struct blockTrans { 62 | E *A, *B; 63 | intT *OA, *OB, *L; 64 | 65 | blockTrans(E *AA, E *BB, intT *OOA, intT *OOB, intT *LL) 66 | : A(AA), B(BB), OA(OOA), OB(OOB), L(LL) {} 67 | 68 | void transR(intT rStart, intT rCount, intT rLength, 69 | intT cStart, intT cCount, intT cLength) { 70 | //cout << "cc,rc: " << cCount << "," << rCount << endl; 71 | if (cCount < _TRANS_THRESHHOLD && rCount < _TRANS_THRESHHOLD) { 72 | for (intT i=rStart; i < rStart + rCount; i++) 73 | for (intT j=cStart; j < cStart + cCount; j++) { 74 | E* pa = A+OA[i*rLength + j]; 75 | E* pb = B+OB[j*cLength + i]; 76 | intT l = L[i*rLength + j]; 77 | //cout << "pa,pb,l: " << pa << "," << pb << "," << l << endl; 78 | for (intT k=0; k < l; k++) *(pb++) = *(pa++); 79 | } 80 | } else if (cCount > rCount) { 81 | intT l1 = cCount/2; 82 | intT l2 = cCount - cCount/2; 83 | par_do([&](){this->transR(rStart,rCount,rLength,cStart,l1,cLength);}, 84 | [&](){transR(rStart,rCount,rLength,cStart + l1,l2,cLength);}); 85 | } else { 86 | intT l1 = rCount/2; 87 | intT l2 = rCount - rCount/2; 88 | par_do([&](){this->transR(rStart,l1,rLength,cStart,cCount,cLength);}, 89 | [&](){transR(rStart + l1,l2,rLength,cStart,cCount,cLength);}); 90 | } 91 | } 92 | 93 | void trans(intT rCount, intT cCount) { 94 | transR(0,rCount,cCount,0,cCount,rCount); 95 | } 96 | 97 | } ; 98 | 99 | #endif // A_TRANSPOSE_INCLUDED 100 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/unionFind.h: -------------------------------------------------------------------------------- 1 | #ifndef UNION_FIND_H 2 | #define UNION_FIND_H 3 | 4 | //adapted from PBBS 5 | #include "utils.h" 6 | #include "parallel.h" 7 | 8 | struct unionFind { 9 | 10 | intT *parents; 11 | intT *hooks; 12 | 13 | unionFind(intT n) { 14 | parents = newA(intT, n); 15 | parallel_for (0, n, [&](intT i) {parents[i] = intMax();}); 16 | hooks = newA(intT, n); 17 | parallel_for (0, n, [&](intT i) {hooks[i] = intMax();}); 18 | } 19 | 20 | void del() { 21 | free(parents); 22 | free(hooks); 23 | } 24 | 25 | inline intT find(intT i) { 26 | intT j = i; 27 | if (parents[j] == intMax()) return j; 28 | do j = parents[j]; 29 | while (parents[j] < intMax()); 30 | intT tmp; 31 | while((tmp=parents[i]) v) swap(u,v); 41 | // if(hooks[u] == intMax() && __sync_bool_compare_and_swap(&hooks[u], intMax(), u)){ 42 | if(hooks[u] == intMax() && utils::myCAS(&hooks[u], intMax(), u)){ 43 | parents[u]=v; 44 | break; 45 | }} 46 | } 47 | }; 48 | 49 | struct edgeUnionFind { 50 | typedef pair edgeT; 51 | intT *parents; 52 | edgeT *hooks; 53 | intT n; 54 | 55 | edgeUnionFind(intT nn): n(nn) { 56 | parents = newA(intT, n); 57 | parallel_for (0, n, [&](intT i) {parents[i] = intMax();}); 58 | hooks = newA(edgeT, n); 59 | parallel_for (0, n, [&](intT i) { 60 | hooks[i] = make_pair(intMax(), intMax());}); 61 | } 62 | 63 | void del() {free(parents);} 64 | 65 | inline intT find(intT i) { 66 | intT j = i; 67 | if (parents[j] == intMax()) return j; 68 | do j = parents[j]; 69 | while (parents[j] < intMax()); 70 | intT tmp; 71 | while((tmp=parents[i]) v) swap(u,v); 83 | // if(hooks[u].first == intMax() && __sync_bool_compare_and_swap(&hooks[u].first, intMax(), c_from)){ 84 | if(hooks[u].first == intMax() && utils::myCAS(&hooks[u].first, intMax(), c_from)){ 85 | parents[u]=v; 86 | hooks[u].second=c_to; 87 | break; 88 | } 89 | } 90 | return parents[u]; 91 | } 92 | 93 | edgeT getEdge(intT idx) { 94 | return hooks[idx]; 95 | } 96 | 97 | }; 98 | 99 | 100 | #endif 101 | 102 | -------------------------------------------------------------------------------- /include/dbscan/pbbs/utils.h: -------------------------------------------------------------------------------- 1 | // This code is part of the Problem Based Benchmark Suite (PBBS) 2 | // Copyright (c) 2010 Guy Blelloch and the PBBS team 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a 5 | // copy of this software and associated documentation files (the 6 | // "Software"), to deal in the Software without restriction, including 7 | // without limitation the rights (to use, copy, modify, merge, publish, 8 | // distribute, sublicense, and/or sell copies of the Software, and to 9 | // permit persons to whom the Software is furnished to do so, subject to 10 | // the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included 13 | // in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #ifndef UTILS_H 24 | #define UTILS_H 25 | #include 26 | #include 27 | #include "parallel.h" 28 | 29 | /* 30 | #if defined(__APPLE__) 31 | #define PTCMPXCH " cmpxchgl %2,%1\n" 32 | #else 33 | #define PTCMPXCH " cmpxchgq %2,%1\n" 34 | 35 | // Needed to make frequent large allocations efficient with standard 36 | // malloc implementation. Otherwise they are allocated directly from 37 | // vm. 38 | #include 39 | static int __ii = mallopt(M_MMAP_MAX,0); 40 | static int __jj = mallopt(M_TRIM_THRESHOLD,-1); 41 | #endif 42 | */ 43 | 44 | #define newA(__E,__n) (__E*) malloc((__n)*sizeof(__E)) 45 | 46 | namespace utils { 47 | 48 | // returns the log base 2 rounded up (works on ints or longs or unsigned versions) 49 | template 50 | static int log2Up(T i) { 51 | int a=0; 52 | T b=i-1; 53 | while (b > 0) {b = b >> 1; a++;} 54 | return a; 55 | } 56 | 57 | inline unsigned int hash(unsigned int a) 58 | { 59 | a = (a+0x7ed55d16) + (a<<12); 60 | a = (a^0xc761c23c) ^ (a>>19); 61 | a = (a+0x165667b1) + (a<<5); 62 | a = (a+0xd3a2646c) ^ (a<<9); 63 | a = (a+0xfd7046c5) + (a<<3); 64 | a = (a^0xb55a4f09) ^ (a>>16); 65 | return a; 66 | } 67 | 68 | inline int hashInt(unsigned int a) { 69 | return hash(a) & (((unsigned) 1 << 31) - 1); 70 | } 71 | 72 | inline unsigned int hash2(unsigned int a) 73 | { 74 | return (((unsigned int) 1103515245 * a) + (unsigned int) 12345) % 75 | (unsigned int) 0xFFFFFFFF; 76 | } 77 | 78 | /* 79 | 80 | // compare and swap on 8 byte quantities 81 | inline bool LCAS(long *ptr, long oldv, long newv) { 82 | unsigned char ret; 83 | // Note that sete sets a 'byte' not the word 84 | __asm__ __volatile__ ( 85 | " lock\n" 86 | " cmpxchgq %2,%1\n" 87 | " sete %0\n" 88 | : "=q" (ret), "=m" (*ptr) 89 | : "r" (newv), "m" (*ptr), "a" (oldv) 90 | : "memory"); 91 | return ret; 92 | } 93 | 94 | // compare and swap on 4 byte quantity 95 | inline bool SCAS(int *ptr, int oldv, int newv) { 96 | unsigned char ret; 97 | // Note that sete sets a 'byte' not the word 98 | __asm__ __volatile__ ( 99 | " lock\n" 100 | " cmpxchgl %2,%1\n" 101 | " sete %0\n" 102 | : "=q" (ret), "=m" (*ptr) 103 | : "r" (newv), "m" (*ptr), "a" (oldv) 104 | : "memory"); 105 | return ret; 106 | } 107 | 108 | //#if defined(MCX16) 109 | //ET should be 128 bits and 128-bit aligned 110 | template 111 | inline bool CAS128(ET* a, ET b, ET c) { 112 | return __sync_bool_compare_and_swap_16((__int128*)a,*((__int128*)&b),*((__int128*)&c)); 113 | } 114 | //#endif 115 | 116 | // The conditional should be removed by the compiler 117 | // this should work with pointer types, or pairs of integers 118 | template 119 | inline bool CAS(ET *ptr, ET oldv, ET newv) { 120 | if (sizeof(ET) == 1) { 121 | return __sync_bool_compare_and_swap_1((bool*) ptr, *((bool*) &oldv), *((bool*) &newv)); 122 | } else if (sizeof(ET) == 8) { 123 | return __sync_bool_compare_and_swap_8((long*) ptr, *((long*) &oldv), *((long*) &newv)); 124 | //return utils::LCAS((long*) ptr, *((long*) &oldv), *((long*) &newv)); 125 | } else if (sizeof(ET) == 4) { 126 | return __sync_bool_compare_and_swap_4((int *) ptr, *((int *) &oldv), *((int *) &newv)); 127 | //return utils::SCAS((int *) ptr, *((int *) &oldv), *((int *) &newv)); 128 | } 129 | //#if defined(MCX16) 130 | else if (sizeof(ET) == 16) { 131 | return utils::CAS128(ptr, oldv, newv); 132 | } 133 | //#endif 134 | else { 135 | std::cout << "common/utils.h CAS bad length " << sizeof(ET) << std::endl; 136 | abort(); 137 | } 138 | } 139 | 140 | */ 141 | 142 | /* 143 | 144 | template 145 | // inline bool CAS_GCC(ET *ptr, ET oldv, ET newv) { 146 | inline bool CAS(ET *ptr, ET oldv, ET newv) { 147 | if (sizeof(ET) == 4) { 148 | return __sync_bool_compare_and_swap((int*)ptr, *((int*)&oldv), *((int*)&newv)); 149 | } else if (sizeof(ET) == 8) { 150 | return __sync_bool_compare_and_swap((long*)ptr, *((long*)&oldv), *((long*)&newv)); 151 | } 152 | #ifdef MCX16 153 | else if(sizeof(ET) == 16) 154 | return __sync_bool_compare_and_swap_16((__int128*)ptr,*((__int128*)&oldv),*((__int128*)&newv)); 155 | #endif 156 | else { 157 | std::cout << "common/utils.h CAS_GCC bad length" << sizeof(ET) << std::endl; 158 | abort(); 159 | } 160 | } 161 | 162 | */ 163 | 164 | /* 165 | 166 | inline long xaddl(long *variable, long value) { 167 | asm volatile( 168 | "lock; xaddl %%eax, %2;" 169 | :"=a" (value) //Output 170 | : "a" (value), "m" (*variable) //Input 171 | :"memory" ); 172 | return value; 173 | } 174 | 175 | inline int xaddi(int *variable, int value) { 176 | asm volatile( 177 | "lock; xadd %%eax, %2;" 178 | :"=a" (value) //Output 179 | : "a" (value), "m" (*variable) //Input 180 | :"memory" ); 181 | return value; 182 | } 183 | 184 | // The conditional should be removed by the compiler 185 | // this should work with pointer types, or pairs of integers 186 | template 187 | inline ET xadd(ET *variable, ET value) { 188 | if (sizeof(ET) == 8) { 189 | return xaddl((long*)variable,(long)value); 190 | } else if (sizeof(ET) == 4) { 191 | return xaddi((int*)variable,(int)value); 192 | } else { 193 | std::cout << "xadd bad length" << std::endl; 194 | abort(); 195 | } 196 | } 197 | 198 | 199 | template 200 | inline ET fetchAndAdd(ET *a, ET b) { 201 | volatile ET newV, oldV; 202 | //abort(); 203 | do {oldV = *a; newV = oldV + b;} 204 | while (!CAS_GCC(a, oldV, newV)); 205 | return oldV; 206 | } 207 | 208 | template 209 | inline void writeAdd(ET *a, ET b) { 210 | volatile ET newV, oldV; 211 | do { oldV = *a; newV = oldV + b;} 212 | while (!CAS_GCC(a, oldV, newV)); 213 | } 214 | 215 | template 216 | inline bool writeAddOnce(ET *a, ET b) { 217 | volatile ET newV, oldV; 218 | oldV = *a; newV = oldV + b; 219 | return CAS_GCC(a, oldV, newV); 220 | } 221 | 222 | template 223 | inline bool writeAddOnce(ET *a, ET b, intT k) { 224 | volatile ET newV, oldV; 225 | for (intT i=0; i 235 | inline bool writeMax(ET *a, ET b) { 236 | ET c; bool r=0; 237 | do c = *a; 238 | while (c < b && !(r=CAS_GCC(a,c,b))); 239 | return r; 240 | } 241 | 242 | template 243 | inline bool writeMin(ET *a, ET b) { 244 | ET c; bool r=0; 245 | do c = *a; 246 | while (c > b && !(r=CAS_GCC(a,c,b))); 247 | return r; 248 | } 249 | 250 | template 251 | inline bool writeMin(ET **a, ET *b) { 252 | ET* c; bool r = 0; 253 | do c = *a; 254 | while (c > b && !(r=CAS_GCC(a,c,b))); 255 | return r; 256 | } 257 | 258 | template 259 | inline bool writeMin(ET *a, ET b, F f) { 260 | ET c; bool r=0; 261 | do c = *a; 262 | while (f(b,c) && !(r=CAS_GCC(a,c,b))); 263 | return r; 264 | } 265 | 266 | template 267 | struct identityF { E operator() (const E& x) {return x;}}; 268 | 269 | template 270 | struct addF { E operator() (const E& a, const E& b) const {return a+b;}}; 271 | 272 | template 273 | struct absF { E operator() (const E& a) const {return std::abs(a);}}; 274 | 275 | template 276 | struct zeroF { E operator() (const E& a) const {return 0;}}; 277 | 278 | template 279 | struct maxF { E operator() (const E& a, const E& b) const {return (a>b) ? a : b;}}; 280 | 281 | template 282 | struct minF { E operator() (const E& a, const E& b) const {return (a 285 | struct firstF {E1 operator() (std::pair a) {return a.first;} }; 286 | 287 | template 288 | struct secondF {E2 operator() (std::pair a) {return a.second;} }; 289 | 290 | */ 291 | 292 | template 293 | bool myCAS(eType* p, eType o, eType n) { 294 | return std::atomic_compare_exchange_strong_explicit( 295 | reinterpret_cast*>(p), &o, n, std::memory_order_relaxed, std::memory_order_relaxed); 296 | } 297 | 298 | template 299 | inline bool writeMin(ET *a, ET b) { 300 | ET c; bool r=0; 301 | do c = *a; 302 | // while (c > b && !(r=CAS_GCC(a,c,b))); 303 | while (c > b &&!(r=myCAS(a,c,b))); 304 | return r; 305 | } 306 | 307 | template 308 | struct identityF { E operator() (const E& x) {return x;}}; 309 | 310 | template 311 | struct addF { E operator() (const E& a, const E& b) const {return a+b;}}; 312 | 313 | } 314 | 315 | #endif -------------------------------------------------------------------------------- /include/dbscan/pbbs/work_stealing_job.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020-present Guy Blelloch and other contributors 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #ifndef PARLAY_INTERNAL_WORK_STEALING_JOB_H_ 22 | #define PARLAY_INTERNAL_WORK_STEALING_JOB_H_ 23 | 24 | #include 25 | 26 | #include 27 | 28 | namespace parlay { 29 | 30 | // Jobs are thunks -- i.e., functions that take no arguments 31 | // and return nothing. Could be a lambda, e.g. [] () {}. 32 | 33 | struct WorkStealingJob { 34 | WorkStealingJob() { 35 | done.store(false, std::memory_order_relaxed); 36 | } 37 | ~WorkStealingJob() = default; 38 | void operator()() { 39 | assert(done.load(std::memory_order_relaxed) == false); 40 | execute(); 41 | done.store(true, std::memory_order_relaxed); 42 | } 43 | bool finished() { 44 | return done.load(std::memory_order_relaxed); 45 | } 46 | virtual void execute() = 0; 47 | std::atomic done; 48 | }; 49 | 50 | // Holds a type-specific reference to a callable object 51 | template 52 | struct JobImpl : WorkStealingJob { 53 | explicit JobImpl(F& _f) : WorkStealingJob(), f(_f) { } 54 | void execute() override { 55 | f(); 56 | } 57 | F& f; 58 | }; 59 | 60 | template 61 | JobImpl make_job(F& f) { return JobImpl(f); } 62 | 63 | } 64 | 65 | #endif // PARLAY_INTERNAL_WORK_STEALING_JOB_H_ 66 | -------------------------------------------------------------------------------- /include/dbscan/point.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "pbbs/parallel.h" 9 | 10 | using namespace std; 11 | 12 | // ************************************************************* 13 | // any dimensional POINTS 14 | // ************************************************************* 15 | 16 | template class point { 17 | public: 18 | using floatT = double; 19 | using pointT = point; 20 | 21 | floatT x[_dim]; 22 | static const int dim = _dim; 23 | static constexpr double empty = numeric_limits::max(); 24 | 25 | point() { for (int i=0; i<_dim; ++i) x[i]=empty; } 26 | point(floatT* p) { for (int i=0; i<_dim; ++i) x[i]=p[i]; } 27 | point(pointT* p) { for (int i=0; i<_dim; ++i) x[i]=p->x[i]; } 28 | 29 | int dimension() {return _dim;} 30 | void setEmpty() {x[0]=empty;} 31 | bool isEmpty() {return x[0]==empty;} 32 | 33 | pointT operator-(pointT op2) { 34 | floatT xx[_dim]; 35 | for (int i=0; i<_dim; ++i) xx[i] = x[i]-op2.x[i]; 36 | return pointT(xx); 37 | } 38 | pointT operator/(floatT dv) { 39 | floatT xx[_dim]; 40 | for (int i=0; i<_dim; ++i) xx[i] = x[i]/dv; 41 | return pointT(xx); 42 | } 43 | pointT operator*(floatT dv) { 44 | floatT xx[_dim]; 45 | for (int i=0; i<_dim; ++i) xx[i] = x[i]*dv; 46 | return pointT(xx); 47 | } 48 | floatT& operator[](int i) {return x[i];} 49 | friend bool operator==(pointT a, pointT b) { 50 | for (intT ii=0; ii 0.0) return false;} 52 | return true; 53 | } 54 | friend bool operator!=(pointT a, pointT b) {return !(a==b);} 55 | 56 | floatT* coordinate() {return x;} 57 | floatT coordinate(int i) {return x[i];} 58 | void updateX(int i, floatT val) {x[i]=val;}//Deprecate 59 | void updateCoordinate(int i, floatT val) {x[i]=val;} 60 | pointT average(pointT p2) { 61 | auto pp = pointT(); 62 | for (int i=0; i<_dim; ++i) pp.x[i] = (p2[i] + x[i])/2; 63 | return pp; 64 | } 65 | void minCoords(pointT b) { 66 | for (int i=0; i<_dim; ++i) x[i] = min(x[i], b.x[i]);} 67 | void minCoords(floatT* b) { 68 | for (int i=0; i<_dim; ++i) x[i] = min(x[i], b[i]);} 69 | void maxCoords(pointT b) { 70 | for (int i=0; i<_dim; ++i) x[i] = max(x[i], b.x[i]);} 71 | void maxCoords(floatT* b) { 72 | for (int i=0; i<_dim; ++i) x[i] = max(x[i], b[i]);} 73 | intT quadrant(pointT center) { 74 | intT index = 0; 75 | intT offset = 1; 76 | for (int i=0; i<_dim; ++i) { 77 | if (x[i] > center.x[i]) index += offset; 78 | offset *= 2; 79 | } 80 | return index; 81 | } 82 | bool outOfBox(pointT center, floatT hsize) { 83 | for (int i=0; i<_dim; ++i) { 84 | if (x[i]-hsize > center.x[i] || x[i]+hsize < center.x[i]) 85 | return true; 86 | } 87 | return false; 88 | } 89 | inline floatT dist(pointT p) { 90 | floatT xx=0; 91 | for (int i=0; i<_dim; ++i) xx += (x[i]-p.x[i])*(x[i]-p.x[i]); 92 | return sqrt(xx); 93 | } 94 | inline floatT distSqr(pointT p) { 95 | floatT xx=0; 96 | for (int i=0; i<_dim; ++i) xx += (x[i]-p.x[i])*(x[i]-p.x[i]); 97 | return xx; 98 | } 99 | floatT dot(pointT p2) { 100 | floatT r = 0; 101 | for(int i=0; i 118 | static std::ostream& operator<<(std::ostream& os, const point v) { 119 | for (int i=0; i 36 | struct hashFloatToCell { 37 | typedef double floatT; 38 | typedef point pointT; 39 | static const unsigned int prime = -5; 40 | static const unsigned int mask = -1; 41 | static const unsigned int range = (1 << 29); 42 | static const bool noRandom = false; 43 | int rands[10] = {846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492, 596516649, 1189641421, 120120309}; 44 | int randInt[dim]; 45 | floatT r; 46 | pointT pMin; 47 | hashFloatToCell(pointT pMinn, floatT rr): r(rr), pMin(pMinn) { 48 | srand(time(NULL)); 49 | for (intT i = 0; i < dim; i++) { 50 | if(noRandom) randInt[i] = rands[i] % range + 1; 51 | else randInt[i] = rand() % range + 1;} 52 | } 53 | inline uintT primeHash(intT* x, intT n) { 54 | unsigned long long temp = 0; 55 | uintT key = 0; 56 | for (intT i=0; i> 32); 59 | if (temp >= prime) temp -= prime; 60 | temp += key; 61 | if (temp >= prime) temp -= prime; 62 | key = (uintT) temp; 63 | } 64 | return key;} 65 | //+1: later cell, -1 earlier cell, 0 same cell 66 | inline int compareCell(floatT* x1, floatT* x2) { 67 | for(int i=0; i xx2) return 1; 72 | else return -1; 73 | }} 74 | return 0;} 75 | //+1: larger point, -1 smaller point, 0 same point 76 | inline int comparePoint(floatT* x1, floatT* x2) { 77 | for(int i=0; i x2[i]) return 1; 80 | else return -1; 81 | }} 82 | return 0;} 83 | inline uintT hash(floatT *x) { 84 | intT xx[dim]; 85 | for(int i=0; icoordinate, ../common/ndHash.h 91 | template 92 | struct aFloatHash { 93 | typedef double floatT; 94 | typedef hashFloatToCell hashFunc; 95 | typedef objT* eType; 96 | typedef objT* kType; 97 | hashFunc* hashF; 98 | objT* e; 99 | aFloatHash(hashFunc* hashFF):hashF(hashFF) { 100 | e = new objT();} 101 | ~aFloatHash() {} 102 | eType empty() {return e;} 103 | kType getKey(eType v) {return v;} 104 | uintT hash(kType c) { 105 | return hashF->hash(c->coordinate()); 106 | } 107 | int cmp(kType c1, kType c2) { 108 | if (c1->isEmpty() || c2->isEmpty()) return 1; 109 | return hashF->comparePoint(c1->coordinate(), c2->coordinate()); 110 | } 111 | //inline int diffPoint(floatT* p1, floatT* p2) {return hashF->comparePoint(p1, p2);} 112 | bool replaceQ(eType c1, eType c2) {return 1;} 113 | }; 114 | 115 | // ************************************************************* 116 | // Misc 117 | // ************************************************************* 118 | template 119 | point pMinSerial(point* items, intT n) { 120 | point pMin = point(items[0].x); 121 | for(intT p=0; p 127 | point pMinParallel(point* items, intT n) { 128 | point pMin = point(items[0].x); 129 | // intT P = getWorkers()*8; 130 | static const intT P = 36 * 8; 131 | intT blockSize = (n+P-1)/P; 132 | point localMin[P]; 133 | for (intT i=0; i(items[0].x);} 135 | parallel_for(0, P, [&](intT p) { 136 | intT s = p*blockSize; 137 | intT e = min((p+1)*blockSize,n); 138 | for (intT j=s; j(items[0].x); 142 | for(intT p=0; p={sys.version_info.major}.{sys.version_info.minor},<4', 60 | install_requires=[f'numpy>={numpy.__version__},<2'], 61 | extras_require={ 62 | 'scikit-learn': ['scikit-learn'], 63 | 'example': ['scikit-learn', 'matplotlib'], 64 | 'py36': ['scikit-learn', 'matplotlib', 'pytest'], 65 | }, 66 | zip_safe=False, 67 | 68 | # To be removed when setuptools is good enough to support pyproject.toml 69 | # completely. 70 | author="Yiqiu Wang", 71 | author_email="yiqiu_wang@icloud.com", 72 | description="Theoretically efficient and practical parallel DBSCAN", 73 | long_description=long_description, 74 | long_description_content_type="text/markdown", 75 | keywords='cluster clustering density dbscan', 76 | url="https://github.com/wangyiqiu/dbscan-python", 77 | license='MIT', 78 | classifiers=[ 79 | 'Development Status :: 2 - Pre-Alpha', 80 | 'Intended Audience :: Science/Research', 81 | 'Intended Audience :: Developers', 82 | "License :: OSI Approved :: MIT License", 83 | 'Programming Language :: C++', 84 | 'Programming Language :: Python :: 3.8', 85 | 'Topic :: Software Development', 86 | 'Topic :: Scientific/Engineering', 87 | "Operating System :: POSIX :: Linux", 88 | ], 89 | ) 90 | -------------------------------------------------------------------------------- /src/Caller.cpp: -------------------------------------------------------------------------------- 1 | #include "Caller.h" 2 | #include "capi.cpp" 3 | 4 | // Deprecated Caller class API 5 | namespace Wrapper { 6 | 7 | // Default constructor 8 | Caller::Caller () {} 9 | 10 | // Overloaded constructor 11 | Caller::Caller (double* PF, intT dim, intT n) { 12 | this->PF = PF; 13 | this->n = n; 14 | this->dim = dim; 15 | } 16 | 17 | // Destructor 18 | Caller::~Caller () {} 19 | 20 | intT* Caller::computeDBSCAN(double epsilon, intT minPts, bool* coreFlag, intT* labels) { 21 | DBSCAN(dim, n, PF, epsilon, minPts, coreFlag, labels); 22 | return labels; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Caller.h: -------------------------------------------------------------------------------- 1 | #include "dbscan/pbbs/parallel.h" 2 | #include "dbscan/capi.h" 3 | 4 | // Deprecated Caller class API 5 | namespace Wrapper { 6 | class [[deprecated("If you must have a function that is able to work on data of unknown dimension, use the DBSCAN in dbscan/capi.h. I would highly consider using dbscan/algo.h instead if you know the number of dimensions at compile time.")]] 7 | Caller { 8 | public: 9 | int n; 10 | int dim; 11 | double* PF; 12 | Caller(); 13 | Caller(double* PF, int dim, int n); 14 | ~Caller(); 15 | int* computeDBSCAN(double epsilon, int minPts, bool* coreFlag, int* cluster); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/capi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | A C API that instantiates templates for the C++ DBSCAN function. This API is 3 | also useful in C++ because it allows for the selection of the number of 4 | dimensions in the data. 5 | */ 6 | 7 | #include "dbscan/algo.h" 8 | #include "dbscan/capi.h" 9 | #include "dbscan/pbbs/parallel.h" 10 | #include "dbscan/pbbs/utils.h" 11 | 12 | // https://artificial-mind.net/blog/2020/10/31/constexpr-for 13 | template 14 | static constexpr void constexpr_for(F&& f) 15 | { 16 | if constexpr (Start < End) 17 | { 18 | f(std::integral_constant()); 19 | constexpr_for(f); 20 | } 21 | } 22 | 23 | extern "C" int DBSCAN(intT dim, intT n, floatT* PF, double epsilon, intT minPts, bool* coreFlag, intT* labels) { 24 | auto coreFlag2 = newA(intT, n); 25 | int error = 1; // didn't match number of dimensions 26 | constexpr_for([&](auto i){ 27 | if (dim == i) { 28 | error = DBSCAN(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels); 29 | free(coreFlag2); 30 | } 31 | }); 32 | return error; 33 | } 34 | 35 | /* 36 | Equivalent to the following, but can be controlled via compile time flags 37 | 38 | extern "C" int DBSCAN(intT dim, intT n, floatT* PF, double epsilon, intT minPts, bool* coreFlag, intT* labels) { 39 | auto coreFlag2 = newA(intT, n); 40 | if (dim == 2) {DBSCAN<2>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 41 | else if (dim == 3) {DBSCAN<3>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 42 | else if (dim == 4) {DBSCAN<4>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 43 | else if (dim == 5) {DBSCAN<5>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 44 | else if (dim == 6) {DBSCAN<6>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 45 | else if (dim == 7) {DBSCAN<7>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 46 | else if (dim == 8) {DBSCAN<8>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 47 | else if (dim == 9) {DBSCAN<9>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 48 | else if (dim == 10) {DBSCAN<10>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 49 | else if (dim == 11) {DBSCAN<11>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 50 | else if (dim == 12) {DBSCAN<12>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 51 | else if (dim == 13) {DBSCAN<13>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 52 | else if (dim == 14) {DBSCAN<14>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 53 | else if (dim == 15) {DBSCAN<15>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 54 | else if (dim == 16) {DBSCAN<16>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 55 | else if (dim == 17) {DBSCAN<17>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 56 | else if (dim == 18) {DBSCAN<18>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 57 | else if (dim == 19) {DBSCAN<19>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 58 | else if (dim == 20) {DBSCAN<20>(n, PF, epsilon, minPts, coreFlag, coreFlag2, labels);} 59 | else { 60 | return 1; 61 | } 62 | free(coreFlag2); 63 | return 0; 64 | } 65 | */ 66 | -------------------------------------------------------------------------------- /src/dbscan: -------------------------------------------------------------------------------- 1 | ../include/dbscan -------------------------------------------------------------------------------- /src/dbscanmodule.cpp: -------------------------------------------------------------------------------- 1 | #include "Python.h" 2 | #include "numpy/arrayobject.h" 3 | #include "dbscan/capi.h" 4 | #include "dbscan/pbbs/parallel.h" 5 | 6 | 7 | static PyObject* DBSCAN_py(PyObject* self, PyObject* args, PyObject *kwargs) 8 | { 9 | PyObject *Xobj; 10 | PyArrayObject *X = NULL; 11 | double eps = 0.5; 12 | int min_samples = 5; 13 | 14 | static const char *kwlist[] = {"X", "eps", "min_samples", NULL}; 15 | 16 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|di:DBSCAN", (char**)kwlist, 17 | &Xobj, &eps, &min_samples)) 18 | { 19 | return NULL; 20 | } 21 | 22 | // Check the number of dimensions and that we actually recieved an np.ndarray 23 | X = (PyArrayObject*)PyArray_FROMANY( 24 | Xobj, 25 | NPY_DOUBLE, 26 | 2, 27 | 2, 28 | NPY_ARRAY_CARRAY_RO 29 | ); 30 | 31 | if (X == NULL) 32 | { 33 | return NULL; 34 | } 35 | 36 | npy_intp *dims = PyArray_SHAPE(X); 37 | 38 | npy_intp n = dims[0]; 39 | npy_intp dim = dims[1]; 40 | 41 | if (dim < DBSCAN_MIN_DIMS) 42 | { 43 | PyErr_SetString(PyExc_ValueError, "DBSCAN: invalid input data dimensionality (has to >=" Py_STRINGIFY(DBSCAN_MIN_DIMS) ")"); 44 | return NULL; 45 | } 46 | 47 | if (dim > DBSCAN_MAX_DIMS) 48 | { 49 | PyErr_SetString(PyExc_ValueError, "DBSCAN: dimension >" Py_STRINGIFY(DBSCAN_MAX_DIMS) " is not supported"); 50 | return NULL; 51 | } 52 | 53 | if (n > 100000000) 54 | { 55 | PyErr_WarnEx(PyExc_RuntimeWarning, "DBSCAN: large n, the program behavior might be undefined due to overflow", 1); 56 | } 57 | 58 | PyArrayObject* core_samples = (PyArrayObject*)PyArray_SimpleNew(1, &n, NPY_BOOL); 59 | PyArrayObject* labels = (PyArrayObject*)PyArray_SimpleNew(1, &n, NPY_INT); 60 | 61 | parlay::internal::start_scheduler(); 62 | 63 | DBSCAN( 64 | dim, 65 | n, 66 | (double*)PyArray_DATA(X), 67 | eps, 68 | min_samples, 69 | (bool*)PyArray_DATA(core_samples), 70 | (int*)PyArray_DATA(labels) 71 | ); 72 | 73 | parlay::internal::stop_scheduler(); 74 | 75 | return PyTuple_Pack(2, labels, core_samples); 76 | } 77 | 78 | PyDoc_STRVAR(doc_DBSCAN, 79 | "DBSCAN(X, eps=0.5, min_samples=5)\n--\n\n\ 80 | Run DBSCAN on a set of n samples of dimension dim with a minimum seperation\n\ 81 | between the clusters (which must include at least min_samples) of eps. Points\n\ 82 | that do not fit in any cluster are labeled as noise (-1).\n\ 83 | \n\ 84 | This function returns a tuple consisting of an int array of length n containing\n\ 85 | the labels after clustering and a bool array of length n which differentiates\n\ 86 | whether or not the sample is the core sample of its cluster.\n\ 87 | \n\ 88 | Parameters\n\ 89 | ----------\n\ 90 | X : np.ndarray[tuple[n, dim], np.float64]\n\ 91 | 2-D array representing the samples.\n\ 92 | eps : float\n\ 93 | minimum seperation between the clusters.\n\ 94 | min_samples : int\n\ 95 | minimum number of samples in the clusters.\n\ 96 | \n\ 97 | Returns\n\ 98 | -------\n\ 99 | labels : np.ndarray[tuple[n], np.int_]\n\ 100 | the labels after clustering\n\ 101 | core_samples : np.ndarray[tuple[n], np.bool_]\n\ 102 | is each sample the core sample of its cluster\n\ 103 | \n"); 104 | 105 | static struct PyMethodDef methods[] = { 106 | {"DBSCAN", (PyCFunction)(void*)(PyCFunctionWithKeywords) DBSCAN_py, METH_VARARGS | METH_KEYWORDS, doc_DBSCAN}, 107 | {NULL, NULL, 0, NULL} 108 | }; 109 | 110 | static struct PyModuleDef dbscanModule = 111 | { 112 | PyModuleDef_HEAD_INIT, 113 | "_dbscan", 114 | "", 115 | 0, 116 | methods 117 | }; 118 | 119 | PyMODINIT_FUNC 120 | PyInit__dbscan(void) 121 | { 122 | import_array(); 123 | PyObject *module = PyModule_Create(&dbscanModule); 124 | #ifdef DBSCAN_VERSION 125 | PyModule_AddStringConstant(module, "__version__", DBSCAN_VERSION); 126 | #endif 127 | PyModule_AddIntMacro(module, DBSCAN_MIN_DIMS); 128 | PyModule_AddIntMacro(module, DBSCAN_MAX_DIMS); 129 | 130 | return module; 131 | } 132 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | set(CMAKE_CXX_STANDARD 17) 3 | 4 | # check whether googletest is locally installed, if not download and fetch 5 | message(STATUS "--------------- GoogleTest -------------") 6 | find_package(GTest CONFIG) 7 | if(NOT GTest_FOUND) 8 | # new way of including googletest 9 | # Download and unpack googletest at configure time 10 | configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) 11 | execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . 12 | RESULT_VARIABLE result 13 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test/googletest-download ) 14 | if(result) 15 | message(FATAL_ERROR "CMake step for googletest failed: ${result}") 16 | endif() 17 | execute_process(COMMAND ${CMAKE_COMMAND} --build . 18 | RESULT_VARIABLE result 19 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test/googletest-download ) 20 | if(result) 21 | message(FATAL_ERROR "Build step for googletest failed: ${result}") 22 | endif() 23 | 24 | # Prevent overriding the parent project's compiler/linker 25 | # settings on Windows 26 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 27 | 28 | # Add googletest directly to our build. This defines 29 | # the gtest and gtest_main targets. 30 | add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src 31 | ${CMAKE_BINARY_DIR}/googletest-build 32 | EXCLUDE_FROM_ALL) 33 | set(GTest_LIBRARIES "gtest") 34 | else() 35 | message(STATUS "using locally installed GoogleTest") 36 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 37 | set(GTest_LIBRARIES GTest::gtest) 38 | endif() 39 | 40 | include(CTest) 41 | 42 | project(sourceFiles) 43 | add_library(sourceFiles INTERFACE) 44 | target_include_directories(sourceFiles INTERFACE ../src/) 45 | 46 | add_executable(grid_test grid_test.cpp) 47 | target_link_libraries(grid_test PRIVATE 48 | sourceFiles 49 | ${GTest_LIBRARIES}) 50 | add_test(NAME grid_test COMMAND grid_test) 51 | 52 | add_executable(dbscan_test dbscan_test.cpp ../src/capi.cpp) 53 | target_link_libraries(dbscan_test PRIVATE 54 | sourceFiles 55 | ${GTest_LIBRARIES}) 56 | add_test(NAME dbscan_test COMMAND dbscan_test) 57 | 58 | # add_executable(dbscan_test dbscan_test.cpp) 59 | # target_link_libraries(dbscan_test PRIVATE 60 | # gridLib 61 | # dbscanLib 62 | # ${GTest_LIBRARIES}) 63 | # add_test(NAME dbscan_test COMMAND dbscan_test) 64 | -------------------------------------------------------------------------------- /test/CMakeLists.txt.in: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project(googletest-download NONE) 4 | 5 | include(ExternalProject) 6 | ExternalProject_Add(googletest 7 | GIT_REPOSITORY https://github.com/google/googletest.git 8 | GIT_TAG main 9 | SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" 10 | BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" 11 | CONFIGURE_COMMAND "" 12 | BUILD_COMMAND "" 13 | INSTALL_COMMAND "" 14 | TEST_COMMAND "" 15 | ) 16 | -------------------------------------------------------------------------------- /test/dbscan_test.cpp: -------------------------------------------------------------------------------- 1 | #include "dbscan/capi.h" 2 | #include "gtest/gtest.h" 3 | #include "dbscan/pbbs/parallel.h" 4 | #include 5 | #include 6 | 7 | TEST(testDBSCAN, smallCluster1) { 8 | int n = 5; 9 | int dim = 2; 10 | 11 | std::unique_ptr data(new double[n * dim]); 12 | std::unique_ptr coreFlag(new bool[n]); 13 | std::unique_ptr cluster(new int[n]); 14 | 15 | data[0] = 0; data[1] = 2; 16 | data[2] = 1; data[3] = 3; 17 | data[4] = 1.5; data[5] = 2.5; 18 | data[6] = 2.5; data[7] = 1.5; 19 | data[8] = 4; data[9] = 0; 20 | 21 | DBSCAN(dim, n, data.get(), 1.42, 3, coreFlag.get(), cluster.get()); 22 | 23 | EXPECT_EQ(coreFlag[0], 0); 24 | EXPECT_EQ(coreFlag[1], 1); 25 | EXPECT_EQ(coreFlag[2], 1); 26 | EXPECT_EQ(coreFlag[3], 0); 27 | EXPECT_EQ(coreFlag[4], 0); 28 | 29 | EXPECT_EQ(cluster[0], cluster[1]); 30 | EXPECT_EQ(cluster[0], cluster[2]); 31 | EXPECT_EQ(cluster[0], cluster[3]); 32 | EXPECT_EQ(cluster[4], -1); 33 | } 34 | 35 | TEST(testDBSCAN, smallCluster1_largeEps) { 36 | int n = 5; 37 | int dim = 2; 38 | 39 | std::unique_ptr data(new double[n * dim]); 40 | std::unique_ptr coreFlag(new bool[n]); 41 | std::unique_ptr cluster(new int[n]); 42 | 43 | data[0] = 0; data[1] = 2; 44 | data[2] = 1; data[3] = 3; 45 | data[4] = 1.5; data[5] = 2.5; 46 | data[6] = 2.5; data[7] = 1.5; 47 | data[8] = 4; data[9] = 0; 48 | 49 | DBSCAN(dim, n, data.get(), 5.7, 3, coreFlag.get(), cluster.get()); 50 | 51 | EXPECT_EQ(coreFlag[0], 1); 52 | EXPECT_EQ(coreFlag[1], 1); 53 | EXPECT_EQ(coreFlag[2], 1); 54 | EXPECT_EQ(coreFlag[3], 1); 55 | EXPECT_EQ(coreFlag[4], 1); 56 | 57 | EXPECT_EQ(cluster[0], cluster[1]); 58 | EXPECT_EQ(cluster[0], cluster[2]); 59 | EXPECT_EQ(cluster[0], cluster[3]); 60 | EXPECT_EQ(cluster[0], cluster[4]); 61 | } 62 | 63 | TEST(testDBSCAN, smallCluster1_smallEps) { 64 | int n = 5; 65 | int dim = 2; 66 | 67 | std::unique_ptr data(new double[n * dim]); 68 | std::unique_ptr coreFlag(new bool[n]); 69 | std::unique_ptr cluster(new int[n]); 70 | 71 | data[0] = 0; data[1] = 2; 72 | data[2] = 1; data[3] = 3; 73 | data[4] = 1.5; data[5] = 2.5; 74 | data[6] = 2.5; data[7] = 1.5; 75 | data[8] = 4; data[9] = 0; 76 | 77 | DBSCAN(dim, n, data.get(), 0.7, 3, coreFlag.get(), cluster.get()); 78 | 79 | EXPECT_EQ(coreFlag[0], 0); 80 | EXPECT_EQ(coreFlag[1], 0); 81 | EXPECT_EQ(coreFlag[2], 0); 82 | EXPECT_EQ(coreFlag[3], 0); 83 | EXPECT_EQ(coreFlag[4], 0); 84 | 85 | EXPECT_EQ(cluster[0], -1); 86 | EXPECT_EQ(cluster[1], -1); 87 | EXPECT_EQ(cluster[2], -1); 88 | EXPECT_EQ(cluster[3], -1); 89 | EXPECT_EQ(cluster[4], -1); 90 | } 91 | 92 | int main(int argc, char **argv) { 93 | parlay::internal::start_scheduler(); 94 | ::testing::InitGoogleTest(&argc, argv); 95 | auto result = RUN_ALL_TESTS(); 96 | parlay::internal::stop_scheduler(); 97 | return result; 98 | } 99 | -------------------------------------------------------------------------------- /test/grid_test.cpp: -------------------------------------------------------------------------------- 1 | #include "dbscan/cell.h" 2 | #include "dbscan/grid.h" 3 | #include "dbscan/point.h" 4 | #include "gtest/gtest.h" 5 | #include "dbscan/pbbs/parallel.h" 6 | #include 7 | #include 8 | 9 | TEST(testGrid, construction) { 10 | static const int dim = 2; 11 | using pointT = point; 12 | using gridT = grid; 13 | typedef cell cellT; 14 | 15 | int n = 9; 16 | 17 | std::unique_ptr data(new double[n * dim]); 18 | 19 | pointT* PRead = (pointT*) data.get(); 20 | 21 | data[0] = 0.5; data[1] = 3.5; 22 | data[2] = 0; data[3] = 0; 23 | data[4] = 0.5; data[5] = 2.5; 24 | data[6] = 1.5; data[7] = 3.5; 25 | data[8] = 1.5; data[9] = 0.5; 26 | data[10] = 2.5; data[11] = 1.5; 27 | data[12] = 2.5; data[13] = 0.5; 28 | data[14] = 2.5; data[15] = 0.5; 29 | data[16] = 0.5; data[17] = 3.5; 30 | 31 | std::unique_ptr G(new gridT(n+1, PRead[0], 1)); 32 | std::unique_ptr I(new intT[n]); 33 | std::unique_ptr P(new pointT[n]); 34 | G->insertParallel(PRead, P.get(), n, I.get()); 35 | 36 | EXPECT_EQ(G->numCell(), 7); 37 | } 38 | 39 | TEST(testGrid, basic) { 40 | static const int dim = 2; 41 | using pointT = point; 42 | using gridT = grid; 43 | typedef cell cellT; 44 | 45 | int n = 4; 46 | 47 | std::unique_ptr data(new double[n * dim]); 48 | 49 | data[0] = 0; data[1] = 0; 50 | data[2] = 0.5; data[3] = 0; 51 | data[4] = 0; data[5] = 1.5; 52 | data[6] = 2; data[7] = 0; 53 | 54 | pointT* PRead = (pointT*) data.get(); 55 | 56 | { 57 | std::unique_ptr G(new gridT(n+1, PRead[0], 1.0)); 58 | std::unique_ptr I(new intT[n]); 59 | std::unique_ptr P(new pointT[n]); 60 | G->insertParallel(PRead, P.get(), 1, I.get()); 61 | EXPECT_EQ(G->numCell(), 1); 62 | } 63 | 64 | { 65 | std::unique_ptr G(new gridT(n+1, PRead[0], 1.0)); 66 | std::unique_ptr I(new intT[n]); 67 | std::unique_ptr P(new pointT[n]); 68 | G->insertParallel(PRead, P.get(), 2, I.get()); 69 | EXPECT_EQ(G->numCell(), 1); 70 | } 71 | 72 | { 73 | std::unique_ptr G(new gridT(n+1, PRead[0], 0.5)); 74 | std::unique_ptr I(new intT[n]); 75 | std::unique_ptr P(new pointT[n]); 76 | G->insertParallel(PRead, P.get(), 2, I.get()); 77 | EXPECT_EQ(G->numCell(), 2); 78 | } 79 | 80 | { 81 | std::unique_ptr G(new gridT(n+1, PRead[0], 1)); 82 | std::unique_ptr I(new intT[n]); 83 | std::unique_ptr P(new pointT[n]); 84 | G->insertParallel(PRead, P.get(), 3, I.get()); 85 | EXPECT_EQ(G->numCell(), 2); 86 | } 87 | 88 | { 89 | std::unique_ptr G(new gridT(n+1, PRead[0], 0.5)); 90 | std::unique_ptr I(new intT[n]); 91 | std::unique_ptr P(new pointT[n]); 92 | G->insertParallel(PRead, P.get(), 3, I.get()); 93 | EXPECT_EQ(G->numCell(), 3); 94 | } 95 | 96 | { 97 | std::unique_ptr G(new gridT(n+1, PRead[0], 0.5)); 98 | std::unique_ptr I(new intT[n]); 99 | std::unique_ptr P(new pointT[n]); 100 | G->insertParallel(PRead, P.get(), n, I.get()); 101 | EXPECT_EQ(G->numCell(), 4); 102 | } 103 | 104 | { 105 | std::unique_ptr G(new gridT(n+1, PRead[0], 1)); 106 | std::unique_ptr I(new intT[n]); 107 | std::unique_ptr P(new pointT[n]); 108 | G->insertParallel(PRead, P.get(), n, I.get()); 109 | EXPECT_EQ(G->numCell(), 3); 110 | } 111 | 112 | { 113 | std::unique_ptr G(new gridT(n+1, PRead[0], 2.01)); 114 | std::unique_ptr I(new intT[n]); 115 | std::unique_ptr P(new pointT[n]); 116 | G->insertParallel(PRead, P.get(), n, I.get()); 117 | EXPECT_EQ(G->numCell(), 1); 118 | } 119 | } 120 | 121 | TEST(testGrid, countNghCell) { 122 | static const int dim = 2; 123 | using pointT = point; 124 | using gridT = grid; 125 | typedef cell cellT; 126 | 127 | int n = 6; 128 | 129 | std::unique_ptr data(new double[n * dim]); 130 | 131 | pointT* PRead = (pointT*) data.get(); 132 | 133 | data[0] = 0; data[1] = 0; 134 | data[2] = 2.5; data[3] = 2.5; 135 | data[4] = 2.5; data[5] = 3.5; 136 | data[6] = 2.5; data[7] = 4.5; 137 | data[8] = 3.5; data[9] = 3.5; 138 | data[10] = 3.5; data[11] = 3.5; 139 | 140 | std::unique_ptr G(new gridT(n+1, PRead[0], 1)); 141 | std::unique_ptr I(new intT[n]); 142 | std::unique_ptr P(new pointT[n]); 143 | G->insertParallel(PRead, P.get(), n, I.get()); 144 | 145 | EXPECT_EQ(G->numCell(), 5); 146 | 147 | int count = 0; 148 | 149 | auto nbrhoodSize = [&](cellT* c) { 150 | count ++; 151 | return false; 152 | }; 153 | 154 | G->nghCellMap(G->getCell(P[1].coordinate()), nbrhoodSize); 155 | 156 | EXPECT_EQ(count, 4); 157 | 158 | } 159 | 160 | TEST(testGrid, countNghPoint) { 161 | static const int dim = 2; 162 | using pointT = point; 163 | using gridT = grid; 164 | typedef cell cellT; 165 | 166 | int n = 6; 167 | 168 | std::unique_ptr data(new double[n * dim]); 169 | 170 | pointT* PRead = (pointT*) data.get(); 171 | 172 | data[0] = 0; data[1] = 0; 173 | data[2] = 2.5; data[3] = 2.5; 174 | data[4] = 2.5; data[5] = 3.5; 175 | data[6] = 2.5; data[7] = 4.5; 176 | data[8] = 3.5; data[9] = 3.5; 177 | data[10] = 3.5; data[11] = 3.5; 178 | 179 | std::unique_ptr G(new gridT(n+1, PRead[0], 1)); 180 | std::unique_ptr I(new intT[n]); 181 | std::unique_ptr P(new pointT[n]); 182 | G->insertParallel(PRead, P.get(), n, I.get()); 183 | 184 | EXPECT_EQ(G->numCell(), 5); 185 | 186 | int count = 0; 187 | 188 | auto nbrhoodSize = [&](pointT* p) { 189 | count ++; 190 | return false; 191 | }; 192 | 193 | G->nghPointMap(P[1].coordinate(), nbrhoodSize); 194 | 195 | EXPECT_EQ(count, 5); 196 | 197 | } 198 | 199 | int main(int argc, char **argv) { 200 | parlay::internal::start_scheduler(); 201 | ::testing::InitGoogleTest(&argc, argv); 202 | auto result = RUN_ALL_TESTS(); 203 | parlay::internal::stop_scheduler(); 204 | return result; 205 | } 206 | -------------------------------------------------------------------------------- /test/test_python_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is almost identical to "example.py", except that it compares it 3 | to the correct answer to create a test case. 4 | """ 5 | 6 | import numpy as np 7 | 8 | from sklearn.datasets import make_blobs 9 | from sklearn.preprocessing import StandardScaler 10 | 11 | from sklearn.cluster import DBSCAN as goldDBSCAN 12 | from dbscan import sklDBSCAN as ourDBSCAN 13 | 14 | import unittest 15 | 16 | class ExampleTest(unittest.TestCase): 17 | 18 | def verify_output(self, X): 19 | # ###################################################################### 20 | # Compute DBSCAN 21 | 22 | labels = goldDBSCAN(eps=0.3, min_samples=10).fit(X).labels_ 23 | true_labels = ourDBSCAN(eps=0.3, min_samples=10).fit(X).labels_ 24 | 25 | # ###################################################################### 26 | # Test case 27 | 28 | mapping = {-1: -1} 29 | 30 | for i, (label, true_label) in enumerate(zip(labels, true_labels)): 31 | if true_label in mapping: 32 | self.assertTrue( 33 | mapping[true_label] == label, 34 | "Contradiction at slot {}. mapping={}. true={}. ours={}." \ 35 | .format(i, mapping, true_label, label) 36 | ) 37 | else: 38 | mapping[true_label] = label 39 | 40 | def test_example(self): 41 | # ###################################################################### 42 | # Generate sample data 43 | centers = [[1, 1], [-1, -1], [1, -1]] 44 | X, labels_true = make_blobs(n_samples=750, centers=centers, 45 | cluster_std=0.4, random_state=0) 46 | X = StandardScaler().fit_transform(X) 47 | self.verify_output(X) 48 | --------------------------------------------------------------------------------