Exemple #1
0
	def setUp(self):
		''' Common piece of code to initialise the test '''

		# build reference state
		Mw=4
		M=3
		alpha=0.*np.pi/180.
		ainf=15.0*np.pi/180.
		Uinf=20.*np.array([np.cos(ainf),np.sin(ainf)])
		chord=5.0
		b=0.5*chord
		self.S0=uvlm2d_sta.solver(M,Mw,b,Uinf,alpha,rho=1.225)
		#self.S0.build_flat_plate()
		self.S0.build_camber_plate(Mcamb=50,Pcamb=4)
		self.S0.solve_static_Gamma2d()
Exemple #2
0
    def test_camber_aerofoil_speed(self):
        '''
		Calculate steady case / very short wake can be used. A cambered aerofoil,
		rotated of a alpha0 angle wrt the horizontal line is taken as a reference.
		The aerofoil has a nonzero velocity, such that an effective angle of 
		attack, alpha_eff, is achieved. The flow speed magnitude is kept constant.

		Two linear models are created. Both must be built around the aerofoil at 
		a geometrical angle of alpha0. However, the initial velocity profiles
		used for the linearisations are different and such to produce effective
		angles of attack of alpha_eff01 and alpha_eff02.
		'''

        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)

        CFXlin01, CFZlin01 = np.zeros((Na, Nm)), np.zeros((Na, Nm))
        CFXlin02, CFZlin02 = np.zeros((Na, Nm)), np.zeros((Na, Nm))
        CFXnnl, CFZnnl = np.zeros((Na, Nm)), np.zeros((Na, Nm))

        FZlin01, FXlin01 = np.zeros((Na, Nm)), np.zeros((Na, Nm))
        FZlin02, FXlin02 = np.zeros((Na, Nm)), np.zeros((Na, Nm))
        FZnnl, FXnnl = np.zeros((Na, Nm)), np.zeros((Na, Nm))

        # Reference input:
        Mw = 2
        M = MList[mm]
        chord = 3.
        b = 0.5 * chord
        Uinf0 = np.array([20., 0.])
        rho = 1.225
        alpha0 = 4.0 * np.pi / 180.  # angle of reference aerofoil w.r.t. horizontal line

        # reference condition 1/2: aerofoil at 0 deg angle
        S01 = uvlm.solver(M, Mw, b, Uinf0, alpha0, rho=1.225)
        S01.build_camber_plate(Mcamb=10, Pcamb=4)
        S02 = copy.deepcopy(S01)

        alpha_eff01 = 0. * np.pi / 180.
        alpha_inf = alpha_eff01 - alpha0
        Uinf_here = geo.rotate_speed(Uinf0, alpha_inf)
        S01.dZetadt[:, :] = -(Uinf_here - Uinf0)
        S01.solve_static_Gamma2d()
        Fref01 = np.sum(S01.Faero, 0)

        alpha_eff02 = 10. * np.pi / 180.
        alpha_inf = alpha_eff02 - alpha0
        Uinf_here = geo.rotate_speed(Uinf0, alpha_inf)
        S02.dZetadt[:, :] = -(Uinf_here - Uinf0)
        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_eff = np.pi / 180. * DAlphaList[aa]
            alpha_inf = alpha_eff - alpha0
            Uinf_here = geo.rotate_speed(Uinf0, alpha_inf)
            qinf_tot = 0.5 * rho * (S01.Uabs)**2
            print('\tAlpha effective=%.2f deg' % DAlphaList[aa])

            ### Reference nonlinear solution
            Sref = uvlm.solver(M, Mw, b, Uinf0, alpha0, rho)
            Sref.build_camber_plate(Mcamb=10, Pcamb=4)
            Sref.Wzeta[:, :] = Uinf_here - Uinf0
            Sref.solve_static_Gamma2d()
            FXnnl[aa, mm], FZnnl[aa, mm] = np.sum(Sref.Faero, 0)

            ### Linearised solution 01:
            Slin01 = linuvlm.solver(S01)
            Slin01.dZetadt[:, :] = -(Uinf_here -
                                     (S01.Uzeta[0, :] - S01.dZetadt[0, :]))
            Slin01.solve_static_Gamma2d()
            dFtot01 = np.sum(Slin01.Faero, 0)
            FXlin01[aa, mm], FZlin01[aa, mm] = dFtot01 + Fref01

            ### Linearised solution 02:
            Slin02 = linuvlm.solver(S02)
            Slin02.dZetadt[:, :] = -(Uinf_here -
                                     (S02.Uzeta[0, :] - S02.dZetadt[0, :]))
            Slin02.solve_static_Gamma2d()
            FXlin02[aa, mm], FZlin02[aa, mm] = np.sum(Slin02.Faero, 0)
            dFtot02 = np.sum(Slin02.Faero, 0)
            FXlin02[aa, mm], FZlin02[aa, mm] = dFtot02 + Fref02

        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,
                 FXlin01[:, mm],
                 'k',
                 lw=2,
                 ls='--',
                 label=r'M=%.2d lin 0 deg' % MList[mm])
        ax1.plot(DAlphaList,
                 FXlin02[:, mm],
                 'b',
                 lw=2,
                 ls=':',
                 label=r'M=%.2d lin 10 deg' % MList[mm])
        ax1.plot(DAlphaList,
                 FXnnl[:, mm],
                 'r',
                 lw=Nm - mm,
                 label=r'M=%.2d nnl' % MList[mm])
        #
        ax2.plot(DAlphaList,
                 FZlin01[:, mm],
                 'k',
                 lw=2,
                 ls='--',
                 label=r'M=%.2d lin 0 deg' % MList[mm])
        ax2.plot(DAlphaList,
                 FZlin02[:, mm],
                 'b',
                 lw=Nm - mm,
                 ls=':',
                 label=r'M=%.2d lin 10 deg' % MList[mm])
        ax2.plot(DAlphaList,
                 FZnnl[:, mm],
                 'r',
                 lw=2,
                 label=r'M=%.2d nnl' % MList[mm])

        ax1.set_xlabel(r'\alpha [deg]')
        ax1.set_title(r'Horizontal Force [N]')
        ax1.legend()
        ax2.set_xlabel(r'\alpha [deg]')
        ax2.set_title(r'Vertical Force [N]')
        ax2.legend()

        if self.SHOW_PLOT: plt.show()
        else: plt.close()
