Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
e201e79
Modify pyADflow.py for the propeller model
shamsheersc19 Apr 16, 2024
a9730f6
Modify adflow.pyf for the propeller model
shamsheersc19 Apr 16, 2024
20cba1f
Modify actuatorRegionData.F90 for the propeller model
shamsheersc19 Apr 17, 2024
c2cd5ce
Modify actuatorRegion.F90 for the propeller model
shamsheersc19 Apr 17, 2024
5d43602
Modify residuals.F90 for the propeller model
shamsheersc19 Apr 19, 2024
ad86edc
Bring back heat and VolLocal in actuator code
shamsheersc19 Apr 24, 2024
d8e2367
Modify Makefile_tapenade for the propeller model
shamsheersc19 Apr 24, 2024
0dc4ccf
Modify residuals_d.f90 for the propeller model
shamsheersc19 Apr 25, 2024
621dd3f
Modify adjointUtils.f90 for propeller model
shamsheersc19 Apr 25, 2024
e580506
Modify residuals_b.f90 for propeller model
shamsheersc19 Apr 26, 2024
6cae183
Modify residuals_fast_b.f90 for the propeller model
shamsheersc19 Apr 27, 2024
d6a5c76
Change remaining %force and %torque to %f and %t
shamsheersc19 May 1, 2024
d089303
Fix errors related to `fact` in merged adjoint code
shamsheersc19 May 1, 2024
d7dc861
Restrict lines to 132 char in actuatorRegion.F90
shamsheersc19 May 2, 2024
d9d61f7
Merge remote-tracking branch 'mdolab/main' into merge_prop
eirikurj Feb 26, 2025
8db8741
update variable naming and inconsitencies
eirikurj Feb 27, 2025
6ce5859
update AD files
eirikurj Feb 27, 2025
13d83f8
update actuator test
eirikurj Feb 27, 2025
a9ead6a
black
eirikurj Feb 27, 2025
bcbc6f3
order arguments
eirikurj Feb 27, 2025
6bb42a3
fprettify
eirikurj Feb 27, 2025
0d21897
Fix dot product typo in actuator disk setup
Mar 4, 2025
8838805
Add warnings about the torque not working for uniform actuator region
Mar 4, 2025
c9c9342
Merge branch 'merge_prop' into merge_prop
eirikurj Mar 4, 2025
ef0a338
Merge pull request #5 from eytanadler/merge_prop
eirikurj Mar 4, 2025
4745f0c
update complex test
eirikurj Mar 5, 2025
77745ae
Merge remote-tracking branch 'mdolab/main' into merge_prop
Jun 4, 2025
f11d5f0
Strip down current implementation in order to move initialization to
Jun 5, 2025
6933de3
move cell tagging logic to python
Jun 6, 2025
01fbfa7
finalize cell tagging in fortran
Jun 6, 2025
7ada581
Streamline spatial cell metrics computation
Jun 13, 2025
45f5053
Save the number of local cells in python after adding it to the fortran
Jun 16, 2025
21d3dfd
Prescribe the source terms in python
Jun 17, 2025
5d49abf
Check inputs to ActuatorRegion class
Jun 18, 2025
d73e354
fix bug where procs with 0 AR cells would crash
Jun 18, 2025
1b9f33d
Plug AR BCs into existing aeroProblem BC system
Jun 18, 2025
5f59193
Adjust actuator_test to new setup (non gradients tests pass!)
Jun 18, 2025
08df874
Remove actuator region code from BC routines
Jun 20, 2025
8df7833
Add BSplineActuatorRegion
Jun 27, 2025
389516d
Add ActuatoRegionHandler to move 'business logic' to its own file
Jun 27, 2025
27ae76c
only expose user-usable ActuatorRegions. Also fix flow tests
Jun 27, 2025
b63bb1e
Run tapenade
Jul 10, 2025
04872d1
Update tapenade makefile and run again
Jul 11, 2025
f01b625
zero seeds for Actuator Region
Sep 23, 2025
a2533ee
Fix radius normalization and run black
Sep 24, 2025
533be38
reverse thrust vector direction to be consistent with nomenclature
Sep 24, 2025
a220b00
allocate memory for _d version aswell
Sep 30, 2025
0dbf1ea
scale thrust distribution analytically
Oct 6, 2025
116b241
scale thrust distribution numerically
Oct 6, 2025
c707051
Use numerically saver comparision
Oct 6, 2025
3ffa406
Flip thrust vector in test to point forward
Oct 6, 2025
84e4701
Only continue loop after all MPI reductions. Also remove unneeded
Oct 6, 2025
b9e823c
fix bug where method call was changed but not definition
Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions adflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
from .pyADflow_C import ADFLOW_C
from .oversetCheck import OversetCheck
from .checkZipper import checkZipper


