optFuncs.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 
4  DAFoam : Discrete Adjoint with OpenFOAM
5  Version : v3
6 
7  Description:
8  Common functions for DAFoam optimization setup.
9 
10 """
11 
12 
13 # =============================================================================
14 # Imports
15 # =============================================================================
16 import time
17 import sys
18 import numpy as np
19 import warnings
20 import copy
21 from petsc4py import PETSc
22 
23 warnings.filterwarnings("once")
24 np.set_printoptions(precision=16, suppress=True)
25 
26 
28  """
29  Update the design surface and run the primal solver to get objective function values.
30  """
31 
32  Info("\n")
33  Info("+--------------------------------------------------------------------------+")
34  Info("| Evaluating Objective Functions %03d |" % DASolver.nSolvePrimals)
35  Info("+--------------------------------------------------------------------------+")
36  Info("Design Variables: ")
37  Info(xDV)
38 
39  a = time.time()
40 
41  # Setup an empty dictionary for the evaluated function values
42  funcs = {}
43 
44  # Set the current design variables in the DV object
45  DVGeo.setDesignVars(xDV)
46  DASolver.setDesignVars(xDV)
47 
48  # Evaluate the geometric constraints and add them to the funcs dictionary
49  DVCon.evalFunctions(funcs)
50 
51  # Solve the CFD problem
52  DASolver()
53 
54  # Populate the required values from the CFD problem
55  DASolver.evalFunctions(funcs, evalFuncs=evalFuncs)
56 
57  b = time.time()
58 
59  # Print the current solution to the screen
60  Info("Objective Functions: ")
61  Info(funcs)
62  Info("Flow Runtime: %g" % (b - a))
63 
64  fail = funcs["fail"]
65 
66  return funcs, fail
67 
68 
70  """
71  Update the design surface and run the primal solver to get objective function values.
72  This is the multipoint version of calcObjFuncValues
73  """
74 
75  Info("\n")
76  Info("+--------------------------------------------------------------------------+")
77  Info("| Evaluating Objective Functions %03d |" % DASolver.nSolvePrimals)
78  Info("+--------------------------------------------------------------------------+")
79  Info("Design Variables: ")
80  Info(xDV)
81 
82  a = time.time()
83 
84  fail = False
85 
86  # Setup an empty dictionary for the evaluated function values
87  funcsMP = {}
88 
89  # Set the current design variables in the DV object
90  DVGeo.setDesignVars(xDV)
91  DASolver.setDesignVars(xDV)
92 
93  nMultiPoints = DASolver.getOption("nMultiPoints")
94 
95  for i in range(nMultiPoints):
96 
97  Info("--Solving Primal for Configuration %d--" % i)
98 
99  funcs = {}
100 
101  # Evaluate the geometric constraints and add them to the funcs dictionary
102  DVCon.evalFunctions(funcs)
103 
104  # set the multi point condition provided by users in the
105  # runScript.py script. This function should define what
106  # conditions to change for each case i
107  setMultiPointCondition(xDV, i)
108 
109  # Solve the CFD problem
110  DASolver()
111 
112  # Populate the required values from the CFD problem
113  DASolver.evalFunctions(funcs, evalFuncs=evalFuncs)
114 
115  # save the state vector for case i and will be used in solveAdjoint
116  DASolver.saveMultiPointField(i)
117 
118  # if any of the multipoint primal fails, return fail=True
119  if funcs["fail"] is True:
120  fail = True
121 
122  if DASolver.getOption("debug"):
123  Info("Objective Functions for Configuration %d: " % i)
124  Info(funcs)
125 
126  # assign funcs to funcsMP
127  setMultiPointObjFuncs(funcs, funcsMP, i)
128 
129  funcsMP["fail"] = fail
130 
131  Info("Objective Functions MultiPoint: ")
132  Info(funcsMP)
133 
134  b = time.time()
135  Info("Flow Runtime: %g" % (b - a))
136 
137  return funcsMP, fail
138 
139 
141  """
142  Update the design surface and run the primal solver to get objective function values.
143  This is the unsteady adjoint version of calcObjFuncValues
144  """
145 
146  Info("\n")
147  Info("+--------------------------------------------------------------------------+")
148  Info("| Evaluating Objective Functions %03d |" % DASolver.nSolvePrimals)
149  Info("+--------------------------------------------------------------------------+")
150  Info("Design Variables: ")
151  Info(xDV)
152 
153  a = time.time()
154 
155  # Setup an empty dictionary for the evaluated function values
156  funcs = {}
157 
158  # Set the current design variables in the DV object
159  DVGeo.setDesignVars(xDV)
160  DASolver.setDesignVars(xDV)
161 
162  # Evaluate the geometric constraints and add them to the funcs dictionary
163  DVCon.evalFunctions(funcs)
164 
165  # Solve the CFD problem
166  DASolver()
167 
168  # Set values for the unsteady adjoint objectives. This function needs to be
169  # implemented in run scripts
170  setObjFuncsUnsteady(DASolver, funcs, evalFuncs)
171 
172  # assign state lists to mats
173  DASolver.setTimeInstanceVar(mode="list2Mat")
174 
175  b = time.time()
176 
177  # Print the current solution to the screen
178  Info("Objective Functions: ")
179  Info(funcs)
180  Info("Flow Runtime: %g" % (b - a))
181 
182  fail = funcs["fail"]
183 
184  return funcs, fail
185 
186 
187 def calcObjFuncSens(xDV, funcs):
188  """
189  Run the adjoint solver and get objective function sensitivities.
190  """
191 
192  Info("\n")
193  Info("+--------------------------------------------------------------------------+")
194  Info("| Evaluating Objective Function Sensitivities %03d |" % DASolver.nSolveAdjoints)
195  Info("+--------------------------------------------------------------------------+")
196 
197  a = time.time()
198 
199  # write the design variable values to file
200  DASolver.writeDesignVariable("designVariableHist.txt", xDV)
201 
202  # write the deform FFDs
203  DASolver.writeDeformedFFDs()
204 
205  # Setup an empty dictionary for the evaluated derivative values
206  funcsSens = {}
207 
208  # Evaluate the geometric constraint derivatives
209  DVCon.evalFunctionsSens(funcsSens)
210 
211  # Solve the adjoint
212  DASolver.solveAdjoint()
213 
214  # Evaluate the CFD derivatives
215  DASolver.evalFunctionsSens(funcsSens, evalFuncs=evalFuncs)
216 
217  b = time.time()
218 
219  # Print the current solution to the screen
220  with np.printoptions(precision=16, threshold=5, suppress=True):
221  Info("Objective Function Sensitivity: ")
222  Info(funcsSens)
223  Info("Adjoint Runtime: %g s" % (b - a))
224 
225  # write the sensitivity values to file
226  DASolver.writeTotalDeriv("totalDerivHist.txt", funcsSens, evalFuncs)
227 
228  fail = funcsSens["fail"]
229 
230  return funcsSens, fail
231 
232 
233 def calcObjFuncSensMP(xDV, funcs):
234  """
235  Run the adjoint solver and get objective function sensitivities.
236  This is the multipoint version of calcObjFuncSens
237  """
238 
239  Info("\n")
240  Info("+--------------------------------------------------------------------------+")
241  Info("| Evaluating Objective Function Sensitivities %03d |" % DASolver.nSolveAdjoints)
242  Info("+--------------------------------------------------------------------------+")
243 
244  fail = False
245 
246  a = time.time()
247 
248  # write the design variable values to file
249  DASolver.writeDesignVariable("designVariableHist.txt", xDV)
250 
251  # write the deform FFDs
252  DASolver.writeDeformedFFDs()
253 
254  # Setup an empty dictionary for the evaluated derivative values
255  funcsSensMP = {}
256 
257  nMultiPoints = DASolver.getOption("nMultiPoints")
258 
259  for i in range(nMultiPoints):
260 
261  Info("--Solving Adjoint for Configuration %d--" % i)
262 
263  funcsSens = {}
264 
265  # Evaluate the geometric constraint derivatives
266  DVCon.evalFunctionsSens(funcsSens)
267 
268  # set the state vector for case i
269  DASolver.setMultiPointField(i)
270 
271  # set the multi point condition provided by users in the
272  # runScript.py script. This function should define what
273  # conditions to change for each case i
274  setMultiPointCondition(xDV, i)
275 
276  # Solve the adjoint
277  DASolver.solveAdjoint()
278 
279  # Evaluate the CFD derivatives
280  DASolver.evalFunctionsSens(funcsSens, evalFuncs=evalFuncs)
281 
282  if funcsSens["fail"] is True:
283  fail = True
284 
285  if DASolver.getOption("debug"):
286  with np.printoptions(precision=16, threshold=5, suppress=True):
287  Info("Objective Function Sensitivity: ")
288  Info(funcsSens)
289 
290  # assign funcs to funcsMP
291  setMultiPointObjFuncsSens(xDV, funcs, funcsSens, funcsSensMP, i)
292 
293  funcsSensMP["fail"] = fail
294 
295  # Print the current solution to the screen
296  with np.printoptions(precision=16, threshold=5, suppress=True):
297  Info("Objective Function Sensitivity MultiPoiint: ")
298  Info(funcsSensMP)
299 
300  b = time.time()
301  Info("Adjoint Runtime: %g s" % (b - a))
302 
303  return funcsSensMP, fail
304 
305 
306 def calcObjFuncSensUnsteady(xDV, funcs):
307  """
308  Run the adjoint solver and get objective function sensitivities.
309  This is the unsteady adjoint version of calcObjFuncSens
310  """
311 
312  Info("\n")
313  Info("+--------------------------------------------------------------------------+")
314  Info("| Evaluating Objective Function Sensitivities %03d |" % DASolver.nSolveAdjoints)
315  Info("+--------------------------------------------------------------------------+")
316 
317  fail = False
318 
319  a = time.time()
320 
321  # write the design variable values to file
322  DASolver.writeDesignVariable("designVariableHist.txt", xDV)
323 
324  # write the deform FFDs
325  DASolver.writeDeformedFFDs()
326 
327  # assign the state mats to lists
328  DASolver.setTimeInstanceVar(mode="mat2List")
329 
330  # Setup an empty dictionary for the evaluated derivative values
331  funcsSensCombined = {}
332 
333  funcsSensAllInstances = []
334 
335  mode = DASolver.getOption("unsteadyAdjoint")["mode"]
336  nTimeInstances = DASolver.getOption("unsteadyAdjoint")["nTimeInstances"]
337  if mode == "hybridAdjoint":
338  iEnd = -1
339  elif mode == "timeAccurateAdjoint":
340  iEnd = 0
341 
342  # NOTE: calling calcRes here is critical because it will setup the correct
343  # old time levels for setTimeInstanceField. Otherwise, the residual for the
344  # first adjoint time instance will be incorrect because the residuals have
345  # not been computed and the old time levels will be zeros for all variables,
346  # this will create issues for the setTimeInstanceField call (nOldTimes)
347  DASolver.calcPrimalResidualStatistics("calc")
348 
349  # set these vectors zeros
350  DASolver.zeroTimeAccurateAdjointVectors()
351 
352  for i in range(nTimeInstances - 1, iEnd, -1):
353 
354  Info("--Solving Adjoint for Time Instance %d--" % i)
355 
356  funcsSens = {}
357 
358  # Evaluate the geometric constraint derivatives
359  DVCon.evalFunctionsSens(funcsSens)
360 
361  # set the state vector for case i
362  DASolver.setTimeInstanceField(i)
363 
364  # Solve the adjoint
365  DASolver.solveAdjoint()
366 
367  # Evaluate the CFD derivatives
368  DASolver.evalFunctionsSens(funcsSens, evalFuncs=evalFuncs)
369 
370  if funcsSens["fail"] is True:
371  fail = True
372 
373  if DASolver.getOption("debug"):
374  with np.printoptions(precision=16, threshold=5, suppress=True):
375  Info("Objective Function Sensitivity: ")
376  Info(funcsSens)
377 
378  funcsSensAllInstances.append(funcsSens)
379 
380  setObjFuncsSensUnsteady(DASolver, funcs, funcsSensAllInstances, funcsSensCombined)
381 
382  funcsSensCombined["fail"] = fail
383 
384  # Print the current solution to the screen
385  with np.printoptions(precision=16, threshold=5, suppress=True):
386  Info("Objective Function Sensitivity Unsteady Adjoint: ")
387  Info(funcsSensCombined)
388 
389  b = time.time()
390  Info("Adjoint Runtime: %g s" % (b - a))
391 
392  return funcsSensCombined, fail
393 
394 
395 def runPrimal(objFun=calcObjFuncValues):
396  """
397  Just run the primal
398  """
399 
400  xDV = DVGeo.getValues()
401  funcs = {}
402  funcs, fail = objFun(xDV)
403 
404  return funcs, fail
405 
406 
407 def runAdjoint(objFun=calcObjFuncValues, sensFun=calcObjFuncSens, fileName=None):
408  """
409  Just run the primal and adjoint
410  """
411 
412  DASolver.runColoring()
413  xDV = DVGeo.getValues()
414  funcs = {}
415  funcs, fail = objFun(xDV)
416  funcsSens = {}
417  funcsSens, fail = sensFun(xDV, funcs)
418 
419  # Optionally, we can write the sensitivity to a file if fileName is provided
420  if fileName is not None:
421  if gcomm.rank == 0:
422  fOut = open(fileName, "w")
423  for funcName in evalFuncs:
424  for shapeVar in xDV:
425  fOut.write(funcName + " " + shapeVar + "\n")
426  try:
427  nDVs = len(funcsSens[funcName][shapeVar])
428  except Exception:
429  nDVs = 1
430  for n in range(nDVs):
431  line = str(funcsSens[funcName][shapeVar][n]) + "\n"
432  fOut.write(line)
433  fOut.flush()
434  fOut.close()
435 
436  return funcsSens, fail
437 
438 
439 def runForwardAD(dvName="None", seedIndex=-1):
440  """
441  Run the forward mode AD for the primal solver to compute the brute force total
442  derivative. This is primarily used in verification of the adjoint accuracy
443  """
444 
445  if not DASolver.getOption("useAD")["mode"] == "forward":
446  Info("runForwardAD only supports useAD->mode=forward!")
447  Info("Please set useAD->mode to forward and rerun!")
448  exit(1)
449  DASolver.setOption("useAD", {"dvName": dvName, "seedIndex": seedIndex})
450  DASolver.updateDAOption()
451  DASolver()
452 
453 
454 def solveCL(CL_star, alphaName, liftName, objFun=calcObjFuncValues, eps=1e-2, tol=1e-4, maxit=10):
455  """
456  Adjust the angle of attack or pitch to match the target lift.
457  This is usually needed for wing aerodynamic optimization
458  """
459 
460  Info("\n")
461  Info("+--------------------------------------------------------------------------+")
462  Info("| Running SolveCL to find alpha that matches target CL |")
463  Info("+--------------------------------------------------------------------------+")
464  Info("eps: %g tol: %g maxit: %g" % (eps, tol, maxit))
465 
466  xDVs = DVGeo.getValues()
467  alpha = xDVs[alphaName]
468 
469  for i in range(maxit):
470  # Solve the CFD problem
471  xDVs[alphaName] = alpha
472  funcs = {}
473  funcs, fail = objFun(xDVs)
474  CL0 = funcs[liftName]
475  Info("alpha: %f, CL: %f" % (alpha.real, CL0))
476  if abs(CL0 - CL_star) / CL_star < tol:
477  Info("Completed! alpha = %f" % alpha.real)
478  return alpha.real
479  # compute sens
480  alphaVal = alpha + eps
481  xDVs[alphaName] = alphaVal
482  funcsP = {}
483  funcsP, fail = objFun(xDVs)
484  CLP = funcsP[liftName]
485  deltaAlpha = (CL_star - CL0) * eps / (CLP - CL0)
486  alpha += deltaAlpha
487 
488  return alpha.real
489 
490 
491 def calcFDSens(objFun=calcObjFuncValues, fileName=None):
492  """
493  Compute finite-difference sensitivity
494  """
495 
496  xDV = DVGeo.getValues()
497 
498  # gradFD
499  deltaX = DASolver.getOption("adjPartDerivFDStep")["FFD"]
500  # initialize gradFD
501  gradFD = {}
502  for funcName in evalFuncs:
503  gradFD[funcName] = {}
504  for shapeVar in xDV:
505  gradFD[funcName][shapeVar] = np.zeros(len(xDV[shapeVar]))
506  if gcomm.rank == 0:
507  print("-------FD----------", deltaX, flush=True)
508 
509  for shapeVar in xDV:
510  try:
511  nDVs = len(xDV[shapeVar])
512  except Exception:
513  nDVs = 1
514  for i in range(nDVs):
515  funcp = {}
516  funcm = {}
517  xDV[shapeVar][i] += deltaX
518  funcp, fail = objFun(xDV)
519  xDV[shapeVar][i] -= 2.0 * deltaX
520  funcm, fail = objFun(xDV)
521  xDV[shapeVar][i] += deltaX
522  for funcName in evalFuncs:
523  gradFD[funcName][shapeVar][i] = (funcp[funcName] - funcm[funcName]) / (2.0 * deltaX)
524  Info(gradFD)
525  # write FD results
526  if fileName is not None:
527  if gcomm.rank == 0:
528  fOut = open(fileName, "w")
529  fOut.write("DeltaX: " + str(deltaX) + "\n")
530  for funcName in evalFuncs:
531  for shapeVar in xDV:
532  fOut.write(funcName + " " + shapeVar + "\n")
533  try:
534  nDVs = len(gradFD[funcName][shapeVar])
535  except Exception:
536  nDVs = 1
537  for n in range(nDVs):
538  line = str(gradFD[funcName][shapeVar][n]) + "\n"
539  fOut.write(line)
540  fOut.flush()
541  fOut.close()
542 
543 
545 
546  if DASolver.getOption("runLowOrderPrimal4PC")["active"]:
547  DASolver.setOption("runLowOrderPrimal4PC", {"isPC": True})
548  DASolver.updateDAOption()
549  DASolver()
550  DASolver.dRdWTPC = PETSc.Mat().create(PETSc.COMM_WORLD)
551  DASolver.solver.calcdRdWT(DASolver.xvVec, DASolver.wVec, 1, DASolver.dRdWTPC)
552  DASolver.setOption("runLowOrderPrimal4PC", {"isPC": False})
553  DASolver.updateDAOption()
554 
555 
556 class Info(object):
557  """
558  Print information and flush to screen for parallel cases
559  """
560 
561  def __init__(self, message):
562  if gcomm.rank == 0:
563  print(message, flush=True)
564  gcomm.Barrier()
dafoam.optFuncs.calcObjFuncSensUnsteady
def calcObjFuncSensUnsteady(xDV, funcs)
Definition: optFuncs.py:306
dafoam.optFuncs.runLowOrderPrimal4PC
def runLowOrderPrimal4PC()
Definition: optFuncs.py:544
dafoam.optFuncs.runForwardAD
def runForwardAD(dvName="None", seedIndex=-1)
Definition: optFuncs.py:439
dafoam.optFuncs.calcObjFuncValuesUnsteady
def calcObjFuncValuesUnsteady(xDV)
Definition: optFuncs.py:140
dafoam.optFuncs.calcObjFuncValues
def calcObjFuncValues(xDV)
Definition: optFuncs.py:27
dafoam.optFuncs.runPrimal
def runPrimal(objFun=calcObjFuncValues)
Definition: optFuncs.py:395
dafoam.optFuncs.Info.__init__
def __init__(self, message)
Definition: optFuncs.py:561
dafoam.optFuncs.calcObjFuncSens
def calcObjFuncSens(xDV, funcs)
Definition: optFuncs.py:187
dafoam.optFuncs.calcObjFuncSensMP
def calcObjFuncSensMP(xDV, funcs)
Definition: optFuncs.py:233
dafoam.optFuncs.runAdjoint
def runAdjoint(objFun=calcObjFuncValues, sensFun=calcObjFuncSens, fileName=None)
Definition: optFuncs.py:407
dafoam.optFuncs.calcObjFuncValuesMP
def calcObjFuncValuesMP(xDV)
Definition: optFuncs.py:69
dafoam.optFuncs.solveCL
def solveCL(CL_star, alphaName, liftName, objFun=calcObjFuncValues, eps=1e-2, tol=1e-4, maxit=10)
Definition: optFuncs.py:454
dafoam.optFuncs.Info
Definition: optFuncs.py:556
dafoam.optFuncs.calcFDSens
def calcFDSens(objFun=calcObjFuncValues, fileName=None)
Definition: optFuncs.py:491