def setup(self): nn = self.options['num_nodes'] flight_phase = self.options['flight_phase'] # any control variables other than throttle and braking need to be defined here controls = self.add_subsystem('controls', IndepVarComp(), promotes_outputs=['*']) controls.add_output('proprpm',val=np.ones((nn,))*2000, units='rpm') # assume TO happens on battery backup if flight_phase in ['climb', 'cruise','descent']: controls.add_output('hybridization',val=0.0) else: controls.add_output('hybridization',val=1.0) hybrid_factor = self.add_subsystem('hybrid_factor', LinearInterpolator(num_nodes=nn), promotes_inputs=[('start_val', 'hybridization'), ('end_val', 'hybridization')]) propulsion_promotes_outputs = ['fuel_flow','thrust'] propulsion_promotes_inputs = ["fltcond|*", "ac|propulsion|*", "throttle", "propulsor_active", "ac|weights*", 'duration'] self.add_subsystem('propmodel', TwinSeriesHybridElectricPropulsionSystem(num_nodes=nn), promotes_inputs=propulsion_promotes_inputs, promotes_outputs=propulsion_promotes_outputs) self.connect('proprpm', ['propmodel.prop1.rpm', 'propmodel.prop2.rpm']) self.connect('hybrid_factor.vec', 'propmodel.hybrid_split.power_split_fraction') # use a different drag coefficient for takeoff versus cruise if flight_phase not in ['v0v1', 'v1v0', 'v1vr', 'rotate']: cd0_source = 'ac|aero|polar|CD0_cruise' else: cd0_source = 'ac|aero|polar|CD0_TO' self.add_subsystem('drag', PolarDrag(num_nodes=nn), promotes_inputs=['fltcond|CL', 'ac|geom|*', ('CD0', cd0_source), 'fltcond|q', ('e', 'ac|aero|polar|e')], promotes_outputs=['drag']) self.add_subsystem('OEW',TwinSeriesHybridEmptyWeight(), promotes_inputs=[('P_TO','ac|propulsion|engine|rating'),'*'], promotes_outputs=['OEW']) self.connect('propmodel.propellers_weight', 'W_propeller') self.connect('propmodel.eng1.component_weight', 'W_engine') self.connect('propmodel.gen1.component_weight', 'W_generator') self.connect('propmodel.motors_weight', 'W_motors') intfuel = self.add_subsystem('intfuel', Integrator(num_nodes=nn, method='simpson', diff_units='s', time_setup='duration'), promotes_inputs=['*'], promotes_outputs=['*']) intfuel.add_integrand('fuel_used', rate_name='fuel_flow', val=1.0, units='kg') self.add_subsystem('weight', AddSubtractComp(output_name='weight', input_names=['ac|weights|MTOW', 'fuel_used'], units='kg', vec_size=[1, nn], scaling_factors=[1, -1]), promotes_inputs=['*'], promotes_outputs=['weight'])
def setup(self): nn = self.options['num_nodes'] flight_phase = self.options['flight_phase'] # a propulsion system needs to be defined in order to provide thrust # information for the mission analysis code # propulsion_promotes_outputs = ['fuel_flow', 'thrust'] propulsion_promotes_inputs = ["fltcond|*", "throttle"] self.add_subsystem('propmodel', CFM56(num_nodes=nn, plot=False), promotes_inputs=propulsion_promotes_inputs) doubler = om.ExecComp(['thrust=2*thrust_in', 'fuel_flow=2*fuel_flow_in'], thrust_in={'val': 1.0*np.ones((nn,)), 'units': 'kN'}, thrust={'val': 1.0*np.ones((nn,)), 'units': 'kN'}, fuel_flow={'val': 1.0*np.ones((nn,)), 'units': 'kg/s', 'tags': ['integrate', 'state_name:fuel_used', 'state_units:kg', 'state_val:1.0', 'state_promotes:True']}, fuel_flow_in={'val': 1.0*np.ones((nn,)), 'units': 'kg/s'}) self.add_subsystem('doubler', doubler, promotes_outputs=['*']) self.connect('propmodel.thrust', 'doubler.thrust_in') self.connect('propmodel.fuel_flow', 'doubler.fuel_flow_in') # use a different drag coefficient for takeoff versus cruise if flight_phase not in ['v0v1', 'v1v0', 'v1vr', 'rotate']: cd0_source = 'ac|aero|polar|CD0_cruise' else: cd0_source = 'ac|aero|polar|CD0_TO' self.add_subsystem('drag', PolarDrag(num_nodes=nn), promotes_inputs=['fltcond|CL', 'ac|geom|*', ('CD0', cd0_source), 'fltcond|q', ('e', 'ac|aero|polar|e')], promotes_outputs=['drag']) # generally the weights module will be custom to each airplane passthru = om.ExecComp('OEW=x', x={'val': 1.0, 'units': 'kg'}, OEW={'val': 1.0, 'units': 'kg'}) self.add_subsystem('OEW', passthru, promotes_inputs=[('x', 'ac|weights|OEW')], promotes_outputs=['OEW']) self.add_subsystem('weight', oc.AddSubtractComp(output_name='weight', input_names=['ac|weights|MTOW', 'fuel_used'], units='kg', vec_size=[1, nn], scaling_factors=[1, -1]), promotes_inputs=['*'], promotes_outputs=['weight'])
def setup(self): nn = self.options['num_nodes'] flight_phase = self.options['flight_phase'] # no control variables other than throttle and braking """ # propulsion system 1: simple turbofan (textbook formula) from examples.propulsion_layouts.simple_turbofan import TurbofanPropulsionSystem self.add_subsystem('propmodel', TurbofanPropulsionSystem(num_nodes=nn), promotes_inputs=['fltcond|*', 'ac|propulsion|max_thrust', 'throttle', 'ac|propulsion|num_engine'], promotes_outputs=['thrust', 'fuel_flow']) """ # propulsion system 2: pycycle surrogate from examples.propulsion_layouts.turbofan_surrogate import TurbofanPropulsionSystem self.add_subsystem('propmodel', TurbofanPropulsionSystem(num_nodes=nn), promotes_inputs=['fltcond|*', 'throttle'], promotes_outputs=['thrust', 'fuel_flow']) #""" # aerodynamic model if flight_phase not in ['v0v1', 'v1v0', 'v1vr', 'rotate']: cd0_source = 'ac|aero|polar|CD0_cruise' else: cd0_source = 'ac|aero|polar|CD0_TO' self.add_subsystem('drag', PolarDrag(num_nodes=nn), promotes_inputs=['fltcond|CL', 'ac|geom|*', ('CD0', cd0_source), 'fltcond|q', ('e', 'ac|aero|polar|e')], promotes_outputs=['drag']) # weight estimation models self.add_subsystem('OEW', JetAirlinerEmptyWeight(), promotes_inputs=['ac|geom|*', 'ac|weights|*', 'ac|propulsion|*', 'ac|misc|*'], promotes_outputs=['OEW']) # airplanes which consume fuel will need to integrate # fuel usage across the mission and subtract it from TOW nn_simpson = int((nn - 1) / 2) self.add_subsystem('intfuel', Integrator(num_intervals=nn_simpson, method='simpson', quantity_units='kg', diff_units='s', time_setup='duration'), promotes_inputs=[('dqdt', 'fuel_flow'), 'duration', ('q_initial', 'fuel_used_initial')], promotes_outputs=[('q', 'fuel_used'), ('q_final', 'fuel_used_final')]) self.add_subsystem('weight', AddSubtractComp(output_name='weight', input_names=['ac|weights|MTOW', 'fuel_used'], units='kg', vec_size=[1, nn], scaling_factors=[1, -1]), promotes_inputs=['*'], promotes_outputs=['weight'])
def setup(self): nn = self.options['num_nodes'] flight_phase = self.options['flight_phase'] # any control variables other than throttle and braking need to be defined here controls = self.add_subsystem('controls', om.IndepVarComp(), promotes_outputs=['*']) controls.add_output('prop1rpm', val=np.ones((nn,)) * 2000, units='rpm') # a propulsion system needs to be defined in order to provide thrust # information for the mission analysis code propulsion_promotes_outputs = ['fuel_flow', 'thrust'] propulsion_promotes_inputs = ["fltcond|*", "ac|propulsion|*", "throttle"] self.add_subsystem('propmodel', TurbopropPropulsionSystem(num_nodes=nn), promotes_inputs=propulsion_promotes_inputs, promotes_outputs=propulsion_promotes_outputs) self.connect('prop1rpm', 'propmodel.prop1.rpm') # use a different drag coefficient for takeoff versus cruise if flight_phase not in ['v0v1', 'v1v0', 'v1vr', 'rotate']: cd0_source = 'ac|aero|polar|CD0_cruise' else: cd0_source = 'ac|aero|polar|CD0_TO' self.add_subsystem('drag', PolarDrag(num_nodes=nn), promotes_inputs=['fltcond|CL', 'ac|geom|*', ('CD0', cd0_source), 'fltcond|q', ('e', 'ac|aero|polar|e')], promotes_outputs=['drag']) # generally the weights module will be custom to each airplane self.add_subsystem('OEW', SingleTurboPropEmptyWeight(), promotes_inputs=['*', ('P_TO', 'ac|propulsion|engine|rating')], promotes_outputs=['OEW']) self.connect('propmodel.prop1.component_weight', 'W_propeller') self.connect('propmodel.eng1.component_weight', 'W_engine') # airplanes which consume fuel will need to integrate # fuel usage across the mission and subtract it from TOW intfuel = self.add_subsystem('intfuel', oc.Integrator(num_nodes=nn, method='simpson', diff_units='s', time_setup='duration'), promotes_inputs=['*'], promotes_outputs=['*']) intfuel.add_integrand('fuel_used', rate_name='fuel_flow', val=1.0, units='kg') self.add_subsystem('weight', oc.AddSubtractComp(output_name='weight', input_names=['ac|weights|MTOW', 'fuel_used'], units='kg', vec_size=[1, nn], scaling_factors=[1, -1]), promotes_inputs=['*'], promotes_outputs=['weight'])
def setup(self): nn = self.options['num_nodes'] iv = self.add_subsystem('conditions', IndepVarComp(), promotes_outputs=['*']) self.add_subsystem('polardrag', PolarDrag(num_nodes=nn), promotes_inputs=['*'], promotes_outputs=['*']) iv.add_output('fltcond|CL', val=np.linspace(0, 1.5, nn)) iv.add_output('fltcond|q', val=np.ones(nn) * 0.5 * 1.225 * 70**2, units='N * m**-2') iv.add_output('ac|geom|wing|S_ref', val=30, units='m**2') iv.add_output('ac|geom|wing|AR', val=15) iv.add_output('CD0', val=0.02) iv.add_output('e', val=0.8)
def setup(self): nn = self.options['num_nodes'] flight_phase = self.options['flight_phase'] # any control variables other than throttle and braking need to be defined here controls = self.add_subsystem('controls', IndepVarComp(), promotes_outputs=['*']) controls.add_output('prop1rpm', val=np.ones((nn, )) * 2000, units='rpm') propulsion_promotes_outputs = ['thrust'] propulsion_promotes_inputs = [ "fltcond|*", "ac|propulsion|*", "throttle", "ac|weights|*", "duration" ] self.add_subsystem( 'propmodel', AllElectricSinglePropulsionSystemWithThermal_Compressible( num_nodes=nn), promotes_inputs=propulsion_promotes_inputs, promotes_outputs=propulsion_promotes_outputs) self.connect('prop1rpm', 'propmodel.prop1.rpm') # use a different drag coefficient for takeoff versus cruise if flight_phase not in ['v0v1', 'v1v0', 'v1vr', 'rotate']: cd0_source = 'ac|aero|polar|CD0_cruise' else: cd0_source = 'ac|aero|polar|CD0_TO' self.add_subsystem('drag', PolarDrag(num_nodes=nn), promotes_inputs=[ 'fltcond|CL', 'ac|geom|*', ('CD0', cd0_source), 'fltcond|q', ('e', 'ac|aero|polar|e') ], promotes_outputs=['drag']) self.add_subsystem('weight', LinearInterpolator(num_nodes=nn, units='kg'), promotes_inputs=[('start_val', 'ac|weights|MTOW'), ('end_val', 'ac|weights|MTOW')], promotes_outputs=[('vec', 'weight')])
def setup(self): n_int_per_seg = self.options['n_int_per_seg'] track_battery = self.options['track_battery'] track_fuel = self.options['track_fuel'] nn = (n_int_per_seg * 2 + 1) nn_tot = 3 * nn + 2 #===Some of the generic components take "weight" as an input. Need to re-label Takeoff Weight (TOW) as just weight dvlist = [['ac|weights|MTOW', 'weight', 2000.0, 'kg'], ['mission|takeoff|v1', 'v1', 40, 'm/s'], ['mission|takeoff|vr', 'vr', 45, 'm/s'], ['ac|aero|polar|CD0_TO', 'CD0', 0.005, None], ['ac|aero|polar|e', 'e', 0.95, None], [ 'fltcond|takeoff|q', 'fltcond|q', 100 * np.ones(nn_tot), 'Pa' ]] self.add_subsystem('dvs', DVLabel(dvlist), promotes_inputs=["*"], promotes_outputs=["*"]) #===We assume the takeoff starts at 1 m/s to avoid singularities at v=0 const = self.add_subsystem('const', IndepVarComp()) const.add_output('v0', val=1, units='m/s') const.add_output('reaction_time', val=2, units='s') #===Lift and Drag Calculations feed the EOMs for the takeoff roll=== self.add_subsystem( 'CL', TakeoffCLs(n_int_per_seg=n_int_per_seg), promotes_inputs=["ac|geom|*", "fltcond|*", "weight"], ) self.add_subsystem( 'drag', PolarDrag(num_nodes=nn_tot), promotes_inputs=['ac|geom|*', 'fltcond|q', 'CD0', 'e'], promotes_outputs=['drag']) self.add_subsystem('lift', Lift(num_nodes=nn_tot), promotes_inputs=['ac|geom|*', 'fltcond|q'], promotes_outputs=['lift']) self.connect('CL.CL_takeoff', 'drag.fltcond|CL') self.connect('CL.CL_takeoff', 'lift.fltcond|CL') #==The takeoff distance integrator numerically integrates the quantity (speed / acceleration) with respect to speed to obtain distance. Obtain this quantity: self.add_subsystem( 'accel', TakeoffAccels(n_int_per_seg=n_int_per_seg), promotes_inputs=['lift', 'drag', 'takeoff|thrust', 'weight'], promotes_outputs=['_inverse_accel']) self.add_subsystem( 'mult', ElementMultiplyDivideComp( output_name='_rate_to_integrate', input_names=['_inverse_accel', 'fltcond|takeoff|Utrue'], vec_size=nn_tot, input_units=['s**2/m', 'm/s']), promotes_inputs=['*'], promotes_outputs=['_*']) if track_fuel: self.add_subsystem( 'fuelflowmult', ElementMultiplyDivideComp( output_name='_fuel_flow_rate_to_integrate', input_names=['_inverse_accel', 'takeoff|fuel_flow'], vec_size=nn_tot, input_units=['s**2/m', 'kg/s']), promotes_inputs=['*'], promotes_outputs=['_*']) if track_battery: self.add_subsystem( 'batterymult', ElementMultiplyDivideComp( output_name='_battery_rate_to_integrate', input_names=['_inverse_accel', 'takeoff|battery_load'], vec_size=nn_tot, input_units=['s**2/m', 'J / s']), promotes_inputs=['*'], promotes_outputs=['*']) #==The following boilerplate splits the input flight conditions, thrusts, and fuel flows into the takeoff segments for further analysis inputs_to_split = [ '_rate_to_integrate', 'fltcond|takeoff|Utrue', 'takeoff|thrust', 'drag', 'lift' ] units = ['s', 'm/s', 'N', 'N', 'N'] if track_battery: inputs_to_split.append('_battery_rate_to_integrate') units.append('J*s/m') if track_fuel: inputs_to_split.append('_fuel_flow_rate_to_integrate') units.append('kg*s/m') segments_to_split_into = ['v0v1', 'v1vr', 'v1v0', 'vtrans', 'v2'] nn_each_segment = [nn, nn, nn, 1, 1] split_inst = VectorSplitComp() for kth, input_name in enumerate(inputs_to_split): output_names_list = [] for segment in segments_to_split_into: output_names_list.append(input_name + '_' + segment) split_inst.add_relation(output_names=output_names_list, input_name=input_name, vec_sizes=nn_each_segment, units=units[kth]) splitter = self.add_subsystem('splitter', subsys=split_inst, promotes_inputs=["*"], promotes_outputs=["*"]) #==Now integrate the three continuous segments: 0 to v1, v1 to rotation with reduced power if applicable, and hard braking self.add_subsystem( 'v0v1_dist', IntegrateQuantity(num_intervals=n_int_per_seg, quantity_units='m', diff_units='m/s', force_signs=True)) self.add_subsystem( 'v1vr_dist', IntegrateQuantity(num_intervals=n_int_per_seg, quantity_units='m', diff_units='m/s', force_signs=True)) self.add_subsystem( 'v1v0_dist', IntegrateQuantity(num_intervals=n_int_per_seg, quantity_units='m', diff_units='m/s', force_signs=True)) self.connect('_rate_to_integrate_v0v1', 'v0v1_dist.rate') self.connect('_rate_to_integrate_v1vr', 'v1vr_dist.rate') self.connect('_rate_to_integrate_v1v0', 'v1v0_dist.rate') self.connect('const.v0', 'v0v1_dist.lower_limit') self.connect('v1', 'v0v1_dist.upper_limit') self.connect('v1', 'v1v0_dist.lower_limit') self.connect('const.v0', 'v1v0_dist.upper_limit') self.connect('v1', 'v1vr_dist.lower_limit') self.connect('vr', 'v1vr_dist.upper_limit') if track_fuel: self.add_subsystem( 'v0v1_fuel', IntegrateQuantity(num_intervals=n_int_per_seg, quantity_units='kg', diff_units='m/s', force_signs=True)) self.add_subsystem( 'v1vr_fuel', IntegrateQuantity(num_intervals=n_int_per_seg, quantity_units='kg', diff_units='m/s', force_signs=True)) self.connect('_fuel_flow_rate_to_integrate_v0v1', 'v0v1_fuel.rate') self.connect('_fuel_flow_rate_to_integrate_v1vr', 'v1vr_fuel.rate') self.connect('const.v0', 'v0v1_fuel.lower_limit') self.connect('v1', ['v0v1_fuel.upper_limit', 'v1vr_fuel.lower_limit']) self.connect('vr', 'v1vr_fuel.upper_limit') if track_battery: self.add_subsystem( 'v0v1_battery', IntegrateQuantity(num_intervals=n_int_per_seg, quantity_units='J', diff_units='m/s', force_signs=True)) self.add_subsystem( 'v1vr_battery', IntegrateQuantity(num_intervals=n_int_per_seg, quantity_units='J', diff_units='m/s', force_signs=True)) self.connect('_battery_rate_to_integrate_v0v1', 'v0v1_battery.rate') self.connect('_battery_rate_to_integrate_v1vr', 'v1vr_battery.rate') self.connect('const.v0', 'v0v1_battery.lower_limit') self.connect( 'v1', ['v0v1_battery.upper_limit', 'v1vr_battery.lower_limit']) self.connect('vr', 'v1vr_battery.upper_limit') #==Next compute the transition and climb phase to the specified clearance height. First, need the steady climb-out angle at v2 speed self.add_subsystem( 'gamma', TakeoffV2ClimbAngle(), promotes_inputs=["drag_v2", "takeoff|thrust_v2", "weight"], promotes_outputs=["takeoff|climb|gamma"]) self.add_subsystem('transition', TakeoffTransition(), promotes_inputs=[ "fltcond|takeoff|Utrue_vtrans", "takeoff|climb|gamma" ], promotes_outputs=["h_transition", "s_transition"]) self.add_subsystem( 'climb', TakeoffClimb(), promotes_inputs=["takeoff|climb|gamma", "h_transition"], promotes_outputs=["s_climb"]) self.add_subsystem('reaction', ElementMultiplyDivideComp( output_name='s_reaction', input_names=['v1', 'reaction_time'], vec_size=1, input_units=['m/s', 's']), promotes_inputs=['v1']) self.connect('const.reaction_time', 'reaction.reaction_time') self.add_subsystem('total_to_distance_continue', AddSubtractComp(output_name='takeoff|distance', input_names=[ 's_v0v1', 's_v1vr', 's_reaction', 's_transition', 's_climb' ], vec_size=1, units='m'), promotes_outputs=["*"]) self.add_subsystem('total_to_distance_abort', AddSubtractComp( output_name='takeoff|distance_abort', input_names=['s_v0v1', 's_v1v0', 's_reaction'], vec_size=1, units='m'), promotes_outputs=["*"]) self.connect('reaction.s_reaction', 'total_to_distance_continue.s_reaction') self.connect('reaction.s_reaction', 'total_to_distance_abort.s_reaction') self.connect('v0v1_dist.delta_quantity', [ 'total_to_distance_continue.s_v0v1', 'total_to_distance_abort.s_v0v1' ]) self.connect('v1vr_dist.delta_quantity', 'total_to_distance_continue.s_v1vr') self.connect('s_transition', 'total_to_distance_continue.s_transition') self.connect('s_climb', 'total_to_distance_continue.s_climb') self.connect('v1v0_dist.delta_quantity', 'total_to_distance_abort.s_v1v0') if track_battery: self.add_subsystem( 'total_battery', AddSubtractComp(output_name='takeoff|total_battery_energy', input_names=['battery_v0v1', 'battery_v1vr'], units='J'), promotes_outputs=["*"]) self.connect('v0v1_battery.delta_quantity', 'total_battery.battery_v0v1') self.connect('v1vr_battery.delta_quantity', 'total_battery.battery_v1vr') if track_fuel: self.add_subsystem('total_fuel', AddSubtractComp( output_name='takeoff|total_fuel', input_names=['fuel_v0v1', 'fuel_v1vr'], units='kg', scaling_factors=[-1, -1]), promotes_outputs=["*"]) self.connect('v0v1_fuel.delta_quantity', 'total_fuel.fuel_v0v1') self.connect('v1vr_fuel.delta_quantity', 'total_fuel.fuel_v1vr') self.add_subsystem( 'climb_weight', AddSubtractComp(output_name='weight_after_takeoff', input_names=['weight', 'takeoff|total_fuel'], units='kg', scaling_factors=[1, -1]), promotes_inputs=["*"], promotes_outputs=["*"])
def setup(self): nn = self.options['num_nodes'] flight_phase = self.options['flight_phase'] #=============AERODYNAMICS====================== # use a different drag coefficient for takeoff versus cruise if flight_phase not in ['v0v1', 'v1v0', 'v1vr', 'rotate']: cd0_source = 'ac|aero|polar|CD0_cruise' else: cd0_source = 'ac|aero|polar|CD0_TO' self.add_subsystem('airframe_drag', PolarDrag(num_nodes=nn), promotes_inputs=[ 'fltcond|CL', 'ac|geom|*', ('CD0', cd0_source), 'fltcond|q', ('e', 'ac|aero|polar|e') ]) self.promote_add(sources=[ 'airframe_drag.drag', 'variable_duct.force.F_net', 'motor_duct.force.F_net' ], prom_name='drag', factors=[1.0, -2.0, -2.0], vec_size=nn, units='N') #=============PROPULSION======================= # Hybrid propulsion motor (model one side only) self.add_subsystem('hybrid_throttle', LinearInterpolator(num_nodes=nn, units=None), promotes_inputs=[ ('start_val', 'hybrid_throttle_start'), ('end_val', 'hybrid_throttle_end') ]) self.add_subsystem('hybrid_motor', SimpleMotor(num_nodes=nn, efficiency=0.95), promotes_inputs=[('elec_power_rating', 'ac|propulsion|motor|rating')]) self.connect('hybrid_motor.shaft_power_out', 'engine.hybrid_power') self.connect('hybrid_throttle.vec', 'hybrid_motor.throttle') # Add a surrogate model for the engine. Inputs are Mach, Alt, Throttle, Hybrid power self.add_subsystem('engine', N3Hybrid(num_nodes=nn, plot=False), promotes_inputs=["fltcond|*", "throttle"]) # double the thrust and fuel flow of the engine and integrate fuel flow self.promote_mult('engine.thrust', prom_name='thrust', factor=2.0, vec_size=nn, units='kN') self.promote_mult('engine.fuel_flow', prom_name='fuel_flow', factor=2.0, vec_size=nn, units='kg/s', tags=[ 'integrate', 'state_name:fuel_used', 'state_units:kg', 'state_val:1.0', 'state_promotes:True' ]) # Hybrid propulsion battery self.add_subsystem('battery', SOCBattery(num_nodes=nn, efficiency=0.95, specific_energy=400), promotes_inputs=[('battery_weight', 'ac|propulsion|battery|weight')]) self.promote_add(sources=[ 'hybrid_motor.elec_load', 'refrig.elec_load', 'motorfaultprot.elec_load', 'battery_coolant_pump.elec_load', 'motor_coolant_pump.elec_load' ], prom_name='elec_load', factors=[1.0, 1.0, 1.0, 1.0, 1.0], vec_size=nn, val=1.0, units='kW') self.connect('elec_load', 'battery.elec_load') #=============THERMAL====================== thermal_params = self.add_subsystem('thermal_params', om.IndepVarComp(), promotes_outputs=['*']) # properties thermal_params.add_output('rho_coolant', val=1020 * np.ones((nn, )), units='kg/m**3') # controls thermal_params.add_output('mdot_coolant_battery', val=4.8 * np.ones((nn, )), units='kg/s') thermal_params.add_output('mdot_coolant_motor', val=1.2 * np.ones((nn, )), units='kg/s') # fault protection needs separate cooler because it needs 40C inflow temp at 3gpm thermal_params.add_output('mdot_coolant_fault_prot', val=0.19 * np.ones((nn, )), units='kg/s') thermal_params.add_output('bypass_heat_pump', val=np.ones((nn, ))) thermal_params.add_output('variable_duct_nozzle_area_start', val=20, units='inch**2') thermal_params.add_output('variable_duct_nozzle_area_end', val=20, units='inch**2') thermal_params.add_output('heat_pump_specific_power', val=200., units='W/kg') thermal_params.add_output('heat_pump_eff_factor', val=0.4, units=None) self.add_subsystem('li_battery', LinearInterpolator(num_nodes=nn, units='inch**2'), promotes_outputs=[('vec', 'variable_duct_nozzle_area')]) self.connect('variable_duct_nozzle_area_start', 'li_battery.start_val') self.connect('variable_duct_nozzle_area_end', 'li_battery.end_val') self.add_subsystem( 'li_motor', LinearInterpolator(num_nodes=nn, units='inch**2'), promotes_inputs=[ ('start_val', 'ac|propulsion|thermal|hx_motor|nozzle_area'), ('end_val', 'ac|propulsion|thermal|hx_motor|nozzle_area') ], promotes_outputs=[('vec', 'motor_duct_area_nozzle_in')]) hx_design_vars = [ 'ac|propulsion|thermal|hx|n_wide_cold', 'ac|propulsion|thermal|hx|n_long_cold', 'ac|propulsion|thermal|hx|n_tall' ] #===========MOTOR LOOP======================= self.add_subsystem('motorheatsink', LiquidCooledMotor(num_nodes=nn, case_cooling_coefficient=2100., quasi_steady=False), promotes_inputs=[('power_rating', 'ac|propulsion|motor|rating')]) self.connect('hybrid_motor.heat_out', 'motorheatsink.q_in') self.connect('hybrid_motor.component_weight', 'motorheatsink.motor_weight') self.add_subsystem('motorfaultprot', MotorFaultProtection(num_nodes=nn)) self.connect('hybrid_motor.elec_load', 'motorfaultprot.motor_power') self.add_subsystem('hx_motor', HXGroup(num_nodes=nn), promotes_inputs=[(a, a.replace('hx', 'hx_motor')) for a in hx_design_vars]) self.connect('rho_coolant', 'hx_motor.rho_hot') fault_prot_promotes = [ ('ac|propulsion|thermal|hx|n_wide_cold', 'ac|propulsion|thermal|hx_motor|n_wide_cold'), ('ac|propulsion|thermal|hx|n_long_cold', 'ac|propulsion|thermal|hx_fault_prot|n_long_cold'), ('ac|propulsion|thermal|hx|n_tall', 'ac|propulsion|thermal|hx_motor|n_tall') ] self.add_subsystem('hx_fault_prot', HXGroup(num_nodes=nn), promotes_inputs=fault_prot_promotes) self.connect('rho_coolant', [ 'hx_fault_prot.rho_hot', 'motor_hose.rho_coolant', 'motor_coolant_pump.rho_coolant' ]) self.add_subsystem('motor_duct', ImplicitCompressibleDuct_ExternalHX(num_nodes=nn, cfg=0.90), promotes_inputs=[('p_inf', 'fltcond|p'), ('T_inf', 'fltcond|T'), ('Utrue', 'fltcond|Utrue'), ('area_nozzle_in', 'motor_duct_area_nozzle_in')]) self.add_subsystem('motor_hose', SimpleHose(num_nodes=nn), promotes_inputs=[ ('hose_length', 'ac|geom|thermal|hx_to_motor_length'), ('hose_diameter', 'ac|geom|thermal|hx_to_motor_diameter') ]) self.add_subsystem( 'motor_coolant_pump', SimplePump(num_nodes=nn), promotes_inputs=[ ('power_rating', 'ac|propulsion|thermal|hx_motor|pump_power_rating') ]) self.promote_add( sources=['motor_hose.delta_p', 'hx_motor.delta_p_hot'], prom_name='pressure_drop_motor_loop', factors=[1.0, -1.0], vec_size=nn, units='Pa') self.connect('pressure_drop_motor_loop', 'motor_coolant_pump.delta_p') # in to HXGroup: self.connect('motor_duct.sta2.T', 'hx_fault_prot.T_in_cold') self.connect('hx_fault_prot.T_out_cold', 'hx_motor.T_in_cold') self.connect('motor_duct.sta2.rho', ['hx_motor.rho_cold', 'hx_fault_prot.rho_cold']) self.connect('motor_duct.mdot', ['hx_motor.mdot_cold', 'hx_fault_prot.mdot_cold']) self.connect('motorheatsink.T_out', 'hx_motor.T_in_hot') self.connect('motorfaultprot.T_out', 'hx_fault_prot.T_in_hot') self.connect('mdot_coolant_motor', [ 'motorheatsink.mdot_coolant', 'hx_motor.mdot_hot', 'motor_hose.mdot_coolant', 'motor_coolant_pump.mdot_coolant' ]) self.connect('mdot_coolant_fault_prot', ['motorfaultprot.mdot_coolant', 'hx_fault_prot.mdot_hot']) #out from HXGroup self.connect('hx_motor.frontal_area', ['motor_duct.area_2', 'motor_duct.area_3']) self.connect('hx_motor.delta_p_cold', 'motorfaultprot.delta_p_motor_hx') self.connect('hx_fault_prot.delta_p_cold', 'motorfaultprot.delta_p_fault_prot_hx') self.connect('motorfaultprot.delta_p_stack', 'motor_duct.sta3.delta_p') self.connect('hx_motor.heat_transfer', 'motorfaultprot.heat_transfer_motor_hx') self.connect('hx_fault_prot.heat_transfer', 'motorfaultprot.heat_transfer_fault_prot_hx') self.connect('motorfaultprot.heat_transfer', 'motor_duct.sta3.heat_in') self.connect('hx_motor.T_out_hot', 'motorheatsink.T_in') self.connect('hx_fault_prot.T_out_hot', 'motorfaultprot.T_in') #=========BATTERY LOOP===================== self.add_subsystem('batteryheatsink', LiquidCooledBattery(num_nodes=nn, quasi_steady=False), promotes_inputs=[('battery_weight', 'ac|propulsion|battery|weight')]) self.connect('battery.heat_out', 'batteryheatsink.q_in') # self.connect('mdot_coolant_battery', ['batteryheatsink.mdot_coolant', 'hx_battery.mdot_hot', 'battery_hose.mdot_coolant', 'battery_coolant_pump.mdot_coolant']) self.connect('mdot_coolant_battery', [ 'batteryheatsink.mdot_coolant', 'refrig.mdot_coolant', 'hx_battery.mdot_hot', 'battery_hose.mdot_coolant', 'battery_coolant_pump.mdot_coolant' ]) self.connect('batteryheatsink.T_out', 'refrig.T_in_cold') self.connect('refrig.T_out_cold', 'batteryheatsink.T_in') self.add_subsystem('hx_battery', HXGroup(num_nodes=nn), promotes_inputs=hx_design_vars) self.connect('rho_coolant', [ 'hx_battery.rho_hot', 'battery_hose.rho_coolant', 'battery_coolant_pump.rho_coolant' ]) # Hot side balance param will be set to the cooling duct nozzle area self.add_subsystem('refrig', HeatPumpWithIntegratedCoolantLoop(num_nodes=nn), promotes_inputs=[ ('power_rating', 'ac|propulsion|thermal|heatpump|power_rating') ]) self.connect('heat_pump_eff_factor', 'refrig.eff_factor') self.connect('heat_pump_specific_power', 'refrig.specific_power') self.add_subsystem('variable_duct', ImplicitCompressibleDuct_ExternalHX(num_nodes=nn, cfg=0.95), promotes_inputs=[('p_inf', 'fltcond|p'), ('T_inf', 'fltcond|T'), ('Utrue', 'fltcond|Utrue')]) self.connect('variable_duct_nozzle_area', 'variable_duct.area_nozzle_in') self.add_subsystem('battery_hose', SimpleHose(num_nodes=nn), promotes_inputs=[ ('hose_length', 'ac|geom|thermal|hx_to_battery_length'), ('hose_diameter', 'ac|geom|thermal|hx_to_battery_diameter') ]) self.add_subsystem('battery_coolant_pump', SimplePump(num_nodes=nn), promotes_inputs=[ ('power_rating', 'ac|propulsion|thermal|hx|pump_power_rating') ]) self.promote_add( sources=['battery_hose.delta_p', 'hx_battery.delta_p_hot'], prom_name='pressure_drop_battery_loop', factors=[1.0, -1.0], vec_size=nn, units='Pa') self.connect('pressure_drop_battery_loop', 'battery_coolant_pump.delta_p') # in to HXGroup: self.connect('variable_duct.sta2.T', 'hx_battery.T_in_cold') self.connect('variable_duct.sta2.rho', 'hx_battery.rho_cold') self.connect('variable_duct.mdot', 'hx_battery.mdot_cold') #out from HXGroup self.connect('hx_battery.frontal_area', ['variable_duct.area_2', 'variable_duct.area_3']) self.connect('hx_battery.delta_p_cold', 'variable_duct.sta3.delta_p') self.connect('hx_battery.heat_transfer', 'variable_duct.sta3.heat_in') self.connect('hx_battery.T_out_hot', 'refrig.T_in_hot') self.connect('refrig.T_out_hot', 'hx_battery.T_in_hot') # self.connect('hx_battery.T_out_hot', 'batteryheatsink.T_in') # self.connect('batteryheatsink.T_out', 'hx_battery.T_in_hot') #=============WEIGHTS====================== # generally the weights module will be custom to each airplane # Motor, Battery, TMS, N+3 weight delta self.promote_add(sources=[ 'refrig.component_weight', 'hx_motor.component_weight', 'hx_battery.component_weight', 'battery_hose.component_weight', 'battery_coolant_pump.component_weight', 'motor_hose.component_weight', 'motor_coolant_pump.component_weight', 'hx_fault_prot.component_weight', 'hybrid_motor.component_weight' ], promoted_sources=['ac|weights|OEW'], prom_name='OEW', factors=[ 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 1.0 ], vec_size=1, units='kg') self.add_subsystem('weight', oc.AddSubtractComp(output_name='weight', input_names=[ 'ac|design_mission|TOW', 'fuel_used' ], units='kg', vec_size=[1, nn], scaling_factors=[1, -1]), promotes_inputs=['*'], promotes_outputs=['weight']) self.promote_add(sources=[], promoted_sources=[ 'ac|design_mission|TOW', 'OEW', 'fuel_used_final', 'ac|propulsion|battery|weight' ], prom_name='margin', factors=[1.0, -1.0, -1.0, -2.0], vec_size=1, units='kg')