from .actuatorRegion import UniformActuatorRegion, BSplineActuatorRegion
331 changes: 331 additions & 0 deletions adflow/actuatorRegion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
from abc import ABC, abstractmethod
from typing import Tuple
from types import ModuleType
import os

from mpi4py import MPI

import numpy as np
import numpy.typing as npt
import scipy.interpolate as interpolate


from baseclasses import AeroProblem


class AbstractActuatorRegion(ABC):
def __init__(self, centerPoint: npt.NDArray, thrustVector: npt.NDArray, thrust: float, heat: float):
if centerPoint.shape != (3,):
raise ValueError('"centerPoint" must have shape "(3,)"')
if thrustVector.shape != (3,):
raise ValueError('"thrustVector" must have shape "(3,)"')
if np.linalg.norm(thrustVector) == 0:
raise ValueError('"trustVector" can not have a length of "0"')
if thrust < 0:
raise ValueError('"thrust" must not be smaller than 0.')
if heat < 0:
raise ValueError('"heat" must not be smaller than 0.')

self.centerPoint = centerPoint
self.thrustVector = thrustVector / np.linalg.norm(thrustVector)
self.iRegion = -1
self.nLocalCells = -1
self.familyName = ""

self._heatBcName = "Heat"
self._thrustBcName = "Thrust"

self._boundaryConditions = {
self._thrustBcName: thrust,
self._heatBcName: heat,
}

def setBCValue(self, bcName, bcValue):
if bcName not in self._boundaryConditions.keys():
raise ValueError(
f'"{bcName}" is not a valid Boundary Condition. Valid Conditions are: {list(self._boundaryConditions.keys())}'
)

self._boundaryConditions[bcName] = bcValue

def _computeScalingConstant(self, cellValues, totalValue, comm):
computedTotalvalue = comm.allreduce(np.sum(cellValues), op=MPI.SUM)

if np.isclose(computedTotalvalue, 0):
return 0.0

return totalValue / computedTotalvalue

@abstractmethod
def tagActiveCells(
self, distance2axis: npt.NDArray, distance2plane: npt.NDArray, tangent: npt.NDArray
) -> npt.NDArray:
flags = np.zeros_like(distance2axis)
return flags

@abstractmethod
def computeCellForceVector(
self,
distance2axis: npt.NDArray,
distance2plane: npt.NDArray,
tangent: npt.NDArray,
cellVolume: npt.NDArray,
totalVolume: float,
comm: MPI.Intracomm,
) -> npt.NDArray:
force = np.zeros((3, len(distance2axis)))
return force

@abstractmethod
def computeCellHeatVector(
self,
distance2axis: npt.NDArray,
distance2plane: npt.NDArray,
tangent: npt.NDArray,
cellVolume: npt.NDArray,
totalVolume: float,
comm: MPI.Intracomm,
) -> npt.NDArray:
heat = np.zeros_like(distance2axis)
return heat


class ActuatorRegionHandler:
def __init__(self, adflow_fortran: ModuleType, comm):
self._comm = comm
self._adflow_fortran = adflow_fortran

self._actuatorRegions = list()

def addActuatorRegion(
self,
actuatorRegion: AbstractActuatorRegion,
familyName: str,
familyID: int,
relaxStart: float,
relaxEnd: float,
):
# compute the distance of each cell to the AR plane and axis
ncells = self._adflow_fortran.adjointvars.ncellslocal[0]

