예제 #1
0
파일: rotor.py 프로젝트: satwik-hub/WISDEM
    def setup(self):
        RefBlade = self.options['RefBlade']
        npts_coarse_power_curve = self.options['npts_coarse_power_curve']
        npts_spline_power_curve = self.options['npts_spline_power_curve']
        regulation_reg_II5 = self.options['regulation_reg_II5']
        regulation_reg_III = self.options['regulation_reg_III']
        flag_Cp_Ct_Cq_Tables = self.options['flag_Cp_Ct_Cq_Tables']
        Analysis_Level = self.options['Analysis_Level']
        FASTpref = self.options['FASTpref']
        topLevelFlag = self.options['topLevelFlag']
        rc_verbosity = self.options['rc_verbosity']
        rc_tex_table = self.options['rc_tex_table']
        rc_generate_plots = self.options['rc_generate_plots']
        rc_show_plots = self.options['rc_show_plots']
        rc_show_warnings = self.options['rc_show_warnings']
        rc_discrete = self.options['rc_discrete']
        user_update_routine = self.options['user_update_routine']
        NPTS = len(RefBlade['pf']['s'])

        rotorIndeps = IndepVarComp()
        rotorIndeps.add_discrete_output('tiploss', True)
        rotorIndeps.add_discrete_output('hubloss', True)
        rotorIndeps.add_discrete_output('wakerotation', True)
        rotorIndeps.add_discrete_output('usecd', True)
        rotorIndeps.add_discrete_output(
            'nSector',
            val=4,
            desc=
            'number of sectors to divide rotor face into in computing thrust and power'
        )
        self.add_subsystem('rotorIndeps', rotorIndeps, promotes=['*'])

        if topLevelFlag:
            sharedIndeps = IndepVarComp()
            sharedIndeps.add_output(
                'lifetime',
                val=20.0,
                units='year',
                desc='project lifetime for fatigue analysis')
            sharedIndeps.add_output('hub_height', val=0.0, units='m')
            sharedIndeps.add_output('rho', val=1.225, units='kg/m**3')
            sharedIndeps.add_output('mu', val=1.81e-5, units='kg/(m*s)')
            sharedIndeps.add_output('shearExp', val=0.2)
            self.add_subsystem('sharedIndeps', sharedIndeps, promotes=['*'])

        # --- Rotor Aero & Power ---
        self.add_subsystem('rg',
                           RotorGeometry(
                               RefBlade=RefBlade,
                               topLevelFlag=True,
                               verbosity=rc_verbosity,
                               tex_table=rc_tex_table,
                               generate_plots=rc_generate_plots,
                               show_plots=rc_show_plots,
                               show_warnings=rc_show_warnings,
                               discrete=rc_discrete,
                               user_update_routine=user_update_routine),
                           promotes=['*'])
        self.add_subsystem('ra',
                           RotorAeroPower(
                               RefBlade=RefBlade,
                               npts_coarse_power_curve=npts_coarse_power_curve,
                               npts_spline_power_curve=npts_spline_power_curve,
                               regulation_reg_II5=regulation_reg_II5,
                               regulation_reg_III=regulation_reg_III,
                               flag_Cp_Ct_Cq_Tables=flag_Cp_Ct_Cq_Tables,
                               topLevelFlag=False),
                           promotes=['*'])
        self.add_subsystem(
            'rs',
            RotorStructure(RefBlade=RefBlade,
                           topLevelFlag=False,
                           Analysis_Level=Analysis_Level),
            promotes=[
                'fst_vt_in',
                'VfactorPC',
                'turbulence_class',
                'gust_stddev',
                'pitch_extreme',
                'azimuth_extreme',
                'rstar_damage',
                'Mxb_damage',
                'Myb_damage',
                'strain_ult_spar',
                'strain_ult_te',
                'm_damage',
                'gamma_fatigue',
                'gamma_freq',
                'gamma_f',
                'gamma_m',
                'dynamic_amplification',
                'azimuth_load180',
                'azimuth_load0',
                'azimuth_load120',
                'azimuth_load240',
                'nSector',
                'rho',
                'mu',
                'shearExp',
                'tiploss',
                'hubloss',
                'wakerotation',
                'usecd',
                'bladeLength',
                'R',
                'V_mean',
                'chord',
                'theta',
                'precurve',
                'presweep',
                'Rhub',
                'Rtip',
                'r',
                'r_in',
                'airfoils_cl',
                'airfoils_cd',
                'airfoils_cm',
                'airfoils_aoa',
                'airfoils_Re',
                'z',
                'EA',
                'EIxx',
                'EIyy',
                'EIxy',
                'GJ',
                'rhoA',
                'rhoJ',
                'x_ec',
                'y_ec',
                'Tw_iner',
                'flap_iner',
                'edge_iner',
                'eps_crit_spar',
                'eps_crit_te',
                'xu_strain_spar',
                'xl_strain_spar',
                'yu_strain_spar',
                'yl_strain_spar',
                'xu_strain_te',
                'xl_strain_te',
                'yu_strain_te',
                'yl_strain_te',
                'precurveTip',
                'presweepTip',
                'precone',
                'tilt',
                'yaw',
                'nBlades',
                'downwind',
                'control_tsr',
                'control_pitch',
                'lifetime',
                'hub_height',
                'mass_one_blade',
                'mass_all_blades',
                'I_all_blades',
                'freq_pbeam',
                'freq_distance',
                'freq_curvefem',
                'modes_coef_curvefem',
                'tip_deflection',
                'tip_position',
                'ground_clearance',
                'strainU_spar',
                'strainL_spar',
                'strainU_te',
                'strainL_te',  #'eps_crit_spar','eps_crit_te',
                'root_bending_moment',
                'Mxyz',
                'damageU_spar',
                'damageL_spar',
                'damageU_te',
                'damageL_te',
                'delta_bladeLength_out',
                'delta_precurve_sub_out',
                'Fxyz_1',
                'Fxyz_2',
                'Fxyz_3',
                'Fxyz_4',
                'Fxyz_5',
                'Fxyz_6',
                'Mxyz_1',
                'Mxyz_2',
                'Mxyz_3',
                'Mxyz_4',
                'Mxyz_5',
                'Mxyz_6',
                'Fxyz_total',
                'Mxyz_total',
                'TotalCone',
                'Pitch'
            ])

        # self.add_subsystem('rc', RotorCost(RefBlade=RefBlade, verbosity=rc_verbosity),
        #                    promotes=['bladeLength','total_blade_cost','Rtip','Rhub','r','chord','le_location','materials','upperCS','lowerCS','websCS','profile'])

        self.add_subsystem('obj_cmp',
                           ExecComp('obj = -AEP',
                                    AEP={
                                        'units': 'kW*h',
                                        'value': 1000000.0
                                    },
                                    obj={'units': 'kW*h'}),
                           promotes=['*'])

        # Connections between rotor_aero and rotor_structure
        self.connect('V_mean', 'wind.Uref')
        self.connect('wind_zvec', 'wind.z')
        self.connect('rated_V', ['rs.V_hub', 'rs.setuppc.Vrated'])
        self.connect('rated_Omega', [
            'rs.Omega', 'rs.aero_rated.Omega_load',
            'rs.aero_rated_0.Omega_load', 'rs.aero_rated_120.Omega_load',
            'rs.aero_rated_240.Omega_load'
        ])
        self.connect('rated_pitch', 'rs.aero_rated.pitch_load')
        self.connect('V_extreme50', 'rs.aero_extrm.V_load')
        self.connect('V_extreme_full', 'rs.aero_extrm_forces.Uhub')
        self.connect('theta', 'rs.tip.theta', src_indices=[NPTS - 1])

        # Connections to AeroelasticSE
        if Analysis_Level >= 1:
            self.add_subsystem(
                'aeroelastic',
                FASTLoadCases(RefBlade=RefBlade,
                              npts_coarse_power_curve=npts_coarse_power_curve,
                              npts_spline_power_curve=npts_spline_power_curve,
                              FASTpref=FASTpref),
                promotes=[
                    'fst_vt_in', 'fst_vt_out', 'FASTpref_updated', 'r',
                    'le_location', 'chord', 'theta', 'precurve', 'shearExp',
                    'presweep', 'Rhub', 'Rtip', 'turbulence_class',
                    'turbine_class', 'V_R25', 'rho', 'mu', 'control_maxTS',
                    'control_maxOmega', 'hub_height', 'airfoils_cl',
                    'airfoils_cd', 'airfoils_cm', 'airfoils_aoa',
                    'airfoils_Re', 'airfoils_coord_x', 'airfoils_coord_y',
                    'rthick'
                ])

            self.connect('rhoA', 'aeroelastic.beam:rhoA')
            self.connect('EIxx', 'aeroelastic.beam:EIxx')
            self.connect('EIyy', 'aeroelastic.beam:EIyy')
            self.connect('Tw_iner', 'aeroelastic.beam:Tw_iner')
            self.connect('modes_coef_curvefem',
                         'aeroelastic.modes_coef_curvefem')
            self.connect('rs.z_az', 'aeroelastic.z_az')
            self.connect('V', 'aeroelastic.U_init')
            self.connect('Omega', 'aeroelastic.Omega_init')
            self.connect('pitch', 'aeroelastic.pitch_init')
            self.connect('rated_V', 'aeroelastic.Vrated')
            self.connect('rs.gust.V_gust', 'aeroelastic.Vgust')
            self.connect('V_mean', 'aeroelastic.V_mean_iec')
            self.connect('machine_rating', 'aeroelastic.control_ratedPower')
            if Analysis_Level > 1:
                self.connect('aeroelastic.dx_defl', 'rs.tip.dx')
                self.connect('aeroelastic.dy_defl', 'rs.tip.dy')
                self.connect('aeroelastic.dz_defl', 'rs.tip.dz')
                self.connect('aeroelastic.loads_Px',
                             'rs.loads_strain.aeroloads_Px')
                self.connect('aeroelastic.loads_Py',
                             'rs.loads_strain.aeroloads_Py')
                self.connect('aeroelastic.loads_Pz',
                             'rs.loads_strain.aeroloads_Pz')
                self.connect('aeroelastic.loads_Omega',
                             'rs.loads_strain.aeroloads_Omega')
                self.connect('aeroelastic.loads_azimuth',
                             'rs.loads_strain.aeroloads_azimuth')
                self.connect('aeroelastic.loads_pitch',
                             'rs.loads_strain.aeroloads_pitch')
                self.connect('aeroelastic.root_bending_moment',
                             'rs.root_bending_moment_in')
                self.connect('aeroelastic.Mxyz', 'rs.Mxyz_in')
