G+Smo  24.08.0
Geometry + Simulation Modules
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
gsFitting.hpp
Go to the documentation of this file.
1 
16 #include <gsCore/gsBasis.h>
17 #include <gsCore/gsGeometry.h>
18 #include <gsCore/gsLinearAlgebra.h>
19 #include <gsNurbs/gsBSpline.h>
21 
22 
23 
24 namespace gismo
25 {
26 
27 template<class T>
29 {
30  if ( m_result )
31  delete m_result;
32 }
33 
34 template<class T>
36  gsMatrix<T> const & points,
37  gsBasis<T> & basis)
38 {
39  GISMO_ASSERT(points.cols()==param_values.cols(), "Pointset dimensions problem "<< points.cols() << " != " <<param_values.cols() );
40  GISMO_ASSERT(basis.domainDim()==param_values.rows(), "Parameter values are inconsistent: "<< basis.domainDim() << " != " <<param_values.rows() );
41 
42  m_param_values = param_values;
43  m_points = points;
44  m_result = nullptr;
45  m_basis = &basis;
46  m_points.transposeInPlace();
47 
48  m_offset.resize(2);
49  m_offset[0] = 0;
50  m_offset[1] = m_points.rows();
51 }
52 
53 template<class T>
55  gsMatrix<T> const& points,
56  gsVector<index_t> offset,
57  gsMappedBasis<2,T> & mbasis)
58 {
59  m_param_values = param_values;
60  m_points = points;
61  m_result = nullptr;
62  m_basis = &mbasis;
63  m_points.transposeInPlace();
64  m_offset = give(offset);
65 }
66 
67 template<class T>
68 void gsFitting<T>::compute(T lambda)
69 {
70  m_last_lambda = lambda;
71 
72  // Wipe out previous result
73  if ( m_result )
74  delete m_result;
75 
76  const int num_basis = m_basis->size();
77  const short_t dimension = m_points.cols();
78 
79  //left side matrix
80  //gsMatrix<T> A_mat(num_basis,num_basis);
81  gsSparseMatrix<T> A_mat(num_basis + m_constraintsLHS.rows(), num_basis + m_constraintsLHS.rows());
82  //gsMatrix<T>A_mat(num_basis,num_basis);
83  //To optimize sparse matrix an estimation of nonzero elements per
84  //column can be given here
85  int nonZerosPerCol = 1;
86  for (int i = 0; i < m_basis->domainDim(); ++i) // to do: improve
87  // nonZerosPerCol *= m_basis->degree(i) + 1;
88  nonZerosPerCol *= ( 2 * m_basis->basis(0).degree(i) + 1 ) * 4;
89  // TODO: improve by taking constraints nonzeros into account.
90  A_mat.reservePerColumn( nonZerosPerCol );
91 
92  //right side vector (more dimensional!)
93  gsMatrix<T> m_B(num_basis + m_constraintsRHS.rows(), dimension);
94  m_B.setZero(); // enusure that all entries are zero in the beginning
95 
96  // building the matrix A and the vector b of the system of linear
97  // equations A*x==b
98 
99  assembleSystem(A_mat, m_B);
100 
101 
102  // --- Smoothing matrix computation
103  //test degree >=3
104  if(lambda > 0)
105  applySmoothing(lambda, A_mat);
106 
107  if(m_constraintsLHS.rows() > 0)
108  extendSystem(A_mat, m_B);
109 
110  //Solving the system of linear equations A*x=b (works directly for a right side which has a dimension with higher than 1)
111 
112  //gsDebugVar( A_mat.nonZerosPerCol().maxCoeff() );
113  //gsDebugVar( A_mat.nonZerosPerCol().minCoeff() );
114  A_mat.makeCompressed();
115 
116  typename gsSparseSolver<T>::BiCGSTABILUT solver( A_mat );
117 
118  if ( solver.preconditioner().info() != gsEigen::Success )
119  {
120  gsWarn<< "The preconditioner failed. Aborting.\n";
121 
122  return;
123  }
124  // Solves for many right hand side columns
125  gsMatrix<T> x;
126 
127  x = solver.solve(m_B); //toDense()
128 
129  // If there were constraints, we obtained too many coefficients.
130  x.conservativeResize(num_basis, gsEigen::NoChange);
131 
132  //gsMatrix<T> x (m_B.rows(), m_B.cols());
133  //x=A_mat.fullPivHouseholderQr().solve( m_B);
134  // Solves for many right hand side columns
135  // finally generate the B-spline curve
136 
137  if (const gsBasis<T> * bb = dynamic_cast<const gsBasis<T> *>(m_basis))
138  m_result = bb->makeGeometry( give(x) ).release();
139  else
140  m_mresult = gsMappedSpline<2,T> ( *static_cast<gsMappedBasis<2,T>*>(m_basis),give(x));
141 }
142 
143 template <class T>
144 void gsFitting<T>::parameterCorrection(T accuracy,
145  index_t maxIter,
146  T tolOrth)
147 {
148  if ( !m_result )
149  compute(m_last_lambda);
150 
151  const index_t d = m_param_values.rows();
152  const index_t n = m_points.cols();
153  T maxAng, avgAng;
154  std::vector<gsMatrix<T> > vals;
155  gsMatrix<T> DD, der;
156  for (index_t it = 0; it<maxIter; ++it)
157  {
158  maxAng = -1;
159  avgAng = 0;
160  //auto der = gsEigen::Map<typename gsMatrix<T>::Base, 0, gsEigen::Stride<-1,-1> >
161  //(vals[1].data()+k, n, m_points.rows(), gsEigen::Stride<-1,-1>(d*n,d) );
162 
163 # pragma omp parallel for default(shared) private(der,DD,vals)
164  for (index_t s = 0; s<m_points.rows(); ++s)
165  //for (index_t s = 1; s<m_points.rows()-1; ++s) //(! curve) skip first and last point
166  {
167  vals = m_result->evalAllDers(m_param_values.col(s), 1);
168  for (index_t k = 0; k<d; ++k)
169  {
170  der = vals[1].reshaped(d,n);
171  DD = vals[0].transpose() - m_points.row(s);
172  const T cv = ( DD.normalized() * der.row(k).transpose().normalized() ).value();
173  const T a = math::abs(0.5*EIGEN_PI-math::acos(cv));
174 # pragma omp critical (max_avg_ang)
175  {
176  maxAng = math::max(maxAng, a );
177  avgAng += a;
178  }
179  }
180  /*
181  auto der = gsEigen::Map<typename gsMatrix<T>::Base, 0, gsEigen::Stride<-1,-1> >
182  (vals[1].data()+k, n, m_points.rows(), gsEigen::Stride<-1,-1>(d*n,d) );
183  maxAng = ( DD.colwise().normalized() *
184  der.colwise().normalized().transpose()
185  ).array().acos().maxCoeff();
186  */
187  }
188 
189  avgAng /= d*m_points.rows();
190  //gsInfo << "Avg-deviation: "<< avgAng << " / max: "<<maxAng<<"\n";
191 
192  // if (math::abs(0.5*EIGEN_PI-maxAng) <= tolOrth ) break;
193 
194  gsVector<T> newParam;
195 # pragma omp parallel for default(shared) private(newParam)
196  for (index_t i = 0; i<m_points.rows(); ++i)
197  //for (index_t i = 1; i<m_points.rows()-1; ++i) //(!curve) skip first last pt
198  {
199  const auto & curr = m_points.row(i).transpose();
200  newParam = m_param_values.col(i);
201  m_result->closestPointTo(curr, newParam, accuracy, true);
202 
203  // Decide whether to accept the correction or to drop it
204  if ((m_result->eval(newParam) - curr).norm()
205  < (m_result->eval(m_param_values.col(i))
206  - curr).norm())
207  m_param_values.col(i) = newParam;
208 
209  // (!) There might be the same parameter for two points
210  // or ordering constraints in the case of structured/grid data
211  }
212 
213  // refit
214  compute(m_last_lambda);
215  }
216 }
217 
218 
219 template <class T>
221  gsMatrix<T>& m_B)
222 {
223  const int num_patches ( m_basis->nPieces() ); //initialize
224 
225  //for computing the value of the basis function
226  gsMatrix<T> value, curr_point;
227  gsMatrix<index_t> actives;
228 
229  for (index_t h = 0; h < num_patches; h++ )
230  {
231  auto & basis = m_basis->basis(h);
232 
233 //# pragma omp parallel for default(shared) private(curr_point,actives,value)
234  for (index_t k = m_offset[h]; k < m_offset[h+1]; ++k)
235  {
236  curr_point = m_param_values.col(k);
237 
238  //computing the values of the basis functions at the current point
239  basis.eval_into(curr_point, value);
240 
241  // which functions have been computed i.e. which are active
242  basis.active_into(curr_point, actives);
243 
244  const index_t numActive = actives.rows();
245 
246  for (index_t i = 0; i != numActive; ++i)
247  {
248  const index_t ii = actives.at(i);
249 //# pragma omp critical (acc_m_B)
250  m_B.row(ii) += value.at(i) * m_points.row(k);
251  for (index_t j = 0; j != numActive; ++j)
252 //# pragma omp critical (acc_A_mat)
253  A_mat(ii, actives.at(j)) += value.at(i) * value.at(j);
254  }
255  }
256  }
257 }
258 
259 template <class T>
261  gsMatrix<T>& m_B)
262 {
263  index_t basisSize = m_basis->size();
264 
265  // Idea: maybe we can resize here instead of doing it in compute().
266 
267  // This way does not work, as these block operations are read-only for sparse matrices.
268  /*A_mat.topRightCorner(m_constraintsLHS.cols(), m_constraintsLHS.rows()) = m_constraintsLHS.transpose();
269  A_mat.bottomLeftCorner(m_constraintsLHS.rows(), m_constraintsLHS.cols()) = m_constraintsLHS;*/
270  m_B.bottomRows(m_constraintsRHS.rows()) = m_constraintsRHS;
271 
272  for (index_t k=0; k<m_constraintsLHS.outerSize(); ++k)
273  {
274  for (typename gsSparseMatrix<T>::InnerIterator it(m_constraintsLHS,k); it; ++it)
275  {
276  A_mat(basisSize + it.row(), it.col()) = it.value();
277  A_mat(it.col(), basisSize + it.row()) = it.value();
278  }
279  }
280 }
281 
282 template<class T>
284 {
285  m_last_lambda = lambda;
286 
287  const int num_basis=m_basis->size();
288 
289  gsSparseMatrix<T> A_mat(num_basis + m_constraintsLHS.rows(), num_basis + m_constraintsLHS.rows());
290  int nonZerosPerCol = 1;
291  for (int i = 0; i < m_basis->domainDim(); ++i) // to do: improve
292  nonZerosPerCol *= ( 2 * m_basis->basis(0).degree(i) + 1 );
293  A_mat.reservePerColumn( nonZerosPerCol );
294  const_cast<gsFitting*>(this)->applySmoothing(lambda, A_mat);
295  return A_mat;
296 }
297 
298 template<class T>
300 {
301  m_last_lambda = lambda;
302  const int num_patches(m_basis->nPieces()); //initialize
303  const short_t dim(m_basis->domainDim());
304  const short_t stride = dim * (dim + 1) / 2;
305 
306  gsVector<index_t> numNodes(dim);
307  gsMatrix<T> quNodes, der2, localA;
308  gsVector<T> quWeights;
309  gsMatrix<index_t> actives;
310 
311 # ifdef _OPENMP
312  const int tid = omp_get_thread_num();
313  const int nt = omp_get_num_threads();
314 # endif
315 
316  for (index_t h = 0; h < num_patches; h++)
317  {
318  auto & basis = m_basis->basis(h);
319 
320  //gsDebugVar(dim);
321  //gsDebugVar(stride);
322 
323  for (short_t i = 0; i != dim; ++i)
324  {
325  numNodes[i] = basis.degree(i);//+1;
326  }
327 
328  gsGaussRule<T> QuRule(numNodes); // Reference Quadrature rule
329 
330  typename gsBasis<T>::domainIter domIt = basis.makeDomainIterator();
331 
332 
333 # ifdef _OPENMP
334  for ( domIt->next(tid); domIt->good(); domIt->next(nt) )
335 # else
336  for (; domIt->good(); domIt->next() )
337 # endif
338  {
339  // Map the Quadrature rule to the element and compute basis derivatives
340  QuRule.mapTo(domIt->lowerCorner(), domIt->upperCorner(), quNodes, quWeights);
341  basis.deriv2_into(quNodes, der2);
342  basis.active_into(domIt->center, actives);
343  const index_t numActive = actives.rows();
344  localA.setZero(numActive, numActive);
345 
346  // perform the quadrature
347  for (index_t k = 0; k < quWeights.rows(); ++k)
348  {
349  const T weight = quWeights[k] * lambda;
350 
351  for (index_t i = 0; i != numActive; ++i)
352  for (index_t j = 0; j != numActive; ++j)
353  {
354  T localAij = 0; // temporary variable
355 
356  for (short_t s = 0; s < stride; s++)
357  {
358  // The pure second derivatives
359  // d^2u N_i * d^2u N_j + ...
360  if (s < dim)
361  {
362  localAij += der2(i * stride + s, k) *
363  der2(j * stride + s, k);
364  }
365  // Mixed derivatives 2 * dudv N_i * dudv N_j + ...
366  else
367  {
368  localAij += T(2) * der2(i * stride + s, k) *
369  der2(j * stride + s, k);
370  }
371  }
372  localA(i, j) += weight * localAij;
373  }
374  }
375 
376  for (index_t i = 0; i != numActive; ++i)
377  {
378  const int ii = actives(i, 0);
379  for (index_t j = 0; j != numActive; ++j)
380  A_mat(ii, actives(j, 0)) += localA(i, j);
381  }
382  }
383 
384  }
385 }
386 
387 template<class T>
389 {
390  m_pointErrors.clear();
391 
392  gsMatrix<T> val_i;
393  //->eval_into(m_param_values.col(0), val_i);
394  m_result->eval_into(m_param_values, val_i);
395  m_pointErrors.push_back( (m_points.row(0) - val_i.col(0).transpose()).norm() );
396  m_max_error = m_min_error = m_pointErrors.back();
397 
398  for (index_t i = 1; i < m_points.rows(); i++)
399  {
400  //m_result->eval_into(m_param_values.col(i), val_i);
401 
402  const T err = (m_points.row(i) - val_i.col(i).transpose()).norm() ;
403 
404  m_pointErrors.push_back(err);
405 
406  if ( err > m_max_error ) m_max_error = err;
407  if ( err < m_min_error ) m_min_error = err;
408  }
409 }
410 
411 
412 template<class T>
414 {
415  m_pointErrors.clear();
416 
417  gsMatrix<T> values;
418  m_result->eval_into(m_param_values, values);
419 
420  for (index_t i = 0; i != m_points.rows(); i++)
421  {
422  const T err = (m_points.row(i) - values.col(i).transpose()).cwiseAbs().maxCoeff();
423 
424  m_pointErrors.push_back(err);
425 
426  if ( i == 0 || m_max_error < err ) m_max_error = err;
427  if ( i == 0 || err < m_min_error ) m_min_error = err;
428  }
429 }
430 
431 
432 
433 template<class T>
434 void gsFitting<T>::computeApproxError(T& error, int type) const
435 
436 {
437  gsMatrix<T> curr_point, results;
438 
439  const int num_patches(m_basis->nPieces());
440 
441  error = 0;
442 
443  for (index_t h = 0; h < num_patches; h++)
444  {
445 
446  for (index_t k = m_offset[h]; k < m_offset[h + 1]; ++k)
447  {
448  curr_point = m_param_values.col(k);
449 
450  if (m_result)
451  m_result->eval_into(curr_point, results);
452  else
453  {
454  m_mresult.eval_into(h, curr_point, results);
455  }
456 
457  const T err = (m_points.row(k) - results.transpose()).squaredNorm();
458 
459  switch (type) {
460  case 0:
461  error += err;
462  break;
463  case 1:
464  error += math::sqrt(err);
465  break;
466  default:
467  gsWarn << "Unknown type in computeApproxError(error, type)...\n";
468  break;
469  }
470  }
471  }
472 }
473 
474 template<class T>
475 void gsFitting<T>::get_Error(std::vector<T>& errors, int type) const
476 {
477  errors.clear();
478  errors.reserve(m_points.rows());
479 
480  gsMatrix<T> curr_point, results;
481 
482  T err = 0;
483 
484  const int num_patches(m_basis->nPieces());
485 
486  for (index_t h = 0; h < num_patches; h++)
487  {
488  for (index_t k = m_offset[h]; k < m_offset[h + 1]; ++k)
489  {
490  curr_point = m_param_values.col(k);
491 
492  if (m_result)
493  m_result->eval_into(curr_point, results);
494  else
495  {
496  m_mresult.eval_into(h, curr_point, results);
497  }
498 
499  results.transposeInPlace();
500 
501  err = (m_points.row(k) - results).template lpNorm<gsEigen::Infinity>();
502 
503  switch (type)
504  {
505  case 0:
506  errors.push_back(err);
507  break;
508  case 1:
509  errors.push_back(math::sqrt(err));
510  break;
511  default:
512  gsWarn << "Unknown type in get_Error(errors, type)...\n";
513  break;
514  }
515  }
516  }
517 }
518 
519 template<class T>
520 void gsFitting<T>::setConstraints(const std::vector<index_t>& indices,
521  const std::vector<gsMatrix<T> >& coefs)
522 {
523  if(coefs.size() == 0)
524  return;
525 
526  GISMO_ASSERT(indices.size() == coefs.size(),
527  "Prescribed indices and coefs must have the same length.");
528 
529  gsSparseMatrix<T> lhs(indices.size(), m_basis->size());
530  gsMatrix<T> rhs(indices.size(), coefs[0].cols());
531 
532  index_t duplicates = 0;
533  for(size_t r=0; r<indices.size(); r++)
534  {
535  index_t fix = indices[r];
536  lhs(r-duplicates, fix) = 1;
537  rhs.row(r-duplicates) = coefs[r];
538  }
539 
540  setConstraints(lhs, rhs);
541 }
542 
543 template<class T>
544 void gsFitting<T>::setConstraints(const std::vector<boxSide>& fixedSides)
545 {
546  if(fixedSides.size() == 0)
547  return;
548 
549  std::vector<index_t> indices;
550  std::vector<gsMatrix<T> > coefs;
551 
552  for(std::vector<boxSide>::const_iterator it=fixedSides.begin(); it!=fixedSides.end(); ++it)
553  {
554  gsMatrix<index_t> ind = this->m_basis->basis(0).boundary(*it);
555  for(index_t r=0; r<ind.rows(); r++)
556  {
557  index_t fix = ind(r,0);
558  // If it is a new constraint, add it.
559  if(std::find(indices.begin(), indices.end(), fix) == indices.end())
560  {
561  indices.push_back(fix);
562  coefs.push_back(this->m_result->coef(fix));
563  }
564  }
565  }
566  setConstraints(indices, coefs);
567 }
568 
569 template<class T>
570 void gsFitting<T>::setConstraints(const std::vector<boxSide>& fixedSides,
571  const std::vector<gsBSpline<T> >& fixedCurves)
572 {
573  std::vector<gsBSpline<T> > tmp = fixedCurves;
574  std::vector<gsGeometry<T> *> fixedCurves_input(tmp.size());
575  for (size_t k=0; k!=fixedCurves.size(); k++)
576  fixedCurves_input[k] = dynamic_cast<gsGeometry<T> *>(&(tmp[k]));
577  setConstraints(fixedSides, fixedCurves_input);
578 }
579 
580 template<class T>
581 void gsFitting<T>::setConstraints(const std::vector<boxSide>& fixedSides,
582  const std::vector<gsGeometry<T> * >& fixedCurves)
583 {
584  if(fixedSides.size() == 0)
585  return;
586 
587  GISMO_ASSERT(fixedCurves.size() == fixedSides.size(),
588  "fixedCurves and fixedSides are of different sizes.");
589 
590  std::vector<index_t> indices;
591  std::vector<gsMatrix<T> > coefs;
592  for(size_t s=0; s<fixedSides.size(); s++)
593  {
594  gsMatrix<T> coefsThisSide = fixedCurves[s]->coefs();
595  gsMatrix<index_t> indicesThisSide = m_basis->basis(0).boundaryOffset(fixedSides[s],0);
596  GISMO_ASSERT(coefsThisSide.rows() == indicesThisSide.rows(),
597  "Coef number mismatch between prescribed curve and basis side.");
598 
599  for(index_t r=0; r<indicesThisSide.rows(); r++)
600  {
601  index_t fix = indicesThisSide(r,0);
602  // If it is a new constraint, add it.
603  if(std::find(indices.begin(), indices.end(), fix) == indices.end())
604  {
605  indices.push_back(fix);
606  coefs.push_back(coefsThisSide.row(r));
607  }
608  }
609  }
610 
611  setConstraints(indices, coefs);
612 }
613 
614 
615 } // namespace gismo
Iterator over the elements of a tensor-structured grid.
Abstract base class representing a geometry map.
Definition: gsGeometry.h:92
void computeApproxError(T &error, int type=0) const
Computes the approximation error of the fitted curve to the original point cloud. ...
Definition: gsFitting.hpp:434
void setConstraints(const gsSparseMatrix< T > &lhs, const gsMatrix< T > &rhs)
Definition: gsFitting.h:133
virtual ~gsFitting()
Destructor.
Definition: gsFitting.hpp:28
void applySmoothing(T lambda, gsSparseMatrix< T > &A_mat)
Definition: gsFitting.hpp:299
#define short_t
Definition: gsConfig.h:35
void computeMaxNormErrors()
Computes the maximum norm error for each point.
Definition: gsFitting.hpp:413
void computeErrors()
Computes the euclidean error for each point.
Definition: gsFitting.hpp:388
Provides declaration of Basis abstract interface.
S give(S &x)
Definition: gsMemory.h:266
Provides declaration of Geometry abstract interface.
#define index_t
Definition: gsConfig.h:32
#define GISMO_ASSERT(cond, message)
Definition: gsDebug.h:89
A B-spline function of one argument, with arbitrary target dimension.
Definition: gsBSpline.h:50
void get_Error(std::vector< T > &errors, int type=0) const
return the errors for each point
Definition: gsFitting.hpp:475
void assembleSystem(gsSparseMatrix< T > &A_mat, gsMatrix< T > &B)
Assembles system for the least square fit.
Definition: gsFitting.hpp:220
Class for performing a least squares fit of a parametrized point cloud with a gsGeometry.
Definition: gsFitting.h:32
T at(index_t i) const
Returns the i-th element of the vectorization of the matrix.
Definition: gsMatrix.h:211
#define gsWarn
Definition: gsDebug.h:50
gsFitting()
default constructor
Definition: gsFitting.h:36
virtual short_t domainDim() const =0
Dimension of the (source) domain.
virtual void mapTo(const gsVector< T > &lower, const gsVector< T > &upper, gsMatrix< T > &nodes, gsVector< T > &weights) const
Maps quadrature rule (i.e., points and weights) from the reference domain to an element.
Definition: gsQuadRule.h:177
Represents a B-spline curve/function with one parameter.
void extendSystem(gsSparseMatrix< T > &A_mat, gsMatrix< T > &m_B)
Extends the system of equations by taking constraints into account.
Definition: gsFitting.hpp:260
This is the main header file that collects wrappers of Eigen for linear algebra.
EIGEN_STRONG_INLINE abs_expr< E > abs(const E &u)
Absolute value.
Definition: gsExpressions.h:4488
Class that represents the (tensor) Gauss-Legendre quadrature rule.
Definition: gsGaussRule.h:27
A basis represents a family of scalar basis functions defined over a common parameter domain...
Definition: gsBasis.h:78
void compute(T lambda=0)
Computes the least squares fit for a gsBasis.
Definition: gsFitting.hpp:68