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)
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
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
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, :])
#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