1 /* *****************************************************************
2     MESQUITE -- The Mesh Quality Improvement Toolkit
3 
4     Copyright 2004 Sandia Corporation and Argonne National
5     Laboratory.  Under the terms of Contract DE-AC04-94AL85000
6     with Sandia Corporation, the U.S. Government retains certain
7     rights in this software.
8 
9     This library is free software; you can redistribute it and/or
10     modify it under the terms of the GNU Lesser General Public
11     License as published by the Free Software Foundation; either
12     version 2.1 of the License, or (at your option) any later version.
13 
14     This library is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17     Lesser General Public License for more details.
18 
19     You should have received a copy of the GNU Lesser General Public License
20     (lgpl.txt) along with this library; if not, write to the Free Software
21     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 
23     diachin2@llnl.gov, djmelan@sandia.gov, mbrewer@sandia.gov,
24     pknupp@sandia.gov, tleurent@mcs.anl.gov, tmunson@mcs.anl.gov
25 
26   ***************************************************************** */
27 //
28 //    AUTHOR: Thomas Leurent <tleurent@mcs.anl.gov>
29 //       ORG: Argonne National Laboratory
30 //    E-MAIL: tleurent@mcs.anl.gov
31 //
32 // ORIG-DATE: 15-Jan-03 at 08:05:56
33 //  LAST-MOD: 15-Jun-04 at 15:45:00 by Thomas Leurent
34 //
35 // DESCRIPTION:
36 // ============
37 /*!
38   \file   FeasibleNewton.cpp
39   \brief
40 
41   Implements the FeasibleNewton class member functions.
42 
43   \author Thomas Leurent
44   \author Todd Munson
45   \date   2003-01-15
46 */
47 // DESCRIP-END.
48 //
49 
50 #include "FeasibleNewton.hpp"
51 #include "MsqFreeVertexIndexIterator.hpp"
52 #include "MsqDebug.hpp"
53 #include "XYPlanarDomain.hpp"
54 
55 using namespace MBMesquite;
56 
get_name() const57 std::string FeasibleNewton::get_name() const { return "FeasibleNewton"; }
58 
get_patch_set()59 PatchSet* FeasibleNewton::get_patch_set()
60   { return PatchSetUser::get_patch_set(); }
61 
FeasibleNewton(ObjectiveFunction * of)62 FeasibleNewton::FeasibleNewton(ObjectiveFunction* of)
63   : VertexMover(of),
64     PatchSetUser(true),
65     convTol(1e-6),
66     coordsMem(0),
67     havePrintedDirectionMessage(false)
68 {
69   TerminationCriterion* default_crit=get_inner_termination_criterion();
70   default_crit->add_absolute_gradient_L2_norm( 5e-5 );
71 }
72 
73 
initialize(PatchData & pd,MsqError & err)74 void FeasibleNewton::initialize(PatchData &pd, MsqError &err)
75 {
76   // Cannot do anything.  Variable sizes with maximum size dependent
77   // upon the entire MeshSet.
78   coordsMem = pd.create_vertices_memento(err); MSQ_CHKERR(err);
79   havePrintedDirectionMessage = false;
80 }
81 
initialize_mesh_iteration(PatchData & pd,MsqError &)82 void FeasibleNewton::initialize_mesh_iteration(PatchData &pd, MsqError &/*err*/)
83 {
84   pd.reorder();
85 }
86 
optimize_vertex_positions(PatchData & pd,MsqError & err)87 void FeasibleNewton::optimize_vertex_positions(PatchData &pd,
88                                                MsqError &err)
89 {
90   MSQ_FUNCTION_TIMER( "FeasibleNewton::optimize_vertex_positions" );
91   MSQ_DBGOUT(2) << "\no  Performing Feasible Newton optimization.\n";
92 
93   //
94   // the only valid 2D meshes that FeasibleNewton works for are truly planar which
95   // lie in the X-Y coordinate plane.
96   //
97 
98   XYPlanarDomain *xyPlanarDomainPtr = dynamic_cast<XYPlanarDomain*>(pd.get_domain());
99     // only optimize if input mesh is a volume or an XYPlanarDomain
100   if (!pd.domain_set() || xyPlanarDomainPtr != NULL)
101   {
102     const double sigma   = 1e-4;
103     const double beta0   = 0.25;
104     const double beta1   = 0.80;
105     const double tol1    = 1e-8;
106     const double tol2    = 1e-12;
107     const double epsilon = 1e-10;
108     double original_value, new_value;
109     double beta;
110 
111     int nv = pd.num_free_vertices();
112     std::vector<Vector3D> grad(nv), d(nv);
113     bool fn_bool=true;// bool used for determining validity of patch
114 
115     OFEvaluator& objFunc = get_objective_function_evaluator();
116 
117     int i;
118 
119     // TODD -- Don't blame the code for bad things happening when using a
120     //         bad termination test or requesting more accuracy than is
121     //	     possible.
122     //
123     //         Also,
124 
125     // 1.  Allocate a hessian and calculate the sparsity pattern.
126     mHessian.initialize(pd, err); MSQ_ERRRTN(err);
127 
128     // does the Feasible Newton iteration until stopping is required.
129     // Terminate when inner termination criterion signals.
130 
131     /* Computes the value of the stopping criterion*/
132     TerminationCriterion* term_crit=get_inner_termination_criterion();
133     while ( !term_crit->terminate() ) {
134       fn_bool = objFunc.update( pd, original_value, grad, mHessian,  err );
135       MSQ_ERRRTN(err);
136       if (!fn_bool) {
137         MSQ_SETERR(err)("invalid patch for hessian calculation", MsqError::INTERNAL_ERROR);
138         return;
139       }
140 
141       if (MSQ_DBG(3)) { // avoid expensive norm calculations if debug flag is off
142         MSQ_DBGOUT(3) << "  o  objective function: " << original_value << std::endl;
143         MSQ_DBGOUT(3) << "  o  gradient norm: " << length(grad) << std::endl;
144         MSQ_DBGOUT(3) << "  o  Hessian norm: " << mHessian.norm() << std::endl;
145       }
146 
147       // Prints out free vertices coordinates.
148       //
149       // Comment out the following because it is way to verbose for larger
150       // problems.  Consider doing:
151       //  inner_term_crit->write_mesh_steps( "filename.vtk" );
152       // instead.
153       // - j.kraftcheck 2010-11-17
154 //      if (MSQ_DBG(3)) {
155 //        MSQ_DBGOUT(3) << "\n  o Free vertices ("<< pd.num_free_vertices()
156 //                  <<")original coordinates:\n ";
157 //        MSQ_ERRRTN(err);
158 //        const MsqVertex* toto1 = pd.get_vertex_array(err); MSQ_ERRRTN(err);
159 //        MsqFreeVertexIndexIterator ind1(pd, err); MSQ_ERRRTN(err);
160 //        ind1.reset();
161 //        while (ind1.next()) {
162 //          MSQ_DBGOUT(3) << "\t\t\t" << toto1[ind1.value()];
163 //        }
164 //      }
165 
166       // 4. Calculate a direction using preconditionned conjugate gradients
167       //    to find a zero of the Newton system of equations (H*d = -g)
168       //    (a) stop if conjugate iteration limit reached
169       //    (b) stop if relative residual is small
170       //    (c) stop if direction of negative curvature is obtained
171 
172       mHessian.cg_solver(arrptr(d), arrptr(grad), err); MSQ_ERRRTN(err);
173 
174       // 5. Check for descent direction (inner produce of gradient and
175       //    direction is negative.
176       double alpha = inner( grad, d );
177       // TODD -- Add back in if you encounter problems -- do a gradient
178       //         step if the direction from the conjugate gradient solver
179       //         is not a descent direction for the objective function.  We
180       //         SHOULD always get a descent direction from the conjugate
181       //         method though, unless the preconditioner is not positive
182       //         definite.
183       // If direction is positive, does a gradient (steepest descent) step.
184 
185       if (alpha > -epsilon) {
186 
187         MSQ_DBGOUT(3) << "  o  alpha = " << alpha << " (rejected)" << std::endl;
188 
189         if (!havePrintedDirectionMessage) {
190           MSQ_PRINT(1)("Newton direction not guaranteed descent.  Ensure preconditioner is positive definite.\n");
191           havePrintedDirectionMessage = true;
192         }
193 
194         // TODD: removed performing gradient step here since we will use
195         // gradient if step does not produce descent.  Instead we set
196         // alpha to a small negative value.
197 
198         alpha = -epsilon;
199 
200         // alpha = inner(grad, grad, nv); // compute norm squared of gradient
201         // if (alpha < 1) alpha = 1;	// take max with constant
202         // for (i = 0; i < nv; ++i) {
203         //   d[i] = -grad[i] / alpha; 	// compute scaled gradient
204         // }
205         // alpha = inner(grad, d, nv);  	// recompute alpha
206         // 				// equal to one for large gradient
207       }
208       else {
209         MSQ_DBGOUT(3) << "  o  alpha = " << alpha << std::endl;
210       }
211 
212       alpha *= sigma;
213       beta = 1.0;
214       pd.recreate_vertices_memento(coordsMem, err); MSQ_ERRRTN(err);
215 
216       // TODD: Unrolling the linesearch loop.  We do a function and
217       // gradient evaluation when beta = 1.  Otherwise, we end up
218       // in the linesearch regime.  We expect that several
219       // evaluations will be made, so we only do a function evaluation
220       // and finish with a gradient evaluation.  When beta = 1, we also
221       // check the gradient for stability.
222 
223       // TODD -- the Armijo linesearch is based on the objective function,
224       //         so theoretically we only need to evaluate the objective
225       //         function.  However, near a very accurate solution, say with
226       //         the two norm of the gradient of the objective function less
227       //         than 1e-5, the numerical error in the objective function
228       //         calculation is enough that the Armijo linesearch will
229       //         fail.  To correct this situation, the iterate is accepted
230       //         when the norm of the gradient is also small.  If you need
231       //         high accuracy and have a large mesh, talk with Todd about
232       //         the numerical issues so that we can fix it.
233 
234       // TODD -- the Armijo linesearch here is only good for differentiable
235       //         functions.  When projections are involved, you should change
236       //	       to a form of the linesearch meant for nondifferentiable
237       //         functions.
238 
239       pd.move_free_vertices_constrained(arrptr(d), nv, beta, err); MSQ_ERRRTN(err);
240       fn_bool = objFunc.evaluate(pd, new_value, grad, err);
241       if (err.error_code() == err.BARRIER_VIOLATED)
242         err.clear();  // barrier violated does not represent an actual error here
243       MSQ_ERRRTN(err);
244 
245       if ((fn_bool && (original_value - new_value >= -alpha*beta - epsilon)) ||
246           (fn_bool && (length(arrptr(grad), nv) < 100*convTol))) {
247         // Armijo linesearch rules passed.
248         MSQ_DBGOUT(3) << "  o  beta = " << beta << " (accepted without line search)" << std::endl;
249       }
250       else {
251         if (!fn_bool) {
252   	// Function undefined.  Use the higher decrease rate.
253           beta *= beta0;
254           MSQ_DBGOUT(3) << "  o  beta = " << beta << " (invalid step)" << std::endl;
255         }
256         else {
257           // Function defined, but not sufficient decrease
258           // Use the lower decrease rate.
259           beta *= beta1;
260           MSQ_DBGOUT(3) << "  o  beta = " << beta << " (insufficient decrease)" << std::endl;
261         }
262         pd.set_to_vertices_memento(coordsMem, err); MSQ_ERRRTN(err);
263 
264         // Standard Armijo linesearch rules
265 
266         MSQ_DBGOUT(3) << "  o  Doing line search" << std::endl;
267         while (beta >= tol1) {
268           // 6. Search along the direction
269           //    (a) trial = x + beta*d
270           pd.move_free_vertices_constrained(arrptr(d), nv, beta, err); MSQ_ERRRTN(err);
271           //    (b) function evaluation
272           fn_bool = objFunc.evaluate(pd, new_value, err);
273           if (err.error_code() == err.BARRIER_VIOLATED)
274             err.clear();  // barrier violated does not represent an actual error here
275           MSQ_ERRRTN(err);
276 
277           //    (c) check for sufficient decrease and stop
278           if (!fn_bool) {
279 	    // function not defined at trial point
280             beta *= beta0;
281           }
282           else if (original_value - new_value >= -alpha*beta - epsilon ) {
283             // iterate is acceptable.
284             break;
285           }
286           else {
287             // iterate is not acceptable -- shrink beta
288             beta *= beta1;
289           }
290           pd.set_to_vertices_memento(coordsMem, err); MSQ_ERRRTN(err);
291         }
292 
293         if (beta < tol1) {
294           // assert(pd.set_to_vertices_memento called last)
295 
296           // TODD -- Lower limit on steplength reached.  Direction does not
297 	         //         appear to make sufficient progress decreasing the
298           //         objective function.  This can happen when you are
299           //         very near a solution due to numerical errors in
300 	         //         computing the objective function.  It can also happen
301           //         when the direction is not a descent direction and when
302 	         //         you are projecting the iterates onto a surface.
303           //
304           //         The latter cases require the use of a linesearch on
305           //         a gradient step.  If this linesearch terminate with
306           //         insufficient decrease, then you are at a critical
307           //         point and should stop!
308           //
309           //         The numerical errors with the objective function cannot
310           //         be overcome.  Eventually, the gradient step will
311           //         fail to compute a new direction and you will stop.
312 
313           MSQ_PRINT(1)("Sufficient decrease not obtained in linesearch; switching to gradient.\n");
314 
315           alpha = inner(arrptr(grad), arrptr(grad), nv); 	// compute norm squared of gradient
316           if (alpha < 1) alpha = 1;	// take max with constant
317 	  for (i = 0; i < nv; ++i) {
318 	    d[i] = -grad[i] / alpha; 	// compute scaled gradient
319 	  }
320           alpha = inner(arrptr(grad), arrptr(d), nv);  	// recompute alpha
321 	  alpha *= sigma;                 // equal to one for large gradient
322 	  beta = 1.0;
323 
324 	  // Standard Armijo linesearch rules
325           while (beta >= tol2) {
326 	    // 6. Search along the direction
327 	    //    (a) trial = x + beta*d
328 	    pd.move_free_vertices_constrained(arrptr(d), nv, beta, err); MSQ_ERRRTN(err);
329 	    //    (b) function evaluation
330 	    fn_bool = objFunc.evaluate(pd, new_value, err);
331             if (err.error_code() == err.BARRIER_VIOLATED)
332               err.clear();  // barrier violated does not represent an actual error here
333             MSQ_ERRRTN(err);
334 
335 	    //    (c) check for sufficient decrease and stop
336 	    if (!fn_bool) {
337 	      // function not defined at trial point
338 	      beta *= beta0;
339 	    }
340 	    else if (original_value - new_value >= -alpha*beta - epsilon ) {
341 	      // iterate is acceptable.
342 	      break;
343 	   }
344 	    else {
345 	      // iterate is not acceptable -- shrink beta
346 	     beta *= beta1;
347 	   }
348 	    pd.set_to_vertices_memento(coordsMem, err); MSQ_ERRRTN(err);
349 	  }
350 
351 	  if (beta < tol2) {
352 	    // assert(pd.set_to_vertices_memento called last)
353 
354 	    // TODD -- Lower limit on steplength reached.  Gradient does not
355 	    //         appear to make sufficient progress decreasing the
356 	    //         objective function.  This can happen when you are
357 	    //         very near a solution due to numerical errors in
358 	    //         computing the objective function.  Most likely you
359 	    //         are at a critical point for the problem.
360 
361 	    MSQ_PRINT(1)("Sufficient decrease not obtained with gradient; critical point likely found.\n");
362 	    break;
363 	  }
364         }
365 
366         // Compute the gradient at the new point -- needed by termination check
367         fn_bool = objFunc.update(pd, new_value, grad, err); MSQ_ERRRTN(err);
368       }
369 
370       // Prints out free vertices coordinates.
371 //    if (MSQ_DBG(3)) {
372 //      MSQ_DBGOUT(3) << "  o Free vertices new coordinates: \n";
373 //      const MsqVertex* toto1 = pd.get_vertex_array(err); MSQ_ERRRTN(err);
374 //      MsqFreeVertexIndexIterator ind(pd, err); MSQ_ERRRTN(err);
375 //      ind.reset();
376 //      while (ind.next()) {
377 //        MSQ_DBGOUT(3) << "\t\t\t" << toto1[ind.value()];
378 //      }
379 //    }
380 
381       // checks stopping criterion
382       term_crit->accumulate_patch( pd, err ); MSQ_ERRRTN(err);
383       term_crit->accumulate_inner( pd, new_value, arrptr(grad), err ); MSQ_ERRRTN(err);
384     }
385     MSQ_PRINT(2)("FINISHED\n");
386   }
387   else
388   {
389     std::cout << "WARNING: Feasible Newton optimization only supported for volume meshes"
390         << std::endl <<  "   and XYPlanarDomain surface meshes." << std::endl
391         << std::endl << "Try a different solver such as Steepest Descent." << std::endl;
392   }
393 }
394 
395 
terminate_mesh_iteration(PatchData &,MsqError &)396 void FeasibleNewton::terminate_mesh_iteration(PatchData &/*pd*/, MsqError &/*err*/)
397 {
398 
399     //Michael::  Should the vertices memento be delete here???
400   //  cout << "- Executing FeasibleNewton::iteration_complete()\n";
401 }
402 
cleanup()403 void FeasibleNewton::cleanup()
404 {
405   delete coordsMem; coordsMem = NULL;
406 }
407 
408 
409