def test_flat_rotation_about_zero(self): ''' Calculate steady case / very short wake can be used. A flat plate at at zero angle of attack is taken as reference condition. The state is perturbed of an angle dalpha. ''' MList = [1, 8, 16] DAlphaList = [0.1, 0.5, 1., 2., 4., 6., 8., 10., 12., 14.] # degs Nm = len(MList) Na = len(DAlphaList) CDlin, CLlin = np.zeros((Na, Nm)), np.zeros((Na, Nm)) CDnnl, CLnnl = np.zeros((Na, Nm)), np.zeros((Na, Nm)) for mm in range(len(MList)): # input: reference condition: flat plate at zero angle Mw = 2 M = MList[mm] alpha = 0. * np.pi / 180. chord = 3. b = 0.5 * chord Uinf = np.array([20., 0.]) rho = 1.225 S0 = uvlm.solver(M, Mw, b, Uinf, alpha, rho=1.225) S0.build_flat_plate() S0.solve_static_Gamma2d() print('Testing steady aerofoil for M=%d...' % M) for aa in range(len(DAlphaList)): # Perturbation dalpha = np.pi / 180. * DAlphaList[aa] # angle [rad] dUinf = 0.0 # velocity [m/s] qinf_tot = 0.5 * rho * (S0.Uabs + dUinf)**2 print('\talpha==%.2f deg' % DAlphaList[aa]) ### Linearised solution # Perturb reference state ZetaRot = geo.rotate_aerofoil(S0.Zeta, dalpha) dZeta = ZetaRot - S0.Zeta Slin = linuvlm.solver(S0) Slin.Zeta = dZeta # solve Slin.solve_static_Gamma2d() # store data CDlin[aa,mm],CLlin[aa,mm]=np.sum(Slin.Faero,0)/\ (qinf_tot*Slin.S0.chord*(alpha+dalpha)) ### Reference nonlinear solution Sref = uvlm.solver(M, Mw, b, Uinf, alpha + dalpha, rho) Sref.build_flat_plate() # solve Sref.solve_static_Gamma2d() # store CDnnl[aa,mm],CLnnl[aa,mm]=np.sum(Sref.Faero,0)/\ (qinf_tot*Sref.chord*(alpha+dalpha)) clist = [ 'k', 'r', 'b', '0.6', ] fig = plt.figure('Aerodynamic coefficients', (12, 4)) ax1 = fig.add_subplot(121) ax2 = fig.add_subplot(122) for mm in range(Nm): # ax1.plot(DAlphaList, CDlin[:, mm], clist[mm], lw=Nm - mm, ls='--', label=r'M=%.2d lin' % MList[mm]) ax1.plot(DAlphaList, CDnnl[:, mm], clist[mm], lw=Nm - mm, label=r'M=%.2d nnl' % MList[mm]) # ax2.plot(DAlphaList, CLlin[:, mm], clist[mm], lw=Nm - mm, ls='--', label=r'M=%.2d lin' % MList[mm]) ax2.plot(DAlphaList, CLnnl[:, mm], clist[mm], lw=Nm - mm, label=r'M=%.2d nnl' % MList[mm]) ax1.set_xlabel(r'\alpha [deg]') ax1.set_title(r'CD') ax1.legend() ax2.set_xlabel(r'\alpha [deg]') ax2.set_title(r'CL') ax2.legend() if self.SHOW_PLOT: plt.show() else: plt.close()
def test_impulse(self): ''' Impulsive start (with/out Hall's correction) against Wagner solution ''' print('Testing aerofoil impulsive start...') # set-up M = 20 WakeFact = 20 c = 3. b = 0.5 * c uinf = 20.0 aeff = 0.1 * np.pi / 180. T = 8.0 ### reference static solution - at zero - Hall's correction S0 = uvlm2d_sta.solver(M=M, Mw=M * WakeFact, b=b, Uinf=np.array([uinf, 0.]), alpha=0.0, rho=1.225) S0.build_flat_plate() S0.solve_static_Gamma2d() Ftot0 = np.sum(S0.Faero, 0) Slin1 = lin_uvlm2d_dyn.solver(S0, T=T) ZetaRot = geo.rotate_aerofoil(S0.Zeta, aeff) dZeta = ZetaRot - S0.Zeta Slin1.Zeta = dZeta for tt in range(Slin1.NT): Slin1.THZeta[tt, :, :] = dZeta Slin1._imp_start = True Slin1.eps_Hall = 0.003 Slin1.solve_dyn_Gamma2d() ### reference static solution - at zero - no Hall's correction S0 = uvlm2d_sta.solver(M=M, Mw=M * WakeFact, b=b, Uinf=np.array([uinf, 0.]), alpha=0.0, rho=1.225) S0.build_flat_plate() S0.eps_Hall = 1.0 S0.solve_static_Gamma2d() Slin2 = lin_uvlm2d_dyn.solver(S0, T=T) Slin2.Zeta = dZeta for tt in range(Slin2.NT): Slin2.THZeta[tt, :, :] = dZeta Slin2._imp_start = True Slin2.eps_Hall = 1.0 Slin2.solve_dyn_Gamma2d() ### Analytical solution CLv_an = an.wagner_imp_start(aeff, uinf, c, Slin1.time) ##### Post-process numerical solution - Hall's correction THFtot1 = 0.0 * Slin1.THFaero for tt in range(Slin1.NT): THFtot1[tt, :] = Slin1.THFaero[tt, :] + Ftot0 THCFtot1 = THFtot1 / S0.qinf / S0.chord # Mass and circulatory contribution THCFmass1 = Slin1.THFaero_m / S0.qinf / S0.chord THCFcirc1 = THCFtot1 - THCFmass1 ##### Post-process numerical solution - no Hall's correction THFtot2 = 0.0 * Slin2.THFaero for tt in range(Slin2.NT): THFtot2[tt, :] = Slin2.THFaero[tt, :] + Ftot0 THCFtot2 = THFtot2 / S0.qinf / S0.chord # Mass and circulatory contribution THCFmass2 = Slin2.THFaero_m / S0.qinf / S0.chord THCFcirc2 = THCFtot2 - THCFmass2 plt.close('all') # non-dimensional time sv = 2.0 * S0.Uabs * Slin1.time / S0.chord fig = plt.figure('Lift coefficient', (10, 6)) ax = fig.add_subplot(111) # Wagner ax.plot(0.5 * sv, CLv_an, '0.6', lw=3, label='An Tot') # numerical ax.plot(0.5 * sv, THCFtot1[:, 1], 'k', lw=1, label='Num Tot - Hall') ax.plot(0.5 * sv, THCFmass1[:, 1], 'b', lw=1, label='Num Mass - Hall') ax.plot(0.5 * sv, THCFcirc1[:, 1], 'r', lw=1, label='Num Jouk - Hall') # numerical ax.plot(0.5 * sv, THCFtot2[:, 1], 'k--', lw=2, label='Num Tot') ax.plot(0.5 * sv, THCFmass2[:, 1], 'b--', lw=2, label='Num Mass') ax.plot(0.5 * sv, THCFcirc2[:, 1], 'r--', lw=2, label='Num Jouk') ax.set_xlabel(r'$s/2=U_\infty t/c$') ax.set_ylabel('force') ax.set_title('Lift') ax.legend() fig2 = plt.figure('Lift coefficient - zoom', (10, 6)) ax = fig2.add_subplot(111) # Wagner ax.plot(0.5 * sv, CLv_an, '0.6', lw=3, label='An Tot') # numerical ax.plot(0.5 * sv, THCFtot1[:, 1], 'k', lw=1, label='Num Tot - Hall') ax.plot(0.5 * sv, THCFmass1[:, 1], 'b', lw=1, label='Num Mass - Hall') ax.plot(0.5 * sv, THCFcirc1[:, 1], 'r', lw=1, label='Num Jouk - Hall') # numerical ax.plot(0.5 * sv, THCFtot2[:, 1], 'k--', lw=2, label='Num Tot') ax.plot(0.5 * sv, THCFmass2[:, 1], 'b--', lw=2, label='Num Mass') ax.plot(0.5 * sv, THCFcirc2[:, 1], 'r--', lw=2, label='Num Jouk') ax.set_xlabel(r'$s/2=U_\infty t/c$') ax.set_ylabel('force') ax.set_title('Lift') ax.legend() ax.set_xlim(0., 6.) fig3 = plt.figure("Lift coefficient - Hall's correction effect", (10, 6)) ax = fig3.add_subplot(111) # Wagner ax.plot(0.5 * sv, CLv_an, '0.6', lw=3, label='An Tot') # numerical ax.plot(0.5 * sv, THCFtot1[:, 1], 'k', lw=1, label='Num Tot - Hall') ax.plot(0.5 * sv, THCFmass1[:, 1], 'b', lw=1, label='Num Mass - Hall') ax.plot(0.5 * sv, THCFcirc1[:, 1], 'r', lw=1, label='Num Jouk - Hall') # numerical ax.plot(0.5 * sv, THCFtot2[:, 1], 'k--', lw=2, label='Num Tot') ax.plot(0.5 * sv, THCFmass2[:, 1], 'b--', lw=2, label='Num Mass') ax.plot(0.5 * sv, THCFcirc2[:, 1], 'r--', lw=2, label='Num Jouk') ax.set_xlabel(r'$s/2=U_\infty t/c$') ax.set_ylabel('force') ax.set_title('Lift') ax.legend() ax.set_xlim(15., 0.5 * sv[-1]) ax.set_ylim(0.10, 0.116) fig = plt.figure('Drag coefficient', (10, 6)) ax = fig.add_subplot(111) ax.plot(Slin1.time, THCFtot1[:, 0], 'k', label='Num Tot') ax.plot(Slin1.time, THCFmass1[:, 0], 'b', label='Num Mass') ax.plot(Slin1.time, THCFcirc1[:, 0], 'r', label='Num Jouk') ax.plot(Slin1.time, THCFtot2[:, 0], 'k', label='Num Tot') ax.plot(Slin1.time, THCFmass2[:, 0], 'b', label='Num Mass') ax.plot(Slin1.time, THCFcirc2[:, 0], 'r', label='Num Jouk') ax.set_xlabel('time') ax.set_ylabel('force') ax.set_title('Drag') ax.legend() fig = plt.figure('Vortex rings circulation time history', (10, 6)) ax = fig.add_subplot(111) clist = ['0.2', '0.4', '0.6'] Mlist = [0, int(S0.M / 2), S0.M - 1] for kk in range(len(Mlist)): mm = Mlist[kk] ax.plot(Slin1.time, Slin1.THGamma[:, mm], color=clist[kk], label='M=%.2d - Hall' % (mm)) clist = ['r', 'y', 'b'] MWlist = [0, int(S0.Mw / 2), S0.Mw - 1] for kk in range(len(MWlist)): mm = Mlist[kk] ax.plot(Slin1.time, Slin1.THGammaW[:, mm], color=clist[kk], label='Mw=%.2d - Hall' % (mm)) clist = ['0.2', '0.4', '0.6'] Mlist = [0, int(S0.M / 2), S0.M - 1] for kk in range(len(Mlist)): mm = Mlist[kk] ax.plot(Slin2.time, Slin2.THGamma[:, mm], color=clist[kk], linestyle='--', label='M=%.2d' % (mm)) clist = ['r', 'y', 'b'] MWlist = [0, int(S0.Mw / 2), S0.Mw - 1] for kk in range(len(MWlist)): mm = Mlist[kk] ax.plot(Slin2.time, Slin2.THGammaW[:, mm], color=clist[kk], linestyle='--', label='Mw=%.2dl' % (mm)) ax.set_xlabel('time') ax.set_ylabel('Gamma') ax.legend(ncol=2) if self.SHOW_PLOT: plt.show() else: plt.close() # Final error ErCDend = np.abs(THCFtot1[-1, 0]) ErCDend2 = np.abs(THCFtot2[-1, 0]) ErCLend = np.abs(THCFtot1[-1, 1] - CLv_an[-1]) / CLv_an[-1] ErCLend2 = np.abs(THCFtot2[-1, 1] - CLv_an[-1]) / CLv_an[-1] # Expected values CLexp = 0.010914578545119 CLexp2 = 0.010964883644861 changeCLnum = np.abs(THCFtot1[-1, 1] - CLexp) changeCLnum2 = np.abs(THCFtot2[-1, 1] - CLexp2) # Check/raise error self.assertTrue( ErCDend < 1e-15, msg= 'Final CD %.2e (with Hall correction) above limit value of %.2e!' % (ErCDend, self.TOL_analytical)) self.assertTrue( ErCDend2 < 1e-15, msg= 'Final CD %.2e (without Hall correction) above limit value of %.2e!' % (ErCDend2, self.TOL_analytical)) tolCL, tolCL2 = 0.4e-2, 0.2e-2 self.assertTrue(ErCLend<tolCL,msg='Relative CL error %.2e '\ '(with Hall correction) above tolerance %.2e!' %(ErCLend,tolCL)) self.assertTrue(ErCLend2<tolCL2,msg='Relative CL error %.2e '\ '(without Hall correction) above tolerance %.2e!'%(ErCLend2,tolCL2)) if changeCLnum > self.TOL_numerical: warnings.warn('Relative change in CL numerical solution '\ '(with Hall correction) of %.2e !!!'%changeCLnum) if changeCLnum2 > self.TOL_numerical: warnings.warn('Relative change in CL numerical solution '\ '(without Hall correction) of %.2e !!!'%changeCLnum2)
def test_camber_rotation(self): ''' Calculate steady case / very short wake can be used. A cambered aerofoil is tested at different angles of attack. Two linearisations, about zero and 10deg are used. ''' DAlphaList = [ 0.0, 0.1, 0.2, 0.3, 0.5, 1., 2., 4., 6., 8., 9.5, 9.7, 9.8, 9.9, 10., 10.1, 10.2, 10.3, 10.5, 12., 14., 16., 18. ] # degs MList = [20] Nm, mm = 1, 0 Na = len(DAlphaList) Llin01, Dlin01 = np.zeros((Na, Nm)), np.zeros((Na, Nm)) Llin02, Dlin02 = np.zeros((Na, Nm)), np.zeros((Na, Nm)) Lnnl, Dnnl = np.zeros((Na, Nm)), np.zeros((Na, Nm)) # input: Mw = 2 M = MList[mm] chord = 3. b = 0.5 * chord Uinf = np.array([20., 0.]) rho = 1.225 # reference condition 1: aerofoil at 0 deg angle alpha01 = 0. * np.pi / 180. S01 = uvlm.solver(M, Mw, b, Uinf, alpha01, rho=1.225) S01.build_camber_plate(Mcamb=10, Pcamb=4) S01.solve_static_Gamma2d() Fref01 = np.sum(S01.Faero, 0) # reference condition 2: aerofoil at 10 deg angle alpha02 = 10. * np.pi / 180. S02 = uvlm.solver(M, Mw, b, Uinf, alpha02, rho=1.225) S02.build_camber_plate(Mcamb=10, Pcamb=4) S02.solve_static_Gamma2d() Fref02 = np.sum(S02.Faero, 0) print('Testing steady aerofoil for M=%d...' % M) for aa in range(len(DAlphaList)): # Perturbation alpha_tot = np.pi / 180. * DAlphaList[aa] dUinf = 0.0 # velocity [m/s] qinf_tot = 0.5 * rho * (S01.Uabs + dUinf)**2 print('\talpha==%.2f deg' % DAlphaList[aa]) ### Linearised solution 01: # Perturb reference state dalpha01 = alpha_tot - alpha01 # angle [rad] ZetaRot = geo.rotate_aerofoil(S01.Zeta, dalpha01) dZeta = ZetaRot - S01.Zeta Slin01 = linuvlm.solver(S01) Slin01.Zeta = dZeta # solve Slin01.solve_static_Gamma2d() # store data dFtot01 = np.sum(Slin01.Faero, 0) Dlin01[aa, mm], Llin01[aa, mm] = dFtot01 + Fref01 ### Linearised solution 02: # Perturb reference state dalpha02 = alpha_tot - alpha02 # angle [rad] ZetaRot = geo.rotate_aerofoil(S02.Zeta, dalpha02) dZeta = ZetaRot - S02.Zeta Slin02 = linuvlm.solver(S02) Slin02.Zeta = dZeta # solve Slin02.solve_static_Gamma2d() # store data dFtot02 = np.sum(Slin02.Faero, 0) Dlin02[aa, mm], Llin02[aa, mm] = dFtot02 + Fref02 ### Reference nonlinear solution Sref = uvlm.solver(M, Mw, b, Uinf, alpha_tot, rho) Sref.build_camber_plate(Mcamb=10, Pcamb=4) # solve Sref.solve_static_Gamma2d() # store Dnnl[aa, mm], Lnnl[aa, mm] = np.sum(Sref.Faero, 0) clist = [ 'k', 'r', 'b', '0.6', ] ### Aerodynamic forces fig1 = plt.figure('Drag', (12, 4)) fig2 = plt.figure('Lift', (12, 4)) ax1 = fig1.add_subplot(111) ax2 = fig2.add_subplot(111) ax1.plot(DAlphaList, Dnnl[:, mm], 'r', lw=Nm - mm, label=r'M=%.2d nnl' % MList[mm]) ax1.plot(DAlphaList, Dlin01[:, mm], 'k', lw=2, ls='--', label=r'M=%.2d lin 0 deg' % MList[mm]) ax1.plot(DAlphaList, Dlin02[:, mm], 'b', lw=2, ls=':', label=r'M=%.2d lin 10 deg' % MList[mm]) # ax2.plot(DAlphaList, Lnnl[:, mm], 'r', lw=2, label=r'M=%.2d nnl' % MList[mm]) ax2.plot(DAlphaList, Llin01[:, mm], 'k', lw=2, ls='--', label=r'M=%.2d lin 0 deg' % MList[mm]) ax2.plot(DAlphaList, Llin02[:, mm], 'b', lw=Nm - mm, ls=':', label=r'M=%.2d lin 10 deg' % MList[mm]) ax1.set_xlabel(r'\alpha [deg]') ax1.set_title(r'Drag') ax1.legend() ax2.set_xlabel(r'\alpha [deg]') ax2.set_title(r'Lift') ax2.legend() fig1 = plt.figure('Relative error lift', (12, 4)) ax1 = fig1.add_subplot(111) ax1.plot(DAlphaList, Llin01[:, mm] / Lnnl[:, mm] - 1., 'k', lw=2, ls='--', label=r'M=%.2d lin 0 deg' % MList[mm]) ax1.plot(DAlphaList, Llin02[:, mm] / Lnnl[:, mm] - 1., 'b', lw=2, ls=':', label=r'M=%.2d lin 10 deg' % MList[mm]) ax1.set_xlabel(r'$\alpha$ [deg]') ax1.set_ylabel(r'relative error') ax1.set_title(r'Lift error w.r.t. exact solution') ax1.legend() ax2.legend() if self.SHOW_PLOT: plt.show() else: plt.close()
def test_steady(self): ''' Calculate steady case / very short wake can be used. ''' ### random geometry c = 3. b = 0.5 * c uinf = 20.0 T = 1.0 WakeFact = 2 alpha0 = 1.00 * np.pi / 180. dalpha = 2.00 * np.pi / 180. alpha_tot = alpha0 + dalpha TimeList = [] THCFList = [] MList = [4, 8, 16] for mm in range(len(MList)): M = MList[mm] print('Testing steady aerofoil M=%d...' % M) ### reference solution (static) S0 = uvlm2d_sta.solver(M=M, Mw=M * WakeFact, b=b, Uinf=np.array([uinf, 0.]), alpha=alpha0, rho=1.225) S0.build_flat_plate() S0.eps_Hall = 1.0 # no correction S0.solve_static_Gamma2d() Ftot0 = np.sum(S0.Faero, 0) ### linearisation Slin = lin_uvlm2d_dyn.solver(S0, T) # perturb reference state ZetaRot = geo.rotate_aerofoil(S0.Zeta, dalpha) dZeta = ZetaRot - S0.Zeta Slin.Zeta = dZeta for tt in range(Slin.NT): Slin.THZeta[tt, :, :] = dZeta # solve Slin.solve_dyn_Gamma2d() # total force THFtot = 0.0 * Slin.THFaero for tt in range(Slin.NT): THFtot[tt, :] = Slin.THFaero[tt, :] + Ftot0 TimeList.append(Slin.time) THCFList.append(THFtot / S0.qinf / S0.chord) clist = [ 'k', 'r', 'b', '0.6', ] fig = plt.figure('Aerodynamic forces', (12, 4)) ax = fig.add_subplot(131) for mm in range(len(MList)): ax.plot(TimeList[mm], THCFList[mm][:, 1], clist[mm], label=r'M=%.2d' % MList[mm]) ax.set_xlabel('time [s]') ax.set_title('Lift coefficient') ax.legend() ax = fig.add_subplot(132) for mm in range(len(MList)): ax.plot(TimeList[mm], THCFList[mm][:, 1] / alpha_tot, clist[mm], label=r'M=%.2d' % MList[mm]) ax.set_xlabel('time [s]') ax.set_title('CL alpha') ax.legend() ax = fig.add_subplot(133) for mm in range(len(MList)): ax.plot(TimeList[mm], THCFList[mm][:, 0], clist[mm], label=r'M=%.2d' % MList[mm]) ax.set_xlabel('time [s]') ax.set_title('Drag coefficient') ax.legend() if self.SHOW_PLOT: plt.show() else: plt.close() ### Testing maxDrag = 0.0 ClaExpected = 2. * np.pi ClaError = 0.0 for mm in range(len(MList)): # zero drag maxDragHere = np.max(np.abs(THCFList[mm][:, 0])) if maxDragHere > maxDrag: maxDrag = maxDragHere # Cla relative error maxClaErrorHere = np.max( np.abs(THCFList[mm][:, 1] / alpha_tot / ClaExpected - 1.0)) if maxClaErrorHere > ClaError: ClaError = maxClaErrorHere self.assertTrue(maxDragHere<self.TOL_zero,msg=\ 'Max Drag %.2e above tolerance %.2e!' %(maxDragHere,self.TOL_zero)) self.assertTrue(maxClaErrorHere < self.TOL_analytical, msg='Cla error %.2e' ' above tolerance %.2e!' % (maxClaErrorHere, self.TOL_analytical))