Add line local parameterization.
This CL adds a local parameterization for a n-dimensional
line, which is represented as an origin point and a direction.
The line direction is updated in the same way as a
homogeneous vector and the origin point is updated
perpendicular to the line direction.
Change-Id: I733f395e5cc4250abf9778c26fe0a5ae1de6b624
diff --git a/docs/source/nnls_modeling.rst b/docs/source/nnls_modeling.rst
index 2604027..0883c6d 100644
--- a/docs/source/nnls_modeling.rst
+++ b/docs/source/nnls_modeling.rst
@@ -1339,6 +1339,7 @@
same update as :class:`QuaternionParameterization` but takes into
account Eigen's internal memory element ordering.
+.. _`homogeneous_vector_parameterization`:
.. class:: HomogeneousVectorParameterization
In computer vision, homogeneous vectors are commonly used to
@@ -1362,6 +1363,27 @@
last element of :math:`x` is the scalar component of the homogeneous
vector.
+.. class:: LineParameterization
+
+ This class provides a parameterization for lines, where the line is
+ over-parameterized by an origin point and a direction vector. So the
+ parameter vector size needs to be two times the ambient space dimension,
+ where the first half is interpreted as the origin point and the second
+ half as the direction.
+
+ To give an example: Given n distinct points in 3D (measurements) we search
+ for the line which has the closest distance to all of these. We parameterize
+ the line with a 3D origin point and a 3D direction vector. As a cost
+ function the distance between the line and the given points is used.
+ We use six parameters for the line (two 3D vectors) but a line in 3D only
+ has four degrees of freedom. To make the over-parameterization visible to
+ the optimizer and covariance estimator this line parameterization can be
+ used.
+
+ The plus operator for the line direction is the same as for the
+ :ref:`HomogeneousVectorParameterization <homogeneous_vector_parameterization>`.
+ The update of the origin point is perpendicular to the line direction
+ before the update.
.. class:: ProductParameterization
diff --git a/internal/ceres/householder_vector.h b/include/ceres/internal/householder_vector.h
similarity index 89%
rename from internal/ceres/householder_vector.h
rename to include/ceres/internal/householder_vector.h
index 6d85217..051a88d 100644
--- a/internal/ceres/householder_vector.h
+++ b/include/ceres/internal/householder_vector.h
@@ -28,8 +28,8 @@
//
// Author: vitus@google.com (Michael Vitus)
-#ifndef CERES_PUBLIC_HOUSEHOLDER_VECTOR_H_
-#define CERES_PUBLIC_HOUSEHOLDER_VECTOR_H_
+#ifndef CERES_PUBLIC_INTERNAL_HOUSEHOLDER_VECTOR_H_
+#define CERES_PUBLIC_INTERNAL_HOUSEHOLDER_VECTOR_H_
#include "Eigen/Core"
#include "glog/logging.h"
@@ -42,9 +42,9 @@
// vector as pivot instead of first. This computes the vector v with v(n) = 1
// and beta such that H = I - beta * v * v^T is orthogonal and
// H * x = ||x||_2 * e_n.
-template <typename Scalar>
-void ComputeHouseholderVector(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x,
- Eigen::Matrix<Scalar, Eigen::Dynamic, 1>* v,
+template <typename Derived, typename Scalar, int N>
+void ComputeHouseholderVector(const Eigen::DenseBase<Derived>& x,
+ Eigen::Matrix<Scalar, N, 1>* v,
Scalar* beta) {
CHECK(beta != nullptr);
CHECK(v != nullptr);
@@ -82,4 +82,4 @@
} // namespace internal
} // namespace ceres
-#endif // CERES_PUBLIC_HOUSEHOLDER_VECTOR_H_
+#endif // CERES_PUBLIC_INTERNAL_HOUSEHOLDER_VECTOR_H_
diff --git a/include/ceres/internal/line_parameterization.h b/include/ceres/internal/line_parameterization.h
new file mode 100644
index 0000000..b2ec9e1
--- /dev/null
+++ b/include/ceres/internal/line_parameterization.h
@@ -0,0 +1,172 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2020 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: jodebo_beck@gmx.de (Johannes Beck)
+//
+
+#ifndef CERES_PUBLIC_INTERNAL_LINE_PARAMETERIZATION_H_
+#define CERES_PUBLIC_INTERNAL_LINE_PARAMETERIZATION_H_
+
+#include "householder_vector.h"
+
+namespace ceres {
+
+template <int AmbientSpaceDimension>
+bool LineParameterization<AmbientSpaceDimension>::Plus(
+ const double* x_ptr,
+ const double* delta_ptr,
+ double* x_plus_delta_ptr) const {
+ // We seek a box plus operator of the form
+ //
+ // [o*, d*] = Plus([o, d], [delta_o, delta_d])
+ //
+ // where o is the origin point, d is the direction vector, delta_o is
+ // the delta of the origin point and delta_d the delta of the direction and
+ // o* and d* is the updated origin point and direction.
+ //
+ // We separate the Plus operator into the origin point and directional part
+ // d* = Plus_d(d, delta_d)
+ // o* = Plus_o(o, d, delta_o)
+ //
+ // The direction update function Plus_d is the same as for the homogeneous vector
+ // parameterization:
+ //
+ // d* = H_{v(d)} [0.5 sinc(0.5 |delta_d|) delta_d, cos(0.5 |delta_d|)]^T
+ //
+ // where H is the householder matrix
+ // H_{v} = I - (2 / |v|^2) v v^T
+ // and
+ // v(d) = d - sign(d_n) |d| e_n.
+ //
+ // The origin point update function Plus_o is defined as
+ //
+ // o* = o + H_{v(d)} [0.5 delta_o, 0]^T.
+
+ static constexpr int kDim = AmbientSpaceDimension;
+ using AmbientVector = Eigen::Matrix<double, kDim, 1>;
+ using AmbientVectorRef = Eigen::Map<Eigen::Matrix<double, kDim, 1>>;
+ using ConstAmbientVectorRef = Eigen::Map<const Eigen::Matrix<double, kDim, 1>>;
+ using ConstTangentVectorRef =
+ Eigen::Map<const Eigen::Matrix<double, kDim - 1, 1>>;
+
+
+ ConstAmbientVectorRef o(x_ptr);
+ ConstAmbientVectorRef d(x_ptr + kDim);
+
+ ConstTangentVectorRef delta_o(delta_ptr);
+ ConstTangentVectorRef delta_d(delta_ptr + kDim - 1);
+ AmbientVectorRef o_plus_delta(x_plus_delta_ptr);
+ AmbientVectorRef d_plus_delta(x_plus_delta_ptr + kDim);
+
+ const double norm_delta_d = delta_d.norm();
+
+ o_plus_delta = o;
+
+ // Shortcut for zero delta direction.
+ if (norm_delta_d == 0.0) {
+ d_plus_delta = d;
+
+ if (delta_o.isZero(0.0)) {
+ return true;
+ }
+ }
+
+ // Calculate the householder transformation which is needed for f_d and f_o.
+ AmbientVector v;
+ double beta;
+ internal::ComputeHouseholderVector(d, &v, &beta);
+
+ if (norm_delta_d != 0.0) {
+ // Map the delta from the minimum representation to the over parameterized
+ // homogeneous vector. See section A6.9.2 on page 624 of Hartley & Zisserman
+ // (2nd Edition) for a detailed description. Note there is a typo on Page
+ // 625, line 4 so check the book errata.
+ const double norm_delta_div_2 = 0.5 * norm_delta_d;
+ const double sin_delta_by_delta =
+ std::sin(norm_delta_div_2) / norm_delta_div_2;
+
+ // Apply the delta update to remain on the unit sphere. See section A6.9.3
+ // on page 625 of Hartley & Zisserman (2nd Edition) for a detailed
+ // description.
+ AmbientVector y;
+ y.template head<kDim - 1>() = 0.5 * sin_delta_by_delta * delta_d;
+ y[kDim - 1] = std::cos(norm_delta_div_2);
+
+ d_plus_delta = d.norm() * (y - v * (beta * (v.transpose() * y)));
+ }
+
+ // The null space is in the direction of the line, so the tangent space is
+ // perpendicular to the line direction. This is achieved by using the
+ // householder matrix of the direction and allow only movements
+ // perpendicular to e_n.
+ //
+ // The factor of 0.5 is used to be consistent with the line direction
+ // update.
+ AmbientVector y;
+ y << 0.5 * delta_o, 0;
+ o_plus_delta += y - v * (beta * (v.transpose() * y));
+
+ return true;
+}
+
+template <int AmbientSpaceDimension>
+bool LineParameterization<AmbientSpaceDimension>::ComputeJacobian(
+ const double* x_ptr, double* jacobian_ptr) const {
+ static constexpr int kDim = AmbientSpaceDimension;
+ using AmbientVector = Eigen::Matrix<double, kDim, 1>;
+ using ConstAmbientVectorRef = Eigen::Map<const Eigen::Matrix<double, kDim, 1>>;
+ using MatrixRef = Eigen::Map<
+ Eigen::Matrix<double, 2 * kDim, 2 * (kDim - 1), Eigen::RowMajor>>;
+
+ ConstAmbientVectorRef d(x_ptr + kDim);
+ MatrixRef jacobian(jacobian_ptr);
+
+ // Clear the Jacobian as only half of the matrix is not zero.
+ jacobian.setZero();
+
+ AmbientVector v;
+ double beta;
+ internal::ComputeHouseholderVector(d, &v, &beta);
+
+ // The Jacobian is equal to J = 0.5 * H.leftCols(kDim - 1) where H is
+ // the Householder matrix (H = I - beta * v * v') for the origin point. For
+ // the line direction part the Jacobian is scaled by the norm of the
+ // direction.
+ for (int i = 0; i < kDim - 1; ++i) {
+ jacobian.block(0, i, kDim, 1) = -0.5 * beta * v(i) * v;
+ jacobian.col(i)(i) += 0.5;
+ }
+
+ jacobian.template block<kDim, kDim - 1>(kDim, kDim - 1) =
+ jacobian.template block<kDim, kDim - 1>(0, 0) * d.norm();
+ return true;
+}
+
+} // namespace ceres
+
+#endif // CERES_PUBLIC_INTERNAL_LINE_PARAMETERIZATION_H_
diff --git a/include/ceres/local_parameterization.h b/include/ceres/local_parameterization.h
index 93c7973..0d7b507 100644
--- a/include/ceres/local_parameterization.h
+++ b/include/ceres/local_parameterization.h
@@ -262,6 +262,33 @@
const int size_;
};
+// This provides a parameterization for lines, where the line is
+// over-parameterized by an origin point and a direction vector. So the
+// parameter vector size needs to be two times the ambient space dimension,
+// where the first half is interpreted as the origin point and the second half
+// as the direction.
+//
+// The plus operator for the line direction is the same as for the
+// HomogeneousVectorParameterization. The update of the origin point is
+// perpendicular to the line direction before the update.
+//
+// This local parameterization is a special case of the affine Grassmannian
+// manifold (see https://en.wikipedia.org/wiki/Affine_Grassmannian_(manifold))
+// for the case Graff_1(R^n).
+template <int AmbientSpaceDimension>
+class CERES_EXPORT LineParameterization : public LocalParameterization {
+ public:
+ static_assert(AmbientSpaceDimension >= 2,
+ "The ambient space must be at least 2");
+
+ bool Plus(const double* x,
+ const double* delta,
+ double* x_plus_delta) const override;
+ bool ComputeJacobian(const double* x, double* jacobian) const override;
+ int GlobalSize() const override { return 2 * AmbientSpaceDimension; }
+ int LocalSize() const override { return 2 * (AmbientSpaceDimension - 1); }
+};
+
// Construct a local parameterization by taking the Cartesian product
// of a number of other local parameterizations. This is useful, when
// a parameter block is the cartesian product of two or more
@@ -328,5 +355,7 @@
} // namespace ceres
#include "ceres/internal/reenable_warnings.h"
+#include "ceres/internal/line_parameterization.h"
#endif // CERES_PUBLIC_LOCAL_PARAMETERIZATION_H_
+
diff --git a/internal/ceres/householder_vector_test.cc b/internal/ceres/householder_vector_test.cc
index 69a6d3c..9eaca75 100644
--- a/internal/ceres/householder_vector_test.cc
+++ b/internal/ceres/householder_vector_test.cc
@@ -28,7 +28,7 @@
//
// Author: vitus@google.com (Michael Vitus)
-#include "ceres/householder_vector.h"
+#include "ceres/internal/householder_vector.h"
#include "ceres/internal/eigen.h"
#include "glog/logging.h"
#include "gtest/gtest.h"
diff --git a/internal/ceres/local_parameterization.cc b/internal/ceres/local_parameterization.cc
index b6316f1..5fedefa 100644
--- a/internal/ceres/local_parameterization.cc
+++ b/internal/ceres/local_parameterization.cc
@@ -32,7 +32,7 @@
#include <algorithm>
#include "Eigen/Geometry"
-#include "ceres/householder_vector.h"
+#include "ceres/internal/householder_vector.h"
#include "ceres/internal/eigen.h"
#include "ceres/internal/fixed_array.h"
#include "ceres/rotation.h"
@@ -248,16 +248,16 @@
// (2nd Edition) for a detailed description. Note there is a typo on Page
// 625, line 4 so check the book errata.
const double norm_delta_div_2 = 0.5 * norm_delta;
- const double sin_delta_by_delta = sin(norm_delta_div_2) /
+ const double sin_delta_by_delta = std::sin(norm_delta_div_2) /
norm_delta_div_2;
Vector y(size_);
y.head(size_ - 1) = 0.5 * sin_delta_by_delta * delta;
- y(size_ - 1) = cos(norm_delta_div_2);
+ y(size_ - 1) = std::cos(norm_delta_div_2);
Vector v(size_);
double beta;
- internal::ComputeHouseholderVector<double>(x, &v, &beta);
+ internal::ComputeHouseholderVector(x, &v, &beta);
// Apply the delta update to remain on the unit sphere. See section A6.9.3
// on page 625 of Hartley & Zisserman (2nd Edition) for a detailed
@@ -274,7 +274,7 @@
Vector v(size_);
double beta;
- internal::ComputeHouseholderVector<double>(x, &v, &beta);
+ internal::ComputeHouseholderVector(x, &v, &beta);
// The Jacobian is equal to J = 0.5 * H.leftCols(size_ - 1) where H is the
// Householder matrix (H = I - beta * v * v').
diff --git a/internal/ceres/local_parameterization_test.cc b/internal/ceres/local_parameterization_test.cc
index 9334287..f165049 100644
--- a/internal/ceres/local_parameterization_test.cc
+++ b/internal/ceres/local_parameterization_test.cc
@@ -36,9 +36,9 @@
#include "Eigen/Geometry"
#include "ceres/autodiff_local_parameterization.h"
-#include "ceres/householder_vector.h"
#include "ceres/internal/autodiff.h"
#include "ceres/internal/eigen.h"
+#include "ceres/internal/householder_vector.h"
#include "ceres/random.h"
#include "ceres/rotation.h"
#include "gtest/gtest.h"
@@ -418,45 +418,41 @@
}
// Functor needed to implement automatically differentiated Plus for
-// homogeneous vectors. Note this explicitly defined for vectors of size 4.
+// homogeneous vectors.
+template <int Dim>
struct HomogeneousVectorParameterizationPlus {
template <typename Scalar>
bool operator()(const Scalar* p_x,
const Scalar* p_delta,
Scalar* p_x_plus_delta) const {
- Eigen::Map<const Eigen::Matrix<Scalar, 4, 1>> x(p_x);
- Eigen::Map<const Eigen::Matrix<Scalar, 3, 1>> delta(p_delta);
- Eigen::Map<Eigen::Matrix<Scalar, 4, 1>> x_plus_delta(p_x_plus_delta);
+ Eigen::Map<const Eigen::Matrix<Scalar, Dim, 1>> x(p_x);
+ Eigen::Map<const Eigen::Matrix<Scalar, Dim - 1, 1>> delta(p_delta);
+ Eigen::Map<Eigen::Matrix<Scalar, Dim, 1>> x_plus_delta(p_x_plus_delta);
- const Scalar squared_norm_delta =
- delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
+ const Scalar squared_norm_delta = delta.squaredNorm();
- Eigen::Matrix<Scalar, 4, 1> y;
+ Eigen::Matrix<Scalar, Dim, 1> y;
Scalar one_half(0.5);
if (squared_norm_delta > Scalar(0.0)) {
Scalar norm_delta = sqrt(squared_norm_delta);
Scalar norm_delta_div_2 = 0.5 * norm_delta;
const Scalar sin_delta_by_delta =
sin(norm_delta_div_2) / norm_delta_div_2;
- y[0] = sin_delta_by_delta * delta[0] * one_half;
- y[1] = sin_delta_by_delta * delta[1] * one_half;
- y[2] = sin_delta_by_delta * delta[2] * one_half;
- y[3] = cos(norm_delta_div_2);
+ y.template head<Dim - 1>() = sin_delta_by_delta * one_half * delta;
+ y[Dim - 1] = cos(norm_delta_div_2);
} else {
// We do not just use y = [0,0,0,1] here because that is a
// constant and when used for automatic differentiation will
// lead to a zero derivative. Instead we take a first order
// approximation and evaluate it at zero.
- y[0] = delta[0] * one_half;
- y[1] = delta[1] * one_half;
- y[2] = delta[2] * one_half;
- y[3] = Scalar(1.0);
+ y.template head<Dim - 1>() = delta * one_half;
+ y[Dim - 1] = Scalar(1.0);
}
- Eigen::Matrix<Scalar, Eigen::Dynamic, 1> v(4);
+ Eigen::Matrix<Scalar, Dim, 1> v;
Scalar beta;
- internal::ComputeHouseholderVector<Scalar>(x, &v, &beta);
+ internal::ComputeHouseholderVector(x, &v, &beta);
x_plus_delta = x.norm() * (y - v * (beta * v.dot(y)));
@@ -484,7 +480,7 @@
EXPECT_NEAR(x_plus_delta_norm, x_norm, kTolerance);
// Autodiff jacobian at delta_x = 0.
- AutoDiffLocalParameterization<HomogeneousVectorParameterizationPlus, 4, 3>
+ AutoDiffLocalParameterization<HomogeneousVectorParameterizationPlus<4>, 4, 3>
autodiff_jacobian;
double jacobian_autodiff[12];
@@ -565,6 +561,177 @@
EXPECT_DEATH_IF_SUPPORTED(HomogeneousVectorParameterization x(1), "size");
}
+// Functor needed to implement automatically differentiated Plus for
+// line parameterization.
+template <int AmbientSpaceDim>
+struct LineParameterizationPlus {
+ template <typename Scalar>
+ bool operator()(const Scalar* p_x,
+ const Scalar* p_delta,
+ Scalar* p_x_plus_delta) const {
+ static constexpr int kTangetSpaceDim = AmbientSpaceDim - 1;
+ Eigen::Map<const Eigen::Matrix<Scalar, AmbientSpaceDim, 1>> origin_point(
+ p_x);
+ Eigen::Map<const Eigen::Matrix<Scalar, AmbientSpaceDim, 1>> dir(
+ p_x + AmbientSpaceDim);
+ Eigen::Map<const Eigen::Matrix<Scalar, kTangetSpaceDim, 1>>
+ delta_origin_point(p_delta);
+ Eigen::Map<Eigen::Matrix<Scalar, AmbientSpaceDim, 1>>
+ origin_point_plus_delta(p_x_plus_delta);
+
+ HomogeneousVectorParameterizationPlus<AmbientSpaceDim> dir_plus;
+ dir_plus(dir.data(),
+ p_delta + kTangetSpaceDim,
+ p_x_plus_delta + AmbientSpaceDim);
+
+ Eigen::Matrix<Scalar, AmbientSpaceDim, 1> v;
+ Scalar beta;
+ internal::ComputeHouseholderVector(dir, &v, &beta);
+
+ Eigen::Matrix<Scalar, AmbientSpaceDim, 1> y;
+ y << 0.5 * delta_origin_point, Scalar(0.0);
+ origin_point_plus_delta = origin_point + y - v * (beta * v.dot(y));
+
+ return true;
+ }
+};
+
+template <int AmbientSpaceDim>
+static void LineParameterizationHelper(const double* x_ptr,
+ const double* delta) {
+ const double kTolerance = 1e-14;
+
+ static constexpr int ParameterDim = 2 * AmbientSpaceDim;
+ static constexpr int TangientParameterDim = 2 * (AmbientSpaceDim - 1);
+
+ LineParameterization<AmbientSpaceDim> line_parameterization;
+
+ using ParameterVector = Eigen::Matrix<double, ParameterDim, 1>;
+ ParameterVector x_plus_delta = ParameterVector::Zero();
+ line_parameterization.Plus(x_ptr, delta, x_plus_delta.data());
+
+ // Ensure the update maintains the norm for the line direction.
+ Eigen::Map<const ParameterVector> x(x_ptr);
+ const double dir_plus_delta_norm =
+ x_plus_delta.template tail<AmbientSpaceDim>().norm();
+ const double dir_norm = x.template tail<AmbientSpaceDim>().norm();
+ EXPECT_NEAR(dir_plus_delta_norm, dir_norm, kTolerance);
+
+ // Ensure the update of the origin point is perpendicular to the line
+ // direction.
+ const double dot_prod_val = x.template tail<AmbientSpaceDim>().dot(
+ x_plus_delta.template head<AmbientSpaceDim>() -
+ x.template head<AmbientSpaceDim>());
+ EXPECT_NEAR(dot_prod_val, 0.0, kTolerance);
+
+ // Autodiff jacobian at delta_x = 0.
+ AutoDiffLocalParameterization<LineParameterizationPlus<AmbientSpaceDim>,
+ ParameterDim,
+ TangientParameterDim>
+ autodiff_jacobian;
+
+ using JacobianMatrix = Eigen::
+ Matrix<double, ParameterDim, TangientParameterDim, Eigen::RowMajor>;
+ constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
+ JacobianMatrix jacobian_autodiff = JacobianMatrix::Constant(kNaN);
+ JacobianMatrix jacobian_analytic = JacobianMatrix::Constant(kNaN);
+
+ autodiff_jacobian.ComputeJacobian(x_ptr, jacobian_autodiff.data());
+ line_parameterization.ComputeJacobian(x_ptr, jacobian_analytic.data());
+
+ EXPECT_FALSE(jacobian_autodiff.hasNaN());
+ EXPECT_FALSE(jacobian_analytic.hasNaN());
+ EXPECT_TRUE(jacobian_autodiff.isApprox(jacobian_analytic))
+ << "auto diff:\n"
+ << jacobian_autodiff << "\n"
+ << "analytic diff:\n"
+ << jacobian_analytic;
+}
+
+TEST(LineParameterization, ZeroTest3D) {
+ double x[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 1.0};
+ double delta[4] = {0.0, 0.0, 0.0, 0.0};
+
+ LineParameterizationHelper<3>(x, delta);
+}
+
+TEST(LineParameterization, ZeroTest4D) {
+ double x[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0};
+ double delta[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
+
+ LineParameterizationHelper<4>(x, delta);
+}
+
+TEST(LineParameterization, ZeroOriginPointTest3D) {
+ double x[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 1.0};
+ double delta[4] = {0.0, 0.0, 1.0, 2.0};
+
+ LineParameterizationHelper<3>(x, delta);
+}
+
+TEST(LineParameterization, ZeroOriginPointTest4D) {
+ double x[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0};
+ double delta[6] = {0.0, 0.0, 0.0, 1.0, 2.0, 3.0};
+
+ LineParameterizationHelper<4>(x, delta);
+}
+
+TEST(LineParameterization, ZeroDirTest3D) {
+ double x[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 1.0};
+ double delta[4] = {3.0, 2.0, 0.0, 0.0};
+
+ LineParameterizationHelper<3>(x, delta);
+}
+
+TEST(LineParameterization, ZeroDirTest4D) {
+ double x[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0};
+ double delta[6] = {3.0, 2.0, 1.0, 0.0, 0.0, 0.0};
+
+ LineParameterizationHelper<4>(x, delta);
+}
+
+TEST(LineParameterization, AwayFromZeroTest3D1) {
+ Eigen::Matrix<double, 6, 1> x;
+ x.head<3>() << 1.54, 2.32, 1.34;
+ x.tail<3>() << 0.52, 0.25, 0.15;
+ x.tail<3>().normalize();
+
+ double delta[4] = {4.0, 7.0, 1.0, -0.5};
+
+ LineParameterizationHelper<3>(x.data(), delta);
+}
+
+TEST(LineParameterization, AwayFromZeroTest4D1) {
+ Eigen::Matrix<double, 8, 1> x;
+ x.head<4>() << 1.54, 2.32, 1.34, 3.23;
+ x.tail<4>() << 0.52, 0.25, 0.15, 0.45;
+ x.tail<4>().normalize();
+
+ double delta[6] = {4.0, 7.0, -3.0, 0.0, 1.0, -0.5};
+
+ LineParameterizationHelper<4>(x.data(), delta);
+}
+
+TEST(LineParameterization, AwayFromZeroTest3D2) {
+ Eigen::Matrix<double, 6, 1> x;
+ x.head<3>() << 7.54, -2.81, 8.63;
+ x.tail<3>() << 2.52, 5.25, 4.15;
+
+ double delta[4] = {4.0, 7.0, 1.0, -0.5};
+
+ LineParameterizationHelper<3>(x.data(), delta);
+}
+
+TEST(LineParameterization, AwayFromZeroTest4D2) {
+ Eigen::Matrix<double, 8, 1> x;
+ x.head<4>() << 7.54, -2.81, 8.63, 6.93;
+ x.tail<4>() << 2.52, 5.25, 4.15, 1.45;
+
+ double delta[6] = {4.0, 7.0, -3.0, 2.0, 1.0, -0.5};
+
+ LineParameterizationHelper<4>(x.data(), delta);
+}
+
class ProductParameterizationTest : public ::testing::Test {
protected:
void SetUp() final {