Step 9: Develop a Material Object

In this step, the basic components of the Material class will be presented. To demonstrate its use, a new MooseObject that provides the values for permeability and viscosity needed by the DarcyPressure class will be developed. A test for this new object shall be written in accordance with the procedures outlined in the previous step. In addition, any input files which create DarcyPressure objects will be need to be modified.

Material Objects

The Materials System in MOOSE is one for defining spatially and/or temporally varying properties and storing their values in a single object. The types of terms that belong to this system are the coefficients in the governing equations, though they may or may not be continuously differentiable with respect to the problem domain. The values are always indexed at individual quadrature points (QPs) and the associated class methods tend to be invoked at every time step. The properties may then be accessed by other MOOSE systems. And, like many systems in MOOSE, Materials can be coupled to variables if a property being calculated depends on them.

Material objects are developed in C++ similar to how those in other MOOSE systems are, such as Kernels. Like Kernel objects, they inherit a default set of input parameters and virtual methods from the base class. Thus, by recalling some of the preceding steps of this tutorial, the reader may recognize the format and procedure for developing a new MooseObject as it is presented in the Demonstration section.

Producing and Consuming Properties

The Materials System relies on a producer-consumer relationship among objects:

  • Material (and ADMaterial) objects produce properties.

  • Other MOOSE objects (including materials themselves) consume these properties.

The life span of a material property will proceed as follows:

  1. Each property must be declared to be available for use. The declareADProperty<T>("name") method does this and returns a writable reference, where T may be any valid C++ type, such as Real or std::vector<Real>.

  2. The values to be associated with "name" are computed and set from within one of the available methods—typically, computeQpProperties().

  3. Other objects may then call getADMaterialProperty<T>("name") to retrieve the property referred to by "name".

Property Output

Every Material object will have input parameters to control how properties are saved to output files. A property can be designated for output by providing its name to the "output_properties" parameter in the input file. The values for these properties are written to one or more Output object(s), such as an ExodusII file, by providing their names to the "outputs" parameter. These parameters will be made especially useful when developing a regression test as part of the demonstration for this step.

warningwarning:Property output parameters

The "outputs" parameter defaults to none and, therefore, must be explicitly set in order to write the values requested by "output_properties" to any output files. Also, while the properties produced by Material objects can be any valid C++ type, not all types are supported when outputting. Please see the Material Property Output discussion for more information.

Demonstration

Now that the basic purpose of the Materials System has been established, another object in the isotropic, divergence-free weak form of Darcy's equation can be identified, i.e.,

(1)

For this demonstration, a new Material object will be developed to produce the term in Eq. (1) and the DarcyPressure class will be modified to consume it. The new MooseObject shall accept an input for the diameter of steel spheres tightly packed to form a porous medium. The Material Properties section of the Problem Statement provided the following linear relationship between the permeability () of such media and the sphere diameter ():

(2)

The new object shall restrict the input diameter to the domain of Eq. (2) and enforce the requirement that .

Since the pressure vessel model uses , pressure_diffusion.i shall be modified to use this value as input. It will be shown that this input reproduces the isotropic permeability value , introduced in Step 5. Thus, the results of the darcy_pressure_test.i file created in the previous step should still match the gold file after modifying its inputs.

Source Code

To produce the material property term in Eq. (1), a new ADMaterial object can be created and it shall be called PackedColumn: a name intended to depict a coarse filter medium in a slender tube. Start by making the directories to store files for objects that are part of the Materials System:

cd ~/projects/babbler
mkdir include/materials src/materials

In include/materials, create a file name PackedColumn.h and add the code given in Listing 1. Here, the header file for the base class was included so that it can be inherited. In addition, LinearInterpolation.h was included so that an object of the LinearInterpolation class—a member of the Utilities System in MOOSE—can be made to evaluate Eq. (2). The validParams() and constructor methods were the first members declared, as is typical, and the computeQpProperties() method from the base class was overridden. Two variables, _diameter and _input_viscosity, were declared to store the values input for and , respectively. The former of these two variables will need to be passed to a LinearInterpolation object, in order to obtain , so _permeability_interpolation was declared for this purpose. Finally, two ADMaterialProperty variables were declared and will, ultimately, become available to other MOOSE objects that need them.

