https://mooseframework.inl.gov
Loading...
Searching...
No Matches
LibtorchActionDistribution.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://www.mooseframework.org
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
14#include "LibtorchRandomUtils.h"
15#include "LibtorchUtils.h"
16#include "MooseError.h"
17
18#include <cmath>
19#include <limits>
20#include "libmesh/utility.h"
21
22namespace
23{
24
31std::vector<Real>
32setActionScalingFactors(const std::vector<Real> & factors, const unsigned int expected_size)
33{
34 const auto normalized = factors.empty() ? std::vector<Real>(expected_size, 1.0) : factors;
35
36 if (normalized.size() != expected_size)
37 mooseError("The number of output_scaling_factors entries must match the number of action "
38 "outputs.");
39
40 for (const auto factor : normalized)
41 if (std::abs(factor) == 0.0)
42 mooseError("The output_scaling_factors entries must be non-zero.");
43
44 return normalized;
45}
46
47} // namespace
48
49namespace Moose
50{
51
53 const std::string & name,
54 const unsigned int num_inputs,
55 const unsigned int num_outputs,
56 const torch::DeviceType device_type,
57 const torch::ScalarType data_type,
58 const std::vector<Real> & output_scaling_factors)
59 : _name(name),
60 _num_inputs(num_inputs),
61 _num_outputs(num_outputs),
62 _device_type(device_type),
63 _data_type(data_type),
64 _output_scaling_factors(setActionScalingFactors(output_scaling_factors, num_outputs))
65{
66 auto action_scale = _output_scaling_factors;
68 _action_scale_tensor = register_buffer(
69 "action_scale", _action_scale_tensor.transpose(0, 1).to(_data_type).to(_device_type));
70}
71
72void
74{
75 auto action_scale =
76 _action_scale_tensor.detach().reshape({-1}).to(torch::kCPU).to(torch::kDouble);
78}
79
80torch::Tensor
81LibtorchActionDistribution::prepareFeatures(const torch::Tensor & input) const
82{
83 auto features = input;
84 if (_data_type != features.scalar_type())
85 features = features.to(_data_type);
86 if (_device_type != features.device().type())
87 features = features.to(_device_type);
88 return features;
89}
90
91torch::Tensor
92LibtorchActionDistribution::prepareAction(const torch::Tensor & action) const
93{
94 auto scaled_action = action;
95 if (_data_type != scaled_action.scalar_type())
96 scaled_action = scaled_action.to(_data_type);
97 if (_device_type != scaled_action.device().type())
98 scaled_action = scaled_action.to(_device_type);
99 return scaled_action;
100}
101
103 const std::string & name,
104 const unsigned int num_inputs,
105 const unsigned int num_outputs,
106 const torch::DeviceType device_type,
107 const torch::ScalarType data_type,
108 const bool build_on_construct,
109 const std::vector<Real> & output_scaling_factors,
110 const bool state_independent_std)
112 name, num_inputs, num_outputs, device_type, data_type, output_scaling_factors),
113 _state_independent_std(state_independent_std)
114{
115 if (build_on_construct)
117}
118
119void
121{
122 _mean_module = register_module(
123 "mean", torch::nn::Linear(torch::nn::LinearOptions(_num_inputs, _num_outputs).bias(true)));
125
127 _log_std_parameter = register_parameter(
128 "log_std",
129 torch::zeros({_num_outputs},
130 torch::TensorOptions().dtype(_data_type).device(_device_type)));
131 else
132 {
133 _std_module = register_module(
134 "std", torch::nn::Linear(torch::nn::LinearOptions(_num_inputs, _num_outputs).bias(true)));
136 }
137}
138
139void
140LibtorchGaussianActionDistribution::initialize(const c10::optional<at::Generator> generator)
141{
142 Moose::orthogonalInitializeTensor(_mean_module->weight, 1.0, generator);
143 torch::nn::init::zeros_(_mean_module->bias);
144
146 {
147 torch::nn::init::zeros_(_log_std_parameter);
148 return;
149 }
150
151 Moose::orthogonalInitializeTensor(_std_module->weight, 1.0, generator);
152 torch::nn::init::zeros_(_std_module->bias);
153}
154
155void
157{
158 const auto features = prepareFeatures(input);
159 _mean = _mean_module->forward(features);
160
162 {
163 if (_mean.dim() <= 1)
165 else
166 _log_std_tensor = _log_std_parameter.view({1, -1}).expand(_mean.sizes());
167 }
168 else
169 _log_std_tensor = _std_module->forward(features);
170
171 _log_std_tensor = torch::clamp(_log_std_tensor, std::log(1e-12), -std::log(1e-12));
172 _std_tensor = torch::exp(_log_std_tensor);
173}
174
175torch::Tensor
176LibtorchGaussianActionDistribution::sample(const c10::optional<at::Generator> generator) const
177{
178 return at::normal(_mean, _std_tensor, generator) * actionScaleTensor();
179}
180
181torch::Tensor
186
187torch::Tensor
188LibtorchGaussianActionDistribution::logProbability(const torch::Tensor & action) const
189{
190 const auto scaled_action = prepareAction(action);
191 const auto log_action_scale = torch::log(torch::abs(actionScaleTensor()));
192 const auto unscaled_action = scaled_action / actionScaleTensor();
193
194 constexpr Real pi = 3.14159265358979323846;
195 const torch::Tensor var = _std_tensor * _std_tensor;
196 return -((unscaled_action - _mean) * (unscaled_action - _mean)) / (2.0 * var) - _log_std_tensor -
197 0.5 * std::log(2.0 * pi) - log_action_scale;
198}
199
200torch::Tensor
202{
203 const auto log_action_scale = torch::log(torch::abs(actionScaleTensor()));
204 constexpr Real pi = 3.14159265358979323846;
205 return 0.5 * std::log(2.0 * pi) + _log_std_tensor + 0.5 + log_action_scale;
206}
207
209 const std::string & name,
210 const unsigned int num_inputs,
211 const unsigned int num_outputs,
212 const std::vector<Real> & minimum_values,
213 const std::vector<Real> & maximum_values,
214 const torch::DeviceType device_type,
215 const torch::ScalarType data_type,
216 const bool build_on_construct,
217 const std::vector<Real> & output_scaling_factors)
219 name, num_inputs, num_outputs, device_type, data_type, output_scaling_factors),
220 _minimum_values(minimum_values),
221 _maximum_values(maximum_values)
222{
223 if (_minimum_values.size() != _num_outputs || _maximum_values.size() != _num_outputs)
224 mooseError("The number of minimum_values and maximum_values entries must match the number "
225 "of action outputs.");
226
227 for (const auto i : make_range(_minimum_values.size()))
228 if (!(_maximum_values[i] > _minimum_values[i]))
229 mooseError("maximum_values entries must be strictly greater than minimum_values entries.");
230
231 auto min_value = _minimum_values;
233 _min_tensor = _min_tensor.transpose(0, 1).to(_data_type).to(_device_type);
234 auto max_value = _maximum_values;
236 _max_tensor = _max_tensor.transpose(0, 1).to(_data_type).to(_device_type);
237
238 if (build_on_construct)
240}
241
242void
244{
245 _alpha_module = register_module(
246 "alpha", torch::nn::Linear(torch::nn::LinearOptions(_num_inputs, _num_outputs).bias(true)));
247 _beta_module = register_module(
248 "beta", torch::nn::Linear(torch::nn::LinearOptions(_num_inputs, _num_outputs).bias(true)));
249
252}
253
254void
255LibtorchBetaActionDistribution::initialize(const c10::optional<at::Generator> generator)
256{
257 Moose::orthogonalInitializeTensor(_alpha_module->weight, 1.0, generator);
258 torch::nn::init::zeros_(_alpha_module->bias);
259
260 Moose::orthogonalInitializeTensor(_beta_module->weight, 1.0, generator);
261 torch::nn::init::zeros_(_beta_module->bias);
262}
263
264void
265LibtorchBetaActionDistribution::reset(const torch::Tensor & input)
266{
267 const auto features = prepareFeatures(input);
268 // softplus(x) + 1 keeps alpha, beta > 1 so the Beta density stays unimodal. torch::softplus
269 // switches to the identity for large logits, where log(exp(x) + 1) would overflow to inf.
270 _alpha_tensor = torch::softplus(_alpha_module->forward(features)) + 1.0;
271 _beta_tensor = torch::softplus(_beta_module->forward(features)) + 1.0;
272
273 _alpha_beta_tensor = torch::clamp_min(_alpha_tensor + _beta_tensor, 1e-8);
275 _log_norm = at::lgamma(_alpha_tensor) + at::lgamma(_beta_tensor) - at::lgamma(_alpha_beta_tensor);
276}
277
278torch::Tensor
279LibtorchBetaActionDistribution::sample(const c10::optional<at::Generator> generator) const
280{
281 const auto alpha_sample = at::_standard_gamma(_alpha_tensor, generator);
282 const auto beta_sample = at::_standard_gamma(_beta_tensor, generator);
283 const auto sampled = alpha_sample / (alpha_sample + beta_sample);
284 return (_min_tensor + (_max_tensor - _min_tensor) * sampled) * actionScaleTensor();
285}
286
287torch::Tensor
292
293torch::Tensor
294LibtorchBetaActionDistribution::logProbability(const torch::Tensor & action) const
295{
296 const auto scaled_action = prepareAction(action);
297 const auto log_action_scale = torch::log(torch::abs(actionScaleTensor()));
298 const auto unscaled_action = scaled_action / actionScaleTensor();
299 const auto scale = torch::clamp_min(_max_tensor - _min_tensor, 1e-8);
300 const auto normalized = (unscaled_action - _min_tensor) / scale;
301 const auto clipped = torch::clamp(normalized, 1e-8, 1.0 - 1e-8);
302 auto log_prob = (_alpha_tensor - 1.0) * torch::log(clipped) +
303 (_beta_tensor - 1.0) * torch::log1p(-clipped) - _log_norm - torch::log(scale) -
304 log_action_scale;
305
306 const auto out_of_bounds = (normalized < 0.0) | (normalized > 1.0);
307 if (out_of_bounds.any().item<bool>())
308 log_prob = torch::where(out_of_bounds,
309 torch::full_like(log_prob, -std::numeric_limits<Real>::infinity()),
310 log_prob);
311
312 return log_prob;
313}
314
315torch::Tensor
317{
318 const auto log_action_scale = torch::log(torch::abs(actionScaleTensor()));
319 const auto scale = torch::clamp_min(_max_tensor - _min_tensor, 1e-8);
320 return _log_norm - (_beta_tensor - 1.0) * torch::digamma(_beta_tensor) -
321 (_alpha_tensor - 1.0) * torch::digamma(_alpha_tensor) +
322 (_alpha_beta_tensor - 2.0) * torch::digamma(_alpha_beta_tensor) + torch::log(scale) +
323 log_action_scale;
324}
325
326} // namespace Moose
327
328#endif
void mooseError(Args &&... args)
Real scale
const std::string name
Definition Setup.h:21
Reusable continuous-action distribution interface for actor policies.
const torch::DeviceType _device_type
Torch device used by this distribution.
torch::Tensor _action_scale_tensor
Registered libtorch buffer holding the per-action scaling factors.
torch::Tensor prepareAction(const torch::Tensor &action) const
Convert actions to the configured device and scalar type.
void synchronizeScalingFactorsFromBuffer()
Sync cached scaling metadata from the registered buffers after loading state.
std::vector< Real > _output_scaling_factors
Cached per-action scaling factors applied in physical units.
torch::Tensor prepareFeatures(const torch::Tensor &input) const
Convert actor features to the configured device and scalar type.
const torch::Tensor & actionScaleTensor() const
Return the registered tensor that stores per-action scaling factors.
const unsigned int _num_outputs
Number of action dimensions produced by this distribution.
const unsigned int _num_inputs
Number of actor features feeding this distribution.
const torch::ScalarType _data_type
Torch scalar type used by this distribution.
LibtorchActionDistribution(const std::string &name, unsigned int num_inputs, unsigned int num_outputs, torch::DeviceType device_type=torch::kCPU, torch::ScalarType scalar_type=torch::kDouble, const std::vector< Real > &output_scaling_factors={})
Build an action-distribution module for an actor network.
virtual void reset(const torch::Tensor &input) override
Refresh the Beta distribution parameters from the latest actor features.
torch::Tensor _min_tensor
Tensor form of the lower action bounds.
torch::nn::Linear _beta_module
Linear head that produces the Beta beta parameters.
torch::nn::Linear _alpha_module
Linear head that produces the Beta alpha parameters.
const std::vector< Real > _maximum_values
Upper action bounds in physical units.
torch::Tensor _max_tensor
Tensor form of the upper action bounds.
virtual void initialize(c10::optional< at::Generator > generator=c10::nullopt) override
Initialize the trainable Beta distribution parameters.
const std::vector< Real > _minimum_values
Lower action bounds in physical units.
torch::Tensor _alpha_tensor
Cached Beta alpha parameters from the latest reset.
torch::Tensor _mean
Cached normalized Beta mean from the latest reset.
LibtorchBetaActionDistribution(const std::string &name, unsigned int num_inputs, unsigned int num_outputs, const std::vector< Real > &minimum_values, const std::vector< Real > &maximum_values, torch::DeviceType device_type=torch::kCPU, torch::ScalarType scalar_type=torch::kDouble, bool build_on_construct=true, const std::vector< Real > &output_scaling_factors={})
Build the Beta action distribution used for bounded controls.
torch::Tensor _log_norm
Cached log normalization factor for the latest Beta distribution state.
torch::Tensor _beta_tensor
Cached Beta beta parameters from the latest reset.
torch::Tensor _alpha_beta_tensor
Cached sum of the alpha and beta parameters.
void constructDistribution()
Build and register the Beta distribution heads.
virtual torch::Tensor deterministicAction() const override
Return the Beta mean action used for deterministic evaluation.
virtual torch::Tensor entropy() const override
Compute the entropy of the current Beta distribution.
virtual torch::Tensor sample(c10::optional< at::Generator > generator=c10::nullopt) const override
Draw a stochastic Beta action sample in physical units.
virtual torch::Tensor logProbability(const torch::Tensor &action) const override
Evaluate the log-probability of an action under the current Beta distribution.
void constructDistribution()
Build and register the Gaussian distribution heads.
torch::Tensor _std_tensor
Cached Gaussian action standard deviation from the latest reset.
torch::nn::Linear _std_module
Linear head that produces state-dependent Gaussian log-std inputs.
torch::Tensor _mean
Cached Gaussian action mean from the latest reset.
virtual torch::Tensor entropy() const override
Compute the entropy of the current Gaussian distribution.
torch::nn::Linear _mean_module
Linear head that produces the Gaussian action mean.
virtual torch::Tensor deterministicAction() const override
Return the Gaussian mean action used for deterministic evaluation.
virtual torch::Tensor sample(c10::optional< at::Generator > generator=c10::nullopt) const override
Draw a stochastic Gaussian action sample in physical units.
torch::Tensor _log_std_tensor
Cached Gaussian action log standard deviation from the latest reset.
torch::Tensor _log_std_parameter
Trainable state-independent Gaussian log-std.
virtual void initialize(c10::optional< at::Generator > generator=c10::nullopt) override
Initialize the trainable Gaussian distribution parameters.
virtual torch::Tensor logProbability(const torch::Tensor &action) const override
Evaluate the log-probability of an action under the current Gaussian distribution.
virtual void reset(const torch::Tensor &input) override
Refresh the Gaussian distribution parameters from the latest actor features.
const bool _state_independent_std
Whether the Gaussian std ignores the current actor features.
LibtorchGaussianActionDistribution(const std::string &name, unsigned int num_inputs, unsigned int num_outputs, torch::DeviceType device_type=torch::kCPU, torch::ScalarType scalar_type=torch::kDouble, bool build_on_construct=true, const std::vector< Real > &output_scaling_factors={}, bool state_independent_std=true)
Build the Gaussian action distribution used for unbounded controls.
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
void vectorToTensor(const std::vector< DataType > &vector, torch::Tensor &tensor, const bool detach=false)
void tensorToVector(torch::Tensor &tensor, std::vector< DataType > &vector)
void orthogonalInitializeTensor(torch::Tensor &tensor, Real gain=1.0, c10::optional< at::Generator > generator=c10::nullopt)
if(subdm)