Hello,
I am attaching a simple example that uses TSAdjoint to calculate the
sensitivity of a 1 degree of freedom ODE. When using theta methods, it returns
the right sensitivity as given by the analytical solution. When I set
`ts.setThetaEndpoint(True)`, it does not work. Is there a theoretical reason
why? I was using this option because using the options
ts.setType(ts.Type.THETA)
ts.setTheta(0.5)
ts.setThetaEndpoint(True)
is equivalent to using ts.Type.CN
Thanks
Miguel
Miguel A. Salazar de Troya
Postdoctoral Researcher, Lawrence Livermore National Laboratory
B141
Rm: 1085-5
Ph: 1(925) 422-6411
# Testing TSAdjoint and matrix-free Jacobian
import sys, petsc4py
petsc4py.init(sys.argv)
import numpy as np
from petsc4py import PETSc
class VDP(object):
n = 1
comm = PETSc.COMM_SELF
def __init__(self, mu_=1.0, mf_=False):
self.mu_ = mu_
self.mf_ = mf_
self.init_cond = 10.0
if self.mf_:
self.J_ = PETSc.Mat().createDense([self.n, self.n], comm=self.comm)
self.J_.setUp()
self.Jp_ = PETSc.Mat().createDense([self.n, 1], comm=self.comm)
self.Jp_.setUp()
def initialCondition(self, u):
u[0] = self.init_cond
u.assemble()
def evalIFunction(self, ts, t, u, udot, f):
mu = self.mu_
f[0] = udot[0] - mu * u[0]
f.assemble()
def evalIJacobian(self, ts, t, u, udot, sigma, A, B):
if not self.mf_:
J = A
else:
J = self.J_
mu = self.mu_
J[0, 0] = sigma * 1.0 - mu
J.assemble()
if A != B:
B.assemble()
return True # same nonzero pattern
def evalFunction(self, ts, t, u, f):
mu = self.mu_
f[0] = u[0] * mu
f.assemble()
def evalJacobian(self, ts, t, u, A, B):
if not self.mf_:
J = A
else:
J = self.J_
mu = self.mu_
J[0, 0] = mu
J.assemble()
if A != B:
B.assemble()
return True # same nonzero pattern
def evalJacobianP(self, ts, t, u, C):
if not self.mf_:
Jp = C
else:
Jp = self.Jp_
Jp[0, 0] = u[0]
Jp.assemble()
return True
def integrand(self, ts, t, u, f):
f[0] = u[0] * self.mu_ * self.mu_
f.assemble()
def formRHSintegrandJacobian(self, ts, t, u, J, P):
J[0, 0] = 1.0 * self.mu_ * self.mu_
J.assemble()
return True
def formRHSintegrandJacobianP(self, ts, t, u, J):
J[0, 0] = u[0] * self.mu_ * 2.0
J.assemble()
return True
class JacShell:
def __init__(self, ode):
self.ode_ = ode
def mult(self, A, x, y):
"y <- A * x"
self.ode_.J_.mult(x, y)
def multTranspose(self, A, x, y):
"y <- A' * x"
self.ode_.J_.multTranspose(x, y)
class JacPShell:
def __init__(self, ode):
self.ode_ = ode
def multTranspose(self, A, x, y):
"y <- A' * x"
self.ode_.Jp_.multTranspose(x, y)
OptDB = PETSc.Options()
mu_ = OptDB.getScalar("mu", 2.0e0)
mf_ = OptDB.getBool("mf", False)
ode = VDP(mu_, mf_)
if not mf_:
J = PETSc.Mat().createDense([ode.n, ode.n], comm=ode.comm)
J.setUp()
Jp = PETSc.Mat().createDense([ode.n, 1], comm=ode.comm)
Jp.setUp()
else:
J = PETSc.Mat().create()
J.setSizes([ode.n, ode.n])
J.setType("python")
shell = JacShell(ode)
J.setPythonContext(shell)
J.setUp()
J.assemble()
Jp = PETSc.Mat().create()
Jp.setSizes([ode.n, 1])
Jp.setType("python")
shell = JacPShell(ode)
Jp.setPythonContext(shell)
Jp.setUp()
Jp.assemble()
u = PETSc.Vec().createSeq(ode.n, comm=ode.comm)
f = u.duplicate()
adj_u = PETSc.Vec().createSeq(ode.n, comm=ode.comm)
adj_p = PETSc.Vec().createSeq(1, comm=ode.comm)
ts = PETSc.TS().create(comm=ode.comm)
ts.setType(ts.Type.THETA)
ts.setTheta(0.5) # adjoint for 1.0 (backward Euler) is currently broken in PETSc
#ts.setThetaEndpoint(True)
ts.setProblemType(False.ProblemType.LINEAR)
ts.setProblemType(ts.EquationType.IMPLICIT)
ts.setIFunction(ode.evalIFunction, f)
ts.setIJacobian(ode.evalIJacobian, J)
ts.setRHSJacobianP(ode.evalJacobianP, Jp)
integral = True
if integral:
ts.createQuadratureTS()
tsq = ts.getQuadratureTS()[1]
Jint = J.duplicate()
Jintp = J.duplicate()
tsq.setRHSFunction(ode.integrand)
tsq.setRHSJacobian(ode.formRHSintegrandJacobian, Jint)
tsq.setRHSJacobianP(ode.formRHSintegrandJacobianP, Jintp)
ts.setSaveTrajectory()
ts.setTime(0.0)
ts.setTimeStep(0.001)
final_T = 1.0
ts.setMaxTime(final_T)
ts.setMaxSteps(1000)
ts.setExactFinalTime(PETSc.TS.ExactFinalTime.MATCHSTEP)
ts.setFromOptions()
ode.initialCondition(u)
ts.solve(u)
print("Cost function")
a, b = ode.init_cond, ode.mu_
if integral:
ts.getCostIntegral().view()
print("Exact value: {0:.5f}".format(a * b * (np.exp(b * final_T) - 1.0)))
else:
print(u.array[0])
print("Exact value: {0:.5f}".format(a * (np.exp(b * final_T))))
# I don't understand why I need to define it as 1.0 for the sensitivities of the final state to work
# The reason is because of the way PETSc distingushes between time integrals
# and just scalars functions of the final state
if integral:
adj_u[0] = 0.0
else:
adj_u[0] = 1.0
adj_u.assemble()
adj_p[0] = 0
adj_p.assemble()
ts.setCostGradients(adj_u, adj_p)
ts.adjointSolve()
# Real sensitivity of the final solution
if integral:
sensitivity_a = 1.0 * b * (-1.0 + np.exp(b * final_T))
sensitivity_b = a * b * final_T * np.exp(b * final_T) + a * (np.exp(b * final_T) - 1)
else:
sensitivity_a = np.exp(b * final_T)
sensitivity_b = a * final_T * np.exp(b * final_T)
print(" ")
print("Numerical sensitivity w.r.t. a")
print(adj_u.array[0])
print("Real sensitivity w.r.t a")
print(sensitivity_a)
print(" ")
print("Numerical sensitivity w.r.t. b")
if integral:
print(adj_p.array[0])
else:
print(adj_p.array[0])
print("Real sensitivity w.r.t b")
print(sensitivity_b)