https://mooseframework.inl.gov
Loading...
Searching...
No Matches
LibtorchRLTrajectoryAssembler.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
13
14#include "MooseError.h"
15
16#include "libmesh/utility.h"
17
18#include <numeric>
19
21 const unsigned int input_timesteps,
22 const unsigned int timestep_window,
23 const bool shift_outputs,
24 const bool average_reward_over_timestep_window)
25 : _timestep_window(timestep_window),
26 _shift_outputs(shift_outputs),
27 _average_reward_over_timestep_window(average_reward_over_timestep_window),
28 _observation_history(input_timesteps)
29{
31 mooseError("The RL trajectory timestep window must be at least 1.");
32}
33
34std::vector<LibtorchRLTrajectoryBuffer::Trajectory>
36 const ReporterData & actions,
37 const ReporterData & log_probabilities,
38 const std::vector<std::vector<Real>> & rewards) const
39{
40 if (actions.size() != log_probabilities.size())
41 mooseError("The number of log-probability reporters (",
42 log_probabilities.size(),
43 ") must match the number of action reporters (",
44 actions.size(),
45 ").");
46
47 checkSampleCount(observations, "observation", rewards.size());
48 checkSampleCount(actions, "action", rewards.size());
49 checkSampleCount(log_probabilities, "log-probability", rewards.size());
50
51 std::vector<LibtorchRLTrajectoryBuffer::Trajectory> trajectories;
52
53 for (const auto sample_i : index_range(rewards))
54 {
55 const auto & reward_sample = rewards[sample_i];
56 const auto num_transitions = numTransitions(reward_sample.size());
57 if (!num_transitions)
58 continue;
59
60 std::vector<std::vector<Real>> component_trajectories(observations.size());
61 for (const auto observation_i : index_range(observations))
62 component_trajectories[observation_i] =
63 downsample((*observations[observation_i])[sample_i], 0, num_transitions + 1);
64
66 trajectory.observations.reserve(num_transitions);
67 trajectory.next_observations.reserve(num_transitions);
68 trajectory.actions.assign(num_transitions, std::vector<Real>());
69 trajectory.log_probabilities.assign(num_transitions, std::vector<Real>());
70
71 for (auto & action_row : trajectory.actions)
72 action_row.reserve(actions.size());
73 for (auto & log_probability_row : trajectory.log_probabilities)
74 log_probability_row.reserve(log_probabilities.size());
75
76 for (const auto step_i : make_range(num_transitions))
77 {
78 trajectory.observations.push_back(
79 _observation_history.stackTrajectoryObservation(component_trajectories, step_i));
80 trajectory.next_observations.push_back(
81 _observation_history.stackTrajectoryObservation(component_trajectories, step_i + 1));
82 }
83
84 for (const auto action_i : index_range(actions))
85 {
86 const auto action_sequence =
87 downsample((*actions[action_i])[sample_i], _shift_outputs, num_transitions);
88 const auto log_probability_sequence =
89 downsample((*log_probabilities[action_i])[sample_i], _shift_outputs, num_transitions);
90
91 for (const auto step_i : make_range(num_transitions))
92 {
93 trajectory.actions[step_i].push_back(action_sequence[step_i]);
94 trajectory.log_probabilities[step_i].push_back(log_probability_sequence[step_i]);
95 }
96 }
97
99 ? windowAverage(reward_sample, num_transitions)
100 : downsample(reward_sample, _timestep_window, num_transitions);
101 // Full-solve rollout states are discarded after transfer, so GAE must stop at the boundary.
102 trajectory.terminals.assign(num_transitions, false);
103 trajectory.terminals.back() = true;
104
105 trajectories.push_back(std::move(trajectory));
106 }
107
108 return trajectories;
109}
110
111unsigned int
112LibtorchRLTrajectoryAssembler::numTransitions(const std::size_t raw_sequence_size) const
113{
114 // Raw index 0 holds the initial value, so each transition consumes one full window after it.
115 return raw_sequence_size ? (raw_sequence_size - 1) / _timestep_window : 0;
116}
117
118void
120 const std::string & quantity,
121 const std::size_t num_samples)
122{
123 for (const auto component_i : index_range(data))
124 if (data[component_i]->size() != num_samples)
125 mooseError("Entry ",
126 component_i,
127 " of the ",
128 quantity,
129 " reporters holds ",
130 data[component_i]->size(),
131 " samples, but the reward reporter holds ",
132 num_samples,
133 " samples.");
134}
135
136std::vector<Real>
137LibtorchRLTrajectoryAssembler::downsample(const std::vector<Real> & sample,
138 const unsigned int offset,
139 const unsigned int num_entries) const
140{
141 std::vector<Real> values;
142 values.reserve(num_entries);
143
144 for (const auto entry_i : make_range(num_entries))
145 {
146 const auto raw_index = offset + entry_i * _timestep_window;
147 if (raw_index >= sample.size())
148 mooseError("Reporter data is shorter than required by the configured timestep window and "
149 "history stacking.");
150 values.push_back(sample[raw_index]);
151 }
152
153 return values;
154}
155
156std::vector<Real>
157LibtorchRLTrajectoryAssembler::windowAverage(const std::vector<Real> & sample,
158 const unsigned int num_entries) const
159{
160 std::vector<Real> values;
161 values.reserve(num_entries);
162
163 for (const auto entry_i : make_range(num_entries))
164 {
165 const auto window_begin = 1 + entry_i * _timestep_window;
166 const auto window_end = window_begin + _timestep_window;
167 if (window_end > sample.size())
169 "Reporter reward data is shorter than required by the configured timestep window.");
170
171 const Real sum =
172 std::accumulate(sample.begin() + window_begin, sample.begin() + window_end, 0.0);
173 values.push_back(sum / _timestep_window);
174 }
175
176 return values;
177}
178
179#endif
void mooseError(Args &&... args)
std::array< Real, 2 > values
std::vector< Real > stackTrajectoryObservation(const std::vector< std::vector< Real > > &component_trajectories, unsigned int time_index) const
std::vector< Real > windowAverage(const std::vector< Real > &sample, unsigned int num_entries) const
Average a raw reporter sequence over consecutive timestep windows after the initial entry.
const bool _average_reward_over_timestep_window
Whether rewards are averaged over each window instead of sampled at its end.
unsigned int numTransitions(std::size_t raw_sequence_size) const
Count the transitions available in a raw reporter sequence.
LibtorchRLTrajectoryAssembler(unsigned int input_timesteps, unsigned int timestep_window, bool shift_outputs, bool average_reward_over_timestep_window)
Build the trajectory assembler.
const LibtorchObservationHistoryHelper _observation_history
Observation history stacking helper.
const unsigned int _timestep_window
Raw reporter entries per transition.
std::vector< Real > downsample(const std::vector< Real > &sample, unsigned int offset, unsigned int num_entries) const
Take every timestep_window-th entry of a raw reporter sequence.
std::vector< LibtorchRLTrajectoryBuffer::Trajectory > assemble(const ReporterData &observations, const ReporterData &actions, const ReporterData &log_probabilities, const std::vector< std::vector< Real > > &rewards) const
Assemble one trajectory per sample that holds at least one transition.
const bool _shift_outputs
Whether actions and log probabilities are read one raw entry after the observation.
static void checkSampleCount(const ReporterData &data, const std::string &quantity, std::size_t num_samples)
Check that every component of a rollout quantity holds one sequence per sample.
Vector data for one on-policy trajectory.
std::vector< Real > rewards
Scalar rewards for each transition.
std::vector< std::vector< Real > > observations
Observations for each transition.
std::vector< std::vector< Real > > actions
Actions applied at each transition.
std::vector< std::vector< Real > > next_observations
Next observations for each transition.
std::vector< std::vector< Real > > log_probabilities
Action log-probabilities recorded during rollout.
std::vector< bool > terminals
Terminal flags for each transition. A true value cuts value bootstrapping at that step.