예제 #2
0
    def setup(self):
        self.verbosity = self.options['verbosity']

        if self.options['topLevelFlag']:
            sharedIndeps = IndepVarComp()
            sharedIndeps.add_output('machine_rating',     units='kW', val=0.0)
            sharedIndeps.add_output('blade_mass',         units='kg', val=0.0)
            sharedIndeps.add_output('hub_mass',           units='kg', val=0.0)
            sharedIndeps.add_output('pitch_system_mass',  units='kg', val=0.0)
            sharedIndeps.add_output('spinner_mass',       units='kg', val=0.0)
            sharedIndeps.add_output('lss_mass',           units='kg', val=0.0)
            sharedIndeps.add_output('bearings_mass',      units='kg', val=0.0)
            sharedIndeps.add_output('gearbox_mass',       units='kg', val=0.0)
            sharedIndeps.add_output('main_bearing_mass',  units='kg', val=0.0)
            sharedIndeps.add_discrete_output('main_bearing_number',  val=0)
            sharedIndeps.add_output('hss_mass',           units='kg', val=0.0)
            sharedIndeps.add_output('generator_mass',     units='kg', val=0.0)
            sharedIndeps.add_output('bedplate_mass',      units='kg', val=0.0)
            sharedIndeps.add_output('yaw_mass',           units='kg', val=0.0)
            sharedIndeps.add_output('vs_electronics_mass',units='kg', val=0.0)
            sharedIndeps.add_output('hvac_mass',          units='kg', val=0.0)
            sharedIndeps.add_output('cover_mass',         units='kg', val=0.0)
            sharedIndeps.add_output('platforms_mass',     units='kg', val=0.0)
            sharedIndeps.add_output('transformer_mass',   units='kg', val=0.0)
            sharedIndeps.add_output('tower_mass',         units='kg', val=0.0)
            sharedIndeps.add_discrete_output('crane', val=False)
            self.add_subsystem('sharedIndeps', sharedIndeps, promotes=['*'])
        
        indeps = IndepVarComp()
        indeps.add_output('blade_mass_cost_coeff',         units='USD/kg', val=14.6)
        indeps.add_output('hub_mass_cost_coeff',           units='USD/kg', val=3.9)
        indeps.add_output('pitch_system_mass_cost_coeff',  units='USD/kg', val=22.1)
        indeps.add_output('spinner_mass_cost_coeff',       units='USD/kg', val=11.1)
        indeps.add_output('lss_mass_cost_coeff',           units='USD/kg', val=11.9)
        indeps.add_output('bearings_mass_cost_coeff',      units='USD/kg', val=4.5)
        indeps.add_output('gearbox_mass_cost_coeff',       units='USD/kg', val=12.9)
        indeps.add_output('hss_mass_cost_coeff',           units='USD/kg', val=6.8)
        indeps.add_output('generator_mass_cost_coeff',     units='USD/kg', val=12.4)
        indeps.add_output('bedplate_mass_cost_coeff',      units='USD/kg', val=2.9)
        indeps.add_output('yaw_mass_cost_coeff',           units='USD/kg', val=8.3)
        indeps.add_output('vs_electronics_mass_cost_coeff',units='USD/kg', val=18.8)
        indeps.add_output('hvac_mass_cost_coeff',          units='USD/kg', val=124.0)
        indeps.add_output('cover_mass_cost_coeff',         units='USD/kg', val=5.7)
        indeps.add_output('elec_connec_machine_rating_cost_coeff',units='USD/kW', val=41.85)
        indeps.add_output('platforms_mass_cost_coeff',     units='USD/kg', val=17.1)
        indeps.add_output('base_hardware_cost_coeff',      units='USD/kg', val=0.7)
        indeps.add_output('transformer_mass_cost_coeff',   units='USD/kg', val=18.8)
        indeps.add_output('tower_mass_cost_coeff',         units='USD/kg', val=2.9)
        indeps.add_output('controls_machine_rating_cost_coeff', units='USD/kW', val=21.15)
        indeps.add_output('crane_cost',                    units='USD', val=12e3)
        
        indeps.add_output('hub_assemblyCostMultiplier',    val=0.0)
        indeps.add_output('hub_overheadCostMultiplier',    val=0.0)
        indeps.add_output('nacelle_assemblyCostMultiplier',val=0.0)
        indeps.add_output('nacelle_overheadCostMultiplier',val=0.0)
        indeps.add_output('tower_assemblyCostMultiplier',  val=0.0)
        indeps.add_output('tower_overheadCostMultiplier',  val=0.0)
        indeps.add_output('turbine_assemblyCostMultiplier',val=0.0)
        indeps.add_output('turbine_overheadCostMultiplier',val=0.0)
        indeps.add_output('hub_profitMultiplier',          val=0.0)
        indeps.add_output('nacelle_profitMultiplier',      val=0.0)
        indeps.add_output('tower_profitMultiplier',        val=0.0)
        indeps.add_output('turbine_profitMultiplier',      val=0.0)
        indeps.add_output('hub_transportMultiplier',       val=0.0)
        indeps.add_output('nacelle_transportMultiplier',   val=0.0)
        indeps.add_output('tower_transportMultiplier',     val=0.0)
        indeps.add_output('turbine_transportMultiplier',   val=0.0)
        self.add_subsystem('indeps', indeps, promotes=['*'])
        
        self.add_subsystem('blade_c'       , BladeCost2015(),         promotes=['*'])
        self.add_subsystem('hub_c'         , HubCost2015(),           promotes=['*'])
        self.add_subsystem('pitch_c'       , PitchSystemCost2015(),   promotes=['*'])
        self.add_subsystem('spinner_c'     , SpinnerCost2015(),       promotes=['*'])
        self.add_subsystem('hub_adder'     , HubSystemCostAdder2015(),promotes=['*'])
        self.add_subsystem('rotor_adder'   , RotorCostAdder2015(),    promotes=['*'])
        self.add_subsystem('lss_c'         , LowSpeedShaftCost2015(), promotes=['*'])
        self.add_subsystem('bearing_c'     , BearingsCost2015(),      promotes=['*'])
        self.add_subsystem('gearbox_c'     , GearboxCost2015(),       promotes=['*'])
        self.add_subsystem('hss_c'         , HighSpeedSideCost2015(), promotes=['*'])
        self.add_subsystem('generator_c'   , GeneratorCost2015(),     promotes=['*'])
        self.add_subsystem('bedplate_c'    , BedplateCost2015(),      promotes=['*'])
        self.add_subsystem('yaw_c'         , YawSystemCost2015(),     promotes=['*'])
        self.add_subsystem('hvac_c'        , HydraulicCoolingCost2015(), promotes=['*'])
        self.add_subsystem('controls_c'    , ControlsCost2015(),      promotes=['*'])
        self.add_subsystem('vs_c'          , VariableSpeedElecCost2015(), promotes=['*'])
        self.add_subsystem('elec_c'        , ElecConnecCost2015(),    promotes=['*'])
        self.add_subsystem('cover_c'       , NacelleCoverCost2015(),  promotes=['*'])
        self.add_subsystem('other_c'       , OtherMainframeCost2015(),promotes=['*'])
        self.add_subsystem('transformer_c' , TransformerCost2015(),   promotes=['*'])
        self.add_subsystem('nacelle_adder' , NacelleSystemCostAdder2015(), promotes=['*'])
        self.add_subsystem('tower_c'       , TowerCost2015(),         promotes=['*'])
        self.add_subsystem('tower_adder'   , TowerCostAdder2015(),    promotes=['*'])
        self.add_subsystem('turbine_c'     , TurbineCostAdder2015(),  promotes=['*'])
        self.add_subsystem('outputs'       , Outputs2Screen(verbosity=self.verbosity), promotes=['*'])
