Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor fix to mesh generator #430

Merged
merged 10 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/oas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false # continue other jobs even if one of the jobs in matrix fails
matrix:
dep-versions: ["oldest", "latest"]
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Set versions to test here ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -163,12 +164,13 @@ jobs:
coverage xml

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true

env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

# --- linting and formatting ---
black:
uses: mdolab/.github/.github/workflows/black.yaml@main
Expand Down
2 changes: 1 addition & 1 deletion openaerostruct/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.7.1"
__version__ = "2.7.2"

Check warning on line 1 in openaerostruct/__init__.py

View check run for this annotation

Codecov / codecov/patch

openaerostruct/__init__.py#L1

Added line #L1 was not covered by tests
2 changes: 1 addition & 1 deletion openaerostruct/docs/user_reference/mesh_surface_dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Here is a list of the keys and default values of the ``mesh_dict``, which is use
* - num_x
- 3
-
- Number of chordwise vertices. Needs to be 2 or an odd number.
- Number of chordwise vertices.
* - num_y
- 5
-
Expand Down
2 changes: 2 additions & 0 deletions openaerostruct/geometry/tests/test_vsp_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def test_full(self):
"span": 10.0,
"root_chord": 1,
"span_cos_spacing": 0.0,
"offset": [-0.5, 0, 0], # chordwise coordinate goes from -0.5 to 0.5
}

oas_mesh = generate_mesh(mesh_dict)
Expand All @@ -46,6 +47,7 @@ def test_symm(self):
"span": 10.0,
"root_chord": 1,
"span_cos_spacing": 0.0,
"offset": [-0.5, 0, 0], # chordwise coordinate goes from -0.5 to 0.5
}

