From d8297ef3d5e7f706823958d292978eb81ac79511 Mon Sep 17 00:00:00 2001 From: sseraj Date: Thu, 8 Aug 2024 16:36:32 -0600 Subject: [PATCH 01/17] fixed CST TE thickness --- pygeo/parameterization/DVGeoCST.py | 54 +++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 052ffb24..8224ad78 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -213,7 +213,8 @@ def __init__( # Save the upper and lower trailing edge coordinates if it is not sharp if self.sharp: - self.thicknessTE = np.array([0.0]) + self.yUpperTE = np.array([0.0]) + self.yLowerTE = np.array([0.0]) else: if self.foilCoords[cornerIdx[0], self.yIdx] > self.foilCoords[cornerIdx[1], self.yIdx]: self.coordUpperTE = self.foilCoords[cornerIdx[0]] @@ -221,7 +222,9 @@ def __init__( else: self.coordUpperTE = self.foilCoords[cornerIdx[1]] self.coordLowerTE = self.foilCoords[cornerIdx[0]] - self.thicknessTE = self.coordUpperTE[self.yIdx] - self.coordLowerTE[self.yIdx] + + self.yUpperTE = self.coordUpperTE[self.yIdx] + self.yLowerTE = self.coordLowerTE[self.yIdx] # Compute splines for the upper and lower surfaces (used to split the foil in addPointSet). # preFoil defines the leading edge as the point furthest from the trailing edge @@ -237,9 +240,20 @@ def __init__( print(f"######## Fitting CST coefficients to coordinates in {datFile} ########") for dvType in ["upper", "lower"]: if self.comm.rank == 0: + xPts = self.foilCoords[self.idxFoil[dvType], self.xIdx] + yPts = self.foilCoords[self.idxFoil[dvType], self.yIdx] + + if dvType == "upper": + yTE = self.yUpperTE + else: + yTE = self.yLowerTE + + # Subtract the linear TE thickness function + yMinusTE = yPts - yTE * xPts + self.defaultDV[dvType] = self.computeCSTfromCoords( - self.foilCoords[self.idxFoil[dvType], self.xIdx], - self.foilCoords[self.idxFoil[dvType], self.yIdx], + xPts, + yMinusTE, self.defaultDV[dvType].size, N1=self.defaultDV[f"n1_{dvType}"], N2=self.defaultDV[f"n2_{dvType}"], @@ -247,8 +261,6 @@ def __init__( ) # Compute the quality of the fit by computing an L2 norm of the fit vs. the actual coordinates - xPts = self.foilCoords[self.idxFoil[dvType], self.xIdx] - yTE = self.thicknessTE / 2 if dvType == "upper" else -self.thicknessTE / 2 ptsFit = chord * self.computeCSTCoordinates( xPts / chord, self.defaultDV["n1_lower"], @@ -257,9 +269,7 @@ def __init__( yTE, dtype=self.dtype, ) - L2norm = np.sqrt( - 1 / ptsFit.size * np.sum((self.foilCoords[self.idxFoil[dvType], self.yIdx] - ptsFit) ** 2) - ) + L2norm = np.sqrt(1 / ptsFit.size * np.sum((yPts - ptsFit) ** 2)) print(f"{dvType.capitalize()} surface") print(f" L2 norm of coordinates in dat file versus fit coordinates: {L2norm}") @@ -306,7 +316,8 @@ def addPointSet(self, points, ptName, boundTol=1e-10, **kwargs): "points": points, "xMax": self.xMax.copy(), "xMin": self.xMin.copy(), - "thicknessTE": self.thicknessTE.copy(), + "yUpperTE": self.yUpperTE.copy(), + "yLowerTE": self.yLowerTE.copy(), } # Determine which points are on the upper and lower surfaces @@ -824,19 +835,31 @@ def update(self, ptSetName, **kwargs): ptsY = points[:, self.yIdx] xMax = self.points[ptSetName]["xMax"] xMin = self.points[ptSetName]["xMin"] - thicknessTE = self.points[ptSetName]["thicknessTE"] + yUpperTE = self.points[ptSetName]["yUpperTE"] + yLowerTE = self.points[ptSetName]["yLowerTE"] # Scale the airfoil to the range 0 to 1 in x direction shift = xMin chord = xMax - xMin scaledX = (ptsX - shift) / chord - yTE = thicknessTE / chord / 2 # half the scaled trailing edge thickness + scaledYUpperTE = yUpperTE / chord + scaledYLowerTE = yLowerTE / chord ptsY[idxUpper] = desVars["chord"] * self.computeCSTCoordinates( - scaledX[idxUpper], desVars["n1_upper"], desVars["n2_upper"], desVars["upper"], yTE, dtype=self.dtype + scaledX[idxUpper], + desVars["n1_upper"], + desVars["n2_upper"], + desVars["upper"], + scaledYUpperTE, + dtype=self.dtype, ) ptsY[idxLower] = desVars["chord"] * self.computeCSTCoordinates( - scaledX[idxLower], desVars["n1_lower"], desVars["n2_lower"], desVars["lower"], -yTE, dtype=self.dtype + scaledX[idxLower], + desVars["n1_lower"], + desVars["n2_lower"], + desVars["lower"], + scaledYLowerTE, + dtype=self.dtype, ) ptsY[idxTE] *= desVars["chord"] / chord @@ -845,7 +868,8 @@ def update(self, ptSetName, **kwargs): # Scale the point set's properties based on the new chord length self.points[ptSetName]["xMax"] = (xMax - shift) * desVars["chord"] / chord + shift - self.points[ptSetName]["thicknessTE"] *= desVars["chord"] / chord + self.points[ptSetName]["yUpperTE"] *= desVars["chord"] / chord + self.points[ptSetName]["yLowerTE"] *= desVars["chord"] / chord self.updated[ptSetName] = True From 52f942e34b36f8456828c290b8b3780f1a3173a3 Mon Sep 17 00:00:00 2001 From: sseraj Date: Thu, 8 Aug 2024 16:38:19 -0600 Subject: [PATCH 02/17] updated CST tests --- tests/reg_tests/test_DVGeometryCST.py | 42 ++++++++++++++++++--------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index 274e524f..a9b9a024 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -180,7 +180,8 @@ def test_addPointSet_sorted(self): # that they'll be included in the upper and lower surface (which is ok) idxUpper = np.arange(1, idxLE + self.LEUpper) idxLower = np.arange(idxLE + self.LEUpper, coords.shape[0] - 1) - thickTE = coords[0, 1] - coords[-1, 1] + yUpperTE = coords[0, 1] + yLowerTE = coords[-1, 1] self.DVGeo.addPointSet(coords, "test") @@ -189,7 +190,8 @@ def test_addPointSet_sorted(self): self.assertIn(idx, self.DVGeo.points["test"]["upper"]) for idx in idxLower: self.assertIn(idx, self.DVGeo.points["test"]["lower"]) - np.testing.assert_equal(thickTE, self.DVGeo.points["test"]["thicknessTE"]) + np.testing.assert_equal(yUpperTE, self.DVGeo.points["test"]["yUpperTE"]) + np.testing.assert_equal(yLowerTE, self.DVGeo.points["test"]["yLowerTE"]) self.assertEqual(min(coords[:, 0]), self.DVGeo.points["test"]["xMin"]) self.assertEqual(max(coords[:, 0]), self.DVGeo.points["test"]["xMax"]) @@ -200,7 +202,8 @@ def test_addPointSet_randomized(self): idxLE = np.argmin(coords[:, 0]) idxUpper = np.arange(0, idxLE + self.LEUpper) idxLower = np.arange(idxLE + self.LEUpper, coords.shape[0]) - thickTE = coords[0, 1] - coords[-1, 1] + yUpperTE = coords[0, 1] + yLowerTE = coords[-1, 1] # Randomize the index order (do indices so we can track where they end up) rng = np.random.default_rng(1) @@ -223,7 +226,8 @@ def test_addPointSet_randomized(self): for idx in idxLowerRand: if idx != idxShuffle[-1]: self.assertIn(idx, self.DVGeo.points["test"]["lower"]) - np.testing.assert_equal(thickTE, self.DVGeo.points["test"]["thicknessTE"]) + np.testing.assert_equal(yUpperTE, self.DVGeo.points["test"]["yUpperTE"]) + np.testing.assert_equal(yLowerTE, self.DVGeo.points["test"]["yLowerTE"]) self.assertEqual(min(coords[:, 0]), self.DVGeo.points["test"]["xMin"]) self.assertEqual(max(coords[:, 0]), self.DVGeo.points["test"]["xMax"]) @@ -241,7 +245,8 @@ def test_addPointSet_bluntTE(self): # includes a blunt trailing edge with point # that they'll be included in the upper and lower surface (which is ok) idxUpper = np.arange(1, idxLE + self.LEUpper) idxLower = np.arange(idxLE + self.LEUpper, coords.shape[0] - nPointsTE + 2 - 1) - thickTE = coords[0, 1] - coords[coords.shape[0] - nPointsTE + 1, 1] + yUpperTE = coords[0, 1] + yLowerTE = coords[coords.shape[0] - nPointsTE + 1, 1] self.DVGeo.addPointSet(coords, "test") @@ -250,7 +255,8 @@ def test_addPointSet_bluntTE(self): # includes a blunt trailing edge with point self.assertIn(idx, self.DVGeo.points["test"]["upper"]) for idx in idxLower: self.assertIn(idx, self.DVGeo.points["test"]["lower"]) - np.testing.assert_equal(thickTE, self.DVGeo.points["test"]["thicknessTE"]) + np.testing.assert_equal(yUpperTE, self.DVGeo.points["test"]["yUpperTE"]) + np.testing.assert_equal(yLowerTE, self.DVGeo.points["test"]["yLowerTE"]) self.assertEqual(min(coords[:, 0]), self.DVGeo.points["test"]["xMin"]) self.assertEqual(max(coords[:, 0]), self.DVGeo.points["test"]["xMax"]) @@ -278,7 +284,8 @@ def test_addPointSet_sorted(self): isUpper = isUpper == 1 isLower = np.logical_not(isUpper) - thickTE = coords[0, 1] - coords[-1, 1] + yUpperTE = coords[0, 1] + yLowerTE = coords[-1, 1] # Divide up the points among the procs (mostly evenly, but not quite to check the harder case) if self.N_PROCS == 1: @@ -304,7 +311,8 @@ def test_addPointSet_sorted(self): for idx in idxLower: if idx != coords.shape[0] - 1: self.assertIn(idx, self.DVGeo.points["test"]["lower"]) - np.testing.assert_equal(thickTE, self.DVGeo.points["test"]["thicknessTE"]) + np.testing.assert_equal(yUpperTE, self.DVGeo.points["test"]["yUpperTE"]) + np.testing.assert_equal(yLowerTE, self.DVGeo.points["test"]["yLowerTE"]) self.assertEqual(min(coords[:, 0]), self.DVGeo.points["test"]["xMin"]) self.assertEqual(max(coords[:, 0]), self.DVGeo.points["test"]["xMax"]) @@ -319,7 +327,8 @@ def test_addPointSet_randomized(self): isUpper = isUpper == 1 isLower = np.logical_not(isUpper) - thickTE = coords[0, 1] - coords[-1, 1] + yUpperTE = coords[0, 1] + yLowerTE = coords[-1, 1] # Randomize the index order (do indices so we can track where they end up) rng = np.random.default_rng(1) @@ -379,7 +388,8 @@ def test_addPointSet_randomized(self): for idx in idxLower: if idx != idxEnd: self.assertIn(idx, self.DVGeo.points["test"]["lower"]) - np.testing.assert_equal(thickTE, self.DVGeo.points["test"]["thicknessTE"]) + np.testing.assert_equal(yUpperTE, self.DVGeo.points["test"]["yUpperTE"]) + np.testing.assert_equal(yLowerTE, self.DVGeo.points["test"]["yLowerTE"]) self.assertEqual(min(coords[:, 0]), self.DVGeo.points["test"]["xMin"]) self.assertEqual(max(coords[:, 0]), self.DVGeo.points["test"]["xMax"]) @@ -404,7 +414,8 @@ def test_addPointSet_sharp(self): # that they'll be included in the upper and lower surface (which is ok) idxUpper = np.arange(1, idxLE) idxLower = np.arange(idxLE, coords.shape[0] - 1) - thickTE = 0.0 + yUpperTE = 0.0 + yLowerTE = 0.0 DVGeo.addPointSet(coords, "test") @@ -413,7 +424,8 @@ def test_addPointSet_sharp(self): self.assertIn(idx, DVGeo.points["test"]["upper"]) for idx in idxLower: self.assertIn(idx, DVGeo.points["test"]["lower"]) - np.testing.assert_equal(thickTE, DVGeo.points["test"]["thicknessTE"]) + np.testing.assert_equal(yUpperTE, DVGeo.points["test"]["yUpperTE"]) + np.testing.assert_equal(yLowerTE, DVGeo.points["test"]["yLowerTE"]) self.assertEqual(min(coords[:, 0]), DVGeo.points["test"]["xMin"]) self.assertEqual(max(coords[:, 0]), DVGeo.points["test"]["xMax"]) self.assertTrue(DVGeo.sharp) @@ -433,7 +445,8 @@ def test_addPointSet_closed(self): # that they'll be included in the upper and lower surface (which is ok) idxUpper = np.arange(1, idxLE) idxLower = np.arange(idxLE, coords.shape[0] - 2) - thickTE = coords[0, 1] - coords[-2, 1] + yUpperTE = coords[0, 1] + yLowerTE = coords[-2, 1] DVGeo.addPointSet(coords, "test") @@ -442,7 +455,8 @@ def test_addPointSet_closed(self): self.assertIn(idx, DVGeo.points["test"]["upper"]) for idx in idxLower: self.assertIn(idx, DVGeo.points["test"]["lower"]) - np.testing.assert_equal(thickTE, DVGeo.points["test"]["thicknessTE"]) + np.testing.assert_equal(yUpperTE, DVGeo.points["test"]["yUpperTE"]) + np.testing.assert_equal(yLowerTE, DVGeo.points["test"]["yLowerTE"]) self.assertEqual(min(coords[:, 0]), DVGeo.points["test"]["xMin"]) self.assertEqual(max(coords[:, 0]), DVGeo.points["test"]["xMax"]) self.assertFalse(DVGeo.sharp) From e4449ca1758e5c0cd82f292fe39b106908e82bcc Mon Sep 17 00:00:00 2001 From: sseraj Date: Thu, 8 Aug 2024 16:44:10 -0600 Subject: [PATCH 03/17] fixed TE plotting --- pygeo/parameterization/DVGeoCST.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 8224ad78..f92ff63e 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -1217,7 +1217,7 @@ def computeCSTfromCoords(xCoord, yCoord, nCST, N1=0.5, N2=1.0, dtype=float): return w @staticmethod - def plotCST(upperCoeff, lowerCoeff, N1=0.5, N2=1.0, nPts=100, ax=None, **kwargs): + def plotCST(upperCoeff, lowerCoeff, yUpperTE, yLowerTE, N1=0.5, N2=1.0, nPts=100, ax=None, **kwargs): """Simple utility to generate a plot from CST coefficients. Parameters @@ -1226,6 +1226,10 @@ def plotCST(upperCoeff, lowerCoeff, N1=0.5, N2=1.0, nPts=100, ax=None, **kwargs) One dimensional array of CST coefficients for the upper surface. lowerCoeff : ndarray One dimensional array of CST coefficients for the lower surface. + yUpperTE : float + y-coordinate for the upper surface trailing edge point. + yLowerTE : float + y-coordinate for the lower surface trailing edge point. N1 : float First class shape parameter. N2 : float @@ -1250,8 +1254,8 @@ def plotCST(upperCoeff, lowerCoeff, N1=0.5, N2=1.0, nPts=100, ax=None, **kwargs) ax = plt.gca() x = np.linspace(0, 1, nPts) - yUpper = DVGeometryCST.computeCSTCoordinates(x, N1, N2, upperCoeff, 0.0) - yLower = DVGeometryCST.computeCSTCoordinates(x, N1, N2, lowerCoeff, 0.0) + yUpper = DVGeometryCST.computeCSTCoordinates(x, N1, N2, upperCoeff, yUpperTE) + yLower = DVGeometryCST.computeCSTCoordinates(x, N1, N2, lowerCoeff, yLowerTE) ax.plot(x, yUpper, **kwargs) ax.plot(x, yLower, **kwargs) From 8c20af09032e4924aa8dc83c3892c92814166850 Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 9 Aug 2024 11:10:51 -0600 Subject: [PATCH 04/17] updated plotCST test --- tests/reg_tests/test_DVGeometryCST.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index a9b9a024..afcdb18c 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -608,7 +608,7 @@ class TestFunctionality(unittest.TestCase): """ def test_plotCST(self): - DVGeometryCST.plotCST(np.ones(4), np.ones(3)) + DVGeometryCST.plotCST(np.ones(4), np.ones(3), 1e-3, 1e-3) def test_print(self): curDir = os.path.abspath(os.path.dirname(__file__)) From bd023ee9c8d8da552c5b6cf5211ad49e524d7a04 Mon Sep 17 00:00:00 2001 From: sseraj Date: Fri, 9 Aug 2024 11:26:50 -0600 Subject: [PATCH 05/17] added option to manually specify idxFoil --- pygeo/parameterization/DVGeoCST.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index f92ff63e..9b5453f7 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -74,6 +74,9 @@ class DVGeometryCST(BaseDVGeometry): Index of the column in the point set to use as the chordwise (x in CST) coordinates, by default 0 idxVertical : int, optional Index of the column in the point set to use as the vertical (y in CST) airfoil coordinates, by default 1 + idxFoil : dict, optional + Dictionary with keys ``"upper"`` and ``"lower"`` specifying the coordinate indices (rows of the dat file) + that correspond to the upper and lower surfaces. If None, these indices are determined using a spline fit. comm : MPI communicator, optional Communicator for DVGeometryCST instance, by default MPI.COMM_WORLD isComplex : bool, optional @@ -94,6 +97,7 @@ def __init__( numCST=8, idxChord=0, idxVertical=1, + idxFoil=None, comm=MPI.COMM_WORLD, isComplex=False, debug=False, @@ -107,6 +111,7 @@ def __init__( super().__init__(datFile, name=name) self.xIdx = idxChord self.yIdx = idxVertical + self.idxFoil = idxFoil self.comm = comm self.isComplex = isComplex if isComplex: @@ -232,8 +237,9 @@ def __init__( self.upperSpline, self.lowerSpline = self.foil.splitAirfoil() # Fit CST parameters to the airfoil's upper and lower surface - self.idxFoil = {} - self.idxFoil["upper"], self.idxFoil["lower"] = self._splitUpperLower(self.foilCoords) + if self.idxFoil is None: + self.idxFoil = {} + self.idxFoil["upper"], self.idxFoil["lower"] = self._splitUpperLower(self.foilCoords) chord = self.xMax - self.xMin self.defaultDV["chord"][0] = chord if self.comm.rank == 0: From f92ea60ce49e09900e9bbf79b5360d9ed0417d3e Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 13 Aug 2024 16:38:16 -0600 Subject: [PATCH 06/17] moved thickness subtraction, fixed fit error computation --- pygeo/parameterization/DVGeoCST.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 9b5453f7..4b34bfea 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -254,12 +254,10 @@ def __init__( else: yTE = self.yLowerTE - # Subtract the linear TE thickness function - yMinusTE = yPts - yTE * xPts - self.defaultDV[dvType] = self.computeCSTfromCoords( xPts, - yMinusTE, + yPts, + yTE, self.defaultDV[dvType].size, N1=self.defaultDV[f"n1_{dvType}"], N2=self.defaultDV[f"n2_{dvType}"], @@ -268,11 +266,11 @@ def __init__( # Compute the quality of the fit by computing an L2 norm of the fit vs. the actual coordinates ptsFit = chord * self.computeCSTCoordinates( - xPts / chord, + (xPts - self.xMin) / chord, self.defaultDV["n1_lower"], self.defaultDV["n2_lower"], self.defaultDV[dvType], - yTE, + yTE / chord, dtype=self.dtype, ) L2norm = np.sqrt(1 / ptsFit.size * np.sum((yPts - ptsFit) ** 2)) @@ -1183,7 +1181,7 @@ def computeCSTdydN2(x, N1, N2, w, dtype=float): return dydN2 @staticmethod - def computeCSTfromCoords(xCoord, yCoord, nCST, N1=0.5, N2=1.0, dtype=float): + def computeCSTfromCoords(xCoord, yCoord, yTE, nCST, N1=0.5, N2=1.0, dtype=float): """ Compute the CST coefficients that fit a set of airfoil coordinates (either for the upper or lower surface, not both). @@ -1215,11 +1213,15 @@ def computeCSTfromCoords(xCoord, yCoord, nCST, N1=0.5, N2=1.0, dtype=float): # Normalize x and y chord = np.max(xCoord) - np.min(xCoord) xCoord = (xCoord - np.min(xCoord)) / chord - yCoord /= chord + yCoord = yCoord / chord + yTE = yTE / chord + + # Subtract the linear TE thickness function + yMinusTE = yCoord - yTE * xCoord # Compute the coefficients via linear least squares dydw = DVGeometryCST.computeCSTdydw(xCoord, N1, N2, np.ones(nCST), dtype=dtype) - w = np.linalg.lstsq(dydw.T, yCoord, rcond=None)[0] + w = np.linalg.lstsq(dydw.T, yMinusTE, rcond=None)[0] return w @staticmethod From 9d0ed72b0bf74f66ed93d61f2b3486de2f464587 Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 13 Aug 2024 16:38:44 -0600 Subject: [PATCH 07/17] updated CST fit tests --- tests/reg_tests/test_DVGeometryCST.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index afcdb18c..87e3eacf 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -131,17 +131,22 @@ def test_fitCST(self): idxLE = np.argmin(coords[:, 0]) idxUpper = np.arange(0, idxLE + self.LEUpper) idxLower = np.arange(idxLE + self.LEUpper, coords.shape[0]) - yTE = coords[0, 1] + yUpperTE = coords[0, 1] + yLowerTE = coords[-1, 1] N1 = 0.5 N2 = 1.0 for nCST in range(2, 10): # Fit the CST parameters and then compute the coordinates # with those parameters and check that it's close - upperCST = DVGeometryCST.computeCSTfromCoords(coords[idxUpper, 0], coords[idxUpper, 1], nCST, N1=N1, N2=N2) - lowerCST = DVGeometryCST.computeCSTfromCoords(coords[idxLower, 0], coords[idxLower, 1], nCST, N1=N1, N2=N2) - fitCoordsUpper = DVGeometryCST.computeCSTCoordinates(coords[idxUpper, 0], N1, N2, upperCST, yTE) - fitCoordsLower = DVGeometryCST.computeCSTCoordinates(coords[idxLower, 0], N1, N2, lowerCST, -yTE) + upperCST = DVGeometryCST.computeCSTfromCoords( + coords[idxUpper, 0], coords[idxUpper, 1], yUpperTE, nCST, N1=N1, N2=N2 + ) + lowerCST = DVGeometryCST.computeCSTfromCoords( + coords[idxLower, 0], coords[idxLower, 1], yLowerTE, nCST, N1=N1, N2=N2 + ) + fitCoordsUpper = DVGeometryCST.computeCSTCoordinates(coords[idxUpper, 0], N1, N2, upperCST, yUpperTE) + fitCoordsLower = DVGeometryCST.computeCSTCoordinates(coords[idxLower, 0], N1, N2, lowerCST, yLowerTE) # Loosen the tolerances for the challenging e63 airfoil if self.fName == "e63.dat": From a151519547559ff3a9e0dd6801dc8241b939045b Mon Sep 17 00:00:00 2001 From: sseraj Date: Thu, 15 Aug 2024 15:23:00 -0600 Subject: [PATCH 08/17] more normalization fixes --- pygeo/parameterization/DVGeoCST.py | 3 ++- tests/reg_tests/test_DVGeometryCST.py | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 4b34bfea..eebaed9b 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -991,7 +991,8 @@ def computeCSTCoordinates(x, N1, N2, w, yte, dtype=float): """ Compute the vertical coordinates of a CST curve. - This function assumes x has been normalized to the range [0,1]. + This function assumes x has been normalized to the range [0,1] + and that yte has been normalized by the chord. Parameters ---------- diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index 87e3eacf..f4ffccc5 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -136,6 +136,13 @@ def test_fitCST(self): N1 = 0.5 N2 = 1.0 + # Normalize the x-coordinates + xMin = np.min(coords[:, 0]) + xMax = np.max(coords[:, 0]) + chord = xMax - xMin + xScaledUpper = (coords[idxUpper, 0] - xMin) / chord + xScaledLower = (coords[idxLower, 0] - xMin) / chord + for nCST in range(2, 10): # Fit the CST parameters and then compute the coordinates # with those parameters and check that it's close @@ -145,17 +152,21 @@ def test_fitCST(self): lowerCST = DVGeometryCST.computeCSTfromCoords( coords[idxLower, 0], coords[idxLower, 1], yLowerTE, nCST, N1=N1, N2=N2 ) - fitCoordsUpper = DVGeometryCST.computeCSTCoordinates(coords[idxUpper, 0], N1, N2, upperCST, yUpperTE) - fitCoordsLower = DVGeometryCST.computeCSTCoordinates(coords[idxLower, 0], N1, N2, lowerCST, yLowerTE) + fitCoordsUpper = ( + DVGeometryCST.computeCSTCoordinates(xScaledUpper, N1, N2, upperCST, yUpperTE / chord) * chord + ) + fitCoordsLower = ( + DVGeometryCST.computeCSTCoordinates(xScaledLower, N1, N2, lowerCST, yLowerTE / chord) * chord + ) # Loosen the tolerances for the challenging e63 airfoil if self.fName == "e63.dat": if nCST < 4: - atol = 1e-1 - rtol = 1.0 + atol = 5e-3 + rtol = 1e-1 else: - atol = 1e-2 - rtol = 6e-1 + atol = 2e-3 + rtol = 1e-1 else: atol = 1e-3 rtol = 1e-1 From 1d83aa3395190c00e28daa9f4053dd4c0d6f73cc Mon Sep 17 00:00:00 2001 From: sseraj Date: Wed, 21 Aug 2024 14:50:18 -0600 Subject: [PATCH 09/17] set idxFoil directly from geometry --- pygeo/parameterization/DVGeoCST.py | 58 +++++++++++++++++------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index eebaed9b..85f195a2 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -74,9 +74,6 @@ class DVGeometryCST(BaseDVGeometry): Index of the column in the point set to use as the chordwise (x in CST) coordinates, by default 0 idxVertical : int, optional Index of the column in the point set to use as the vertical (y in CST) airfoil coordinates, by default 1 - idxFoil : dict, optional - Dictionary with keys ``"upper"`` and ``"lower"`` specifying the coordinate indices (rows of the dat file) - that correspond to the upper and lower surfaces. If None, these indices are determined using a spline fit. comm : MPI communicator, optional Communicator for DVGeometryCST instance, by default MPI.COMM_WORLD isComplex : bool, optional @@ -97,7 +94,6 @@ def __init__( numCST=8, idxChord=0, idxVertical=1, - idxFoil=None, comm=MPI.COMM_WORLD, isComplex=False, debug=False, @@ -111,7 +107,6 @@ def __init__( super().__init__(datFile, name=name) self.xIdx = idxChord self.yIdx = idxVertical - self.idxFoil = idxFoil self.comm = comm self.isComplex = isComplex if isComplex: @@ -169,21 +164,34 @@ def __init__( self.foilCoords[:, self.xIdx] = coords[:, 0] self.foilCoords[:, self.yIdx] = coords[:, 1] + # Determine if the dat file is closed (first and last points are the same); remove the duplicate point if so + distance = np.linalg.norm(self.foilCoords[0, :] - self.foilCoords[-1, :]) + distTol = 1e-12 + if distance < distTol: + self.foilCoords = self.foilCoords[:-1, :] + + # Flip the y-coordinates to counter-clockwise if they are clockwise + if self.foilCoords[0, self.yIdx] < self.foilCoords[-1, self.yIdx]: + self.foilCoords = np.flip(self.foilCoords, self.yIdx) + # Set the leading and trailing edge x coordinates self.xMin = np.min(self.foilCoords[:, self.xIdx]) self.xMax = np.max(self.foilCoords[:, self.xIdx]) + # The airfoil file can have one or two points at the minimum x-coordinate + idxLE = np.where(self.foilCoords[:, self.xIdx] == self.xMin)[0] + if len(idxLE) > 2: + raise ValueError(f"There can only be one or two points at the minimum x-coordinate, not {len(idxLE)}") + # Check that the leading edge is at y = 0 - idxLE = np.argmin(self.foilCoords[:, self.xIdx]) - yLE = self.foilCoords[idxLE, self.yIdx] - if abs(yLE) > 1e-2: - raise ValueError(f"Leading edge y (or idxVertical) value must equal zero, not {yLE}") + for idx in idxLE: + yLE = self.foilCoords[idx, self.yIdx] + if abs(yLE) > 1e-2: + raise ValueError(f"Leading edge y (or idxVertical) value must equal zero, not {yLE}") - # Determine if the dat file is closed (first and last points are the same); remove the duplicate point if so - distance = np.linalg.norm(self.foilCoords[0, :] - self.foilCoords[-1, :]) - distTol = 1e-12 - if distance < distTol: - self.foilCoords = self.foilCoords[:-1, :] + # If there is one leading edge point, add a duplicate index to make setting idxFoil easier + if len(idxLE) == 1: + idxLE = np.repeat(idxLE, 2) # Traverse the airfoil surface to find the corner(s) defining the trailing edge (ignore anything in the front # half, chordwise, of the airfoil) @@ -216,30 +224,32 @@ def __init__( # Airfoil is sharp if only one corner is detected self.sharp = len(cornerIdx) == 1 - # Save the upper and lower trailing edge coordinates if it is not sharp + # Save the trailing edge coordinates and surface indices + self.idxFoil = {} if self.sharp: self.yUpperTE = np.array([0.0]) self.yLowerTE = np.array([0.0]) + + self.idxFoil["upper"] = np.arange(cornerIdx[0], idxLE[0] + 1) + self.idxFoil["lower"] = np.concatenate( + (np.arange(idxLE[1], self.foilCoords.shape[0]), np.array([cornerIdx[0]])) + ) else: - if self.foilCoords[cornerIdx[0], self.yIdx] > self.foilCoords[cornerIdx[1], self.yIdx]: - self.coordUpperTE = self.foilCoords[cornerIdx[0]] - self.coordLowerTE = self.foilCoords[cornerIdx[1]] - else: - self.coordUpperTE = self.foilCoords[cornerIdx[1]] - self.coordLowerTE = self.foilCoords[cornerIdx[0]] + self.coordUpperTE = self.foilCoords[cornerIdx[0]] + self.coordLowerTE = self.foilCoords[cornerIdx[1]] self.yUpperTE = self.coordUpperTE[self.yIdx] self.yLowerTE = self.coordLowerTE[self.yIdx] + self.idxFoil["upper"] = np.arange(cornerIdx[0], idxLE[0] + 1) + self.idxFoil["lower"] = np.arange(idxLE[1], cornerIdx[1] + 1) + # Compute splines for the upper and lower surfaces (used to split the foil in addPointSet). # preFoil defines the leading edge as the point furthest from the trailing edge self.foil = Airfoil(coords) self.upperSpline, self.lowerSpline = self.foil.splitAirfoil() # Fit CST parameters to the airfoil's upper and lower surface - if self.idxFoil is None: - self.idxFoil = {} - self.idxFoil["upper"], self.idxFoil["lower"] = self._splitUpperLower(self.foilCoords) chord = self.xMax - self.xMin self.defaultDV["chord"][0] = chord if self.comm.rank == 0: From a13437c8e822b341ba7c7a79b4f9dcd3d74bccb3 Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 10:46:44 -0600 Subject: [PATCH 10/17] fixed flipping --- pygeo/parameterization/DVGeoCST.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 85f195a2..308a0c69 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -172,7 +172,7 @@ def __init__( # Flip the y-coordinates to counter-clockwise if they are clockwise if self.foilCoords[0, self.yIdx] < self.foilCoords[-1, self.yIdx]: - self.foilCoords = np.flip(self.foilCoords, self.yIdx) + self.foilCoords = np.flip(self.foilCoords, self.xIdx) # Set the leading and trailing edge x coordinates self.xMin = np.min(self.foilCoords[:, self.xIdx]) From 133f1658222cb938bac82f7e4c48be127cfdbae9 Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 11:04:43 -0600 Subject: [PATCH 11/17] moved airfoil dat files to input_files --- {tests/reg_tests => input_files}/e63.dat | 0 {tests/reg_tests => input_files}/naca0012.dat | 0 .../naca0012_closed.dat | 0 .../naca0012_sharp.dat | 0 {tests/reg_tests => input_files}/naca2412.dat | 0 tests/reg_tests/test_DVGeometryCST.py | 36 ++++++++----------- 6 files changed, 14 insertions(+), 22 deletions(-) rename {tests/reg_tests => input_files}/e63.dat (100%) rename {tests/reg_tests => input_files}/naca0012.dat (100%) rename {tests/reg_tests => input_files}/naca0012_closed.dat (100%) rename {tests/reg_tests => input_files}/naca0012_sharp.dat (100%) rename {tests/reg_tests => input_files}/naca2412.dat (100%) diff --git a/tests/reg_tests/e63.dat b/input_files/e63.dat similarity index 100% rename from tests/reg_tests/e63.dat rename to input_files/e63.dat diff --git a/tests/reg_tests/naca0012.dat b/input_files/naca0012.dat similarity index 100% rename from tests/reg_tests/naca0012.dat rename to input_files/naca0012.dat diff --git a/tests/reg_tests/naca0012_closed.dat b/input_files/naca0012_closed.dat similarity index 100% rename from tests/reg_tests/naca0012_closed.dat rename to input_files/naca0012_closed.dat diff --git a/tests/reg_tests/naca0012_sharp.dat b/input_files/naca0012_sharp.dat similarity index 100% rename from tests/reg_tests/naca0012_sharp.dat rename to input_files/naca0012_sharp.dat diff --git a/tests/reg_tests/naca2412.dat b/input_files/naca2412.dat similarity index 100% rename from tests/reg_tests/naca2412.dat rename to input_files/naca2412.dat diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index f4ffccc5..6c4a436f 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -23,6 +23,9 @@ except ImportError: prefoilImported = False +baseDir = os.path.dirname(os.path.abspath(__file__)) +inputDir = os.path.join(baseDir, "../../input_files") + # LEUpper is true if the leading edge (minimum x) point is considered to be on the upper surface airfoils = [ {"fName": "naca2412.dat", "LEUpper": False}, @@ -57,7 +60,6 @@ def setUp(self): self.x = np.linspace(0, 1, 100) self.yte = 1e-3 self.CS_delta = 1e-200 - self.curDir = os.path.abspath(os.path.dirname(__file__)) def test_ClassShape(self): """Test that for w_i = 1, the class shape has the expected shape""" @@ -126,7 +128,7 @@ def test_dydw(self): def test_fitCST(self): """Test the CST parameter fitting""" # Read in airfoil coordinates to test with and split up the surfaces - coords = readCoordFile(os.path.join(self.curDir, self.fName)) + coords = readCoordFile(os.path.join(inputDir, self.fName)) coords = np.hstack((coords, np.zeros((coords.shape[0], 1)))) idxLE = np.argmin(coords[:, 0]) idxUpper = np.arange(0, idxLE + self.LEUpper) @@ -182,8 +184,7 @@ class DVGeometryCSTPointSetSerial(unittest.TestCase): N_PROCS = 1 def setUp(self): - self.curDir = os.path.abspath(os.path.dirname(__file__)) - self.datFile = os.path.join(self.curDir, self.fName) + self.datFile = os.path.join(inputDir, self.fName) self.comm = MPI.COMM_WORLD self.DVGeo = DVGeometryCST(self.datFile, comm=self.comm) @@ -284,8 +285,7 @@ class DVGeometryCSTPointSetParallel(unittest.TestCase): N_PROCS = 4 def setUp(self): - self.curDir = os.path.abspath(os.path.dirname(__file__)) - self.datFile = os.path.join(self.curDir, self.fName) + self.datFile = os.path.join(inputDir, self.fName) self.comm = MPI.COMM_WORLD self.DVGeo = DVGeometryCST(self.datFile, comm=self.comm) @@ -416,8 +416,7 @@ class DVGeometryCSTSharpOrClosed(unittest.TestCase): N_PROCS = 1 def test_addPointSet_sharp(self): - curDir = os.path.abspath(os.path.dirname(__file__)) - datFile = os.path.join(curDir, "naca0012_sharp.dat") + datFile = os.path.join(inputDir, "naca0012_sharp.dat") comm = MPI.COMM_WORLD DVGeo = DVGeometryCST(datFile, comm=comm) @@ -447,8 +446,7 @@ def test_addPointSet_sharp(self): self.assertTrue(DVGeo.sharp) def test_addPointSet_closed(self): - curDir = os.path.abspath(os.path.dirname(__file__)) - datFile = os.path.join(curDir, "naca0012_closed.dat") + datFile = os.path.join(inputDir, "naca0012_closed.dat") comm = MPI.COMM_WORLD DVGeo = DVGeometryCST(datFile, comm=comm) @@ -485,8 +483,7 @@ class DVGeometryCSTSensitivity(unittest.TestCase): N_PROCS = 1 def setUp(self): - self.curDir = os.path.abspath(os.path.dirname(__file__)) - self.datFile = os.path.join(self.curDir, "naca2412.dat") + self.datFile = os.path.join(inputDir, "naca2412.dat") self.rng = np.random.default_rng(1) self.comm = MPI.COMM_WORLD if self.dvName in ["upper", "lower"]: @@ -627,10 +624,9 @@ def test_plotCST(self): DVGeometryCST.plotCST(np.ones(4), np.ones(3), 1e-3, 1e-3) def test_print(self): - curDir = os.path.abspath(os.path.dirname(__file__)) nUpper = 5 nLower = 3 - self.DVGeo = DVGeometryCST(os.path.join(curDir, "naca2412.dat"), numCST=[nUpper, nLower]) + self.DVGeo = DVGeometryCST(os.path.join(inputDir, "naca2412.dat"), numCST=[nUpper, nLower]) self.DVGeo.addDV("upper", dvType="upper") self.DVGeo.addDV("lower", dvType="lower") @@ -640,11 +636,10 @@ def test_print(self): self.DVGeo.printDesignVariables() def test_getNDV(self): - curDir = os.path.abspath(os.path.dirname(__file__)) nUpper = 5 nLower = 3 nOther = 3 # N1, N2, and chord - self.DVGeo = DVGeometryCST(os.path.join(curDir, "naca2412.dat"), numCST=[nUpper, nLower]) + self.DVGeo = DVGeometryCST(os.path.join(inputDir, "naca2412.dat"), numCST=[nUpper, nLower]) self.DVGeo.addDV("upper", dvType="upper") self.DVGeo.addDV("lower", dvType="lower") @@ -655,10 +650,9 @@ def test_getNDV(self): self.assertEqual(nUpper + nLower + nOther, self.DVGeo.getNDV()) def test_getValues(self): - curDir = os.path.abspath(os.path.dirname(__file__)) nUpper = 5 nLower = 3 - self.DVGeo = DVGeometryCST(os.path.join(curDir, "naca2412.dat"), numCST=[nUpper, nLower]) + self.DVGeo = DVGeometryCST(os.path.join(inputDir, "naca2412.dat"), numCST=[nUpper, nLower]) upper = np.full((nUpper,), 0.3) lower = 0.1 * np.ones(nLower) @@ -687,8 +681,7 @@ def test_getValues(self): np.testing.assert_array_equal(DVs[dvName], valDVs[dvName]) def test_getVarNames(self): - curDir = os.path.abspath(os.path.dirname(__file__)) - self.DVGeo = DVGeometryCST(os.path.join(curDir, "naca2412.dat")) + self.DVGeo = DVGeometryCST(os.path.join(inputDir, "naca2412.dat")) dvNames = ["amy", "joesph", "maryann", "tobysue", "sir blue bus"] @@ -707,8 +700,7 @@ def test_getVarNames(self): @unittest.skipUnless(prefoilImported, "preFoil is required for DVGeometryCST") class TestErrorChecking(unittest.TestCase): def setUp(self): - curDir = os.path.abspath(os.path.dirname(__file__)) - self.DVGeo = DVGeometryCST(os.path.join(curDir, "naca2412.dat"), numCST=4) + self.DVGeo = DVGeometryCST(os.path.join(inputDir, "naca2412.dat"), numCST=4) def test_addPointSet_min_out_of_bounds(self): points = np.array( From 47d9e0b80cb39a8aac35ff9954929dde82d7a7f0 Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 14:01:33 -0600 Subject: [PATCH 12/17] added CST fit reg tests --- input_files/naca0012_clockwise.dat | 160 +++++++++++++++++ input_files/naca0012_zeroLE.dat | 161 ++++++++++++++++++ .../reg_tests/ref/test_DVGeometryCST_e63.ref | 34 ++++ .../ref/test_DVGeometryCST_naca0012.ref | 18 ++ .../ref/test_DVGeometryCST_naca0012_sharp.ref | 18 ++ .../test_DVGeometryCST_naca0012_zeroLE.ref | 18 ++ .../ref/test_DVGeometryCST_naca2412.ref | 34 ++++ tests/reg_tests/test_DVGeometryCST.py | 45 +++++ 8 files changed, 488 insertions(+) create mode 100644 input_files/naca0012_clockwise.dat create mode 100644 input_files/naca0012_zeroLE.dat create mode 100644 tests/reg_tests/ref/test_DVGeometryCST_e63.ref create mode 100644 tests/reg_tests/ref/test_DVGeometryCST_naca0012.ref create mode 100644 tests/reg_tests/ref/test_DVGeometryCST_naca0012_sharp.ref create mode 100644 tests/reg_tests/ref/test_DVGeometryCST_naca0012_zeroLE.ref create mode 100644 tests/reg_tests/ref/test_DVGeometryCST_naca2412.ref diff --git a/input_files/naca0012_clockwise.dat b/input_files/naca0012_clockwise.dat new file mode 100644 index 00000000..58187626 --- /dev/null +++ b/input_files/naca0012_clockwise.dat @@ -0,0 +1,160 @@ + 1.000000 -0.1260000E-02 + 0.9937200 -0.2137733E-02 + 0.9827658 -0.3652628E-02 + 0.9699775 -0.5395763E-02 + 0.9556484 -0.7317189E-02 + 0.9402486 -0.9345839E-02 + 0.9242123 -0.1141940E-01 + 0.9078338 -0.1349729E-01 + 0.8912799 -0.1555747E-01 + 0.8746367 -0.1758921E-01 + 0.8579464 -0.1958767E-01 + 0.8412299 -0.2155079E-01 + 0.8244974 -0.2347775E-01 + 0.8077546 -0.2536826E-01 + 0.7910046 -0.2722215E-01 + 0.7742495 -0.2903929E-01 + 0.7574910 -0.3081946E-01 + 0.7407304 -0.3256231E-01 + 0.7239692 -0.3426741E-01 + 0.7072086 -0.3593415E-01 + 0.6904498 -0.3756179E-01 + 0.6736942 -0.3914944E-01 + 0.6569430 -0.4069607E-01 + 0.6401976 -0.4220048E-01 + 0.6234593 -0.4366132E-01 + 0.6067296 -0.4507707E-01 + 0.5900098 -0.4644604E-01 + 0.5733015 -0.4776639E-01 + 0.5566061 -0.4903607E-01 + 0.5399254 -0.5025287E-01 + 0.5232609 -0.5141440E-01 + 0.5066145 -0.5251805E-01 + 0.4899881 -0.5356103E-01 + 0.4733836 -0.5454034E-01 + 0.4568033 -0.5545275E-01 + 0.4402494 -0.5629481E-01 + 0.4237245 -0.5706282E-01 + 0.4072312 -0.5775284E-01 + 0.3907728 -0.5836065E-01 + 0.3743523 -0.5888176E-01 + 0.3579737 -0.5931135E-01 + 0.3416411 -0.5964430E-01 + 0.3253592 -0.5987512E-01 + 0.3091334 -0.5999796E-01 + 0.2929700 -0.6000654E-01 + 0.2768762 -0.5989415E-01 + 0.2608607 -0.5965360E-01 + 0.2449337 -0.5927721E-01 + 0.2291078 -0.5875675E-01 + 0.2133984 -0.5808348E-01 + 0.1978253 -0.5724818E-01 + 0.1824140 -0.5624130E-01 + 0.1671985 -0.5505334E-01 + 0.1522253 -0.5367551E-01 + 0.1375585 -0.5210105E-01 + 0.1232868 -0.5032750E-01 + 0.1095300 -0.4836006E-01 + 0.9644040E-01 -0.4621587E-01 + 0.8419250E-01 -0.4392743E-01 + 0.7295464E-01 -0.4154215E-01 + 0.6284856E-01 -0.3911578E-01 + 0.5391754E-01 -0.3670115E-01 + 0.4612202E-01 -0.3433829E-01 + 0.3936201E-01 -0.3205037E-01 + 0.3350894E-01 -0.2984552E-01 + 0.2843159E-01 -0.2772130E-01 + 0.2401083E-01 -0.2566907E-01 + 0.2014561E-01 -0.2367712E-01 + 0.1675353E-01 -0.2173266E-01 + 0.1376927E-01 -0.1982291E-01 + 0.1114227E-01 -0.1793562E-01 + 0.8834520E-02 -0.1605954E-01 + 0.6818652E-02 -0.1418471E-01 + 0.5076404E-02 -0.1230310E-01 + 0.3597085E-02 -0.1040943E-01 + 0.2375947E-02 -0.8502398E-02 + 0.1411548E-02 -0.6585675E-02 + 0.7030324E-03 -0.4670019E-02 + 0.2448913E-03 -0.2768967E-02 + 0.2616688E-04 -0.9084721E-03 + 0.2616688E-04 0.9084721E-03 + 0.2448913E-03 0.2768967E-02 + 0.7030324E-03 0.4670019E-02 + 0.1411548E-02 0.6585675E-02 + 0.2375947E-02 0.8502398E-02 + 0.3597085E-02 0.1040943E-01 + 0.5076404E-02 0.1230310E-01 + 0.6818652E-02 0.1418471E-01 + 0.8834520E-02 0.1605954E-01 + 0.1114227E-01 0.1793562E-01 + 0.1376927E-01 0.1982291E-01 + 0.1675353E-01 0.2173266E-01 + 0.2014561E-01 0.2367712E-01 + 0.2401083E-01 0.2566907E-01 + 0.2843159E-01 0.2772130E-01 + 0.3350894E-01 0.2984552E-01 + 0.3936201E-01 0.3205037E-01 + 0.4612202E-01 0.3433829E-01 + 0.5391754E-01 0.3670115E-01 + 0.6284856E-01 0.3911578E-01 + 0.7295464E-01 0.4154215E-01 + 0.8419250E-01 0.4392743E-01 + 0.9644040E-01 0.4621587E-01 + 0.1095300 0.4836006E-01 + 0.1232868 0.5032750E-01 + 0.1375585 0.5210105E-01 + 0.1522253 0.5367551E-01 + 0.1671985 0.5505334E-01 + 0.1824140 0.5624130E-01 + 0.1978253 0.5724818E-01 + 0.2133984 0.5808348E-01 + 0.2291078 0.5875675E-01 + 0.2449337 0.5927721E-01 + 0.2608607 0.5965360E-01 + 0.2768762 0.5989415E-01 + 0.2929700 0.6000654E-01 + 0.3091334 0.5999796E-01 + 0.3253592 0.5987512E-01 + 0.3416411 0.5964430E-01 + 0.3579737 0.5931135E-01 + 0.3743523 0.5888176E-01 + 0.3907728 0.5836065E-01 + 0.4072312 0.5775284E-01 + 0.4237245 0.5706282E-01 + 0.4402494 0.5629481E-01 + 0.4568033 0.5545275E-01 + 0.4733836 0.5454034E-01 + 0.4899881 0.5356103E-01 + 0.5066145 0.5251805E-01 + 0.5232609 0.5141440E-01 + 0.5399254 0.5025287E-01 + 0.5566061 0.4903607E-01 + 0.5733015 0.4776639E-01 + 0.5900098 0.4644604E-01 + 0.6067296 0.4507707E-01 + 0.6234593 0.4366132E-01 + 0.6401976 0.4220048E-01 + 0.6569430 0.4069607E-01 + 0.6736942 0.3914944E-01 + 0.6904498 0.3756179E-01 + 0.7072086 0.3593415E-01 + 0.7239692 0.3426741E-01 + 0.7407304 0.3256231E-01 + 0.7574910 0.3081946E-01 + 0.7742495 0.2903929E-01 + 0.7910046 0.2722215E-01 + 0.8077546 0.2536826E-01 + 0.8244974 0.2347775E-01 + 0.8412299 0.2155079E-01 + 0.8579464 0.1958767E-01 + 0.8746367 0.1758921E-01 + 0.8912799 0.1555747E-01 + 0.9078338 0.1349729E-01 + 0.9242123 0.1141940E-01 + 0.9402486 0.9345839E-02 + 0.9556484 0.7317189E-02 + 0.9699775 0.5395763E-02 + 0.9827658 0.3652628E-02 + 0.9937200 0.2137733E-02 + 1.000000 0.1260000E-02 diff --git a/input_files/naca0012_zeroLE.dat b/input_files/naca0012_zeroLE.dat new file mode 100644 index 00000000..22f3f9ea --- /dev/null +++ b/input_files/naca0012_zeroLE.dat @@ -0,0 +1,161 @@ + 1.000000 0.1260000E-02 + 0.9937200 0.2137733E-02 + 0.9827658 0.3652628E-02 + 0.9699775 0.5395763E-02 + 0.9556484 0.7317189E-02 + 0.9402486 0.9345839E-02 + 0.9242123 0.1141940E-01 + 0.9078338 0.1349729E-01 + 0.8912799 0.1555747E-01 + 0.8746367 0.1758921E-01 + 0.8579464 0.1958767E-01 + 0.8412299 0.2155079E-01 + 0.8244974 0.2347775E-01 + 0.8077546 0.2536826E-01 + 0.7910046 0.2722215E-01 + 0.7742495 0.2903929E-01 + 0.7574910 0.3081946E-01 + 0.7407304 0.3256231E-01 + 0.7239692 0.3426741E-01 + 0.7072086 0.3593415E-01 + 0.6904498 0.3756179E-01 + 0.6736942 0.3914944E-01 + 0.6569430 0.4069607E-01 + 0.6401976 0.4220048E-01 + 0.6234593 0.4366132E-01 + 0.6067296 0.4507707E-01 + 0.5900098 0.4644604E-01 + 0.5733015 0.4776639E-01 + 0.5566061 0.4903607E-01 + 0.5399254 0.5025287E-01 + 0.5232609 0.5141440E-01 + 0.5066145 0.5251805E-01 + 0.4899881 0.5356103E-01 + 0.4733836 0.5454034E-01 + 0.4568033 0.5545275E-01 + 0.4402494 0.5629481E-01 + 0.4237245 0.5706282E-01 + 0.4072312 0.5775284E-01 + 0.3907728 0.5836065E-01 + 0.3743523 0.5888176E-01 + 0.3579737 0.5931135E-01 + 0.3416411 0.5964430E-01 + 0.3253592 0.5987512E-01 + 0.3091334 0.5999796E-01 + 0.2929700 0.6000654E-01 + 0.2768762 0.5989415E-01 + 0.2608607 0.5965360E-01 + 0.2449337 0.5927721E-01 + 0.2291078 0.5875675E-01 + 0.2133984 0.5808348E-01 + 0.1978253 0.5724818E-01 + 0.1824140 0.5624130E-01 + 0.1671985 0.5505334E-01 + 0.1522253 0.5367551E-01 + 0.1375585 0.5210105E-01 + 0.1232868 0.5032750E-01 + 0.1095300 0.4836006E-01 + 0.9644040E-01 0.4621587E-01 + 0.8419250E-01 0.4392743E-01 + 0.7295464E-01 0.4154215E-01 + 0.6284856E-01 0.3911578E-01 + 0.5391754E-01 0.3670115E-01 + 0.4612202E-01 0.3433829E-01 + 0.3936201E-01 0.3205037E-01 + 0.3350894E-01 0.2984552E-01 + 0.2843159E-01 0.2772130E-01 + 0.2401083E-01 0.2566907E-01 + 0.2014561E-01 0.2367712E-01 + 0.1675353E-01 0.2173266E-01 + 0.1376927E-01 0.1982291E-01 + 0.1114227E-01 0.1793562E-01 + 0.8834520E-02 0.1605954E-01 + 0.6818652E-02 0.1418471E-01 + 0.5076404E-02 0.1230310E-01 + 0.3597085E-02 0.1040943E-01 + 0.2375947E-02 0.8502398E-02 + 0.1411548E-02 0.6585675E-02 + 0.7030324E-03 0.4670019E-02 + 0.2448913E-03 0.2768967E-02 + 0.2616688E-04 0.9084721E-03 + 0.0 0.0 + 0.2616688E-04 -0.9084721E-03 + 0.2448913E-03 -0.2768967E-02 + 0.7030324E-03 -0.4670019E-02 + 0.1411548E-02 -0.6585675E-02 + 0.2375947E-02 -0.8502398E-02 + 0.3597085E-02 -0.1040943E-01 + 0.5076404E-02 -0.1230310E-01 + 0.6818652E-02 -0.1418471E-01 + 0.8834520E-02 -0.1605954E-01 + 0.1114227E-01 -0.1793562E-01 + 0.1376927E-01 -0.1982291E-01 + 0.1675353E-01 -0.2173266E-01 + 0.2014561E-01 -0.2367712E-01 + 0.2401083E-01 -0.2566907E-01 + 0.2843159E-01 -0.2772130E-01 + 0.3350894E-01 -0.2984552E-01 + 0.3936201E-01 -0.3205037E-01 + 0.4612202E-01 -0.3433829E-01 + 0.5391754E-01 -0.3670115E-01 + 0.6284856E-01 -0.3911578E-01 + 0.7295464E-01 -0.4154215E-01 + 0.8419250E-01 -0.4392743E-01 + 0.9644040E-01 -0.4621587E-01 + 0.1095300 -0.4836006E-01 + 0.1232868 -0.5032750E-01 + 0.1375585 -0.5210105E-01 + 0.1522253 -0.5367551E-01 + 0.1671985 -0.5505334E-01 + 0.1824140 -0.5624130E-01 + 0.1978253 -0.5724818E-01 + 0.2133984 -0.5808348E-01 + 0.2291078 -0.5875675E-01 + 0.2449337 -0.5927721E-01 + 0.2608607 -0.5965360E-01 + 0.2768762 -0.5989415E-01 + 0.2929700 -0.6000654E-01 + 0.3091334 -0.5999796E-01 + 0.3253592 -0.5987512E-01 + 0.3416411 -0.5964430E-01 + 0.3579737 -0.5931135E-01 + 0.3743523 -0.5888176E-01 + 0.3907728 -0.5836065E-01 + 0.4072312 -0.5775284E-01 + 0.4237245 -0.5706282E-01 + 0.4402494 -0.5629481E-01 + 0.4568033 -0.5545275E-01 + 0.4733836 -0.5454034E-01 + 0.4899881 -0.5356103E-01 + 0.5066145 -0.5251805E-01 + 0.5232609 -0.5141440E-01 + 0.5399254 -0.5025287E-01 + 0.5566061 -0.4903607E-01 + 0.5733015 -0.4776639E-01 + 0.5900098 -0.4644604E-01 + 0.6067296 -0.4507707E-01 + 0.6234593 -0.4366132E-01 + 0.6401976 -0.4220048E-01 + 0.6569430 -0.4069607E-01 + 0.6736942 -0.3914944E-01 + 0.6904498 -0.3756179E-01 + 0.7072086 -0.3593415E-01 + 0.7239692 -0.3426741E-01 + 0.7407304 -0.3256231E-01 + 0.7574910 -0.3081946E-01 + 0.7742495 -0.2903929E-01 + 0.7910046 -0.2722215E-01 + 0.8077546 -0.2536826E-01 + 0.8244974 -0.2347775E-01 + 0.8412299 -0.2155079E-01 + 0.8579464 -0.1958767E-01 + 0.8746367 -0.1758921E-01 + 0.8912799 -0.1555747E-01 + 0.9078338 -0.1349729E-01 + 0.9242123 -0.1141940E-01 + 0.9402486 -0.9345839E-02 + 0.9556484 -0.7317189E-02 + 0.9699775 -0.5395763E-02 + 0.9827658 -0.3652628E-02 + 0.9937200 -0.2137733E-02 + 1.000000 -0.1260000E-02 diff --git a/tests/reg_tests/ref/test_DVGeometryCST_e63.ref b/tests/reg_tests/ref/test_DVGeometryCST_e63.ref new file mode 100644 index 00000000..b8fec55c --- /dev/null +++ b/tests/reg_tests/ref/test_DVGeometryCST_e63.ref @@ -0,0 +1,34 @@ +{ + "lowerCST": { + "__ndarray__": [ + -0.041052455781363316, + 0.0988276492914589, + -0.029709516208264605, + 0.22876930789750818, + -0.036213269084885584, + 0.2835255955178898, + 0.06942606140458908, + 0.3682053931683023 + ], + "dtype": "float64", + "shape": [ + 8 + ] + }, + "upperCST": { + "__ndarray__": [ + 0.11430159218390888, + 0.21707988867569167, + 0.07307160384005389, + 0.3391956670398648, + 0.01789477900865324, + 0.40784005800063433, + 0.09973565405701679, + 0.491975692194145 + ], + "dtype": "float64", + "shape": [ + 8 + ] + } +} \ No newline at end of file diff --git a/tests/reg_tests/ref/test_DVGeometryCST_naca0012.ref b/tests/reg_tests/ref/test_DVGeometryCST_naca0012.ref new file mode 100644 index 00000000..3aeccdb2 --- /dev/null +++ b/tests/reg_tests/ref/test_DVGeometryCST_naca0012.ref @@ -0,0 +1,18 @@ +{ + "upperCST": { + "__ndarray__": [ + 0.17346139862235613, + 0.14967142058169658, + 0.17786970157346033, + 0.12162537931229431, + 0.17125918055294803, + 0.12158018412794566, + 0.14839760541138639, + 0.13864883886869403 + ], + "dtype": "float64", + "shape": [ + 8 + ] + } +} \ No newline at end of file diff --git a/tests/reg_tests/ref/test_DVGeometryCST_naca0012_sharp.ref b/tests/reg_tests/ref/test_DVGeometryCST_naca0012_sharp.ref new file mode 100644 index 00000000..d6565d6a --- /dev/null +++ b/tests/reg_tests/ref/test_DVGeometryCST_naca0012_sharp.ref @@ -0,0 +1,18 @@ +{ + "upperCST": { + "__ndarray__": [ + 0.1733353255372565, + 0.1520691142858227, + 0.1722198593787986, + 0.13841157793390244, + 0.14689191790823877, + 0.15727904811194815, + 0.11966039028011803, + 0.17921189097239776 + ], + "dtype": "float64", + "shape": [ + 8 + ] + } +} \ No newline at end of file diff --git a/tests/reg_tests/ref/test_DVGeometryCST_naca0012_zeroLE.ref b/tests/reg_tests/ref/test_DVGeometryCST_naca0012_zeroLE.ref new file mode 100644 index 00000000..fa9abd43 --- /dev/null +++ b/tests/reg_tests/ref/test_DVGeometryCST_naca0012_zeroLE.ref @@ -0,0 +1,18 @@ +{ + "upperCST": { + "__ndarray__": [ + 0.1730958381222164, + 0.1507233077236812, + 0.17568197294751375, + 0.12476167835810108, + 0.1680704042863872, + 0.12387230428720869, + 0.14725535729854697, + 0.13902121127396566 + ], + "dtype": "float64", + "shape": [ + 8 + ] + } +} \ No newline at end of file diff --git a/tests/reg_tests/ref/test_DVGeometryCST_naca2412.ref b/tests/reg_tests/ref/test_DVGeometryCST_naca2412.ref new file mode 100644 index 00000000..47dd6466 --- /dev/null +++ b/tests/reg_tests/ref/test_DVGeometryCST_naca2412.ref @@ -0,0 +1,34 @@ +{ + "lowerCST": { + "__ndarray__": [ + -0.16615767624403266, + -0.08987585351577812, + -0.15582142165311424, + -0.017159899575701378, + -0.17153227428794338, + -0.019655184707028376, + -0.10547303290473756, + -0.06541772277770336 + ], + "dtype": "float64", + "shape": [ + 8 + ] + }, + "upperCST": { + "__ndarray__": [ + 0.18018474179350882, + 0.2110669552431756, + 0.1966232347728433, + 0.23082903912163238, + 0.16614792941519352, + 0.2269965790418504, + 0.18957112749197577, + 0.21246014723030537 + ], + "dtype": "float64", + "shape": [ + 8 + ] + } +} \ No newline at end of file diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index 6c4a436f..4eb668d0 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -9,6 +9,7 @@ import unittest # External modules +from baseclasses import BaseRegTest from mpi4py import MPI import numpy as np from parameterized import parameterized_class @@ -33,6 +34,16 @@ {"fName": "e63.dat", "LEUpper": False}, ] +airfoils_cst_reg = [ + {"name": "naca2412"}, + {"name": "naca0012"}, + {"name": "naca0012_closed"}, + {"name": "naca0012_clockwise"}, + {"name": "naca0012_sharp"}, + {"name": "naca0012_zeroLE"}, + {"name": "e63"}, +] + # Parameterization of design variables DVs = [ {"dvName": "upper", "dvNum": 5}, @@ -177,6 +188,40 @@ def test_fitCST(self): np.testing.assert_allclose(fitCoordsLower, coords[idxLower, 1], atol=atol, rtol=rtol) +@parameterized_class(airfoils_cst_reg) +class DVGeometryCSTFitRegTest(unittest.TestCase): + N_PROCS = 1 + + def train_cst_fit(self): + self.test_cst_fit(train=True) + + def test_cst_fit(self, train=False): + datFile = os.path.join(inputDir, f"{self.name}.dat") + DVGeo = DVGeometryCST(datFile, numCST=8) + + upperCST = DVGeo.defaultDV["upper"] + lowerCST = DVGeo.defaultDV["lower"] + + if self.name in ["naca0012_closed", "naca0012_clockwise"]: + # The closed and clockwise versions of naca0012.dat should have the same CST coefficients + # as the original, so use the same ref file + refName = "naca0012" + else: + refName = self.name + + refFile = os.path.join(baseDir, "ref", f"test_DVGeometryCST_{refName}.ref") + tol = 1e-12 + with BaseRegTest(refFile, train=train) as handler: + # Regression test the upper surface CST coefficients + handler.root_add_val("upperCST", upperCST, tol=tol) + if "naca0012" in self.name: + # Test that the coefficients are symmetric for symmetric airfoils + np.testing.assert_allclose(upperCST, -lowerCST, rtol=tol) + else: + # Regression test the lower surface CST coefficients for asymmetric airfoils + handler.root_add_val("lowerCST", lowerCST, tol=tol) + + @unittest.skipUnless(prefoilImported, "preFoil is required for DVGeometryCST") @parameterized_class(airfoils) class DVGeometryCSTPointSetSerial(unittest.TestCase): From 0b65fc682f3b6b490daa012b374ee6f410f7851f Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 15:50:13 -0600 Subject: [PATCH 13/17] os join fix --- tests/reg_tests/test_DVGeometryCST.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index 4eb668d0..2d6c426d 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -25,7 +25,7 @@ prefoilImported = False baseDir = os.path.dirname(os.path.abspath(__file__)) -inputDir = os.path.join(baseDir, "../../input_files") +inputDir = os.path.join(baseDir, "..", "..", "input_files") # LEUpper is true if the leading edge (minimum x) point is considered to be on the upper surface airfoils = [ From af28885aa59ef63cf2919c3ec8dbe905fd325021 Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 15:50:35 -0600 Subject: [PATCH 14/17] plotCST kwargs --- pygeo/parameterization/DVGeoCST.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 308a0c69..37c36059 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -1236,7 +1236,7 @@ def computeCSTfromCoords(xCoord, yCoord, yTE, nCST, N1=0.5, N2=1.0, dtype=float) return w @staticmethod - def plotCST(upperCoeff, lowerCoeff, yUpperTE, yLowerTE, N1=0.5, N2=1.0, nPts=100, ax=None, **kwargs): + def plotCST(upperCoeff, lowerCoeff, yUpperTE=0.0, yLowerTE=0.0, N1=0.5, N2=1.0, nPts=100, ax=None, **kwargs): """Simple utility to generate a plot from CST coefficients. Parameters @@ -1245,13 +1245,13 @@ def plotCST(upperCoeff, lowerCoeff, yUpperTE, yLowerTE, N1=0.5, N2=1.0, nPts=100 One dimensional array of CST coefficients for the upper surface. lowerCoeff : ndarray One dimensional array of CST coefficients for the lower surface. - yUpperTE : float + yUpperTE : float, optional y-coordinate for the upper surface trailing edge point. - yLowerTE : float + yLowerTE : float, optional y-coordinate for the lower surface trailing edge point. - N1 : float + N1 : float, optional First class shape parameter. - N2 : float + N2 : float, optional Second class shape parameter. nPts : int, optional Number of coordinates to compute on each surface. From 70369a2f9232f327d8d4dd8bbc93757c9ebfcd8b Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 15:50:52 -0600 Subject: [PATCH 15/17] version bump --- pygeo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygeo/__init__.py b/pygeo/__init__.py index 6231ad1a..3bdc954e 100644 --- a/pygeo/__init__.py +++ b/pygeo/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.13.1" +__version__ = "1.14.0" from .pyNetwork import pyNetwork from .pyGeo import pyGeo From a58dfe7bc83faa15f7ebcd2110b1d3c50075f8f3 Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 15:58:51 -0600 Subject: [PATCH 16/17] automatically determine yTE --- pygeo/parameterization/DVGeoCST.py | 8 +++++--- tests/reg_tests/test_DVGeometryCST.py | 8 ++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 37c36059..2711eae5 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -267,7 +267,6 @@ def __init__( self.defaultDV[dvType] = self.computeCSTfromCoords( xPts, yPts, - yTE, self.defaultDV[dvType].size, N1=self.defaultDV[f"n1_{dvType}"], N2=self.defaultDV[f"n2_{dvType}"], @@ -1192,7 +1191,7 @@ def computeCSTdydN2(x, N1, N2, w, dtype=float): return dydN2 @staticmethod - def computeCSTfromCoords(xCoord, yCoord, yTE, nCST, N1=0.5, N2=1.0, dtype=float): + def computeCSTfromCoords(xCoord, yCoord, nCST, N1=0.5, N2=1.0, dtype=float): """ Compute the CST coefficients that fit a set of airfoil coordinates (either for the upper or lower surface, not both). @@ -1225,7 +1224,10 @@ def computeCSTfromCoords(xCoord, yCoord, yTE, nCST, N1=0.5, N2=1.0, dtype=float) chord = np.max(xCoord) - np.min(xCoord) xCoord = (xCoord - np.min(xCoord)) / chord yCoord = yCoord / chord - yTE = yTE / chord + + # Find the y-coordinate at the trailing edge + idxTE = np.argmax(xCoord) + yTE = yCoord[idxTE] # Subtract the linear TE thickness function yMinusTE = yCoord - yTE * xCoord diff --git a/tests/reg_tests/test_DVGeometryCST.py b/tests/reg_tests/test_DVGeometryCST.py index 2d6c426d..cc26f7bf 100644 --- a/tests/reg_tests/test_DVGeometryCST.py +++ b/tests/reg_tests/test_DVGeometryCST.py @@ -159,12 +159,8 @@ def test_fitCST(self): for nCST in range(2, 10): # Fit the CST parameters and then compute the coordinates # with those parameters and check that it's close - upperCST = DVGeometryCST.computeCSTfromCoords( - coords[idxUpper, 0], coords[idxUpper, 1], yUpperTE, nCST, N1=N1, N2=N2 - ) - lowerCST = DVGeometryCST.computeCSTfromCoords( - coords[idxLower, 0], coords[idxLower, 1], yLowerTE, nCST, N1=N1, N2=N2 - ) + upperCST = DVGeometryCST.computeCSTfromCoords(coords[idxUpper, 0], coords[idxUpper, 1], nCST, N1=N1, N2=N2) + lowerCST = DVGeometryCST.computeCSTfromCoords(coords[idxLower, 0], coords[idxLower, 1], nCST, N1=N1, N2=N2) fitCoordsUpper = ( DVGeometryCST.computeCSTCoordinates(xScaledUpper, N1, N2, upperCST, yUpperTE / chord) * chord ) From 0a099fa52a7c797b134fb473a4402607e2aa8c0e Mon Sep 17 00:00:00 2001 From: sseraj Date: Tue, 27 Aug 2024 16:51:53 -0600 Subject: [PATCH 17/17] cross-product check --- pygeo/parameterization/DVGeoCST.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pygeo/parameterization/DVGeoCST.py b/pygeo/parameterization/DVGeoCST.py index 2711eae5..1ea46ba9 100644 --- a/pygeo/parameterization/DVGeoCST.py +++ b/pygeo/parameterization/DVGeoCST.py @@ -170,8 +170,14 @@ def __init__( if distance < distTol: self.foilCoords = self.foilCoords[:-1, :] - # Flip the y-coordinates to counter-clockwise if they are clockwise - if self.foilCoords[0, self.yIdx] < self.foilCoords[-1, self.yIdx]: + # Check if the coordinates are clockwise or counter-clockwise by taking the cross-product of + # vectors from adjacent points at the trailing edge + vec1 = np.append(self.foilCoords[0] - self.foilCoords[-1], 0) + vec2 = np.append(self.foilCoords[1] - self.foilCoords[0], 0) + crossProduct = np.cross(vec1, vec2) + + # Flip the y-coordinates to counter-clockwise if they are clockwise (negative cross-product) + if crossProduct[2] < 0: self.foilCoords = np.flip(self.foilCoords, self.xIdx) # Set the leading and trailing edge x coordinates