Listing 1: Header file for the PackedColumn class.


#pragma once

#include "Material.h"

// A helper class from MOOSE that linearly interpolates abscissa-ordinate pairs
#include "LinearInterpolation.h"

/**
 * Computes the permeability of a porous medium made up of packed steel spheres of a specified
 * diameter in accordance with Pamuk and Ozdemir (2012). This also provides a specified dynamic
 * viscosity of the fluid in the medium.
 */
class PackedColumn : public Material
{
public:
  static InputParameters validParams();

  PackedColumn(const InputParameters & parameters);

protected:
  /// Necessary override. This is where the property values are set.
  virtual void computeQpProperties() override;

  /// The inputs for the diameter of spheres in the column and the dynamic viscosity of the fluid
  const Real & _diameter;
  const Real & _input_viscosity;

  /// This object interpolates permeability (m^2) based on the diameter (mm)
  LinearInterpolation _permeability_interpolation;

  /// The material property objects that hold values for permeability (K) and dynamic viscosity (mu)
  ADMaterialProperty<Real> & _permeability;
  ADMaterialProperty<Real> & _viscosity;
};

In src/kernels, create a file named PackedColumn.C and add the code given in Listing 2. To enforce the necessary restrictions on values for and , their inputs are parsed with the addRangeCheckedParam() method. This method is like addParam(), i.e., it sets a default value in lieu of user input, for which diameter = 1 () and viscosity = 7.98e-04 () were used here. However, it has an additional argument that accepts logical expressions, which operate on the parameter itself. If the expression is false for a given input, an error message is invoked and the application terminates. This method provides more convenient means for enforcing , or for that matter, than hard-coding a condition that leads to a paramError()—the approach followed in the previous step, but not all types of parameters are able to be validated in this way.

In the constructor definition, two Real type properties by the names "permeability" and "viscosity" were declared available for consumption using the declareADProperty() method that was mentioned in the Producing and Consuming Properties section. Two vector variables, sphere_sizes and permeability, were declared and set to the domain and range of Eq. (2) in an abscissa-ordinate pair fashion. These vectors are then passed to the setData() method of the LinearInterpolation object, which is a wise task to handle in the constructor, since there's no need to repeatedly send invariant copies of the data at each solve step and at each quadrature point (QP) index. Finally, in the computeQpProperties() definition, the references to the properties are set, for which MOOSE and libMesh work together to resolve the _qp indexing scheme. Here, the _diameter variable was passed to the sample() method to retrieve the permeability.

Listing 2: Source file for the PackedColumn class.


#include "PackedColumn.h"

registerMooseObject("BabblerApp", PackedColumn);

InputParameters
PackedColumn::validParams()
{
  InputParameters params = Material::validParams();
  params.addClassDescription("Computes the permeability of a porous medium made up of packed "
                             "steel spheres of a specified diameter in accordance with Pamuk and "
                             "Ozdemir (2012). This also provides a specified dynamic viscosity of "
                             "the fluid in the medium.");

  // Optional params for ball diameter and viscosity - inputs must satisfy range checked conditions
  params.addRangeCheckedParam<Real>(
      "diameter",
      1.0,
      "(1 <= diameter) & (diameter <= 3)",
      "The diameter (mm) of the steel spheres packed within the column that is used to compute "
      "its permeability. The input must be in the range [1, 3]. The default value is 1 mm.");
  params.addRangeCheckedParam<Real>(
      "viscosity",
      7.98e-04,
      "viscosity != 0",
      "The dynamic viscosity ($\\mu$) of the fluid, the default value is that of water at 30 "
      "degrees Celcius (7.98e-04 Pa-s).");

  return params;
}