예제 #3
0
    def setup(self):

        RefBlade = self.options['RefBlade']
        Nsection_Tow = self.options['Nsection_Tow']
        user_update_routine = self.options['user_update_routine']
        if 'Analysis_Level' in self.options['FASTpref']:
            Analysis_Level = self.options['FASTpref']['Analysis_Level']
        else:
            Analysis_Level = 0

        # Define all input variables from all models
        myIndeps = IndepVarComp()
        myIndeps.add_discrete_output('crane', False)

        # Turbine Costs
        myIndeps.add_discrete_output('bearing_number', 0)

        # Tower and Frame3DD options
        myIndeps.add_output('project_lifetime', 0.0, units='yr')
        myIndeps.add_output('max_taper_ratio', 0.0)
        myIndeps.add_output('min_diameter_thickness_ratio', 0.0)

        # Environment
        myIndeps.add_output('wind_bottom_height', 0.0, units='m')
        myIndeps.add_output('wind_beta', 0.0, units='deg')
        myIndeps.add_output('cd_usr', -1.)

        # Environment Offshore
        myIndeps.add_output('offshore', True)
        myIndeps.add_output('water_depth', 0.0)
        myIndeps.add_output('wave_height', 0.0)
        myIndeps.add_output('wave_period', 0.0)
        myIndeps.add_output('mean_current_speed', 0.0)

        # Design standards
        myIndeps.add_output('gamma_b', 0.0)
        myIndeps.add_output('gamma_n', 0.0)

        # RNA
        myIndeps.add_discrete_output('rna_weightM', True)

        # Column
        myIndeps.add_output('morison_mass_coefficient', 2.0)
        myIndeps.add_output('material_density', 0.0, units='kg/m**3')
        myIndeps.add_output('E', 0.0, units='N/m**2')
        myIndeps.add_output('yield_stress', 0.0, units='N/m**2')

        # Pontoons
        myIndeps.add_output('G', 0.0, units='N/m**2')

        # LCOE
        myIndeps.add_output('labor_cost_rate', 0.0, units='USD/min')
        myIndeps.add_output('material_cost_rate', 0.0, units='USD/kg')
        myIndeps.add_output('painting_cost_rate', 0.0, units='USD/m**2')
        myIndeps.add_discrete_output('number_of_turbines', 0)
        myIndeps.add_output(
            'annual_opex', 0.0,
            units='USD/kW/yr')  # TODO: Replace with output connection
        myIndeps.add_output(
            'bos_costs', 0.0,
            units='USD/kW')  # TODO: Replace with output connection
        myIndeps.add_output('fixed_charge_rate', 0.0)
        myIndeps.add_output('wake_loss_factor', 0.0)

        myIndeps.add_output('overhang', 0.0, units='m')
        myIndeps.add_output('hub_cm', np.zeros(3), units='m')
        myIndeps.add_output('nac_cm', np.zeros(3), units='m')
        myIndeps.add_output('hub_I', np.zeros(6), units='kg*m**2')
        myIndeps.add_output('nac_I', np.zeros(6), units='kg*m**2')
        myIndeps.add_output('hub_mass', 0.0, units='kg')
        myIndeps.add_output('nac_mass', 0.0, units='kg')
        myIndeps.add_output('hss_mass', 0.0, units='kg')
        myIndeps.add_output('lss_mass', 0.0, units='kg')
        myIndeps.add_output('cover_mass', 0.0, units='kg')
        myIndeps.add_output('pitch_system_mass', 0.0, units='kg')
        myIndeps.add_output('platforms_mass', 0.0, units='kg')
        myIndeps.add_output('spinner_mass', 0.0, units='kg')
        myIndeps.add_output('transformer_mass', 0.0, units='kg')
        myIndeps.add_output('vs_electronics_mass', 0.0, units='kg')
        myIndeps.add_output('yaw_mass', 0.0, units='kg')
        myIndeps.add_output('gearbox_mass', 0.0, units='kg')
        myIndeps.add_output('generator_mass', 0.0, units='kg')
        myIndeps.add_output('bedplate_mass', 0.0, units='kg')
        myIndeps.add_output('main_bearing_mass', 0.0, units='kg')

        self.add_subsystem('myIndeps', myIndeps, promotes=['*'])

        # Add components
        self.add_subsystem('rotorse',
                           RotorSE(RefBlade=RefBlade,
                                   npts_coarse_power_curve=20,
                                   npts_spline_power_curve=200,
                                   regulation_reg_II5=True,
                                   regulation_reg_III=True,
                                   Analysis_Level=Analysis_Level,
                                   FASTpref=self.options['FASTpref'],
                                   topLevelFlag=True,
                                   user_update_routine=user_update_routine),
                           promotes=['*'])

        self.add_subsystem(
            'rna',
            RNA(nLC=1),
            promotes=['hub_mass', 'nac_mass', 'nac_cm', 'hub_cm', 'tilt'])

        # Tower and substructure
        self.add_subsystem(
            'tow',
            TowerSE(nLC=1,
                    nPoints=Nsection_Tow + 1,
                    nFull=5 * Nsection_Tow + 1,
                    wind='PowerWind',
                    topLevelFlag=False,
                    monopile=True),
            promotes=[
                'water_density', 'water_viscosity', 'wave_beta',
                'significant_wave_height', 'significant_wave_period',
                'material_density', 'E', 'G', 'tower_section_height',
                'tower_wall_thickness', 'tower_outer_diameter',
                'tower_outfitting_factor', 'tower_buckling_length',
                'transition_piece_mass', 'transition_piece_height',
                'max_taper', 'min_d_to_t', 'rna_mass', 'rna_cg', 'rna_I',
                'tower_add_gravity', 'tower_mass', 'tower_I_base',
                'hub_height', 'foundation_height', 'monopile', 'soil_G',
                'soil_nu', 'suctionpile_depth', 'gamma_f', 'gamma_m',
                'gamma_b', 'gamma_n', 'gamma_fatigue', 'labor_cost_rate',
                'material_cost_rate', 'painting_cost_rate', 'z_full', 'd_full',
                't_full', 'DC', 'shear', 'geom', 'tower_force_discretization',
                'nM', 'Mmethod', 'lump', 'tol', 'shift'
            ])

        # Turbine constraints
        self.add_subsystem('tcons',
                           TurbineConstraints(nFull=5 * Nsection_Tow + 1),
                           promotes=['*'])

        # Turbine costs
        self.add_subsystem('tcost',
                           Turbine_CostsSE_2015(
                               verbosity=self.options['VerbosityCosts'],
                               topLevelFlag=False),
                           promotes=['*'])

        # LCOE Calculation
        self.add_subsystem(
            'plantfinancese',
            PlantFinance(verbosity=self.options['VerbosityCosts']),
            promotes=['machine_rating', 'lcoe'])

        # Set up connections

        # Connections to DriveSE
        self.connect('Fxyz_total', 'rna.loads.F')
        self.connect('Mxyz_total', 'rna.loads.M')
        self.connect('mass_all_blades', 'rna.blades_mass')
        self.connect('I_all_blades', 'rna.blades_I')

        self.connect('material_density', 'tow.tower.rho')

        # Connections to TowerSE
        self.connect('rna.loads.top_F', 'tow.pre.rna_F')
        self.connect('rna.loads.top_M', 'tow.pre.rna_M')
        self.connect('rna.rna_I_TT', ['rna_I', 'tow.pre.mI'])
        self.connect('rna.rna_cm', ['rna_cg', 'tow.pre.mrho'])
        self.connect('rna.rna_mass', ['rna_mass', 'tow.pre.mass'])
        self.connect('rs.gust.V_gust', 'tow.wind.Uref')
        self.connect('wind_reference_height', ['tow.wind.zref', 'wind.zref'])
        # self.connect('wind_bottom_height',      ['tow.wind.z0','tow.wave.z_surface', 'wind.z0'])  # offshore
        self.connect('wind_bottom_height', ['tow.wind.z0', 'wind.z0'])
        self.connect('shearExp', ['tow.wind.shearExp'])
        # self.connect('morison_mass_coefficient','tow.cm')                                         # offshore
        self.connect('yield_stress', 'tow.sigma_y')
        self.connect('max_taper_ratio', 'max_taper')
        self.connect('min_diameter_thickness_ratio', 'min_d_to_t')

        self.connect('rho', 'tow.windLoads.rho')
        self.connect('mu', 'tow.windLoads.mu')
        self.connect('wind_beta', 'tow.windLoads.beta')

        # Connections to TurbineConstraints
        self.connect('nBlades', 'blade_number')
        self.connect('control_maxOmega', 'rotor_omega')
        self.connect('tow.post.structural_frequencies', 'tower_freq')

        # Connections to TurbineCostSE
        self.connect('mass_one_blade', 'blade_mass')
        self.connect('total_blade_cost', 'blade_cost_external')

        # Connections to PlantFinanceSE
        self.connect('AEP', 'plantfinancese.turbine_aep')
        self.connect('turbine_cost_kW', 'plantfinancese.tcc_per_kW')
        self.connect('number_of_turbines', 'plantfinancese.turbine_number')
        self.connect('bos_costs', 'plantfinancese.bos_per_kW')
        self.connect('annual_opex', 'plantfinancese.opex_per_kW')
