https://mooseframework.inl.gov
Loading...
Searching...
No Matches
LibtorchDRLControl.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
12#include "LibtorchDRLControl.h"
13#include "LibtorchRandomUtils.h"
14#include "Transient.h"
15#include "LibtorchUtils.h"
16
17#include <mutex>
18
20
23{
26 "Sets the value of multiple 'Real' input parameters and postprocessors based on a Deep "
27 "Reinforcement Learning (DRL) neural network trained using a PPO algorithm.");
28 params.set<ExecFlagEnum>("execute_on") = EXEC_TIMESTEP_BEGIN;
29 params.suppressParameter<bool>("torch_script_format");
30
31 params.addParam<unsigned int>("seed", "Seed for the random number generator.");
32
33 params.addRangeCheckedParam<unsigned int>(
34 "num_steps_in_period",
35 1,
36 "1<=num_steps_in_period",
37 "Number of time steps between policy evaluations. The observation history advances once "
38 "per period, so the trainer's 'timestep_window' should match this value.");
39 params.addRangeCheckedParam<Real>(
40 "control_smoothing_factor",
41 1.0,
42 "control_smoothing_factor>0 & control_smoothing_factor<=1",
43 "Exponential smoothing factor for the applied control signal: the fraction of the change "
44 "from the previously applied signal toward the new policy action that is applied at each "
45 "time step. A value of 1 applies the policy action directly.");
46
47 params.addParam<std::vector<Real>>(
48 "control_offsets",
49 {},
50 "Optional constant offsets added to each policy signal before setting the controlled "
51 "parameter. This can be used to train a policy on deviations from a nominal signal.");
52
53 params.addParam<bool>(
54 "stochastic",
55 true,
56 "If true, sample from the policy distribution; otherwise use the deterministic action.");
57
58 params.addParam<std::vector<Real>>(
59 "min_control_value", {}, "Optional lower bounds for each control signal.");
60 params.addParam<std::vector<Real>>(
61 "max_control_value", {}, "Optional upper bounds for each control signal.");
62 params.addParam<bool>(
63 "state_independent_std",
64 true,
65 "If true, interpret the unbounded Gaussian actor as learning one log-std per action "
66 "dimension. If false, use a state-dependent std head.");
67
68 return params;
69}
70
72 : LibtorchNeuralNetControl(parameters),
73 _current_control_signal_log_probabilities(declareRestartableData<std::vector<Real>>(
74 "current_control_signal_log_probabilities", std::vector<Real>(_control_names.size(), 0.0))),
75 _previous_control_signal(declareRestartableData<std::vector<Real>>(
76 "previous_control_signal", std::vector<Real>(_control_names.size(), 0.0))),
77 _current_smoothed_signal(declareRestartableData<std::vector<Real>>(
78 "current_smoothed_signal", std::vector<Real>(_control_names.size(), 0.0))),
79 _control_offsets(isParamSetByUser("control_offsets")
80 ? getParam<std::vector<Real>>("control_offsets")
81 : std::vector<Real>(_control_names.size(), 0.0)),
82 _has_control_offsets(isParamSetByUser("control_offsets")),
83 _policy_generator(Moose::makeLibtorchCPUGenerator()),
84 _policy_generator_state(declareRestartableData<std::vector<std::uint8_t>>(
85 "policy_generator_state", std::vector<std::uint8_t>())),
86 _num_steps_in_period(getParam<unsigned int>("num_steps_in_period")),
87 _control_smoothing_factor(getParam<Real>("control_smoothing_factor")),
88 _stochastic(getParam<bool>("stochastic"))
89{
90 const auto & execute_on = getParam<ExecFlagEnum>("execute_on");
91 if (execute_on.size() != 1 || !execute_on.contains(EXEC_TIMESTEP_BEGIN))
92 paramError("execute_on", "LibtorchDRLControl only supports 'TIMESTEP_BEGIN' for 'execute_on'.");
93
94 if (isParamValid("seed"))
95 setPolicySampleSeed(getParam<unsigned int>("seed"));
96
97 if (_control_offsets.size() != _control_names.size())
98 paramError("control_offsets",
99 "The number of control offsets must match the number of controlled parameters.");
100
102}
103
104void
111
112void
114{
115 const auto & filename = getParam<std::string>("filename");
116 unsigned int num_inputs = _observation_names.size() * _input_timesteps;
117 unsigned int num_outputs = _control_names.size();
118 std::vector<unsigned int> num_neurons_per_layer =
119 getParam<std::vector<unsigned int>>("num_neurons_per_layer");
120 std::vector<std::string> activation_functions =
121 isParamSetByUser("activation_function")
122 ? getParam<std::vector<std::string>>("activation_function")
123 : std::vector<std::string>({"relu"});
124
125 const std::vector<Real> & minimum_values = getParam<std::vector<Real>>("min_control_value");
126 const std::vector<Real> & maximum_values = getParam<std::vector<Real>>("max_control_value");
127 const auto input_shift_factors =
129 const auto input_scaling_factors =
131
132 _actor_nn =
133 std::make_shared<Moose::LibtorchActorNeuralNet>(filename,
134 num_inputs,
135 num_outputs,
136 num_neurons_per_layer,
137 activation_functions,
138 minimum_values,
139 maximum_values,
140 torch::kCPU,
141 torch::kDouble,
142 true,
143 input_shift_factors,
144 input_scaling_factors,
146 getParam<bool>("state_independent_std"));
147
149 _nn = _actor_nn;
150}
151
152void
154{
155 if (!_actor_nn)
156 {
157 mooseAssert(!_nn, "LibtorchDRLControl should not store a non-actor controller network.");
160 return;
161 }
162
163 // Transfers also execute the control once at step 0, before the transient starts.
165 "LibtorchDRLControl should only execute on TIMESTEP_BEGIN or from a transfer "
166 "before the first time step.");
167
168 const unsigned int n_controls = _control_names.size();
169 const bool first_control_execution = _old_observations.empty();
170
171 // Fill a vector with the current observation values.
173
174 // Seed the observation history with the initial observation when the control first runs.
175 if (first_control_execution)
177
179 {
180 torch::Tensor input_tensor = prepareInputTensor();
181 torch::Tensor action = _actor_nn->evaluate(input_tensor, _stochastic, _policy_generator);
183
184 if (_stochastic)
185 {
186 torch::Tensor log_probability = _actor_nn->logProbability(action);
187 _current_control_signal_log_probabilities = {log_probability.data_ptr<Real>(),
188 log_probability.data_ptr<Real>() +
189 log_probability.size(1)};
190 }
191 else
192 _current_control_signal_log_probabilities.assign(n_controls, 0.0);
193
194 _current_control_signals = {action.data_ptr<Real>(), action.data_ptr<Real>() + action.size(1)};
195
196 // The history only advances on evaluations, so each lag spans one period. This matches the
197 // trainer, which stacks observations sampled every timestep_window steps.
199 }
200
202
203 for (const auto i : index_range(_current_smoothed_signal))
207
209}
210
211Real
212LibtorchDRLControl::computeControlOffset(const unsigned int control_i) const
213{
214 return _control_offsets[control_i];
215}
216
217void
219{
220 for (const auto control_i : index_range(_control_names))
221 {
222 const Real applied_signal =
223 computeControlOffset(control_i) + _current_smoothed_signal[control_i];
224 setControllableValueByName<Real>(_control_names[control_i], applied_signal);
225 }
226}
227
228void
230{
231 _actor_nn = std::make_shared<Moose::LibtorchActorNeuralNet>(input_nn);
232 _nn = _actor_nn;
233}
234
235void
237{
238 const auto * check = dynamic_cast<const Moose::LibtorchActorNeuralNet *>(&input_nn);
239 if (!check)
240 mooseError("This needs to be a LibtorchActorNeuralNet!");
241 loadControlNeuralNet(*check);
242}
243
244void
246{
247 {
248 std::lock_guard<std::mutex> lock(_policy_generator.mutex());
249 _policy_generator.set_current_seed(seed);
250 }
252}
253
254bool
256{
257 // Evaluate at step 0 and on the first step of every period: 1, P + 1, 2P + 1, ... Tying the
258 // schedule to the time step keeps it aligned with the trainer's reporter downsampling.
259 if (_t_step <= 0)
260 return true;
261 return (static_cast<unsigned int>(_t_step) - 1) % _num_steps_in_period == 0;
262}
263
264void
266{
267 if (!_stochastic || _policy_generator_state.empty())
268 return;
269
270 auto state_tensor =
271 torch::from_blob(_policy_generator_state.data(),
272 {static_cast<long>(_policy_generator_state.size())},
273 torch::TensorOptions().dtype(torch::kUInt8).device(torch::kCPU))
274 .clone();
275 std::lock_guard<std::mutex> lock(_policy_generator.mutex());
276 _policy_generator.set_state(state_tensor);
277}
278
279void
281{
282 if (!_stochastic)
283 {
285 return;
286 }
287
288 std::lock_guard<std::mutex> lock(_policy_generator.mutex());
289 const auto state_tensor = _policy_generator.get_state().contiguous();
290 const auto * data = state_tensor.data_ptr<std::uint8_t>();
291 _policy_generator_state.assign(data, data + static_cast<std::size_t>(state_tensor.numel()));
292}
293
294Real
295LibtorchDRLControl::getSignalLogProbability(const unsigned int signal_index) const
296{
297 mooseAssert(signal_index < _control_names.size(),
298 "The index of the requested control signal is not in the [0," +
299 std::to_string(_control_names.size()) + ") range!");
301}
302
303#endif
registerMooseObject("StochasticToolsApp", LibtorchDRLControl)
const ExecFlagType EXEC_TIMESTEP_BEGIN
void ErrorVector unsigned int
void suppressParameter(const std::string &name)
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)
T & set(const std::string &name, bool quiet_mode=false)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
A time-dependent, neural-network-based controller which is associated with a Proximal Policy Optimiza...
bool _has_control_offsets
Whether any offset data was provided by the user.
virtual void loadControlNeuralNetFromFile() override
Load the actor neural network from the configured checkpoint file.
LibtorchDRLControl(const InputParameters &parameters)
Construct using input parameters.
std::vector< Real > _control_offsets
Constant offsets added to each control signal before it is applied.
at::Generator _policy_generator
Owned libtorch CPU generator used for policy sampling.
void restorePolicyGeneratorState()
Restore the owned libtorch generator state from restartable storage.
void savePolicyGeneratorState()
Mirror the owned libtorch generator state into restartable storage.
Real getSignalLogProbability(const unsigned int signal_index) const
Get the logarithmic probability of (signal_index)-th signal of the control neural net.
virtual void execute() override
Compute the actions and their corresponding logarithmic probabilities.
std::vector< Real > & _previous_control_signal
The smoothed control signal from the previous execution, saved for restart/recover.
std::vector< Real > & _current_control_signal_log_probabilities
The log probability of control signals from the last evaluation of the controller.
Real computeControlOffset(unsigned int control_i) const
Return the offset to add to a control signal at the current time.
static InputParameters validParams()
Return the input parameters supported by this control.
const bool _stochastic
Whether to sample actions stochastically instead of using the deterministic actor output.
const Real _control_smoothing_factor
Exponential smoothing factor in (0, 1] applied to the control signal at each time step.
void loadControlNeuralNet(const Moose::LibtorchActorNeuralNet &input_nn)
Copy an actor network into this DRL controller.
bool shouldEvaluatePolicy() const
Report whether the policy is evaluated at the current time step.
std::vector< Real > & _current_smoothed_signal
The current smoothed control signal applied to the controllable parameters.
const unsigned int _num_steps_in_period
Number of time steps between policy evaluations.
void setPolicySampleSeed(uint64_t seed)
Reset the owned policy-sampling generator to a known seed.
std::shared_ptr< Moose::LibtorchActorNeuralNet > _actor_nn
Actor network used when the controller operates as a stochastic policy.
std::vector< std::uint8_t > & _policy_generator_state
Restartable serialized state for the owned policy-sampling generator.
virtual void initialSetup() override
Restore any restartable controller state after base setup completes.
void applyControlSignals()
Apply the current smoothed control signals, including optional offsets.
const std::vector< Real > _action_scaling_factors
virtual void initialSetup() override
const unsigned int _input_timesteps
const std::vector< PostprocessorName > & _observation_names
const std::vector< Real > _observation_shift_factors
static InputParameters validParams()
const std::vector< Real > _observation_scaling_factors
std::vector< Real > _current_observation
std::vector< Real > & _current_control_signals
const std::vector< std::string > & _control_names
torch::Tensor prepareInputTensor()
std::shared_ptr< Moose::LibtorchNeuralNetBase > _nn
const LibtorchObservationHistoryHelper _observation_history
std::vector< std::vector< Real > > & _old_observations
void initializeHistory(const std::vector< Real > &observation, std::vector< std::vector< Real > > &old_observations) const
std::vector< Real > expandObservationFactors(const std::vector< Real > &observation_factors) const
void advanceHistory(const std::vector< Real > &observation, std::vector< std::vector< Real > > &old_observations) const
void paramError(const std::string &param, Args... args) const
bool isParamSetByUser(const std::string &name) const
void mooseError(Args &&... args) const
bool isParamValid(const std::string &name) const
Feed-forward actor network coupled to a Gaussian or Beta action distribution.
const ExecFlagType & _current_execute_flag
void loadLibtorchActorNeuralNetState(Moose::LibtorchActorNeuralNet &nn, const std::string &filename)
Load an actor checkpoint written as a native libtorch state archive.