Skip to content

Commit

Permalink
Adding sliding curves for DVGeoMulti (#231)
Browse files Browse the repository at this point in the history
* Adding sliding curves for intersections

* Adding test for sliding curves

* Adding check to ensure one coordinate in sliding curve test changed

* Addressing Sabet's comments

* address sabet's comments

* Fixing test assertion

* Updating test tolerances

---------

Co-authored-by: Anil Yildirim <anily@umich.edu>
  • Loading branch information
bernardopacini and anilyil authored Jan 17, 2024
1 parent e47a454 commit 9bbcdf0
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 14 deletions.
55 changes: 41 additions & 14 deletions pygeo/parameterization/DVGeoMulti.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def addIntersection(
project=False,
marchDir=1,
includeCurves=False,
slidingCurves=None,
intDir=None,
curveEpsDict=None,
trackSurfaces=None,
Expand Down Expand Up @@ -233,6 +234,10 @@ def addIntersection(
includeCurves : bool, optional
Flag to specify whether to include features curves in the inverse-distance deformation.
slidingCurves : list, optional
The list of curves to project to, but on which the mesh nodes are not frozen in their initial positions.
This allows the mesh nodes to slide along the feature curve.
intDir : int, optional
If there are multiple intersection curves, this specifies which curve to choose.
The sign determines the direction and the value (1, 2, 3) specifies the axis (x, y, z).
Expand Down Expand Up @@ -270,6 +275,8 @@ def addIntersection(
# Assign mutable defaults
if featureCurves is None:
featureCurves = []
if slidingCurves is None:
slidingCurves = []
if curveEpsDict is None:
curveEpsDict = {}
if trackSurfaces is None:
Expand All @@ -290,6 +297,7 @@ def addIntersection(
project,
marchDir,
includeCurves,
slidingCurves,
intDir,
curveEpsDict,
trackSurfaces,
Expand Down Expand Up @@ -1006,6 +1014,7 @@ def __init__(
project,
marchDir,
includeCurves,
slidingCurves,
intDir,
curveEpsDict,
trackSurfaces,
Expand Down Expand Up @@ -1195,6 +1204,10 @@ def __init__(
# flag to include feature curves in ID-warping
self.incCurves = includeCurves

# List of curves that allow nodes to slide on them. We only use these for the projection step,
# but these curves are not included as seeds in the curve-based deformation.
self.slidingCurves = slidingCurves

# direction to pick if we have multiple intersection curves
self.intDir = intDir

Expand Down Expand Up @@ -1433,7 +1446,7 @@ def addPointSet(self, pts, ptSetName, compMap, comm):
elemIDs[:] = elemIDs + 1
# (we need to do this separetely because Fortran will actively change elemIDs contents.
self.curveSearchAPI.mindistancecurve(
ptsToCurves.T, self.seam0.T, self.seamConn.T + 1, xyzProj.T, tanProj.T, dist2, elemIDs
ptsToCurves.T, self.seam0.T, self.seamConnFull.T + 1, xyzProj.T, tanProj.T, dist2, elemIDs
)

# Adjust indices back to Python standards
Expand Down Expand Up @@ -1543,7 +1556,7 @@ def update(self, ptSetName, delta):
# we use the initial seam coordinates here
coor = self.seam0
# bar connectivity for the remeshed elements
conn = self.seamConn
conn = self.seamConnWarp
# deltas for each point (nNode, 3) in size
if self.seam.shape == self.seam0.shape:
dr = self.seam - self.seam0
Expand Down Expand Up @@ -1650,7 +1663,7 @@ def sens(self, dIdPt, ptSetName, comm):
# we use the initial seam coordinates here
coor = self.seam0
# bar connectivity for the remeshed elements
conn = self.seamConn
conn = self.seamConnWarp

# Get the two end points for the line elements
r0 = coor[conn[:, 0]]
Expand Down Expand Up @@ -1787,7 +1800,7 @@ def project(self, ptSetName, newPts):
# conn of the current curve
seamBeg = self.seamBeg[curveName]
seamEnd = self.seamEnd[curveName]
curveConn = self.seamConn[seamBeg:seamEnd]
curveConn = self.seamConnFull[seamBeg:seamEnd]

# Project these to the combined curves using pySurf
# Get number of points
Expand Down Expand Up @@ -2769,7 +2782,8 @@ def _getIntersectionSeam(self, comm, firstCall=False):
self.distFeature = {}

remeshedCurves = np.zeros((0, 3), dtype=self.dtype)
remeshedCurveConn = np.zeros((0, 2), dtype="int32")
remeshedCurveConnFull = np.zeros((0, 2), dtype="int32")
remeshedCurveConnWarp = np.zeros((0, 2), dtype="int32")

# loop over each curve, figure out what nodes get re-meshed, re-mesh, and append to seam...
for curveName in self.featureCurveNames:
Expand Down Expand Up @@ -2890,9 +2904,16 @@ def _getIntersectionSeam(self, comm, firstCall=False):
# increment the connectivitiy data
newBarsConn += len(remeshedCurves)

# append this new curve to the featureCurve data
# Append this new curve to the featureCurve data.
remeshedCurves = np.vstack((remeshedCurves, newCoor))
remeshedCurveConn = np.vstack((remeshedCurveConn, newBarsConn))

# By excluding sliding curves here in the 'warp' array,
# they are not used as seeds for the curved-based deformation.
# This means that points on these curves get warped like any other point.
# We also still want the 'full' connectivity because that is used for projections.
remeshedCurveConnFull = np.vstack((remeshedCurveConnFull, newBarsConn))
if curveName not in self.slidingCurves:
remeshedCurveConnWarp = np.vstack((remeshedCurveConnWarp, newBarsConn))

# number of new nodes added in the opposite direction
nNewNodesReverse = 0
Expand All @@ -2919,7 +2940,10 @@ def _getIntersectionSeam(self, comm, firstCall=False):
newBarsConn = newBarsConn + len(remeshedCurves)

remeshedCurves = np.vstack((remeshedCurves, newCoor))
remeshedCurveConn = np.vstack((remeshedCurveConn, newBarsConn))
remeshedCurveConnFull = np.vstack((remeshedCurveConnFull, newBarsConn))

if curveName not in self.slidingCurves:
remeshedCurveConnWarp = np.vstack((remeshedCurveConnWarp, newBarsConn))

if curveName in curveBegCoor:
# finally, put the modified initial and final points back in place.
Expand All @@ -2937,28 +2961,31 @@ def _getIntersectionSeam(self, comm, firstCall=False):
if firstCall:
# save the beginning and end indices of these elements
self.seamBeg[curveName] = (
len(finalConn) + len(remeshedCurveConn) - (nNewNodes + nNewNodesReverse) + 2
len(finalConn) + len(remeshedCurveConnFull) - (nNewNodes + nNewNodesReverse) + 2
)
self.seamEnd[curveName] = len(finalConn) + len(remeshedCurveConn)
self.seamEnd[curveName] = len(finalConn) + len(remeshedCurveConnFull)

# Output the feature curves
if self.comm.rank == 0 and self.debug:
curvename = f"featureCurves_{self.counter}"
tecplot_interface.writeTecplotFEdata(remeshedCurves, remeshedCurveConn, curvename, curvename)
tecplot_interface.writeTecplotFEdata(remeshedCurves, remeshedCurveConnFull, curvename, curvename)

# now we are done going over curves,
# so we can append all the new curves to the "seam",
# which now contains the intersection, and re-meshed feature curves

# increment the conn from curves
remeshedCurveConn += len(seam)
remeshedCurveConnFull += len(seam)
remeshedCurveConnWarp += len(seam)
# stack the nodes
seam = np.vstack((seam, remeshedCurves))
# stack the conn
finalConn = np.vstack((finalConn, remeshedCurveConn))
finalConnFull = np.vstack((finalConn, remeshedCurveConnFull))
finalConnWarp = np.vstack((finalConn, remeshedCurveConnWarp))

# save the connectivity
self.seamConn = finalConn
self.seamConnFull = finalConnFull
self.seamConnWarp = finalConnWarp

self.counter += 1

Expand Down
133 changes: 133 additions & 0 deletions tests/reg_tests/test_DVGeometryMulti.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,139 @@ def twist(val, geo, nRefAxPts=nRefAxPts):
with self.assertRaises(Error):
DVGeo.addPointSet(np.array([[-1.0, 0.0, 0.0]]), "test_error")

def test_slidingCurves(self):
# box1 and box2 intersect
comps = ["box1", "box2"]
ffdFiles = [os.path.join(inputDir, f"{comp}.xyz") for comp in comps]
triMeshFiles = [os.path.join(inputDir, f"{comp}.cgns") for comp in comps]

# Define the communicator
comm = MPI.COMM_WORLD

# Set up real component DVGeo objects
DVGeoBox1 = DVGeometry(ffdFiles[0])
DVGeoBox2 = DVGeometry(ffdFiles[1])

# Set up real DVGeometryMulti object
DVGeo = DVGeometryMulti(comm=comm)
DVGeo.addComponent("box1", DVGeoBox1, triMeshFiles[0])
DVGeo.addComponent("box2", DVGeoBox2, triMeshFiles[1])

# Define some feature curves
featureCurves = {
# Curves on box1
"part_22_1d": None,
"part_23_1d": None,
# Curves on box2
"part_35_1d": 1,
"part_37_1d": 1,
"part_39_1d": 1,
}
curveEpsDict = {
# Curves on box1
"part_22_1d": 1e-3,
"part_23_1d": 1e-3,
# Curves on box2
"part_35_1d": 1e-3,
"part_37_1d": 1e-3,
"part_39_1d": 1e-3,
# Intersection curve
"intersection": 1e-3,
}

slidingCurves = [
# Curves on box1
"part_22_1d",
"part_23_1d",
]

# Define a name for the point set
ptSetName = "test_set"

# Define a test point set
pts = np.array(
[
[1.0, -0.4, 0.5], # curve 22
[1.0, -0.2, 0.5], # curve 22
[1.0, 0.2, 0.5], # curve 23
[1.0, 0.4, 0.5], # curve 23
]
)

# Compute the processor sizes with integer division
sizes = np.zeros(comm.size, dtype="intc")
nPtsGlobal = pts.shape[0]
sizes[:] = nPtsGlobal // comm.size

# Add the leftovers
sizes[: nPtsGlobal % comm.size] += 1

# Compute the processor displacements
disp = np.zeros(comm.size + 1, dtype="intc")
disp[1:] = np.cumsum(sizes)

# Split up the point set
localPts = pts[disp[comm.rank] : disp[comm.rank + 1]]

# Add the intersection between box1 and box2
DVGeo.addIntersection(
"box1",
"box2",
dStarA=1.0,
dStarB=0.15,
featureCurves=featureCurves,
project=True,
includeCurves=True,
slidingCurves=slidingCurves,
curveEpsDict=curveEpsDict,
)

# Add a few design variables
DVGeoDict = DVGeo.getDVGeoDict()
for comp in comps:
# Create reference axis
nRefAxPts = DVGeoDict[comp].addRefAxis("box", xFraction=0.5, alignIndex="j", rotType=4)
nTwist = nRefAxPts - 1

# Set up a twist variable
def twist(val, geo, nRefAxPts=nRefAxPts):
for i in range(1, nRefAxPts):
geo.rot_z["box"].coef[i] = val[i - 1]

DVGeoDict[comp].addGlobalDV(dvName=f"{comp}_twist", value=[0] * nTwist, func=twist)

# Add the point set
DVGeo.addPointSet(localPts, ptSetName, comm=comm, applyIC=True)

# Apply twist to box 2
dvDict = DVGeo.getValues()
dvDict["box2_twist"] = 10
DVGeo.setDesignVars(dvDict)

# Update the point set
ptsUpdated = DVGeo.update(ptSetName)

# Create the send buffer
procPoints = ptsUpdated.flatten()
sendbuf = [procPoints, sizes[comm.rank] * 3]

# Create the receiving buffer
globalPoints = np.zeros(nPtsGlobal * 3)
recvbuf = [globalPoints, sizes * 3, disp[0:-1] * 3, MPI.DOUBLE]

# Allgather the updated coordinates
comm.Allgatherv(sendbuf, recvbuf)

# Reshape into a nPtsGlobal, 3 array
ptsUpdated = globalPoints.reshape((nPtsGlobal, 3))

# Test that the X and Z coordinates are unchanged and Y coordinates are changed
np.testing.assert_allclose(pts[:, 0], ptsUpdated[:, 0], rtol=1e-10, atol=1e-10)
np.testing.assert_allclose(pts[:, 2], ptsUpdated[:, 2], rtol=1e-10, atol=1e-10)

with self.assertRaises(AssertionError):
np.testing.assert_allclose(pts[:, 1], ptsUpdated[:, 1], rtol=1e-4, atol=1e-10)


@unittest.skipUnless(pysurfInstalled, "requires pySurf")
class TestDVGeoMultiEdgeCases(unittest.TestCase):
Expand Down

0 comments on commit 9bbcdf0

Please sign in to comment.