distance2plane, distance2axis, tangent = self._adflow_fortran.actuatorregion.computeinitialspatialmetrics(
actuatorRegion.centerPoint, actuatorRegion.thrustVector, ncells
)
tangent = tangent.T

# tag the active cells
flag = actuatorRegion.tagActiveCells(distance2axis, distance2plane, tangent)
iRegion, nLocalCells = self._adflow_fortran.actuatorregion.addactuatorregion(
flag,
familyName,
familyID,
relaxStart,
relaxEnd,
)
actuatorRegion.iRegion = iRegion
actuatorRegion.nLocalCells = nLocalCells
actuatorRegion.familyName = familyName

# book keep the new region
self._actuatorRegions.append(actuatorRegion)

def updateActuatorRegionsBC(self, aeroproblem: AeroProblem):
for actuatorRegion in self._actuatorRegions:

# set BC values
for (bcName, familyName), bcValue in aeroproblem.bcVarData.items():
if familyName != actuatorRegion.familyName:
continue
actuatorRegion.setBCValue(bcName, bcValue)

# compute local metrics
distance2plane, distance2axis, tangent, volume = self._adflow_fortran.actuatorregion.computespatialmetrics(
actuatorRegion.iRegion,
actuatorRegion.nLocalCells,
actuatorRegion.centerPoint,
actuatorRegion.thrustVector,
)
tangent = tangent.T

totalVolume = self._comm.allreduce(np.sum(volume), op=MPI.SUM)

# compute the source terms
force = actuatorRegion.computeCellForceVector(
distance2axis, distance2plane, tangent, volume, totalVolume, self._comm
)
heat = actuatorRegion.computeCellHeatVector(
distance2axis, distance2plane, tangent, volume, totalVolume, self._comm
)

# skip this proc if no cells are active
if actuatorRegion.nLocalCells == 0:
continue

# apply the source terms in Fortran
self._adflow_fortran.actuatorregion.populatebcvalues(
actuatorRegion.iRegion,
actuatorRegion.nLocalCells,
force.T,
heat.T,
)

def writeActuatorRegions(self, fileName: str, outputDir: str):
# Join to get the actual filename root
fileName = os.path.join(outputDir, fileName)

# Ensure extension is .plt even if the user didn't specify
fileName, ext = os.path.splitext(fileName)
fileName += ".plt"

# just call the underlying fortran routine
self._adflow_fortran.actuatorregion.writeactuatorregions(fileName)


class CircularActuatorRegion(AbstractActuatorRegion):
def __init__(
self,
centerPoint: npt.NDArray,
thrustVector: npt.NDArray,
innerDiameter: float,
outerDiameter: float,
regionDepth: float,
thrust: float = 0,
heat: float = 0,
):
super().__init__(centerPoint, thrustVector, thrust, heat)

if regionDepth <= 0:
raise ValueError('"depth" must be greater than 0.')
if innerDiameter < 0:
raise ValueError('"innerDiameter" must not be smaller than 0.')
if outerDiameter <= 0:
raise ValueError('"outerDiameter" must be greater than 0.')
if outerDiameter <= innerDiameter:
raise ValueError('"outerDiameter" must be greater than "innerDiameter".')

self._innerDiameter = innerDiameter
self._outerDiameter = outerDiameter
self._regionDepth = regionDepth

def tagActiveCells(self, distance2axis, distance2plane, tangent):
flags = np.zeros_like(distance2axis)

indices = np.logical_and(
np.logical_and(
distance2axis >= self._innerDiameter / 2,
distance2axis <= self._outerDiameter / 2,
),
np.logical_and(distance2plane >= 0, distance2plane <= self._regionDepth),
)

flags[indices] = 1

return flags


class UniformActuatorRegion(CircularActuatorRegion):
def computeCellForceVector(self, distance2axis, distance2plane, tangent, cellVolume, totalVolume, comm):
thrustBC = self._boundaryConditions[self._thrustBcName]

cellThrusts = thrustBC * cellVolume / totalVolume
scaling_constant = self._computeScalingConstant(cellThrusts, thrustBC, comm)
force = np.outer(scaling_constant * cellThrusts, -self.thrustVector)

