https://mooseframework.inl.gov
Loading...
Searching...
No Matches
TorchScript1DProfileFunctorMaterial.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
11
12#ifdef MOOSE_LIBTORCH_ENABLED
13
14#include "ExecFlagEnum.h"
15
16#include <cmath>
17#include <cstdint>
18#include <exception>
19
21
24{
26
27 params.addClassDescription("Evaluates a TorchScript model that returns sampled one-dimensional "
28 "profiles and exposes those profiles as interpolated Real functors.");
29
30 params.addRequiredParam<UserObjectName>(
31 "torch_script_userobject",
32 "TorchScriptUserObject containing the deployed TorchScript model.");
33
34 params.addParam<std::vector<PostprocessorName>>(
35 "input_names",
36 {},
37 "Ordered postprocessor values passed to the model. Specify either "
38 "input_names or input_values, but not both.");
39
40 params.addParam<std::vector<Real>>(
41 "input_values",
42 {},
43 "Ordered constant values passed directly to the model. Specify either "
44 "input_names or input_values, but not both.");
45
46 params.addRequiredParam<std::vector<MooseFunctorName>>(
47 "profile_names",
48 "Names assigned to model output channels. The number of names must "
49 "equal the model output channel count.");
50
51 params.addRequiredParam<std::vector<Real>>(
52 "profile_coordinates",
53 "Strictly increasing coordinates corresponding to the model output "
54 "stations.");
55
56 params.addRequiredParam<Point>("profile_origin",
57 "Physical point corresponding to profile coordinate zero.");
58
59 params.addRequiredParam<RealVectorValue>(
60 "profile_direction",
61 "Physical direction of increasing profile coordinate. The direction "
62 "is normalized internally.");
63
64 params.addParam<Real>(
65 "coordinate_scale", 1.0, "Physical distance corresponding to one model-coordinate unit.");
66
67 params.addParam<MooseEnum>("out_of_range_behavior",
68 MooseEnum("error clamp extrapolate", "error"),
69 "Behavior when a MOOSE evaluation point lies outside the supplied "
70 "profile coordinate range.");
71
72 params.addParam<MooseEnum>("tensor_dtype",
73 MooseEnum("float32 float64", "float32"),
74 "Floating-point type expected by the TorchScript model.");
75
76 // The model consumes only scalar inputs (constants or postprocessor values),
77 // so re-evaluation is meaningful at most once per step. Offer only the flags
78 // whose material setup hooks re-run inference: INITIAL -> initialSetup(),
79 // TIMESTEP_BEGIN -> timestepSetup().
80 ExecFlagEnum execute_options;
82 execute_options = EXEC_INITIAL;
83 params.addParam<ExecFlagEnum>(
84 "execute_on",
85 execute_options,
86 "When the TorchScript model is (re-)evaluated to rebuild the profiles. " +
87 execute_options.getDocString());
88
89 return params;
90}
91
93 const InputParameters & parameters)
94 : FunctorMaterial(parameters),
95 _torch_script_userobject(getUserObject<TorchScriptUserObject>("torch_script_userobject")),
96 _input_names(getParam<std::vector<PostprocessorName>>("input_names")),
97 _input_values(getParam<std::vector<Real>>("input_values")),
98 _profile_names(getParam<std::vector<MooseFunctorName>>("profile_names")),
99 _profile_coordinates(getParam<std::vector<Real>>("profile_coordinates")),
100 _profile_origin(getParam<Point>("profile_origin")),
101 _profile_direction(getParam<RealVectorValue>("profile_direction")),
102 _coordinate_scale(getParam<Real>("coordinate_scale")),
103 _out_of_range_behavior(getParam<MooseEnum>("out_of_range_behavior")),
104 _tensor_dtype(getParam<MooseEnum>("tensor_dtype")),
105 _profile_update_schedule(_execute_enum.begin(), _execute_enum.end())
106{
107 const bool using_postprocessors = !_input_names.empty();
108 const bool using_constants = !_input_values.empty();
109
110 if (using_postprocessors == using_constants)
111 paramError("input_names", "Specify exactly one of input_names or input_values.");
112
113 if (_profile_names.empty())
114 paramError("profile_names", "At least one output profile name is required.");
115
116 if (_profile_coordinates.empty())
117 paramError("profile_coordinates", "At least one profile coordinate is required.");
118
119 for (const auto i : index_range(_profile_coordinates))
120 {
121 if (!std::isfinite(_profile_coordinates[i]))
122 paramError("profile_coordinates", "All profile coordinates must be finite.");
123
124 if (i > 0 && _profile_coordinates[i] <= _profile_coordinates[i - 1])
125 paramError("profile_coordinates", "Profile coordinates must be strictly increasing.");
126 }
127
128 if (!std::isfinite(_coordinate_scale) || _coordinate_scale <= 0.0)
129 paramError("coordinate_scale", "coordinate_scale must be finite and greater than zero.");
130
131 const Real direction_norm = _profile_direction.norm();
132
133 if (!std::isfinite(direction_norm) || direction_norm <= 0.0)
134 paramError("profile_direction", "profile_direction must be finite and nonzero.");
135
136 _profile_direction /= direction_norm;
137
138 if (using_postprocessors)
139 for (const auto & input_name : _input_names)
141
142 _profiles.resize(_profile_names.size());
143
144 for (const auto profile_index : index_range(_profile_names))
145 addFunctorProperty<Real>(
146 _profile_names[profile_index],
147 [this, profile_index](const auto & spatial_arg, const auto &) -> Real
148 { return sampleProfile(profile_index, spatial_arg.getPoint()); },
150}
151
152torch::Tensor
154{
155 std::vector<Real> values;
156
157 if (!_input_values.empty())
159 else
160 {
161 values.resize(_postprocessor_inputs.size());
162
163 for (const auto i : index_range(_postprocessor_inputs))
165 }
166
167 for (const auto i : index_range(values))
168 if (!std::isfinite(values[i]))
169 mooseError("TorchScript1DProfileFunctorMaterial '",
170 name(),
171 "' received non-finite model input ",
172 i,
173 ": ",
174 values[i]);
175
176 auto input = torch::from_blob(values.data(),
177 {1, static_cast<std::int64_t>(values.size())},
178 torch::TensorOptions().dtype(torch::kFloat64).device(torch::kCPU))
179 .clone();
180
181 if (_tensor_dtype == "float32")
182 input = input.to(torch::kFloat32);
183
184 return input.to(_app.getLibtorchDevice());
185}
186
187void
189{
190 torch::Tensor output;
191
192 try
193 {
194 torch::NoGradGuard no_grad;
195
197 .detach()
198 .to(torch::kCPU)
199 .to(torch::kFloat64)
200 .contiguous();
201 }
202 catch (const c10::Error & error)
203 {
204 mooseError("TorchScript inference failed in ", name(), ":\n", error.what());
205 }
206 catch (const std::exception & error)
207 {
208 mooseError("TorchScript inference failed in ", name(), ":\n", error.what());
209 }
210
211 /*
212 * Normalize supported output layouts to [C, N].
213 *
214 * [N] -> [1, N]
215 * [C, N] -> unchanged
216 * [1, C, N] -> [C, N]
217 */
218 if (output.dim() == 1)
219 {
220 if (_profile_names.size() != 1)
221 mooseError("TorchScript model in ",
222 name(),
223 " returned a one-dimensional tensor, but ",
224 _profile_names.size(),
225 " profile names were requested.");
226
227 output = output.unsqueeze(0);
228 }
229 else if (output.dim() == 3)
230 {
231 if (output.size(0) != 1)
232 mooseError("TorchScript model in ",
233 name(),
234 " returned a three-dimensional tensor whose batch dimension is ",
235 output.size(0),
236 ". Only a batch size of one is supported.");
237
238 output = output.squeeze(0);
239 }
240
241 if (output.dim() != 2)
242 mooseError("TorchScript model in ",
243 name(),
244 " must return [N], [C,N], or [1,C,N]. Received a tensor with ",
245 output.dim(),
246 " dimensions.");
247
248 const auto expected_channels = static_cast<std::int64_t>(_profile_names.size());
249
250 const auto expected_stations = static_cast<std::int64_t>(_profile_coordinates.size());
251
252 if (output.size(0) != expected_channels)
253 paramError("profile_names",
254 "TorchScript model in ",
255 name(),
256 " returned ",
257 output.size(0),
258 " profile channels, but profile_names contains ",
259 expected_channels,
260 " entries.");
261
262 if (output.size(1) != expected_stations)
263 paramError("profile_coordinates",
264 "TorchScript model in ",
265 name(),
266 " returned ",
267 output.size(1),
268 " stations per profile, but profile_coordinates contains ",
269 expected_stations,
270 " entries.");
271
272 const auto output_values = output.accessor<double, 2>();
273
274 for (const auto channel : index_range(_profile_names))
275 {
276 std::vector<Real> values(_profile_coordinates.size());
277
278 for (const auto station : index_range(_profile_coordinates))
279 {
280 values[station] = output_values[channel][station];
281
282 if (!std::isfinite(values[station]))
283 mooseError("TorchScript model in ",
284 name(),
285 " returned a non-finite value for profile '",
286 _profile_names[channel],
287 "' at station ",
288 station,
289 ".");
290 }
291
292 const bool extrapolate = _out_of_range_behavior == "extrapolate";
293
294 try
295 {
296 _profiles[channel] =
297 std::make_unique<LinearInterpolation>(_profile_coordinates, values, extrapolate);
298 }
299 catch (const std::exception & error)
300 {
301 mooseError("Failed to construct profile interpolator '",
302 _profile_names[channel],
303 "' in ",
304 name(),
305 ":\n",
306 error.what());
307 }
308 }
309}
310
311void
316
317void
323
324Real
326 const Point & point) const
327{
328 if (profile_index >= _profiles.size())
329 mooseError("Invalid profile index ", profile_index, " requested from ", name(), ".");
330
331 if (!_profiles[profile_index])
332 mooseError("Profile '",
333 _profile_names[profile_index],
334 "' was evaluated before TorchScript inference completed.");
335
336 const Real coordinate = ((point - _profile_origin) * _profile_direction) / _coordinate_scale;
337
338 if (!std::isfinite(coordinate))
339 mooseError("A non-finite spatial coordinate was calculated while evaluating "
340 "profile '",
341 _profile_names[profile_index],
342 "'.");
343
344 if (_out_of_range_behavior == "error" &&
345 (coordinate < _profile_coordinates.front() || coordinate > _profile_coordinates.back()))
346 mooseError("Profile coordinate ",
347 coordinate,
348 " is outside [",
349 _profile_coordinates.front(),
350 ", ",
352 "] while evaluating profile '",
353 _profile_names[profile_index],
354 "' in ",
355 name(),
356 ".");
357
358 /*
359 * LinearInterpolation clamps endpoint values when extrapolation is false.
360 * Therefore:
361 *
362 * error handled explicitly above
363 * clamp extrapolation=false
364 * extrapolate extrapolation=true
365 */
366 return _profiles[profile_index]->sample(coordinate);
367}
368
369#endif
const ExecFlagType EXEC_TIMESTEP_BEGIN
const ExecFlagType EXEC_INITIAL
std::array< Real, 2 > values
registerMooseObject("NavierStokesApp", TorchScript1DProfileFunctorMaterial)
void addAvailableFlags(const ExecFlagType &flag, Args... flags)
static InputParameters validParams()
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
torch::DeviceType getLibtorchDevice() const
const std::string & name() const
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
MooseApp & _app
virtual const PostprocessorValue & getPostprocessorValueByName(const PostprocessorName &name) const
Evaluates a TorchScript model on the schedule given by the execute_on parameter (default: once,...
const std::vector< PostprocessorName > _input_names
Postprocessor-based model inputs.
void updateProfiles()
Evaluate the TorchScript model once and (re)build every profile interpolator in _profiles.
const TorchScriptUserObject & _torch_script_userobject
Existing MOOSE object that owns and evaluates the TorchScript module.
const std::vector< Real > _profile_coordinates
Coordinates at which the model output values are defined.
const std::vector< MooseFunctorName > _profile_names
Names assigned to the output profile functors.
RealVectorValue _profile_direction
Unit direction of increasing profile coordinate.
const Point _profile_origin
Physical origin of the one-dimensional profile coordinate.
std::vector< const PostprocessorValue * > _postprocessor_inputs
References to postprocessor values, populated when input_names is used.
const std::set< ExecFlagType > _profile_update_schedule
Execute flags on which updateProfiles() re-runs inference.
TorchScript1DProfileFunctorMaterial(const InputParameters &parameters)
const MooseEnum _out_of_range_behavior
error, clamp, or extrapolate
Real sampleProfile(unsigned int profile_index, const Point &point) const
Evaluate a cached profile at a physical point.
const Real _coordinate_scale
Physical distance corresponding to one profile-coordinate unit.
torch::Tensor buildInputTensor() const
Construct the model input tensor.
const std::vector< Real > _input_values
Direct constant model inputs.
std::vector< std::unique_ptr< LinearInterpolation > > _profiles
Cached interpolator for each model output channel.
torch::Tensor evaluate(const torch::Tensor &input) const