NEML2 2.0.0
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
Running your first model

Problem description

Let us start with the simplest example for solid mechanics. Consider a solid material whose elastic behavior (mapping from strain ε to stress σ, or vice versa) can be described as

σ=3Kvolε+2Gdevε,

where K is the bulk modulus, and G is the shear modulus.

Searching for available models

All available material models are listed in the syntax documentation. The documentation of each model provides a brief description, followed by a list of input file options. Each option has a short description right next to it, and can be expanded to show additional details.

There is an existing model that solves this exact problem: LinearIsotropicElasticity. The syntax documentation lists the input file options associated with this model.

Writing the input file

As explained in the syntax documentation for LinearIsotropicElasticity, the option "strain" is used to specify the name of the variable for the elastic strain, and the option "stress" is used to specify the name of the variable for the stress. The options "coefficients" and "coefficient_types" are used to specify the values of the parameters, in this case K and G.

Using these information, the input file for constructing this model can be composed as:

[Models]
[my_model]
type = LinearIsotropicElasticity
strain = 'forces/E'
stress = 'state/S'
coefficient_types = 'BULK_MODULUS SHEAR_MODULUS'
coefficients = '1.4e5 7.8e4'
[]
[]

Choosing the frontend

There are three common ways of interacting with NEML2 input files:

  • Calling the appropriate APIs in a C++ program
  • Calling the appropriate APIs in a Python script
  • Using the NEML2 Runner

These methods are discussed in the getting started guide. In this set of tutorials, the C++ example code and the Python script example code are shown side-by-side with tabs, and in most cases the C++ APIs and the Python APIs have a nice one-to-one correspondance.

Loading a model from the input file

The following code parses the given input file named "input.i" and retrieves a Model named "my_model". Once retrieved, we can print out a summary of the model by streaming it to the console:

  • #include "neml2/models/Model.h"
    int
    main()
    {
    using namespace neml2;
    auto & model = load_model("input.i", "my_model");
    std::cout << model << std::endl;
    }
    Definition DiagnosticsInterface.cxx:30
    Model & load_model(const std::filesystem::path &path, const std::string &mname)
    A convenient function to load an input file and get a model.
    Definition Model.cxx:48

    Output:

    Name: my_model
    Input: forces/E [SR2]
    Output: state/S [SR2]
    Parameters: G [Scalar][Float][cpu]
    K [Scalar][Float][cpu]
  • import neml2
    model = neml2.load_model("input.i", "my_model")
    print(model)

    Output:

    Name: my_model
    Input: forces/E [SR2]
    Output: state/S [SR2]
    Parameters: G [Scalar][Float][cpu]
    K [Scalar][Float][cpu]

The summary includes information about the model's name, primary floating point numeric type (denoted as "Dtype"), current device, input variables, output variables, parameters, and buffers (if any). Note that the variables and parameters are additionally marked with tensor types surrounded by square brackets, i.e., [SR2] and [Scalar]. These are NEML2's primitive tensor types which will be extensively discussed in another set of tutorials.

Model structure and forward operators

Before going over model evaluation, let us zoom out from this particular example and briefly discuss the structure of NEML2 models.

All NEML2 models, including this simple elasticity model under consideration, take the following general form

y=f(x;p,b),

where x and y are respectively sets of input and output variables, p is the set of parameters, and b is the set of buffers. The utilities of parameters and buffers will be discussed in another tutorial. The forward operator f is responsible for mapping from input variables x to y. NEML2 provides three forward operators for all models:

All three forward operators take a map/dictionary of variable values as input and return the requested output variables and/or their derivatives.

In addition to these standard forward operators, some models in NEML2 also support calculating second derivatives. Three additional forward operators are provided to request second derivatives:

Evaluating the model

Model evaluation consists of two simple steps:

  1. Specify the input variable values
  2. Call the appropriate forward operator

In this example, the elasticity model can be evaluated using the following code:

Note
Note that set_default_dtype(kFloat64) is used to change the default precision to double precision.
  • #include "neml2/models/Model.h"
    #include "neml2/tensors/SR2.h"
    int
    main()
    {
    using namespace neml2;
    auto & model = load_model("input.i", "my_model");
    // Create the strain
    auto strain_name = VariableName("forces", "E");
    auto strain = SR2::fill(0.1, 0.05, -0.03, 0.02, 0.06, 0.03);
    // Evaluate the model
    auto output = model.value({{strain_name, strain}});
    // Get the stress
    auto stress_name = VariableName("state", "S");
    auto & stress = output[stress_name];
    std::cout << "strain: \n" << strain << std::endl;
    std::cout << "stress: \n" << stress << std::endl;
    }
    static SR2 fill(const Real &a, const TensorOptions &options=default_tensor_options())
    Fill the diagonals with a11 = a22 = a33 = a.
    Definition SR2.cxx:53
    constexpr auto kFloat64
    Definition types.h:53
    void set_default_dtype(Dtype dtype)
    Definition defaults.cxx:32
    LabeledAxisAccessor VariableName
    Definition LabeledAxisAccessor.h:185

    Output:

    strain:
    0.1000
    0.0500
    -0.0300
    0.0283
    0.0849
    0.0424
    [ CPUDoubleType{6} ]
    stress:
    26160.0000
    18360.0000
    5880.0000
    4412.3463
    13237.0389
    6618.5195
    [ CPUDoubleType{6} ]
  • import neml2
    from neml2.tensors import SR2
    import torch
    torch.set_default_dtype(torch.double)
    model = neml2.load_model("input.i", "my_model")
    # Create the strain
    strain = SR2.fill(0.1, 0.05, -0.03, 0.02, 0.06, 0.03)
    # Evaluate the model
    output = model.value({"forces/E": strain})
    # Get the stress
    stress = output["state/S"]
    print("strain:")
    print(strain)
    print("stress:")
    print(stress)

    Output:

    strain:
    0.1000
    0.0500
    -0.0300
    0.0283
    0.0849
    0.0424
    [ CPUDoubleType{6} ]
    <SR2 of shape [][6]>
    stress:
    26160.0000
    18360.0000
    5880.0000
    4412.3463
    13237.0389
    6618.5195
    [ CPUDoubleType{6} ]
    <Tensor of shape [][6]>