예제 #1
0
def main():
    interp = ViternaAirfoil().create_akima('mh117',
                                           Re_scaling=False,
                                           extend_alpha=True)

    def ccblade_interp(alpha, Re, Mach):
        shape = alpha.shape
        x = np.concatenate(
            [alpha.flatten()[:, np.newaxis],
             Re.flatten()[:, np.newaxis]],
            axis=-1)
        y = interp(x)
        y.shape = shape + (2, )
        return y[..., 0], y[..., 1]

    num_nodes = 1
    num_blades = 3
    num_radial = 15
    num_cp = 6
    chord = 10.
    theta = np.linspace(65., 25., num_cp) * np.pi / 180.
    pitch = 0.
    prop_data = {
        'num_radial': num_radial,
        'num_cp': num_cp,
        'pitch': pitch,
        'chord': chord,
        'theta': theta,
        'spline_type': 'akima',
        'B': num_blades,
        'interp': interp
    }

    hub_diameter = 30.  # cm
    prop_diameter = 150.  # cm
    c0 = np.sqrt(1.4 * 287.058 * 300.)  # meters/second
    rho0 = 1.4 * 98600. / (c0 * c0)  # kg/m^3
    omega = 236.

    prob = Problem()

    comp = IndepVarComp()
    comp.add_discrete_input('B', val=num_blades)
    comp.add_output('rho', val=rho0, shape=num_nodes, units='kg/m**3')
    comp.add_output('mu', val=1., shape=num_nodes, units='N/m**2*s')
    comp.add_output('asound', val=c0, shape=num_nodes, units='m/s')
    comp.add_output('v', val=77.2, shape=num_nodes, units='m/s')
    comp.add_output('alpha', val=0., shape=num_nodes, units='rad')
    comp.add_output('incidence', val=0., shape=num_nodes, units='rad')
    comp.add_output('precone', val=0., units='deg')
    comp.add_output('omega', val=omega, shape=num_nodes, units='rad/s')
    comp.add_output('hub_diameter',
                    val=hub_diameter,
                    shape=num_nodes,
                    units='cm')
    comp.add_output('prop_diameter',
                    val=prop_diameter,
                    shape=num_nodes,
                    units='cm')
    comp.add_output('pitch', val=pitch, shape=num_nodes, units='rad')
    comp.add_output('chord_dv', val=chord, shape=num_cp, units='cm')
    comp.add_output('theta_dv', val=theta, shape=num_cp, units='rad')
    prob.model.add_subsystem('inputs_comp', comp, promotes=['*'])

    prob.model.add_subsystem('bemt_group',
                             BEMTGroup(num_nodes=num_nodes,
                                       prop_data=prop_data),
                             promotes_inputs=[
                                 'rho', 'mu', 'v', 'alpha', 'incidence',
                                 'omega', 'hub_diameter', 'prop_diameter',
                                 'pitch', 'chord_dv', 'theta_dv'
                             ],
                             promotes_outputs=[
                                 ('normal_load_dist', 'openbemt_normal_load'),
                                 ('circum_load_dist', 'openbemt_circum_load')
                             ])

    comp = GeometryGroup(num_nodes=num_nodes,
                         num_cp=num_cp,
                         num_radial=num_radial)
    prob.model.add_subsystem(
        'geometry_group',
        comp,
        promotes_inputs=[
            'hub_diameter', 'prop_diameter', 'chord_dv', 'theta_dv', 'pitch'
        ],
        promotes_outputs=['radii', 'dradii', 'chord', 'theta'])

    comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
    prob.model.add_subsystem(
        'inflow_comp',
        comp,
        promotes_inputs=['v', 'omega', 'radii', 'precone'],
        promotes_outputs=['Vx', 'Vy'])

    comp = CCBladeGroup(num_nodes=num_nodes,
                        num_radial=num_radial,
                        airfoil_interp=ccblade_interp,
                        turbine=False,
                        phi_residual_solve_nonlinear='bracketing')
    prob.model.add_subsystem('ccblade_group',
                             comp,
                             promotes_inputs=[
                                 'B', 'radii', 'dradii', 'chord', 'theta',
                                 'rho', 'mu', 'asound', 'Vx', 'Vy', 'v',
                                 'precone', 'omega', 'hub_diameter',
                                 'prop_diameter'
                             ],
                             promotes_outputs=[('Np', 'ccblade_normal_load'),
                                               ('Tp', 'ccblade_circum_load')])

    prob.setup()
    prob.final_setup()
    prob.run_model()

    make_plots(prob)
예제 #2
0
def main():

    num_nodes = 1
    num_blades = 3
    num_radial = 15
    num_cp = 6
    af_filename = 'mh117.dat'
    chord = np.tile(10., (num_nodes, num_cp))
    theta = np.tile(np.linspace(65., 25., num_cp)*np.pi/180., (num_nodes, 1))
    pitch = 0.

    hub_diameter = 30.  # cm
    prop_diameter = 150.  # cm
    c0 = np.sqrt(1.4*287.058*300.)  # meters/second
    rho0 = 1.4*98600./(c0*c0)  # kg/m^3
    omega = 236.

    prob = Problem()

    v = np.linspace(1., 1.5, num_nodes)*77.2
    comp = IndepVarComp()
    comp.add_output('rho', val=rho0, shape=num_nodes, units='kg/m**3')
    comp.add_output('mu', val=1., shape=num_nodes, units='N/m**2*s')
    comp.add_output('asound', val=c0, shape=num_nodes, units='m/s')
    comp.add_output('v', val=v, shape=num_nodes, units='m/s')
    comp.add_output('alpha', val=0., shape=num_nodes, units='rad')
    comp.add_output('incidence', val=0., shape=num_nodes, units='rad')
    comp.add_output('precone', val=0., shape=num_nodes, units='deg')
    comp.add_output('omega', val=omega, shape=num_nodes, units='rad/s')
    comp.add_output('hub_diameter', val=hub_diameter, shape=num_nodes, units='cm')
    comp.add_output('prop_diameter', val=prop_diameter, shape=num_nodes, units='cm')
    comp.add_output('pitch', val=pitch, shape=num_nodes, units='rad')
    comp.add_output('chord_dv', val=chord, shape=(num_nodes, num_cp), units='cm')
    comp.add_output('theta_dv', val=theta, shape=(num_nodes, num_cp), units='rad')
    prob.model.add_subsystem('indep_var_comp', comp, promotes=['*'])

    comp = GeometryGroup(num_nodes=num_nodes, num_cp=num_cp,
                         num_radial=num_radial)
    prob.model.add_subsystem(
        'geometry_group', comp,
        promotes_inputs=['hub_diameter', 'prop_diameter', 'chord_dv',
                         'theta_dv', 'pitch'],
        promotes_outputs=['radii', 'dradii', 'chord', 'theta'])

    comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
    prob.model.add_subsystem(
        'inflow_comp', comp,
        promotes_inputs=['v', 'omega', 'radii', 'precone'],
        promotes_outputs=['Vx', 'Vy'])

    comp = CCBladeGroup(num_nodes=num_nodes, num_radial=num_radial,
                        num_blades=num_blades, af_filename=af_filename,
                        turbine=False)
    prob.model.add_subsystem(
        'ccblade_group', comp,
        promotes_inputs=['radii', 'dradii', 'chord', 'theta', 'rho', 'mu',
                         'asound', 'v', 'precone', 'omega', 'Vx', 'Vy',
                         'precone', 'hub_diameter', 'prop_diameter'],
        promotes_outputs=['thrust', 'torque', 'efficiency'])

    # prob.model.add_design_var('chord_dv', lower=1., upper=20.,
    #                           scaler=5e-2)
    # prob.model.add_design_var('theta_dv',
    #                           lower=20.*np.pi/180., upper=90*np.pi/180.)

    # prob.model.add_objective('efficiency', scaler=-1.,)
    # prob.model.add_constraint('thrust', equals=700., scaler=1e-3,
    #                           indices=np.arange(num_nodes))
    # prob.driver = pyOptSparseDriver()
    # prob.driver.options['optimizer'] = 'SNOPT'

    prob.setup()
    prob.final_setup()
    st = time.time()
    # prob.run_driver()
    prob.run_model()
    elapsed_time = time.time() - st

    make_plots(prob)

    return elapsed_time
