18#include "libmesh/id_types.h"
19#include "neml2/tensors/functions/jacrev.h"
20#include "neml2/dispatchers/ValueMapLoader.h"
21#include "neml2/misc/string_utils.h"
22#include "neml2/base/Settings.h"
31 params.addParam<
bool>(
32 "manage_state_advance",
34 "Keep state and forces on the device and advance it to old_state and old_forces without a "
35 "roundtrip through MOOSE materials. This is only recommended for explicit time integration "
36 "or when absolutely no restepping occurs (e.g. failed timesteps).");
37 params.addParam<
bool>(
38 "debug_inputs_on_failure",
40 "When a NEML2 solve fails, append a detailed dump of input tensors (defined/missing, "
41 "shapes, and devices) to the error message.");
50 params.addClassDescription(
"Execute the specified NEML2 model");
52 params.addRequiredParam<UserObjectName>(
53 "batch_index_generator",
54 "The NEML2BatchIndexGenerator used to generate the element-to-batch-index map.");
55 params.addParam<std::vector<UserObjectName>>(
58 "List of MOOSE*ToNEML2 user objects gathering MOOSE data as NEML2 input variables");
59 params.addParam<std::vector<UserObjectName>>(
62 "List of MOOSE*ToNEML2 user objects gathering MOOSE data as NEML2 model parameters");
69 params.set<
ExecFlagEnum>(
"execute_on") = execute_options;
79 _manage_state_advance(getParam<bool>(
"manage_state_advance")),
80 _debug_inputs_on_failure(getParam<bool>(
"debug_inputs_on_failure")),
89 for (
const auto & gatherer_name : getParam<std::vector<UserObjectName>>(
"gatherers"))
91 for (
const auto & gatherer_name : getParam<std::vector<UserObjectName>>(
"param_gatherers"))
101 for (
const auto & gatherer_name :
getParam<
std::vector<UserObjectName>>(
"gatherers"))
105 const auto & uo = getUserObjectByName<MOOSEToNEML2>(gatherer_name,
false);
108 auto sep =
model().settings().history_separator();
109 auto [base_name, history_order] = neml2::parse_history(uo.NEML2Name(), sep);
112 "The gatherer for history variable `",
114 "` is not needed when `manage_state_advance = true`.");
121 for (
const auto & gatherer_name :
getParam<
std::vector<UserObjectName>>(
"param_gatherers"))
125 const auto & uo = getUserObjectByName<MOOSEToNEML2>(gatherer_name,
false);
131 for (
const auto & [iname, ivar] :
model().input_variables())
137 paramError(
"gatherers",
"The required model input `", iname,
"` is not gathered");
142 for (
const auto & [iname, ivar] :
model().input_variables())
143 if (ivar->history_order() > 0)
155 const neml2::VariableName & var)
159 "The NEML2 input variable `",
161 "` gathered by UO '",
163 "' is already gathered by another gatherer.");
169 const std::string & param)
173 "The NEML2 model parameter `",
175 "` gathered by UO '",
177 "' is already gathered by another gatherer.");
200 mooseError(
"The mesh changed while `manage_state_advance = true` for NEML2 model executor '",
202 "'. This mode requires a fixed mesh because state history is cached on the device.");
226 auto success =
solve();
248 for (
auto & [var, val] :
_in)
259 for (
const auto & [p, tensor] : dy)
260 model().get_parameter(p).requires_grad_(
true);
262 catch (std::exception & e)
264 mooseError(
"An error occurred while filling inputs for the NEML2 model. Error message:\n",
274 std::vector<neml2::Tensor> defined;
275 for (
const auto & [key, value] :
_in)
276 defined.push_back(value);
277 const auto s = neml2::utils::broadcast_dynamic_sizes(defined);
280 for (
auto & [key, value] :
_in)
281 if (value.dynamic_sizes() != s)
282 _in[key] = value.dynamic_unsqueeze(0).dynamic_expand(s);
293 auto sep =
model().settings().history_separator();
294 auto [base_name, order] = neml2::parse_history(
name, sep);
295 mooseAssert(order > 0,
"Invalid history order");
298 auto curr_name = order == 1 ? base_name : base_name + sep + std::to_string(order - 1);
299 if (
_out.count(curr_name))
301 else if (
_in.count(curr_name))
304 mooseError(
"Failed to find cached value for history variable: ",
name);
314 TIME_SECTION(
"NEML2 solve", 3,
"Solving NEML2 material model");
317 auto prev_dtype = neml2::get_default_dtype();
318 neml2::set_default_dtype(neml2::kFloat64);
324 neml2::ValueMapLoader loader(
_in, 0);
333 neml2::set_default_dtype(prev_dtype);
335 catch (std::exception & e)
341 auto shape_to_string = [](
const neml2::TensorShapeRef & shape) -> std::string
343 std::ostringstream os;
345 for (std::size_t i = 0; i < shape.size(); ++i)
355 std::ostringstream os;
356 os <<
"\nNEML2 input variables:\n";
357 for (
const auto & [var, val] :
model().input_variables())
359 os <<
" - " << var <<
": ";
360 const auto it =
_in.find(var);
363 else if (!it->second.defined())
367 const auto & val = it->second;
368 const auto & v =
model().input_variable(var);
369 neml2::TensorShape expected;
370 const auto & intmd_sizes = v.intmd_sizes();
371 expected.insert(expected.end(), intmd_sizes.begin(), intmd_sizes.end());
372 const auto & base_sizes = v.base_sizes();
373 expected.insert(expected.end(), base_sizes.begin(), base_sizes.end());
375 os <<
"device=" << val.device() <<
" dtype=" << val.scalar_type()
376 <<
" sizes=" << shape_to_string(val.sizes())
377 <<
" batch=" << shape_to_string(val.batch_sizes().concrete())
378 <<
" expected_base=" << shape_to_string(expected);
382 auto cpu = val.detach().to(val.options().device(at::kCPU));
383 auto flat = cpu.reshape({-1});
384 auto min = flat.min().item<
double>();
385 auto max = flat.max().item<
double>();
386 auto mean = flat.mean().item<
double>();
387 auto has_nan = at::isnan(flat).any().item<
bool>();
388 auto has_inf = at::isinf(flat).any().item<
bool>();
389 os <<
" min=" << min <<
" max=" << max <<
" mean=" << mean
390 <<
" nan=" << (has_nan ?
"true" :
"false")
391 <<
" inf=" << (has_inf ?
"true" :
"false");
400 os <<
"NEML2 stateful variables:\n";
403 os <<
" - " << var <<
": ";
404 const auto it_out =
_out.find(var);
405 const auto it_in =
_in.find(var);
406 if (it_out ==
_out.end() || it_in ==
_in.end())
410 const auto it = it_out !=
_out.end() ? it_out : it_in;
411 const auto & val = it->second;
412 os <<
"device=" << val.device() <<
" dtype=" << val.scalar_type()
413 <<
" sizes=" << shape_to_string(val.sizes())
414 <<
" batch=" << shape_to_string(val.batch_sizes().concrete());
418 auto cpu = val.detach().to(val.options().device(at::kCPU));
419 auto flat = cpu.reshape({-1});
420 auto min = flat.min().item<
double>();
421 auto max = flat.max().item<
double>();
422 auto mean = flat.mean().item<
double>();
423 auto has_nan = at::isnan(flat).any().item<
bool>();
424 auto has_inf = at::isinf(flat).any().item<
bool>();
425 os <<
" min=" << min <<
" max=" << max <<
" mean=" << mean
426 <<
" nan=" << (has_nan ?
"true" :
"false")
427 <<
" inf=" << (has_inf ?
"true" :
"false");
454 for (
auto & [p, target] : dy)
455 target = neml2::jacrev(
_out[y],
456 model().get_parameter(p),
468 for (
auto & [x, target] : dy)
471 if (source.defined())
472 target = source.to(
output_device()).dynamic_expand({neml2::Size(N)});
478 catch (std::exception & e)
480 mooseError(
"An error occurred while retrieving outputs from the NEML2 model. Error message:\n",
493 processor_id_type pid;
502 std::string msg =
"NEML2 model execution failed on at least one processor with ID " +
503 std::to_string(pid) +
". Error message:\n";
506 msg +=
"\nTo recover, the solution will fail and then be re-attempted with a reduced time "
508 _console << COLOR_YELLOW << msg << COLOR_DEFAULT << std::endl;
520 mooseError(
"NEML2 output variables and derivatives must be retrieved during object "
521 "construction. This is a code problem.");
529 if (!
model().output_variables().
count(output_name))
530 mooseError(
"Trying to retrieve a non-existent NEML2 output variable '", output_name,
"'.");
537 const neml2::VariableName & input_name)
const
541 if (!
model().output_variables().
count(output_name))
542 mooseError(
"Trying to retrieve the derivative of NEML2 output variable '",
544 "' with respect to NEML2 input variable '",
546 "', but the NEML2 output variable does not exist.");
548 if (!
model().input_variables().
count(input_name))
549 mooseError(
"Trying to retrieve the derivative of NEML2 output variable '",
551 "' with respect to NEML2 input variable '",
553 "', but the NEML2 input variable does not exist.");
560 const std::string & parameter_name)
const
564 if (!
model().output_variables().
count(output_name))
565 mooseError(
"Trying to retrieve the derivative of NEML2 output variable '",
567 "' with respect to NEML2 model parameter '",
569 "', but the NEML2 output variable does not exist.");
571 if (
model().named_parameters().
count(parameter_name) != 1)
572 mooseError(
"Trying to retrieve the derivative of NEML2 output variable '",
574 "' with respect to NEML2 model parameter '",
576 "', but the NEML2 model parameter does not exist.");
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
const ExecFlagType EXEC_TIMESTEP_END
const ExecFlagType EXEC_INITIAL
const ExecFlagType EXEC_LINEAR
const ExecFlagType EXEC_NONLINEAR
registerMooseObject("MooseApp", NEML2ModelExecutor)
if(!dmm->_nl) SETERRQ(PETSC_COMM_WORLD
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
A MultiMooseEnum object to hold "execute_on" flags.
void setFailNextNonlinearConvergenceCheck()
Skip further residual evaluations and fail the next nonlinear convergence check(s)
virtual bool solverSystemConverged(const unsigned int solver_sys_num) override
virtual bool isTransient() const override
virtual bool startedInitialSetup()
Returns true if we are in or beyond the initialSetup stage.
const std::string & name() const
Get the name of the class.
void paramError(const std::string ¶m, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
const T & getParam(const std::string &name) const
Retrieve a parameter for the object.
NEML2BatchIndexGenerator iterates over the mesh and generates a map from element ID to batch index wh...
bool isEmpty() const
Whether the batch is empty.
std::size_t getBatchIndex() const
Get the current batch index (in almost all cases this is the total batch size)
NEML2ModelExecutor executes a NEML2 model.
std::map< neml2::VariableName, std::map< std::string, neml2::Tensor > > _retrieved_parameter_derivatives
set of parameter derivatives that were retrieved (by other objects)
neml2::DerivMap _retrieved_derivatives
set of derivatives that were retrieved (by other objects)
void initialSetup() override
Gets called at the beginning of the simulation before this object is asked to do its job.
const bool _debug_inputs_on_failure
Dump input tensor info on failure to aid debugging.
neml2::ValueMap _out
The output variables of the material model.
bool _error
Whether an error was encountered.
void execute() override
Execute method.
const neml2::Tensor & getOutputDerivative(const neml2::VariableName &output_name, const neml2::VariableName &input_name) const
Get a reference(!) to the requested output derivative view.
std::string _error_message
Error message.
static InputParameters actionParams()
Parameters that can be specified under the NEML2Action common area.
virtual bool solve()
Perform the material update.
std::set< std::string > _gathered_parameter_names
neml2::ValueMap _retrieved_outputs
set of output variables that were retrieved (by other objects)
neml2::ValueMap _in
The input variables of the material model.
std::vector< const MOOSEToNEML2 * > _param_gatherers
virtual void checkExecutionStage() const final
Prevent output and derivative retrieval after construction.
static InputParameters validParams()
const bool _manage_state_advance
Advance state on device (rather than via MOSOE material properties)
bool _output_ready
flag that indicates if output data has been fully computed
const neml2::Tensor & getOutputParameterDerivative(const neml2::VariableName &output_name, const std::string ¶meter_name) const
Get a reference(!) to the requested output parameter derivative view.
virtual void addGatheredVariable(const UserObjectName &, const neml2::VariableName &)
Register a NEML2 input variable gathered by a gatherer.
std::size_t getBatchIndex(dof_id_type elem_id) const
Get the batch index for the given element ID.
void advanceState()
Save stateful variables for on-device state advance.
void finalize() override
Finalize.
std::map< std::string, neml2::Tensor > _model_params
The model parameters to update (gathered from MOOSE)
const NEML2BatchIndexGenerator & _batch_index_generator
The NEML2BatchIndexGenerator used to generate the element-to-batch-index map.
neml2::DerivMap _dout_din
The derivative of the output variables w.r.t. the input variables.
virtual void extractOutputs()
Extract output derivatives with respect to input variables and model parameters.
void initialize() override
Called before execute() is ever called so that data can be cleared.
neml2::ValueMap _state_vars
Cached variables from the last successful step (for on-device advance)
virtual void fillInputs()
Fill input variables and model parameters using the gatherers.
NEML2ModelExecutor(const InputParameters ¶ms)
const neml2::Tensor & getOutput(const neml2::VariableName &output_name) const
Get a reference(!) to the requested output view.
virtual void expandInputs()
Expand tensor shapes if necessary to conformal sizes.
std::vector< const MOOSEToNEML2 * > _gatherers
MOOSE data gathering user objects.
virtual void addGatheredParameter(const UserObjectName &, const std::string &)
Register a NEML2 model parameter gathered by a gatherer.
void meshChanged() override
Called on this object when the mesh changes.
std::set< neml2::VariableName > _gathered_variable_names
Interface class to provide common input parameters, members, and methods for MOOSEObjects that use NE...
static InputParameters validParams()
virtual void validateModel() const
Validate the NEML2 material model.
neml2::Model & model() const
Get the NEML2 model.
neml2::WorkScheduler * scheduler()
Get the work scheduler.
const neml2::Device & output_device() const
Get the target output device.
const std::unique_ptr< DispatcherType > & dispatcher() const
Get the work dispatcher.
const neml2::Device & device() const
Get the target compute device.
const ExecFlagType & _current_execute_flag
Reference to FEProblemBase.
void maxloc(T &r, unsigned int &max_id) const
processor_id_type rank() const
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
int & _t_step
The number of the time step.
std::set< std::string > _depend_uo
Depend UserObjects that to be used both for determining user object sorting and by AuxKernel for find...
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this user object.
const Parallel::Communicator & _communicator
ExecFlagEnum getDefaultExecFlagEnum()
bool shouldCompute(const SubProblem &)
Determine whether the NEML2 material model should be evaluated.
static std::string NEML2_help_message