Source code for openconcept.components.splitter

import numpy as np
from openmdao.api import ExplicitComponent
from openmdao.api import Group


[docs]class PowerSplit(ExplicitComponent): """ A power split mechanism for mechanical or electrical power. Inputs ------ power_in : float Power fed to the splitter. (vector, W) power_rating : float Maximum rated power of the split mechanism. (scalar, W) power_split_fraction: If `'rule'` is set to `'fraction'`, sets percentage of input power directed to Output A (minus losses). (vector, dimensionless) power_split_amount: If `'rule'` is set to `'fixed'`, sets amount of input power to Output A (minus losses). (vector, W) Outputs ------- power_out_A : float Power sent to first output (vector, W) power_out_B : float Power sent to second output (vector, W) heat_out : float Waste heat produced (vector, W) component_cost : float Nonrecurring cost of the component (scalar, USD) component_weight : float Weight of the component (scalar, kg) component_sizing_margin : float Equal to 1 when fed full rated power (vector, dimensionless) Options ------- num_nodes : int Number of analysis points to run (sets vec length; default 1) rule : str Power split control rule to use efficiency : float Component efficiency (default 1) weight_inc : float Weight per unit rated power (default 0, kg/W) weight_base : float Base weight (default 0, kg) cost_inc : float Nonrecurring cost per unit power (default 0, USD/W) cost_base : float Base cost (default 0 USD) """ def initialize(self): # define control rules self.options.declare('num_nodes', default=1, desc='Number of flight/control conditions') self.options.declare('rule', default='fraction', desc='Control strategy - fraction or fixed power') self.options.declare('efficiency', default=1., desc='Efficiency (dimensionless)') self.options.declare('weight_inc', default=0., desc='kg per input watt') self.options.declare('weight_base', default=0., desc='kg base weight') self.options.declare('cost_inc', default=0., desc='$ cost per input watt') self.options.declare('cost_base', default=0., desc='$ cost base') def setup(self): nn = self.options['num_nodes'] self.add_input('power_in', units='W', desc='Input shaft power or incoming electrical load', shape=(nn,)) self.add_input('power_rating', val=99999999, units='W', desc='Split mechanism power rating') rule = self.options['rule'] if rule == 'fraction': self.add_input('power_split_fraction', val=0.5, desc='Fraction of power to output A', shape=(nn,)) elif rule == 'fixed': self.add_input('power_split_amount', units='W', desc='Raw amount of power to output A', shape=(nn,)) else: msg = 'Specify either "fraction" or "fixed" as power split control rule' raise ValueError(msg) eta = self.options['efficiency'] weight_inc = self.options['weight_inc'] weight_base = self.options['weight_base'] cost_inc = self.options['cost_inc'] cost_base = self.options['cost_base'] self.add_output('power_out_A', units='W', desc='Output power or load to A', shape=(nn,)) self.add_output('power_out_B', units='W', desc='Output power or load to B', shape=(nn,)) self.add_output('heat_out', units='W', desc='Waste heat out', shape=(nn,)) self.add_output('component_cost', units='USD', desc='Splitter component cost') self.add_output('component_weight', units='kg', desc='Splitter component weight') self.add_output('component_sizing_margin', desc='Fraction of rated power', shape=(nn,)) if rule == 'fraction': self.declare_partials(['power_out_A', 'power_out_B'], ['power_in', 'power_split_fraction'], rows=range(nn), cols=range(nn)) elif rule == 'fixed': self.declare_partials(['power_out_A', 'power_out_B'], ['power_in', 'power_split_amount'], rows=range(nn), cols=range(nn)) self.declare_partials('heat_out', 'power_in', val=(1 - eta) * np.ones(nn), rows=range(nn), cols=range(nn)) self.declare_partials('component_cost', 'power_rating', val=cost_inc) self.declare_partials('component_weight', 'power_rating', val=weight_inc) self.declare_partials('component_sizing_margin', 'power_in', rows=range(nn), cols=range(nn)) self.declare_partials('component_sizing_margin', 'power_rating') def compute(self, inputs, outputs): nn = self.options['num_nodes'] rule = self.options['rule'] eta = self.options['efficiency'] weight_inc = self.options['weight_inc'] weight_base = self.options['weight_base'] cost_inc = self.options['cost_inc'] cost_base = self.options['cost_base'] if rule == 'fraction': outputs['power_out_A'] = inputs['power_in'] * inputs['power_split_fraction'] * eta outputs['power_out_B'] = inputs['power_in'] * (1 - inputs['power_split_fraction']) * eta elif rule == 'fixed': # check to make sure enough power is available # if inputs['power_in'] < inputs['power_split_amount']: not_enough_idx = np.where(inputs['power_in'] < inputs['power_split_amount']) po_A = np.zeros(nn) po_B = np.zeros(nn) po_A[not_enough_idx] = inputs['power_in'][not_enough_idx] * eta po_B[not_enough_idx] = np.zeros(nn)[not_enough_idx] # else: enough_idx = np.where(inputs['power_in'] >= inputs['power_split_amount']) po_A[enough_idx] = inputs['power_split_amount'][enough_idx] * eta po_B[enough_idx] = (inputs['power_in'][enough_idx] - inputs['power_split_amount'][enough_idx]) * eta outputs['power_out_A'] = po_A outputs['power_out_B'] = po_B outputs['heat_out'] = inputs['power_in'] * (1 - eta) outputs['component_cost'] = inputs['power_rating'] * cost_inc + cost_base outputs['component_weight'] = inputs['power_rating'] * weight_inc + weight_base outputs['component_sizing_margin'] = inputs['power_in'] / inputs['power_rating'] def compute_partials(self, inputs, J): nn = self.options['num_nodes'] rule = self.options['rule'] eta = self.options['efficiency'] if rule == 'fraction': J['power_out_A', 'power_in'] = inputs['power_split_fraction'] * eta J['power_out_A', 'power_split_fraction'] = inputs['power_in'] * eta J['power_out_B', 'power_in'] = (1 - inputs['power_split_fraction']) * eta J['power_out_B', 'power_split_fraction'] = -inputs['power_in'] * eta elif rule == 'fixed': not_enough_idx = np.where(inputs['power_in'] < inputs['power_split_amount']) enough_idx = np.where(inputs['power_in'] >= inputs['power_split_amount']) # if inputs['power_in'] < inputs['power_split_amount']: Jpo_A_pi = np.zeros(nn) Jpo_A_ps = np.zeros(nn) Jpo_B_pi = np.zeros(nn) Jpo_B_ps = np.zeros(nn) Jpo_A_pi[not_enough_idx] = eta * np.ones(nn)[not_enough_idx] Jpo_A_ps[not_enough_idx] = np.zeros(nn)[not_enough_idx] Jpo_B_pi[not_enough_idx] = np.zeros(nn)[not_enough_idx] Jpo_B_ps[not_enough_idx] = np.zeros(nn)[not_enough_idx] # else: Jpo_A_ps[enough_idx] = eta * np.ones(nn)[enough_idx] Jpo_A_pi[enough_idx] = np.zeros(nn)[enough_idx] Jpo_B_ps[enough_idx] = -eta * np.ones(nn)[enough_idx] Jpo_B_pi[enough_idx] = eta * np.ones(nn)[enough_idx] J['power_out_A', 'power_in'] = Jpo_A_pi J['power_out_A', 'power_split_amount'] = Jpo_A_ps J['power_out_B', 'power_in'] = Jpo_B_pi J['power_out_B', 'power_split_amount'] = Jpo_B_ps J['component_sizing_margin', 'power_in'] = 1 / inputs['power_rating'] J['component_sizing_margin', 'power_rating'] = - (inputs['power_in'] / inputs['power_rating'] ** 2)