Recuperated Brayton Cycle
Introduction
As shown in the Brayton Cycle modelingguide, and the given input files closed_brayton_cycle.i and open_brayton_cycle.i, a Brayton Cycle Power Conversion Unit (PCU) consists of a motor, compressor, turbine, and generator all coupled by a single shaft.
Detailed descriptions of the compressor and turbine components used in this example can be found in the Brayton Cycle modeling guide. In the aforementioned example, a simplified startup transient with a simplified heat source was conducted which demonstrated the Thermal Hydraulics module’s capability to produce torque, power, mass flow rate, and pressure ratios for all PCU components based on the shaft speed.
In this example a different heat source and recuperator were added to the same open Brayton Cycle PCU to demonstrate a more nuanced model along with a more realistic piping structure. Also, a Proportional-Integral-Derivative (PID) controller was added to the motor to conduct a more realistic startup transient of the system.
A heat rate of 105.75 kW is applied as a uniform volumetric source to a 10-m-long steel tube with an outer radius of 15 cm. Gas which has been compressed in the compressor, and preheated in the recuperator, passes over the exterior of the heat structure and removes heat via forced convection.
The recuperator is represented by an annular cylindrical heat structure of the same material and width as the main heat source but only 5 m in length and without internal heat generation. Instead, hot exhaust gas leaving the turbine transfers heat to the inside of the recuperator, and cooler gas from the compressor outlet removes heat from the outer surface. This preheats the gas entering the main heat source and improves thermal efficiency of the cycle.
Transient Description
Initially the shaft system is at rest, and the working fluid and heat structures are the ambient temperature and pressure. At t = 0 s the motor PID activates and ramps the PCU shaft to approximately 87,000 RPM over the course of 1600 s. When t = 1000 s, heat generation in the main heat structure is activated and linearly increases from 0 kW to a maximum power of 105.75 kW by t = 8600 s.
As the working fluid begins to heat, torque supplied to the shaft from the turbine increases. The motor PID control logic slowly decreases the torque supplied by the motor to 0 N·m once the turbine torque is greater than the torque supplied by the motor. This allows for a graceful transition of PCU control from the motor PID to the working fluid and heat source, and prevents drastically over-speeding the system above the rated speed of 96,000 RPM.
Input File description
The recuperated Brayton Cycle example is executed with the input file recuperated_brayton_cycle.i. Shown below in Figure 1 is a diagram of the modeled system.