Exemple #3
0
    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()
Exemple #4
0
    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()
Exemple #5
0
    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)
Exemple #6
0
    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))
Exemple #7
0
    def test_sin_gust(self):
        '''
		Plunge motion at a fixed reduced frequency
		'''

        print('Testing aerofoil in sinusoidal gust...')

        # random geometry
        c = 3.
        b = 0.5 * c

        # gust profile
        w0 = 0.001
        uinf = 20.0
        L = 10. * c  # <--- gust wakelength

        # discretisation
        WakeFact = 20
        Ncycles = 18  # number of "cycles"
        Mfact = 14
        if c > L: M = int(np.ceil(4 * Mfact * c / L))
        else: M = Mfact * 4

        # Reference state
        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._saveout = False
        S0.solve_static_Gamma2d()
        Ftot0 = np.sum(S0.Faero, 0)

        # Linearised model
        Slin = lin_uvlm2d_dyn.solver(S0, T=Ncycles * L / uinf)
        Slin = set_gust.sin(Slin, w0, L, ImpStart=False)
        Slin.eps_Hall = 0.002
        Slin._savename='lindyn_sin_gust_M%.3d_wk%.2d_ccl%.2d.h5'\
                                                           %(M,WakeFact,Ncycles)
        Slin.solve_dyn_Gamma2d()

        # total force
        THFtot = 0.0 * Slin.THFaero
        for tt in range(Slin.NT):
            THFtot[tt, :] = Slin.THFaero[tt, :] + Ftot0
        THCF = THFtot / S0.qinf / S0.chord

        # Analytical solution
        CLv = an.sears_lift_sin_gust(w0, L, uinf, c, Slin.time)

        fig = plt.figure('Aerodynamic force coefficients', (10, 6))
        ax = fig.add_subplot(111)
        ax.set_title(r'Lift')
        ax.plot(Slin.time, CLv, 'k', lw=2, label=r"Analytical")
        ax.plot(Slin.time, THCF[:, 1], 'b', label=r'Numerical')
        ax.legend()
        if self.SHOW_PLOT: plt.show()
        else: plt.close()

        ### Detect peak and tim eof peak in last cycle
        # error on CL time-history not representative (amplified by lag)
        ttvec = Slin.time > float(Ncycles - 2) / Ncycles * Slin.T
        # num sol.
        ttmax = np.argmax(THCF[ttvec, 1])
        Tmax = Slin.time[ttvec][ttmax]
        CLmax = THCF[ttvec, 1][ttmax]
        # an. sol
        ttmax = np.argmax(CLv[ttvec])
        Tmax_an = Slin.time[ttvec][ttmax]
        CLmax_an = CLv[ttvec][ttmax]

        # Expected values at low fidelity
        TmaxExp = 25.9205357
        CLmaxExp = 0.00019761846109

        ### Error
        ErCLmax = np.abs(CLmax / CLmax_an - 1.0)
        Period = L / uinf
        ErTmax = np.abs((Tmax - Tmax_an) / Period)

        tolCL = 0.5e-2
        # tolTmax=6.e-2
        self.assertTrue(ErCLmax<tolCL,msg='Sinusoidal Gust: relative CLmax '\
                      'error %.2e above tolerance %.2e!' %(ErCLmax,tolCL))
        # self.assertTrue(ErTmax<tolTmax,msg='Sinusoidal Gust: relative Tmax '\
        # 		           'error %.2e above tolerance %.2e!' %(ErTmax,tolTmax))

        # Issue warnings
        changeTmaxnum = np.abs(Tmax - TmaxExp)
        changeCLnum = np.abs(CLmax - CLmaxExp)
        if changeCLnum > self.TOL_numerical:
            warnings.warn('Relative change in CLmax numerical solution '\
                                                'of %.2e !!!'%changeCLnum)
        if changeTmaxnum > self.TOL_numerical:
            warnings.warn('Relative change in Tmax numerical solution '\
                                              'of %.2e !!!'%changeTmaxnum)