예제 #4
0
    def setup(self):
        RefBlade = self.options['RefBlade']
        topLevelFlag = self.options['topLevelFlag']
        NINPUT = len(RefBlade['ctrl_pts']['r_in'])
        NAF = len(RefBlade['outer_shape_bem']['airfoil_position']['grid'])

        verbosity = self.options['verbosity']
        tex_table = self.options['tex_table']
        generate_plots = self.options['generate_plots']
        show_plots = self.options['show_plots']
        show_warnings = self.options['show_warnings']
        discrete = self.options['discrete']
        user_update_routine = self.options['user_update_routine']

        # Independent variables that are unique to TowerSE
        if topLevelFlag:
            geomIndeps = IndepVarComp()
            geomIndeps.add_output('bladeLength', 0.0, units='m')
            geomIndeps.add_output('hubFraction', 0.0)
            geomIndeps.add_output('r_max_chord', 0.0)
            geomIndeps.add_output('chord_in', np.zeros(NINPUT), units='m')
            geomIndeps.add_output('theta_in', np.zeros(NINPUT), units='deg')
            geomIndeps.add_output('precurve_in', np.zeros(NINPUT), units='m')
            geomIndeps.add_output('presweep_in', np.zeros(NINPUT), units='m')
            # geomIndeps.add_output('precurveTip', 0.0, units='m')
            # geomIndeps.add_output('presweepTip', 0.0, units='m')
            geomIndeps.add_output('precone', 0.0, units='deg')
            geomIndeps.add_output('tilt', 0.0, units='deg')
            geomIndeps.add_output('yaw', 0.0, units='deg')
            geomIndeps.add_discrete_output('nBlades', 3)
            geomIndeps.add_discrete_output('downwind', False)
            geomIndeps.add_discrete_output('turbine_class',
                                           val='I',
                                           desc='IEC turbine class')
            geomIndeps.add_discrete_output('blade_in_overwrite',
                                           val={},
                                           desc='IEC turbine class')
            geomIndeps.add_output(
                'V_mean_overwrite',
                val=0.0,
                desc=
                'optional overwrite value for mean velocity for using user defined CDFs'
            )
            geomIndeps.add_output('airfoil_position', val=np.zeros(NAF))
            geomIndeps.add_output('sparT_in',
                                  val=np.zeros(NINPUT),
                                  units='m',
                                  desc='spar cap thickness parameters')
            geomIndeps.add_output('teT_in',
                                  val=np.zeros(NINPUT),
                                  units='m',
                                  desc='trailing-edge thickness parameters')
            # geomIndeps.add_output('leT_in', val=np.zeros(NINPUT), units='m', desc='leading-edge thickness parameters')
            self.add_subsystem('geomIndeps', geomIndeps, promotes=['*'])

        # --- Rotor Definition ---
        self.add_subsystem('loc', Location(), promotes=['*'])
        self.add_subsystem('turbineclass', TurbineClass(), promotes=['*'])
        #self.add_subsystem('spline0', BladeGeometry(RefBlade))
        self.add_subsystem('spline',
                           BladeGeometry(
                               RefBlade=RefBlade,
                               verbosity=verbosity,
                               tex_table=tex_table,
                               generate_plots=generate_plots,
                               show_plots=show_plots,
                               show_warnings=show_warnings,
                               discrete=discrete,
                               user_update_routine=user_update_routine),
                           promotes=['*'])
        self.add_subsystem('geom',
                           CCBladeGeometry(NINPUT=NINPUT),
                           promotes=[
                               'precone', 'precurve_in', 'presweep_in',
                               'precurveTip', 'presweepTip', 'R', 'Rtip'
                           ])
