From d768705817e250ee75ec72cea665cd12ed3ccb58 Mon Sep 17 00:00:00 2001 From: Joshua Anibal Date: Wed, 6 Mar 2024 19:18:05 -0500 Subject: [PATCH 1/6] added contraint --- pygeo/constraints/DVCon.py | 28 +- pygeo/constraints/thicknessConstraint.py | 110 ++ ...VConstraints_projected_thickness1D_box.ref | 783 +++++++++++++ ...VConstraints_projected_thickness2D_box.ref | 1005 +++++++++++++++++ tests/reg_tests/test_DVConstraints.py | 104 ++ 5 files changed, 2027 insertions(+), 3 deletions(-) create mode 100644 tests/reg_tests/ref/test_DVConstraints_projected_thickness1D_box.ref create mode 100644 tests/reg_tests/ref/test_DVConstraints_projected_thickness2D_box.ref diff --git a/pygeo/constraints/DVCon.py b/pygeo/constraints/DVCon.py index 2403b145..058b1a82 100644 --- a/pygeo/constraints/DVCon.py +++ b/pygeo/constraints/DVCon.py @@ -19,7 +19,7 @@ from .locationConstraint import LocationConstraint from .planarityConstraint import PlanarityConstraint from .radiusConstraint import RadiusConstraint -from .thicknessConstraint import ThicknessConstraint, ThicknessToChordConstraint +from .thicknessConstraint import ThicknessConstraint, ThicknessToChordConstraint, ProjectedThicknessConstraint from .volumeConstraint import CompositeVolumeConstraint, TriangulatedVolumeConstraint, VolumeConstraint @@ -437,6 +437,7 @@ def addThicknessConstraints2D( surfaceName="default", DVGeoName="default", compNames=None, + projected=False, ): r""" Add a set of thickness constraints that span a logically a @@ -578,6 +579,10 @@ def addThicknessConstraints2D( with this constraint should be added. If None, the point set is added to all components. + projected : bool + Use the component of the toothpick thickness aligned with + the orginal thickness direciton. + Examples -------- >>> # Take unique square in x-z plane and and 10 along z-direction (spanWise) @@ -611,7 +616,13 @@ def addThicknessConstraints2D( conName = "%s_thickness_constraints_%d" % (self.name, len(self.constraints[typeName])) else: conName = name - self.constraints[typeName][conName] = ThicknessConstraint( + + if projected: + thickness_class = ProjectedThicknessConstraint + else: + thickness_class = ThicknessConstraint + + self.constraints[typeName][conName] = thickness_class( conName, coords, lower, upper, scaled, scale, self.DVGeometries[DVGeoName], addToPyOpt, compNames ) @@ -629,6 +640,7 @@ def addThicknessConstraints1D( surfaceName="default", DVGeoName="default", compNames=None, + projected=False ): r""" Add a set of thickness constraints oriented along a poly-line. @@ -727,6 +739,10 @@ def addThicknessConstraints1D( with this constraint should be added. If None, the point set is added to all components. + projected : bool + Use the component of the toothpick thickness aligned with + the orginal thickness direciton. + """ self._checkDVGeo(DVGeoName) @@ -760,7 +776,13 @@ def addThicknessConstraints1D( conName = "%s_thickness_constraints_%d" % (self.name, len(self.constraints[typeName])) else: conName = name - self.constraints[typeName][conName] = ThicknessConstraint( + + if projected: + thickness_class = ProjectedThicknessConstraint + else: + thickness_class = ThicknessConstraint + + self.constraints[typeName][conName] = thickness_class( conName, coords, lower, upper, scaled, scale, self.DVGeometries[DVGeoName], addToPyOpt, compNames ) diff --git a/pygeo/constraints/thicknessConstraint.py b/pygeo/constraints/thicknessConstraint.py index 7b435705..58b0918c 100644 --- a/pygeo/constraints/thicknessConstraint.py +++ b/pygeo/constraints/thicknessConstraint.py @@ -88,6 +88,116 @@ def writeTecplot(self, handle): handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) +class ProjectedThicknessConstraint(GeometricConstraint): + """ + DVConstraints representation of a set of projected thickness + constraints. One of these objects is created each time a + addThicknessConstraints2D or addThicknessConstraints1D call is + made. The user should not have to deal with this class directly. + + This is different from ThicknessConstraints becuase it measures the projected + thickness along the orginal direction of the constraint. + """ + + def __init__(self, name, coords, lower, upper, scaled, scale, DVGeo, addToPyOpt, compNames): + super().__init__(name, len(coords) // 2, lower, upper, scale, DVGeo, addToPyOpt) + + self.coords = coords + self.scaled = scaled + + # First thing we can do is embed the coordinates into DVGeo + # with the name provided: + self.DVGeo.addPointSet(self.coords, self.name, compNames=compNames) + + # Now get the reference lengths and directions + self.D0 = np.zeros(self.nCon) + self.dir_vec = np.zeros((self.nCon, 3)) + for i in range(self.nCon): + vec = self.coords[2 * i] - self.coords[2 * i + 1] + self.D0[i] = geo_utils.norm.euclideanNorm(vec) + self.dir_vec[i] = vec / self.D0[i] + + + def evalFunctions(self, funcs, config): + """ + Evaluate the functions this object has and place in the funcs dictionary + + Parameters + ---------- + funcs : dict + Dictionary to place function values + """ + # Pull out the most recent set of coordinates: + self.coords = self.DVGeo.update(self.name, config=config) + D = np.zeros(self.nCon) + for i in range(self.nCon): + vec = self.coords[2 * i] - self.coords[2 * i + 1] + + # take the dot product with the direction vector + D[i] = vec[0]*self.dir_vec[i, 0] + vec[1]*self.dir_vec[i, 1] + vec[2]*self.dir_vec[i, 2] + + if self.scaled: + D[i] /= self.D0[i] + + funcs[self.name] = D + + def evalFunctionsSens(self, funcsSens, config): + """ + Evaluate the sensitivity of the functions this object has and + place in the funcsSens dictionary + + Parameters + ---------- + funcsSens : dict + Dictionary to place function values + """ + + nDV = self.DVGeo.getNDV() + if nDV > 0: + dTdPt = np.zeros((self.nCon, self.coords.shape[0], self.coords.shape[1])) + for i in range(self.nCon): + D_b = 1.0 + + if self.scaled: + D_b /= self.D0[i] + + # d(dot(vec,n)/dvec = n + vec_b = self.dir_vec[i]*D_b + + dTdPt[i, 2 * i, :] = vec_b + dTdPt[i, 2 * i + 1, :] = -vec_b + + funcsSens[self.name] = self.DVGeo.totalSensitivity(dTdPt, self.name, config=config) + + def writeTecplot(self, handle): + """ + Write the visualization of this set of thickness constraints + to the open file handle + """ + + handle.write("Zone T=%s\n" % self.name) + handle.write("Nodes = %d, Elements = %d ZONETYPE=FELINESEG\n" % (len(self.coords), len(self.coords) // 2)) + handle.write("DATAPACKING=POINT\n") + for i in range(len(self.coords)): + handle.write(f"{self.coords[i, 0]:f} {self.coords[i, 1]:f} {self.coords[i, 2]:f}\n") + + for i in range(len(self.coords) // 2): + handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) + + handle.write("Zone T=%s_ref_directions\n" % self.name) + handle.write("Nodes = %d, Elements = %d ZONETYPE=FELINESEG\n" % (len(self.dir_vec)*2, len(self.dir_vec))) + handle.write("DATAPACKING=POINT\n") + + for i in range(self.nCon): + pt1 = self.coords[i*2 + 1] + pt2 = pt1 + self.dir_vec[i] + handle.write(f"{pt1[0]:f} {pt1[1]:f} {pt1[2]:f}\n") + handle.write(f"{pt2[0]:f} {pt2[1]:f} {pt2[2]:f}\n") + + for i in range(self.nCon): + handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) + + class ThicknessToChordConstraint(GeometricConstraint): """ ThicknessToChordConstraint represents of a set of diff --git a/tests/reg_tests/ref/test_DVConstraints_projected_thickness1D_box.ref b/tests/reg_tests/ref/test_DVConstraints_projected_thickness1D_box.ref new file mode 100644 index 00000000..7d426f2c --- /dev/null +++ b/tests/reg_tests/ref/test_DVConstraints_projected_thickness1D_box.ref @@ -0,0 +1,783 @@ +{ + "derivs_base": { + "DVCon1_thickness_constraints_0": { + "local": { + "__ndarray__": [ + [ + -0.12126517326732109, + -0.12126517326732109, + 0.12126517326732109, + 0.12126517326732109, + -1.2727910288051387e-07, + -1.2727910288051387e-07, + 1.2727910288051387e-07, + 1.2727910288051387e-07, + -0.36379551980196323, + -0.36379551980196323, + 0.36379551980196323, + 0.36379551980196323, + -3.818373086415416e-07, + -3.818373086415416e-07, + 3.818373086415416e-07, + 3.818373086415416e-07, + -0.003697126844351293, + -3.757260922471972e-05, + -0.003697126844351293, + -3.757260922471972e-05, + 0.003697126844351293, + 3.757260922471972e-05, + 0.003697126844351293, + 3.757260922471972e-05, + -0.01109138053305388, + -0.00011271782767415916, + -0.01109138053305388, + -0.00011271782767415916, + 0.01109138053305388, + 0.00011271782767415916, + 0.01109138053305388, + 0.00011271782767415916 + ], + [ + -0.047822407715745203, + -0.047822407715745203, + 0.047822407715745203, + 0.047822407715745203, + -0.002572740594303018, + -0.002572740594303018, + 0.002572740594303018, + 0.002572740594303018, + -0.14346722314723562, + -0.14346722314723562, + 0.14346722314723562, + 0.14346722314723562, + -0.0077182217829090535, + -0.0077182217829090535, + 0.0077182217829090535, + 0.0077182217829090535, + -0.05415944595801659, + -0.020445405731935214, + -0.05415944595801659, + -0.020445405731935214, + 0.05415944595801659, + 0.020445405731935214, + 0.05415944595801659, + 0.020445405731935214, + -0.16247833787404978, + -0.06133621719580564, + -0.16247833787404978, + -0.06133621719580564, + 0.16247833787404978, + 0.06133621719580564, + 0.16247833787404978, + 0.06133621719580564 + ], + [ + -0.009543132789533537, + -0.009543132789533537, + 0.009543132789533544, + 0.009543132789533544, + -0.023860205922432542, + -0.023860205922432542, + 0.023860205922432532, + 0.023860205922432532, + -0.02862939836860061, + -0.02862939836860061, + 0.02862939836860063, + 0.02862939836860063, + -0.07158061776729763, + -0.07158061776729763, + 0.0715806177672976, + 0.0715806177672976, + -0.03885736037695713, + -0.052739300911076786, + -0.03885736037695713, + -0.052739300911076786, + 0.03885736037695715, + 0.05273930091107678, + 0.03885736037695715, + 0.05273930091107678, + -0.11657208113087139, + -0.15821790273323036, + -0.11657208113087139, + -0.15821790273323036, + 0.11657208113087145, + 0.15821790273323033, + 0.11657208113087145, + 0.15821790273323033 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 32 + ] + }, + "local_x": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 32 + ] + }, + "twist": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + -2.309868122508139e-21, + -5.305517456302115e-20, + 1.1549340612540695e-21 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 3 + ] + } + }, + "DVCon1_thickness_constraints_1": { + "local": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 32 + ] + }, + "local_x": { + "__ndarray__": [ + [ + -0.48506069306928434, + 0.48506069306928434, + -0.48506069306928434, + 0.48506069306928434, + -5.091164115220552e-07, + 5.091164115220552e-07, + -5.091164115220552e-07, + 5.091164115220552e-07, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.01478850737740517, + -0.0001502904368988788, + 0.01478850737740517, + 0.0001502904368988788, + -0.01478850737740517, + -0.0001502904368988788, + 0.01478850737740517, + 0.0001502904368988788, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.19128963086298081, + 0.19128963086298076, + -0.19128963086298081, + 0.19128963086298076, + -0.010290962377212071, + 0.010290962377212063, + -0.010290962377212071, + 0.010290962377212063, + 7.073480220092073e-33, + 6.371224576683845e-17, + 7.073480220092073e-33, + 6.371224576683845e-17, + 3.805377138976352e-34, + 3.4275790130196204e-18, + 3.805377138976352e-34, + 3.4275790130196204e-18, + -0.21663778383206636, + -0.08178162292774085, + 0.21663778383206622, + 0.0817816229277408, + -0.21663778383206636, + -0.08178162292774085, + 0.21663778383206622, + 0.0817816229277408, + 8.010800543383016e-33, + 3.024108988744738e-33, + 7.215487668423823e-17, + 2.7238752229677132e-17, + 8.010800543383016e-33, + 3.024108988744738e-33, + 7.215487668423823e-17, + 2.7238752229677132e-17 + ], + [ + -0.03817253115813411, + 0.03817253115813411, + -0.03817253115813411, + 0.03817253115813411, + -0.09544082368973024, + 0.09544082368973024, + -0.09544082368973024, + 0.09544082368973024, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.15542944150782848, + -0.2109572036443072, + 0.15542944150782848, + 0.2109572036443072, + -0.15542944150782848, + -0.2109572036443072, + 0.15542944150782848, + 0.2109572036443072, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 32 + ] + }, + "twist": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + -8.997715628516394e-20, + -5.76749059637757e-20, + -5.623572267822751e-21 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 3 + ] + } + }, + "DVCon1_thickness_constraints_2": { + "local": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 32 + ] + }, + "local_x": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 32 + ] + }, + "twist": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 3, + 3 + ] + } + } + }, + "funcs_base": { + "DVCon1_thickness_constraints_0": { + "__ndarray__": [ + 1.0, + 1.0000000000000002, + 1.0 + ], + "dtype": "float64", + "shape": [ + 3 + ] + }, + "DVCon1_thickness_constraints_1": { + "__ndarray__": [ + 1.9999999999999996, + 2.0, + 2.0 + ], + "dtype": "float64", + "shape": [ + 3 + ] + }, + "DVCon1_thickness_constraints_2": { + "__ndarray__": [ + 7.999999999999943, + 7.999999999999945, + 7.999999999999941 + ], + "dtype": "float64", + "shape": [ + 3 + ] + } + } +} \ No newline at end of file diff --git a/tests/reg_tests/ref/test_DVConstraints_projected_thickness2D_box.ref b/tests/reg_tests/ref/test_DVConstraints_projected_thickness2D_box.ref new file mode 100644 index 00000000..3279b63e --- /dev/null +++ b/tests/reg_tests/ref/test_DVConstraints_projected_thickness2D_box.ref @@ -0,0 +1,1005 @@ +{ + "derivs_base": { + "DVCon1_thickness_constraints_0": { + "local": { + "__ndarray__": [ + [ + -0.23684604187080124, + -0.0511587448522478, + 0.23684604187080124, + 0.05115874485224777, + -2.4859199816308616e-07, + -5.3695871401867114e-08, + 2.48591998163086e-07, + 5.369587140186705e-08, + -0.42632287483453873, + -0.2557937245809811, + 0.4263228748345387, + 0.25579372458098104, + -4.4746559613422315e-07, + -2.684793573449347e-07, + 4.4746559613422283e-07, + 2.684793573449345e-07, + -0.007220950878028082, + -7.338400249522697e-05, + -0.001559725383805096, + -1.585094447952799e-05, + 0.007220950878028081, + 7.338400249522693e-05, + 0.001559725383805095, + 1.5850944479527972e-05, + -0.01299771156420341, + -0.00013209120432629455, + -0.007798626928773763, + -7.925472249670833e-05, + 0.012997711564203407, + 0.00013209120432629447, + 0.0077986269287737605, + 7.925472249670828e-05 + ], + [ + -0.0018947683136502616, + -0.6499055388934788, + 0.0018947683136502616, + 0.6499055388934788, + -1.9887359629314045e-09, + -6.821364429595086e-07, + 1.9887359629314045e-09, + 6.821364429595086e-07, + -0.03979013473586854, + -0.27853094419557106, + 0.03979013473586854, + 0.27853094419557106, + -4.176345537817249e-08, + -2.923441887434984e-07, + 4.176345537817249e-08, + 2.923441887434984e-07, + -5.776760637433893e-05, + -5.87072013357254e-07, + -0.019814289209309062, + -0.00020136570284690284, + 5.776760637433893e-05, + 5.87072013357254e-07, + 0.019814289209309062, + 0.00020136570284690284, + -0.0012131197384103178, + -1.2328512326734267e-05, + -0.008491838200716625, + -8.62995866107634e-05, + 0.0012131197384103178, + 1.2328512326734267e-05, + 0.008491838200716625, + 8.62995866107634e-05 + ], + [ + -8.265778756164641e-07, + -1.7854082046362824e-07, + 8.265778756164458e-07, + 1.785408204636242e-07, + -0.2333070545630445, + -0.050394323596638924, + 0.2333070545630446, + 0.05039432359663891, + -1.4878401742498353e-06, + -8.927041034340212e-07, + 1.4878401742498023e-06, + 8.927041034340013e-07, + -0.4199526976885393, + -0.2519716182981591, + 0.4199526976885394, + 0.2519716182981591, + -0.00016266210797477183, + -0.010670082094427848, + -3.5135015190794425e-05, + -0.0023047377237536497, + 0.00016266210797476945, + 0.010670082094427772, + 3.513501519079389e-05, + 0.0023047377237536324, + -0.00029279179398859957, + -0.019206147745962448, + -0.00017567507617356594, + -0.011523688633172859, + 0.0002927917939885953, + 0.019206147745962306, + 0.00017567507617356333, + 0.011523688633172774 + ], + [ + -6.612622930539705e-09, + -2.2681296906915773e-06, + 6.612622930539559e-09, + 2.2681296906915273e-06, + -0.0018664564155067215, + -0.6401945577209941, + 0.0018664564155067222, + 0.6401945577209943, + -1.3886508206207785e-07, + -9.720555780797532e-07, + 1.3886508206207478e-07, + 9.720555780797316e-07, + -0.039195584872624596, + -0.2743690951372562, + 0.03919558487262461, + 0.2743690951372563, + -1.3012968491585852e-06, + -8.536065579511542e-05, + -0.00044634482428277387, + -0.029278705267110017, + 1.301296849158566e-06, + 8.536065579511481e-05, + 0.00044634482428276736, + 0.02927870526710981, + -2.7327233934807416e-05, + -0.0017925737784195755, + -0.00019129063826099177, + -0.012548016495992088, + 2.7327233934807016e-05, + 0.0017925737784195627, + 0.00019129063826098895, + 0.012548016495991998 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 32 + ] + }, + "local_x": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 32 + ] + }, + "twist": { + "__ndarray__": [ + [ + 0.0, + 8.470329472543003e-22, + 1.6543612251060553e-24 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 4.2351647362715017e-20, + 1.3552527156068805e-18, + -2.6020852139652106e-18 + ], + [ + -1.2705494208814505e-19, + -4.0115480381963664e-18, + 5.204170427930421e-18 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 3 + ] + } + }, + "DVCon1_thickness_constraints_1": { + "local": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 32 + ] + }, + "local_x": { + "__ndarray__": [ + [ + -0.7275910396039265, + 0.7275910396039265, + -0.24253034653464212, + 0.24253034653464214, + -7.636746172830832e-07, + 7.636746172830832e-07, + -2.545582057610277e-07, + 2.545582057610277e-07, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.02218276106610776, + -0.00022543565534831832, + 0.02218276106610776, + 0.00022543565534831832, + -0.0073942536887025845, + -7.514521844943943e-05, + 0.007394253688702585, + 7.514521844943943e-05, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.24253034653464206, + 0.24253034653464206, + -0.7275910396039266, + 0.7275910396039266, + -2.5455820576102763e-07, + 2.5455820576102763e-07, + -7.636746172830833e-07, + 7.636746172830833e-07, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.007394253688702583, + -7.514521844943942e-05, + 0.007394253688702583, + 7.514521844943942e-05, + -0.02218276106610776, + -0.00022543565534831838, + 0.02218276106610776, + 0.00022543565534831838, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -2.539247230322905e-06, + 2.539247230322961e-06, + -8.464157434409683e-07, + 8.464157434409873e-07, + -0.7167192706097865, + 0.7167192706097862, + -0.2389064235365955, + 0.23890642353659547, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.0004996979949957914, + -0.032778492147987365, + 0.0004996979949957988, + 0.0327784921479876, + -0.00016656599833193047, + -0.010926164049329121, + 0.00016656599833193294, + 0.0109261640493292, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -8.464157434409683e-07, + 8.464157434409683e-07, + -2.539247230322905e-06, + 2.539247230322905e-06, + -0.2389064235365955, + 0.2389064235365955, + -0.7167192706097865, + 0.7167192706097865, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.00016656599833193047, + -0.010926164049329121, + 0.00016656599833193047, + 0.010926164049329121, + -0.0004996979949957914, + -0.032778492147987365, + 0.0004996979949957914, + 0.032778492147987365, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 32 + ] + }, + "twist": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 4.319868030996932e-20, + 1.3552527156068805e-18, + -1.734723475976807e-18 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 3 + ] + } + }, + "DVCon1_thickness_constraints_2": { + "local": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 32 + ] + }, + "local_x": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 32 + ] + }, + "twist": { + "__ndarray__": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dtype": "float64", + "shape": [ + 4, + 3 + ] + } + } + }, + "funcs_base": { + "DVCon1_thickness_constraints_0": { + "__ndarray__": [ + 0.9999999999999999, + 0.9999999999999999, + 1.0, + 1.0 + ], + "dtype": "float64", + "shape": [ + 4 + ] + }, + "DVCon1_thickness_constraints_1": { + "__ndarray__": [ + 1.9999999999999993, + 1.9999999999999996, + 2.0, + 1.9999999999999998 + ], + "dtype": "float64", + "shape": [ + 4 + ] + }, + "DVCon1_thickness_constraints_2": { + "__ndarray__": [ + 7.999999999999945, + 7.999999999999947, + 7.999999999999943, + 7.999999999999941 + ], + "dtype": "float64", + "shape": [ + 4 + ] + } + } +} \ No newline at end of file diff --git a/tests/reg_tests/test_DVConstraints.py b/tests/reg_tests/test_DVConstraints.py index 54cb2f57..b9409edf 100644 --- a/tests/reg_tests/test_DVConstraints.py +++ b/tests/reg_tests/test_DVConstraints.py @@ -318,6 +318,55 @@ def test_thickness1D_box(self, train=False, refDeriv=False): handler.assert_allclose( funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 ) + + def test_projected_thickness1D_box(self, train=False, refDeriv=False): + refFile = os.path.join(self.base_path, "ref/test_DVConstraints_projected_thickness1D_box.ref") + with BaseRegTest(refFile, train=train) as handler: + DVGeo, DVCon = self.generate_dvgeo_dvcon("box") + DVGeo.addLocalDV("local_x", lower=-0.5, upper=0.5, axis="x", scale=1) + ptList = [[0.0, 0.0, 0.1], [0.0, 0., 5.0]] + ptList2 = [[-0.5, 0.0, 2.0], [0.5, 0.0, 2.0]] + ptList3 = [[-0.5, 0.0, 0], [1, 1.0, 8.0]] # corner to corner + DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[0, 1, 0], projected=True, scaled=False) + DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[1, 0, 0], projected=True, scaled=False) + DVCon.addThicknessConstraints1D(ptList2, nCon=3, axis=[0, 0, 1], projected=True, scaled=False) + + funcs, funcsSens = generic_test_base(DVGeo, DVCon, handler) + + funcSens = {} + # Check that unscaled thicknesses are computed correctly at baseline + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_0"], np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_1"], 2.0 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + + # add skew to one face + nodes = DVGeo.FFD.coef + dx = np.max(nodes[:,0]) - np.min(nodes[:,0]) + dy = np.max(nodes[:,1]) - np.min(nodes[:,1]) + y_scale = ((nodes[:,1] - np.min(nodes[:,1]))/dy)*(nodes[:,0] - np.min(nodes[:,0]))/dx + + DVGeo.setDesignVars({"local_x": y_scale}) + + funcs = {} + DVCon.evalFunctions(funcs) + + # DVCon1_thickness_constraints_0 should stay the same since the thickness contraint is projected! + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_0"], np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_1"], 2.5 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + # The z direction is uneffected by the changes + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 + ) def test_thickness2D(self, train=False, refDeriv=False): refFile = os.path.join(self.base_path, "ref/test_DVConstraints_thickness2D.ref") @@ -398,6 +447,61 @@ def test_thickness2D_box(self, train=False, refDeriv=False): funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 ) + def test_projected_thickness2D_box(self, train=False, refDeriv=False): + refFile = os.path.join(self.base_path, "ref/test_DVConstraints_projected_thickness2D_box.ref") + with BaseRegTest(refFile, train=train) as handler: + DVGeo, DVCon = self.generate_dvgeo_dvcon("box") + DVGeo.addLocalDV("local_x", lower=-0.5, upper=0.5, axis="x", scale=1) + + leList = [[-0.25, 0.0, 0.1], [-0.25, 0.0, 7.9]] + teList = [[0.75, 0.0, 0.1], [0.75, 0.0, 7.9]] + + leList2 = [[0.0, -0.25, 0.1], [0.0, -0.25, 7.9]] + teList2 = [[0.0, 0.25, 0.1], [0.0, 0.25, 7.9]] + + leList3 = [[-0.5, -0.25, 0.1], [0.5, -0.25, 0.1]] + teList3 = [[-0.5, 0.25, 0.1], [0.5, 0.25, 0.1]] + + DVCon.addThicknessConstraints2D(leList, teList, 2, 2, scaled=False , projected=True) + DVCon.addThicknessConstraints2D(leList2, teList2, 2, 2, scaled=False, projected=True) + DVCon.addThicknessConstraints2D(leList3, teList3, 2, 2, scaled=False, projected=True) + + funcs, funcsSens = generic_test_base(DVGeo, DVCon, handler) + # Check that unscaled thicknesses are computed correctly at baseline + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_0"], np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_1"], 2.0 * np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + + # add skew to one face + nodes = DVGeo.FFD.coef + dx = np.max(nodes[:,0]) - np.min(nodes[:,0]) + dy = np.max(nodes[:,1]) - np.min(nodes[:,1]) + y_scale = ((nodes[:,1] - np.min(nodes[:,1]))/dy)*(nodes[:,0] - np.min(nodes[:,0]))/dx + + DVGeo.setDesignVars({"local_x": y_scale}) + + funcs = {} + DVCon.evalFunctions(funcs) + + # DVCon1_thickness_constraints_0 should stay the same since the thickness contraint is projected! + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_0"], np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_1"], np.array([2.25, 2.75, 2.25, 2.75]), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + # The z direction is uneffected by the changes + handler.assert_allclose( + funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 + ) + + def test_volume(self, train=False, refDeriv=False): refFile = os.path.join(self.base_path, "ref/test_DVConstraints_volume.ref") with BaseRegTest(refFile, train=train) as handler: From 3867987c847bd4d00cbb39d11f671ea21df11d97 Mon Sep 17 00:00:00 2001 From: Joshua Anibal Date: Wed, 6 Mar 2024 19:43:46 -0500 Subject: [PATCH 2/6] spelling and formatting --- pygeo/constraints/DVCon.py | 19 +++++---- pygeo/constraints/thicknessConstraint.py | 29 +++++++------ tests/reg_tests/test_DVConstraints.py | 53 +++++++++++++----------- 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/pygeo/constraints/DVCon.py b/pygeo/constraints/DVCon.py index 3fde13c8..47b5a8bf 100644 --- a/pygeo/constraints/DVCon.py +++ b/pygeo/constraints/DVCon.py @@ -19,7 +19,12 @@ from .locationConstraint import LocationConstraint from .planarityConstraint import PlanarityConstraint from .radiusConstraint import RadiusConstraint -from .thicknessConstraint import ProximityConstraint, ThicknessConstraint, ThicknessToChordConstraint, ProjectedThicknessConstraint +from .thicknessConstraint import ( + ProximityConstraint, + ThicknessConstraint, + ThicknessToChordConstraint, + ProjectedThicknessConstraint, +) from .volumeConstraint import CompositeVolumeConstraint, TriangulatedVolumeConstraint, VolumeConstraint @@ -581,7 +586,7 @@ def addThicknessConstraints2D( projected : bool Use the component of the toothpick thickness aligned with - the orginal thickness direciton. + the original thickness direction. Examples -------- @@ -621,7 +626,7 @@ def addThicknessConstraints2D( thickness_class = ProjectedThicknessConstraint else: thickness_class = ThicknessConstraint - + self.constraints[typeName][conName] = thickness_class( conName, coords, lower, upper, scaled, scale, self.DVGeometries[DVGeoName], addToPyOpt, compNames ) @@ -640,7 +645,7 @@ def addThicknessConstraints1D( surfaceName="default", DVGeoName="default", compNames=None, - projected=False + projected=False, ): r""" Add a set of thickness constraints oriented along a poly-line. @@ -741,7 +746,7 @@ def addThicknessConstraints1D( projected : bool Use the component of the toothpick thickness aligned with - the orginal thickness direciton. + the original thickness direction. """ self._checkDVGeo(DVGeoName) @@ -776,12 +781,12 @@ def addThicknessConstraints1D( conName = "%s_thickness_constraints_%d" % (self.name, len(self.constraints[typeName])) else: conName = name - + if projected: thickness_class = ProjectedThicknessConstraint else: thickness_class = ThicknessConstraint - + self.constraints[typeName][conName] = thickness_class( conName, coords, lower, upper, scaled, scale, self.DVGeometries[DVGeoName], addToPyOpt, compNames ) diff --git a/pygeo/constraints/thicknessConstraint.py b/pygeo/constraints/thicknessConstraint.py index 7826621c..419e26dd 100644 --- a/pygeo/constraints/thicknessConstraint.py +++ b/pygeo/constraints/thicknessConstraint.py @@ -94,7 +94,7 @@ class ProjectedThicknessConstraint(GeometricConstraint): constraints. One of these objects is created each time a addThicknessConstraints2D or addThicknessConstraints1D call is made. The user should not have to deal with this class directly. - + This is different from ThicknessConstraints becuase it measures the projected thickness along the orginal direction of the constraint. """ @@ -116,7 +116,6 @@ def __init__(self, name, coords, lower, upper, scaled, scale, DVGeo, addToPyOpt, vec = self.coords[2 * i] - self.coords[2 * i + 1] self.D0[i] = geo_utils.norm.euclideanNorm(vec) self.dir_vec[i] = vec / self.D0[i] - def evalFunctions(self, funcs, config): """ @@ -132,13 +131,13 @@ def evalFunctions(self, funcs, config): D = np.zeros(self.nCon) for i in range(self.nCon): vec = self.coords[2 * i] - self.coords[2 * i + 1] - + # take the dot product with the direction vector - D[i] = vec[0]*self.dir_vec[i, 0] + vec[1]*self.dir_vec[i, 1] + vec[2]*self.dir_vec[i, 2] - + D[i] = vec[0] * self.dir_vec[i, 0] + vec[1] * self.dir_vec[i, 1] + vec[2] * self.dir_vec[i, 2] + if self.scaled: D[i] /= self.D0[i] - + funcs[self.name] = D def evalFunctionsSens(self, funcsSens, config): @@ -157,13 +156,13 @@ def evalFunctionsSens(self, funcsSens, config): dTdPt = np.zeros((self.nCon, self.coords.shape[0], self.coords.shape[1])) for i in range(self.nCon): D_b = 1.0 - + if self.scaled: D_b /= self.D0[i] - + # d(dot(vec,n)/dvec = n - vec_b = self.dir_vec[i]*D_b - + vec_b = self.dir_vec[i] * D_b + dTdPt[i, 2 * i, :] = vec_b dTdPt[i, 2 * i + 1, :] = -vec_b @@ -183,20 +182,20 @@ def writeTecplot(self, handle): for i in range(len(self.coords) // 2): handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) - + handle.write("Zone T=%s_ref_directions\n" % self.name) - handle.write("Nodes = %d, Elements = %d ZONETYPE=FELINESEG\n" % (len(self.dir_vec)*2, len(self.dir_vec))) + handle.write("Nodes = %d, Elements = %d ZONETYPE=FELINESEG\n" % (len(self.dir_vec) * 2, len(self.dir_vec))) handle.write("DATAPACKING=POINT\n") - + for i in range(self.nCon): - pt1 = self.coords[i*2 + 1] + pt1 = self.coords[i * 2 + 1] pt2 = pt1 + self.dir_vec[i] handle.write(f"{pt1[0]:f} {pt1[1]:f} {pt1[2]:f}\n") handle.write(f"{pt2[0]:f} {pt2[1]:f} {pt2[2]:f}\n") for i in range(self.nCon): handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) - + class ThicknessToChordConstraint(GeometricConstraint): """ diff --git a/tests/reg_tests/test_DVConstraints.py b/tests/reg_tests/test_DVConstraints.py index 3da5fe79..f951abb4 100644 --- a/tests/reg_tests/test_DVConstraints.py +++ b/tests/reg_tests/test_DVConstraints.py @@ -319,21 +319,21 @@ def test_thickness1D_box(self, train=False, refDeriv=False): handler.assert_allclose( funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 ) - + def test_projected_thickness1D_box(self, train=False, refDeriv=False): refFile = os.path.join(self.base_path, "ref/test_DVConstraints_projected_thickness1D_box.ref") with BaseRegTest(refFile, train=train) as handler: DVGeo, DVCon = self.generate_dvgeo_dvcon("box") DVGeo.addLocalDV("local_x", lower=-0.5, upper=0.5, axis="x", scale=1) - ptList = [[0.0, 0.0, 0.1], [0.0, 0., 5.0]] + ptList = [[0.0, 0.0, 0.1], [0.0, 0.0, 5.0]] ptList2 = [[-0.5, 0.0, 2.0], [0.5, 0.0, 2.0]] - ptList3 = [[-0.5, 0.0, 0], [1, 1.0, 8.0]] # corner to corner - DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[0, 1, 0], projected=True, scaled=False) - DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[1, 0, 0], projected=True, scaled=False) + ptList3 = [[-0.5, 0.0, 0], [1, 1.0, 8.0]] # corner to corner + DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[0, 1, 0], projected=True, scaled=False) + DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[1, 0, 0], projected=True, scaled=False) DVCon.addThicknessConstraints1D(ptList2, nCon=3, axis=[0, 0, 1], projected=True, scaled=False) funcs, funcsSens = generic_test_base(DVGeo, DVCon, handler) - + funcSens = {} # Check that unscaled thicknesses are computed correctly at baseline handler.assert_allclose( @@ -345,19 +345,19 @@ def test_projected_thickness1D_box(self, train=False, refDeriv=False): handler.assert_allclose( funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 ) - + # add skew to one face nodes = DVGeo.FFD.coef - dx = np.max(nodes[:,0]) - np.min(nodes[:,0]) - dy = np.max(nodes[:,1]) - np.min(nodes[:,1]) - y_scale = ((nodes[:,1] - np.min(nodes[:,1]))/dy)*(nodes[:,0] - np.min(nodes[:,0]))/dx - + dx = np.max(nodes[:, 0]) - np.min(nodes[:, 0]) + dy = np.max(nodes[:, 1]) - np.min(nodes[:, 1]) + y_scale = ((nodes[:, 1] - np.min(nodes[:, 1])) / dy) * (nodes[:, 0] - np.min(nodes[:, 0])) / dx + DVGeo.setDesignVars({"local_x": y_scale}) - + funcs = {} DVCon.evalFunctions(funcs) - - # DVCon1_thickness_constraints_0 should stay the same since the thickness contraint is projected! + + # DVCon1_thickness_constraints_0 should stay the same since the thickness constraint is projected! handler.assert_allclose( funcs["DVCon1_thickness_constraints_0"], np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 ) @@ -463,7 +463,7 @@ def test_projected_thickness2D_box(self, train=False, refDeriv=False): leList3 = [[-0.5, -0.25, 0.1], [0.5, -0.25, 0.1]] teList3 = [[-0.5, 0.25, 0.1], [0.5, 0.25, 0.1]] - DVCon.addThicknessConstraints2D(leList, teList, 2, 2, scaled=False , projected=True) + DVCon.addThicknessConstraints2D(leList, teList, 2, 2, scaled=False, projected=True) DVCon.addThicknessConstraints2D(leList2, teList2, 2, 2, scaled=False, projected=True) DVCon.addThicknessConstraints2D(leList3, teList3, 2, 2, scaled=False, projected=True) @@ -478,30 +478,33 @@ def test_projected_thickness2D_box(self, train=False, refDeriv=False): handler.assert_allclose( funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 ) - + # add skew to one face nodes = DVGeo.FFD.coef - dx = np.max(nodes[:,0]) - np.min(nodes[:,0]) - dy = np.max(nodes[:,1]) - np.min(nodes[:,1]) - y_scale = ((nodes[:,1] - np.min(nodes[:,1]))/dy)*(nodes[:,0] - np.min(nodes[:,0]))/dx - + dx = np.max(nodes[:, 0]) - np.min(nodes[:, 0]) + dy = np.max(nodes[:, 1]) - np.min(nodes[:, 1]) + y_scale = ((nodes[:, 1] - np.min(nodes[:, 1])) / dy) * (nodes[:, 0] - np.min(nodes[:, 0])) / dx + DVGeo.setDesignVars({"local_x": y_scale}) - + funcs = {} DVCon.evalFunctions(funcs) - - # DVCon1_thickness_constraints_0 should stay the same since the thickness contraint is projected! + + # DVCon1_thickness_constraints_0 should stay the same since the thickness constraint is projected! handler.assert_allclose( funcs["DVCon1_thickness_constraints_0"], np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 ) handler.assert_allclose( - funcs["DVCon1_thickness_constraints_1"], np.array([2.25, 2.75, 2.25, 2.75]), name="thickness_base", rtol=1e-7, atol=1e-7 + funcs["DVCon1_thickness_constraints_1"], + np.array([2.25, 2.75, 2.25, 2.75]), + name="thickness_base", + rtol=1e-7, + atol=1e-7, ) # The z direction is uneffected by the changes handler.assert_allclose( funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(4), name="thickness_base", rtol=1e-7, atol=1e-7 ) - def test_volume(self, train=False, refDeriv=False): refFile = os.path.join(self.base_path, "ref/test_DVConstraints_volume.ref") From 0b8011affabf322888927201847eef284c6cd8ed Mon Sep 17 00:00:00 2001 From: Joshua Anibal Date: Wed, 6 Mar 2024 19:52:41 -0500 Subject: [PATCH 3/6] formatting try 2 --- pygeo/constraints/DVCon.py | 6 +++--- tests/reg_tests/test_DVConstraints.py | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pygeo/constraints/DVCon.py b/pygeo/constraints/DVCon.py index 47b5a8bf..46fde2d2 100644 --- a/pygeo/constraints/DVCon.py +++ b/pygeo/constraints/DVCon.py @@ -585,9 +585,9 @@ def addThicknessConstraints2D( If None, the point set is added to all components. projected : bool - Use the component of the toothpick thickness aligned with + Use the component of the toothpick thickness aligned with the original thickness direction. - + Examples -------- >>> # Take unique square in x-z plane and and 10 along z-direction (spanWise) @@ -745,7 +745,7 @@ def addThicknessConstraints1D( If None, the point set is added to all components. projected : bool - Use the component of the toothpick thickness aligned with + Use the component of the toothpick thickness aligned with the original thickness direction. """ diff --git a/tests/reg_tests/test_DVConstraints.py b/tests/reg_tests/test_DVConstraints.py index f951abb4..021b5dde 100644 --- a/tests/reg_tests/test_DVConstraints.py +++ b/tests/reg_tests/test_DVConstraints.py @@ -327,14 +327,12 @@ def test_projected_thickness1D_box(self, train=False, refDeriv=False): DVGeo.addLocalDV("local_x", lower=-0.5, upper=0.5, axis="x", scale=1) ptList = [[0.0, 0.0, 0.1], [0.0, 0.0, 5.0]] ptList2 = [[-0.5, 0.0, 2.0], [0.5, 0.0, 2.0]] - ptList3 = [[-0.5, 0.0, 0], [1, 1.0, 8.0]] # corner to corner DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[0, 1, 0], projected=True, scaled=False) DVCon.addThicknessConstraints1D(ptList, nCon=3, axis=[1, 0, 0], projected=True, scaled=False) DVCon.addThicknessConstraints1D(ptList2, nCon=3, axis=[0, 0, 1], projected=True, scaled=False) funcs, funcsSens = generic_test_base(DVGeo, DVCon, handler) - funcSens = {} # Check that unscaled thicknesses are computed correctly at baseline handler.assert_allclose( funcs["DVCon1_thickness_constraints_0"], np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 From 5479d8fa993728e5f47dc2c29baefb069bb30fbe Mon Sep 17 00:00:00 2001 From: Joshua Anibal Date: Wed, 6 Mar 2024 19:55:35 -0500 Subject: [PATCH 4/6] add to mphys wrapper --- pygeo/mphys/mphys_dvgeo.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pygeo/mphys/mphys_dvgeo.py b/pygeo/mphys/mphys_dvgeo.py index a159e6e7..53d1354d 100644 --- a/pygeo/mphys/mphys_dvgeo.py +++ b/pygeo/mphys/mphys_dvgeo.py @@ -531,6 +531,7 @@ def nom_addThicknessConstraints2D( surfaceName="default", DVGeoName="default", compNames=None, + projected=False, ): self.DVCon.addThicknessConstraints2D( leList, @@ -542,11 +543,21 @@ def nom_addThicknessConstraints2D( surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, + projected=projected, ) self.add_output(name, distributed=False, val=np.ones((nSpan * nChord,)), shape=nSpan * nChord) def nom_addThicknessConstraints1D( - self, name, ptList, nCon, axis, scaled=True, surfaceName="default", DVGeoName="default", compNames=None + self, + name, + ptList, + nCon, + axis, + scaled=True, + surfaceName="default", + DVGeoName="default", + compNames=None, + projected=False, ): self.DVCon.addThicknessConstraints1D( ptList, @@ -557,6 +568,7 @@ def nom_addThicknessConstraints1D( surfaceName=surfaceName, DVGeoName=DVGeoName, compNames=compNames, + projected=projected, ) self.add_output(name, distributed=False, val=np.ones(nCon), shape=nCon) From 5c166ef894a8203bb55226ee929a19a2c5b0d8ee Mon Sep 17 00:00:00 2001 From: Joshua Anibal Date: Wed, 6 Mar 2024 19:57:52 -0500 Subject: [PATCH 5/6] guess at how to do reorder imports --- pygeo/constraints/DVCon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygeo/constraints/DVCon.py b/pygeo/constraints/DVCon.py index 46fde2d2..0b66d7f4 100644 --- a/pygeo/constraints/DVCon.py +++ b/pygeo/constraints/DVCon.py @@ -20,10 +20,10 @@ from .planarityConstraint import PlanarityConstraint from .radiusConstraint import RadiusConstraint from .thicknessConstraint import ( + ProjectedThicknessConstraint, ProximityConstraint, ThicknessConstraint, ThicknessToChordConstraint, - ProjectedThicknessConstraint, ) from .volumeConstraint import CompositeVolumeConstraint, TriangulatedVolumeConstraint, VolumeConstraint From fb5835faf51b6cdd24d655ee2071bed51bc3074d Mon Sep 17 00:00:00 2001 From: Joshua Anibal Date: Tue, 9 Jul 2024 15:54:06 -0400 Subject: [PATCH 6/6] add distance constraint --- pygeo/constraints/DVCon.py | 265 +++++++++++++++++++++++ pygeo/constraints/radiusConstraint.py | 20 ++ pygeo/constraints/thicknessConstraint.py | 84 +++++++ pygeo/mphys/mphys_dvgeo.py | 31 +++ tests/reg_tests/test_DVConstraints.py | 89 ++++++++ 5 files changed, 489 insertions(+) diff --git a/pygeo/constraints/DVCon.py b/pygeo/constraints/DVCon.py index 0b66d7f4..3c87831e 100644 --- a/pygeo/constraints/DVCon.py +++ b/pygeo/constraints/DVCon.py @@ -24,6 +24,7 @@ ProximityConstraint, ThicknessConstraint, ThicknessToChordConstraint, + DistanceConstraint, ) from .volumeConstraint import CompositeVolumeConstraint, TriangulatedVolumeConstraint, VolumeConstraint @@ -3421,6 +3422,270 @@ def addMonotonicConstraints( config=config, ) + def addDistanceConstraints( + self, + anchored_pts, + moving_pts, + lower=1.0, + upper=3.0, + scaled=True, + scale=1.0, + name=None, + addToPyOpt=True, + DVGeoName="default", + compNames=None + ): + r""" + Add a set of distance constraints. + The values of the distance costraints is just the distance from each + element in the achored_pts to the same element in the moving_pts array. + Only the 'moving_pts' are embedded into the geometry and are updated. + + Parameters + ---------- + anchored_pts : list or array of size (N x 3) + The list of points used in the distance calculations that do not move + + moving_pts : list or array of size (N x 3) + The list of points used in the distance calculations that are + embbeded in the geometry and are updated. + + nCon : int + The number of thickness constraints to add + + axis : list or array of length 3 + The direction along which the projections will occur. + Typically this will be y or z axis ([0,1,0] or [0,0,1]) + + lower : float or array of size nCon + The lower bound for the constraint. A single float will + apply the same bounds to all constraints, while the array + option will use different bounds for each constraint. + + upper : float or array of size nCon + The upper bound for the constraint. A single float will + apply the same bounds to all constraints, while the array + option will use different bounds for each constraint. + + scaled : bool + Flag specifying whether or not the constraint is to be + implemented in a scaled fashion or not. + + * scaled=True: The initial length of each thickness + constraint is defined to be 1.0. In this case, the lower + and upper bounds are given in multiple of the initial + length. lower=0.85, upper=1.15, would allow for 15% + change in each direction from the original length. For + aerodynamic shape optimizations, this option is used + most often. + + * scaled=False: No scaling is applied and the physical lengths + must be specified for the lower and upper bounds. + + scale : float or array of size nCon + This is the optimization scaling of the + constraint. Typically this parameter will not need to be + changed. If the thickness constraints are scaled, this + already results in well-scaled constraint values, and + scale can be left at 1.0. If scaled=False, it may changed + to a more suitable value of the resulting physical + thickness have magnitudes vastly different than O(1). + + name : str + Normally this does not need to be set. Only use this if + you have multiple DVCon objects and the constraint names + need to be distinguished **or** you are using this set of + thickness constraints for something other than a direct + constraint in pyOptSparse. + + addToPyOpt : bool + Normally this should be left at the default of True. If + the values need to be processed (modified) *before* they are + given to the optimizer, set this flag to False. + + surfaceName : str + Name of the surface to project to. This should be the same + as the surfaceName provided when setSurface() was called. + For backward compatibility, the name is 'default' by default. + + DVGeoName : str + Name of the DVGeo object to compute the constraint with. You only + need to set this if you're using multiple DVGeo objects + for a problem. For backward compatibility, the name is 'default' by default + + compNames : list + If using DVGeometryMulti, the components to which the point set associated + with this constraint should be added. + If None, the point set is added to all components. + + """ + self._checkDVGeo(DVGeoName) + + typeName = "distCon" + if typeName not in self.constraints: + self.constraints[typeName] = OrderedDict() + + if name is None: + conName = "%s_distance_constraints_%d" % (self.name, len(self.constraints[typeName])) + else: + conName = name + + self.constraints[typeName][conName] = DistanceConstraint( + conName, moving_pts, anchored_pts, lower, upper, scaled, scale, self.DVGeometries[DVGeoName], addToPyOpt, compNames + ) + + def addDistanceConstraints1D( + self, + ptList, + nCon, + axis, + lower=1.0, + upper=3.0, + scaled=True, + scale=1.0, + name=None, + addToPyOpt=True, + surfaceName="default", + DVGeoName="default", + compNames=None + ): + r""" + Add a set of distance constraints oriented along a poly-line. + It is different from a thickness contraint becuase it does not project + in both direactions to define a thickness contstraint. + It always meassures the distance from a surface to a fixed point. + + See below for a schematic + + .. code-block:: text + + Planform view of the wing: The '+' are the (three dimensional) + points that are supplied in ptList: + + + Airfoil + \ + \ + ---------------------- ___ + / ---- + | -- + \ ____----- + -----x------x--------- + | | + --------------x | + \ | 2 distance constraints + \ | + \ | + x ---------- Ploy line + Parameters + ---------- + ptList : list or array of size (N x 3) where N >=2 + The list of points forming a poly-line along which the + thickness constraints will be added. + + nCon : int + The number of thickness constraints to add + + axis : list or array of length 3 + The direction along which the projections will occur. + Typically this will be y or z axis ([0,1,0] or [0,0,1]) + + lower : float or array of size nCon + The lower bound for the constraint. A single float will + apply the same bounds to all constraints, while the array + option will use different bounds for each constraint. + + upper : float or array of size nCon + The upper bound for the constraint. A single float will + apply the same bounds to all constraints, while the array + option will use different bounds for each constraint. + + scaled : bool + Flag specifying whether or not the constraint is to be + implemented in a scaled fashion or not. + + * scaled=True: The initial length of each thickness + constraint is defined to be 1.0. In this case, the lower + and upper bounds are given in multiple of the initial + length. lower=0.85, upper=1.15, would allow for 15% + change in each direction from the original length. For + aerodynamic shape optimizations, this option is used + most often. + + * scaled=False: No scaling is applied and the physical lengths + must be specified for the lower and upper bounds. + + scale : float or array of size nCon + This is the optimization scaling of the + constraint. Typically this parameter will not need to be + changed. If the thickness constraints are scaled, this + already results in well-scaled constraint values, and + scale can be left at 1.0. If scaled=False, it may changed + to a more suitable value of the resulting physical + thickness have magnitudes vastly different than O(1). + + name : str + Normally this does not need to be set. Only use this if + you have multiple DVCon objects and the constraint names + need to be distinguished **or** you are using this set of + thickness constraints for something other than a direct + constraint in pyOptSparse. + + addToPyOpt : bool + Normally this should be left at the default of True. If + the values need to be processed (modified) *before* they are + given to the optimizer, set this flag to False. + + surfaceName : str + Name of the surface to project to. This should be the same + as the surfaceName provided when setSurface() was called. + For backward compatibility, the name is 'default' by default. + + DVGeoName : str + Name of the DVGeo object to compute the constraint with. You only + need to set this if you're using multiple DVGeo objects + for a problem. For backward compatibility, the name is 'default' by default + + compNames : list + If using DVGeometryMulti, the components to which the point set associated + with this constraint should be added. + If None, the point set is added to all components. + + """ + self._checkDVGeo(DVGeoName) + + p0, p1, p2 = self._getSurfaceVertices(surfaceName=surfaceName) + + # Create mesh of intersections + constr_line = Curve(X=ptList, k=2) + s = np.linspace(0, 1, nCon) + anchored_pts = constr_line(s) + moving_pts = np.zeros((nCon, 3)) + # Project all the points + for i in range(nCon): + # Project actual node: + # we only take the up point + up, _, fail = geo_utils.projectNode(anchored_pts[i], axis, p0, p1 - p0, p2 - p0) + if fail > 0: + raise Error( + "There was an error projecting a node " + "at (%f, %f, %f) with normal (%f, %f, %f)." % (anchored_pts[i, 0], anchored_pts[i, 1], anchored_pts[i, 2], axis[0], axis[1], axis[2]) + ) + moving_pts[i] = up + + typeName = "distCon" + if typeName not in self.constraints: + self.constraints[typeName] = OrderedDict() + + if name is None: + conName = "%s_distance_constraints_%d" % (self.name, len(self.constraints[typeName])) + else: + conName = name + + self.constraints[typeName][conName] = DistanceConstraint( + conName, moving_pts, anchored_pts, lower, upper, scaled, scale, self.DVGeometries[DVGeoName], addToPyOpt, compNames + ) + def _checkDVGeo(self, name="default"): """check if DVGeo exists""" if name not in self.DVGeometries.keys(): diff --git a/pygeo/constraints/radiusConstraint.py b/pygeo/constraints/radiusConstraint.py index 7e758b7f..fd6e4594 100644 --- a/pygeo/constraints/radiusConstraint.py +++ b/pygeo/constraints/radiusConstraint.py @@ -182,6 +182,7 @@ def writeTecplot(self, handle): to the open file handle """ r, c = self.computeCircle(self.coords) + p1, p2, p3 = self.splitPointSets(self.coords) # Compute origin and unit vectors (xi, eta) of 2d space _, nxi, neta = self.computeReferenceFrames(self.coords) @@ -204,3 +205,22 @@ def writeTecplot(self, handle): for i in range(self.nCon): for j in range(nres): handle.write("%d %d\n" % (i * nres + j + 1, i * nres + (j + 1) % nres + 1)) + + handle.write("Zone T=%s_circ_points\n" % self.name) + handle.write("Nodes = %d, Elements = %d ZONETYPE=FELINESEG\n" % (self.nCon * 3*2, self.nCon * 3)) + handle.write("DATAPACKING=POINT\n") + + for i in range(self.nCon): + for pt_dir in [p1, p2, p3]: + # for each con plot the vecotrs from the center to the coord points defining the circle + pt1 = c[i] + pt2 = pt_dir[i] + handle.write(f"{pt1[0]:f} {pt1[1]:f} {pt1[2]:f}\n") + handle.write(f"{pt2[0]:f} {pt2[1]:f} {pt2[2]:f}\n") + + # write out the elements + for i in range(self.nCon*3): + handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) + + + diff --git a/pygeo/constraints/thicknessConstraint.py b/pygeo/constraints/thicknessConstraint.py index 419e26dd..492125d9 100644 --- a/pygeo/constraints/thicknessConstraint.py +++ b/pygeo/constraints/thicknessConstraint.py @@ -88,6 +88,90 @@ def writeTecplot(self, handle): handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) +class DistanceConstraint(GeometricConstraint): + """ + DVConstraints representation of a set of distance + constraints. One of these objects is created each time a + addDistanceConstraints or addDistanceConstraints1D call is + made. The user should not have to deal with this class directly. + """ + + def __init__(self, name, moving_pts, anchor_pts, lower, upper, scaled, scale, DVGeo, addToPyOpt, compNames): + super().__init__(name, len(moving_pts), lower, upper, scale, DVGeo, addToPyOpt) + + self.moving_pts = moving_pts + self.scaled = scaled + self.anchored_pts = anchor_pts + + # First thing we can do is embed the coordinates into DVGeo + # with the name provided: + self.DVGeo.addPointSet(self.moving_pts, self.name, compNames=compNames) + + # Now get the reference lengths + self.D0 = np.zeros(self.nCon) + for i in range(self.nCon): + self.D0[i] = geo_utils.norm.euclideanNorm(self.moving_pts[i] - self.anchored_pts[i]) + + def evalFunctions(self, funcs, config): + """ + Evaluate the functions this object has and place in the funcs dictionary + + Parameters + ---------- + funcs : dict + Dictionary to place function values + """ + # Pull out the most recent set of coordinates: + self.moving_pts = self.DVGeo.update(self.name, config=config) + D = np.zeros(self.nCon) + for i in range(self.nCon): + dx = self.moving_pts[i] - self.anchored_pts[i] + D[i] = geo_utils.norm.euclideanNorm(dx) + if self.scaled: + D[i] /= self.D0[i] + funcs[self.name] = D + + def evalFunctionsSens(self, funcsSens, config): + """ + Evaluate the sensitivity of the functions this object has and + place in the funcsSens dictionary + + Parameters + ---------- + funcsSens : dict + Dictionary to place function values + """ + + nDV = self.DVGeo.getNDV() + if nDV > 0: + dDdPt = np.zeros((self.nCon, self.moving_pts.shape[0], self.moving_pts.shape[1])) + + for i in range(self.nCon): + p1b, _ = geo_utils.eDist_b(self.moving_pts[i, :], self.anchored_pts[i] ) + + if self.scaled: + p1b /= self.D0[i] + dDdPt[i, i, :] = p1b + + funcsSens[self.name] = self.DVGeo.totalSensitivity(dDdPt, self.name, config=config) + + + def writeTecplot(self, handle): + """ + Write the visualization of this set of thickness constraints + to the open file handle + """ + handle.write("Zone T=%s\n" % self.name) + handle.write("Nodes = %d, Elements = %d ZONETYPE=FELINESEG\n" % (len(self.moving_pts)*2, len(self.moving_pts))) + handle.write("DATAPACKING=POINT\n") + for i in range(self.nCon): + handle.write(f"{self.moving_pts[i, 0]:f} {self.moving_pts[i, 1]:f} {self.moving_pts[i, 2]:f}\n") + handle.write(f"{self.anchored_pts[i, 0]:f} {self.anchored_pts[i, 1]:f} {self.anchored_pts[i, 2]:f}\n") + + for i in range(len(self.moving_pts)): + handle.write("%d %d\n" % (2 * i + 1, 2 * i + 2)) + + class ProjectedThicknessConstraint(GeometricConstraint): """ DVConstraints representation of a set of projected thickness diff --git a/pygeo/mphys/mphys_dvgeo.py b/pygeo/mphys/mphys_dvgeo.py index 53d1354d..5285ee6a 100644 --- a/pygeo/mphys/mphys_dvgeo.py +++ b/pygeo/mphys/mphys_dvgeo.py @@ -1,3 +1,4 @@ +import os # External modules from mpi4py import MPI import numpy as np @@ -31,6 +32,7 @@ def initialize(self): self.options.declare("type", default=None) self.options.declare("options", default=None) self.options.declare("DVGeoInfo", default=None) + self.options.declare("output_dir", default="./") def setup(self): # create a constraints object to go with this DVGeo(s) @@ -88,6 +90,7 @@ def setup(self): self.DVCon.setDVGeo(DVGeo, name=DVConName) self.omPtSetList = [] + self.call_counter = 0 def compute(self, inputs, outputs): # check for inputs that have been added but the points have not been added to dvgeo @@ -113,6 +116,8 @@ def compute(self, inputs, outputs): # compute the DVCon constraint values constraintfunc = dict() self.DVCon.evalFunctions(constraintfunc, includeLinear=True) + file_name = os.path.join(self.options['output_dir'], f"cons_{self.call_counter}.dat") + self.DVCon.writeTecplot(file_name) for constraintname in constraintfunc: # if any constraint returned a fail flag throw an error to OpenMDAO @@ -124,6 +129,7 @@ def compute(self, inputs, outputs): # we ran a compute so the inputs changed. update the dvcon jac # next time the jacvec product routine is called self.update_jac = True + self.call_counter += 1 def nom_addChild(self, ffd_file, DVGeoName=None, childName=None): # if we have multiple DVGeos use the one specified by name @@ -572,6 +578,31 @@ def nom_addThicknessConstraints1D( ) self.add_output(name, distributed=False, val=np.ones(nCon), shape=nCon) + def nom_addDistanceConstraints1D( + self, + name, + ptList, + nCon, + axis, + scaled=True, + surfaceName="default", + DVGeoName="default", + compNames=None, + projected=False, + ): + self.DVCon.addDistanceConstraints1D( + ptList, + nCon, + axis, + name=name, + scaled=scaled, + surfaceName=surfaceName, + DVGeoName=DVGeoName, + compNames=compNames, + projected=projected, + ) + self.add_output(name, distributed=False, val=np.ones(nCon), shape=nCon) + def nom_addVolumeConstraint( self, name, diff --git a/tests/reg_tests/test_DVConstraints.py b/tests/reg_tests/test_DVConstraints.py index 021b5dde..1db89962 100644 --- a/tests/reg_tests/test_DVConstraints.py +++ b/tests/reg_tests/test_DVConstraints.py @@ -367,6 +367,95 @@ def test_projected_thickness1D_box(self, train=False, refDeriv=False): funcs["DVCon1_thickness_constraints_2"], 8.0 * np.ones(3), name="thickness_base", rtol=1e-7, atol=1e-7 ) + def test_distance_box(self, train=False, refDeriv=False): + refFile = os.path.join(self.base_path, "ref/test_DVConstraints_distance_box.ref") + with BaseRegTest(refFile, train=train) as handler: + DVGeo, DVCon = self.generate_dvgeo_dvcon("box") + DVGeo.addLocalDV("local_x", lower=-0.5, upper=0.5, axis="x", scale=1) + + moving_pts = np.array([ + [0.0, 0.0, 0.0], + [-0.5, 0.0, 2.0], + [0.5, 0.1, 7.0], + ]) + + anchored_pts = np.array([ + [0.0, 0.0, -1.0], # this point is outside the FFD, but it shouldn't matter + [-0.5, 0.0, 0.0], + [0.5, 0.1, 10.0], + ]) + + DVCon.addDistanceConstraints(anchored_pts, moving_pts, scaled=False) + DVCon.addDistanceConstraints(anchored_pts, moving_pts, scaled=True) + DVCon.writeTecplot('distCon0.dat') + funcs, funcsSens = generic_test_base(DVGeo, DVCon, handler) + print('funcs', funcs) + DVCon.writeTecplot('distCon1.dat') + + # Check that unscaled thicknesses are computed correctly at baseline + handler.assert_allclose( + funcs["DVCon1_distance_constraints_0"], np.arange(1,4), name="distance_0_base", rtol=1e-12, atol=1e-12 + ) + handler.assert_allclose( + funcs["DVCon1_distance_constraints_1"], np.ones(len(moving_pts)), name="distance_1_base", rtol=1e-7, atol=1e-7 + ) + DVs = DVGeo.getValues() + DVGeo.setDesignVars({"local_x": 0.5*np.ones_like(DVs["local_x"])}) + funcs = {} + DVCon.evalFunctions(funcs) + + handler.assert_allclose( + funcs["DVCon1_distance_constraints_0"], np.sqrt(np.arange(1,4)**2 + 0.5**2*np.ones(3)), name="distance_0_altered", rtol=1e-12, atol=1e-12 + ) + handler.assert_allclose( + funcs["DVCon1_distance_constraints_1"], np.sqrt(np.arange(1,4)**2 + 0.5**2*np.ones(3))/np.arange(1,4), name="distance_1_altered", rtol=1e-12, atol=1e-12 + ) + + def test_distance_1D_c172(self, train=True, refDeriv=False): + # refFile = os.path.join(self.base_path, "ref/test_DVConstraints_distance_1D_box.ref") + refFile = os.path.join(self.base_path, "ref/test_DVConstraints_distance_1D_c172.ref") + with BaseRegTest(refFile, train=train) as handler: + DVGeo, DVCon = self.generate_dvgeo_dvcon("c172") + DVGeo.addLocalDV("local_x", lower=-0.5, upper=0.5, axis="x", scale=1) + + # This line is below the geometry + + + anchored_pts = np.array([ + [0.47, 0.0, 1e-2], + [0.47, 0.0, 5.24], + ]) + + anchored_pts_1 = anchored_pts.copy() + anchored_pts_1[:,1] -= 1.0 + + nCon = 5 + + DVCon.addDistanceConstraints1D(anchored_pts, nCon, axis=[0, 1, 0], scaled=False) + DVCon.addDistanceConstraints1D(anchored_pts_1, nCon, axis=[0, 1, 0], scaled=False) + + DVCon.writeTecplot('distCon0.dat') + funcs, funcsSens = generic_test_base(DVGeo, DVCon, handler) + DVCon.writeTecplot('distCon1.dat') + + + # Check that unscaled thicknesses are computed correctly at baseline + base_dis = funcs["DVCon1_distance_constraints_0"] + handler.assert_allclose( + funcs["DVCon1_distance_constraints_1"], base_dis + 1.0, name="distance_1_base", rtol=1e-12, atol=1e-12 + ) + DVs = DVGeo.getValues() + DVGeo.setDesignVars({"local_x": 0.5*np.ones_like(DVs["local_x"])}) + funcs = {} + DVCon.evalFunctions(funcs) + handler.assert_allclose( + funcs["DVCon1_distance_constraints_0"], np.sqrt(base_dis**2 + 0.5**2*np.ones(5)), name="distance_altered", rtol=1e-12, atol=1e-12 + ) + handler.assert_allclose( + funcs["DVCon1_distance_constraints_1"], np.sqrt((base_dis+1)**2 + 0.5**2*np.ones(5)), name="distance_1_altered", rtol=1e-12, atol=1e-12 + ) + + def test_thickness2D(self, train=False, refDeriv=False): refFile = os.path.join(self.base_path, "ref/test_DVConstraints_thickness2D.ref") with BaseRegTest(refFile, train=train) as handler: