MOAB: Mesh Oriented datABase  (version 5.4.1)
QualityAssessorTest.cpp
Go to the documentation of this file.
00001 /* *****************************************************************
00002     MESQUITE -- The Mesh Quality Improvement Toolkit
00003 
00004     Copyright 2007 Sandia National Laboratories.  Developed at the
00005     University of Wisconsin--Madison under SNL contract number
00006     624796.  The U.S. Government and the University of Wisconsin
00007     retain certain rights to this software.
00008 
00009     This library is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU Lesser General Public
00011     License as published by the Free Software Foundation; either
00012     version 2.1 of the License, or (at your option) any later version.
00013 
00014     This library is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00017     Lesser General Public License for more details.
00018 
00019     You should have received a copy of the GNU Lesser General Public License
00020     (lgpl.txt) along with this library; if not, write to the Free Software
00021     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00022 
00023     (2007) [email protected]
00024 
00025   ***************************************************************** */
00026 
00027 /** \file QualityAssessorTest.cpp
00028  *  \brief
00029  *  \author Jason Kraftcheck
00030  */
00031 
00032 #include "Mesquite.hpp"
00033 #include "QualityAssessor.hpp"
00034 #include "AspectRatioGammaQualityMetric.hpp"
00035 #include "LocalSizeQualityMetric.hpp"
00036 #include "ConditionNumberQualityMetric.hpp"
00037 #include "TShapeNB1.hpp"
00038 #include "IdealShapeTarget.hpp"
00039 #include "TQualityMetric.hpp"
00040 #include "PlanarDomain.hpp"
00041 #include "Settings.hpp"
00042 #include "MeshImpl.hpp"
00043 #include "PatchData.hpp"
00044 #include "MsqMeshEntity.hpp"
00045 #include "ArrayMesh.hpp"
00046 #include "InstructionQueue.hpp"
00047 
00048 #include "UnitUtil.hpp"
00049 
00050 #include <algorithm>
00051 #include <numeric>
00052 #include <iomanip>
00053 
00054 using namespace MBMesquite;
00055 using namespace std;
00056 
00057 class QualityAssessorTest : public CppUnit::TestFixture
00058 {
00059   private:
00060     CPPUNIT_TEST_SUITE( QualityAssessorTest );
00061     CPPUNIT_TEST( test_basic_stats_element );
00062     CPPUNIT_TEST( test_basic_stats_vertex );
00063     CPPUNIT_TEST( test_basic_stats_sample );
00064     CPPUNIT_TEST( test_histogram_known_range );
00065     CPPUNIT_TEST( test_histogram_unknown_range );
00066     CPPUNIT_TEST( test_power_mean );
00067     CPPUNIT_TEST( test_invalid_count );
00068     CPPUNIT_TEST( test_inverted_count );
00069     CPPUNIT_TEST( test_output_control );
00070     CPPUNIT_TEST( test_tag_element );
00071     CPPUNIT_TEST( test_tag_vertex );
00072     CPPUNIT_TEST( test_tag_inverted );
00073     CPPUNIT_TEST( test_print_inverted );
00074     CPPUNIT_TEST( test_print_stats );
00075     CPPUNIT_TEST( test_print_name );
00076     CPPUNIT_TEST( test_modify_metric );
00077     CPPUNIT_TEST( test_free_only );
00078     CPPUNIT_TEST_SUITE_END();
00079 
00080     vector< double > vertCoords, invertCoords;
00081     vector< int > fixedFlags;
00082     vector< unsigned long > triConn, invertConn;
00083 
00084     MeshImpl myMesh, invertedMesh;
00085     PlanarDomain myDomain;
00086     Settings mySettings;
00087 
00088   public:
00089     void setUp();
00090     void tearDown();
00091 
00092   public:
00093     QualityAssessorTest() : myDomain( PlanarDomain::XY ) {}
00094 
00095     void test_basic_stats_element();
00096     void test_basic_stats_vertex();
00097     void test_basic_stats_sample();
00098     void test_histogram_known_range();
00099     void test_histogram_unknown_range();
00100     void test_power_mean();
00101     void test_invalid_count();
00102     void test_inverted_count();
00103     void test_output_control();
00104     void test_tag_element();
00105     void test_tag_vertex();
00106     void test_tag_inverted();
00107     void test_print_inverted();
00108     void test_print_stats();
00109     void test_print_name();
00110     void test_modify_metric();
00111     void test_free_only();
00112 };
00113 
00114 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( QualityAssessorTest, "Unit" );
00115 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( QualityAssessorTest, "QualityAssessorTest" );
00116 
00117 void QualityAssessorTest::setUp()
00118 {
00119     MsqError err;
00120 
00121     // create the following mesh:
00122     /*2.0
00123      *  (3)------------------(2)
00124      *   |\                __/|
00125      *   | \       3    __/ / |
00126      *   |  \        __/   /  |
00127      *   |   \    __/  4  /   |
00128      *   |    \ _/       /    |
00129      *1.0|  0 (4)------(5)  2 |
00130      *   |    /. \__    .\    |
00131      *   |   / .    \__5. \   |
00132      *   |  /  .       \__ \  |
00133      *   | /   .   1    . \_\ |
00134      *   |/    .        .    \|
00135      *  (0)------------------(1)
00136      *0.0     1.0      2.0   3.0
00137      */
00138     const char vtk_file[] = "# vtk DataFile Version 3.0\n"
00139                             "QualityAssessorTest input 1\n"
00140                             "ASCII\n"
00141                             "DATASET UNSTRUCTURED_GRID\n"
00142                             "POINTS 6 float\n"
00143                             "0 0 0\n"
00144                             "3 0 0\n"
00145                             "3 2 0\n"
00146                             "0 2 0\n"
00147                             "1 1 0\n"
00148                             "2 1 0\n"
00149                             "CELLS 6 24\n"
00150                             "3 4 3 0\n"
00151                             "3 0 1 4\n"
00152                             "3 1 2 5\n"
00153                             "3 2 3 4\n"
00154                             "3 4 5 2\n"
00155                             "3 5 4 1\n"
00156                             "CELL_TYPES 6\n"
00157                             "5 5 5 5 5 5\n"
00158                             "POINT_DATA 6\n"
00159                             "SCALARS fixed int\n"
00160                             "LOOKUP_TABLE default\n"
00161                             "1 1 1 1 0 0\n";
00162 
00163     // define a mesh with two triangles, one of which is inverted wrt the other
00164     const char invert_file[] = "# vtk DataFile Version 3.0\n"
00165                                "QualityAssessorTest input 2\n"
00166                                "ASCII\n"
00167                                "DATASET UNSTRUCTURED_GRID\n"
00168                                "POINTS 3 float\n"
00169                                "0 0 0\n"
00170                                "1 0 0\n"
00171                                "0 1 0\n"
00172                                "CELLS 2 8\n"
00173                                "3 0 1 2\n"
00174                                "3 0 2 1\n"
00175                                "CELL_TYPES 2\n"
00176                                "5 5\n"
00177                                "POINT_DATA 3\n"
00178                                "SCALARS fixed int\n"
00179                                "LOOKUP_TABLE default\n"
00180                                "1 1 1\n";
00181 
00182     myMesh.clear();
00183     invertedMesh.clear();
00184 
00185     const char* tmpname = "QualityAssessorTest.vtk";
00186     FILE* filp          = fopen( tmpname, "w" );
00187     CPPUNIT_ASSERT( NULL != filp );
00188     size_t w = fwrite( vtk_file, sizeof( vtk_file ), 1, filp );
00189     fclose( filp );
00190     myMesh.clear();
00191     myMesh.read_vtk( tmpname, err );
00192     remove( tmpname );
00193     CPPUNIT_ASSERT_EQUAL( (size_t)1, w );
00194     ASSERT_NO_ERROR( err );
00195 
00196     filp = fopen( tmpname, "w" );
00197     CPPUNIT_ASSERT( NULL != filp );
00198     w = fwrite( invert_file, sizeof( invert_file ), 1, filp );
00199     fclose( filp );
00200     invertedMesh.clear();
00201     invertedMesh.read_vtk( tmpname, err );
00202     remove( tmpname );
00203     CPPUNIT_ASSERT_EQUAL( (size_t)1, w );
00204     ASSERT_NO_ERROR( err );
00205 }
00206 
00207 void QualityAssessorTest::tearDown() {}
00208 
00209 // Define a decorator (wrapper) for a QualityMetric
00210 // instance that records each metric value.
00211 class MetricLogger : public QualityMetric
00212 {
00213   public:
00214     MetricLogger( QualityMetric* metric ) : invalidCount( 0 ), mMetric( metric ) {}
00215 
00216     vector< double > mValues;
00217     vector< Mesh::EntityHandle > mHandles;
00218     int invalidCount;
00219 
00220     double min() const
00221     {
00222         return *min_element( mValues.begin(), mValues.end() );
00223     }
00224     double max() const
00225     {
00226         return *max_element( mValues.begin(), mValues.end() );
00227     }
00228     double avg() const
00229     {
00230         double x = 0;
00231         return accumulate( mValues.begin(), mValues.end(), x ) / mValues.size();
00232     }
00233     double sqrsum() const
00234     {
00235         double result = 0.0;
00236         for( unsigned i = 0; i < mValues.size(); ++i )
00237             result += mValues[i] * mValues[i];
00238         return result;
00239     }
00240     double rms() const
00241     {
00242         return sqrt( sqrsum() / mValues.size() );
00243     }
00244     double dev() const
00245     {
00246         return sqrt( sqrsum() / mValues.size() - avg() * avg() );
00247     }
00248     double pmean( double p ) const
00249     {
00250         double result = 0.0;
00251         for( unsigned i = 0; i < mValues.size(); ++i )
00252             result += pow( mValues[i], p );
00253         return pow( result / mValues.size(), 1. / p );
00254     }
00255 
00256     bool no_duplicate_evals() const
00257     {
00258         vector< Mesh::EntityHandle > handles( mHandles );
00259         sort( handles.begin(), handles.end() );
00260         return handles.end() == unique( handles.begin(), handles.end() );
00261     }
00262 
00263     MetricType get_metric_type() const
00264     {
00265         return mMetric->get_metric_type();
00266     }
00267 
00268     std::string get_name() const
00269     {
00270         return mMetric->get_name();
00271     }
00272 
00273     int get_negate_flag() const
00274     {
00275         return mMetric->get_negate_flag();
00276     }
00277 
00278     void get_evaluations( PatchData& pd, vector< size_t >& handles, bool free, MsqError& err )
00279     {
00280         return mMetric->get_evaluations( pd, handles, free, err );
00281     }
00282 
00283     bool evaluate( PatchData& pd, size_t handle, double& value, MsqError& err )
00284     {
00285         bool rval = mMetric->evaluate( pd, handle, value, err );
00286         mValues.push_back( value );
00287         if( get_metric_type() == QualityMetric::VERTEX_BASED )
00288             mHandles.push_back( pd.get_vertex_handles_array()[handle] );
00289         else if( handle < pd.num_elements() )
00290             mHandles.push_back( pd.get_element_handles_array()[handle] );
00291         if( !rval ) ++invalidCount;
00292         return rval;
00293     }
00294 
00295     bool evaluate_with_indices( PatchData&, size_t, double&, vector< size_t >&, MsqError& err )
00296     {  // shouldn't be called by QualityAssessor
00297         CPPUNIT_ASSERT( false );
00298         MSQ_SETERR( err )( MsqError::NOT_IMPLEMENTED );
00299         return false;
00300     }
00301     bool evaluate_with_gradient( PatchData&, size_t, double&, vector< size_t >&, vector< Vector3D >&, MsqError& err )
00302     {  // shouldn't be called by QualityAssessor
00303         CPPUNIT_ASSERT( false );
00304         MSQ_SETERR( err )( MsqError::NOT_IMPLEMENTED );
00305         return false;
00306     }
00307     bool evaluate_with_Hessian( PatchData&,
00308                                 size_t,
00309                                 double&,
00310                                 vector< size_t >&,
00311                                 vector< Vector3D >&,
00312                                 vector< Matrix3D >&,
00313                                 MsqError& err )
00314     {  // shouldn't be called by QualityAssessor
00315         CPPUNIT_ASSERT( false );
00316         MSQ_SETERR( err )( MsqError::NOT_IMPLEMENTED );
00317         return false;
00318     }
00319 
00320   private:
00321     QualityMetric* mMetric;
00322 };
00323 
00324 void QualityAssessorTest::test_basic_stats_element()
00325 {
00326     AspectRatioGammaQualityMetric metric;
00327     MetricLogger logger( &metric );
00328     QualityAssessor qa( &logger );
00329     qa.disable_printing_results();
00330 
00331     MsqError err;
00332     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00333     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00334     ASSERT_NO_ERROR( err );
00335 
00336     // check didn't evaluate any element more than once
00337     CPPUNIT_ASSERT( logger.no_duplicate_evals() );
00338 
00339     // check one eval for each element
00340     vector< Mesh::ElementHandle > elems;
00341     myMesh.get_all_elements( elems, err );
00342     ASSERT_NO_ERROR( err );
00343     CPPUNIT_ASSERT_EQUAL( elems.size(), logger.mValues.size() );
00344 
00345     // check values
00346     const QualityAssessor::Assessor* results = qa.get_results( &logger );
00347     CPPUNIT_ASSERT( results != NULL );
00348     CPPUNIT_ASSERT_EQUAL( (int)elems.size(), results->get_count() );
00349     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.min(), results->get_minimum(), 1e-6 );
00350     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.avg(), results->get_average(), 1e-6 );
00351     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.rms(), results->get_rms(), 1e-6 );
00352     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.max(), results->get_maximum(), 1e-6 );
00353     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.dev(), results->get_stddev(), 1e-6 );
00354 }
00355 
00356 void QualityAssessorTest::test_basic_stats_vertex()
00357 {
00358     LocalSizeQualityMetric metric;
00359     MetricLogger logger( &metric );
00360     QualityAssessor qa( &logger );
00361     qa.measure_free_samples_only( false );
00362     qa.disable_printing_results();
00363 
00364     MsqError err;
00365     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00366     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00367     ASSERT_NO_ERROR( err );
00368 
00369     // check didn't evaluate any vertex more than once
00370     CPPUNIT_ASSERT( logger.no_duplicate_evals() );
00371 
00372     // check one eval for each element
00373     vector< Mesh::VertexHandle > verts;
00374     myMesh.get_all_vertices( verts, err );
00375     ASSERT_NO_ERROR( err );
00376     CPPUNIT_ASSERT_EQUAL( verts.size(), logger.mValues.size() );
00377 
00378     // check values
00379     const QualityAssessor::Assessor* results = qa.get_results( &logger );
00380     CPPUNIT_ASSERT( results != NULL );
00381     CPPUNIT_ASSERT_EQUAL( (int)verts.size(), results->get_count() );
00382     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.min(), results->get_minimum(), 1e-6 );
00383     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.avg(), results->get_average(), 1e-6 );
00384     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.rms(), results->get_rms(), 1e-6 );
00385     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.max(), results->get_maximum(), 1e-6 );
00386     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.dev(), results->get_stddev(), 1e-6 );
00387 }
00388 
00389 void QualityAssessorTest::test_basic_stats_sample()
00390 {
00391     TShapeNB1 tm;
00392     IdealShapeTarget tc;
00393     TQualityMetric metric( &tc, &tm );
00394     MetricLogger logger( &metric );
00395     QualityAssessor qa( &logger );
00396     qa.disable_printing_results();
00397 
00398     MsqError err;
00399     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00400     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00401     ASSERT_NO_ERROR( err );
00402 
00403     // check didn't evaluate any sample more than once
00404     CPPUNIT_ASSERT( logger.no_duplicate_evals() );
00405 
00406     // count total number of sample points in mesh
00407     vector< Mesh::ElementHandle > elems;
00408     myMesh.get_all_elements( elems, err );
00409     ASSERT_NO_ERROR( err );
00410 
00411     vector< Mesh::VertexHandle > free_verts;
00412     PatchData global_patch;
00413     global_patch.set_mesh( &myMesh );
00414     global_patch.set_domain( &myDomain );
00415     global_patch.attach_settings( &mySettings );
00416     global_patch.set_mesh_entities( elems, free_verts, err );
00417     ASSERT_NO_ERROR( err );
00418 
00419     size_t num_samples = 0;
00420     for( size_t i = 0; i < global_patch.num_elements(); ++i )
00421         num_samples += global_patch.get_samples( i ).num_nodes();
00422 
00423     // check correct number of metric evaluations
00424     CPPUNIT_ASSERT_EQUAL( num_samples, logger.mValues.size() );
00425 
00426     // check values
00427     const QualityAssessor::Assessor* results = qa.get_results( &logger );
00428     CPPUNIT_ASSERT( results != NULL );
00429     CPPUNIT_ASSERT_EQUAL( num_samples, (size_t)results->get_count() );
00430     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.min(), results->get_minimum(), 1e-6 );
00431     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.avg(), results->get_average(), 1e-6 );
00432     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.rms(), results->get_rms(), 1e-6 );
00433     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.max(), results->get_maximum(), 1e-6 );
00434     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.dev(), results->get_stddev(), 1e-6 );
00435 }
00436 
00437 void QualityAssessorTest::test_histogram_known_range()
00438 {
00439     const double lower = 1.0, upper = 3.0;
00440     const int intervals = 5;
00441 
00442     AspectRatioGammaQualityMetric metric;
00443     MetricLogger logger( &metric );
00444     QualityAssessor qa;
00445     qa.add_histogram_assessment( &logger, lower, upper, intervals );
00446     qa.disable_printing_results();
00447 
00448     MsqError err;
00449     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00450     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00451     ASSERT_NO_ERROR( err );
00452 
00453     // calculate expected histogram
00454     std::vector< int > counts( intervals + 2, 0 );
00455     for( vector< double >::iterator i = logger.mValues.begin(); i != logger.mValues.end(); ++i )
00456     {
00457         int bucket = (int)( ( *i - lower ) * intervals / ( upper - lower ) );
00458         if( bucket < 0 )
00459             ++counts.front();
00460         else if( bucket >= intervals )
00461             ++counts.back();
00462         else
00463             ++counts[bucket + 1];
00464     }
00465 
00466     // check values
00467     const QualityAssessor::Assessor* results = qa.get_results( &logger );
00468     CPPUNIT_ASSERT( results != NULL );
00469     CPPUNIT_ASSERT( results->have_histogram() );
00470 
00471     double r_lower, r_upper;
00472     vector< int > r_counts;
00473     results->get_histogram( r_lower, r_upper, r_counts, err );
00474     ASSERT_NO_ERROR( err );
00475     CPPUNIT_ASSERT_DOUBLES_EQUAL( lower, r_lower, DBL_EPSILON );
00476     CPPUNIT_ASSERT_DOUBLES_EQUAL( upper, r_upper, DBL_EPSILON );
00477     CPPUNIT_ASSERT_EQUAL( intervals + 2, (int)r_counts.size() );
00478 
00479     CPPUNIT_ASSERT_EQUAL( counts.front(), r_counts.front() );
00480     CPPUNIT_ASSERT_EQUAL( counts.back(), r_counts.back() );
00481     switch( intervals )
00482     {
00483         default:
00484             for( unsigned i = 11; i < intervals + 1; ++i )
00485                 CPPUNIT_ASSERT_EQUAL( counts[i], r_counts[i] );
00486         case 10:
00487             CPPUNIT_ASSERT_EQUAL( counts[10], r_counts[10] );
00488         case 9:
00489             CPPUNIT_ASSERT_EQUAL( counts[9], r_counts[9] );
00490         case 8:
00491             CPPUNIT_ASSERT_EQUAL( counts[8], r_counts[8] );
00492         case 7:
00493             CPPUNIT_ASSERT_EQUAL( counts[7], r_counts[7] );
00494         case 6:
00495             CPPUNIT_ASSERT_EQUAL( counts[6], r_counts[6] );
00496         case 5:
00497             CPPUNIT_ASSERT_EQUAL( counts[5], r_counts[5] );
00498         case 4:
00499             CPPUNIT_ASSERT_EQUAL( counts[4], r_counts[4] );
00500         case 3:
00501             CPPUNIT_ASSERT_EQUAL( counts[3], r_counts[3] );
00502         case 2:
00503             CPPUNIT_ASSERT_EQUAL( counts[2], r_counts[2] );
00504         case 1:
00505             CPPUNIT_ASSERT_EQUAL( counts[1], r_counts[1] );
00506         case 0:;
00507     }
00508 }
00509 
00510 void QualityAssessorTest::test_histogram_unknown_range()
00511 {
00512     const int intervals = 10;
00513 
00514     AspectRatioGammaQualityMetric metric;
00515     MetricLogger logger( &metric );
00516     QualityAssessor qa( &logger, intervals );
00517     qa.disable_printing_results();
00518 
00519     MsqError err;
00520     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00521     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00522     ASSERT_NO_ERROR( err );
00523 
00524     // check values
00525     const QualityAssessor::Assessor* results = qa.get_results( &logger );
00526     CPPUNIT_ASSERT( results != NULL );
00527     CPPUNIT_ASSERT( results->have_histogram() );
00528 
00529     double r_lower, r_upper;
00530     vector< int > r_counts;
00531     results->get_histogram( r_lower, r_upper, r_counts, err );
00532     ASSERT_NO_ERROR( err );
00533     CPPUNIT_ASSERT( r_lower <= logger.min() );
00534     CPPUNIT_ASSERT( r_upper >= logger.min() );
00535     // allow some freedom in choice of range, but not more than 30%
00536     CPPUNIT_ASSERT( ( 0.30 * ( r_upper - r_lower ) ) <= ( logger.max() - logger.min() ) );
00537     // range must contain all metric values
00538     CPPUNIT_ASSERT_EQUAL( 0, r_counts.front() );
00539     CPPUNIT_ASSERT_EQUAL( 0, r_counts.back() );
00540     CPPUNIT_ASSERT_EQUAL( intervals + 2, (int)r_counts.size() );
00541 
00542     // calculate expected histogram
00543     std::vector< int > counts( intervals, 0 );
00544     for( vector< double >::iterator i = logger.mValues.begin(); i != logger.mValues.end(); ++i )
00545     {
00546         double fract = ( *i - r_lower ) / ( r_upper - r_lower );
00547         int bucket;
00548         if( fabs( fract - 1.0 ) < DBL_EPSILON )
00549             bucket = intervals - 1;
00550         else
00551             bucket = (int)( intervals * fract );
00552         CPPUNIT_ASSERT( bucket >= 0 );
00553         CPPUNIT_ASSERT( bucket < intervals );
00554         ++counts[bucket];
00555     }
00556 
00557     // QA should have evaluated metric twice, so adjust values
00558     CPPUNIT_ASSERT_EQUAL( 12, (int)logger.mValues.size() );
00559     for( vector< int >::iterator j = counts.begin(); j != counts.end(); ++j )
00560         *j /= 2;
00561 
00562     switch( intervals )
00563     {
00564         default:
00565             for( int i = 10; i < intervals; ++i )
00566                 CPPUNIT_ASSERT_EQUAL( counts[i], r_counts[i + 1] );
00567         case 10:
00568             CPPUNIT_ASSERT_EQUAL( counts[9], r_counts[10] );
00569         case 9:
00570             CPPUNIT_ASSERT_EQUAL( counts[8], r_counts[9] );
00571         case 8:
00572             CPPUNIT_ASSERT_EQUAL( counts[7], r_counts[8] );
00573         case 7:
00574             CPPUNIT_ASSERT_EQUAL( counts[6], r_counts[7] );
00575         case 6:
00576             CPPUNIT_ASSERT_EQUAL( counts[5], r_counts[6] );
00577         case 5:
00578             CPPUNIT_ASSERT_EQUAL( counts[4], r_counts[5] );
00579         case 4:
00580             CPPUNIT_ASSERT_EQUAL( counts[3], r_counts[4] );
00581         case 3:
00582             CPPUNIT_ASSERT_EQUAL( counts[2], r_counts[3] );
00583         case 2:
00584             CPPUNIT_ASSERT_EQUAL( counts[1], r_counts[2] );
00585         case 1:
00586             CPPUNIT_ASSERT_EQUAL( counts[0], r_counts[1] );
00587         case 0:;
00588     }
00589 }
00590 
00591 void QualityAssessorTest::test_power_mean()
00592 {
00593     const double P1 = 3.0, P2 = 0.5;
00594     AspectRatioGammaQualityMetric metric1;
00595     LocalSizeQualityMetric metric2;
00596     ConditionNumberQualityMetric metric3;
00597     MetricLogger logger1( &metric1 ), logger2( &metric2 );
00598     QualityAssessor qa( &metric3 );
00599     qa.add_quality_assessment( &logger1, 0, P1 );
00600     qa.add_quality_assessment( &logger2, 0, P2 );
00601     qa.disable_printing_results();
00602 
00603     MsqError err;
00604     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00605     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00606     ASSERT_NO_ERROR( err );
00607 
00608     // get results
00609     const QualityAssessor::Assessor *result1, *result2, *result3;
00610     result1 = qa.get_results( &logger1 );
00611     result2 = qa.get_results( &logger2 );
00612     result3 = qa.get_results( &metric3 );
00613     CPPUNIT_ASSERT( NULL != result1 );
00614     CPPUNIT_ASSERT( NULL != result2 );
00615     CPPUNIT_ASSERT( NULL != result3 );
00616 
00617     // check flags
00618     CPPUNIT_ASSERT( result1->have_power_mean() );
00619     CPPUNIT_ASSERT( result2->have_power_mean() );
00620     CPPUNIT_ASSERT( !result3->have_power_mean() );
00621 
00622     // check values
00623     CPPUNIT_ASSERT_DOUBLES_EQUAL( P1, result1->get_power(), DBL_EPSILON );
00624     CPPUNIT_ASSERT_DOUBLES_EQUAL( P2, result2->get_power(), DBL_EPSILON );
00625     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger1.pmean( P1 ), result1->get_power_mean(), logger1.pmean( P1 ) * DBL_EPSILON );
00626     CPPUNIT_ASSERT_DOUBLES_EQUAL( logger2.pmean( P2 ), result2->get_power_mean(), logger2.pmean( P2 ) * DBL_EPSILON );
00627 }
00628 
00629 void QualityAssessorTest::test_invalid_count()
00630 {
00631     MsqError err;
00632     ConditionNumberQualityMetric metric;
00633     QualityAssessor qa( &metric );
00634     qa.measure_free_samples_only( false );
00635     qa.disable_printing_results();
00636     const QualityAssessor::Assessor* results = qa.get_results( &metric );
00637     CPPUNIT_ASSERT( NULL != results );
00638 
00639     // try mesh with only valid elements
00640     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00641     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00642     ASSERT_NO_ERROR( err );
00643     CPPUNIT_ASSERT_EQUAL( 0, results->get_invalid_element_count() );
00644 
00645     // try mesh with one inverted element
00646     MeshDomainAssoc mesh_and_domain2 = MeshDomainAssoc( &invertedMesh, &myDomain );
00647     qa.loop_over_mesh( &mesh_and_domain2, &mySettings, err );
00648     ASSERT_NO_ERROR( err );
00649     CPPUNIT_ASSERT_EQUAL( 1, results->get_invalid_element_count() );
00650 }
00651 
00652 void QualityAssessorTest::test_inverted_count()
00653 {
00654     int inverted, samples;
00655     MsqError err;
00656     QualityAssessor qa;
00657     qa.measure_free_samples_only( false );
00658     qa.disable_printing_results();
00659 
00660     // try mesh with only valid elements
00661     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00662     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00663     ASSERT_NO_ERROR( err );
00664     inverted = samples = -1;
00665     qa.get_inverted_element_count( inverted, samples, err );
00666     ASSERT_NO_ERROR( err );
00667     CPPUNIT_ASSERT_EQUAL( 0, inverted );
00668     CPPUNIT_ASSERT_EQUAL( 0, samples );
00669 
00670     // try mesh with one inverted element
00671     MeshDomainAssoc mesh_and_domain2 = MeshDomainAssoc( &invertedMesh, &myDomain );
00672     qa.loop_over_mesh( &mesh_and_domain2, &mySettings, err );
00673     ASSERT_NO_ERROR( err );
00674     inverted = samples = -1;
00675     qa.get_inverted_element_count( inverted, samples, err );
00676     ASSERT_NO_ERROR( err );
00677     CPPUNIT_ASSERT_EQUAL( 1, inverted );
00678     CPPUNIT_ASSERT_EQUAL( 1, samples );
00679 }
00680 
00681 // Define class to handle redirecting output streams
00682 // (e.g. std::cout) to a stringstream instance.  We
00683 // use this to catch errors where QualityAssessor is
00684 // writing to a stream it should not be.
00685 class StreamRedirector
00686 {
00687   private:
00688     streambuf *outbuf, *errbuf, *logbuf;
00689 
00690   public:
00691     ostringstream outstr, errstr, logstr;
00692     StreamRedirector()
00693     {
00694         outbuf = cout.rdbuf();
00695         errbuf = cerr.rdbuf();
00696         logbuf = clog.rdbuf();
00697     }
00698 
00699     ~StreamRedirector()
00700     {
00701         restore();
00702     }
00703 
00704     void redirect()
00705     {
00706         clear();
00707         cout.rdbuf( outstr.rdbuf() );
00708         cerr.rdbuf( errstr.rdbuf() );
00709         clog.rdbuf( logstr.rdbuf() );
00710     }
00711 
00712     void restore()
00713     {
00714         cout.rdbuf( outbuf );
00715         cerr.rdbuf( errbuf );
00716         clog.rdbuf( logbuf );
00717     }
00718     // reset all streams to empty
00719     void clear()
00720     {
00721         string empty;
00722         outstr.str( empty );
00723         errstr.str( empty );
00724         logstr.str( empty );
00725     }
00726 
00727     void print_stream_bytes( const char* prefix, string bytes )
00728     {
00729         // fix output stream so we can use it to print results
00730         streambuf* sbuf = cout.rdbuf();
00731         cout.rdbuf( outbuf );
00732         cout << prefix << ": " << bytes.size() << " characters: ";
00733         for( string::iterator i = bytes.begin(); i != bytes.end(); ++i )
00734             if( isprint( *i ) || *i == ' ' )
00735                 cout << *i;
00736             else
00737                 switch( *i )
00738                 {
00739                     case '\a':
00740                         cout << "\\a";
00741                         break;
00742                     case '\b':
00743                         cout << "\\b";
00744                         break;
00745                     case '\t':
00746                         cout << "\\t";
00747                         break;
00748                     case '\n':
00749                         cout << "\\n";
00750                         break;
00751                     case '\v':
00752                         cout << "\\v";
00753                         break;
00754                     case '\f':
00755                         cout << "\\f";
00756                         break;
00757                     case '\r':
00758                         cout << "\\r";
00759                         break;
00760                     case '\0':
00761                         cout << "\\0";
00762                         break;
00763                     default:
00764                         cout << '\\' << setw( 3 ) << setfill( '0' ) << oct << *i;
00765                         break;
00766                 }
00767         cout << endl;
00768         // restore cout to incomming state
00769         cout.rdbuf( sbuf );
00770     }
00771 
00772     void check_stream( const char* name, ostringstream& str, bool& result )
00773     {
00774         string s = str.str();
00775         if( s.empty() ) return;
00776         result = true;
00777         print_stream_bytes( name, s );
00778     }
00779 
00780     // check if any stream was written to (may clear streams, depending on platform)
00781     bool have_data()
00782     {
00783         bool result = false;
00784         check_stream( "std::cout", outstr, result );
00785         check_stream( "std::cerr", errstr, result );
00786         check_stream( "std::clog", logstr, result );
00787         return result;
00788     }
00789 };
00790 
00791 void QualityAssessorTest::test_output_control()
00792 {
00793     // Redirect std::cout, std:cerr, and std::clog
00794     // so we know when they're written to.
00795     StreamRedirector redir;
00796 
00797     MsqError err;
00798     ConditionNumberQualityMetric metric;
00799 
00800     // disable output from constructor
00801     QualityAssessor qa1( &metric, 0, 0, false, 0, false );
00802     redir.redirect();
00803     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &invertedMesh, &myDomain );
00804     qa1.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00805     redir.restore();
00806     // make sure nothing was written to output streams
00807     CPPUNIT_ASSERT( !redir.have_data() );
00808 
00809     // disable output from function
00810     QualityAssessor qa2( &metric );
00811     qa2.disable_printing_results();
00812     redir.redirect();
00813     qa2.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00814     redir.restore();
00815     // make sure nothing was written to output streams
00816     CPPUNIT_ASSERT( !redir.have_data() );
00817 
00818     // send output to alternate stream
00819     stringstream deststr;
00820     QualityAssessor qa3( deststr, &metric );
00821     redir.redirect();
00822     qa3.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00823     redir.restore();
00824     // make sure nothing was written to output streams
00825     CPPUNIT_ASSERT( !redir.have_data() );
00826     // check that some data was written to specified stream
00827     CPPUNIT_ASSERT( !deststr.str().empty() );
00828 
00829     // redir destructor should now restore normal output streams
00830 }
00831 
00832 void QualityAssessorTest::test_tag_element()
00833 {
00834     const char* tag_name = "xxxTEST_TAGxxx";
00835     ConditionNumberQualityMetric metric;
00836     MetricLogger logger( &metric );
00837     QualityAssessor qa( &logger, 0, 0, false, tag_name );
00838     qa.disable_printing_results();
00839 
00840     MsqError err;
00841     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00842     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00843     ASSERT_NO_ERROR( err );
00844 
00845     TagHandle tag;
00846     tag = myMesh.tag_get( tag_name, err );
00847     ASSERT_NO_ERROR( err );
00848 
00849     string name;
00850     Mesh::TagType type;
00851     unsigned length;
00852     myMesh.tag_properties( tag, name, type, length, err );
00853     ASSERT_NO_ERROR( err );
00854     CPPUNIT_ASSERT_EQUAL( name, string( tag_name ) );
00855     CPPUNIT_ASSERT_EQUAL( Mesh::DOUBLE, type );
00856     CPPUNIT_ASSERT_EQUAL( 1u, length );
00857 
00858     CPPUNIT_ASSERT_EQUAL( logger.mValues.size(), logger.mHandles.size() );
00859     CPPUNIT_ASSERT( !logger.mValues.empty() );
00860     vector< double > tag_values( logger.mValues.size() );
00861     myMesh.tag_get_element_data( tag, logger.mHandles.size(), &logger.mHandles[0], arrptr( tag_values ), err );
00862     ASSERT_NO_ERROR( err );
00863 
00864     for( unsigned i = 0; i < logger.mValues.size(); ++i )
00865         CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.mValues[1], tag_values[1], DBL_EPSILON );
00866 }
00867 
00868 void QualityAssessorTest::test_tag_vertex()
00869 {
00870     const char* tag_name = "vertex-test-tag";
00871     LocalSizeQualityMetric metric;
00872     MetricLogger logger( &metric );
00873     QualityAssessor qa( &logger, 0, 0, false, tag_name );
00874     qa.disable_printing_results();
00875 
00876     MsqError err;
00877     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00878     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00879     ASSERT_NO_ERROR( err );
00880 
00881     TagHandle tag;
00882     tag = myMesh.tag_get( tag_name, err );
00883     ASSERT_NO_ERROR( err );
00884 
00885     string name;
00886     Mesh::TagType type;
00887     unsigned length;
00888     myMesh.tag_properties( tag, name, type, length, err );
00889     ASSERT_NO_ERROR( err );
00890     CPPUNIT_ASSERT_EQUAL( name, string( tag_name ) );
00891     CPPUNIT_ASSERT_EQUAL( Mesh::DOUBLE, type );
00892     CPPUNIT_ASSERT_EQUAL( 1u, length );
00893 
00894     CPPUNIT_ASSERT_EQUAL( logger.mValues.size(), logger.mHandles.size() );
00895     vector< double > tag_values( logger.mValues.size() );
00896     myMesh.tag_get_vertex_data( tag, logger.mHandles.size(), &logger.mHandles[0], arrptr( tag_values ), err );
00897     ASSERT_NO_ERROR( err );
00898 
00899     for( unsigned i = 0; i < logger.mValues.size(); ++i )
00900         CPPUNIT_ASSERT_DOUBLES_EQUAL( logger.mValues[1], tag_values[1], DBL_EPSILON );
00901 }
00902 
00903 void QualityAssessorTest::test_tag_inverted()
00904 {
00905     const char* tag_name = "123INVERT456";
00906     QualityAssessor qa( false, false, tag_name );
00907 
00908     MsqError err;
00909     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &invertedMesh, &myDomain );
00910     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00911     ASSERT_NO_ERROR( err );
00912 
00913     TagHandle tag;
00914     tag = invertedMesh.tag_get( tag_name, err );
00915     ASSERT_NO_ERROR( err );
00916 
00917     string name;
00918     Mesh::TagType type;
00919     unsigned length;
00920     invertedMesh.tag_properties( tag, name, type, length, err );
00921     ASSERT_NO_ERROR( err );
00922     CPPUNIT_ASSERT_EQUAL( name, string( tag_name ) );
00923     CPPUNIT_ASSERT_EQUAL( Mesh::INT, type );
00924     CPPUNIT_ASSERT_EQUAL( 1u, length );
00925 
00926     vector< Mesh::ElementHandle > elements;
00927     invertedMesh.get_all_elements( elements, err );
00928     ASSERT_NO_ERROR( err );
00929 
00930     // expect two elements, one inverted
00931     CPPUNIT_ASSERT_EQUAL( (size_t)2, elements.size() );
00932     int data[2];
00933     invertedMesh.tag_get_element_data( tag, 2, arrptr( elements ), data, err );
00934     ASSERT_NO_ERROR( err );
00935 
00936     if( data[0] )
00937     {
00938         CPPUNIT_ASSERT_EQUAL( 0, data[1] );
00939     }
00940     else
00941     {
00942         CPPUNIT_ASSERT_EQUAL( 0, data[0] );
00943         CPPUNIT_ASSERT_EQUAL( 1, data[1] );
00944     }
00945 }
00946 
00947 void QualityAssessorTest::test_print_inverted()
00948 {
00949     MsqError err;
00950     stringstream str;
00951     QualityAssessor qa( str );
00952     qa.measure_free_samples_only( false );
00953     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &invertedMesh, &myDomain );
00954     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00955     ASSERT_NO_ERROR( err );
00956 
00957     // get inverted count from QA
00958     int expected = 0, other = 0;
00959     qa.get_inverted_element_count( expected, other, err );
00960     ASSERT_NO_ERROR( err );
00961 
00962     // At some point, expect "n INVERTED" to appear in output,
00963     // where 'n' is the count of inverted elements.
00964     int value;
00965     string prev, curr;
00966     for( ; str >> curr && curr != "INVERTED"; prev = curr )
00967         ;
00968     str.str( prev );
00969     str >> value;
00970 
00971     CPPUNIT_ASSERT_EQUAL( expected, value );
00972 }
00973 
00974 void QualityAssessorTest::test_print_stats()
00975 {
00976     MsqError err;
00977     stringstream str;
00978     ConditionNumberQualityMetric metric;
00979     QualityAssessor qa( str, &metric );
00980     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
00981     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
00982     ASSERT_NO_ERROR( err );
00983 
00984     // get results
00985     const QualityAssessor::Assessor* results = qa.get_results( &metric );
00986     CPPUNIT_ASSERT( NULL != results );
00987 
00988     // Expect metric name followed by 5 numerical values for statistics,
00989     // at some point in output.
00990 
00991     // Find occurance of metric name in output
00992     for( ;; )
00993     {
00994         stringstream name( metric.get_name() );
00995         string s, n;
00996         name >> n;
00997         while( str >> s && s != n )
00998             ;
00999         CPPUNIT_ASSERT( str );
01000         while( name >> n && str >> s && s == n )
01001             ;
01002         if( !name ) break;
01003     }
01004 
01005     // Read stats
01006     double min_s, max_s, avg_s, rms_s, dev_s;
01007     str >> min_s >> avg_s >> rms_s >> max_s >> dev_s;
01008 
01009     // The following commented out because they no longer pass due to a change
01010     //  in the QA Summary format
01011 
01012     // compare results
01013     //  CPPUNIT_ASSERT_DOUBLES_EQUAL( results->get_minimum(), min_s, min_s * 0.01 );
01014     //  CPPUNIT_ASSERT_DOUBLES_EQUAL( results->get_average(), avg_s, avg_s * 0.01 );
01015     //  CPPUNIT_ASSERT_DOUBLES_EQUAL( results->get_rms    (), rms_s, rms_s * 0.01 );
01016     //  CPPUNIT_ASSERT_DOUBLES_EQUAL( results->get_maximum(), max_s, max_s * 0.01 );
01017     //  CPPUNIT_ASSERT_DOUBLES_EQUAL( results->get_stddev (), dev_s, dev_s * 0.01 );
01018 }
01019 
01020 void QualityAssessorTest::test_print_name()
01021 {
01022     const char* NAME = "test_print_name_NAME";
01023 
01024     // expect QualityAssessor Name to be printed somewhere in output.
01025     MsqError err;
01026     stringstream str;
01027     ConditionNumberQualityMetric metric;
01028     QualityAssessor qa( str, &metric, 0, 0, false, 0, 0, NAME );
01029     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &myMesh, &myDomain );
01030     qa.loop_over_mesh( &mesh_and_domain, &mySettings, err );
01031     ASSERT_NO_ERROR( err );
01032 
01033     // seach output for first occurance of name
01034     string s;
01035     while( str >> s && s != NAME )
01036         ;
01037     CPPUNIT_ASSERT_EQUAL( s, string( NAME ) );
01038 }
01039 
01040 // Test that adding a metric more than once changes
01041 // properties for a single occurance of the metric,
01042 // rather than introducing multiple copies.
01043 void QualityAssessorTest::test_modify_metric()
01044 {
01045     MsqError err;
01046     ConditionNumberQualityMetric metric;
01047     QualityAssessor qa( &metric );
01048     const QualityAssessor::Assessor* results = qa.get_results( &metric );
01049     CPPUNIT_ASSERT( NULL != results );
01050 
01051     CPPUNIT_ASSERT( !results->have_histogram() );
01052     qa.add_histogram_assessment( &metric, 0.0, 1.0, 10 );
01053     CPPUNIT_ASSERT( results->have_histogram() );
01054 
01055     CPPUNIT_ASSERT( !results->have_power_mean() );
01056     qa.add_quality_assessment( &metric, 0, 3.0 );
01057     CPPUNIT_ASSERT( results->have_power_mean() );
01058 }
01059 
01060 void QualityAssessorTest::test_free_only()
01061 {
01062     /*
01063      *   +--+--------o-----o
01064      *   |   \       |     |    + - fixed
01065      *   |    \      |     |    o - free
01066      *   +-----+-----o-----o
01067      */
01068 
01069     double coords[]      = { 0, 0, 0, 1, 0, 0, 0.5, 1, 0, 0, 1, 0, 2, 0, 0, 3, 0, 0, 3, 1, 0, 2, 1, 0 };
01070     int fixed[]          = { true, true, true, true, false, false, false, false };
01071     unsigned long conn[] = { 0, 1, 2, 3, 1, 4, 7, 2, 4, 5, 6, 7 };
01072 
01073     MsqError err;
01074     ArrayMesh mesh( 3, 8, coords, fixed, 3, QUADRILATERAL, conn );
01075     ConditionNumberQualityMetric metric;
01076     QualityAssessor qa_all( &metric, 0, 0.0, false, 0, false );
01077     QualityAssessor qa_free( &metric, 0, 0.0, true, 0, false );
01078     InstructionQueue q;
01079     q.add_quality_assessor( &qa_all, err );
01080     q.add_quality_assessor( &qa_free, err );
01081     PlanarDomain xy( PlanarDomain::XY );
01082     MeshDomainAssoc mesh_and_domain = MeshDomainAssoc( &mesh, &xy );
01083     q.run_instructions( &mesh_and_domain, err );
01084     ASSERT_NO_ERROR( err );
01085 
01086     const QualityAssessor::Assessor* data;
01087     data = qa_all.get_results( &metric );
01088     CPPUNIT_ASSERT( 0 != data );
01089     CPPUNIT_ASSERT_EQUAL( 3, data->get_count() );
01090 
01091     data = qa_free.get_results( &metric );
01092     CPPUNIT_ASSERT( 0 != data );
01093     CPPUNIT_ASSERT_EQUAL( 2, data->get_count() );
01094 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines