Debugging

Visualising the system

It is sometimes useful when debugging a problem to see dolfin-adjoint’s interpretation of your forward system, and the other models it derives from that. The visualise function visualises the system as a graph using TensorFlow. To do this add

tape = get_working_tape()
tape.visualise()

This will create a log directory with TensorFlow event files. We can then run TensorBoard, which comes bundled with TensorFlow, and point it to this log directory:

tensorboard --logdir=log

By default the log directory is a directory log in the current working directory. This can be changed by specifying a directory in the logdir keyword argument to visualise.

Finally, we can open http://localhost:6006/ in a web browser to view the graph. To demonstrate, let us use a simplified version of our old Burgers’ equation example:

from dolfin import *
from fenics_adjoint import *

n = 30
mesh = UnitSquareMesh(n, n)
V = VectorFunctionSpace(mesh, "CG", 2)

u = project(Expression(("sin(2*pi*x[0])", "cos(2*pi*x[1])"), degree=2),  V)
u_next = Function(V)
v = TestFunction(V)

nu = Constant(0.0001)

timestep = Constant(0.01)

F = (inner((u_next - u)/timestep, v)
     + inner(grad(u_next)*u_next, v)
     + nu*inner(grad(u_next), grad(v)))*dx

bc = DirichletBC(V, (0.0, 0.0), "on_boundary")

solve(F == 0, u_next, bc)

tape = get_working_tape()
tape.visualise()

Here we solve the equation for only one timestep.

more info Download the code to find graph.

Running TensorBoard and opening http://localhost:6006/ in our web browser shows us the following graph:

../_images/simplified_burgers.png

Each node corresponds to an elementary operation, and we see that the structure is what we should expect: two functions, two constants and a set of boundary conditions go into an equation and we get one function out. To increase readability further we can add names to the functions:

from dolfin import *
from fenics_adjoint import *

n = 30
mesh = UnitSquareMesh(n, n)
V = VectorFunctionSpace(mesh, "CG", 2)

u = project(Expression(("sin(2*pi*x[0])", "cos(2*pi*x[1])"), degree=2),  V)
u_next = Function(V, name="u_next")
v = TestFunction(V)

nu = Constant(0.0001, name="nu")

timestep = Constant(0.01, name="dt")

F = (inner((u_next - u)/timestep, v)
     + inner(grad(u_next)*u_next, v)
     + nu*inner(grad(u_next), grad(v)))*dx

bc = DirichletBC(V, (0.0, 0.0), "on_boundary")

solve(F == 0, u_next, bc)

tape = get_working_tape()
tape.visualise()

The resulting graph is the following:

../_images/simplified_burgers_named.png

Here we have expanded some of the nodes by double-clicking on them. We see that it is indeed u_next that is found by the equation solve.

more info Download the code to find the graph with names.

If we have a time loop, the graph can easily become large and difficult to read. The name_scope factory function can be used to make this easier. To demonstrate this, we add a time loop to our example above:

from dolfin import *
from fenics_adjoint import *

n = 30
mesh = UnitSquareMesh(n, n)
V = VectorFunctionSpace(mesh, "CG", 2)

u = project(Expression(("sin(2*pi*x[0])", "cos(2*pi*x[1])"), degree=2),  V)
u_next = Function(V, name="u_next")
v = TestFunction(V)

nu = Constant(0.0001, name="nu")

timestep = Constant(0.01, name="dt")

F = (inner((u_next - u)/timestep, v)
     + inner(grad(u_next)*u_next, v)
     + nu*inner(grad(u_next), grad(v)))*dx

bc = DirichletBC(V, (0.0, 0.0), "on_boundary")

tape = get_working_tape()

t = 0.0
end = 0.05
while (t <= end):
    with tape.name_scope("Timestep"):
        solve(F == 0, u_next, bc)
        u.assign(u_next)
        t += float(timestep)

tape.visualise()

Here, we should note the line after we enter the while loop. In this line we use a with statement together with name_scope and a name for the scope. This will ensure that all nodes corresponding to one time step will be added to its own scope. After the time loop, we call visualise and get the following graph:

../_images/burgers_timesteps.png

We can then expand the scopes as we like. Here we have expanded the first time step:

../_images/burgers_timesteps_expanded.png

more info Download the code to find the graph with name scopes.

In the next section we discuss parallelisation.