Exemple #1
0
def GetCoeffs(VMOPTS, Forces, VMINPUT, VelA_G=None):
    """@brief Calculate force coefficients from UVLM solution.
    
    @returns force coefficients in frame defined by free-stream AoA."""

    # Declare temp traids.
    Psi = np.zeros((3))
    Coeff = np.zeros((3))

    # Sum all forces.
    for i in range(VMOPTS.M.value + 1):
        for j in range(VMOPTS.N.value + 1):
            Coeff[:] += Forces[i, j, :]
        #end for j
    #end for i

    # divide by denom.
    if VelA_G != None:
        denom = 0.5 * (np.linalg.norm(VMINPUT.U_infty -
                                      VelA_G))**2.0 * VMINPUT.area
    else:
        denom = 0.5 * np.linalg.norm(VMINPUT.U_infty)**2.0 * VMINPUT.area

    Coeff[:] = Coeff[:] / denom

    # account for rotation of aerodynamic FoR (freestream velocity)
    Psi[0] = VMINPUT.alpha

    # get transformation matrix.
    CalphaG = Psi2TransMat(Psi)

    return np.dot(CalphaG, Coeff)
Exemple #2
0
def InitSection(VMOPTS, VMINPUT, ElasticAxis):
    """@brief Initialise section based on aero & aeroelastic elastic options.
    @param VMOPTS UVLM solver options.
    @param VMINPUT UVLM solver inputs.
    @param ElasticAxis Position of elastic axis, defined as Theodorsen's a 
    parameter - i.e the number of semi-chords aft of midchord.
    @return Section cross section coordinates."""

    Section = np.zeros((VMOPTS.M.value + 1, 3), ct.c_double, 'C')

    # Calculate rotation due to twist.
    Psi = np.zeros((3))
    Psi[0] = VMINPUT.theta

    # Get transformation matrix.
    R = Psi2TransMat(Psi)

    # Get delta chord.
    DeltaC = VMINPUT.c / VMOPTS.M.value

    # Get UVLM discretisation offset.
    Offset = 0.25 * DeltaC

    # based on elastic axis at (z= 0, y = 0) get leading edge position.
    LeadingEdge = 0.5 * VMINPUT.c + ElasticAxis * 0.5 * VMINPUT.c - Offset

    # calculate section coordinates.
    for j in range(VMOPTS.M.value + 1):
        Section[j, :] = np.dot(R, [0.0, LeadingEdge - j * DeltaC, 0.0])

    return Section
Exemple #3
0
def CoincidentGridForce(XBINPUT, PsiDefor, Section, AeroForces, BeamForces):
    """@brief Calculates aero forces and moments on the beam nodes from those on
    the aerodynamic grid.
    @param XBINPUT Beam input options.
    @param PsiDefor Array of beam nodal rotations.
    @param Section Array containing sectional camberline coordinates.
    @param AeroForces Aerodynamic grid forces to be mapped to beam nodes.
    @param BeamForces BeamForces to overwrite.
    
    @details All Beam forces calculated in in a-frame, while aero forces 
    are defined in earth frame."""

    # Zero beam forces.
    BeamForces[:, :] = 0.0

    # Get transformation matrix between a-frame and earth.
    CGa = Psi2TransMat(XBINPUT.PsiA_G)
    CaG = CGa.T

    # Num Nodes
    NumNodes = XBINPUT.NumNodesTot

    # Get number of nodes per beam element.
    NumNodesElem = XBINPUT.NumNodesElem

    # Loop along beam length.
    for iNode in range(NumNodes):

        iElem, iiElem = iNode2iElem(iNode, NumNodes, NumNodesElem)

        # Calculate transformation matrix for each node.
        CaB = Psi2TransMat(PsiDefor[iElem, iiElem, :])

        # Loop through each sectional coordinate.
        for jSection in range(Section.shape[0]):

            # Get Section coord and AeroForce in a-frame.
            Section_A = np.dot(CaB, Section[jSection, :])
            AeroForce_A = np.dot(CaG, AeroForces[jSection][iNode][:])

            # Calc moment.
            BeamForces[iNode, 3:] += np.cross(Section_A, AeroForce_A)

            # Calc force.
            BeamForces[iNode, :3] += AeroForce_A
