https://mooseframework.inl.gov
LibtorchNeuralNetControl.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #ifdef MOOSE_LIBTORCH_ENABLED
11 
13 #include "TorchScriptModule.h"
14 #include "LibtorchUtils.h"
15 
16 #include "Transient.h"
17 
19 
22 {
24  params.addClassDescription("Controls the value of multiple controllable input parameters using a "
25  "Libtorch-based neural network.");
26  params.addRequiredParam<std::vector<std::string>>("parameters",
27  "The input parameter(s) to control.");
28  params.addRequiredParam<std::vector<PostprocessorName>>(
29  "responses", "The responses (prostprocessors) which are used for the control.");
30  params.addParam<std::vector<Real>>(
31  "response_shift_factors",
32  "Constants which will be used to shift the response values. This is used for the "
33  "manipulation of the neural net inputs for better training efficiency.");
34  params.addParam<std::vector<Real>>(
35  "response_scaling_factors",
36  "Constants which will be used to multiply the shifted response values. This is used for "
37  "the manipulation of the neural net inputs for better training efficiency.");
38  params.addParam<std::string>("filename",
39  "Define if the neural net is supposed to be loaded from a file.");
40  params.addParam<bool>("torch_script_format",
41  false,
42  "If we want to load the neural net using the torch-script format.");
43  params.addParam<unsigned int>(
44  "input_timesteps",
45  1,
46  "Number of time steps to use in the input data, if larger than 1, "
47  "data from the previous timesteps will be used as well as inputs in the training.");
48  params.addParam<std::vector<unsigned int>>("num_neurons_per_layer",
49  "The number of neurons on each hidden layer.");
50  params.addParam<std::vector<std::string>>(
51  "activation_function",
52  std::vector<std::string>({"relu"}),
53  "The type of activation functions to use. It is either one value "
54  "or one value per hidden layer.");
55 
56  params.addParam<std::vector<Real>>(
57  "action_scaling_factors",
58  "Scale factor that multiplies the NN output to obtain a physically meaningful value.");
59 
60  return params;
61 }
62 
64  : Control(parameters),
65  _old_responses(declareRestartableData<std::vector<std::vector<Real>>>("old_responses")),
66  _control_names(getParam<std::vector<std::string>>("parameters")),
67  _current_control_signals(std::vector<Real>(_control_names.size(), 0.0)),
68  _response_names(getParam<std::vector<PostprocessorName>>("responses")),
69  _input_timesteps(getParam<unsigned int>("input_timesteps")),
70  _response_shift_factors(isParamValid("response_shift_factors")
71  ? getParam<std::vector<Real>>("response_shift_factors")
72  : std::vector<Real>(_response_names.size(), 0.0)),
73  _response_scaling_factors(isParamValid("response_scaling_factors")
74  ? getParam<std::vector<Real>>("response_scaling_factors")
75  : std::vector<Real>(_response_names.size(), 1.0)),
76  _action_scaling_factors(isParamValid("action_scaling_factors")
77  ? getParam<std::vector<Real>>("action_scaling_factors")
78  : std::vector<Real>(_control_names.size(), 1.0))
79 {
80  // We first check if the input parameters make sense and throw errors if different parameter
81  // combinations are not allowed
82  conditionalParameterError("filename",
83  {"num_neurons_per_layer", "activation_function"},
84  !getParam<bool>("torch_script_format"));
85 
86  if (_response_names.size() != _response_shift_factors.size())
87  paramError("response_shift_factors",
88  "The number of shift factors is not the same as the number of responses!");
89 
90  if (_response_names.size() != _response_scaling_factors.size())
91  paramError(
92  "response_scaling_factors",
93  "The number of normalization coefficients is not the same as the number of responses!");
94 
95  if (_control_names.size() != _action_scaling_factors.size())
96  paramError("action_scaling_factors",
97  "The number of normalization coefficients is not the same as the number of "
98  "controlled parameters!");
99 
100  // We link to the postprocessor values so that we can fetch them any time. This also raises
101  // errors if we don't have the postprocessors requested in the input.
102  for (unsigned int resp_i = 0; resp_i < _response_names.size(); ++resp_i)
104 
105  // If the user wants to read the neural net from file, we do it. We can read it from a
106  // torchscript file, or we can create a shell and read back the parameters
107  if (isParamValid("filename"))
108  {
109  std::string filename = getParam<std::string>("filename");
110  if (getParam<bool>("torch_script_format"))
111  _nn = std::make_shared<Moose::TorchScriptModule>(filename);
112  else
113  {
114  unsigned int num_inputs = _response_names.size() * _input_timesteps;
115  unsigned int num_outputs = _control_names.size();
116  std::vector<unsigned int> num_neurons_per_layer =
117  getParam<std::vector<unsigned int>>("num_neurons_per_layer");
118  std::vector<std::string> activation_functions =
119  parameters.isParamSetByUser("activation_function")
120  ? getParam<std::vector<std::string>>("activation_function")
121  : std::vector<std::string>({"relu"});
122  auto nn = std::make_shared<Moose::LibtorchArtificialNeuralNet>(
123  filename, num_inputs, num_outputs, num_neurons_per_layer, activation_functions);
124 
125  try
126  {
127  torch::load(nn, filename);
128  _nn = std::make_shared<Moose::LibtorchArtificialNeuralNet>(*nn);
129  }
130  catch (const c10::Error & e)
131  {
132  mooseError(
133  "The requested pytorch parameter file could not be loaded. This can either be the"
134  "result of the file not existing or a misalignment in the generated container and"
135  "the data in the file. Make sure the dimensions of the generated neural net are the"
136  "same as the dimensions of the parameters in the input file!\n",
137  e.msg());
138  }
139  }
140  }
141 }
142 
143 void
145 {
146  if (_nn)
147  {
148  const unsigned int n_controls = _control_names.size();
149  const unsigned int num_old_timesteps = _input_timesteps - 1;
150 
151  // Fetch current reporter values and populate _current_response
153 
154  // If this is the first timestep, we fill up the old values with the initial value
155  if (_old_responses.empty())
156  _old_responses.assign(num_old_timesteps, _current_response);
157 
158  // Organize the old an current solution into a tensor so we can evaluate the neural net
159  torch::Tensor input_tensor = prepareInputTensor();
160 
161  // Evaluate the neural network to get the control values then convert it back to vectors
162  torch::Tensor action = _nn->forward(input_tensor);
163 
164  _current_control_signals = {action.data_ptr<Real>(), action.data_ptr<Real>() + action.size(1)};
165  for (unsigned int control_i = 0; control_i < n_controls; ++control_i)
166  {
167  // We scale the controllable value for physically meaningful control action
168  setControllableValueByName<Real>(_control_names[control_i],
169  _current_control_signals[control_i] *
170  _action_scaling_factors[control_i]);
171  }
172 
173  // We add the curent solution to the old solutions and move everything in there one step
174  // backward
175  std::rotate(_old_responses.rbegin(), _old_responses.rbegin() + 1, _old_responses.rend());
177  }
178 }
179 
180 Real
181 LibtorchNeuralNetControl::getSignal(const unsigned int signal_index) const
182 {
183  mooseAssert(signal_index < _control_names.size(),
184  "The index of the requested control signal is not in the [0," +
185  std::to_string(_control_names.size()) + ") range!");
186  return _current_control_signals[signal_index];
187 }
188 
189 void
191  const std::string & param_name,
192  const std::vector<std::string> & conditional_params,
193  bool should_be_defined)
194 {
195  if (parameters().isParamSetByUser(param_name))
196  for (const auto & param : conditional_params)
197  if (parameters().isParamSetByUser(param) != should_be_defined)
198  paramError(param,
199  "This parameter should",
200  (should_be_defined ? " " : " not "),
201  "be defined when ",
202  param_name,
203  " is defined!");
204 }
205 
206 void
208 {
209  // Gather the current response values from the reporters
210  _current_response.clear();
211 
212  for (const auto & resp_i : index_range(_response_names))
213  _current_response.push_back((*_response_values[resp_i] - _response_shift_factors[resp_i]) *
214  _response_scaling_factors[resp_i]);
215 }
216 
217 void
219 {
220  _nn = std::make_shared<Moose::LibtorchArtificialNeuralNet>(input_nn);
221 }
222 
223 torch::Tensor
225 {
226  const unsigned int num_old_timesteps = _input_timesteps - 1;
227 
228  // We convert the standard vectors to libtorch tensors
229  std::vector<Real> raw_input(_current_response);
230 
231  for (const auto & step_i : make_range(num_old_timesteps))
232  raw_input.insert(raw_input.end(), _old_responses[step_i].begin(), _old_responses[step_i].end());
233 
234  torch::Tensor input_tensor;
235  LibtorchUtils::vectorToTensor(raw_input, input_tensor);
236 
237  return input_tensor.transpose(0, 1);
238 }
239 
242 {
243  if (!hasControlNeuralNet())
244  mooseError("The neural network in the controller must exist!");
245  return *_nn;
246 }
247 
248 #endif
A time-dependent, neural network-based control of multiple input parameters.
const std::vector< PostprocessorName > & _response_names
Names of the postprocessors which contain the observations of the system.
const std::vector< Real > _action_scaling_factors
Multipliers for the actions.
std::vector< Real > _current_response
The values of the current observed postprocessor values.
const std::vector< Real > _response_scaling_factors
Scaling constants (multipliers) for the responses.
static InputParameters validParams()
Class constructor.
Definition: Control.C:16
LibtorchNeuralNetControl(const InputParameters &parameters)
Construct using input parameters.
const Moose::LibtorchNeuralNetBase & controlNeuralNet() const
Return a reference to the stored neural network.
static InputParameters validParams()
std::shared_ptr< Moose::LibtorchNeuralNetBase > _nn
Pointer to the neural net object which is supposed to be used to control the parameter values...
Real getSignal(const unsigned int signal_index) const
Get the (signal_index)-th signal of the control neural net.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
registerMooseObject("MooseApp", LibtorchNeuralNetControl)
void addRequiredParam(const std::string &name, const std::string &doc_string)
This method adds a parameter and documentation string to the InputParameters object that will be extr...
torch::Tensor prepareInputTensor()
Function that prepares the input tensor for the controller neural network.
const std::vector< std::string > & _control_names
The names of the controllable parameters.
bool isParamValid(const std::string &name) const
Test if the supplied parameter is valid.
bool hasControlNeuralNet() const
Return true if the object already has a neural netwok.
void vectorToTensor(std::vector< DataType > &vector, torch::Tensor &tensor, const bool detach=false)
Utility function that converts a standard vector to a torch::Tensor.
Definition: LibtorchUtils.C:19
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
std::vector< std::vector< Real > > & _old_responses
This variable is populated if the controller needs acess to older values of the observed postprocesso...
virtual const PostprocessorValue & getPostprocessorValueByName(const PostprocessorName &name) const
Retrieve the value of the Postprocessor.
bool isParamSetByUser(const std::string &name) const
Method returns true if the parameter was set by the user.
bool isParamSetByUser(const std::string &nm) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const unsigned int _input_timesteps
Number of timesteps to use as input data from the reporters (this influences how many past results ar...
This base class is meant to gather the functions and members common in every neural network based on ...
Base class for Control objects.
Definition: Control.h:33
IntRange< T > make_range(T beg, T end)
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type.
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
const InputParameters & parameters() const
Get the parameters of the object.
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
virtual void execute() override
Execute neural network to determine the controllable parameter values.
std::vector< const Real * > _response_values
Links to the current response postprocessor values.
void updateCurrentResponse()
Function that updates the values of the current response.
void loadControlNeuralNet(const Moose::LibtorchArtificialNeuralNet &input_nn)
Function responsible for loading the neural network for the controller.
std::vector< Real > _current_control_signals
The control signals from the last evaluation of the controller.
void ErrorVector unsigned int
auto index_range(const T &sizable)
void conditionalParameterError(const std::string &param_name, const std::vector< std::string > &conditional_param, bool should_be_defined=true)
Function responsible for checking for potential user errors in the input file.
const std::vector< Real > _response_shift_factors
Shifting constants for the responses.