PCLDUFactorisation.cpp

00001 /*
00002 
00003 Copyright (c) 2005-2014, University of Oxford.
00004 All rights reserved.
00005 
00006 University of Oxford means the Chancellor, Masters and Scholars of the
00007 University of Oxford, having an administrative office at Wellington
00008 Square, Oxford OX1 2JD, UK.
00009 
00010 This file is part of Chaste.
00011 
00012 Redistribution and use in source and binary forms, with or without
00013 modification, are permitted provided that the following conditions are met:
00014  * Redistributions of source code must retain the above copyright notice,
00015    this list of conditions and the following disclaimer.
00016  * Redistributions in binary form must reproduce the above copyright notice,
00017    this list of conditions and the following disclaimer in the documentation
00018    and/or other materials provided with the distribution.
00019  * Neither the name of the University of Oxford nor the names of its
00020    contributors may be used to endorse or promote products derived from this
00021    software without specific prior written permission.
00022 
00023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00024 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00025 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00026 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
00027 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00028 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
00029 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00030 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
00032 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00033 
00034 */
00035 
00036 #include <iostream>
00037 
00038 #include "PetscVecTools.hpp" // Includes Ublas so must come first
00039 #include "PCLDUFactorisation.hpp"
00040 #include "Exception.hpp"
00041 #include "Warnings.hpp"
00042 
00043 PCLDUFactorisation::PCLDUFactorisation(KSP& rKspObject)
00044 {
00045 #ifdef TRACE_KSP
00046     mPCContext.mScatterTime = 0.0;
00047     mPCContext.mA1PreconditionerTime = 0.0;;
00048     mPCContext.mA2PreconditionerTime = 0.0;;
00049     mPCContext.mGatherTime = 0.0;;
00050 #endif
00051 
00052     PCLDUFactorisationCreate(rKspObject);
00053     PCLDUFactorisationSetUp();
00054 }
00055 
00056 PCLDUFactorisation::~PCLDUFactorisation()
00057 {
00058 #ifdef TRACE_KSP
00059     if (PetscTools::AmMaster())
00060     {
00061         std::cout << " -- LDU factorisation preconditioner profile information: " << std::endl;
00062         std::cout << "\t mScatterTime: " << mPCContext.mScatterTime << std::endl;
00063         std::cout << "\t mA1PreconditionerTime: " << mPCContext.mA1PreconditionerTime << std::endl;
00064         std::cout << "\t mA2PreconditionerTime: " << mPCContext.mA2PreconditionerTime << std::endl;
00065         std::cout << "\t mExtraLAOperations: " << mPCContext.mExtraLAOperations << std::endl;
00066         std::cout << "\t mGatherTime: " << mPCContext.mGatherTime << std::endl;
00067     }
00068 #endif
00069 
00070     PetscTools::Destroy(mPCContext.A11_matrix_subblock);
00071     PetscTools::Destroy(mPCContext.A22_matrix_subblock);
00072     PetscTools::Destroy(mPCContext.B_matrix_subblock);
00073 
00074     PCDestroy(PETSC_DESTROY_PARAM(mPCContext.PC_amg_A11));
00075     PCDestroy(PETSC_DESTROY_PARAM(mPCContext.PC_amg_A22));
00076 
00077     PetscTools::Destroy(mPCContext.x1_subvector);
00078     PetscTools::Destroy(mPCContext.y1_subvector);
00079     PetscTools::Destroy(mPCContext.x2_subvector);
00080     PetscTools::Destroy(mPCContext.y2_subvector);
00081     PetscTools::Destroy(mPCContext.z);
00082     PetscTools::Destroy(mPCContext.temp);
00083 
00084     VecScatterDestroy(PETSC_DESTROY_PARAM(mPCContext.A11_scatter_ctx));
00085     VecScatterDestroy(PETSC_DESTROY_PARAM(mPCContext.A22_scatter_ctx));
00086 }
00087 
00088 void PCLDUFactorisation::PCLDUFactorisationCreate(KSP& rKspObject)
00089 {
00090     KSPGetPC(rKspObject, &mPetscPCObject);
00091 
00092     Mat system_matrix, dummy;
00093     MatStructure flag;
00094     KSPGetOperators(rKspObject, &system_matrix, &dummy, &flag);
00095 
00096     PetscInt num_rows, num_columns;
00097     MatGetSize(system_matrix, &num_rows, &num_columns);
00098 
00099     PetscInt num_local_rows, num_local_columns;
00100     MatGetLocalSize(system_matrix, &num_local_rows, &num_local_columns);
00101 
00102     // Odd number of rows: impossible in Bidomain.
00103     // Odd number of local rows: impossible if V_m and phi_e for each node are stored in the same processor.
00104     if ((num_rows%2 != 0) || (num_local_rows%2 != 0))
00105     {
00106         TERMINATE("Wrong matrix parallel layout detected in PCLDUFactorisation.");
00107     }
00108 
00109     // Allocate memory
00110     unsigned subvector_num_rows = num_rows/2;
00111     unsigned subvector_local_rows = num_local_rows/2;
00112     mPCContext.x1_subvector = PetscTools::CreateVec(subvector_num_rows, subvector_local_rows);
00113     mPCContext.x2_subvector = PetscTools::CreateVec(subvector_num_rows, subvector_local_rows);
00114     mPCContext.y1_subvector = PetscTools::CreateVec(subvector_num_rows, subvector_local_rows);
00115     mPCContext.y2_subvector = PetscTools::CreateVec(subvector_num_rows, subvector_local_rows);
00116     mPCContext.z = PetscTools::CreateVec(subvector_num_rows, subvector_local_rows);
00117     mPCContext.temp = PetscTools::CreateVec(subvector_num_rows, subvector_local_rows);
00118 
00119     // Create scatter contexts
00120     {
00121         // Needed by VecScatterCreate in order to find out parallel layout.
00122         Vec dummy_vec = PetscTools::CreateVec(num_rows, num_local_rows);
00123 
00124         PetscVecTools::SetupInterleavedVectorScatterGather(dummy_vec, mPCContext.A11_scatter_ctx, mPCContext.A22_scatter_ctx);
00125 
00126         PetscTools::Destroy(dummy_vec);
00127     }
00128 
00129     // Get matrix sublock A11
00130     {
00131         // Work out local row range for subblock A11 (same as x1 or y1)
00132         PetscInt low, high, global_size;
00133         VecGetOwnershipRange(mPCContext.x1_subvector, &low, &high);
00134         VecGetSize(mPCContext.x1_subvector, &global_size);
00135         assert(global_size == num_rows/2);
00136 
00137         IS A11_local_rows;
00138         IS A11_columns;
00139         ISCreateStride(PETSC_COMM_WORLD, high-low, 2*low, 2, &A11_local_rows);
00140         ISCreateStride(PETSC_COMM_WORLD, global_size, 0, 2, &A11_columns);
00141 
00142 #if (PETSC_VERSION_MAJOR == 3 && PETSC_VERSION_MINOR >= 1) //PETSc 3.1 or later
00143         MatGetSubMatrix(system_matrix, A11_local_rows, A11_local_rows,
00144             MAT_INITIAL_MATRIX, &mPCContext.A11_matrix_subblock);
00145 #else
00146         MatGetSubMatrix(system_matrix, A11_local_rows, A11_columns, PETSC_DECIDE,
00147             MAT_INITIAL_MATRIX, &mPCContext.A11_matrix_subblock);
00148 #endif
00149         ISDestroy(PETSC_DESTROY_PARAM(A11_local_rows));
00150         ISDestroy(PETSC_DESTROY_PARAM(A11_columns));
00151     }
00152 
00153     // Get matrix sublock A22
00154     {
00155         // Work out local row range for subblock A22 (same as x2 or y2)
00156         PetscInt low, high, global_size;
00157         VecGetOwnershipRange(mPCContext.x2_subvector, &low, &high);
00158         VecGetSize(mPCContext.x2_subvector, &global_size);
00159         assert(global_size == num_rows/2);
00160 
00161         IS A22_local_rows;
00162         IS A22_columns;
00163         ISCreateStride(PETSC_COMM_WORLD, high-low, 2*low+1, 2, &A22_local_rows);
00164         ISCreateStride(PETSC_COMM_WORLD, global_size, 1, 2, &A22_columns);
00165 
00166 #if (PETSC_VERSION_MAJOR == 3 && PETSC_VERSION_MINOR >= 1) //PETSc 3.1 or later
00167         MatGetSubMatrix(system_matrix, A22_local_rows, A22_local_rows,
00168             MAT_INITIAL_MATRIX, &mPCContext.A22_matrix_subblock);
00169 #else
00170         MatGetSubMatrix(system_matrix, A22_local_rows, A22_columns, PETSC_DECIDE,
00171             MAT_INITIAL_MATRIX, &mPCContext.A22_matrix_subblock);
00172 #endif
00173 
00174         ISDestroy(PETSC_DESTROY_PARAM(A22_local_rows));
00175         ISDestroy(PETSC_DESTROY_PARAM(A22_columns));
00176     }
00177 
00178     // Get matrix sublock B (the upper triangular one)
00179     {
00180         // Work out local row range for subblock B (same as A11, x1 or y1)
00181         PetscInt low, high, global_size;
00182         VecGetOwnershipRange(mPCContext.x1_subvector, &low, &high);
00183         VecGetSize(mPCContext.x1_subvector, &global_size);
00184         assert(global_size == num_rows/2);
00185 
00186         IS B_local_rows;
00187         IS B_columns;
00188         ISCreateStride(PETSC_COMM_WORLD, high-low, 2*low, 2, &B_local_rows);
00189 
00190 #if (PETSC_VERSION_MAJOR == 3 && PETSC_VERSION_MINOR >= 1) //PETSc 3.1 or later
00191         ISCreateStride(PETSC_COMM_WORLD, high-low, 2*low+1, 2, &B_columns);
00192         MatGetSubMatrix(system_matrix, B_local_rows, B_columns,
00193             MAT_INITIAL_MATRIX, &mPCContext.B_matrix_subblock);
00194 #else
00195     ISCreateStride(PETSC_COMM_WORLD, global_size, 1, 2, &B_columns);
00196         MatGetSubMatrix(system_matrix, B_local_rows, B_columns, PETSC_DECIDE,
00197             MAT_INITIAL_MATRIX, &mPCContext.B_matrix_subblock);
00198 #endif
00199 
00200         ISDestroy(PETSC_DESTROY_PARAM(B_local_rows));
00201         ISDestroy(PETSC_DESTROY_PARAM(B_columns));
00202     }
00203 
00204     /*
00205      * Experimental (#1082): in PP removing the mass matrix from the A22 block seems to work better.
00206      *                       This is equivalent to do A22 = A22 + B in this implementation.
00207      */
00208 //#if (PETSC_VERSION_MAJOR == 2 && PETSC_VERSION_MINOR == 2) //PETSc 2.2
00209 //     PetscScalar petsc_one = 1.0;
00210 //     MatAXPY(&petsc_one, mPCContext.B_matrix_subblock, mPCContext.A22_matrix_subblock, DIFFERENT_NONZERO_PATTERN);
00211 // #else
00212 //     MatAXPY(mPCContext.A22_matrix_subblock, 1.0, mPCContext.B_matrix_subblock, DIFFERENT_NONZERO_PATTERN);
00213 //#endif
00214 
00215 //     // Shift the block
00216 //#if (PETSC_VERSION_MAJOR == 2 && PETSC_VERSION_MINOR == 2) //PETSc 2.2
00217 //     PetscScalar shift = -1e-8;
00218 //     MatShift(&shift, mPCContext.A22_matrix_subblock);
00219 // #else
00220 //     PetscScalar shift = -1e-8;
00221 //     MatShift(mPCContext.A22_matrix_subblock, shift);
00222 //#endif
00223 
00224     PCSetType(mPetscPCObject, PCSHELL);
00225 #if (PETSC_VERSION_MAJOR == 2 && PETSC_VERSION_MINOR == 2) //PETSc 2.2
00226     // Register PC context and call-back function
00227     PCShellSetApply(mPetscPCObject, PCLDUFactorisationApply, (void*) &mPCContext);
00228 #else
00229     // Register PC context so it gets passed to PCBlockDiagonalApply
00230     PCShellSetContext(mPetscPCObject, &mPCContext);
00231     // Register call-back function
00232     PCShellSetApply(mPetscPCObject, PCLDUFactorisationApply);
00233 #endif
00234 }
00235 
00236 void PCLDUFactorisation::PCLDUFactorisationSetUp()
00237 {
00238     // These options will get read by PCSetFromOptions
00239 //     PetscOptionsSetValue("-pc_hypre_boomeramg_max_iter", "1");
00240 //     PetscOptionsSetValue("-pc_hypre_boomeramg_strong_threshold", "0.0");
00241 //     PetscOptionsSetValue("-pc_hypre_type", "boomeramg");
00242 
00243     /*
00244      * Set up preconditioner for block A11
00245      */
00246     PCCreate(PETSC_COMM_WORLD, &(mPCContext.PC_amg_A11));
00247     PCSetOperators(mPCContext.PC_amg_A11, mPCContext.A11_matrix_subblock, mPCContext.A11_matrix_subblock, SAME_PRECONDITIONER);
00248 
00249     // Choose between the two following blocks in order to approximate inv(A11) with one AMG cycle
00250     // or with an CG solve with high tolerance
00252 //    PCSetType(mPCContext.PC_amg_A11, PCBJACOBI);
00253 
00254     // We are expecting an error from PETSC on systems that don't have the hypre library, so suppress it 
00255     // in case it aborts 
00256     PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL);
00257     PetscErrorCode pc_set_error = PCSetType(mPCContext.PC_amg_A11, PCHYPRE);
00258     if (pc_set_error != 0)
00259     {
00260         WARNING("PETSc hypre preconditioning library is not installed");
00261     }
00262     // Stop supressing error
00263     PetscPopErrorHandler();     
00264 
00265     //PCHYPRESetType(mPCContext.PC_amg_A11, "euclid");
00266     PetscOptionsSetValue("-pc_hypre_type", "euclid");
00267     PetscOptionsSetValue("-pc_hypre_euclid_levels", "0");
00268 
00269 //     PCSetType(mPCContext.PC_amg_A11, PCHYPRE);
00270 //     PetscOptionsSetValue("-pc_hypre_type", "boomeramg");
00271 //     PetscOptionsSetValue("-pc_hypre_boomeramg_max_iter", "1");
00272 //     PetscOptionsSetValue("-pc_hypre_boomeramg_strong_threshold", "0.0");
00273 //     PetscOptionsSetValue("-pc_hypre_boomeramg_coarsen_type", "HMIS");
00274 
00276 //    PCSetType(mPCContext.PC_amg_A11, PCKSP);
00277 //    KSP ksp1;
00278 //    PCKSPGetKSP(mPCContext.PC_amg_A11,&ksp1);
00279 //    KSPSetType(ksp1, KSPCG);
00280 //    KSPSetTolerances(ksp1, 0.1, PETSC_DEFAULT, PETSC_DEFAULT, PETSC_DEFAULT);
00281 //
00282 //    PC prec1;
00283 //    KSPGetPC(ksp1, &prec1);
00284 //    PCSetType(prec1, PCBJACOBI);
00285 //    PCSetFromOptions(prec1);
00286 //    PCSetOperators(prec1, mPCContext.A11_matrix_subblock, mPCContext.A11_matrix_subblock, DIFFERENT_NONZERO_PATTERN);//   SAME_PRECONDITIONER);
00287 //    PCSetUp(prec1);
00288 //
00289 //    KSPSetFromOptions(ksp1);
00290 //    KSPSetUp(ksp1);
00292 
00293     PCSetFromOptions(mPCContext.PC_amg_A11);
00294     PCSetUp(mPCContext.PC_amg_A11);
00295 
00296     /*
00297      * Set up amg preconditioner for block A22
00298      */
00299     PCCreate(PETSC_COMM_WORLD, &(mPCContext.PC_amg_A22));
00300     PCSetOperators(mPCContext.PC_amg_A22, mPCContext.A22_matrix_subblock, mPCContext.A22_matrix_subblock, SAME_PRECONDITIONER);
00301 
00302     // Choose between the two following blocks in order to approximate inv(A11) with one AMG cycle
00303     // or with an CG solve with high tolerance
00305     // We are expecting an error from PETSC on systems that don't have the hypre library, so suppress it 
00306     // in case it aborts 
00307     PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL);
00308     PCSetType(mPCContext.PC_amg_A22, PCHYPRE);
00309     // Stop supressing error 
00310     PetscPopErrorHandler();
00311 
00312     PetscOptionsSetValue("-pc_hypre_type", "boomeramg");
00313     PetscOptionsSetValue("-pc_hypre_boomeramg_max_iter", "1");
00314     PetscOptionsSetValue("-pc_hypre_boomeramg_strong_threshold", "0.0");
00315     PetscOptionsSetValue("-pc_hypre_boomeramg_coarsen_type", "HMIS");
00316     //    PetscOptionsSetValue("-pc_hypre_boomeramg_interp_type","ext+i");
00317 
00319 //    PCSetType(mPCContext.PC_amg_A22, PCKSP);
00320 //    KSP ksp2;
00321 //    PCKSPGetKSP(mPCContext.PC_amg_A22,&ksp2);
00322 //    KSPSetType(ksp2, KSPCG);
00323 //    KSPSetTolerances(ksp2, 0.1, PETSC_DEFAULT, PETSC_DEFAULT, PETSC_DEFAULT);
00324 //
00325 //    PC prec2;
00326 //    KSPGetPC(ksp2, &prec2);
00327 //    PCSetType(prec2, PCBJACOBI);
00328 //    PCSetFromOptions(prec2);
00329 //    PCSetOperators(prec2, mPCContext.A22_matrix_subblock, mPCContext.A22_matrix_subblock, DIFFERENT_NONZERO_PATTERN);//   SAME_PRECONDITIONER);
00330 //    PCSetUp(prec2);
00331 //
00332 //    KSPSetFromOptions(ksp2);
00333 //    KSPSetUp(ksp2);
00335 
00336     PCSetFromOptions(mPCContext.PC_amg_A22);
00337     PCSetUp(mPCContext.PC_amg_A22);
00338 }
00339 #if (PETSC_VERSION_MAJOR == 3 && PETSC_VERSION_MINOR >= 1) //PETSc 3.1 or later
00340 PetscErrorCode PCLDUFactorisationApply(PC pc_object, Vec x, Vec y)
00341 {
00342   void* pc_context;
00343 
00344   PCShellGetContext(pc_object, &pc_context);
00345 #else
00346 PetscErrorCode PCLDUFactorisationApply(void* pc_context, Vec x, Vec y)
00347 {
00348 #endif
00350 
00351     // Cast the pointer to a PC context to our defined type
00352     PCLDUFactorisation::PCLDUFactorisationContext* block_diag_context = (PCLDUFactorisation::PCLDUFactorisationContext*) pc_context;
00353     assert(block_diag_context!=NULL);
00354 
00355     /*
00356      * Split vector x into two. x = [x1 x2]'
00357      */
00358 #ifdef TRACE_KSP
00359     double init_time = MPI_Wtime();
00360 #endif
00361 
00362     PetscVecTools::DoInterleavedVecScatter(x, block_diag_context->A11_scatter_ctx, block_diag_context->x1_subvector, block_diag_context->A22_scatter_ctx, block_diag_context->x2_subvector);
00363 
00364 #ifdef TRACE_KSP
00365     block_diag_context->mScatterTime += MPI_Wtime() - init_time;
00366 #endif
00367 
00368     /*
00369      * Apply preconditioner: [y1 y2]' = inv(P)[x1 x2]'
00370      *
00371      *    z  = inv(A11)*x1
00372      *    y2 = inv(A22)*(x2 - B*z)
00373      *    y1 = z - inv(A11)(B*y2)
00374      */
00375 #ifdef TRACE_KSP
00376     init_time = MPI_Wtime();
00377 #endif
00378     //z  = inv(A11)*x1
00379     PCApply(block_diag_context->PC_amg_A11, block_diag_context->x1_subvector, block_diag_context->z);
00380 #ifdef TRACE_KSP
00381     block_diag_context->mA1PreconditionerTime += MPI_Wtime() - init_time;
00382 #endif
00383 
00384     //y2 = inv(A22)*(x2 - B*z)
00385 #ifdef TRACE_KSP
00386     init_time = MPI_Wtime();
00387 #endif
00388     MatMult(block_diag_context->B_matrix_subblock,block_diag_context->z,block_diag_context->temp); //temp = B*z
00389     double minus_one = -1.0;
00390 #if (PETSC_VERSION_MAJOR == 2 && PETSC_VERSION_MINOR == 2) //PETSc 2.2
00391     VecAYPX(&minus_one, block_diag_context->x2_subvector, block_diag_context->temp); // temp <-- x2 - temp
00392 #else
00393     VecAYPX(block_diag_context->temp, minus_one, block_diag_context->x2_subvector); // temp <-- x2 - temp
00394 #endif
00395 #ifdef TRACE_KSP
00396     block_diag_context->mExtraLAOperations += MPI_Wtime() - init_time;
00397 #endif
00398 
00399 
00400 #ifdef TRACE_KSP
00401     init_time = MPI_Wtime();
00402 #endif
00403     PCApply(block_diag_context->PC_amg_A22, block_diag_context->temp, block_diag_context->y2_subvector); // y2 = inv(A22)*temp
00404 #ifdef TRACE_KSP
00405     block_diag_context->mA2PreconditionerTime += MPI_Wtime() - init_time;
00406 #endif
00407 
00408     // y1 = z - inv(A11)(B*y2)
00409 #ifdef TRACE_KSP
00410     init_time = MPI_Wtime();
00411 #endif
00412     MatMult(block_diag_context->B_matrix_subblock,block_diag_context->y2_subvector,block_diag_context->temp); //temp = B*y2
00413 #ifdef TRACE_KSP
00414     block_diag_context->mExtraLAOperations += MPI_Wtime() - init_time;
00415 #endif
00416 #ifdef TRACE_KSP
00417     init_time = MPI_Wtime();
00418 #endif
00419     PCApply(block_diag_context->PC_amg_A11, block_diag_context->temp, block_diag_context->y1_subvector); // y1 = inv(A11)*temp
00420 #ifdef TRACE_KSP
00421     block_diag_context->mA1PreconditionerTime += MPI_Wtime() - init_time;
00422 #endif
00423 
00424 #ifdef TRACE_KSP
00425     init_time = MPI_Wtime();
00426 #endif
00427 #if (PETSC_VERSION_MAJOR == 2 && PETSC_VERSION_MINOR == 2) //PETSc 2.2
00428     VecAYPX(&minus_one, block_diag_context->z, block_diag_context->y1_subvector); // y1 <-- z - y1
00429 #else
00430     VecAYPX(block_diag_context->y1_subvector, minus_one, block_diag_context->z); // y1 <-- z - y1
00431 #endif
00432 #ifdef TRACE_KSP
00433     block_diag_context->mExtraLAOperations += MPI_Wtime() - init_time;
00434 #endif
00435 
00436     /*
00437      * Gather vectors y1 and y2. y = [y1 y2]'
00438      */
00439 #ifdef TRACE_KSP
00440     init_time = MPI_Wtime();
00441 #endif
00442 
00443     PetscVecTools::DoInterleavedVecGather(y, block_diag_context->A11_scatter_ctx, block_diag_context->y1_subvector, block_diag_context->A22_scatter_ctx, block_diag_context->y2_subvector);
00444 
00445 #ifdef TRACE_KSP
00446     block_diag_context->mGatherTime += MPI_Wtime() - init_time;
00447 #endif
00448 
00449     return 0;
00450 }

Generated by  doxygen 1.6.2