scrna-matrix 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. scrna_matrix-0.1.6/.clang-format +5 -0
  2. scrna_matrix-0.1.6/.pytest_cache/README.md +8 -0
  3. scrna_matrix-0.1.6/CMakeLists.txt +342 -0
  4. scrna_matrix-0.1.6/PKG-INFO +356 -0
  5. scrna_matrix-0.1.6/README.md +332 -0
  6. scrna_matrix-0.1.6/docs/AUDIT.md +751 -0
  7. scrna_matrix-0.1.6/docs/BENCHMARKS.md +186 -0
  8. scrna_matrix-0.1.6/docs/MANUSCRIPT.tex +100 -0
  9. scrna_matrix-0.1.6/examples/scanpy_integration.ipynb +1006 -0
  10. scrna_matrix-0.1.6/examples/scrna_anndata.py +287 -0
  11. scrna_matrix-0.1.6/include/matrix/block_csr.hpp +438 -0
  12. scrna_matrix-0.1.6/include/matrix/hnsw_index.hpp +991 -0
  13. scrna_matrix-0.1.6/include/matrix/knn_graph.hpp +686 -0
  14. scrna_matrix-0.1.6/include/matrix/simd_math.hpp +874 -0
  15. scrna_matrix-0.1.6/pyproject.toml +140 -0
  16. scrna_matrix-0.1.6/python/scrna_matrix/__init__.py +62 -0
  17. scrna_matrix-0.1.6/src/python_bindings.cpp +737 -0
  18. scrna_matrix-0.1.6/tests/benchmark_1m_cells.py +213 -0
  19. scrna_matrix-0.1.6/tests/test_hnsw_persist.cpp +732 -0
  20. scrna_matrix-0.1.6/tests/test_matrix_ops.cpp +1494 -0
  21. scrna_matrix-0.1.6/tests/test_python_bindings.py +1130 -0
  22. scrna_matrix-0.1.6/tests/test_wide_accumulation.cpp +372 -0
  23. scrna_matrix-0.1.6/third_party/hnswlib/LICENSE +201 -0
  24. scrna_matrix-0.1.6/third_party/hnswlib/VERSION.txt +164 -0
  25. scrna_matrix-0.1.6/third_party/hnswlib/bruteforce.h +163 -0
  26. scrna_matrix-0.1.6/third_party/hnswlib/hnswalg.h +1536 -0
  27. scrna_matrix-0.1.6/third_party/hnswlib/hnswlib.h +228 -0
  28. scrna_matrix-0.1.6/third_party/hnswlib/space_ip.h +400 -0
  29. scrna_matrix-0.1.6/third_party/hnswlib/space_l2.h +324 -0
  30. scrna_matrix-0.1.6/third_party/hnswlib/stop_condition.h +276 -0
  31. scrna_matrix-0.1.6/third_party/hnswlib/visited_list_pool.h +78 -0
