From 5c4f16f56a85cb1fd1e3d8d68b728e546fb8fc8e Mon Sep 17 00:00:00 2001 From: Zachary Ferguson Date: Mon, 7 Sep 2026 21:11:30 -0400 Subject: [PATCH] Return named structs from the normalization functions `normalization_and_jacobian` and its Hessian variant returned std::tuple, so every caller had to remember that element 1 was the Jacobian and reach for std::get<1> to pull it out. They now return NormalizationAndJacobian and NormalizationAndJacobianAndHessian, which name the fields. Existing structured-binding call sites are unaffected -- the structs are aggregates, so `const auto [xhat, J] = ...` still works. The Python bindings convert to a tuple explicitly so `ipctk` keeps its current return type. BREAKING: C++ callers that named std::tuple or used std::get must switch to the .normalized/.jacobian/.hessian members. Co-Authored-By: Claude Opus 5 --- python/src/geometry/normal.cpp | 9 ++- src/ipc/geometry/normal.hpp | 105 ++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 42 deletions(-) diff --git a/python/src/geometry/normal.cpp b/python/src/geometry/normal.cpp index 2defaf4fc..8af05bd1b 100644 --- a/python/src/geometry/normal.cpp +++ b/python/src/geometry/normal.cpp @@ -2,6 +2,8 @@ #include +#include + using namespace ipc; void define_normal(py::module_& m) @@ -9,7 +11,8 @@ void define_normal(py::module_& m) m.def( "normalization_and_jacobian", [](Eigen::ConstRef x) { - return normalization_and_jacobian(x); + const auto [normalized, jacobian] = normalization_and_jacobian(x); + return std::make_tuple(normalized, jacobian); }, R"ipc_qu8mg5v7( Computes the normalization and Jacobian of a vector. @@ -45,7 +48,9 @@ void define_normal(py::module_& m) m.def( "normalization_and_jacobian_and_hessian", [](Eigen::ConstRef x) { - return normalization_and_jacobian_and_hessian(x); + const auto [normalized, jacobian, hessian] = + normalization_and_jacobian_and_hessian(x); + return std::make_tuple(normalized, jacobian, hessian); }, R"ipc_qu8mg5v7( Computes the normalization, Jacobian, and Hessian of a vector. diff --git a/src/ipc/geometry/normal.hpp b/src/ipc/geometry/normal.hpp index de821f927..66df2305c 100644 --- a/src/ipc/geometry/normal.hpp +++ b/src/ipc/geometry/normal.hpp @@ -4,12 +4,41 @@ #include #include -#include namespace ipc { // ============================================================================= +/// @brief A normalized vector paired with the Jacobian of the normalization. +/// @tparam T The scalar type. +/// @tparam dim The dimension (2 or 3). +/// @tparam max_dim The maximum dimension (2 or 3). +template +struct NormalizationAndJacobian { + /// @brief The normalized vector x/‖x‖. + Eigen::Matrix normalized; + /// @brief The Jacobian of the normalization evaluated at x. + Eigen::Matrix jacobian; +}; + +/// @brief A normalized vector paired with the first two derivatives of the +/// normalization. +/// @tparam T The scalar type. +/// @tparam dim The dimension (2 or 3). +/// @tparam max_dim The maximum dimension (2 or 3). +template +struct NormalizationAndJacobianAndHessian { + /// @brief The normalized vector x/‖x‖. + Eigen::Matrix normalized; + /// @brief The Jacobian of the normalization evaluated at x. + Eigen::Matrix jacobian; + /// @brief The Hessian of the normalization, one matrix per component. + std::array< + Eigen::Matrix, + max_dim> + hessian; +}; + namespace detail { /// @brief Computes the normalization and Jacobian of a vector. @@ -18,9 +47,9 @@ namespace detail { /// @tparam T The scalar type. /// @tparam dim The dimension (2 or 3). /// @param x The input vector. - /// @return A tuple containing the normalized vector and its Jacobian. + /// @return The normalized vector and its Jacobian. template - inline std::tuple, Eigen::Matrix> + inline NormalizationAndJacobian normalization_and_jacobian(const Eigen::Vector& x) { static_assert(dim == 2 || dim == 3, "normalization is only 2D or 3D"); @@ -36,12 +65,9 @@ namespace detail { /// @tparam T The scalar type. /// @tparam dim The dimension (2 or 3). /// @param x The input vector. - /// @return A tuple of the normalized vector, its Jacobian, and its Hessian. + /// @return The normalized vector, its Jacobian, and its Hessian. template - inline std::tuple< - Eigen::Vector, - Eigen::Matrix, - std::array, dim>> + inline NormalizationAndJacobianAndHessian normalization_and_jacobian_and_hessian(const Eigen::Vector& x) { static_assert(dim == 2 || dim == 3, "normalization is only 2D or 3D"); @@ -65,28 +91,29 @@ namespace detail { /// /// Accepts any Eigen vector expression (row or column). The dimension is /// resolved at compile time when the argument type knows it and with a single -/// branch otherwise. When the dimension is known the return type is -/// std::tuple, Eigen::Matrix>; otherwise it -/// is std::tuple, MatrixMax3>. +/// branch otherwise. When the dimension is known the result holds +/// Eigen::Vector and Eigen::Matrix; otherwise it holds +/// VectorMax3 and MatrixMax3. /// /// @param x The input vector. -/// @return A tuple containing the normalized vector and its Jacobian. +/// @return The normalized vector and its Jacobian. template inline auto normalization_and_jacobian(const Eigen::MatrixBase& x) { using T = typename DerivedX::Scalar; + using DynamicResult = NormalizationAndJacobian; if constexpr (dim_v == 2) { return detail::normalization_and_jacobian(x); } else if constexpr (dim_v == 3) { return detail::normalization_and_jacobian(x); } else if (x.size() == 2) { - const auto [xhat, J] = detail::normalization_and_jacobian(x); - return std::tuple, MatrixMax3>(xhat, J); + const auto fixed = detail::normalization_and_jacobian(x); + return DynamicResult { fixed.normalized, fixed.jacobian }; } else { assert(x.size() == 3); - const auto [xhat, J] = detail::normalization_and_jacobian(x); - return std::tuple, MatrixMax3>(xhat, J); + const auto fixed = detail::normalization_and_jacobian(x); + return DynamicResult { fixed.normalized, fixed.jacobian }; } } @@ -99,55 +126,53 @@ inline auto normalization_jacobian(const Eigen::MatrixBase& x) using T = typename DerivedX::Scalar; if constexpr (dim_v == 2) { - return std::get<1>(detail::normalization_and_jacobian(x)); + return detail::normalization_and_jacobian(x).jacobian; } else if constexpr (dim_v == 3) { - return std::get<1>(detail::normalization_and_jacobian(x)); + return detail::normalization_and_jacobian(x).jacobian; } else if (x.size() == 2) { return MatrixMax3( - std::get<1>(detail::normalization_and_jacobian(x))); + detail::normalization_and_jacobian(x).jacobian); } else { assert(x.size() == 3); return MatrixMax3( - std::get<1>(detail::normalization_and_jacobian(x))); + detail::normalization_and_jacobian(x).jacobian); } } /// @brief Computes the normalization, Jacobian, and Hessian of a vector. /// @param x The input vector. -/// @return A tuple of the normalized vector, its Jacobian, and its Hessian. +/// @return The normalized vector, its Jacobian, and its Hessian. template inline auto normalization_and_jacobian_and_hessian(const Eigen::MatrixBase& x) { using T = typename DerivedX::Scalar; - using Ret = - std::tuple, MatrixMax3, std::array, 3>>; + using DynamicResult = + NormalizationAndJacobianAndHessian; if constexpr (dim_v == 2) { return detail::normalization_and_jacobian_and_hessian(x); } else if constexpr (dim_v == 3) { return detail::normalization_and_jacobian_and_hessian(x); } else if (x.size() == 2) { - const auto [xhat, J, H] = + const auto fixed = detail::normalization_and_jacobian_and_hessian(x); - return Ret( - xhat, J, - std::array, 3> { - MatrixMax3(H[0]), - MatrixMax3(H[1]), - MatrixMax3(), // H[2] is empty in 2D - }); + return DynamicResult { fixed.normalized, fixed.jacobian, + std::array, 3> { + MatrixMax3(fixed.hessian[0]), + MatrixMax3(fixed.hessian[1]), + MatrixMax3(), // hessian[2] is empty in 2D + } }; } else { assert(x.size() == 3); - const auto [xhat, J, H] = + const auto fixed = detail::normalization_and_jacobian_and_hessian(x); - return Ret( - xhat, J, - std::array, 3> { - MatrixMax3(H[0]), - MatrixMax3(H[1]), - MatrixMax3(H[2]), - }); + return DynamicResult { fixed.normalized, fixed.jacobian, + std::array, 3> { + MatrixMax3(fixed.hessian[0]), + MatrixMax3(fixed.hessian[1]), + MatrixMax3(fixed.hessian[2]), + } }; } } @@ -157,7 +182,7 @@ normalization_and_jacobian_and_hessian(const Eigen::MatrixBase& x) template inline auto normalization_hessian(const Eigen::MatrixBase& x) { - return std::get<2>(normalization_and_jacobian_and_hessian(x)); + return normalization_and_jacobian_and_hessian(x).hessian; } namespace detail {