PRAgMaTIc  master
Smooth.h
Go to the documentation of this file.
1 /* Copyright (C) 2010 Imperial College London and others.
2  *
3  * Please see the AUTHORS file in the main source directory for a
4  * full list of copyright holders.
5  *
6  * Gerard Gorman
7  * Applied Modelling and Computation Group
8  * Department of Earth Science and Engineering
9  * Imperial College London
10  *
11  * g.gorman@imperial.ac.uk
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  * notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above
19  * copyright notice, this list of conditions and the following
20  * disclaimer in the documentation and/or other materials provided
21  * with the distribution.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
26  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
28  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
30  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
32  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
33  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
34  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #ifndef SMOOTH_H
39 #define SMOOTH_H
40 
41 #include <algorithm>
42 #include <cmath>
43 #include <set>
44 #include <map>
45 #include <vector>
46 #include <limits>
47 #include <random>
48 
49 #include "Colour.h"
50 
51 #include <Eigen/Core>
52 #include <Eigen/Dense>
53 #include <errno.h>
54 
55 #ifdef _OPENMP
56 #include <omp.h>
57 #endif
58 
59 #include "ElementProperty.h"
60 #include "Mesh.h"
61 #include "MetricTensor.h"
62 
63 
66 template<typename real_t, int dim>
67  class Smooth{
68  public:
70  Smooth(Mesh<real_t> &mesh):nloc(dim+1), msize(dim==2?3:6){
71  _mesh = &mesh;
72 
73  mpi_nparts = 1;
74  rank=0;
75 #ifdef HAVE_MPI
76  MPI_Comm_size(_mesh->get_mpi_comm(), &mpi_nparts);
77  MPI_Comm_rank(_mesh->get_mpi_comm(), &rank);
78 #endif
79 
80  epsilon_q = DBL_EPSILON;
81 
82  // Set the orientation of elements.
83  property = NULL;
84  int NElements = _mesh->get_number_elements();
85  for(int i=0;i<NElements;i++){
86  const int *n=_mesh->get_element(i);
87  if(n[0]<0)
88  continue;
89 
90  if(dim==2){
91  property = new ElementProperty<real_t>(_mesh->get_coords(n[0]),
92  _mesh->get_coords(n[1]),
93  _mesh->get_coords(n[2]));
94  }else{
95  property = new ElementProperty<real_t>(_mesh->get_coords(n[0]),
96  _mesh->get_coords(n[1]),
97  _mesh->get_coords(n[2]),
98  _mesh->get_coords(n[3]));
99  }
100 
101  break;
102  }
103  }
104 
107  delete property;
108  }
109 
110  // Smart laplacian mesh smoothing.
111  void smart_laplacian(int max_iterations=10, double quality_tol=-1.0){
112  // Calculate all element qualities
113  int NElements = _mesh->get_number_elements();
114  quality.resize(NElements);
115 
116  double qsum=0;
117 #pragma omp parallel
118  {
119 #pragma omp for schedule(guided) reduction(+:qsum)
120  for(int i=0;i<NElements;i++){
121  const int *n=_mesh->get_element(i);
122  if(n[0]<0){
123  quality[i] = 1.0;
124  continue;
125  }
126 
127  update_quality(i);
128  qsum+=quality[i];
129  }
130  }
131  good_q = qsum/NElements;
132 
133  if(quality_tol>0)
134  good_q = quality_tol;
135 
136  init_cache();
137 
138  std::vector<int> halo_elements;
139  if(mpi_nparts>1){
140  for(int i=0;i<NElements;i++){
141  const int *n=_mesh->get_element(i);
142  if(n[0]<0)
143  continue;
144 
145  for(size_t j=0;j<nloc;j++){
146  if(!_mesh->is_owned_node(n[j])){
147  halo_elements.push_back(i);
148  break;
149  }
150  }
151  }
152  }
153 
154  // Use this to keep track of vertices that are still to be visited.
155  int NNodes = _mesh->get_number_nodes();
156  std::vector<int> active_vertices(NNodes, 0);
157 
158  // Find the maximum colour across partitions
159  int max_colour = 0;
160  if(!colour_sets.empty())
161  max_colour = colour_sets.rbegin()->first;
162 #ifdef HAVE_MPI
163  if(mpi_nparts>1){
164  MPI_Allreduce(MPI_IN_PLACE, &max_colour, 1, MPI_INT, MPI_MAX, _mesh->get_mpi_comm());
165  }
166 #endif
167 
168  // First sweep through all vertices. Add vertices adjacent to any
169  // vertex moved into the active_vertex list.
170 #pragma omp parallel
171  {
172  for(int ic=0;ic<=max_colour;ic++){
173  if(colour_sets.count(ic)){
174  int node_set_size = colour_sets[ic].size();
175 #pragma omp for schedule(guided)
176  for(int cn=0;cn<node_set_size;cn++){
177  index_t node = colour_sets[ic][cn];
178  active_vertices[node] = 0;
179 
180  if(smart_laplacian_kernel(node)){
181  active_vertices[node] = 1;
182 
183  for(typename std::vector<index_t>::const_iterator it=_mesh->NNList[node].begin();it!=_mesh->NNList[node].end();++it){
184  active_vertices[*it] = 1;
185  }
186  }
187  }
188  }
189 
190  if(mpi_nparts>1){
191 #pragma omp single
192  {
193  halo_update<real_t, dim, dim==2?3:6>(_mesh->get_mpi_comm(), _mesh->send, _mesh->recv, _mesh->_coords, _mesh->metric);
194 
195  for(std::vector<int>::const_iterator ie=halo_elements.begin();ie!=halo_elements.end();++ie)
196  update_quality(*ie);
197  }
198  }
199  }
200 
201  for(int iter=1;iter<max_iterations;iter++){
202  for(int ic=0;ic<=max_colour;ic++){
203  if(colour_sets.count(ic)){
204  int node_set_size = colour_sets[ic].size();
205 #pragma omp for schedule(guided)
206  for(int cn=0;cn<node_set_size;cn++){
207  index_t node = colour_sets[ic][cn];
208 
209  // Only process if it is active.
210  if(active_vertices[node]){
211  active_vertices[node] = 0;
212 
213  if(smart_laplacian_kernel(node)){
214  active_vertices[node] = 1;
215 
216  for(typename std::vector<index_t>::const_iterator it=_mesh->NNList[node].begin();it!=_mesh->NNList[node].end();++it){
217  active_vertices[*it] = 1;
218  }
219  }
220  }
221  }
222  }
223  if(mpi_nparts>1){
224 #pragma omp single
225  {
226  halo_update<real_t, dim, dim==2?3:6>(_mesh->get_mpi_comm(), _mesh->send, _mesh->recv, _mesh->_coords, _mesh->metric);
227 
228  for(std::vector<int>::const_iterator ie=halo_elements.begin();ie!=halo_elements.end();++ie)
229  update_quality(*ie);
230  }
231  }
232  }
233  }
234  }
235 
236  return;
237  }
238 
239  // Linf optimisation based smoothing..
240  void optimisation_linf(int max_iterations=10, double quality_tol=-1.0){
241  // Calculate all element qualities
242  int NElements = _mesh->get_number_elements();
243  quality.resize(NElements);
244 
245  double qsum=0;
246 #pragma omp parallel
247  {
248 #pragma omp for schedule(guided) reduction(+:qsum)
249  for(int i=0;i<NElements;i++){
250  const int *n=_mesh->get_element(i);
251  if(n[0]<0){
252  quality[i] = 1.0;
253  continue;
254  }
255 
256  update_quality(i);
257  qsum+=quality[i];
258  }
259  }
260  good_q = qsum/NElements;
261 
262  if(quality_tol>0)
263  good_q = quality_tol;
264 
265  init_cache();
266 
267  std::vector<int> halo_elements;
268  if(mpi_nparts>1){
269  for(int i=0;i<NElements;i++){
270  const int *n=_mesh->get_element(i);
271  if(n[0]<0)
272  continue;
273 
274  for(size_t j=0;j<nloc;j++){
275  if(!_mesh->is_owned_node(n[j])){
276  halo_elements.push_back(i);
277  break;
278  }
279  }
280  }
281  }
282 
283  // Use this to keep track of vertices that are still to be visited.
284  int NNodes = _mesh->get_number_nodes();
285  std::vector<int> active_vertices(NNodes, 0);
286 
287  // First sweep through all vertices. Add vertices adjacent to any
288  // vertex moved into the active_vertex list.
289  int max_colour = 0;
290  if(!colour_sets.empty())
291  max_colour = colour_sets.rbegin()->first;
292 #ifdef HAVE_MPI
293  if(mpi_nparts>1){
294  MPI_Allreduce(MPI_IN_PLACE, &max_colour, 1, MPI_INT, MPI_MAX, _mesh->get_mpi_comm());
295  }
296 #endif
297 
298 #pragma omp parallel
299  {
300  for(int ic=1;ic<=max_colour;ic++){
301  if(colour_sets.count(ic)){
302  int node_set_size = colour_sets[ic].size();
303 #pragma omp for schedule(guided)
304  for(int cn=0;cn<node_set_size;cn++){
305  index_t node = colour_sets[ic][cn];
306  active_vertices[node] = 0;
307 
308  if(optimisation_linf_kernel(node)){
309  active_vertices[node] = 1;
310 
311  for(typename std::vector<index_t>::const_iterator it=_mesh->NNList[node].begin();it!=_mesh->NNList[node].end();++it){
312  active_vertices[*it] = 1;
313  }
314  }
315  }
316  }
317 
318  if(mpi_nparts>1){
319 #pragma omp single
320  {
321  halo_update<real_t, dim, dim==2?3:6>(_mesh->get_mpi_comm(), _mesh->send, _mesh->recv, _mesh->_coords, _mesh->metric);
322 
323  for(std::vector<int>::const_iterator ie=halo_elements.begin();ie!=halo_elements.end();++ie)
324  update_quality(*ie);
325  }
326  }
327  }
328 
329  for(int iter=1;iter<max_iterations;iter++){
330  for(int ic=1;ic<=max_colour;ic++){
331  if(colour_sets.count(ic)){
332  int node_set_size = colour_sets[ic].size();
333 #pragma omp for schedule(guided)
334  for(int cn=0;cn<node_set_size;cn++){
335  index_t node = colour_sets[ic][cn];
336 
337  // Only process if it is active.
338  if(active_vertices[node]){
339  active_vertices[node] = 0;
340 
341  if(optimisation_linf_kernel(node)){
342  active_vertices[node] = 1;
343 
344  for(typename std::vector<index_t>::const_iterator it=_mesh->NNList[node].begin();it!=_mesh->NNList[node].end();++it){
345  active_vertices[*it] = 1;
346  }
347  }
348  }
349  }
350  }
351  if(mpi_nparts>1){
352 #pragma omp single
353  {
354  halo_update<real_t, dim, dim==2?3:6>(_mesh->get_mpi_comm(), _mesh->send, _mesh->recv, _mesh->_coords, _mesh->metric);
355 
356  for(std::vector<int>::const_iterator ie=halo_elements.begin();ie!=halo_elements.end();++ie)
357  update_quality(*ie);
358  }
359  }
360  }
361  }
362  }
363 
364  return;
365  }
366 
367  // Laplacian smoothing
368  void laplacian(int max_iterations=10){
369  init_cache();
370 
371  std::vector<int> halo_elements;
372  int NElements = _mesh->get_number_elements();
373  if(mpi_nparts>1){
374  for(int i=0;i<NElements;i++){
375  const int *n=_mesh->get_element(i);
376  if(n[0]<0)
377  continue;
378 
379  for(size_t j=0;j<nloc;j++){
380  if(!_mesh->is_owned_node(n[j])){
381  halo_elements.push_back(i);
382  break;
383  }
384  }
385  }
386  }
387 
388  // Sweep through all vertices.
389  int max_colour = 0;
390  if(!colour_sets.empty())
391  max_colour = colour_sets.rbegin()->first;
392 #ifdef HAVE_MPI
393  if(mpi_nparts>1){
394  MPI_Allreduce(MPI_IN_PLACE, &max_colour, 1, MPI_INT, MPI_MAX, _mesh->get_mpi_comm());
395  }
396 #endif
397 
398 #pragma omp parallel
399  {
400  for(int iter=1;iter<max_iterations;iter++){
401  for(int ic=1;ic<=max_colour;ic++){
402  if(colour_sets.count(ic)){
403  int node_set_size = colour_sets[ic].size();
404 #pragma omp for schedule(guided)
405  for(int cn=0;cn<node_set_size;cn++){
406  index_t node = colour_sets[ic][cn];
407 
408  laplacian_kernel(node);
409  }
410  }
411  if(mpi_nparts>1){
412 #pragma omp single
413  {
414  halo_update<real_t, dim, dim==2?3:6>(_mesh->get_mpi_comm(), _mesh->send, _mesh->recv, _mesh->_coords, _mesh->metric);
415  }
416  }
417  }
418  }
419  }
420 
421  return;
422  }
423 
424  private:
425 
426  // Laplacian smooth kernels
427  inline bool laplacian_kernel(index_t node){
428  bool update;
429  if(dim==2)
430  update = laplacian_2d_kernel(node);
431  else
432  update = laplacian_3d_kernel(node);
433 
434  return update;
435  }
436 
437  inline bool laplacian_2d_kernel(index_t node){
438  real_t p[2];
439  laplacian_2d_kernel(node, p);
440 
441  double mp[3];
442  bool valid = generate_location_2d(node, p, mp);
443  if(!valid){
444  // Try the mid point.
445  for(size_t j=0;j<2;j++)
446  p[j] = 0.5*(p[j] + _mesh->_coords[node*2+j]);
447 
448  valid = generate_location_2d(node, p, mp);
449  }
450 
451  // Give up
452  if(!valid)
453  return false;
454 
455  for(size_t j=0;j<2;j++)
456  _mesh->_coords[node*2+j] = p[j];
457 
458  for(size_t j=0;j<3;j++)
459  _mesh->metric[node*3+j] = mp[j];
460 
461  return true;
462  }
463 
464  inline bool laplacian_3d_kernel(index_t node){
465  real_t p[3];
466  laplacian_3d_kernel(node, p);
467 
468  double mp[6];
469  bool valid = generate_location_3d(node, p, mp);
470  if(!valid){
471  // Try the mid point.
472  for(size_t j=0;j<3;j++)
473  p[j] = 0.5*(p[j] + _mesh->_coords[node*3+j]);
474 
475  valid = generate_location_3d(node, p, mp);
476  }
477  if(!valid)
478  return false;
479 
480  for(size_t j=0;j<3;j++)
481  _mesh->_coords[node*3+j] = p[j];
482 
483  for(size_t j=0;j<6;j++)
484  _mesh->metric[node*6+j] = mp[j];
485 
486  return true;
487  }
488 
489  inline void laplacian_2d_kernel(index_t node, real_t *p){
490  std::set<index_t> patch(_mesh->get_node_patch(node));
491 
492  real_t x0 = get_x(node);
493  real_t y0 = get_y(node);
494 
495  Eigen::Matrix<real_t, Eigen::Dynamic, Eigen::Dynamic> A = Eigen::Matrix<real_t, Eigen::Dynamic, Eigen::Dynamic>::Zero(2, 2);
496  Eigen::Matrix<real_t, Eigen::Dynamic, 1> q = Eigen::Matrix<real_t, Eigen::Dynamic, 1>::Zero(2);
497 
498  const real_t *m0 = _mesh->get_metric(node);
499  for(typename std::set<index_t>::const_iterator il=patch.begin();il!=patch.end();++il){
500  real_t x = get_x(*il)-x0;
501  real_t y = get_y(*il)-y0;
502 
503  const real_t *m1 = _mesh->get_metric(*il);
504  double m[] = {0.5*(m0[0]+m1[0]), 0.5*(m0[1]+m1[1]), 0.5*(m0[2]+m1[2])};
505 
506  q[0] += (m[0]*x + m[1]*y);
507  q[1] += (m[1]*x + m[2]*y);
508 
509  A[0] += m[0]; A[1] += m[1];
510  A[3] += m[2];
511  }
512  A[2]=A[1];
513 
514  // Want to solve the system Ap=q to find the new position, p.
515  Eigen::Matrix<real_t, Eigen::Dynamic, 1> b = Eigen::Matrix<real_t, Eigen::Dynamic, 1>::Zero(2);
516  A.svd().solve(q, &b);
517 
518  for(size_t i=0;i<2;i++)
519  p[i] = b[i];
520 
521  p[0] += x0;
522  p[1] += y0;
523 
524  return;
525  }
526 
527  inline void laplacian_3d_kernel(index_t node, real_t *p){
528  std::set<index_t> patch(_mesh->get_node_patch(node));
529 
530  real_t x0 = get_x(node);
531  real_t y0 = get_y(node);
532  real_t z0 = get_z(node);
533 
534  Eigen::Matrix<real_t, Eigen::Dynamic, Eigen::Dynamic> A = Eigen::Matrix<real_t, Eigen::Dynamic, Eigen::Dynamic>::Zero(3, 3);
535  Eigen::Matrix<real_t, Eigen::Dynamic, 1> q = Eigen::Matrix<real_t, Eigen::Dynamic, 1>::Zero(3);
536 
537  const real_t *m0 = _mesh->get_metric(node);
538  for(typename std::set<index_t>::const_iterator il=patch.begin();il!=patch.end();++il){
539  real_t x = get_x(*il)-x0;
540  real_t y = get_y(*il)-y0;
541  real_t z = get_z(*il)-z0;
542 
543  const real_t *m1 = _mesh->get_metric(*il);
544  double m[] = {0.5*(m0[0]+m1[0]), 0.5*(m0[1]+m1[1]), 0.5*(m0[2]+m1[2]),
545  0.5*(m0[3]+m1[3]), 0.5*(m0[4]+m1[4]),
546  0.5*(m0[5]+m1[5])};
547 
548  q[0] += m[0]*x + m[1]*y + m[2]*z;
549  q[1] += m[1]*x + m[3]*y + m[4]*z;
550  q[2] += m[2]*x + m[4]*y + m[5]*z;
551 
552  A[0] += m[0]; A[1] += m[1]; A[2] += m[2];
553  A[4] += m[3]; A[5] += m[4];
554  A[8] += m[5];
555  }
556  A[3] = A[1];
557  A[6] = A[2];
558  A[7] = A[5];
559 
560  // Want to solve the system Ap=q to find the new position, p.
561  Eigen::Matrix<real_t, Eigen::Dynamic, 1> b = Eigen::Matrix<real_t, Eigen::Dynamic, 1>::Zero(3);
562  A.svd().solve(q, &b);
563 
564  for(int i=0;i<3;i++)
565  p[i] = b[i];
566 
567  p[0] += x0;
568  p[1] += y0;
569  p[2] += z0;
570 
571  return;
572  }
573 
574  // Smart Laplacian kernels
575  inline bool smart_laplacian_kernel(index_t node){
576  bool update;
577  if(dim==2)
578  update = smart_laplacian_2d_kernel(node);
579  else
580  update = smart_laplacian_3d_kernel(node);
581 
582  return update;
583  }
584 
585  bool smart_laplacian_2d_kernel(index_t node){
586  real_t p[2];
587  laplacian_2d_kernel(node, p);
588 
589  double mp[3];
590  bool valid = generate_location_2d(node, p, mp);
591  if(!valid){
592  // Try the mid point.
593  for(size_t j=0;j<2;j++)
594  p[j] = 0.5*(p[j] + _mesh->_coords[node*2+j]);
595 
596  valid = generate_location_2d(node, p, mp);
597  }
598 
599  // Give up
600  if(!valid)
601  return false;
602 
603  real_t functional = functional_Linf(node, p, mp);
604  real_t functional_orig = functional_Linf(node);
605 
606  if(functional-functional_orig<epsilon_q)
607  return false;
608 
609  for(size_t j=0;j<2;j++)
610  _mesh->_coords[node*2+j] = p[j];
611 
612  for(size_t j=0;j<3;j++)
613  _mesh->metric[node*3+j] = mp[j];
614 
615  return true;
616  }
617 
618  inline bool smart_laplacian_3d_kernel(index_t node){
619  real_t p[3];
620  laplacian_3d_kernel(node, p);
621 
622  double mp[6];
623  bool valid = generate_location_3d(node, p, mp);
624  if(!valid){
625  // Try the mid point.
626  for(size_t j=0;j<3;j++)
627  p[j] = 0.5*(p[j] + _mesh->_coords[node*2+j]);
628 
629  valid = generate_location_3d(node, p, mp);
630  }
631 
632  // Give up
633  if(!valid)
634  return false;
635 
636  real_t functional = functional_Linf(node, p, mp);
637  real_t functional_orig = functional_Linf(node);
638 
639  if(functional-functional_orig<epsilon_q)
640  return false;
641 
642  for(size_t j=0;j<3;j++)
643  _mesh->_coords[node*3+j] = p[j];
644 
645  for(size_t j=0;j<6;j++)
646  _mesh->metric[node*6+j] = mp[j];
647 
648  return true;
649  }
650 
651  // l-infinity optimisation kernels
652  inline bool optimisation_linf_kernel(index_t node){
653  bool update;
654  if(dim==2)
655  update = optimisation_linf_2d_kernel(node);
656  else
657  update = optimisation_linf_3d_kernel(node);
658 
659  return update;
660 
661  }
662 
663  inline bool optimisation_linf_2d_kernel(index_t n0){
664  const double *m0 = _mesh->get_metric(n0);
665  const double *x0 = _mesh->get_coords(n0);
666 
667  // Find the worst element.
668  std::pair<double, index_t> worst_element(DBL_MAX, -1);
669  for(typename std::set<index_t>::const_iterator it=_mesh->NEList[n0].begin();it!=_mesh->NEList[n0].end();++it){
670  if(quality[*it]<worst_element.first)
671  worst_element = std::pair<double, index_t>(quality[*it], *it);
672  }
673  assert(worst_element.second!=-1);
674 
675  // Return if it is already "good enough".
676  if(worst_element.first>good_q)
677  return false;
678 
679  // Find direction of steepest ascent for quality of worst element.
680  double search[2], grad_w[2];
681  {
682  const index_t *n=_mesh->get_element(worst_element.second);
683  size_t loc=0;
684  for(;loc<3;loc++)
685  if(n[loc]==n0)
686  break;
687 
688  int n1 = n[(loc+1)%3];
689  int n2 = n[(loc+2)%3];
690 
691  const double *x1 = _mesh->get_coords(n1);
692  const double *x2 = _mesh->get_coords(n2);
693 
694  property->lipnikov_grad(loc, x0, x1, x2, m0, grad_w);
695 
696  double mag = sqrt(grad_w[0]*grad_w[0]+grad_w[1]*grad_w[1]);
697  assert(mag!=0);
698 
699  for(int i=0;i<2;i++)
700  search[i] = grad_w[i]/mag;
701  }
702 
703  // Estimate how far we move along this search path until we make
704  // another element of a similar quality to the current worst. This
705  // is effectively a simplex method for linear programming.
706  double alpha;
707  {
708  double bbox[] = {DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX};
709  for(typename std::vector<index_t>::const_iterator it=_mesh->NNList[n0].begin();it!=_mesh->NNList[n0].end();++it){
710  const double *x1 = _mesh->get_coords(*it);
711 
712  bbox[0] = std::min(bbox[0], x1[0]);
713  bbox[1] = std::max(bbox[1], x1[0]);
714 
715  bbox[2] = std::min(bbox[2], x1[1]);
716  bbox[3] = std::max(bbox[3], x1[1]);
717  }
718  alpha = (bbox[1]-bbox[0] + bbox[3]-bbox[2])/2.0;
719  }
720 
721  for(typename std::set<index_t>::const_iterator it=_mesh->NEList[n0].begin();it!=_mesh->NEList[n0].end();++it){
722  if(*it==worst_element.second)
723  continue;
724 
725  const index_t *n=_mesh->get_element(*it);
726  size_t loc=0;
727  for(;loc<3;loc++)
728  if(n[loc]==n0)
729  break;
730 
731  int n1 = n[(loc+1)%3];
732  int n2 = n[(loc+2)%3];
733 
734  const double *x1 = _mesh->get_coords(n1);
735  const double *x2 = _mesh->get_coords(n2);
736 
737  double grad[2];
738  property->lipnikov_grad(loc, x0, x1, x2, m0, grad);
739 
740  double new_alpha =
741  (quality[*it]-worst_element.first)/
742  ((search[0]*grad_w[0]+search[1]*grad_w[1])-
743  (search[0]*grad[0]+search[1]*grad[1]));
744 
745  if(new_alpha>0)
746  alpha = std::min(alpha, new_alpha);
747  }
748 
749  bool linf_update;
750  for(int isearch=0;isearch<10;isearch++){
751  linf_update = false;
752 
753  // Only want to step half that distance so we do not degrade the other elements too much.
754  alpha*=0.5;
755 
756  double new_x0[2];
757  for(int i=0;i<2;i++){
758  new_x0[i] = x0[i] + alpha*search[i];
759  if(!std::isnormal(new_x0[i]))
760  return false;
761  }
762 
763  double new_m0[3];
764  bool valid = generate_location_2d(n0, new_x0, new_m0);
765 
766  if(!valid)
767  continue;
768 
769  // Need to check that we have not decreased the Linf norm. Start by assuming the best.
770  linf_update = true;
771  std::vector<double> new_quality;
772  for(typename std::set<index_t>::const_iterator it=_mesh->NEList[n0].begin();it!=_mesh->NEList[n0].end();++it){
773  const index_t *n=_mesh->get_element(*it);
774  size_t loc=0;
775  for(;loc<3;loc++)
776  if(n[loc]==n0)
777  break;
778 
779  int n1 = n[(loc+1)%3];
780  int n2 = n[(loc+2)%3];
781 
782  const double *x1 = _mesh->get_coords(n1);
783  const double *x2 = _mesh->get_coords(n2);
784 
785  const double *m1 = _mesh->get_metric(n1);
786  const double *m2 = _mesh->get_metric(n2);
787 
788  double new_q = property->lipnikov(new_x0, x1, x2, new_m0, m1, m2);
789  new_quality.push_back(new_q);
790 
791  if(!std::isnormal(new_q) || new_quality.back()<worst_element.first){
792  linf_update = false;
793  break;
794  }
795  }
796 
797  if(!linf_update)
798  continue;
799 
800  // Update information
801  // go backwards and pop quality
802  assert(_mesh->NEList[n0].size()==new_quality.size());
803  for(typename std::set<index_t>::const_reverse_iterator it=_mesh->NEList[n0].rbegin();it!=_mesh->NEList[n0].rend();++it){
804  quality[*it] = new_quality.back();
805  new_quality.pop_back();
806  }
807  assert(new_quality.empty());
808 
809  for(size_t i=0;i<dim;i++)
810  _mesh->_coords[n0*dim+i] = new_x0[i];
811 
812  for(size_t i=0;i<msize;i++)
813  _mesh->metric[n0*msize+i] = new_m0[i];
814 
815  break;
816  }
817 
818  return linf_update;
819  }
820 
821  inline bool optimisation_linf_3d_kernel(index_t n0){
822  const double *m0 = _mesh->get_metric(n0);
823  const double *x0 = _mesh->get_coords(n0);
824 
825  // Find the worst element.
826  std::pair<double, index_t> worst_element(DBL_MAX, -1);
827  for(typename std::set<index_t>::const_iterator it=_mesh->NEList[n0].begin();it!=_mesh->NEList[n0].end();++it){
828  if(quality[*it]<worst_element.first)
829  worst_element = std::pair<double, index_t>(quality[*it], *it);
830  }
831  assert(worst_element.second!=-1);
832 
833  // Jump out if already good enough.
834  if(worst_element.first>good_q)
835  return false;
836 
837  // Find direction of steepest ascent for quality of worst element.
838  double grad_w[3], search[3];
839  {
840  const index_t *n=_mesh->get_element(worst_element.second);
841  size_t loc=0;
842  for(;loc<4;loc++)
843  if(n[loc]==n0)
844  break;
845 
846  int n1, n2, n3;
847  switch(loc){
848  case 0:
849  n1 = n[1];
850  n2 = n[2];
851  n3 = n[3];
852  break;
853  case 1:
854  n1 = n[2];
855  n2 = n[0];
856  n3 = n[3];
857  break;
858  case 2:
859  n1 = n[0];
860  n2 = n[1];
861  n3 = n[3];
862  break;
863  case 3:
864  n1 = n[0];
865  n2 = n[2];
866  n3 = n[1];
867  break;
868  }
869 
870  const double *x1 = _mesh->get_coords(n1);
871  const double *x2 = _mesh->get_coords(n2);
872  const double *x3 = _mesh->get_coords(n3);
873 
874  property->lipnikov_grad(loc, x0, x1, x2, x3, m0, grad_w);
875 
876  double mag = sqrt(grad_w[0]*grad_w[0] + grad_w[1]*grad_w[1] + grad_w[2]*grad_w[2]);
877  if(!std::isnormal(mag)){
878  std::cout<<"mag issues "<<mag<<", "<<grad_w[0]<<", "<<grad_w[1]<<", "<<grad_w[2]<<std::endl;
879  std::cout<<"This usually means that the metric field is rubbish\n";
880  }
881  assert(std::isnormal(mag));
882 
883  for(int i=0;i<3;i++)
884  search[i] = grad_w[i]/mag;
885  }
886 
887  // Estimate how far we move along this search path until we make
888  // another element of a similar quality to the current worst. This
889  // is effectively a simplex method for linear programming.
890  double alpha;
891  {
892  double bbox[] = {DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX};
893  for(typename std::vector<index_t>::const_iterator it=_mesh->NNList[n0].begin();it!=_mesh->NNList[n0].end();++it){
894  const double *x1 = _mesh->get_coords(*it);
895 
896  bbox[0] = std::min(bbox[0], x1[0]);
897  bbox[1] = std::max(bbox[1], x1[0]);
898 
899  bbox[2] = std::min(bbox[2], x1[1]);
900  bbox[3] = std::max(bbox[3], x1[1]);
901 
902  bbox[4] = std::min(bbox[4], x1[2]);
903  bbox[5] = std::max(bbox[5], x1[2]);
904  }
905  alpha = (bbox[1]-bbox[0] + bbox[3]-bbox[2] + bbox[5]-bbox[4])/6.0;
906  }
907  for(typename std::set<index_t>::const_iterator it=_mesh->NEList[n0].begin();it!=_mesh->NEList[n0].end();++it){
908  if(*it==worst_element.second)
909  continue;
910 
911  const index_t *n=_mesh->get_element(*it);
912  size_t loc=0;
913  for(;loc<4;loc++)
914  if(n[loc]==n0)
915  break;
916 
917  int n1, n2, n3;
918  switch(loc){
919  case 0:
920  n1 = n[1];
921  n2 = n[2];
922  n3 = n[3];
923  break;
924  case 1:
925  n1 = n[2];
926  n2 = n[0];
927  n3 = n[3];
928  break;
929  case 2:
930  n1 = n[0];
931  n2 = n[1];
932  n3 = n[3];
933  break;
934  case 3:
935  n1 = n[0];
936  n2 = n[2];
937  n3 = n[1];
938  break;
939  }
940 
941  const double *x1 = _mesh->get_coords(n1);
942  const double *x2 = _mesh->get_coords(n2);
943  const double *x3 = _mesh->get_coords(n3);
944 
945  double grad[3];
946  property->lipnikov_grad(loc, x0, x1, x2, x3, m0, grad);
947 
948  double new_alpha =
949  (quality[*it]-worst_element.first)/
950  ((search[0]*grad_w[0]+search[1]*grad_w[1]+search[2]*grad_w[2])-
951  (search[0]*grad[0]+search[1]*grad[1]+search[2]*grad[2]));
952 
953  if(new_alpha>0)
954  alpha = std::min(alpha, new_alpha);
955  }
956 
957  bool linf_update;
958  for(int isearch=0;isearch<10;isearch++){
959  linf_update = false;
960 
961  // Only want to step half that distance so we do not degrade the other elements too much.
962  alpha*=0.5;
963 
964  double new_x0[3];
965  for(int i=0;i<3;i++){
966  new_x0[i] = x0[i] + alpha*search[i];
967  }
968 
969  double new_m0[6];
970  bool valid = generate_location_3d(n0, new_x0, new_m0);
971 
972  if(!valid)
973  continue;
974 
975  // Need to check that we have not decreased the Linf norm. Start by assuming the best.
976  linf_update = true;
977  std::vector<double> new_quality;
978  for(typename std::set<index_t>::const_iterator it=_mesh->NEList[n0].begin();it!=_mesh->NEList[n0].end();++it){
979  const index_t *n=_mesh->get_element(*it);
980  size_t loc=0;
981  for(;loc<4;loc++)
982  if(n[loc]==n0)
983  break;
984 
985  int n1, n2, n3;
986  switch(loc){
987  case 0:
988  n1 = n[1];
989  n2 = n[2];
990  n3 = n[3];
991  break;
992  case 1:
993  n1 = n[2];
994  n2 = n[0];
995  n3 = n[3];
996  break;
997  case 2:
998  n1 = n[0];
999  n2 = n[1];
1000  n3 = n[3];
1001  break;
1002  case 3:
1003  n1 = n[0];
1004  n2 = n[2];
1005  n3 = n[1];
1006  break;
1007  }
1008 
1009  const double *x1 = _mesh->get_coords(n1);
1010  const double *x2 = _mesh->get_coords(n2);
1011  const double *x3 = _mesh->get_coords(n3);
1012 
1013 
1014  const double *m1 = _mesh->get_metric(n1);
1015  const double *m2 = _mesh->get_metric(n2);
1016  const double *m3 = _mesh->get_metric(n3);
1017 
1018  double new_q = property->lipnikov(new_x0, x1, x2, x3, new_m0, m1, m2, m3);
1019 
1020  if(new_q>worst_element.first){
1021  new_quality.push_back(new_q);
1022  }else{
1023  // This means that the linear approximation was not sufficient.
1024  linf_update = false;
1025  break;
1026  }
1027  }
1028 
1029  if(!linf_update)
1030  continue;
1031 
1032  // Update information
1033  // go backwards and pop quality
1034  assert(_mesh->NEList[n0].size()==new_quality.size());
1035  for(typename std::set<index_t>::const_reverse_iterator it=_mesh->NEList[n0].rbegin();it!=_mesh->NEList[n0].rend();++it){
1036  quality[*it] = new_quality.back();
1037  new_quality.pop_back();
1038  }
1039  assert(new_quality.empty());
1040 
1041  for(size_t i=0;i<dim;i++)
1042  _mesh->_coords[n0*dim+i] = new_x0[i];
1043 
1044  for(size_t i=0;i<msize;i++)
1045  _mesh->metric[n0*msize+i] = new_m0[i];
1046 
1047  break;
1048  }
1049 
1050  return linf_update;
1051  }
1052 
1053  void init_cache(){
1054  colour_sets.clear();
1055 
1056  int NNodes = _mesh->get_number_nodes();
1057  std::vector<char> colour(NNodes);
1058 
1059  Colour::GebremedhinManne(MPI_COMM_WORLD, NNodes, _mesh->NNList, _mesh->send, _mesh->recv, _mesh->node_owner, colour);
1060 
1061  int NElements = _mesh->get_number_elements();
1062  std::vector<bool> is_boundary(NNodes, false);
1063  for(int i=0;i<NElements;i++){
1064  const int *n=_mesh->get_element(i);
1065  if(n[0]==-1)
1066  continue;
1067 
1068  for(size_t j=0;j<nloc;j++){
1069  if(_mesh->boundary[i*nloc+j]>0){
1070  for(size_t k=1;k<nloc;k++){
1071  is_boundary[n[(j+k)%nloc]] = true;
1072  }
1073  }
1074  }
1075  }
1076 
1077  for(int i=0;i<NNodes;i++){
1078  if((colour[i]<0)||(!_mesh->is_owned_node(i))||(_mesh->NNList[i].empty())||is_boundary[i])
1079  continue;
1080 
1081  colour_sets[colour[i]].push_back(i);
1082  }
1083 
1084  return;
1085  }
1086 
1087  inline real_t get_x(index_t nid){
1088  return _mesh->_coords[nid*dim];
1089  }
1090 
1091  inline real_t get_y(index_t nid){
1092  return _mesh->_coords[nid*dim+1];
1093  }
1094 
1095  inline real_t get_z(index_t nid){
1096  if(dim==3){
1097  return _mesh->_coords[nid*dim+2];
1098  }else{
1099  std::cerr<<"ERROR: inline real_t get_z(index_t nid) should not get called for 2D";
1100  return -1;
1101  }
1102  }
1103 
1104  inline real_t functional_Linf(index_t node){
1105  double patch_quality = std::numeric_limits<double>::max();
1106 
1107  for(typename std::set<index_t>::const_iterator ie=_mesh->NEList[node].begin();ie!=_mesh->NEList[node].end();++ie){
1108  patch_quality = std::min(patch_quality, quality[*ie]);
1109  }
1110 
1111  return patch_quality;
1112  }
1113 
1114  inline real_t functional_Linf(index_t n0, const real_t *p, const real_t *mp) const{
1115  real_t f;
1116  if(dim==2){
1117  f = functional_Linf_2d(n0, p, mp);
1118  }else{
1119  f = functional_Linf_3d(n0, p, mp);
1120  }
1121  return f;
1122  }
1123 
1124  inline real_t functional_Linf_2d(index_t n0, const real_t *p, const real_t *mp) const{
1125  real_t functional = DBL_MAX;
1126  for(typename std::set<index_t>::iterator ie=_mesh->NEList[n0].begin();ie!=_mesh->NEList[n0].end();++ie){
1127  const index_t *n=_mesh->get_element(*ie);
1128  assert(n[0]>=0);
1129  int iloc = 0;
1130 
1131  while(n[iloc]!=(int)n0){
1132  iloc++;
1133  }
1134  int loc1 = (iloc+1)%3;
1135  int loc2 = (iloc+2)%3;
1136 
1137  const real_t *x1 = _mesh->get_coords(n[loc1]);
1138  const real_t *x2 = _mesh->get_coords(n[loc2]);
1139 
1140  const double *m1 = _mesh->get_metric(n[loc1]);
1141  const double *m2 = _mesh->get_metric(n[loc2]);
1142 
1143  real_t fnl = property->lipnikov(p, x1, x2,
1144  mp, m1, m2);
1145  functional = std::min(functional, fnl);
1146  }
1147 
1148  return functional;
1149  }
1150 
1151  inline real_t functional_Linf_3d(index_t n0, const real_t *p, const real_t *mp) const{
1152  real_t functional = DBL_MAX;
1153  for(typename std::set<index_t>::iterator ie=_mesh->NEList[n0].begin();ie!=_mesh->NEList[n0].end();++ie){
1154  const index_t *n=_mesh->get_element(*ie);
1155  size_t loc=0;
1156  for(;loc<4;loc++)
1157  if(n[loc]==n0)
1158  break;
1159 
1160  int n1, n2, n3;
1161  switch(loc){
1162  case 0:
1163  n1 = n[1];
1164  n2 = n[2];
1165  n3 = n[3];
1166  break;
1167  case 1:
1168  n1 = n[2];
1169  n2 = n[0];
1170  n3 = n[3];
1171  break;
1172  case 2:
1173  n1 = n[0];
1174  n2 = n[1];
1175  n3 = n[3];
1176  break;
1177  case 3:
1178  n1 = n[0];
1179  n2 = n[2];
1180  n3 = n[1];
1181  break;
1182  }
1183 
1184  const double *x1 = _mesh->get_coords(n1);
1185  const double *x2 = _mesh->get_coords(n2);
1186  const double *x3 = _mesh->get_coords(n3);
1187 
1188  const double *m1 = _mesh->get_metric(n1);
1189  const double *m2 = _mesh->get_metric(n2);
1190  const double *m3 = _mesh->get_metric(n3);
1191 
1192  real_t fnl = property->lipnikov(p, x1, x2, x3,
1193  mp,m1, m2, m3);
1194 
1195  functional = std::min(functional, fnl);
1196  }
1197  return functional;
1198  }
1199 
1200  bool generate_location_2d(index_t node, const real_t *p, double *mp){
1201  // Interpolate metric at this new position.
1202  real_t l[]={-1, -1, -1};
1203  int best_e=-1;
1204  real_t tol=-1;
1205 
1206  for(typename std::set<index_t>::const_iterator ie=_mesh->NEList[node].begin();ie!=_mesh->NEList[node].end();++ie){
1207  const index_t *n=_mesh->get_element(*ie);
1208  assert(n[0]>=0);
1209 
1210  const real_t *x0 = _mesh->get_coords(n[0]);
1211  const real_t *x1 = _mesh->get_coords(n[1]);
1212  const real_t *x2 = _mesh->get_coords(n[2]);
1213 
1214  /* Check for inversion by looking at the area
1215  * of the element whose node is being moved.*/
1216  real_t area;
1217  if(n[0]==node){
1218  area = property->area(p, x1, x2);
1219  }else if(n[1]==node){
1220  area = property->area(x0, p, x2);
1221  }else{
1222  area = property->area(x0, x1, p);
1223  }
1224  if(area<0)
1225  return false;
1226 
1227  real_t L = property->area(x0, x1, x2);
1228 
1229  real_t ll[3];
1230  ll[0] = property->area(p, x1, x2)/L;
1231  ll[1] = property->area(x0, p, x2)/L;
1232  ll[2] = property->area(x0, x1, p )/L;
1233 
1234  real_t min_l = std::min(ll[0], std::min(ll[1], ll[2]));
1235  if(best_e==-1){
1236  tol = min_l;
1237  best_e = *ie;
1238  for(size_t i=0;i<nloc;i++)
1239  l[i] = ll[i];
1240  }else{
1241  if(min_l>tol){
1242  tol = min_l;
1243  best_e = *ie;
1244  for(size_t i=0;i<nloc;i++)
1245  l[i] = ll[i];
1246  }
1247  }
1248  }
1249  assert(best_e!=-1);
1250  assert(tol>-DBL_EPSILON);
1251 
1252  const index_t *n=_mesh->get_element(best_e);
1253  assert(n[0]>=0);
1254 
1255  for(size_t i=0;i<msize;i++)
1256  mp[i] =
1257  l[0]*_mesh->metric[n[0]*msize+i]+
1258  l[1]*_mesh->metric[n[1]*msize+i]+
1259  l[2]*_mesh->metric[n[2]*msize+i];
1260 
1261  return true;
1262  }
1263 
1264  bool generate_location_3d(index_t node, const real_t *p, double *mp){
1265  // Interpolate metric at this new position.
1266  real_t l[]={-1, -1, -1, -1};
1267  int best_e=-1;
1268  real_t tol=-1;
1269 
1270  for(typename std::set<index_t>::const_iterator ie=_mesh->NEList[node].begin();ie!=_mesh->NEList[node].end();++ie){
1271  const index_t *n=_mesh->get_element(*ie);
1272  assert(n[0]>=0);
1273 
1274  const real_t *x0 = _mesh->get_coords(n[0]);
1275  const real_t *x1 = _mesh->get_coords(n[1]);
1276  const real_t *x2 = _mesh->get_coords(n[2]);
1277  const real_t *x3 = _mesh->get_coords(n[3]);
1278 
1279  /* Check for inversion by looking at the volume
1280  * of element whose node is being moved.*/
1281  real_t volume;
1282  if(n[0]==node){
1283  volume = property->volume(p, x1, x2, x3);
1284  }else if(n[1]==node){
1285  volume = property->volume(x0, p, x2, x3);
1286  }else if(n[2]==node){
1287  volume = property->volume(x0, x1, p, x3);
1288  }else{
1289  volume = property->volume(x0, x1, x2, p);
1290  }
1291  if(volume<0)
1292  return false;
1293 
1294  real_t L = property->volume(x0, x1, x2, x3);
1295 
1296  real_t ll[4];
1297  ll[0] = property->volume(p, x1, x2, x3)/L;
1298  ll[1] = property->volume(x0, p, x2, x3)/L;
1299  ll[2] = property->volume(x0, x1, p, x3)/L;
1300  ll[3] = property->volume(x0, x1, x2, p )/L;
1301 
1302  real_t min_l = std::min(std::min(ll[0], ll[1]), std::min(ll[2], ll[3]));
1303  if(best_e==-1){
1304  tol = min_l;
1305  best_e = *ie;
1306  for(size_t i=0;i<nloc;i++)
1307  l[i] = ll[i];
1308  }else{
1309  if(min_l>tol){
1310  tol = min_l;
1311  best_e = *ie;
1312  for(size_t i=0;i<nloc;i++)
1313  l[i] = ll[i];
1314  }
1315  }
1316  }
1317  assert(best_e!=-1);
1318  assert(tol>-10*DBL_EPSILON);
1319 
1320  const index_t *n=_mesh->get_element(best_e);
1321  assert(n[0]>=0);
1322 
1323  for(size_t i=0;i<msize;i++)
1324  mp[i] =
1325  l[0]*_mesh->metric[n[0]*msize+i]+
1326  l[1]*_mesh->metric[n[1]*msize+i]+
1327  l[2]*_mesh->metric[n[2]*msize+i]+
1328  l[3]*_mesh->metric[n[3]*msize+i];
1329 
1330  return true;
1331  }
1332 
1333  inline void update_quality(index_t element){
1334  if(dim==2)
1335  update_quality_2d(element);
1336  else
1337  update_quality_3d(element);
1338  }
1339 
1340  inline void update_quality_2d(index_t element){
1341  const index_t *n=_mesh->get_element(element);
1342 
1343  const double *x0 = _mesh->get_coords(n[0]);
1344  const double *x1 = _mesh->get_coords(n[1]);
1345  const double *x2 = _mesh->get_coords(n[2]);
1346 
1347  const double *m0 = _mesh->get_metric(n[0]);
1348  const double *m1 = _mesh->get_metric(n[1]);
1349  const double *m2 = _mesh->get_metric(n[2]);
1350 
1351  quality[element] = property->lipnikov(x0, x1, x2,
1352  m0, m1, m2);
1353  return;
1354  }
1355 
1356  inline void update_quality_3d(index_t element){
1357  const index_t *n=_mesh->get_element(element);
1358 
1359  const double *x0 = _mesh->get_coords(n[0]);
1360  const double *x1 = _mesh->get_coords(n[1]);
1361  const double *x2 = _mesh->get_coords(n[2]);
1362  const double *x3 = _mesh->get_coords(n[3]);
1363 
1364  const double *m0 = _mesh->get_metric(n[0]);
1365  const double *m1 = _mesh->get_metric(n[1]);
1366  const double *m2 = _mesh->get_metric(n[2]);
1367  const double *m3 = _mesh->get_metric(n[3]);
1368 
1369  quality[element] = property->lipnikov(x0, x1, x2, x3,
1370  m0, m1, m2, m3);
1371  return;
1372  }
1373 
1374  Mesh<real_t> *_mesh;
1375  ElementProperty<real_t> *property;
1376 
1377  const size_t nloc, msize;
1378 
1379  int mpi_nparts, rank;
1380  real_t good_q, epsilon_q;
1381  std::vector<real_t> quality;
1382  std::map<int, std::vector<index_t> > colour_sets;
1383 };
1384 
1385 #endif
1386 
void smart_laplacian(int max_iterations=10, double quality_tol=-1.0)
Definition: Smooth.h:111
int index_t
Calculates a number of element properties.
Manages mesh data.
Definition: Mesh.h:70
void laplacian(int max_iterations=10)
Definition: Smooth.h:368
static void GebremedhinManne(size_t NNodes, const std::vector< std::vector< index_t > > &NNList, std::vector< char > &colour)
Definition: Colour.h:102
~Smooth()
Default destructor.
Definition: Smooth.h:106
Smooth(Mesh< real_t > &mesh)
Default constructor.
Definition: Smooth.h:70
Applies Laplacian smoothen in metric space.
Definition: Smooth.h:67
void optimisation_linf(int max_iterations=10, double quality_tol=-1.0)
Definition: Smooth.h:240