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)
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
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)
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
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)
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)
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)
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)
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)
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')
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)