oas_mesh = generate_mesh(mesh_dict)
Expand Down
74 changes: 38 additions & 36 deletions openaerostruct/geometry/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ def gen_rect_mesh(num_x, num_y, span, chord, span_cos_spacing=0.0, chord_cos_spa
mesh = np.zeros((num_x, num_y, 3))
ny2 = (num_y + 1) // 2

# --- spanwise discretization ---
# Hotfix a special case for spacing bunched at the root and tips
if span_cos_spacing == 2.0:
beta = np.linspace(0, np.pi, ny2)
Expand All @@ -396,23 +397,17 @@ def gen_rect_mesh(num_x, num_y, span, chord, span_cos_spacing=0.0, chord_cos_spa
half_wing = cosine * span_cos_spacing + (1 - span_cos_spacing) * uniform
full_wing = np.hstack((-half_wing[:-1], half_wing[::-1])) * span

nx2 = (num_x + 1) // 2
beta = np.linspace(0, np.pi / 2, nx2)

# mixed spacing with span_cos_spacing as a weighting factor
# this is for the chordwise spacing
cosine = 0.5 * np.cos(beta) # cosine spacing
uniform = np.linspace(0, 0.5, nx2)[::-1] # uniform spacing
half_wing = cosine * chord_cos_spacing + (1 - chord_cos_spacing) * uniform
full_wing_x = np.hstack((-half_wing[:-1], half_wing[::-1])) * chord

# Special case if there are only 2 chordwise nodes
if num_x <= 2:
full_wing_x = np.array([0.0, chord])
# --- chordwise discretization ---
cosine = 0.5 * (1 - np.cos(np.linspace(0, np.pi, num_x))) # cosine spacing from 0 to 1
uniform = np.linspace(0, 1, num_x) # uniform spacing
# mixed spacing with chord_cos_spacing as a weighting factor
wing_x = cosine * chord_cos_spacing + (1 - chord_cos_spacing) * uniform
wing_x *= chord # apply chord length

# --- form 3D mesh array ---
for ind_x in range(num_x):
for ind_y in range(num_y):
mesh[ind_x, ind_y, :] = [full_wing_x[ind_x], full_wing[ind_y], 0]
mesh[ind_x, ind_y, :] = [wing_x[ind_x], full_wing[ind_y], 0]

return mesh

Expand Down Expand Up @@ -557,24 +552,12 @@ def add_chordwise_panels(mesh, num_x, chord_cos_spacing):

# Obtain mesh and num properties
num_y = mesh.shape[1]
nx2 = (num_x + 1) // 2

# Create beta, an array of linear sampling points to pi/2
beta = np.linspace(0, np.pi / 2, nx2)

# Obtain the two spacings that we will use to blend
cosine = 0.5 * np.cos(beta) # cosine spacing
uniform = np.linspace(0, 0.5, nx2)[::-1] # uniform spacing

# Create half of the wing in the chordwise direction
half_wing = cosine * chord_cos_spacing + (1 - chord_cos_spacing) * uniform

if chord_cos_spacing == 0.0:
full_wing_x = np.linspace(0, 1.0, num_x)

else:
# Mirror this half wing into a full wing; offset by 0.5 so it goes 0 to 1
full_wing_x = np.hstack((-half_wing[:-1], half_wing[::-1])) + 0.5
# chordwise discretization
cosine = 0.5 * (1 - np.cos(np.linspace(0, np.pi, num_x))) # cosine spacing from 0 to 1
uniform = np.linspace(0, 1, num_x) # uniform spacing
# mixed spacing with chord_cos_spacing as a weighting factor
wing_x = cosine * chord_cos_spacing + (1 - chord_cos_spacing) * uniform

# Obtain the leading and trailing edges
le = mesh[0, :, :]
Expand All @@ -586,7 +569,7 @@ def add_chordwise_panels(mesh, num_x, chord_cos_spacing):
new_mesh[-1, :, :] = te

for i in range(1, num_x - 1):
w = full_wing_x[i]
w = wing_x[i]
new_mesh[i, :, :] = (1 - w) * le + w * te

return new_mesh
Expand Down Expand Up @@ -635,6 +618,29 @@ def get_default_geo_dict():


def generate_mesh(input_dict):
"""
Generate an OAS mesh.

Parameters
----------
input_dict : dict
Dictionary containing user-provided parameters for the surface definition.
See the following for more information:
https://mdolab-openaerostruct.readthedocs-hosted.com/en/latest/user_reference/mesh_surface_dict.html#mesh-dict

Returns
-------
mesh : numpy array
Nodal coordinates defining the mesh.
shape = (nx, ny, 3),
where nx is the number of chordwise discretization nodes, ny is the number of spanwise discretization nodes.
If input_dict["symmetry"] is True, mesh defines left half of wing.
twist : numpy array, optional
Only for CRM wing (input_dict["wing_type"] == "CRM").
Twist values at the spanwise locations.

"""

# Get defaults and update surface with the user-provided input
surf_dict = get_default_geo_dict()

Expand Down Expand Up @@ -677,10 +683,6 @@ def generate_mesh(input_dict):
if not num_y % 2:
raise ValueError("num_y must be an odd number.")

# Check to make sure that an odd number of chordwise points (num_x) was provided
if not num_x % 2 and not num_x == 2:
raise ValueError("num_x must be an odd number.")

# Generate rectangular mesh
if surf_dict["wing_type"] == "rect":
span = surf_dict["span"]
Expand Down
8 changes: 7 additions & 1 deletion openaerostruct/tests/test_multiple_rect.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ def test(self):
}

# Create a dictionary to store options about the surface
mesh_dict = {"num_y": 5, "num_x": 3, "wing_type": "rect", "symmetry": True, "offset": np.array([50, 0.0, 0.0])}
mesh_dict = {
"num_y": 5,
"num_x": 3,
"wing_type": "rect",
"symmetry": True,
"offset": np.array([49.5, 0.0, 0.0]),
}

mesh = generate_mesh(mesh_dict)

Expand Down
2 changes: 1 addition & 1 deletion openaerostruct/tests/test_scaneagle.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def test(self):
indep_var_comp.add_output("W0", val=10.0, units="kg")
indep_var_comp.add_output("speed_of_sound", val=322.2, units="m/s")
indep_var_comp.add_output("load_factor", val=1.0)
indep_var_comp.add_output("empty_cg", val=np.array([0.2, 0.0, 0.0]), units="m")
indep_var_comp.add_output("empty_cg", val=np.array([0.35, 0.0, 0.0]), units="m")

prob.model.add_subsystem("prob_vars", indep_var_comp, promotes=["*"])

Expand Down
8 changes: 4 additions & 4 deletions openaerostruct/tests/test_struct_point_masses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
class Test(unittest.TestCase):
def test(self):
# Create a dictionary to store options about the surface
mesh_dict = {"num_y": 31, "wing_type": "rect", "span": 10, "symmetry": True}
mesh_dict = {"num_y": 31, "num_x": 3, "wing_type": "rect", "span": 10, "symmetry": True}

mesh = generate_mesh(mesh_dict)

Expand Down Expand Up @@ -48,7 +48,7 @@ def test(self):

point_masses = np.array([[10.0]])

point_mass_locations = np.array([[1.0, -10.0, 0.0]])
point_mass_locations = np.array([[1.5, -10.0, 0.0]])

indep_var_comp.add_output("point_masses", val=point_masses, units="kg")
indep_var_comp.add_output("point_mass_locations", val=point_mass_locations, units="m")
Expand All @@ -69,7 +69,7 @@ def test(self):

def test_multiple_masses(self):
# Create a dictionary to store options about the surface
mesh_dict = {"num_y": 31, "wing_type": "rect", "span": 10, "symmetry": True}
mesh_dict = {"num_y": 31, "num_x": 3, "wing_type": "rect", "span": 10, "symmetry": True}

mesh = generate_mesh(mesh_dict)

Expand Down Expand Up @@ -106,7 +106,7 @@ def test_multiple_masses(self):

point_masses = np.array([[10.0, 20.0]])

point_mass_locations = np.array([[1.0, -1.0, 0.0], [1.0, -2.0, 0.0]])
point_mass_locations = np.array([[1.5, -1.0, 0.0], [1.5, -2.0, 0.0]])

indep_var_comp.add_output("point_masses", val=point_masses, units="kg")
indep_var_comp.add_output("point_mass_locations", val=point_mass_locations, units="m")
Expand Down
Loading