PackedColumn::PackedColumn(const InputParameters & parameters)
  : Material(parameters),
    _diameter(getParam<Real>("diameter")),
    _input_viscosity(getParam<Real>("viscosity")),

    // Declare two material properties by getting a reference from the MOOSE Material system
    _permeability(declareADProperty<Real>("permeability")),
    _viscosity(declareADProperty<Real>("viscosity"))
{
  // From Pamuk and Ozdemir (2012): Table 1
  std::vector<Real> sphere_sizes = {1, 3};                // mm
  std::vector<Real> permeability = {0.8451e-9, 8.968e-9}; // m^2

  // Set the abscissa-ordinate data on the LinearInterpolation object.
  _permeability_interpolation.setData(sphere_sizes, permeability);
}

void
PackedColumn::computeQpProperties()
{
  _permeability[_qp] = _permeability_interpolation.sample(_diameter);
  _viscosity[_qp] = _input_viscosity;
}

The DarcyPressure class needs to be modified to consume the "permeability" and "viscosity" properties at each QP index. Consequently, it also no longer needs input for the corresponding values. In DarcyPressure.h, change the declaration of the _permeability and _viscosity variables to the following:

  /// The material properties which hold the values for K and mu
  const ADMaterialProperty<Real> & _permeability;
  const ADMaterialProperty<Real> & _viscosity;

Do not modify any other parts of DarcyPressure.h.

All definitions in the source file need to be modified, so the reader may simply overwrite it with the syntax given in Listing 3. Still, they should take a moment to review the changes made by running git diff *DarcyPressure.C.

Listing 3: Source file for the DarcyPressure class modified to consume material properties produced by a PackedColumn object.


#include "DarcyPressure.h"

registerMooseObject("BabblerApp", DarcyPressure);

InputParameters
DarcyPressure::validParams()
{
  InputParameters params = ADKernelGrad::validParams();
  params.addClassDescription("Compute the diffusion term for Darcy pressure ($p$) equation: "
                             "$-\\nabla \\cdot \\frac{\\mathbf{K}}{\\mu} \\nabla p = 0$");
  return params;
}

DarcyPressure::DarcyPressure(const InputParameters & parameters)
  : ADKernelGrad(parameters),
    _permeability(getADMaterialProperty<Real>("permeability")),
    _viscosity(getADMaterialProperty<Real>("viscosity"))
{
}

ADRealVectorValue
DarcyPressure::precomputeQpResidual()
{
  return (_permeability[_qp] / _viscosity[_qp]) * _grad_u[_qp];
}

Be sure to recompile the application before proceeding:

cd ~/projects/babbler
make -j4

Input File

An input file specialized to test the PackedColumn class is in order. Start by creating a directory to store the test files:

cd ~/projects/babbler
mkdir test/tests/materials/packed_column

In this folder, create a file named packed_column_test.i and add the inputs given in Listing 4. In the [filter] block, a PackedColumn object is created with a "viscosity" input that is trivial for testing purposes as its value goes directly to the property reference. However, for the "diameter" input, the median value of the domain () was selected because this obviously corresponds to the median value of the range () and, therefore, can be verified in such terms. Outputs were requested for both properties by means which were discussed in the Property Output section, where the format will be an ExodusII file so that an Exodiff tester may reference it.

Listing 4: Input file to test the PackedColumn class with an Exodiff object.

[Mesh<<<{"href": "../../../syntax/Mesh/index.html"}>>>]
  type = GeneratedMesh
  dim = 2
  nx = 2
  ny = 2
  xmax = 1
  ymax = 1
[]

[Problem<<<{"href": "../../../syntax/Problem/index.html"}>>>]
  solve = false
[]

[Variables<<<{"href": "../../../syntax/Variables/index.html"}>>>]
  [u]
  []
[]

[Materials<<<{"href": "../../../syntax/Materials/index.html"}>>>]
  [filter]
    type = PackedColumn
    diameter = 2
    viscosity = 1e-03
    output_properties = 'permeability viscosity'
    outputs = exodus
  []
[]

[Executioner<<<{"href": "../../../syntax/Executioner/index.html"}>>>]
  type = Steady
[]

[Outputs<<<{"href": "../../../syntax/Outputs/index.html"}>>>]
  exodus<<<{"description": "Output the results using the default settings for Exodus output."}>>> = true
