Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to nanobind #28

Merged
merged 4 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading