Skip to content

Commit dbd3c62

Browse files
Improve nurbs surface fitting efficiency with Eigen sparse matrix solver
Compared to the existing Eigen dense solver, using the Eigen sparse solver significantly reduces solution time and memory usage, without relying on UMFPACK/SuiteSparse libraries that are based on the GPL open source license.
1 parent 84086e9 commit dbd3c62

File tree

2 files changed

+230
-1
lines changed

2 files changed

+230
-1
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#include <iostream>
2+
#include <stdexcept>
3+
4+
#include <pcl/surface/on_nurbs/nurbs_solve.h>
5+
6+
#include <Eigen/Dense>
7+
#include <Eigen/Sparse>
8+
9+
#include <chrono>
10+
#include <vector>
11+
12+
using namespace pcl;
13+
using namespace on_nurbs;
14+
15+
void
16+
NurbsSolve::assign(unsigned rows, unsigned cols, unsigned dims)
17+
{
18+
m_Ksparse.clear();
19+
m_xeig = Eigen::MatrixXd::Zero(cols, dims);
20+
m_feig = Eigen::MatrixXd::Zero(rows, dims);
21+
}
22+
23+
void
24+
NurbsSolve::K(unsigned i, unsigned j, double v)
25+
{
26+
m_Ksparse.set(i, j, v);
27+
}
28+
29+
void
30+
NurbsSolve::x(unsigned i, unsigned j, double v)
31+
{
32+
m_xeig(i, j) = v;
33+
}
34+
35+
void
36+
NurbsSolve::f(unsigned i, unsigned j, double v)
37+
{
38+
m_feig(i, j) = v;
39+
}
40+
41+
double
42+
NurbsSolve::K(unsigned i, unsigned j)
43+
{
44+
return m_Ksparse.get(i, j);
45+
}
46+
47+
double
48+
NurbsSolve::x(unsigned i, unsigned j)
49+
{
50+
return m_xeig(i, j);
51+
}
52+
53+
double
54+
NurbsSolve::f(unsigned i, unsigned j)
55+
{
56+
return m_feig(i, j);
57+
}
58+
59+
void
60+
NurbsSolve::resize(unsigned rows)
61+
{
62+
m_feig.conservativeResize(rows, m_feig.cols());
63+
// Note: m_Ksparse is not resized here; assumed handled by assign()
64+
}
65+
66+
void
67+
NurbsSolve::printK()
68+
{
69+
m_Ksparse.printLong();
70+
}
71+
72+
void
73+
NurbsSolve::printX()
74+
{
75+
std::cout << m_xeig << std::endl;
76+
}
77+
78+
void
79+
NurbsSolve::printF()
80+
{
81+
std::cout << m_feig << std::endl;
82+
}
83+
84+
bool
85+
NurbsSolve::solve()
86+
{
87+
auto start_time = std::chrono::high_resolution_clock::now();
88+
if (!m_quiet) {
89+
std::cout << "[NurbsSolve] Start solving..." << std::endl;
90+
}
91+
92+
int n_rows, n_cols;
93+
m_Ksparse.size(n_rows, n_cols);
94+
95+
unsigned rows, cols, dims;
96+
getSize(rows, cols, dims);
97+
98+
if (n_rows <= 0 || n_cols <= 0) {
99+
if (!m_quiet)
100+
std::cerr << "[NurbsSolve::solve] Invalid matrix size." << std::endl;
101+
return false;
102+
}
103+
104+
// Convert SparseMat to Eigen::SparseMatrix
105+
std::vector<int> rowinds, colinds;
106+
std::vector<double> values;
107+
m_Ksparse.get(rowinds, colinds, values);
108+
109+
// Use triplet list for efficient construction
110+
std::vector<Eigen::Triplet<double>> tripletList;
111+
tripletList.reserve(values.size());
112+
for (size_t k = 0; k < values.size(); ++k) {
113+
tripletList.emplace_back(rowinds[k], colinds[k], values[k]);
114+
}
115+
116+
Eigen::SparseMatrix<double> Keig_sparse(n_rows, n_cols);
117+
Keig_sparse.setFromTriplets(tripletList.begin(), tripletList.end());
118+
Keig_sparse.makeCompressed();
119+
120+
// Choose solver
121+
// std::string solver_type = "Eigen::SparseQR";
122+
// Eigen::SparseQR<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int>> solver;
123+
124+
125+
// // NOTE: SparseLU may get wrong result in windows
126+
// std::string solver_type = "Eigen::SparseLU COLAMDOrdering";
127+
// Eigen::SparseLU<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int>> solver;
128+
// std::string solver_type = "Eigen::SparseLU AMDOrdering";
129+
// Eigen::SparseLU<Eigen::SparseMatrix<double>, Eigen::AMDOrdering<int>> solver;
130+
131+
std::string solver_type = "Eigen::SimplicialLDLT";
132+
Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
133+
134+
if (!m_quiet) {
135+
std::cout << "[NurbsSolve::solve] solver_type: " << solver_type << std::endl;
136+
}
137+
138+
Eigen::SparseMatrix<double> KtK;
139+
Eigen::MatrixXd Ktf;
140+
Eigen::SparseMatrix<double> Kt;
141+
142+
// For least-squares: solve min ||K * x - f||^2
143+
// We solve normal equations: (K^T K) x = K^T f
144+
Kt = Keig_sparse.transpose();
145+
KtK = Kt * Keig_sparse;
146+
Ktf = Kt * m_feig;
147+
148+
// Solve KtK * x = Ktf
149+
solver.compute(KtK);
150+
if (solver.info() != Eigen::Success) {
151+
if (!m_quiet) {
152+
std::cerr << "[NurbsSolve::solve] compute failed" << std::endl;
153+
std::cerr << "[NurbsSolve::solve] solver.info: " << solver.info() << std::endl;
154+
}
155+
return false;
156+
}
157+
158+
m_xeig = solver.solve(Ktf);
159+
if (solver.info() != Eigen::Success) {
160+
if (!m_quiet) {
161+
std::cerr << "[NurbsSolve::solve] Solve failed" << std::endl;
162+
std::cerr << "[NurbsSolve::solve] solver.info: " << solver.info() << std::endl;
163+
}
164+
return false;
165+
}
166+
167+
if (!m_quiet) {
168+
auto end_time = std::chrono::high_resolution_clock::now(); // Record the end time
169+
auto duration =
170+
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time)
171+
.count();
172+
auto elapsed_time = static_cast<double>(duration) / 1000000000.0;
173+
std::cout << "[NurbsSolve] Solving completed. Time elapsed: " << elapsed_time
174+
<< " seconds" << std::endl;
175+
}
176+
177+
return true;
178+
}
179+
/*
180+
Eigen::MatrixXd
181+
NurbsSolve::diff ()
182+
{
183+
int n_rows, n_cols;
184+
m_Ksparse.size (n_rows, n_cols);
185+
186+
// Convert to Eigen sparse for multiplication
187+
std::vector<int> rowinds, colinds;
188+
std::vector<double> values;
189+
m_Ksparse.get (rowinds, colinds, values);
190+
191+
std::vector<Eigen::Triplet<double>> tripletList;
192+
for (size_t k = 0; k < values.size(); ++k)
193+
{
194+
tripletList.emplace_back(rowinds[k], colinds[k], values[k]);
195+
}
196+
197+
Eigen::SparseMatrix<double> Keig_sparse(n_rows, n_cols);
198+
Keig_sparse.setFromTriplets(tripletList.begin(), tripletList.end());
199+
200+
Eigen::MatrixXd Kx = Keig_sparse * m_xeig;
201+
return Kx - m_feig;
202+
}*/
203+
Eigen::MatrixXd
204+
NurbsSolve::diff()
205+
{
206+
207+
int n_rows, n_cols, n_dims;
208+
m_Ksparse.size(n_rows, n_cols);
209+
n_dims = m_feig.cols();
210+
211+
if (n_rows != m_feig.rows()) {
212+
printf("[NurbsSolve::diff] K.rows: %d f.rows: %d\n", n_rows, (int)m_feig.rows());
213+
throw std::runtime_error("[NurbsSolve::diff] Rows of equation do not match\n");
214+
}
215+
216+
Eigen::MatrixXd f = Eigen::MatrixXd::Zero(n_rows, n_dims);
217+
218+
for (int r = 0; r < n_rows; r++) {
219+
for (int c = 0; c < n_cols; c++) {
220+
f.row(r) = f.row(r) + m_xeig.row(c) * m_Ksparse.get(r, c);
221+
}
222+
}
223+
224+
return (f - m_feig);
225+
}

surface/src/on_nurbs/on_nurbs.cmake

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ set(ON_NURBS_SOURCES
4848
)
4949

5050
set(USE_UMFPACK 0 CACHE BOOL "Use UmfPack for solving sparse systems of equations (e.g. in surface/on_nurbs)")
51-
if(USE_UMFPACK)
51+
option(USE_NURBS_EIGEN_SPARSE_SOLVER "Use Eigen sparse solver" ON)
52+
53+
if(USE_NURBS_EIGEN_SPARSE_SOLVER)
54+
set(ON_NURBS_SOURCES ${ON_NURBS_SOURCES} src/on_nurbs/nurbs_solve_eigen_sparse.cpp)
55+
elseif(USE_UMFPACK)
5256
find_package(CHOLMOD REQUIRED)
5357
find_package(UMFPACK REQUIRED)
5458
set(ON_NURBS_SOURCES ${ON_NURBS_SOURCES} src/on_nurbs/nurbs_solve_umfpack.cpp)

0 commit comments

Comments
 (0)