Clean up the dolfin-adjoint API

Registered by Patrick Farrell

The dolfin-adjoint API has grown rather than been designed, and it relies on a bit too much magic. Let's take the opportunity now to clean it up.

Blueprint information

Status:
Not started
Approver:
None
Priority:
Undefined
Drafter:
None
Direction:
Needs approval
Assignee:
None
Definition:
New
Series goal:
None
Implementation:
Unknown
Milestone target:
None

Related branches

Sprints

Whiteboard

[PEF]

The current API is

Overloaded things from DOLFIN
------------------------------------------------
assemble
assemble_system
LUSolver
down_cast
AdjointPETScKrylovSolver
AdjointKrylovMatrix
solve
NewtonSolver
KrylovSolver
NonlinearVariationalSolver
NonlinearVariationalProblem
LinearVariationalSolver
LinearVariationalProblem
project
Function
interpolate
Constant

dolfin-adjoint objects
--------------------------------
InitialConditionParameter
ScalarParameter
ScalarParameters
Functional

dolfin-adjoint functions
-----------------------------------
adj_html
adj_reset
adj_checkpointing
adj_inc_timestep
adj_check_checkpoints
adj_compute_propagator_svd
replay_dolfin
compute_adjoint
compute_tlm
compute_gradient
taylor_test

a) One problem is that the adjointer is hidden -- users don't necessarily get the idea that there's a global object recording state, as it's never visible to them. David thinks that showing users the adjointer would remove some of the magic.

b) It annoys me that the API is inconsistent (some functions with adj_, some not). I think all functions should live in the same namespace. Not sure about the objects, though.

c) David wants to replace

from dolfin import *
from dolfin_adjoint import *

with

from dolfin_adjoint import *

(i.e. dolfin_adjoint automatically provides all necessary symbols) as this allows for things like

import dolfin_adjoint as dolfin

to be used in scripts where they do "import dolfin" instead of "from dolfin import *".

Suggestions?

[MER, July 11 2012]

With regard to (b) I strongly support consistentifying the API (I would prefer functions without the prefix adj_, but with more descriptive names).

With regard to (c), I like having the possibility of explicitly using dolfin (and not dolfin_adjoint) functionality, especially for testing, debugging and supporting code where dolfin_adjoint should be an optional dependency. Therefore, I would prefer having it as is. (dolfin_adjoint should have a check that dolfin is installed though)

With regard to (a), yes. the scope of dolfin-adjoint objects/functions confuse
me. If we could make it more explicit (when desired) via exposing the adjointer,
and at the same time keeping "the minimal intervention for simple cases syntax", that would be great. I have no strong preferences/knowledge of how this should be done though, so please take this as an opinion rather than a suggestion.

(d) I find the name InitialConditionParameter confusing (because it can be something
else than a (physical) initial condition right?) Why do the different *Parameter* have
to have different names? Could there be have one Control[Parameter] (for instance), and the type automatically extracted from the argument(s)?

[PEF, July 11 2012]

c) I think if we do

from dolfin import *

inside dolfin_adjoint, then both cases will work fine. i.e. users could do

from dolfin import *

to get just dolfin, or do

from dolfin import *
from dolfin_adjoint import *

to get the current functionality, or do

import dolfin_adjoint as dolfin

if they currently use

import dolfin.

So I think everyone can be happy? (i.e. both David's use case and yours are satisfied)

[[MER: Yes!]]

b) Should all functions/objects live in some namespace? i.e. would you rather have

dJdic = adj.compute_gradient(adj.Functional, adj.Parameter)
or
dJdic = compute_gradient(Functional, Parameter)?

[[MER: The latter.]]

a) Can you be more specific about what confused you -- the scope of which objects?

[[MER: Here the confusion starts ;-) The functionality provided by replay for instance, what is it that provides this? After reading the code, I think it is a global adjointer object, created in adjglobals, right? So, I guess I'm confused about how to really stop/start annotating, reset the annotations, whether ever annotations could ever be "dropped" etc. ]]

[PEF]
I'll clarify the situation in your head (or try to), and then we can discuss how to make it so that the situation is obvious to others. Basically, you're correct.

At the moment, there is one adjointer, created in adjglobals. All functions implicitly refer to this global adjointer. Each solve/assign/etc records the relevant details with the adjointer (unless annotation is turned off). When you ask dolfin_adjoint to replay, it asks the adjointer for each equation in turn, and solves it.

How to start/stop annotating: either pass annotate=False to something that would annotate, or to deactivate annotation globally, do parameters["adjoint"]["stop_annotating"] = True.

How to reset the annotation: call adj_reset().

Is that all clear?

[[MER: Yes. ]]

One proposal is to

a) give the capacity to create other adjointers
b) make the default adjointer a default kwarg to all of the routines that use it, so that when people look at the help?? they see that it takes in an adjointer

Would that have prevented the confusion in the first place?

[[MER: I think this sounds good. However, I think my (previous) confusion could also be removed by adding a section on the design of dolfin-adjoint (as in there is a global adjointer object that records stuff, you can turn this off/on by ..., etc) in the tutorial/documentation. ]]

(?)

Work Items

This blueprint contains Public information 
Everyone can see this information.