Propulsion Modeling

OpenConcept is designed to facilitate bottoms-up modeling of aircraft propulsion architectures with conceptual-level fidelity. Electric and fuel-burning components are supported.

Single Turboprop Example

This example illustrates the simples possible case (turboprop engine connected to a propeller). The propulsion system is instantiated as an OpenMDAO Group.

Source: `examples/propulsion_layouts/simple_turboprop.py`

class TurbopropPropulsionSystem(Group):
    """This is an example model of the simplest possible propulsion system
        consisting of a constant-speed prop and a turboshaft.

        This is the Pratt and Whitney Canada PT6A-66D with 4-bladed
        propeller used by the SOCATA-DAHER TBM-850.

        Inputs
        ------
        ac|propulsion|engine|rating : float
            The maximum rated shaft power of the engine
        ac|propulsion|propeller|diameter : float
            Diameter of the propeller

        Options
        -------
        num_nodes : float
            Number of analysis points to run (default 1)
    """
    def initialize(self):
        self.options.declare('num_nodes', default=1, desc="Number of mission analysis points to run")

    def setup(self):
        nn = self.options['num_nodes']

        # rename incoming design variables
        dvlist = [['ac|propulsion|engine|rating', 'eng1_rating', 850, 'hp'],
                  ['ac|propulsion|propeller|diameter', 'prop1_diameter', 2.3, 'm']]
        self.add_subsystem('dvs', DVLabel(dvlist),
                           promotes_inputs=["*"], promotes_outputs=["*"])

        # introduce model components
        self.add_subsystem('eng1',
                           SimpleTurboshaft(num_nodes=nn, weight_inc=0.14 / 1000, weight_base=104),
                           promotes_inputs=["throttle"], promotes_outputs=["fuel_flow"])
        self.add_subsystem('prop1',
                           SimplePropeller(num_nodes=nn, num_blades=4,
                                           design_J=2.2, design_cp=0.55),
                           promotes_inputs=["fltcond|*"], promotes_outputs=["thrust"])

        # connect design variables to model component inputs
        self.connect('eng1_rating', 'eng1.shaft_power_rating')
        self.connect('eng1_rating', 'prop1.power_rating')
        self.connect('prop1_diameter', 'prop1.diameter')

        # connect components to each other
        self.connect('eng1.shaft_power_out', 'prop1.shaft_power_in')

Series Hybrid Example

This example illustrates the complexities which arise when electrical components are included.

Source: `examples.propulsion_layouts.simple_series_hybrid.py`