예제 #5
0
    def setup(self):
        nLC           = self.options['nLC']
        nPoints       = self.options['nPoints']
        nFull         = self.options['nFull']
        # nDEL          = self.options['nDEL']
        wind          = self.options['wind']
        topLevelFlag  = self.options['topLevelFlag']
        self.monopile = self.options['monopile']
        
        # Independent variables that are unique to TowerSE
        towerIndeps = IndepVarComp()
        #towerIndeps.add_output('tower_M_DEL', np.zeros(nDEL))
        #towerIndeps.add_output('tower_z_DEL', np.zeros(nDEL), units='m')
        towerIndeps.add_output('tower_force_discretization', 5.0)
        towerIndeps.add_output('suctionpile_depth', 0.0, units='m')
        towerIndeps.add_output('soil_G', 0.0, units='N/m**2')
        towerIndeps.add_output('soil_nu', 0.0)
        towerIndeps.add_discrete_output('monopile', False)
        towerIndeps.add_discrete_output('tower_add_gravity', True)
        towerIndeps.add_discrete_output('shear', True)
        towerIndeps.add_discrete_output('geom', False)
        towerIndeps.add_discrete_output('nM', 2)
        towerIndeps.add_discrete_output('Mmethod', 1)
        towerIndeps.add_discrete_output('lump', 0)
        towerIndeps.add_output('tol', 1e-9)
        towerIndeps.add_output('shift', 0.0)
        towerIndeps.add_output('DC', 0.0)
        towerIndeps.add_output('water_density', 1025.0, units='kg/m**3')
        towerIndeps.add_output('water_viscosity', 8.9e-4, units='kg/m/s')
        towerIndeps.add_output('significant_wave_height', 0.0, units='m')
        towerIndeps.add_output('significant_wave_period', 0.0, units='s')
        towerIndeps.add_output('wave_beta', 0.0, units='deg')
        self.add_subsystem('towerIndeps', towerIndeps, promotes=['*'])

        # Independent variables that may be duplicated at higher levels of aggregation
        if topLevelFlag:
            sharedIndeps = IndepVarComp()
            sharedIndeps.add_output('air_density', 1.225, units='kg/m**3')
            sharedIndeps.add_output('air_viscosity', 1.81206e-5, units='kg/m/s')
            sharedIndeps.add_output('shearExp', 0.0)
            sharedIndeps.add_output('wind_reference_height', 0.0, units='m')
            sharedIndeps.add_output('wind_z0', 0.0, units='m')
            sharedIndeps.add_output('wind_beta', 0.0, units='deg')
            sharedIndeps.add_output('cd_usr', -1.)
            sharedIndeps.add_output('yaw', 0.0, units='deg')
            sharedIndeps.add_output('E', 0.0, units='N/m**2')
            sharedIndeps.add_output('G', 0.0, units='N/m**2')
            sharedIndeps.add_output('sigma_y', 0.0, units='N/m**2')
            sharedIndeps.add_output('rna_mass', 0.0, units='kg')
            sharedIndeps.add_output('rna_cg', np.zeros(3), units='m')
            sharedIndeps.add_output('rna_I', np.zeros(6), units='kg*m**2')
            sharedIndeps.add_output('gamma_f', 0.0)
            sharedIndeps.add_output('gamma_m', 0.0)
            sharedIndeps.add_output('gamma_n', 0.0)
            sharedIndeps.add_output('gamma_b', 0.0)
            sharedIndeps.add_output('gamma_fatigue', 0.0)
            sharedIndeps.add_output('life', 0.0)
            sharedIndeps.add_output('m_SN', 0.0)
            self.add_subsystem('sharedIndeps', sharedIndeps, promotes=['*'])

        self.add_subsystem('geom', TowerLeanSE(nPoints=nPoints, nFull=nFull, topLevelFlag=topLevelFlag), promotes=['*'])
        self.add_subsystem('props', CylindricalShellProperties(nFull=nFull))
        self.add_subsystem('soil', TowerSoil())

        # Connections for geometry and mass
        self.connect('d_full', 'props.d')
        self.connect('t_full', 'props.t')
        self.connect('d_full', 'soil.d0', src_indices=[0])
        self.connect('suctionpile_depth', 'soil.depth')
        self.connect('soil_G', 'soil.G')
        self.connect('soil_nu', 'soil.nu')
        
        # Add in all Components that drive load cases
        # Note multiple load cases have to be handled by replicating components and not groups/assemblies.
        # Replicating Groups replicates the IndepVarComps which doesn't play nicely in OpenMDAO
        for iLC in range(nLC):
            lc = '' if nLC==1 else str(iLC+1)
            
            if wind is None or wind.lower() in ['power', 'powerwind', '']:
                self.add_subsystem('wind'+lc, PowerWind(nPoints=nFull))
            elif wind.lower() == 'logwind':
                self.add_subsystem('wind'+lc, LogWind(nPoints=nFull))
            else:
                raise ValueError('Unknown wind type, '+wind)

            self.add_subsystem('windLoads'+lc, CylinderWindDrag(nPoints=nFull), promotes=['cd_usr'])

            if self.monopile:
                self.add_subsystem('wave'+lc, LinearWaves(nPoints=nFull), promotes=['z_floor'])
                self.add_subsystem('waveLoads'+lc, CylinderWaveDrag(nPoints=nFull), promotes=['cm','cd_usr'])

            self.add_subsystem('distLoads'+lc, AeroHydroLoads(nPoints=nFull), promotes=['yaw'])

            self.add_subsystem('pre'+lc, TowerPreFrame(nFull=nFull), promotes=['monopile'])
            self.add_subsystem('tower'+lc, CylinderFrame3DD(npts=nFull, nK=1, nMass=1, nPL=1), promotes=['E','G','tol','Mmethod','geom','lump','shear',
                                                                             'nM','shift','sigma_y'])
            self.add_subsystem('post'+lc, TowerPostFrame(nFull=nFull), promotes=['E','sigma_y','DC','life','m_SN',
                                                                                 'gamma_b','gamma_f','gamma_fatigue','gamma_m','gamma_n'])
            
            self.connect('z_full', ['wind'+lc+'.z', 'windLoads'+lc+'.z', 'distLoads'+lc+'.z', 'pre'+lc+'.z', 'tower'+lc+'.z', 'post'+lc+'.z'])
            self.connect('d_full', ['windLoads'+lc+'.d', 'tower'+lc+'.d', 'post'+lc+'.d'])
            if self.monopile:
                self.connect('z_full', ['wave'+lc+'.z', 'waveLoads'+lc+'.z'])
                self.connect('d_full', 'waveLoads'+lc+'.d')

            if topLevelFlag:
                self.connect('rna_mass', 'pre'+lc+'.mass')
                self.connect('rna_cg', 'pre'+lc+'.mrho')
                self.connect('rna_I', 'pre'+lc+'.mI')
                self.connect('material_density', 'tower'+lc+'.rho')

            self.connect('pre'+lc+'.kidx', 'tower'+lc+'.kidx')
            self.connect('pre'+lc+'.kx', 'tower'+lc+'.kx')
            self.connect('pre'+lc+'.ky', 'tower'+lc+'.ky')
            self.connect('pre'+lc+'.kz', 'tower'+lc+'.kz')
            self.connect('pre'+lc+'.ktx', 'tower'+lc+'.ktx')
            self.connect('pre'+lc+'.kty', 'tower'+lc+'.kty')
            self.connect('pre'+lc+'.ktz', 'tower'+lc+'.ktz')
            self.connect('pre'+lc+'.midx', 'tower'+lc+'.midx')
            self.connect('pre'+lc+'.m', 'tower'+lc+'.m')
            self.connect('pre'+lc+'.mIxx', 'tower'+lc+'.mIxx')
            self.connect('pre'+lc+'.mIyy', 'tower'+lc+'.mIyy')
            self.connect('pre'+lc+'.mIzz', 'tower'+lc+'.mIzz')
            self.connect('pre'+lc+'.mIxy', 'tower'+lc+'.mIxy')
            self.connect('pre'+lc+'.mIxz', 'tower'+lc+'.mIxz')
            self.connect('pre'+lc+'.mIyz', 'tower'+lc+'.mIyz')
            self.connect('pre'+lc+'.mrhox', 'tower'+lc+'.mrhox')
            self.connect('pre'+lc+'.mrhoy', 'tower'+lc+'.mrhoy')
            self.connect('pre'+lc+'.mrhoz', 'tower'+lc+'.mrhoz')

            self.connect('pre'+lc+'.plidx', 'tower'+lc+'.plidx')
            self.connect('pre'+lc+'.Fx', 'tower'+lc+'.Fx')
            self.connect('pre'+lc+'.Fy', 'tower'+lc+'.Fy')
            self.connect('pre'+lc+'.Fz', 'tower'+lc+'.Fz')
            self.connect('pre'+lc+'.Mxx', 'tower'+lc+'.Mxx')
            self.connect('pre'+lc+'.Myy', 'tower'+lc+'.Myy')
            self.connect('pre'+lc+'.Mzz', 'tower'+lc+'.Mzz')
            self.connect('tower_force_discretization', 'tower'+lc+'.dx')
            self.connect('tower_add_gravity', 'tower'+lc+'.addGravityLoadForExtraMass')
            self.connect('t_full', ['tower'+lc+'.t','post'+lc+'.t'])
            self.connect('soil.k', 'pre'+lc+'.k_monopile')

            self.connect('tower'+lc+'.f1', 'post'+lc+'.f1')
            self.connect('tower'+lc+'.f2', 'post'+lc+'.f2')
            self.connect('tower'+lc+'.Fz_out', 'post'+lc+'.Fz')
            self.connect('tower'+lc+'.Mxx_out', 'post'+lc+'.Mxx')
            self.connect('tower'+lc+'.Myy_out', 'post'+lc+'.Myy')
            self.connect('tower'+lc+'.axial_stress', 'post'+lc+'.axial_stress')
            self.connect('tower'+lc+'.shear_stress', 'post'+lc+'.shear_stress')
            self.connect('tower'+lc+'.hoop_stress_euro', 'post'+lc+'.hoop_stress')
        
            # connections to wind, wave
            if topLevelFlag:
                self.connect('wind_reference_height', 'wind'+lc+'.zref')
                self.connect('wind_z0', 'wind'+lc+'.z0')
                if self.monopile:
                    self.connect('wind_z0', 'wave'+lc+'.z_surface')
                #self.connect('z_floor', 'waveLoads'+lc+'.wlevel')
                if wind=='PowerWind':
                    self.connect('shearExp', 'wind'+lc+'.shearExp')
                
                # connections to windLoads1
                self.connect('air_density', 'windLoads'+lc+'.rho')
                self.connect('air_viscosity', 'windLoads'+lc+'.mu')
                self.connect('wind_beta', 'windLoads'+lc+'.beta')

            if self.monopile:
                # connections to waveLoads1
                self.connect('water_density', ['wave'+lc+'.rho', 'waveLoads'+lc+'.rho'])
                self.connect('water_viscosity', 'waveLoads'+lc+'.mu')
                self.connect('wave_beta', 'waveLoads'+lc+'.beta')
                self.connect('significant_wave_height', 'wave'+lc+'.hmax')
                self.connect('significant_wave_period', 'wave'+lc+'.T')
                    
                self.connect('wind'+lc+'.U', 'windLoads'+lc+'.U')
                self.connect('wave'+lc+'.U', 'waveLoads'+lc+'.U')
                self.connect('wave'+lc+'.A', 'waveLoads'+lc+'.A')
                self.connect('wave'+lc+'.p', 'waveLoads'+lc+'.p')

            # connections to distLoads1
            self.connect('windLoads'+lc+'.windLoads_Px', 'distLoads'+lc+'.windLoads_Px')
            self.connect('windLoads'+lc+'.windLoads_Py', 'distLoads'+lc+'.windLoads_Py')
            self.connect('windLoads'+lc+'.windLoads_Pz', 'distLoads'+lc+'.windLoads_Pz')
            self.connect('windLoads'+lc+'.windLoads_qdyn', 'distLoads'+lc+'.windLoads_qdyn')
            self.connect('windLoads'+lc+'.windLoads_beta', 'distLoads'+lc+'.windLoads_beta')
            #self.connect('windLoads'+lc+'.windLoads_Px0', 'distLoads'+lc+'.windLoads_Px0')
            #self.connect('windLoads'+lc+'.windLoads_Py0', 'distLoads'+lc+'.windLoads_Py0')
            #self.connect('windLoads'+lc+'.windLoads_Pz0', 'distLoads'+lc+'.windLoads_Pz0')
            #self.connect('windLoads'+lc+'.windLoads_qdyn0', 'distLoads'+lc+'.windLoads_qdyn0')
            #self.connect('windLoads'+lc+'.windLoads_beta0', 'distLoads'+lc+'.windLoads_beta0')
            self.connect('windLoads'+lc+'.windLoads_z', 'distLoads'+lc+'.windLoads_z')
            self.connect('windLoads'+lc+'.windLoads_d', 'distLoads'+lc+'.windLoads_d')

            if self.monopile:
                self.connect('waveLoads'+lc+'.waveLoads_Px', 'distLoads'+lc+'.waveLoads_Px')
                self.connect('waveLoads'+lc+'.waveLoads_Py', 'distLoads'+lc+'.waveLoads_Py')
                self.connect('waveLoads'+lc+'.waveLoads_Pz', 'distLoads'+lc+'.waveLoads_Pz')
                self.connect('waveLoads'+lc+'.waveLoads_pt', 'distLoads'+lc+'.waveLoads_qdyn')
                self.connect('waveLoads'+lc+'.waveLoads_beta', 'distLoads'+lc+'.waveLoads_beta')
                #self.connect('waveLoads'+lc+'.waveLoads_Px0', 'distLoads'+lc+'.waveLoads_Px0')
                #self.connect('waveLoads'+lc+'.waveLoads_Py0', 'distLoads'+lc+'.waveLoads_Py0')
                #self.connect('waveLoads'+lc+'.waveLoads_Pz0', 'distLoads'+lc+'.waveLoads_Pz0')
                #self.connect('waveLoads'+lc+'.waveLoads_qdyn0', 'distLoads'+lc+'.waveLoads_qdyn0')
                #self.connect('waveLoads'+lc+'.waveLoads_beta0', 'distLoads'+lc+'.waveLoads_beta0')
                self.connect('waveLoads'+lc+'.waveLoads_z', 'distLoads'+lc+'.waveLoads_z')
                self.connect('waveLoads'+lc+'.waveLoads_d', 'distLoads'+lc+'.waveLoads_d')

            # Tower connections
            self.connect('tower_buckling_length', ['tower'+lc+'.L_reinforced', 'post'+lc+'.L_reinforced'])
            #self.connect('tower_M_DEL', 'post'+lc+'.M_DEL')
            #self.connect('tower_z_DEL', 'post'+lc+'.z_DEL')

            self.connect('props.Az', 'tower'+lc+'.Az')
            self.connect('props.Asx', 'tower'+lc+'.Asx')
            self.connect('props.Asy', 'tower'+lc+'.Asy')
            self.connect('props.Jz', 'tower'+lc+'.Jz')
            self.connect('props.Ixx', 'tower'+lc+'.Ixx')
            self.connect('props.Iyy', 'tower'+lc+'.Iyy')

            self.connect('distLoads'+lc+'.Px',   'tower'+lc+'.Px')
            self.connect('distLoads'+lc+'.Py',   'tower'+lc+'.Py')
            self.connect('distLoads'+lc+'.Pz',   'tower'+lc+'.Pz')
            self.connect('distLoads'+lc+'.qdyn', 'tower'+lc+'.qdyn')
예제 #6
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')