Skip to content

Commit

Permalink
C++ formatting and performance improvements (#113)
Browse files Browse the repository at this point in the history
* C++ format and performance gains
  • Loading branch information
eloyfelix authored Dec 19, 2024
1 parent 97904ae commit a9bc94e
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 90 deletions.
2 changes: 1 addition & 1 deletion FPSim2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
except Exception as e:
pass

__version__ = "0.5.2"
__version__ = "0.5.3"
6 changes: 4 additions & 2 deletions FPSim2/src/include/popcnt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

#include <nmmintrin.h>

static inline uint64_t popcntll(const uint64_t X) {
static inline uint64_t popcntll(const uint64_t X)
{
return _mm_popcnt_u64(X);
}

#else // unix (linux, osx) intel / arm

static inline uint64_t popcntll(const uint64_t X) {
static inline uint64_t popcntll(const uint64_t X)
{
return __builtin_popcountll(X);
}

Expand Down
3 changes: 2 additions & 1 deletion FPSim2/src/include/result.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

#include <cstdint>

struct Result {
struct Result
{
uint32_t idx;
uint32_t mol_id;
float coeff;
Expand Down
55 changes: 33 additions & 22 deletions FPSim2/src/include/sim.hpp
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
#pragma once

#include "result.hpp"
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>

namespace py = pybind11;

inline uint32_t SubstructCoeff(const uint32_t &rel_co_popcnt,
const uint32_t &common_popcnt);

inline float TanimotoCoeff(const uint32_t &common_popcnt,
const uint32_t &qcount,
const uint32_t &ocount);

inline float TverskyCoeff(const uint32_t &common_popcnt,
const uint32_t &rel_co_popcnt,
const uint32_t &rel_co_popcnt2,
const float &a, const float &b);
inline uint32_t SubstructCoeff(uint32_t rel_co_popcnt,
uint32_t common_popcnt) noexcept
{
uint32_t sum = rel_co_popcnt + common_popcnt;
return sum ? common_popcnt / sum : 0;
}

inline float TanimotoCoeff(uint32_t common_popcnt,
uint32_t qcount,
uint32_t ocount) noexcept
{
return static_cast<float>(common_popcnt) / (qcount + ocount - common_popcnt);
}

inline float TverskyCoeff(uint32_t common_popcnt,
uint32_t rel_co_popcnt,
uint32_t rel_co_popcnt2,
float a,
float b) noexcept
{
float denominator = common_popcnt + a * rel_co_popcnt + b * rel_co_popcnt2;
return denominator > 0.0f ? common_popcnt / denominator : 0.0f;
}

py::array_t<uint32_t> SubstructureScreenout(const py::array_t<uint64_t> py_query,
const py::array_t<uint64_t> py_db,
const uint32_t start,
const uint32_t end);
uint32_t start,
uint32_t end);

py::array_t<Result> TanimotoSearch(const py::array_t<uint64_t> py_query,
const py::array_t<uint64_t> py_db,
const float threshold,
const uint32_t start,
const uint32_t end);
float threshold,
uint32_t start,
uint32_t end);

py::array_t<Result> TverskySearch(const py::array_t<uint64_t> py_query,
const py::array_t<uint64_t> py_db,
const float threshold,
const float a,
const float b,
const uint32_t start,
const uint32_t end);
float threshold,
float a,
float b,
uint32_t start,
uint32_t end);
15 changes: 8 additions & 7 deletions FPSim2/src/include/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@

namespace py = pybind11;

namespace utils {
namespace utils
{

uint64_t PyPopcount(const py::array_t<uint64_t> py_query);

py::list BitStrToIntList(const std::string &bit_string);

bool cmp(const Result &l, const Result &r);

void SortResults(py::array_t<Result> py_res);

// zero-copy C++ vector to NumPy array
template<typename T>
inline py::array_t<T> Vector2NumPy(std::vector<T> *vec) {
template <typename T>
inline py::array_t<T> Vector2NumPy(std::vector<T> *vec)
{
// memory freed when the NumPy object is destroyed
auto free_when_done = py::capsule(vec, [](void* ptr) {
delete reinterpret_cast<std::vector<T> *>(ptr);
});
auto free_when_done = py::capsule(vec, [](void *ptr)
{ delete reinterpret_cast<std::vector<T> *>(ptr); });
return py::array_t<T>(vec->size(), vec->data(), free_when_done);
}
}
73 changes: 30 additions & 43 deletions FPSim2/src/sim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,15 @@
#include "result.hpp"
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <stdexcept>
#include <vector>

namespace py = pybind11;

inline float TanimotoCoeff(const uint32_t &common_popcnt,
const uint32_t &qcount,
const uint32_t &ocount) {
return (float)common_popcnt / (qcount + ocount - common_popcnt);
}

inline float TverskyCoeff(const uint32_t &common_popcnt,
const uint32_t &rel_co_popcnt,
const uint32_t &rel_co_popcnt2,
const float &a, const float &b) {
float coeff = 0.0;
coeff = common_popcnt + a * rel_co_popcnt + b * rel_co_popcnt2;
if (coeff != 0.0)
coeff = common_popcnt / coeff;
return coeff;
}

inline uint32_t SubstructCoeff(const uint32_t &rel_co_popcnt,
const uint32_t &common_popcnt) {
uint32_t coeff = 0;
coeff = rel_co_popcnt + common_popcnt;
if (coeff != 0)
coeff = common_popcnt / coeff;
return coeff;
}

py::array_t<uint32_t> SubstructureScreenout(const py::array_t<uint64_t> py_query,
const py::array_t<uint64_t> py_db,
const uint32_t start,
const uint32_t end) {
const uint32_t end)
{

// direct access to np arrays without checks
const auto query = py_query.unchecked<1>();
Expand All @@ -53,9 +28,11 @@ py::array_t<uint32_t> SubstructureScreenout(const py::array_t<uint64_t> py_query
uint32_t coeff;
uint64_t common_popcnt = 0;
uint64_t rel_co_popcnt = 0;
for (auto i = start; i < end; i++, dbptr += fp_shape,
common_popcnt = 0, rel_co_popcnt = 0) {
for (size_t j = 1; j < popcnt_idx; j++) {
for (auto i = start; i < end; i++, dbptr += fp_shape,
common_popcnt = 0, rel_co_popcnt = 0)
{
for (size_t j = 1; j < popcnt_idx; j++)
{
common_popcnt += popcntll(qptr[j] & dbptr[j]);
rel_co_popcnt += popcntll(qptr[j] & ~dbptr[j]);
}
Expand All @@ -74,8 +51,8 @@ py::array_t<Result> TanimotoSearch(const py::array_t<uint64_t> py_query,
const py::array_t<uint64_t> py_db,
const float threshold,
const uint32_t start,
const uint32_t end) {

const uint32_t end)
{
// direct access to np arrays without checks
const auto query = py_query.unchecked<1>();
const auto *qptr = (uint64_t *)query.data(0);
Expand All @@ -86,20 +63,27 @@ py::array_t<Result> TanimotoSearch(const py::array_t<uint64_t> py_query,
const auto popcnt_idx = fp_shape - 1;

auto results = new std::vector<Result>();
results->reserve((end - start) / 8);

float coeff;
uint64_t common_popcnt = 0;
for (auto i = start; i < end; i++, dbptr += fp_shape, common_popcnt = 0) {
for (auto j = 1; j < popcnt_idx; j++)
const uint64_t qcount = qptr[popcnt_idx];

for (auto i = start; i < end; i++, dbptr += fp_shape)
{
common_popcnt = 0;
for (size_t j = 1; j < popcnt_idx; j++)
{
common_popcnt += popcntll(qptr[j] & dbptr[j]);
coeff = TanimotoCoeff(common_popcnt, qptr[popcnt_idx], dbptr[popcnt_idx]);
}

const float coeff = TanimotoCoeff(common_popcnt, qcount, dbptr[popcnt_idx]);
if (coeff >= threshold)
results->push_back({i, (uint32_t)dbptr[0], coeff});
{
results->push_back({i, static_cast<uint32_t>(dbptr[0]), coeff});
}
}
std::sort(results->begin(), results->end(), utils::cmp);

// acquire the GIL before calling Python code
py::gil_scoped_acquire acquire;
return utils::Vector2NumPy<Result>(results);
}
Expand All @@ -110,7 +94,8 @@ py::array_t<Result> TverskySearch(const py::array_t<uint64_t> py_query,
const float a,
const float b,
const uint32_t start,
const uint32_t end) {
const uint32_t end)
{

// direct access to np arrays without checks
const auto query = py_query.unchecked<1>();
Expand All @@ -128,8 +113,10 @@ py::array_t<Result> TverskySearch(const py::array_t<uint64_t> py_query,
uint64_t rel_co_popcnt = 0;
uint64_t rel_co_popcnt2 = 0;
for (auto i = start; i < end; i++, dbptr += fp_shape,
common_popcnt = 0, rel_co_popcnt = 0, rel_co_popcnt2 = 0) {
for (auto j = 1; j < popcnt_idx; j++) {
common_popcnt = 0, rel_co_popcnt = 0, rel_co_popcnt2 = 0)
{
for (auto j = 1; j < popcnt_idx; j++)
{
// popcnts of both relative complements and intersection
common_popcnt += popcntll(qptr[j] & dbptr[j]);
rel_co_popcnt += popcntll(qptr[j] & ~dbptr[j]);
Expand All @@ -144,4 +131,4 @@ py::array_t<Result> TverskySearch(const py::array_t<uint64_t> py_query,
// acquire the GIL before calling Python code
py::gil_scoped_acquire acquire;
return utils::Vector2NumPy<Result>(results);
}
}
21 changes: 14 additions & 7 deletions FPSim2/src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,41 @@ typedef SSIZE_T ssize_t;

namespace py = pybind11;

namespace utils {
namespace utils
{

uint64_t PyPopcount(const py::array_t<uint64_t> py_query) {
uint64_t PyPopcount(const py::array_t<uint64_t> py_query)
{
const auto query = py_query.unchecked<1>();
uint64_t qcount = 0;
for (ssize_t i = 0; i < query.shape(0); i++)
qcount += popcntll(query(i));
return qcount;
}

py::list BitStrToIntList(const std::string &bit_string) {
py::list BitStrToIntList(const std::string &bit_string)
{
py::list efp;
size_t len = bit_string.length();
for (size_t i = 0; i < len; i += 64) {
for (size_t i = 0; i < len; i += 64)
{
uint64_t value = 0;
for (size_t j = 0; j < 64 && (i + j) < len; ++j) {
for (size_t j = 0; j < 64 && (i + j) < len; ++j)
{
value = (value << 1) | (bit_string[i + j] - '0');
}
efp.append(value);
}
return efp;
}

bool cmp(const Result &l, const Result &r) {
bool cmp(const Result &l, const Result &r)
{
return l.coeff > r.coeff;
}

void SortResults(py::array_t<Result> py_res) {
void SortResults(py::array_t<Result> py_res)
{
auto res = py_res.unchecked<1>();
Result *ptr = (Result *)res.data(0);
std::sort(&ptr[0], &ptr[res.shape(0)], cmp);
Expand Down
15 changes: 8 additions & 7 deletions FPSim2/src/wraps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

namespace py = pybind11;

PYBIND11_MODULE(FPSim2lib, m) {
PYBIND11_MODULE(FPSim2lib, m)
{
m.doc() = R"pbdoc(
FPSim2lib
---------
Expand All @@ -23,21 +24,21 @@ PYBIND11_MODULE(FPSim2lib, m) {
PYBIND11_NUMPY_DTYPE(Result, idx, mol_id, coeff);

m.def("SubstructureScreenout", &SubstructureScreenout,
py::call_guard<py::gil_scoped_release>(), R"pbdoc(
py::call_guard<py::gil_scoped_release>(), R"pbdoc(
Substructure search
Runs a Tversky (a=1, b=0) substructure screenout.
)pbdoc");

m.def("TanimotoSearch", &TanimotoSearch,
py::call_guard<py::gil_scoped_release>(), R"pbdoc(
py::call_guard<py::gil_scoped_release>(), R"pbdoc(
Tanimoto search
Runs a Tanimoto similarity search.
)pbdoc");

m.def("TverskySearch", &TverskySearch,
py::call_guard<py::gil_scoped_release>(), R"pbdoc(
py::call_guard<py::gil_scoped_release>(), R"pbdoc(
Tversky search
Runs a Tversky similarity search.
Expand All @@ -46,21 +47,21 @@ PYBIND11_MODULE(FPSim2lib, m) {
auto mutils = m.def_submodule("utils");

mutils.def("PyPopcount", &utils::PyPopcount, py::call_guard<py::gil_scoped_release>(),
R"pbdoc(
R"pbdoc(
Calc popcount
Calcs the popcount of a NumPy int array.
)pbdoc");

mutils.def("BitStrToIntList", &utils::BitStrToIntList,
R"pbdoc(
R"pbdoc(
Bitstring to Python int list
Converts RDKit FP bitstring into a Python int list.
)pbdoc");

mutils.def("SortResults", &utils::SortResults, py::call_guard<py::gil_scoped_release>(),
R"pbdoc(
R"pbdoc(
Sort results
Sort, inplace, the results NumPy array.
Expand Down

0 comments on commit a9bc94e

Please sign in to comment.