class SingleSeriesHybridElectricPropulsionSystem(Group):
    """This is an example model of a series-hybrid propulsion system. One motor
        draws electrical load from two sources in a fractional split| a battery pack,
        and a turbogenerator setup. The control inputs are the power split fraction and the
        motor throttle setting; the turboshaft throttle matches the power level necessary
        to drive the generator at the required power level.

        Fuel flows and prop thrust should be fairly accurate.
        Heat constraints haven't yet been incorporated.

        The "pilot" controls thrust by varying the motor throttles from 0 to 100+% of rated power.
        She may also vary the percentage of battery versus fuel being
        used by varying the power_split_fraction.

        This module alone cannot produce accurate fuel flows, battery loads, etc.
        You must do the following, either with an implicit solver or with the optimizer:
        - Set eng1.throttle such that gen1.elec_power_out = hybrid_split.power_out_A

        The battery does not track its own state of charge (SOC);
        it is connected to elec_load simply so that the discharge rate can be compared to
        the discharge rate capability of the battery. SOC and fuel flows should be time-integrated
        at a higher level (in the mission analysis codes).

        Inputs
        ------
        ac|propulsion|engine|rating : float
            Turboshaft range extender power rating (scalar, kW)
        ac|propulsion|propeller|diameter : float
            Propeller diameter (scalar, m)
        ac|propulsion|motor|rating : float
            Motor power rating (scalar, kW)
        ac|propulsion|generator|rating : float
            Range extender elec gen rating (scalar, kW)
        ac|weights|W_battery : float
            Battery weight (scalar, kg)

        TODO list all the control inputs

        Outputs
        -------
        thrust : float
            Propulsion system total thrust (vector, N)
        fuel_flow : float
            Fuel flow consumed by the turboshaft (vector, kg/s)

        Options
        -------
        num_nodes : float
            Number of analysis points to run (default 1)
        specific_energy : float
            Battery specific energy (default 300 Wh/kg)
    """
    def initialize(self):
        self.options.declare('num_nodes', default=1, desc="Number of mission analysis points to run")
        self.options.declare('specific_energy', default=300, desc="Battery spec energy in Wh/kg")

    def setup(self):
        nn = self.options['num_nodes']
        e_b = self.options['specific_energy']

        # define design variables that are independent of flight condition or control states
        dvlist = [['ac|propulsion|engine|rating', 'eng_rating', 260.0, 'kW'],
                    ['ac|propulsion|propeller|diameter', 'prop_diameter', 2.5, 'm'],
                    ['ac|propulsion|motor|rating', 'motor_rating', 240.0, 'kW'],
                    ['ac|propulsion|generator|rating', 'gen_rating', 250.0, 'kW'],
                    ['ac|weights|W_battery', 'batt_weight', 2000, 'kg']]
        self.add_subsystem('dvs', DVLabel(dvlist), promotes_inputs=["*"], promotes_outputs=["*"])

        # introduce model components
        self.add_subsystem('motor1', SimpleMotor(efficiency=0.97, num_nodes=nn))
        self.add_subsystem('prop1', SimplePropeller(num_nodes=nn),
                           promotes_inputs=["fltcond|*"], promotes_outputs=['thrust'])
        self.connect('motor1.shaft_power_out', 'prop1.shaft_power_in')

        self.add_subsystem('hybrid_split', PowerSplit(rule='fraction', num_nodes=nn))
        self.connect('motor1.elec_load', 'hybrid_split.power_in')

        self.add_subsystem('eng1',
                           SimpleTurboshaft(num_nodes=nn,
                                            weight_inc=0.14 / 1000,
                                            weight_base=104),
                           promotes_outputs=["fuel_flow"])
        self.add_subsystem('gen1',SimpleGenerator(efficiency=0.97, num_nodes=nn))

        self.connect('eng1.shaft_power_out', 'gen1.shaft_power_in')

        self.add_subsystem('batt1', SimpleBattery(num_nodes=nn, specific_energy=e_b))
        self.connect('hybrid_split.power_out_A', 'batt1.elec_load')

        # need to use the optimizer to drive hybrid_split.power_out_B to the
        # same value as gen1.elec_power_out.
        # create a residual equation for power in vs power out from the generator
        self.add_subsystem('eng_gen_resid',
                           AddSubtractComp(output_name='eng_gen_residual',
                                           input_names=['gen_power_available', 'gen_power_required'],
                                           vec_size=nn, units='kW',
                                           scaling_factors=[1, -1]))
        self.connect('hybrid_split.power_out_B', 'eng_gen_resid.gen_power_required')
        self.connect('gen1.elec_power_out', 'eng_gen_resid.gen_power_available')

        # add the weights of all the motors and props
        # (forward-compatibility for twin series hybrid layout)
        addweights = AddSubtractComp(output_name='motors_weight',
                                     input_names=['motor1_weight'],
                                     units='kg')
        addweights.add_equation(output_name='propellers_weight',
                                input_names=['prop1_weight'],
                                units='kg')
        self.add_subsystem('add_weights', subsys=addweights,
                           promotes_inputs=['*'],promotes_outputs=['*'])

        self.connect('motor1.component_weight', 'motor1_weight')
        self.connect('prop1.component_weight', 'prop1_weight')

        #connect design variables to model component inputs
        self.connect('eng_rating', 'eng1.shaft_power_rating')
        self.connect('prop_diameter', ['prop1.diameter'])
        self.connect('motor_rating', ['motor1.elec_power_rating'])
        self.connect('motor_rating', ['prop1.power_rating'])
        self.connect('gen_rating', 'gen1.elec_power_rating')
        self.connect('batt_weight', 'batt1.battery_weight')

Components