예제 #1
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()
예제 #2
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)
예제 #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()
예제 #4
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))