Step 1

The first step creates an axially symmetric flow channel with a uniform porosity of . This region will become the pebble bed in future steps. In this initial step, the flow channel has no pressure drop or heat source. The inflow boundary is situated at the top, the outflow boundary is situated at the bottom, and the left and right boundaries are slip walls.

We expect that velocity in the y-direction and pressure are uniform in the flow channel and velocity in the x-direction is 0. The heat equation is not solved at all, and the fluid temperature will not be added as a variable.

This tutorial uses the finite volume method to discretize the thermal-hydraulics equations. We solve a pseudo-transient problem (i.e., we let a transient run long enough to achieve a steady-state solution). This approach is usually more stable than solving a steady-state problem directly.

Geometry

We define the bed height and bed radius at the top of the input file.

# ==============================================================================
# Model description:
# Step1 - An axisymmetric flow channel with porosity.
# ------------------------------------------------------------------------------
# Idaho Falls, INL, August 15, 2023 04:03 PM
# Author(s): Joseph R. Brennan, Dr. Sebastian Schunert, Dr. Mustafa K. Jaradat
#            and Dr. Paolo Balestra.
# ==============================================================================
bed_height = 10.0
bed_radius = 1.2
(htgr/generic-pbr-tutorial/step1.i)

Then we use the GeneratedMeshGenerator to create a Cartesian mesh of the desired height and width. Notice how we use the ${x} syntax, where x stands for any defined parameter, usually defined at the top of the input file.

[Mesh]
  [gen]
    type = GeneratedMeshGenerator
    dim = 2
    xmin = 0
    xmax = ${bed_radius}
    ymin = 0
    ymax = ${bed_height}
    nx = 6
    ny = 40
  []
  coord_type = RZ
[]
(htgr/generic-pbr-tutorial/step1.i)

The parameters of GeneratedMeshGenerator are explained here. In two-dimensional geometry, the boundaries are named bottom (id=0), right (id=1), top (id=2), and left (id=3). The line coord_type = RZ sets the coordinate system to be axisymmetric (aka RZ). The symmetry axis defaults to the y-axis, which is what we want, so we do not need to set it explicitly.

To visualize the geometry, you can run:

./pronghorn-opt -i step1.i --mesh-only

This command produces the file step1_in.e that can be visualized using paraview. It is shown in Figure 1.

Figure 1: Geometry for Step 1.

Setting the Fluid Properties

Fluid properties are set by first defining a HeliumFluidProperties fluid properties object and second by creating a material that inserts the fluid properties into functors (if you do not know what a functor is then click here).

The first task is performed by the following input syntax:

[FluidProperties]
  [fluid_properties_obj]
    type = HeliumFluidProperties
  []
[]
(htgr/generic-pbr-tutorial/step1.i)

The second task is performed by the GeneralFunctorFluidProps object:

[FunctorMaterials]
  [fluid_props_to_mat_props]
    type = GeneralFunctorFluidProps
    fp = fluid_properties_obj
    porosity = porosity
    pressure = pressure
    T_fluid = ${T_fluid}
    speed = speed
    characteristic_length = 1
  []
[]
(htgr/generic-pbr-tutorial/step1.i)

This object takes pressure and temperature provided as variables to parameters pressure and T_fluid, respectively, and computes fluid properties, such as density and viscosity. Density and viscosity are named rho and mu. Note that T_fluid is set to a constant value in this input file because we do not solve the heat equation and therefore have no fluid temperature defined as a variable.

This object also requires providing functors to the porosity, speed, and characteristic_length parameters.

Porosity is defined as an auxiliary variable here:

[AuxVariables]
  [porosity]
    family = MONOMIAL
    order = CONSTANT
    fv = true
    initial_condition = ${bed_porosity}
  []
[]
(htgr/generic-pbr-tutorial/step1.i)

and set equal to the bed_porosity parameter of 0.39. speed is a functor that is computed automatically by the Navier-Stokes FV action, and characteristic_length is only used for computing Reynolds numbers, which are not required in Step 1; therefore, we set characteristic_length = 1 for now.

Setting up the Thermal-Hydraulics Problem

The thermal-hydraulics problem is set up using the finite volume Navier-Stokes action. This action is discussed in detail here. The corresponding block in the input file is:

[Modules]
  [NavierStokesFV]
    # general control parameters
    compressibility = 'weakly-compressible'
    porous_medium_treatment = true

    # material property parameters
    density = rho
    dynamic_viscosity = mu

    # porous medium treatment parameters
    porosity = porosity

    # initial conditions
    initial_velocity = '1e-6 1e-6 0'
    initial_pressure = 5.4e6

    # boundary conditions
    inlet_boundaries = top
    momentum_inlet_types = fixed-velocity
    momentum_inlet_function = '0 -${flow_vel}'
    wall_boundaries = 'left right'
    momentum_wall_types = 'slip slip'
    outlet_boundaries = bottom
    momentum_outlet_types = fixed-pressure
    pressure_function = ${outlet_pressure}
  []
[]
(htgr/generic-pbr-tutorial/step1.i)

Understanding the finite volume Navier-Stokes action is important. Therefore, each line will be explained.

    compressibility = 'weakly-compressible'
(htgr/generic-pbr-tutorial/step1.i)

This line indicates that weakly compressible fluid equations are solved. These equations allow the density of the fluid to vary, and the discretization methods assume Mach numbers of less than 0.2. For compressible flows in reactors, the weakly compressible formulation is the most commonly used equation set.

    porous_medium_treatment = true

    # material property parameters
(htgr/generic-pbr-tutorial/step1.i)