Exemple #8
0
    def test_plunge(self):
        '''
		Plunge motion at low, medium-high and high reduced frequencies. 
		Especially at high reduced frequencies, the discretisation used are not
		refined enought to track correctly the lift. The accuracy of the induced
		drag is, instead, higher.
		Increasing the mesh (M) provides a big improvement, but the test case
		will be very slow.

		@warning: these test cases are different from those implemented in 
		test_dyn.py for the geometrically-exact solution.
		'''

        # random geometry/frequency
        c = 3.
        b = 0.5 * c
        f0 = 2.  #Hz
        w0 = 2. * np.pi * f0  #rad/s

        for case in ['low', 'medium-high', 'high']:
            print('Testing aerofoil in plunge motion at %s frequency...' %
                  case)
            if case is 'low':
                ktarget = 0.1
                H = 0.001 * b
                Ncycles = 5.
                WakeFact = 16
                M = 40
            if case is 'medium-high':
                ktarget = .75
                H = 0.01 * b
                Ncycles = 9.
                WakeFact = 20
                M = 60
            if case is 'high':
                ktarget = 1.0
                H = 0.001 * b
                Ncycles = 10.
                WakeFact = 20
                M = 70

            # speed/time
            uinf = b * w0 / ktarget
            T = 2. * np.pi * Ncycles / w0

            ### reference static solution
            S0 = uvlm2d_sta.solver(M=M,
                                   Mw=M * WakeFact,
                                   b=b,
                                   Uinf=np.array([uinf, 0.]),
                                   alpha=0.0 * np.pi / 180.,
                                   rho=1.225)
            S0.build_flat_plate()
            S0._saveout = False
            S0.solve_static_Gamma2d()
            Ftot0 = np.sum(S0.Faero, 0)

            # Linearised solution
            Slin = lin_uvlm2d_dyn.solver(S0, T)
            Slin = set_dyn.plunge(Slin, f0, H)
            Slin.eps_Hall = 0.003
            Slin._savename='lindyn_plunge_%s_M%.3d_wk%.2d_ccl%.2d_hall%.2e.h5'\
                                           %(case,M,WakeFact,Ncycles,Slin.eps_Hall)
            Slin.solve_dyn_Gamma2d()

            # Total force
            THFtot = 0.0 * Slin.THFaero
            for tt in range(Slin.NT):
                THFtot[tt, :] = Slin.THFaero[tt, :] + Ftot0

            THCF = THFtot / S0.qinf / S0.chord
            THCFmass = Slin.THFaero_m / S0.qinf / S0.chord
            THCFcirc = THCF - THCFmass

            ### post-process
            hc_num = (Slin.THZeta[:, 0, 1] - H) / S0.chord
            aeffv_num = np.zeros((Slin.NT))
            aeffv_num = -np.arctan(Slin.THdZetadt[:, 0, 1] / S0.Uinf[0])

            ### Analytical solution
            hv_an = -H * np.cos(w0 * Slin.time)
            hc_an = hv_an / S0.chord
            dhv = w0 * H * np.sin(w0 * Slin.time)
            aeffv_an = np.arctan(-dhv / S0.Uabs)
            # drag - Garrik
            Cdv = an.garrick_drag_plunge(w0, H, S0.chord, S0.rho, uinf,
                                         Slin.time)
            # lift - Theodorsen
            Ltot_an, Lcirc_an, Lmass_an = an.theo_lift(w0, 0, H, S0.chord,
                                                       S0.rho, S0.Uinf[0], 0.0)

            ph_tot = np.angle(Ltot_an)
            ph_circ = np.angle(Lcirc_an)
            ph_mass = np.angle(Lmass_an)
            CLtot_an = np.abs(Ltot_an) * np.cos(w0 * Slin.time +
                                                ph_tot) / (S0.chord * S0.qinf)
            CLcirc_an = np.abs(Lcirc_an) * np.cos(w0 * Slin.time + ph_circ) / (
                S0.chord * S0.qinf)
            CLmass_an = np.abs(Lmass_an) * np.cos(w0 * Slin.time + ph_mass) / (
                S0.chord * S0.qinf)

            ### Phase plots
            plt.close('all')
            fig = plt.figure(
                'Induced drag in plunge motion -'
                ' Phase vs kinematics', (10, 6))
            ax = fig.add_subplot(111)
            ax.plot(180. / np.pi * aeffv_an,
                    Cdv,
                    'k',
                    lw=2,
                    label=r'Analytical')
            ax.plot(180. / np.pi * aeffv_num,
                    THCF[:, 0],
                    'b',
                    label=r'Numerical')
            ax.set_xlabel('deg')
            ax.set_title('M=%s' % M)
            ax.legend()

            fig = plt.figure('Lift in plunge motion - '
                             'Phase vs kinematics', (10, 6))
            ax = fig.add_subplot(111)
            # analytical
            ax.plot(hc_an,
                    CLtot_an,
                    'k',
                    lw=2,
                    marker='o',
                    markevery=(.3),
                    label=r'An Tot')
            # numerical
            ax.plot(hc_num, THCF[:, 1], 'b', label=r'Num Tot')
            ax.set_xlabel('h/c')
            ax.legend()

            ### Time histories
            fig = plt.figure('Time histories', (12, 5))
            ax = fig.add_subplot(121)
            ax.plot(Slin.time, hc_num, 'y', lw=2, label='h/c')
            ax.plot(Slin.time,
                    aeffv_num,
                    '0.6',
                    lw=2,
                    label='Angle of attack [10 deg]')
            ax.plot(Slin.time, CLtot_an, 'k', lw=2, label='An Tot')
            ax.plot(Slin.time, THCF[:, 1], 'b', lw=1, label='Num Tot')
            ax.set_xlabel('time')
            ax.set_xlim((1. - 1. / Ncycles) * T, T)
            ax.set_title('Lift coefficient')
            ax.legend()
            ax = fig.add_subplot(122)
            ax.plot(Slin.time, Cdv, 'k', lw=2, label='An Tot')
            ax.plot(Slin.time, THCF[:, 0], 'b', lw=1, label='Num Tot')
            ax.set_xlim((1. - 1. / Ncycles) * T, T)
            ax.set_xlabel('time')
            ax.set_title('Drag coefficient')
            ax.legend()

            if self.SHOW_PLOT: plt.show()
            else: plt.close()

            ### Error in last cycle
            ttvec = Slin.time > float(Ncycles - 1) / Ncycles * Slin.T
            ErCL = np.abs(THCF[ttvec, 1] - CLtot_an[ttvec]) / np.max(
                CLtot_an[ttvec])
            #ErCD=np.abs(THCF[ttvec,0]-Cdv[ttvec])/np.max(np.abs(Cdv[ttvec]))
            # plt.plot(Slin.time[ttvec],ErCD,'b',label='CD')
            # plt.plot(Slin.time[ttvec],ErCL,'k',label='CL')
            # plt.legend()
            # plt.show()
            ErCLmax = np.max(ErCL)
            #ErCDmax=np.max(ErCD)

            # Set tolerance and compare vs. expect results at low refinement

            if case is 'low':
                tolCL = 1.0e-2
                ErCLnumExp = 0.0090214585275968876
            if case is 'medium-high':
                tolCL = 1.8e-2
                ErCLnumExp = 0.017774364218129
            if case is 'high':
                tolCL = 2.0e-2
                ErCLnumExp = 0.019179543357842

            # Check/raise error
            self.assertTrue(ErCLmax<tolCL,msg='Plunge case %s: relative CL '\
                    'error %.2e above tolerance %.2e!' %(case,ErCLmax,tolCL))
            # Issue warnings
            changeCLnum = np.abs(ErCLmax - ErCLnumExp)
            if changeCLnum > self.TOL_numerical:
                warnings.warn('Relative change in CL numerical solution '\
                                                   'of %.2e !!!'%changeCLnum)
Exemple #9
0
        self.THFdistr = self.THFdistr * S0.Fref

        ### wake
        # self.WzetaW=self.WzetaW*self.Uabs
        # self.UzetaW=self.UzetaW*self.Uabs


if __name__ == '__main__':

    import time
    import uvlm2d_sta
    import pp_uvlm2d as pp

    ### build reference state
    Mw = 8
    M = 3
    alpha = 2. * np.pi / 180.
    ainf = 2.0 * np.pi / 180.
    Uinf = 20. * np.array([np.cos(ainf), np.sin(ainf)])
    chord = 3.
    b = 0.5 * chord

    # static exact solution
    S0 = uvlm2d_sta.solver(M, Mw, b, Uinf, alpha, rho=1.225)
    S0.build_camber_plate(Mcamb=30, Pcamb=4)
    S0.solve_static_Gamma2d()

    # linearise dynamic
    T = 2.0
    Slin = solver(S0, T)
    Slin.solve_dyn_Gamma2d()