Exemple #4
0
def CoincidentGrid(PosDefor,
                   PsiDefor,
                   Section,
                   VelA_A,
                   OmegaA_A,
                   PosDotDef,
                   PsiDotDef,
                   XBINPUT,
                   AeroGrid,
                   AeroVels,
                   OriginA_G=None,
                   PsiA_G=None,
                   ctrlSurf=None):
    """@brief Creates aero grid and velocities 
    based on beam nodal information.
    @param PosDefor Array of beam nodal displacements.
    @param PsiDefor Array of beam nodal rotations.
    @param Section Array containing sectional camberline coordinates.
    @param VelA_A Velocity of a-frame projected in a-frame.
    @param OmegaA_A Angular vel of a-frame projected in a-frame.
    @param PosDotDef Array of beam nodal velocities.
    @param PsiDotDef Array of beam nodal angular velocities.
    @param XBINPUT Beam input options.
    @param AeroGrid Aerodynamic grid to be updated.
    @param AeroVels Aerodynamic grid velocities to be updated
    @param OriginA_G Origin of a-frame.
    @param PsiA_G Attitude of a-frame w.r.t earth.
    @param ctrlSurf Control surface definition. 
    
    @details AeroGrid and AeroVels are projected in G-frame.
    """

    NumNodes = PosDefor.shape[0]
    NumNodesElem = XBINPUT.NumNodesElem

    for iNode in range(PosDefor.shape[0]):
        # Work out what element we are in.
        iElem, iiElem = iNode2iElem(iNode, NumNodes, NumNodesElem)

        # Calculate transformation matrix for each node.
        CaB = Psi2TransMat(PsiDefor[iElem, iiElem, :])

        # Loop through section coordinates.
        for jSection in range(Section.shape[0]):
            # Calculate instantaneous aero grid points
            AeroGrid[jSection,iNode,:] = PosDefor[iNode,:] \
                                         + np.dot(CaB,Section[jSection,:])

            # Calculate inertial angular velocity of B-frame projected in
            # the B-frame.
            Omega_B_B = (np.dot(Tangential(PsiDefor[iElem, iiElem, :]),
                                PsiDotDef[iElem, iiElem, :]) +
                         np.dot(CaB.T, OmegaA_A))

            # Calculate inertial velocity at grid points (projected in A-frame).
            AeroVels[jSection, iNode, :] = (
                VelA_A + np.dot(Skew(OmegaA_A), PosDefor[iNode, :]) +
                PosDotDef[iNode, :] +
                np.dot(CaB, np.dot(Skew(Omega_B_B), Section[jSection, :])))

            if (ctrlSurf != None and jSection > ctrlSurf.iMin
                    and jSection <= ctrlSurf.iMax and iNode >= ctrlSurf.jMin
                    and iNode <= ctrlSurf.jMax):
                # Apply changes to Zeta and ZetaDot in a-frame due to control
                # surface movement.

                # Check if control surface indices are within range of grid
                assert (ctrlSurf.iMin >= 0), 'CtrlSurf iMin less then zero'
                assert (ctrlSurf.iMax < Section.shape[0]), (
                    'CtrlSurf iMax ' + 'greater than section iMax')
                assert (ctrlSurf.jMin >= 0), 'CtrlSurf jMin less then zero'
                assert (ctrlSurf.jMax < PosDefor.shape[0]), (
                    'CtrlSurf jMax ' + 'greater than section jMax')

                # Determine hinge point
                hingePoint = Section[ctrlSurf.iMin, :]

                # Use a CRV to define rotation in sectional frame
                hingeRot = np.array([ctrlSurf.beta, 0.0, 0.0])
                hingeRotDot = np.array([ctrlSurf.betaDot, 0.0, 0.0])

                # Find lever arm from hinge point
                leverArm = Section[jSection, :] - hingePoint

                # Deformed position of node in B-frame
                CBBnew = Psi2TransMat(hingeRot)
                newSectionPoint = hingePoint + np.dot(CBBnew, leverArm)

                # Overwrite corresponding element in Aerogrid
                AeroGrid[jSection, iNode, :] = (PosDefor[iNode, :] +
                                                np.dot(CaB, newSectionPoint))

                # Overwrite corresponding element in AeroVels
                # There is an extra velocity contribution due to the rotation
                # of the hinge, with reference frame Bnew. Hence,
                # CBBnew * ( Omega_Bnew_Bnew X Point__Bnew), which is the
                # velocity at a point on the flap due to the rotation
                # of the hinge projected in the B-frame. is added.
                # Note: Skew(hingeRotDot) is an acceptable definition of the
                # angular rate as it corresponds to planar motion within the
                # section. In general Skew(T(\psi)\dot{\psi}) is required.
                # Warning: should omega_Bnew_Bnew  (Skew(hingeRotDot))
                # be an inertial velocity?
                AeroVels[jSection, iNode, :] = (
                    VelA_A + np.dot(Skew(OmegaA_A), PosDefor[iNode, :]) +
                    PosDotDef[iNode, :] + np.dot(
                        CaB,
                        np.dot(Skew(Omega_B_B), newSectionPoint) +
                        np.dot(CBBnew, np.dot(Skew(hingeRotDot), leverArm))))

        #END for jSection
    #END for iNode

    if ((OriginA_G is not None) and (PsiA_G is not None)):
        # Get transformation from a-frame to earth frame.
        CaG = Psi2TransMat(PsiA_G)
        CGa = CaG.T

        # Add the origin to grids in earth frame and transform velocities.
        for iNode in range(PosDefor.shape[0]):
            for jSection in range(Section.shape[0]):
                AeroGrid[jSection,iNode,:] = OriginA_G + \
                                             np.dot(
                                             CGa, AeroGrid[jSection,iNode,:])

                AeroVels[jSection,
                         iNode, :] = np.dot(CGa, AeroVels[jSection, iNode, :])
