diff --git a/.gitignore b/.gitignore
index 29f482e5..95e0c9e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,8 +124,9 @@ dmypy.json
# This is the cython debug symbols to ignore.
cython_debug/
-# This is a folder full of random tests to ignore.
+# These are folders for scripts in development to ignore.
/testing_in_development/
+/experimental/
# These are folders created by testing to ignore.
/tests/_trial_temp/
@@ -135,7 +136,6 @@ cython_debug/
/tests/unit/fixtures/_trial_temp/
# These are folders and files created by releasing.
-
/build
/dist
/Output
diff --git a/.idea/dictionaries/camer.xml b/.idea/dictionaries/camer.xml
index c9686354..cdbfd53c 100644
--- a/.idea/dictionaries/camer.xml
+++ b/.idea/dictionaries/camer.xml
@@ -160,6 +160,7 @@
viridisvorticesvstab
+ vstackweissteinwettedwhitcomb
diff --git a/.idea/statistic.xml b/.idea/statistic.xml
index dc5cfd72..12066828 100644
--- a/.idea/statistic.xml
+++ b/.idea/statistic.xml
@@ -5,7 +5,9 @@
-
+
+
+
diff --git a/pterasoftware/convergence.py b/pterasoftware/convergence.py
index 385aef0d..e4c678d3 100644
--- a/pterasoftware/convergence.py
+++ b/pterasoftware/convergence.py
@@ -182,6 +182,9 @@ def analyze_steady_convergence(
next_ref_wing_cross_section = ref_wing_cross_sections[
ref_wing_cross_section_id + 1
]
+ # ToDo: Modify this to allow for new geometry with custom
+ # planes for the wing cross sections. As of now,
+ # it fails for vertical wings.
section_length = (
next_ref_wing_cross_section.y_le
- ref_wing_cross_section.y_le
@@ -749,6 +752,9 @@ def analyze_unsteady_convergence(
ref_wing_cross_section_movement_id + 1
].base_wing_cross_section
)
+ # ToDo: Modify this to allow for new geometry
+ # with custom planes for the wing cross
+ # sections. As of now, it fails for vertical wings.
section_length = (
next_ref_base_wing_cross_section.y_le
- ref_base_wing_cross_section.y_le
diff --git a/pterasoftware/functions.py b/pterasoftware/functions.py
index 8226387d..1d283e21 100644
--- a/pterasoftware/functions.py
+++ b/pterasoftware/functions.py
@@ -35,12 +35,18 @@
update_ring_vortex_solvers_panel_attributes: This function populates a ring
vortex solver's attributes with the attributes of a given panel.
- calculate_steady_freestream_wing_influences: This method finds the vector of
+ calculate_steady_freestream_wing_influences: This function finds the vector of
freestream-wing influence coefficients associated with this problem.
numba_1d_explicit_cross: This function takes in two arrays, each of which contain
N vectors of 3 components. The function then calculates and returns the cross
product of the two vectors at each position.
+
+ reflect_point_across_plane: This function finds the coordinates of the reflection
+ of a point across a plane in 3D space.
+
+ interp_between_points: This function finds the MxN points between M pairs of
+ points in 3D space given an array of N normalized spacings.
"""
import logging
@@ -602,7 +608,7 @@ def update_ring_vortex_solvers_panel_attributes(
def calculate_steady_freestream_wing_influences(steady_solver):
- """This method finds the vector of freestream-wing influence coefficients
+ """This function finds the vector of freestream-wing influence coefficients
associated with this problem.
:return: None
@@ -650,3 +656,102 @@ def numba_1d_explicit_cross(vectors_1, vectors_2):
vectors_1[i, 0] * vectors_2[i, 1] - vectors_1[i, 1] * vectors_2[i, 0]
)
return crosses
+
+
+def reflect_point_across_plane(point, plane_unit_normal, plane_point):
+ """This function finds the coordinates of the reflection of a point across a
+ plane in 3D space.
+
+ As the plane doesn't necessarily include the origin, this is an affine
+ transformation. The transformation matrix and details are discussed in the
+ following source: https://en.wikipedia.org/wiki/Transformation_matrix#Reflection_2
+
+ :param point: (3,) array of floats
+ This is a (3,) array of the coordinates of the point to reflect. The units
+ are meters.
+ :param plane_unit_normal: (3,) array of floats
+ This is a (3,) array of the components of the plane's unit normal vector. It
+ must have unit magnitude. The units are meters.
+ :param plane_point: (3,) array of floats
+ This is a (3,) array of the coordinates of a point on the plane. The units
+ are meters.
+ :return: (3,) array of floats
+ This is a (3,) array of the components of the reflected point. The units are
+ meters.
+ """
+ [x, y, z] = point
+ [a, b, c] = plane_unit_normal
+
+ # Find the dot product between the point on the plane and unit normal.
+ d = np.dot(-plane_point, plane_unit_normal)
+
+ # To make the transformation matrix readable, define new variables for the
+ # products of the vector components and the dot product.
+ ab = a * b
+ ac = a * c
+ ad = a * d
+ bc = b * c
+ bd = b * d
+ cd = c * d
+ a2 = a**2
+ b2 = b**2
+ c2 = c**2
+
+ # Define the affine transformation matrix. See the citation in the docstring for
+ # details.
+ transformation_matrix = np.array(
+ [
+ [1 - 2 * a2, -2 * ab, -2 * ac, -2 * ad],
+ [-2 * ab, 1 - 2 * b2, -2 * bc, -2 * bd],
+ [-2 * ac, -2 * bc, 1 - 2 * c2, -2 * cd],
+ [0, 0, 0, 1],
+ ]
+ )
+
+ expanded_coordinates = np.array([[x], [y], [z], [1]])
+
+ transformed_expanded_coordinates = transformation_matrix @ expanded_coordinates
+
+ # Extract the reflected coordinates in 3D space.
+ [x_prime, y_prime, z_prime] = transformed_expanded_coordinates[:-1, 0]
+
+ return np.array([x_prime, y_prime, z_prime])
+
+
+@njit(cache=True, fastmath=False)
+def interp_between_points(start_points, end_points, norm_spacings):
+ """This function finds the MxN points between M pairs of points in 3D space given
+ an array of N normalized spacings.
+
+ :param start_points: (M, 3) array of floats
+ This is the (M, 3) array containing the coordinates of the M starting points.
+ The units are meters.
+ :param end_points: (M, 3) array of floats
+ This is the (M, 3) array containing the coordinates of the M ending points.
+ The units are meters.
+ :param norm_spacings: (N,) array of floats
+ This is the (N,) array of the N spacings between the starting points and
+ ending points. The values are unitless and must be normalized from 0 to 1.
+ :return points: (M, N, 3) array of floats
+ This is the (M, N, 3) array of the coordinates of the MxN interpolated
+ points. The units are meters.
+ """
+ m = start_points.shape[0]
+ n = norm_spacings.size
+
+ points = np.zeros((m, n, 3))
+
+ for i in range(m):
+ start_point = start_points[i, :]
+ end_point = end_points[i, :]
+
+ vector = end_point - start_point
+
+ for j in range(n):
+ norm_spacing = norm_spacings[j]
+
+ spacing = norm_spacing * vector
+
+ points[i, j, :] = start_point + spacing
+
+ return points
diff --git a/pterasoftware/geometry.py b/pterasoftware/geometry.py
index 52f3051d..7ed0784b 100644
--- a/pterasoftware/geometry.py
+++ b/pterasoftware/geometry.py
@@ -5,8 +5,7 @@
Wing: This is a class used to contain the wings of an Airplane object.
- WingCrossSection: This class is used to contain the cross sections of a Wing
- object.
+ WingCrossSection: This class is used to contain the cross sections of a Wing object.
Airfoil: This class is used to contain the airfoil of a WingCrossSection object.
@@ -37,7 +36,7 @@ class Airplane:
This class contains the following public methods:
set_reference_dimensions_from_wing: This method sets the reference dimensions
- of the current_airplane from measurements obtained from the main wing.
+ of the airplane from measurements obtained from the main wing.
This class contains the following class attributes:
None
@@ -48,20 +47,23 @@ class Airplane:
def __init__(
self,
- name="Untitled",
+ wings,
+ name="Untitled Airplane",
x_ref=0.0,
y_ref=0.0,
z_ref=0.0,
weight=0.0,
- wings=None,
s_ref=None,
c_ref=None,
b_ref=None,
):
"""This is the initialization method.
+ :param wings: list of Wing objects
+ This is a list of the airplane's wings defined as Wing objects. It must
+ contain at least one Wing object.
:param name: str, optional
- A sensible name for your current_airplane. The default is "Untitled".
+ A sensible name for your airplane. The default is "Untitled Airplane".
:param x_ref: float, optional
This is the x coordinate of the moment reference point. It should be the
x coordinate of the center of gravity. The default is 0.0.
@@ -74,9 +76,6 @@ def __init__(
:param weight: float, optional
This parameter holds the weight of the aircraft in Newtons. This is used
by the trim functions. The default value is 0.0.
- :param wings: list of Wing objects, optional
- This is a list of the current_airplane's wings defined as Wing objects.
- The default is None, which this method converts to an empty list.
:param s_ref: float, optional if more than one wing is in the wings list.
This is the reference wetted area. If not set, it populates from first
wing object.
@@ -87,26 +86,24 @@ def __init__(
This is the reference calculate_span. If not set, it populates from first
wing object.
"""
+ # Initialize the list of wings or raise an exception if it is empty.
+ if len(wings) > 0:
+ self.wings = wings
+ else:
+ raise Exception("An airplane's list of wings must have at least one entry.")
- # Initialize the name and the moment reference point.
+ # Initialize the name, moment reference point coordinates, and weight.
self.name = name
self.x_ref = x_ref
self.y_ref = y_ref
self.z_ref = z_ref
- self.xyz_ref = np.array([self.x_ref, self.y_ref, self.z_ref])
-
- # Initialize the weight.
self.weight = weight
- # If wings was passed as None, set wings to an empty list.
- if wings is None:
- wings = []
- self.wings = wings
+ # Create a (3,) array to hold the moment reference point coordinates.
+ self.xyz_ref = np.array([self.x_ref, self.y_ref, self.z_ref])
- # If the wing list is not empty, set the wing reference dimensions to be the
- # main wing's reference dimensions.
- if len(self.wings) > 0:
- self.set_reference_dimensions_from_main_wing()
+ # Set the wing reference dimensions to be the main wing's reference dimensions.
+ self.set_reference_dimensions_from_main_wing()
# If any of the passed reference dimensions are not None, set that reference
# dimension to be what was passed.
@@ -130,7 +127,7 @@ def __init__(
self.total_near_field_moment_coefficients_wind_axes = None
def set_reference_dimensions_from_main_wing(self):
- """This method sets the reference dimensions of the current_airplane from
+ """This method sets the reference dimensions of the airplane from
measurements obtained from the main wing.
This method assumes the main wing to be the first wing in the wings list
@@ -153,9 +150,9 @@ def set_reference_dimensions_from_main_wing(self):
class Wing:
"""This is a class used to contain the wings of an Airplane object.
- If the wing is symmetric across the XZ plane, just define the right half and
- supply "symmetric=True" in the constructor. If the wing is not symmetric across
- the XZ plane, just define the wing.
+ If the wing is symmetric about some plane, just define the right half,
+ supply "symmetric=True" in the constructor, and include the symmetry plane's
+ normal vector in the "symmetry_plane_normal" attribute.
Citation:
Adapted from: geometry.Wing in AeroSandbox
@@ -163,14 +160,16 @@ class Wing:
Date of Retrieval: 04/24/2020
This class contains the following public methods:
- projected_area: This method calculates the projected area of the wing and
- assigns it to the projected_area attribute.
+ unit_up_vector: This method sets a property for the wing's up orientation
+ vector, which is defined as the cross product of its unit chordwise and unit
+ normal vectors.
+
+ projected_area: This method defines a property for the area of the wing
+ projected onto the plane defined by the projected unit normal vector.
- wetted_area: This method calculates the wetted area of the wing based on the
- areas of its panels and assigns it to the wetted_area attribute.
+ wetted_area: This method defines a property for the wing's wetted area.
- span: This method calculates the span of the wing and assigns it to the span
- attribute.
+ span: This method defines a property for the wing's span.
standard_mean_chord: This method calculates the standard mean chord of the
wing and assigns it to the standard_mean_chord attribute.
@@ -187,84 +186,155 @@ class Wing:
def __init__(
self,
+ wing_cross_sections,
name="Untitled Wing",
x_le=0.0,
y_le=0.0,
z_le=0.0,
- wing_cross_sections=None,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
symmetric=False,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
num_chordwise_panels=8,
chordwise_spacing="cosine",
):
"""This is the initialization method.
+ :param wing_cross_sections: list of WingCrossSection objects
+ This is a list of WingCrossSection objects, that represent the wing's
+ cross sections.
:param name: str, optional
This is a sensible name for the wing. The default is "Untitled Wing".
:param x_le: float, optional
This is the x coordinate of the leading edge of the wing, relative to the
- current_airplane's reference
- point. The default is 0.0.
+ airplane's reference point. The default is 0.0.
:param y_le: float, optional
This is the y coordinate of the leading edge of the wing, relative to the
- current_airplane's reference
- point. The default is 0.0.
+ airplane's reference point. The default is 0.0.
:param z_le: float, optional
This is the z coordinate of the leading edge of the wing, relative to the
- current_airplane's reference
- point. The default is 0.0.
- :param wing_cross_sections: list of WingCrossSection objects, optional
- This is a list of WingCrossSection objects, that represent the wing's
- cross sections. The default is None.
+ airplane's reference point. The default is 0.0.
+ :param unit_normal_vector: array, optional
+ This is an (3,) array of floats that represents the unit normal vector
+ of the wing's symmetry plane. It is also the direction vector that the
+ wing's span will be assessed relative to. Additionally, this vector
+ crossed with the "unit_chordwise_vector" defines the normal vector of the
+ plane that the wing's projected area will reference. It must be
+ equivalent to this wing's root wing cross section's "unit_normal_vector"
+ attribute. The default is np.array([0.0, 1.0, 0.0]), which is the XZ
+ plane's unit normal vector.
:param symmetric: bool, optional
Set this to true if the wing is across the xz plane. Set it to false if
not. The default is false.
+ :param unit_chordwise_vector: array, optional
+ This is an (3,) array of floats that represents the unit vector that
+ defines the wing's chordwise direction. This vector crossed with the
+ "unit_normal_vector" defines the normal vector of the plane that
+ the wing's projected area will reference. This vector must be parallel to
+ the intersection of the wing's symmetry plane with each of its wing cross
+ section's planes. The default is np.array([1.0, 0.0, 0.0]), which is the
+ X unit vector.
:param num_chordwise_panels: int, optional
This is the number of chordwise panels to be used on this wing. The
default is 8.
:param chordwise_spacing: str, optional
This is the type of spacing between the wing's chordwise panels. It can
- be set to "cosine" or "uniform".
- Cosine is highly recommended. The default is cosine.
+ be set to "cosine" or "uniform". Using a cosine spacing is highly
+ recommended for steady simulations and a uniform spacing is highly
+ recommended for unsteady simulations. The default is "cosine".
"""
+ # Initialize the list of wing cross sections or raise an exception if it
+ # contains less than two entries.
+ if len(wing_cross_sections) >= 2:
+ self.wing_cross_sections = wing_cross_sections
+ else:
+ raise Exception(
+ "An wing's list of wing cross sections must have at least "
+ "two entries."
+ )
+
# Initialize the name and the position of the wing's leading edge.
self.name = name
self.x_le = x_le
self.y_le = y_le
self.z_le = z_le
+ self.unit_normal_vector = unit_normal_vector
+ self.symmetric = symmetric
+ self.unit_chordwise_vector = unit_chordwise_vector
+ self.num_chordwise_panels = num_chordwise_panels
+
+ # If the value for the chordwise spacing valid, initial it. Otherwise,
+ # raise an exception.
+ if chordwise_spacing in ["cosine", "uniform"]:
+ self.chordwise_spacing = chordwise_spacing
+ else:
+ raise Exception('The chordwise spacing must be "cosine" or "uniform".')
+
+ # Create a (3,) array to hold the leading edge's coordinates.
self.leading_edge = np.array([self.x_le, self.y_le, self.z_le])
- # If wing_cross_sections is set to None, set it to an empty list.
- if wing_cross_sections is None:
- wing_cross_sections = []
+ # Check that the wing's symmetry plane is equal to its root wing cross
+ # section's plane.
+ if not np.array_equal(
+ self.unit_normal_vector,
+ self.wing_cross_sections[0].unit_normal_vector,
+ ):
+ raise Exception(
+ "The wing's symmetry plane must be the same as its root wing cross "
+ "section's plane."
+ )
- # Initialize the other attributes.
- self.wing_cross_sections = wing_cross_sections
- self.symmetric = symmetric
- self.num_chordwise_panels = num_chordwise_panels
- self.chordwise_spacing = chordwise_spacing
+ # Check that the root wing cross section's leading edge isn't offset from the
+ # wing's leading edge.
+ if np.any(self.wing_cross_sections[0].leading_edge):
+ raise Exception(
+ "The root wing cross section's leading edge must not be offset from "
+ "the wing's leading edge."
+ )
- # Catch invalid values of chordwise_spacing.
- if self.chordwise_spacing not in ["cosine", "uniform"]:
- raise Exception("Invalid value of chordwise_spacing!")
+ # Check that the wing's chordwise and normal directions are perpendicular.
+ if np.dot(self.unit_chordwise_vector, self.unit_normal_vector) != 0:
+ raise Exception(
+ "Every wing cross section's plane must intersect with the wing's "
+ "symmetry plane along a line that is parallel with the wing's "
+ "chordwise direction."
+ )
# Find the number of spanwise panels on the wing by adding each cross
# section's number of spanwise panels. Exclude the last cross section's
# number of spanwise panels as this is irrelevant. If the wing is symmetric,
# multiply the summation by two.
self.num_spanwise_panels = 0
- for cross_section in self.wing_cross_sections[:-1]:
- self.num_spanwise_panels += cross_section.num_spanwise_panels
+ for wing_cross_section in self.wing_cross_sections[:-1]:
+ self.num_spanwise_panels += wing_cross_section.num_spanwise_panels
if self.symmetric:
self.num_spanwise_panels *= 2
- if self.symmetric and self.wing_cross_sections[0].y_le != 0:
- raise Exception("Symmetric wing with root wing cross section off XZ plane!")
+ # Check that all the wing cross sections have valid unit normal vectors.
+ for wing_cross_section in self.wing_cross_sections:
+
+ # Find the vector parallel to the intersection of this wing cross
+ # section's plane and the wing's symmetry plane.
+ plane_intersection_vector = np.cross(
+ self.unit_normal_vector, wing_cross_section.unit_normal_vector
+ )
+
+ # If this vector is not parallel to the wing's chordwise vector, raise an
+ # exception.
+ if np.any(np.cross(plane_intersection_vector, self.unit_chordwise_vector)):
+ raise Exception(
+ "Every wing cross section's plane must intersect with the wing's "
+ "symmetry plane along a line that is parallel with the wing's "
+ "chordwise direction."
+ )
# Calculate the number of panels on this wing.
self.num_panels = self.num_spanwise_panels * self.num_chordwise_panels
- # Initialize the panels attribute. Then mesh the wing, which will
- # populate this attribute.
+ for wing_cross_section in self.wing_cross_sections:
+ wing_cross_section.wing_unit_chordwise_vector = self.unit_chordwise_vector
+
+ # Initialize the panels attribute. Then mesh the wing, which will populate
+ # this attribute.
self.panels = None
meshing.mesh_wing(self)
@@ -273,12 +343,23 @@ def __init__(
self.wake_ring_vortex_vertices = np.empty((0, self.num_spanwise_panels + 1, 3))
self.wake_ring_vortices = np.zeros((0, self.num_spanwise_panels), dtype=object)
+ @property
+ def unit_up_vector(self):
+ """This method sets a property for the wing's up orientation
+ vector, which is defined as the cross product of its unit chordwise and unit
+ normal vectors.
+
+ :return: (3,) array of floats
+ This is the wing's unit up vector. The units are meters.
+ """
+ return np.cross(self.unit_chordwise_vector, self.unit_normal_vector)
+
@property
def projected_area(self):
- """This method calculates the projected area of the wing and assigns it to
- the projected_area attribute.
+ """This method defines a property for the area of the wing projected onto the
+ plane defined by the projected unit normal vector.
- If the wing is symmetrical, the area of the mirrored half is included.
+ If the wing is symmetric, the area of the mirrored half is included.
:return projected_area: float
This attribute is the projected area of the wing. It has units of square
@@ -286,33 +367,19 @@ def projected_area(self):
"""
projected_area = 0
- # Iterate through the wing cross sections and add the area of their
- # corresponding wing sections to the total projected area.
- for wing_cross_section_id, wing_cross_section in enumerate(
- self.wing_cross_sections[:-1]
- ):
- next_wing_cross_section = self.wing_cross_sections[
- wing_cross_section_id + 1
- ]
-
- span = abs(next_wing_cross_section.y_le - wing_cross_section.y_le)
-
- chord = wing_cross_section.chord
- next_chord = next_wing_cross_section.chord
- mean_chord = (chord + next_chord) / 2
-
- projected_area += mean_chord * span
-
- # If the wing is symmetric, double the projected area.
- if self.symmetric:
- projected_area *= 2
+ # Iterate through the chordwise and spanwise indices of the panels and add
+ # their area to the total projected area.
+ for chordwise_location in range(self.num_chordwise_panels):
+ for spanwise_location in range(self.num_spanwise_panels):
+ projected_area += self.panels[
+ chordwise_location, spanwise_location
+ ].calculate_projected_area(self.unit_up_vector)
return projected_area
@property
def wetted_area(self):
- """This method calculates the wetted area of the wing based on the areas of
- its panels and assigns it to the wetted_area attribute.
+ """This method defines a property for the wing's wetted area.
If the wing is symmetrical, the area of the mirrored half is included.
@@ -326,24 +393,34 @@ def wetted_area(self):
# their area to the total wetted area.
for chordwise_location in range(self.num_chordwise_panels):
for spanwise_location in range(self.num_spanwise_panels):
- # Add each panel's area to the total wetted area of the wing.
wetted_area += self.panels[chordwise_location, spanwise_location].area
return wetted_area
@property
def span(self):
- """This method calculates the span of the wing and assigns it to the span
- attribute.
+ """This method defines a property for the wing's span.
- If the wing is symmetrical, this method includes the span of the mirrored half.
+ The span is found by first finding vector connecting the leading edges of the
+ root and tip wing cross sections. Then, this vector is projected onto the
+ symmetry plane's unit normal vector. The span is defined as the magnitude of
+ this projection. If the wing is symmetrical, this method includes the span of
+ the mirrored half.
:return span: float
- This attribute is the wingspan. It has units of meters.
+ This is the wing's span. It has units of meters.
"""
- # Calculate the span (y-distance between the root and the tip) of the entire
- # wing.
- span = self.wing_cross_sections[-1].y_le - self.wing_cross_sections[0].y_le
+ root_to_tip_leading_edge = (
+ self.wing_cross_sections[-1].leading_edge
+ - self.wing_cross_sections[0].leading_edge
+ )
+
+ projected_leading_edge = (
+ np.dot(root_to_tip_leading_edge, self.unit_normal_vector)
+ * self.unit_normal_vector
+ )
+
+ span = np.linalg.norm(projected_leading_edge)
# If the wing is symmetric, multiply the span by two.
if self.symmetric:
@@ -354,7 +431,9 @@ def span(self):
@property
def standard_mean_chord(self):
"""This method calculates the standard mean chord of the wing and assigns it
- to the standard_mean_chord attribute.
+ to the standard_mean_chord attribute. The standard mean chord is defined as
+ the projected area divided by the span. See their respective methods for the
+ definitions of span and projected area.
:return: float
This is the standard mean chord of the wing. It has units of meters.
@@ -387,14 +466,26 @@ def mean_aerodynamic_chord(self):
root_chord = wing_cross_section.chord
tip_chord = next_wing_cross_section.chord
- section_length = next_wing_cross_section.y_le - wing_cross_section.y_le
+
+ # Find this section's span by following the same procedure as for the
+ # overall wing span.
+ section_leading_edge = (
+ next_wing_cross_section.leading_edge - wing_cross_section.leading_edge
+ )
+
+ projected_section_leading_edge = (
+ np.dot(section_leading_edge, self.unit_normal_vector)
+ * self.unit_normal_vector
+ )
+
+ section_span = np.linalg.norm(projected_section_leading_edge)
# Each wing section is, by definition, trapezoidal (at least when
- # projected on to the body-frame's XY plane). For a trapezoid,
+ # projected on to the wing's projection plane). For a trapezoid,
# the integral from the cited equation can be shown to evaluate to the
# following.
integral += (
- section_length
+ section_span
* (root_chord**2 + root_chord * tip_chord + tip_chord**2)
/ 3
)
@@ -414,8 +505,14 @@ class WingCrossSection:
Date of Retrieval: 04/26/2020
This class contains the following public methods:
- trailing_edge: This method calculates the coordinates of the trailing edge of
- this wing cross section and assigns them to the trailing_edge attribute.
+ unit_chordwise_vector: This method defines a property for the wing cross
+ section's unit chordwise vector.
+
+ unit_up_vector: This method defines a property for the wing cross section's
+ unit up vector.
+
+ trailing_edge: This method defines a property for the coordinates of this
+ wing cross section's trailing edge.
This class contains the following class attributes:
None
@@ -426,12 +523,13 @@ class WingCrossSection:
def __init__(
self,
+ airfoil,
x_le=0.0,
y_le=0.0,
z_le=0.0,
chord=1.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
twist=0.0,
- airfoil=None,
control_surface_type="symmetric",
control_surface_hinge_point=0.75,
control_surface_deflection=0.0,
@@ -440,54 +538,56 @@ def __init__(
):
"""This is the initialization method.
+ :param airfoil: Airfoil
+ This is the airfoil to be used at this wing cross section.
:param x_le: float, optional
- This is the x coordinate of the leading edge of the cross section
- relative to the wing's datum. The default
- value is 0.0.
+ This is the x coordinate of the leading edge of the wing cross section
+ relative to the wing's datum. The default value is 0.0.
:param y_le: float, optional
- This is the y coordinate of the leading edge of the cross section
- relative to the wing's datum. The default
- value is 0.0.
+ This is the y coordinate of the leading edge of the wing cross section
+ relative to the wing's leading edge. The default value is 0.0.
:param z_le: float, optional
- This is the z coordinate of the leading edge of the cross section
- relative to the wing's datum. The default
- value is 0.0.
+ This is the z coordinate of the leading edge of the wing cross section
+ relative to the wing's datum. The default value is 0.0.
:param chord: float, optional
- This is the chord of the wing at this cross section. The default value is
- 1.0.
+ This is the chord of the wing at this wing cross section. The default
+ value is 1.0.
+ :param unit_normal_vector: array, optional
+ This is an (3,) array of floats that represents the unit normal vector
+ of the plane this wing cross section lies on. If this wing cross section
+ is a wing's root, this vector must be equal to the wing's
+ unit_normal_vector attribute. Also, every wing cross section
+ must have a plane that intersects its parent wing's symmetry plane at a
+ line parallel to the parent wing's "unit_chordwise_vector". The default
+ is np.array([ 0.0, 1.0, 0.0]), which is the XZ plane's unit normal vector.
:param twist: float, optional
This is the twist of the cross section about the leading edge in degrees.
The default value is 0.0.
- :param airfoil: Airfoil, optional
- This is the airfoil to be used at this cross section. The default value
- is None.
:param control_surface_type: str, optional
- This is type of control surfaces for this cross section. It can be
- "symmetric" or "asymmetric". An example
- of symmetric control surfaces are flaps. An example of asymmetric control
- surfaces are ailerons. The default
- value is "symmetric".
+ This is type of control surfaces for this wing cross section. It can be
+ "symmetric" or "asymmetric". An example of symmetric control surfaces are
+ flaps. An example of asymmetric control surfaces are ailerons. The
+ default value is "symmetric".
:param control_surface_hinge_point: float, optional
This is the location of the control surface hinge from the leading edge
as a fraction of chord. The default value is 0.75.
:param control_surface_deflection: float, optional
- This is the Control deflection in degrees. Deflection downwards is
- positive. The default value is 0.0
- degrees.
+ This is the control deflection in degrees. Deflection downwards is
+ positive. The default value is 0.0 degrees.
:param num_spanwise_panels: int, optional
- This is the number of spanwise panels to be used between this cross
- section and the next one. The default
- value is 8.
+ This is the number of spanwise panels to be used between this wing cross
+ section and the next one. The default value is 8.
:param spanwise_spacing: str, optional
- This can be 'cosine' or 'uniform'. Using cosine spacing is highly
- recommended. The default value is 'cosine'.
+ This can be "cosine" or "uniform". Using cosine spacing is highly
+ recommended. The default value is "cosine".
"""
- # Initialize all the class attributes.
+ # Initialize all the user-provided attributes.
self.x_le = x_le
self.y_le = y_le
self.z_le = z_le
self.chord = chord
+ self.unit_normal_vector = unit_normal_vector
self.twist = twist
self.airfoil = airfoil
self.control_surface_type = control_surface_type
@@ -495,38 +595,76 @@ def __init__(
self.control_surface_deflection = control_surface_deflection
self.num_spanwise_panels = num_spanwise_panels
self.spanwise_spacing = spanwise_spacing
+
+ # Create a (3,) array to hold the leading edge's coordinates.
self.leading_edge = np.array([x_le, y_le, z_le])
+ # Define an attribute for the parent wing's unit chordwise vector, which will
+ # be set by this wing cross section's parent wing's initialization method.
+ self.wing_unit_chordwise_vector = None
+
# Catch bad values of the chord length.
if self.chord <= 0:
- raise Exception("Invalid value of chord")
+ raise Exception("A wing cross section's chord length must be positive.")
- # Catch invalid values of control_surface_type.
+ # Catch invalid values of the control surface type.
if self.control_surface_type not in ["symmetric", "asymmetric"]:
- raise Exception("Invalid value of control_surface_type")
+ raise Exception(
+ 'A wing cross section\'s control surface type must be "symmetric" or '
+ '"asymmetric".'
+ )
- # Catch invalid values of spanwise_spacing.
+ # Catch invalid values of the spanwise spacing.
if self.spanwise_spacing not in ["cosine", "uniform"]:
- raise Exception("Invalid value of spanwise_spacing!")
+ raise Exception(
+ 'A wing cross section\'s spanwise spacing must be "cosine" or '
+ '"uniform".'
+ )
@property
- def trailing_edge(self):
- """This method calculates the coordinates of the trailing edge of this wing
- cross section and assigns them to the trailing_edge attribute.
+ def unit_chordwise_vector(self):
+ """This method defines a property for the wing cross section's unit chordwise
+ vector.
- :return: array
- This is a 1D array that contains the coordinates of this wing cross
- section's trailing edge.
- """
+ The unit chordwise vector is defined as the parent wing's unit chordwise
+ vector, rotated by the wing cross section's twist about the wing cross
+ section's normal vector.
+ :return: (3,) array of floats
+ This is the unit vector for the wing cross section's chordwise direction.
+ The units are meters.
+ """
# Find the rotation matrix given the cross section's twist.
- rotation_matrix = functions.angle_axis_rotation_matrix(
- self.twist * np.pi / 180, np.array([0, 1, 0])
+ twist_rotation_matrix = functions.angle_axis_rotation_matrix(
+ self.twist * np.pi / 180, self.unit_normal_vector
)
# Use the rotation matrix and the leading edge coordinates to calculate the
- # trailing edge coordinates.
- return self.leading_edge + rotation_matrix @ np.array([self.chord, 0.0, 0.0])
+ # unit chordwise vector.
+ return twist_rotation_matrix @ self.wing_unit_chordwise_vector
+
+ @property
+ def unit_up_vector(self):
+ """This method defines a property for the wing cross section's unit up vector.
+
+ :return: (3,) array of floats
+ This is the unit vector for the wing cross section's chordwise direction.
+ The units are meters.
+ """
+ return np.cross(self.unit_chordwise_vector, self.unit_normal_vector)
+
+ @property
+ def trailing_edge(self):
+ """This method defines a property for the coordinates of this wing cross
+ section's trailing edge.
+
+ :return: (3,) array of floats
+ This is an array of the coordinates of this wing cross section's trailing
+ edge.
+ """
+ chordwise_vector = self.chord * self.unit_chordwise_vector
+
+ return self.leading_edge + chordwise_vector
class Airfoil:
@@ -613,22 +751,24 @@ def __init__(
self.coordinates = coordinates
else:
# If not, populate the coordinates from the directory.
- self.populate_coordinates() # populates self.coordinates
-
- # Check that the coordinates have been set.
- assert hasattr(self, "coordinates")
+ self.populate_coordinates()
- # Initialize other attributes.
+ # Initialize other user-supplied attributes.
self.repanel = repanel
- self.mcl_coordinates = None
- self.upper_minus_mcl = None
- self.thickness = None
self.n_points_per_side = n_points_per_side
+ # Check that the coordinates have been set.
+ assert hasattr(self, "coordinates")
+
# If repanel is True, repanel the airfoil.
if self.repanel:
self.repanel_current_airfoil(n_points_per_side=self.n_points_per_side)
+ # Initialize other attributes that will be set by populate_mcl_coordinates.
+ self.mcl_coordinates = None
+ self.upper_minus_mcl = None
+ self.thickness = None
+
# Populate the mean camber line attributes.
self.populate_mcl_coordinates()
diff --git a/pterasoftware/meshing.py b/pterasoftware/meshing.py
index 601c3787..fb35e473 100644
--- a/pterasoftware/meshing.py
+++ b/pterasoftware/meshing.py
@@ -11,22 +11,14 @@
quadrilateral mesh of its geometry, and then populates the object's panels with
the mesh data.
- get_wing_cross_section_scaling_factors: Get the scaling factors for each wing
- cross section. These factors allow the cross sections to intersect correctly at
- dihedral breaks.
-
- get_panel_vertices: This function calculates the vertices of the panels on a wing.
-
- get_normalized_projected_quarter_chords: This method returns the quarter chords
- of a collection of wing cross sections based on the coordinates of their leading
- and trailing edges. These quarter chords are also projected on to the YZ plane
- and normalized by their magnitudes.
+ get_panel_vertices: This function calculates the vertices of the panels on a wing
+ section.
get_transpose_mcl_vectors: This function takes in the inner and outer airfoils of
a wing cross section and its chordwise coordinates. It returns a list of four
- vectors column vectors. They are, in order, the inner airfoil's local up
- direction, the inner airfoil's local back direction, the outer airfoil's local up
- direction, and the outer airfoil's local back direction.
+ column vectors. They are, in order, the inner airfoil's local up direction,
+ the inner airfoil's local back direction, the outer airfoil's local up direction,
+ and the outer airfoil's local back direction.
get_wing_section_panels: This function takes in arrays panel attributes and
returns a 2D array of panel objects.
@@ -51,7 +43,6 @@ def mesh_wing(wing):
This is the wing to be meshed.
:return: None
"""
-
# Define the number of chordwise panels and points.
num_chordwise_panels = wing.num_chordwise_panels
num_chordwise_coordinates = num_chordwise_panels + 1
@@ -62,124 +53,10 @@ def mesh_wing(wing):
else:
chordwise_coordinates = functions.cosspace(0, 1, num_chordwise_coordinates)
- # Initialize two empty 0 x 3 arrays to hold the corners of each wing cross
- # section. They will eventually be L x 3 arrays, where L is number of wing cross
- # sections.
- wing_cross_sections_leading_edges = np.empty((0, 3))
- wing_cross_sections_trailing_edges = np.empty((0, 3))
-
- # Iterate through the meshed wing cross sections and vertically stack the global
- # location of each wing cross sections leading and trailing edges.
- # wing_cross_section.trailing_edge is a method that returns the wing cross section's
- # trailing edge's coordinates.
- for wing_cross_section in wing.wing_cross_sections:
- wing_cross_sections_leading_edges = np.vstack(
- (
- wing_cross_sections_leading_edges,
- wing_cross_section.leading_edge + wing.leading_edge,
- )
- )
- wing_cross_sections_trailing_edges = np.vstack(
- (
- wing_cross_sections_trailing_edges,
- wing_cross_section.trailing_edge + wing.leading_edge,
- )
- )
-
- normalized_projected_quarter_chords = get_normalized_projected_quarter_chords(
- wing_cross_sections_leading_edges, wing_cross_sections_trailing_edges
- )
-
- # Get the number of wing cross sections.
+ # Get the number of wing cross sections and wing sections.
num_wing_cross_sections = len(wing.wing_cross_sections)
num_wing_sections = num_wing_cross_sections - 1
- # Then, construct the normal directions for each wing cross section. Make the
- # normals for the inner wing cross sections, where we need to merge directions.
- if num_wing_cross_sections > 2:
- # Add together the adjacent normalized wing section quarter chords projected
- # onto the YZ plane.
- wing_sections_local_normals = (
- normalized_projected_quarter_chords[:-1, :]
- + normalized_projected_quarter_chords[1:, :]
- )
-
- # Create a list of the magnitudes of the summed adjacent normalized wing
- # section quarter chords projected onto the YZ plane.
- wing_sections_local_normals_len = np.linalg.norm(
- wing_sections_local_normals, axis=1
- )
-
- # Convert the list to a column vector.
- transpose_wing_sections_local_normals_len = np.expand_dims(
- wing_sections_local_normals_len, axis=1
- )
-
- # Normalize the summed adjacent normalized wing section quarter chords
- # projected onto the YZ plane by their magnitudes.
- wing_sections_local_unit_normals = (
- wing_sections_local_normals / transpose_wing_sections_local_normals_len
- )
-
- # Vertically stack the first normalized wing section quarter chord, the inner
- # normalized wing section quarter chords, and the last normalized wing
- # section quarter chord.
- wing_sections_local_unit_normals = np.vstack(
- (
- normalized_projected_quarter_chords[0, :],
- wing_sections_local_unit_normals,
- normalized_projected_quarter_chords[-1, :],
- )
- )
- else:
- # Vertically stack the first normalized wing section quarter chord, and the
- # last normalized wing section quarter chord.
- wing_sections_local_unit_normals = np.vstack(
- (
- normalized_projected_quarter_chords[0, :],
- normalized_projected_quarter_chords[-1, :],
- )
- )
-
- # Then, construct the back directions for each wing cross section.
- wing_cross_sections_local_back_vectors = (
- wing_cross_sections_trailing_edges - wing_cross_sections_leading_edges
- )
-
- # Create a list of the wing cross section chord lengths.
- wing_cross_sections_chord_lengths = np.linalg.norm(
- wing_cross_sections_local_back_vectors, axis=1
- )
-
- # Convert the list to a column vector.
- transpose_wing_cross_sections_chord_lengths = np.expand_dims(
- wing_cross_sections_chord_lengths, axis=1
- )
-
- # Normalize the wing cross section back vectors by their magnitudes.
- wing_cross_sections_local_back_unit_vectors = (
- wing_cross_sections_local_back_vectors
- / transpose_wing_cross_sections_chord_lengths
- )
-
- # Then, construct the up direction for each wing cross section.
- wing_cross_sections_local_up_unit_vectors = np.cross(
- wing_cross_sections_local_back_unit_vectors,
- wing_sections_local_unit_normals,
- axis=1,
- )
-
- # If the wing is symmetric, set the local up position of the root cross section
- # to be the in local Z direction.
- if wing.symmetric:
- wing_cross_sections_local_up_unit_vectors[0] = np.array([0, 0, 1])
-
- # Get the scaling factor (airfoils at dihedral breaks need to be "taller" to
- # compensate).
- wing_cross_sections_scaling_factors = get_wing_cross_section_scaling_factors(
- wing.symmetric, normalized_projected_quarter_chords
- )
-
# Initialize an empty array that will hold the panels of this wing. It currently
# has 0 columns and M rows, where M is the number of the wing's chordwise panels.
wing_panels = np.empty((num_chordwise_panels, 0), dtype=object)
@@ -204,7 +81,6 @@ def mesh_wing(wing):
hinge_point=inner_wing_cross_section.control_surface_hinge_point,
)
outer_airfoil = outer_wing_cross_section.airfoil.add_control_surface(
- # The inner wing cross section dictates control surface deflections.
deflection=inner_wing_cross_section.control_surface_deflection,
hinge_point=inner_wing_cross_section.control_surface_hinge_point,
)
@@ -232,18 +108,15 @@ def mesh_wing(wing):
front_outer_vertices,
back_inner_vertices,
back_outer_vertices,
- ] = get_panel_vertices(
- inner_wing_cross_section_num,
- wing_cross_sections_local_back_unit_vectors,
- wing_cross_sections_local_up_unit_vectors,
- wing_cross_sections_chord_lengths,
- wing_cross_sections_scaling_factors,
- wing_cross_sections_leading_edges,
+ ] = get_wing_section_panel_vertices(
+ wing.leading_edge,
+ inner_wing_cross_section,
+ outer_wing_cross_section,
transpose_mcl_vectors,
spanwise_coordinates,
)
- # Compute a matrix that is M x N, where M and N are the number of chordwise
+ # Compute a matrix that is (M, N), where M and N are the number of chordwise
# and spanwise panels. The values are either 1 if the panel at that location
# is a trailing edge, or 0 if not.
wing_section_is_trailing_edge = np.vstack(
@@ -253,7 +126,7 @@ def mesh_wing(wing):
)
)
- # Compute a matrix that is M x N, where M and N are the number of chordwise
+ # Compute a matrix that is (M, N), where M and N are the number of chordwise
# and spanwise panels. The values are either 1 if the panel at that location
# is a leading edge, or 0 if not.
wing_section_is_leading_edge = np.vstack(
@@ -292,7 +165,6 @@ def mesh_wing(wing):
)
outer_airfoil = outer_wing_cross_section.airfoil.add_control_surface(
deflection=inner_wing_cross_section.control_surface_deflection,
- # The inner wing cross section dictates control surface deflections.
hinge_point=inner_wing_cross_section.control_surface_hinge_point,
)
else:
@@ -305,7 +177,6 @@ def mesh_wing(wing):
)
outer_airfoil = outer_wing_cross_section.airfoil.add_control_surface(
deflection=-inner_wing_cross_section.control_surface_deflection,
- # The inner wing cross section dictates control surface deflections.
hinge_point=inner_wing_cross_section.control_surface_hinge_point,
)
@@ -321,18 +192,15 @@ def mesh_wing(wing):
front_outer_vertices,
back_inner_vertices,
back_outer_vertices,
- ] = get_panel_vertices(
- inner_wing_cross_section_num,
- wing_cross_sections_local_back_unit_vectors,
- wing_cross_sections_local_up_unit_vectors,
- wing_cross_sections_chord_lengths,
- wing_cross_sections_scaling_factors,
- wing_cross_sections_leading_edges,
+ ] = get_wing_section_panel_vertices(
+ wing.leading_edge,
+ inner_wing_cross_section,
+ outer_wing_cross_section,
transpose_mcl_vectors,
spanwise_coordinates,
)
- # Compute a matrix that is M x N, where M and N are the number of
+ # Compute a matrix that is (M, N), where M and N are the number of
# chordwise and spanwise panels. The values are either 1 if the panel at
# that location is a trailing edge, or 0 if not.
wing_section_is_trailing_edge = np.vstack(
@@ -344,7 +212,7 @@ def mesh_wing(wing):
)
)
- # Compute a matrix that is M x N, where M and N are the number of
+ # Compute a matrix that is (M, N), where M and N are the number of
# chordwise and spanwise panels. The values are either 1 if the panel at
# that location is a leading edge, or 0 if not.
wing_section_is_leading_edge = np.vstack(
@@ -356,18 +224,35 @@ def mesh_wing(wing):
)
)
- # Reflect the vertices across the XZ plane.
- front_inner_vertices_reflected = front_inner_vertices * np.array([1, -1, 1])
- front_outer_vertices_reflected = front_outer_vertices * np.array([1, -1, 1])
- back_inner_vertices_reflected = back_inner_vertices * np.array([1, -1, 1])
- back_outer_vertices_reflected = back_outer_vertices * np.array([1, -1, 1])
-
- # Shift the reflected vertices to account for the wing's leading edge
- # position.
- front_inner_vertices_reflected[:, :, 1] += 2 * wing.y_le
- front_outer_vertices_reflected[:, :, 1] += 2 * wing.y_le
- back_inner_vertices_reflected[:, :, 1] += 2 * wing.y_le
- back_outer_vertices_reflected[:, :, 1] += 2 * wing.y_le
+ # Reflect the vertices across the symmetry plane.
+ front_inner_vertices_reflected = np.apply_along_axis(
+ functions.reflect_point_across_plane,
+ -1,
+ front_inner_vertices,
+ wing.unit_normal_vector,
+ wing.leading_edge,
+ )
+ front_outer_vertices_reflected = np.apply_along_axis(
+ functions.reflect_point_across_plane,
+ -1,
+ front_outer_vertices,
+ wing.unit_normal_vector,
+ wing.leading_edge,
+ )
+ back_inner_vertices_reflected = np.apply_along_axis(
+ functions.reflect_point_across_plane,
+ -1,
+ back_inner_vertices,
+ wing.unit_normal_vector,
+ wing.leading_edge,
+ )
+ back_outer_vertices_reflected = np.apply_along_axis(
+ functions.reflect_point_across_plane,
+ -1,
+ back_outer_vertices,
+ wing.unit_normal_vector,
+ wing.leading_edge,
+ )
# Get the reflected wing section's panels.
wing_section_panels = get_wing_section_panels(
@@ -383,8 +268,7 @@ def mesh_wing(wing):
# of the wing's panel matrix.
wing_panels = np.hstack((np.flip(wing_section_panels, axis=1), wing_panels))
- # Iterate through the panels and populate their left and right edge flags. Also
- # populate their local position attributes.
+ # Iterate through the panels and populate their local position attributes.
for chordwise_position in range(wing.num_chordwise_panels):
for spanwise_position in range(wing.num_spanwise_panels):
this_panel = wing_panels[chordwise_position, spanwise_position]
@@ -403,111 +287,39 @@ def mesh_wing(wing):
wing.panels = wing_panels
-def get_wing_cross_section_scaling_factors(
- symmetric, wing_section_quarter_chords_proj_yz_norm
-):
- """Get the scaling factors for each wing cross section. These factors allow the
- cross sections to intersect correctly at dihedral breaks.
-
- :param symmetric: bool
- This parameter is True if the wing is symmetric and False otherwise.
- :param wing_section_quarter_chords_proj_yz_norm: array
- This parameter is a (N x 3) array of floats, where N is the number of wing
- sections (1 less than the number of wing cross sections). For each wing
- section, this parameter contains the 3 components of the normalized quarter
- chord projected onto the YZ plane.
- :return wing_cross_section_scaling_factors: array
- This function returns a 1D array of floats of length (N + 1), where N is the
- number of wing sections. These values are the corresponding scaling factor
- for each of the wing's wing cross sections. These scaling factors stretch
- their profiles to account for changes in dihedral at a give wing cross section.
- """
- num_wing_cross_sections = len(wing_section_quarter_chords_proj_yz_norm) + 1
-
- # Get the scaling factor (airfoils at dihedral breaks need to be "taller" to
- # compensate).
- wing_cross_section_scaling_factors = np.ones(num_wing_cross_sections)
-
- for i in range(num_wing_cross_sections):
- if i == 0:
- if symmetric:
- first_chord_norm = wing_section_quarter_chords_proj_yz_norm[0]
- mirrored_first_chord_norm = first_chord_norm * np.array([1, 1, -1])
-
- product = first_chord_norm * mirrored_first_chord_norm
- collapsed_product = np.sum(product)
- this_scaling_factor = 1 / np.sqrt((1 + collapsed_product) / 2)
- else:
- this_scaling_factor = 1
- elif i == num_wing_cross_sections - 1:
- this_scaling_factor = 1
- else:
- this_chord_norm = wing_section_quarter_chords_proj_yz_norm[i - 1, :]
- next_chord_norm = wing_section_quarter_chords_proj_yz_norm[i, :]
-
- product = this_chord_norm * next_chord_norm
- collapsed_product = np.sum(product)
- this_scaling_factor = 1 / np.sqrt((1 + collapsed_product) / 2)
-
- wing_cross_section_scaling_factors[i] = this_scaling_factor
-
- return wing_cross_section_scaling_factors
-
-
-def get_panel_vertices(
- inner_wing_cross_section_num,
- wing_cross_sections_local_back_unit_vectors,
- wing_cross_sections_local_up_unit_vectors,
- wing_cross_sections_chord_lengths,
- wing_cross_sections_scaling_factors,
- wing_cross_sections_leading_edges,
+def get_wing_section_panel_vertices(
+ wing_leading_edge,
+ inner_wing_cross_section,
+ outer_wing_cross_section,
transpose_mcl_vectors,
spanwise_coordinates,
):
- """This function calculates the vertices of the panels on a wing.
-
- :param inner_wing_cross_section_num: int
- This parameter is the integer index of this wing's section's inner wing cross
- section.
- :param wing_cross_sections_local_back_unit_vectors: array
- This parameter is an array of floats with size (X, 3), where X is this wing's
- number of wing cross sections. It holds two unit vectors that correspond to
- the wing cross sections' local-back directions, written in the body frame.
- :param wing_cross_sections_local_up_unit_vectors: array
- This parameter is an array of floats with size (X, 3), where X is this wing's
- number of wing cross sections. It holds two unit vectors that correspond to
- the wing cross sections' local-up directions, written in the body frame.
- :param wing_cross_sections_chord_lengths: array
- This parameter is a 1D array of floats with length X, where X is this wing's
- number of wing cross sections. It holds the chord lengths of this wing's wing
- cross section in meters.
- :param wing_cross_sections_scaling_factors: array
- This parameter is a 1D array of floats with length X, where X is this wing's
- number of wing cross sections. It holds this wing's wing cross sections'
- scaling factors. These factors stretch the shape of the wing cross sections
- to account for changes in dihedral at a give wing cross section.
- :param wing_cross_sections_leading_edges: array
- This parameter is an array of floats with size (Xx3), where X is this wing's
- number of wing cross sections. It holds the coordinates of the leading edge
- points of this wing's wing cross sections. The units are in meters.
- :param transpose_mcl_vectors: list
- This parameter is a list of 4 (M x 1) arrays of floats, where M is the number
- of chordwise points. The first array contains the local-up component of the
- mean-camber-line slope at each of the chordwise points along the inner wing
+ """This function calculates the vertices of the panels on a wing section.
+
+ :param wing_leading_edge: (3,) array of floats
+ This is an array of the wing's leading edge coordinates. The units are meters.
+ :param inner_wing_cross_section: WingCrossSection
+ This is this wing section's inner Wing Cross Section object.
+ :param outer_wing_cross_section: WingCrossSection
+ This is this wing section's outer Wing Cross Section object.
+ :param transpose_mcl_vectors: list of 4 (M, 1) arrays of floats
+ This parameter is a list of 4 (M, 1) arrays where M is the number of
+ chordwise points. The first array contains the local-up component of the
+ mean-camber-line's slope at each of the chordwise points along the inner wing
cross section. The second array contains the local-back component of the
- mean-camber-line slope at each of the chordwise points along the inner wing
+ mean-camber-line's slope at each of the chordwise points along the inner wing
cross section. The third and fourth arrays are the same but for the outer
- wing cross section.
- :param spanwise_coordinates: array
- This parameter is a 1D array of floats with length N, where N is the number
- of spanwise points. It holds the distances of each spanwise point along the
- wing cross section and is normalized from 0 to 1.
- :return: list
- This function returns a list with four arrays. Each array is size (MxNx3),
- where M is the number of chordwise points and N is the number of spanwise
- points. The arrays are the body frame coordinates of this wing's panels'
- front-inner, front-outer, back-inner, and back-outer vertices. The units are
- in meters.
+ wing cross section instead of the inner wing cross section. The units are
+ meters.
+ :param spanwise_coordinates: (N, 1) array of floats
+ This parameter is a (N, 1) array of floats, where N is the number of spanwise
+ points. It holds the distances of each spanwise point along the wing section
+ and is normalized from 0 to 1. These values are unitless.
+ :return: list of 4 (M, N, 3) arrays of floats
+ This function returns a list with four (M, N, 3) arrays, where M is the
+ number of chordwise points and N is the number of spanwise points. The arrays
+ are the coordinates of this wing's panels' front-inner, front-outer,
+ back-inner, and back-outer vertices. The units are in meters.
"""
[
transpose_inner_mcl_up_vector,
@@ -518,116 +330,64 @@ def get_panel_vertices(
# Convert the inner wing cross section's non dimensional local back airfoil frame
# coordinates to meshed wing coordinates.
- inner_wing_cross_section_mcl_local_back = (
- wing_cross_sections_local_back_unit_vectors[inner_wing_cross_section_num, :]
+ inner_wing_cross_section_mcl_back = (
+ inner_wing_cross_section.unit_chordwise_vector
+ * inner_wing_cross_section.chord
* transpose_inner_mcl_back_vector
- * wing_cross_sections_chord_lengths[inner_wing_cross_section_num]
)
# Convert the inner wing cross section's non dimensional local up airfoil frame
# coordinates to meshed wing coordinates.
- inner_wing_cross_section_mcl_local_up = (
- wing_cross_sections_local_up_unit_vectors[inner_wing_cross_section_num, :]
+ inner_wing_cross_section_mcl_up = (
+ inner_wing_cross_section.unit_up_vector
+ * inner_wing_cross_section.chord
* transpose_inner_mcl_up_vector
- * wing_cross_sections_chord_lengths[inner_wing_cross_section_num]
- * wing_cross_sections_scaling_factors[inner_wing_cross_section_num]
)
- # Define the index of this wing section's outer wing cross section.
- outer_wing_cross_section_num = inner_wing_cross_section_num + 1
-
# Convert the outer wing cross section's non dimensional local back airfoil frame
# coordinates to meshed wing coordinates.
- outer_wing_cross_section_mcl_local_back = (
- wing_cross_sections_local_back_unit_vectors[outer_wing_cross_section_num, :]
+ outer_wing_cross_section_mcl_back = (
+ outer_wing_cross_section.unit_chordwise_vector
+ * outer_wing_cross_section.chord
* transpose_outer_mcl_back_vector
- * wing_cross_sections_chord_lengths[outer_wing_cross_section_num]
)
# Convert the outer wing cross section's non dimensional local up airfoil frame
# coordinates to meshed wing coordinates.
- outer_wing_cross_section_mcl_local_up = (
- wing_cross_sections_local_up_unit_vectors[outer_wing_cross_section_num, :]
+ outer_wing_cross_section_mcl_up = (
+ outer_wing_cross_section.unit_up_vector
+ * outer_wing_cross_section.chord
* transpose_outer_mcl_up_vector
- * wing_cross_sections_chord_lengths[outer_wing_cross_section_num]
- * wing_cross_sections_scaling_factors[outer_wing_cross_section_num]
)
# Convert the inner wing cross section's meshed wing coordinates to absolute
- # coordinates. This is size M x 3, where M is the number of chordwise points.
+ # coordinates. This is size (M, 3) where M is the number of chordwise points.
inner_wing_cross_section_mcl = (
- wing_cross_sections_leading_edges[inner_wing_cross_section_num, :]
- + inner_wing_cross_section_mcl_local_back
- + inner_wing_cross_section_mcl_local_up
+ wing_leading_edge
+ + inner_wing_cross_section.leading_edge
+ + inner_wing_cross_section_mcl_back
+ + inner_wing_cross_section_mcl_up
)
# Convert the outer wing cross section's meshed wing coordinates to absolute
- # coordinates. This is size M x 3, where M is the number of chordwise points.
+ # coordinates. This is size (M, 3) where M is the number of chordwise points.
outer_wing_cross_section_mcl = (
- wing_cross_sections_leading_edges[outer_wing_cross_section_num, :]
- + outer_wing_cross_section_mcl_local_back
- + outer_wing_cross_section_mcl_local_up
- )
-
- # Make section_mcl_coordinates: M x N x 3 array of mean camberline coordinates.
- # The first index is chordwise point number, second index is spanwise point
- # number, third is the x, y, and z coordinates. M is the number of chordwise
- # points. N is the number of spanwise points. Put a reversed version (from 1 to
- # 0) of the non dimensional spanwise coordinates in a row vector. This is size 1
- # x N, where N is the number of spanwise points.
- reversed_nondim_spanwise_coordinates_row_vector = np.expand_dims(
- (1 - spanwise_coordinates), 0
- )
-
- # Convert the reversed non dimensional spanwise coordinate row vector (from 1 to
- # 0) to a matrix. This is size 1 x N x 1, where N is the number of spanwise points.
- reversed_nondim_spanwise_coordinates_matrix = np.expand_dims(
- reversed_nondim_spanwise_coordinates_row_vector, 2
+ wing_leading_edge
+ + outer_wing_cross_section.leading_edge
+ + outer_wing_cross_section_mcl_back
+ + outer_wing_cross_section_mcl_up
)
- # Convert the inner and outer wing cross section's mean camberline coordinates
- # column vectors to matrices. These are size M x 1 x 3, where M is the number of
- # chordwise points.
- inner_wing_cross_section_mcl_matrix = np.expand_dims(
- inner_wing_cross_section_mcl, 1
- )
- outer_wing_cross_section_mcl_matrix = np.expand_dims(
- outer_wing_cross_section_mcl, 1
- )
-
- # Put the non dimensional spanwise coordinates (from 0 to 1) in a row vector.
- # This is size 1 x N, where N is the number of spanwise points.
- nondim_spanwise_coordinates_row_vector = np.expand_dims(spanwise_coordinates, 0)
-
- # Convert the non dimensional spanwise coordinate row vector (from to 0 to 1) to
- # a matrix. This is size 1 x N x 1, where N is the number of spanwise points.
- nondim_spanwise_coordinates_matrix = np.expand_dims(
- nondim_spanwise_coordinates_row_vector, 2
- )
-
- # Linearly interpolate between inner and outer wing cross sections. This uses the
- # following equation:
- #
- # f(a, b, i) = i * a + (1 - i) * b
- #
- # "a" is an N x 3 array of the coordinates points along the outer wing cross
- # section's mean camber line.
- #
- # "b" is an N x 3 array of the coordinates of points along the inner wing cross
- # section's mean camber line.
- #
- # "i" is a 1D array (or vector) of length M that holds the nondimensionalized
- # spanwise panel spacing from 0 to 1.
- #
- # This produces an M x N x 3 array where each slot holds the coordinates of a
- # point on the surface between the inner and outer wing cross sections.
- wing_section_mcl_vertices = (
- reversed_nondim_spanwise_coordinates_matrix
- * inner_wing_cross_section_mcl_matrix
- + nondim_spanwise_coordinates_matrix * outer_wing_cross_section_mcl_matrix
+ # Find the vertices of the points on this wing section with interpolation. This
+ # returns an (M, N, 3) array, where M and N are the number of chordwise points
+ # and spanwise points.
+ wing_section_mcl_vertices = functions.interp_between_points(
+ inner_wing_cross_section_mcl,
+ outer_wing_cross_section_mcl,
+ spanwise_coordinates,
)
- # Compute the corners of each panel.
+ # Extract the coordinates for corners of each panel.
front_inner_vertices = wing_section_mcl_vertices[:-1, :-1, :]
front_outer_vertices = wing_section_mcl_vertices[:-1, 1:, :]
back_inner_vertices = wing_section_mcl_vertices[1:, :-1, :]
@@ -641,81 +401,12 @@ def get_panel_vertices(
]
-def get_normalized_projected_quarter_chords(
- wing_cross_sections_leading_edges, wing_cross_sections_trailing_edges
-):
- """This method returns the quarter chords of a collection of wing cross sections
- based on the coordinates of their leading and trailing edges. These quarter
- chords are also projected on to the YZ plane and normalized by their magnitudes.
-
- :param wing_cross_sections_leading_edges: array
- This parameter is an array of floats with size (X, 3), where X is this wing's
- number of wing cross sections. For each cross section, this array holds the
- body-frame coordinates of its leading edge point in meters.
- :param wing_cross_sections_trailing_edges: array
- This parameter is an array of floats with size (X, 3), where X is this wing's
- number of wing cross sections. For each cross section, this array holds the
- body-frame coordinates of its trailing edge point in meters.
- :return normalized_projected_quarter_chords: array
- This functions returns an array of floats with size (X - 1, 3), where X is
- this wing's number of wing cross sections. This array holds each wing
- section's quarter chords projected on to the YZ plane and normalized by their
- magnitudes.
- """
- # Get the location of each wing cross section's quarter chord point.
- wing_cross_sections_quarter_chord_points = (
- wing_cross_sections_leading_edges
- + 0.25
- * (wing_cross_sections_trailing_edges - wing_cross_sections_leading_edges)
- )
-
- # Get a (L - 1) x 3 array of vectors connecting the wing cross section quarter
- # chord points, where L is the number of wing cross sections.
- quarter_chords = (
- wing_cross_sections_quarter_chord_points[1:, :]
- - wing_cross_sections_quarter_chord_points[:-1, :]
- )
-
- # Get directions for transforming 2D airfoil data to 3D by the following steps.
- #
- # Project quarter chords onto YZ plane and normalize.
- #
- # Create an L x 2 array with just the y and z components of this wing section's
- # quarter chord vectors.
- projected_quarter_chords = quarter_chords[:, 1:]
-
- # Create a list of the lengths of each row of the projected_quarter_chords array.
- projected_quarter_chords_len = np.linalg.norm(projected_quarter_chords, axis=1)
-
- # Convert projected_quarter_chords_len into a column vector.
- transpose_projected_quarter_chords_len = np.expand_dims(
- projected_quarter_chords_len, axis=1
- )
- # Normalize the coordinates by the magnitudes
- normalized_projected_quarter_chords = (
- projected_quarter_chords / transpose_projected_quarter_chords_len
- )
-
- # Create a column vector of all zeros with height equal to the number of quarter
- # chord vectors
- column_of_zeros = np.zeros((len(quarter_chords), 1))
-
- # Horizontally stack the zero column vector with the
- # normalized_projected_quarter_chords to give each normalized projected quarter
- # chord an X coordinate.
- normalized_projected_quarter_chords = np.hstack(
- (column_of_zeros, normalized_projected_quarter_chords)
- )
-
- return normalized_projected_quarter_chords
-
-
def get_transpose_mcl_vectors(inner_airfoil, outer_airfoil, chordwise_coordinates):
"""This function takes in the inner and outer airfoils of a wing cross section
- and its chordwise coordinates. It returns a list of four vectors column vectors.
- They are, in order, the inner airfoil's local up direction, the inner airfoil's
- local back direction, the outer airfoil's local up direction, and the outer
- airfoil's local back direction.
+ and its chordwise coordinates. It returns a list of four column vectors. They
+ are, in order, the inner airfoil's local up direction, the inner airfoil's local
+ back direction, the outer airfoil's local up direction, and the outer airfoil's
+ local back direction.
:param inner_airfoil: Airfoil
This is the wing cross section's inner airfoil object.
@@ -725,7 +416,7 @@ def get_transpose_mcl_vectors(inner_airfoil, outer_airfoil, chordwise_coordinate
This is a 1D array of the normalized chordwise coordinates where we'd like to
sample each airfoil's mean camber line.
:return: list of 4 (2x1) arrays
- This is a list of four vectors column vectors. They are, in order, the inner
+ This is a list of four column vectors. They are, in order, the inner
airfoil's local up direction, the inner airfoil's local back direction,
the outer airfoil's local up direction, and the outer airfoil's local back
direction.
diff --git a/pterasoftware/models/__init__.py b/pterasoftware/models/__init__.py
index e69de29b..7a233c17 100644
--- a/pterasoftware/models/__init__.py
+++ b/pterasoftware/models/__init__.py
@@ -0,0 +1,2 @@
+"""This module doesn't import anything. It is only here to make the models directory
+a package."""
diff --git a/pterasoftware/movement.py b/pterasoftware/movement.py
index 649c9887..8ba9b6e6 100644
--- a/pterasoftware/movement.py
+++ b/pterasoftware/movement.py
@@ -135,6 +135,7 @@ def __init__(
if delta_time is None:
delta_times = []
for airplane_movement in self.airplane_movements:
+
# For a given airplane object, the ideal time step length is that
# which sheds ring vortices off the main wing that have roughly the
# same chord length as the panels on the main wing. This is based on
@@ -413,6 +414,7 @@ def generate_airplanes(self, num_steps=10, delta_time=0.1):
# Iterate through the wing movement locations.
for wing_movement_location, wing_movement in enumerate(self.wing_movements):
+
# Generate this wing's vector of other wing's based on its movement.
this_wings_list_of_wings = np.array(
wing_movement.generate_wings(num_steps=num_steps, delta_time=delta_time)
@@ -785,6 +787,7 @@ def generate_wings(self, num_steps=10, delta_time=0.1):
# Iterate through the time steps.
for step in range(num_steps):
+
# Get the reference position at this time step.
x_le = x_le_list[step]
y_le = y_le_list[step]
diff --git a/pterasoftware/output.py b/pterasoftware/output.py
index 0ad42110..38c27563 100644
--- a/pterasoftware/output.py
+++ b/pterasoftware/output.py
@@ -137,8 +137,10 @@ def draw(
:return: None
"""
- # Initialize the plotter.
+ # Initialize the plotter and set it to use parallel projection (instead of
+ # perspective).
plotter = pv.Plotter(window_size=window_size, lighting=None)
+ plotter.enable_parallel_projection()
# Get the solver's geometry.
if isinstance(
diff --git a/pterasoftware/panel.py b/pterasoftware/panel.py
index 59e9dc0f..9f3ab0d6 100644
--- a/pterasoftware/panel.py
+++ b/pterasoftware/panel.py
@@ -13,16 +13,46 @@
import numpy as np
-# ToDo: Update the list of methods for this class.
class Panel:
"""This class is used to contain the panels of a wing.
This class contains the following public methods:
- calculate_collocation_point_location: This method calculates the location of
- the collocation point.
+ right_leg: This method defines a property for the panel's right leg vector as
+ a (3,) array.
- calculate_area_and_normal: This method calculates the panel's area and the
- panel's normal unit vector.
+ front_leg: This method defines a property for the panel's front leg vector as
+ a (3,) array.
+
+ left_leg: This method defines a property for the panel's left leg vector as a
+ (3,) array.
+
+ back_leg: This method defines a property for the panel's back leg vector as a
+ (3,) array.
+
+ front_right_vortex_vertex: This method defines a property for the coordinates
+ of the front-right vertex of the ring vortex as a (3,) array.
+
+ front_left_vortex_vertex: This method defines a property for the coordinates
+ of the front-left vertex of the ring vortex as a (3,) array.
+
+ collocation_point: This method defines a property for the coordinates of the
+ panel's collocation point as a (3,) array.
+
+ area: This method defines a property which is an estimate of the panel's area.
+
+ unit_normal: This method defines a property for an estimate of the panel's
+ unit normal vector as a (3,) array.
+
+ unit_spanwise: This method defines a property for the panel's unit spanwise
+ vector as a ( 3,) array.
+
+ unit_chordwise: This method defines a property for the panel's unit chordwise
+ vector as a (3,) array.
+
+ average_span: This method defines a property for the average span of the panel.
+
+ average_chord: This method defines a property for the average chord of the
+ panel.
calculate_normalized_induced_velocity: This method calculates the velocity
induced at a point by this panel's vortices, assuming a unit vortex strength.
@@ -30,6 +60,9 @@ class Panel:
calculate_induced_velocity: This method calculates the velocity induced at a
point by this panel's vortices with their given vortex strengths.
+ calculate_projected_area: This method calculates the area of the panel
+ projected on some plane defined by its unit normal vector.
+
update_coefficients: This method updates the panel's force coefficients.
This class contains the following class attributes:
@@ -50,16 +83,16 @@ def __init__(
):
"""This is the initialization method.
- :param front_right_vertex: 1D array with three elements
+ :param front_right_vertex: (3,) array
This is an array containing the x, y, and z coordinates of the panel's
front right vertex.
- :param front_left_vertex: 1D array with three elements
+ :param front_left_vertex: (3,) array
This is an array containing the x, y, and z coordinates of the panel's
front left vertex.
- :param back_left_vertex: 1D array with three elements
+ :param back_left_vertex: (3,) array
This is an array containing the x, y, and z coordinates of the panel's
back left vertex.
- :param back_right_vertex: 1D array with three elements
+ :param back_right_vertex: (3,) array
This is an array containing the x, y, and z coordinates of the panel's
back right vertex.
:param is_leading_edge: bool
@@ -100,39 +133,81 @@ def __init__(
self.side_force_coefficient = None
self.lift_coefficient = None
- # ToDo: Update this method's documentation.
@property
def right_leg(self):
+ """This method defines a property for the panel's right leg vector as a (3,
+ ) array.
+
+ :return: (3,) array of floats
+ This is the panel's right leg vector, which is defined from back to
+ front. The units are in meters.
+ """
return self.front_right_vertex - self.back_right_vertex
- # ToDo: Update this method's documentation.
@property
def front_leg(self):
+ """This method defines a property for the panel's front leg vector as a (3,
+ ) array.
+
+ :return: (3,) array of floats
+ This is the panel's front leg vector, which is defined from right to
+ left. The units are in meters.
+ """
return self.front_left_vertex - self.front_right_vertex
- # ToDo: Update this method's documentation.
@property
def left_leg(self):
+ """This method defines a property for the panel's left leg vector as a (3,
+ ) array.
+
+ :return: (3,) array of floats
+ This is the panel's left leg vector, which is defined from front to
+ back. The units are in meters.
+ """
return self.back_left_vertex - self.front_left_vertex
- # ToDo: Update this method's documentation.
@property
def back_leg(self):
+ """This method defines a property for the panel's back leg vector as a (3,
+ ) array.
+
+ :return: (3,) array of floats
+ This is the panel's back leg vector, which is defined from left to
+ right. The units are in meters.
+ """
return self.back_right_vertex - self.back_left_vertex
- # ToDo: Update this method's documentation.
@property
def front_right_vortex_vertex(self):
+ """This method defines a property for the coordinates of the front-right
+ vertex of the ring vortex as a (3,) array.
+
+ :return: (3,) array of floats
+ This is the coordinates of the ring vortex's front-right vertex. The
+ units are in meters.
+ """
return self.back_right_vertex + 0.75 * self.right_leg
- # ToDo: Update this method's documentation.
@property
def front_left_vortex_vertex(self):
+ """This method defines a property for the coordinates of the front-left
+ vertex of the ring vortex as a (3,) array.
+
+ :return: (3,) array of floats
+ This is the coordinates of the ring vortex's front-left vertex. The units
+ are in meters.
+ """
return self.front_left_vertex + 0.25 * self.left_leg
- # ToDo: Update this method's documentation.
@property
def collocation_point(self):
+ """This method defines a property for the coordinates of the panel's
+ collocation point as a (3,) array.
+
+ :return: (3,) array of floats
+ This is the coordinates of the panel's collocation point. The units are
+ in meters.
+ """
# Find the location of points three quarters of the way down the left and
# right legs of the panel.
right_three_quarter_chord_mark = self.back_right_vertex + 0.25 * self.right_leg
@@ -149,59 +224,123 @@ def collocation_point(self):
# populate the class attribute.
return right_three_quarter_chord_mark + 0.5 * three_quarter_chord_vector
- # ToDo: Update this method's documentation.
@property
def area(self):
+ """This method defines a property which is an estimate of the panel's area.
+
+ This is only an estimate because the surface defined by four line segments in
+ 3-space is a hyperboloid, and there doesn't seem to be a close-form equation
+ for the surface area of a hyperboloid between four points. Instead,
+ we estimate the area using the cross product of panel's diagonal vectors,
+ which should be relatively accurate if the panel can be approximated as a
+ planar, convex quadrilateral.
+
+ :return: float
+ This is an estimate of the panel's area. The units are square meters.
+ """
return np.linalg.norm(self._cross) / 2
- # ToDo: Update this method's documentation.
@property
def unit_normal(self):
+ """This method defines a property for an estimate of the panel's unit
+ normal vector as a (3,) array.
+
+ :return: (3,) array of floats
+ This is an estimate of the panel's unit normal vector as a (3,) array.
+ The sign is determined via the right-hand rule given the orientation of
+ panel's leg vectors (front-right to front-left to back-left to
+ back-right). The units are in meters.
+ """
return self._cross / np.linalg.norm(self._cross)
- # ToDo: Update this method's documentation.
@property
def unit_spanwise(self):
+ """This method defines a property for the panel's unit spanwise vector as a (
+ 3,) array.
+
+ :return: (3,) array of floats
+ This is the panel's unit spanwise vector as a (3,) array. The positive
+ direction is defined as left to right, which is opposite the direction of
+ the front leg. The units are in meters.
+ """
front_spanwise = -self.front_leg
back_spanwise = self.back_leg
+
spanwise = (front_spanwise + back_spanwise) / 2
- return spanwise / np.linalg.norm(spanwise)
- # ToDo: Update this method's documentation.
- @property
- def average_span(self):
- front_leg_length = np.linalg.norm(self.front_leg)
- back_leg_length = np.linalg.norm(self.back_leg)
- return (front_leg_length + back_leg_length) / 2
+ return spanwise / np.linalg.norm(spanwise)
- # ToDo: Update this method's documentation.
@property
def unit_chordwise(self):
+ """This method defines a property for the panel's unit chordwise vector as a
+ (3,) array.
+
+ :return: (3,) array of floats
+ This is the panel's unit chordwise vector as a (3,) array. The positive
+ direction is defined as front to back. The units are in meters.
+ """
right_chordwise = -self.right_leg
left_chordwise = self.left_leg
+
chordwise = (right_chordwise + left_chordwise) / 2
+
return chordwise / np.linalg.norm(chordwise)
- # ToDo: Update this method's documentation.
+ @property
+ def average_span(self):
+ """This method defines a property for the average span of the panel.
+
+ :return: float
+ This is the average span, which is defined as the average of the front
+ and back leg lengths. The units are meters.
+ """
+ front_leg_length = np.linalg.norm(self.front_leg)
+ back_leg_length = np.linalg.norm(self.back_leg)
+
+ return (front_leg_length + back_leg_length) / 2
+
@property
def average_chord(self):
+ """This method defines a property for the average chord of the panel.
+
+ :return: float
+ This is the average chord, which is defined as the average of the right
+ and left leg lengths. The units are meters.
+ """
right_leg_length = np.linalg.norm(self.right_leg)
left_leg_length = np.linalg.norm(self.left_leg)
+
return (right_leg_length + left_leg_length) / 2
- # ToDo: Update this method's documentation.
@property
def _first_diagonal(self):
+ """This method defines a property for the panel's first diagonal vector.
+
+ :return: (3,) array of floats
+ This is the first diagonal vector, which is defined as the vector from
+ the back-left vertex to the front-right vertex. The units are meters.
+ """
return self.front_right_vertex - self.back_left_vertex
- # ToDo: Update this method's documentation.
@property
def _second_diagonal(self):
+ """This method defines a property for the panel's second diagonal vector.
+
+ :return: (3,) array of floats
+ This is the second diagonal vector, which is defined as the vector from
+ the back-right vertex to the front-left vertex. The units are meters.
+ """
return self.front_left_vertex - self.back_right_vertex
- # ToDo: Update this method's documentation.
@property
def _cross(self):
+ """This method defines a property for cross product of the panel's first and
+ second diagonal vectors.
+
+ :return: (3,) array of floats
+ This is the cross product of the panel's first and second diagonal
+ vectors. The units are meters.
+ """
return np.cross(self._first_diagonal, self._second_diagonal)
def calculate_normalized_induced_velocity(self, point):
@@ -217,7 +356,6 @@ def calculate_normalized_induced_velocity(self, point):
This is a vector containing the x, y, and z components of the induced
velocity.
"""
-
normalized_induced_velocity = np.zeros(3)
if self.ring_vortex is not None:
@@ -244,7 +382,6 @@ def calculate_induced_velocity(self, point):
This is a vector containing the x, y, and z components of the induced
velocity.
"""
-
induced_velocity = np.zeros(3)
if self.ring_vortex is not None:
@@ -256,12 +393,38 @@ def calculate_induced_velocity(self, point):
return induced_velocity
+ def calculate_projected_area(self, n_hat):
+ """This method calculates the area of the panel projected on some plane
+ defined by its unit normal vector.
+
+ :param n_hat: (3,) array of floats
+ This is a (3,) array of the components of the projection plane's unit
+ normal vector. The vector must have a magnitude of one. The units are
+ meters.
+ :return: float
+ This is the area of the panel projected onto the plane defined by the
+ normal vector. The units are square meters.
+ """
+ # Find the projections of the first and second diagonal vectors onto the
+ # plane's unit normal vector.
+ proj_n_hat_first_diag = np.dot(self._first_diagonal, n_hat) * n_hat
+ proj_n_hat_second_diag = np.dot(self._second_diagonal, n_hat) * n_hat
+
+ # Find the projection of the first and second diagonal onto the plane.
+ proj_plane_first_diag = self._first_diagonal - proj_n_hat_first_diag
+ proj_plane_second_diag = self._second_diagonal - proj_n_hat_second_diag
+
+ # The projected area is found by dividing the magnitude of cross product of
+ # the diagonal vectors by two. Read the area method for a more detailed
+ # explanation.
+ proj_cross = np.cross(proj_plane_first_diag, proj_plane_second_diag)
+ return np.linalg.norm(proj_cross) / 2
+
def update_coefficients(self, dynamic_pressure):
"""This method updates the panel's force coefficients.
:return: None
"""
-
induced_drag = -self.near_field_force_wind_axes[0]
side_force = self.near_field_force_wind_axes[1]
lift = -self.near_field_force_wind_axes[2]
diff --git a/tests/integration/fixtures/airplane_fixtures.py b/tests/integration/fixtures/airplane_fixtures.py
index 2cb5c0d6..dbc1c1ea 100644
--- a/tests/integration/fixtures/airplane_fixtures.py
+++ b/tests/integration/fixtures/airplane_fixtures.py
@@ -13,9 +13,6 @@
make_multiple_wing_steady_validation_airplane: This function creates a multi-wing
airplane object to be used as a fixture for testing steady solvers.
- make_asymmetric_unsteady_validation_airplane: This function creates an asymmetric
- airplane object to be used as a fixture for testing unsteady solvers.
-
make_symmetric_unsteady_validation_airplane: This function creates a symmetric
airplane object to be used as a fixture for testing unsteady solvers.
@@ -23,6 +20,7 @@
a multi-wing, symmetric airplane object to be used as a fixture for testing
unsteady solvers.
"""
+import numpy as np
import pterasoftware as ps
@@ -31,6 +29,16 @@ def make_steady_validation_airplane():
"""This function creates an airplane object to be used as a fixture for testing
steady solvers.
+ The parameters of this airplane were found to be converged based on the following
+ call to analyze_steady_convergence:
+ converged_parameters = ps.convergence.analyze_steady_convergence(
+ ref_problem=steady_validation_problem,
+ solver_type="steady horseshoe vortex lattice method",
+ panel_aspect_ratio_bounds=(4, 1),
+ num_chordwise_panels_bounds=(3, 20),
+ convergence_criteria=0.1,
+ ).
+
:return steady_validation_airplane: Airplane
This is the airplane fixture.
"""
@@ -38,21 +46,65 @@ def make_steady_validation_airplane():
steady_validation_airplane = ps.geometry.Airplane(
wings=[
ps.geometry.Wing(
- symmetric=True,
wing_cross_sections=[
ps.geometry.WingCrossSection(
- airfoil=ps.geometry.Airfoil(name="naca2412"),
+ airfoil=ps.geometry.Airfoil(
+ name="naca2412",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ chord=1.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=0.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=20,
+ spanwise_spacing="cosine",
),
ps.geometry.WingCrossSection(
+ airfoil=ps.geometry.Airfoil(
+ name="naca2412",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
x_le=1.0,
y_le=5.0,
- twist=5.0,
+ z_le=0.0,
chord=0.75,
- airfoil=ps.geometry.Airfoil(name="naca2412"),
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=5.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=8,
+ spanwise_spacing="cosine",
),
],
+ name="Main Wing",
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ symmetric=True,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
+ num_chordwise_panels=14,
+ chordwise_spacing="cosine",
)
],
+ name="Steady Validation Airplane",
+ x_ref=0.0,
+ y_ref=0.0,
+ z_ref=0.0,
+ weight=0.0,
+ s_ref=None,
+ c_ref=None,
+ b_ref=None,
)
return steady_validation_airplane
@@ -61,109 +113,130 @@ def make_multiple_wing_steady_validation_airplane():
"""This function creates a multi-wing airplane object to be used as a fixture
for testing steady solvers.
+ The parameters of this airplane were found to be converged based on the following
+ call to analyze_steady_convergence:
+ converged_parameters = ps.convergence.analyze_steady_convergence(
+ ref_problem=steady_validation_problem,
+ solver_type="steady horseshoe vortex lattice method",
+ panel_aspect_ratio_bounds=(4, 1),
+ num_chordwise_panels_bounds=(3, 20),
+ convergence_criteria=0.1,
+ ).
+
:return multiple_wing_steady_validation_airplane: Airplane
This is the airplane fixture.
"""
# Create and return the airplane object.
multiple_wing_steady_validation_airplane = ps.geometry.Airplane(
- x_ref=0.0,
- y_ref=0.0,
- z_ref=0.0,
- weight=1 * 9.81,
wings=[
ps.geometry.Wing(
- x_le=0.0,
- y_le=0.0,
- z_le=0.0,
wing_cross_sections=[
ps.geometry.WingCrossSection(
- x_le=0.0,
- y_le=0.0,
- z_le=0.0,
- chord=1.0,
- twist=0.0,
airfoil=ps.geometry.Airfoil(
name="naca23012",
coordinates=None,
repanel=True,
- n_points_per_side=400,
+ n_points_per_side=50,
),
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ chord=1.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=0.0,
control_surface_type="symmetric",
control_surface_hinge_point=0.75,
control_surface_deflection=0.0,
- num_spanwise_panels=8,
+ num_spanwise_panels=69,
spanwise_spacing="uniform",
),
ps.geometry.WingCrossSection(
- x_le=1.0,
- y_le=5.0,
- z_le=0.0,
- chord=0.75,
- twist=0.0,
airfoil=ps.geometry.Airfoil(
name="naca23012",
coordinates=None,
repanel=True,
- n_points_per_side=400,
+ n_points_per_side=50,
),
+ x_le=1.0,
+ y_le=5.0,
+ z_le=0.0,
+ chord=0.75,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=0.0,
control_surface_type="symmetric",
control_surface_hinge_point=0.75,
control_surface_deflection=0.0,
- num_spanwise_panels=8,
- spanwise_spacing="uniform",
+ num_spanwise_panels=69,
+ spanwise_spacing="cosine",
),
],
+ name="Main Wing",
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
symmetric=True,
- num_chordwise_panels=8,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
+ num_chordwise_panels=12,
chordwise_spacing="uniform",
),
ps.geometry.Wing(
- x_le=5.0,
- y_le=0.0,
- z_le=0.0,
wing_cross_sections=[
ps.geometry.WingCrossSection(
- x_le=0.0,
- y_le=0.0,
- z_le=0.0,
- chord=1.00,
- twist=-5.0,
airfoil=ps.geometry.Airfoil(
name="naca0010",
coordinates=None,
repanel=True,
- n_points_per_side=400,
+ n_points_per_side=50,
),
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ chord=1.00,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=-5.0,
control_surface_type="symmetric",
control_surface_hinge_point=0.75,
control_surface_deflection=0.0,
- num_spanwise_panels=8,
+ num_spanwise_panels=16,
spanwise_spacing="uniform",
),
ps.geometry.WingCrossSection(
- x_le=1.0,
- y_le=1.0,
- z_le=0.0,
- chord=0.75,
- twist=-5.0,
airfoil=ps.geometry.Airfoil(
name="naca0010",
coordinates=None,
repanel=True,
- n_points_per_side=400,
+ n_points_per_side=50,
),
+ x_le=1.0,
+ y_le=1.0,
+ z_le=0.0,
+ chord=0.75,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=-5.0,
control_surface_type="symmetric",
control_surface_hinge_point=0.75,
control_surface_deflection=0.0,
- num_spanwise_panels=8,
- spanwise_spacing="uniform",
+ num_spanwise_panels=16,
+ spanwise_spacing="cosine",
),
],
+ name="Horizontal Stabilizer",
+ x_le=5.0,
+ y_le=0.0,
+ z_le=0.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
symmetric=True,
- num_chordwise_panels=8,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
+ num_chordwise_panels=12,
chordwise_spacing="uniform",
),
],
+ name="Multiple Wing Steady Validation Airplane",
+ x_ref=0.0,
+ y_ref=0.0,
+ z_ref=0.0,
+ weight=1 * 9.81,
s_ref=None,
c_ref=None,
b_ref=None,
@@ -171,45 +244,23 @@ def make_multiple_wing_steady_validation_airplane():
return multiple_wing_steady_validation_airplane
-def make_asymmetric_unsteady_validation_airplane():
- """This function creates an asymmetric airplane object to be used as a fixture
- for testing unsteady solvers.
-
- :return asymmetric_unsteady_validation_airplane: Airplane
- This is the airplane fixture.
- """
- # Create and return the airplane object.
- asymmetric_unsteady_validation_airplane = ps.geometry.Airplane(
- y_ref=5.0,
- wings=[
- ps.geometry.Wing(
- num_chordwise_panels=8,
- chordwise_spacing="uniform",
- wing_cross_sections=[
- ps.geometry.WingCrossSection(
- airfoil=ps.geometry.Airfoil(name="naca2412"),
- num_spanwise_panels=16,
- spanwise_spacing="cosine",
- chord=1.0,
- ),
- ps.geometry.WingCrossSection(
- y_le=10.0,
- chord=1.0,
- airfoil=ps.geometry.Airfoil(name="naca2412"),
- num_spanwise_panels=16,
- spanwise_spacing="cosine",
- ),
- ],
- )
- ],
- )
- return asymmetric_unsteady_validation_airplane
-
-
def make_symmetric_unsteady_validation_airplane():
"""This function creates a symmetric airplane object to be used as a fixture for
testing unsteady solvers.
+ The parameters of this airplane were found to be converged based on the following
+ call to analyze_unsteady_convergence:
+ converged_parameters = ps.convergence.analyze_unsteady_convergence(
+ ref_problem=unsteady_validation_problem,
+ prescribed_wake=True,
+ free_wake=True,
+ num_chords_bounds=(3, 9),
+ panel_aspect_ratio_bounds=(4, 1),
+ num_chordwise_panels_bounds=(4, 11),
+ coefficient_mask=[True, False, True, False, True, False],
+ convergence_criteria=1.0,
+ ).
+
:return symmetric_unsteady_validation_airplane: Airplane
This is the airplane fixture.
"""
@@ -217,27 +268,70 @@ def make_symmetric_unsteady_validation_airplane():
symmetric_unsteady_validation_airplane = ps.geometry.Airplane(
wings=[
ps.geometry.Wing(
- symmetric=True,
- chordwise_spacing="uniform",
wing_cross_sections=[
ps.geometry.WingCrossSection(
- airfoil=ps.geometry.Airfoil(name="naca2412"),
+ airfoil=ps.geometry.Airfoil(
+ name="naca2412",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
chord=2.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=0.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=18,
spanwise_spacing="cosine",
),
ps.geometry.WingCrossSection(
+ airfoil=ps.geometry.Airfoil(
+ name="naca2412",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.0,
y_le=5.0,
+ z_le=0.0,
chord=2.0,
- airfoil=ps.geometry.Airfoil(name="naca2412"),
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=0.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=18,
spanwise_spacing="cosine",
),
],
+ name="Main Wing",
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ symmetric=True,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
+ num_chordwise_panels=7,
+ chordwise_spacing="uniform",
),
],
+ name="Symmetric Unsteady Validation Airplane",
+ x_ref=0.0,
+ y_ref=0.0,
+ z_ref=0.0,
+ weight=0.0,
+ s_ref=None,
+ c_ref=None,
+ b_ref=None,
)
return symmetric_unsteady_validation_airplane
+# ToDo: Check that this test case has converged characteristics.
def make_symmetric_multiple_wing_unsteady_validation_airplane():
"""This function creates a multi-wing, symmetric airplane object to be used as a
fixture for testing unsteady solvers.
@@ -249,66 +343,166 @@ def make_symmetric_multiple_wing_unsteady_validation_airplane():
symmetric_multiple_wing_steady_validation_airplane = ps.geometry.Airplane(
wings=[
ps.geometry.Wing(
- symmetric=True,
- chordwise_spacing="uniform",
wing_cross_sections=[
ps.geometry.WingCrossSection(
- airfoil=ps.geometry.Airfoil(name="naca2412"),
+ airfoil=ps.geometry.Airfoil(
+ name="naca2412",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
chord=1.5,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=0.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=8,
spanwise_spacing="cosine",
),
ps.geometry.WingCrossSection(
+ airfoil=ps.geometry.Airfoil(
+ name="naca2412",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
x_le=0.5,
y_le=5.0,
z_le=0.0,
chord=1.0,
- airfoil=ps.geometry.Airfoil(name="naca2412"),
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=0.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=8,
spanwise_spacing="cosine",
),
],
- ),
- ps.geometry.Wing(
+ name="Main Wing",
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
symmetric=True,
- z_le=1.75,
- x_le=6.25,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
+ num_chordwise_panels=8,
chordwise_spacing="uniform",
+ ),
+ ps.geometry.Wing(
wing_cross_sections=[
ps.geometry.WingCrossSection(
- airfoil=ps.geometry.Airfoil(name="naca0010"),
- spanwise_spacing="cosine",
+ airfoil=ps.geometry.Airfoil(
+ name="naca0010",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
+ chord=1.0,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
twist=-5.0,
- chord=1.00,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=8,
+ spanwise_spacing="cosine",
),
ps.geometry.WingCrossSection(
+ airfoil=ps.geometry.Airfoil(
+ name="naca0010",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.25,
y_le=1.5,
- twist=-5.0,
+ z_le=0.0,
chord=0.75,
- x_le=0.25,
- airfoil=ps.geometry.Airfoil(name="naca0010"),
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ twist=-5.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=8,
spanwise_spacing="cosine",
),
],
- ),
- ps.geometry.Wing(
- symmetric=False,
- z_le=0.125,
+ name="Horizontal Stabilizer",
x_le=6.25,
+ y_le=0.0,
+ z_le=1.75,
+ unit_normal_vector=np.array([0.0, 1.0, 0.0]),
+ symmetric=True,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
+ num_chordwise_panels=8,
chordwise_spacing="uniform",
+ ),
+ ps.geometry.Wing(
wing_cross_sections=[
ps.geometry.WingCrossSection(
- airfoil=ps.geometry.Airfoil(name="naca0010"),
- spanwise_spacing="cosine",
+ airfoil=ps.geometry.Airfoil(
+ name="naca0010",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.0,
+ y_le=0.0,
+ z_le=0.0,
chord=1.0,
+ unit_normal_vector=np.array([0.0, 0.0, 1.0]),
+ twist=0.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=8,
+ spanwise_spacing="cosine",
),
ps.geometry.WingCrossSection(
+ airfoil=ps.geometry.Airfoil(
+ name="naca0010",
+ coordinates=None,
+ repanel=True,
+ n_points_per_side=50,
+ ),
+ x_le=0.25,
+ y_le=0.0,
z_le=1.5,
chord=0.75,
- x_le=0.25,
- airfoil=ps.geometry.Airfoil(name="naca0010"),
+ unit_normal_vector=np.array([0.0, 0.0, 1.0]),
+ twist=0.0,
+ control_surface_type="symmetric",
+ control_surface_hinge_point=0.75,
+ control_surface_deflection=0.0,
+ num_spanwise_panels=8,
spanwise_spacing="cosine",
),
],
+ name="Vertical Stabilizer",
+ x_le=6.25,
+ y_le=0.0,
+ z_le=0.125,
+ unit_normal_vector=np.array([0.0, 0.0, 1.0]),
+ symmetric=False,
+ unit_chordwise_vector=np.array([1.0, 0.0, 0.0]),
+ num_chordwise_panels=8,
+ chordwise_spacing="uniform",
),
],
+ name="Symmetric Multiple Wing Unsteady Validation Airplane",
+ x_ref=0.0,
+ y_ref=0.0,
+ z_ref=0.0,
+ weight=0.0,
+ s_ref=None,
+ c_ref=None,
+ b_ref=None,
)
return symmetric_multiple_wing_steady_validation_airplane
diff --git a/tests/integration/fixtures/movement_fixtures.py b/tests/integration/fixtures/movement_fixtures.py
index d69598ab..e1dfa4cb 100644
--- a/tests/integration/fixtures/movement_fixtures.py
+++ b/tests/integration/fixtures/movement_fixtures.py
@@ -34,7 +34,7 @@ def make_static_validation_movement():
# Construct an airplane object and an operating point object.
unsteady_validation_airplane = (
- airplane_fixtures.make_asymmetric_unsteady_validation_airplane()
+ airplane_fixtures.make_symmetric_unsteady_validation_airplane()
)
unsteady_validation_operating_point = (
operating_point_fixtures.make_validation_operating_point()
@@ -95,8 +95,7 @@ def make_static_validation_movement():
unsteady_validation_movement = ps.movement.Movement(
airplane_movements=[unsteady_validation_airplane_movement],
operating_point_movement=unsteady_validation_operating_point_movement,
- num_steps=None,
- delta_time=None,
+ num_chords=6,
)
# Delete the now extraneous constructing fixtures.
diff --git a/tests/integration/fixtures/operating_point_fixtures.py b/tests/integration/fixtures/operating_point_fixtures.py
index 2d1e00ae..5fab9d0b 100644
--- a/tests/integration/fixtures/operating_point_fixtures.py
+++ b/tests/integration/fixtures/operating_point_fixtures.py
@@ -22,5 +22,12 @@ def make_validation_operating_point():
"""
# Create and return an operating point fixture.
- operating_point_fixture = ps.operating_point.OperatingPoint()
+ operating_point_fixture = ps.operating_point.OperatingPoint(
+ density=1.225,
+ velocity=10.0,
+ alpha=5.0,
+ beta=0.0,
+ external_thrust=0.0,
+ nu=15.06e-6,
+ )
return operating_point_fixture
diff --git a/tests/integration/test_steady_horseshoe_vortex_lattice_method.py b/tests/integration/test_steady_horseshoe_vortex_lattice_method.py
index 2db35068..85aad148 100644
--- a/tests/integration/test_steady_horseshoe_vortex_lattice_method.py
+++ b/tests/integration/test_steady_horseshoe_vortex_lattice_method.py
@@ -2,15 +2,15 @@
Based on an identical XFLR5 testing case, the expected output for the single-wing case
is:
- CL: 0.790
- CDi: 0.019
- Cm: -0.690
+ CL: 0.789
+ CDi: 0.020
+ Cm: -0.685
Based on an identical XFLR5 testing case, the expected output for the multi-wing case
is:
- CL: 0.524
- CDi: 0.007
- Cm: -0.350
+ CL: 0.513
+ CDi: 0.008
+ Cm: -0.336
Note: The expected output was created using XFLR5's inviscid VLM1 analysis type,
which is a horseshoe vortex lattice method solver.
@@ -84,29 +84,29 @@ def test_method(self):
self.steady_horseshoe_vortex_lattice_method_validation_solver.run()
# Calculate the percent errors of the output.
- c_di_expected = 0.019
+ c_di_expected = 0.020
c_di_calculated = (
self.steady_horseshoe_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_force_coefficients_wind_axes[0]
)
- c_di_error = abs(c_di_calculated - c_di_expected) / c_di_expected
+ c_di_error = abs((c_di_calculated - c_di_expected) / c_di_expected)
- c_l_expected = 0.790
+ c_l_expected = 0.789
c_l_calculated = (
self.steady_horseshoe_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_force_coefficients_wind_axes[2]
)
- c_l_error = abs(c_l_calculated - c_l_expected) / c_l_expected
+ c_l_error = abs((c_l_calculated - c_l_expected) / c_l_expected)
- c_m_expected = -0.690
+ c_m_expected = -0.685
c_m_calculated = (
self.steady_horseshoe_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_moment_coefficients_wind_axes[1]
)
- c_m_error = abs(c_m_calculated - c_m_expected) / c_m_expected
+ c_m_error = abs((c_m_calculated - c_m_expected) / c_m_expected)
# Set the allowable percent error.
allowable_error = 0.10
@@ -115,13 +115,13 @@ def test_method(self):
solver=self.steady_horseshoe_vortex_lattice_method_validation_solver,
show_wake_vortices=False,
show_streamlines=True,
- scalar_type="side force",
+ scalar_type="lift",
)
# Assert that the percent errors are less than the allowable error.
- self.assertTrue(abs(c_di_error) < allowable_error)
- self.assertTrue(abs(c_l_error) < allowable_error)
- self.assertTrue(abs(c_m_error) < allowable_error)
+ self.assertTrue(c_di_error < allowable_error)
+ self.assertTrue(c_l_error < allowable_error)
+ self.assertTrue(c_m_error < allowable_error)
def test_method_multiple_wings(self):
"""This method tests the solver's output with multi-wing geometry.
@@ -135,29 +135,29 @@ def test_method_multiple_wings(self):
)
# Calculate the percent errors of the output.
- c_di_expected = 0.007
+ c_di_expected = 0.008
c_di_calculated = self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_force_coefficients_wind_axes[
0
]
- c_di_error = abs(c_di_calculated - c_di_expected) / c_di_expected
+ c_di_error = abs((c_di_calculated - c_di_expected) / c_di_expected)
- c_l_expected = 0.524
+ c_l_expected = 0.513
c_l_calculated = self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_force_coefficients_wind_axes[
2
]
- c_l_error = abs(c_l_calculated - c_l_expected) / c_l_expected
+ c_l_error = abs((c_l_calculated - c_l_expected) / c_l_expected)
- c_m_expected = -0.350
+ c_m_expected = -0.336
c_m_calculated = self.steady_multiple_wing_horseshoe_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_moment_coefficients_wind_axes[
1
]
- c_m_error = abs(c_m_calculated - c_m_expected) / c_m_expected
+ c_m_error = abs((c_m_calculated - c_m_expected) / c_m_expected)
# Set the allowable percent error.
allowable_error = 0.10
@@ -170,6 +170,6 @@ def test_method_multiple_wings(self):
)
# Assert that the percent errors are less than the allowable error.
- self.assertTrue(abs(c_di_error) < allowable_error)
- self.assertTrue(abs(c_l_error) < allowable_error)
- self.assertTrue(abs(c_m_error) < allowable_error)
+ self.assertTrue(c_di_error < allowable_error)
+ self.assertTrue(c_l_error < allowable_error)
+ self.assertTrue(c_m_error < allowable_error)
diff --git a/tests/integration/test_steady_ring_vortex_lattice_method.py b/tests/integration/test_steady_ring_vortex_lattice_method.py
index be7c5719..3f4879ef 100644
--- a/tests/integration/test_steady_ring_vortex_lattice_method.py
+++ b/tests/integration/test_steady_ring_vortex_lattice_method.py
@@ -1,9 +1,9 @@
"""This module is a testing case for the steady ring vortex lattice method solver.
-Based on an identical XFLR5 testing case, the expected output for this case is:
- CL: 0.788
+Based on an identical XFLR5 VLM2 testing case, the expected output for this case is:
+ CL: 0.784
CDi: 0.019
- Cm: -0.687
+ Cm: -0.678
Note: The expected output was created using XFLR5's inviscid VLM2 analysis type,
which is a ring vortex lattice method solver.
@@ -77,23 +77,23 @@ def test_method(self):
0
].total_near_field_force_coefficients_wind_axes[0]
)
- c_di_error = abs(c_di_calculated - c_di_expected) / c_di_expected
+ c_di_error = abs((c_di_calculated - c_di_expected) / c_di_expected)
- c_l_expected = 0.788
+ c_l_expected = 0.784
c_l_calculated = (
self.steady_ring_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_force_coefficients_wind_axes[2]
)
- c_l_error = abs(c_l_calculated - c_l_expected) / c_l_expected
+ c_l_error = abs((c_l_calculated - c_l_expected) / c_l_expected)
- c_m_expected = -0.687
+ c_m_expected = -0.678
c_m_calculated = (
self.steady_ring_vortex_lattice_method_validation_solver.airplanes[
0
].total_near_field_moment_coefficients_wind_axes[1]
)
- c_m_error = abs(c_m_calculated - c_m_expected) / c_m_expected
+ c_m_error = abs((c_m_calculated - c_m_expected) / c_m_expected)
# Set the allowable percent error.
allowable_error = 0.10
@@ -106,6 +106,6 @@ def test_method(self):
)
# Assert that the percent errors are less than the allowable error.
- self.assertTrue(abs(c_di_error) < allowable_error)
- self.assertTrue(abs(c_l_error) < allowable_error)
- self.assertTrue(abs(c_m_error) < allowable_error)
+ self.assertTrue(c_di_error < allowable_error)
+ self.assertTrue(c_l_error < allowable_error)
+ self.assertTrue(c_m_error < allowable_error)
diff --git a/tests/integration/test_steady_trim.py b/tests/integration/test_steady_trim.py
index 1163a967..c221822e 100644
--- a/tests/integration/test_steady_trim.py
+++ b/tests/integration/test_steady_trim.py
@@ -42,10 +42,10 @@ def setUp(self):
:return: None
"""
- self.v_x_ans = 2.848
- self.alpha_ans = 1.943
+ self.v_x_ans = 2.9222951743478016
+ self.alpha_ans = 1.933469345202583
self.beta_ans = 0.000
- self.thrust_ans = 0.084
+ self.thrust_ans = 0.0884579818006783
self.ans_corruption = 0.05
diff --git a/tests/integration/test_unsteady_convergence.py b/tests/integration/test_unsteady_convergence.py
index 286416c4..256405f5 100644
--- a/tests/integration/test_unsteady_convergence.py
+++ b/tests/integration/test_unsteady_convergence.py
@@ -78,7 +78,7 @@ def test_unsteady_convergence(self):
converged_num_chordwise = converged_parameters[3]
wake_state_ans = True
- num_chords_ans = 4
+ num_chords_ans = 3
panel_ar_ans = 4
num_chordwise_ans = 4
diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py
index 2e4478a7..25274482 100644
--- a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py
+++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_static_geometry.py
@@ -1,12 +1,13 @@
"""This is a testing case for the unsteady ring vortex lattice method solver with
static, multi-wing geometry.
-Note: This case does not currently test the solver's output against an expected output. Instead, it just tests that
-the solver doesn't throw an error.
+Note: This case does not currently test the solver's output against an expected
+output. Instead, it just tests that the solver doesn't throw an error.
This module contains the following classes:
- TestUnsteadyRingVortexLatticeMethodMultipleWingStaticGeometry: This is a class for testing the unsteady ring
- vortex lattice method solver on static, multi-wing geometry.
+ TestUnsteadyRingVortexLatticeMethodMultipleWingStaticGeometry: This is a class
+ for testing the unsteady ring vortex lattice method solver on static, multi-wing
+ geometry.
This module contains the following exceptions:
None
diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py
index a592c599..0ecb0246 100644
--- a/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py
+++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_multiple_wing_variable_geometry.py
@@ -1,8 +1,8 @@
"""This is a testing case for the unsteady ring vortex lattice method solver with
variable, multi-wing geometry.
-Note: This case does not currently test the solver's output against an expected output. Instead, it just tests that
-the solver doesn't throw an error.
+Note: This case does not currently test the solver's output against an expected
+output. Instead, it just tests that the solver doesn't throw an error.
This module contains the following classes:
TestUnsteadyRingVortexLatticeMethodMultipleWingVariableGeometry: This is a class
diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py
index bb60abfd..0a81769e 100644
--- a/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py
+++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_static_geometry.py
@@ -2,14 +2,14 @@
static geometry.
Based on an equivalent XFLR5 testing case, the expected output for this case is:
- CL: 0.588
- CDi: 0.011
- Cm: -0.197
+ CL: 0.485
+ CDi: 0.015
+ Cm: -0.166
Note: The expected output was created using XFLR5's inviscid VLM2 analysis type,
-which is a ring vortex lattice
-method solver. The geometry in this case is static. Therefore, the results of this unsteady solver should converge to
-be close to XFLR5's static result.
+which is a ring vortex lattice method solver. The geometry in this case is static.
+Therefore, the results of this unsteady solver should converge to be close to XFLR5's
+static result.
This module contains the following classes:
TestUnsteadyRingVortexLatticeMethodStaticGeometry: This is a class for testing
@@ -80,15 +80,15 @@ def test_method(self):
this_airplane = this_solver.current_airplanes[0]
# Calculate the percent errors of the output.
- c_di_expected = 0.011
+ c_di_expected = 0.015
c_di_calculated = this_airplane.total_near_field_force_coefficients_wind_axes[0]
c_di_error = abs(c_di_calculated - c_di_expected) / c_di_expected
- c_l_expected = 0.588
+ c_l_expected = 0.485
c_l_calculated = this_airplane.total_near_field_force_coefficients_wind_axes[2]
c_l_error = abs(c_l_calculated - c_l_expected) / c_l_expected
- c_m_expected = -0.197
+ c_m_expected = -0.166
c_m_calculated = this_airplane.total_near_field_moment_coefficients_wind_axes[1]
c_m_error = abs(c_m_calculated - c_m_expected) / c_m_expected
diff --git a/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py b/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py
index 45cf0886..edff4a19 100644
--- a/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py
+++ b/tests/integration/test_unsteady_ring_vortex_lattice_method_variable_geometry.py
@@ -1,8 +1,8 @@
"""This is a testing case for the unsteady ring vortex lattice method solver with
variable geometry.
-Note: This case does not currently test the solver's output against an expected output. Instead, it just tests that
-the solver doesn't throw an error.
+Note: This case does not currently test the solver's output against an expected
+output. Instead, it just tests that the solver doesn't throw an error.
This module contains the following classes:
TestUnsteadyRingVortexLatticeMethodVariableGeometry: This is a class for testing
diff --git a/tests/references/testing references.xfl b/tests/references/testing references.xfl
new file mode 100644
index 00000000..989e51eb
Binary files /dev/null and b/tests/references/testing references.xfl differ