[]

Execute the input file in Listing 4, confirm that the solver completes, and that a file named packed_column_test_out.e is generated:

cd ~/projects/babbler/test/tests/materials/packed_column
../../../../babbler-opt -i packed_column_test.i

The inputs in problems/pressure_diffusion.i need to be modified to use a PackedColumn object. In this file, update the [Kernels] block and add a [Materials] one, e.g.,

[Kernels<<<{"href": "../../../syntax/Kernels/index.html"}>>>]
  [diffusion]
    type = DarcyPressure # Zero-gravity, divergence-free form of Darcy's law
    variable = pressure # Operate on the "pressure" variable from above
  []
[]

[Materials<<<{"href": "../../../syntax/Materials/index.html"}>>>]
  [filter]
    type = PackedColumn # Provides permeability and viscosity of water through packed 1mm spheres
  []
[]

Since this model uses the default input values, there was no need to specify them explicitly.

createTASK:Update the darcy_pressure_test.i file.

A similar modification as the one just made to pressure_diffusion.i must also be made to darcy_pressure_test.i. Otherwise, the associated test will fail. It is left up to the reader to implement the correct changes to this file.

Results

Use PEACOCK to query the "permeability" and "viscosity" outputs from packed_column_test.i on each element:

cd ~/projects/babbler/test/tests/materials/packed_column
peacock -r packed_column_test_out.e

In the ExodusViewer tab, the property values can be resolved by referring to the color bar (enabling the "View Mesh" checkbox might help with interpreting the image). Verify that the values match the expected ones and note that, since neither property was made spatially dependent, the PackedColumn object assigned the same number to all QPs and the contours render as a solid color. These results are illustrated in Figure 1.

Figure 1: Rendering of the "permeability" and "viscosity" material properties produced by the PackedColumn test. These properties are spatially invariant and so their values are uniformly distributed throughout the mesh.

Test

Since the results of the packed_column_test.i input file have been deemed good, the ExodusII output can now become a certified gold file:

cd ~/projects/babbler/test/tests/materials/packed_column
mkdir gold
mv packed_colum_test_out.e gold

Next, create a file named tests in test/tests/materials/packed_column and add the inputs given in Listing 5.

Listing 5: Test specification file for the PackedColumn class.

[Tests]
  [test]
    type = Exodiff
    input = packed_column_test.i
    exodiff = packed_column_test_out.e
  []
[]

The RunException tester that was specified in the previous step and was used to test proper invocation of the paramError(), when the input is viscosity = 0, is no longer useful. In fact, this test will fail and may simply be removed. In the test/tests/kernels/darcy_pressure/tests file, remove the [zero_viscosity_error] block, but leave the [test] block unchanged. Also, delete zero_viscosity_error.i.

schooltip:Limit the scope of tests to the application itself.

One might wonder if it would be beneficial to test that an input which does not satisfy a logical expression passed to the addRangeCheckedParam() method leads to an error. This surely wouldn't hurt. Just know that almost all developer tools available from MOOSE already have their own testing systems.

Be sure that the darcy_pressure_test.i file has been updated in the manner demonstrated in the Input File section. Finally, run TestHarness:

cd ~/projects/babbler
./run_tests -j4

If the tests passed, the terminal output should look something like that shown below.


test:materials/packed_column.test ......................................................................... OK
test:kernels/darcy_pressure.test .......................................................................... OK
test:kernels/simple_diffusion.test ........................................................................ OK
--------------------------------------------------------------------------------------------------------------
Ran 3 tests in 0.4 seconds. Average test time 0.1 seconds, maximum test time 0.1 seconds.
3 passed, 0 skipped, 0 pending, 0 failed

Commit

There are many changes to add to the git tracker here, but a wildcard can help simplify the command:

cd ~/projects/babbler
git add *

Disregard the warning about .gitignore files. Now, commit and push the changes to the remote repository:

git commit -m "developed material to compute properties of fluid flow through packed steel sphere medium and modified Darcy kernel"
git push