← Back to team overview

dolfin team mailing list archive

[jobh@xxxxxxxxx: Fwd: Logging]

 

Attached is a patch from Joachim containing some improvements to the
log system. I think it looks good, and it matches the Python logging
system quite well (which we use from UFL and FFC).

I'll comment more on it later and apply the patch.

--
Anders
--- Begin Message --- Hei, jeg sendte denne til dolfin-lista på onsdag, men det ser ikke ut som om den kom gjennom. Er lista lukket? Har ikke fått noen bounce.

-j.


-------- Original Message --------
Subject: Logging
Date: Wed, 24 Mar 2010 16:46:48 +0100
From: Joachim Berdal Haga <jobh@xxxxxxxxx>
To: dolfin@xxxxxxxxxxxxxxxxxxx

I've recently had the pleasure to use dolfin again (through ascot), and
it occurred to me that dolfin is very verbose, to the point where my own
output is lost in the haystack. While the logging framework includes a
log_level argument to info(), it is not much used, perhaps because the
meaning of the log levels is not defined or documented. The result is
that logging is in practice either on or off.

The following bundle rectifies this situation, by (a) defining log
levels, (b) using them. The default logging level is now less verbose,
and it can be modified in a meaningful way by set_log_level() and not
only be logging(false).

I copied the log levels from the python logging module, and added a
couple in between. As a result, the log level changes meaning from
(lower == more severe) to (higher == more severe). The levels are (from
LogLevel.h):

     CRITICAL  = 50,             /* errors that may lead to data
corruption and suchlike */
     ERROR     = 40,             /* things that go boom */
     WARNING   = 30,             /* things that may go boom later */
     INFO      = 20,             /* information of general interest */
     PROGRESS  = 16,             /* what's happening (broadly) */
     TRACE     = 13,             /* what's happening (in detail) */
     DEBUG     = 10              /* sundry */

Note some deficiencies in this patch: (1) the levels are not exported to
python; (2) the levels for the various info() calls were chosen very
quickly and "by ear", and, (3) I don't know the original design/plan for
how the logging was supposed to be used, and this may be the wrong way
to do it (i.e., should a call to info(TRACE, msg) instead be a call to
trace(msg)? Should a "standard" logging framework be used? Should info()
be called log() and have a mandatory severity argument, so that the user
is forced to think about it?)

A change to the progress output is also included which prints the
progress bar in-line with the title. This cuts down the number of lines
a lot. Vertical space is precious in this wide-screen age.

I'm not subscribed to the list btw.

-j.

# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: jobh@rodin-20100324145048-uwyapvgdytgrirkb
# target_branch: http://bazaar.launchpad.net/~dolfin-core/dolfin/main/
# testament_sha1: 33c3dbaa065dd48a46e72f7b32fa1941aa3932e1
# timestamp: 2010-03-24 15:51:43 +0100
# base_revision_id: logg@xxxxxxxxx-20100126125337-y1ikvifyi5c1jikj
# 
# Begin patch
=== modified file 'dolfin/common/timing.cpp'
--- dolfin/common/timing.cpp	2009-08-10 20:06:04 +0000
+++ dolfin/common/timing.cpp	2010-03-24 14:50:48 +0000
@@ -26,6 +26,8 @@
   clock_t __toc_time = std::clock();
 
   double elapsed_time = ((double) (__toc_time - __tic_time)) / CLOCKS_PER_SEC;
+  if (elapsed_time < 1e-10)
+    elapsed_time = 0;
 
   return elapsed_time;
 }

=== modified file 'dolfin/fem/DirichletBC.cpp'
--- dolfin/fem/DirichletBC.cpp	2009-12-03 08:48:41 +0000
+++ dolfin/fem/DirichletBC.cpp	2010-03-24 10:33:06 +0000
@@ -343,7 +343,7 @@
       values[i] = x_values[i] - values[i];
   }
 
-  info("Applying boundary conditions to linear system.");
+  info(PROGRESS, "Applying boundary conditions to linear system.");
 
   // Modify RHS vector (b[i] = value) and apply changes
   if (b)
@@ -576,7 +576,7 @@
   const DofMap& dofmap = _function_space->dofmap();
 
   // Initialize facets, needed for geometric search
-  info("Computing facets, needed for geometric application of boundary conditions.");
+  info(TRACE, "Computing facets, needed for geometric application of boundary conditions.");
   mesh.init(mesh.topology().dim() - 1);
 
   // Iterate over facets

=== modified file 'dolfin/fem/DofMap.cpp'
--- dolfin/fem/DofMap.cpp	2009-11-17 13:09:50 +0000
+++ dolfin/fem/DofMap.cpp	2010-03-23 15:50:40 +0000
@@ -130,8 +130,8 @@
   // Recursively extract UFC sub dofmap
   boost::shared_ptr<ufc::dof_map>
     ufc_sub_dof_map(extract_sub_dofmap(*_ufc_dofmap, ufc_offset, component, _ufc_mesh, dolfin_mesh));
-  info(2, "Extracted dof map for sub system: %s", ufc_sub_dof_map->signature());
-  info(2, "Offset for sub system: %d", ufc_offset);
+  info(DEBUG, "Extracted dof map for sub system: %s", ufc_sub_dof_map->signature());
+  info(DEBUG, "Offset for sub system: %d", ufc_offset);
 
   // Create dofmap
   DofMap* sub_dofmap = 0;