예제 #3
0
    def test_hover(self):
        # num_nodes = 40
        nstart = 0
        nend = 39
        num_nodes = nend - nstart + 1
        chord = 0.060
        theta = 0.0
        Rtip = 0.656
        Rhub = 0.19 * Rtip
        rho = 1.225
        omega = 800.0 * np.pi / 30
        Vinf = 0.0
        turbine = False
        B = 3

        r = np.linspace(Rhub + 0.01 * Rtip, Rtip - 0.01 * Rtip, 30)
        num_radial = r.shape[-1]
        r = np.tile(r, (num_nodes, 1))

        chord = np.tile(chord, (num_nodes, num_radial))
        theta = np.tile(theta, (num_nodes, num_radial))

        # Airfoil interpolator.
        af = af_from_files(["airfoils/naca0012v2.txt"])

        pitch = np.linspace(1e-4, 20 * np.pi / 180, 40)[nstart:nend + 1]
        prob = om.Problem()

        comp = om.IndepVarComp()
        comp.add_output('v', val=Vinf, shape=num_nodes, units='m/s')
        comp.add_output('omega', val=np.tile(omega, num_nodes), units='rad/s')
        comp.add_output('radii',
                        val=r,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('chord',
                        val=chord,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('theta',
                        val=theta,
                        shape=(num_nodes, num_radial),
                        units='rad')
        comp.add_output('precone', val=0., shape=num_nodes, units='rad')
        comp.add_output('rho', val=rho, shape=(num_nodes, 1), units='kg/m**3')
        comp.add_output('mu', val=1.0, shape=(num_nodes, 1), units='N/m**2*s')
        comp.add_output('asound', val=1.0, shape=(num_nodes, 1), units='m/s')
        comp.add_output('hub_radius', val=Rhub, shape=num_nodes, units='m')
        comp.add_output('prop_radius', val=Rtip, shape=num_nodes, units='m')
        comp.add_output('pitch', val=pitch, shape=num_nodes, units='rad')
        prob.model.add_subsystem('ivc', comp, promotes_outputs=['*'])

        comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
        prob.model.add_subsystem(
            "simple_inflow",
            comp,
            promotes_inputs=["v", "omega", "radii", "precone"],
            promotes_outputs=["Vx", "Vy"])

        comp = make_component(
            CCBladeResidualComp(num_nodes=num_nodes,
                                num_radial=num_radial,
                                B=B,
                                af=af,
                                turbine=turbine))
        comp.linear_solver = om.DirectSolver(assemble_jac=True)
        prob.model.add_subsystem('ccblade',
                                 comp,
                                 promotes_inputs=[('r', 'radii'), 'chord',
                                                  'theta', 'Vx', 'Vy', 'rho',
                                                  'mu', 'asound',
                                                  ('Rhub', 'hub_radius'),
                                                  ('Rtip', 'prop_radius'),
                                                  'precone', 'pitch'],
                                 promotes_outputs=['Np', 'Tp'])

        comp = FunctionalsComp(num_nodes=num_nodes,
                               num_radial=num_radial,
                               num_blades=B)
        prob.model.add_subsystem(
            'ccblade_torquethrust_comp',
            comp,
            promotes_inputs=[
                'hub_radius', 'prop_radius', 'radii', 'Np', 'Tp', 'v', 'omega'
            ],
            promotes_outputs=['thrust', 'torque', 'efficiency'])

        prob.setup()
        prob.final_setup()
        prob.run_model()

        # these are not directly from the experimental data, but have been compared to the experimental data and compare favorably.
        # this is more of a regression test on the Vx=0 case
        CTcomp = np.array([
            9.452864991304056e-9, 6.569947366946672e-5, 0.00022338783939012262,
            0.0004420355541809959, 0.0007048495858030926,
            0.0010022162314665929, 0.0013268531109981317,
            0.0016736995380106938, 0.0020399354072946035,
            0.0024223576277264307, 0.0028189858460418893,
            0.0032281290309981213, 0.003649357660426685, 0.004081628946214875,
            0.004526034348853718, 0.004982651929181267, 0.0054553705714941,
            0.005942700094508395, 0.006447634897014323, 0.006963626871239654,
            0.007492654931894796, 0.00803866268066438, 0.008597914974368199,
            0.009163315934297088, 0.00973817187875574, 0.010309276997090536,
            0.010827599471613264, 0.011322361524464346, 0.01180210507896255,
            0.012276543435307877, 0.012749323136224754, 0.013223371028562213,
            0.013697833731701945, 0.01417556699620018, 0.014646124777465859,
            0.015112116772851365, 0.015576452747370885, 0.01602507607909594,
            0.016461827164870473, 0.016880126012974343
        ])
        CQcomp = np.array([
            0.000226663607327854, 0.0002270862930229147, 0.0002292742856722754,
            0.00023412703235791698, 0.00024192624628054639,
            0.0002525855612031453, 0.00026638347417704255,
            0.00028314784456601373, 0.00030299181501156373,
            0.0003259970210015136, 0.00035194661281707764,
            0.00038102864688744595, 0.0004132249034847219,
            0.00044859355432807347, 0.0004873204055790553,
            0.0005293656187218555, 0.0005753409000182888,
            0.0006250099998058788, 0.0006788861946930185,
            0.0007361096750412038, 0.0007970800153713466,
            0.0008624036743669367, 0.0009315051772818803,
            0.0010035766105979213, 0.0010791941808362153,
            0.0011566643573792704, 0.001229236439467123, 0.0013007334425769355,
            0.001372124993921022, 0.0014449961686871802, 0.0015197156782734364,
            0.0015967388663224156, 0.0016761210460920718,
            0.0017578748614666766, 0.0018409716992061841,
            0.0019248522013432586, 0.0020103360819251357, 0.002096387027559033,
            0.002182833604491109, 0.0022686470790128036
        ])
        T = prob.get_val('thrust', units='N')
        Q = prob.get_val('torque', units='N*m')
        A = np.pi * Rtip**2
        CT = T / (rho * A * (omega * Rtip)**2)
        CQ = Q / (rho * A * (omega * Rtip)**2 * Rtip)

        assert_allclose(CT, CTcomp[nstart:nend + 1], atol=1e-4, rtol=1)
        assert_allclose(CQ, CQcomp[nstart:nend + 1], atol=1e-4, rtol=1)
예제 #4
0
def main():
    interp = ViternaAirfoil().create_akima(
        'mh117', Re_scaling=False, extend_alpha=True)

    def ccblade_interp(alpha, Re, Mach):
        shape = alpha.shape
        x = np.concatenate(
            [
                alpha.flatten()[:, np.newaxis],
                Re.flatten()[:, np.newaxis]
            ], axis=-1)
        y = interp(x)
        y.shape = shape + (2,)
        return y[..., 0], y[..., 1]

    num_nodes = 1
    num_blades = 3
    num_radial = 15
    num_cp = 6
    chord = 10.
    theta = np.linspace(65., 25., num_cp)*np.pi/180.
    pitch = 0.

    hub_diameter = 30.  # cm
    prop_diameter = 150.  # cm
    c0 = np.sqrt(1.4*287.058*300.)  # meters/second
    rho0 = 1.4*98600./(c0*c0)  # kg/m^3
    omega = 236.

    prob = Problem()

    comp = IndepVarComp()
    comp.add_discrete_input('B', val=num_blades)
    comp.add_output('rho', val=rho0, shape=num_nodes, units='kg/m**3')
    comp.add_output('mu', val=1., shape=num_nodes, units='N/m**2*s')
    comp.add_output('asound', val=c0, shape=num_nodes, units='m/s')
    comp.add_output('v', val=77.2, shape=num_nodes, units='m/s')
    comp.add_output('alpha', val=0., shape=num_nodes, units='rad')
    comp.add_output('incidence', val=0., shape=num_nodes, units='rad')
    comp.add_output('precone', val=0., units='deg')
    comp.add_output('omega', val=omega, shape=num_nodes, units='rad/s')
    comp.add_output('hub_diameter', val=hub_diameter, shape=num_nodes, units='cm')
    comp.add_output('prop_diameter', val=prop_diameter, shape=num_nodes, units='cm')
    comp.add_output('pitch', val=pitch, shape=num_nodes, units='rad')
    comp.add_output('chord_dv', val=chord, shape=num_cp, units='cm')
    comp.add_output('theta_dv', val=theta, shape=num_cp, units='rad')
    prob.model.add_subsystem('indep_var_comp', comp, promotes=['*'])

    comp = GeometryGroup(num_nodes=num_nodes, num_cp=num_cp,
                         num_radial=num_radial)
    prob.model.add_subsystem(
        'geometry_group', comp,
        promotes_inputs=['hub_diameter', 'prop_diameter', 'chord_dv',
                         'theta_dv', 'pitch'],
        promotes_outputs=['radii', 'dradii', 'chord', 'theta'])

    comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
    prob.model.add_subsystem(
        'inflow_comp', comp,
        promotes_inputs=['v', 'omega', 'radii', 'precone'],
        promotes_outputs=['Vx', 'Vy'])

    comp = CCBladeGroup(num_nodes=num_nodes, num_radial=num_radial,
                        num_blades=num_blades,
                        airfoil_interp=ccblade_interp, turbine=False,
                        phi_residual_solve_nonlinear='bracketing')
    prob.model.add_subsystem(
        'ccblade_group', comp,
        promotes_inputs=['radii', 'dradii', 'chord', 'theta', 'rho',
                         'mu', 'asound', 'v', 'omega', 'Vx', 'Vy', 'precone',
                         'hub_diameter', 'prop_diameter'],
        promotes_outputs=['thrust', 'torque', 'efficiency'])

    prob.model.add_design_var('chord_dv', lower=1., upper=20.,
                              scaler=5e-2)
    prob.model.add_design_var('theta_dv',
                              lower=20.*np.pi/180., upper=90*np.pi/180.)

    prob.model.add_objective('efficiency', scaler=-1.,)
    prob.model.add_constraint('thrust', equals=700., scaler=1e-3,
                              indices=np.arange(num_nodes))
    prob.driver = pyOptSparseDriver()
    prob.driver.options['optimizer'] = 'SNOPT'

    prob.setup()
    prob.final_setup()
    st = time.time()
    prob.run_driver()
    elapsed_time = time.time() - st

    make_plots(prob)

    return elapsed_time
예제 #5
0
    def test_camber(self):
        # Copied from CCBlade.jl/test/runtests.jl

        # inputs
        chord = 0.10
        D = 1.6
        RPM = 2100
        rho = 1.225
        pitch = 1.0  # pitch distance in meters.

        # --- rotor definition ---
        turbine = False
        Rhub = 0.0
        Rtip = D / 2
        Rhub_eff = 1e-6  # something small to eliminate hub effects
        Rtip_eff = 100.0  # something large to eliminate tip effects
        B = 2  # number of blades

        # --- section definitions ---

        num_nodes = 1
        R = D / 2.0
        r = np.linspace(R / 10, R, 11)
        dr = r[1] - r[0]
        r = np.tile(r, (num_nodes, 1))
        dr = np.tile(dr, r.shape)
        theta = np.arctan(pitch / (2 * np.pi * r))

        jlmain.eval("""
                function affunc(alpha, Re, M)
                    alpha0 = -3*pi/180
                    cl = 6.2*(alpha - alpha0)
                    cd = 0.008 - 0.003*cl + 0.01*cl*cl
                    return cl, cd
                end
            """)
        affunc = jlmain.affunc

        prob = om.Problem()
        num_radial = r.shape[-1]

        comp = om.IndepVarComp()
        comp.add_output('v', val=[5.0], units='m/s')
        comp.add_output('omega', val=np.tile(RPM, num_nodes), units='rpm')
        comp.add_output('radii', val=r, units='m')
        comp.add_output('dradii', val=dr, units='m')
        comp.add_output('chord',
                        val=chord,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('theta',
                        val=theta,
                        shape=(num_nodes, num_radial),
                        units='rad')
        comp.add_output('precone', val=0., shape=num_nodes, units='rad')
        comp.add_output('rho', val=rho, shape=(num_nodes, 1), units='kg/m**3')
        comp.add_output('mu', val=1.0, shape=(num_nodes, 1), units='N/m**2*s')
        comp.add_output('asound', val=1.0, shape=(num_nodes, 1), units='m/s')
        comp.add_output('hub_radius_eff',
                        val=Rhub_eff,
                        shape=num_nodes,
                        units='m')
        comp.add_output('prop_radius_eff',
                        val=Rtip_eff,
                        shape=num_nodes,
                        units='m')
        comp.add_output('hub_radius', val=Rhub, shape=num_nodes, units='m')
        comp.add_output('prop_radius', val=Rtip, shape=num_nodes, units='m')
        comp.add_output('pitch', val=0.0, shape=num_nodes, units='rad')
        prob.model.add_subsystem('ivc', comp, promotes_outputs=['*'])

        comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
        prob.model.add_subsystem(
            "simple_inflow",
            comp,
            promotes_inputs=["v", "omega", "radii", "precone"],
            promotes_outputs=["Vx", "Vy"])

        comp = make_component(
            CCBladeResidualComp(num_nodes=num_nodes,
                                num_radial=num_radial,
                                B=B,
                                af=affunc,
                                turbine=turbine))
        comp.linear_solver = om.DirectSolver(assemble_jac=True)
        prob.model.add_subsystem('ccblade',
                                 comp,
                                 promotes_inputs=[('r', 'radii'), 'chord',
                                                  'theta', 'Vx', 'Vy', 'rho',
                                                  'mu', 'asound',
                                                  ('Rhub', 'hub_radius_eff'),
                                                  ('Rtip', 'prop_radius_eff'),
                                                  'precone', 'pitch'],
                                 promotes_outputs=['Np', 'Tp'])

        prob.setup()
        prob.final_setup()
        prob.run_model()

        Np = prob.get_val('Np', units='N/m')
        Tp = prob.get_val('Tp', units='N/m')
        T = np.sum(Np * dr) * B
        Q = np.sum(r * Tp * dr) * B

        assert_allclose(1223.0506862888788, T, atol=1e-8)
        assert_allclose(113.79919472569034, Q, atol=1e-8)

        assert_allclose(prob.get_val('Np', units='N/m')[0, 3],
                        427.3902632382494,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('Tp', units='N/m')[0, 3],
                        122.38414345762305,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.a')[0, 3],
                        2.2845512476210943,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.ap')[0, 3],
                        0.05024950801920044,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.u', units='m/s')[0, 3],
                        11.422756238105471,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.v', units='m/s')[0, 3],
                        3.2709314141649575,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.phi', units='rad')[0, 3],
                        0.2596455971546484,
                        atol=1e-8,
                        rtol=1)
        # assert_allclose(prob.get_val('ccblade.alpha', units='rad')[0, 3], 0.23369406105568025, atol=1e-8, rtol=1)
        assert_allclose(prob.get_val('ccblade.W', units='m/s')[0, 3],
                        63.96697566502531,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.cl')[0, 3],
                        1.773534419416163,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.cd')[0, 3],
                        0.03413364011028978,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.cn')[0, 3],
                        1.7053239640124302,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.ct')[0, 3],
                        0.48832327407767123,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.F')[0, 3],
                        1.0,
                        atol=1e-8,
                        rtol=1)
        assert_allclose(prob.get_val('ccblade.G')[0, 3],
                        1.0,
                        atol=1e-8,
                        rtol=1)

        theta = np.arctan(pitch / (2 * np.pi * r)) - 3 * np.pi / 180
        prob.set_val('theta', theta, units='rad')
        prob.run_model()

        Np = prob.get_val('Np', units='N/m')
        Tp = prob.get_val('Tp', units='N/m')
        T = np.sum(Np * dr) * B
        Q = np.sum(r * Tp * dr) * B

        assert_allclose(T, 1e3 * 0.962407923825140, atol=1e-3)
        assert_allclose(Q, 1e2 * 0.813015017103876, atol=1e-4)
예제 #6
0
    def test_multiple_adv_ratios(self):
        # num_nodes = 20
        nstart = 0
        nend = 19
        num_nodes = nend - nstart + 1

        r = 0.0254 * np.array([
            0.7526, 0.7928, 0.8329, 0.8731, 0.9132, 0.9586, 1.0332, 1.1128,
            1.1925, 1.2722, 1.3519, 1.4316, 1.5114, 1.5911, 1.6708, 1.7505,
            1.8302, 1.9099, 1.9896, 2.0693, 2.1490, 2.2287, 2.3084, 2.3881,
            2.4678, 2.5475, 2.6273, 2.7070, 2.7867, 2.8661, 2.9410
        ]).reshape(1, -1)
        num_radial = r.shape[-1]
        r = np.tile(r, (num_nodes, 1))

        chord = 0.0254 * np.array([
            0.6270, 0.6255, 0.6231, 0.6199, 0.6165, 0.6125, 0.6054, 0.5973,
            0.5887, 0.5794, 0.5695, 0.5590, 0.5479, 0.5362, 0.5240, 0.5111,
            0.4977, 0.4836, 0.4689, 0.4537, 0.4379, 0.4214, 0.4044, 0.3867,
            0.3685, 0.3497, 0.3303, 0.3103, 0.2897, 0.2618, 0.1920
        ]).reshape((1, num_radial))
        chord = np.tile(chord, (num_nodes, 1))

        theta = np.pi / 180.0 * np.array([
            40.2273, 38.7657, 37.3913, 36.0981, 34.8803, 33.5899, 31.6400,
            29.7730, 28.0952, 26.5833, 25.2155, 23.9736, 22.8421, 21.8075,
            20.8586, 19.9855, 19.1800, 18.4347, 17.7434, 17.1005, 16.5013,
            15.9417, 15.4179, 14.9266, 14.4650, 14.0306, 13.6210, 13.2343,
            12.8685, 12.5233, 12.2138
        ]).reshape((1, num_radial))
        theta = np.tile(theta, (num_nodes, 1))

        rho = 1.225
        Rhub = 0.0254 * .5
        Rtip = 0.0254 * 3.0
        B = 2  # number of blades
        turbine = False

        J = np.linspace(0.1, 0.9, 20)[nstart:nend + 1]  # advance ratio
        # J = np.linspace(0.1, 0.9, 20)

        omega = 8000.0 * np.pi / 30

        n = omega / (2 * np.pi)
        D = 2 * Rtip

        Vinf = J * D * n
        dr = r[0, 1] - r[0, 0]

        # Airfoil interpolator.
        af = af_from_files(["airfoils/NACA64_A17.dat"])

        prob = om.Problem()

        comp = om.IndepVarComp()
        comp.add_output('v', val=Vinf, units='m/s')
        comp.add_output('omega', val=np.tile(omega, num_nodes), units='rad/s')
        comp.add_output('radii',
                        val=r,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('dradii',
                        val=dr,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('chord',
                        val=chord,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('theta',
                        val=theta,
                        shape=(num_nodes, num_radial),
                        units='rad')
        comp.add_output('precone', val=0., shape=num_nodes, units='rad')
        comp.add_output('rho', val=rho, shape=(num_nodes, 1), units='kg/m**3')
        comp.add_output('mu', val=1.0, shape=(num_nodes, 1), units='N/m**2*s')
        comp.add_output('asound', val=1.0, shape=(num_nodes, 1), units='m/s')
        comp.add_output('hub_radius', val=Rhub, shape=num_nodes, units='m')
        comp.add_output('prop_radius', val=Rtip, shape=num_nodes, units='m')
        comp.add_output('pitch', val=0.0, shape=num_nodes, units='rad')
        prob.model.add_subsystem('ivc', comp, promotes_outputs=['*'])

        comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
        prob.model.add_subsystem(
            "simple_inflow",
            comp,
            promotes_inputs=["v", "omega", "radii", "precone"],
            promotes_outputs=["Vx", "Vy"])

        comp = make_component(
            CCBladeResidualComp(num_nodes=num_nodes,
                                num_radial=num_radial,
                                B=B,
                                af=af,
                                turbine=turbine))
        comp.linear_solver = om.DirectSolver(assemble_jac=True)
        prob.model.add_subsystem('ccblade',
                                 comp,
                                 promotes_inputs=[('r', 'radii'), 'chord',
                                                  'theta', 'Vx', 'Vy', 'rho',
                                                  'mu', 'asound',
                                                  ('Rhub', 'hub_radius'),
                                                  ('Rtip', 'prop_radius'),
                                                  'precone', 'pitch'],
                                 promotes_outputs=['Np', 'Tp'])

        comp = FunctionalsComp(num_nodes=num_nodes,
                               num_radial=num_radial,
                               num_blades=B)
        prob.model.add_subsystem(
            'ccblade_torquethrust_comp',
            comp,
            promotes_inputs=[
                'hub_radius', 'prop_radius', 'radii', 'Np', 'Tp', 'v', 'omega'
            ],
            promotes_outputs=['thrust', 'torque', 'efficiency'])

        prob.setup()
        prob.final_setup()
        prob.run_model()

        CTtest = np.array([
            0.12007272369269596, 0.11685168049381163, 0.113039580103141,
            0.1085198115279716, 0.10319003920478352, 0.09725943970861005,
            0.09086388629270399, 0.0839976424307642, 0.07671734636145504,
            0.06920018426158829, 0.06146448769092457, 0.05351803580676398,
            0.04536760940952122, 0.037021384017275026, 0.028444490366496933,
            0.019460037067085302, 0.0101482712198453, 0.0009431056296630114,
            -0.008450506440868786, -0.018336716057292365
        ])[nstart:nend + 1]
        CPtest = np.array([
            0.04881364095025621, 0.0495814889974431, 0.05010784517185014,
            0.0503081363797726, 0.05016808708430908, 0.04959928236064606,
            0.04857913603557237, 0.0470530147170992, 0.045034787733797786,
            0.042554367414565766, 0.03958444136790737, 0.036104210007643946,
            0.032085430933805684, 0.02750011065614528, 0.022307951002430132,
            0.016419276860939747, 0.009846368564156775, 0.002833135813003493,
            -0.004876593891333574, -0.013591625085158215
        ])[nstart:nend + 1]
        etatest = np.array([
            0.24598190455626265, 0.3349080300487075, 0.4155652767326253,
            0.48818637673414306, 0.5521115225679999, 0.6089123481436948,
            0.6595727776885079, 0.7046724703349897, 0.7441662053086512,
            0.7788447616541276, 0.8090611349633181, 0.8347808848055981,
            0.8558196582739432, 0.8715046719672315, 0.8791362131978436,
            0.8670633642311274, 0.7974063895510229, 0.2715632768892098, 0.0,
            0.0
        ])[nstart:nend + 1]

        T = prob.get_val('thrust', units='N')
        Q = prob.get_val('torque', units='N*m')
        eff = prob.get_val('efficiency')

        CT = T / (rho * n**2 * D**4)
        CQ = Q / (rho * n**2 * D**5) * 2 * np.pi

        assert_allclose(CT, CTtest, atol=1e-3, rtol=1)
        assert_allclose(CQ, CPtest, atol=1e-3, rtol=1)
        assert_allclose(eff[T > 0.0], etatest[T > 0.0], atol=1e-3, rtol=1)
예제 #7
0
    def test_propeller_aero4students(self):
        # Copied from CCBlade.jl/test/runtests.jl

        # -------- verification: propellers.  using script at http://www.aerodynamics4students.com/propulsion/blade-element-propeller-theory.php ------
        # I increased their tolerance to 1e-6

        # inputs
        chord = 0.10
        D = 1.6
        RPM = 2100
        rho = 1.225
        pitch = 1.0  # pitch distance in meters.

        # --- rotor definition ---
        turbine = False
        # Rhub = 0.0
        # Rtip = D/2
        Rhub_eff = 1e-6  # something small to eliminate hub effects
        Rtip_eff = 100.0  # something large to eliminate tip effects
        B = 2  # number of blades

        # --- section definitions ---

        num_nodes = 60
        R = D / 2.0
        r = np.linspace(R / 10, R, 11)
        dr = r[1] - r[0]
        r = np.tile(r, (num_nodes, 1))
        theta = np.arctan(pitch / (2 * np.pi * r))

        jlmain.eval("""
                function affunc(alpha, Re, M)
                    cl = 6.2*alpha
                    cd = 0.008 - 0.003*cl + 0.01*cl*cl
                    return cl, cd
                end
            """)
        affunc = jlmain.affunc

        prob = om.Problem()
        num_radial = r.shape[-1]

        comp = om.IndepVarComp()
        comp.add_output('v', val=np.arange(1, 60 + 1)[:num_nodes], units='m/s')
        comp.add_output('omega', val=np.tile(RPM, num_nodes), units='rpm')
        comp.add_output('radii', val=r, units='m')
        comp.add_output('chord',
                        val=chord,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('theta',
                        val=theta,
                        shape=(num_nodes, num_radial),
                        units='rad')
        comp.add_output('precone', val=0., shape=num_nodes, units='rad')
        comp.add_output('rho', val=rho, shape=(num_nodes, 1), units='kg/m**3')
        comp.add_output('mu', val=1.0, shape=(num_nodes, 1), units='N/m**2*s')
        comp.add_output('asound', val=1.0, shape=(num_nodes, 1), units='m/s')
        comp.add_output('hub_radius', val=Rhub_eff, shape=num_nodes, units='m')
        comp.add_output('prop_radius',
                        val=Rtip_eff,
                        shape=num_nodes,
                        units='m')
        comp.add_output('pitch', val=0.0, shape=num_nodes, units='rad')
        prob.model.add_subsystem('ivc', comp, promotes_outputs=['*'])

        comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
        prob.model.add_subsystem(
            "simple_inflow",
            comp,
            promotes_inputs=["v", "omega", "radii", "precone"],
            promotes_outputs=["Vx", "Vy"])

        comp = make_component(
            CCBladeResidualComp(num_nodes=num_nodes,
                                num_radial=num_radial,
                                B=B,
                                af=affunc,
                                turbine=turbine))
        comp.linear_solver = om.DirectSolver(assemble_jac=True)
        prob.model.add_subsystem('ccblade',
                                 comp,
                                 promotes_inputs=[('r', 'radii'), 'chord',
                                                  'theta', 'Vx', 'Vy', 'rho',
                                                  'mu', 'asound',
                                                  ('Rhub', 'hub_radius'),
                                                  ('Rtip', 'prop_radius'),
                                                  'precone', 'pitch'],
                                 promotes_outputs=['Np', 'Tp'])

        prob.setup()
        prob.final_setup()
        prob.run_model()

        Np = prob.get_val('Np', units='N/m')
        Tp = prob.get_val('Tp', units='N/m')
        T = np.sum(Np * dr, axis=1) * B
        Q = np.sum(r * Tp * dr, axis=1) * B

        tsim = 1e3 * np.array([
            1.045361193032356, 1.025630300048415, 1.005234466788998,
            0.984163367036026, 0.962407923825140, 0.939960208707079,
            0.916813564966455, 0.892962691000145, 0.868403981825492,
            0.843134981103815, 0.817154838249790, 0.790463442573673,
            0.763063053839278, 0.734956576558370, 0.706148261507327,
            0.676643975451150, 0.646450304160057, 0.615575090105131,
            0.584027074365864, 0.551815917391907, 0.518952127358381,
            0.485446691671386, 0.451311288662196, 0.416557935286392,
            0.381199277009438, 0.345247916141561, 0.308716772800348,
            0.271618894441869, 0.233967425339051, 0.195775319296371,
            0.157055230270717, 0.117820154495231, 0.078082266879117,
            0.037854059197644, -0.002852754149850, -0.044026182837742,
            -0.085655305814570, -0.127728999394140, -0.170237722799272,
            -0.213169213043848, -0.256515079286031, -0.300266519551194,
            -0.344414094748869, -0.388949215983616, -0.433863576642539,
            -0.479150401337354, -0.524801553114807, -0.570810405128802,
            -0.617169893200684, -0.663873474163182, -0.710915862524620,
            -0.758291877949762, -0.805995685105502, -0.854022273120508,
            -0.902366919041604, -0.951025170820984, -0.999992624287163,
            -1.049265666456123, -1.098840222937414, -1.148712509929845
        ])
        qsim = 1e2 * np.array([
            0.803638686218187, 0.806984572453978, 0.809709290183008,
            0.811743686838315, 0.813015017103876, 0.813446921530685,
            0.812959654049620, 0.811470393912576, 0.808893852696513,
            0.805141916379142, 0.800124489784850, 0.793748780791057,
            0.785921727832179, 0.776548246109426, 0.765532528164390,
            0.752778882688809, 0.738190986274448, 0.721673076180745,
            0.703129918771009, 0.682467282681955, 0.659592296506578,
            0.634413303042323, 0.606840565246423, 0.576786093366321,
            0.544164450503912, 0.508891967461804, 0.470887571011192,
            0.430072787279711, 0.386371788290446, 0.339711042057184,
            0.290019539402947, 0.237229503458026, 0.181274942660876,
            0.122093307308376, 0.059623821454727, -0.006190834182631,
            -0.075406684829235, -0.148076528546541, -0.224253047813501,
            -0.303980950928302, -0.387309291734422, -0.474283793689904,
            -0.564946107631716, -0.659336973911858, -0.757495165410553,
            -0.859460291551374, -0.965266648683888, -1.074949504731187,
            -1.188540970723477, -1.306072104649531, -1.427575034895290,
            -1.553080300508925, -1.682614871422754, -1.816205997296014,
            -1.953879956474228, -2.095662107769925, -2.241576439746701,
            -2.391647474158875, -2.545897099743367, -2.704346566395035
        ])

        assert_allclose(T, tsim[:num_nodes], atol=1e-2, rtol=1)
        assert_allclose(Q, qsim[:num_nodes], atol=2e-3, rtol=1)

        # Run with v = 20.0 m/s.
        prob.set_val('v', 20.0, units='m/s', indices=0)
        prob.run_model()

        Vhub = prob.get_val('v', units='m/s', indices=0)
        omega = prob.get_val('omega', units='rad/s', indices=0)
        Np = prob.get_val('Np', units='N/m', indices=0)
        Tp = prob.get_val('Tp', units='N/m', indices=0)
        T = np.sum(Np * dr) * B
        Q = np.sum(r[0, :] * Tp * dr) * B

        P = Q * omega
        n = omega / (2 * np.pi)
        CT = T / (rho * n**2 * D**4)
        CQ = Q / (rho * n**2 * D**5)
        eff = T * Vhub / P

        assert_allclose(CT, 0.056110238632657, atol=1e-7, rtol=1)
        assert_allclose(CQ, 0.004337202960642, atol=1e-8, rtol=1)
        assert_allclose(eff, 0.735350632777002, atol=1e-6, rtol=1)
예제 #8
0
    def test_openbemt_optimization(self):

        interp = ViternaAirfoil().create_akima('mh117',
                                               Re_scaling=False,
                                               extend_alpha=True)

        def ccblade_interp(alpha, Re, Mach):
            shape = alpha.shape
            x = np.concatenate(
                [alpha.flatten()[:, np.newaxis],
                 Re.flatten()[:, np.newaxis]],
                axis=-1)
            y = interp(x)
            y.shape = shape + (2, )
            return y[..., 0], y[..., 1]

        num_nodes = 1
        num_blades = 3
        num_radial = 15
        num_cp = 6
        chord = 10.
        theta = np.linspace(65., 25., num_cp) * np.pi / 180.
        pitch = 0.
        prop_data = {
            'num_radial': num_radial,
            'num_cp': num_cp,
            'pitch': pitch,
            'chord': chord,
            'theta': theta,
            'spline_type': 'akima',
            'B': num_blades,
            'interp': interp
        }

        hub_diameter = 30.  # cm
        prop_diameter = 150.  # cm
        c0 = np.sqrt(1.4 * 287.058 * 300.)  # meters/second
        rho0 = 1.4 * 98600. / (c0 * c0)  # kg/m^3
        omega = 236.

        prob_ccblade = om.Problem()
        prob_openbemt = om.Problem()

        comp = om.IndepVarComp()
        comp.add_output('rho', val=rho0, shape=num_nodes, units='kg/m**3')
        comp.add_output('mu', val=1., shape=num_nodes, units='N/m**2*s')
        comp.add_output('asound', val=c0, shape=num_nodes, units='m/s')
        comp.add_output('v', val=77.2, shape=num_nodes, units='m/s')
        comp.add_output('alpha', val=0., shape=num_nodes, units='rad')
        comp.add_output('incidence', val=0., shape=num_nodes, units='rad')
        comp.add_output('precone', val=0., units='deg')
        comp.add_output('omega', val=omega, shape=num_nodes, units='rad/s')
        comp.add_output('hub_diameter', val=hub_diameter, units='cm')
        comp.add_output('prop_diameter', val=prop_diameter, units='cm')
        comp.add_output('pitch', val=pitch, shape=num_nodes, units='rad')
        comp.add_output('chord_dv', val=chord, shape=num_cp, units='cm')
        comp.add_output('theta_dv', val=theta, shape=num_cp, units='rad')
        prob_ccblade.model.add_subsystem('indep_var_comp',
                                         comp,
                                         promotes=['*'])
        prob_openbemt.model.add_subsystem('indep_var_comp',
                                          comp,
                                          promotes=['*'])

        comp = GeometryGroup(num_nodes=num_nodes,
                             num_cp=num_cp,
                             num_radial=num_radial)
        prob_ccblade.model.add_subsystem(
            'geometry_group',
            comp,
            promotes_inputs=[
                'hub_diameter', 'prop_diameter', 'chord_dv', 'theta_dv',
                'pitch'
            ],
            promotes_outputs=['radii', 'dradii', 'chord', 'theta'])

        comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
        prob_ccblade.model.add_subsystem(
            'inflow_comp',
            comp,
            promotes_inputs=['v', 'omega', 'radii', 'precone'],
            promotes_outputs=['Vx', 'Vy'])

        comp = CCBladeGroup(num_nodes=num_nodes,
                            num_radial=num_radial,
                            num_blades=num_blades,
                            airfoil_interp=ccblade_interp,
                            turbine=False)
        prob_ccblade.model.add_subsystem(
            'ccblade_group',
            comp,
            promotes_inputs=[
                'radii', 'dradii', 'chord', 'theta', 'rho', 'mu', 'asound',
                'v', 'omega', 'Vx', 'Vy', 'precone', 'hub_diameter',
                'prop_diameter'
            ],
            promotes_outputs=['thrust', 'torque', 'efficiency'])

        prob_ccblade.model.add_design_var('chord_dv',
                                          lower=1.,
                                          upper=20.,
                                          scaler=5e-2)
        prob_ccblade.model.add_design_var('theta_dv',
                                          lower=20. * np.pi / 180.,
                                          upper=90 * np.pi / 180.)

        prob_ccblade.model.add_objective(
            'efficiency',
            scaler=-1.,
        )
        prob_ccblade.model.add_constraint('thrust',
                                          equals=700.,
                                          scaler=1e-3,
                                          indices=np.arange(num_nodes))
        prob_ccblade.driver = om.pyOptSparseDriver()
        prob_ccblade.driver.options['optimizer'] = 'SNOPT'

        prob_ccblade.setup()
        prob_ccblade.final_setup()
        prob_ccblade.run_driver()

        ccblade_normal_load = prob_ccblade.get_val('ccblade_group.Np',
                                                   units='N/m') * num_blades
        ccblade_circum_load = prob_ccblade.get_val('ccblade_group.Tp',
                                                   units='N/m') * num_blades

        prob_openbemt.model.add_subsystem(
            'bemt_group',
            BEMTGroup(num_nodes=num_nodes, prop_data=prop_data),
            promotes_inputs=[
                'rho', 'mu', 'v', 'alpha', 'incidence', 'omega',
                'hub_diameter', 'prop_diameter', 'pitch', 'chord_dv',
                'theta_dv'
            ],
            promotes_outputs=[('normal_load_dist', 'openbemt_normal_load'),
                              ('circum_load_dist', 'openbemt_circum_load'),
                              'dradii', 'thrust', 'efficiency'])

        prob_openbemt.model.add_design_var('chord_dv',
                                           lower=1.,
                                           upper=20.,
                                           scaler=5e-2)
        prob_openbemt.model.add_design_var('theta_dv',
                                           lower=20. * np.pi / 180.,
                                           upper=90 * np.pi / 180.)

        prob_openbemt.model.add_objective(
            'efficiency',
            scaler=-1.,
        )
        prob_openbemt.model.add_constraint('thrust',
                                           equals=700.,
                                           scaler=1e-3,
                                           indices=np.arange(num_nodes))
        prob_openbemt.driver = om.pyOptSparseDriver()
        prob_openbemt.driver.options['optimizer'] = 'SNOPT'

        prob_openbemt.setup()
        prob_openbemt.final_setup()
        prob_openbemt.model.bemt_group.phi_group.nonlinear_solver.options[
            'iprint'] = 0
        prob_openbemt.run_driver()

        openbemt_normal_load = prob_openbemt.get_val('openbemt_normal_load',
                                                     units='N')
        openbemt_circum_load = prob_openbemt.get_val('openbemt_circum_load',
                                                     units='N')
        dradii = prob_openbemt.get_val('dradii', units='m')
        openbemt_normal_load /= dradii
        openbemt_circum_load /= dradii

        assert_rel_error(self, ccblade_normal_load, openbemt_normal_load, 1e-2)
        assert_rel_error(self, ccblade_circum_load, openbemt_circum_load, 1e-2)
예제 #9
0
    def test_multiple_adv_ratios(self):
        # num_nodes = 20
        nstart = 0
        nend = 19
        num_nodes = nend - nstart + 1

        r = 0.0254 * np.array([
            0.7526, 0.7928, 0.8329, 0.8731, 0.9132, 0.9586, 1.0332, 1.1128,
            1.1925, 1.2722, 1.3519, 1.4316, 1.5114, 1.5911, 1.6708, 1.7505,
            1.8302, 1.9099, 1.9896, 2.0693, 2.1490, 2.2287, 2.3084, 2.3881,
            2.4678, 2.5475, 2.6273, 2.7070, 2.7867, 2.8661, 2.9410
        ]).reshape(1, -1)
        num_radial = r.shape[-1]
        r = np.tile(r, (num_nodes, 1))

        chord = 0.0254 * np.array([
            0.6270, 0.6255, 0.6231, 0.6199, 0.6165, 0.6125, 0.6054, 0.5973,
            0.5887, 0.5794, 0.5695, 0.5590, 0.5479, 0.5362, 0.5240, 0.5111,
            0.4977, 0.4836, 0.4689, 0.4537, 0.4379, 0.4214, 0.4044, 0.3867,
            0.3685, 0.3497, 0.3303, 0.3103, 0.2897, 0.2618, 0.1920
        ]).reshape((1, num_radial))
        chord = np.tile(chord, (num_nodes, 1))

        theta = np.pi / 180.0 * np.array([
            40.2273, 38.7657, 37.3913, 36.0981, 34.8803, 33.5899, 31.6400,
            29.7730, 28.0952, 26.5833, 25.2155, 23.9736, 22.8421, 21.8075,
            20.8586, 19.9855, 19.1800, 18.4347, 17.7434, 17.1005, 16.5013,
            15.9417, 15.4179, 14.9266, 14.4650, 14.0306, 13.6210, 13.2343,
            12.8685, 12.5233, 12.2138
        ]).reshape((1, num_radial))
        theta = np.tile(theta, (num_nodes, 1))

        rho = 1.225
        Rhub = 0.0254 * .5
        Rtip = 0.0254 * 3.0
        B = 2  # number of blades
        turbine = False

        J = np.linspace(0.1, 0.9, 20)[nstart:nend + 1]  # advance ratio
        # J = np.linspace(0.1, 0.9, 20)

        omega = 8000.0 * np.pi / 30

        n = omega / (2 * np.pi)
        D = 2 * Rtip

        Vinf = J * D * n
        dr = r[0, 1] - r[0, 0]

        # Airfoil interpolator.
        af = af_from_files(["airfoils/NACA64_A17.dat"])[0]

        prob = om.Problem()

        comp = om.IndepVarComp()
        comp.add_output('v', val=Vinf, units='m/s')
        comp.add_output('omega', val=np.tile(omega, num_nodes), units='rad/s')
        comp.add_output('radii',
                        val=r,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('dradii',
                        val=dr,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('chord',
                        val=chord,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('theta',
                        val=theta,
                        shape=(num_nodes, num_radial),
                        units='rad')
        comp.add_output('precone', val=0., shape=num_nodes, units='rad')
        comp.add_output('rho', val=rho, shape=(num_nodes, 1), units='kg/m**3')
        comp.add_output('mu', val=1.0, shape=(num_nodes, 1), units='N/m**2*s')
        comp.add_output('asound', val=1.0, shape=(num_nodes, 1), units='m/s')
        comp.add_output('hub_radius', val=Rhub, shape=num_nodes, units='m')
        comp.add_output('prop_radius', val=Rtip, shape=num_nodes, units='m')
        comp.add_output('pitch', val=0.0, shape=num_nodes, units='rad')
        prob.model.add_subsystem('ivc', comp, promotes_outputs=['*'])

        comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
        prob.model.add_subsystem(
            "simple_inflow",
            comp,
            promotes_inputs=["v", "omega", "radii", "precone"],
            promotes_outputs=["Vx", "Vy"])

        comp = ccb.LocalInflowAngleComp(num_nodes=num_nodes,
                                        num_radial=num_radial,
                                        num_blades=B,
                                        airfoil_interp=af,
                                        turbine=turbine,
                                        debug_print=False)
        comp.linear_solver = om.DirectSolver(assemble_jac=True)
        prob.model.add_subsystem('ccblade',
                                 comp,
                                 promotes_inputs=[
                                     'radii', 'chord', 'theta', 'Vx', 'Vy',
                                     'rho', 'mu', 'asound', 'hub_radius',
                                     'prop_radius', 'precone', 'pitch'
                                 ],
                                 promotes_outputs=['Np', 'Tp'])

        comp = ccb.FunctionalsComp(num_nodes=num_nodes,
                                   num_radial=num_radial,
                                   num_blades=B)
        prob.model.add_subsystem(
            'ccblade_torquethrust_comp',
            comp,
            promotes_inputs=[
                'hub_radius', 'prop_radius', 'radii', 'Np', 'Tp', 'v', 'omega'
            ],
            promotes_outputs=['thrust', 'torque', 'efficiency'])

        prob.setup()
        prob.final_setup()
        prob.run_model()

        etatest = np.array([
            0.24598190455626265, 0.3349080300487075, 0.4155652767326253,
            0.48818637673414306, 0.5521115225679999, 0.6089123481436948,
            0.6595727776885079, 0.7046724703349897, 0.7441662053086512,
            0.7788447616541276, 0.8090611349633181, 0.8347808848055981,
            0.8558196582739432, 0.8715046719672315, 0.8791362131978436,
            0.8670633642311274, 0.7974063895510229, 0.2715632768892098, 0.0,
            0.0
        ])[nstart:nend + 1]

        thrust = prob.get_val('thrust', units='N')
        eff = prob.get_val('efficiency')
        assert_array_almost_equal(eff[thrust > 0.0],
                                  etatest[thrust > 0.0],
                                  decimal=2)
예제 #10
0
def main():

    num_nodes = 1
    num_blades = 10
    num_radial = 15
    num_cp = 6

    af_filename = 'airfoils/mh117.dat'
    chord = 20.
    theta = 5.0 * np.pi / 180.0
    pitch = 0.

    # Numbers taken from the Aviary group's study of the RVLT tiltwing
    # turboelectric concept vehicle.
    n_props = 4
    hub_diameter = 30.  # cm
    prop_diameter = 15 * 30.48  # 15 ft in cm
    c0 = np.sqrt(1.4 * 287.058 * 300.)  # meters/second
    rho0 = 1.4 * 98600. / (c0 * c0)  # kg/m^3
    omega = 236.  # rad/s

    # Find the thrust per rotor from the vehicle's mass.
    m_full = 6367  # kg
    g = 9.81  # m/s**2
    thrust_vtol = 0.1 * m_full * g / n_props

    prob = Problem()

    comp = IndepVarComp()
    comp.add_discrete_output('B', val=num_blades)
    comp.add_output('rho', val=rho0, shape=num_nodes, units='kg/m**3')
    comp.add_output('mu', val=1., shape=num_nodes, units='N/m**2*s')
    comp.add_output('asound', val=c0, shape=num_nodes, units='m/s')
    comp.add_output('v', val=2., shape=num_nodes, units='m/s')
    comp.add_output('alpha', val=0., shape=num_nodes, units='rad')
    comp.add_output('incidence', val=0., shape=num_nodes, units='rad')
    comp.add_output('precone', val=0., units='deg')
    comp.add_output('hub_diameter',
                    val=hub_diameter,
                    shape=num_nodes,
                    units='cm')
    comp.add_output('prop_diameter',
                    val=prop_diameter,
                    shape=num_nodes,
                    units='cm')
    comp.add_output('pitch', val=pitch, shape=num_nodes, units='rad')
    comp.add_output('chord_dv', val=chord, shape=num_cp, units='cm')
    comp.add_output('theta_dv', val=theta, shape=num_cp, units='rad')
    comp.add_output('thrust_vtol', val=thrust_vtol, shape=num_nodes, units='N')
    prob.model.add_subsystem('indep_var_comp', comp, promotes=['*'])

    comp = GeometryGroup(num_nodes=num_nodes,
                         num_cp=num_cp,
                         num_radial=num_radial)
    prob.model.add_subsystem(
        'geometry_group',
        comp,
        promotes_inputs=[
            'hub_diameter', 'prop_diameter', 'chord_dv', 'theta_dv', 'pitch'
        ],
        promotes_outputs=['radii', 'dradii', 'chord', 'theta'])

    balance_group = Group()

    comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
    balance_group.add_subsystem(
        'inflow_comp',
        comp,
        promotes_inputs=['v', 'omega', 'radii', 'precone'],
        promotes_outputs=['Vx', 'Vy'])

    comp = CCBladeGroup(num_nodes=num_nodes,
                        num_radial=num_radial,
                        num_blades=num_blades,
                        af_filename=af_filename,
                        turbine=False)
    balance_group.add_subsystem(
        'ccblade_group',
        comp,
        promotes_inputs=[
            'B', 'radii', 'dradii', 'chord', 'theta', 'rho', 'mu', 'asound',
            'v', 'precone', 'omega', 'Vx', 'Vy', 'precone', 'hub_diameter',
            'prop_diameter'
        ],
        promotes_outputs=['thrust', 'torque', 'efficiency'])

    comp = BalanceComp()
    comp.add_balance(
        name='omega',
        eq_units='N',
        lhs_name='thrust',
        rhs_name='thrust_vtol',
        val=omega,
        units='rad/s',
        # lower=0.,
    )
    balance_group.add_subsystem('thrust_balance_comp', comp, promotes=['*'])

    balance_group.linear_solver = DirectSolver(assemble_jac=True)
    balance_group.nonlinear_solver = NewtonSolver(maxiter=50, iprint=2)
    balance_group.nonlinear_solver.options['solve_subsystems'] = True
    # prob.model.nonlinear_solver.linesearch = BoundsEnforceLS()
    balance_group.nonlinear_solver.options['atol'] = 1e-9

    prob.model.add_subsystem('thrust_balance_group',
                             balance_group,
                             promotes=['*'])

    prob.setup()
    prob.final_setup()

    # Calculate the induced axial velocity at the rotor for hover, used for
    # non-diminsionalation.
    rho = prob.get_val('rho', units='kg/m**3')[0]
    hub_diameter = prob.get_val('hub_diameter', units='m')[0]
    prop_diameter = prob.get_val('prop_diameter', units='m')[0]
    thrust_vtol = prob.get_val('thrust_vtol', units='N')[0]
    A_rotor = 0.25 * np.pi * (prop_diameter**2 - hub_diameter**2)
    v_h = np.sqrt(thrust_vtol / (2 * rho * A_rotor))

    # Climb:
    climb_velocity_nondim = np.linspace(0.1, 2., 10)
    induced_velocity_nondim = np.zeros_like(climb_velocity_nondim)
    for vc, vi in np.nditer([climb_velocity_nondim, induced_velocity_nondim],
                            op_flags=[['readonly'], ['writeonly']]):

        # Run the model with the requested climb velocity.
        prob.set_val('v', vc * v_h, units='m/s')
        print(f"v = {prob.get_val('v', units='m/s')}")
        prob.run_model()

        # Calculate the area-weighted average induced velocity at the rotor.
        # Need the area of each blade section.
        radii = prob.get_val('radii', units='m')
        dradii = prob.get_val('dradii', units='m')
        dArea = 2 * np.pi * radii * dradii

        # Get the induced velocity at the rotor plane for each blade section.
        Vx = prob.get_val('Vx', units='m/s')
        a = prob.get_val('ccblade_group.ccblade_comp.a')

        # Get the area-weighted average of the induced velocity.
        vi[...] = np.sum(a * Vx * dArea / A_rotor) / v_h

    # Induced velocity from plain old momentum theory (for climb).
    induced_velocity_mt = (-0.5 * climb_velocity_nondim +
                           np.sqrt((0.5 * climb_velocity_nondim)**2 + 1.))

    fig, ax = plt.subplots()
    ax.plot(climb_velocity_nondim,
            -induced_velocity_nondim,
            label='CCBlade.jl (climb)')
    ax.plot(climb_velocity_nondim,
            induced_velocity_mt,
            label='Momentum Theory (climb)')

    # Descent:
    # climb_velocity_nondim = np.linspace(-3.5, -2.6, 10)
    climb_velocity_nondim = np.linspace(-5.0, -2.1, 10)
    induced_velocity_nondim = np.zeros_like(climb_velocity_nondim)
    for vc, vi in np.nditer([climb_velocity_nondim, induced_velocity_nondim],
                            op_flags=[['readonly'], ['writeonly']]):

        # Run the model with the requested climb velocity.
        prob.set_val('v', vc * v_h, units='m/s')
        print(f"vc = {vc}, v = {prob.get_val('v', units='m/s')}")
        prob.run_model()

        # Calculate the area-weighted average induced velocity at the rotor.
        # Need the area of each blade section.
        radii = prob.get_val('radii', units='m')
        dradii = prob.get_val('dradii', units='m')
        dArea = 2 * np.pi * radii * dradii

        # Get the induced velocity at the rotor plane for each blade section.
        Vx = prob.get_val('Vx', units='m/s')
        a = prob.get_val('ccblade_group.ccblade_comp.a')

        # Get the area-weighted average of the induced velocity.
        vi[...] = np.sum(a * Vx * dArea / A_rotor) / v_h

    # Plot the induced velocity for descent.
    ax.plot(climb_velocity_nondim,
            -induced_velocity_nondim,
            label='CCBlade.jl (descent)')

    # Induced velocity from plain old momentum theory (for descent).
    induced_velocity_mt = (-0.5 * climb_velocity_nondim -
                           np.sqrt((0.5 * climb_velocity_nondim)**2 - 1.))
    ax.plot(climb_velocity_nondim,
            induced_velocity_mt,
            label='Momentum Theory (descent)')

    # Empirical region:
    climb_velocity_nondim = np.linspace(-1.9, -1.5, 5)
    induced_velocity_nondim = np.zeros_like(climb_velocity_nondim)
    for vc, vi in np.nditer([climb_velocity_nondim, induced_velocity_nondim],
                            op_flags=[['readonly'], ['writeonly']]):

        # Run the model with the requested climb velocity.
        prob.set_val('v', vc * v_h, units='m/s')
        print(f"vc = {vc}, v = {prob.get_val('v', units='m/s')}")
        prob.run_model()

        # Calculate the area-weighted average induced velocity at the rotor.
        # Need the area of each blade section.
        radii = prob.get_val('radii', units='m')
        dradii = prob.get_val('dradii', units='m')
        dArea = 2 * np.pi * radii * dradii

        # Get the induced velocity at the rotor plane for each blade section.
        Vx = prob.get_val('Vx', units='m/s')
        a = prob.get_val('ccblade_group.ccblade_comp.a')

        # Get the area-weighted average of the induced velocity.
        vi[...] = np.sum(a * Vx * dArea / A_rotor) / v_h

    # Plot the induced velocity for the empirical region.
    ax.plot(climb_velocity_nondim,
            -induced_velocity_nondim,
            label='CCBlade.jl (empirical region)')

    ax.set_xlabel('Vc/vh')
    ax.set_ylabel('Vi/vh')
    ax.legend()
    fig.savefig('induced_velocity.png')
예제 #11
0
    def test_propeller_aero4students(self):
        # From test/runtests.jl

        # inputs
        chord = 0.10
        D = 1.6
        RPM = 2100
        rho = 1.225
        pitch = 1.0  # pitch distance in meters.

        # --- rotor definition ---
        turbine = False
        # Rhub = 0.0
        # Rtip = D/2
        Rhub_eff = 1e-6  # something small to eliminate hub effects
        Rtip_eff = 100.0  # something large to eliminate tip effects
        B = 2  # number of blades

        # --- section definitions ---

        num_nodes = 60
        R = D / 2.0
        r = np.linspace(R / 10, R, 11)
        dr = r[1] - r[0]
        r = np.tile(r, (num_nodes, 1))
        # print(f"r =\n{r}")
        dr = np.tile(dr, r.shape)
        theta = np.arctan(pitch / (2 * np.pi * r))

        def affunc(alpha, Re, M):
            cl = 6.2 * alpha
            cd = 0.008 - 0.003 * cl + 0.01 * cl * cl
            return cl, cd

        prob = om.Problem()
        num_radial = r.shape[-1]

        comp = om.IndepVarComp()
        comp.add_output('v', val=np.arange(1, num_nodes + 1), units='m/s')
        comp.add_output('omega', val=np.tile(RPM, num_nodes), units='rpm')
        comp.add_output('radii', val=r, units='m')
        comp.add_output('dradii', val=dr, units='m')
        comp.add_output('chord',
                        val=chord,
                        shape=(num_nodes, num_radial),
                        units='m')
        comp.add_output('theta',
                        val=theta,
                        shape=(num_nodes, num_radial),
                        units='rad')
        comp.add_output('precone', val=0., units='rad')
        comp.add_output('rho', val=rho, shape=(num_nodes, 1), units='kg/m**3')
        comp.add_output('mu', val=1.0, shape=(num_nodes, 1), units='N/m**2*s')
        comp.add_output('asound', val=1.0, shape=(num_nodes, 1), units='m/s')
        comp.add_output('hub_radius', val=Rhub_eff, units='m')
        comp.add_output('prop_radius', val=Rtip_eff, units='m')
        comp.add_discrete_output('B', val=B)
        prob.model.add_subsystem('ivc', comp, promotes_outputs=['*'])

        comp = SimpleInflow(num_nodes=num_nodes, num_radial=num_radial)
        prob.model.add_subsystem(
            "simple_inflow",
            comp,
            promotes_inputs=["v", "omega", "radii", "precone"],
            promotes_outputs=["Vx", "Vy"])

        comp = ccb.LocalInflowAngleComp(num_nodes=num_nodes,
                                        num_radial=num_radial,
                                        airfoil_interp=affunc,
                                        turbine=turbine,
                                        debug_print=False)
        comp.linear_solver = om.DirectSolver(assemble_jac=True)
        prob.model.add_subsystem('ccblade',
                                 comp,
                                 promotes_inputs=[
                                     'radii', 'chord', 'theta', 'Vx', 'Vy',
                                     'rho', 'mu', 'asound', 'hub_radius',
                                     'prop_radius', 'precone', 'B'
                                 ],
                                 promotes_outputs=['Np', 'Tp'])

        comp = ccb.FunctionalsComp(num_nodes=num_nodes, num_radial=num_radial)
        prob.model.add_subsystem(
            'ccblade_torquethrust_comp',
            comp,
            promotes_inputs=['radii', 'dradii', 'Np', 'Tp', 'v', 'omega', 'B'],
            promotes_outputs=['thrust', 'torque'])

        prob.setup()
        prob.final_setup()
        prob.run_model()

        tsim = 1e3 * np.array([
            1.045361193032356, 1.025630300048415, 1.005234466788998,
            0.984163367036026, 0.962407923825140, 0.939960208707079,
            0.916813564966455, 0.892962691000145, 0.868403981825492,
            0.843134981103815, 0.817154838249790, 0.790463442573673,
            0.763063053839278, 0.734956576558370, 0.706148261507327,
            0.676643975451150, 0.646450304160057, 0.615575090105131,
            0.584027074365864, 0.551815917391907, 0.518952127358381,
            0.485446691671386, 0.451311288662196, 0.416557935286392,
            0.381199277009438, 0.345247916141561, 0.308716772800348,
            0.271618894441869, 0.233967425339051, 0.195775319296371,
            0.157055230270717, 0.117820154495231, 0.078082266879117,
            0.037854059197644, -0.002852754149850, -0.044026182837742,
            -0.085655305814570, -0.127728999394140, -0.170237722799272,
            -0.213169213043848, -0.256515079286031, -0.300266519551194,
            -0.344414094748869, -0.388949215983616, -0.433863576642539,
            -0.479150401337354, -0.524801553114807, -0.570810405128802,
            -0.617169893200684, -0.663873474163182, -0.710915862524620,
            -0.758291877949762, -0.805995685105502, -0.854022273120508,
            -0.902366919041604, -0.951025170820984, -0.999992624287163,
            -1.049265666456123, -1.098840222937414, -1.148712509929845
        ])
        qsim = 1e2 * np.array([
            0.803638686218187, 0.806984572453978, 0.809709290183008,
            0.811743686838315, 0.813015017103876, 0.813446921530685,
            0.812959654049620, 0.811470393912576, 0.808893852696513,
            0.805141916379142, 0.800124489784850, 0.793748780791057,
            0.785921727832179, 0.776548246109426, 0.765532528164390,
            0.752778882688809, 0.738190986274448, 0.721673076180745,
            0.703129918771009, 0.682467282681955, 0.659592296506578,
            0.634413303042323, 0.606840565246423, 0.576786093366321,
            0.544164450503912, 0.508891967461804, 0.470887571011192,
            0.430072787279711, 0.386371788290446, 0.339711042057184,
            0.290019539402947, 0.237229503458026, 0.181274942660876,
            0.122093307308376, 0.059623821454727, -0.006190834182631,
            -0.075406684829235, -0.148076528546541, -0.224253047813501,
            -0.303980950928302, -0.387309291734422, -0.474283793689904,
            -0.564946107631716, -0.659336973911858, -0.757495165410553,
            -0.859460291551374, -0.965266648683888, -1.074949504731187,
            -1.188540970723477, -1.306072104649531, -1.427575034895290,
            -1.553080300508925, -1.682614871422754, -1.816205997296014,
            -1.953879956474228, -2.095662107769925, -2.241576439746701,
            -2.391647474158875, -2.545897099743367, -2.704346566395035
        ])

        assert_rel_error(self, tsim, prob['thrust'], 1e-5)
        assert_rel_error(self, qsim, prob['torque'], 1e-5)