Source code for openaerostruct.transfer.compute_transformation_matrix
import numpy as np
import openmdao.api as om
from openaerostruct.utils.vector_algebra import get_array_indices
[docs]
class ComputeTransformationMatrix(om.ExplicitComponent):
"""
Compute the transformation matrix used to apply the rotations obtained
from the FEM system to the aerodynamic mesh.
Technically the order of rotations (x, y, z) matter, but here we assume
they are independent. This allows us to construct a single transformation
matrix and apply it to the moment arms going from the structural mesh
to the aerodynamic mesh.
Parameters
----------
disp[ny, 6] : numpy array
Displacements and rotations acting on the structural spar which come
from solving the FEM system. Contains displacements for all six degrees
of freedom, including displacements in the x, y, and z directions, and
rotations about the x, y, and z axes.
Returns
-------
transformation_matrix[ny, 3, 3] : numpy array
Array containing the transformation matrices to apply the rotations
from the FEM results. These are the angles that come from the FEM
solver that rotate the structural spar. They will rotate the mesh based
on the both the angle (which comes from `disp`) and from the distance
of the aerodynamic mesh nodes to the structural mesh nodes (basically
a moment arm).
"""
def initialize(self):
self.options.declare("surface", types=dict)
def setup(self):
self.surface = surface = self.options["surface"]
mesh = surface["mesh"]
self.nx = mesh.shape[0]
self.ny = mesh.shape[1]
self.add_input("disp", val=np.zeros((self.ny, 6)), units="m")
self.add_output("transformation_matrix", shape=(self.ny, 3, 3))
# Create index arrays for each relevant input and output.
# This allows us to set up the rows and cols for the sparse Jacobians.
transform_indices = get_array_indices(self.ny, 3, 3)
# Set up the rows and cols for `transformation_matrix` wrt `disp`
rows = np.einsum("ijk,l->ijkl", transform_indices, np.ones(3, int)).flatten()
cols = np.einsum("il,jk->ijkl", get_array_indices(self.ny, 6)[:, 3:], np.ones((3, 3), int)).flatten()
self.declare_partials("transformation_matrix", "disp", rows=rows, cols=cols)
def compute(self, inputs, outputs):
# We populate the diagonal of each transformation matrix to account
# for the equivalent identity matrices we're adding in later steps.
# We need to do this because we're treating the three rotational
# matrices as independent and combining them all by adding.
outputs["transformation_matrix"] = 0.0
for i in range(3):
outputs["transformation_matrix"][:, i, i] -= 2.0
# These are the rotations obtained from the FEM solution
rx = inputs["disp"][:, 3]
ry = inputs["disp"][:, 4]
rz = inputs["disp"][:, 5]
# We then apply rotations to each corresponding entry in the
# transformation matrix, cumulatively adding or subtracting to
# obtain the final result.
# T[ 1:, 1:] += [[cos(rx), -sin(rx)], [ sin(rx), cos(rx)]]
outputs["transformation_matrix"][:, 1, 1] += np.cos(rx)
outputs["transformation_matrix"][:, 1, 2] -= np.sin(rx)
outputs["transformation_matrix"][:, 2, 1] += np.sin(rx)
outputs["transformation_matrix"][:, 2, 2] += np.cos(rx)
# T[::2, ::2] += [[cos(ry), sin(ry)], [-sin(ry), cos(ry)]]
outputs["transformation_matrix"][:, 0, 0] += np.cos(ry)
outputs["transformation_matrix"][:, 0, 2] += np.sin(ry)
outputs["transformation_matrix"][:, 2, 0] -= np.sin(ry)
outputs["transformation_matrix"][:, 2, 2] += np.cos(ry)
# T[ :2, :2] += [[cos(rz), -sin(rz)], [ sin(rz), cos(rz)]]
outputs["transformation_matrix"][:, 0, 0] += np.cos(rz)
outputs["transformation_matrix"][:, 0, 1] -= np.sin(rz)
outputs["transformation_matrix"][:, 1, 0] += np.sin(rz)
outputs["transformation_matrix"][:, 1, 1] += np.cos(rz)
def compute_partials(self, inputs, partials):
# Because of the way this is constructed, the derivatives are rather
# straightforward. We simply take the trigonometric deriv for each
# entry in the matrix.
rx = inputs["disp"][:, 3]
ry = inputs["disp"][:, 4]
rz = inputs["disp"][:, 5]
derivs = partials["transformation_matrix", "disp"].reshape((self.ny, 3, 3, 3))
derivs[:, :, :, :] = 0.0
derivs[:, 1, 1, 0] -= np.sin(rx)
derivs[:, 1, 2, 0] -= np.cos(rx)
derivs[:, 2, 1, 0] += np.cos(rx)
derivs[:, 2, 2, 0] -= np.sin(rx)
derivs[:, 0, 0, 1] -= np.sin(ry)
derivs[:, 0, 2, 1] += np.cos(ry)
derivs[:, 2, 0, 1] -= np.cos(ry)
derivs[:, 2, 2, 1] -= np.sin(ry)
derivs[:, 0, 0, 2] -= np.sin(rz)
derivs[:, 0, 1, 2] -= np.cos(rz)
derivs[:, 1, 0, 2] += np.cos(rz)
derivs[:, 1, 1, 2] -= np.sin(rz)