pax_global_header00006660000000000000000000000064150505036020014506gustar00rootroot0000000000000052 comment=27dfa5a357a3eef3934d8dfd7f9a2e2e217fccc1 scikit-bio-scikit-bio-binaries-27dfa5a/000077500000000000000000000000001505050360200200635ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/.github/000077500000000000000000000000001505050360200214235ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/.github/workflows/000077500000000000000000000000001505050360200234605ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/.github/workflows/main.yml000066400000000000000000000043001505050360200251240ustar00rootroot00000000000000name: skbb CI on: push: branches: [ main ] pull_request: branches: [ main ] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: build-and-test: strategy: matrix: os: [ubuntu-latest, macos-13, macos-latest, ubuntu-24.04-arm] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: conda-incubator/setup-miniconda@v3 with: miniconda-version: "latest" auto-update-conda: true - name: Build shell: bash -l {0} run: | df -h . cplatform=`conda info |awk '/platform/{print $3}'` echo "Conda platform: ${cplatform}" if [[ "$(uname -s)" == "Linux" ]]; then if [[ "${cplatform}" == "linux-64" ]]; then # only do GPU compute on x86 platforms for now echo "Enabling GPU build" # cuda-compiler and gxx must match conda create -q --yes --strict-channel-priority -n skbb-build -c conda-forge -c bioconda gxx_${cplatform} cuda-compiler export NV_CUDA=Y else conda create -q --yes --strict-channel-priority -n skbb-build -c conda-forge -c bioconda gxx_${cplatform} fi else conda create -q --yes --strict-channel-priority -n skbb-build -c conda-forge -c bioconda clangxx_${cplatform} fi conda activate skbb-build conda install --yes --strict-channel-priority -c conda-forge libcblas liblapacke blas-devel make conda clean --yes -t df -h . echo "$(uname -s)" $CXX --version $CC --version export PERFORMING_CONDA_BUILD=True echo "======= begin env =====" env echo "======= end env =====" # all == build (shlib,bins,tests) and install make clean && make clean_install && make all df -h . - name: Test shell: bash -l {0} run: | conda activate skbb-build if [[ "$(uname -s)" == "Linux" ]]; then lscpu fi # keep it low for runs in containers # and a weird number to potentially catch potential bugs export OMP_NUM_THREADS=3 make test scikit-bio-scikit-bio-binaries-27dfa5a/LICENSE.txt000066400000000000000000000027531505050360200217150ustar00rootroot00000000000000Copyright (c) 2013--, scikit-bio development team. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the names scikit-bio, skbio, or biocore nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. scikit-bio-scikit-bio-binaries-27dfa5a/Makefile000066400000000000000000000006571505050360200215330ustar00rootroot00000000000000.PHONY: all api install test_bins test clean clean_install all: $(MAKE) api $(MAKE) install $(MAKE) test_bins api: cd src && $(MAKE) api install: cd src && $(MAKE) install test_bins: cd src && $(MAKE) test_bins cd api_tests && $(MAKE) test_bins test: cd src && $(MAKE) test cd api_tests && $(MAKE) test clean: cd api_tests && $(MAKE) clean cd src && $(MAKE) clean clean_install: cd src && $(MAKE) clean_install scikit-bio-scikit-bio-binaries-27dfa5a/README.rst000066400000000000000000000154101505050360200215530ustar00rootroot00000000000000|License| |Version| |Downloads| |Platforms| .. image:: https://scikit.bio/_images/logo.svg :width: 300 px :target: https://scikit.bio :alt: scikit-bio logo *scikit-bio-binaries is an open-source, BSD-licensed package providing optimized algorithms for bioinformatics in support of scikit-bio.* Installation ------------ You can install the latest release of scikit-bio-binaries using ``conda`` (``_):: conda install -c conda-forge scikit-bio-binaries Alternatively, in the near future you will be able to install the latest release of scikit-bio-binaries using ``pip``:: pip install scikit-bio-binaries Local build instructions ------------------------ The package can be build from source on your local machine. We recommend using the conda-provided compilers and libraries, but system-installed ones should work as well. If you decide to create a dedicated build environment in ``conda`` (``_):: cplatform=`conda info |awk '/platform/{print $3}'` if [[ "$(uname -s)" == "Linux" ]]; then conda create -n skbb-build -c conda-forge gxx_${cplatform} else conda create -n skbb-build -c conda-forge clangxx_${cplatform} fi conda activate skbb-build conda install -c conda-forge libcblas liblapacke blas-devel make make clean && make clean_install && make all To test that the build succeeded, run:: make test GPU support ~~~~~~~~~~~ To build NVIDIA-GPU-enabled code, you will also need the CUDA compiler. On Linux, you can install it from conda with:: conda install -c conda-forge cuda-compiler The CUDA build is not enabled by default. If you are interested in building the GPU-accelerated libraries, set:: export NV_CUDA=Y To build AMD-GPU-enabled code, you will also need the HIP compiler. The HIP build is not enabled by default. If you are interested in building the GPU-accelerated libraries, set:: export AMD_HIP=Y There is also support for OpenMP Offload and OpenACC builds, by setting either ``AMD_CXX`` or ``NV_CXX``, but it is still experimental. Provided files and their usage ------------------------------ The functions provided by scikit-bio-binaries are packaged as a shared library and will be installed in:: $CONDA_PREFIX/lib/libskbb.so If GPU code was build, you will also have:: $CONDA_PREFIX/lib/libskbb_acc_nv.so The C header files that describe how to access them are avaialable in ``_, but will also be installed in:: $CONDA_PREFIX/include/scikit-bio-binaries/ Compiled languages can use the provided C headers during compilation, and link against the provided shared library. See ``_ for an example:: $(CC) $(CFLAGS) my_code.c $(LDFLAGS) -lskbb -o my_exe Python users can instead use the `ctypes `_ module to import the shared library at runtime. No header files are needed (or provided), but you can use the C headers to guide your implementation. For example:: import ctypes dll = ctypes.CDLL("libskbb.so") skbb_version = dll.skbb_get_api_version() Environment considerations -------------------------- Multi-core support ~~~~~~~~~~~~~~~~~~ This package uses OpenMP to make use of multiple CPU cores. By default, scikit-bio-binaries will use all the cores that are available on the system. To restrict the number of cores used, set:: export OMP_NUM_THREADS=nthreads Older CPU support ~~~~~~~~~~~~~~~~~~ On x86_64 based CPU platforms, scikit-bio-binaries will auto-detect the CPU generation, i.e. if it supports avx2 or avx512 vector instructions. To force the most compatible binary variant, one can set:: export SKBB_MAX_CPU=basic To check which binary is used (scikit-bio-binaries will print it to standard output at runtime), set:: export SKBB_CPU_INFO=Y GPU support ~~~~~~~~~~~ If the code has been compiled for GPUs, scikit-bio-binaries will auto-detect the presence of either NVIDIA or AMD GPUs, and use such a GPU for the GPU-enabled algorithms. To force CPU-only compute, one can set:: export SKBB_USE_GPU=N To check if a GPU is used (scikit-bio-binaries will print it to standard output at runtime), set:: export SKBB_GPU_INFO=Y Additional timing information ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When evaluating the performance of scikit-bio-binaries it is sometimes necessary to distinguish the time spent interacting with the data from the compute proper. Additional informational messages can be enabled by setting:: export SKBB_TIMING_INFO=Y Adoption -------- In the near future, ``scikit-bio-binaries`` will be used by ``scikit-bio`` and ``unifrac-binaries``. License ------- scikit-bio-binaries is available under the new BSD license. See `LICENSE.txt `_ for scikit-bio's license. Team ---- The library is currently mainatined by **Igor Sfiligoi** at the University of California San Diego (UCSD) (@sfiligoi). Guidance and support is also provided by **Dr. Qiyun Zhu** at Arizona State University (ASU) (@qiyunzhu), **Dr. Daniel McDonald** at the University of California San Diego (UCSD) (@wasade), and **Dr. Rob Knight** at the University of California San Diego (UCSD) (@rob-knight). Credits ------- The algorithms in this package are based on code developped as part of the **scikit-bio** (``_) package. See the main ``scikit-bio`` page for credits about the original algorithm contributers. Funding ------- The development of scikit-bio is currently supported by the U.S. Department of Energy, Office of Science under award number `DE-SC0024320 `_, awarded to Dr. Qiyun Zhu at ASU (lead PI), Dr. James Morton at Gutz Analytics, and Dr. Rob Knight at UCSD. Citation -------- If you use scikit-bio derived code, including scikit-bio-binaries, for any published research, please see our `Zenodo page `_ for how to cite. Branding -------- The logo of scikit-bio was created by `Alina Prassas `_. Vector and bitmap image files are available at the `logos `_ directory. .. |License| image:: https://anaconda.org/conda-forge/scikit-bio-binaries/badges/license.svg :target: https://anaconda.org/conda-forge/scikit-bio-binaries .. |Version| image:: https://anaconda.org/conda-forge/scikit-bio-binaries/badges/version.svg :target: https://anaconda.org/conda-forge/scikit-bio-binaries .. |Downloads| image:: https://anaconda.org/conda-forge/scikit-bio-binaries/badges/downloads.svg :target: https://anaconda.org/conda-forge/scikit-bio-binaries .. |Platforms| image:: https://anaconda.org/conda-forge/scikit-bio-binaries/badges/platforms.svg :target: https://anaconda.org/conda-forge/scikit-bio-binaries scikit-bio-scikit-bio-binaries-27dfa5a/api_tests/000077500000000000000000000000001505050360200220565ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/api_tests/Makefile000066400000000000000000000006141505050360200235170ustar00rootroot00000000000000.PHONY: test_bins test clean SKBB_TESTS := test_distance.exe test_ordination.exe test: $(SKBB_TESTS) ./test_distance.exe ./test_ordination.exe test_bins: $(SKBB_TESTS) test_distance.exe: test_distance.c $(CC) $(CFLAGS) -std=c99 -O0 -g $< $(LDFLAGS) -lskbb -o $@ test_ordination.exe: test_ordination.c $(CC) $(CFLAGS) -std=c99 -O0 -g $< $(LDFLAGS) -lskbb -o $@ clean: -rm -f *.o *.exe scikit-bio-scikit-bio-binaries-27dfa5a/api_tests/test_distance.c000066400000000000000000000065621505050360200250640ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include "scikit-bio-binaries/distance.h" #include "scikit-bio-binaries/util.h" #include #include #include #include int global_failed = false; int failed = false; #define ASSERT(x) {if(!(x)) \ { fprintf(stderr, "failed assert [%s:%i]\n", __FILE__, __LINE__); \ failed = true; global_failed = true;}} void test_permanova_ties() { failed = false; // Same as tests in src/tests const double matrix_fp64[] = { 0., 1., 1., 4., 1., 0., 3., 2., 1., 3., 0., 3., 4., 2., 3., 0.}; const float matrix_fp32[] = { 0., 1., 1., 4., 1., 0., 3., 2., 1., 3., 0., 3., 4., 2., 3., 0.}; const unsigned int grouping_equal[] = { 0, 0, 1, 1}; const unsigned int grouping_equal_swapped[] = { 1, 1, 0, 0}; const unsigned int n_samples = 4; // value from skbio const float exp_stat = 2.0; // using a different random than skbio, so result different const float exp_pvalue = 0.66; double stat_fp64, pvalue_fp64; float stat_fp32, pvalue_fp32; skbb_permanova_fp64(n_samples, matrix_fp64, grouping_equal, 999, -1, &stat_fp64, &pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); skbb_permanova_fp32(n_samples, matrix_fp32, grouping_equal, 999, 5, &stat_fp32, &pvalue_fp32); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); skbb_permanova_fp32(n_samples, matrix_fp32, grouping_equal_swapped, 999, 3, &stat_fp32, &pvalue_fp32); ASSERT(fabs(stat_fp32 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp32 - exp_pvalue) < 0.05); skbb_permanova_fp64(n_samples, matrix_fp64, grouping_equal_swapped, 999, -1, &stat_fp64, &pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); if (failed) { printf("ERROR: Permanova test failed\n"); } else { printf("INFO: Permanova test succeeded\n"); } } int main(int argc, char** argv) { global_failed = false; { failed = false; /* we are assuming we are testing our own build */ ASSERT(skbb_get_api_version()==SKBB_API_CURRENT_VERSION); if (failed) { printf("ERROR: Version check failed\n"); } } test_permanova_ties(); { failed = false; printf("[INFO] Forcibly enable acc reporting, current flag: %i\n",(int)skbb_is_acc_reporting()); skbb_set_acc_reporting_flag(true); ASSERT(skbb_is_acc_reporting()); printf("[INFO] Forcibly disable acc, current mode: %i\n",(int)skbb_get_acc_mode()); ASSERT(skbb_set_acc_mode(SKBB_ACC_CPU)); ASSERT(skbb_get_acc_mode()==0); if (failed) { printf("ERROR: Permanova test failed\n"); } else { printf("INFO: Permanova test succeeded\n"); } } test_permanova_ties(); printf("\n"); if (global_failed) { printf("ERROR: Some distance tests failed\n"); } else { printf("INFO: All distance tests succeeded\n"); } return failed ? 1 : 0; } scikit-bio-scikit-bio-binaries-27dfa5a/api_tests/test_ordination.c000066400000000000000000000364631505050360200254430ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include "scikit-bio-binaries/ordination.h" #include "scikit-bio-binaries/util.h" #include #include #include #include #include int global_failed = false; int failed = false; #define ASSERT(x) {if(!(x)) \ { fprintf(stderr, "failed assert [%s:%i]\n", __FILE__, __LINE__); \ failed = true; global_failed = true;}} void test_center_mat() { failed = false; // Same as tests in src/tests // unweighted unifrac of test.biom double matrix[] = { 0.0000000000, 0.2000000000, 0.5714285714, 0.6000000000, 0.5000000000, 0.2000000000, 0.2000000000, 0.0000000000, 0.4285714286 ,0.6666666667, 0.6000000000, 0.3333333333, 0.5714285714, 0.4285714286, 0.0000000000, 0.7142857143, 0.8571428571, 0.4285714286, 0.6000000000, 0.6666666667, 0.7142857143, 0.0000000000, 0.3333333333, 0.4000000000, 0.5000000000, 0.6000000000, 0.8571428571, 0.3333333333, 0.0000000000, 0.6000000000, 0.2000000000, 0.3333333333, 0.4285714286, 0.4000000000, 0.6000000000, 0.0000000000}; const unsigned int n_samples = 6; double exp[] = { 0.05343726, 0.04366213, -0.0329743 , -0.07912698, -0.00495654, 0.01995843, 0.04366213, 0.073887 , 0.04867914, -0.11112434, -0.04973167,-0.00537226, -0.0329743 , 0.04867914, 0.20714475, -0.07737528, -0.17044974, 0.02497543, -0.07912698, -0.11112434, -0.07737528, 0.14830877, 0.11192366, 0.00739418, -0.00495654, -0.04973167, -0.17044974, 0.11192366, 0.18664966,-0.07343537, 0.01995843, -0.00537226, 0.02497543, 0.00739418, -0.07343537, 0.02647959 }; // fp64 { double *centered = (double *) malloc(6*6*sizeof(double)); skbb_center_distance_matrix_fp64(n_samples, matrix, centered); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(centered[i]),float(exp[i])); ASSERT(fabs(centered[i] - exp[i]) < 0.000001); } free(centered); } // mixed { float *centered_fp32 = (float *) malloc(6*6*sizeof(float)); skbb_center_distance_matrix_fp64_to_fp32(n_samples, matrix, centered_fp32); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(centered_fp32[i]),float(exp[i])); ASSERT(fabs(centered_fp32[i] - exp[i]) < 0.000001); } free(centered_fp32); } float *matrix_fp32 = (float *) malloc(6*6*sizeof(float)); for(int i = 0; i < (6*6); i++) matrix_fp32[i] = matrix[i]; // fp32 { float *centered_fp32 = (float *) malloc(6*6*sizeof(float)); skbb_center_distance_matrix_fp32(n_samples, matrix_fp32, centered_fp32); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(centered_fp32[i]),float(exp[i])); ASSERT(fabs(centered_fp32[i] - exp[i]) < 0.000001); } free(centered_fp32); } // fp32 in-place { skbb_center_distance_matrix_fp32(n_samples, matrix_fp32, matrix_fp32); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(matrix_fp32[i]),float(exp[i])); ASSERT(fabs(matrix_fp32[i] - exp[i]) < 0.000001); } } free(matrix_fp32); // fp64 in-place { skbb_center_distance_matrix_fp64(n_samples, matrix, matrix); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(matrix_fp32[i]),float(exp[i])); ASSERT(fabs(matrix[i] - exp[i]) < 0.000001); } } if (failed) { printf("ERROR: Center distance matrix test failed\n"); } else { printf("INFO: Center distance matrix test succeeded\n"); } } void test_pcoa_fsvd() { failed = false; // Same as tests in src/tests // unweighted unifrac of crawford.biom double matrix[] = { 0. , 0.71836067 , 0.71317361 , 0.69746044 , 0.62587207 , 0.72826674 , 0.72065895 , 0.72640581 , 0.73606053, 0.71836067 , 0. , 0.70302967 , 0.73407301 , 0.6548042 , 0.71547381 , 0.78397813 , 0.72318399 , 0.76138933, 0.71317361 , 0.70302967 , 0. , 0.61041275 , 0.62331299 , 0.71848305 , 0.70416337 , 0.75258475 , 0.79249029, 0.69746044 , 0.73407301 , 0.61041275 , 0. , 0.6439278 , 0.70052733 , 0.69832716 , 0.77818938 , 0.72959894, 0.62587207 , 0.6548042 , 0.62331299 , 0.6439278 , 0. , 0.75782689 , 0.71005144 , 0.75065046 , 0.78944369, 0.72826674 , 0.71547381 , 0.71848305 , 0.70052733 , 0.75782689 , 0. , 0.63593642 , 0.71283615 , 0.58314638, 0.72065895 , 0.78397813 , 0.70416337 , 0.69832716 , 0.71005144 , 0.63593642 , 0. , 0.69200762 , 0.68972056, 0.72640581 , 0.72318399 , 0.75258475 , 0.77818938 , 0.75065046 , 0.71283615 , 0.69200762 , 0. , 0.71514083, 0.73606053 , 0.76138933 , 0.79249029 , 0.72959894 , 0.78944369 , 0.58314638 , 0.68972056 , 0.71514083 , 0. }; float matrix_fp32[9*9]; for (int i=0; i<(9*9); i++) matrix_fp32[i] = matrix[i]; const unsigned int n_samples = 9; // Test centering double exp2[] = { 0.22225336 , -0.025481 , -0.03491711 , -0.02614685 , 0.01899659 , -0.05103589 , -0.03971812 , -0.02699392 , -0.03695706, -0.025481 , 0.24282669 , -0.01744751 , -0.04206624 , 0.0107569 , -0.03151438 , -0.07706765 , -0.0143721 , -0.0456347, -0.03491711 , -0.01744751 , 0.21652901 , 0.02791465 , 0.0177328 , -0.04682078 , -0.03082866 , -0.04921529 , -0.08294711, -0.02614685 , -0.04206624 , 0.02791465 , 0.21190401 , 0.00235833 , -0.03639361 , -0.02904855 , -0.07112525 , -0.03739649, 0.01899659 , 0.0107569 , 0.0177328 , 0.00235833 , 0.20745566 , -0.08039931 , -0.03952883 , -0.05229812 , -0.08507403, -0.05103589 , -0.03151438 , -0.04682078 , -0.03639361 , -0.08039931 , 0.20604732 , 0.00964595 , -0.02533192 , 0.05580262, -0.03971812 , -0.07706765 , -0.03082866 , -0.02904855 , -0.03952883 , 0.00964595 , 0.21765972 , -0.00489531 , -0.00621856, -0.02699392 , -0.0143721 , -0.04921529 , -0.07112525 , -0.05229812 , -0.02533192 , -0.00489531 , 0.2514242 , -0.00719229, -0.03695706 , -0.0456347 , -0.08294711 , -0.03739649 , -0.08507403 , 0.05580262 , -0.00621856 , -0.00719229 , 0.24561762 }; double *centered = (double *) malloc(9*9*sizeof(double)); float *centered_fp32 = (float *) malloc(9*9*sizeof(float)); skbb_center_distance_matrix_fp64(n_samples, matrix, centered); for(int i = 0; i < (9*9); i++) { //printf("%i %f %f\n",i,float(centered[i]),float(exp[i])); ASSERT(fabs(centered[i] - exp2[i]) < 0.00001); } skbb_center_distance_matrix_fp64_to_fp32(n_samples, matrix, centered_fp32); for(int i = 0; i < (9*9); i++) { //printf("%i %f %f\n",i,float(centered_fp32[i]),float(exp[i])); ASSERT(fabs(centered_fp32[i] - exp2[i]) < 0.00001); } if (failed) { printf("ERROR: Center larger distance matrix test failed\n"); } else { printf("INFO: Center larger distance matrix test succeeded\n"); } // Test eigens double exp3a[] = {0.45752162, 0.3260088 , 0.2791141 , 0.26296948, 0.20924533}; double exp3b[] = { -0.17316152, 0.17579996, 0.23301609, -0.74519625, -0.05194624, -0.19959264, 0.53235665, -0.53370018, 0.2173474 , 0.26736004, -0.35794942, -0.27956624, 0.01114096, 0.40488848, -0.13121464, -0.2296467 , -0.47494333, -0.12571292, 0.02313551, -0.46916459, -0.44501584, 0.05597451, 0.07717711, -0.15881922, 0.24594442, 0.40335552, -0.16290597, -0.30327343, 0.03778646, 0.21664806, 0.23769142, -0.29034629, 0.46813757, 0.13858945, 0.58624834, 0.2407584 , 0.51300752, 0.48211607, 0.34422672, -0.42416046, 0.52356078, -0.0693768 , -0.30890127, -0.26195855, -0.23971493}; { double eigenvalues[5]; double eigenvectors[5*9]; skbb_fsvd_inplace_fp64(n_samples, centered, 5, -1, eigenvalues, eigenvectors); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp3a[i])); ASSERT(fabs(eigenvalues[i] - exp3a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(eigenvectors[i]),float(exp3b[i]),float(fabs(eigenvectors[i]) - fabs(exp3b[i]))); ASSERT( fabs(fabs(eigenvectors[i]) - fabs(exp3b[i])) < 0.00001); } } { float eigenvalues[5]; float eigenvectors[5*9]; skbb_fsvd_inplace_fp32(n_samples, centered_fp32, 5, 42, eigenvalues, eigenvectors); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp3a[i])); ASSERT(fabs(eigenvalues[i] - exp3a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(eigenvectors[i]),float(exp3b[i]),float(fabs(eigenvectors[i]) - fabs(exp3b[i]))); ASSERT( fabs(fabs(eigenvectors[i]) - fabs(exp3b[i])) < 0.00001); } } free(centered_fp32); free(centered); if (failed) { printf("ERROR: FSVD test failed\n"); } else { printf("INFO: FSVD test succeeded\n"); } failed = false; // Test PCoA (incudes the above calls double *exp4a = exp3a; // same eigenvals; double exp4b[] = { -0.11712705, 0.10037682, -0.12310531, -0.38214073, -0.02376195, -0.13500515, 0.30396064, 0.28196047, 0.11145694, 0.12229942, -0.24211822, -0.15962444, -0.00588591, 0.20762904, -0.06002196, -0.15533382, -0.27117925, 0.06641571, 0.01186402, -0.21461156, -0.30101024, 0.03195987, -0.04077363, -0.08144337, 0.1125032 , 0.27283106, -0.09301471, 0.16022314, 0.0193771 , 0.09910206, 0.16077529, -0.16577955, -0.24732293, 0.07106943, 0.26816958, 0.16284981, 0.29291283, -0.25470794, 0.17652136, -0.19402517, 0.35413832, -0.0396122 , 0.1631964 , -0.13433378, -0.10965362}; double exp4c[] = {0.22630343, 0.16125338, 0.13805791, 0.13007231, 0.10349879}; { double eigenvalues[5]; double samples[5*9]; double proportion_explained[5]; skbb_pcoa_fsvd_fp64(n_samples, matrix, 5, 999999, eigenvalues, samples, proportion_explained); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples[i]),float(exp4b[i]),float(fabs(samples[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained[i] - exp4c[i]) < 0.00001); } } // Test PCoA fp32 { float eigenvalues_fp32[5]; float samples_fp32[5*9]; float proportion_explained_fp32[5]; skbb_pcoa_fsvd_fp32(n_samples, matrix_fp32, 5, -100, eigenvalues_fp32, samples_fp32, proportion_explained_fp32); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues_fp32[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues_fp32[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples_fp32[i]),float(exp4b[i]),float(fabs(samples_fp32[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples_fp32[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained_fp32[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained_fp32[i] - exp4c[i]) < 0.00001); } } // Test PCoA mixed mode { float eigenvalues_fp32[5]; float samples_fp32[5*9]; float proportion_explained_fp32[5]; skbb_pcoa_fsvd_fp64_to_fp32(n_samples, matrix, 5, -1, eigenvalues_fp32, samples_fp32, proportion_explained_fp32); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues_fp32[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues_fp32[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples_fp32[i]),float(exp4b[i]),float(fabs(samples_fp32[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples_fp32[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained_fp32[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained_fp32[i] - exp4c[i]) < 0.00001); } } // test in-place { double eigenvalues[5]; double samples[5*9]; double proportion_explained[5]; skbb_pcoa_fsvd_inplace_fp64(n_samples, matrix, 5, 1, eigenvalues, samples, proportion_explained); // Note: matrix content has been destroyed for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples[i]),float(exp4b[i]),float(fabs(samples[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained[i] - exp4c[i]) < 0.00001); } } // in-plave fp32 { float eigenvalues[5]; float samples[5*9]; float proportion_explained[5]; skbb_pcoa_fsvd_inplace_fp32(n_samples, matrix_fp32, 5, -9, eigenvalues, samples, proportion_explained); // Note: matrix_fp32 content has been destroyed for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples[i]),float(exp4b[i]),float(fabs(samples[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained[i] - exp4c[i]) < 0.00001); } } if (failed) { printf("ERROR: PCoA test failed\n"); } else { printf("INFO: PCoA test succeeded\n"); } } int main(int argc, char** argv) { global_failed = false; { failed = false; /* we are assuming we are testing our own build */ ASSERT(skbb_get_api_version()==SKBB_API_CURRENT_VERSION); if (failed) { printf("ERROR: Version check failed\n"); } } test_center_mat(); skbb_set_random_seed(33); // no test, just exercising util.h test_pcoa_fsvd(); printf("\n"); if (global_failed) { printf("ERROR: Some ordination tests failed\n"); } else { printf("INFO: All ordination tests succeeded\n"); } return failed ? 1 : 0; } scikit-bio-scikit-bio-binaries-27dfa5a/scripts/000077500000000000000000000000001505050360200215525ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/scripts/enable_amd_compiler.sh000066400000000000000000000002261505050360200260470ustar00rootroot00000000000000export AMD_CXX=amdclang++ # no special flags needed in most cases export AMD_CPPFLAGS= export AMD_CXXFLAGS= export AMD_CFLAGS= export AMD_LDFLAGS= scikit-bio-scikit-bio-binaries-27dfa5a/scripts/enable_nv_compiler.sh000066400000000000000000000002151505050360200257270ustar00rootroot00000000000000export NV_CXX=nvc++ # no special flags needed in most cases export NV_CPPFLAGS= export NV_CXXFLAGS= export NV_CFLAGS= export NV_LDFLAGS= scikit-bio-scikit-bio-binaries-27dfa5a/src/000077500000000000000000000000001505050360200206525ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/src/Makefile000066400000000000000000000270161505050360200223200ustar00rootroot00000000000000.PHONY: all api install test_bins test clean clean_install all: $(MAKE) api $(MAKE) test_bins $(MAKE) install PLATFORM := $(shell uname -s) ARCH := $(shell uname -m) ifeq ($(PLATFORM),Darwin) # no GPUs on MacOS NOGPU := 1 endif ifeq ($(PREFIX),) PREFIX := $(CONDA_PREFIX) endif ifndef NOGPU # NVIDIA GPUs CXXFLAGS += -DSKBB_ENABLE_ACC_NV=1 SKBB_ENABLE_ACC_NV := 1 # AMD GPUs CXXFLAGS += -DSKBB_ENABLE_ACC_AMD=1 SKBB_ENABLE_ACC_AMD := 1 LDFLAGS += -ldl endif BLASLIB=-llapacke -lcblas ifeq ($(PLATFORM),Darwin) SO_LDDFLAGS = -L$(PREFIX)/lib -fopenmp -dynamiclib -install_name @rpath/libskbb.so else SO_LDDFLAGS = -L$(PREFIX)/lib -fopenmp -shared endif ifeq ($(ARCH),x86_64) ifeq ($(PLATFORM),Darwin) # No new x86 on Mac, so no need to make our life complicated else # create optimized versions for all the higher x86_64 levels CXXFLAGS += -DSKBB_ENABLE_CPU_X86_LEVELS=1 CXXFLAGS += -DSKBB_ENABLE_CPU_X86V3=1 -DSKBB_ENABLE_CPU_X86V4=1 SKBB_ENABLE_CPU_X86V3 := 1 SKBB_ENABLE_CPU_X86V4 := 1 # cpu_x86_v3 is AVX2 X86V3FLAGS := -march=x86-64-v3 -mtune=broadwell # cpu_x86_v4 is AVX512 X86V4FLAGS := -march=x86-64-v4 -mtune=znver4 endif endif CXXFLAGS += -O3 -ffast-math -fopenmp -Wall -std=c++17 -pedantic -I. $(OPT) -fPIC ifneq ($(SKBB_ENABLE_ACC_NV),) ifneq ($(NV_CUDA),) # CUDA version SKBB_ENABLE_ACC_NV_BINS := 1 NV_CXX := nvcc NV_CXXFLAGS := -DSKBB_CUDA NV_CXXFLAGS += -O3 --use_fast_math -std=c++17 -I. $(OPT) -Xcompiler -fPIC NV_CXXFLAGS += -arch=all-major else ifneq ($(NV_CXX),) # OpenACC version SKBB_ENABLE_ACC_NV_BINS := 1 NV_CXXFLAGS += -acc NV_CXXFLAGS += -Ofast -std=c++17 -I. $(OPT) -fPIC NV_LDFLAGS += -shared -acc -Bstatic_pgi NV_CXXFLAGS += -gpu=ccall NV_LDFLAGS += -gpu=ccall endif endif endif ifneq ($(SKBB_ENABLE_ACC_AMD),) ifneq ($(AMD_HIP),) # HIP version SKBB_ENABLE_ACC_AMD_BINS := 1 AMD_CXX := hipcc AMD_CXXFLAGS := -DSKBB_HIP AMD_CXXFLAGS += -O3 -ffast-math -std=c++17 -I. $(OPT) -fPIC AMD_CXXFLAGS += --offload-arch=gfx1100,gfx1101,gfx1102,gfx1103,gfx1030,gfx1031,gfx90a,gfx942 AMD_SO_LDDFLAGS += -shared -fgpu-rdc --hip-link else ifneq ($(AMD_CXX),) SKBB_ENABLE_ACC_AMD_BINS := 1 AMD_CXXFLAGS += -fopenmp -fopenmp-offload-mandatory -DOMPGPU=1 AMD_CXXFLAGS += -O3 -ffast-math -std=c++17 -I. $(OPT) -fPIC AMD_LDFLAGS += -shared -fopenmp AMD_CXXFLAGS += --offload-arch=gfx1100,gfx1101,gfx1102,gfx1103,gfx1030,gfx1031,gfx90a,gfx942 AMD_LDFLAGS += --offload-arch=gfx1100,gfx1101,gfx1102,gfx1103,gfx1030,gfx1031,gfx90a,gfx942 endif endif endif clean: # all source files are in subdirectories rm -f *.cpp *.hpp *.h *.cu rm -f *.o *.so *.a *.exe ## # Utility/helper modules SKBB_OBJS := util_rand.o util_rand.o: util/rand.cpp util/rand.hpp $(CXX) $(CXXFLAGS) -c $< -o $@ SKBB_OBJS += skbb_detect_acc.o skbb_detect_acc.o: util/skbb_detect_acc.cpp util/skbb_detect_acc.hpp util/skbb_accapi.hpp $(CXX) $(CXXFLAGS) -c $< -o $@ ## # skbb_accapi dynamic code wrappers generation # #TBD acc-specific binary variants SKBB_OBJS += skbb_accapi_cpu.o # Use the same compiler for CPU-based code # So no need for futher levels of indirection skbb_accapi_cpu.cpp: util/skbb_accapi.hpp ./tools/generate_skbb_accapi.py cpu direct > $@ skbb_accapi_cpu.o: skbb_accapi_cpu.cpp util/skbb_accapi.hpp util/skbb_accapi_impl.hpp $(CXX) $(CXXFLAGS) -DSKBB_ACC_NM=skbb_cpu -c $< -o $@ # # True accelerated variants will use a separate compiler # thus separate generic and acc-cpecific files # ifdef SKBB_ENABLE_ACC_NV SKBB_OBJS += skbb_accapi_acc_nv.o skbb_accapi_dyn_acc_nv.h: util/skbb_accapi_impl.hpp ./tools/generate_skbb_accapi.py acc_nv api_h > $@ skbb_accapi_dyn_acc_nv.cpp: util/skbb_accapi_impl.hpp skbb_accapi_dyn_acc_nv.h ./tools/generate_skbb_accapi.py acc_nv api > $@ skbb_accapi_acc_nv.cpp: util/skbb_accapi_impl.hpp skbb_accapi_dyn_acc_nv.h ./tools/generate_skbb_accapi.py acc_nv indirect > $@ skbb_accapi_acc_nv.o: skbb_accapi_acc_nv.cpp util/skbb_accapi.hpp skbb_accapi_dyn_acc_nv.h $(CXX) $(CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_nv -c $< -o $@ skbb_accapi_dyn_acc_nv.o: skbb_accapi_dyn_acc_nv.cpp skbb_accapi_dyn_acc_nv.h skbb_accapi_dyn_acc_nv.h $(NV_CXX) $(NV_CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_nv -c $< -o $@ endif ifdef SKBB_ENABLE_ACC_AMD SKBB_OBJS += skbb_accapi_acc_amd.o skbb_accapi_dyn_acc_amd.h: util/skbb_accapi_impl.hpp ./tools/generate_skbb_accapi.py acc_amd api_h > $@ skbb_accapi_dyn_acc_amd.cpp: util/skbb_accapi_impl.hpp skbb_accapi_dyn_acc_amd.h ./tools/generate_skbb_accapi.py acc_amd api > $@ skbb_accapi_acc_amd.cpp: util/skbb_accapi_impl.hpp skbb_accapi_dyn_acc_amd.h ./tools/generate_skbb_accapi.py acc_amd indirect > $@ skbb_accapi_acc_amd.o: skbb_accapi_acc_amd.cpp util/skbb_accapi.hpp skbb_accapi_dyn_acc_amd.h $(CXX) $(CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_amd -c $< -o $@ skbb_accapi_dyn_acc_amd.o: skbb_accapi_dyn_acc_amd.cpp skbb_accapi_dyn_acc_amd.h skbb_accapi_dyn_acc_amd.h $(AMD_CXX) $(AMD_CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_amd -c $< -o $@ endif ## # Distance methods SKBB_OBJS += dist_permanova.o dist_permanova.o: distance/permanova.cpp distance/permanova.hpp distance/permanova_dyn.hpp util/skbb_accapi.hpp util/skbb_detect_acc.hpp util/rand.hpp util/skbb_dgb_info.hpp $(CXX) $(CXXFLAGS) -c $< -o $@ # # permanova dynamic code wrappers generation # SKBB_OBJS += permanova_cpu.o # Use the same compiler for CPU-based code # So no need for futher levels of indirection permanova_cpu.cpp: distance/permanova_dyn.hpp ./tools/generate_permanova_dyn.py cpu direct > $@ permanova_cpu.o: permanova_cpu.cpp distance/permanova_dyn.hpp distance/permanova_dyn_impl.hpp $(CXX) $(CXXFLAGS) -DSKBB_ACC_NM=skbb_cpu -c $< -o $@ ifeq ($(SKBB_ENABLE_CPU_X86V3),1) SKBB_OBJS += permanova_cpu_x86_v3.o permanova_cpu_x86_v3.cpp: distance/permanova_dyn.hpp ./tools/generate_permanova_dyn.py cpu_x86_v3 direct > $@ permanova_cpu_x86_v3.o: permanova_cpu_x86_v3.cpp distance/permanova_dyn.hpp distance/permanova_dyn_impl.hpp $(CXX) $(CXXFLAGS) $(X86V3FLAGS) -DSKBB_ACC_NM=skbb_cpu_x86_v3 -c $< -o $@ endif ifeq ($(SKBB_ENABLE_CPU_X86V4),1) SKBB_OBJS += permanova_cpu_x86_v4.o permanova_cpu_x86_v4.cpp: distance/permanova_dyn.hpp ./tools/generate_permanova_dyn.py cpu_x86_v4 direct > $@ permanova_cpu_x86_v4.o: permanova_cpu_x86_v4.cpp distance/permanova_dyn.hpp distance/permanova_dyn_impl.hpp $(CXX) $(CXXFLAGS) $(X86V4FLAGS) -DSKBB_ACC_NM=skbb_cpu_x86_v4 -c $< -o $@ endif # # True accelerated variants will use a separate compiler # thus separate generic and acc-cpecific files # ifdef SKBB_ENABLE_ACC_NV SKBB_OBJS += permanova_acc_nv.o permanova_dyn_acc_nv.h: distance/permanova_dyn_impl.hpp ./tools/generate_permanova_dyn.py acc_nv api_h > $@ permanova_acc_nv.cpp: distance/permanova_dyn_impl.hpp permanova_dyn_acc_nv.h ./tools/generate_permanova_dyn.py acc_nv indirect > $@ permanova_acc_nv.o: permanova_acc_nv.cpp permanova_dyn_acc_nv.h distance/permanova_dyn.hpp util/skbb_dl.cpp $(CXX) $(CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_nv -c $< -o $@ ifneq ($(NV_CUDA),) # CUDA version permanova_dyn_acc_nv.cu: distance/permanova_dyn_impl.hpp permanova_dyn_acc_nv.h ./tools/generate_permanova_dyn.py acc_nv api > $@ permanova_dyn_acc_nv.o: permanova_dyn_acc_nv.cu distance/permanova_dyn.hpp distance/permanova_dyn_impl.hpp $(NV_CXX) $(NV_CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_nv -c $< -o $@ else # OpenACC version permanova_dyn_acc_nv.cpp: distance/permanova_dyn_impl.hpp permanova_dyn_acc_nv.h ./tools/generate_permanova_dyn.py acc_nv api > $@ permanova_dyn_acc_nv.o: permanova_dyn_acc_nv.cpp distance/permanova_dyn.hpp distance/permanova_dyn_impl.hpp $(NV_CXX) $(NV_CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_nv -c $< -o $@ endif endif ## ifdef SKBB_ENABLE_ACC_AMD SKBB_OBJS += permanova_acc_amd.o permanova_dyn_acc_amd.h: distance/permanova_dyn_impl.hpp ./tools/generate_permanova_dyn.py acc_amd api_h > $@ permanova_acc_amd.cpp: distance/permanova_dyn_impl.hpp permanova_dyn_acc_amd.h ./tools/generate_permanova_dyn.py acc_amd indirect > $@ permanova_acc_amd.o: permanova_acc_amd.cpp permanova_dyn_acc_amd.h distance/permanova_dyn.hpp util/skbb_dl.cpp $(CXX) $(CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_amd -c $< -o $@ ifneq ($(AMD_HIP),) # HIP version permanova_dyn_acc_amd.hip: distance/permanova_dyn_impl.hpp permanova_dyn_acc_amd.h ./tools/generate_permanova_dyn.py acc_amd api > $@ permanova_dyn_acc_amd.o: permanova_dyn_acc_amd.hip distance/permanova_dyn.hpp distance/permanova_dyn_impl.hpp $(AMD_CXX) $(AMD_CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_amd -c $< -o $@ else # OpenMP Target version permanova_dyn_acc_amd.cpp: distance/permanova_dyn_impl.hpp permanova_dyn_acc_amd.h ./tools/generate_permanova_dyn.py acc_amd api > $@ permanova_dyn_acc_amd.o: permanova_dyn_acc_amd.cpp distance/permanova_dyn.hpp distance/permanova_dyn_impl.hpp $(AMD_CXX) $(AMD_CXXFLAGS) -DSKBB_ACC_NM=skbb_acc_amd -c $< -o $@ endif endif ## # Ordination methods SKBB_OBJS += ord_pcoa.o ord_pcoa.o: ordination/principal_coordinate_analysis.cpp ordination/principal_coordinate_analysis.hpp util/skbb_dgb_info.hpp $(CXX) $(CXXFLAGS) -c $< -o $@ ## SHBB_EXTERN_OBJS := skbb_extern_distance.o skbb_extern_ordination.o skbb_extern_util.o SHBB_EXTERN_HS := distance.h ordination.h util.h skbb_extern_util.o: extern/skbb_util.cpp extern/util.h util/rand.hpp util/skbb_detect_acc.hpp $(CXX) $(CXXFLAGS) -c $< -o $@ skbb_extern_distance.o: extern/skbb_distance.cpp extern/distance.h distance/permanova.hpp $(CXX) $(CXXFLAGS) -c $< -o $@ skbb_extern_ordination.o: extern/skbb_ordination.cpp extern/ordination.h ordination/principal_coordinate_analysis.hpp $(CXX) $(CXXFLAGS) -c $< -o $@ ## SKBB_SHLIBS := libskbb.so libskbb.so: $(SHBB_EXTERN_OBJS) libskbb_cpu.a $(CXX) $(SO_LDDFLAGS) -o $@ $(SHBB_EXTERN_OBJS) libskbb_cpu.a $(BLASLIB) libskbb_cpu.a: $(SKBB_OBJS) ar -rc $@ $(SKBB_OBJS) ifdef SKBB_ENABLE_ACC_NV_BINS SKBB_SHLIBS += libskbb_acc_nv.so ifneq ($(NV_CUDA),) #CUDA version libskbb_acc_nv.so: skbb_accapi_dyn_acc_nv.o permanova_dyn_acc_nv.o $(CXX) $(SO_LDDFLAGS) -o $@ skbb_accapi_dyn_acc_nv.o permanova_dyn_acc_nv.o -lcudart_static else #OpenACC version libskbb_acc_nv.so: skbb_accapi_dyn_acc_nv.o permanova_dyn_acc_nv.o $(NV_CXX) $(NV_LDFLAGS) $(SO_LDDFLAGS) -o $@ skbb_accapi_dyn_acc_nv.o permanova_dyn_acc_nv.o endif endif ifdef SKBB_ENABLE_ACC_AMD_BINS SKBB_SHLIBS += libskbb_acc_amd.so ifneq ($(AMD_HIP),) #HIP version libskbb_acc_amd.so: skbb_accapi_dyn_acc_amd.o permanova_dyn_acc_amd.o $(AMD_CXX) $(AMD_SO_LDDFLAGS) -o $@ skbb_accapi_dyn_acc_amd.o permanova_dyn_acc_amd.o else #OpenMP Target version libskbb_acc_amd.so: skbb_accapi_dyn_acc_amd.o permanova_dyn_acc_amd.o $(AMD_CXX) $(AMD_LDFLAGS) $(SO_LDDFLAGS) -o $@ skbb_accapi_dyn_acc_amd.o permanova_dyn_acc_amd.o endif endif api: $(SKBB_SHLIBS) ## install: mkdir -p "${PREFIX}/lib" mkdir -p "${PREFIX}/include/scikit-bio-binaries" for f in $(SKBB_SHLIBS); do rm -f "${PREFIX}/lib/$${f}"; cp "$${f}" "${PREFIX}/lib/"; done for f in $(SHBB_EXTERN_HS); do rm -f "${PREFIX}/include/scikit-bio-binaries/$${f}"; cp "extern/$${f}" "${PREFIX}/include/scikit-bio-binaries/"; done clean_install: rm -f "${PREFIX}/lib/libskbb.so" for f in $(SKBB_SHLIBS); do rm -f "${PREFIX}/lib/$${f}"; done for f in $(SHBB_EXTERN_HS); do rm -f "${PREFIX}/include/scikit-bio-binaries/$${f}"; done ## test_bins: test_pcoa.exe test_permanova.exe test: test_bins ./test_pcoa.exe ./test_permanova.exe test_pcoa.exe: tests/test_pcoa.cpp libskbb_cpu.a $(CXX) $(CXXFLAGS) $< -o $@ libskbb_cpu.a $(LDFLAGS) $(BLASLIB) test_permanova.exe: tests/test_permanova.cpp libskbb_cpu.a $(CXX) $(CXXFLAGS) $< -o $@ libskbb_cpu.a $(LDFLAGS) scikit-bio-scikit-bio-binaries-27dfa5a/src/distance/000077500000000000000000000000001505050360200224445ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/src/distance/permanova.cpp000066400000000000000000000324001505050360200251370ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and functions for computing permanova */ #include "distance/permanova.hpp" #include "util/skbb_dgb_info.hpp" #include "util/skbb_detect_acc.hpp" #define SKBB_ACC_NM skbb_cpu #include "distance/permanova_dyn.hpp" #undef SKBB_ACC_NM #ifdef SKBB_ENABLE_CPU_X86V3 #define SKBB_ACC_NM skbb_cpu_x86_v3 #include "distance/permanova_dyn.hpp" #undef SKBB_ACC_NM #endif #ifdef SKBB_ENABLE_CPU_X86V4 #define SKBB_ACC_NM skbb_cpu_x86_v4 #include "distance/permanova_dyn.hpp" #undef SKBB_ACC_NM #endif #ifdef SKBB_ENABLE_ACC_NV #define SKBB_ACC_NM skbb_acc_nv #include "util/skbb_accapi.hpp" #include "distance/permanova_dyn.hpp" #undef SKBB_ACC_NM #endif #ifdef SKBB_ENABLE_ACC_AMD #define SKBB_ACC_NM skbb_acc_amd #include "util/skbb_accapi.hpp" #include "distance/permanova_dyn.hpp" #undef SKBB_ACC_NM #endif #include "util/rand.hpp" #include #include // // ======================= permanova ======================== // // Compute PERMANOVA pseudo-F partial statistic using permutations // mat is symmetric matrix of size n_dims x n_dims // grouping is an array of size n_dims // group_sizes is an array of size n_groups (i.e. maxel(grouping)) // // Results in permutted_sWs, and array of size (n_perm+1) template static inline void permanova_perm_fp_sW_T(const uint32_t n_dims, const TFloat mat[], const uint32_t grouping[], uint32_t n_groups, const uint32_t group_sizes[], const uint32_t n_perm, const int seed, TFloat *permutted_sWs) { SETUP_TDBG("permanova_perm_fp_sW") #if defined(SKBB_ENABLE_ACC_NV) || defined(SKBB_ENABLE_ACC_AMD) // There is acc-specific logic here, initialize skbio_use_acc ASAP auto use_acc = skbb::check_use_acc(); // mat_size only needed for non-cpu paths const uint64_t mat_size = uint64_t(n_dims)*uint64_t(n_dims); #endif uint32_t PERM_CHUNK = 1; // just a dummy default if (false) { #if defined(SKBB_ENABLE_ACC_NV) } else if (use_acc==skbb::ACC_NV) { PERM_CHUNK = skbb_acc_nv::pmn_get_max_parallelism(); #endif #if defined(SKBB_ENABLE_ACC_AMD) } else if (use_acc==skbb::ACC_AMD) { PERM_CHUNK = skbb_acc_amd::pmn_get_max_parallelism(); #endif } else { // default to CPU // Note: Do not need to worry about architectural levels for this PERM_CHUNK = skbb_cpu::pmn_get_max_parallelism(); } // need temp bufffer for bulk processing const uint32_t step_perms = std::min(n_perm+1,PERM_CHUNK); const uint64_t permutted_groupings_size = uint64_t(n_dims)*uint64_t(step_perms); uint32_t *permutted_groupings = new uint32_t[permutted_groupings_size]; // first copy the original in all of the buffer rows #pragma omp parallel for schedule(static,1) default(shared) for (uint32_t grouping_el=0; grouping_el < step_perms; grouping_el++) { uint32_t *my_grouping = permutted_groupings + uint64_t(grouping_el)*uint64_t(n_dims); for (uint32_t i=0; i(mat),&mat_out,mat_size); mat_device = mat_out; skbb_acc_nv::acc_copyin_buf(inv_group_sizes,&inv_group_sizes_device,n_groups); skbb_acc_nv::acc_create_buf(permutted_groupings,&permutted_groupings_device,permutted_groupings_size); skbb_acc_nv::acc_create_buf(permutted_sWs,&permutted_sWs_device,n_perm); } #endif #if defined(SKBB_ENABLE_ACC_AMD) if (use_acc==skbb::ACC_AMD) { TFloat* mat_out = 0; // must remove const as it will indeed write to GPU memory skbb_acc_amd::acc_copyin_buf(const_cast(mat),&mat_out,mat_size); mat_device = mat_out; skbb_acc_amd::acc_copyin_buf(inv_group_sizes,&inv_group_sizes_device,n_groups); skbb_acc_amd::acc_create_buf(permutted_groupings,&permutted_groupings_device,permutted_groupings_size); skbb_acc_amd::acc_create_buf(permutted_sWs,&permutted_sWs_device,n_perm); } #endif TDBG_STEP("initialize") // now permute and compute sWs for (uint32_t tp=0; tp < (n_perm+1); tp+=step_perms) { const uint32_t max_p = std::min(tp+step_perms,n_perm+1); // Split in chunks for data locality // first, permute the buffers #pragma omp parallel for schedule(static,1) default(shared) for (uint32_t p=tp; p(n_dims, mat_device, max_p-tp, permutted_groupings_device, inv_group_sizes_device, permutted_sWs_device+tp); #endif #if defined(SKBB_ENABLE_ACC_AMD) } else if (use_acc==skbb::ACC_AMD) { skbb_acc_amd::acc_update_device(permutted_groupings,permutted_groupings_device,0,permutted_groupings_size); skbb_acc_amd::pmn_f_stat_sW(n_dims, mat_device, max_p-tp, permutted_groupings_device, inv_group_sizes_device, permutted_sWs_device+tp); #endif } else { // as above, default to CPU #ifdef SKBB_ENABLE_CPU_X86_LEVELS auto use_cpu_x86 = skbb::check_use_cpu_x86(); #endif if (false) { #if defined(SKBB_ENABLE_CPU_X86V3) } else if (use_cpu_x86==skbb::CPU_X86_V3) { skbb_cpu_x86_v3::pmn_f_stat_sW(n_dims, mat, max_p-tp, permutted_groupings, inv_group_sizes, permutted_sWs+tp); #endif #if defined(SKBB_ENABLE_CPU_X86V4) } else if (use_cpu_x86==skbb::CPU_X86_V4) { skbb_cpu_x86_v4::pmn_f_stat_sW(n_dims, mat, max_p-tp, permutted_groupings, inv_group_sizes, permutted_sWs+tp); #endif } else { skbb_cpu::pmn_f_stat_sW( n_dims, mat, max_p-tp, permutted_groupings, inv_group_sizes, permutted_sWs+tp); } } } TDBG_STEP("pmn_f_stat_sW") #if defined(SKBB_ENABLE_ACC_NV) if (use_acc==skbb::ACC_NV) { skbb_acc_nv::acc_copyout_buf(permutted_sWs,permutted_sWs_device,n_perm); skbb_acc_nv::acc_destroy_buf(permutted_groupings_device,permutted_groupings_size); skbb_acc_nv::acc_destroy_buf(inv_group_sizes_device,n_groups); // must remove const, as it will indeed destroy the copy in GPU memory skbb_acc_nv::acc_destroy_buf(const_cast(mat_device),mat_size); } #endif #if defined(SKBB_ENABLE_ACC_AMD) if (use_acc==skbb::ACC_AMD) { skbb_acc_amd::acc_copyout_buf(permutted_sWs,permutted_sWs_device,n_perm); skbb_acc_amd::acc_destroy_buf(permutted_groupings_device,permutted_groupings_size); skbb_acc_amd::acc_destroy_buf(inv_group_sizes_device,n_groups); // must remove const, as it will indeed destroy the copy in GPU memory skbb_acc_amd::acc_destroy_buf(const_cast(mat_device),mat_size); } #endif delete[] inv_group_sizes; delete[] permutted_groupings; TDBG_STEP("finalize") } // Compute the square sum of the upper triangle template static inline TFloat sum_upper_square(const uint32_t n_dims, const TFloat mat[]) { // Use full precision for intermediate compute, to minimize accumulation errors double sum = 0.0; // not optimal parallelism, but this is cheap anyway #pragma omp parallel for shared(mat) reduction(+:sum) for (uint32_t row=0; row < (n_dims-1); row++) { // no columns in last row const TFloat * mat_row = mat + uint64_t(row)*uint64_t(n_dims); for (uint32_t col=row+1; col < n_dims; col++) { // diagonal is always zero TFloat val = mat_row[col]; // mat[row,col]; sum+=val*val; } // for tcol } // for trow return sum; } // Compute the permanova values for all of the permutations // mat is symmetric matrix of size n_dims x n_dims // grouping is an array of size n_dims // // Results in permutted_fstats, and array of size (n_perm+1) template static inline void permanova_all_T(const uint32_t n_dims, const TFloat mat[], const uint32_t grouping[], const uint32_t n_perm, const int seed, TFloat *permutted_fstats) { // first count the elements in the grouping uint32_t n_groups = (*std::max_element(grouping,grouping+n_dims)) + 1; uint32_t *group_sizes = new uint32_t[n_groups]; for (uint32_t i=0; i(n_dims,mat,grouping, n_groups,group_sizes, n_perm, seed, permutted_sWs); } // get the normalization factor TFloat s_T = sum_upper_square(n_dims, mat)/n_dims; TFloat inv_ngroups_1 = TFloat(1.0)/ (n_groups - 1); TFloat inv_dg = TFloat(1.0)/ (n_dims - n_groups); for (uint32_t i=0; i<(n_perm+1); i++) { // reminder permutted_sWs == permutted_fstats TFloat s_W = permutted_fstats[i]; TFloat s_A = s_T - s_W; permutted_fstats[i] = (s_A * inv_ngroups_1) / (s_W * inv_dg); } delete[] group_sizes; } // Compute the permanova values for all of the permutations // mat is symmetric matrix of size n_dims x n_dims // grouping is an array of size n_dims // // Results in permutted_fstats, and array of size (n_perm+1) template static inline void permanova_T(const uint32_t n_dims, const TFloat mat[], const uint32_t grouping[], const uint32_t n_perm, const int seed, TFloat &fstat, TFloat &pvalue) { // First compute all the permutations TFloat *permutted_fstats = new TFloat[n_perm+1]; permanova_all_T(n_dims,mat,grouping,n_perm,seed,permutted_fstats); // keep the first one and compute p_value, too TFloat myfstat = permutted_fstats[0]; fstat = myfstat; if (n_perm>0) { uint32_t count_larger = 0; for (uint32_t i=0; i= myfstat) count_larger++; } pvalue = (TFloat(1.0)*(count_larger+1))/(n_perm+1); } else { pvalue = 0.0; // just to have a deterministic value } delete[] permutted_fstats; } // // Instantiate concrete functions from tempates // void skbb::permanova(unsigned int n_dims, const double mat[], const uint32_t grouping[], unsigned int n_perm, int seed, double &fstat_out, double &pvalue_out) { SETUP_TDBG("permanova_fp64") permanova_T(n_dims, mat, grouping, n_perm, seed, fstat_out, pvalue_out); TDBG_STEP("permanova") } void skbb::permanova(unsigned int n_dims, const float mat[], const uint32_t grouping[], unsigned int n_perm, int seed, float &fstat_out, float &pvalue_out) { SETUP_TDBG("permanova_fp32") permanova_T(n_dims, mat, grouping, n_perm, seed, fstat_out, pvalue_out); TDBG_STEP("permanova") } scikit-bio-scikit-bio-binaries-27dfa5a/src/distance/permanova.hpp000066400000000000000000000036131505050360200251500ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and functions for computing permanova */ #ifndef PERMANOVA_HPP #define PERMANOVA_HPP #include namespace skbb { /* * Test for significant differences between groups using PERMANOVA. * * Permutational Multivariate Analysis of Variance (PERMANOVA) is a * non-parametric method that tests whether two or more groups of objects * (e.g., samples) are significantly different based on a categorical factor. * It is conceptually similar to ANOVA except that it operates on a distance * matrix, which allows for multivariate analysis. PERMANOVA computes a * pseudo-F statistic. * * Statistical significance is assessed via a permutation test. The assignment * of objects to groups (`grouping`) is randomly permuted a number of times * (controlled via `n_perm`). A pseudo-F statistic is computed for each * permutation and the p-value is the proportion of permuted pseudo-F * statisics that are equal to or greater than the original (unpermuted) * pseudo-F statistic. * * Input parameters: * n_dims - size of the matrix * mat - distance matrix (n_dims x n_dims) * grouping - grouping array of size n_dims * n_perm - number of permutations * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * fstat_out - pseudo-F statistics * pvalue_out - p-value */ void permanova(unsigned int n_dims, const double mat[], const uint32_t grouping[], unsigned int n_perm, int seed, double &fstat_out, double &pvalue_out); void permanova(unsigned int n_dims, const float mat[], const uint32_t grouping[], unsigned int n_perm, int seed, float &fstat_out, float &pvalue_out); } #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/distance/permanova_dyn.hpp000066400000000000000000000021641505050360200260220ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * * This file is used to create the necessary interfaces between * permanova_dyn_impl.hpp * by means of * generate_permanova_dyn.py * */ #ifdef SKBB_ACC_NM // do nothing if SKBB_ACC_NM is not defined #include namespace SKBB_ACC_NM { // Return the recommended max parallelism to use in pmn_f_stat_sW int pmn_get_max_parallelism(); // Compute PERMANOVA pseudo-F partial statistic // mat is symmetric matrix of size n_dims x n_dims // groupings is a matrix of size n_dims x n_grouping_dims // inv_group_sizes is an array of size maxel(groupings) // Results in group_sWs, and array of size n_grouping_dims // Note: Best results when n_grouping_dims fits in L1 cache template void pmn_f_stat_sW( const uint32_t n_dims, const TFloat * mat, const uint32_t n_grouping_dims, const uint32_t *groupings, const TFloat *inv_group_sizes, TFloat *group_sWs); } #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/distance/permanova_dyn_impl.hpp000066400000000000000000000300311505050360200270350ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * * This file is used to create the necessary interfaces * by means of * generate_permanova_dyn.py * * Anything ending in _T will get a acc-specific function wrapper. * */ #include "distance/permanova_dyn.hpp" #include #include #if defined(SKBB_CUDA) #include #include #include #elif defined(SKBB_HIP) #include #include #include #elif defined(_OPENACC) #include #elif !(defined(_OPENACC) || defined(OMPGPU)) #include #define SKBB_CPU Y #endif static inline int pmn_get_max_parallelism_T() { #if defined(SKBB_CPU) // No good reason to do more than max threads // (but use 2x to reduce thread spawning overhead) // but we do use 16x blocking, so account for that, too return 2*omp_get_max_threads()*16; #elif defined(SKBB_CUDA) int deviceID; cudaDeviceProp props; if (cudaGetDevice(&deviceID)!=cudaSuccess) return 4000; // should never get in here, but just in case cudaGetDeviceProperties(&props, deviceID); // GPUs typically need at least 64 blocks per SM to be fully loaded // We want a few multiples of that to deal with unbalanced load // Most permanovas are multiple of 100, so double that makes a good constant return 200*props.multiProcessorCount; #elif defined(SKBB_HIP) int deviceID = 0; hipDeviceProp_t props; if (hipGetDevice(&deviceID)!=hipSuccess) return 4000; // should never get in here, but just in case if (hipGetDeviceProperties(&props, deviceID)!=hipSuccess) throw std::runtime_error("hipGetDeviceProperties failed"); // GPUs typically need at least 64 blocks per SM to be fully loaded // We want a few multiples of that to deal with unbalanced load // Most permanovas are multiple of 100, so double that makes a good constant return 200*props.multiProcessorCount; #else // 1k is enough for consumer-grade GPUs // 4k needed for larger GPUs // Keeping it at 4k not slowing down consumer GPUs // Getting much higher than 4k seems to slow down the compute return 4000; // should likely by dynamic, but there are no portable functions avaialble #endif } #if !(defined(_OPENACC) || defined(OMPGPU)) // CPU version, uses tilling // no internal parallelism, all parallelism handled outside // Compute PERMANOVA pseudo-F partial statistic // mat is symmetric matrix of size n_dims x in_n // grouping is an array of size in_n // inv_group_sizes is an array of size maxel(grouping) template static inline void pmn_f_stat_sW_block( const uint32_t n_dims, const TFloat * mat, const uint32_t *grouping_arr[], const TFloat *inv_group_sizes, TFloat out_sW_arr[]) { // Use full precision for intermediate compute, to minimize accumulation errors double s_W[NBLOCK]; for (int i=0; i static inline void pmn_f_stat_sW_cpu( const uint32_t n_dims, const TFloat * mat, const uint32_t n_grouping_dims, const uint32_t *groupings, const TFloat *inv_group_sizes, TFloat *group_sWs) { constexpr int NBLOCK = 16; // CPU version, call function #pragma omp parallel for for (uint32_t gblock=0; gblock < n_grouping_dims; gblock+=NBLOCK) { const uint32_t *grouping_arr[NBLOCK]; for (int i=0; i(n_dims,mat, grouping_arr, inv_group_sizes, group_sWs+grouping_el); } else { uint32_t gblock2=gblock; // Note: if we ever change NBLOCK, we need to update this logic, too if ((gblock2+(8-1))(n_dims,mat, grouping_arr, inv_group_sizes, group_sWs+grouping_el); gblock2+=8; } if ((gblock2+(4-1))(n_dims,mat, grouping_arr, inv_group_sizes, group_sWs+grouping_el); gblock2+=4; } if ((gblock2+(2-1))(n_dims,mat, grouping_arr, inv_group_sizes, group_sWs+grouping_el); gblock2+=2; } if (gblock2(n_dims,mat, grouping_arr, inv_group_sizes, group_sWs+grouping_el); } } // if can use big block } } #elif (defined(SKBB_CUDA) || defined(SKBB_HIP)) template __global__ void pmn_f_stat_sW_cuda_one( const uint32_t n_dims, const TFloat * mat, const uint32_t n_grouping_dims, const uint32_t *groupings, const TFloat *inv_group_sizes, TFloat *group_sWs) { const uint32_t grouping_el = blockIdx.x; const uint32_t icol = threadIdx.x; const uint32_t TILE = blockDim.x; __shared__ double s_W_arr[128]; // we will not have more than 128 threads __shared__ uint32_t row_grouping[128]; // we will not have more than 128 threads const uint32_t *grouping = groupings + uint64_t(grouping_el)*uint64_t(n_dims); // Use full precision for intermediate compute, to minimize accumulation errors double s_W = 0.0; for (uint32_t trow=0; trow < (n_dims-1); trow+=TILE) { // no columns in last row for (uint32_t tcol=trow+1; tcol < n_dims; tcol+=TILE) { // diagonal is always zero const uint32_t max_row = min(trow+TILE,n_dims-1); const uint32_t max_col = min(tcol+TILE,n_dims); __syncthreads(); // read grouping[row] in advance into shared memory, using the whole block // helps with read coalesence here, and shared memory is OK to read sparse { const uint32_t irow = threadIdx.x; uint32_t row=trow+irow; if (row < max_row) row_grouping[irow] = grouping[row]; } __syncthreads(); TFloat local_s_W = 0.0; for (uint32_t row=trow; row < max_row; row++) { const uint32_t min_col = max(tcol,row+1); const uint32_t irow = row-trow; uint32_t group_idx = row_grouping[irow]; const TFloat * mat_row = mat + uint64_t(row)*uint64_t(n_dims); uint32_t col= min_col + icol; if ( col < max_col) { // since TILE==col_stride, we get at most one thread picking this up if (grouping[col] == group_idx) { TFloat val = mat_row[col]; // mat[row,col]; local_s_W += val * val * inv_group_sizes[group_idx]; } } } s_W += local_s_W; } // for tcol } // for trow // now we need to collect data from all the threads s_W_arr[icol] = s_W; __syncthreads(); // first sum across workers (4*32) if (threadIdx.x<32) { s_W = 0.0; for (uint32_t t=threadIdx.x; t < blockDim.x; t+=32) { s_W += s_W_arr[t]; } s_W_arr[threadIdx.x] = s_W; } __syncthreads(); // then sum using partial warps (4*8) if (threadIdx.x<8) { s_W = 0.0; for (uint32_t t=threadIdx.x; t < 32; t+=8) { s_W += s_W_arr[t]; } s_W_arr[threadIdx.x] = s_W; } __syncthreads(); // finally sum the remainder and write to global memory if (threadIdx.x==0) { s_W = 0.0; for (uint32_t t=threadIdx.x; t < 8; t++) { s_W += s_W_arr[t]; } group_sWs[grouping_el] = s_W; } } template static inline void pmn_f_stat_sW_gpu( const uint32_t n_dims, const TFloat * mat, const uint32_t n_grouping_dims, const uint32_t *groupings, const TFloat *inv_group_sizes, TFloat *group_sWs) { pmn_f_stat_sW_cuda_one<<>>(n_dims,mat,n_grouping_dims,groupings,inv_group_sizes,group_sWs); #if defined(SKBB_CUDA) if (cudaDeviceSynchronize()!=cudaSuccess) throw std::runtime_error("cudaDeviceSynchronize failed"); #else if (hipDeviceSynchronize()!=hipSuccess) throw std::runtime_error("hipDeviceSynchronize failed"); #endif } #else template static inline void pmn_f_stat_sW_gpu( const uint32_t n_dims, const TFloat * mat, const uint32_t n_grouping_dims, const uint32_t *groupings, const TFloat *inv_group_sizes, TFloat *group_sWs) { // GPU version, just put all the code in here const uint64_t groupings_size = uint64_t(n_dims)*uint64_t(n_grouping_dims); #ifdef OMPGPU #pragma omp target teams distribute map(to:groupings[0:groupings_size]) map(from:group_sWs[0:n_grouping_dims]) #else #pragma acc parallel loop gang copyin(groupings[0:groupings_size]) copyout(group_sWs[0:n_grouping_dims]) default(present) #endif for (uint32_t grouping_el=0; grouping_el < n_grouping_dims; grouping_el++) { const uint32_t *grouping = groupings + uint64_t(grouping_el)*uint64_t(n_dims); // Use full precision for intermediate compute, to minimize accumulation errors double s_W = 0.0; for (uint32_t row=0; row < (n_dims-1); row++) { // no columns in last row uint32_t group_idx = grouping[row]; #ifdef OMPGPU #pragma omp parallel for simd reduction(+:s_W) #else #pragma acc loop vector reduction(+:s_W) #endif for (uint32_t col=row+1; col < n_dims; col++) { // diagonal is always zero if (grouping[col] == group_idx) { const TFloat * mat_row = mat + uint64_t(row)*uint64_t(n_dims); TFloat val = mat_row[col]; // mat[row,col]; s_W += val * val * inv_group_sizes[group_idx]; } } } group_sWs[grouping_el] = s_W; } } #endif template static inline void pmn_f_stat_sW_T( const uint32_t n_dims, const TFloat * mat, const uint32_t n_grouping_dims, const uint32_t *groupings, const TFloat *inv_group_sizes, TFloat *group_sWs) { #if defined(SKBB_CPU) pmn_f_stat_sW_cpu(n_dims, mat, n_grouping_dims, groupings, inv_group_sizes, group_sWs); #else pmn_f_stat_sW_gpu(n_dims, mat, n_grouping_dims, groupings, inv_group_sizes, group_sWs); #endif } scikit-bio-scikit-bio-binaries-27dfa5a/src/extern/000077500000000000000000000000001505050360200221575ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/src/extern/distance.h000066400000000000000000000042041505050360200241220ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * These are the public functions accessible from the * libskbb * shared library, * the source of which is maintained in the scikit-bio-binaries repo. * https://github.com/scikit-bio/scikit-bio-binaries * */ #ifndef SKBB_EXTERN_DISTANCE_H #define SKBB_EXTERN_DISTANCE_H #ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN #endif /* * Test for significant differences between groups using PERMANOVA. * * Permutational Multivariate Analysis of Variance (PERMANOVA) is a * non-parametric method that tests whether two or more groups of objects * (e.g., samples) are significantly different based on a categorical factor. * It is conceptually similar to ANOVA except that it operates on a distance * matrix, which allows for multivariate analysis. PERMANOVA computes a * pseudo-F statistic. * * Statistical significance is assessed via a permutation test. The assignment * of objects to groups (`grouping`) is randomly permuted a number of times * (controlled via `n_perm`). A pseudo-F statistic is computed for each * permutation and the p-value is the proportion of permuted pseudo-F * statisics that are equal to or greater than the original (unpermuted) * pseudo-F statistic. * * Input parameters: * n_dims - size of the matrix * mat - distance matrix (n_dims x n_dims) * grouping - grouping array of size n_dims * n_perm - number of permutations * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters, as pointers: * fstat_ptr - pseudo-F statistics * pvalue_ptr - p-value */ #define SKBB_PERMANOVA_API_MIN_VERSION 1 EXTERN void skbb_permanova_fp64(unsigned int n_dims, const double mat[], const unsigned int grouping[], unsigned int n_perm, int seed, double *fstat_ptr, double *pvalue_ptr); EXTERN void skbb_permanova_fp32(unsigned int n_dims, const float mat[], const unsigned int grouping[], unsigned int n_perm, int seed, float *fstat_ptr, float *pvalue_ptr); #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/extern/ordination.h000066400000000000000000000120501505050360200244740ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * These are the public functions accessible from the * libskbb * shared library, * the source of which is maintained in the scikit-bio-binaries repo. * https://github.com/scikit-bio/scikit-bio-binaries * */ #ifndef SKBB_EXTERN_ORDINATION_H #define SKBB_EXTERN_ORDINATION_H #ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN #endif /* * Centers a distance matrix. * * Following computation as per * Numerical Ecology (Legendre & Legendre 1998). * * Input parameters: * n_dims - Size of the matrix * mat - Distance matrix (n_dims x n_dims) * * Output parameters: * centered - Resulting matrix (n_dims x n_dims), pre-allocated * * Note: mat and centered are allowed to point to the same memory area (e.g. in-place) */ #define SKBB_CDM_API_MIN_VERSION 1 EXTERN void skbb_center_distance_matrix_fp64(unsigned int n_dims, const double mat[], double centered[]); EXTERN void skbb_center_distance_matrix_fp32(unsigned int n_dims, const float mat[], float centered[]); EXTERN void skbb_center_distance_matrix_fp64_to_fp32(unsigned int n_dims, const double mat[], float centered[]); /* * Performs singular value decomposition, or more specifically in * this case eigendecomposition, using fast heuristic algorithm * nicknamed "FSVD" (FastSVD), adapted and optimized from the algorithm * described by Halko et al (2011). * N. Halko, P.G. Martinsson, Y. Shkolnisky, and M. Tygert. * Original Paper: https://arxiv.org/abs/1007.5510 * * Input parameters: * n_dims - Size of the matrix * centered - Centered distance matrix (n_dims x n_dims), will be overwritten during compute * n_eighs - Number of eigenvalues to return * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * eigenvalues - Array of size n_eighs, pre-allocated * eigenvectors - Matrix of size (n_dims x n_eighs), pre-allocated */ #define SKBB_FSVD_API_MIN_VERSION 1 EXTERN void skbb_fsvd_inplace_fp64(unsigned int n_dims, double centered[], unsigned int n_eighs, int seed, double eigenvalues[], double eigenvectors[]); EXTERN void skbb_fsvd_inplace_fp32(unsigned int n_dims, float centered[], unsigned int n_eighs, int seed, float eigenvalues[], float eigenvectors[]); /* * Perform Principal Coordinate Analysis. * * Principal Coordinate Analysis (PCoA) is a method similar * to Principal Components Analysis (PCA) with the difference that PCoA * operates on distance matrices, typically with non-euclidian and thus * ecologically meaningful distances like UniFrac in microbiome research. * * In ecology, the euclidean distance preserved by Principal * Component Analysis (PCA) is often not a good choice because it * deals poorly with double zeros (Species have unimodal * distributions along environmental gradients, so if a species is * absent from two sites at the same site, it can't be known if an * environmental variable is too high in one of them and too low in * the other, or too low in both, etc. On the other hand, if an * species is present in two sites, that means that the sites are * similar.). * * Implemented using FSVD method. * * Note that the returned eigenvectors are not normalized to unit length. * * Input parameters: * n_dims - Size of the matrix * mat - Distance matrix (n_dims x n_dims) * n_eighs - Number of eigenvalues to return * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * eigenvalues - Array of size n_eighs, pre-allocated * samples - The position of the samples in the ordination space, * row-indexed by the sample id. * Matrix of size (n_eighs x n_dims), pre-allocated * proportion_explained - Array of size n_eighs, pre-allocated * The index corresponds to the ordination axis labels. */ #define SKBB_PCOA_FSVD_API_MIN_VERSION 1 EXTERN void skbb_pcoa_fsvd_fp64(unsigned int n_dims, const double mat[], unsigned int n_eighs, int seed, double eigenvalues[], double samples[], double proportion_explained[]); EXTERN void skbb_pcoa_fsvd_fp32(unsigned int n_dims, const float mat[], unsigned int n_eighs, int seed, float eigenvalues[], float samples[], float proportion_explained[]); EXTERN void skbb_pcoa_fsvd_fp64_to_fp32(unsigned int n_dims, const double mat[], unsigned int n_eighs, int seed, float eigenvalues[], float samples[], float proportion_explained[]); // in-place version, will use mat as temp buffer internally EXTERN void skbb_pcoa_fsvd_inplace_fp64(unsigned int n_dims, double mat[], unsigned int n_eighs, int seed, double eigenvalues[], double samples[], double proportion_explained[]); EXTERN void skbb_pcoa_fsvd_inplace_fp32(unsigned int n_dims, float mat[], unsigned int n_eighs, int seed, float eigenvalues[], float samples[], float proportion_explained[]); #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/extern/skbb_distance.cpp000066400000000000000000000013211505050360200254530ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include "extern/distance.h" #include "distance/permanova.hpp" void skbb_permanova_fp64(unsigned int n_dims, const double mat[], const unsigned int grouping[], unsigned int n_perm, int seed, double *fstat_ptr, double *pvalue_ptr) { skbb::permanova(n_dims, mat, grouping, n_perm, seed, *fstat_ptr, *pvalue_ptr); } void skbb_permanova_fp32(unsigned int n_dims, const float mat[], const unsigned int grouping[], unsigned int n_perm, int seed, float *fstat_ptr, float *pvalue_ptr) { skbb::permanova(n_dims, mat, grouping, n_perm, seed, *fstat_ptr, *pvalue_ptr); } scikit-bio-scikit-bio-binaries-27dfa5a/src/extern/skbb_ordination.cpp000066400000000000000000000126251505050360200260400ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include "extern/ordination.h" #include "ordination/principal_coordinate_analysis.hpp" /* * Centers a distance matrix. * * Following computation as per * Numerical Ecology (Legendre & Legendre 1998). * * Input parameters: * n_dims - Size of the matrix * mat - Distance matrix (n_dims x n_dims) * * Output parameters: * centered - Resulting matrix (n_dims x n_dims), pre-allocated * * Note: mat and centered are allowed to point to the same memory area (e.g. in-place) */ void skbb_center_distance_matrix_fp64(unsigned int n_dims, const double mat[], double centered[]) { skbb::mat_to_centered(n_dims, mat, centered); } void skbb_center_distance_matrix_fp32(unsigned int n_dims, const float mat[], float centered[]) { skbb::mat_to_centered(n_dims, mat, centered); } void skbb_center_distance_matrix_fp64_to_fp32(unsigned int n_dims, const double mat[], float centered[]) { skbb::mat_to_centered(n_dims, mat, centered); } /* * Performs singular value decomposition, or more specifically in * this case eigendecomposition, using fast heuristic algorithm * nicknamed "FSVD" (FastSVD), adapted and optimized from the algorithm * described by Halko et al (2011). * N. Halko, P.G. Martinsson, Y. Shkolnisky, and M. Tygert. * Original Paper: https://arxiv.org/abs/1007.5510 * * Input parameters: * n_dims - Size of the matrix * centered - Centered distance matrix (n_dims x n_dims), will be overwritten during compute * n_eighs - Number of eigenvalues to return * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * eigenvalues - Array of size n_eighs, pre-allocated * eigenvectors - Matrix of size (n_dims x n_eighs), pre-allocated */ void skbb_fsvd_inplace_fp64(unsigned int n_dims, double centered[], unsigned int n_eighs, int seed, double eigenvalues[], double eigenvectors[]) { skbb::find_eigens_fast(n_dims, centered, n_eighs, seed, eigenvalues, eigenvectors); } void skbb_fsvd_inplace_fp32(unsigned int n_dims, float centered[], unsigned int n_eighs, int seed, float eigenvalues[], float eigenvectors[]) { skbb::find_eigens_fast(n_dims, centered, n_eighs, seed, eigenvalues, eigenvectors); } /* * Perform Principal Coordinate Analysis. * * Principal Coordinate Analysis (PCoA) is a method similar * to Principal Components Analysis (PCA) with the difference that PCoA * operates on distance matrices, typically with non-euclidian and thus * ecologically meaningful distances like UniFrac in microbiome research. * * In ecology, the euclidean distance preserved by Principal * Component Analysis (PCA) is often not a good choice because it * deals poorly with double zeros (Species have unimodal * distributions along environmental gradients, so if a species is * absent from two sites at the same site, it can't be known if an * environmental variable is too high in one of them and too low in * the other, or too low in both, etc. On the other hand, if an * species is present in two sites, that means that the sites are * similar.). * * Implemented using FSVD method. * * Note that the returned eigenvectors are not normalized to unit length. * * Input parameters: * n_dims - Size of the matrix * mat - Distance matrix (n_dims x n_dims) * n_eighs - Number of eigenvalues to return * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * eigenvalues - Array of size n_eighs, pre-allocated * samples - The position of the samples in the ordination space, * row-indexed by the sample id. * Matrix of size (n_eighs x n_dims), pre-allocated * proportion_explained - Array of size n_eighs, pre-allocated * The index corresponds to the ordination axis labels. */ void skbb_pcoa_fsvd_fp64(unsigned int n_dims, const double mat[], unsigned int n_eighs, int seed, double eigenvalues[], double samples[], double proportion_explained[]) { skbb::pcoa_fsvd(n_dims, mat, n_eighs, seed, eigenvalues, samples, proportion_explained); } void skbb_pcoa_fsvd_fp32(unsigned int n_dims, const float mat[], unsigned int n_eighs, int seed, float eigenvalues[], float samples[], float proportion_explained[]) { skbb::pcoa_fsvd(n_dims, mat, n_eighs, seed, eigenvalues, samples, proportion_explained); } void skbb_pcoa_fsvd_fp64_to_fp32(unsigned int n_dims, const double mat[], unsigned int n_eighs, int seed, float eigenvalues[], float samples[], float proportion_explained[]) { skbb::pcoa_fsvd(n_dims, mat, n_eighs, seed, eigenvalues, samples, proportion_explained); } // in-place version, will use mat as temp buffer internally void skbb_pcoa_fsvd_inplace_fp64(unsigned int n_dims, double mat[], unsigned int n_eighs, int seed, double eigenvalues[], double samples[], double proportion_explained[]) { skbb::pcoa_fsvd_inplace(n_dims, mat, n_eighs, seed, eigenvalues, samples, proportion_explained); } void skbb_pcoa_fsvd_inplace_fp32(unsigned int n_dims, float mat[], unsigned int n_eighs, int seed, float eigenvalues[], float samples[], float proportion_explained[]) { skbb::pcoa_fsvd_inplace(n_dims, mat, n_eighs, seed, eigenvalues, samples, proportion_explained); } scikit-bio-scikit-bio-binaries-27dfa5a/src/extern/skbb_util.cpp000066400000000000000000000013221505050360200246370ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include "extern/util.h" #include "util/rand.hpp" #include "util/skbb_detect_acc.hpp" unsigned int skbb_get_api_version() { return SKBB_API_CURRENT_VERSION; } void skbb_set_random_seed(unsigned int new_seed) { skbb::set_random_seed(new_seed); } unsigned int skbb_get_acc_mode() { return skbb::check_use_acc(); } bool skbb_set_acc_mode(unsigned int acc_type) { return skbb::set_use_acc(acc_type); } bool skbb_is_acc_reporting() { return skbb::check_report_acc(); } void skbb_set_acc_reporting_flag(bool new_value) { skbb::set_report_acc(new_value); } scikit-bio-scikit-bio-binaries-27dfa5a/src/extern/util.h000066400000000000000000000051441505050360200233110ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * These are the public functions accessible from the * libskbb * shared library, * the source of which is maintained in the scikit-bio-binaries repo. * https://github.com/scikit-bio/scikit-bio-binaries * */ #ifndef SKBB_EXTERN_UTIL_H #define SKBB_EXTERN_UTIL_H #ifdef __cplusplus #define EXTERN extern "C" #else #include #define EXTERN #endif /* ====================================================== */ /* * Version of the API this header refers to. * * Note to developers: * Increase the number every time there is a change in the API. * */ #define SKBB_API_CURRENT_VERSION 1 /* * All other functions have a min API constant defined * When supporting flexible dependencies, * use them to determine if the a fuction * has its implementation in the shared library in use */ /* What API version is the shared library providing */ EXTERN unsigned int skbb_get_api_version(); /* ====================================================== */ /* * Set random seed used by any and all the functions * in this library */ #define SKBB_RANDOM_API_MIN_VERSION 1 EXTERN void skbb_set_random_seed(unsigned int new_seed); /* ====================================================== */ /* * If the library supports accelerated compute * these functions control its logic. * * By default, the accelerator implementation is auto-detected, * but the the environment variable * SKBB_USE_GPU = N * allows for disabling of accelerated compute altogether. * * To allow monitoring of the selection logic, the env. variable * SKBB_GPU_INFO = Y * can be used to enable runtime reporting. * * The following functions can both * check what auto-detection discovered * and force a different outcome. */ #define SKBB_ACC_API_MIN_VERSION 1 /* no acceleration, always possible */ #define SKBB_ACC_CPU 0 /* true acceleration, may not be available */ #define SKBB_ACC_NV 1 #define SKBB_ACC_AMD 2 /* What variant of accelerated code is currently in use */ EXTERN unsigned int skbb_get_acc_mode(); /* * Override the acceleration mode. * Use with caution. * * Returns: * true if acc_type should be one of the above supported ones * false otherwise, and the internal value is not changed */ EXTERN bool skbb_set_acc_mode(unsigned int acc_type); /* Is reporting of accelerated compute use enabled? */ EXTERN bool skbb_is_acc_reporting(); /* Change accelerated compute use reporting flag */ EXTERN void skbb_set_acc_reporting_flag(bool new_value); #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/ordination/000077500000000000000000000000001505050360200230205ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/src/ordination/principal_coordinate_analysis.cpp000066400000000000000000000537551505050360200316360ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and function that provide PCoA functionality */ #include "principal_coordinate_analysis.hpp" #include "util/skbb_dgb_info.hpp" #include "util/rand.hpp" #include #include #include #include #include // // ======================= PCoA ======================== // // Compute the E_matrix with means // centered must be pre-allocated and same size as mat (n_dims*n_dims)...will work even if centered==mat // row_means must be pre-allocated and n_dims in size template static inline void E_matrix_means(const uint32_t n_dims, // IN const TRealIn mat[], // IN TReal centered[], TReal row_means[], TReal &global_mean) { // OUT /* Compute E matrix from a distance matrix and store in temp centered matrix. Squares and divides by -2 the input elementwise. Eq. 9.20 in Legendre & Legendre 1998. Compute sum of the rows at the same time. */ TReal global_sum = 0.0; #pragma omp parallel for shared(mat,centered,row_means) reduction(+: global_sum) for (uint32_t row=0; row static inline void F_matrix_inplace(const TReal * __restrict__ row_means, const TReal global_mean, TReal * __restrict__ centered, const uint32_t n_dims) { /* Compute F matrix from E matrix. Centring step: for each element, the mean of the corresponding row and column are substracted, and the mean of the whole matrix is added. Eq. 9.21 in Legendre & Legendre 1998. Pseudo-code: row_means = E_matrix.mean(axis=1, keepdims=True) col_means = Transpose(row_means) matrix_mean = E_matrix.mean() return E_matrix - row_means - col_means + matrix_mean */ // use a tiled pattern to maximize locality of row_means #pragma omp parallel for shared(centered,row_means) for (uint32_t trow=0; trow static inline void mat_to_centered_T(const uint32_t n_dims, const TRealIn mat[], TReal centered[]) { TReal global_mean; TReal *row_means = (TReal *) malloc(uint64_t(n_dims)*sizeof(TReal)); E_matrix_means(n_dims, mat, centered, row_means, global_mean); F_matrix_inplace(row_means, global_mean, centered, n_dims); free(row_means); } void skbb::mat_to_centered(const uint32_t n_dims, const double mat[], double centered[]) { mat_to_centered_T(n_dims, mat, centered); } void skbb::mat_to_centered(const uint32_t n_dims, const float mat[], float centered[]) { mat_to_centered_T(n_dims, mat, centered); } void skbb::mat_to_centered(const uint32_t n_dims, const double mat[], float centered[]) { mat_to_centered_T(n_dims, mat, centered); } // Matrix dot multiplication // Expects FORTRAN-style ColOrder // mat must be cols x rows // other must be cols x rows (ColOrder... rows elements together) template static inline void mat_dot_T(const TReal *mat, const TReal *other, const uint32_t rows, const uint32_t cols, TReal *out); template<> inline void mat_dot_T(const double *mat, const double *other, const uint32_t rows, const uint32_t cols, double *out) { cblas_dgemm(CblasColMajor,CblasNoTrans,CblasNoTrans, rows , cols, rows, 1.0, mat, rows, other, rows, 0.0, out, rows); } template<> inline void mat_dot_T(const float *mat, const float *other, const uint32_t rows, const uint32_t cols, float *out) { cblas_sgemm(CblasColMajor,CblasNoTrans,CblasNoTrans, rows , cols, rows, 1.0, mat, rows, other, rows, 0.0, out, rows); } // Expects FORTRAN-style ColOrder // Based on N. Halko, P.G. Martinsson, Y. Shkolnisky, and M. Tygert. // Original Paper: https://arxiv.org/abs/1007.5510 // Step 1 // centered == n x n // randomized = k*2 x n (ColOrder... n elements together) // Note on seed: // If <0, treat it as invalid and use the global random generator template static inline void centered_randomize_T(const TReal centered[], const uint32_t n_dims, const uint32_t k, const int seed, TReal randomized[]) { uint64_t matrix_els = uint64_t(n_dims)*uint64_t(k); TReal * tmp = (TReal *) malloc(matrix_els*sizeof(TReal)); auto& globalRandomGenerator = skbb::get_random_generator(); unsigned int gen_seed = (seed<0) ? globalRandomGenerator() : seed; // Form a real n x k matrix whose entries are independent, identically // distributed Gaussian random variables of zero mean and unit variance TReal *G = tmp; { skbb::TRAND rand; rand.seed(gen_seed); std::normal_distribution distribution; for (uint64_t i=0; i(centered,G,n_dims,k,randomized); // power method... single iteration.. store in 2nd part of output // Reusing tmp buffer for intermediate storage mat_dot_T(centered,randomized,n_dims,k,tmp); mat_dot_T(centered,tmp,n_dims,k,randomized+matrix_els); free(tmp); } // templated LAPACKE wrapper // Compute QR // H is in,overwritten by Q on out // H is (r x c), Q is (r x qc), with rc<=c template static inline int qr_i_T(const uint32_t rows, const uint32_t cols, TReal *H, uint32_t &qcols); template<> inline int qr_i_T(const uint32_t rows, const uint32_t cols, double *H, uint32_t &qcols) { qcols= std::min(rows,cols); double *tau= new double[qcols]; int rc = LAPACKE_dgeqrf(LAPACK_COL_MAJOR, rows, cols, H, rows, tau); if (rc==0) { qcols= std::min(rows,cols); rc = LAPACKE_dorgqr(LAPACK_COL_MAJOR, rows, qcols, qcols, H, rows, tau); } delete[] tau; return rc; } template<> inline int qr_i_T(const uint32_t rows, const uint32_t cols, float *H, uint32_t &qcols) { qcols= std::min(rows,cols); float *tau= new float[qcols]; int rc = LAPACKE_sgeqrf(LAPACK_COL_MAJOR, rows, cols, H, rows, tau); if (rc==0) { qcols= std::min(rows,cols); rc = LAPACKE_sorgqr(LAPACK_COL_MAJOR, rows, qcols, qcols, H, rows, tau); } delete[] tau; return rc; } namespace skbb { // helper class, since QR ops are multi function template class QR { public: uint32_t rows; uint32_t cols; TReal *Q; // will take ownership of _H QR(const uint32_t _rows, const uint32_t _cols, TReal *_H) : rows(_rows) , Q(_H) { int rc = qr_i_T(_rows, _cols, Q, cols); if (rc!=0) { fprintf(stderr, "qr_i_T(_rows,_cols, H, cols) failed with %i\n", rc); exit(1); // should never fail } } ~QR() { free(Q); } // res = mat * Q // mat must be rows x rows // res will be rows * cols void qdot_r_sq(const TReal *mat, TReal *res); // res = Q * mat // mat must be cols * cols // res will be rows * cols void qdot_l_sq(const TReal *mat, TReal *res); }; } template<> inline void skbb::QR::qdot_r_sq(const double *mat, double *res) { cblas_dgemm(CblasColMajor,CblasNoTrans,CblasNoTrans, rows , cols, rows, 1.0, mat, rows, Q, rows, 0.0, res, rows); } template<> inline void skbb::QR::qdot_r_sq(const float *mat, float *res) { cblas_sgemm(CblasColMajor,CblasNoTrans,CblasNoTrans, rows , cols, rows, 1.0, mat, rows, Q, rows, 0.0, res, rows); } template<> inline void skbb::QR::qdot_l_sq(const double *mat, double *res) { cblas_dgemm(CblasColMajor,CblasNoTrans,CblasNoTrans, rows , cols, cols, 1.0, Q, rows, mat, cols, 0.0, res, rows); } template<> void skbb::QR::qdot_l_sq(const float *mat, float *res) { cblas_sgemm(CblasColMajor,CblasNoTrans,CblasNoTrans, rows , cols, cols, 1.0, Q, rows, mat, cols, 0.0, res, rows); } // compute svd, and return S and V // T = input // S output // T is Vt on output template inline int svd_it_T(const uint32_t rows, const uint32_t cols, TReal *T, TReal *S); template<> inline int svd_it_T(const uint32_t rows, const uint32_t cols, double *T, double *S) { double *superb = (double *) malloc(sizeof(double)*rows); int res =LAPACKE_dgesvd(LAPACK_COL_MAJOR, 'N', 'O', rows, cols, T, rows, S, NULL, rows, NULL, cols, superb); free(superb); return res; } template<> inline int svd_it_T(const uint32_t rows, const uint32_t cols, float *T, float *S) { float *superb = (float *) malloc(sizeof(float)*rows); int res =LAPACKE_sgesvd(LAPACK_COL_MAJOR, 'N', 'O', rows, cols, T, rows, S, NULL, rows, NULL, cols, superb); free(superb); return res; } // square matrix transpose, with org not alingned template inline void transpose_sq_st_T(const uint64_t n, const uint64_t stride, const TReal in[], TReal out[]) { // n expected to be small, so simple single-thread perfect // org_n>=n guaranteed for (uint64_t i=0; i inline void transpose_T(const uint64_t rows, const uint64_t cols, const TReal in[], TReal out[]) { // To be optimizedc for (uint64_t i=0; i inline void find_eigens_fast_T(const uint32_t n_dims, TReal centered[], const uint32_t n_eighs, const int seed, TReal eigenvalues[], TReal eigenvectors[]) { SETUP_TDBG("find_eigens_fast") const uint32_t k = n_eighs+2; int rc; TReal *S = (TReal *) malloc(uint64_t(n_dims)*sizeof(TReal)); // take worst case size as a start TReal *Ut = NULL; { TReal *H = (TReal *) malloc(sizeof(TReal)*uint64_t(n_dims)*uint64_t(k)*2); // step 1 centered_randomize_T(centered, n_dims, k, seed, H); TDBG_STEP("center") // step 2 // QR decomposition of H skbb::QR qr_obj(n_dims, k*2, H); // H is now owned by qr_obj, as Q // step 3 // T = centered * Q (since centered^T == centered, due to being symmetric) // centered = n x n // T = n x ref TReal *T = (TReal *) malloc(sizeof(TReal)*uint64_t(qr_obj.rows)*uint64_t(qr_obj.cols)); qr_obj.qdot_r_sq(centered,T); // step 4 // compute svd // update T in-place, Wt on output (Vt according to the LAPACK nomenclature) rc=svd_it_T(qr_obj.rows,qr_obj.cols, T, S); if (rc!=0) { fprintf(stderr, "svd_it_T(n_dims, T, S) failed with %i\n",rc); exit(1); // should never fail, but just in case, like a segfault } TDBG_STEP("svd") // step 5 // Compute U = Q*Wt^t { // transpose Wt -> W, Wt uses n_dims strides TReal * W = (TReal *) malloc(sizeof(TReal)*uint64_t(qr_obj.cols)*uint64_t(qr_obj.cols)); transpose_sq_st_T(qr_obj.cols, qr_obj.rows, T, W); // Wt == T on input Ut = T; // Ut takes ownership of the T buffer qr_obj.qdot_l_sq(W, Ut); free(W); } } // we don't need qr_obj anymore, release memory // step 6 // get the interesting subset, and return for (uint32_t i=0; i(n_dims, n_eighs, Ut, eigenvectors); free(Ut); TDBG_STEP("finalize") } void skbb::find_eigens_fast(const uint32_t n_dims, double centered[], const uint32_t n_eighs,const int seed, double eigenvalues[], double eigenvectors[]) { find_eigens_fast_T(n_dims, centered, n_eighs, seed, eigenvalues, eigenvectors); } void skbb::find_eigens_fast(const uint32_t n_dims, float centered[], const uint32_t n_eighs, const int seed, float eigenvalues[], float eigenvectors[]) { find_eigens_fast_T(n_dims, centered, n_eighs, seed, eigenvalues, eigenvectors); } // helper class namespace skbb { template class NewCentered { private: const uint32_t n_dims; TReal * centered_buf; public: NewCentered(const uint32_t _n_dims) : n_dims(_n_dims) , centered_buf(NULL) {} TReal * get_buf() { if (centered_buf==NULL) centered_buf = new TReal[uint64_t(n_dims)*uint64_t(n_dims)]; return centered_buf; } void release_buf() { if (centered_buf!=NULL) delete[] centered_buf; centered_buf=NULL; } ~NewCentered() { if (centered_buf!=NULL) release_buf(); } private: NewCentered(const NewCentered &other) = delete; NewCentered& operator=(const NewCentered &other) = delete; }; template class InPlaceCentered { private: TReal * mat; public: InPlaceCentered(TReal * _mat) : mat(_mat) {} TReal * get_buf() { return mat; } void release_buf() {} ~InPlaceCentered() {} }; } /* * Perform Principal Coordinate Analysis. * * Principal Coordinate Analysis (PCoA) is a method similar * to Principal Components Analysis (PCA) with the difference that PCoA * operates on distance matrices, typically with non-euclidian and thus * ecologically meaningful distances like UniFrac in microbiome research. * * In ecology, the euclidean distance preserved by Principal * Component Analysis (PCA) is often not a good choice because it * deals poorly with double zeros (Species have unimodal * distributions along environmental gradients, so if a species is * absent from two sites at the same site, it can't be known if an * environmental variable is too high in one of them and too low in * the other, or too low in both, etc. On the other hand, if an * species is present in two sites, that means that the sites are * similar.). * * Implemented using FSVD method. * * Note that the returned eigenvectors are not normalized to unit length. * * Input parameters: * n_dims - Size of the matrix * mat - Distance matrix (n_dims x n_dims) * center_obj- Buffer holding the centered matrix buffer (n_dims x n_dims) * n_eighs - Number of eigenvalues to return * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * eigenvalues - Array of size n_eighs, pre-allocated * samples - The position of the samples in the ordination space, * row-indexed by the sample id. * Matrix of size (n_eighs x n_dims), pre-allocated * proportion_explained - Array of size n_eighs, pre-allocated * The index corresponds to the ordination axis labels. */ template static inline void pcoa_T(const uint32_t n_dims, TRealIn mat[], TCenter ¢er_obj, const uint32_t n_eighs, const int seed, TReal eigenvalues[], TReal samples[], TReal proportion_explained[]) { TReal diag_sum = 0.0; // we will use the samples buffer to hold the eigenvectors during compute TReal * const eigenvectors = samples; { TReal *centered = center_obj.get_buf(); // First must center the matrix mat_to_centered_T(n_dims, mat, centered); // get the sum of the diagonal, needed later // and centered will be updated in-place in find_eigen for (uint32_t i=0; i(n_dims, centered, n_eighs, seed, eigenvalues, eigenvectors); center_obj.release_buf(); } // expects eigenvalues to be ordered and non-negative // The above unction guarantees that // Scale eigenvalues to have length = sqrt(eigenvalue). This // works because np.linalg.eigh returns normalized // eigenvectors. Each row contains the coordinates of the // objects in the space of principal coordinates. Note that at // least one eigenvalue is zero because only n-1 axes are // needed to represent n points in a euclidean space. // samples = eigvecs * np.sqrt(eigvals) // we will just update in place and pass out // Reminder: // samples == eigenvectors; // use proportion_explained as tmp buffer here { TReal *sqvals = proportion_explained; for (uint32_t i=0; i cobj(n_dims); pcoa_T(n_dims, mat, cobj, n_eighs, seed, eigenvalues, samples, proportion_explained); TDBG_STEP("pcoa") } void skbb::pcoa_fsvd(const uint32_t n_dims, const float mat[], const uint32_t n_eighs, const int seed, float eigenvalues[], float samples[], float proportion_explained[]) { SETUP_TDBG("pcoa_fsvd_fp32") skbb::NewCentered cobj(n_dims); pcoa_T(n_dims, mat, cobj, n_eighs, seed, eigenvalues, samples, proportion_explained); TDBG_STEP("pcoa") } void skbb::pcoa_fsvd(const uint32_t n_dims, const double mat[], const uint32_t n_eighs, const int seed, float eigenvalues[], float samples[], float proportion_explained[]) { SETUP_TDBG("pcoa_fsvd_mixed") skbb::NewCentered cobj(n_dims); pcoa_T(n_dims, mat, cobj, n_eighs, seed, eigenvalues, samples, proportion_explained); TDBG_STEP("pcoa") } void skbb::pcoa_fsvd_inplace(const uint32_t n_dims, double mat[], const uint32_t n_eighs, const int seed, double eigenvalues[], double samples[], double proportion_explained[]) { SETUP_TDBG("pcoa_fsvd_inplace_fp64") skbb::InPlaceCentered cobj(mat); pcoa_T(n_dims, mat, cobj, n_eighs, seed, eigenvalues, samples, proportion_explained); TDBG_STEP("pcoa") } void skbb::pcoa_fsvd_inplace(const uint32_t n_dims, float mat[], const uint32_t n_eighs, const int seed, float eigenvalues[], float samples[], float proportion_explained[]) { SETUP_TDBG("pcoa_fsvd_inplace_fp32") skbb::InPlaceCentered cobj(mat); pcoa_T(n_dims, mat, cobj, n_eighs, seed, eigenvalues, samples, proportion_explained); TDBG_STEP("pcoa") } scikit-bio-scikit-bio-binaries-27dfa5a/src/ordination/principal_coordinate_analysis.hpp000066400000000000000000000112221505050360200316220ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and functions for PCoA */ #ifndef SKBB_PCOA_HPP #define SKBB_PCOA_HPP #include namespace skbb { /* * Centers a distance matrix. * * Following computation as per * Numerical Ecology (Legendre & Legendre 1998). * * Input parameters: * n_dims - Size of the matrix * mat - Distance matrix (n_dims x n_dims) * * Output parameters: * centered - Resulting matrix (n_dims x n_dims), pre-allocated * * Note: mat and centered are allowed to point to the same memory area (e.g. in-place) */ void mat_to_centered(const uint32_t n_dims, const double mat[], double centered[]); void mat_to_centered(const uint32_t n_dims, const float mat[], float centered[]); void mat_to_centered(const uint32_t n_dims, const double mat[], float centered[]); /* * Performs singular value decomposition, or more specifically in * this case eigendecomposition, using fast heuristic algorithm * nicknamed "FSVD" (FastSVD), adapted and optimized from the algorithm * described by Halko et al (2011). * N. Halko, P.G. Martinsson, Y. Shkolnisky, and M. Tygert. * Original Paper: https://arxiv.org/abs/1007.5510 * * Input parameters: * n_dims - Size of the matrix * centered - Centered distance matrix (n_dims x n_dims), will be overwritten during compute * n_eighs - Number of eigenvalues to return * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * eigenvalues - Array of size n_eighs, pre-allocated * eigenvectors - Matrix of size (n_dims x n_eighs), pre-allocated */ void find_eigens_fast(const uint32_t n_dims, double centered[], const uint32_t n_eighs, const int seed, double eigenvalues[], double eigenvectors[]); void find_eigens_fast(const uint32_t n_dims, float centered[], const uint32_t n_eighs, const int seed, float eigenvalues[], float eigenvectors[]); /* * Perform Principal Coordinate Analysis. * * Principal Coordinate Analysis (PCoA) is a method similar * to Principal Components Analysis (PCA) with the difference that PCoA * operates on distance matrices, typically with non-euclidian and thus * ecologically meaningful distances like UniFrac in microbiome research. * * In ecology, the euclidean distance preserved by Principal * Component Analysis (PCA) is often not a good choice because it * deals poorly with double zeros (Species have unimodal * distributions along environmental gradients, so if a species is * absent from two sites at the same site, it can't be known if an * environmental variable is too high in one of them and too low in * the other, or too low in both, etc. On the other hand, if an * species is present in two sites, that means that the sites are * similar.). * * Implemented using FSVD method. * * Note that the returned eigenvectors are not normalized to unit length. * * Input parameters: * n_dims - Size of the matrix * mat - Distance matrix (n_dims x n_dims) * n_eighs - Number of eigenvalues to return * seed - Optional random seed, if non-negative. Use system random seed if <0 * * Output parameters: * eigenvalues - Array of size n_eighs, pre-allocated * samples - The position of the samples in the ordination space, * row-indexed by the sample id. * Matrix of size (n_eighs x n_dims), pre-allocated * proportion_explained - Array of size n_eighs, pre-allocated * The index corresponds to the ordination axis labels. */ void pcoa_fsvd(const uint32_t n_dims, const double mat[], const uint32_t n_eighs, const int seed, double eigenvalues[], double samples[], double proportion_explained[]); void pcoa_fsvd(const uint32_t n_dims, const float mat[], const uint32_t n_eighs, const int seed, float eigenvalues[], float samples[], float proportion_explained[]); void pcoa_fsvd(const uint32_t n_dims, const double mat[], const uint32_t n_eighs, const int seed, float eigenvalues[], float samples[], float proportion_explained[]); // in-place version, will use mat as temp buffer internally void pcoa_fsvd_inplace(const uint32_t n_dims, double mat[], const uint32_t n_eighs, const int seed, double eigenvalues[], double samples[], double proportion_explained[]); void pcoa_fsvd_inplace(const uint32_t n_dims, float mat[], const uint32_t n_eighs, const int seed, float eigenvalues[], float samples[], float proportion_explained[]); } #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/tests/000077500000000000000000000000001505050360200220145ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/src/tests/test_helper.hpp000066400000000000000000000057341505050360200250540ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include #include #include #include #include /* * test harness adapted from * https://github.com/noporpoise/BitArray/blob/master/dev/bit_array_test.c */ const char *suite_name; char suite_pass; int suites_run = 0, suites_failed = 0, suites_empty = 0; int tests_in_suite = 0, tests_run = 0, tests_failed = 0; #define QUOTE(str) #str #define ASSERT(x) {tests_run++; tests_in_suite++; if(!(x)) \ { fprintf(stderr, "failed assert [%s:%i] %s\n", __FILE__, __LINE__, QUOTE(x)); \ suite_pass = 0; tests_failed++; }} #define ASSERTINTEQ(x,y) {tests_run++; tests_in_suite++; if(!((x)==(y))) \ { fprintf(stderr, "failed assert [%s:%i] %i==%i (%s==%s)\n", __FILE__, __LINE__, x,y, QUOTE(x), QUOTE(y)); \ suite_pass = 0; tests_failed++; }} #define ASSERTINTLE(x,y) {tests_run++; tests_in_suite++; if(!((x)<=(y))) \ { fprintf(stderr, "failed assert [%s:%i] %i<=%i (%s<=%s)\n", __FILE__, __LINE__, x,y, QUOTE(x), QUOTE(y)); \ suite_pass = 0; tests_failed++; }} inline void SUITE_START(const char *name) { suite_pass = 1; suite_name = name; suites_run++; tests_in_suite = 0; } inline void SUITE_END() { printf("Testing %s ", suite_name); size_t suite_i; for(suite_i = strlen(suite_name); suite_i < 80-8-5; suite_i++) printf("."); printf("%s\n", suite_pass ? " pass" : " fail"); if(!suite_pass) suites_failed++; if(!tests_in_suite) suites_empty++; } /* * End adapted code */ inline std::vector _bool_array_to_vector(bool *arr, unsigned int n) { std::vector vec; for(unsigned int i = 0; i < n; i++) vec.push_back(arr[i]); return vec; } inline std::vector _uint32_array_to_vector(uint32_t *arr, unsigned int n) { std::vector vec; for(unsigned int i = 0; i < n; i++) vec.push_back(arr[i]); return vec; } inline std::vector _double_array_to_vector(double *arr, unsigned int n) { std::vector vec; for(unsigned int i = 0; i < n; i++) vec.push_back(arr[i]); return vec; } inline std::vector _string_array_to_vector(std::string *arr, unsigned int n) { std::vector vec; for(unsigned int i = 0; i < n; i++) vec.push_back(arr[i]); return vec; } inline bool vec_almost_equal(const std::vector &a, const std::vector &b) { if(a.size() != b.size()) { printf("vec_almost_equal size check failed, %i != %i\n",int(a.size()),int(b.size())); return false; } for(unsigned int i = 0; i < a.size(); i++) { if(!(fabs(a[i] - b[i]) < 0.000001)) { // sufficient given the tests printf("vec_almost_equal[%i] failed, %f - %f = %f\n",i,a[i],b[i], fabs(a[i] - b[i])); return false; } } return true; } scikit-bio-scikit-bio-binaries-27dfa5a/src/tests/test_pcoa.cpp000066400000000000000000000346721505050360200245150ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include "ordination/principal_coordinate_analysis.hpp" #include "util/rand.hpp" #include #include "tests/test_helper.hpp" void test_center_mat() { SUITE_START("test center mat"); // unweighted unifrac of test.biom double matrix[] = { 0.0000000000, 0.2000000000, 0.5714285714, 0.6000000000, 0.5000000000, 0.2000000000, 0.2000000000, 0.0000000000, 0.4285714286 ,0.6666666667, 0.6000000000, 0.3333333333, 0.5714285714, 0.4285714286, 0.0000000000, 0.7142857143, 0.8571428571, 0.4285714286, 0.6000000000, 0.6666666667, 0.7142857143, 0.0000000000, 0.3333333333, 0.4000000000, 0.5000000000, 0.6000000000, 0.8571428571, 0.3333333333, 0.0000000000, 0.6000000000, 0.2000000000, 0.3333333333, 0.4285714286, 0.4000000000, 0.6000000000, 0.0000000000}; const uint32_t n_samples = 6; double exp[] = { 0.05343726, 0.04366213, -0.0329743 , -0.07912698, -0.00495654, 0.01995843, 0.04366213, 0.073887 , 0.04867914, -0.11112434, -0.04973167,-0.00537226, -0.0329743 , 0.04867914, 0.20714475, -0.07737528, -0.17044974, 0.02497543, -0.07912698, -0.11112434, -0.07737528, 0.14830877, 0.11192366, 0.00739418, -0.00495654, -0.04973167, -0.17044974, 0.11192366, 0.18664966,-0.07343537, 0.01995843, -0.00537226, 0.02497543, 0.00739418, -0.07343537, 0.02647959 }; // fp64 { double *centered = (double *) malloc(6*6*sizeof(double)); skbb::mat_to_centered(n_samples, matrix, centered); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(centered[i]),float(exp[i])); ASSERT(fabs(centered[i] - exp[i]) < 0.000001); } free(centered); } // mixed { float *centered_fp32 = (float *) malloc(6*6*sizeof(float)); skbb::mat_to_centered(n_samples, matrix, centered_fp32); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(centered_fp32[i]),float(exp[i])); ASSERT(fabs(centered_fp32[i] - exp[i]) < 0.000001); } free(centered_fp32); } float *matrix_fp32 = (float *) malloc(6*6*sizeof(float)); for(int i = 0; i < (6*6); i++) matrix_fp32[i] = matrix[i]; // fp32 { float *centered_fp32 = (float *) malloc(6*6*sizeof(float)); skbb::mat_to_centered(n_samples, matrix_fp32, centered_fp32); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(centered_fp32[i]),float(exp[i])); ASSERT(fabs(centered_fp32[i] - exp[i]) < 0.000001); } free(centered_fp32); } // fp32 in-place { skbb::mat_to_centered(n_samples, matrix_fp32, matrix_fp32); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(matrix_fp32[i]),float(exp[i])); ASSERT(fabs(matrix_fp32[i] - exp[i]) < 0.000001); } } free(matrix_fp32); // fp64 in-place { skbb::mat_to_centered(n_samples, matrix, matrix); for(int i = 0; i < (6*6); i++) { //printf("%i %f %f\n",i,float(matrix_fp32[i]),float(exp[i])); ASSERT(fabs(matrix[i] - exp[i]) < 0.000001); } } SUITE_END(); } void test_pcoa_fsvd() { SUITE_START("test pcoa"); constexpr int rand_seed_invalid = -1; // must be <0 constexpr int rand_seed_valid = 33333; // must be >=0 // unweighted unifrac of crawford.biom double matrix[] = { 0. , 0.71836067 , 0.71317361 , 0.69746044 , 0.62587207 , 0.72826674 , 0.72065895 , 0.72640581 , 0.73606053, 0.71836067 , 0. , 0.70302967 , 0.73407301 , 0.6548042 , 0.71547381 , 0.78397813 , 0.72318399 , 0.76138933, 0.71317361 , 0.70302967 , 0. , 0.61041275 , 0.62331299 , 0.71848305 , 0.70416337 , 0.75258475 , 0.79249029, 0.69746044 , 0.73407301 , 0.61041275 , 0. , 0.6439278 , 0.70052733 , 0.69832716 , 0.77818938 , 0.72959894, 0.62587207 , 0.6548042 , 0.62331299 , 0.6439278 , 0. , 0.75782689 , 0.71005144 , 0.75065046 , 0.78944369, 0.72826674 , 0.71547381 , 0.71848305 , 0.70052733 , 0.75782689 , 0. , 0.63593642 , 0.71283615 , 0.58314638, 0.72065895 , 0.78397813 , 0.70416337 , 0.69832716 , 0.71005144 , 0.63593642 , 0. , 0.69200762 , 0.68972056, 0.72640581 , 0.72318399 , 0.75258475 , 0.77818938 , 0.75065046 , 0.71283615 , 0.69200762 , 0. , 0.71514083, 0.73606053 , 0.76138933 , 0.79249029 , 0.72959894 , 0.78944369 , 0.58314638 , 0.68972056 , 0.71514083 , 0. }; float matrix_fp32[9*9]; for (int i=0; i<(9*9); i++) matrix_fp32[i] = matrix[i]; const uint32_t n_samples = 9; // Test centering double exp2[] = { 0.22225336 , -0.025481 , -0.03491711 , -0.02614685 , 0.01899659 , -0.05103589 , -0.03971812 , -0.02699392 , -0.03695706, -0.025481 , 0.24282669 , -0.01744751 , -0.04206624 , 0.0107569 , -0.03151438 , -0.07706765 , -0.0143721 , -0.0456347, -0.03491711 , -0.01744751 , 0.21652901 , 0.02791465 , 0.0177328 , -0.04682078 , -0.03082866 , -0.04921529 , -0.08294711, -0.02614685 , -0.04206624 , 0.02791465 , 0.21190401 , 0.00235833 , -0.03639361 , -0.02904855 , -0.07112525 , -0.03739649, 0.01899659 , 0.0107569 , 0.0177328 , 0.00235833 , 0.20745566 , -0.08039931 , -0.03952883 , -0.05229812 , -0.08507403, -0.05103589 , -0.03151438 , -0.04682078 , -0.03639361 , -0.08039931 , 0.20604732 , 0.00964595 , -0.02533192 , 0.05580262, -0.03971812 , -0.07706765 , -0.03082866 , -0.02904855 , -0.03952883 , 0.00964595 , 0.21765972 , -0.00489531 , -0.00621856, -0.02699392 , -0.0143721 , -0.04921529 , -0.07112525 , -0.05229812 , -0.02533192 , -0.00489531 , 0.2514242 , -0.00719229, -0.03695706 , -0.0456347 , -0.08294711 , -0.03739649 , -0.08507403 , 0.05580262 , -0.00621856 , -0.00719229 , 0.24561762 }; double *centered = (double *) malloc(9*9*sizeof(double)); float *centered_fp32 = (float *) malloc(9*9*sizeof(float)); skbb::mat_to_centered(n_samples, matrix, centered); for(int i = 0; i < (9*9); i++) { //printf("%i %f %f\n",i,float(centered[i]),float(exp[i])); ASSERT(fabs(centered[i] - exp2[i]) < 0.00001); } skbb::mat_to_centered(n_samples, matrix, centered_fp32); for(int i = 0; i < (9*9); i++) { //printf("%i %f %f\n",i,float(centered_fp32[i]),float(exp[i])); ASSERT(fabs(centered_fp32[i] - exp2[i]) < 0.00001); } // Test eigens double exp3a[] = {0.45752162, 0.3260088 , 0.2791141 , 0.26296948, 0.20924533}; double exp3b[] = { -0.17316152, 0.17579996, 0.23301609, -0.74519625, -0.05194624, -0.19959264, 0.53235665, -0.53370018, 0.2173474 , 0.26736004, -0.35794942, -0.27956624, 0.01114096, 0.40488848, -0.13121464, -0.2296467 , -0.47494333, -0.12571292, 0.02313551, -0.46916459, -0.44501584, 0.05597451, 0.07717711, -0.15881922, 0.24594442, 0.40335552, -0.16290597, -0.30327343, 0.03778646, 0.21664806, 0.23769142, -0.29034629, 0.46813757, 0.13858945, 0.58624834, 0.2407584 , 0.51300752, 0.48211607, 0.34422672, -0.42416046, 0.52356078, -0.0693768 , -0.30890127, -0.26195855, -0.23971493}; { double eigenvalues[5]; double eigenvectors[5*9]; skbb::find_eigens_fast(n_samples, centered, 5, rand_seed_invalid, eigenvalues, eigenvectors); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp3a[i])); ASSERT(fabs(eigenvalues[i] - exp3a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(eigenvectors[i]),float(exp3b[i]),float(fabs(eigenvectors[i]) - fabs(exp3b[i]))); ASSERT( fabs(fabs(eigenvectors[i]) - fabs(exp3b[i])) < 0.00001); } } { float eigenvalues[5]; float eigenvectors[5*9]; skbb::find_eigens_fast(n_samples, centered_fp32, 5, rand_seed_valid, eigenvalues, eigenvectors); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp3a[i])); ASSERT(fabs(eigenvalues[i] - exp3a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(eigenvectors[i]),float(exp3b[i]),float(fabs(eigenvectors[i]) - fabs(exp3b[i]))); ASSERT( fabs(fabs(eigenvectors[i]) - fabs(exp3b[i])) < 0.00001); } } free(centered_fp32); free(centered); // Test PCoA (incudes the above calls double *exp4a = exp3a; // same eigenvals; double exp4b[] = { -0.11712705, 0.10037682, -0.12310531, -0.38214073, -0.02376195, -0.13500515, 0.30396064, 0.28196047, 0.11145694, 0.12229942, -0.24211822, -0.15962444, -0.00588591, 0.20762904, -0.06002196, -0.15533382, -0.27117925, 0.06641571, 0.01186402, -0.21461156, -0.30101024, 0.03195987, -0.04077363, -0.08144337, 0.1125032 , 0.27283106, -0.09301471, 0.16022314, 0.0193771 , 0.09910206, 0.16077529, -0.16577955, -0.24732293, 0.07106943, 0.26816958, 0.16284981, 0.29291283, -0.25470794, 0.17652136, -0.19402517, 0.35413832, -0.0396122 , 0.1631964 , -0.13433378, -0.10965362}; double exp4c[] = {0.22630343, 0.16125338, 0.13805791, 0.13007231, 0.10349879}; { double eigenvalues[5]; double samples[5*9]; double proportion_explained[5]; skbb::pcoa_fsvd(n_samples, matrix, 5, rand_seed_invalid, eigenvalues, samples, proportion_explained); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples[i]),float(exp4b[i]),float(fabs(samples[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained[i] - exp4c[i]) < 0.00001); } } // Test PCoA fp32 { float eigenvalues_fp32[5]; float samples_fp32[5*9]; float proportion_explained_fp32[5]; skbb::pcoa_fsvd(n_samples, matrix_fp32, 5, rand_seed_valid, eigenvalues_fp32, samples_fp32, proportion_explained_fp32); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues_fp32[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues_fp32[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples_fp32[i]),float(exp4b[i]),float(fabs(samples_fp32[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples_fp32[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained_fp32[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained_fp32[i] - exp4c[i]) < 0.00001); } } // Test PCoA mixed mode { float eigenvalues_fp32[5]; float samples_fp32[5*9]; float proportion_explained_fp32[5]; skbb::pcoa_fsvd(n_samples, matrix, 5, rand_seed_valid, eigenvalues_fp32, samples_fp32, proportion_explained_fp32); for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues_fp32[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues_fp32[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples_fp32[i]),float(exp4b[i]),float(fabs(samples_fp32[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples_fp32[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained_fp32[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained_fp32[i] - exp4c[i]) < 0.00001); } } // test in-place { double eigenvalues[5]; double samples[5*9]; double proportion_explained[5]; skbb::pcoa_fsvd_inplace(n_samples, matrix, 5, rand_seed_valid, eigenvalues, samples, proportion_explained); // Note: matrix content has been destroyed for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples[i]),float(exp4b[i]),float(fabs(samples[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained[i] - exp4c[i]) < 0.00001); } } // in-plave fp32 { float eigenvalues[5]; float samples[5*9]; float proportion_explained[5]; skbb::pcoa_fsvd_inplace(n_samples, matrix_fp32, 5, rand_seed_invalid, eigenvalues, samples, proportion_explained); // Note: matrix_fp32 content has been destroyed for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(eigenvalues[i]),float(exp4a[i])); ASSERT(fabs(eigenvalues[i] - exp4a[i]) < 0.00001); } // signs may flip, that's normal for(int i = 0; i < (5*9); i++) { //printf("%i %f %f %f\n",i,float(samples[i]),float(exp4b[i]),float(fabs(samples[i]) - fabs(exp4b[i]))); ASSERT( fabs(fabs(samples[i]) - fabs(exp4b[i])) < 0.00001); } for(int i = 0; i < 5; i++) { //printf("%i %f %f\n",i,float(proportion_explained[i]),float(exp4c[i])); ASSERT(fabs(proportion_explained[i] - exp4c[i]) < 0.00001); } } SUITE_END(); } int main(int argc, char** argv) { test_center_mat(); skbb::set_random_seed(33); // no test, but exercise util/rand test_pcoa_fsvd(); printf("\n"); printf(" %i / %i suites failed\n", suites_failed, suites_run); printf(" %i / %i suites empty\n", suites_empty, suites_run); printf(" %i / %i tests failed\n", tests_failed, tests_run); printf("\n THE END.\n"); return tests_failed ? EXIT_FAILURE : EXIT_SUCCESS; } scikit-bio-scikit-bio-binaries-27dfa5a/src/tests/test_permanova.cpp000066400000000000000000000172601505050360200255550ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #include "distance/permanova.hpp" #include "util/skbb_detect_acc.hpp" #include #include "tests/test_helper.hpp" void test_permanova_ties() { SUITE_START("test permanova ties"); // Same as skbio tests const double matrix_fp64[] = { 0., 1., 1., 4., 1., 0., 3., 2., 1., 3., 0., 3., 4., 2., 3., 0.}; const float matrix_fp32[] = { 0., 1., 1., 4., 1., 0., 3., 2., 1., 3., 0., 3., 4., 2., 3., 0.}; const uint32_t grouping_equal[] = { 0, 0, 1, 1}; const uint32_t grouping_equal_swapped[] = { 1, 1, 0, 0}; const uint32_t n_samples = 4; constexpr int rand_seed_invalid = -1; // must be <0 constexpr int rand_seed_valid = 4242; // must be >=0 // value from skbio const float exp_stat = 2.0; // using a different random than skbio, so result different const float exp_pvalue = 0.66; double stat_fp64, pvalue_fp64; float stat_fp32, pvalue_fp32; skbb::permanova(n_samples, matrix_fp64, grouping_equal, 999, rand_seed_valid, stat_fp64, pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp32, grouping_equal, 999, rand_seed_invalid, stat_fp32, pvalue_fp32); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp32, grouping_equal_swapped, 999, rand_seed_valid, stat_fp32, pvalue_fp32); ASSERT(fabs(stat_fp32 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp32 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp64, grouping_equal_swapped, 999, rand_seed_invalid, stat_fp64, pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); SUITE_END(); } void test_permanova_noties() { SUITE_START("test permanova noties"); // Same as skbio tests const double matrix_fp64[] = { 0., 1., 5., 4., 5., 0., 3., 2., 1., 3., 0., 3., 4., 2., 3., 0.}; const float matrix_fp32[] = { 0., 1., 5., 4., 1., 0., 3., 2., 5., 3., 0., 3., 4., 2., 3., 0.}; const uint32_t grouping_equal[] = { 0, 0, 1, 1}; const uint32_t grouping_equal_swapped[] = { 1, 1, 0, 0}; const uint32_t n_samples = 4; constexpr int rand_seed_invalid = -14567; // must be <0 constexpr int rand_seed_valid = 4242003; // must be >=0 // value from skbio const float exp_stat = 4.4; // using a different random than skbio, so result different const float exp_pvalue = 0.34; double stat_fp64, pvalue_fp64; float stat_fp32, pvalue_fp32; skbb::permanova(n_samples, matrix_fp64, grouping_equal, 999, rand_seed_valid, stat_fp64, pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp32, grouping_equal, 999, rand_seed_valid, stat_fp32, pvalue_fp32); ASSERT(fabs(stat_fp32 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp32 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp32, grouping_equal_swapped, 999, rand_seed_invalid, stat_fp32, pvalue_fp32); ASSERT(fabs(stat_fp32 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp32 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp64, grouping_equal_swapped, 999, rand_seed_invalid, stat_fp64, pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); SUITE_END(); } void test_permanova_unequal() { SUITE_START("test permanova unequal"); // Same as skbio tests const double matrix_fp64[] = { 0.0, 1.0, 0.1, 0.5678, 1.0, 1.0, 1.0, 0.0, 0.002, 0.42, 0.998, 0.0, 0.1, 0.002, 0.0, 1.0, 0.123, 1.0, 0.5678, 0.42, 1.0, 0.0, 0.123, 0.43, 1.0, 0.998, 0.123, 0.123, 0.0, 0.5, 1.0, 0.0, 1.0, 0.43, 0.5, 0.0 }; const float matrix_fp32[] = { 0.0, 1.0, 0.1, 0.5678, 1.0, 1.0, 1.0, 0.0, 0.002, 0.42, 0.998, 0.0, 0.1, 0.002, 0.0, 1.0, 0.123, 1.0, 0.5678, 0.42, 1.0, 0.0, 0.123, 0.43, 1.0, 0.998, 0.123, 0.123, 0.0, 0.5, 1.0, 0.0, 1.0, 0.43, 0.5, 0.0 }; const uint32_t grouping_equal[] = { 0, 1, 2, 1, 0 , 0}; const uint32_t grouping_equal_swapped[] = { 1, 2, 0, 2, 1, 1}; const uint32_t n_samples = 6; constexpr int rand_seed_invalid = -3; // must be <0 constexpr int rand_seed_valid = 1; // must be >=0 // value from skbio const float exp_stat = 0.578848; // using a different random than skbio, so result different const float exp_pvalue = 0.65; double stat_fp64, pvalue_fp64; float stat_fp32, pvalue_fp32; skbb::permanova(n_samples, matrix_fp64, grouping_equal, 999, rand_seed_invalid, stat_fp64, pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp32, grouping_equal, 999, rand_seed_invalid, stat_fp32, pvalue_fp32); ASSERT(fabs(stat_fp32 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp32 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp32, grouping_equal, 999, rand_seed_valid, stat_fp32, pvalue_fp32); ASSERT(fabs(stat_fp32 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp32 - exp_pvalue) < 0.05); skbb::permanova(n_samples, matrix_fp64, grouping_equal_swapped, 999, rand_seed_valid, stat_fp64, pvalue_fp64); ASSERT(fabs(stat_fp64 - exp_stat) < 0.00001); ASSERT(fabs(pvalue_fp64 - exp_pvalue) < 0.05); SUITE_END(); } int main(int argc, char** argv) { test_permanova_ties(); test_permanova_noties(); test_permanova_unequal(); { SUITE_START("test acc"); printf("[INFO] Forcibly enable acc reporting, current flag: %i\n",int(skbb::check_report_acc())); skbb::set_report_acc(true); ASSERT(skbb::check_report_acc()); printf("[INFO] Forcibly disable acc, current mode: %i\n",int(skbb::check_use_acc())); ASSERT(skbb::set_use_acc(skbb::ACC_CPU)); ASSERT(skbb::check_use_acc()==0); SUITE_END(); } #ifdef SKBB_ENABLE_CPU_X86_LEVELS { SUITE_START("test x86 CPU levels"); printf("[INFO] Forcibly enable x86 CPU reporting, current flag: %i\n",int(skbb::check_report_cpu_x86())); skbb::set_report_cpu_x86(true); ASSERT(skbb::check_report_cpu_x86()); printf("[INFO] Forcibly disable higher levels of x86 CPU, current mode: %i\n",int(skbb::check_use_cpu_x86())); ASSERT(skbb::set_use_cpu_x86(skbb::CPU_X86_BASE)); ASSERT(skbb::check_use_cpu_x86()==0); SUITE_END(); } #endif test_permanova_ties(); test_permanova_noties(); test_permanova_unequal(); printf("\n"); printf(" %i / %i suites failed\n", suites_failed, suites_run); printf(" %i / %i suites empty\n", suites_empty, suites_run); printf(" %i / %i tests failed\n", tests_failed, tests_run); printf("\n THE END.\n"); return tests_failed ? EXIT_FAILURE : EXIT_SUCCESS; } scikit-bio-scikit-bio-binaries-27dfa5a/src/tools/000077500000000000000000000000001505050360200220125ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/src/tools/generate_permanova_dyn.py000077500000000000000000000020321505050360200271000ustar00rootroot00000000000000#!/usr/bin/env python3 # # Parse permanova_dyn_impl.hpp # and generate concrete implementations # for all inline functions ending in _T # # # Arguments: # $1 - namespace # $2 - method # from skbb_generate_helper import print_header,print_body import sys variant = sys.argv[1] method =sys.argv[2] # # ========================== # with open('distance/permanova_dyn_impl.hpp','r') as fd: lines=fd.readlines() # # ========================== # # print out the header print('// Generated from permanova_dyn_impl.hpp (using method %s)'%method); print('// Do not edit by hand'); print(''); if method in ('direct','indirect',): # we are generating permanova_dyn.cpp print('#include "distance/permanova_dyn.hpp"'); if method in ('indirect','api',): # we referencing the api print('#include "permanova_dyn_%s.h"'%variant); if method in ('direct','api',): # we are generating the actual code print('#include "distance/permanova_dyn_impl.hpp"'); nmspace = print_header(variant,method) print_body(method, lines, nmspace) scikit-bio-scikit-bio-binaries-27dfa5a/src/tools/generate_skbb_accapi.py000077500000000000000000000020141505050360200264570ustar00rootroot00000000000000#!/usr/bin/env python3 # # Parse skbb_accapi_impl.hpp # and generate concrete implementations # for all inline functions ending in _T # # # Arguments: # $1 - namespace # $2 - method # from skbb_generate_helper import print_header,print_body import sys variant = sys.argv[1] method =sys.argv[2] # # ========================== # with open('util/skbb_accapi_impl.hpp','r') as fd: lines=fd.readlines() # # ========================== # # print out the header print('// Generated from skbb_accapi_impl.hpp (using method %s)'%method); print('// Do not edit by hand'); print(''); if method in ('direct','indirect',): # we are generating skbb_accapi.cpp print('#include "util/skbb_accapi.hpp"'); if method in ('indirect','api',): # we referencing the dynamic api print('#include "skbb_accapi_dyn_%s.h"'%variant); if method in ('direct','api',): # we are generating the actual code print('#include "util/skbb_accapi_impl.hpp"'); nmspace = print_header(variant,method) print_body(method, lines, nmspace) scikit-bio-scikit-bio-binaries-27dfa5a/src/tools/skbb_generate_helper.py000066400000000000000000000160011505050360200265140ustar00rootroot00000000000000# # Helper function that parse a header file # and generate concrete implementations # for all inline functions ending in _T # # # ========================== # # replace template name with concrete type def patch_type(s,t): return s.replace('TFloat',t).replace('TNum',t) # extract argument name from the arg definition def get_arg_name(s): return s.split()[-1].split('*')[-1] # extract type from the arg definition def get_arg_type(s): arg_name = get_arg_name(s) return s[:-len(arg_name)] # # ========================== # def print_func_direct_noargs(ftype,nmspace,fname): print('%s %s::%s() {'%(ftype,nmspace,fname)) if ftype=='void': print(' %s_T();'%fname); else: print(' return %s_T();'%fname) print('}'); def print_func_direct_args(ftype,nmspace,fname,ttype,fargs): print('template<>') print('%s %s::%s('%(ftype,nmspace,fname)) for el in fargs[:-1]: print('\t\t\t%s,'%patch_type(el,ttype)) print('\t\t\t%s) {'%patch_type(fargs[-1],ttype)) print(' %s_T('%fname) for el in fargs[:-1]: print('\t%s,'%get_arg_name(el)) print('\t%s);'%get_arg_name(fargs[-1])) print('}'); # # ========================== # def print_func_indirect_noargs(ftype,nmspace,fname): print('static %s (*dl_%s_%s)() = NULL;'%(ftype,nmspace,fname)) print('%s %s::%s() {'%(ftype,nmspace,fname)) if (ftype=='bool') and (fname.find('found_gpu')>=0): #this is the initial check, allow for the shared library to not being avaialble print(' if (!dl_load_check()) return false; /* shlib not found */') print(' cond_dl_load("%s_%s", (void **) &dl_%s_%s);'%(nmspace,fname,nmspace,fname)) if ftype=='void': print(' (*dl_%s_%s)();'%(nmspace,fname)); else: print(' return (*dl_%s_%s)();'%(nmspace,fname)) print('}'); def print_func_indirect_args(ftype,nmspace,fname,ttype,fargs): print('static void (*dl_%s_%s_%s)('%(nmspace,fname,ttype)) for el in fargs[:-1]: print('\t\t\t%s,'%get_arg_type(patch_type(el,ttype))) print('\t\t\t%s) = NULL;'%get_arg_type(patch_type(fargs[-1],ttype))) print('template<>') print('%s %s::%s('%(ftype,nmspace,fname)) for el in fargs[:-1]: print('\t\t\t%s,'%patch_type(el,ttype)) print('\t\t\t%s) {'%patch_type(fargs[-1],ttype)) print(' cond_dl_load("%s_%s_%s", (void **) &dl_%s_%s_%s);'%(nmspace,fname,ttype,nmspace,fname,ttype)) print(' (*dl_%s_%s_%s)('%(nmspace,fname,ttype)) for el in fargs[:-1]: print('\t%s,'%get_arg_name(el)) print('\t%s);'%get_arg_name(fargs[-1])) print('}'); # # ========================== # def print_func_api_h_noargs(ftype,nmspace,fname): print('extern "C" %s %s_%s();'%(ftype,nmspace,fname)) def print_func_api_h_args(ftype,nmspace,fname,ttype,fargs): print('extern "C" %s %s_%s_%s('%(ftype,nmspace,fname,ttype)) for el in fargs[:-1]: print('\t\t\t%s,'%patch_type(el,ttype)) print('\t\t\t%s);'%patch_type(fargs[-1],ttype)) # # ========================== # def print_func_api_noargs(ftype,nmspace,fname): print('%s %s_%s() {'%(ftype,nmspace,fname)) if ftype=='void': print(' %s_T();'%fname); else: print(' return %s_T();'%fname) print('}'); def print_func_api_args(ftype,nmspace,fname,ttype,fargs): print('%s %s_%s_%s('%(ftype,nmspace,fname,ttype)) for el in fargs[:-1]: print('\t\t\t%s,'%patch_type(el,ttype)) print('\t\t\t%s) {'%patch_type(fargs[-1],ttype)) print(' %s_T('%fname) for el in fargs[:-1]: print('\t%s,'%get_arg_name(el)) print('\t%s);'%get_arg_name(fargs[-1])) print('}'); # # ========================== # def print_func_noargs(method,ftype,nmspace,fname): if method=='direct': print_func_direct_noargs(ftype,nmspace,fname) elif method=='indirect': print_func_indirect_noargs(ftype,nmspace,fname) elif method=='api': print_func_api_noargs(ftype,nmspace,fname) elif method=='api_h': print_func_api_h_noargs(ftype,nmspace,fname) else: raise "Unknown generation method '%s'"%method def print_func_args(method,ftype,nmspace,fname,ttype,fargs): if method=='direct': print_func_direct_args(ftype,nmspace,fname,ttype,fargs) elif method=='api': print_func_api_args(ftype,nmspace,fname,ttype,fargs) elif method=='indirect': print_func_indirect_args(ftype,nmspace,fname,ttype,fargs) elif method=='api_h': print_func_api_h_args(ftype,nmspace,fname,ttype,fargs) else: raise "Unknown generation method '%s'"%method # # ========================== # # Print out the common header of the # concrete implementation # Also returns # namespace # # Args: # variant # method - direct, indirect, api, api_h def print_header(variant,method): if method in ('api_h',): # bool and unit_t are not standard in C without these header print('#include ') print('#include ') if method in ('indirect',): # function expected by skbb_ld print('static const char *dl_get_lib_name() { return "libskbb_%s.so";}'%variant) print('#include "util/skbb_dl.cpp"') print('') return "skbb_%s"%variant # # ========================== # # Print out the main body of the # concrete implementation # # Args: # method - direct, indirect, api, api_h # lines - content of the header file # nmspace - namespace to use for the concrete implementations def print_body(method,lines,nmspace): # now we can generate the concrete functions i=0 while (i=0: # special case, no arguments print_func_noargs(method,ftype,nmspace,fname) print(''); continue # assuming ftype == void simplifies the code if ftype!='void': raise "Unsupported templated void found!" ftypes = set() fargs = [] line = lines[i] i+=1 while True: # get all the arguments up to the optional close larr=line.split(') ')[0].strip().split(',') for el in larr: el = el.strip() if len(el)>0: fargs.append(el) if el.find('TFloat')>=0: ftypes |= {'float','double'} elif el.find('TNum')>=0: ftypes |= {'float','double','uint64_t','uint32_t','bool'} if line.find(') ')>=0: break # found end of args, exit the loop line = lines[i] i+=1 for ft in ftypes: print_func_args(method,ftype,nmspace,fname,ft,fargs) print(''); scikit-bio-scikit-bio-binaries-27dfa5a/src/util/000077500000000000000000000000001505050360200216275ustar00rootroot00000000000000scikit-bio-scikit-bio-binaries-27dfa5a/src/util/rand.cpp000066400000000000000000000030411505050360200232550ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and functions that provide library-wide random number generator */ #include "rand.hpp" // need a library-global random generator static skbb::TRAND myRandomGenerator; void skbb::set_random_seed(uint32_t new_seed) { myRandomGenerator.seed(new_seed); } skbb::TRAND& skbb::get_random_generator() { return myRandomGenerator; } static inline void rga_init(const uint32_t array_size, skbb::TRAND randomGenerators[], skbb::TRAND& rand) { // use the provided random generator to get a unique starting seed for (uint32_t el=0; el < array_size; el++) { auto new_seed = rand(); randomGenerators[el].seed(new_seed); } } skbb::RandomGeneratorArray::RandomGeneratorArray(uint32_t array_size) : randomGenerators(new skbb::TRAND[array_size]) { rga_init(array_size,randomGenerators,myRandomGenerator); } skbb::RandomGeneratorArray::RandomGeneratorArray(uint32_t array_size, int seed) : randomGenerators(new skbb::TRAND[array_size]) { if (seed<0) { // no seed, just use global rand rga_init(array_size,randomGenerators,myRandomGenerator); } else { // use the global random generator to get a unique starting seed skbb::TRAND local_random; local_random.seed((unsigned int)(seed)); // now use the loca random for the rest of the logic rga_init(array_size,randomGenerators,local_random); } } scikit-bio-scikit-bio-binaries-27dfa5a/src/util/rand.hpp000066400000000000000000000027661505050360200232770ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and functions that provide random number handling */ #ifndef SKBB_RAND_HPP #define SKBB_RAND_HPP #include #include namespace skbb { typedef std::mt19937 TRAND; // Set random seed used by any and all the functions // in this library void set_random_seed(uint32_t new_seed); // return the global random generator TRAND& get_random_generator(); // List of random generators, e.g. for use in threads class RandomGeneratorArray { private: TRAND *randomGenerators; public: // Arguments: // array_size - number of random generators // seed - Optional random seed, if non-negative. Use system random seed if <0 RandomGeneratorArray(uint32_t array_size, int seed); RandomGeneratorArray(uint32_t array_size); // equivalent to passing seed<0 ~RandomGeneratorArray() { delete[] randomGenerators; } // forbid copying the object RandomGeneratorArray(const RandomGeneratorArray &other) = delete; RandomGeneratorArray& operator=(const RandomGeneratorArray &other) = delete; // return the index-specific random generatora // Note: No boundary checking, caller must ensure i is in a valid range TRAND& get_random_generator(uint32_t i) { return randomGenerators[i]; } }; } #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/util/skbb_accapi.hpp000066400000000000000000000033661505050360200245710ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ // Implementation specific acceleration primitives /* * * This file is used to create the necessary interfaces * from skbb_accapi_impl.hpp * by means of * generate_skbb_accapi.py * */ #ifdef SKBB_ACC_NM // do nothing if SKBB_ACC_NM is not defined #include namespace SKBB_ACC_NM { // do we have access to a GPU? bool acc_found_gpu(); // is the implementation async, and need the alt structures? bool acc_need_alt(); // wait for the async compute to finish void acc_wait(); // create the equivalent buffer in the device memory space, if partitioned // the content in undefined // Use buf_device from now on template void acc_create_buf(T *buf_host, T **buf_device, uint64_t size); // create the equivalent buffer in the device memory space, if partitioned // also copy the buffer over // Use buf_device from now on template void acc_copyin_buf(T *buf_host, T **buf_device, uint64_t size); // make a copy from host to device buffer, if partitioned template void acc_update_device(T *buf_host, T *buf_device, uint64_t start, uint64_t end); // make a copy from device to host buffer, if partitioned // destroy the equivalent buffer in the device memory space, if partitioned template void acc_copyout_buf(T *buf_host, T *buf_device, uint64_t size); // destroy the equivalent buffer in the device memory space, if partitioned template void acc_destroy_buf(T *buf_device, uint64_t size); } #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/util/skbb_accapi_impl.hpp000066400000000000000000000125051505050360200256050ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ // Implementation specific acceleration primitives /* * * This file is used to create the necessary interfaces * by means of * generate_skbb_accapi.py * * Anything ending in _T will get a acc-specific function wrapper. * */ #include "util/skbb_accapi.hpp" #include #if defined(SKBB_CUDA) #include #include #elif defined(SKBB_HIP) #include #include #include #elif defined(OMPGPU) #include #elif defined(_OPENACC) #include #endif static inline bool acc_found_gpu_T() { #if defined(SKBB_CUDA) int deviceCount; cudaError_t error = cudaGetDeviceCount(&deviceCount); if (error != cudaSuccess) return false; return deviceCount != 0; #elif defined(SKBB_HIP) int deviceCount; hipError_t error = hipGetDeviceCount(&deviceCount); if (error != hipSuccess) return false; return deviceCount != 0; #elif defined(OMPGPU) return omp_get_num_devices() > 0; #elif defined(_OPENACC) return acc_get_device_type() != acc_device_host; #else return false; #endif } // is the implementation async, and need the alt structures? static inline bool acc_need_alt_T() { #if defined(_OPENACC) || defined(OMPGPU) || defined(SKBB_CUDA) || defined(SKBB_HIP) return true; #else return false; #endif } static inline void acc_wait_T() { #if defined(SKBB_CUDA) if (cudaDeviceSynchronize()!=cudaSuccess) throw std::runtime_error("cudaDeviceSynchronize failed"); #elif defined(SKBB_HIP) if (hipDeviceSynchronize()!=hipSuccess) throw std::runtime_error("hipDeviceSynchronize failed"); #elif defined(OMPGPU) // TODO: Change if we ever implement async in OMPGPU #elif defined(_OPENACC) #pragma acc wait #endif } template static inline void acc_create_buf_T( TNum* buf_host, TNum** buf_device, uint64_t size) { #if defined(SKBB_CUDA) if (cudaMalloc((void**)buf_device, sizeof(TNum) * size)!=cudaSuccess) throw std::runtime_error("cudaMalloc failed"); #elif defined(SKBB_HIP) if (hipMalloc((void**)buf_device, sizeof(TNum) * size)!=hipSuccess) throw std::runtime_error("hipMalloc failed"); #elif defined(OMPGPU) #pragma omp target enter data map(alloc:buf_host[0:size]) *buf_device = buf_host; #elif defined(_OPENACC) #pragma acc enter data create(buf_host[0:size]) *buf_device = buf_host; #else *buf_device = buf_host; #endif } template static inline void acc_copyin_buf_T( TNum* buf_host, TNum** buf_device, uint64_t size) { #if defined(SKBB_CUDA) if (cudaMalloc((void**)buf_device, sizeof(TNum) * size)!=cudaSuccess) throw std::runtime_error("cudaMalloc failed"); if (cudaMemcpy(*buf_device, buf_host, sizeof(TNum) * size, cudaMemcpyHostToDevice)!=cudaSuccess) throw std::runtime_error("cudaMemcpy failed"); #elif defined(SKBB_HIP) if (hipMalloc((void**)buf_device, sizeof(TNum) * size)!=hipSuccess) throw std::runtime_error("hipMalloc failed"); if (hipMemcpy(*buf_device, buf_host, sizeof(TNum) * size, hipMemcpyHostToDevice)!=hipSuccess) throw std::runtime_error("hipMemcpy failed"); #elif defined(OMPGPU) #pragma omp target enter data map(to:buf_host[0:size]) *buf_device = buf_host; #elif defined(_OPENACC) #pragma acc enter data copyin(buf_host[0:size]) *buf_device = buf_host; #else *buf_device = buf_host; #endif } template static inline void acc_update_device_T( TNum *buf_host, TNum *buf_device, uint64_t start, uint64_t end) { #if defined(SKBB_CUDA) if (cudaMemcpy(buf_device+start, buf_host+start, sizeof(TNum) * (end-start), cudaMemcpyHostToDevice)!=cudaSuccess) throw std::runtime_error("cudaMemcpy failed"); #elif defined(SKBB_HIP) if (hipMemcpy(buf_device+start, buf_host+start, sizeof(TNum) * (end-start), hipMemcpyHostToDevice)!=hipSuccess) throw std::runtime_error("hipMemcpy failed"); #elif defined(OMPGPU) // assert buf_host==buf_device #pragma omp target update to(buf_host[start:end]) #elif defined(_OPENACC) // assert buf_host==buf_device #pragma acc update device(buf_host[start:end]) #endif } template static inline void acc_copyout_buf_T( TNum *buf_host, TNum *buf_device, uint64_t size) { #if defined(SKBB_CUDA) if (cudaMemcpy(buf_host, buf_device, sizeof(TNum) * size, cudaMemcpyDeviceToHost)!=cudaSuccess) throw std::runtime_error("cudaMemcpy failed"); cudaFree(buf_device); #elif defined(SKBB_HIP) if (hipMemcpy(buf_host, buf_device, sizeof(TNum) * size, hipMemcpyDeviceToHost)!=hipSuccess) throw std::runtime_error("hipMemcpy failed"); if (hipFree(buf_device)!=hipSuccess) {} // ignore any errors, not critical #elif defined(OMPGPU) // assert buf_host==buf_device #pragma omp target exit data map(from:buf_device[0:size]) #elif defined(_OPENACC) // assert buf_host==buf_device #pragma acc exit data copyout(buf_device[0:size]) #endif } template static inline void acc_destroy_buf_T( TNum *buf_device, uint64_t size) { #if defined(SKBB_CUDA) cudaFree(buf_device); #elif defined(SKBB_HIP) if (hipFree(buf_device)!=hipSuccess) {} // ignore any errors, not critical #elif defined(OMPGPU) #pragma omp target exit data map(delete:buf_device[0:size]) #elif defined(_OPENACC) #pragma acc exit data delete(buf_device[0:size]) #endif } scikit-bio-scikit-bio-binaries-27dfa5a/src/util/skbb_detect_acc.cpp000066400000000000000000000227201505050360200254150ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and functions that accelerator selection logic */ #include "util/skbb_detect_acc.hpp" #ifdef SKBB_ENABLE_ACC_NV #define SKBB_ACC_NM skbb_acc_nv #include "util/skbb_accapi.hpp" #undef SKBB_ACC_NM #endif #ifdef SKBB_ENABLE_ACC_AMD #define SKBB_ACC_NM skbb_acc_amd #include "util/skbb_accapi.hpp" #undef SKBB_ACC_NM #endif #include #include #include static constexpr unsigned int ACC_REPORT_FALSE=0; static constexpr unsigned int ACC_REPORT_TRUE=1; // test only once, then use persistent value static int skbb_report_acc = -1; static int skbb_use_acc = -1; // test only once, then use persistent value bool skbb::check_report_acc() { if (skbb_report_acc>=0) return skbb_report_acc!=ACC_REPORT_FALSE; // keep the cached version bool print_info = false; if (const char* env_p = std::getenv("SKBB_GPU_INFO")) { print_info = true; std::string env_s(env_p); if ((env_s=="NO") || (env_s=="N") || (env_s=="no") || (env_s=="n") || (env_s=="NEVER") || (env_s=="never")) { print_info = false; } } skbb_report_acc = print_info ? ACC_REPORT_TRUE : ACC_REPORT_FALSE; return print_info; } void skbb::set_report_acc(bool new_value) { skbb_report_acc = new_value ? ACC_REPORT_TRUE : ACC_REPORT_FALSE; } unsigned int skbb::check_use_acc() { if (skbb_use_acc>=0) return skbb_use_acc; // keep the cached version bool print_info = skbb::check_report_acc(); int detected_acc = skbb::ACC_CPU; #if defined(SKBB_ENABLE_ACC_NV) bool detected_nv_acc = skbb_acc_nv::acc_found_gpu(); if (print_info) { if (detected_nv_acc) { printf("INFO (skbio_bins): NVIDIA GPU detected\n"); } else { printf("INFO (skbio_bins): NVIDIA GPU not detected\n"); } } if ((detected_acc==skbb::ACC_CPU) && detected_nv_acc) { detected_acc = skbb::ACC_NV; if (const char* env_p = std::getenv("SKBB_USE_NVIDIA_GPU")) { std::string env_s(env_p); if ((env_s=="NO") || (env_s=="N") || (env_s=="no") || (env_s=="n") || (env_s=="NEVER") || (env_s=="never")) { if (print_info) printf("INFO (skbio_bins): NVIDIA GPU was detected but use explicitly disabled\n"); detected_acc = skbb::ACC_CPU; } } } #endif #if defined(SKBB_ENABLE_ACC_AMD) bool detected_amd_acc = skbb_acc_amd::acc_found_gpu(); if (print_info) { if (detected_amd_acc) { printf("INFO (skbio_bins): AMD GPU detected\n"); } else { printf("INFO (skbio_bins): AMD GPU not detected\n"); } } if ((detected_acc==skbb::ACC_CPU) && detected_amd_acc) { detected_acc = skbb::ACC_AMD; if (const char* env_p = std::getenv("SKBB_USE_AMD_GPU")) { std::string env_s(env_p); if ((env_s=="NO") || (env_s=="N") || (env_s=="no") || (env_s=="n") || (env_s=="NEVER") || (env_s=="never")) { if (print_info) printf("INFO (skbio_bins): AMD GPU was detected but use explicitly disabled\n"); detected_acc = skbb::ACC_CPU; } } } #endif if (const char* env_p = std::getenv("SKBB_USE_GPU")) { std::string env_s(env_p); if ((env_s=="NO") || (env_s=="N") || (env_s=="no") || (env_s=="n") || (env_s=="NEVER") || (env_s=="never")) { if (detected_acc!=skbb::ACC_CPU) { if (print_info) printf("INFO (skbio_bins): GPU was detected but use explicitly disabled\n"); detected_acc = skbb::ACC_CPU; } } } if (print_info) { if (detected_acc == skbb::ACC_CPU) { printf("INFO (skbio_bins): Using CPU (not GPU)\n"); #if defined(SKBB_ENABLE_ACC_NV) } else if (detected_acc == skbb::ACC_NV) { printf("INFO (skbio_bins): Using NVIDIA GPU\n"); #endif #if defined(SKBB_ENABLE_ACC_AMD) } else if (detected_acc == skbb::ACC_AMD) { printf("INFO (skbio_bins): Using AMD GPU\n"); #endif } else { printf("WARNING (skbio_bins): Logic error in picking GPU\n"); } } // we can assume int is atomic skbb_use_acc = detected_acc; return skbb_use_acc; } // acc_type must be one of the supported ones bool skbb::set_use_acc(unsigned int acc_type) { if (acc_type == skbb::ACC_CPU) { skbb_use_acc = acc_type; #if defined(SKBB_ENABLE_ACC_NV) } else if (acc_type == skbb::ACC_NV) { skbb_use_acc = acc_type; #endif #if defined(SKBB_ENABLE_ACC_AMD) } else if (acc_type == skbb::ACC_AMD) { skbb_use_acc = acc_type; #endif } else { return false; } bool print_info = skbb::check_report_acc(); if (print_info) { if (skbb_use_acc == skbb::ACC_CPU) { printf("INFO (skbio_bins): Using CPU (not GPU)\n"); #if defined(SKBB_ENABLE_ACC_NV) } else if (skbb_use_acc == skbb::ACC_NV) { printf("INFO (skbio_bins): Using NVIDIA GPU\n"); #endif #if defined(SKBB_ENABLE_ACC_AMD) } else if (skbb_use_acc == skbb::ACC_AMD) { printf("INFO (skbio_bins): Using AMD GPU\n"); #endif } else { printf("WARNING (skbio_bins): Logic error in picking GPU\n"); } } return true; } #ifdef SKBB_ENABLE_CPU_X86_LEVELS // test only once, then use persistent value static int skbb_report_cpu_x86 = -1; static int skbb_use_cpu_x86 = -1; // test only once, then use persistent value bool skbb::check_report_cpu_x86() { if (skbb_report_cpu_x86>=0) return skbb_report_cpu_x86!=ACC_REPORT_FALSE; // keep the cached version bool print_info = false; if (const char* env_p = std::getenv("SKBB_CPU_INFO")) { print_info = true; std::string env_s(env_p); if ((env_s=="NO") || (env_s=="N") || (env_s=="no") || (env_s=="n") || (env_s=="NEVER") || (env_s=="never")) { print_info = false; } } skbb_report_cpu_x86 = print_info ? ACC_REPORT_TRUE : ACC_REPORT_FALSE; return print_info; } void skbb::set_report_cpu_x86(bool new_value) { skbb_report_cpu_x86 = new_value ? ACC_REPORT_TRUE : ACC_REPORT_FALSE; } unsigned int skbb::check_use_cpu_x86() { if (skbb_use_cpu_x86>=0) return skbb_use_cpu_x86; // keep the cached version bool print_info = skbb::check_report_cpu_x86(); int detected_cpu_x86 = skbb::CPU_X86_BASE; __builtin_cpu_init (); const char* env_max_cpu = getenv("SKBB_MAX_CPU"); bool allow_v3 = true; bool allow_v4 = true; if (env_max_cpu!=NULL) { if ((strcmp(env_max_cpu,"basic")==0) || (strcmp(env_max_cpu,"base")==0) || (strcmp(env_max_cpu,"x86-64-v2")==0) || (strcmp(env_max_cpu,"sse")==0) || (strcmp(env_max_cpu,"sse3")==0) || (strcmp(env_max_cpu,"sse4")==0) || (strcmp(env_max_cpu,"avx")==0) ) { allow_v3 = false; allow_v4 = false; } else if ((strcmp(env_max_cpu,"x86-64-v3")==0) || (strcmp(env_max_cpu,"avx2")==0)) { allow_v4 = false; } // ignore any unknown strings } // do the check in priority order, since they are supersets #if defined(SKBB_ENABLE_CPU_X86V4) if (detected_cpu_x86 == skbb::CPU_X86_BASE) { bool has_v4 = __builtin_cpu_supports ("x86-64-v4"); if (has_v4) { if (allow_v4) { if (print_info) printf("INFO (skbio_bins): x86-64-v4 CPU detected\n"); detected_cpu_x86 = skbb::CPU_X86_V4; } else { if (print_info) printf("INFO (skbio_bins): x86-64-v4 CPU was detected but use explicitly disabled\n"); } } else { if (print_info) printf("INFO (skbio_bins): x86-64-v4 CPU not detected\n"); } } #endif #if defined(SKBB_ENABLE_CPU_X86V3) if (detected_cpu_x86 == skbb::CPU_X86_BASE) { bool has_v3 = __builtin_cpu_supports ("x86-64-v3"); if (has_v3) { if (allow_v3) { if (print_info) printf("INFO (skbio_bins): x86-64-v3 CPU detected\n"); detected_cpu_x86 = skbb::CPU_X86_V3; } else { if (print_info) printf("INFO (skbio_bins): x86-64-v3 CPU was detected but use explicitly disabled\n"); } } else { if (print_info) printf("INFO (skbio_bins): x86-64-v3 CPU not detected\n"); } } #endif if (print_info) { if (detected_cpu_x86 == skbb::CPU_X86_BASE) { printf("INFO (skbio_bins): Using base x86 CPU\n"); #if defined(SKBB_ENABLE_CPU_X86V3) } else if (detected_cpu_x86 == skbb::CPU_X86_V3) { printf("INFO (skbio_bins): Using x86-64-v3 CPU\n"); #endif #if defined(SKBB_ENABLE_CPU_X86V4) } else if (detected_cpu_x86 == skbb::CPU_X86_V4) { printf("INFO (skbio_bins): Using x86-64-v4 CPU\n"); #endif } else { printf("WARNING (skbio_bins): Logic error in picking x86 CPU level\n"); } } // we can assume int is atomic skbb_use_cpu_x86 = detected_cpu_x86; return skbb_use_cpu_x86; } // cpu_x86_levele must be one of the supported ones bool skbb::set_use_cpu_x86(unsigned int cpu_x86_level) { if (cpu_x86_level == skbb::CPU_X86_BASE) { skbb_use_cpu_x86 = cpu_x86_level; #if defined(SKBB_ENABLE_CPU_X86V3) } else if (cpu_x86_level == skbb::CPU_X86_V3) { skbb_use_cpu_x86 = cpu_x86_level; #endif #if defined(SKBB_ENABLE_CPU_X86V4) } else if (cpu_x86_level == skbb::CPU_X86_V4) { skbb_use_cpu_x86 = cpu_x86_level; #endif } else { return false; } bool print_info = skbb::check_report_cpu_x86(); if (print_info) { if (skbb_use_cpu_x86 == skbb::CPU_X86_BASE) { printf("INFO (skbio_bins): Using base x86 CPU\n"); #if defined(SKBB_ENABLE_CPU_X86V3) } else if (skbb_use_cpu_x86 == skbb::CPU_X86_V3) { printf("INFO (skbio_bins): Using x86-64-v3 CPU\n"); #endif #if defined(SKBB_ENABLE_CPU_X86V4) } else if (skbb_use_cpu_x86 == skbb::CPU_X86_V4) { printf("INFO (skbio_bins): Using x86-64-v4 CPU\n"); #endif } else { printf("WARNING (skbio_bins): Logic error in picking x86 CPU level\n"); } } return true; } #endif /* SKBB_ENABLE_CPU_X86_LEVELS */ scikit-bio-scikit-bio-binaries-27dfa5a/src/util/skbb_detect_acc.hpp000066400000000000000000000032521505050360200254210ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2016-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ /* * Classes, methods and functions that accelerator selection logic */ #ifndef SKBB_DETECT_ACC_HPP #define SKBB_DETECT_ACC_HPP #include namespace skbb { static constexpr unsigned int ACC_CPU=0; static constexpr unsigned int ACC_NV=1; static constexpr unsigned int ACC_AMD=2; // is reporting of ACC use enabled? bool check_report_acc(); // override the default value void set_report_acc(bool new_value); // ======= // what variant of accelerated code should I use unsigned int check_use_acc(); // override the default value // Returns: // true if acc_type should be one of the above supported ones // false otherwise, and the internal value is not changed bool set_use_acc(unsigned int acc_type); #ifdef SKBB_ENABLE_CPU_X86_LEVELS // optional, internal, x86 levels interface static constexpr unsigned int CPU_X86_BASE=0; static constexpr unsigned int CPU_X86_V3=3; static constexpr unsigned int CPU_X86_V4=4; // is reporting of ACC use enabled? bool check_report_cpu_x86(); // override the default value void set_report_cpu_x86(bool new_value); // ======= // what variant of accelerated code should I use unsigned int check_use_cpu_x86(); // override the default value // Returns: // true if acc_type should be one of the above supported ones // false otherwise, and the internal value is not changed bool set_use_cpu_x86(unsigned int cpu_x86_level); #endif /* SKBB_ENABLE_CPU_X86_LEVELS */ } #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/util/skbb_dgb_info.hpp000066400000000000000000000032071505050360200251120ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ #ifndef SKBB_DBG_INFO_HPP #define SKBB_DBG_INFO_HPP #include // To be used once per function #define SETUP_TDBG(method) const char *tdbg_method=method; \ bool print_tdbg = false;\ if (const char* env_p = std::getenv("SKBB_TIMING_INFO")) { \ print_tdbg = true; \ std::string env_s(env_p); \ if ((env_s=="NO") || (env_s=="N") || (env_s=="no") || (env_s=="n") || \ (env_s=="NEVER") || (env_s=="never")) print_tdbg = false; \ } \ std::chrono::time_point tgdb_t0; \ if(print_tdbg) {\ tgdb_t0 = std::chrono::high_resolution_clock::now(); \ printf("INFO (skbio_bins): Starting %s\n",tdbg_method); \ } // Can be invoked many time in a function // But only after SETUP_TDBG was called once #define TDBG_STEP(sname) if(print_tdbg) {\ auto tgdb_t1 = std::chrono::high_resolution_clock::now(); \ auto time_span = std::chrono::duration_cast>(tgdb_t1 - tgdb_t0); \ printf("INFO (skbio_bins): dt %6.2f : Completed %s.%s\n",time_span.count(),tdbg_method,sname); \ tgdb_t0 = tgdb_t1; \ } #endif scikit-bio-scikit-bio-binaries-27dfa5a/src/util/skbb_dl.cpp000066400000000000000000000043461505050360200237420ustar00rootroot00000000000000/* * BSD 3-Clause License * * Copyright (c) 2023-2025, UniFrac development team. * Copyright (c) 2025--, scikit-bio development team. * All rights reserved. * * See LICENSE file for more details */ // This file is intended to be included in other .cpp files #include #include #include #include #include #include /* Handle pointing to the approriate shared library implementing the functionality * Initialized on first use. */ static void *dl_handle = NULL; static void dl_load(const char *fncname, void **dl_ptr) { char *error; if (dl_handle==NULL) { const char* lib_name = dl_get_lib_name(); const char* env_cpu_info = getenv("SKBB_GPU_INFO"); if ((env_cpu_info!=NULL) && (env_cpu_info[0]=='Y')) { printf("INFO (skbio_bins): Using shared library %s\n",lib_name); } dl_handle = dlopen(lib_name, RTLD_LAZY); if (!dl_handle) { fputs(dlerror(), stderr); exit(1); } } *dl_ptr = dlsym(dl_handle, fncname); if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } } static pthread_mutex_t dl_mutex = PTHREAD_MUTEX_INITIALIZER; static void cond_dl_load(const char *fncname, void **dl_ptr) { pthread_mutex_lock(&dl_mutex); if ((*dl_ptr)==NULL) dl_load(fncname,dl_ptr); pthread_mutex_unlock(&dl_mutex); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" /* This function is sometimes used when included, so prevent the compiler warning */ static bool dl_load_check() { pthread_mutex_lock(&dl_mutex); if (dl_handle==NULL) { const char* lib_name = dl_get_lib_name(); dl_handle = dlopen(lib_name, RTLD_LAZY); if (!dl_handle) { // no such shared library pthread_mutex_unlock(&dl_mutex); return false; } // only print out if the library exists const char* env_cpu_info = getenv("SKBB_GPU_INFO"); if ((env_cpu_info!=NULL) && (env_cpu_info[0]=='Y')) { printf("INFO (skbio_bins): Using shared library %s\n",lib_name); } } pthread_mutex_unlock(&dl_mutex); return true; } #pragma GCC diagnostic pop