Skip to content

Commit c6c328e

Browse files
Adding force computation for aerostructural analysis and optimization (#187)
* First semi-functioning version of calcSurfaceForce utility in Python + C++. * Updated getForces() implementation matching ADflow methodology and DAFoam coordinate indexing. * Updates to make getForces() (in C++) differentiable. * Fixing PETSc type mismatch. * Changed getForces() routine to try to fix failing build.
1 parent 88454c8 commit c6c328e

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed

dafoam/pyDAFoam.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2339,6 +2339,69 @@ def mapdXvTodFFD(self, totalDerivXv):
23392339

23402340
return dFdFFD
23412341

2342+
def getForces(self, groupName=None):
2343+
"""Return the forces on this processor on the families defined by groupName.
2344+
Parameters
2345+
----------
2346+
groupName : str
2347+
Group identifier to get only forces cooresponding to the
2348+
desired group. The group must be a family or a user-supplied
2349+
group of families. The default is None which corresponds to
2350+
all wall-type surfaces.
2351+
2352+
Returns
2353+
-------
2354+
forces : array (N,3)
2355+
Forces on this processor. Note that N may be 0, and an
2356+
empty array of shape (0, 3) can be returned.
2357+
"""
2358+
# Calculate number of surface points
2359+
nPts, nCells = self._getSurfaceSize(self.allWallsGroup)
2360+
2361+
# Initialize PETSc vectors
2362+
pointListTemp = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2363+
pointListTemp.setSizes((nPts, PETSc.DECIDE), bsize=1)
2364+
pointListTemp.setFromOptions()
2365+
2366+
fX = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2367+
fX.setSizes((nPts, PETSc.DECIDE), bsize=1)
2368+
fX.setFromOptions()
2369+
2370+
fY = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2371+
fY.setSizes((nPts, PETSc.DECIDE), bsize=1)
2372+
fY.setFromOptions()
2373+
2374+
fZ = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
2375+
fZ.setSizes((nPts, PETSc.DECIDE), bsize=1)
2376+
fZ.setFromOptions()
2377+
2378+
# Compute forces
2379+
self.solver.getForces(fX, fY, fZ, pointListTemp)
2380+
2381+
# Copy data from PETSc vectors
2382+
forces = np.zeros((nPts,3))
2383+
forces[:,0] = np.copy(fX.getValues(range(0,nPts)))
2384+
forces[:,1] = np.copy(fY.getValues(range(0,nPts)))
2385+
forces[:,2] = np.copy(fZ.getValues(range(0,nPts)))
2386+
2387+
pointList = np.copy(pointListTemp.getValues(range(0,nPts)))
2388+
2389+
# Cleanup PETSc vectors
2390+
fX.destroy()
2391+
fY.destroy()
2392+
fZ.destroy()
2393+
pointListTemp.destroy()
2394+
2395+
# Reorder points to match sorted convention
2396+
indices = np.argsort(pointList)
2397+
forces = forces[indices]
2398+
2399+
if groupName is None:
2400+
groupName = self.allWallsGroup
2401+
2402+
# Finally map the vector as required.
2403+
return self.mapVector(forces, self.allWallsGroup, groupName)
2404+
23422405
def calcTotalDeriv(self, dRdX, dFdX, psi, totalDeriv):
23432406
"""
23442407
Compute total derivative

src/adjoint/DASolver/DASolver.C

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,202 @@ void DASolver::setDAObjFuncList()
316316
}
317317
}
318318

319+
void DASolver::getForces(Vec fX, Vec fY, Vec fZ, Vec pointList)
320+
{
321+
/*
322+
Description:
323+
Compute the nodal forces for all of the nodes on the
324+
fluid-structure-interaction patches.
325+
326+
Output:
327+
forces : a (nPoint x 3) array of forces for all of the nodes
328+
of the desired patches.
329+
*/
330+
Info << "Calculating surface forces" << endl;
331+
// Zero point force arrays
332+
VecZeroEntries(fX);
333+
VecZeroEntries(fY);
334+
VecZeroEntries(fZ);
335+
336+
VecZeroEntries(pointList);
337+
338+
#ifndef SolidDASolver
339+
// Generate patches, point mesh, and point boundary mesh
340+
const polyBoundaryMesh& patches = meshPtr_->boundaryMesh();
341+
const pointMesh& pMesh = pointMesh::New(meshPtr_());
342+
const pointBoundaryMesh& boundaryMesh = pMesh.boundary();
343+
344+
// Find wall patches and sort in alphabetical order
345+
label nWallPatch = 0;
346+
forAll(patches, patchI)
347+
{
348+
if (patches[patchI].type() == "wall")
349+
{
350+
nWallPatch += 1;
351+
}
352+
}
353+
List<word> patchList(nWallPatch);
354+
355+
label iWallPatch = 0;
356+
forAll(patches, patchI)
357+
{
358+
if (patches[patchI].type() == "wall")
359+
{
360+
patchList[iWallPatch] = patches[patchI].name();
361+
iWallPatch += 1;
362+
}
363+
}
364+
SortableList<word> patchListSort(patchList);
365+
366+
// compute size of point and connectivity arrays
367+
label nPoints = 0;
368+
forAll(patchListSort, cI)
369+
{
370+
// Get number of points in patch
371+
label patchIPoints = boundaryMesh.findPatchID(patchListSort[cI]);
372+
nPoints += boundaryMesh[patchIPoints].size();
373+
}
374+
375+
Info << "Total number of points: " << nPoints << endl;
376+
377+
// Initialize surface field for face-centered forces
378+
volVectorField volumeForceField(
379+
IOobject(
380+
"volumeForceField",
381+
meshPtr_->time().timeName(),
382+
meshPtr_(),
383+
IOobject::NO_READ,
384+
IOobject::NO_WRITE),
385+
meshPtr_(),
386+
dimensionedVector("surfaceForce", dimensionSet(1, 1, -2, 0, 0, 0, 0), vector::zero),
387+
fixedValueFvPatchScalarField::typeName);
388+
389+
// this code is pulled from:
390+
// src/functionObjects/forcces/forces.C
391+
// modified slightly
392+
vector force(vector::zero);
393+
394+
const objectRegistry& db = meshPtr_->thisDb();
395+
const volScalarField& p = db.lookupObject<volScalarField>("p");
396+
397+
const surfaceVectorField::Boundary& Sfb = meshPtr_->Sf().boundaryField();
398+
399+
const DATurbulenceModel& daTurb = daModelPtr_->getDATurbulenceModel();
400+
tmp<volSymmTensorField> tdevRhoReff = daTurb.devRhoReff();
401+
const volSymmTensorField::Boundary& devRhoReffb = tdevRhoReff().boundaryField();
402+
403+
// iterate over patches and extract boundary surface forces
404+
forAll(patchListSort, cI)
405+
{
406+
// get the patch id label
407+
label patchI = meshPtr_->boundaryMesh().findPatchID(patchListSort[cI]);
408+
// create a shorter handle for the boundary patch
409+
const fvPatch& patch = meshPtr_->boundary()[patchI];
410+
// normal force
411+
vectorField fN(Sfb[patchI] * p.boundaryField()[patchI]);
412+
// tangential force
413+
vectorField fT(Sfb[patchI] & devRhoReffb[patchI]);
414+
// sum them up
415+
forAll(patch, faceI)
416+
{
417+
force.x() = fN[faceI].x() + fT[faceI].x();
418+
force.y() = fN[faceI].y() + fT[faceI].y();
419+
force.z() = fN[faceI].z() + fT[faceI].z();
420+
volumeForceField.boundaryFieldRef()[patchI][faceI] = force;
421+
}
422+
}
423+
volumeForceField.write();
424+
425+
// The above volumeForceField is face-centered, we need to interpolate it to point-centered
426+
label pointCounter = 0;
427+
List<label> globalIndex(nPoints, -1);
428+
429+
pointField meshPoints = meshPtr_->points();
430+
431+
vector nodeForce(vector::zero);
432+
433+
PetscScalar* vecArrayFX;
434+
VecGetArray(fX, &vecArrayFX);
435+
PetscScalar* vecArrayFY;
436+
VecGetArray(fY, &vecArrayFY);
437+
PetscScalar* vecArrayFZ;
438+
VecGetArray(fZ, &vecArrayFZ);
439+
440+
PetscScalar* vecArrayPointList;
441+
VecGetArray(pointList, &vecArrayPointList);
442+
443+
forAll(patchListSort, cI)
444+
{
445+
// get the patch id label
446+
label patchI = meshPtr_->boundaryMesh().findPatchID(patchListSort[cI]);
447+
448+
// Loop over Faces
449+
forAll(meshPtr_->boundaryMesh()[patchI], faceI)
450+
{
451+
// Get number of points
452+
const label nPoints = meshPtr_->boundaryMesh()[patchI][faceI].size();
453+
454+
// Divide force to nodes
455+
nodeForce = volumeForceField.boundaryFieldRef()[patchI][faceI] / double(nPoints);
456+
457+
forAll(meshPtr_->boundaryMesh()[patchI][faceI], pointI)
458+
{
459+
// this is the index that corresponds to meshPoints, which contains both volume and surface points
460+
// so we can't directly reuse this index because we want to have only surface points
461+
label faceIPointIndexI = meshPtr_->boundaryMesh()[patchI][faceI][pointI];
462+
463+
// Loop over globalMapping array to check if this node is already included
464+
bool found = false;
465+
label iPoint;
466+
for (label i = 0; i < pointCounter; i++){
467+
if (faceIPointIndexI == globalIndex[i])
468+
{
469+
found = true;
470+
iPoint = i;
471+
break;
472+
}
473+
}
474+
475+
// If node is already included, add value to its entry
476+
PetscScalar val1, val2, val3;
477+
assignValueCheckAD(val1, nodeForce[0]);
478+
assignValueCheckAD(val2, nodeForce[1]);
479+
assignValueCheckAD(val3, nodeForce[2]);
480+
if (found) {
481+
// Add Force
482+
vecArrayFX[iPoint] += val1;
483+
vecArrayFY[iPoint] += val2;
484+
vecArrayFZ[iPoint] += val3;
485+
}
486+
// If node is not already included, add it as the newest point and add global index mapping
487+
else{
488+
// Add Force
489+
vecArrayFX[pointCounter] = val1;
490+
vecArrayFY[pointCounter] = val2;
491+
vecArrayFZ[pointCounter] = val3;
492+
// Add to Node Order Array
493+
vecArrayPointList[pointCounter] = faceIPointIndexI;
494+
// Add to Global - Local Mapping
495+
globalIndex[pointCounter] = faceIPointIndexI;
496+
497+
// Increment counter
498+
pointCounter += 1;
499+
}
500+
}
501+
}
502+
}
503+
VecRestoreArray(fX, &vecArrayFX);
504+
VecRestoreArray(fY, &vecArrayFY);
505+
VecRestoreArray(fZ, &vecArrayFZ);
506+
507+
VecRestoreArray(pointList, &vecArrayPointList);
508+
#endif
509+
510+
511+
Info << "Calculating surface force.... Completed!" << endl;
512+
return;
513+
}
514+
319515
void DASolver::reduceStateResConLevel(
320516
const dictionary& maxResConLv4JacPCMat,
321517
HashTable<List<List<word>>>& stateResConInfo) const

src/adjoint/DASolver/DASolver.H

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <petscksp.h>
1616
#include "Python.h"
1717
#include "fvCFD.H"
18+
#include "fvMesh.H"
1819
#include "runTimeSelectionTables.H"
1920
#include "OFstream.H"
2021
#include "functionObjectList.H"
@@ -31,6 +32,7 @@
3132
#include "DAField.H"
3233
#include "DAPartDeriv.H"
3334
#include "DALinearEqn.H"
35+
#include "volPointInterpolation.H"
3436

3537
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
3638

@@ -616,6 +618,9 @@ public:
616618
/// return the value of the objective function
617619
scalar getObjFuncValue(const word objFuncName);
618620

621+
/// return the forces of the desired fluid-structure-interaction patches
622+
void getForces(Vec fX, Vec fY, Vec fZ, Vec pointList);
623+
619624
/// print all DAOption
620625
void printAllOptions()
621626
{

src/pyDASolvers/DASolvers.H

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ public:
408408
return returnVal;
409409
}
410410

411+
/// return the value of objective
412+
void getForces(Vec fX, Vec fY, Vec fZ, Vec pointList)
413+
{
414+
DASolverPtr_->getForces(fX, fY, fZ, pointList);
415+
}
416+
411417
/// call DASolver::printAllOptions
412418
void printAllOptions()
413419
{

src/pyDASolvers/pyDASolvers.pyx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ cdef extern from "DASolvers.H" namespace "Foam":
6161
int getNLocalCells()
6262
int checkMesh()
6363
double getObjFuncValue(char *)
64+
void getForces(PetscVec, PetscVec, PetscVec, PetscVec)
6465
void printAllOptions()
6566
void updateDAOption(object)
6667
double getPrevPrimalSolTime()
@@ -261,6 +262,9 @@ cdef class pyDASolvers:
261262
def getObjFuncValue(self, objFuncName):
262263
return self._thisptr.getObjFuncValue(objFuncName)
263264

265+
def getForces(self, Vec fX, Vec fY, Vec fZ, Vec pointList):
266+
self._thisptr.getForces(fX.vec, fY.vec, fZ.vec, pointList.vec)
267+
264268
def printAllOptions(self):
265269
self._thisptr.printAllOptions()
266270

0 commit comments

Comments
 (0)