=== modified file 'dolfin/fem/DofMapBuilder.cpp'
--- dolfin/fem/DofMapBuilder.cpp	2009-11-04 10:46:13 +0000
+++ dolfin/fem/DofMapBuilder.cpp	2010-03-24 10:33:06 +0000
@@ -47,7 +47,7 @@
 {
   // FIXME: Split this function into two; deciding ownership and then renumbering
 
-  info("Building parallel dof map");
+  info(TRACE, "Building parallel dof map");
 
   // Check that dof map has not been built
   if (dofmap._map.get())
@@ -223,6 +223,6 @@
 
   delete [] _dofmap;
 
-  info("Finished building parallel dof map");
+  info(TRACE, "Finished building parallel dof map");
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/fem/EqualityBC.cpp'
--- dolfin/fem/EqualityBC.cpp	2009-11-06 10:10:55 +0000
+++ dolfin/fem/EqualityBC.cpp	2010-03-24 10:33:06 +0000
@@ -76,7 +76,7 @@
 //-----------------------------------------------------------------------------
 void EqualityBC::apply(GenericMatrix& A, GenericVector& b) const
 {
-  info("Applying equality boundary conditions to linear system.");
+  info(TRACE, "Applying equality boundary conditions to linear system.");
 
   if (equal_dofs.size() < 2)
   {

=== modified file 'dolfin/fem/FiniteElement.cpp'
--- dolfin/fem/FiniteElement.cpp	2009-11-04 10:46:13 +0000
+++ dolfin/fem/FiniteElement.cpp	2010-03-23 15:50:40 +0000
@@ -21,7 +21,7 @@
 {
   // Recursively extract sub element
   boost::shared_ptr<const FiniteElement> sub_finite_element = extract_sub_element(*this, component);
-  info(2, "Extracted finite element for sub system: %s", sub_finite_element->signature().c_str());
+  info(DEBUG, "Extracted finite element for sub system: %s", sub_finite_element->signature().c_str());
 
   return sub_finite_element;
 }

=== modified file 'dolfin/fem/Form.cpp'
--- dolfin/fem/Form.cpp	2010-01-26 12:53:37 +0000
+++ dolfin/fem/Form.cpp	2010-03-24 10:33:06 +0000
@@ -211,8 +211,8 @@
     assert(element.get());
     if (element->signature() != _function_spaces[i]->element().signature())
     {
-      info("Expected element: %s", element->signature());
-      info("Input element:    %s", _function_spaces[i]->element().signature().c_str());
+      info(ERROR, "Expected element: %s", element->signature());
+      info(ERROR, "Input element:    %s", _function_spaces[i]->element().signature().c_str());
       error("Wrong type of function space for argument %d.", i);
     }
   }

=== modified file 'dolfin/fem/SystemAssembler.cpp'
--- dolfin/fem/SystemAssembler.cpp	2009-10-08 19:11:44 +0000
+++ dolfin/fem/SystemAssembler.cpp	2010-03-24 10:33:06 +0000
@@ -76,7 +76,7 @@
                                           bool add_values)
 {
   Timer timer("Assemble system");
-  info("Assembling linear system and applying boundary conditions...");
+  info(PROGRESS, "Assembling linear system and applying boundary conditions...");
 
   // FIXME: 1. Need consistency check between a and L
   // FIXME: 2. Some things can be simplified since we know it's a matrix and a vector

=== modified file 'dolfin/io/MFile.cpp'
--- dolfin/io/MFile.cpp	2009-05-18 17:17:47 +0000
+++ dolfin/io/MFile.cpp	2010-03-23 15:50:40 +0000
@@ -58,7 +58,7 @@
   // Close file
   fclose(fp);
 
-  info("Saved vector to file %s in Octave/MATLAB format.", filename.c_str());
+  info(TRACE, "Saved vector to file %s in Octave/MATLAB format.", filename.c_str());
 }
 //-----------------------------------------------------------------------------
 void MFile::operator<<(const Mesh& mesh)
@@ -150,7 +150,7 @@
   // Increase the number of meshes saved to this file
   counter++;
 
-  info(1, "Saved mesh %s (%s) to file %s in Octave/MATLAB format.",
+  info(TRACE, "Saved mesh %s (%s) to file %s in Octave/MATLAB format.",
           mesh.name().c_str(), mesh.label().c_str(), filename.c_str());
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/io/MatlabFile.cpp'
--- dolfin/io/MatlabFile.cpp	2009-05-02 20:55:48 +0000
+++ dolfin/io/MatlabFile.cpp	2010-03-23 15:50:40 +0000
@@ -60,9 +60,9 @@
   // Close file
   fclose(fp);
 
-//  info(1, "Saved matrix %s (%s) to file %s in sparse MATLAB format",
+//  info(TRACE, "Saved matrix %s (%s) to file %s in sparse MATLAB format",
 //          A.name().c_str(), A.label().c_str(), filename.c_str());
-  info(1, "Saved matrix to file %s in sparse MATLAB format", filename.c_str());
+  info(TRACE, "Saved matrix to file %s in sparse MATLAB format", filename.c_str());
 }
 //-----------------------------------------------------------------------------
 

=== modified file 'dolfin/io/OctaveFile.cpp'
--- dolfin/io/OctaveFile.cpp	2009-05-02 20:55:48 +0000
+++ dolfin/io/OctaveFile.cpp	2010-03-23 15:50:40 +0000
@@ -60,9 +60,9 @@
   fclose(fp);
   delete [] row;
 
-//  info(1, "Saved matrix %s (%s) to file %s in Octave format.",
+//  info(TRACE, "Saved matrix %s (%s) to file %s in Octave format.",
 //          A.name().c_str(), A.label().c_str(), filename.c_str());
 
-  info(1, "Saved matrix to file %s in Octave format.", filename.c_str());
+  info(TRACE, "Saved matrix to file %s in Octave format.", filename.c_str());
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/io/VTKFile.cpp'
--- dolfin/io/VTKFile.cpp	2009-12-01 23:34:47 +0000
+++ dolfin/io/VTKFile.cpp	2010-03-23 15:50:40 +0000
@@ -71,7 +71,7 @@
   // Finalise and write pvd files
   finalize(vtu_filename);
 
-  info(1, "Saved mesh %s (%s) to file %s in VTK format.",
+  info(TRACE, "Saved mesh %s (%s) to file %s in VTK format.",
           mesh.name().c_str(), mesh.label().c_str(), filename.c_str());
 }
 //----------------------------------------------------------------------------
@@ -123,7 +123,7 @@
   // Finalise and write pvd files
   finalize(vtu_filename);
 
-  info(1, "Saved function %s (%s) to file %s in VTK format.",
+  info(TRACE, "Saved function %s (%s) to file %s in VTK format.",
           u.name().c_str(), u.label().c_str(), filename.c_str());
 }
 //----------------------------------------------------------------------------

=== modified file 'dolfin/io/XMLFile.cpp'
--- dolfin/io/XMLFile.cpp	2010-01-04 20:03:57 +0000
+++ dolfin/io/XMLFile.cpp	2010-03-23 15:50:40 +0000
@@ -94,7 +94,7 @@
                                      stderr);
   ret = xmlRelaxNGValidateDoc(validator, document);
   if ( ret == 0 ) {
-    info(0, "%s validates", filename.c_str());
+    info(DEBUG, "%s validates", filename.c_str());
   }
   else if ( ret < 0 ) {
     error("%s failed to load", filename.c_str());

=== modified file 'dolfin/io/XMLFile.h'
--- dolfin/io/XMLFile.h	2010-01-04 20:03:57 +0000
+++ dolfin/io/XMLFile.h	2010-03-23 15:50:40 +0000
@@ -134,7 +134,7 @@
 
     template<class T> void read_xml_map(T& map)
     {
-      info(1, "Reading map from file %s.", filename.c_str());
+      info(TRACE, "Reading map from file %s.", filename.c_str());
       XMLMap xml_map(map, *this);
       XMLDolfin xml_dolfin(xml_map, *this);
       xml_dolfin.handle();
@@ -145,7 +145,7 @@
 
     template<class T> void read_xml_array(T& x)
     {
-      info(1, "Reading array from file %s.", filename.c_str());
+      info(TRACE, "Reading array from file %s.", filename.c_str());
       XMLArray xml_array(x, *this);
       XMLDolfin xml_dolfin(xml_array, *this);
       xml_dolfin.handle();

=== modified file 'dolfin/la/CholmodCholeskySolver.cpp'
--- dolfin/la/CholmodCholeskySolver.cpp	2009-12-10 11:38:15 +0000
+++ dolfin/la/CholmodCholeskySolver.cpp	2010-03-24 10:33:06 +0000
@@ -67,7 +67,7 @@
 	             (double*) std::tr1::get<2>(data), M, nnz);
 
   // Factorize
-  info("Cholesky-factorizing linear system of size %d x %d (CHOLMOD).",M,M);
+  info(PROGRESS, "Cholesky-factorizing linear system of size %d x %d (CHOLMOD).",M,M);
   cholmod.factorize();
 
   return 1;
@@ -86,7 +86,7 @@
   // Initialise solution vector and solve
   x.resize(N);
 
-  info("Solving factorized linear system of size %d x %d (CHOLMOD).", N, N);
+  info(PROGRESS, "Solving factorized linear system of size %d x %d (CHOLMOD).", N, N);
 
   cholmod.factorized_solve(x.data(), b.data());
 
@@ -156,7 +156,7 @@
 					  uint M, uint nz)
 {
   if(factorized)
-    info(1, "CholeskySolver already contains a factorized matrix! Clearing and starting over.");
+    info(DEBUG, "CholeskySolver already contains a factorized matrix! Clearing and starting over.");
 
   // Clear any data
   clear();

=== modified file 'dolfin/la/DefaultFactory.cpp'
--- dolfin/la/DefaultFactory.cpp	2009-09-21 18:15:02 +0000
+++ dolfin/la/DefaultFactory.cpp	2010-03-24 10:33:06 +0000
@@ -79,7 +79,7 @@
   }
 
   // Fallback
-  info("Linear algebra backend \"" + backend + "\" not available, using " + default_backend + ".");
+  info(WARNING, "Linear algebra backend \"" + backend + "\" not available, using " + default_backend + ".");
   return DefaultFactory::instance();
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/la/EpetraKrylovSolver.cpp'
--- dolfin/la/EpetraKrylovSolver.cpp	2009-09-08 16:38:36 +0000
+++ dolfin/la/EpetraKrylovSolver.cpp	2010-03-24 10:33:06 +0000
@@ -108,7 +108,7 @@
 
   // Write a message
   if (parameters["report"])
-    info("Solving linear system of size %d x %d (Krylov solver).", M, N);
+    info(PROGRESS, "Solving linear system of size %d x %d (Krylov solver).", M, N);
 
   // Reinitialize solution vector if necessary
   if (x.size() != M)
@@ -199,7 +199,7 @@
   // Start solve
   linear_solver.Iterate(parameters["maximum_iterations"], parameters["relative_tolerance"]);
 
-  info("AztecOO Krylov solver (%s, %s) converged in %d iterations.",
+  info(PROGRESS, "AztecOO Krylov solver (%s, %s) converged in %d iterations.",
           method.c_str(), pc_type.c_str(), linear_solver.NumIters());
 
   return linear_solver.NumIters();

=== modified file 'dolfin/la/ITLKrylovSolver.cpp'
--- dolfin/la/ITLKrylovSolver.cpp	2009-09-08 16:38:36 +0000
+++ dolfin/la/ITLKrylovSolver.cpp	2010-03-24 10:33:06 +0000
@@ -107,7 +107,7 @@
 
   // Check exit condition
   if(errno_ == 0)
-    info("ITLSolver (%s, %s) converged in %d iterations. Resid=%8.2e",
+    info(PROGRESS, "ITLSolver (%s, %s) converged in %d iterations. Resid=%8.2e",
 	    method.c_str(), pc_type.c_str(), iter.iterations(), iter.resid());
   else
     warning("ITLKrylovSolver: (%s, %s) failed to converge!\n\t%d iterations,"

=== modified file 'dolfin/la/KrylovSolver.cpp'
--- dolfin/la/KrylovSolver.cpp	2009-10-28 17:00:27 +0000
+++ dolfin/la/KrylovSolver.cpp	2010-03-23 15:50:40 +0000
@@ -41,7 +41,7 @@
   p.add("maximum_iterations",  10000);
   p.add("gmres_restart",       30);
   p.add("shift_nonzero",       0.0);
-  p.add("report",              true);
+  p.add("report",              true); /* deprecate? */
   p.add("monitor_convergence", false);
 
   return p;

=== modified file 'dolfin/la/LAPACKSolvers.cpp'
--- dolfin/la/LAPACKSolvers.cpp	2009-12-15 13:22:38 +0000
+++ dolfin/la/LAPACKSolvers.cpp	2010-03-24 10:33:06 +0000
@@ -31,7 +31,7 @@
   double* work = new double[m*lwork];
 
   // Call DGELSS
-  info("Solving least squares system of size %d x %d using DGELS.", m, n);
+  info(PROGRESS, "Solving least squares system of size %d x %d using DGELS.", m, n);
   dgels(&trans, &m, &n, &nrhs, A.values, &lda, b.values, &ldb, work, &lwork, &status);
 
   // Check output status

=== modified file 'dolfin/la/PETScKrylovSolver.cpp'
--- dolfin/la/PETScKrylovSolver.cpp	2009-09-27 19:11:40 +0000
+++ dolfin/la/PETScKrylovSolver.cpp	2010-03-24 10:33:06 +0000
@@ -112,8 +112,7 @@
     error("Non-matching dimensions for linear system.");
 
   // Write a message
-  if (parameters["report"])
-    info("Solving linear system of size %d x %d (Krylov solver).", M, N);
+  info(PROGRESS, "Solving linear system of size %d x %d (Krylov solver).", M, N);
 
   // Reinitialize KSP solver if necessary
   init(M, N);
@@ -173,8 +172,7 @@
     error("Non-matching dimensions for linear system.");
 
   // Write a message
-  if (parameters["report"])
-    info("Solving virtual linear system of size %d x %d (Krylov solver).", M, N);
+  info(PROGRESS, "Solving virtual linear system of size %d x %d (Krylov solver).", M, N);
 
   // Reinitialize KSP solver if necessary
   init(M, N);
@@ -255,7 +253,7 @@
   // Set up solver environment
   if (MPI::num_processes() > 1)
   {
-    info("Creating parallel PETSc Krylov solver.");
+    info(TRACE, "Creating parallel PETSc Krylov solver.");
     KSPCreate(PETSC_COMM_WORLD, ksp.get());
   }
   else
@@ -374,10 +372,6 @@
 //-----------------------------------------------------------------------------
 void PETScKrylovSolver::write_report(int num_iterations)
 {
-  // Check if we should write the report
-  if (!parameters["report"])
-    return;
-
   // Get name of solver and preconditioner
   const KSPType ksp_type;
   const PCType pc_type;
@@ -388,7 +382,7 @@
   PCGetType(pc, &pc_type);
 
   // Report number of iterations and solver type
-  info("PETSc Krylov solver (%s, %s) converged in %d iterations.",
+  info(PROGRESS, "PETSc Krylov solver (%s, %s) converged in %d iterations.",
           ksp_type, pc_type, num_iterations);
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/la/PETScLUSolver.cpp'
--- dolfin/la/PETScLUSolver.cpp	2009-09-28 12:21:23 +0000
+++ dolfin/la/PETScLUSolver.cpp	2010-03-24 10:33:06 +0000
@@ -68,7 +68,7 @@
 
   // Write a message
   if (report)
-    info("Solving linear system of size %d x %d (PETSc LU solver, %s).",
+    info(PROGRESS, "Solving linear system of size %d x %d (PETSc LU solver, %s).",
          A.size(0), A.size(1), solver_type);
 
   // Solve linear system
@@ -98,7 +98,7 @@
 
   // Write a message
   if ( report )
-    info("Solving linear system of size %d x %d (PETSc LU solver).",
+    info(PROGRESS, "Solving linear system of size %d x %d (PETSc LU solver).",
 		A.size(0), A.size(1));
 
   // Solve linear system
@@ -155,7 +155,7 @@
   // Set up solver environment
   if (MPI::num_processes() > 1)
   {
-    info("Creating parallel PETSc Krylov solver (for LU factorization).");
+    info(TRACE, "Creating parallel PETSc Krylov solver (for LU factorization).");
     KSPCreate(PETSC_COMM_WORLD, &ksp);
   }
   else

=== modified file 'dolfin/la/SLEPcEigenSolver.cpp'
--- dolfin/la/SLEPcEigenSolver.cpp	2009-12-02 19:49:45 +0000
+++ dolfin/la/SLEPcEigenSolver.cpp	2010-03-23 15:50:40 +0000
@@ -166,7 +166,7 @@
 
   const EPSType eps_type = 0;
   EPSGetType(eps, &eps_type);
-  info("Eigenvalue solver (%s) converged in %d iterations.",
+  info(PROGRESS, "Eigenvalue solver (%s) converged in %d iterations.",
        eps_type, num_iterations);
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/la/SingularSolver.cpp'
--- dolfin/la/SingularSolver.cpp	2009-09-08 16:38:36 +0000
+++ dolfin/la/SingularSolver.cpp	2010-03-24 10:33:06 +0000
@@ -31,7 +31,7 @@
                                    GenericVector& x,
                                    const GenericVector& b)
 {
-  info("Solving singular system...");
+  info(TRACE, "Solving singular system...");
 
   // Propagate parameters
   linear_solver.parameters.update(parameters("linear_solver"));
@@ -60,7 +60,7 @@
                                    const GenericVector& b,
                                    const GenericMatrix& M)
 {
-  info("Solving singular system...");
+  info(TRACE, "Solving singular system...");
 
   // Propagate parameters
   linear_solver.parameters.update(parameters("linear_solver"));
@@ -171,7 +171,7 @@
   assert(B);
   assert(c);
 
-  info("Creating extended hopefully non-singular system...");
+  info(TRACE, "Creating extended hopefully non-singular system...");
 
   // Reset matrix
   B->zero();

=== modified file 'dolfin/la/SparsityPattern.cpp'
--- dolfin/la/SparsityPattern.cpp	2010-01-02 15:34:51 +0000
+++ dolfin/la/SparsityPattern.cpp	2010-03-23 15:50:40 +0000
@@ -194,7 +194,8 @@
     return;
 
   // Print some useful information
-  info_statistics();
+  if (get_log_level() <= DEBUG)
+    info_statistics();
 
   // Communicate non-local blocks if any
   if (row_range_min != 0 || row_range_max != shape[0])

=== modified file 'dolfin/la/UmfpackLUSolver.cpp'
--- dolfin/la/UmfpackLUSolver.cpp	2009-12-10 11:38:15 +0000
+++ dolfin/la/UmfpackLUSolver.cpp	2010-03-24 10:33:06 +0000
@@ -73,7 +73,7 @@
     (const long int*) std::tr1::get<1>(data), std::tr1::get<2>(data), M, nnz);
 
   // Factorize
-  info("LU-factorizing linear system of size %d x %d (UMFPACK).", M, M);
+  info(PROGRESS, "LU-factorizing linear system of size %d x %d (UMFPACK).", M, M);
   umfpack.factorize();
 
   return 1;
@@ -92,7 +92,7 @@
   // Initialise solution vector and solve
   x.resize(N);
 
-  info("Solving factorized linear system of size %d x %d (UMFPACK).", N, N);
+  info(PROGRESS, "Solving factorized linear system of size %d x %d (UMFPACK).", N, N);
   // Solve for tranpose since we use compressed rows and UMFPACK expected compressed columns
   umfpack.factorized_solve(x.data(), b.data(), true);
 

=== added file 'dolfin/log/LogLevel.h'
--- dolfin/log/LogLevel.h	1970-01-01 00:00:00 +0000
+++ dolfin/log/LogLevel.h	2010-03-23 15:50:40 +0000
@@ -0,0 +1,19 @@
+#ifndef __LOGLEVEL_H
+#define __LOGLEVEL_H
+
+namespace dolfin
+{
+  /* These match the levels in the python 'logging' module (and adds trace/progress) */
+
+  enum LogLevel {
+    CRITICAL  = 50,             /* errors that may lead to data corruption and suchlike */
+    ERROR     = 40,             /* things that go boom */
+    WARNING   = 30,             /* things that may go boom later */
+    INFO      = 20,             /* information of general interest */
+    PROGRESS  = 16,             /* what's happening (broadly) */
+    TRACE     = 13,             /* what's happening (in detail) */
+    DEBUG     = 10              /* sundry */
+  };
+}
+
+#endif

=== modified file 'dolfin/log/Logger.cpp'
--- dolfin/log/Logger.cpp	2009-09-15 11:57:07 +0000
+++ dolfin/log/Logger.cpp	2010-03-23 15:50:40 +0000
@@ -25,7 +25,7 @@
 
 //-----------------------------------------------------------------------------
 Logger::Logger()
-  : active(true), log_level(0), indentation_level(0), logstream(&std::cout),
+  : active(true), log_level(PROGRESS), indentation_level(0), logstream(&std::cout),
     process_number(-1)
 {
   if (MPI::num_processes() > 1)
@@ -61,7 +61,7 @@
 void Logger::warning(std::string msg) const
 {
   std::string s = std::string("*** Warning: ") + msg;
-  write(0, s);
+  write(WARNING, s);
 }
 //-----------------------------------------------------------------------------
 void Logger::error(std::string msg) const
@@ -84,34 +84,24 @@
 //-----------------------------------------------------------------------------
 void Logger::progress(std::string title, double p) const
 {
-  int N = DOLFIN_TERM_WIDTH - 15;
-  int n = static_cast<int>(p*static_cast<double>(N));
-
-  // Print the title
-  std::string s = "| " + title;
-  for (uint i = 0; i < (N - title.size() - 1); i++)
-    s += " ";
-  s += "|";
-  write(0, s);
-
-  // Print the progress bar
-  s = "|";
+  std::stringstream line;
+  line << title << " [";
+
+  const int N = DOLFIN_TERM_WIDTH - title.size() - 12 - 2*indentation_level;
+  const int n = static_cast<int>(p*static_cast<double>(N));
+
   for (int i = 0; i < n; i++)
-    s += "=";
-  if (n > 0 && n < N)
-  {
-    s += "|";
-    n++;
-  }
-  for (int i = n; i < N; i++)
-    s += "-";
-  s += "| ";
-  std::stringstream line;
+    line << '=';
+  if (n < N)
+    line << '>';
+  for (int i = n+1; i < N; i++)
+    line << ' ';
+
   line << std::setiosflags(std::ios::fixed);
   line << std::setprecision(1);
-  line << 100.0*p;
-  s += line.str() + "%";
-  write(0, s);
+  line << "] " << 100.0*p << '%';
+
+  write(PROGRESS, line.str());
 }
 //-----------------------------------------------------------------------------
 void Logger::set_output_stream(std::ostream& ostream)
@@ -138,7 +128,7 @@
   // Print a message
   std::stringstream line;
   line << "Elapsed time: " << elapsed_time << " (" << task << ")";
-  info(line.str(), 1);
+  info(line.str(), TRACE);
 
   // Store values for summary
   map_iterator it = timings.find(task);
@@ -207,13 +197,13 @@
 void Logger::__debug(std::string msg) const
 {
   std::string s = std::string("Debug: ") + msg;
-  write(0, s);
+  write(DEBUG, s);
 }
 //-----------------------------------------------------------------------------
 void Logger::write(int log_level, std::string msg) const
 {
   // Check log level
-  if (!active || log_level > this->log_level)
+  if (!active || log_level < this->log_level)
     return;
 
   // Prefix with process number if running in parallel

=== modified file 'dolfin/log/Logger.h'
--- dolfin/log/Logger.h	2009-07-10 11:29:15 +0000
+++ dolfin/log/Logger.h	2010-03-23 15:50:40 +0000
@@ -15,10 +15,10 @@
 #include <ostream>
 #include <map>
 #include <dolfin/common/types.h>
+#include "LogLevel.h"
 
 namespace dolfin
 {
-
   class Logger
   {
   public:
@@ -30,10 +30,10 @@
     ~Logger();
 
     /// Print message
-    void info(std::string msg, int log_level = 0) const;
+    void info(std::string msg, int log_level = INFO) const;
 
     /// Print underlined message
-    void info_underline(std::string msg, int log_level = 0) const;
+    void info_underline(std::string msg, int log_level = INFO) const;
 
     /// Print warning
     void warning(std::string msg) const;
@@ -42,7 +42,7 @@
     void error(std::string msg) const;
 
     /// Begin task (increase indentation level)
-    void begin(std::string msg, int log_level = 0);
+    void begin(std::string msg, int log_level = PROGRESS);
 
     /// End task (decrease indentation level)
     void end();

=== modified file 'dolfin/log/Progress.cpp'
--- dolfin/log/Progress.cpp	2009-07-02 16:25:23 +0000
+++ dolfin/log/Progress.cpp	2010-03-24 14:50:48 +0000
@@ -28,8 +28,8 @@
   // LogManager::logger.progress(title, 0.0);
   t = time();
 
-  // When log level is more than 0, progress is always visible
-  if (LogManager::logger.get_log_level() > 0 )
+  // When log level is TRACE or lower, always display at least the 100% message
+  if (LogManager::logger.get_log_level() <= TRACE )
     always = true;
 }
 //-----------------------------------------------------------------------------
@@ -40,8 +40,8 @@
   // LogManager::logger.progress(title, 0.0);
   t = time();
 
-  // When log level is more than 0, progress is always visible
-  if (LogManager::logger.get_log_level() > 0 )
+  // When log level is TRACE or lower, always display at least the 100% message
+  if (LogManager::logger.get_log_level() <= TRACE )
     always = true;
 }
 //-----------------------------------------------------------------------------
@@ -71,25 +71,45 @@
 //-----------------------------------------------------------------------------
 void Progress::update(double p)
 {
+  if (finished)
+    return;
+
   //p = std::max(std::min(p, 1.0), 0.0);
   //const bool p_check = p - this->p >= p_step - DOLFIN_EPS;
 
   const double t = time();
-  const bool t_check = t - this->t >= t_step - DOLFIN_EPS;
+  const bool t_check = (t - this->t >= t_step - DOLFIN_EPS);
+  if (t_check) {
+    // reset time for next update
+    this->p = p;
+    this->t = t;
+  }
+
+  bool do_log_update = t_check;
+
+  if (t_check && !always && !displayed && p >= 0.7) {
+    // skip the first update, since it will probably reach 100%
+    // before the next time t_check is true
+    do_log_update = false;
+
+    // ...but don't skip the next (pretend we displayed this one)
+    displayed = true;
+  }
+
+  if (p >= 1.0) {
+    // always display 100% message if a message has already been shown
+    if (displayed || always)
+      do_log_update = true;
+
+    // ...but don't show more than one
+    finished = true;
+  }
 
   // Only update when the increase is significant
-  if (t_check || always || (p >= 1.0 && displayed && !finished))
+  if (do_log_update)
   {
     LogManager::logger.progress(title, p);
-    this->p = p;
-    this->t = t;
-    always = false;
     displayed = true;
   }
-
-  // Update finished flag
-  if (p >= 1.0)
-    finished = true;
-
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/log/log.cpp'
--- dolfin/log/log.cpp	2009-12-03 08:48:41 +0000
+++ dolfin/log/log.cpp	2010-03-23 15:50:40 +0000
@@ -136,12 +136,12 @@
   LogManager::logger.logging(active);
 }
 //-----------------------------------------------------------------------------
-void dolfin::set_log_level(uint level)
+void dolfin::set_log_level(int level)
 {
   LogManager::logger.set_log_level(level);
 }
 //-----------------------------------------------------------------------------
-dolfin::uint dolfin::get_log_level()
+int dolfin::get_log_level()
 {
   return LogManager::logger.get_log_level();
 }

=== modified file 'dolfin/log/log.h'
--- dolfin/log/log.h	2009-12-03 08:48:41 +0000
+++ dolfin/log/log.h	2010-03-23 15:50:40 +0000
@@ -12,6 +12,7 @@
 #include <string>
 #include <cassert>
 #include <dolfin/common/types.h>
+#include "LogLevel.h"
 
 namespace dolfin
 {
@@ -64,10 +65,10 @@
   void logging(bool active=true);
 
   /// Set log level
-  void set_log_level(uint level);
+  void set_log_level(int level);
 
   /// Get log level
-  uint get_log_level();
+  int get_log_level();
 
   /// Print summary of timings and tasks, optionally clearing stored timings
   void summary(bool reset=false);

=== modified file 'dolfin/main/SubSystemsManager.cpp'
--- dolfin/main/SubSystemsManager.cpp	2009-09-23 08:40:36 +0000
+++ dolfin/main/SubSystemsManager.cpp	2010-03-23 15:50:40 +0000
@@ -70,7 +70,7 @@
   if ( sub_systems_manager.petsc_initialized )
     return;
 
-  info(1, "Initializing PETSc (ignoring command-line arguments).");
+  info(TRACE, "Initializing PETSc (ignoring command-line arguments).");
 
   // Dummy command-line arguments for PETSc. This is needed since
   // PetscInitializeNoArguments() does not seem to work.
@@ -95,7 +95,7 @@
 
   // Print message if PETSc is intialised with command line arguments
   if (argc > 1)
-    info(1, "Initializing PETSc with given command-line arguments.");
+    info(TRACE, "Initializing PETSc with given command-line arguments.");
 
   // Initialize PETSc
   PetscInitialize(&argc, &argv, PETSC_NULL, PETSC_NULL);

=== modified file 'dolfin/main/init.cpp'
--- dolfin/main/init.cpp	2009-07-02 13:09:31 +0000
+++ dolfin/main/init.cpp	2010-03-24 10:33:06 +0000
@@ -12,7 +12,7 @@
 //-----------------------------------------------------------------------------
 void dolfin::dolfin_init(int argc, char* argv[])
 {
-  info("Initializing DOLFIN version %s.", DOLFIN_VERSION);
+  info(PROGRESS, "Initializing DOLFIN version %s.", DOLFIN_VERSION);
 
 #ifdef HAS_PETSC
   SubSystemsManager::init_petsc(argc, argv);

=== modified file 'dolfin/mesh/BoundaryComputation.cpp'
--- dolfin/mesh/BoundaryComputation.cpp	2009-11-04 10:52:55 +0000
+++ dolfin/mesh/BoundaryComputation.cpp	2010-03-23 15:50:40 +0000
@@ -43,7 +43,7 @@
   // the boundary. A facet is on the boundary if it is connected to
   // exactly one cell.
 
-  info(1, "Computing boundary mesh.");
+  info(TRACE, "Computing boundary mesh.");
 
   // Open boundary mesh for editing
   const uint D = mesh.topology().dim();

=== modified file 'dolfin/mesh/LocalMeshCoarsening.cpp'
--- dolfin/mesh/LocalMeshCoarsening.cpp	2009-10-08 19:11:44 +0000
+++ dolfin/mesh/LocalMeshCoarsening.cpp	2010-03-24 10:33:06 +0000
@@ -34,7 +34,7 @@
                                                         MeshFunction<bool>& cell_marker,
                                                         bool coarsen_boundary)
 {
-  info("Coarsen simplicial mesh by edge collapse.");
+  info(TRACE, "Coarsen simplicial mesh by edge collapse.");
 
   // Get size of old mesh
   //const uint num_vertices = mesh.size(0);

=== modified file 'dolfin/mesh/LocalMeshData.cpp'
--- dolfin/mesh/LocalMeshData.cpp	2009-09-11 09:28:01 +0000
+++ dolfin/mesh/LocalMeshData.cpp	2010-03-24 10:33:06 +0000
@@ -176,7 +176,7 @@
     for (uint p = 0; p < num_processes; p++)
     {
       std::pair<uint, uint> local_range = MPI::local_range(p, num_global_vertices);
-      info("Sending %d vertices to process %d, range is (%d, %d)",
+      info(PROGRESS, "Sending %d vertices to process %d, range is (%d, %d)",
            local_range.second - local_range.first, p, local_range.first, local_range.second);
       for (uint i = local_range.first; i < local_range.second; i++)
       {
@@ -209,7 +209,7 @@
     for (uint p = 0; p < num_processes; p++)
     {
       std::pair<uint, uint> local_range = MPI::local_range(p, num_global_cells);
-      info("Sending %d cells to process %d, range is (%d, %d)",
+      info(PROGRESS, "Sending %d cells to process %d, range is (%d, %d)",
            local_range.second - local_range.first, p, local_range.first, local_range.second);
       for (uint i = local_range.first; i < local_range.second; i++)
       {
@@ -276,7 +276,7 @@
     vertex_coordinates.push_back(coordinates);
   }
   
-  info("Received %d vertex coordinates", vertex_coordinates.size());
+  info(PROGRESS, "Received %d vertex coordinates", vertex_coordinates.size());
 
 }
 //-----------------------------------------------------------------------------
@@ -287,7 +287,7 @@
   for (uint i = 0; i < values.size(); i++)
     vertex_indices.push_back(values[i]);
 
-  info("Received %d vertex indices", vertex_coordinates.size());
+  info(PROGRESS, "Received %d vertex indices", vertex_coordinates.size());
 }
 //-----------------------------------------------------------------------------
 void LocalMeshData::unpack_cell_vertices(const std::vector<uint>& values)
@@ -306,6 +306,6 @@
     cell_vertices.push_back(vertices);
   }
 
-  info("Received %d cell vertices", cell_vertices.size());
+  info(PROGRESS, "Received %d cell vertices", cell_vertices.size());
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/mesh/LocalMeshRefinement.cpp'
--- dolfin/mesh/LocalMeshRefinement.cpp	2009-10-08 19:11:44 +0000
+++ dolfin/mesh/LocalMeshRefinement.cpp	2010-03-24 10:33:06 +0000
@@ -38,7 +38,7 @@
                                                     MeshFunction<bool>& cell_marker,
                                                     bool refine_boundary)
 {
-  info("Refining simplicial mesh by edge bisection.");
+  info(TRACE, "Refining simplicial mesh by edge bisection.");
 
   // Get size of old mesh
   const uint num_vertices = mesh.size(0);
@@ -518,7 +518,7 @@
     mat = newmesh.data().create_mesh_function("material indicators", newmesh.type().dim());
     for(dolfin::uint i=0; i< newmesh.num_cells(); i++)
       (*mat)[i] = (*oldmesh.data().mesh_function("material indicators"))[cell_map[i]];
-    info("MeshData MeshFunction \"material indicators\" transformed.");
+    info(TRACE, "MeshData MeshFunction \"material indicators\" transformed.");
   }
 
   //Rewrite boundary indicators
@@ -591,7 +591,7 @@
       }
     }
 
-  info("MeshData \"boundary indicators\" transformed.");
+  info(TRACE, "MeshData \"boundary indicators\" transformed.");
   }
 
 }

=== modified file 'dolfin/mesh/Mesh.cpp'
--- dolfin/mesh/Mesh.cpp	2009-12-16 13:56:35 +0000
+++ dolfin/mesh/Mesh.cpp	2010-03-24 10:33:06 +0000
@@ -204,7 +204,7 @@
 {
   // FIXME: Move implementation to separate class and just call function here
 
-  info("No cells marked for coarsening, assuming uniform mesh coarsening.");
+  info(DEBUG, "No cells marked for coarsening, assuming uniform mesh coarsening.");
   MeshFunction<bool> cell_marker(*this);
   cell_marker.init(this->topology().dim());
   for (CellIterator c(*this); !c.end(); ++c)

=== modified file 'dolfin/mesh/MeshPartitioning.cpp'
--- dolfin/mesh/MeshPartitioning.cpp	2009-10-08 19:11:44 +0000
+++ dolfin/mesh/MeshPartitioning.cpp	2010-03-24 10:33:06 +0000
@@ -501,7 +501,7 @@
   for (uint i = 0; i < entity_indices.size(); ++i)
   {
     if (entity_indices[i] < 0)
-      info("Missing global number for local entity (%d, %d).", d, i);
+      info(WARNING, "Missing global number for local entity (%d, %d).", d, i);
     assert(entity_indices[i] >= 0);
     (*global_entity_indices)[i] = static_cast<uint>(entity_indices[i]);
   }
@@ -586,7 +586,7 @@
                            &ncommonnodes, &nparts,
                            tpwgts, ubvec, options,
                            &edgecut, part, &(*comm)); 
-  info("Partitioned mesh, edge cut is %d.", edgecut);
+  info(PROGRESS, "Partitioned mesh, edge cut is %d.", edgecut);
 
   // Copy mesh_data
   cell_partition.clear();

=== modified file 'dolfin/mesh/RivaraRefinement.cpp'
--- dolfin/mesh/RivaraRefinement.cpp	2009-10-08 19:11:44 +0000
+++ dolfin/mesh/RivaraRefinement.cpp	2010-03-24 10:33:06 +0000
@@ -20,7 +20,7 @@
 			      MeshFunction<uint>& cell_map,
 			      std::vector<int>& facet_map)
 {
-  info("Refining simplicial mesh by recursive Rivara bisection.");
+  info(TRACE, "Refining simplicial mesh by recursive Rivara bisection.");
 
   int dim = mesh.topology().dim();
 

=== modified file 'dolfin/mesh/SubDomain.cpp'
--- dolfin/mesh/SubDomain.cpp	2009-10-08 19:11:44 +0000
+++ dolfin/mesh/SubDomain.cpp	2010-03-23 15:50:40 +0000
@@ -38,7 +38,7 @@
 //-----------------------------------------------------------------------------
 void SubDomain::mark(MeshFunction<uint>& sub_domains, uint sub_domain) const
 {
-  info(1, "Computing sub domain markers for sub domain %d.", sub_domain);
+  info(TRACE, "Computing sub domain markers for sub domain %d.", sub_domain);
 
   // Get the dimension of the entities we are marking
   const uint dim = sub_domains.dim();

=== modified file 'dolfin/mesh/UniformMeshRefinement.cpp'
--- dolfin/mesh/UniformMeshRefinement.cpp	2009-05-02 20:55:48 +0000
+++ dolfin/mesh/UniformMeshRefinement.cpp	2010-03-23 15:50:40 +0000
@@ -27,7 +27,7 @@
 //-----------------------------------------------------------------------------
 void UniformMeshRefinement::refine_simplex(Mesh& mesh)
 {
-  info(1, "Refining simplicial mesh uniformly.");
+  info(TRACE, "Refining simplicial mesh uniformly.");
 
   // Generate cell - edge connectivity if not generated
   mesh.init(mesh.topology().dim(), 1);

=== modified file 'dolfin/mesh/UnitSphere.cpp'
--- dolfin/mesh/UnitSphere.cpp	2009-08-06 17:57:54 +0000
+++ dolfin/mesh/UnitSphere.cpp	2010-03-24 10:33:06 +0000
@@ -20,7 +20,7 @@
   // Receive mesh according to parallel policy
   if (MPI::is_receiver()) { MeshPartitioning::partition(*this); return; }
 
-  info("UnitSphere is experimental. It may be of poor quality mesh");
+  info(WARNING, "UnitSphere is experimental. It may be of poor quality mesh");
 
   uint ny=nx;
   uint nz=nx;

=== modified file 'dolfin/nls/NewtonSolver.cpp'
--- dolfin/nls/NewtonSolver.cpp	2009-10-06 19:35:28 +0000
+++ dolfin/nls/NewtonSolver.cpp	2010-03-24 10:33:06 +0000
@@ -107,7 +107,7 @@
   }
 
   if (newton_converged)
-    info("Newton solver finished in %d iterations and %d linear solver iterations.",
+    info(PROGRESS, "Newton solver finished in %d iterations and %d linear solver iterations.",
             newton_iteration, krylov_iterations);
   else
     warning("Newton solver did not converge.");

=== modified file 'dolfin/ode/ComplexODE.cpp'
--- dolfin/ode/ComplexODE.cpp	2009-05-02 20:55:48 +0000
+++ dolfin/ode/ComplexODE.cpp	2010-03-24 10:33:06 +0000
@@ -14,7 +14,7 @@
 ComplexODE::ComplexODE(uint n, real T) : ODE(2*n, T), n(n), j(0.0, 1.0),
                                            zvalues(0), fvalues(0), yvalues(0)
 {
-  info("Creating complex ODE of size %d (%d complex components).", N, n);
+  info(TRACE, "Creating complex ODE of size %d (%d complex components).", N, n);
 
   // Initialize complex solution vector and right-hand side
   zvalues = new complex[n];

=== modified file 'dolfin/ode/MonoAdaptivity.cpp'
--- dolfin/ode/MonoAdaptivity.cpp	2009-09-08 16:38:36 +0000
+++ dolfin/ode/MonoAdaptivity.cpp	2010-03-24 10:33:06 +0000
@@ -75,7 +75,7 @@
     controller.reset(k);
     _accept = false;
 
-    //info("e = %.3e  tol = %.3e", error, tol);
+    //info(DEBUG, "e = %.3e  tol = %.3e", error, tol);
   }
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/ode/MultiAdaptiveJacobian.cpp'
--- dolfin/ode/MultiAdaptiveJacobian.cpp	2009-07-10 12:16:02 +0000
+++ dolfin/ode/MultiAdaptiveJacobian.cpp	2010-03-24 10:33:06 +0000
@@ -37,7 +37,7 @@
   for (uint pos = 0; pos < sum; pos++)
     Jvalues[pos] = 0.0;
 
-  info("Generated Jacobian data structure for %d dependencies.", sum);
+  info(PROGRESS, "Generated Jacobian data structure for %d dependencies.", sum);
 
   // Compute maximum number of dependencies
   uint maxsize = 0;
@@ -83,7 +83,7 @@
 {
   // Compute Jacobian at the beginning of the slab
   double t = to_double(ts.starttime());
-  //info("Recomputing Jacobian matrix at t = %f.", t);
+  //info(PROGRESS, "Recomputing Jacobian matrix at t = %f.", t);
 
   // Compute Jacobian
   for (uint i = 0; i < ode.size(); i++)
@@ -96,7 +96,7 @@
   /*
   // Compute Jacobian at the end of the slab
   double t = ts.endtime();
-  //info("Recomputing Jacobian matrix at t = %f.", t);
+  //info(PROGRESS, "Recomputing Jacobian matrix at t = %f.", t);
 
   // Compute Jacobian
   for (uint i = 0; i < ode.size(); i++)
@@ -275,7 +275,7 @@
 	// searching, but we were clever enough to pick out the value
 	// before when we had the chance... :-)
 	const double dfdu = Jlookup[dep];
-	//info("Looks like df_%d/du_%d = %f", i0, i1, dfdu);
+	//info(DEBUG, "Looks like df_%d/du_%d = %f", i0, i1, dfdu);
 
 	// Iterate over quadrature points of other element
 	const double tmp0 = k0 * dfdu;
@@ -447,7 +447,7 @@
 	// searching, but we were clever enough to pick out the value
 	// before when we had the chance... :-)
 	const double dfdu = Jlookup[dep];
-	//info("Looks like df_%d/du_%d = %f", i0, i1, dfdu);
+	//info(DEBUG, "Looks like df_%d/du_%d = %f", i0, i1, dfdu);
 
 	// Iterate over quadrature points of other element
 	const double tmp0 = k0 * dfdu;

=== modified file 'dolfin/ode/MultiAdaptiveTimeSlab.cpp'
--- dolfin/ode/MultiAdaptiveTimeSlab.cpp	2009-09-08 16:38:36 +0000
+++ dolfin/ode/MultiAdaptiveTimeSlab.cpp	2010-03-24 10:33:06 +0000
@@ -43,7 +43,7 @@
   real_zero(N, u);
 
   // Initialize transpose of dependencies if necessary
-  info("Computing transpose (inverse) of dependency pattern.");
+  info(TRACE, "Computing transpose (inverse) of dependency pattern.");
   if (ode.dependencies.sparse() && !ode.transpose.sparse())
     ode.transpose.transp(ode.dependencies);
 }
@@ -100,7 +100,7 @@
 //-----------------------------------------------------------------------------
 bool MultiAdaptiveTimeSlab::solve()
 {
-  //info("Solving time slab system on [%f, %f].", _a, _b);
+  //info(TRACE, "Solving time slab system on [%f, %f].", _a, _b);
 
   // Copy u0 to u. This happens automatically in feval if user has set
   // dependencies correctly, but you never know...
@@ -120,7 +120,7 @@
   //for (uint i = 0; i < N; i++)
   // {
   //  real endval = jx[elast[i] * method->nsize() + method->nsize() - 1];
-  //  info("i = %d: u = %.16e", i, endval);
+  //  info(DEBUG, "i = %d: u = %.16e", i, endval);
   // }
 }
 //-----------------------------------------------------------------------------
@@ -418,7 +418,7 @@
   // Get next available position
   uint pos = size_e.next++;
 
-  //info("  Creating element e = %d for i = %d at [%f, %f]", pos, index, a, b);
+  //info(DEBUG, "  Creating element e = %d for i = %d at [%f, %f]", pos, index, a, b);
 
   //if ( index == 145 )
   //  cout << "Modified: " << b - a << endl << endl;
@@ -456,7 +456,7 @@
   // Add dependencies to elements that depend on the given element if the
   // depending elements use larger time steps
 
-  //info("Checking dependencies to element %d (component %d)", element, index);
+  //info(TRACE, "Checking dependencies to element %d (component %d)", element, index);
 
   // Get list of components depending on current component
   const std::vector<uint>& deps = ode.transpose[i0];
@@ -486,7 +486,7 @@
     if ( !within(a0, b0, a1, b1) || s0 == s1 )
       continue;
 
-    //info("  Checking element %d (component %d)", e1, i1);
+    //info(DEBUG, "  Checking element %d (component %d)", e1, i1);
 
     // Iterate over dofs for element
     for (uint n = 0; n < method->nsize(); n++)
@@ -494,7 +494,7 @@
       //const uint j = j1 + n;
       const real t = a1 + k1*method->npoint(n);
 
-      //info("    Checking dof at t = %f", t);
+      //info(DEBUG, "    Checking dof at t = %f", t);
 
       // Check if dof is contained in the current element
       if ( within(t, a0, b0) )
@@ -528,7 +528,7 @@
 
   if ( newsize <= size_s.size ) return;
 
-  //info("Reallocating: ns = %d", newsize);
+  //info(DEBUG, "Reallocating: ns = %d", newsize);
 
   Alloc::realloc(&sa, size_s.size, newsize);
   Alloc::realloc(&sb, size_s.size, newsize);
@@ -542,7 +542,7 @@
 
   if ( newsize <= size_e.size ) return;
 
-  //info("Reallocating: ne = %d", newsize);
+  //info(DEGUG, "Reallocating: ne = %d", newsize);
 
   Alloc::realloc(&ei, size_e.size, newsize);
   Alloc::realloc(&es, size_e.size, newsize);
@@ -558,7 +558,7 @@
 
   if ( newsize <= size_j.size ) return;
 
-  //info("Reallocating: nj = %d", newsize);
+  //info(DEBUG, "Reallocating: nj = %d", newsize);
 
   Alloc::realloc(&jx, size_j.size, newsize);
 
@@ -571,7 +571,7 @@
 
   if ( newsize <= size_d.size ) return;
 
-  //info("Reallocating: nd = %d", newsize);
+  //info(DEBUG, "Reallocating: nd = %d", newsize);
 
   Alloc::realloc(&de, size_d.size, newsize);
 

=== modified file 'dolfin/ode/ODE.cpp'
--- dolfin/ode/ODE.cpp	2009-09-10 15:24:42 +0000
+++ dolfin/ode/ODE.cpp	2010-03-24 10:33:06 +0000
@@ -27,7 +27,7 @@
 {
   not_working_in_parallel("ODE solver");
 
-  info("Creating ODE of size %d.", N);
+  info(TRACE, "Creating ODE of size %d.", N);
   parameters = default_parameters();
 
   #ifdef HAS_GMP

=== modified file 'dolfin/ode/ODECollection.cpp'
--- dolfin/ode/ODECollection.cpp	2009-09-10 12:07:47 +0000
+++ dolfin/ode/ODECollection.cpp	2010-03-24 10:33:06 +0000
@@ -14,7 +14,7 @@
 ODECollection::ODECollection(ODE& ode, uint num_systems)
   : ode(ode), num_systems(num_systems), states(0)
 {
-  info("Creating ODE collection of size %d x %d.", num_systems, ode.size());
+  info(TRACE, "Creating ODE collection of size %d x %d.", num_systems, ode.size());
 
   // Allocate state vectors
   states = new real[num_systems*ode.size()];

=== modified file 'dolfin/ode/ODESolver.cpp'
--- dolfin/ode/ODESolver.cpp	2009-09-10 11:07:52 +0000
+++ dolfin/ode/ODESolver.cpp	2010-03-24 10:33:06 +0000
@@ -39,7 +39,7 @@
   time_stepper.solve();
 
   // Report elapsed time
-  info("ODE solution computed in %.3f seconds.", toc());
+  info(PROGRESS, "ODE solution computed in %.3f seconds.", toc());
 
   end();
 }
@@ -57,7 +57,7 @@
   u.flush();
 
   // Report elapsed time
-  info("ODE solution computed in %.3f seconds.", toc());
+  info(PROGRESS, "ODE solution computed in %.3f seconds.", toc());
 
   end();
 }

=== modified file 'dolfin/ode/cGqMethod.cpp'
--- dolfin/ode/cGqMethod.cpp	2009-09-01 15:30:03 +0000
+++ dolfin/ode/cGqMethod.cpp	2010-03-24 10:33:06 +0000
@@ -21,7 +21,7 @@
 //-----------------------------------------------------------------------------
 cGqMethod::cGqMethod(uint q) : Method(q, q + 1, q)
 {
-  info("Initializing continuous Galerkin method cG(%d).", q);
+  info(TRACE, "Initializing continuous Galerkin method cG(%d).", q);
 
   init();
 

=== modified file 'dolfin/ode/dGqMethod.cpp'
--- dolfin/ode/dGqMethod.cpp	2009-09-01 15:30:03 +0000
+++ dolfin/ode/dGqMethod.cpp	2010-03-24 10:33:06 +0000
@@ -20,7 +20,7 @@
 //-----------------------------------------------------------------------------
 dGqMethod::dGqMethod(unsigned int q) : Method(q, q + 1, q + 1)
 {
-  info("Initializing discontinuous Galerkin method dG(%d).", q);
+  info(TRACE, "Initializing discontinuous Galerkin method dG(%d).", q);
 
   init();
 

=== modified file 'dolfin/parameter/GlobalParameters.cpp'
--- dolfin/parameter/GlobalParameters.cpp	2009-10-29 09:41:24 +0000
+++ dolfin/parameter/GlobalParameters.cpp	2010-03-24 10:33:06 +0000
@@ -70,7 +70,7 @@
 //-----------------------------------------------------------------------------
 void GlobalParameters::parse(int argc, char* argv[])
 {
-  info("Parsing command-line arguments...");
+  info(TRACE, "Parsing command-line arguments...");
 
   // Extract DOLFIN and PETSc arguments
   std::vector<std::string> args_dolfin;

=== modified file 'dolfin/parameter/Parameters.cpp'
--- dolfin/parameter/Parameters.cpp	2009-12-08 21:18:18 +0000
+++ dolfin/parameter/Parameters.cpp	2010-03-24 10:33:06 +0000
@@ -214,7 +214,7 @@
 //-----------------------------------------------------------------------------
 void Parameters::parse(int argc, char* argv[])
 {
-  info("Parsing command-line arguments...");
+  info(TRACE, "Parsing command-line arguments...");
   parse_dolfin(argc, argv);
 }
 //-----------------------------------------------------------------------------

=== modified file 'dolfin/plot/plot.cpp'
--- dolfin/plot/plot.cpp	2009-10-07 09:26:57 +0000
+++ dolfin/plot/plot.cpp	2010-03-24 10:33:06 +0000
@@ -29,7 +29,7 @@
   if (dolfin::MPI::num_processes() > 1)
   {
     if (dolfin::MPI::process_number() == 0)
-      info("On screen plotting from C++ not yet working in parallel.");
+      warning("On screen plotting from C++ not yet working in parallel.");
     return;
   }
 

=== modified file 'dolfin/swig/log_post.i'
--- dolfin/swig/log_post.i	2009-09-11 05:53:28 +0000
+++ dolfin/swig/log_post.i	2010-03-23 15:50:40 +0000
@@ -53,6 +53,11 @@
     __debug(file, line, func, message)
 
 def info(*args):
+    if args and isinstance(args[0], int):
+        if args[0] < get_log_level():
+            return
+        args = args[1:]
+
     if len(args) > 0 and isinstance(args[0],(Variable,Parameters)):
         if len(args) > 1:
             _info(args[0].str(*args[1:]))

=== modified file 'dolfin/swig/parameter_post.i'
--- dolfin/swig/parameter_post.i	2009-12-17 03:34:54 +0000
+++ dolfin/swig/parameter_post.i	2010-03-23 15:50:40 +0000
@@ -18,6 +18,14 @@
 %extend dolfin::Parameter
 {
 %pythoncode%{
+def warn_once(self, msg):
+    cls = self.__class__
+    if not hasattr(cls, '_warned'):
+        cls._warned = set()
+    if not msg in cls._warned:
+        cls._warned.add(msg)
+        print msg
+
 def value(self):
     val_type = self.type_str()
     if val_type == "string":
@@ -27,9 +35,10 @@
     elif val_type == "bool":
         return bool(self)
     elif val_type == "real":
-        if has_gmp():
+        from logging import DEBUG
+        if get_log_level() <= DEBUG and has_gmp():
             # FIXME: Is it possible to convert real to some high-precision Python type?
-            print "Warning: Converting real-valued parameter to double, might loose precision."
+            self.warn_once("Warning: Converting real-valued parameter '%s' to double, might lose precision."%self.key())
         return float(self)
     else:
         raise TypeError, "unknown value type '%s' of parameter '%s'"%(val_type, self.key())
@@ -49,9 +58,10 @@
     elif val_type == "bool":
         return 
     elif val_type == "real":
-        if has_gmp():
+        from logging import DEBUG
+        if get_log_level() <= DEBUG and has_gmp():
             # FIXME: Is it possible to convert real to some high-precision Python type?
-            print "Warning: Converting real-valued parameter to double, might loose precision."
+            self.warn_once("Warning: Converting real-valued parameter '%s' to double, might lose precision."%self.key())
         local_range = self._get_real_range()
         if local_range[0] == 0 and local_range[0] == local_range[0]:
             return

# Begin bundle
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
8+CjT77OQJLhvDx/unIcx/8XckU4UJASNWGt


--- End Message ---

Attachment: signature.asc
Description: Digital signature


Follow ups