Problem description
In this tutorial, we will revisit the problem defined in the previous tutorial and demonstrate how to work with model parameters.
Recall that the linear elasticity material model can be mathematically written as
\[
\boldsymbol{\sigma} = 3 K \operatorname{vol} \boldsymbol{\varepsilon} + 2 G \operatorname{dev} \boldsymbol{\varepsilon}.
\]
Also recall that all NEML2 models can be written in the following general form
\[
y = f(x; p, b).
\]
Pattern matching suggests the following set definitions:
\[
x = \left\{ \boldsymbol{\varepsilon} \right\}, \quad y = \left\{ \boldsymbol{\sigma} \right\}, \quad p = \left\{ K, G \right\}, \quad b = \varnothing.
\]
Parameter vs buffer
Both \( K \) and \( G \) are here categorized as model parameters. The major differences between parameters and buffers are
- Parameters are "trainable", whereas buffers are not. NEML2 can use automatic differentiation to calculate the derivative of output variables with respect to the model parameters, but not for the model buffers.
- Parameters can be (recursively) defined by other models, whereas buffers cannot. This feature is discussed in a later tutorial (Model parameters (revisited)).
In summary, parameter is a more powerful superset of buffer. However, there is overhead cost associated with maintaining a parameter that buffers avoid.
- Note
- Some models allow users to choose whether to declare data as parameters or buffers.
Retrieving the parameter value
All model parameters are associated with a unique name, either predefined by the model itself or chosen by the user. The following code iterates through all parameters in the model and print out their values:
C++
#include "neml2/models/Model.h"
#include "neml2/tensors/Tensor.h"
int
main()
{
auto model = load_model("input.i", "my_model");
for (auto && [pname, pval] : model->named_parameters())
std::cout << pname <<
":\n" <<
Tensor(*pval) << std::endl;
}
Definition DiagnosticsInterface.cxx:30
Output:
G:
78000
[ CPUFloatType{} ]
K:
140000
[ CPUFloatType{} ]
Python
import neml2
for pname, pval in model.named_parameters().items():
print("{}:".format(pname))
print(pval.tensor())
std::shared_ptr< 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:43
Output:
G:
78000
[ CPUFloatType{} ]
<Tensor of shape [][]>
K:
140000
[ CPUFloatType{} ]
<Tensor of shape [][]>
neml2::Model::get_parameter can be used to retrieve a specific parameter given its name. In other words, the above code is equivalent to
Updating the parameter value
Model parameters can always be changed by changing the input file. However, in certain cases (e.g., training and optimization), the parameter values should preferrably be updated at runtime (e.g., after each epoch or optimization iteration).
The neml2::Model::set_parameter and neml2::Model::set_parameters methods can be used for that purpose:
C++
#include "neml2/models/Model.h"
#include "neml2/tensors/Tensor.h"
#include "neml2/tensors/Scalar.h"
int
main()
{
auto model = load_model("input.i", "my_model");
auto & G = model->get_parameter("G");
std::cout <<
"G (before modification):\n" <<
Tensor(G) << std::endl;
G = Scalar::full(59000);
std::cout <<
"G (after modification):\n" <<
Tensor(G) << std::endl;
}
Output:
G (before modification):
78000
[ CPUFloatType{} ]
G (after modification):
59000
[ CPUFloatType{} ]
Python
import neml2
from neml2.tensors import Scalar
print("G (before modification):")
print(model.G.tensor())
model.G = Scalar.full(59000)
print("G (after modification):")
print(model.G.tensor())
Output:
G (before modification):
78000
[ CPUFloatType{} ]
<Tensor of shape [][]>
G (after modification):
59000
[ CPUDoubleType{} ]
<Tensor of shape [][]>