@@ -0,0 +1,5 @@
1
+ # Inherits the shared conservative settings from the repo root's
2
+ # .clang-format (see that file's header comment) and overrides only the
3
+ # indent width, matching this module's existing convention.
4
+ BasedOnStyle: InheritParentConfig
5
+ IndentWidth: 2
@@ -0,0 +1,8 @@
1
+ # pytest cache directory #
2
+
3
+ This directory contains data from the pytest's cache plugin,
4
+ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
5
+
6
+ **Do not** commit this to version control.
7
+
8
+ See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
@@ -0,0 +1,342 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(scrna_matrix LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 20)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
7
+
8
+ if(NOT CMAKE_BUILD_TYPE)
9
+ set(CMAKE_BUILD_TYPE Release)
10
+ endif()
11
+
12
+ option(SCRNA_BUILD_TESTS "Build C++ unit tests" ON)
13
+ option(SCRNA_BUILD_PYTHON "Build pybind11 Python bindings" ON)
14
+ option(SCRNA_ENABLE_AVX512 "Enable AVX-512 code paths" OFF)
15
+ option(SCRNA_ENABLE_HNSW "Enable approximate k-NN via vendored hnswlib" ON)
16
+ option(SCRNA_HNSW_NATIVE_ISA
17
+ "Compile hnswlib's own AVX/AVX-512 kernels by putting ISA flags on the target. \
18
+ Off by default: it makes the binary require those extensions at runtime. \
19
+ Our ScrnaCosineSpace already reaches AVX-512 safely via target attributes." OFF)
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Header-only core library
23
+ # ---------------------------------------------------------------------------
24
+ add_library(scrna_matrix INTERFACE)
25
+ target_include_directories(scrna_matrix INTERFACE
26
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
27
+ $<INSTALL_INTERFACE:include>
28
+ )
29
+ target_compile_features(scrna_matrix INTERFACE cxx_std_20)
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # SIMD / arch capability detection
33
+ #
34
+ # Deliberately NO -mavx2 / -mavx512f on the target. Applying an ISA flag to the
35
+ # whole interface lets the compiler emit those instructions anywhere it likes,
36
+ # including in code that never goes through a runtime capability check -- so the
37
+ # resulting binary SIGILLs on any CPU that lacks the extension. Instead the
38
+ # kernels in simd_math.hpp carry per-function `target` attributes and are
39
+ # selected at runtime, which keeps the rest of the binary at the baseline ISA.
40
+ #
41
+ # So what we probe for here is not "does the compiler accept -mavx2" but "can
42
+ # this compiler build a target-attributed AVX2 function", which is the mechanism
43
+ # actually relied upon. On non-x86 targets (arm64/Apple Silicon) both probes fail
44
+ # and only the scalar path is compiled.
45
+ # ---------------------------------------------------------------------------
46
+ include(CheckCXXSourceCompiles)
47
+
48
+ check_cxx_source_compiles("
49
+ #include <immintrin.h>
50
+ __attribute__((target(\"avx2,fma\")))
51
+ static float probe(const float* p, const int* q) {
52
+ __m256i vi = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(q));
53
+ __m256 vg = _mm256_i32gather_ps(p, vi, 4);
54
+ return _mm256_cvtss_f32(_mm256_fmadd_ps(vg, vg, vg));
55
+ }
56
+ int main() { float a[8]={0}; int b[8]={0}; return (int)probe(a,b); }
57
+ " SCRNA_HAS_AVX2_TARGET_ATTR)
58
+
59
+ check_cxx_source_compiles("
60
+ #include <immintrin.h>
61
+ __attribute__((target(\"avx512f,avx512bw\")))
62
+ static float probe(const float* p, const int* q) {
63
+ __m512i vi = _mm512_loadu_si512(reinterpret_cast<const void*>(q));
64
+ __m512 vg = _mm512_i32gather_ps(vi, p, 4);
65
+ return _mm512_reduce_add_ps(vg);
66
+ }
67
+ int main() { float a[16]={0}; int b[16]={0}; return (int)probe(a,b); }
68
+ " SCRNA_HAS_AVX512_TARGET_ATTR)
69
+
70
+ if(SCRNA_HAS_AVX2_TARGET_ATTR)
71
+ target_compile_definitions(scrna_matrix INTERFACE SCRNA_HAVE_AVX2=1)
72
+ message(STATUS "scrna_matrix: AVX2 gather kernel compiled in (runtime-dispatched).")
73
+ else()
74
+ message(STATUS "scrna_matrix: AVX2 unavailable for this target; scalar path only.")
75
+ endif()
76
+
77
+ if(SCRNA_ENABLE_AVX512 AND SCRNA_HAS_AVX512_TARGET_ATTR)
78
+ target_compile_definitions(scrna_matrix INTERFACE SCRNA_HAVE_AVX512=1)
79
+ message(STATUS "scrna_matrix: AVX-512 gather kernel compiled in (runtime-dispatched).")
80
+ endif()
81
+
82
+ # ---------------------------------------------------------------------------
83
+ # hnswlib (vendored, header-only, Apache-2.0) for approximate k-NN
84
+ # ---------------------------------------------------------------------------
85
+ if(SCRNA_ENABLE_HNSW)
86
+ if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/third_party/hnswlib/hnswlib.h)
87
+ message(FATAL_ERROR "SCRNA_ENABLE_HNSW=ON but third_party/hnswlib/hnswlib.h is missing.")
88
+ endif()
89
+ # SYSTEM: hnswlib is vendored and we do not own its warnings. Without this,
90
+ # -Wshadow and -Wunused-parameter fire inside hnswalg.h and drown the
91
+ # first-party diagnostics that actually need acting on -- and make a
92
+ # -Werror build impossible for reasons that have nothing to do with our code.
93
+ target_include_directories(scrna_matrix SYSTEM INTERFACE
94
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/third_party>)
95
+ target_compile_definitions(scrna_matrix INTERFACE SCRNA_ENABLE_HNSW=1)
96
+
97
+ if(SCRNA_HNSW_NATIVE_ISA)
98
+ # hnswlib gates its AVX kernels on compile-time __AVX__/__AVX512F__ and does
99
+ # not use per-function target attributes, so reaching them means putting the
100
+ # ISA flag on the target -- which is exactly the arrangement the rest of this
101
+ # build avoids, because it lets the compiler emit those instructions in code
102
+ # that no runtime check guards.
103
+ if(SCRNA_HAS_AVX2_TARGET_ATTR)
104
+ target_compile_options(scrna_matrix INTERFACE -mavx2 -mfma)
105
+ endif()
106
+ if(SCRNA_ENABLE_AVX512 AND SCRNA_HAS_AVX512_TARGET_ATTR)
107
+ target_compile_options(scrna_matrix INTERFACE -mavx512f -mavx512bw)
108
+ endif()
109
+ message(WARNING
110
+ "SCRNA_HNSW_NATIVE_ISA=ON: the resulting binary may execute AVX2/AVX-512 "
111
+ "instructions without a runtime guard and will fault on CPUs lacking them. "
112
+ "Only use this when the build and target CPUs are known to match.")
113
+ else()
114
+ message(STATUS
115
+ "scrna_matrix: hnswlib enabled; its distances run through ScrnaCosineSpace "
116
+ "(runtime-dispatched AVX2/AVX-512). hnswlib's own kernels stay at baseline ISA.")
117
+ endif()
118
+ endif()
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # OpenMP -- MANDATORY.
122
+ #
123
+ # This was `find_package(OpenMP QUIET)` with a `message(WARNING)` fallback, and
124
+ # that fallback was actively harmful. Without OpenMP, `_OPENMP` is undefined and
125
+ # every `#pragma omp` in knn_graph.hpp is ignored by the preprocessor, so the
126
+ # library still builds, still passes its whole test suite, and still reports
127
+ # "100% tests passed" -- while never executing a single parallel iteration.
128
+ #
129
+ # That is exactly what had happened here: the shipped configuration on the
130
+ # development host had OpenMP_CXX_FLAGS=NOTFOUND, so the concurrency paths
131
+ # (parallel_for_checked, the per-thread ThreadState indexing, the atomics
132
+ # ordering in the error latch) were dead code that CI was silently signing off
133
+ # on. A warning in a thousand-line configure log is not a control.
134
+ #
135
+ # So it is REQUIRED, and configuration fails on a host that cannot supply it.
136
+ # The single-threaded build is not a supported configuration: this library's
137
+ # stated purpose is k-NN over 1M+ cells, where losing the parallel loop is a
138
+ # correctness-of-benchmark and time-to-result problem, not a minor degradation.
139
+ #
140
+ # There is deliberately NO opt-out variable. If you need to build without
141
+ # OpenMP, that is a decision to make explicitly by editing this line, so it
142
+ # shows up in review and in `git blame`, rather than by setting a flag that
143
+ # silently reintroduces untested single-threaded stubs.
144
+ #
145
+ # Toolchain notes (this is the common failure and the message must fix it, not
146
+ # merely report it):
147
+ # * Apple Clang ships no OpenMP runtime. -> brew install libomp (auto-wired below)
148
+ # * Homebrew GCC has it built in. -> -DCMAKE_CXX_COMPILER=g++-15
149
+ # * Debian/Ubuntu Clang. -> apt install libomp-dev
150
+
151
+ # Resolve the toolchain before searching. On macOS + Apple Clang this locates
152
+ # Homebrew's keg-only libomp and hands FindOpenMP the flags it cannot derive
153
+ # itself; everywhere else it is a no-op. See cmake/PtoOpenMP.cmake -- it
154
+ # makes the requirement satisfiable without relaxing it.
155
+ include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/PtoOpenMP.cmake" OPTIONAL
156
+ RESULT_VARIABLE _scrna_omp_helper)
157
+ if(_scrna_omp_helper)
158
+ pto_openmp_apply_hints()
159
+ endif()
160
+
161
+ find_package(OpenMP QUIET COMPONENTS CXX)
162
+ if(NOT OpenMP_CXX_FOUND)
163
+ if(COMMAND pto_openmp_failure_message)
164
+ pto_openmp_failure_message(_scrna_omp_help)
165
+ message(FATAL_ERROR "\n${_scrna_omp_help}\n")
166
+ endif()
167
+ message(FATAL_ERROR
168
+ "OpenMP is REQUIRED for scrna_matrix and was not found. "
169
+ "macOS: brew install libomp. Debian/Ubuntu: apt-get install libomp-dev.")
170
+ endif()
171
+
172
+ if(NOT TARGET OpenMP::OpenMP_CXX)
173
+ message(FATAL_ERROR
174
+ "OpenMP was reported as found but the OpenMP::OpenMP_CXX imported target "
175
+ "does not exist. Refusing to continue: linking would silently drop the "
176
+ "runtime and every '#pragma omp' would compile to serial code.")
177
+ endif()
178
+
179
+ target_link_libraries(scrna_matrix INTERFACE OpenMP::OpenMP_CXX)
180
+
181
+ # `find_package(OpenMP)` succeeding is necessary but not sufficient: it proves
182
+ # the compiler accepts the flag, not that `_OPENMP` is actually defined when
183
+ # compiling this target with these flags. Prove the macro end-to-end, because
184
+ # `_OPENMP` is the thing every parallel region in this library is guarded on.
185
+ include(CheckCXXSourceCompiles)
186
+ include(CMakePushCheckState)
187
+ cmake_push_check_state(RESET)
188
+ set(CMAKE_REQUIRED_FLAGS "${OpenMP_CXX_FLAGS}")
189
+ set(CMAKE_REQUIRED_LIBRARIES OpenMP::OpenMP_CXX)
190
+ check_cxx_source_compiles("
191
+ #include <omp.h>
192
+ #ifndef _OPENMP
193
+ #error _OPENMP is not defined; parallel regions would compile to serial code
194
+ #endif
195
+ int main() {
196
+ int n = 0;
197
+ #pragma omp parallel reduction(+ : n)
198
+ { n += 1; }
199
+ return n > 0 ? 0 : 1;
200
+ }" SCRNA_OPENMP_PRAGMAS_ACTIVE)
201
+ cmake_pop_check_state()
202
+
203
+ if(NOT SCRNA_OPENMP_PRAGMAS_ACTIVE)
204
+ message(FATAL_ERROR
205
+ "OpenMP::OpenMP_CXX was found, but a translation unit compiled with "
206
+ "OpenMP_CXX_FLAGS ('${OpenMP_CXX_FLAGS}') either does not define _OPENMP or "
207
+ "cannot compile a '#pragma omp parallel' region.\n"
208
+ "Building in this state would produce a library whose k-NN construction is "
209
+ "silently single-threaded and whose concurrency code is never executed. "
210
+ "Refusing.\n"
211
+ " Apple Clang : brew install libomp\n"
212
+ " Homebrew GCC: cmake -DCMAKE_CXX_COMPILER=g++-15 ...\n"
213
+ " Debian/Ubuntu Clang: apt-get install libomp-dev")
214
+ endif()
215
+
216
+ message(STATUS " OpenMP : ${OpenMP_CXX_FLAGS} (pragmas verified active)")
217
+
218
+ # ---------------------------------------------------------------------------
219
+ # Eigen3 (optional; linked if present)
220
+ #
221
+ # Nothing in include/matrix currently uses Eigen -- the scalar fallbacks are
222
+ # plain C++ and have no third-party dependency. This stays as an opt-in hook for
223
+ # future dense-side work; its absence disables nothing.
224
+ # ---------------------------------------------------------------------------
225
+ find_package(Eigen3 QUIET NO_MODULE)
226
+ if(TARGET Eigen3::Eigen)
227
+ target_link_libraries(scrna_matrix INTERFACE Eigen3::Eigen)
228
+ endif()
229
+
230
+ # ---------------------------------------------------------------------------
231
+ # Tests
232
+ # ---------------------------------------------------------------------------
233
+ if(SCRNA_BUILD_TESTS)
234
+ enable_testing()
235
+ add_executable(test_matrix_ops tests/test_matrix_ops.cpp)
236
+ target_link_libraries(test_matrix_ops PRIVATE scrna_matrix)
237
+ add_test(NAME test_matrix_ops COMMAND test_matrix_ops)
238
+
239
+ # Wide accumulation (docs/AUDIT.md S2, REVIEW finding 5 + its residual, T5).
240
+ # The last case in this binary scans the module's own sources for a bare
241
+ # `float` accumulator, so it needs to know where they are. Passed as a
242
+ # definition rather than assumed relative to the cwd: ctest runs from the
243
+ # build tree, and a guard that silently finds no files reads as a pass.
244
+ add_executable(test_wide_accumulation tests/test_wide_accumulation.cpp)
245
+ target_link_libraries(test_wide_accumulation PRIVATE scrna_matrix)
246
+ target_compile_definitions(test_wide_accumulation
247
+ PRIVATE SCRNA_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
248
+ add_test(NAME test_wide_accumulation COMMAND test_wide_accumulation)
249
+
250
+ # Index serialization. Built unconditionally; the translation unit compiles
251
+ # to a no-op main() when SCRNA_ENABLE_HNSW is off, so the test list does not
252
+ # change shape with the option.
253
+ add_executable(test_hnsw_persist tests/test_hnsw_persist.cpp)
254
+ target_link_libraries(test_hnsw_persist PRIVATE scrna_matrix)
255
+ add_test(NAME test_hnsw_persist COMMAND test_hnsw_persist)
256
+ endif()
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # Python bindings (pybind11)
260
+ # ---------------------------------------------------------------------------
261
+ if(SCRNA_BUILD_PYTHON)
262
+ find_package(pybind11 CONFIG QUIET)
263
+ if(pybind11_FOUND)
264
+ pybind11_add_module(scrna_matrix_py src/python_bindings.cpp)
265
+ target_link_libraries(scrna_matrix_py PRIVATE scrna_matrix)
266
+
267
+ # Wheel packaging. scikit-build-core collects whatever CMake installs and
268
+ # lays it into the wheel, so the extension goes *inside* the Python package
269
+ # directory rather than at the wheel root -- python/scrna_matrix/__init__.py
270
+ # re-exports it from there. Harmless for an ordinary `cmake --install`.
271
+ install(TARGETS scrna_matrix_py
272
+ LIBRARY DESTINATION scrna_matrix
273
+ RUNTIME DESTINATION scrna_matrix)
274
+
275
+ # pybind11 spells this PYTHON_EXECUTABLE; FindPython spells it Python_EXECUTABLE.
276
+ set(_scrna_python "${PYTHON_EXECUTABLE}")
277
+ if(NOT _scrna_python)
278
+ set(_scrna_python "${Python_EXECUTABLE}")
279
+ endif()
280
+
281
+ # -------------------------------------------------------------------------
282
+ # De-duplicate the OpenMP runtime the extension loads (macOS only).
283
+ #
284
+ # scrna_matrix links OpenMP::OpenMP_CXX, which on Apple Clang resolves to
285
+ # Homebrew's keg-only libomp (PtoOpenMP.cmake, above). That is correct for
286
+ # the plain C++ build. But when this extension is imported into a Python
287
+ # whose numpy/scipy already loaded their OWN OpenMP runtime -- e.g. any
288
+ # conda-forge interpreter, since conda-forge's numpy/scipy are built against
289
+ # conda-forge's own `llvm-openmp` package -- dyld ends up with two DIFFERENT
290
+ # libomp.dylib images resident in one process. Both are the same upstream
291
+ # LLVM OpenMP runtime (verified: identical compatibility version 5.0.0,
292
+ # identical __kmpc_* export set), so they are ABI-compatible, but the
293
+ # runtime's own duplicate-initialization guard treats a second distinct
294
+ # image as unsafe and aborts the process with "OMP: Error #15". The
295
+ # unsafe workaround is KMP_DUPLICATE_LIB_OK=TRUE; the real fix is to never
296
+ # load two copies in the first place.
297
+ #
298
+ # So: if the target Python's own environment ships a libomp.dylib distinct
299
+ # from the one scrna_matrix compiled against, repoint the BUILT extension's
300
+ # load command at that one instead, post-link. Only the .dylib reference
301
+ # changes -- compilation still uses Homebrew's headers/flags, which is safe
302
+ # because the ABI is the same runtime. This is scoped to scrna_matrix_py
303
+ # only: the core library and its C++ test suite are untouched and keep
304
+ # using Homebrew's libomp exactly as before.
305
+ if(APPLE AND _scrna_python AND PTO_LIBOMP_PREFIX)
306
+ execute_process(
307
+ COMMAND "${_scrna_python}" -c "import sys; print(sys.prefix)"
308
+ OUTPUT_VARIABLE _scrna_py_prefix
309
+ OUTPUT_STRIP_TRAILING_WHITESPACE
310
+ ERROR_QUIET)
311
+ set(_scrna_py_libomp "${_scrna_py_prefix}/lib/libomp.dylib")
312
+ set(_scrna_built_libomp "${PTO_LIBOMP_PREFIX}/lib/libomp.dylib")
313
+ if(_scrna_py_prefix AND EXISTS "${_scrna_py_libomp}"
314
+ AND NOT "${_scrna_py_libomp}" STREQUAL "${_scrna_built_libomp}")
315
+ find_program(_scrna_install_name_tool install_name_tool)
316
+ if(_scrna_install_name_tool)
317
+ add_custom_command(TARGET scrna_matrix_py POST_BUILD
318
+ COMMAND "${_scrna_install_name_tool}" -change
319
+ "${_scrna_built_libomp}" "${_scrna_py_libomp}"
320
+ "$<TARGET_FILE:scrna_matrix_py>"
321
+ COMMENT "scrna_matrix_py: repointing OpenMP runtime at ${_scrna_py_libomp} (the target Python's own copy) to avoid a duplicate-runtime abort")
322
+ message(STATUS
323
+ "scrna_matrix_py: will load OpenMP from ${_scrna_py_libomp} at import "
324
+ "time instead of ${_scrna_built_libomp}, matching the runtime "
325
+ "numpy/scipy already initialize in this interpreter.")
326
+ endif()
327
+ endif()
328
+ endif()
329
+
330
+ # Binding-level tests (adoption vs copy, buffer lifetime, GIL release).
331
+ # Skips itself if pytest/numpy/scipy are unavailable.
332
+ if(SCRNA_BUILD_TESTS AND _scrna_python)
333
+ add_test(NAME test_python_bindings
334
+ COMMAND ${_scrna_python} -m pytest
335
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_python_bindings.py -q)
336
+ set_tests_properties(test_python_bindings PROPERTIES
337
+ ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:scrna_matrix_py>")
338
+ endif()
339
+ else()
340
+ message(WARNING "pybind11 not found (pip install pybind11 or add as submodule); skipping Python module.")
341
+ endif()
342
+ endif()