10#ifdef MOOSE_LIBTORCH_ENABLED
30readArchiveTensor(torch::serialize::InputArchive & archive,
31 const std::string & key,
32 torch::Tensor & tensor)
36 archive.read(key, tensor);
39 catch (
const c10::Error &)
51copyTensor(torch::Tensor & destination,
const torch::Tensor & source)
53 destination.data().copy_(source.to(destination.options()));
58isOptionalArtificialNeuralNetBuffer(
const std::string & key)
60 return key ==
"input_shift" || key ==
"input_scale" || key ==
"output_scale";
70template <
typename NamedTensorList>
72hasNamedTensor(
const NamedTensorList & tensors,
const std::string & key, torch::Tensor & tensor)
74 for (
const auto & entry : tensors)
93 const std::string & filename,
98 torch::serialize::InputArchive archive;
99 archive.load_from(filename);
101 for (
auto & parameter : nn.named_parameters())
103 torch::Tensor stored_tensor;
104 if (!readArchiveTensor(archive, parameter.key(), stored_tensor))
106 error =
"Missing serialized parameter: " + parameter.key();
110 copyTensor(parameter.value(), stored_tensor);
113 for (
auto & buffer : nn.named_buffers())
115 torch::Tensor stored_tensor;
116 if (!readArchiveTensor(archive, buffer.key(), stored_tensor))
118 if (isOptionalArtificialNeuralNetBuffer(buffer.key()))
121 error =
"Missing serialized buffer: " + buffer.key();
125 copyTensor(buffer.value(), stored_tensor);
131 catch (
const c10::Error & e)
147 const std::string & filename,
152 const auto scripted = torch::jit::load(filename);
153 const auto scripted_parameters = scripted.named_parameters();
154 const auto scripted_buffers = scripted.named_buffers();
156 for (
auto & parameter : nn.named_parameters())
158 torch::Tensor stored_tensor;
159 if (!hasNamedTensor(scripted_parameters, parameter.key(), stored_tensor))
161 error =
"Missing scripted parameter: " + parameter.key();
165 copyTensor(parameter.value(), stored_tensor);
168 for (
auto & buffer : nn.named_buffers())
170 torch::Tensor stored_tensor;
171 if (!hasNamedTensor(scripted_buffers, buffer.key(), stored_tensor))
173 if (isOptionalArtificialNeuralNetBuffer(buffer.key()))
176 error =
"Missing scripted buffer: " + buffer.key();
180 copyTensor(buffer.value(), stored_tensor);
186 catch (
const c10::Error & e)
199 const std::string name,
200 const unsigned int num_inputs,
201 const unsigned int num_outputs,
202 const std::vector<unsigned int> & num_neurons_per_layer,
203 const std::vector<std::string> & activation_function,
204 const torch::DeviceType device_type,
205 const torch::ScalarType data_type,
206 const bool build_on_construct,
207 const std::vector<Real> & input_shift_factors,
208 const std::vector<Real> & input_scaling_factors,
209 const std::vector<Real> & output_scaling_factors)
211 _num_inputs(num_inputs),
212 _num_outputs(num_outputs),
213 _num_neurons_per_layer(num_neurons_per_layer),
214 _activation_function(
MultiMooseEnum(
"relu sigmoid elu gelu linear tanh",
"relu")),
215 _device_type(device_type),
216 _data_type(data_type),
217 _input_shift_factors(
218 setAffineFactors(input_shift_factors, num_inputs, 0.0,
"input_shift_factors")),
219 _input_scaling_factors(
220 setAffineFactors(input_scaling_factors, num_inputs, 1.0,
"input_scaling_factors")),
221 _output_scaling_factors(
222 setAffineFactors(output_scaling_factors, num_outputs, 1.0,
"output_scaling_factors"))
230 mooseError(
"The number of activation functions should be either one or the same as the number "
233 if (build_on_construct)
239 : torch::nn::Module(),
241 _num_inputs(nn.numInputs()),
242 _num_outputs(nn.numOutputs()),
243 _num_neurons_per_layer(nn.numNeuronsPerLayer()),
244 _activation_function(nn.activationFunctions()),
245 _device_type(nn.deviceType()),
246 _data_type(nn.dataType()),
247 _input_shift_factors(nn.inputShiftFactors()),
248 _input_scaling_factors(nn.inputScalingFactors()),
249 _output_scaling_factors(nn.outputScalingFactors())
254 if (build_on_construct)
258 const auto & from_params = nn.named_parameters();
259 auto to_params = this->named_parameters();
260 for (
unsigned int param_i : make_range(from_params.size()))
261 to_params[param_i].value().data() = from_params[param_i].value().data().clone();
263 const auto & from_buffers = nn.named_buffers();
264 auto to_buffers = this->named_buffers();
265 for (
unsigned int buffer_i : make_range(from_buffers.size()))
266 to_buffers[buffer_i].value().data() = from_buffers[buffer_i].value().data().clone();
273 if (activation ==
"relu")
275 if (activation ==
"tanh")
286 const auto & activation =
290 torch::nn::init::zeros_(
_weights[i]->bias);
294 torch::nn::init::zeros_(
_weights.back()->bias);
299 const unsigned int expected_size,
300 const Real default_value,
301 const std::string & factor_name)
303 const auto resolved_factors =
304 factors.empty() ? std::vector<Real>(expected_size, default_value) : factors;
306 if (resolved_factors.size() != expected_size)
307 mooseError(
"The number of ", factor_name,
" entries must match ", expected_size,
".");
309 return resolved_factors;
334 auto input_shift =
_input_shift_tensor.detach().reshape({-1}).to(torch::kCPU).to(torch::kDouble);
337 auto input_scale =
_input_scale_tensor.detach().reshape({-1}).to(torch::kCPU).to(torch::kDouble);
352 std::unordered_map<std::string, unsigned int> parameters = {
354 addLayer(
"hidden_layer_" + std::to_string(i + 1), parameters);
361 std::unordered_map<std::string, unsigned int> parameters = {{
"inp_neurons", inp_neurons},
363 addLayer(
"output_layer_", parameters);
370 torch::Tensor input(x);
390 for (
unsigned int i = 0; i <
_weights.size() - 1; ++i)
392 std::string activation =
394 if (activation ==
"relu")
396 else if (activation ==
"sigmoid")
398 else if (activation ==
"tanh")
400 else if (activation ==
"elu")
402 else if (activation ==
"gelu")
404 else if (activation ==
"linear")
405 output =
_weights[i]->forward(output);
413 const std::string & layer_name,
414 const std::unordered_map<std::string, unsigned int> & parameters)
416 auto it = parameters.find(
"inp_neurons");
417 if (it == parameters.end())
418 ::mooseError(
"Number of input neurons not found during the construction of "
419 "LibtorchArtificialNeuralNet!");
420 unsigned int inp_neurons = it->second;
422 it = parameters.find(
"out_neurons");
423 if (it == parameters.end())
424 ::mooseError(
"Number of output neurons not found during the construction of "
425 "LibtorchArtificialNeuralNet!");
426 unsigned int out_neurons = it->second;
428 _weights.push_back(register_module(layer_name, torch::nn::Linear(inp_neurons, out_neurons)));
434 const auto & named_params = this->named_parameters();
435 for (
const auto & param_i : make_range(named_params.size()))
438 json[named_params[param_i].key()] = std::vector<Real>(
439 named_params[param_i].value().data_ptr<Real>(),
440 named_params[param_i].value().data_ptr<Real>() + named_params[param_i].value().numel());
452 network->
store(json);
457 const std::string & filename)
459 std::string archive_error;
460 if (loadArtificialNeuralNetStateFromArchive(nn, filename, archive_error))
463 std::string scripted_error;
464 if (loadArtificialNeuralNetStateFromTorchScript(nn, filename, scripted_error))
467 mooseError(
"The requested pytorch parameter file could not be loaded. This can either be the "
468 "result of the file not existing or a misalignment in the generated container and "
469 "the data in the file. Make sure the dimensions of the generated neural net are the "
470 "same as the dimensions of the parameters in the input file!\nArchive load error: ",
472 "\nTorchScript load error: ",
480 std::ostream & stream, std::shared_ptr<Moose::LibtorchArtificialNeuralNet> & nn,
void * context)
482 std::string n(nn->name());
485 unsigned int ni(nn->numInputs());
488 unsigned int no(nn->numOutputs());
491 unsigned int nhl(nn->numHiddenLayers());
494 std::vector<unsigned int> nnpl(nn->numNeuronsPerLayer());
497 unsigned int afs(nn->activationFunctions().size());
500 std::vector<std::string> items(afs);
501 for (
unsigned int i = 0; i < afs; ++i)
502 items[i] = nn->activationFunctions()[i];
506 auto device_type =
static_cast<std::underlying_type<torch::DeviceType>::type
>(nn->deviceType());
509 auto data_type =
static_cast<std::underlying_type<torch::ScalarType>::type
>(nn->dataType());
512 torch::save(nn, nn->name());
518 std::istream & stream, std::shared_ptr<Moose::LibtorchArtificialNeuralNet> & nn,
void * context)
523 unsigned int num_inputs;
524 dataLoad(stream, num_inputs, context);
526 unsigned int num_outputs;
527 dataLoad(stream, num_outputs, context);
529 unsigned int num_hidden_layers;
530 dataLoad(stream, num_hidden_layers, context);
532 std::vector<unsigned int> num_neurons_per_layer;
533 num_neurons_per_layer.resize(num_hidden_layers);
534 dataLoad(stream, num_neurons_per_layer, context);
536 unsigned int num_activation_items;
537 dataLoad(stream, num_activation_items, context);
539 std::vector<std::string> activation_functions;
540 activation_functions.resize(num_activation_items);
541 dataLoad(stream, activation_functions, context);
543 std::underlying_type<torch::DeviceType>::type device_type;
544 dataLoad(stream, device_type, context);
545 const torch::DeviceType divt(
static_cast<torch::DeviceType
>(device_type));
547 std::underlying_type<torch::ScalarType>::type data_type;
548 dataLoad(stream, data_type, context);
549 const torch::ScalarType datt(
static_cast<torch::ScalarType
>(data_type));
551 nn = std::make_shared<Moose::LibtorchArtificialNeuralNet>(
552 name, num_inputs, num_outputs, num_neurons_per_layer, activation_functions, divt, datt);
void dataStore< Moose::LibtorchArtificialNeuralNet >(std::ostream &stream, std::shared_ptr< Moose::LibtorchArtificialNeuralNet > &nn, void *context)
Serialize the artificial-neural-network metadata needed for restart.
void dataLoad< Moose::LibtorchArtificialNeuralNet >(std::istream &stream, std::shared_ptr< Moose::LibtorchArtificialNeuralNet > &nn, void *context)
Deserialize the artificial-neural-network metadata needed for restart.
void dataLoad< Moose::LibtorchArtificialNeuralNet const >(std::istream &, Moose::LibtorchArtificialNeuralNet const *&, void *)
Placeholder deserializer for reporter-only artificial-neural-network pointers.
void dataStore< Moose::LibtorchArtificialNeuralNet const >(std::ostream &, Moose::LibtorchArtificialNeuralNet const *&, void *)
Placeholder serializer for reporter-only artificial-neural-network pointers.
void dataLoad(std::istream &stream, LineSegment &l, void *context)
void dataStore(std::ostream &stream, LineSegment &l, void *context)
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
if(!dmm->_nl) SETERRQ(PETSC_COMM_WORLD
Simple feed-forward neural net with optional affine input and output scaling.
const unsigned int _num_inputs
Number of neurons on the input layer.
Real determineGain(const std::string &activation)
Map an activation name to the orthogonal-initialization gain we want to use.
virtual torch::Tensor forward(const torch::Tensor &x) override
Run a forward pass through the network.
torch::Tensor _input_shift_tensor
Registered libtorch buffer holding the affine input shifts.
const torch::ScalarType _data_type
The data type used in this neural network.
void store(nlohmann::json &json) const
Store the network architecture in a JSON object for debugging or visualization.
static std::vector< Real > setAffineFactors(const std::vector< Real > &factors, unsigned int expected_size, Real default_value, const std::string &factor_name)
Set affine metadata by either accepting the user values or filling defaults.
std::vector< Real > _input_shift_factors
Affine preprocessing applied to the flattened input.
void synchronizeAffineFactorsFromBuffers()
Update cached affine metadata vectors from the registered libtorch buffers.
MultiMooseEnum _activation_function
Activation functions (either one for all hidden layers or one for every layer separately)
std::vector< Real > _input_scaling_factors
Multiplicative affine preprocessing applied after shifting the input.
std::vector< torch::nn::Linear > _weights
Submodules that hold linear operations and the corresponding weights and biases (y = W * x + b)
void initializeAffineBuffers()
Initialize the registered affine metadata buffers used by serialization.
virtual void addLayer(const std::string &layer_name, const std::unordered_map< std::string, unsigned int > ¶meters)
Add one linear layer to the network.
torch::Tensor _input_scale_tensor
Registered libtorch buffer holding the affine input scaling factors.
const unsigned int _num_outputs
Number of neurons on the output layer.
torch::Tensor _output_scale_tensor
Registered libtorch buffer holding the output scaling factors.
const torch::DeviceType _device_type
The device type used for this neural network.
virtual torch::Tensor preprocessInput(const torch::Tensor &x) const
Apply affine preprocessing to the raw input tensor.
unsigned int numHiddenLayers() const
Return the number of hidden layers.
LibtorchArtificialNeuralNet(const std::string name, const unsigned int num_inputs, const unsigned int num_outputs, const std::vector< unsigned int > &num_neurons_per_layer, const std::vector< std::string > &activation_function={"relu"}, const torch::DeviceType device_type=torch::kCPU, const torch::ScalarType scalar_type=torch::kDouble, const bool build_on_construct=true, const std::vector< Real > &input_shift_factors={}, const std::vector< Real > &input_scaling_factors={}, const std::vector< Real > &output_scaling_factors={})
Build a plain feed-forward neural network.
const std::vector< unsigned int > _num_neurons_per_layer
Hidden layer architecture.
virtual void initializeNeuralNetwork(c10::optional< at::Generator > generator=c10::nullopt)
Initialize the trainable weights and biases.
std::vector< Real > _output_scaling_factors
Multiplicative scaling applied after the network output is formed.
virtual torch::Tensor scaleOutput(const torch::Tensor &y) const
Apply the configured output scaling to a network output tensor.
virtual void constructNeuralNetwork()
Construct the neural network.
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type.
unsigned int size() const
Return the number of active items in the MultiMooseEnum.
void vectorToTensor(const std::vector< DataType > &vector, torch::Tensor &tensor, const bool detach=false)
Utility function that converts a standard vector to a torch::Tensor.
void tensorToVector(torch::Tensor &tensor, std::vector< DataType > &vector)
Utility function that converts a torch::Tensor to a standard vector.
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
void to_json(nlohmann::json &json, const Moose::LibtorchArtificialNeuralNet *const &network)
Dump an artificial neural network into JSON for reporter output and debugging.
void orthogonalInitializeTensor(torch::Tensor &tensor, Real gain=1.0, c10::optional< at::Generator > generator=c10::nullopt)
Fill a tensor with a (semi) orthogonal matrix using the provided generator.
void loadLibtorchArtificialNeuralNetState(Moose::LibtorchArtificialNeuralNet &nn, const std::string &filename)
Load an artificial-neural-network checkpoint written as a native libtorch state archive.
std::string name(const ElemQuality q)