This line activates the porous flow treatment. Porous flow indicates that fluid and solid share a volume and exchange mass, momentum, and energy in this volume. This exchange is usually described by correlations. In this input file, no mass, momentum, or energy exchange occurs. The porosity defines how much of each porous flow element is fluid. The porosity in this input file is defined as a variable and provided in the line:

    porosity = porosity

    # initial conditions
(htgr/generic-pbr-tutorial/step1.i)

Note that porosity can also be provided as a functor material property which offers more flexibility when porosity smoothing is required. Porosity smoothing is not used in this tutorial because the Bernoulli treatment for porosity jumps is used. The names of the density and dynamic viscosity functors need to be provided to the action:

    density = rho
    dynamic_viscosity = mu

    # porous medium treatment parameters
(htgr/generic-pbr-tutorial/step1.i)

Note that these functors are defined by the GeneralFunctorFluidProps object discussed above.

Initial conditions for the velocity and pressure are defined by the lines:

    initial_velocity = '1e-6 1e-6 0'
    initial_pressure = 5.4e6

    # boundary conditions
(htgr/generic-pbr-tutorial/step1.i)

Note that, since we solve a pseudo-transient, the initial conditions should be interpreted as initial guesses of the solution. We set the initial guess of the velocity to a very small number (this precludes problems that some objects might have with zero velocity) and the initial guess of pressure to MPa. The initial condition for pressure is mismatched with the outlet pressure to make the problem slightly harder to solve.

The boundary conditions are set by these lines:

    inlet_boundaries = top
    momentum_inlet_types = fixed-velocity
    momentum_inlet_function = '0 -${flow_vel}'
    wall_boundaries = 'left right'
    momentum_wall_types = 'slip slip'
    outlet_boundaries = bottom
    momentum_outlet_types = fixed-pressure
    pressure_function = ${outlet_pressure}
(htgr/generic-pbr-tutorial/step1.i)

The top boundary is the inlet boundary, where we specify the inlet velocity. The inlet velocity is where we note that the velocity is downward, leading to the negative sign. The left and right boundaries are set to slip walls. The bottom boundary is set to a constant pressure outlet.

The relevant values used for substituting the ${variable_name} expressions are computed at the beginning of the input file:

outlet_pressure = 5.5e6
T_fluid = 300
density = 8.60161

mass_flow_rate = 60.0
flow_area = '${fparse pi * bed_radius * bed_radius}'
flow_vel = '${fparse mass_flow_rate / flow_area / density}'
(htgr/generic-pbr-tutorial/step1.i)

Checking Mass Conservation

We use postprocessors to compute the inlet and outlet mass flow rates.

[Postprocessors]
  [desired_mfr]
    type = Receiver
    default = ${mass_flow_rate}
  []

  [inlet_mfr]
    type = VolumetricFlowRate
    advected_quantity = rho
    vel_x = 'superficial_vel_x'
    vel_y = 'superficial_vel_y'
    boundary = 'top'
    rhie_chow_user_object = pins_rhie_chow_interpolator
  []

  [outlet_mfr]
    type = VolumetricFlowRate
    advected_quantity = rho
    vel_x = 'superficial_vel_x'
    vel_y = 'superficial_vel_y'
    boundary = 'bottom'
    rhie_chow_user_object = pins_rhie_chow_interpolator
  []
[]
(htgr/generic-pbr-tutorial/step1.i)

The desired_mfr postprocessor simply takes the mass_flow_rate parameter computed at the top of the input file and prints it to screen. This is the mass flow rate we want to achieve.

The inlet_mfr and outlet_mfr are VolumetricFlowRate postprocessors. The VolumetricFlowRate allows the computation of velocity weighted integrals over surfaces. If the advected_quantity is denoted by , velocity by , and the normal on the surface by , then VolumetricFlowRate returns:

For set to density, this is the mass flow rate. We expect all three postprocessors to be identical.

Executioner

The executioner object is explained in detail here. For Step 1 we use these specifications:

[Executioner]
  type = Transient
  end_time = 100
  [TimeStepper]
    type = IterationAdaptiveDT
    iteration_window = 2
    optimal_iterations = 8
    cutback_factor = 0.8
    growth_factor = 2
    dt = 1e-3
  []
  dtmax = 5
  line_search = l2
  solve_type = 'NEWTON'
  petsc_options_iname = '-pc_type -pc_factor_shift_type -pc_factor_mat_solver_package'
  petsc_options_value = 'lu NONZERO superlu_dist'
  nl_rel_tol = 1e-6
  nl_abs_tol = 1e-6
[]
(htgr/generic-pbr-tutorial/step1.i)

We run a transient to the end_time of 100 seconds. The time steps are selected adaptively based on the number of nonlinear iterations used by the previous timestep. Details on IterationAdaptiveDT can be found here. A maximum timestep of seconds is used. We use Newton's method to solve the problem and solve the linear problem using lower-upper (LU) decomposition. Relative and absolute convergence tolerances are set to .

Results

Execute step1.i by:

./pronghorn-opt -i step1.i

Execution should take about 10 seconds. An exodus file step1_out.e is created. This file contains the pressure and superficial velocity solutions. These are constant at MPa and m/s. Since they are not very relevant, they are not plotted here. The three postprocessors at seconds are:

desired_mfr = 6.000000e+01
inlet_mfr = -5.999997e+01
outlet_mfr = 5.999997e+01

We conserve mass, since inlet_mfr and outlet_mfr are identical. The mass flow rate is also very close to what we expect ( kg/s). The small difference orgininates from truncating the decimals of the assumed density when computing the inlet velocity.