Skip to content

Commit

Permalink
Merge pull request #28 from paulsengroup/refactor/switch-to-nanobind
Browse files Browse the repository at this point in the history
Switch to nanobind
  • Loading branch information
robomics authored Feb 13, 2024
2 parents d33c009 + 1cb6b15 commit c9d815b
Show file tree
Hide file tree
Showing 16 changed files with 323 additions and 218 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
fail-fast: false
matrix:
platform: [windows-latest, macos-latest, ubuntu-latest]
python-version: ["3.8", "3.12"]
python-version: ["3.9", "3.12"]

runs-on: ${{ matrix.platform }}

Expand Down
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ FetchContent_Declare(
URL_HASH "SHA256=c003924e92a9957a0cf43b85ae9ede29392be5227f427ca2fab9e420ea8217c9"
SYSTEM)

FetchContent_Declare(
nanobind
URL "${CMAKE_CURRENT_SOURCE_DIR}/external/nanobind-v1.8.0.tar.xz"
URL_HASH "SHA256=e70b23b82582aa6394bc8d8604c3736d2f1d8eeae15c309cd7cc37093aa4c406"
SYSTEM)

set(HICTK_ENABLE_TESTING OFF)
set(HICTK_BUILD_EXAMPLES OFF)
set(HICTK_BUILD_BENCHMARKS OFF)
set(HICTK_BUILD_PYTHON_BINDINGS OFF)
set(HICTK_WITH_EIGEN OFF)
set(HICTK_BUILD_TOOLS OFF)
set(HICTK_INSTALL OFF)
FetchContent_MakeAvailable(hictk)
FetchContent_MakeAvailable(hictk nanobind)

