Skip to content

Commit af05b30

Browse files
lucaeroseytanadler
andauthored
Add the possibility to change the position of the scaling for the chord design variable (#410)
* chord scaling position * flake8 * flake8 balc * git pb * pb git * add test chord scaling * change documentation * corrected test * add warnings and check chord scaling * stack level * change version init * Separate chord_scaling_pos test into two separate tests --------- Co-authored-by: Eytan Adler <[email protected]>
1 parent 2567ea9 commit af05b30

File tree

7 files changed

+91
-13
lines changed

7 files changed

+91
-13
lines changed

openaerostruct/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.6.1"
1+
__version__ = "2.6.2"

openaerostruct/docs/user_reference/mesh_surface_dict.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ The surface dict will be provided to Groups, including ``Geometry``, ``AeroPoint
9191
- np.array([0, 5])
9292
- deg
9393
- B-spline control points for twist distribution. Array convention is ``[wing tip, ..., root]`` in symmetry cases, and ``[tip, ..., root, ... tip]`` when ``symmetry = False``.
94+
* - chord_cp
95+
- np.array([0.1, 5])
96+
- m
97+
- B-spline control points for chord distribution. Array convention is the same than ``twist_cp``.
98+
* - chord_scaling_pos
99+
- 0.25
100+
-
101+
- Chord position at which the chord scaling factor is applied. 1 is the trailing edge, 0 is the leading edge.
94102

95103
.. list-table:: Aerodynamics definitions
96104
:widths: 20 20 5 55

openaerostruct/examples/rectangular_wing/opt_chord.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"S_ref_type": "projected", # how we compute the wing area,
5858
# can be 'wetted' or 'projected'
5959
"chord_cp": np.ones(3), # Define chord using 3 B-spline cp's
60+
"chord_scaling_pos": 0.25, # Define the chord scaling position. 0 is the leading edge, 1 is the trailing edge.
6061
# distributed along span
6162
"mesh": mesh,
6263
# Aerodynamic performance of the lifting surface at

openaerostruct/geometry/geometry_mesh.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ShearZ,
1616
Rotate,
1717
)
18+
import warnings
1819

1920

2021
class GeometryMesh(om.Group):
@@ -77,12 +78,23 @@ def setup(self):
7778
# 2. Scale X
7879

7980
val = np.ones(ny)
81+
chord_scaling_pos = 0.25 # if no scaling position is specified : chord scaling w.r.t quarter of chord
8082
if "chord_cp" in surface:
8183
promotes = ["chord"]
84+
if "chord_scaling_pos" in surface:
85+
chord_scaling_pos = surface["chord_scaling_pos"]
8286
else:
87+
if "chord_scaling_pos" in surface:
88+
warnings.warn(
89+
"Chord_scaling_pos has been specified but no chord design variable available", stacklevel=2
90+
)
8391
promotes = []
8492

85-
self.add_subsystem("scale_x", ScaleX(val=val, mesh_shape=mesh_shape), promotes_inputs=promotes)
93+
self.add_subsystem(
94+
"scale_x",
95+
ScaleX(val=val, mesh_shape=mesh_shape, chord_scaling_pos=chord_scaling_pos),
96+
promotes_inputs=promotes,
97+
)
8698

8799
# 3. Sweep
88100

