78
81
eq_lhs, eq_rhs = define_nonlinear_equation(eq.lhs, u)
83
# Suppose we are solving for a variable w, and that variable shows up in the
84
# coefficients of eq_lhs/eq_rhs.
86
# a) the /previous value/ of that variable, and you want to timestep?
87
# b) the /value to be solved for/ in this solve?
88
# i.e. is it timelevel n-1, or n?
89
# if Dolfin is doing a linear solve, we want case a);
90
# if Dolfin is doing a nonlinear solve, we want case b).
91
# so if we are doing a nonlinear solve, we bump the timestep number here
92
# /before/ we map the coefficients -> dependencies,
93
# so that libadjoint records the dependencies with the right timestep number.
95
var = adj_variables.next(u)
97
# Set up the data associated with the matrix on the left-hand side. This goes on the diagonal
98
# of the 'large' system that incorporates all of the timelevels, which is why it is prefixed
100
diag_name = hashlib.md5(str(eq_lhs)).hexdigest() # we don't have a useful human-readable name, so take the md5sum of the string representation of the form
101
diag_deps = [adj_variables[coeff] for coeff in ufl.algorithms.extract_coefficients(eq_lhs) if hasattr(coeff, "function_space")]
102
diag_coeffs = [coeff for coeff in ufl.algorithms.extract_coefficients(eq_lhs) if hasattr(coeff, "function_space")]
103
diag_block = libadjoint.Block(diag_name, dependencies=diag_deps, test_hermitian=debugging["test_hermitian"], test_derivative=debugging["test_derivative"])
105
# Similarly, create the object associated with the right-hand side data.
89
solver_params = kwargs["solver_parameters"]
90
ksp = solver_params["linear_solver"]
95
solver_params = kwargs["solver_parameters"]
96
pc = solver_params["preconditioner"]
100
elif isinstance(args[0], dolfin.cpp.Matrix):
103
eq_lhs = assembly.assembly_cache[args[0]]
105
raise libadjoint.exceptions.LibadjointErrorInvalidInputs("dolfin_adjoint did not assemble your form, and so does not recognise your matrix. Did you from dolfin_adjoint import *?")
108
eq_rhs = assembly.assembly_cache[args[2]]
110
raise libadjoint.exceptions.LibadjointErrorInvalidInputs("dolfin_adjoint did not assemble your form, and so does not recognise your right-hand side. Did you from dolfin_adjoint import *?")
113
if not isinstance(u, dolfin.Function):
114
raise libadjoint.exceptions.LibadjointErrorInvalidInputs("dolfin_adjoint needs the Function you are solving for, rather than its .vector().")
116
newargs[1] = u.vector() # for the real solve later
128
eq_bcs = list(set(assembly.bc_cache[args[0]] + assembly.bc_cache[args[2]]))
130
raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to annotate your equation, sorry!")
132
# Suppose we are solving for a variable w, and that variable shows up in the
133
# coefficients of eq_lhs/eq_rhs.
135
# a) the /previous value/ of that variable, and you want to timestep?
136
# b) the /value to be solved for/ in this solve?
137
# i.e. is it timelevel n-1, or n?
138
# if Dolfin is doing a linear solve, we want case a);
139
# if Dolfin is doing a nonlinear solve, we want case b).
140
# so if we are doing a nonlinear solve, we bump the timestep number here
141
# /before/ we map the coefficients -> dependencies,
142
# so that libadjoint records the dependencies with the right timestep number.
144
var = adj_variables.next(u)
146
# Set up the data associated with the matrix on the left-hand side. This goes on the diagonal
147
# of the 'large' system that incorporates all of the timelevels, which is why it is prefixed
149
diag_name = hashlib.md5(str(eq_lhs) + str(eq_rhs) + str(u) + str(time.time())).hexdigest() # we don't have a useful human-readable name, so take the md5sum of the string representation of the forms
150
diag_deps = [adj_variables[coeff] for coeff in ufl.algorithms.extract_coefficients(eq_lhs) if hasattr(coeff, "function_space")]
151
diag_coeffs = [coeff for coeff in ufl.algorithms.extract_coefficients(eq_lhs) if hasattr(coeff, "function_space")]
152
diag_block = libadjoint.Block(diag_name, dependencies=diag_deps, test_hermitian=debugging["test_hermitian"], test_derivative=debugging["test_derivative"])
154
# Similarly, create the object associated with the right-hand side data.
158
rhs = NonlinearRHS(eq_rhs, F, u, bcs, mass=eq_lhs)
160
# We need to check if this is the first equation,
161
# so that we can register the appropriate initial conditions.
162
# These equations are necessary so that libadjoint can assemble the
163
# relevant adjoint equations for the adjoint variables associated with
164
# the initial conditions.
165
for coeff, dep in zip(rhs.coefficients(),rhs.dependencies()) + zip(diag_coeffs, diag_deps):
166
# If coeff is not known, it must be an initial condition.
167
if not adjointer.variable_known(dep):
169
if not linear: # don't register initial conditions for the first nonlinear solve.
173
fn_space = coeff.function_space()
174
block_name = "Identity: %s" % str(fn_space)
175
identity_block = libadjoint.Block(block_name)
177
init_rhs=Vector(coeff).duplicate()
178
init_rhs.axpy(1.0,Vector(coeff))
180
def identity_assembly_cb(variables, dependencies, hermitian, coefficient, context):
181
assert coefficient == 1
182
return (Matrix(ufl.Identity(fn_space.dim())), Vector(dolfin.Function(fn_space)))
184
identity_block.assemble=identity_assembly_cb
186
if debugging["record_all"]:
187
adjointer.record_variable(dep, libadjoint.MemoryStorage(Vector(coeff)))
189
initial_eq = libadjoint.Equation(dep, blocks=[identity_block], targets=[dep], rhs=RHS(init_rhs))
190
adjointer.register_equation(initial_eq)
191
assert adjointer.variable_known(dep)
194
# c.f. the discussion above. In the linear case, we want to bump the
195
# timestep number /after/ all of the dependencies' timesteps have been
196
# computed for libadjoint.
198
var = adj_variables.next(u)
200
# With the initial conditions out of the way, let us now define the callbacks that
201
# define the actions of the operator the user has passed in on the lhs of this equation.
203
def diag_assembly_cb(dependencies, values, hermitian, coefficient, context):
204
'''This callback must conform to the libadjoint Python block assembly
205
interface. It returns either the form or its transpose, depending on
206
the value of the logical hermitian.'''
208
assert coefficient == 1
210
value_coeffs=[v.data for v in values]
211
eq_l=dolfin.replace(eq_lhs, dict(zip(diag_coeffs, value_coeffs)))
214
# Homogenise the adjoint boundary conditions. This creates the adjoint
215
# solution associated with the lifted discrete system that is actually solved.
216
adjoint_bcs = [dolfin.homogenize(bc) for bc in eq_bcs if isinstance(bc, dolfin.DirichletBC)]
217
if len(adjoint_bcs) == 0: adjoint_bcs = None
218
return (Matrix(dolfin.adjoint(eq_l), bcs=adjoint_bcs, pc=pc, ksp=ksp), Vector(None, fn_space=u.function_space()))
109
rhs = NonlinearRHS(eq_rhs, F, u, bcs, mass=eq_lhs)
111
# We need to check if this is the first equation,
112
# so that we can register the appropriate initial conditions.
113
# These equations are necessary so that libadjoint can assemble the
114
# relevant adjoint equations for the adjoint variables associated with
115
# the initial conditions.
116
for coeff, dep in zip(rhs.coefficients(),rhs.dependencies()) + zip(diag_coeffs, diag_deps):
117
# If coeff is not known, it must be an initial condition.
118
if not adjointer.variable_known(dep):
120
if not linear: # don't register initial conditions for the first nonlinear solve.
124
fn_space = coeff.function_space()
125
block_name = "Identity: %s" % str(fn_space)
126
identity_block = libadjoint.Block(block_name)
128
init_rhs=Vector(coeff).duplicate()
129
init_rhs.axpy(1.0,Vector(coeff))
131
def identity_assembly_cb(variables, dependencies, hermitian, coefficient, context):
132
assert coefficient == 1
133
return (Matrix(ufl.Identity(fn_space.dim())), Vector(dolfin.Function(fn_space)))
135
identity_block.assemble=identity_assembly_cb
137
if debugging["record_all"]:
138
adjointer.record_variable(dep, libadjoint.MemoryStorage(Vector(coeff)))
140
initial_eq = libadjoint.Equation(dep, blocks=[identity_block], targets=[dep], rhs=RHS(init_rhs))
141
adjointer.register_equation(initial_eq)
142
assert adjointer.variable_known(dep)
145
# c.f. the discussion above. In the linear case, we want to bump the
146
# timestep number /after/ all of the dependencies' timesteps have been
147
# computed for libadjoint.
149
var = adj_variables.next(u)
151
# With the initial conditions out of the way, let us now define the callbacks that
152
# define the actions of the operator the user has passed in on the lhs of this equation.
154
def diag_assembly_cb(dependencies, values, hermitian, coefficient, context):
155
'''This callback must conform to the libadjoint Python block assembly
156
interface. It returns either the form or its transpose, depending on
157
the value of the logical hermitian.'''
159
assert coefficient == 1
161
value_coeffs=[v.data for v in values]
162
eq_l=dolfin.replace(eq_lhs, dict(zip(diag_coeffs, value_coeffs)))
165
# Homogenise the adjoint boundary conditions. This creates the adjoint
166
# solution associated with the lifted discrete system that is actually solved.
167
adjoint_bcs = [dolfin.homogenize(bc) for bc in eq_bcs if isinstance(bc, dolfin.DirichletBC)]
168
if len(adjoint_bcs) == 0: adjoint_bcs = None
169
return (Matrix(dolfin.adjoint(eq_l), bcs=adjoint_bcs), Vector(None, fn_space=u.function_space()))
171
return (Matrix(eq_l, bcs=eq_bcs), Vector(None, fn_space=u.function_space()))
172
diag_block.assemble = diag_assembly_cb
174
def diag_action_cb(dependencies, values, hermitian, coefficient, input, context):
175
value_coeffs = [v.data for v in values]
176
eq_l = dolfin.replace(eq_lhs, dict(zip(diag_coeffs, value_coeffs)))
179
eq_l = dolfin.adjoint(eq_l)
181
output_vec = dolfin.assemble(coefficient * dolfin.action(eq_l, input.data))
182
output_fn = dolfin.Function(input.data.function_space())
183
vec = output_fn.vector()
184
for i in range(len(vec)):
185
vec[i] = output_vec[i]
187
return Vector(output_fn)
188
diag_block.action = diag_action_cb
190
if len(diag_deps) > 0:
191
# If this block is nonlinear (the entries of the matrix on the LHS
192
# depend on any variable previously computed), then that will induce
193
# derivative terms in the adjoint equations. Here, we define the
194
# callback libadjoint will need to compute such terms.
195
def derivative_action(dependencies, values, variable, contraction_vector, hermitian, input, coefficient, context):
196
dolfin_variable = values[dependencies.index(variable)].data
197
dolfin_values = [val.data for val in values]
199
current_form = dolfin.replace(eq_lhs, dict(zip(diag_coeffs, dolfin_values)))
201
deriv = dolfin.derivative(current_form, dolfin_variable)
202
args = ufl.algorithms.extract_arguments(deriv)
203
deriv = dolfin.replace(deriv, {args[1]: contraction_vector.data}) # contract over the middle index
206
deriv = dolfin.adjoint(deriv)
208
action = coefficient * dolfin.action(deriv, input.data)
210
return Vector(action)
211
diag_block.derivative_action = derivative_action
213
eqn = libadjoint.Equation(var, blocks=[diag_block], targets=[var], rhs=rhs)
215
cs = adjointer.register_equation(eqn)
216
if cs == int(libadjoint.constants.adj_constants["ADJ_CHECKPOINT_STORAGE_MEMORY"]):
217
for coeff in adj_variables.coeffs.keys():
218
if adj_variables[coeff] == var: continue
219
adjointer.record_variable(adj_variables[coeff], libadjoint.MemoryStorage(Vector(coeff), cs=True))
221
elif cs == int(libadjoint.constants.adj_constants["ADJ_CHECKPOINT_STORAGE_DISK"]):
222
for coeff in adj_variables.coeffs.keys():
223
if adj_variables[coeff] == var: continue
224
adjointer.record_variable(adj_variables[coeff], libadjoint.DiskStorage(Vector(coeff), cs=True))
220
return (Matrix(eq_l, bcs=eq_bcs, pc=pc, ksp=ksp), Vector(None, fn_space=u.function_space()))
221
diag_block.assemble = diag_assembly_cb
223
def diag_action_cb(dependencies, values, hermitian, coefficient, input, context):
224
value_coeffs = [v.data for v in values]
225
eq_l = dolfin.replace(eq_lhs, dict(zip(diag_coeffs, value_coeffs)))
228
eq_l = dolfin.adjoint(eq_l)
230
output_vec = dolfin.assemble(coefficient * dolfin.action(eq_l, input.data))
231
output_fn = dolfin.Function(input.data.function_space())
232
vec = output_fn.vector()
233
for i in range(len(vec)):
234
vec[i] = output_vec[i]
236
return Vector(output_fn)
237
diag_block.action = diag_action_cb
239
if len(diag_deps) > 0:
240
# If this block is nonlinear (the entries of the matrix on the LHS
241
# depend on any variable previously computed), then that will induce
242
# derivative terms in the adjoint equations. Here, we define the
243
# callback libadjoint will need to compute such terms.
244
def derivative_action(dependencies, values, variable, contraction_vector, hermitian, input, coefficient, context):
245
dolfin_variable = values[dependencies.index(variable)].data
246
dolfin_values = [val.data for val in values]
248
current_form = dolfin.replace(eq_lhs, dict(zip(diag_coeffs, dolfin_values)))
250
deriv = dolfin.derivative(current_form, dolfin_variable)
251
args = ufl.algorithms.extract_arguments(deriv)
252
deriv = dolfin.replace(deriv, {args[1]: contraction_vector.data}) # contract over the middle index
255
deriv = dolfin.adjoint(deriv)
257
action = coefficient * dolfin.action(deriv, input.data)
259
return Vector(action)
260
diag_block.derivative_action = derivative_action
262
eqn = libadjoint.Equation(var, blocks=[diag_block], targets=[var], rhs=rhs)
264
cs = adjointer.register_equation(eqn)
265
if cs == int(libadjoint.constants.adj_constants["ADJ_CHECKPOINT_STORAGE_MEMORY"]):
266
for coeff in adj_variables.coeffs.keys():
267
if adj_variables[coeff] == var: continue
268
adjointer.record_variable(adj_variables[coeff], libadjoint.MemoryStorage(Vector(coeff), cs=True))
270
elif cs == int(libadjoint.constants.adj_constants["ADJ_CHECKPOINT_STORAGE_DISK"]):
271
for coeff in adj_variables.coeffs.keys():
272
if adj_variables[coeff] == var: continue
273
adjointer.record_variable(adj_variables[coeff], libadjoint.DiskStorage(Vector(coeff), cs=True))
274
return linear, newargs
228
276
def solve(*args, **kwargs):
229
277
'''This solve routine comes from the dolfin_adjoint package, and wraps the real Dolfin