def setup(self): nn = self.options['num_nodes'] eta_b = self.options['efficiency'] e_b = self.options['specific_energy'] p_b = self.options['specific_power'] cost_inc = self.options['cost_inc'] cost_base = self.options['cost_base'] defaults = [['SOC_initial', 'batt_SOC_initial', 1, None]] self.add_subsystem('defaults', DVLabel(defaults), promotes_inputs=["*"], promotes_outputs=["*"]) self.add_subsystem('batt_base',SimpleBattery(num_nodes=nn, efficiency=eta_b, specific_energy=e_b, specific_power=p_b, cost_inc=cost_inc, cost_base=cost_base), promotes_outputs=['*'],promotes_inputs=['*']) # change in SOC over time is (- elec_load) / max_energy self.add_subsystem('divider',ElementMultiplyDivideComp(output_name='dSOCdt',input_names=['elec_load','max_energy'],vec_size=[nn,1],scaling_factor=-1,divide=[False,True],input_units=['W','kJ']), promotes_inputs=['*'],promotes_outputs=['*']) nn_simpson = int((nn-1)/2) self.add_subsystem('intload',Integrator(num_intervals=nn_simpson, method='simpson', quantity_units=None, diff_units='s', time_setup='duration'), promotes_inputs=[('q_initial','batt_SOC_initial'),'duration',('dqdt','dSOCdt')], promotes_outputs=[('q','SOC'),('q_final','SOC_final')])
def setup(self): nn = self.options['num_nodes'] ivcomp = self.add_subsystem('const_settings', IndepVarComp(), promotes_outputs=["*"]) ivcomp.add_output('propulsor_active', val=np.ones(nn)) ivcomp.add_output('braking', val=np.zeros(nn)) # TODO feet fltcond|Ueas as control param ivcomp.add_output('fltcond|Ueas',val=np.ones((nn,))*90, units='m/s') # TODO feed fltcond|vs as control param ivcomp.add_output('fltcond|vs',val=np.ones((nn,))*1, units='m/s') ivcomp.add_output('zero_accel',val=np.zeros((nn,)),units='m/s**2') # TODO take out the integrator integ = self.add_subsystem('ode_integ', Integrator(num_nodes=nn, diff_units='s', time_setup='duration', method='simpson'), promotes_inputs=['fltcond|vs', 'fltcond|groundspeed'], promotes_outputs=['fltcond|h', 'range']) integ.add_integrand('fltcond|h', rate_name='fltcond|vs', val=1.0, units='m') # TODO Feed fltcond|h as state self.add_subsystem('atmos', ComputeAtmosphericProperties(num_nodes=nn, true_airspeed_in=False), promotes_inputs=['*'], promotes_outputs=['*']) self.add_subsystem('gs',Groundspeeds(num_nodes=nn),promotes_inputs=['*'],promotes_outputs=['*']) # add the user-defined aircraft model # TODO Can I promote up ac| quantities? self.add_subsystem('acmodel',self.options['aircraft_model'](num_nodes=nn, flight_phase=self.options['flight_phase']),promotes_inputs=['*'],promotes_outputs=['*']) self.add_subsystem('clcomp',SteadyFlightCL(num_nodes=nn), promotes_inputs=['*'],promotes_outputs=['*']) self.add_subsystem('lift',Lift(num_nodes=nn), promotes_inputs=['*'],promotes_outputs=['*']) self.add_subsystem('haccel',HorizontalAcceleration(num_nodes=nn), promotes_inputs=['*'],promotes_outputs=['*']) # TODO add range as a state integ.add_integrand('range', rate_name='fltcond|groundspeed', val=1.0, units='m') self.add_subsystem('steadyflt',BalanceComp(name='throttle',val=np.ones((nn,))*0.5,lower=0.01,upper=2.0,units=None,normalize=False,eq_units='m/s**2',rhs_name='accel_horiz',lhs_name='zero_accel',rhs_val=np.zeros((nn,))), promotes_inputs=['accel_horiz','zero_accel'],promotes_outputs=['throttle']) # TODO still needs a Newton solver
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'] eta_b = self.options['efficiency'] e_b = self.options['specific_energy'] p_b = self.options['specific_power'] cost_inc = self.options['cost_inc'] cost_base = self.options['cost_base'] # defaults = [['SOC_initial', 'batt_SOC_initial', 1, None]] # self.add_subsystem('defaults', DVLabel(defaults), # promotes_inputs=["*"], promotes_outputs=["*"]) self.add_subsystem('batt_base', SimpleBattery(num_nodes=nn, efficiency=eta_b, specific_energy=e_b, specific_power=p_b, cost_inc=cost_inc, cost_base=cost_base), promotes_outputs=['*'], promotes_inputs=['*']) # change in SOC over time is (- elec_load) / max_energy self.add_subsystem('divider', ElementMultiplyDivideComp( output_name='dSOCdt', input_names=['elec_load', 'max_energy'], vec_size=[nn, 1], scaling_factor=-1, divide=[False, True], input_units=['W', 'kJ']), promotes_inputs=['*'], promotes_outputs=['*']) integ = self.add_subsystem('ode_integ', Integrator(num_nodes=nn, method='simpson', diff_units='s', time_setup='duration'), promotes_inputs=['*'], promotes_outputs=['*']) integ.add_integrand('SOC', rate_name='dSOCdt', start_name='SOC_initial', end_name='SOC_final', units=None, val=1.0, start_val=1.0)
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_procs(self, pathname, comm, mode, prob_meta): time_units = self._oc_time_units try: num_nodes = prob_meta['oc_num_nodes'] except KeyError: raise NameError( 'Integrator group must be created within an OpenConcept phase') self.add_subsystem( 'ode_integ', Integrator(time_setup='duration', method='simpson', diff_units=time_units, num_nodes=num_nodes)) super(IntegratorGroup, self)._setup_procs(pathname, comm, mode, prob_meta)
def setup(self): segment_names = self.options['segment_names'] segments_to_count = self.options['segments_to_count'] quantity_units = self.options['quantity_units'] diff_units = self.options['diff_units'] n_int_per_seg = self.options['num_intervals'] integrator_option = self.options['integrator'] time_setup = self.options['time_setup'] if segment_names is None: nn_tot = (2*n_int_per_seg + 1) else: nn_tot = (2*n_int_per_seg + 1) * len(segment_names) iv = self.add_subsystem('iv', IndepVarComp()) self.add_subsystem('integral', Integrator(segment_names=segment_names, segments_to_count=segments_to_count, quantity_units=quantity_units, diff_units=diff_units, num_intervals=n_int_per_seg, method=integrator_option, time_setup=time_setup)) if quantity_units is None and diff_units is None: rate_units = None elif quantity_units is None: rate_units = '(' + diff_units +')** -1' elif diff_units is None: rate_units = quantity_units else: rate_units = '('+quantity_units+') / (' + diff_units +')' iv.add_output('rate_to_integrate', val=np.ones((nn_tot,)), units=rate_units) iv.add_output('initial_value', val=0, units=quantity_units) self.connect('iv.rate_to_integrate','integral.dqdt') self.connect('iv.initial_value', 'integral.q_initial') if segment_names is None: if time_setup == 'dt': iv.add_output('dt', val=1, units=diff_units) self.connect('iv.dt', 'integral.dt') elif time_setup == 'duration': iv.add_output('duration', val=1*(nn_tot-1), units=diff_units) self.connect('iv.duration', 'integral.duration') elif time_setup == 'bounds': iv.add_output('t_initial', val=2, units=diff_units) iv.add_output('t_final', val=2 + 1*(nn_tot-1)) self.connect('iv.t_initial','integral.t_initial') self.connect('iv.t_final','integral.t_final') else: for segment_name in segment_names: iv.add_output(segment_name + '|dt', val=1, units=diff_units) self.connect('iv.'+segment_name + '|dt','integral.'+segment_name + '|dt')
def setup(self): nn = self.options['num_nodes'] self.add_subsystem( 'rate', CoolantReservoirRate(num_nodes=nn), promotes_inputs=['T_in', 'T_out', 'mass', 'mdot_coolant']) self.add_subsystem( 'integratetemp', Integrator(num_intervals=int((nn - 1) / 2), quantity_units='K', diff_units='s', method='simpson', time_setup='duration'), promotes_inputs=['duration', ('q_initial', 'T_initial')], promotes_outputs=[('q', 'T_out'), ('q_final', 'T_final')]) self.connect('rate.dTdt', 'integratetemp.dqdt')
def setup(self): nn = self.options['num_nodes'] quasi_steady = self.options['quasi_steady'] self.add_subsystem( 'hex', BandolierCoolingSystem( num_nodes=nn, coolant_specific_heat=self.options['coolant_specific_heat'], fluid_k=self.options['fluid_k'], nusselt=self.options['nusselt'], cell_kr=self.options['cell_kr'], cell_diameter=self.options['cell_diameter'], cell_height=self.options['cell_height'], cell_mass=self.options['cell_mass'], cell_specific_heat=self.options['cell_specific_heat'], battery_weight_fraction=self.options['battery_weight_fraction'] ), promotes_inputs=[ 'q_in', 'mdot_coolant', 'T_in', ('T_battery', 'T'), 'battery_weight', 'n_cpb', 't_channel' ], promotes_outputs=['T_core', 'T_surface', 'T_out', 'dTdt']) if not quasi_steady: ode_integ = self.add_subsystem('ode_integ', Integrator(num_nodes=nn, diff_units='s', method='simpson', time_setup='duration'), promotes_outputs=['*'], promotes_inputs=['*']) ode_integ.add_integrand('T', rate_name='dTdt', units='K', lower=1e-10) else: self.add_subsystem('thermal_bal', om.BalanceComp('T', eq_units='K/s', lhs_name='dTdt', rhs_val=0.0, units='K', lower=1.0, val=299. * np.ones((nn, ))), promotes_inputs=['dTdt'], promotes_outputs=['T'])
def setup(self): nn = self.options['num_nodes'] self.add_subsystem( 'rate', CoolantReservoirRate(num_nodes=nn), promotes_inputs=['T_in', 'T_out', 'mass', 'mdot_coolant']) ode_integ = self.add_subsystem('ode_integ', Integrator(num_nodes=nn, diff_units='s', method='simpson', time_setup='duration'), promotes_outputs=['*'], promotes_inputs=['*']) ode_integ.add_integrand('T_out', rate_name='dTdt', start_name='T_initial', end_name='T_final', units='K', lower=1e-10) self.connect('rate.dTdt', 'dTdt')
def setup(self): nn = self.options['num_nodes'] quasi_steady = self.options['quasi_steady'] self.add_subsystem( 'hex', MotorCoolingJacket( num_nodes=nn, coolant_specific_heat=self.options['coolant_specific_heat'], motor_specific_heat=self.options['motor_specific_heat'], case_cooling_coefficient=self. options['case_cooling_coefficient']), promotes_inputs=[ 'q_in', 'T_in', 'T', 'power_rating', 'mdot_coolant', 'motor_weight' ], promotes_outputs=['T_out', 'dTdt']) if not quasi_steady: ode_integ = self.add_subsystem('ode_integ', Integrator(num_nodes=nn, diff_units='s', method='simpson', time_setup='duration'), promotes_outputs=['*'], promotes_inputs=['*']) ode_integ.add_integrand('T', rate_name='dTdt', units='K', lower=1e-10) else: self.add_subsystem('thermal_bal', om.BalanceComp('T', eq_units='K/s', lhs_name='dTdt', rhs_val=0.0, units='K', lower=1.0, val=299. * np.ones((nn, ))), promotes_inputs=['dTdt'], promotes_outputs=['T'])
def setup(self): nn = self.options['num_nodes'] quasi_steady = self.options['quasi_steady'] if not quasi_steady: self.add_subsystem( 'base', ThermalComponentWithMass( specific_heat=self.options['specific_heat_object'], num_nodes=nn), promotes_inputs=['q_in', 'mass']) ode_integ = self.add_subsystem('ode_integ', Integrator(num_nodes=nn, diff_units='s', method='simpson', time_setup='duration'), promotes_outputs=['*'], promotes_inputs=['*']) ode_integ.add_integrand('T', rate_name='dTdt', units='K', lower=1e-10) self.connect('base.dTdt', 'dTdt') else: self.add_subsystem('base', ThermalComponentMassless(num_nodes=nn), promotes_inputs=['q_in'], promotes_outputs=[('T_object', 'T')]) self.add_subsystem( 'hex', ConstantSurfaceTemperatureColdPlate_NTU( num_nodes=nn, specific_heat=self.options['specific_heat_coolant']), promotes_inputs=[ 'T_in', ('T_surface', 'T'), 'n_parallel', 'channel*', 'mdot_coolant' ], promotes_outputs=['T_out']) self.connect('hex.q', 'base.q_out')
def _setup_procs(self, pathname, comm, mode, prob_meta): time_units = self._oc_time_units self._under_dymos = False self._under_openconcept = False try: num_nodes = prob_meta['oc_num_nodes'] self._under_openconcept = True except KeyError: # TODO test_if_under_dymos if not self._under_dymos: raise NameError( 'Integrator group must be created within an OpenConcept phase or Dymos trajectory' ) if self._under_openconcept: self.add_subsystem( 'ode_integ', Integrator(time_setup='duration', method='simpson', diff_units=time_units, num_nodes=num_nodes)) super(IntegratorGroup, self)._setup_procs(pathname, comm, mode, prob_meta)
def setup(self): nn = self.options['num_nodes'] quasi_steady = self.options['quasi_steady'] if not quasi_steady: self.add_subsystem( 'base', ThermalComponentWithMass( specific_heat=self.options['specific_heat_object'], num_nodes=nn), promotes_inputs=['q_in', 'mass']) self.add_subsystem( 'integratetemp', Integrator(num_intervals=int((nn - 1) / 2), quantity_units='K', diff_units='s', method='simpson', time_setup='duration'), promotes_inputs=['duration', ('q_initial', 'T_initial')], promotes_outputs=[('q', 'T'), ('q_final', 'T_final')]) self.connect('base.dTdt', 'integratetemp.dqdt') else: self.add_subsystem('base', ThermalComponentMassless(num_nodes=nn), promotes_inputs=['q_in'], promotes_outputs=['T']) self.add_subsystem( 'hex', ConstantSurfaceTemperatureColdPlate_NTU( num_nodes=nn, specific_heat=self.options['specific_heat_coolant']), promotes_inputs=[ 'T_in', ('T_surface', 'T'), 'n_parallel', 'channel*', 'mdot_coolant' ], promotes_outputs=['T_out']) self.connect('hex.q', 'base.q_out')
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('prop|rpm', val=np.ones((nn, )) * 1900, 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", "propulsor_active" ] self.add_subsystem('propmodel', TwinTurbopropPropulsionSystem(num_nodes=nn), promotes_inputs=propulsion_promotes_inputs, promotes_outputs=propulsion_promotes_outputs) self.connect('prop|rpm', ['propmodel.prop1.rpm', 'propmodel.prop2.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.propellers_weight', 'W_propeller') self.connect('propmodel.engines_weight', 'W_engine') # 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): quantity_units = self.options['quantity_units'] diff_units = self.options['diff_units'] rate_units = self.options['rate_units'] num_nodes = self.options['num_nodes'] integrator_option = self.options['integrator'] time_setup = self.options['time_setup'] second_integrand = self.options['second_integrand'] zero_start = self.options['zero_start'] final_only = self.options['final_only'] test_auto_names = self.options['test_auto_names'] val = self.options['val'] num_nodes = num_nodes iv = self.add_subsystem('iv', IndepVarComp()) integ = Integrator(diff_units=diff_units, num_nodes=num_nodes, method=integrator_option, time_setup=time_setup) if not test_auto_names: integ.add_integrand('q', rate_name='dqdt', start_name='q_initial', end_name='q_final', units=quantity_units, rate_units=rate_units, zero_start=zero_start, final_only=final_only, val=val) else: integ.add_integrand('q', units=quantity_units, rate_units=rate_units, zero_start=zero_start, final_only=final_only) if second_integrand: integ.add_integrand('q2', rate_name='dq2dt', start_name='q2_initial', end_name='q2_final', units='kJ') iv.add_output('rate_to_integrate_2', val=np.ones((num_nodes, )), units='kW') iv.add_output('initial_value_2', val=0., units='kJ') self.connect('iv.rate_to_integrate_2', 'integral.dq2dt') self.connect('iv.initial_value_2', 'integral.q2_initial') self.add_subsystem('integral', integ) if rate_units and quantity_units: # overdetermined and possibly inconsistent pass elif not rate_units and not quantity_units: if diff_units: rate_units = '(' + diff_units + ')** -1' elif not rate_units: # solve for rate_units in terms of quantity_units if not diff_units: rate_units = quantity_units else: rate_units = '(' + quantity_units + ') / (' + diff_units + ')' elif not quantity_units: # solve for quantity units in terms of rate units if not diff_units: quantity_units = rate_units else: quantity_units = '(' + rate_units + ')*(' + diff_units + ')' iv.add_output('rate_to_integrate', val=np.ones((num_nodes, )), units=rate_units) iv.add_output('initial_value', val=0, units=quantity_units) if not test_auto_names: self.connect('iv.rate_to_integrate', 'integral.dqdt') else: self.connect('iv.rate_to_integrate', 'integral.q_rate') if not zero_start: self.connect('iv.initial_value', 'integral.q_initial') if time_setup == 'dt': iv.add_output('dt', val=1, units=diff_units) self.connect('iv.dt', 'integral.dt') elif time_setup == 'duration': iv.add_output('duration', val=1 * (num_nodes - 1), units=diff_units) self.connect('iv.duration', 'integral.duration') elif time_setup == 'bounds': iv.add_output('t_initial', val=2, units=diff_units) iv.add_output('t_final', val=2 + 1 * (num_nodes - 1), units=diff_units) self.connect('iv.t_initial', 'integral.t_initial') self.connect('iv.t_final', 'integral.t_final')