Figure 1: Diagram of the recuperated Brayton Cycle.
All 90° pipe bends are modeled using VolumeJunction1Phase as shown in the example below:
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[junction2_cold_leg]
type = VolumeJunction1Phase<<<{"description": "Junction between 1-phase flow channels that has a non-zero volume", "href": "../../../../source/components/VolumeJunction1Phase.html"}>>>
connections<<<{"description": "Junction connections"}>>> = 'pipe2:out cold_leg:in'
position<<<{"description": "Spatial position of the center of the junction [m]"}>>> = '${x3} ${y3} 0'
volume<<<{"description": "Volume of the junction [m^3]"}>>> = '${fparse A2*0.1}'
[]
[]
The main heat source is modeled as a heat structure with internal heat generation and forced convection heat transfer to Pipe 4.
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[reactor]
type = HeatStructureCylindrical<<<{"description": "Cylindrical heat structure", "href": "../../../../source/components/HeatStructureCylindrical.html"}>>>
orientation<<<{"description": "Direction of axis from start position to end position (no need to normalize)"}>>> = '-1 0 0'
position<<<{"description": "Start position of axis in 3-D space [m]"}>>> = '${x4} ${y4} 0'
length<<<{"description": "Length of each axial section [m]"}>>> = ${L4}
widths<<<{"description": "Width of each radial region [m]"}>>> = 0.15
n_elems<<<{"description": "Number of elements in each axial section"}>>> = ${n_elems4}
n_part_elems<<<{"description": "Number of elements of each radial region"}>>> = 2
names<<<{"description": "Name of each radial region"}>>> = core
solid_properties<<<{"description": "Solid properties object name for each radial region"}>>> = steel
solid_properties_T_ref<<<{"description": "Density reference temperatures for each radial region. This is required if 'solid_properties' is provided. The density in each region will be a constant value computed by evaluating the density function at the reference temperature."}>>> = '300'
[]
[]
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[total_power]
type = TotalPower<<<{"description": "Prescribes total power via a user supplied value", "href": "../../../../source/components/TotalPower.html"}>>>
power<<<{"description": "Total power [W]"}>>> = 0
[]
[]
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[heat_generation]
type = HeatSourceFromTotalPower<<<{"description": "Heat generation from total power", "href": "../../../../source/components/HeatSourceFromTotalPower.html"}>>>
power<<<{"description": "Component that provides total power"}>>> = total_power
hs<<<{"description": "Heat structure in which to apply heat source"}>>> = reactor
regions<<<{"description": "Heat structure regions where heat generation is to be applied"}>>> = core
[]
[]
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[heat_transfer]
type = HeatTransferFromHeatStructure1Phase<<<{"description": "Connects a 1-phase flow channel and a heat structure", "href": "../../../../source/components/HeatTransferFromHeatStructure1Phase.html"}>>>
flow_channel<<<{"description": "Name of flow channel component to connect to"}>>> = pipe4
hs<<<{"description": "Heat structure name"}>>> = reactor
hs_side<<<{"description": "Heat structure side"}>>> = OUTER
Hw<<<{"description": "Convective heat transfer coefficient [W/(m^2-K)]"}>>> = 10000
[]
[]
The Recuperator is modeled as a heat structure without internal heat generation and heat transfer to both cold_leg and hot_leg.
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[recuperator]
type = HeatStructureCylindrical<<<{"description": "Cylindrical heat structure", "href": "../../../../source/components/HeatStructureCylindrical.html"}>>>
orientation<<<{"description": "Direction of axis from start position to end position (no need to normalize)"}>>> = '0 -1 0'
position<<<{"description": "Start position of axis in 3-D space [m]"}>>> = '${x3} ${y3} 0'
length<<<{"description": "Length of each axial section [m]"}>>> = '${fparse L3/2}'
widths<<<{"description": "Width of each radial region [m]"}>>> = ${recuperator_width}
n_elems<<<{"description": "Number of elements in each axial section"}>>> = '${fparse n_elems3/2}'
n_part_elems<<<{"description": "Number of elements of each radial region"}>>> = 2
names<<<{"description": "Name of each radial region"}>>> = recuperator
solid_properties<<<{"description": "Solid properties object name for each radial region"}>>> = steel
solid_properties_T_ref<<<{"description": "Density reference temperatures for each radial region. This is required if 'solid_properties' is provided. The density in each region will be a constant value computed by evaluating the density function at the reference temperature."}>>> = '300'
inner_radius<<<{"description": "Inner radius of the heat structure [m]"}>>> = ${D1}
[]
[]
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[heat_transfer_cold_leg]
type = HeatTransferFromHeatStructure1Phase<<<{"description": "Connects a 1-phase flow channel and a heat structure", "href": "../../../../source/components/HeatTransferFromHeatStructure1Phase.html"}>>>
flow_channel<<<{"description": "Name of flow channel component to connect to"}>>> = cold_leg
hs<<<{"description": "Heat structure name"}>>> = recuperator
hs_side<<<{"description": "Heat structure side"}>>> = OUTER
Hw<<<{"description": "Convective heat transfer coefficient [W/(m^2-K)]"}>>> = 10000
[]
[]
[Components<<<{"href": "../../../../syntax/Components/index.html"}>>>]
[heat_transfer_hot_leg]
type = HeatTransferFromHeatStructure1Phase<<<{"description": "Connects a 1-phase flow channel and a heat structure", "href": "../../../../source/components/HeatTransferFromHeatStructure1Phase.html"}>>>
flow_channel<<<{"description": "Name of flow channel component to connect to"}>>> = hot_leg
hs<<<{"description": "Heat structure name"}>>> = recuperator
hs_side<<<{"description": "Heat structure side"}>>> = INNER
Hw<<<{"description": "Convective heat transfer coefficient [W/(m^2-K)]"}>>> = 10000
[]
[]
Control Logic
One of the main changes from the Brayton Cycle modeling guide is the addition of a PID controller to the motor for the start up transient. This PID control operates a 3-Phase electric motor which applies torque to the shaft to reach a desired speed set by the user, in this case 87,000 RPM. A speed lower than the rated turbine speed of 96,000 RPM was chosen to prevent significant over-speeding of the turbine during the start up transient.
As the shaft increases in speed, the compressor begins to compress the inlet atmospheric air and produces a coolant flow through the system. Once the heat source begins heating the coolant, the gas begins conducting work on the system via the turbine. However, until a fully developed and fully heated flow is established, the working fluid still requires an assist from the motor to maintain shaft speed and adequate coolant mass flow rate. At this point, a direct shutoff of the motor would cause the system to stall. Conversely, if the motor were to supply a constant torque the shaft would over speed due to the increasing torque supplied by the turbine from the heating coolant.
To prevent stalling or significant over-speeding of the shaft while the coolant is reaching a fully developed and fully heated flow, a shutdown function is applied to the motor. A linearly decreasing torque is applied to the motor starting from the time the turbine first provides more torque and reaches 0 N·m after 35,000 s. This function uses ControlLogic
to determine when the torque supplied by the turbine is greater than the motor, and initiates a graceful ramp down of the motor. The proceeding sections discuss in detail how this control logic is implemented in the input file.
PID Control
Shown below is the ControlLogic
block containing the PID block utilizing PIDControl. Here, the , , and variables are the proportional, integral, and derivative gains respectively. As with any PID control, gains must be determined for a specific system and are not universal. In this specific PID, the output signal is used to govern the torque supplied by the motor to the system shaft based upon the current shaft speed.
[ControlLogic<<<{"href": "../../../../syntax/ControlLogic/index.html"}>>>]
[initial_motor_PID]
type = PIDControl<<<{"description": "Declares a control data named 'output' and uses Proportional Integral Derivative logic on the 'value' control data to set it.", "href": "../../../../source/controllogic/PIDControl.html"}>>>
set_point<<<{"description": "The name of the control data with the set point."}>>> = set_point:value
input<<<{"description": "The name of the control data that we read in."}>>> = shaft_RPM
initial_value<<<{"description": "The initial value for the integral part."}>>> = 0
K_p<<<{"description": "The coefficient for the proportional term."}>>> = 0.0011
K_i<<<{"description": "The coefficient for the integral term."}>>> = 0.00000004
K_d<<<{"description": "The coefficient for the derivative term."}>>> = 0
[]
[]
The PID attempts to reach a desired shaft speed, in RPM, which is determined by the set_point
block using GetFunctionValueControl. The rated speed of the turbine is set at the beginning of the input file as a global parameter speed_rated_rpm
. To prevent over-speeding of the turbine and compressor, the set point of the PID is adjusted to be lower than the rated turbine speed.
[ControlLogic<<<{"href": "../../../../syntax/ControlLogic/index.html"}>>>]
[set_point]
type = GetFunctionValueControl<<<{"description": "Sets a ControlData named 'value' with the value of a function", "href": "../../../../source/controllogic/GetFunctionValueControl.html"}>>>
function<<<{"description": "The name of the function prescribing a value."}>>> = '${fparse speed_rated_rpm - 9000}'
[]
[]
A ParsedFunctionControl is applied to determine whether to send the PID output to the motor or shutdown the motor because the turbine is supplying enough torque.
[ControlLogic<<<{"href": "../../../../syntax/ControlLogic/index.html"}>>>]
[logic]
type = ParsedFunctionControl<<<{"description": "Control that evaluates a parsed function", "href": "../../../../source/controllogic/ParsedFunctionControl.html"}>>>
function<<<{"description": "The function to be evaluated by this control."}>>> = 'if(motor+0.5 > turb, PID, shutdown_fn)'
symbol_names<<<{"description": "Symbols (excluding t,x,y,z) that are bound to the values provided by the corresponding items in the vals vector."}>>> = 'motor turb PID shutdown_fn'
symbol_values<<<{"description": "Constant numeric values, postprocessor names, function names, and scalar variables corresponding to the symbols in symbol_names."}>>> = 'motor_torque turbine_torque initial_motor_PID:output motor_torque_fn_shutdown'
[]
[]
If logic
determines to send the PID signal to the motor, then SetComponentRealValueControl motor_PID
is used to apply the signal directly to the motor
component torque.
[ControlLogic<<<{"href": "../../../../syntax/ControlLogic/index.html"}>>>]
[motor_PID]
type = SetComponentRealValueControl<<<{"description": "Control to set a floating point (Real) value of a component parameter with control data boolean", "href": "../../../../source/controllogic/SetComponentRealValueControl.html"}>>>
component<<<{"description": "The name of the component to be controlled."}>>> = motor
parameter<<<{"description": "The name of the parameter in the component to be controlled."}>>> = torque
value<<<{"description": "The name of control data to be set in the component."}>>> = logic:value
[]
[]
Motor Shutdown
Should the turbine provide more torque than the motor, the logic activates a ParsedFunction, motor_torque_fn_shutdown
, to ramp down the motor allowing for a graceful transition of the system from the motor to the turbine.
[Functions<<<{"href": "../../../../syntax/Functions/index.html"}>>>]
[motor_torque_fn_shutdown]
type = ParsedFunction<<<{"description": "Function created by parsing a string", "href": "../../../../source/functions/MooseParsedFunction.html"}>>>
symbol_values<<<{"description": "Constant numeric values, postprocessor names, function names, and scalar variables corresponding to the symbols in symbol_names."}>>> = 'PID_trip_status time_trip'
symbol_names<<<{"description": "Symbols (excluding t,x,y,z) that are bound to the values provided by the corresponding items in the vals vector."}>>> = 'PID_trip_status time_trip'
expression<<<{"description": "The user defined function."}>>> = 'if(PID_trip_status = 1, max(2.4 - (2.4 * ((t - time_trip) / 35000)),0.0), 1)'
[]
[]
The shutdown function utilizes AuxVariables
values PID_trip_status
and time_trip
. These values are dependent upon a number of factors including the motor PID, startup rate of the heat source, heat source total power, etc. The user has no way of knowing these values while writing the input file, so they must be determined during input file execution. The following steps outline one method to determine, store, and apply initially unknown variables within an input file.
AuxVariables
First, AuxVariables
are used to create two variables, time_trip
and PID_trip_status
. time_trip
is used to store the time at which turbine torque is greater than the motor torque. PID_trip_status
is used to change a trip status from 0 to 1.
[AuxVariables<<<{"href": "../../../../syntax/AuxVariables/index.html"}>>>]
[time_trip]
order<<<{"description": "Specifies the order of the FE shape function to use for this variable (additional orders not listed are allowed)"}>>> = FIRST
family<<<{"description": "Specifies the family of FE shape functions to use for this variable"}>>> = SCALAR
[]
[]
[AuxVariables<<<{"href": "../../../../syntax/AuxVariables/index.html"}>>>]
[PID_trip_status]
order<<<{"description": "Specifies the order of the FE shape function to use for this variable (additional orders not listed are allowed)"}>>> = FIRST
family<<<{"description": "Specifies the family of FE shape functions to use for this variable"}>>> = SCALAR
initial_condition<<<{"description": "Specifies a constant initial condition for this variable"}>>> = 0
[]
[]
Functions
Next, four Functions
are created which are used to output the desired values for the previously mentioned AuxVariables
. These functions are PID_tripped_status_fn
, PID_tripped_constant_value
, time_fn
, and is_tripped_fn
.
time_fn
records the current simulation time.
[Functions<<<{"href": "../../../../syntax/Functions/index.html"}>>>]
[time_fn]
type = ParsedFunction<<<{"description": "Function created by parsing a string", "href": "../../../../source/functions/MooseParsedFunction.html"}>>>
expression<<<{"description": "The user defined function."}>>> = t
[]
[]
is_tripped_fn
determines if the turbine torque is greater than the motor torque using the Postprocessors
values turbine_torque
and motor_torque
.
[Functions<<<{"href": "../../../../syntax/Functions/index.html"}>>>]
[is_tripped_fn]
type = ParsedFunction<<<{"description": "Function created by parsing a string", "href": "../../../../source/functions/MooseParsedFunction.html"}>>>
symbol_names<<<{"description": "Symbols (excluding t,x,y,z) that are bound to the values provided by the corresponding items in the vals vector."}>>> = 'motor_torque turbine_torque'
symbol_values<<<{"description": "Constant numeric values, postprocessor names, function names, and scalar variables corresponding to the symbols in symbol_names."}>>> = 'motor_torque turbine_torque'
expression<<<{"description": "The user defined function."}>>> = 'turbine_torque > motor_torque'
[]
[]
PID_tripped_constant_value
creates a constant function with a value of 1. This is later used to change the PID trip status from 0 to 1. 0 indicates the motor is supplying more torque than the turbine, 1 indicates the turbine is supplying more torque than the motor.
[Functions<<<{"href": "../../../../syntax/Functions/index.html"}>>>]
[PID_tripped_constant_value]
type = ConstantFunction<<<{"description": "A function that returns a constant value as defined by an input parameter.", "href": "../../../../source/functions/ConstantFunction.html"}>>>
value<<<{"description": "The constant value"}>>> = 1
[]
[]
PID_tripped_status_fn
stores the value from the Control
PID_trip_status
(which will be discussed next) into a separate variable of the same name.
[Functions<<<{"href": "../../../../syntax/Functions/index.html"}>>>]
[PID_tripped_status_fn]
type = ParsedFunction<<<{"description": "Function created by parsing a string", "href": "../../../../source/functions/MooseParsedFunction.html"}>>>
symbol_values<<<{"description": "Constant numeric values, postprocessor names, function names, and scalar variables corresponding to the symbols in symbol_names."}>>> = 'PID_trip_status'
symbol_names<<<{"description": "Symbols (excluding t,x,y,z) that are bound to the values provided by the corresponding items in the vals vector."}>>> = 'PID_trip_status'
expression<<<{"description": "The user defined function."}>>> = 'PID_trip_status'
[]
[]
Controls
Multiple variables and functions have been discussed which are now used by Controls
and AuxScalarKernels
to actually record the desired values. Two Controls
use the previously mentioned functions to activate two AuxScalarKernels
which then apply the desired values to the previously mentioned AuxVariables
.
PID_trip_status
is a ConditionalFunctionEnableControl within the Controls
block. When the function is_tripped_fn
becomes true, PID_trip_status
activates the AuxScalarKernel
set_PID_tripped
.
[Controls<<<{"href": "../../../../syntax/Controls/index.html"}>>>]
[PID_trip_status]
type = ConditionalFunctionEnableControl<<<{"description": "Control for enabling/disabling objects when a function value is true", "href": "../../../../source/controls/ConditionalFunctionEnableControl.html"}>>>
conditional_function<<<{"description": "The function to give a true or false value"}>>> = is_tripped_fn
enable_objects<<<{"description": "A list of object tags to enable."}>>> = 'AuxScalarKernels::PID_trip_status_aux'
execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = 'TIMESTEP_END'
[]
[]
time_PID
is also a ConditionalFunctionEnableControl which activates when PID_trip_status
activates. This control then disables the AuxScalarKernel
set_time_PID
which was recording the time up until the point of the PID trip.
[Controls<<<{"href": "../../../../syntax/Controls/index.html"}>>>]
[time_PID]
type = ConditionalFunctionEnableControl<<<{"description": "Control for enabling/disabling objects when a function value is true", "href": "../../../../source/controls/ConditionalFunctionEnableControl.html"}>>>
conditional_function<<<{"description": "The function to give a true or false value"}>>> = PID_tripped_status_fn
disable_objects<<<{"description": "A list of object tags to disable."}>>> = 'AuxScalarKernels::time_trip_aux'
execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = 'TIMESTEP_END'
[]
[]
AuxScalarKernels
Finally, the AuxScalarKernels
apply all of the values and conditions discussed up to this point.
time_trip_aux
takes the function time_fn
and applies it to the AuxVariable
time_trip
. When the time_PID
Control
activates, it disables this AuxScalarVariable
which results in time_trip
remaining constant at the time the PID trip occurred. This value can now be applied to motor_torque_fn_shutdown
.
[AuxScalarKernels<<<{"href": "../../../../syntax/AuxScalarKernels/index.html"}>>>]
[time_trip_aux]
type = FunctionScalarAux<<<{"description": "Sets a value of a scalar variable based on a function.", "href": "../../../../source/auxscalarkernels/FunctionScalarAux.html"}>>>
function<<<{"description": "The functions to set the scalar variable components."}>>> = time_fn
variable<<<{"description": "The name of the variable that this kernel operates on"}>>> = time_trip
execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = 'TIMESTEP_END'
[]
[]
PID_trip_status_aux
, once activated, takes the function PID_tripped_constant_value
and applies it to the AuxVariable
PID_trip_status
. This causes a simple switch from 0 to 1 meaning the turbine went from supplying less torque than the motor to supplying more torque than the motor.
[AuxScalarKernels<<<{"href": "../../../../syntax/AuxScalarKernels/index.html"}>>>]
[PID_trip_status_aux]
type = FunctionScalarAux<<<{"description": "Sets a value of a scalar variable based on a function.", "href": "../../../../source/auxscalarkernels/FunctionScalarAux.html"}>>>
function<<<{"description": "The functions to set the scalar variable components."}>>> = PID_tripped_constant_value
variable<<<{"description": "The name of the variable that this kernel operates on"}>>> = PID_trip_status
execute_on<<<{"description": "The list of flag(s) indicating when this object should be executed. For a description of each flag, see https://mooseframework.inl.gov/source/interfaces/SetupInterface.html."}>>> = 'TIMESTEP_END'
enable<<<{"description": "Set the enabled status of the MooseObject."}>>> = false
[]
[]
Results
Figure 2 shows the first 2000 seconds of the transient where the PID controlled motor increased shaft speed from 0 to approximately 85,000 RPM. This is followed by Figure 3 which displays the shaft speed over the course of the entire transient.

Figure 2: PID start-up of shaft system.

Figure 3: Shaft speed during transient.
As shown in Figure 3, shaft speed is quickly ramped up by the PID and then linearly increases as the working fluid is heated and begins working on the turbine to produce shaft torque. At approximately 20,000 seconds the motor and turbine provide the same amount of torque which initiates the motor shutdown function. A comparison of the motor and turbine torques is shown below in Figure 4.

Figure 4: Comparison of the motor and turbine torques.
Figure 5 visualizes the pressure ratios of both the compressor and turbine during the transient. A slight difference is seen between the compressor and turbine steady state pressure ratios due to frictional losses across the piping system and 90° pipe bends, modeled by VolumeJunction1Phase.

Figure 5: Compressor and turbine pressure ratios.
Finally, coolant temperatures across key points of the system are displayed in Figure 6. The reactor inlet and outlet temperatures were omitted as there was no difference between the cold_leg
outlet and turbine inlet respectively.
Also, the compressor inlet was not plotted as it remained constant at ambient temperature of 300 K. The compressor outlet temperature sees an immediate increase corresponding to the startup of the PID system while the rest of the temperature responses are dependent upon the heat source power output.

Figure 6: Coolant temperatures across key components.