return force

def computeCellHeatVector(self, distance2axis, distance2plane, tangent, cellVolume, totalVolume, comm):
heatBC = self._boundaryConditions[self._heatBcName]

cellHeats = heatBC * cellVolume / totalVolume
scaling_constant = self._computeScalingConstant(cellHeats, heatBC, comm)

return cellHeats * scaling_constant


class BSplineActuatorRegion(CircularActuatorRegion):
def __init__(
self,
centerPoint: npt.NDArray,
thrustVector: npt.NDArray,
innerDiameter: float,
outerDiameter: float,
regionDepth: float,
thrustDistribution: Tuple[npt.NDArray, npt.NDArray, float] = (
np.array([0.0, 0.0, 1.0, 1.0]),
np.array([0.0, 0.0, 0.0, 0.0]),
1,
),
tangentDistribution: Tuple[npt.NDArray, npt.NDArray, float] = (
np.array([0.0, 0.0, 1.0, 1.0]),
np.array([0.0, 0.0, 0.0, 0.0]),
1,
),
radialDistribution: Tuple[npt.NDArray, npt.NDArray, float] = (
np.array([0.0, 0.0, 1.0, 1.0]),
np.array([0.0, 0.0, 0.0, 0.0]),
1,
),
heatDistribution: Tuple[npt.NDArray, npt.NDArray, float] = (
np.array([0.0, 0.0, 1.0, 1.0]),
np.array([0.0, 0.0, 0.0, 0.0]),
1,
),
thrust: float = 0,
heat: float = 0,
):
super().__init__(centerPoint, thrustVector, innerDiameter, outerDiameter, regionDepth, thrust, heat)

self._thrustSpline = interpolate.BSpline(*thrustDistribution)
self._tangentSpline = interpolate.BSpline(*tangentDistribution)
self._radialSpline = interpolate.BSpline(*radialDistribution)
self._heatSpline = interpolate.BSpline(*heatDistribution)

def _formSpline(self, distribution: Tuple[npt.NDArray, npt.NDArray, float], scalingConstant: float):
t = distribution[0]
c = distribution[1]
k = distribution[2]

return interpolate.BSpline(t, c * scalingConstant, k)

def _computeNormalizedRadius(self, distance2axis):
normalizedRadius = distance2axis - self._innerDiameter / 2
normalizedRadius /= self._outerDiameter / 2 - self._innerDiameter / 2
return normalizedRadius

def computeCellForceVector(self, distance2axis, distance2plane, tangent, cellVolume, totalVolume, comm):
thrustBC = self._boundaryConditions[self._thrustBcName]

normalizedRadius = self._computeNormalizedRadius(distance2axis)
thrustFactor = thrustBC * cellVolume / totalVolume

# add axial contribution (thrust)
cellThrusts = self._thrustSpline(normalizedRadius) * thrustFactor

# compute a scaling constant such that the summ of the thrust equals the prescribed thrust
scaling_constant = self._computeScalingConstant(cellThrusts, thrustBC, comm)
force = np.outer(scaling_constant * cellThrusts, -self.thrustVector)

# add tangential contribution (swirl)
force += np.multiply(
tangent.T, np.array([scaling_constant * self._tangentSpline(normalizedRadius) * thrustFactor])
).T

# add radial contribution
radius = np.cross(self.thrustVector, tangent)
force += np.multiply(
radius.T, np.array([scaling_constant * self._radialSpline(normalizedRadius) * 2 * thrustFactor])
).T

return force

def computeCellHeatVector(self, distance2axis, distance2plane, tangent, cellVolume, totalVolume, comm):
heatBC = self._boundaryConditions[self._heatBcName]

normalizedRadius = self._computeNormalizedRadius(distance2axis)

# compute a scaling constant such that the summ of the heat equals the prescribed heat

cellHeats = self._heatSpline(normalizedRadius) * heatBC * cellVolume / totalVolume
scaling_constant = self._computeScalingConstant(cellHeats, heatBC, comm)

return cellHeats * scaling_constant
Loading