From cf21e30b28b25b0fc5f8922d05bbf34faa70536f Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 20 May 2026 12:22:30 +0200 Subject: [PATCH 1/2] feat: add logarithmic, error, sigmoid, softplus, min, max, power, and hypot functions with bindings --- include/hypergraph/hypergraph.h | 247 +++++++++++++++++++++ src/Module.cpp | 10 + tests/TestHyperGraph.py | 380 ++++++++++++++++++++++++++++++++ 3 files changed, 637 insertions(+) diff --git a/include/hypergraph/hypergraph.h b/include/hypergraph/hypergraph.h index 84cab91..bea133d 100644 --- a/include/hypergraph/hypergraph.h +++ b/include/hypergraph/hypergraph.h @@ -111,6 +111,36 @@ HYPERGRAPH_INLINE Variable atanh(const Variable& x); template HYPERGRAPH_INLINE Variable atan2(const Variable& y, const Variable& x); +template +HYPERGRAPH_INLINE Variable log2(const Variable& x); + +template +HYPERGRAPH_INLINE Variable log10(const Variable& x); + +template +HYPERGRAPH_INLINE Variable erf(const Variable& x); + +template +HYPERGRAPH_INLINE Variable erfc(const Variable& x); + +template +HYPERGRAPH_INLINE Variable sigmoid(const Variable& x); + +template +HYPERGRAPH_INLINE Variable softplus(const Variable& x); + +template +HYPERGRAPH_INLINE Variable min(const Variable& x, const Variable& y); + +template +HYPERGRAPH_INLINE Variable max(const Variable& x, const Variable& y); + +template +HYPERGRAPH_INLINE Variable pow(const Variable& x, const Variable& y); + +template +HYPERGRAPH_INLINE Variable hypot(const Variable& x, const Variable& y); + template HYPERGRAPH_INLINE index vertex_id(const U& item) { @@ -199,6 +229,13 @@ class Variable { .def("arcsinh", [](const Type& x) { return hypergraph::asinh(x); }) .def("arccosh", [](const Type& x) { return hypergraph::acosh(x); }) .def("arctanh", [](const Type& x) { return hypergraph::atanh(x); }) + .def("log2", [](const Type& x) { return hypergraph::log2(x); }) + .def("log10", [](const Type& x) { return hypergraph::log10(x); }) + .def("erf", [](const Type& x) { return hypergraph::erf(x); }) + .def("erfc", [](const Type& x) { return hypergraph::erfc(x); }) + .def("sigmoid", [](const Type& x) { return hypergraph::sigmoid(x); }) + .def("softplus", [](const Type& x) { return hypergraph::softplus(x); }) + .def("__pow__", [](const Type& x, const Type& y) { return hypergraph::pow(x, y); }) // operators .def(py::self == py::self) .def(py::self != py::self) @@ -942,8 +979,13 @@ using std::atan2; using std::atanh; using std::cos; using std::cosh; +using std::erf; +using std::erfc; using std::exp; +using std::hypot; using std::log; +using std::log2; +using std::log10; using std::pow; using std::sin; using std::sinh; @@ -1287,4 +1329,209 @@ HYPERGRAPH_INLINE Variable atan2(const Variable& y, const Variable& x) return result; } +template +HYPERGRAPH_INLINE Variable log2(const Variable& x) +{ + using std::log; + using std::log2; + +#ifdef HYPERGRAPH_EXCEPTIONS + if (x.value() <= 0.0) { + throw std::domain_error("log2: argument must be positive"); + } +#endif + + HyperGraph* graph = x.graph(); + + const auto log2_x = log2(x.value()); + const auto inv = 1.0 / x.value(); + const auto ln2 = log(2.0); + const auto inv_ln2 = 1.0 / ln2; + const Variable result = graph->new_tmp_variable(log2_x); + // d(log2(x))/dx = 1/(x·ln2), d²(log2(x))/dx² = -1/(x²·ln2) + graph->add_edge(result, x, inv * inv_ln2, -inv * inv * inv_ln2); + return result; +} + +template +HYPERGRAPH_INLINE Variable log10(const Variable& x) +{ + using std::log; + using std::log10; + +#ifdef HYPERGRAPH_EXCEPTIONS + if (x.value() <= 0.0) { + throw std::domain_error("log10: argument must be positive"); + } +#endif + + HyperGraph* graph = x.graph(); + + const auto log10_x = log10(x.value()); + const auto inv = 1.0 / x.value(); + const auto ln10 = log(10.0); + const auto inv_ln10 = 1.0 / ln10; + const Variable result = graph->new_tmp_variable(log10_x); + // d(log10(x))/dx = 1/(x·ln10), d²(log10(x))/dx² = -1/(x²·ln10) + graph->add_edge(result, x, inv * inv_ln10, -inv * inv * inv_ln10); + return result; +} + +template +HYPERGRAPH_INLINE Variable erf(const Variable& x) +{ + using std::erf; + using std::exp; + + HyperGraph* graph = x.graph(); + + const auto erf_x = erf(x.value()); + // d(erf(x))/dx = 2/sqrt(π) · exp(-x²) + // d²(erf(x))/dx² = -4x/sqrt(π) · exp(-x²) + const auto two_over_sqrt_pi = 2.0 / std::sqrt(M_PI); + const auto exp_neg_x2 = exp(-x.value() * x.value()); + const auto first_deriv = two_over_sqrt_pi * exp_neg_x2; + const auto second_deriv = -2.0 * x.value() * first_deriv; + const Variable result = graph->new_tmp_variable(erf_x); + graph->add_edge(result, x, first_deriv, second_deriv); + return result; +} + +template +HYPERGRAPH_INLINE Variable erfc(const Variable& x) +{ + using std::erfc; + using std::exp; + + HyperGraph* graph = x.graph(); + + const auto erfc_x = erfc(x.value()); + // erfc(x) = 1 - erf(x), so derivatives are negated + // d(erfc(x))/dx = -2/sqrt(π) · exp(-x²) + // d²(erfc(x))/dx² = 4x/sqrt(π) · exp(-x²) + const auto two_over_sqrt_pi = 2.0 / std::sqrt(M_PI); + const auto exp_neg_x2 = exp(-x.value() * x.value()); + const auto first_deriv = -two_over_sqrt_pi * exp_neg_x2; + const auto second_deriv = 2.0 * x.value() * two_over_sqrt_pi * exp_neg_x2; + const Variable result = graph->new_tmp_variable(erfc_x); + graph->add_edge(result, x, first_deriv, second_deriv); + return result; +} + +template +HYPERGRAPH_INLINE Variable sigmoid(const Variable& x) +{ + using std::exp; + + HyperGraph* graph = x.graph(); + + // sigmoid(x) = 1/(1+exp(-x)) + const auto exp_neg_x = exp(-x.value()); + const auto sig = 1.0 / (1.0 + exp_neg_x); + // d(sigmoid)/dx = sigmoid · (1 - sigmoid) + const auto first_deriv = sig * (1.0 - sig); + // d²(sigmoid)/dx² = sigmoid · (1 - sigmoid) · (1 - 2·sigmoid) + const auto second_deriv = first_deriv * (1.0 - 2.0 * sig); + const Variable result = graph->new_tmp_variable(sig); + graph->add_edge(result, x, first_deriv, second_deriv); + return result; +} + +template +HYPERGRAPH_INLINE Variable softplus(const Variable& x) +{ + using std::exp; + using std::log; + + HyperGraph* graph = x.graph(); + + // softplus(x) = log(1 + exp(x)) + // For numerical stability: if x >> 0, softplus(x) ≈ x + const auto exp_x = exp(x.value()); + const auto softplus_x = log(1.0 + exp_x); + // d(softplus)/dx = sigmoid(x) = exp(x)/(1+exp(x)) = 1/(1+exp(-x)) + const auto sig = exp_x / (1.0 + exp_x); + // d²(softplus)/dx² = sigmoid(x)·(1-sigmoid(x)) + const auto second_deriv = sig * (1.0 - sig); + const Variable result = graph->new_tmp_variable(softplus_x); + graph->add_edge(result, x, sig, second_deriv); + return result; +} + +template +HYPERGRAPH_INLINE Variable min(const Variable& x, const Variable& y) +{ + // Subgradient convention: at x == y, derivative w.r.t. x is 1, w.r.t. y is 0 + if (x.value() <= y.value()) { + HyperGraph* graph = x.graph(); + const Variable result = graph->new_tmp_variable(x.value()); + graph->add_edge(result, x, y, 1.0, 0.0, 0.0); + return result; + } else { + HyperGraph* graph = y.graph(); + const Variable result = graph->new_tmp_variable(y.value()); + graph->add_edge(result, x, y, 0.0, 1.0, 0.0); + return result; + } +} + +template +HYPERGRAPH_INLINE Variable max(const Variable& x, const Variable& y) +{ + // Subgradient convention: at x == y, derivative w.r.t. x is 1, w.r.t. y is 0 + if (x.value() >= y.value()) { + HyperGraph* graph = x.graph(); + const Variable result = graph->new_tmp_variable(x.value()); + graph->add_edge(result, x, y, 1.0, 0.0, 0.0); + return result; + } else { + HyperGraph* graph = y.graph(); + const Variable result = graph->new_tmp_variable(y.value()); + graph->add_edge(result, x, y, 0.0, 1.0, 0.0); + return result; + } +} + +template +HYPERGRAPH_INLINE Variable pow(const Variable& x, const Variable& y) +{ + using std::pow; + +#ifdef HYPERGRAPH_EXCEPTIONS + if (x.value() <= 0.0) { + throw std::domain_error("pow(Variable, Variable): base must be positive"); + } +#endif + + // x^y = exp(y·ln(x)) — compose from existing primitives for correct + // second-order derivatives (self and cross terms) + Variable result = hypergraph::exp(y * hypergraph::log(x)); + + // Correct the value to use std::pow for better numerical accuracy + result.set_value(pow(x.value(), y.value())); + + return result; +} + +template +HYPERGRAPH_INLINE Variable hypot(const Variable& x, const Variable& y) +{ + using std::hypot; + +#ifdef HYPERGRAPH_EXCEPTIONS + if (x.value() == 0.0 && y.value() == 0.0) { + throw std::domain_error("hypot: derivative undefined at (0, 0)"); + } +#endif + + // sqrt(x² + y²) — compose from existing primitives for correct + // second-order derivatives (self and cross terms) + Variable result = hypergraph::sqrt(hypergraph::square(x) + hypergraph::square(y)); + + // Correct the value to use std::hypot for better numerical accuracy + result.set_value(hypot(x.value(), y.value())); + + return result; +} + } // namespace hypergraph \ No newline at end of file diff --git a/src/Module.cpp b/src/Module.cpp index 930f2d8..6769009 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -22,4 +22,14 @@ PYBIND11_MODULE(hypergraph, m) { using Var = hypergraph::Variable; m.def("atan2", [](const Var& y, const Var& x) { return hypergraph::atan2(y, x); }, "y"_a, "x"_a); + m.def("log2", [](const Var& x) { return hypergraph::log2(x); }, "x"_a); + m.def("log10", [](const Var& x) { return hypergraph::log10(x); }, "x"_a); + m.def("erf", [](const Var& x) { return hypergraph::erf(x); }, "x"_a); + m.def("erfc", [](const Var& x) { return hypergraph::erfc(x); }, "x"_a); + m.def("sigmoid", [](const Var& x) { return hypergraph::sigmoid(x); }, "x"_a); + m.def("softplus", [](const Var& x) { return hypergraph::softplus(x); }, "x"_a); + m.def("min", [](const Var& x, const Var& y) { return hypergraph::min(x, y); }, "x"_a, "y"_a); + m.def("max", [](const Var& x, const Var& y) { return hypergraph::max(x, y); }, "x"_a, "y"_a); + m.def("pow", [](const Var& x, const Var& y) { return hypergraph::pow(x, y); }, "x"_a, "y"_a); + m.def("hypot", [](const Var& x, const Var& y) { return hypergraph::hypot(x, y); }, "x"_a, "y"_a); } diff --git a/tests/TestHyperGraph.py b/tests/TestHyperGraph.py index 853c820..9e86607 100644 --- a/tests/TestHyperGraph.py +++ b/tests/TestHyperGraph.py @@ -1,4 +1,5 @@ import unittest +import math import hypergraph as hg import numpy as np from numpy.testing import assert_equal, assert_array_equal, assert_almost_equal, assert_array_almost_equal @@ -773,6 +774,385 @@ def test_atan2_x_zero_raises(self): with self.assertRaises(ValueError): hg.atan2(y, x) + # log2, log10 + + def test_log2(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([4, 3]) + + result = a.log2() + assert_almost_equal(result.value, np.log2(4)) + + graph.compute(result) + g = graph.g() + h = graph.h() + x = 4.0 + ln2 = np.log(2) + assert_array_almost_equal(g, [1/(x * ln2), 0]) + assert_array_almost_equal(h, [[-1/(x**2 * ln2), 0], [0, 0]]) + + def test_log2_module(self): + graph = hg.HyperGraph() + + a = graph.new_variable(8) + + result = hg.log2(a) + assert_almost_equal(result.value, 3.0) + + def test_log2_nonpositive_raises(self): + graph = hg.HyperGraph() + + a = graph.new_variable(0) + with self.assertRaises(ValueError): + a.log2() + + b = graph.new_variable(-1) + with self.assertRaises(ValueError): + b.log2() + + def test_log10(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([100, 3]) + + result = a.log10() + assert_almost_equal(result.value, 2.0) + + graph.compute(result) + g = graph.g() + h = graph.h() + x = 100.0 + ln10 = np.log(10) + assert_array_almost_equal(g, [1/(x * ln10), 0]) + assert_array_almost_equal(h, [[-1/(x**2 * ln10), 0], [0, 0]]) + + def test_log10_module(self): + graph = hg.HyperGraph() + + a = graph.new_variable(1000) + + result = hg.log10(a) + assert_almost_equal(result.value, 3.0) + + def test_log10_nonpositive_raises(self): + graph = hg.HyperGraph() + + a = graph.new_variable(0) + with self.assertRaises(ValueError): + a.log10() + + b = graph.new_variable(-1) + with self.assertRaises(ValueError): + hg.log10(b) + + # erf, erfc + + def test_erf(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([1.5, 3]) + + result = a.erf() + assert_almost_equal(result.value, math.erf(1.5)) + + graph.compute(result) + g = graph.g() + h = graph.h() + x = 1.5 + two_over_sqrt_pi = 2.0 / np.sqrt(np.pi) + exp_neg_x2 = np.exp(-x**2) + g1 = two_over_sqrt_pi * exp_neg_x2 + h1 = -2.0 * x * g1 + assert_array_almost_equal(g, [g1, 0]) + assert_array_almost_equal(h, [[h1, 0], [0, 0]]) + + def test_erf_module(self): + graph = hg.HyperGraph() + + a = graph.new_variable(0) + + result = hg.erf(a) + assert_almost_equal(result.value, 0.0) + + def test_erfc(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([1.5, 3]) + + result = a.erfc() + assert_almost_equal(result.value, math.erfc(1.5)) + + graph.compute(result) + g = graph.g() + h = graph.h() + x = 1.5 + two_over_sqrt_pi = 2.0 / np.sqrt(np.pi) + exp_neg_x2 = np.exp(-x**2) + g1 = -two_over_sqrt_pi * exp_neg_x2 + h1 = 2.0 * x * two_over_sqrt_pi * exp_neg_x2 + assert_array_almost_equal(g, [g1, 0]) + assert_array_almost_equal(h, [[h1, 0], [0, 0]]) + + def test_erfc_module(self): + graph = hg.HyperGraph() + + a = graph.new_variable(0) + + result = hg.erfc(a) + assert_almost_equal(result.value, 1.0) + + def test_erf_plus_erfc_is_one(self): + """erf(x) + erfc(x) == 1 for all x""" + graph = hg.HyperGraph() + + a = graph.new_variable(2.0) + + result_erf = a.erf() + result_erfc = a.erfc() + assert_almost_equal(result_erf.value + result_erfc.value, 1.0) + + # sigmoid + + def test_sigmoid(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([2, 3]) + + result = a.sigmoid() + x = 2.0 + sig = 1.0 / (1.0 + np.exp(-x)) + assert_almost_equal(result.value, sig) + + graph.compute(result) + g = graph.g() + h = graph.h() + g1 = sig * (1 - sig) + h1 = g1 * (1 - 2 * sig) + assert_array_almost_equal(g, [g1, 0]) + assert_array_almost_equal(h, [[h1, 0], [0, 0]]) + + def test_sigmoid_zero(self): + graph = hg.HyperGraph() + + a = graph.new_variable(0) + + result = a.sigmoid() + assert_almost_equal(result.value, 0.5) + + graph.compute(result) + g = graph.g() + # sigmoid'(0) = 0.25 + assert_array_almost_equal(g, [0.25]) + + def test_sigmoid_module(self): + graph = hg.HyperGraph() + + a = graph.new_variable(0) + + result = hg.sigmoid(a) + assert_almost_equal(result.value, 0.5) + + # softplus + + def test_softplus(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([2, 3]) + + result = a.softplus() + x = 2.0 + sp = np.log(1 + np.exp(x)) + assert_almost_equal(result.value, sp) + + graph.compute(result) + g = graph.g() + h = graph.h() + sig = 1.0 / (1.0 + np.exp(-x)) + g1 = sig + h1 = sig * (1 - sig) + assert_array_almost_equal(g, [g1, 0]) + assert_array_almost_equal(h, [[h1, 0], [0, 0]]) + + def test_softplus_module(self): + graph = hg.HyperGraph() + + a = graph.new_variable(0) + + result = hg.softplus(a) + assert_almost_equal(result.value, np.log(2)) + + # min, max + + def test_min_x_less(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([3, 7]) + + result = hg.min(a, b) + assert_almost_equal(result.value, 3) + + graph.compute(result) + g = graph.g() + h = graph.h() + assert_array_almost_equal(g, [1, 0]) + assert_array_almost_equal(h, [[0, 0], [0, 0]]) + + def test_min_y_less(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([7, 3]) + + result = hg.min(a, b) + assert_almost_equal(result.value, 3) + + graph.compute(result) + g = graph.g() + h = graph.h() + assert_array_almost_equal(g, [0, 1]) + assert_array_almost_equal(h, [[0, 0], [0, 0]]) + + def test_min_equal(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([5, 5]) + + result = hg.min(a, b) + assert_almost_equal(result.value, 5) + + graph.compute(result) + g = graph.g() + # Subgradient convention: x gets the derivative when equal + assert_array_almost_equal(g, [1, 0]) + + def test_max_x_greater(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([7, 3]) + + result = hg.max(a, b) + assert_almost_equal(result.value, 7) + + graph.compute(result) + g = graph.g() + h = graph.h() + assert_array_almost_equal(g, [1, 0]) + assert_array_almost_equal(h, [[0, 0], [0, 0]]) + + def test_max_y_greater(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([3, 7]) + + result = hg.max(a, b) + assert_almost_equal(result.value, 7) + + graph.compute(result) + g = graph.g() + h = graph.h() + assert_array_almost_equal(g, [0, 1]) + assert_array_almost_equal(h, [[0, 0], [0, 0]]) + + def test_max_equal(self): + graph = hg.HyperGraph() + + a, b = graph.new_variables([5, 5]) + + result = hg.max(a, b) + assert_almost_equal(result.value, 5) + + graph.compute(result) + g = graph.g() + # Subgradient convention: x gets the derivative when equal + assert_array_almost_equal(g, [1, 0]) + + # pow(Variable, Variable) + + def test_pow_variable_variable(self): + graph = hg.HyperGraph() + + x, y = graph.new_variables([2, 3]) + + result = hg.pow(x, y) + assert_almost_equal(result.value, 8) + + graph.compute(result) + g = graph.g() + h = graph.h(full=True) + xv, yv = 2.0, 3.0 + # ∂f/∂x = y·x^(y-1) = 3·4 = 12 + # ∂f/∂y = x^y·ln(x) = 8·ln(2) + assert_array_almost_equal(g, [yv * xv**(yv - 1), xv**yv * np.log(xv)]) + # ∂²f/∂x² = y(y-1)·x^(y-2) = 3·2·1 = 6 + # ∂²f/∂y² = x^y·(ln(x))² = 8·(ln2)² + # ∂²f/∂x∂y = x^(y-1)·(1 + y·ln(x)) = 4·(1 + 3·ln2) + h_xx = yv * (yv - 1) * xv**(yv - 2) + h_yy = xv**yv * np.log(xv)**2 + h_xy = xv**(yv - 1) * (1 + yv * np.log(xv)) + assert_array_almost_equal(h, [[h_xx, h_xy], [h_xy, h_yy]]) + + def test_pow_variable_variable_via_operator(self): + """Test x ** y where y is a Variable, not a double""" + graph = hg.HyperGraph() + + x, y = graph.new_variables([3, 2]) + + result = x ** y + assert_almost_equal(result.value, 9) + + def test_pow_variable_variable_nonpositive_raises(self): + graph = hg.HyperGraph() + + x, y = graph.new_variables([0, 2]) + with self.assertRaises(ValueError): + hg.pow(x, y) + + x2, y2 = graph.new_variables([-1, 2]) + with self.assertRaises(ValueError): + hg.pow(x2, y2) + + # hypot + + def test_hypot(self): + graph = hg.HyperGraph() + + x, y = graph.new_variables([3, 4]) + + result = hg.hypot(x, y) + assert_almost_equal(result.value, 5) + + graph.compute(result) + g = graph.g() + h = graph.h(full=True) + xv, yv = 3.0, 4.0 + hv = 5.0 + # ∂f/∂x = x/h = 3/5, ∂f/∂y = y/h = 4/5 + assert_array_almost_equal(g, [xv/hv, yv/hv]) + # ∂²f/∂x² = y²/h³ = 16/125 + # ∂²f/∂y² = x²/h³ = 9/125 + # ∂²f/∂x∂y = -xy/h³ = -12/125 + h_xx = yv**2 / hv**3 + h_yy = xv**2 / hv**3 + h_xy = -xv * yv / hv**3 + assert_array_almost_equal(h, [[h_xx, h_xy], [h_xy, h_yy]]) + + def test_hypot_origin_raises(self): + graph = hg.HyperGraph() + + x, y = graph.new_variables([0, 0]) + + with self.assertRaises(ValueError): + hg.hypot(x, y) + + def test_hypot_pythagorean(self): + """Verify value for a well-known Pythagorean triple""" + graph = hg.HyperGraph() + + x, y = graph.new_variables([5, 12]) + + result = hg.hypot(x, y) + assert_almost_equal(result.value, 13) + if __name__ == '__main__': unittest.main() From 94e74ee0b0c8022cec79922547365a62228c4c83 Mon Sep 17 00:00:00 2001 From: Thomas Oberbichler Date: Wed, 20 May 2026 14:19:10 +0200 Subject: [PATCH 2/2] Migrate from pybind11 to nanobind for Python bindings (#22) * feat: migrate from pybind11 to nanobind for Python bindings * fix: define _USE_MATH_DEFINES before cmath for MSVC M_PI support --- CMakeLists.txt | 5 +- include/hypergraph/hypergraph.h | 100 ++++++++++++++++---------------- pyproject.toml | 2 +- src/Module.cpp | 16 ++--- 4 files changed, 63 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e77040..9a3e487 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) -find_package(pybind11 CONFIG REQUIRED) +find_package(Python 3.12 COMPONENTS Interpreter Development.Module REQUIRED) +find_package(nanobind CONFIG REQUIRED) FetchContent_Declare( eigen @@ -26,7 +27,7 @@ find_package(BLAS QUIET) # --- Target --- -pybind11_add_module(hypergraph src/Module.cpp) +nanobind_add_module(hypergraph src/Module.cpp) target_include_directories(hypergraph PRIVATE "${CMAKE_SOURCE_DIR}/include" diff --git a/include/hypergraph/hypergraph.h b/include/hypergraph/hypergraph.h index bea133d..e3422a1 100644 --- a/include/hypergraph/hypergraph.h +++ b/include/hypergraph/hypergraph.h @@ -9,12 +9,14 @@ #pragma once +#define _USE_MATH_DEFINES +#include + #include #include #include -#include #include #include #include @@ -200,16 +202,16 @@ class Variable { template static void register_python(TModule& m) { - using namespace pybind11::literals; - namespace py = pybind11; + using namespace nanobind::literals; + namespace nb = nanobind; const std::string name = "Variable"; - py::class_(m, name.c_str()) + nb::class_(m, name.c_str()) // properties - .def_property_readonly("value", &Type::value) + .def_prop_ro("value", &Type::value) // read-only properties - .def_property_readonly("_id", &Type::id) + .def_prop_ro("_id", &Type::id) // methods .def("__abs__", [](const Type& x) { return hypergraph::abs(x); }) .def("__pow__", [](const Type& x, const double a) { return hypergraph::pow(x, a); }) @@ -237,41 +239,41 @@ class Variable { .def("softplus", [](const Type& x) { return hypergraph::softplus(x); }) .def("__pow__", [](const Type& x, const Type& y) { return hypergraph::pow(x, y); }) // operators - .def(py::self == py::self) - .def(py::self != py::self) - .def(py::self < py::self) - .def(py::self > py::self) - .def(py::self <= py::self) - .def(py::self >= py::self) - .def(py::self == double()) - .def(py::self != double()) - .def(py::self < double()) - .def(py::self > double()) - .def(py::self <= double()) - .def(py::self >= double()) - .def(double() == py::self) - .def(double() != py::self) - .def(double() < py::self) - .def(double() > py::self) - .def(double() <= py::self) - .def(double() >= py::self) - .def(-py::self) - .def(py::self + py::self) - .def(py::self + double()) - .def(double() + py::self) - .def(py::self += py::self) - .def(py::self - py::self) - .def(py::self - double()) - .def(double() - py::self) - .def(py::self -= py::self) - .def(py::self * py::self) - .def(py::self * double()) - .def(double() * py::self) - .def(py::self *= py::self) - .def(py::self / py::self) - .def(py::self / double()) - .def(double() / py::self) - .def(py::self /= py::self); + .def(nb::self == nb::self) + .def(nb::self != nb::self) + .def(nb::self < nb::self) + .def(nb::self > nb::self) + .def(nb::self <= nb::self) + .def(nb::self >= nb::self) + .def(nb::self == double()) + .def(nb::self != double()) + .def(nb::self < double()) + .def(nb::self > double()) + .def(nb::self <= double()) + .def(nb::self >= double()) + .def(double() == nb::self) + .def(double() != nb::self) + .def(double() < nb::self) + .def(double() > nb::self) + .def(double() <= nb::self) + .def(double() >= nb::self) + .def(-nb::self) + .def(nb::self + nb::self) + .def(nb::self + double()) + .def(double() + nb::self) + .def(nb::self += nb::self) + .def(nb::self - nb::self) + .def(nb::self - double()) + .def(double() - nb::self) + .def(nb::self -= nb::self) + .def(nb::self * nb::self) + .def(nb::self * double()) + .def(double() * nb::self) + .def(nb::self *= nb::self) + .def(nb::self / nb::self) + .def(nb::self / double()) + .def(double() / nb::self) + .def(nb::self /= nb::self); } }; @@ -656,22 +658,22 @@ class HyperGraph { template static void register_python(TModule& m) { - using namespace pybind11::literals; - namespace py = pybind11; + using namespace nanobind::literals; + namespace nb = nanobind; const std::string name = "HyperGraph"; - py::class_(m, name.c_str()) + nb::class_(m, name.c_str()) // constructors - .def(py::init<>()) + .def(nb::init<>()) // methods .def("new_variable", &Type::new_variable, "value"_a) .def("new_variables", &Type::new_variables, "values"_a) .def("compute", &Type::compute, "expression"_a) - .def("g", py::overload_cast<>(&Type::g, py::const_)) - .def("g", py::overload_cast>(&Type::g, py::const_), "out"_a) - .def("h", py::overload_cast(&Type::h, py::const_), "full"_a = false) - .def("h", py::overload_cast, const bool>(&Type::h, py::const_), "out"_a, "full"_a = false); + .def("g", nb::overload_cast<>(&Type::g, nb::const_)) + .def("g", nb::overload_cast>(&Type::g, nb::const_), "out"_a) + .def("h", nb::overload_cast(&Type::h, nb::const_), "full"_a = false) + .def("h", nb::overload_cast, const bool>(&Type::h, nb::const_), "out"_a, "full"_a = false); } }; diff --git a/pyproject.toml b/pyproject.toml index 6e079f5..a3eab33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["scikit-build-core>=0.10", "pybind11>=2.13"] +requires = ["scikit-build-core>=0.12.2", "nanobind>=2.12.0"] build-backend = "scikit_build_core.build" [project] diff --git a/src/Module.cpp b/src/Module.cpp index 6769009..914204f 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -1,12 +1,12 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include -PYBIND11_MODULE(hypergraph, m) { +NB_MODULE(hypergraph, m) { m.doc() = "HyperJet by Thomas Oberbichler"; m.attr("__author__") = "Thomas Oberbichler"; m.attr("__copyright__") = "Copyright (c) 2019, Thomas Oberbichler"; @@ -14,8 +14,8 @@ PYBIND11_MODULE(hypergraph, m) { m.attr("__email__") = "thomas.oberbichler@gmail.com"; m.attr("__status__") = "Development"; - namespace py = pybind11; - using namespace pybind11::literals; + namespace nb = nanobind; + using namespace nanobind::literals; hypergraph::HyperGraph::register_python(m); hypergraph::Variable::register_python(m);