1 /* *****************************************************************
2     MESQUITE -- The Mesh Quality Improvement Toolkit
3 
4     Copyright 2006 Sandia National Laboratories.  Developed at the
5     University of Wisconsin--Madison under SNL contract number
6     624796.  The U.S. Government and the University of Wisconsin
7     retain certain rights to 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     (2006) kraftche@cae.wisc.edu
24 
25   ***************************************************************** */
26 
27 
28 /** \file CompositeOFTest.cpp
29  *  \brief Unit tests for Composite Objective Functions
30  *  \author Jason Kraftcheck
31  */
32 
33 
34 
35 #include "Mesquite.hpp"
36 #include "CompositeOFAdd.hpp"
37 #include "CompositeOFMultiply.hpp"
38 #include "CompositeOFScalarAdd.hpp"
39 #include "CompositeOFScalarMultiply.hpp"
40 #include "MsqHessian.hpp"
41 #include "IdealWeightInverseMeanRatio.hpp"
42 #include "LPtoPTemplate.hpp"
43 
44 #include "ObjectiveFunctionTests.hpp"
45 #include "PatchDataInstances.hpp"
46 #include "cppunit/extensions/HelperMacros.h"
47 #include "UnitUtil.hpp"
48 #include "Matrix3D.hpp"
49 #include <iterator>
50 
51 using namespace MBMesquite;
52 using std::cout;
53 using std::endl;
54 using std::cerr;
55 
56 /** Fake ObjectiveFunction to pass to Composite OFs */
57 class FauxObjectiveFunction : public ObjectiveFunction
58 {
59   public:
FauxObjectiveFunction(double value,bool invalid=false,bool error=false)60     FauxObjectiveFunction( double value, bool invalid = false, bool error = false)
61       : mValue(value), mInvalid(invalid), mError(error)
62       { ++instanceCount; }
~FauxObjectiveFunction()63     ~FauxObjectiveFunction() { --instanceCount; }
initialize_block_coordinate_descent(MeshDomainAssoc *,const Settings *,PatchSet *,MsqError &)64     bool initialize_block_coordinate_descent( MeshDomainAssoc*, const Settings*, PatchSet*, MsqError& )
65       { CPPUNIT_ASSERT_MESSAGE("This shouldn't ever get called", false ); return false; }
evaluate(EvalType,PatchData &,double & value_out,bool,MsqError & err)66     bool evaluate( EvalType, PatchData&, double& value_out, bool, MsqError& err )
67       {
68         if (mError)
69           MSQ_SETERR(err)(MsqError::INTERNAL_ERROR);
70         value_out = mValue;
71         return !mInvalid;
72       }
clone() const73     ObjectiveFunction* clone() const
74       {
75         ++instanceCount;
76         return new FauxObjectiveFunction(*this);
77       }
clear()78     void clear() {}
min_patch_layers() const79     int min_patch_layers() const { return 0; }
80 
initialize_queue(MeshDomainAssoc *,const Settings *,MsqError &)81     void initialize_queue( MeshDomainAssoc* ,
82                            const Settings* ,
83                            MsqError&  ) {}
84 
get_value() const85     double get_value() const { return mValue; }
get_instance_count()86     static int get_instance_count() { return instanceCount; }
87   private:
88     double mValue;
89     bool mInvalid, mError;
90     static int instanceCount;
91 };
92 int FauxObjectiveFunction::instanceCount = 0;
93 
94 class CompositeOFTest : public CppUnit::TestFixture, public ObjectiveFunctionTests
95 {
96 private:
97   CPPUNIT_TEST_SUITE(CompositeOFTest);
98 
99   CPPUNIT_TEST (test_add_value);
100   CPPUNIT_TEST (test_multiply_value);
101   CPPUNIT_TEST (test_scalar_add_value);
102   CPPUNIT_TEST (test_scalar_multiply_value);
103 
104   CPPUNIT_TEST (test_add_gradient);
105   CPPUNIT_TEST (test_multiply_gradient);
106   CPPUNIT_TEST (test_scalar_add_gradient);
107   CPPUNIT_TEST (test_scalar_multiply_gradient);
108 
109   CPPUNIT_TEST (test_add_hess_diagonal);
110   CPPUNIT_TEST (test_multiply_hess_diagonal);
111   CPPUNIT_TEST (test_scalar_add_hess_diagonal);
112   CPPUNIT_TEST (test_scalar_multiply_hess_diagonal);
113 
114   CPPUNIT_TEST (test_add_hessian);
115   CPPUNIT_TEST (test_multiply_hessian);
116   CPPUNIT_TEST (test_scalar_add_hessian);
117   CPPUNIT_TEST (test_scalar_multiply_hessian);
118 
119   CPPUNIT_TEST (test_clone_add);
120   CPPUNIT_TEST (test_clone_multiply);
121   CPPUNIT_TEST (test_clone_scalar_add);
122   CPPUNIT_TEST (test_clone_scalar_multiply);
123 
124   CPPUNIT_TEST (test_add_invalid);
125   CPPUNIT_TEST (test_multiply_invalid);
126   CPPUNIT_TEST (test_scalar_add_invalid);
127   CPPUNIT_TEST (test_scalar_multiply_invalid);
128 
129   CPPUNIT_TEST (test_add_error);
130   CPPUNIT_TEST (test_multiply_error);
131   CPPUNIT_TEST (test_scalar_add_error);
132   CPPUNIT_TEST (test_scalar_multiply_error);
133 
134   CPPUNIT_TEST_SUITE_END();
135 
136   FauxObjectiveFunction OF1, OF2, OF3, OF4, invalidOF, errorOF;
137   IdealWeightInverseMeanRatio metric;
138   LPtoPTemplate LP1, LP2;
139 
140 public:
141 
CompositeOFTest()142   CompositeOFTest()
143     : OF1(1.0), OF2(3.0), OF3(-7.0), OF4(M_PI),
144       invalidOF(1.0,true,false), errorOF(1.0,false,true),
145       LP1( 1, &metric ), LP2( 2, &metric )
146    {}
147 
148   void test_add_value();
149   void test_multiply_value();
150   void test_scalar_add_value();
151   void test_scalar_multiply_value();
152 
153   void test_add_gradient();
154   void test_multiply_gradient();
155   void test_scalar_add_gradient();
156   void test_scalar_multiply_gradient();
157 
158   void test_add_hess_diagonal();
159   void test_multiply_hess_diagonal();
160   void test_scalar_add_hess_diagonal();
161   void test_scalar_multiply_hess_diagonal();
162 
163   void test_add_hessian();
164   void test_multiply_hessian();
165   void test_scalar_add_hessian();
166   void test_scalar_multiply_hessian();
167 
168   void test_clone_add();
169   void test_clone_multiply();
170   void test_clone_scalar_add();
171   void test_clone_scalar_multiply();
172 
173   void test_add_invalid();
174   void test_multiply_invalid();
175   void test_scalar_add_invalid();
176   void test_scalar_multiply_invalid();
177 
178   void test_add_error();
179   void test_multiply_error();
180   void test_scalar_add_error();
181   void test_scalar_multiply_error();
182 
183   void test_evaluate( double expected_value, ObjectiveFunction& of );
184   void get_hessians( MsqHessian& LP1_hess, MsqHessian& LP2_hess,
185                      ObjectiveFunction& OF, MsqHessian& OF_hess );
186   void test_composite_clone( ObjectiveFunction& of );
187   void test_invalid_eval( ObjectiveFunction& of );
188   void test_eval_fails( ObjectiveFunction& of );
189 };
190 
191 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CompositeOFTest, "CompositeOFTest");
192 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CompositeOFTest, "Unit");
193 
test_evaluate(double expected,ObjectiveFunction & OF)194 void CompositeOFTest::test_evaluate( double expected, ObjectiveFunction& OF )
195 {
196   MsqPrintError err(cout);
197   double value;
198   bool rval = OF.evaluate( ObjectiveFunction::CALCULATE, patch(), value, false, err );
199   ASSERT_NO_ERROR(err);
200   CPPUNIT_ASSERT(rval);
201   CPPUNIT_ASSERT_DOUBLES_EQUAL( expected, value, 1e-6 );
202 }
203 
test_add_value()204 void CompositeOFTest::test_add_value()
205 {
206   CompositeOFAdd add1( &OF1, &OF2 );
207   test_evaluate( OF1.get_value() + OF2.get_value(), add1 );
208 
209   CompositeOFAdd add2( &OF3, &OF4 );
210   test_evaluate( OF3.get_value() + OF4.get_value(), add2 );
211 }
212 
test_multiply_value()213 void CompositeOFTest::test_multiply_value()
214 {
215   CompositeOFMultiply mult1( &OF1, &OF2 );
216   test_evaluate( OF1.get_value() * OF2.get_value(), mult1 );
217 
218   CompositeOFMultiply mult2( &OF3, &OF4 );
219   test_evaluate( OF3.get_value() * OF4.get_value(), mult2 );
220 }
221 
test_scalar_add_value()222 void CompositeOFTest::test_scalar_add_value()
223 {
224   CompositeOFScalarAdd add1( sqrt(2.0), &OF1 );
225   test_evaluate( OF1.get_value() + sqrt(2.0), add1 );
226 
227   CompositeOFScalarAdd add2( -1.0, &OF4 );
228   test_evaluate( OF4.get_value() - 1, add2 );
229 }
230 
test_scalar_multiply_value()231 void CompositeOFTest::test_scalar_multiply_value()
232 {
233   CompositeOFScalarMultiply mult1( sqrt(2.0), &OF1 );
234   test_evaluate( OF1.get_value() * sqrt(2.0), mult1 );
235 
236   CompositeOFScalarMultiply mult2( -1.0, &OF4 );
237   test_evaluate( -OF4.get_value(), mult2 );
238 }
239 
test_add_gradient()240 void CompositeOFTest::test_add_gradient()
241   { CompositeOFAdd OF( &LP1, &LP2 ); compare_numerical_gradient( &OF ); }
test_multiply_gradient()242 void CompositeOFTest::test_multiply_gradient()
243   { CompositeOFMultiply OF( &LP1, &LP2 ); compare_numerical_gradient( &OF ); }
test_scalar_add_gradient()244 void CompositeOFTest::test_scalar_add_gradient()
245   { CompositeOFScalarAdd OF( M_PI, &LP1 ); compare_numerical_gradient( &OF ); }
test_scalar_multiply_gradient()246 void CompositeOFTest::test_scalar_multiply_gradient()
247   { CompositeOFScalarMultiply OF( M_PI, &LP1 ); compare_numerical_gradient( &OF ); }
248 
get_hessians(MsqHessian & LP1_hess,MsqHessian & LP2_hess,ObjectiveFunction & OF,MsqHessian & OF_hess)249 void CompositeOFTest::get_hessians( MsqHessian& LP1_hess,
250                                     MsqHessian& LP2_hess,
251                                     ObjectiveFunction& OF,
252                                     MsqHessian& OF_hess )
253 {
254   MsqPrintError err(cout);
255   PatchData pd;
256   create_twelve_hex_patch( pd, err );
257   ASSERT_NO_ERROR( err );
258 
259   LP1_hess.initialize( pd, err ); ASSERT_NO_ERROR(err);
260   LP2_hess.initialize( pd, err ); ASSERT_NO_ERROR(err);
261   OF_hess .initialize( pd, err ); ASSERT_NO_ERROR(err);
262 
263   std::vector<Vector3D> grad;
264   bool rval;
265   double value;
266   rval = LP1.evaluate_with_Hessian( ObjectiveFunction::CALCULATE, pd, value, grad, LP1_hess, err );
267   ASSERT_NO_ERROR(err);
268   CPPUNIT_ASSERT(rval);
269   rval = LP2.evaluate_with_Hessian( ObjectiveFunction::CALCULATE, pd, value, grad, LP2_hess, err );
270   ASSERT_NO_ERROR(err);
271   CPPUNIT_ASSERT(rval);
272   rval = OF .evaluate_with_Hessian( ObjectiveFunction::CALCULATE, pd, value, grad, OF_hess , err );
273   ASSERT_NO_ERROR(err);
274   CPPUNIT_ASSERT(rval);
275 }
276 
test_add_hess_diagonal()277 void CompositeOFTest::test_add_hess_diagonal()
278 {
279   CompositeOFAdd OF( &LP1, &LP2 );
280   compare_hessian_diagonal( &OF );
281 }
282 
test_multiply_hess_diagonal()283 void CompositeOFTest::test_multiply_hess_diagonal()
284 {
285   CompositeOFMultiply OF( &LP1, &LP2 );
286   std::vector<SymMatrix3D> hess1, hess2, hess;
287 
288   MsqPrintError err(cout);
289   PatchData pd;
290   create_twelve_hex_patch( pd, err );
291   ASSERT_NO_ERROR( err );
292 
293   std::vector<Vector3D> grad1, grad2, grad;
294   bool rval;
295   double value1, value2, value;
296   rval = LP1.evaluate_with_Hessian_diagonal( ObjectiveFunction::CALCULATE, pd, value1, grad1, hess1, err );
297   ASSERT_NO_ERROR(err);
298   CPPUNIT_ASSERT(rval);
299   rval = LP2.evaluate_with_Hessian_diagonal( ObjectiveFunction::CALCULATE, pd, value2, grad2, hess2, err );
300   ASSERT_NO_ERROR(err);
301   CPPUNIT_ASSERT(rval);
302   rval = OF .evaluate_with_Hessian_diagonal( ObjectiveFunction::CALCULATE, pd, value, grad, hess , err );
303   ASSERT_NO_ERROR(err);
304   CPPUNIT_ASSERT(rval);
305 
306   CPPUNIT_ASSERT_EQUAL( pd.num_free_vertices(), grad1.size() );
307   CPPUNIT_ASSERT_EQUAL( pd.num_free_vertices(), grad2.size() );
308   CPPUNIT_ASSERT_EQUAL( pd.num_free_vertices(), grad .size() );
309 
310   CPPUNIT_ASSERT_EQUAL( pd.num_free_vertices(), hess1.size() );
311   CPPUNIT_ASSERT_EQUAL( pd.num_free_vertices(), hess2.size() );
312   CPPUNIT_ASSERT_EQUAL( pd.num_free_vertices(), hess .size() );
313 
314   CPPUNIT_ASSERT_DOUBLES_EQUAL( value1 * value2, value, 1e-6 );
315 
316   for (size_t i = 0; i < pd.num_free_vertices(); ++i) {
317     const Vector3D expected_grad = value2 * grad1[i] + value1 * grad2[i];
318     CPPUNIT_ASSERT_VECTORS_EQUAL( expected_grad, grad[i], 1e-6 );
319 
320     Matrix3D o;
321     o.outer_product( grad1[i], grad2[i] );
322     Matrix3D expect = o + transpose(o);
323     expect += value2 * hess1[i];
324     expect += value1 * hess2[i];
325     CPPUNIT_ASSERT_MATRICES_EQUAL( expect, Matrix3D(hess[i]), 1e-6 );
326   }
327 }
328 
test_scalar_add_hess_diagonal()329 void CompositeOFTest::test_scalar_add_hess_diagonal()
330 {
331   CompositeOFScalarAdd OF( 1111.1, &LP1 );
332   compare_hessian_diagonal( &OF );
333 }
334 
test_scalar_multiply_hess_diagonal()335 void CompositeOFTest::test_scalar_multiply_hess_diagonal()
336 {
337   const double scale = 2.5;
338   CompositeOFScalarMultiply OF( scale, &LP1 );
339   compare_hessian_diagonal( &OF );
340 }
341 
test_add_hessian()342 void CompositeOFTest::test_add_hessian()
343 {
344     // test value and gradient
345   CompositeOFAdd OF( &LP1, &LP2 );
346   compare_hessian_gradient( &OF );
347 
348     // test actual hessian values
349   MsqHessian hess1, hess2, hess;
350   get_hessians( hess1, hess2, OF, hess );
351   Matrix3D *b1, *b2, *b;
352   for (unsigned r = 0; r < hess.size(); ++r) {
353     for (unsigned c = r; c < hess.size(); ++c) {
354       b1 = hess1.get_block(r,c);
355       b2 = hess2.get_block(r,c);
356       b  = hess .get_block(r,c);
357       if (b) {
358         CPPUNIT_ASSERT_MATRICES_EQUAL( *b1 + *b2, *b, 1e-6 );
359       }
360     }
361   }
362 }
363 
test_multiply_hessian()364 void CompositeOFTest::test_multiply_hessian()
365 {
366   MsqError err;
367   PatchData pd;
368   create_twelve_hex_patch( pd, err );
369   ASSERT_NO_ERROR( err );
370 
371   // this should always fail because the Hessian is not sparse
372   CompositeOFMultiply OF( &LP1, &LP2 );
373   double value;
374   MsqHessian hess;
375   hess.initialize( pd, err );
376   ASSERT_NO_ERROR(err);
377   std::vector<Vector3D> grad;
378   OF.evaluate_with_Hessian( ObjectiveFunction::CALCULATE, pd, value, grad, hess, err );
379   CPPUNIT_ASSERT(err);
380 }
381 
test_scalar_add_hessian()382 void CompositeOFTest::test_scalar_add_hessian()
383 {
384     // test value and gradient
385   CompositeOFScalarAdd OF( 1111.1, &LP1 );
386   compare_hessian_gradient( &OF );
387 
388     // test actual hessian values
389   MsqHessian hess1, hess2, hess;
390   get_hessians( hess1, hess2, OF, hess );
391   Matrix3D *b1, *b;
392   for (unsigned r = 0; r < hess.size(); ++r) {
393     for (unsigned c = r; c < hess.size(); ++c) {
394       b1 = hess1.get_block(r,c);
395       b  = hess .get_block(r,c);
396       if (b) {
397         CPPUNIT_ASSERT_MATRICES_EQUAL( *b1, *b, 1e-6 );
398       }
399     }
400   }
401 }
402 
test_scalar_multiply_hessian()403 void CompositeOFTest::test_scalar_multiply_hessian()
404 {
405     // test value and gradient
406   const double scale = 2.5;
407   CompositeOFScalarMultiply OF( scale, &LP1 );
408   compare_hessian_gradient( &OF );
409 
410     // test actual hessian values
411   MsqHessian hess1, hess2, hess;
412   get_hessians( hess1, hess2, OF, hess );
413   Matrix3D *b1, *b;
414   for (unsigned r = 0; r < hess.size(); ++r) {
415     for (unsigned c = r; c < hess.size(); ++c) {
416       b1 = hess1.get_block(r,c);
417       b  = hess .get_block(r,c);
418       if (b) {
419         CPPUNIT_ASSERT_MATRICES_EQUAL( scale * *b1, *b, 1e-6 );
420       }
421     }
422   }
423 }
424 
425 
test_composite_clone(ObjectiveFunction & OF)426 void CompositeOFTest::test_composite_clone( ObjectiveFunction& OF )
427 {
428   // save current count of instances of underlying OFs for later
429   const int init_count = FauxObjectiveFunction::get_instance_count();
430 
431   // clone the objective function
432   ObjectiveFunction* clone = OF.clone();
433 
434   // check that the underlying OFs were also cloned
435   CPPUNIT_ASSERT( init_count < FauxObjectiveFunction::get_instance_count() );
436 
437   // check that the value is the same
438   MsqPrintError err(cout);
439   double orig_val, clone_val;
440   bool rval;
441   rval = OF.evaluate( ObjectiveFunction::CALCULATE, patch(), orig_val, false, err );
442   ASSERT_NO_ERROR(err);
443   CPPUNIT_ASSERT(rval);
444   rval = clone->evaluate( ObjectiveFunction::CALCULATE, patch(), clone_val, false, err );
445   ASSERT_NO_ERROR(err);
446   CPPUNIT_ASSERT(rval);
447   CPPUNIT_ASSERT_DOUBLES_EQUAL( orig_val, clone_val, 1e-6 );
448 
449   // check that cloned instances of underlying OFs are deleted
450   delete clone;
451   CPPUNIT_ASSERT_EQUAL( init_count, FauxObjectiveFunction::get_instance_count() );
452 }
453 
454 
test_clone_add()455 void CompositeOFTest::test_clone_add()
456   { CompositeOFAdd OF( &OF1, &OF2 ); test_composite_clone( OF ); }
test_clone_multiply()457 void CompositeOFTest::test_clone_multiply()
458   { CompositeOFMultiply OF( &OF1, &OF2 ); test_composite_clone( OF ); }
test_clone_scalar_add()459 void CompositeOFTest::test_clone_scalar_add()
460   { CompositeOFScalarAdd OF( 2.1, &OF2 ); test_composite_clone( OF ); }
test_clone_scalar_multiply()461 void CompositeOFTest::test_clone_scalar_multiply()
462   { CompositeOFScalarMultiply OF( 0.333, &OF2 ); test_composite_clone( OF ); }
463 
test_invalid_eval(ObjectiveFunction & OF)464 void CompositeOFTest::test_invalid_eval( ObjectiveFunction& OF )
465 {
466   MsqPrintError err(cout);
467   bool rval;
468   double value;
469   rval = OF.evaluate( ObjectiveFunction::CALCULATE, patch(), value, false, err );
470   ASSERT_NO_ERROR(err);
471   CPPUNIT_ASSERT(rval == false);
472 }
473 
test_eval_fails(ObjectiveFunction & OF)474 void CompositeOFTest::test_eval_fails( ObjectiveFunction& OF )
475 {
476   MsqError err;
477   double value;
478   OF.evaluate( ObjectiveFunction::CALCULATE, patch(), value, false, err );
479   CPPUNIT_ASSERT_EQUAL(MsqError::INTERNAL_ERROR, err.error_code());
480 }
481 
test_add_invalid()482 void CompositeOFTest::test_add_invalid()
483 {
484   CompositeOFAdd add1( &OF1, &invalidOF );
485   test_invalid_eval( add1 );
486 
487   CompositeOFAdd add2( &invalidOF, &OF3 );
488   test_invalid_eval( add2 );
489 }
490 
test_multiply_invalid()491 void CompositeOFTest::test_multiply_invalid()
492 {
493   CompositeOFMultiply mult1( &OF1, &invalidOF );
494   test_invalid_eval( mult1 );
495 
496   CompositeOFMultiply mult2( &invalidOF, &OF3 );
497   test_invalid_eval( mult2 );
498 }
499 
test_scalar_add_invalid()500 void CompositeOFTest::test_scalar_add_invalid()
501 {
502   CompositeOFScalarAdd OF( 2.0, &invalidOF );
503   test_invalid_eval( OF );
504 }
505 
test_scalar_multiply_invalid()506 void CompositeOFTest::test_scalar_multiply_invalid()
507 {
508   CompositeOFScalarMultiply OF( 2.0, &invalidOF );
509   test_invalid_eval( OF );
510 }
511 
test_add_error()512 void CompositeOFTest::test_add_error()
513 {
514   CompositeOFAdd add1( &OF1, &errorOF );
515   test_eval_fails( add1 );
516 
517   CompositeOFAdd add2( &errorOF, &OF3 );
518   test_eval_fails( add2 );
519 }
520 
test_multiply_error()521 void CompositeOFTest::test_multiply_error()
522 {
523   CompositeOFMultiply mult1( &OF1, &errorOF );
524   test_eval_fails( mult1 );
525 
526   CompositeOFMultiply mult2( &errorOF, &OF3 );
527   test_eval_fails( mult2 );
528 }
529 
test_scalar_add_error()530 void CompositeOFTest::test_scalar_add_error()
531 {
532   CompositeOFScalarAdd OF( 2.0, &errorOF );
533   test_eval_fails( OF );
534 }
535 
test_scalar_multiply_error()536 void CompositeOFTest::test_scalar_multiply_error()
537 {
538   CompositeOFScalarMultiply OF( 2.0, &errorOF );
539   test_eval_fails( OF );
540 }
541 
542