From 1277fcde5b703de3ba7b4efef874c52cec98c15b Mon Sep 17 00:00:00 2001 From: Ben Ahlbrand Date: Thu, 23 Oct 2025 23:52:28 -0400 Subject: [PATCH 1/6] Add edges_to_path function with Python binding add edges_to_path function and its binding --- src/edges_to_path.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/edges_to_path.cpp diff --git a/src/edges_to_path.cpp b/src/edges_to_path.cpp new file mode 100644 index 00000000..9aac8872 --- /dev/null +++ b/src/edges_to_path.cpp @@ -0,0 +1,70 @@ +#include "default_types.h" +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace pyigl +{ + // Wrapper for the harmonic function + auto harmonic( + const nb::DRef &V, + const nb::DRef &F, + const nb::DRef &b, + const nb::DRef &bc, + const int k) + { + Eigen::MatrixXN W; + if(!igl::harmonic(V, F, b, bc, k, W)) + { + throw std::runtime_error("Failed to compute harmonic map"); + } + #include "default_types.h" + #include + #include + #include + #include + + namespace nb = nanobind; + using namespace nb::literals; + + namespace pyigl + { + // Wrapper for igl::edges_to_path + auto edges_to_path(const nb::DRef &E) + { + Eigen::VectorXI I, J, K; + igl::edges_to_path(E, I, J, K); + return std::make_tuple(I, J, K); + } + } + +// Bind the wrapper to the Python module +void bind_edges_to_path(nb::module_ &m) +{ + m.def( + "edges_to_path", + &pyigl::edges_to_path, + "E"_a, +R"(Given a set of undirected, unique edges such that all form a +single connected component with exactly 0 or 2 nodes with valence = 1, +determine a path visiting all nodes. + +Parameters +---------- +E : (#E, 2) int array + Undirected edges + +Returns +------- +I : (#E+1,) int array + Nodes in order tracing the chain (loop). If the output is a loop then I[0] == I[-1] +J : (#I-1,) int array + Indices into E of edges tracing I +K : (#I-1,) int array in {0,1} + Column indices so that I[i] == E[J[i], K[i]] for i < len(I)-1)" + ); +} From daf9d05f4e655713ad64411a73298dfa32a023bf Mon Sep 17 00:00:00 2001 From: Ben Ahlbrand Date: Thu, 23 Oct 2025 23:54:28 -0400 Subject: [PATCH 2/6] Refactor edges_to_path wrapper and remove unused code --- src/edges_to_path.cpp | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/src/edges_to_path.cpp b/src/edges_to_path.cpp index 9aac8872..ec0500cd 100644 --- a/src/edges_to_path.cpp +++ b/src/edges_to_path.cpp @@ -2,45 +2,21 @@ #include #include #include -#include +#include namespace nb = nanobind; using namespace nb::literals; namespace pyigl { - // Wrapper for the harmonic function - auto harmonic( - const nb::DRef &V, - const nb::DRef &F, - const nb::DRef &b, - const nb::DRef &bc, - const int k) - { - Eigen::MatrixXN W; - if(!igl::harmonic(V, F, b, bc, k, W)) + // Wrapper for igl::edges_to_path + auto edges_to_path(const nb::DRef &E) { - throw std::runtime_error("Failed to compute harmonic map"); - } - #include "default_types.h" - #include - #include - #include - #include - - namespace nb = nanobind; - using namespace nb::literals; - - namespace pyigl - { - // Wrapper for igl::edges_to_path - auto edges_to_path(const nb::DRef &E) - { - Eigen::VectorXI I, J, K; - igl::edges_to_path(E, I, J, K); - return std::make_tuple(I, J, K); - } + Eigen::VectorXI I, J, K; + igl::edges_to_path(E, I, J, K); + return std::make_tuple(I, J, K); } +} // Bind the wrapper to the Python module void bind_edges_to_path(nb::module_ &m) From 81f9aa44e71f58a314e0ecea8dfc1393befda78e Mon Sep 17 00:00:00 2001 From: Ben Ahlbrand Date: Thu, 23 Oct 2025 23:54:48 -0400 Subject: [PATCH 3/6] whitespace --- src/edges_to_path.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/edges_to_path.cpp b/src/edges_to_path.cpp index ec0500cd..8947b45e 100644 --- a/src/edges_to_path.cpp +++ b/src/edges_to_path.cpp @@ -12,9 +12,9 @@ namespace pyigl // Wrapper for igl::edges_to_path auto edges_to_path(const nb::DRef &E) { - Eigen::VectorXI I, J, K; - igl::edges_to_path(E, I, J, K); - return std::make_tuple(I, J, K); + Eigen::VectorXI I, J, K; + igl::edges_to_path(E, I, J, K); + return std::make_tuple(I, J, K); } } From 707f488b49bf2f6748e78184fbbe929c63cdf28f Mon Sep 17 00:00:00 2001 From: Ben Ahlbrand Date: Fri, 24 Oct 2025 00:01:17 -0400 Subject: [PATCH 4/6] Refactor edges_to_path for int type compatibility Updated edges_to_path function to ensure input is 32-bit int for compatibility with libigl's implementation. --- src/edges_to_path.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/edges_to_path.cpp b/src/edges_to_path.cpp index 8947b45e..e6819c7e 100644 --- a/src/edges_to_path.cpp +++ b/src/edges_to_path.cpp @@ -12,8 +12,18 @@ namespace pyigl // Wrapper for igl::edges_to_path auto edges_to_path(const nb::DRef &E) { - Eigen::VectorXI I, J, K; - igl::edges_to_path(E, I, J, K); + // libigl's implementation internally maps to an int-backed buffer, + // so ensure the input scalar type is 32-bit int to avoid Map pointer mismatches. + using MatXI32 = Eigen::Matrix; + using VecXI32 = Eigen::Matrix; + + MatXI32 Ei = E.template cast(); + VecXI32 Ii, Ji, Ki; + igl::edges_to_path(Ei, Ii, Ji, Ki); + + Eigen::VectorXI I = Ii.template cast(); + Eigen::VectorXI J = Ji.template cast(); + Eigen::VectorXI K = Ki.template cast(); return std::make_tuple(I, J, K); } } From 906f0f646199683f9dee6454e8136fcc1dd9e28b Mon Sep 17 00:00:00 2001 From: Ben Ahlbrand Date: Fri, 24 Oct 2025 15:09:36 -0400 Subject: [PATCH 5/6] Add test for edge_to_paths function Added a new test for edge_to_paths function. --- tests/test_all.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_all.py b/tests/test_all.py index c9af717c..d184e081 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -164,7 +164,12 @@ def test_harmonic_integrated_from_laplacian_and_mass(): M = igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI) Q = igl.harmonic_integrated_from_laplacian_and_mass(L, M, k=1) Q = igl.harmonic_integrated_from_laplacian_and_mass(L, M, k=2) - + +def test_edge_to_paths(): + V,F = triangulated_square() + E, D, G = igl.boundary_facets(F) + _,_,_ = igl.edge_to_paths(E) + def test_tets(): V,F,T = single_tet() F,J,K = igl.boundary_facets(T) From d4731e027dcb0126309e35ff17e85918f15f97be Mon Sep 17 00:00:00 2001 From: Ben Ahlbrand Date: Fri, 24 Oct 2025 15:17:30 -0400 Subject: [PATCH 6/6] Rename test function from edge_to_paths to edges_to_path --- tests/test_all.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_all.py b/tests/test_all.py index d184e081..8a703c72 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -165,10 +165,10 @@ def test_harmonic_integrated_from_laplacian_and_mass(): Q = igl.harmonic_integrated_from_laplacian_and_mass(L, M, k=1) Q = igl.harmonic_integrated_from_laplacian_and_mass(L, M, k=2) -def test_edge_to_paths(): +def test_edges_to_path(): V,F = triangulated_square() E, D, G = igl.boundary_facets(F) - _,_,_ = igl.edge_to_paths(E) + _,_,_ = igl.edges_to_path(E) def test_tets(): V,F,T = single_tet()