Exemple #5
0
    #create updated grid
    CoincidentGrid(PosDefor, PsiDefor, Section, VelA_A, \
                   OmegaA_A, PosDotDef, PsiDotDef, XBINPUT,\
                   AeroGrid, AeroVels)

    print(AeroGrid, '\n')

    #map to beam forces
    CoincidentGridForce(XBINPUT, PsiDefor, Section, AeroForces,\
                        BeamForces)

    print(BeamForces, '\n')

    #check applied forces
    #declare temp traids
    Psi = np.zeros((3))

    #loop through all beam nodes
    BeamForceCheck = np.zeros((3))
    for iNode in range(XBINPUT.NumNodesTot):
        BeamForceCheck[:] += BeamForces[iNode, :3]

    #account for rotation of aerodynamic FoR (freestream velocity)
    Psi[0] = VMINPUT.alpha

    #get transformation matrix
    CalphaG = Psi2TransMat(Psi)

    print(np.dot(CalphaG, BeamForceCheck))
def Solve_Py(XBINPUT, XBOPTS, VMOPTS, VMINPUT, AELAOPTS):
    """Nonlinear static solver using Python to solve aeroelastic
    equation. Assembly of structural matrices is carried out with 
    Fortran subroutines. Aerodynamics solved using PyAero.UVLM."""

    assert XBOPTS.Solution.value == 112, ('NonlinearStatic requested' +
                                          ' with wrong solution code')

    # Initialize beam.
    XBINPUT, XBOPTS, NumNodes_tot, XBELEM, PosIni, PsiIni, XBNODE, NumDof \
                = BeamInit.Static(XBINPUT,XBOPTS)
    # Set initial conditions as undef config.
    PosDefor = PosIni.copy(order='F')
    PsiDefor = PsiIni.copy(order='F')
    if XBOPTS.PrintInfo.value == True:
        sys.stdout.write('Solve nonlinear static case in Python ... \n')
    # Initialise structural eqn tensors.
    KglobalFull = np.zeros((NumDof.value, NumDof.value), ct.c_double, 'F')
    ks = ct.c_int()
    FglobalFull = np.zeros((NumDof.value, NumDof.value), ct.c_double, 'F')
    fs = ct.c_int()
    DeltaS = np.zeros(NumDof.value, ct.c_double, 'F')
    Qglobal = np.zeros(NumDof.value, ct.c_double, 'F')
    x = np.zeros(NumDof.value, ct.c_double, 'F')
    dxdt = np.zeros(NumDof.value, ct.c_double, 'F')
    # Beam Load Step tensors
    Force = np.zeros((XBINPUT.NumNodesTot, 6), ct.c_double, 'F')
    iForceStep = np.zeros((NumNodes_tot.value, 6), ct.c_double, 'F')
    iForceStep_Dof = np.zeros(NumDof.value, ct.c_double, 'F')

    # Initialze Aero.
    Section = InitSection(VMOPTS, VMINPUT, AELAOPTS.ElasticAxis)
    # Declare memory for Aero grid and velocities.
    Zeta = np.zeros((Section.shape[0], PosDefor.shape[0], 3), ct.c_double, 'C')
    ZetaDot = np.zeros((Section.shape[0], PosDefor.shape[0], 3), ct.c_double,
                       'C')
    # Additional Aero solver variables.
    AeroForces = np.zeros((VMOPTS.M.value + 1, VMOPTS.N.value + 1, 3),
                          ct.c_double, 'C')
    Gamma = np.zeros((VMOPTS.M.value, VMOPTS.N.value), ct.c_double, 'C')
    # Init external velocities.
    Uext = InitSteadyExternalVels(VMOPTS, VMINPUT)
    # Create zero triads for motion of reference frame.
    VelA_A = np.zeros((3))
    OmegaA_A = np.zeros((3))
    # Create zero vectors for structural vars not used in static analysis.
    PosDotDef = np.zeros_like(PosDefor, ct.c_double, 'F')
    PsiDotDef = np.zeros_like(PsiDefor, ct.c_double, 'F')

    # Define tecplot stuff.
    FileName = Settings.OutputDir + Settings.OutputFileRoot + 'AeroGrid.dat'
    Variables = ['X', 'Y', 'Z', 'Gamma']
    FileObject = PostProcess.WriteAeroTecHeader(FileName, 'Default', Variables)

    # Start Load Loop.
    for iLoadStep in range(XBOPTS.MaxIterations.value):
        # Reset convergence parameters and loads.
        Iter = 0
        ResLog10 = 0.0
        Force[:, :] = 0.0
        AeroForces[:, :, :] = 0.0
        oldPos = PosDefor.copy(order='F')
        oldPsi = PsiDefor.copy(order='F')

        # Calculate aero loads.
        if hasattr(XBINPUT, 'ForcedVel'):
            CoincidentGrid(PosDefor,
                           PsiDefor,
                           Section,
                           XBINPUT.ForcedVel[0, :3],
                           XBINPUT.ForcedVel[0, 3:],
                           PosDotDef,
                           PsiDotDef,
                           XBINPUT,
                           Zeta,
                           ZetaDot,
                           ctrlSurf=VMINPUT.ctrlSurf)
        else:
            CoincidentGrid(PosDefor,
                           PsiDefor,
                           Section,
                           VelA_A,
                           OmegaA_A,
                           PosDotDef,
                           PsiDotDef,
                           XBINPUT,
                           Zeta,
                           ZetaDot,
                           ctrlSurf=VMINPUT.ctrlSurf)

        if hasattr(XBINPUT, 'ForcedVel'):
            ZetaStar, GammaStar = InitSteadyWake(VMOPTS, VMINPUT, Zeta,
                                                 XBINPUT.ForcedVel[0, :3])
        else:
            ZetaStar, GammaStar = InitSteadyWake(VMOPTS, VMINPUT, Zeta)

        # Define AICs here for debgugging - Rob 16/08/2016
        AIC = np.zeros(
            (VMOPTS.M.value * VMOPTS.N.value, VMOPTS.M.value * VMOPTS.N.value),
            ct.c_double, 'C')
        BIC = np.zeros(
            (VMOPTS.M.value * VMOPTS.N.value, VMOPTS.M.value * VMOPTS.N.value),
            ct.c_double, 'C')

        # Solve for AeroForces.
        UVLMLib.Cpp_Solver_VLM(Zeta, ZetaDot, Uext, ZetaStar, VMOPTS,
                               AeroForces, Gamma, GammaStar, AIC, BIC)

        AeroForces[:, :, :] = AELAOPTS.AirDensity * AeroForces[:, :, :]

        # Write solution to tecplot file.
        PostProcess.WriteUVLMtoTec(FileObject,
                                   Zeta,
                                   ZetaStar,
                                   Gamma,
                                   GammaStar,
                                   iLoadStep,
                                   XBOPTS.NumLoadSteps.value,
                                   iLoadStep * 1.0,
                                   Text=True)

        # Map AeroForces to beam.
        CoincidentGridForce(XBINPUT, PsiDefor, Section, AeroForces, Force)

        # Add gravity loads.
        AddGravityLoads(Force, XBINPUT, XBELEM, AELAOPTS, PsiDefor, VMINPUT.c)

        # Apply factor corresponding to force step.
        if iLoadStep < XBOPTS.NumLoadSteps.value:
            iForceStep = (Force + XBINPUT.ForceStatic)*float( (iLoadStep+1) ) / \
                                                XBOPTS.NumLoadSteps.value
        else:
            # continue at full loading until equilibrium
            iForceStep = Force + XBINPUT.ForceStatic

        if XBOPTS.PrintInfo.value == True:
            sys.stdout.write('  iLoad: %-10d\n' % (iLoadStep + 1))
            sys.stdout.write('   SubIter DeltaF     DeltaX     ResLog10\n')

        # Start Newton Iteration.
        while ((ResLog10 > np.log10(XBOPTS.MinDelta.value))
               & (Iter < XBOPTS.MaxIterations.value)):

            Iter += 1
            if XBOPTS.PrintInfo.value == True:
                sys.stdout.write('   %-7d ' % (Iter))

            # Set structural eqn tensors to zero
            KglobalFull[:, :] = 0.0
            ks = ct.c_int()
            FglobalFull[:, :] = 0.0
            fs = ct.c_int()
            Qglobal[:] = 0.0

            # Assemble matrices for static problem
            BeamLib.Cbeam3_Asbly_Static(XBINPUT, NumNodes_tot, XBELEM, XBNODE,
                                        PosIni, PsiIni, PosDefor, PsiDefor,
                                        iForceStep, NumDof, ks, KglobalFull,
                                        fs, FglobalFull, Qglobal, XBOPTS)

            # Get state vector from current deformation.
            PosDot = np.zeros((NumNodes_tot.value, 3), ct.c_double, 'F')
            PsiDot = np.zeros((XBINPUT.NumElems, Settings.MaxElNod, 3),
                              ct.c_double, 'F')

            BeamLib.Cbeam_Solv_Disp2State(NumNodes_tot, NumDof, XBINPUT,
                                          XBNODE, PosDefor, PsiDefor, PosDot,
                                          PsiDot, x, dxdt)

            # Get forces on unconstrained nodes.
            BeamLib.f_fem_m2v(
                ct.byref(NumNodes_tot), ct.byref(ct.c_int(6)),
                iForceStep.ctypes.data_as(ct.POINTER(ct.c_double)),
                ct.byref(NumDof),
                iForceStep_Dof.ctypes.data_as(ct.POINTER(ct.c_double)),
                XBNODE.Vdof.ctypes.data_as(ct.POINTER(ct.c_int)))

            # Calculate \Delta RHS.
            Qglobal = Qglobal - np.dot(FglobalFull, iForceStep_Dof)

            # Calculate \Delta State Vector.
            DeltaS = -np.dot(np.linalg.inv(KglobalFull), Qglobal)

            if XBOPTS.PrintInfo.value == True:
                sys.stdout.write('%-10.4e %-10.4e ' %
                                 (max(abs(Qglobal)), max(abs(DeltaS))))

            # Update Solution.
            BeamLib.Cbeam3_Solv_Update_Static(XBINPUT, NumNodes_tot, XBELEM,
                                              XBNODE, NumDof, DeltaS, PosIni,
                                              PsiIni, PosDefor, PsiDefor)

            # Record residual at first iteration.
            if (Iter == 1):
                Res0_Qglobal = max(abs(Qglobal)) + 1.e-16
                Res0_DeltaX = max(abs(DeltaS)) + 1.e-16

            # Update residual and compute log10
            Res_Qglobal = max(abs(Qglobal)) + 1.e-16
            Res_DeltaX = max(abs(DeltaS)) + 1.e-16
            ResLog10 = max([
                np.log10(Res_Qglobal / Res0_Qglobal),
                np.log10(Res_DeltaX / Res0_DeltaX)
            ])

            if XBOPTS.PrintInfo.value == True:
                sys.stdout.write('%8.4f\n' % (ResLog10))

            # Stop the solution.
            if (ResLog10 > 10.):
                sys.stderr.write(' STOP\n')
                sys.stderr.write(' The max residual is %e\n' % (ResLog10))
                exit(1)
            elif Res_DeltaX < 1.e-14:
                break
        # END Newton iteration

        # After incremental loading continue until equilibrium reached
        if iLoadStep >= XBOPTS.NumLoadSteps.value:
            Pos_error = PosDefor - oldPos
            Psi_error = PsiDefor - oldPsi
            if( (np.linalg.norm(Pos_error)<=XBOPTS.MinDelta) & \
                (np.linalg.norm(Psi_error)<=XBOPTS.MinDelta) ):
                break
    # END Load step loop

    if XBOPTS.PrintInfo.value == True:
        sys.stdout.write(' ... done\n')

    # Write deformed configuration to file.
    ofile = Settings.OutputDir + Settings.OutputFileRoot + '_SOL112_def.dat'
    if XBOPTS.PrintInfo.value == True:
        sys.stdout.write('Writing file %s ... ' % (ofile))
    fp = open(ofile, 'w')
    fp.write('TITLE="Non-linear static solution: deformed geometry"\n')
    fp.write('VARIABLES="iElem" "iNode" "Px" "Py" "Pz" "Rx" "Ry" "Rz"\n')
    fp.close()
    if XBOPTS.PrintInfo.value == True:
        sys.stdout.write('done\n')
    WriteMode = 'a'

    BeamIO.OutputElems(XBINPUT.NumElems, NumNodes_tot.value, XBELEM, PosDefor,
                       PsiDefor, ofile, WriteMode)

    # Print deformed configuration.
    if XBOPTS.PrintInfo.value == True:
        sys.stdout.write('--------------------------------------\n')
        sys.stdout.write('NONLINEAR STATIC SOLUTION\n')
        sys.stdout.write('%10s %10s %10s\n' % ('X', 'Y', 'Z'))
        for inodi in range(NumNodes_tot.value):
            sys.stdout.write(' ')
            for inodj in range(3):
                sys.stdout.write('%12.5e' % (PosDefor[inodi, inodj]))
            sys.stdout.write('\n')
        sys.stdout.write('--------------------------------------\n')

    # Close Tecplot ascii FileObject.
    PostProcess.CloseAeroTecFile(FileObject)

    if True:
        # print and save deformed wing total force coefficients (may include gravity
        # and other applied static loads). Coefficients in wind-axes.
        cF = np.zeros((3))
        for i in range(VMOPTS.M.value + 1):
            for j in range(VMOPTS.N.value + 1):
                cF += AeroForces[i, j, :]

        Calpha = Psi2TransMat(np.array([VMINPUT.alpha, 0.0, 0.0]))
        cF = np.dot(Calpha, cF)
        cF = cF / (0.5 * AELAOPTS.AirDensity * np.linalg.norm(VMINPUT.U_infty)
                   **2.0 * VMINPUT.c * XBINPUT.BeamLength)
        print("Reference condition total force coefficients: {}".format(cF))
        fileName = Settings.OutputDir + Settings.OutputFileRoot + 'refCf'
        savemat(fileName, {'cF': cF})

    # Return solution
    return PosDefor, PsiDefor, Zeta, ZetaStar, Gamma, GammaStar, iForceStep, NumNodes_tot, NumDof, XBELEM, XBNODE, PosIni, PsiIni, Uext
                                       nu,
                                       M,
                                       N,
                                       mW,
                                       delS,
                                       imageMeth=True)

        ### linearized interface
        xiZeta = np.zeros((3 * (M + 1) * (N + 1), 6 * (N + 1)))
        #transformation from beam axes to aero axes
        e = EApos / 2.0 + 0.5  # EA position
        for ii in range(M + 1):
            for jj in range(N + 1):
                q = (N + 1) * ii + jj
                jElem, jjElem = iNode2iElem(jj, N + 1, XBINPUT.NumNodesElem)
                Cj = Psi2TransMat(PsiDefor[jElem, jjElem, :])
                Tang = Tangential(PsiDefor[jElem, jjElem, :])
                xiZeta[3 * q:3 * q + 3, 6 * jj:6 * jj + 3] = beam2aero
                xiZeta[3 * q:3 * q + 3, 6 * jj + 3:6 * jj + 6] = -np.dot(
                    beam2aero,
                    np.dot(
                        Cj,
                        np.dot(
                            Skew([0, chord *
                                  (e - float(ii) / float(M)), 0]), Tang)))
        # structural DoFs to aero inputs
        xiUa = np.zeros((9 * (M + 1) * (N + 1), 12 * (N + 1)))
        xiUa[:3 * (M + 1) * (N + 1), :6 * (N + 1)] = xiZeta / chord
        xiUa[3 * (M + 1) * (N + 1):6 * (M + 1) * (N + 1),
             6 * (N + 1):12 * (N + 1)] = xiZeta / chord