5 DAFoam : Discrete Adjoint with OpenFOAM
9 The Python interface to DAFoam. It controls the adjoint
10 solvers and external modules for design optimization
22 from mpi4py
import MPI
23 from collections
import OrderedDict
25 from petsc4py
import PETSc
27 petsc4py.init(sys.argv)
29 import tensorflow
as tf
36 Define a set of options to use in PYDAFOAM and set their initial values.
37 This class will be used by PYDAFOAM._getDefOptions()
39 NOTE: Give an initial value for a new option, this help PYDAFOAM determine the type of this
40 option. If it is a list, give at least one default value. If it is a dict, you can leave it
41 blank, e.g., {}. Also, use ## to add comments before the new option such that these comments
42 will be picked up by Doxygen. If possible, give examples.
44 NOTE: We group these options into three categories.
45 - The basic options are those options that will be used for EVERY solvers and EVERY cases.
46 - The intermediate options are options that will be used in some of solvers for special
47 situation (e.g., primalVarBounds to prevent solution from divergence).
48 - The advanced options will be used in special situation to improve performance, e.g.,
49 maxResConLv4JacPCMat to reduce memory of preconditioner. Its usage is highly case dependent.
50 It may also include options that have a default value that is rarely changed, except for
51 very special situation.
316 "propMovement":
False,
317 "couplingSurfaceGroups": {
318 "wingGroup": [
"wing",
"wing_te"],
323 "couplingSurfaceGroups": {
324 "wallGroup": [
"fin_wall"],
330 "couplingSurfaceGroups": {
331 "blade1Group": [
"blade1_ps",
"blade1_ss"],
332 "blade2Group": [
"blade2"],
407 "p_rghMax": 500000.0,
428 "ReThetatMin": 1e-16,
430 "gammaIntMin": 1e-16,
483 self.
useAD = {
"mode":
"reverse",
"dvName":
"None",
"seedIndex": -9999}
534 "jacMatReOrdering":
"rcm",
536 "gmresMaxIters": 1000,
537 "gmresRestart": 1000,
538 "gmresRelTol": 1.0e-6,
539 "gmresAbsTol": 1.0e-14,
540 "gmresTolDiff": 1.0e2,
541 "useNonZeroInitGuess":
False,
546 "fpMinResTolDiff": 1.0e2,
548 "dynAdjustTol":
True,
596 "simpleCoeffs": {
"n": [2, 2, 1],
"delta": 0.001},
597 "preservePatches": [
"None"],
598 "singleProcessorFaceSets": [
"None"],
610 "maxAspectRatio": 1000.0,
613 "maxIncorrectlyOrientedFaces": 0,
649 "test_propeller_default": {
651 "nForceSections": 10,
652 "axis": [1.0, 0.0, 0.0],
655 "interpScheme":
"Poly4Gauss",
666 "modelName":
"model",
676 Main class for pyDAFoam
681 comm : mpi4py communicator
682 An optional argument to pass in an external communicator.
685 The list of options to use with pyDAFoam.
691 Initialize class members
694 assert not os.getenv(
"WM_PROJECT")
is None,
"$WM_PROJECT not found. Please source OpenFOAM-v1812/etc/bashrc"
699 Info(
"-------------------------------------------------------------------------------")
701 Info(
"-------------------------------------------------------------------------------")
732 if self.
getOption(
"printPYDAFOAMOptions"):
780 if "ALL_OPENFOAM_WALL_PATCHES" in self.
getOption(
"designSurfaces"):
790 couplingInfo = self.
getOption(
"couplingInfo")
792 if couplingInfo[
"aerostructural"][
"active"]:
797 elif couplingInfo[
"aeroacoustic"][
"active"]:
798 for groupName
in couplingInfo[
"aeroacoustic"][
"couplingSurfaceGroups"]:
799 self.
addFamilyGroup(groupName, couplingInfo[
"aeroacoustic"][
"couplingSurfaceGroups"][groupName])
800 elif couplingInfo[
"aerothermal"][
"active"]:
844 if self.
getOption(
"tensorflow")[
"active"]:
845 TensorFlowHelper.options = self.
getOption(
"tensorflow")
846 TensorFlowHelper.initialize()
848 self.
solver.initTensorFlowFuncs(TensorFlowHelper.predict, TensorFlowHelper.calcJacVecProd)
849 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
850 self.
solverAD.initTensorFlowFuncs(TensorFlowHelper.predict, TensorFlowHelper.calcJacVecProd)
852 Info(
"pyDAFoam initialization done!")
856 def _solverRegistry(self):
858 Register solver names and set their types. For a new solver, first identify its
859 type and add their names to the following dict
863 "Incompressible": [
"DASimpleFoam",
"DASimpleTFoam",
"DAPisoFoam",
"DAPimpleFoam",
"DAPimpleDyMFoam"],
864 "Compressible": [
"DARhoSimpleFoam",
"DARhoSimpleCFoam",
"DATurboFoam"],
865 "Solid": [
"DASolidDisplacementFoam",
"DALaplacianFoam",
"DAHeatTransferFoam",
"DAScalarTransportFoam"],
876 if self.
DVGeo is not None:
889 Info(
"Updating DVGeo PointSet....")
901 Info(
"Warping the volume mesh....")
904 xvNew = self.
mesh.getSolverGrid()
909 if self.
getOption(
"useAD")[
"mode"] ==
"forward":
910 dvName = self.
getOption(
"useAD")[
"dvName"]
911 dvType = self.
getOption(
"designVar")[dvName][
"designVarType"]
923 def _getDefOptions(self):
925 Setup default options
931 All the DAFoam options.
940 for key
in vars(daOption):
941 value = getattr(daOption, key)
942 defOpts[key] = [type(value), value]
946 def _initializeAdjVectors(self):
948 Initialize the adjoint vector dict
954 A dict that contains adjoint vectors, stored in Petsc format
962 for objFuncName
in objFuncDict:
964 psi = PETSc.Vec().create(PETSc.COMM_WORLD)
965 psi.setSizes((wSize, PETSc.DECIDE), bsize=1)
968 adjVectors[objFuncName] = psi
972 def _initializeAdjTotalDeriv(self):
974 Initialize the adjoint total derivative dict
975 NOTE: this function need to be called after initializing self.objFuncNames4Adj
981 An empty dict that contains total derivative of objective function with respect design variables
984 designVarDict = self.
getOption(
"designVar")
988 for objFuncName
in objFuncDict:
990 adjTotalDeriv[objFuncName] = {}
991 for designVarName
in designVarDict:
992 adjTotalDeriv[objFuncName][designVarName] =
None
996 def _initializeTimeAccurateAdjointVectors(self):
998 Initialize the dRdWTPsi vectors for time accurate adjoint.
999 Here we need to initialize current time step and two previous
1000 time steps 0 and 00 for both state and residuals. This is
1001 because the backward ddt scheme depends on U, U0, and U00
1003 if self.
getOption(
"unsteadyAdjoint")[
"mode"] ==
"timeAccurateAdjoint":
1012 for objFuncName
in objFuncDict:
1014 vecA = PETSc.Vec().create(PETSc.COMM_WORLD)
1015 vecA.setSizes((wSize, PETSc.DECIDE), bsize=1)
1016 vecA.setFromOptions()
1020 vecB = vecA.duplicate()
1024 vecC = vecA.duplicate()
1028 vecD = vecA.duplicate()
1032 vecE = vecA.duplicate()
1036 vecF = vecA.duplicate()
1041 if self.
getOption(
"unsteadyAdjoint")[
"mode"] ==
"timeAccurateAdjoint":
1043 for objFuncName
in objFuncDict:
1045 self.
dRdW0TPsi[objFuncName].zeroEntries()
1052 def _calcObjFuncNames4Adj(self):
1054 Compute the objective function names for which we solve the adjoint equation
1059 objFuncNames4Adj : list
1060 A list of objective function names we will solve the adjoint for
1065 for objFuncName
in objFuncDict:
1066 objFuncSubDict = objFuncDict[objFuncName]
1067 for objFuncPart
in objFuncSubDict:
1068 objFuncSubDictPart = objFuncSubDict[objFuncPart]
1069 if objFuncSubDictPart[
"addToAdjoint"]
is True:
1070 if objFuncName
not in objFuncList:
1071 objFuncList.append(objFuncName)
1072 elif objFuncSubDictPart[
"addToAdjoint"]
is False:
1075 raise Error(
"addToAdjoint can be either True or False")
1078 def _checkOptions(self):
1080 Check if the combination of options are valid.
1081 NOTE: we should add all possible checks here!
1084 if not self.
getOption(
"useAD")[
"mode"]
in [
"fd",
"reverse",
"forward"]:
1085 raise Error(
"useAD->mode only supports fd, reverse, or forward!")
1088 if self.
getOption(
"unsteadyAdjoint")[
"mode"] ==
"timeAccurateAdjoint":
1089 if not self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
1090 raise Error(
"timeAccurateAdjoint only supports useAD->mode=forward|reverse")
1092 if "NONE" not in self.
getOption(
"writeSensMap"):
1093 if not self.
getOption(
"useAD")[
"mode"]
in [
"reverse"]:
1094 raise Error(
"writeSensMap is only compatible with useAD->mode=reverse")
1096 if self.
getOption(
"runLowOrderPrimal4PC")[
"active"]:
1097 self.
setOption(
"runLowOrderPrimal4PC", {
"active":
True,
"isPC":
False})
1099 if self.
getOption(
"adjEqnSolMethod") ==
"fixedPoint":
1101 if self.
comm.rank == 0:
1102 print(
"Fixed-point adjoint mode detected. Unset normalizeStates and normalizeResiduals...")
1105 if len(self.
getOption(
"normalizeStates")) > 0:
1106 raise Error(
"Please do not set any normalizeStates for the fixed-point adjoint!")
1108 self.
setOption(
"normalizeResiduals", [
"None"])
1110 if self.
getOption(
"discipline")
not in [
"aero",
"thermal"]:
1111 raise Error(
"discipline: %s not supported. Options are: aero or thermal" % self.
getOption(
"discipline"))
1114 for coupling
in self.
getOption(
"couplingInfo"):
1115 if self.
getOption(
"couplingInfo")[coupling][
"active"]:
1118 raise Error(
"Only one coupling scenario can be active, while %i found" % nActivated)
1120 nAerothermalSurfaces = len(self.
getOption(
"couplingInfo")[
"aerothermal"][
"couplingSurfaceGroups"].keys())
1121 if nAerothermalSurfaces > 1:
1123 "Only one couplingSurfaceGroups is supported for aerothermal, while %i found" % nAerothermalSurfaces
1126 nAeroStructSurfaces = len(self.
getOption(
"couplingInfo")[
"aerostructural"][
"couplingSurfaceGroups"].keys())
1127 if nAeroStructSurfaces > 1:
1129 "Only one couplingSurfaceGroups is supported for aerostructural, while %i found" % nAeroStructSurfaces
1136 Save the state variable vector to self.wVecMPList
1139 Istart, Iend = self.
wVec.getOwnershipRange()
1140 for i
in range(Istart, Iend):
1150 Set the state variable vector based on self.wVecMPList
1153 Istart, Iend = self.
wVec.getOwnershipRange()
1154 for i
in range(Istart, Iend):
1157 self.
wVec.assemblyBegin()
1158 self.
wVec.assemblyEnd()
1163 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
1170 Set the OpenFOAM state variables based on instance index
1173 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
1178 solver.setTimeInstanceField(instanceI)
1181 solver.ofField2StateVec(self.
wVec)
1188 nLocalAdjointBoundaryStates = self.
solver.getNLocalAdjointBoundaryStates()
1189 nTimeInstances = -99999
1190 adjMode = self.
getOption(
"unsteadyAdjoint")[
"mode"]
1191 if adjMode ==
"hybridAdjoint" or adjMode ==
"timeAccurateAdjoint":
1192 nTimeInstances = self.
getOption(
"unsteadyAdjoint")[
"nTimeInstances"]
1195 self.
stateMat.setSizes(((nLocalAdjointStates,
None), (
None, nTimeInstances)))
1197 self.
stateMat.setPreallocationNNZ((nTimeInstances, nTimeInstances))
1201 self.
stateBCMat.setSizes(((nLocalAdjointBoundaryStates,
None), (
None, nTimeInstances)))
1203 self.
stateBCMat.setPreallocationNNZ((nTimeInstances, nTimeInstances))
1206 self.
timeVec = PETSc.Vec().createSeq(nTimeInstances, bsize=1, comm=PETSc.COMM_SELF)
1207 self.
timeIdxVec = PETSc.Vec().createSeq(nTimeInstances, bsize=1, comm=PETSc.COMM_SELF)
1211 if mode ==
"list2Mat":
1213 elif mode ==
"mat2List":
1214 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
1223 raise Error(
"mode can only be either mat2List or list2Mat!")
1227 Write the design variable history to files in the json format
1230 if self.
comm.rank == 0:
1232 f = open(fileName,
"w")
1234 f = open(fileName,
"a")
1236 f.write(
'\n"Optimization_Iteration_%03d":\n' % self.
nSolveAdjoints)
1240 for dvName
in sorted(xDV):
1241 f.write(
' "%s": ' % dvName)
1243 nDVs = len(xDV[dvName])
1245 for i
in range(nDVs):
1247 f.write(
"%20.15e, " % xDV[dvName][i])
1249 f.write(
"%20.15e " % xDV[dvName][i])
1252 f.write(
" %20.15e" % xDV[dvName])
1254 dvNameCounter = dvNameCounter + 1
1255 if dvNameCounter < nDVNames:
1264 Write the deformed FFDs to the disk during optimization
1267 if self.
comm.rank == 0:
1268 print(
"writeDeformedFFDs is deprecated since v3.0.1!")
1271 if self.getOption("writeDeformedFFDs"):
1273 self.DVGeo.writeTecplot("deformedFFD.dat", self.nSolveAdjoints)
1275 self.DVGeo.writeTecplot("deformedFFD.dat", counter)
1280 Write the total derivatives history to files in the json format
1281 This will only write total derivative for evalFuncs
1284 if self.
comm.rank == 0:
1286 f = open(fileName,
"w")
1288 f = open(fileName,
"a")
1290 f.write(
'\n"Optimization_Iteration_%03d":\n' % (self.
nSolveAdjoints - 1))
1292 nFuncNames = len(evalFuncs)
1294 for funcName
in sorted(evalFuncs):
1295 f.write(
' "%s": \n {\n' % funcName)
1296 nDVNames = len(sens[funcName])
1298 for dvName
in sorted(sens[funcName]):
1299 f.write(
' "%s": ' % dvName)
1301 nDVs = len(sens[funcName][dvName])
1303 for i
in range(nDVs):
1305 f.write(
"%20.15e, " % sens[funcName][dvName][i])
1307 f.write(
"%20.15e " % sens[funcName][dvName][i])
1310 f.write(
" %20.15e" % sens[funcName][dvName])
1312 dvNameCounter = dvNameCounter + 1
1313 if dvNameCounter < nDVNames:
1319 funcNameCounter = funcNameCounter + 1
1320 if funcNameCounter < nFuncNames:
1329 Return the value of objective function at the given time instance and name
1336 Return the derivative value computed by forward mode AD primal solution
1342 Evaluate the desired functions given in iterable object,
1343 'evalFuncs' and add them to the dictionary 'funcs'. The keys
1344 in the funcs dictionary will be have an _<ap.name> appended to
1345 them. Additionally, information regarding whether or not the
1346 last analysis with the solvePrimal was successful is
1347 included. This information is included as "funcs['fail']". If
1348 the 'fail' entry already exits in the dictionary the following
1349 operation is performed:
1351 funcs['fail'] = funcs['fail'] or <did this problem fail>
1353 In other words, if any one problem fails, the funcs['fail']
1354 entry will be False. This information can then be used
1355 directly in the pyOptSparse.
1360 Dictionary into which the functions are saved.
1362 evalFuncs : iterable object containing strings
1363 If not None, use these functions to evaluate.
1365 ignoreMissing : bool
1366 Flag to suppress checking for a valid function. Please use
1367 this option with caution.
1373 >>> CFDsolver.evalFunctions(funcs, ['CD', 'CL'])
1375 >>> # Result will look like:
1376 >>> # {'CD':0.501, 'CL':0.02750}
1379 for funcName
in evalFuncs:
1382 raise Error(
"Primal solution failed for the baseline design!")
1390 objFuncValue = self.
solver.getObjFuncValue(funcName.encode())
1391 funcs[funcName] = objFuncValue
1396 funcs[
"fail"] =
True
1398 funcs[
"fail"] =
False
1404 Evaluate the sensitivity of the desired functions given in
1405 iterable object,'evalFuncs' and add them to the dictionary
1411 Dictionary into which the function derivatives are saved.
1413 evalFuncs : iterable object containing strings
1414 The functions the user wants the derivatives of
1419 >>> CFDsolver.evalFunctionsSens(funcSens, ['CD', 'CL'])
1422 if self.
DVGeo is None:
1423 raise Error(
"DVGeo not set!")
1425 dvs = self.
DVGeo.getValues()
1427 for funcName
in evalFuncs:
1428 funcsSens[funcName] = {}
1430 nDVs = len(dvs[dvName])
1431 funcsSens[funcName][dvName] = np.zeros(nDVs, self.
dtype)
1432 for i
in range(nDVs):
1433 funcsSens[funcName][dvName][i] = self.
adjTotalDeriv[funcName][dvName][i]
1436 funcsSens[
"fail"] =
True
1438 funcsSens[
"fail"] =
False
1444 Set the DVGeometry object that will manipulate 'geometry' in
1445 this object. Note that <SOLVER> does not **strictly** need a
1446 DVGeometry object, but if optimization with geometric
1447 changes is desired, then it is required.
1450 dvGeo : A DVGeometry object.
1451 Object responsible for manipulating the constraints that
1452 this object is responsible for.
1455 >>> CFDsolver = <SOLVER>(comm=comm, options=CFDoptions)
1456 >>> CFDsolver.setDVGeo(DVGeo)
1463 Add a custom grouping of families called groupName. The groupName
1464 must be distinct from the existing families. All families must
1465 in the 'families' list must be present in the mesh file.
1469 User-supplied custom name for the family groupings
1471 List of string. Family names to combine into the family group
1477 "The specified groupName '%s' already exists in the mesh file or has already been added." % groupName
1483 for fam
in families:
1486 "The specified family '%s' for group '%s', does "
1487 "not exist in the mesh file or has "
1488 "not already been added. The current list of "
1489 "families (original and grouped) is: %s" % (fam, groupName, repr(self.
families.keys()))
1497 self.
families[groupName] = sorted(np.unique(indices))
1501 Set the mesh object to the aero_solver to do geometric deformations
1504 mesh : MBMesh or USMesh object
1505 The mesh object for doing the warping
1513 self.
mesh.setExternalMeshIndices(meshInd)
1518 self.
mesh.setSurfaceDefinition(pts, conn, faceSizes)
1522 for funcName
in objFuncs:
1523 for funcPart
in objFuncs[funcName]:
1524 if objFuncs[funcName][funcPart][
"addToAdjoint"]
is True:
1525 if funcName
not in evalFuncs:
1526 evalFuncs.append(funcName)
1531 Return the connectivity of the coordinates at which the forces (or tractions) are
1532 defined. This is the complement of getForces() which returns
1533 the forces at the locations returned in this routine.
1538 Group identifier to get only forces cooresponding to the
1539 desired group. The group must be a family or a user-supplied
1540 group of families. The default is None which corresponds to
1541 all wall-type surfaces.
1544 if groupName
is None:
1558 bc = self.boundaries[name]
1559 nPts = len(bc[
"indicesRed"])
1562 nFace = len(bc[
"facesRed"])
1567 for iFace
in range(nFace):
1568 face = copy.copy(bc[
"facesRed"][iFace])
1569 for i
in range(len(face)):
1570 face[i] += pointOffset
1572 faceSizes.append(len(face))
1576 return conn, faceSizes
1580 This function returns a trianguled verision of the surface
1581 mesh on all processors. The intent is to use this for doing
1582 constraints in DVConstraints.
1586 List of points and vectors describing the surface. This may
1587 be passed directly to DVConstraint setSurface() function.
1590 if groupName
is None:
1597 conn = np.array(conn).flatten()
1598 conn = self.
comm.allgather(conn)
1599 faceSizes = self.
comm.allgather(faceSizes)
1607 for iProc
in range(len(faceSizes)):
1610 for iFace
in range(len(faceSizes[iProc])):
1612 faceSize = faceSizes[iProc][iFace]
1613 faceNodes = conn[iProc][connCounter : connCounter + faceSize]
1617 for i
in range(faceSize):
1620 ptSum += pts[iProc][idx]
1622 avgPt = ptSum / faceSize
1627 for i
in range(faceSize):
1630 v1.append(pts[iProc][idx] - avgPt)
1631 if i < (faceSize - 1):
1632 idxp1 = faceNodes[i + 1]
1633 v2.append(pts[iProc][idxp1] - avgPt)
1637 v2.append(pts[iProc][idx0] - avgPt)
1640 connCounter += faceSize
1646 Print a nicely formatted dictionary of the family names
1652 Set the internal design variables.
1653 At the moment we don't have any internal DVs to set.
1659 def _initializeOptions(self, options):
1661 Initialize the options passed into pyDAFoam
1666 options : dictionary
1667 The list of options to use with pyDAFoam.
1672 raise Error(
"The 'options' keyword argument must be passed pyDAFoam.")
1687 "key %s has wrong format! \
1688 Example: {'iters' : [int, 1]}"
1698 def _initializeComm(self, comm):
1700 Initialize MPI COMM and setup parallel flags
1705 comm = MPI.COMM_WORLD
1709 nProc = self.
comm.size
1725 def _writeOFCaseFiles(self):
1731 Save the field sensitivity dObjFunc/dDesignVar map to disk.
1737 Name of the objective function
1739 Name of the design variable
1740 solutionTime : float
1741 The solution time where the sensitivity will be save
1743 The type of the field, either scalar or vector
1745 The Petsc vector that contains the sensitivity
1748 workingDir = os.getcwd()
1750 sensDir =
"processor%d/%.8f/" % (self.rank, solutionTime)
1752 sensDir =
"%.8f/" % solutionTime
1754 sensDir = os.path.join(workingDir, sensDir)
1757 Istart, Iend = sensVec.getOwnershipRange()
1758 for idxI
in range(Istart, Iend):
1759 sensList.append(sensVec[idxI])
1762 if not os.path.isfile(os.path.join(sensDir,
"sens_%s_%s" % (objFuncName, designVarName))):
1763 fSens = open(os.path.join(sensDir,
"sens_%s_%s" % (objFuncName, designVarName)),
"w")
1764 if fieldType ==
"scalar":
1765 self._writeOpenFoamHeader(fSens,
"volScalarField", sensDir,
"sens_%s_%s" % (objFuncName, designVarName))
1766 fSens.write(
"dimensions [0 0 0 0 0 0 0];\n")
1767 fSens.write(
"internalField nonuniform List<scalar>\n")
1768 fSens.write(
"%d\n" % len(sensList))
1770 for i
in range(len(sensList)):
1771 fSens.write(
"%g\n" % sensList[i])
1774 elif fieldType ==
"vector":
1775 self._writeOpenFoamHeader(fSens,
"volVectorField", sensDir,
"sens_%s_%s" % (objFuncName, designVarName))
1776 fSens.write(
"dimensions [0 0 0 0 0 0 0];\n")
1777 fSens.write(
"internalField nonuniform List<vector>\n")
1778 fSens.write(
"%d\n" % len(sensList) / 3)
1781 for i
in range(len(sensList) / 3):
1784 fSens.write(
"%g " % sensList[counterI])
1785 counterI = counterI + 1
1790 raise Error(
"fieldType %s not valid! Options are: scalar or vector" % fieldType)
1792 fSens.write(
"boundaryField\n")
1794 fSens.write(
' "(.*)"\n')
1796 fSens.write(
" type zeroGradient;\n")
1803 Save the sensitivity dObjFunc/dXs map to disk. where Xs is the wall surface mesh coordinate
1809 Name of the objective function
1811 Name of the design variable
1812 solutionTime : float
1813 The solution time where the sensitivity will be save
1816 dFdXs = self.
mesh.getdXs()
1821 conn = np.array(conn).flatten()
1823 workingDir = os.getcwd()
1825 meshDir =
"processor%d/%.11f/polyMesh/" % (self.
rank, solutionTime)
1826 sensDir =
"processor%d/%.11f/" % (self.
rank, solutionTime)
1828 meshDir =
"%.11f/polyMesh/" % solutionTime
1829 sensDir =
"%.11f/" % solutionTime
1831 meshDir = os.path.join(workingDir, meshDir)
1832 sensDir = os.path.join(workingDir, sensDir)
1834 if not os.path.isdir(sensDir):
1838 raise Error(
"Can not make a directory at %s" % sensDir)
1839 if not os.path.isdir(meshDir):
1843 raise Error(
"Can not make a directory at %s" % meshDir)
1846 if not os.path.isfile(os.path.join(meshDir,
"points")):
1847 fPoints = open(os.path.join(meshDir,
"points"),
"w")
1849 fPoints.write(
"%d\n" % len(pts))
1850 fPoints.write(
"(\n")
1851 for i
in range(len(pts)):
1852 fPoints.write(
"(%g %g %g)\n" % (float(pts[i][0]), float(pts[i][1]), float(pts[i][2])))
1853 fPoints.write(
")\n")
1857 if not os.path.isfile(os.path.join(meshDir,
"faces")):
1858 fFaces = open(os.path.join(meshDir,
"faces"),
"w")
1861 fFaces.write(
"%d\n" % len(faceSizes))
1863 for i
in range(len(faceSizes)):
1864 fFaces.write(
"%d(" % faceSizes[i])
1865 for j
in range(faceSizes[i]):
1866 fFaces.write(
" %d " % conn[counterI])
1873 if not os.path.isfile(os.path.join(meshDir,
"owner")):
1874 fOwner = open(os.path.join(meshDir,
"owner"),
"w")
1876 fOwner.write(
"%d\n" % len(faceSizes))
1878 for i
in range(len(faceSizes)):
1884 if not os.path.isfile(os.path.join(meshDir,
"neighbour")):
1885 fNeighbour = open(os.path.join(meshDir,
"neighbour"),
"w")
1887 fNeighbour.write(
"%d\n" % len(faceSizes))
1888 fNeighbour.write(
"(\n")
1889 for i
in range(len(faceSizes)):
1890 fNeighbour.write(
"0\n")
1891 fNeighbour.write(
")\n")
1895 if not os.path.isfile(os.path.join(meshDir,
"boundary")):
1896 fBoundary = open(os.path.join(meshDir,
"boundary"),
"w")
1898 fBoundary.write(
"1\n")
1899 fBoundary.write(
"(\n")
1900 fBoundary.write(
" allWalls\n")
1901 fBoundary.write(
" {\n")
1902 fBoundary.write(
" type wall;\n")
1903 fBoundary.write(
" nFaces %d;\n" % len(faceSizes))
1904 fBoundary.write(
" startFace 0;\n")
1905 fBoundary.write(
" }\n")
1906 fBoundary.write(
")\n")
1910 if not os.path.isfile(os.path.join(sensDir,
"sens_%s_%s" % (objFuncName, designVarName))):
1911 fSens = open(os.path.join(sensDir,
"sens_%s_%s" % (objFuncName, designVarName)),
"w")
1912 self.
_writeOpenFoamHeader(fSens,
"volVectorField", sensDir,
"sens_%s_%s" % (objFuncName, designVarName))
1913 fSens.write(
"dimensions [0 0 0 0 0 0 0];\n")
1914 fSens.write(
"internalField uniform (0 0 0);\n")
1917 fSens.write(
"boundaryField\n")
1919 fSens.write(
" allWalls\n")
1921 fSens.write(
" type wall;\n")
1922 fSens.write(
" value nonuniform List<vector>\n")
1923 fSens.write(
"%d\n" % len(faceSizes))
1926 for i
in range(len(faceSizes)):
1930 for j
in range(faceSizes[i]):
1931 idxI = conn[counterI]
1932 sensXMean += dFdXs[idxI][0]
1933 sensYMean += dFdXs[idxI][1]
1934 sensZMean += dFdXs[idxI][2]
1936 sensXMean /= faceSizes[i]
1937 sensYMean /= faceSizes[i]
1938 sensZMean /= faceSizes[i]
1939 fSens.write(
"(%f %f %f)\n" % (sensXMean, sensYMean, sensZMean))
1948 Write Petsc vectors or matrices
1951 Info(
"Saving %s to disk...." % name)
1953 viewer = PETSc.Viewer().createASCII(name +
".dat", mode=
"w", comm=PETSc.COMM_WORLD)
1954 viewer.pushFormat(1)
1956 elif mode ==
"Binary":
1957 viewer = PETSc.Viewer().createBinary(name +
".bin", mode=
"w", comm=PETSc.COMM_WORLD)
1960 raise Error(
"mode not valid! Options are: ASCII or Binary")
1964 Read Petsc vectors or matrices
1967 Info(
"Reading %s from disk...." % name)
1968 viewer = PETSc.Viewer().createBinary(name +
".bin", comm=PETSc.COMM_WORLD)
1973 Run primal solver to compute state variables and objectives
1977 xvVec: vector that contains all the mesh point coordinates
1981 wVec: vector that contains all the state variables
1983 self.primalFail: if the primal solution fails, assigns 1, otherwise 0
1991 if self.
getOption(
"useAD")[
"mode"] ==
"forward":
1996 if self.
getOption(
"writeMinorIterations"):
2006 Run adjoint solver to compute the adjoint vector psiVec
2010 xvVec: vector that contains all the mesh point coordinates
2012 wVec: vector that contains all the state variables
2016 self.adjTotalDeriv: the dict contains all the total derivative vectors
2018 self.adjointFail: if the adjoint solution fails, assigns 1, otherwise 0
2023 Info("Saving the xvVec and wVec vectors to disk....")
2025 viewerXv = PETSc.Viewer().createBinary("xvVec_%03d.bin" % self.nSolveAdjoints, mode="w", comm=PETSc.COMM_WORLD)
2026 viewerXv(self.xvVec)
2027 viewerW = PETSc.Viewer().createBinary("wVec_%03d.bin" % self.nSolveAdjoints, mode="w", comm=PETSc.COMM_WORLD)
2031 if self.
getOption(
"useAD")[
"mode"] ==
"forward":
2032 raise Error(
"solveAdjoint only supports useAD->mode=reverse|fd")
2034 if not self.
getOption(
"writeMinorIterations"):
2039 self.
setOption(
"runStatus",
"solveAdjoint")
2044 if self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2050 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2051 dRdWT = PETSc.Mat().create(PETSc.COMM_WORLD)
2053 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2058 if not self.
getOption(
"runLowOrderPrimal4PC")[
"active"]:
2061 self.
dRdWTPC = PETSc.Mat().create(PETSc.COMM_WORLD)
2065 ksp = PETSc.KSP().create(PETSc.COMM_WORLD)
2066 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2068 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2074 for objFuncName
in objFuncDict:
2076 dFdW = PETSc.Vec().create(PETSc.COMM_WORLD)
2077 dFdW.setSizes((wSize, PETSc.DECIDE), bsize=1)
2078 dFdW.setFromOptions()
2079 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2080 self.
solver.calcdFdW(self.
xvVec, self.
wVec, objFuncName.encode(), dFdW)
2081 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2085 if self.
getOption(
"unsteadyAdjoint")[
"mode"] ==
"timeAccurateAdjoint":
2091 dFdW.axpy(-1.0, self.
dR0dW0TPsi[objFuncName])
2095 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2097 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2100 if self.
getOption(
"unsteadyAdjoint")[
"mode"] ==
"timeAccurateAdjoint":
2107 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2109 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2110 self.
solverAD.destroydRdWTMatrixFree()
2115 Info(
"Computing total derivatives....")
2117 designVarDict = self.
getOption(
"designVar")
2118 for designVarName
in designVarDict:
2119 Info(
"Computing total derivatives for %s" % designVarName)
2121 if designVarDict[designVarName][
"designVarType"] ==
"BC":
2122 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2125 dRdBC = PETSc.Mat().create(PETSc.COMM_WORLD)
2126 self.
solver.calcdRdBC(self.
xvVec, self.
wVec, designVarName.encode(), dRdBC)
2128 for objFuncName
in objFuncDict:
2131 dFdBC = PETSc.Vec().create(PETSc.COMM_WORLD)
2132 dFdBC.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2133 dFdBC.setFromOptions()
2135 self.
xvVec, self.
wVec, objFuncName.encode(), designVarName.encode(), dFdBC
2138 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2139 totalDeriv.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2140 totalDeriv.setFromOptions()
2145 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2146 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2147 for i
in range(nDVs):
2148 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2150 totalDeriv.destroy()
2151 totalDerivSeq.destroy()
2154 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2157 for objFuncName
in objFuncDict:
2160 dFdBC = PETSc.Vec().create(PETSc.COMM_WORLD)
2161 dFdBC.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2162 dFdBC.setFromOptions()
2164 self.
xvVec, self.
wVec, objFuncName.encode(), designVarName.encode(), dFdBC
2167 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2168 totalDeriv.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2169 totalDeriv.setFromOptions()
2174 totalDeriv.scale(-1.0)
2175 totalDeriv.axpy(1.0, dFdBC)
2179 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2180 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2181 for i
in range(nDVs):
2182 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2184 totalDeriv.destroy()
2185 totalDerivSeq.destroy()
2188 elif designVarDict[designVarName][
"designVarType"] ==
"AOA":
2189 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2192 dRdAOA = PETSc.Mat().create(PETSc.COMM_WORLD)
2193 self.
solver.calcdRdAOA(self.
xvVec, self.
wVec, designVarName.encode(), dRdAOA)
2195 for objFuncName
in objFuncDict:
2198 dFdAOA = PETSc.Vec().create(PETSc.COMM_WORLD)
2199 dFdAOA.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2200 dFdAOA.setFromOptions()
2202 self.
xvVec, self.
wVec, objFuncName.encode(), designVarName.encode(), dFdAOA
2205 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2206 totalDeriv.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2207 totalDeriv.setFromOptions()
2212 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2213 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2214 for i
in range(nDVs):
2215 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2217 totalDeriv.destroy()
2218 totalDerivSeq.destroy()
2221 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2224 for objFuncName
in objFuncDict:
2227 dFdAOA = PETSc.Vec().create(PETSc.COMM_WORLD)
2228 dFdAOA.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2229 dFdAOA.setFromOptions()
2232 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2233 totalDeriv.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2234 totalDeriv.setFromOptions()
2239 totalDeriv.scale(-1.0)
2240 totalDeriv.axpy(1.0, dFdAOA)
2244 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2245 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2246 for i
in range(nDVs):
2247 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2249 totalDeriv.destroy()
2250 totalDerivSeq.destroy()
2253 elif designVarDict[designVarName][
"designVarType"] ==
"FFD":
2254 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2257 dRdFFD = PETSc.Mat().create(PETSc.COMM_WORLD)
2258 self.
solver.calcdRdFFD(self.
xvVec, self.
wVec, designVarName.encode(), dRdFFD)
2260 for objFuncName
in objFuncDict:
2263 dFdFFD = PETSc.Vec().create(PETSc.COMM_WORLD)
2264 dFdFFD.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2265 dFdFFD.setFromOptions()
2267 self.
xvVec, self.
wVec, objFuncName.encode(), designVarName.encode(), dFdFFD
2270 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2271 totalDeriv.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2272 totalDeriv.setFromOptions()
2277 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2278 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2279 for i
in range(nDVs):
2280 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2281 totalDeriv.destroy()
2282 totalDerivSeq.destroy()
2285 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2287 nDVs = len(self.
DVGeo.getValues()[designVarName])
2290 xvSize = len(self.
xv) * 3
2291 for objFuncName
in objFuncDict:
2294 dFdXv = PETSc.Vec().create(PETSc.COMM_WORLD)
2295 dFdXv.setSizes((xvSize, PETSc.DECIDE), bsize=1)
2296 dFdXv.setFromOptions()
2298 self.
xvVec, self.
wVec, objFuncName.encode(), designVarName.encode(), dFdXv
2302 totalDerivXv = PETSc.Vec().create(PETSc.COMM_WORLD)
2303 totalDerivXv.setSizes((xvSize, PETSc.DECIDE), bsize=1)
2304 totalDerivXv.setFromOptions()
2310 totalDerivXv.scale(-1.0)
2311 totalDerivXv.axpy(1.0, dFdXv)
2318 self.
writePetscVecMat(
"dFdXvTotalDeriv_%s" % objFuncName, totalDerivXv,
"ASCII")
2320 if self.
DVGeo is not None and self.
DVGeo.getNDV() > 0:
2322 if designVarName
in self.
getOption(
"writeSensMap"):
2325 sensSolTime = float(solutionTime) / 1000.0
2330 for i
in range(nDVs):
2331 self.
adjTotalDeriv[objFuncName][designVarName][i] = dFdFFD[designVarName][0][i]
2333 totalDerivXv.destroy()
2336 elif designVarDict[designVarName][
"designVarType"]
in [
"ACTL",
"ACTP",
"ACTD"]:
2337 if self.
getOption(
"useAD")[
"mode"] ==
"fd":
2338 designVarType = designVarDict[designVarName][
"designVarType"]
2339 nDVTable = {
"ACTP": 9,
"ACTD": 10,
"ACTL": 11}
2340 nDVs = nDVTable[designVarType]
2342 dRdACT = PETSc.Mat().create(PETSc.COMM_WORLD)
2344 self.
xvVec, self.
wVec, designVarName.encode(), designVarType.encode(), dRdACT
2347 for objFuncName
in objFuncDict:
2350 dFdACT = PETSc.Vec().create(PETSc.COMM_WORLD)
2351 dFdACT.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2352 dFdACT.setFromOptions()
2356 objFuncName.encode(),
2357 designVarName.encode(),
2358 designVarType.encode(),
2362 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2363 totalDeriv.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2364 totalDeriv.setFromOptions()
2369 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2370 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2371 for i
in range(nDVs):
2372 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2373 totalDeriv.destroy()
2374 totalDerivSeq.destroy()
2377 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2378 designVarType = designVarDict[designVarName][
"designVarType"]
2379 nDVTable = {
"ACTP": 9,
"ACTD": 10,
"ACTL": 11}
2380 nDVs = nDVTable[designVarType]
2382 for objFuncName
in objFuncDict:
2385 dFdACT = PETSc.Vec().create(PETSc.COMM_WORLD)
2386 dFdACT.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2387 dFdACT.setFromOptions()
2389 self.
xvVec, self.
wVec, objFuncName.encode(), designVarName.encode(), dFdACT
2392 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2393 totalDeriv.setSizes((PETSc.DECIDE, nDVs), bsize=1)
2394 totalDeriv.setFromOptions()
2401 totalDeriv.scale(-1.0)
2402 totalDeriv.axpy(1.0, dFdACT)
2407 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2408 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2409 for i
in range(nDVs):
2410 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2411 totalDeriv.destroy()
2412 totalDerivSeq.destroy()
2414 elif designVarDict[designVarName][
"designVarType"] ==
"Field":
2415 if self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2417 xDV = self.
DVGeo.getValues()
2418 nDVs = len(xDV[designVarName])
2419 fieldType = designVarDict[designVarName][
"fieldType"]
2420 if fieldType ==
"scalar":
2422 elif fieldType ==
"vector":
2424 nLocalCells = self.
solver.getNLocalCells()
2427 for objFuncName
in objFuncDict:
2431 dFdField = PETSc.Vec().create(PETSc.COMM_WORLD)
2432 dFdField.setSizes((fieldComp * nLocalCells, PETSc.DECIDE), bsize=1)
2433 dFdField.setFromOptions()
2435 self.
xvVec, self.
wVec, objFuncName.encode(), designVarName.encode(), dFdField
2439 totalDeriv = PETSc.Vec().create(PETSc.COMM_WORLD)
2440 totalDeriv.setSizes((fieldComp * nLocalCells, PETSc.DECIDE), bsize=1)
2441 totalDeriv.setFromOptions()
2448 totalDeriv.scale(-1.0)
2449 totalDeriv.axpy(1.0, dFdField)
2452 if "dFdFieldTotalDeriv" in self.
getOption(
"writeJacobians")
or "all" in self.
getOption(
2456 self.
writePetscVecMat(
"dFdFieldTotalDeriv_%s" % objFuncName, totalDeriv,
"ASCII")
2459 if designVarName
in self.
getOption(
"writeSensMap"):
2463 objFuncName, designVarName, float(solutionTime), fieldType, totalDeriv
2469 totalDerivSeq = PETSc.Vec().createSeq(nDVs, bsize=1, comm=PETSc.COMM_SELF)
2470 self.
solver.convertMPIVec2SeqVec(totalDeriv, totalDerivSeq)
2471 for i
in range(nDVs):
2472 self.
adjTotalDeriv[objFuncName][designVarName][i] = totalDerivSeq[i]
2473 totalDeriv.destroy()
2474 totalDerivSeq.destroy()
2477 raise Error(
"For Field design variable type, we only support useAD->mode=reverse")
2479 raise Error(
"designVarType %s not supported!" % designVarDict[designVarName][
"designVarType"])
2491 Map the Xv derivative (volume derivative) to the FFD derivatives (design variables)
2492 Essentially, we first map the Xv (volume) to Xs (surface) using IDWarp, then, we
2493 further map Xs (surface) to FFD using pyGeo
2497 totalDerivXv: total derivative dFdXv vector
2501 dFdFFD: the mapped total derivative with respect to FFD variables
2504 xvSize = len(self.
xv) * 3
2506 dFdXvTotalArray = np.zeros(xvSize, self.
dtype)
2508 Istart, Iend = totalDerivXv.getOwnershipRange()
2510 for idxI
in range(Istart, Iend):
2511 idxRel = idxI - Istart
2512 dFdXvTotalArray[idxRel] = totalDerivXv[idxI]
2514 self.
mesh.warpDeriv(dFdXvTotalArray)
2515 dFdXs = self.
mesh.getdXs()
2517 dFdFFD = self.
DVGeo.totalSensitivity(dFdXs, ptSetName=self.
ptSetName, comm=self.
comm)
2523 Return the forces on this processor on the families defined by groupName.
2527 Group identifier to get only forces cooresponding to the
2528 desired group. The group must be a family or a user-supplied
2529 group of families. The default is None which corresponds to
2534 forces : array (N,3)
2535 Forces on this processor. Note that N may be 0, and an
2536 empty array of shape (0, 3) can be returned.
2538 Info(
"Computing surface forces")
2540 if groupName
is None:
2544 fX = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2545 fX.setSizes((nPts, PETSc.DECIDE), bsize=1)
2548 fY = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2549 fY.setSizes((nPts, PETSc.DECIDE), bsize=1)
2552 fZ = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2553 fZ.setSizes((nPts, PETSc.DECIDE), bsize=1)
2560 forces = np.zeros((nPts, 3))
2561 forces[:, 0] = np.copy(fX.getArray())
2562 forces[:, 1] = np.copy(fY.getArray())
2563 forces[:, 2] = np.copy(fZ.getArray())
2571 fXSum = np.sum(forces[:, 0])
2572 fYSum = np.sum(forces[:, 1])
2573 fZSum = np.sum(forces[:, 2])
2575 fXTot = self.
comm.allreduce(fXSum, op=MPI.SUM)
2576 fYTot = self.
comm.allreduce(fYSum, op=MPI.SUM)
2577 fZTot = self.
comm.allreduce(fZSum, op=MPI.SUM)
2579 Info(
"Total force:")
2580 Info(
"Fx = %e" % fXTot)
2581 Info(
"Fy = %e" % fYTot)
2582 Info(
"Fz = %e" % fZTot)
2589 Return the acoustic data on this processor.
2593 Group identifier to get only data cooresponding to the
2594 desired group. The group must be a family or a user-supplied
2595 group of families. The default is None which corresponds to
2600 position : array (N,3)
2601 Face positions on this processor. Note that N may be 0, and an
2602 empty array of shape (0, 3) can be returned.
2603 normal : array (N,3)
2604 Face normals on this processor. Note that N may be 0, and an
2605 empty array of shape (0, 3) can be returned.
2607 Face areas on this processor. Note that N may be 0, and an
2608 empty array of shape (0) can be returned.
2609 forces : array (N,3)
2610 Face forces on this processor. Note that N may be 0, and an
2611 empty array of shape (0, 3) can be returned.
2613 Info(
"Computing surface acoustic data")
2615 if groupName
is None:
2616 raise ValueError(
"Aeroacoustic grouName not set!")
2619 x = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2620 x.setSizes((nCls, PETSc.DECIDE), bsize=1)
2623 y = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2624 y.setSizes((nCls, PETSc.DECIDE), bsize=1)
2627 z = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2628 z.setSizes((nCls, PETSc.DECIDE), bsize=1)
2631 nX = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2632 nX.setSizes((nCls, PETSc.DECIDE), bsize=1)
2635 nY = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2636 nY.setSizes((nCls, PETSc.DECIDE), bsize=1)
2639 nZ = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2640 nZ.setSizes((nCls, PETSc.DECIDE), bsize=1)
2643 a = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2644 a.setSizes((nCls, PETSc.DECIDE), bsize=1)
2647 fX = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2648 fX.setSizes((nCls, PETSc.DECIDE), bsize=1)
2651 fY = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2652 fY.setSizes((nCls, PETSc.DECIDE), bsize=1)
2655 fZ = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2656 fZ.setSizes((nCls, PETSc.DECIDE), bsize=1)
2660 self.
solver.
getAcousticData(x, y, z, nX, nY, nZ, a, fX, fY, fZ, groupName.encode())
2663 positions = np.zeros((nCls, 3))
2664 positions[:, 0] = np.copy(x.getArray())
2665 positions[:, 1] = np.copy(y.getArray())
2666 positions[:, 2] = np.copy(z.getArray())
2667 normals = np.zeros((nCls, 3))
2668 normals[:, 0] = np.copy(nX.getArray())
2669 normals[:, 1] = np.copy(nY.getArray())
2670 normals[:, 2] = np.copy(nZ.getArray())
2671 areas = np.zeros(nCls)
2672 areas[:] = np.copy(a.getArray())
2673 forces = np.zeros((nCls, 3))
2674 forces[:, 0] = np.copy(fX.getArray())
2675 forces[:, 1] = np.copy(fY.getArray())
2676 forces[:, 2] = np.copy(fZ.getArray())
2691 return positions, normals, areas, forces
2695 Compute total derivative
2703 totalDeriv = dFdX - [dRdX]^T * psi
2706 dRdX.multTranspose(psi, totalDeriv)
2707 totalDeriv.scale(-1.0)
2708 totalDeriv.axpy(1.0, dFdX)
2712 This function computes partials derivatives dFdAlpha with alpha being the angle of attack (AOA)
2713 We use the analytical method:
2714 CD = Fx * cos(alpha) + Fy * sin(alpha)
2715 CL = - Fx * sin(alpha) + Fy * cos(alpha)
2717 dCD/dAlpha = - Fx * sin(alpha) + Fy * cos(alpha) = CL
2718 dCL/dAlpha = - Fx * cos(alpha) - Fy * sin(alpha) = - CD
2719 NOTE: we need to convert the unit from radian to degree
2727 for objFuncPart
in objFuncDict[objFuncName]:
2728 if objFuncDict[objFuncName][objFuncPart][
"type"] ==
"force":
2730 if objFuncDict[objFuncName][objFuncPart][
"directionMode"] ==
"fixedDirection":
2731 raise Error(
"AOA derivative does not support directionMode=fixedDirection!")
2732 elif objFuncDict[objFuncName][objFuncPart][
"directionMode"] ==
"parallelToFlow":
2733 neededMode =
"normalToFlow"
2735 elif objFuncDict[objFuncName][objFuncPart][
"directionMode"] ==
"normalToFlow":
2736 neededMode =
"parallelToFlow"
2739 raise Error(
"directionMode not valid!")
2745 for objFuncNameNeeded
in objFuncDict:
2746 for objFuncPart
in objFuncDict[objFuncNameNeeded]:
2747 if objFuncDict[objFuncNameNeeded][objFuncPart][
"type"] ==
"force":
2748 if objFuncDict[objFuncNameNeeded][objFuncPart][
"directionMode"] == neededMode:
2749 val = self.
solver.getObjFuncValue(objFuncNameNeeded.encode())
2750 if neededMode ==
"parallelToFlow":
2751 dFdAOA[0] = -val * np.pi / 180.0
2752 elif neededMode ==
"normalToFlow":
2753 dFdAOA[0] = val * np.pi / 180.0
2754 dFdAOA.assemblyBegin()
2755 dFdAOA.assemblyEnd()
2758 dFdAOA.zeroEntries()
2760 def _initSolver(self):
2762 Initialize the solvers. This needs to be called before calling any runs
2766 raise Error(
"pyDAFoam: self._initSolver has been called! One shouldn't initialize solvers twice!")
2768 solverName = self.
getOption(
"solverName")
2769 solverArg = solverName +
" -python " + self.
parallelFlag
2772 from .pyDASolverIncompressible
import pyDASolvers
2776 if self.
getOption(
"useAD")[
"mode"] ==
"forward":
2778 from .pyDASolverIncompressibleADF
import pyDASolvers
as pyDASolversAD
2782 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2784 from .pyDASolverIncompressibleADR
import pyDASolvers
as pyDASolversAD
2790 from .pyDASolverCompressible
import pyDASolvers
2794 if self.
getOption(
"useAD")[
"mode"] ==
"forward":
2796 from .pyDASolverCompressibleADF
import pyDASolvers
as pyDASolversAD
2800 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2802 from .pyDASolverCompressibleADR
import pyDASolvers
as pyDASolversAD
2808 from .pyDASolverSolid
import pyDASolvers
2812 if self.
getOption(
"useAD")[
"mode"] ==
"forward":
2814 from .pyDASolverSolidADF
import pyDASolvers
as pyDASolversAD
2818 elif self.
getOption(
"useAD")[
"mode"] ==
"reverse":
2820 from .pyDASolverSolidADR
import pyDASolvers
as pyDASolversAD
2824 raise Error(
"pyDAFoam: %s not registered! Check _solverRegistry(self)." % solverName)
2828 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
2832 self.
solver.printAllOptions()
2834 adjMode = self.
getOption(
"unsteadyAdjoint")[
"mode"]
2835 if adjMode ==
"hybridAdjoint" or adjMode ==
"timeAccurateAdjoint":
2848 Info(
"+--------------------------------------------------------------------------+")
2849 Info(
"| Running Coloring Solver |")
2850 Info(
"+--------------------------------------------------------------------------+")
2852 solverName = self.
getOption(
"solverName")
2855 from .pyColoringIncompressible
import pyColoringIncompressible
2857 solverArg =
"ColoringIncompressible -python " + self.
parallelFlag
2858 solver = pyColoringIncompressible(solverArg.encode(), self.
options)
2861 from .pyColoringCompressible
import pyColoringCompressible
2863 solverArg =
"ColoringCompressible -python " + self.
parallelFlag
2864 solver = pyColoringCompressible(solverArg.encode(), self.
options)
2867 from .pyColoringSolid
import pyColoringSolid
2869 solverArg =
"ColoringSolid -python " + self.
parallelFlag
2870 solver = pyColoringSolid(solverArg.encode(), self.
options)
2872 raise Error(
"pyDAFoam: %s not registered! Check _solverRegistry(self)." % solverName)
2881 Run decomposePar to parallel run
2885 if self.
comm.size == 1:
2891 if self.
comm.rank == 0:
2892 status = subprocess.call(
"decomposePar", stdout=sys.stdout, stderr=subprocess.STDOUT, shell=
False)
2895 print(
"\nUnable to run decomposePar, the domain has been already decomposed?\n", flush=
True)
2902 Delete the previous primal solution time folder
2905 solTime = self.
solver.getPrevPrimalSolTime()
2907 rootDir = os.getcwd()
2909 checkPath = os.path.join(rootDir,
"processor%d/%g" % (self.
comm.rank, solTime))
2911 checkPath = os.path.join(rootDir,
"%g" % solTime)
2913 if os.path.isdir(checkPath):
2915 shutil.rmtree(checkPath)
2917 raise Error(
"Can not delete %s" % checkPath)
2919 Info(
"Previous solution time %g found and deleted." % solTime)
2921 Info(
"Previous solution time %g not found and nothing deleted." % solTime)
2927 Rename the primal solution folder to specific format for post-processing. The renamed time has the
2928 format like 0.00000001, 0.00000002, etc. One can load these intermediate shapes and fields and
2929 plot them in paraview.
2930 The way it is implemented is that we sort the solution folder and consider the largest time folder
2931 as the solution folder and rename it
2936 The major interation index
2940 rootDir = os.getcwd()
2942 checkPath = os.path.join(rootDir,
"processor%d" % self.
comm.rank)
2946 folderNames = os.listdir(checkPath)
2947 for folderName
in folderNames:
2950 allSolutions.append(folderName)
2953 allSolutions.sort(reverse=
True)
2955 solutionTime = allSolutions[0]
2957 if float(solutionTime) < 1e-4:
2958 Info(
"Solution time %g less than 1e-4, not renamed." % float(solutionTime))
2960 return solutionTime, renamed
2962 distTime =
"%.8f" % (solIndex / 1e8)
2964 src = os.path.join(checkPath, solutionTime)
2965 dst = os.path.join(checkPath, distTime)
2967 Info(
"Moving time %s to %s" % (solutionTime, distTime))
2969 if os.path.isdir(dst):
2970 raise Error(
"%s already exists, moving failed!" % dst)
2973 shutil.move(src, dst)
2975 raise Error(
"Can not move %s to %s" % (src, dst))
2978 return distTime, renamed
2982 Calculate the FFD2XvSeedVec vector:
2983 Given a FFD seed xDvDot, run pyGeo and IDWarp and propagate the seed to Xv seed xVDot:
2984 xSDot = \\frac{dX_{S}}{dX_{DV}}\\xDvDot
2985 xVDot = \\frac{dX_{V}}{dX_{S}}\\xSDot
2987 Then, we assign this vector to FFD2XvSeedVec in DASolver
2988 This will be used in forward mode AD runs
2991 if self.
DVGeo is None:
2992 raise Error(
"DVGeo not set!")
2994 dvName = self.
getOption(
"useAD")[
"dvName"]
2995 seedIndex = self.
getOption(
"useAD")[
"seedIndex"]
2997 xDV = self.
DVGeo.getValues()
3002 for key
in list(xDV.keys()):
3003 xDvDot[key] = np.zeros_like(xDV[key], dtype=self.
dtype)
3004 xDvDot[dvName][seedIndex] = 1.0
3007 xSDot0 = np.zeros_like(self.
xs0, self.
dtype)
3011 xSDot = self.
DVGeo.totalSensitivityProd(xDvDot, ptSetName=self.
ptSetName).reshape(xSDot0.shape)
3013 xVDot = self.
mesh.warpDerivFwd(xSDot)
3015 seedVec = self.
xvVec.duplicate()
3016 seedVec.zeroEntries()
3017 Istart, Iend = seedVec.getOwnershipRange()
3020 for idx
in range(Istart, Iend):
3021 idxRel = idx - Istart
3022 seedVec[idx] = xVDot[idxRel]
3024 seedVec.assemblyBegin()
3025 seedVec.assemblyEnd()
3027 self.
solverAD.setFFD2XvSeedVec(seedVec)
3031 Perturb each design variable and save the delta volume point coordinates
3032 to a mat, this will be used to calculate dRdFFD and dFdFFD in DAFoam
3036 deltaVPointThreshold: float
3037 A threshold, any delta volume coordinates smaller than this value will be ignored
3041 if self.
DVGeo is None:
3042 raise Error(
"DVGeo not set!")
3046 xDV = self.
DVGeo.getValues()
3047 nDVs = len(xDV[designVarName])
3050 oldVolPoints = self.
mesh.getSolverGrid()
3052 nXvs = len(oldVolPoints)
3054 epsFFD = self.
getOption(
"adjPartDerivFDStep")[
"FFD"]
3056 Info(
"Calculating the dXvdFFD matrix with epsFFD: " + str(epsFFD))
3058 dXvdFFDMat = PETSc.Mat().create(PETSc.COMM_WORLD)
3059 dXvdFFDMat.setSizes(((nXvs,
None), (
None, nDVs)))
3060 dXvdFFDMat.setFromOptions()
3061 dXvdFFDMat.setPreallocationNNZ((nDVs, nDVs))
3063 Istart, Iend = dXvdFFDMat.getOwnershipRange()
3066 for i
in range(nDVs):
3068 xDV[designVarName][i] += epsFFD
3074 newVolPoints = self.
mesh.getSolverGrid()
3076 for idx
in range(Istart, Iend):
3077 idxRel = idx - Istart
3078 deltaVal = newVolPoints[idxRel] - oldVolPoints[idxRel]
3079 if abs(deltaVal) > deltaVPointThreshold:
3080 dXvdFFDMat[idx, i] = deltaVal
3082 xDV[designVarName][i] -= epsFFD
3089 dXvdFFDMat.assemblyBegin()
3090 dXvdFFDMat.assemblyEnd()
3097 dXvdFFDMat.destroy()
3103 Update the vol mesh point coordinates based on the current values of design variables
3107 if self.
DVGeo is not None:
3119 self.
mesh.warpMesh()
3123 def _readMeshInfo(self):
3125 Initialize mesh information and read mesh information
3128 dirName = os.getcwd()
3131 self.
xv = copy.copy(self.xv0)
3137 Set the updated surface coordinates for a particular group.
3140 coordinates : numpy array
3141 Numpy array of size Nx3, where N is the number of coordinates on this processor.
3142 This array must have the same shape as the array obtained with getSurfaceCoordinates()
3144 Name of family or group of families for which to return coordinates for.
3146 if self.
mesh is None:
3149 if groupName
is None:
3153 if self.
mesh is None:
3154 raise Error(
"Cannot set new surface coordinate locations without a mesh" "warping object present.")
3166 Return the coordinates for the surfaces defined by groupName.
3171 Group identifier to get only coordinates cooresponding to
3172 the desired group. The group must be a family or a
3173 user-supplied group of families. The default is None which
3174 corresponds to all wall-type surfaces.
3178 xs: numpy array of size nPoints * 3 for surface points
3181 if groupName
is None:
3186 xs = np.zeros((npts, 3), self.
dtype)
3193 bc = self.boundaries[name]
3194 for ptInd
in bc[
"indicesRed"]:
3195 xs[counter, :] = self.
xv[ptInd]
3200 def _getSurfaceSize(self, groupName):
3202 Internal routine to return the size of a particular surface. This
3203 does *NOT* set the actual family group
3205 if groupName
is None:
3210 "'%s' is not a family in the OpenFoam Case or has not been added"
3211 " as a combination of families" % groupName
3222 bc = self.boundaries[name]
3223 nCells += len(bc[
"facesRed"])
3224 nPts += len(bc[
"indicesRed"])
3230 Assign the boundary condition defined in primalBC to the OF fields
3233 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
3236 def _computeBasicFamilyInfo(self):
3238 Loop over the boundary data and compute necessary family
3239 information for the basic patches
3255 bc = self.boundaries[name]
3258 nFace = len(bc[
"faces"])
3265 for iFace
in bc[
"faces"]:
3267 face = self.faces[iFace]
3268 indices.extend(face)
3271 indices = np.unique(indices)
3275 for i
in range(len(indices)):
3276 inverseInd[indices[i]] = i
3286 for iFace
in bc[
"faces"]:
3288 face = self.faces[iFace]
3292 for j
in range(nNodes):
3294 indRed = inverseInd[indOrig]
3295 faceReduced.append(indRed)
3296 facesRed.append(faceReduced)
3299 if not (len(bc[
"faces"]) == len(facesRed)):
3300 raise Error(
"Connectivity for faces on reduced index set is not the same length as original.")
3303 bc[
"facesRed"] = facesRed
3304 bc[
"indicesRed"] = list(indices)
3307 if bc[
"type"] ==
"wall" or bc[
"type"] ==
"slip" or bc[
"type"] ==
"cyclic":
3314 Take the apName and return the mangled point set name.
3316 return "openFoamCoords"
3320 Get the list of indices to pass to the mesh object for the
3325 nCoords = len(self.xv0.flatten())
3327 nCoords = self.
comm.allgather(nCoords)
3329 for i
in range(self.
comm.rank):
3330 offset += nCoords[i]
3332 meshInd = np.arange(nCoords[self.
comm.rank]) + offset
3336 def mapVector(self, vec1, groupName1, groupName2, vec2=None):
3337 """This is the main workhorse routine of everything that deals with
3338 families in pyDAFoam. The purpose of this routine is to convert a
3339 vector 'vec1' (of size Nx3) that was evaluated with
3340 'groupName1' and expand or contract it (and adjust the
3341 ordering) to produce 'vec2' evaluated on groupName2.
3343 A little ascii art might help. Consider the following "mesh"
3344 . Family 'fam1' has 9 points, 'fam2' has 10 pts and 'fam3' has
3345 5 points. Consider that we have also also added two
3346 additional groups: 'f12' containing 'fam1' and 'fma2' and a
3347 group 'f23' that contains families 'fam2' and 'fam3'. The vector
3348 we want to map is 'vec1'. It is length 9+10. All the 'x's are
3351 The call: mapVector(vec1, 'f12', 'f23')
3353 will produce the "returned vec" array, containing the
3354 significant values from 'fam2', where the two groups overlap,
3355 and the new values from 'fam3' set to zero. The values from
3356 fam1 are lost. The returned vec has size 15.
3359 |---------+----------+------|
3361 |xxxxxxxxx xxxxxxxxxx| <- vec1
3362 |xxxxxxxxxx 000000| <- returned vec (vec2)
3367 Array of size Nx3 that will be mapped to a different family set.
3370 The family group where the vector vec1 is currently defined
3373 The family group where we want to the vector to mapped into
3375 vec2 : Numpy array or None
3376 Array containing existing values in the output vector we want to keep.
3377 If this vector is not given, the values will be filled with zeros.
3382 The input vector mapped to the families defined in groupName2.
3386 "'%s' or '%s' is not a family in the mesh file or has not been added"
3387 " as a combination of families" % (groupName1, groupName2)
3391 if groupName1 == groupName2:
3396 vec2 = np.zeros((npts, 3), self.
dtype)
3398 famList1 = self.
families[groupName1]
3399 famList2 = self.
families[groupName2]
3402 This functionality is predicated on the surfaces being traversed in the
3403 same order every time. Loop over the allfamilies list, keeping track of sizes
3404 as we go and if the family is in both famLists, copy the values from vec1 to vec2.
3414 if ind
in famList1
and ind
in famList2:
3415 vec2[vec2counter : npts + vec2counter] = vec1[vec1counter : npts + vec1counter]
3425 def _initializeMeshPointVec(self):
3427 Initialize the mesh point vec: xvVec
3430 xvSize = len(self.
xv) * 3
3431 self.
xvVec = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
3432 self.
xvVec.setSizes((xvSize, PETSc.DECIDE), bsize=1)
3433 self.
xvVec.setFromOptions()
3442 def _initializeStateVec(self):
3444 Initialize state variable vector
3448 self.
wVec = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
3449 self.
wVec.setSizes((wSize, PETSc.DECIDE), bsize=1)
3450 self.
wVec.setFromOptions()
3458 if self.
getOption(
"multiPoint")
is True:
3459 nMultiPoints = self.
getOption(
"nMultiPoints")
3461 for i
in range(nMultiPoints):
3462 self.
wVecMPList[i] = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
3463 self.
wVecMPList[i].setSizes((wSize, PETSc.DECIDE), bsize=1)
3470 Convert a Nx3 mesh point numpy array to a Petsc xvVec
3475 for i
in range(xSize):
3477 globalIdx = self.
solver.getGlobalXvIndex(i, j)
3478 xvVec[globalIdx] = xv[i][j]
3480 xvVec.assemblyBegin()
3487 Convert a 3Nx1 mesh point numpy array to a Petsc xvVec
3491 xSize = int(xSize // 3)
3494 for i
in range(xSize):
3496 globalIdx = self.
solver.getGlobalXvIndex(i, j)
3497 xvVec[globalIdx] = xv[counterI]
3500 xvVec.assemblyBegin()
3506 def _readOFGrid(self, caseDir):
3508 Read in the mesh information we need to run the case using pyofm
3513 The directory containing the openFOAM Mesh files
3516 Info(
"Reading OpenFOAM mesh information...")
3518 from pyofm
import PYOFM
3521 ofm = PYOFM(comm=self.
comm)
3524 fileNames = ofm.getFileNames(caseDir, comm=self.
comm)
3527 x0 = ofm.readVolumeMeshPoints()
3530 faces = ofm.readFaceInfo()
3533 boundaries = ofm.readBoundaryInfo(faces)
3536 owners, neighbours = ofm.readCellInfo()
3538 return fileNames, x0, faces, boundaries, owners, neighbours
3542 Set a value to options.
3543 NOTE: calling this function will only change the values in self.options
3544 It will NOT change values for allOptions_ in DAOption. To make the options changes
3545 from pyDAFoam to DASolvers, call self.updateDAOption()
3550 Name of option to set. Not case sensitive
3552 Value to set. Type is checked for consistency.
3556 If self.options reads:
3559 'solverName': [str, 'DASimpleFoam'],
3560 'flowEndTime': [float, 1.0]
3562 Then, calling self.options('solverName', 'DARhoSimpleFoam') will give:
3565 'solverName': [str, 'DARhoSimpleFoam'],
3566 'flowEndTime': [float, 1.0]
3570 NOTE: if 'value' is of dict type, we will set all the subKey values in
3571 'value' dict to self.options, instead of overiding it. This works for
3572 only THREE levels of subDicts
3574 For example, if self.options reads
3579 'direction': [1.0, 0.0, 0.0],
3583 Then, calling self.setOption('objFunc', {'name': 'CL'}) will give:
3589 'direction': [1.0, 0.0, 0.0],
3597 'objFunc': [dict, {'name': 'CL'}]
3604 Error(
"Option '%-30s' is not a valid %s option." % (name, self.
name))
3609 raise Error(
"Option '%-35s' cannot be modified after the solver " "is created." % name)
3616 if isinstance(value, dict):
3617 for subKey1
in value:
3619 if isinstance(value[subKey1], dict):
3620 for subKey2
in value[subKey1]:
3622 if isinstance(value[subKey1][subKey2], dict):
3623 for subKey3
in value[subKey1][subKey2]:
3624 self.
options[name][1][subKey1][subKey2][subKey3] = value[subKey1][subKey2][subKey3]
3626 self.
options[name][1][subKey1][subKey2] = value[subKey1][subKey2]
3629 self.
options[name][1][subKey1] = value[subKey1]
3636 "Datatype for Option %-35s was not valid \n "
3637 "Expected data type is %-47s \n "
3638 "Received data type is %-47s" % (name, self.
defaultOptions[name][0], type(value))
3641 def _initOption(self, name, value):
3643 Set a value to options. This function will be used only for initializing the options internally.
3644 Do NOT call this function from the run script!
3649 Name of option to set. Not case sensitive
3651 Value to set. Type is checked for consistency.
3657 Error(
"Option '%-30s' is not a valid %s option." % (name, self.
name))
3662 raise Error(
"Option '%-35s' cannot be modified after the solver " "is created." % name)
3668 if isinstance(value, dict):
3669 for subKey
in value:
3671 self.
options[name][1][subKey] = value[subKey]
3678 "Datatype for Option %-35s was not valid \n "
3679 "Expected data type is %-47s \n "
3680 "Received data type is %-47s" % (name, self.
defaultOptions[name][0], type(value))
3685 Set the field value based on the global cellI. This is usually
3686 used if the state variables are design variables, e.g., betaSA
3687 The reason to use global cell index, instead of local one, is
3688 because this index is usually provided by the optimizer. Optimizer
3689 uses global cell index as the design variable
3694 Name of the flow field to set, e.g., U, p, nuTilda
3698 The global cell index to set the value
3700 The component index to set the value (for vectorField only)
3705 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
3710 Set the field value based on the local cellI.
3715 Name of the flow field to set, e.g., U, p, nuTilda
3719 The global cell index to set the value
3721 The component index to set the value (for vectorField only)
3726 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
3731 Update the boundary condition for a field
3736 Name of the flow field to update, e.g., U, p, nuTilda
3738 Type of the flow field: scalar or vector
3743 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
3748 Get a value from options
3753 Name of option to get. Not case sensitive
3758 Return the value of the option.
3764 raise Error(
"%s is not a valid option name." % name)
3768 Update the allOptions_ in DAOption based on the latest self.options in
3769 pyDAFoam. This will pass the changes of self.options from pyDAFoam
3770 to DASolvers. NOTE: need to call this function after calling
3775 raise Error(
"self._initSolver not called!")
3779 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
3787 Synchronize the values in DAOption and actuatorDiskDVs_. We need to synchronize the values
3788 defined in fvSource from DAOption to actuatorDiskDVs_ in the C++ layer
3789 NOTE: we need to call this function whenever we change the actuator design variables
3790 during optimization.
3795 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
3800 Get number of local adjoint states
3806 Return the list of design variable names
3807 NOTE: constraints are not implemented yet
3811 if self.
DVGeo is None:
3814 DVs = self.
DVGeo.getValues()
3817 size = len(DVs[dvName])
3820 DVNames.append(dvName)
3821 DVSizes.append(size)
3822 return DVNames, DVSizes
3826 Return the adjoint state array owns by this processor
3829 states = np.zeros(nLocalStateSize, self.
dtype)
3830 Istart, Iend = self.
wVec.getOwnershipRange()
3831 for i
in range(Istart, Iend):
3833 states[iRel] = self.
wVec[i]
3839 Return the residual array owns by this processor
3842 residuals = np.zeros(nLocalStateSize, self.
dtype)
3843 resVec = self.
wVec.duplicate()
3844 resVec.zeroEntries()
3846 self.
solver.calcResidualVec(resVec)
3848 Istart, Iend = resVec.getOwnershipRange()
3849 for i
in range(Istart, Iend):
3851 residuals[iRel] = resVec[i]
3857 Set the state to the OpenFOAM field
3859 Istart, Iend = self.
wVec.getOwnershipRange()
3860 for i
in range(Istart, Iend):
3862 self.
wVec[i] = states[iRel]
3864 self.
wVec.assemblyBegin()
3865 self.
wVec.assemblyEnd()
3869 if self.
getOption(
"useAD")[
"mode"]
in [
"forward",
"reverse"]:
3876 Convert a MPI vector to a seq array
3878 vecSize = mpiVec.getSize()
3879 seqVec = PETSc.Vec().createSeq(vecSize, bsize=1, comm=PETSc.COMM_SELF)
3880 self.
solver.convertMPIVec2SeqVec(mpiVec, seqVec)
3882 array1 = np.zeros(vecSize, self.
dtype)
3883 for i
in range(vecSize):
3884 array1[i] = seqVec[i]
3890 Convert a Petsc vector to numpy array
3893 Istart, Iend = vec.getOwnershipRange()
3894 size = Iend - Istart
3895 array1 = np.zeros(size, self.
dtype)
3896 for i
in range(Istart, Iend):
3898 array1[iRel] = vec[i]
3903 Convert a numpy array to Petsc vector
3907 vec = PETSc.Vec().create(PETSc.COMM_WORLD)
3908 vec.setSizes((size, PETSc.DECIDE), bsize=1)
3909 vec.setFromOptions()
3912 Istart, Iend = vec.getOwnershipRange()
3913 for i
in range(Istart, Iend):
3915 vec[i] = array1[iRel]
3924 Convert a numpy array to Petsc vector in serial mode
3928 vec = PETSc.Vec().createSeq(size, bsize=1, comm=PETSc.COMM_SELF)
3931 for i
in range(size):
3941 Convert a Petsc vector to numpy array in serial mode
3944 size = vec.getSize()
3945 array1 = np.zeros(size, self.
dtype)
3946 for i
in range(size):
3950 def _printCurrentOptions(self):
3952 Prints a nicely formatted dictionary of all the current solver
3953 options to the stdout on the root processor
3956 Info(
"+---------------------------------------+")
3957 Info(
"| All DAFoam Options: |")
3958 Info(
"+---------------------------------------+")
3965 def _getImmutableOptions(self):
3967 We define the list of options that *cannot* be changed after the
3968 object is created. pyDAFoam will raise an error if a user tries to
3969 change these. The strings for these options are placed in a set
3974 def _writeDecomposeParDict(self):
3976 Write system/decomposeParDict
3978 if self.
comm.rank == 0:
3981 workingDirectory = os.getcwd()
3983 varDir = os.path.join(workingDirectory, sysDir)
3984 fileName =
"decomposeParDict"
3985 fileLoc = os.path.join(varDir, fileName)
3986 f = open(fileLoc,
"w")
3990 decomDict = self.
getOption(
"decomposeParDict")
3991 n = decomDict[
"simpleCoeffs"][
"n"]
3992 f.write(
"numberOfSubdomains %d;\n" % self.
nProcs)
3994 f.write(
"method %s;\n" % decomDict[
"method"])
3996 f.write(
"simpleCoeffs \n")
3998 f.write(
" n (%d %d %d);\n" % (n[0], n[1], n[2]))
3999 f.write(
" delta %g;\n" % decomDict[
"simpleCoeffs"][
"delta"])
4002 f.write(
"distributed false;\n")
4004 f.write(
"roots();\n")
4005 if len(decomDict[
"preservePatches"]) == 1
and decomDict[
"preservePatches"][0] ==
"None":
4009 f.write(
"preservePatches (")
4010 for pPatch
in decomDict[
"preservePatches"]:
4011 f.write(
"%s " % pPatch)
4013 if decomDict[
"singleProcessorFaceSets"][0] !=
"None":
4014 f.write(
"singleProcessorFaceSets (")
4015 for pPatch
in decomDict[
"singleProcessorFaceSets"]:
4016 f.write(
" (%s -1) " % pPatch)
4019 f.write(
"// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n")
4024 def _writeOpenFoamHeader(self, f, className, location, objectName):
4026 Write OpenFOAM header file
4029 f.write(
"/*--------------------------------*- C++ -*---------------------------------*\ \n")
4030 f.write(
"| ======== | | \n")
4031 f.write(
"| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | \n")
4032 f.write(
"| \\ / O peration | Version: v1812 | \n")
4033 f.write(
"| \\ / A nd | Web: www.OpenFOAM.com | \n")
4034 f.write(
"| \\/ M anipulation | | \n")
4035 f.write(
"\*--------------------------------------------------------------------------*/ \n")
4036 f.write(
"FoamFile\n")
4038 f.write(
" version 2.0;\n")
4039 f.write(
" format ascii;\n")
4040 f.write(
" class %s;\n" % className)
4041 f.write(
' location "%s";\n' % location)
4042 f.write(
" object %s;\n" % objectName)
4044 f.write(
"// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n")
4050 Format the error message in a box to make it clear this
4051 was a expliclty raised exception.
4055 msg =
"\n+" +
"-" * 78 +
"+" +
"\n" +
"| pyDAFoam Error: "
4057 for word
in message.split():
4058 if len(word) + i + 1 > 78:
4059 msg +=
" " * (78 - i) +
"|\n| " + word +
" "
4060 i = 1 + len(word) + 1
4064 msg +=
" " * (78 - i) +
"|\n" +
"+" +
"-" * 78 +
"+" +
"\n"
4065 print(msg, flush=
True)
4066 Exception.__init__(self)
4073 Print information and flush to screen for parallel cases
4077 if MPI.COMM_WORLD.rank == 0:
4078 print(message, flush=
True)
4079 MPI.COMM_WORLD.Barrier()
4084 TensorFlow helper class.
4085 NOTE: this is a static class because the callback function
4086 does not accept non-static class members (seg fault)
4096 Initialize parameters and load models
4098 Info(
"Initializing the TensorFlowHelper")
4099 TensorFlowHelper.modelName = TensorFlowHelper.options[
"modelName"]
4100 TensorFlowHelper.nInputs = TensorFlowHelper.options[
"nInputs"]
4101 TensorFlowHelper.nOutputs = TensorFlowHelper.options[
"nOutputs"]
4102 TensorFlowHelper.batchSize = TensorFlowHelper.options[
"batchSize"]
4103 TensorFlowHelper.model = tf.keras.models.load_model(TensorFlowHelper.modelName)
4105 if TensorFlowHelper.nOutputs != 1:
4106 raise Error(
"current version supports nOutputs=1 only!")
4111 Calculate the outputs based on the inputs using the saved model
4114 inputs_tf = np.reshape(inputs, (-1, TensorFlowHelper.nInputs))
4115 outputs_tf = TensorFlowHelper.model.predict(inputs_tf, verbose=
False, batch_size=TensorFlowHelper.batchSize)
4118 outputs[i] = outputs_tf[i, 0]
4123 Calculate the gradients of the outputs wrt the inputs
4126 inputs_tf = np.reshape(inputs, (-1, TensorFlowHelper.nInputs))
4127 inputs_tf_var = tf.Variable(inputs_tf, dtype=tf.float32)
4129 with tf.GradientTape()
as tape:
4130 outputs_tf = TensorFlowHelper.model(inputs_tf_var)
4132 gradients_tf = tape.gradient(outputs_tf, inputs_tf_var)
4134 for i
in range(gradients_tf.shape[0]):
4135 for j
in range(gradients_tf.shape[1]):
4136 idx = i * gradients_tf.shape[1] + j
4137 inputs_b[idx] = gradients_tf.numpy()[i, j] * outputs_b[i]