Skip to content

Commit

Permalink
Embedding tolerance update (#116)
Browse files Browse the repository at this point in the history
Co-authored-by: Eirikur Jonsson <eirikurj@umich.edu>
Co-authored-by: sseraj <sseraj@umich.edu>
  • Loading branch information
3 people authored Mar 2, 2022
1 parent 743c55c commit 80cf705
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 45 deletions.
38 changes: 26 additions & 12 deletions pygeo/parameterization/DVGeo.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,25 @@ class DVGeometry:
filename of FFD file. This must be a ascii formatted plot3D file
in fortran ordering.
complex : bool
isComplex : bool
Make the entire object complex. This should **only** be used when
debugging the entire tool-chain with the complex step method.
child : bool
Flag to indicate that this object is a child of parent DVGeo object
faceFreeze : dict
A dictionary of lists of strings specifying which faces should be
'frozen'. Each dictionary represents one block in the FFD.
For example if faceFreeze =['0':['iLow'],'1':[]], then the
plane of control points corresponding to i=0, and i=1, in block '0'
will not be able to move in DVGeometry.
name : str
This is prepended to every DV name for ensuring design variables names are
unique to pyOptsparse. Only useful when using multiple DVGeos with
TriangulatedSurfaceConstraint()
kmax : int
maximum order of the splines used for the underlying formulation.
Default is a 4th order spline in each direction if the dimensions
Expand Down Expand Up @@ -476,6 +488,7 @@ def addRefAxis(

# Count total number of sections and check if volumes are aligned
# face to face along refaxis direction
# Local indices size is (N_x,N_y,N_z)
lIndex = self.FFD.topo.lIndex
nSections = []
for i in range(len(volOrd)):
Expand All @@ -489,7 +502,7 @@ def addRefAxis(
# Loop through sections and compute node location
place = 0
for j, vol in enumerate(volOrd):
# sectionArr: indices of FFD points grouped by section
# sectionArr: indices of FFD points grouped by section - i.e. the first tensor index now == nSections
sectionArr = np.rollaxis(lIndex[vol], alignIndex, 0)
skip = 0
if j > 0:
Expand Down Expand Up @@ -600,6 +613,9 @@ def addPointSet(self, points, ptName, origConfig=True, **kwargs):
"""

# compNames is only needed for DVGeometryMulti, so remove it if passed
kwargs.pop("compNames", None)

# save this name so that we can zero out the jacobians properly
self.ptSetNames.append(ptName)
self.zeroJacobians([ptName])
Expand All @@ -618,7 +634,7 @@ def addPointSet(self, points, ptName, origConfig=True, **kwargs):
if self.isChild:
self.FFD.attachPoints(self.points[ptName], ptName, interiorOnly=True, **kwargs)
else:
self.FFD.attachPoints(self.points[ptName], ptName, interiorOnly=False)
self.FFD.attachPoints(self.points[ptName], ptName, interiorOnly=False, **kwargs)

if origConfig:
self.FFD.coef = tmpCoef
Expand Down Expand Up @@ -1513,21 +1529,19 @@ def updateCalculations(self, new_pts, isComplex, config):
rotType = self.axis[self.curveIDNames[ipt]]["rotType"]
if rotType == 0:
bp_ = np.copy(base_pt) # copy of original pointset - will not be rotated
if isinstance(ang, (float, int)): # rotation active only if a non-default value is provided
ang *= np.pi / 180 # conv to [rad]
# Rotating the FFD according to inputs
# The FFD points should now be aligned with the main system of reference
base_pt = geo_utils.rotVbyW(bp_, ax_dir, ang)

deriv = self.refAxis.curves[self.curveIDs[ipt]].getDerivative(self.links_s[ipt])
deriv /= geo_utils.euclideanNorm(deriv) # Normalize
new_vec = -np.cross(deriv, self.links_n[ipt])

if isComplex:
new_pts[ipt] = bp_ + new_vec * scale # using "unrotated" bp_ vector
else:
new_pts[ipt] = np.real(bp_ + new_vec * scale)

if isinstance(ang, (float, int)):
# Rotating to be aligned with main sys ref
if isinstance(ang, (float, int)): # rotation active only if a non-default value is provided
ang *= np.pi / 180 # conv to [rad]
# Rotating the FFD according to inputs to be aligned with main sys ref
nv_ = np.copy(new_vec)
new_vec = geo_utils.rotVbyW(nv_, ax_dir, ang)

Expand Down Expand Up @@ -2929,10 +2943,11 @@ def _finalize(self):
self.links_x.append(self.ptAttach[i] - self.refAxis.curves[self.curveIDs[i]](s[i]))
deriv = self.refAxis.curves[self.curveIDs[i]].getDerivative(self.links_s[i])
deriv /= geo_utils.euclideanNorm(deriv) # Normalize
self.links_n.append(np.cross(deriv, self.links_x[-1]))
self.links_n.append(np.cross(deriv, self.links_x[-1])) # using the element just appended to self.links_x

self.links_x = np.array(self.links_x)
self.links_s = np.array(self.links_s)
self.links_n = np.array(self.links_n)
self.finalized = True

def _setInitialValues(self):
Expand Down Expand Up @@ -4100,7 +4115,6 @@ def checkDerivatives(self, ptSetName):

if abs(relErr) > h and abs(absErr) > h:
print(ii, deriv[ii], Jac[DVCountSpanLoc, ii], relErr, absErr)
# print(ii, deriv[ii], Jac[DVCountSpanLoc, ii], relErr, absErr)

DVCountSpanLoc += 1
self.DV_listSpanwiseLocal[key].value[j] = refVal
Expand Down
56 changes: 24 additions & 32 deletions pygeo/pyBlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ def getAttachedPoints(self, ptSetName):
# Embedded Geometry Functions
# ----------------------------------------------------------------------

def attachPoints(self, coordinates, ptSetName, interiorOnly=False, faceFreeze=None, eps=1e-12, **kwargs):
def attachPoints(self, coordinates, ptSetName, interiorOnly=False, embTol=1e-10, nIter=100, eps=1e-12):
"""Embed a set of coordinates into the volumes. This is the
main high level function that is used by DVGeometry when
pyBlock is used as an FFD.
Expand All @@ -785,45 +785,39 @@ def attachPoints(self, coordinates, ptSetName, interiorOnly=False, faceFreeze=No
The name given to this set of coordinates.
interiorOnly : bool
Project only points that lie fully inside the volume
faceFreeze :
A dictionary of lists of strings specifying which faces should be
'frozen'. Each dictionary represents one block in the FFD.
This is only used with child FFD's in DVGeometry.
For example if faceFreeze =['0':['iLow'],'1':[]], then the
plane of control points corresponding to i=0, and i=1, in block '0'
will not be able to move in DVGeometry.
embTol : float
Tolerance on the distance between projected and closest point.
Determines if a point is embedded or not in the FFD volume if interiorOnly is True.
eps : float
Physical tolerance to which to converge Newton search
kwargs : dict
kwargs pass through to the actual projectPoints() function
"""
nIter : int
Maximum number of Newton iterations to perform. The default of 100 should be sufficient for points
that **actually** lie inside the volume, except for pathological or degenerate FFD volumes.
# Unpack kwargs
nIter = kwargs.get("nIter", 100)
"""

# Project Points, if some were actually passed in:
if coordinates is not None:
if not interiorOnly:
volID, u, v, w, D = self.projectPoints(coordinates, checkErrors=True, eps=eps, nIter=nIter)
self.embeddedVolumes[ptSetName] = EmbeddedVolume(volID, u, v, w)
else:
volID, u, v, w, D = self.projectPoints(coordinates, checkErrors=False, eps=eps, nIter=nIter)
checkErrors = not interiorOnly
mask = None
volID, u, v, w, D = self.projectPoints(coordinates, checkErrors, embTol, eps, nIter)

if interiorOnly:
# Create the mask before creating the embedded volume
mask = []
for i in range(len(D)):
Dnrm = np.linalg.norm(D[i])
if Dnrm < 50 * eps: # Sufficiently inside
if Dnrm < embTol: # Sufficiently inside
mask.append(i)

# Now that we have the mask we can create the embedded volume
self.embeddedVolumes[ptSetName] = EmbeddedVolume(volID, u, v, w, mask)
self.embeddedVolumes[ptSetName] = EmbeddedVolume(volID, u, v, w, mask)
# end if (Coordinate not none check)

# ----------------------------------------------------------------------
# Geometric Functions
# ----------------------------------------------------------------------

def projectPoints(self, x0, eps=1e-12, checkErrors=True, nIter=100):
def projectPoints(self, x0, checkErrors, embTol, eps, nIter):
"""Project a set of points x0, into any one of the volumes. It
returns the the volume ID, u, v, w, D of the point in volID or
closest to it.
Expand All @@ -840,16 +834,14 @@ def projectPoints(self, x0, eps=1e-12, checkErrors=True, nIter=100):
----------
x0 : array of points (Nx3 array)
The list or array of points to use
eps : float
Physical tolerance to which to converge Newton search
checkErrors : bool
Flag to print out the error is points have not been projected
to tolerance eps.
nIter : int
Maximum number of Newton iterations to perform. The
default of 100 should be sufficient for points that
**actually** lie inside the volume, except for
pathological or degenerate FFD volumes
to the tolerance defined by embTol.
See Also
--------
See the attachPoints() docstring for the other parameters.
"""

# Make sure we are dealing with a 2D "Nx3" list of points
Expand Down Expand Up @@ -886,7 +878,7 @@ def projectPoints(self, x0, eps=1e-12, checkErrors=True, nIter=100):

# Now, if D0 is close enough to our tolerance, we can
# exit the loop since we know we won't do any better
if D0Norm < eps * 50:
if D0Norm < embTol:
break
# end for (volume loop)

Expand All @@ -910,7 +902,7 @@ def projectPoints(self, x0, eps=1e-12, checkErrors=True, nIter=100):
DMax = nrm

DRms += nrm ** 2
if nrm > eps * 50:
if nrm > embTol:
counter += 1
badPts.append([x0[i], D[i]])

Expand Down
2 changes: 1 addition & 1 deletion tests/reg_tests/commonUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def testSensitivitiesD8(DVGeo, refDeriv, handler):

# add points to the geometry object
ptName = "testPoints"
DVGeo.addPointSet(points, ptName, faceFreeze={})
DVGeo.addPointSet(points, ptName)

# generate dIdPt
nPt = nPoints * 3
Expand Down

0 comments on commit 80cf705

Please sign in to comment.