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