openaerostruct/geometry/geometry_mesh_transformations.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,16 @@ def initialize(self):
131131
"""
132132
self.options.declare("val", desc="Initial value for chord lengths")
133133
self.options.declare("mesh_shape", desc="Tuple containing mesh shape (nx, ny).")
134+
self.options.declare(
135+
"chord_scaling_pos",
136+
default=0.25,
137+
desc="float which indicates the chord fraction where to do the chord scaling. 1. is trailing edge, 0. is leading edge",
138+
)
134139

135140
def setup(self):
136141
mesh_shape = self.options["mesh_shape"]
137142
val = self.options["val"]
138-
143+
self.chord_scaling_pos = self.options["chord_scaling_pos"]
139144
self.add_input("chord", units="m", val=val)
140145
self.add_input("in_mesh", shape=mesh_shape, units="m")
141146

@@ -166,19 +171,19 @@ def compute(self, inputs, outputs):
166171

167172
te = mesh[-1]
168173
le = mesh[0]
169-
quarter_chord = 0.25 * te + 0.75 * le
174+
chord_pos = self.chord_scaling_pos * te + (1 - self.chord_scaling_pos) * le
170175

171-
outputs["mesh"] = np.einsum("ijk,j->ijk", mesh - quarter_chord, chord_dist) + quarter_chord
176+
outputs["mesh"] = np.einsum("ijk,j->ijk", mesh - chord_pos, chord_dist) + chord_pos
172177

173178
def compute_partials(self, inputs, partials):
174179
mesh = inputs["in_mesh"]
175180
chord_dist = inputs["chord"]
176181

177182
te = mesh[-1]
178183
le = mesh[0]
179-
quarter_chord = 0.25 * te + 0.75 * le
184+
chord_pos = self.chord_scaling_pos * te + (1 - self.chord_scaling_pos) * le
180185

181-
partials["mesh", "chord"] = (mesh - quarter_chord).flatten()
186+
partials["mesh", "chord"] = (mesh - chord_pos).flatten()
182187

183188
nx, ny, _ = mesh.shape
184189
nn = nx * ny * 3
@@ -187,12 +192,12 @@ def compute_partials(self, inputs, partials):
187192

188193
d_qc = (np.einsum("ij,i->ij", np.ones((ny, 3)), 1.0 - chord_dist)).flatten()
189194
nnq = (nx - 1) * ny * 3
190-
partials["mesh", "in_mesh"][nn : nn + nnq] = np.tile(0.25 * d_qc, nx - 1)
191-
partials["mesh", "in_mesh"][nn + nnq :] = np.tile(0.75 * d_qc, nx - 1)
195+
partials["mesh", "in_mesh"][nn : nn + nnq] = np.tile(self.chord_scaling_pos * d_qc, nx - 1)
196+
partials["mesh", "in_mesh"][nn + nnq :] = np.tile((1 - self.chord_scaling_pos) * d_qc, nx - 1)
192197

193198
nnq = ny * 3
194-
partials["mesh", "in_mesh"][nn - nnq : nn] += 0.25 * d_qc
195-
partials["mesh", "in_mesh"][:nnq] += 0.75 * d_qc
199+
partials["mesh", "in_mesh"][nn - nnq : nn] += self.chord_scaling_pos * d_qc
200+
partials["mesh", "in_mesh"][:nnq] += (1 - self.chord_scaling_pos) * d_qc
196201

197202

198203
class Sweep(om.ExplicitComponent):

openaerostruct/geometry/tests/test_geometry_mesh.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def test(self):
2121
# The way this is currently set up, we don't actually use the values here
2222
surface["twist_cp"] = np.zeros((5))
2323
surface["chord_cp"] = np.zeros((5))
24+
surface["chord_scaling_pos"] = 0.25
2425
surface["xshear_cp"] = np.zeros((5))
2526
surface["yshear_cp"] = np.zeros((5))
2627
surface["zshear_cp"] = np.zeros((5))

openaerostruct/geometry/tests/test_geometry_mesh_transformations.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import unittest
55

66
import openmdao.api as om
7-
from openmdao.utils.assert_utils import assert_check_partials
7+
from openmdao.utils.assert_utils import assert_check_partials, assert_near_equal
88

99
from openaerostruct.geometry.geometry_mesh_transformations import (
1010
Taper,
@@ -31,7 +31,13 @@ def get_mesh(symmetry):
3131
ny = (2 * NY - 1) if symmetry else NY
3232

3333
# Create a dictionary to store options about the mesh
34-
mesh_dict = {"num_y": ny, "num_x": NX, "wing_type": "CRM", "symmetry": symmetry, "num_twist_cp": NY}
34+
mesh_dict = {
35+
"num_y": ny,
36+
"num_x": NX,
37+
"wing_type": "CRM",
38+
"symmetry": symmetry,
39+
"num_twist_cp": NY,
40+
}
3541

3642
# Generate the aerodynamic mesh based on the previous dictionary
3743
mesh, twist_cp = generate_mesh(mesh_dict)
@@ -132,6 +138,51 @@ def test_scalex_symmetry(self):
132138
check = prob.check_partials(compact_print=True, abs_err_tol=1e-5, rel_err_tol=1e-5)
133139
assert_check_partials(check, atol=1e-6, rtol=1e-6)
134140

141+
def test_scalex_chord_scaling_pos_random(self):
142+
symmetry = False
143+
mesh = get_mesh(symmetry)
144+
145+
# Test for random values of chord_scaling_pos, check derivatives
146+
prob = om.Problem()
147+
group = prob.model
148+
149+
val = self.rng.random(NY)
150+
chord_scaling_pos = self.rng.random(1)
151+
152+
comp = ScaleX(val=val, mesh_shape=mesh.shape, chord_scaling_pos=chord_scaling_pos)
153+
group.add_subsystem("comp", comp)
154+
155+
prob.setup()
156+
157+
prob["comp.in_mesh"] = mesh
158+
159+
prob.run_model()
160+
161+
check = prob.check_partials(compact_print=True, abs_err_tol=1e-5, rel_err_tol=1e-5)
162+
assert_check_partials(check, atol=1e-6, rtol=1e-6)
163+
164+
def test_scalex_chord_scaling_pos_trailing_edge(self):
165+
symmetry = True
166+
mesh = get_mesh(symmetry)
167+
168+
# Test for chord_scaling_pos at trailing edge
169+
prob = om.Problem()
170+
group = prob.model
171+
172+
val = self.rng.random(NY)
173+
174+
comp = ScaleX(val=val, mesh_shape=mesh.shape, chord_scaling_pos=1)
175+
group.add_subsystem("comp", comp)
176+
177+
prob.setup()
178+
179+
prob["comp.in_mesh"] = mesh
180+
181+
prob.run_model()
182+
183+
# If chord_scaling_pos = 1, TE should not move
184+
assert_near_equal(mesh[-1, :, :], prob["comp.mesh"][-1, :, :], tolerance=1e-10)
185+
135186
def test_sweep(self):
136187
symmetry = False
137188
mesh = get_mesh(symmetry)

0 commit comments

Comments
 (0)