ConstrainedOptPack_QPSolverRelaxedLOQO.cpp

Go to the documentation of this file.
```00001 // @HEADER
00002 // ***********************************************************************
00003 //
00004 // Moocho: Multi-functional Object-Oriented arCHitecture for Optimization
00005 //                  Copyright (2003) Sandia Corporation
00006 //
00007 // Under terms of Contract DE-AC04-94AL85000, there is a non-exclusive
00008 // license for use of this work by or on behalf of the U.S. Government.
00009 //
00010 // This library is free software; you can redistribute it and/or modify
00011 // it under the terms of the GNU Lesser General Public License as
00012 // published by the Free Software Foundation; either version 2.1 of the
00014 //
00015 // This library is distributed in the hope that it will be useful, but
00016 // WITHOUT ANY WARRANTY; without even the implied warranty of
00017 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018 // Lesser General Public License for more details.
00019 //
00020 // You should have received a copy of the GNU Lesser General Public
00021 // License along with this library; if not, write to the Free Software
00022 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
00023 // USA
00024 // Questions? Contact Roscoe A. Bartlett (rabartl@sandia.gov)
00025 //
00026 // ***********************************************************************
00028 //
00029 // Here we map from the QPSolverRelaxed QP formulation to the LOQO QP formulation.
00030 //
00031 // QPSolverRelaxed QP formulation:
00032 // ------------------------------
00033 //
00034 // min     g'*d + 1/2 * d'*G*d + (eta + 1/2*eta^2) * M
00035 // s.t.    dL   <= d                   <= dU
00036 //         etaL <= eta
00037 //         eL   <= op(E)*d - b*eta     <= eU
00038 //                 op(F)*d + (1-eta)*f  = 0
00039 //
00040 // LOQO QP formulation:
00041 // -------------------
00042 //
00043 // min      c'*x + 1/2 * x'*Q*x
00044 // s.t.     b <= A*x <= b + r
00045 //          l <= x <= u
00046 //
00047 // Mapping =>
00048 //
00049 // LOQO   QPSolverRelaxed
00050 // ----   ---------------
00051 // x      [ d; eta ]
00052 // c      [ g; M ]
00053 // Q      [ G, 0; 0, M ]
00054 // A      [ op(E), -b; op(F), -f ]
00055 // b      [ eL; -f ]
00056 // r      [ eU-eL; 0 ]
00057 // l      [ dL, etaL ]
00058 // u      [ dU, +inf ]
00059 //
00060 // Above, in the LOQO formulation all singly bounded inequalities
00061 // must be formulated as b(j) <= A(j,:)*x with r(j) = inf.  This
00062 // will require some fudging since eL(j) == -inf may be true in some
00063 // cases.  Here we will have to exchange eL(j) and eU(j) and use
00064 // A(j,:) = -op(E)(j,:).
00065 //
00066
00067 #ifdef CONSTRAINED_OPTIMIZATION_PACK_USE_LOQO
00068
00069 #include <assert.h>
00070
00071 #include <vector>
00072
00073 #include "ConstrainedOptPack_QPSolverRelaxedLOQO.hpp"
00074 #include "ConstrainedOptPack/src/AbstractLinAlgPack_MatrixExtractInvCholFactor.hpp"
00075 #include "AbstractLinAlgPack_SpVectorOp.hpp"
00076 #include "AbstractLinAlgPack/src/AbstractLinAlgPack_MatrixOp.hpp"
00077 #include "AbstractLinAlgPack_SortByDescendingAbsValue.hpp"
00078 #include "AbstractLinAlgPack_sparse_bounds.hpp"
00079 #include "AbstractLinAlgPack/src/AbstractLinAlgPack_EtaVector.hpp"
00080 #include "AbstractLinAlgPack_sparse_bounds.hpp"
00081 #include "DenseLinAlgPack_LinAlgOpPack.hpp"
00082 #include "Midynamic_cast_verbose.h"
00083 #include "MiWorkspacePack.h"
00084
00085 extern "C" {
00086 #include "loqo.h"     // -I\$(LOQODIR)
00087 #include "myalloc.h"  // -I\$(LOQODIR)
00088 } // end extern "C"
00089
00090 namespace LinAlgOpPack {
00091   using AbstractLinAlgPack::Vp_StV;
00092   using AbstractLinAlgPack::Mp_StM;
00093   using AbstractLinAlgPack::Vp_StMtV;
00094 }
00095
00096 namespace ConstrainedOptPack {
00097
00098 // ///////////////////////////////////////
00099 // Members for QPSolverRelaxedLOQO::InitLOQOHessianJacobian
00100
00101 void QPSolverRelaxedLOQO::InitLOQOHessianJacobian::init_hess_jacob(
00102   const MatrixOp& G, const value_type bigM
00103   , const MatrixOp* E, BLAS_Cpp::Transp trans_E, const DVectorSlice* b
00104   , const int loqo_b_stat[], const size_type num_inequal
00105   , const MatrixOp* F, BLAS_Cpp::Transp trans_F, const DVectorSlice* f
00106   , void* _loqo_lp
00107   ) const
00108 {
00109
00110   LOQO* loqo_lp = (LOQO*)_loqo_lp;
00111
00112   const size_type
00113     nd    = G.rows(),
00114     m_in  = E ? b->size() : 0,
00115     m_eq  = F ? f->size() : 0;
00116
00117   TEST_FOR_EXCEPT( !(  loqo_lp->n == nd + 1  ) );
00118   TEST_FOR_EXCEPT( !(  loqo_lp->m == num_inequal + m_eq  ) );
00119
00120   // This default implementation assumes G, E and F are completely dense!
00121
00122   //
00123   // Setup Q
00124   //
00125
00126   loqo_lp->qnz = nd*nd + 1;
00127   MALLOC( loqo_lp->Q, loqo_lp->qnz, double );
00128   MALLOC( loqo_lp->iQ, loqo_lp->qnz, int );
00129   MALLOC( loqo_lp->kQ, nd+2, int );
00130   // Setup kQ[] and iQ[]
00131   {for( size_type j = 1; j <= nd; ++j ) {
00132     loqo_lp->kQ[j-1] = nd*(j-1);
00133     for( size_type i = 1; i <= nd; ++i )
00134       loqo_lp->iQ[ loqo_lp->kQ[j-1] + (i-1) ] = i-1; // zero based in LOQO
00135   }}
00136   loqo_lp->kQ[nd]              = nd*nd;
00137   loqo_lp->iQ[loqo_lp->kQ[nd]] = nd; // zero based in LOQO
00138   loqo_lp->kQ[nd+1]            = nd*nd + 1;
00139   // Setup Q[]
00140   {
00141     DMatrixSlice Q( loqo_lp->Q, nd*nd, nd, nd, nd );
00142     LinAlgOpPack::assign( &Q, G, BLAS_Cpp::no_trans );
00143     loqo_lp->Q[nd*nd] = bigM;
00144   }
00145
00146   //
00147   // Setup A
00148   //
00149
00150   loqo_lp->nz = (num_inequal+m_eq) * (nd+1);
00151   MALLOC( loqo_lp->A,  loqo_lp->nz, double );
00152   MALLOC( loqo_lp->iA, loqo_lp->nz, int );
00153   MALLOC( loqo_lp->kA, nd+2, int );
00154
00155   if( num_inequal == m_in ) {
00156     // All the inequalities have finite bounds
00157     // Setup kA[] and iA[]
00158     {for( size_type j = 1; j <= nd+1; ++j ) {
00159       loqo_lp->kA[j-1] = (m_in+m_eq)*(j-1);
00160       for( size_type i = 1; i <= m_in+m_eq; ++i )
00161         loqo_lp->iA[ loqo_lp->kA[j-1] + (i-1) ] = i-1; // zero based in LOQO
00162     }}
00163     loqo_lp->kA[nd+1] = (m_in+m_eq)*(nd+1);
00164     // Setup A[]
00165     DMatrixSlice A( loqo_lp->A, loqo_lp->nz, loqo_lp->m, loqo_lp->m, nd+1 );
00166     if(E) {
00167       LinAlgOpPack::assign( &A(1,m_in,1,nd), *E, trans_E );  // A(1:m_in,1:nd) = op(E)
00168       LinAlgOpPack::V_StV( &A.col(nd+1)(1,m_in), -1.0, *b ); // A(1:m_in,nd+1) = -b
00169     }
00170     if(F) {
00171       LinAlgOpPack::assign( &A(m_in+1,m_in+m_eq,1,nd), *F, trans_F );  // A(m_in+1:m_in+m_eq,1:nd) = op(F)
00172       LinAlgOpPack::V_StV( &A.col(nd+1)(m_in+1,m_in+m_eq), -1.0, *f ); // A(m_in+1:m_in+m_eq,nd+1) = -f
00173     }
00174   }
00175   else {
00176     // At least one of the inequality constriants has
00177     // both infinite upper and lower bounds.
00178     TEST_FOR_EXCEPT(true); // ToDo: Finish this!
00179   }
00180
00181   // Loop through and adjust A for absent lower bound and using upper bound
00182   if( num_inequal ) {
00183     DMatrixSlice A( loqo_lp->A, loqo_lp->nz, loqo_lp->m, loqo_lp->m, nd+1 );
00184     for(size_type k = 1; k <= num_inequal; ++k ) {
00185       const int j = loqo_b_stat[k-1];
00186       if( j < 0 )
00187         DenseLinAlgPack::Vt_S( &A.row(j), -1.0 );
00188     }
00189   }
00190
00191 }
00192
00193 // ///////////////////////////////////////
00194 // Members for QPSolverRelaxedLOQO
00195
00196 QPSolverRelaxedLOQO::QPSolverRelaxedLOQO(
00197   const init_hess_jacob_ptr_t  init_hess_jacob
00198   ,value_type                  bigM
00199   ,value_type                  nonbinding_lag_mult
00200   )
00201   :init_hess_jacob_(init_hess_jacob)
00202   ,bigM_(bigM)
00203   ,nonbinding_lag_mult_(nonbinding_lag_mult)
00204 {
00205 //  bigM_ = 1.0; // Just test this!
00206   nonbinding_lag_mult_ = 1e-6;
00207 }
00208
00209 QPSolverRelaxedLOQO::~QPSolverRelaxedLOQO()
00210 {
00211   this->release_memory();
00212 }
00213
00214 // Overridden from QPSolverRelaxed
00215
00216 QPSolverStats
00217 QPSolverRelaxedLOQO::get_qp_stats() const
00218 {
00219   return qp_stats_;
00220 }
00221
00222 void QPSolverRelaxedLOQO::release_memory()
00223 {
00224   // Todo: resize to zero all the workspace!
00225 }
00226
00227 QPSolverStats::ESolutionType
00228 QPSolverRelaxedLOQO::imp_solve_qp(
00229       std::ostream* out, EOutputLevel olevel, ERunTests test_what
00230     , const DVectorSlice& g, const MatrixOp& G
00231     , value_type etaL
00232     , const SpVectorSlice& dL, const SpVectorSlice& dU
00233     , const MatrixOp* E, BLAS_Cpp::Transp trans_E, const DVectorSlice* b
00234       , const SpVectorSlice* eL, const SpVectorSlice* eU
00235     , const MatrixOp* F, BLAS_Cpp::Transp trans_F, const DVectorSlice* f
00236     , value_type* obj_d
00237     , value_type* eta, DVectorSlice* d
00238     , SpVector* nu
00239     , SpVector* mu, DVectorSlice* Ed
00240     , DVectorSlice* lambda, DVectorSlice* Fd
00241   )
00242 {
00243   using Teuchos::Workspace;
00244   Teuchos::WorkspaceStore* wss = wsp::default_workspace_store.get();
00245
00246   const value_type inf_bnd  = std::numeric_limits<value_type>::max();
00247 //  const value_type real_big = 1e+20;
00248   const value_type real_big = HUGE_VAL;
00249
00250   const size_type
00251     nd   = g.size(),
00252     m_in = E ? b->size() : 0,
00253     m_eq = F ? f->size() : 0;
00254
00255   //
00256   // Create a LOQO QP definition struct
00257   //
00258
00259   LOQO *loqo_lp = openlp();
00260   TEST_FOR_EXCEPT( !(  loqo_lp  ) );
00261
00262   //
00263   // Setup loqo_r and loqo_b and count the number of actual
00264   // constraints.
00265   //
00266
00267   // LOQO's b vector storage
00268   MALLOC( loqo_lp->b, m_in+m_eq, double ); // May not use all of this storage
00269   DVectorSlice loqo_b( loqo_lp->b, m_in+m_eq );
00270   // LOQO's r vector storage
00271   MALLOC( loqo_lp->r, m_in+m_eq, double ); // May not use all of this storage
00272   DVectorSlice loqo_r( loqo_lp->r, m_in+m_eq );
00273   // Gives status of b.
00274   //                  /  j : if eL(j) > -inf_bnd
00275   // loqo_b_stat(k) = |
00276   //                  \ -j : if eL(j) <= -inf_bnd && eU(j) < +inf_bnd
00277   //
00278   // , for k = 1...num_inequal
00279   //
00280   Workspace<int>               loqo_b_stat_ws(wss,m_in); // May not use all of this
00281   DenseLinAlgPack::VectorSliceTmpl<int>  loqo_b_stat(&loqo_b_stat_ws[0],loqo_b_stat_ws.size());
00282   std::fill( loqo_b_stat.begin(), loqo_b_stat.end(), 0 ); // Initialize to zero
00283
00284   // Fill up loqo_b, loqo_r and loqo_b_stat
00285   size_type num_inequal = 0; // The actual number of bouned general inequalities
00286   if(E) {
00288     AbstractLinAlgPack::sparse_bounds_itr
00289       eLU_itr( eL->begin(), eL->end(), eL->offset()
00290            , eU->begin(), eU->end(), eU->offset(), inf_bnd );
00291     // written iterators
00292     DVectorSlice::iterator
00293       b_itr   = loqo_b.begin(),
00294       r_itr   = loqo_r.begin();
00295     DenseLinAlgPack::VectorSliceTmpl<int>::iterator
00296       b_stat_itr  = loqo_b_stat.begin();
00297     // loop
00298     for( int k = 1; !eLU_itr.at_end(); ++k, ++eLU_itr, ++b_itr, ++r_itr, ++b_stat_itr, ++num_inequal )
00299     {
00300       const size_type j = eLU_itr.indice();
00301       if(eLU_itr.lbound() > -inf_bnd) {
00302         *b_itr = eLU_itr.lbound();
00303         *r_itr = eLU_itr.ubound() >= inf_bnd ? real_big : eLU_itr.ubound() - eLU_itr.lbound();
00304         *b_stat_itr = j; // We need to make A(k,:) = [ +op(E)(j,:), -b(j) ]
00305       }
00306       else {
00307         TEST_FOR_EXCEPT( !( eLU_itr.ubound() < +inf_bnd ) );
00308         *b_itr = -eLU_itr.ubound();
00309         *r_itr = eLU_itr.lbound() <= -inf_bnd ? real_big : - eLU_itr.lbound() + eLU_itr.ubound();
00310         *b_stat_itr = -j; // We need to make A(k,:) = [ -op(E)(j,:), +b(j) ]
00311       }
00312     }
00313   }
00314   if(F) {
00315     LinAlgOpPack::V_StV( &loqo_b(num_inequal+1,num_inequal+m_eq), -1.0, *f );
00316     loqo_r(num_inequal+1,num_inequal+m_eq) = 0.0;
00317   }
00318
00319   //
00320   // Setup the QP dimensions
00321   //
00322
00323   loqo_lp->n = nd+1;
00324   loqo_lp->m = num_inequal + m_eq;
00325
00326   //
00327   // Setup loqo_c, loqo_l and loqo_u
00328   //
00329
00330   // LOQO's c vector storage
00331   MALLOC( loqo_lp->c, nd+1, double );
00332   DVectorSlice loqo_c( loqo_lp->c, nd+1 );
00333   loqo_c(1,nd) = g;
00334   loqo_c(nd+1) = bigM();
00335
00336   // LOQO's l vector storage
00337   MALLOC( loqo_lp->l, nd+1, double );
00338   DVectorSlice loqo_l( loqo_lp->l, nd+1 );
00339   std::fill( loqo_l.begin(), loqo_l.end(), -real_big );
00340   {
00341     SpVectorSlice::const_iterator
00342       dL_itr = dL.begin(),
00343       dL_end = dL.end();
00344     for( ; dL_itr != dL_end; ++dL_itr )
00345       loqo_l( dL_itr->indice() + dL.offset() ) = dL_itr->value();
00346   }
00347   loqo_l(nd+1) = etaL;
00348
00349   // LOQO's u vector storage
00350   MALLOC( loqo_lp->u, nd+1, double );
00351   DVectorSlice loqo_u( loqo_lp->u, nd+1 );
00352   std::fill( loqo_u.begin(), loqo_u.end(), +real_big );
00353   {
00354     SpVectorSlice::const_iterator
00355       dU_itr = dU.begin(),
00356       dU_end = dU.end();
00357     for( ; dU_itr != dU_end; ++dU_itr )
00358       loqo_u( dU_itr->indice() + dU.offset() ) = dU_itr->value();
00359   }
00360   loqo_u(nd+1) = +real_big;
00361
00362   //
00363   // Setup the objective and constraint matrices (using strategy interface).
00364   //
00365
00366   init_hess_jacob().init_hess_jacob(
00367     G,bigM(),E,trans_E,b,&loqo_b_stat[0],num_inequal,F,trans_F,f
00368     ,loqo_lp);
00369
00370   //
00371   // Setup the starting point
00372   //
00373
00374   MALLOC( loqo_lp->x, nd+1, double );
00375   DVectorSlice loqo_x( loqo_lp->x, nd+1 );
00376   loqo_x(1,nd) = *d;
00377   loqo_x(nd+1) = *eta;
00378
00379   //
00380   // Set some control parameters
00381   //
00382
00383 //  strcpy( loqo_lp->name, "loqo_qp" );
00385   loqo_lp->convex    = 1;
00386   switch( olevel ) {
00387     case PRINT_NONE:
00388       loqo_lp->verbose = 0;
00389       break;
00390     case PRINT_BASIC_INFO:
00391       loqo_lp->verbose = 1;
00392       break;
00393     case PRINT_ITER_SUMMARY:
00394       loqo_lp->verbose = 2;
00395       break;
00396     case PRINT_ITER_STEPS:
00397       loqo_lp->verbose = 3;
00398       break;
00399     case PRINT_ITER_ACT_SET:
00400       loqo_lp->verbose = 4;
00401       break;
00402     case PRINT_ITER_VECTORS:
00403       loqo_lp->verbose = 5;
00404       break;
00405     case PRINT_EVERY_THING:
00406       loqo_lp->verbose = 6;
00407       break;
00408     default:
00409       TEST_FOR_EXCEPT(true);
00410   }
00411
00412   //
00413   // Solve the QP
00414   //
00415
00416   if( out && olevel >= PRINT_BASIC_INFO ) {
00417     *out << "\nSolving QP using LOQO ...\n";
00418     out->flush();
00419   }
00420
00421   const int loqo_status = solvelp(loqo_lp);
00422
00423   if( out && olevel >= PRINT_BASIC_INFO ) {
00424     *out << "\nLOQO returned status = " << loqo_status << "\n";
00425   }
00426
00427   //
00428   // Map the solution to the output arguments
00429   //
00430
00431   TEST_FOR_EXCEPT( !(  loqo_lp->x  ) );
00432   DVectorSlice loqo_x_sol( loqo_lp->x, nd+1 );
00433
00434   // d
00435   *d    = loqo_x_sol(1,nd);
00436
00437   // eta
00438   *eta  = loqo_x_sol(nd+1);
00439
00440   // obj_d
00441   if(obj_d)
00442     *obj_d = loqo_lp->primal_obj - (*eta + 0.5 * (*eta)*(*eta)) * bigM();
00443
00444   // nu
00445   if(nu) {
00446     nu->resize(nd,nd);
00447     TEST_FOR_EXCEPT( !(  loqo_lp->z  ) );
00448     TEST_FOR_EXCEPT( !(  loqo_lp->s  ) );
00449     const DVectorSlice
00450       loqo_z(loqo_lp->z,loqo_lp->n),   // Multipliers for l - x <= 0
00451       loqo_s(loqo_lp->s,loqo_lp->n);   // Multipliers for x - u <= 0
00452     DVectorSlice::const_iterator
00453       z_itr = loqo_z.begin(),
00454       s_itr = loqo_s.begin();
00455     typedef SpVector::element_type ele_t;
00456     for( size_type i = 1; i <= nd; ++i, ++z_itr, ++s_itr ) {
00457       if( *z_itr > *s_itr && *z_itr >= nonbinding_lag_mult() ) {
00458         // Lower bound is active
00460       }
00461       else if( *s_itr > *z_itr && *s_itr >= nonbinding_lag_mult() ) {
00462         // Upper bound is active
00464       }
00465     }
00466     // We could look at z(nd+1) and s(nd+1) for the value of kappa?
00467     nu->assume_sorted(true);
00468   }
00469
00470   // mu
00471   if(mu) {
00472     mu->resize(m_in,num_inequal);
00473     DenseLinAlgPack::VectorSliceTmpl<int>::iterator
00474       b_stat_itr  = loqo_b_stat.begin();
00475     TEST_FOR_EXCEPT( !(  loqo_lp->v  ) );
00476     TEST_FOR_EXCEPT( !(  loqo_lp->q  ) );
00477     const DVectorSlice
00478       loqo_v(loqo_lp->v,loqo_lp->m),   // Multipliers for b <= A*x
00479       loqo_q(loqo_lp->q,loqo_lp->m);   // Multipliers for A*x <= b + r
00480     DVectorSlice::const_iterator
00481       v_itr = loqo_v.begin(),
00482       q_itr = loqo_q.begin();
00483     // loop
00484     typedef SpVector::element_type ele_t;
00485     for( size_type k = 1; k <= num_inequal; ++k, ++b_stat_itr, ++v_itr, ++q_itr ) {
00486       const int j = *b_stat_itr;
00487       if( *v_itr > *q_itr && *v_itr >= nonbinding_lag_mult() ) {
00488         // Lower bound is active
00489         if( j < 0 ) // We had to flip this since it was really and upper bound
00491         else // This really was a lower bound
00493       }
00494       else if( *q_itr > *v_itr && *q_itr >= nonbinding_lag_mult() ) {
00495         // Upper bound is active
00497       }
00498     }
00499   }
00500
00501   // Ed
00502   if(Ed) {
00503     LinAlgOpPack::V_MtV( Ed, *E, trans_E, *d );
00504   }
00505
00506   // lambda
00507   if(lambda) {
00508     TEST_FOR_EXCEPT( !(  loqo_lp->y  ) );
00509     const DVectorSlice
00510       loqo_y(loqo_lp->y,loqo_lp->m);         // Multipliers for equalities
00511     DVectorSlice::const_iterator
00512       y_itr = loqo_y.begin() + num_inequal;  // Get iterators to equalities
00513     DVectorSlice::iterator
00514       lambda_itr = lambda->begin();
00515     // loop
00516     for( size_type k = 1; k <= m_eq; ++k, ++y_itr, ++lambda_itr ) {
00517       *lambda_itr = -(*y_itr);
00518     }
00519   }
00520
00521   // Fd
00522   if(Fd) {
00523     LinAlgOpPack::V_MtV( Fd, *F, trans_F, *d );
00524   }
00525
00526   //
00527   // Setup the QP statistics
00528   //
00529
00530   QPSolverStats::ESolutionType solution_type = QPSolverStats::OPTIMAL_SOLUTION; // Assume this?
00531   switch( loqo_status ) { // I had to find this out by trial and error!
00532       case 0:
00533       solution_type = QPSolverStats::OPTIMAL_SOLUTION;
00534       break;
00535     case 2:
00536       solution_type = QPSolverStats::DUAL_FEASIBLE_POINT;
00537       break;
00538     default:
00539       TEST_FOR_EXCEPT(true);
00540   }
00541
00542   qp_stats_.set_stats(
00543     solution_type, QPSolverStats::CONVEX
00544     ,loqo_lp->iter, QPSolverStats::NOT_KNOWN, QPSolverStats::NOT_KNOWN
00545     ,false, *eta > 0.0 );
00546
00547   //
00548   // Clean up dynamically allocated memory for LOQO
00549   //
00550
00551   inv_clo();          // frees memory associated with matrix factorization
00552   closelp(loqo_lp);   // frees all allocated arrays with free(...).
00553
00554   return qp_stats_.solution_type();
00555
00556 }
00557
00558 } // end namespace ConstrainedOptPack
00559
00560 #endif // CONSTRAINED_OPTIMIZATION_PACK_USE_LOQO
```

Generated on Tue Jul 13 09:30:51 2010 for MOOCHO (Single Doxygen Collection) by  1.4.7