add_library(hictkpy_project_options INTERFACE)
target_compile_features(hictkpy_project_options INTERFACE "cxx_std_${CMAKE_CXX_STANDARD}")
Expand Down
1 change: 0 additions & 1 deletion conanfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ hdf5/1.14.2#1e12ccecd5ebc9b5191433862e196743
highfive/2.8.0#19e1a6e78d9329851aa9da409f07d29a
libdeflate/1.19#3ea74a4549efc14d4b1202dc4bfbf602
parallel-hashmap/1.3.11#1e67f4855a3f7cdeb977cc472113baf7
pybind11/2.11.1#e24cefefdb5561ba8d8bc34ab5ba1607
span-lite/0.10.3#1967d71abb32b314387c2ab9c558dd22
spdlog/1.12.0#0e390a2f5c3e96671d0857bc734e4731
xxhash/0.8.2#03fd1c9a839b3f9cdf5ea9742c312187
Expand Down
Binary file added external/nanobind-v1.8.0.tar.xz
Binary file not shown.
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ readme = "README.md"
authors = [
{name = "Roberto Rossini", email = "roberros@uio.no"}
]
requires-python = ">=3.8"
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Topic :: Scientific/Engineering :: Bio-Informatics",
Expand All @@ -27,7 +27,7 @@ classifiers = [

dependencies = [
"numpy",
"pandas!=2.2.0",
"pandas>=2.1.0,!=2.2.0",
"scipy",
]

Expand Down Expand Up @@ -66,7 +66,7 @@ filterwarnings = [
]

[tool.cibuildwheel]
skip = ["*musllinux*", "cp38-macosx_arm64"]
skip = ["*musllinux*"]
test-command = "python -m pytest {project}/test"
test-extras = ["test"]
test-skip = ["*universal2", "pp*"]
Expand All @@ -89,7 +89,7 @@ extend-select = [
extend-ignore = [
"E501", # Line too long
]
target-version = "py38"
target-version = "py39"

[tool.black]
line-length = 120
Expand Down
8 changes: 3 additions & 5 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
# SPDX-License-Identifier: MIT

find_package(
Python 3.8
Python 3.9
COMPONENTS Interpreter Development.Module
REQUIRED)

set(PYBIND11_NEWPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)

pybind11_add_module(
nanobind_add_module(
_hictkpy
NB_STATIC LTO
MODULE
hictkpy.cpp
hictkpy_file.cpp
Expand Down
121 changes: 60 additions & 61 deletions src/hictkpy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
//
// SPDX-License-Identifier: MIT

#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <nanobind/nanobind.h>
#include <nanobind/operators.h>
#include <nanobind/stl/string_view.h>
#include <nanobind/stl/vector.h>

#include "hictk/cooler.hpp"
#include "hictk/file.hpp"
Expand All @@ -15,16 +17,16 @@
#include "hictkpy/pixel_selector.hpp"
#include "hictkpy/singlecell_file.hpp"

namespace py = pybind11;
namespace nb = nanobind;
namespace hictkpy {

template <typename N>
static void declare_thin_pixel_class(pybind11::module_ &m, const std::string &suffix) {
static void declare_thin_pixel_class(nb::module_ &m, const std::string &suffix) {
const auto type_name = std::string{"ThinPixel"} + suffix;
py::class_<hictk::ThinPixel<N>>(m, type_name.c_str())
.def_property_readonly("bin1_id", [](const hictk::ThinPixel<N> &tp) { return tp.bin1_id; })
.def_property_readonly("bin2_id", [](const hictk::ThinPixel<N> &tp) { return tp.bin2_id; })
.def_property_readonly("count", [](const hictk::ThinPixel<N> &tp) { return tp.count; })
nb::class_<hictk::ThinPixel<N>>(m, type_name.c_str())
.def_prop_ro("bin1_id", [](const hictk::ThinPixel<N> &tp) { return tp.bin1_id; })
.def_prop_ro("bin2_id", [](const hictk::ThinPixel<N> &tp) { return tp.bin2_id; })
.def_prop_ro("count", [](const hictk::ThinPixel<N> &tp) { return tp.count; })
.def("__repr__",
[](const hictk::ThinPixel<N> &tp) {
return fmt::format(FMT_COMPILE("bin1_id={}; bin2_id={}; count={};"), tp.bin1_id,
Expand All @@ -36,26 +38,20 @@ static void declare_thin_pixel_class(pybind11::module_ &m, const std::string &su
}

template <typename N>
static void declare_pixel_class(pybind11::module_ &m, const std::string &suffix) {
static void declare_pixel_class(nb::module_ &m, const std::string &suffix) {
const auto type_name = std::string{"Pixel"} + suffix;
py::class_<hictk::Pixel<N>>(m, type_name.c_str())
.def_property_readonly("bin1_id", [](const hictk::Pixel<N> &p) { return p.coords.bin1.id(); })
.def_property_readonly("bin2_id", [](const hictk::Pixel<N> &p) { return p.coords.bin2.id(); })
.def_property_readonly("rel_bin1_id",
[](const hictk::Pixel<N> &p) { return p.coords.bin1.rel_id(); })
.def_property_readonly("rel_bin2_id",
[](const hictk::Pixel<N> &p) { return p.coords.bin2.rel_id(); })
.def_property_readonly("chrom1",
[](const hictk::Pixel<N> &p) { return p.coords.bin1.chrom().name(); })
.def_property_readonly("start1",
[](const hictk::Pixel<N> &p) { return p.coords.bin1.start(); })
.def_property_readonly("end1", [](const hictk::Pixel<N> &p) { return p.coords.bin1.end(); })
.def_property_readonly("chrom2",
[](const hictk::Pixel<N> &p) { return p.coords.bin2.chrom().name(); })
.def_property_readonly("start2",
[](const hictk::Pixel<N> &p) { return p.coords.bin2.start(); })
.def_property_readonly("end2", [](const hictk::Pixel<N> &p) { return p.coords.bin2.end(); })
.def_property_readonly("count", [](const hictk::Pixel<N> &p) { return p.count; })
nb::class_<hictk::Pixel<N>>(m, type_name.c_str())
.def_prop_ro("bin1_id", [](const hictk::Pixel<N> &p) { return p.coords.bin1.id(); })
.def_prop_ro("bin2_id", [](const hictk::Pixel<N> &p) { return p.coords.bin2.id(); })
.def_prop_ro("rel_bin1_id", [](const hictk::Pixel<N> &p) { return p.coords.bin1.rel_id(); })
.def_prop_ro("rel_bin2_id", [](const hictk::Pixel<N> &p) { return p.coords.bin2.rel_id(); })
.def_prop_ro("chrom1", [](const hictk::Pixel<N> &p) { return p.coords.bin1.chrom().name(); })
.def_prop_ro("start1", [](const hictk::Pixel<N> &p) { return p.coords.bin1.start(); })
.def_prop_ro("end1", [](const hictk::Pixel<N> &p) { return p.coords.bin1.end(); })
.def_prop_ro("chrom2", [](const hictk::Pixel<N> &p) { return p.coords.bin2.chrom().name(); })
.def_prop_ro("start2", [](const hictk::Pixel<N> &p) { return p.coords.bin2.start(); })
.def_prop_ro("end2", [](const hictk::Pixel<N> &p) { return p.coords.bin2.end(); })
.def_prop_ro("count", [](const hictk::Pixel<N> &p) { return p.count; })
.def("__repr__",
[](const hictk::Pixel<N> &p) {
return fmt::format(
Expand All @@ -70,24 +66,24 @@ static void declare_pixel_class(pybind11::module_ &m, const std::string &suffix)
});
}

static void declare_pixel_selector_class(pybind11::module_ &m) {
static void declare_pixel_selector_class(nb::module_ &m) {
auto sel =
py::class_<PixelSelector>(m, "PixelSelector")
.def(py::init<std::shared_ptr<const hictk::cooler::PixelSelector>, std::string_view,
nb::class_<PixelSelector>(m, "PixelSelector")
.def(nb::init<std::shared_ptr<const hictk::cooler::PixelSelector>, std::string_view,
bool>(),
py::arg("selector"), py::arg("type"), py::arg("join"))
.def(py::init<std::shared_ptr<const hictk::hic::PixelSelector>, std::string_view, bool>(),
py::arg("selector"), py::arg("type"), py::arg("join"))
.def(py::init<std::shared_ptr<const hictk::hic::PixelSelectorAll>, std::string_view,
nb::arg("selector"), nb::arg("type"), nb::arg("join"))
.def(nb::init<std::shared_ptr<const hictk::hic::PixelSelector>, std::string_view, bool>(),
nb::arg("selector"), nb::arg("type"), nb::arg("join"))
.def(nb::init<std::shared_ptr<const hictk::hic::PixelSelectorAll>, std::string_view,
bool>(),
py::arg("selector"), py::arg("type"), py::arg("join"));
nb::arg("selector"), nb::arg("type"), nb::arg("join"));

sel.def("__repr__", &PixelSelector::repr);

sel.def("coord1", &PixelSelector::get_coord1, "Get query coordinates for the first dimension.");
sel.def("coord2", &PixelSelector::get_coord2, "Get query coordinates for the second dimension.");

sel.def("__iter__", &PixelSelector::make_iterable, py::keep_alive<0, 1>());
sel.def("__iter__", &PixelSelector::make_iterable, nb::keep_alive<0, 1>());

sel.def("to_df", &PixelSelector::to_df, "Retrieve interactions as a pandas DataFrame.");
sel.def("to_numpy", &PixelSelector::to_numpy, "Retrieve interactions as a numpy 2D matrix.");
Expand All @@ -99,10 +95,10 @@ static void declare_pixel_selector_class(pybind11::module_ &m) {
"Get the total number of interactions for the current pixel selection.");
}

static void declare_file_class(pybind11::module_ &m) {
auto file = py::class_<hictk::File>(m, "File").def(
py::init(&file::ctor), py::arg("path"), py::arg("resolution"),
py::arg("matrix_type") = "observed", py::arg("matrix_unit") = "BP",
static void declare_file_class(nb::module_ &m) {
auto file = nb::class_<hictk::File>(m, "File").def(
"__init__", &file::ctor, nb::arg("path"), nb::arg("resolution"),
nb::arg("matrix_type") = "observed", nb::arg("matrix_unit") = "BP",
"Construct a file object to a .hic, .cool or .mcool file given the file path and "
"resolution.\n"
"Resolution is ignored when opening single-resolution Cooler files.");
Expand All @@ -115,7 +111,7 @@ static void declare_file_class(pybind11::module_ &m) {
file.def("is_hic", &hictk::File::is_hic, "Test whether file is in .hic format.");
file.def("is_cooler", &hictk::File::is_cooler, "Test whether file is in .cool format.");

file.def("chromosomes", &get_chromosomes_from_file<hictk::File>, py::arg("include_all") = false,
file.def("chromosomes", &get_chromosomes_from_file<hictk::File>, nb::arg("include_all") = false,
"Get chromosomes sizes as a dictionary mapping names to sizes.");
file.def("bins", &get_bins_from_file<hictk::File>, "Get bins as a pandas DataFrame.");

Expand All @@ -125,29 +121,29 @@ static void declare_file_class(pybind11::module_ &m) {

file.def("attributes", &file::attributes, "Get file attributes as a dictionary.");

file.def("fetch", &file::fetch, py::keep_alive<0, 1>(), py::arg("range1") = "",
py::arg("range2") = "", py::arg("normalization") = "NONE", py::arg("count_type") = "int",
py::arg("join") = false, py::arg("query_type") = "UCSC",
file.def("fetch", &file::fetch, nb::keep_alive<0, 1>(), nb::arg("range1") = "",
nb::arg("range2") = "", nb::arg("normalization") = "NONE", nb::arg("count_type") = "int",
nb::arg("join") = false, nb::arg("query_type") = "UCSC",
"Fetch interactions overlapping a region of interest.");

file.def("avail_normalizations", &file::avail_normalizations,
"Get the list of available normalizations.");
file.def("has_normalization", &hictk::File::has_normalization, py::arg("normalization"),
file.def("has_normalization", &hictk::File::has_normalization, nb::arg("normalization"),
"Check whether a given normalization is available.");
}

static void declare_multires_file_class(pybind11::module_ &m) {
static void declare_multires_file_class(nb::module_ &m) {
auto cooler = m.def_submodule("cooler");

auto mres_file = py::class_<hictk::cooler::MultiResFile>(cooler, "MultiResFile")
.def(py::init(&multires_file::ctor), py::arg("path"),
auto mres_file = nb::class_<hictk::cooler::MultiResFile>(cooler, "MultiResFile")
.def("__init__", &multires_file::ctor, nb::arg("path"),
"Open a multi-resolution Cooler file (.mcool).");

mres_file.def("__repr__", &multires_file::repr);

mres_file.def("path", &hictk::cooler::MultiResFile::path, "Get the file path.");
mres_file.def("chromosomes", &get_chromosomes_from_file<hictk::cooler::MultiResFile>,
py::arg("include_all") = false,
nb::arg("include_all") = false,
"Get chromosomes sizes as a dictionary mapping names to sizes.");
mres_file.def("attributes", &multires_file::get_attrs, "Get file attributes as a dictionary.");
mres_file.def("resolutions", &hictk::cooler::MultiResFile::resolutions,
Expand All @@ -156,19 +152,19 @@ static void declare_multires_file_class(pybind11::module_ &m) {
"Open the Cooler file corresponding to the resolution given as input.");
}

static void declare_singlecell_file_class(pybind11::module_ &m) {
static void declare_singlecell_file_class(nb::module_ &m) {
auto cooler = m.def_submodule("cooler");

auto scell_file = py::class_<hictk::cooler::SingleCellFile>(cooler, "SingleCellFile")
.def(py::init(&singlecell_file::ctor), py::arg("path"),
auto scell_file = nb::class_<hictk::cooler::SingleCellFile>(cooler, "SingleCellFile")
.def("__init__", &singlecell_file::ctor, nb::arg("path"),
"Open a single-cell Cooler file (.scool).");

scell_file.def("__repr__", &singlecell_file::repr);

scell_file.def("path", &hictk::cooler::SingleCellFile::path, "Get the file path.");
scell_file.def("bin_size", &hictk::cooler::SingleCellFile::bin_size, "Get the bin size in bp.");
scell_file.def("chromosomes", &get_chromosomes_from_file<hictk::cooler::SingleCellFile>,
py::arg("include_all") = false,
nb::arg("include_all") = false,
"Get chromosomes sizes as a dictionary mapping names to sizes.");
scell_file.def("bins", &get_bins_from_file<hictk::cooler::SingleCellFile>,
"Get bins as a pandas DataFrame.");
Expand All @@ -178,28 +174,31 @@ static void declare_singlecell_file_class(pybind11::module_ &m) {
"Open the Cooler file corresponding to the cell ID given as input.");
}

namespace py = pybind11;
using namespace pybind11::literals;
namespace nb = nanobind;
using namespace nb::literals;

NB_MODULE(_hictkpy, m) {
[[maybe_unused]] auto np = nb::module_::import_("numpy");
[[maybe_unused]] auto pd = nb::module_::import_("pandas");
[[maybe_unused]] auto ss = nb::module_::import_("scipy.sparse");

PYBIND11_MODULE(_hictkpy, m) {
[[maybe_unused]] auto np = py::module::import("numpy");
[[maybe_unused]] auto pd = py::module::import("pandas");
[[maybe_unused]] auto ss = py::module::import("scipy.sparse");
m.attr("__hictk_version__") = hictk::config::version::str();

m.doc() = "Blazing fast toolkit to work with .hic and .cool files.";

m.def("is_cooler", &file::is_cooler, py::arg("path"),
m.def("is_cooler", &file::is_cooler, nb::arg("path"),
"Test whether path points to a cooler file.");
m.def("is_hic", &file::is_hic, py::arg("path"), "Test whether path points to a .hic file.");
m.def("is_hic", &file::is_hic, nb::arg("path"), "Test whether path points to a .hic file.");

declare_thin_pixel_class<std::int32_t>(m, "Int");
declare_thin_pixel_class<double>(m, "FP");
declare_pixel_class<std::int32_t>(m, "Int");
declare_pixel_class<double>(m, "FP");

declare_pixel_selector_class(m);

declare_file_class(m);

declare_multires_file_class(m);
declare_singlecell_file_class(m);
}
Expand Down
Loading

0 comments on commit c9d815b

Please sign in to comment.