Skip to main content

r2l_core/on_policy/
algorithm.rs

1use crate::{
2    HookResult, break_on_hook_result,
3    buffers::{TrajectoryBatch, buffer::TrajectoryView},
4    error::Error,
5    models::Actor,
6    return_on_hook_result,
7    tensor::R2lTensor,
8    utils::{actor_wrapper::ActorWrapper, buffer_wrapper::TrajectoryViewsWrapper},
9};
10
11/// Trainable on-policy component that owns an actor and learns from rollouts.
12pub trait Agent {
13    /// Tensor type shared with the sampler and rollout buffers.
14    type Tensor: R2lTensor;
15
16    /// Actor type used by samplers to collect new rollouts.
17    type Actor: Actor<Tensor = Self::Tensor> + Clone;
18
19    /// Returns an actor snapshot for rollout collection.
20    fn actor(&self) -> Self::Actor;
21
22    /// Learns from a batch of trajectory containers.
23    ///
24    /// # Errors
25    ///
26    /// Returns an error if the agent update fails.
27    fn learn<B: TrajectoryBatch<Self::Tensor>>(&mut self, buffers: &[B]) -> Result<(), Error>;
28
29    /// Sets the learning rate used by future updates.
30    fn set_learning_rate(&mut self, learning_rate: f64);
31}
32
33/// Rollout collector used by an on-policy training loop.
34pub trait Sampler {
35    /// Tensor type stored in collected trajectories.
36    type Tensor: R2lTensor;
37
38    /// Resets all environments managed by the sampler.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if an environment cannot be reset.
43    fn reset_all_envs(&mut self) -> Result<(), Error> {
44        Ok(())
45    }
46
47    /// Collects rollout data using the provided actor.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if an environment operation fails during collection.
52    fn collect_rollouts<A: Actor<Tensor = Self::Tensor> + Clone>(
53        &mut self,
54        actor: A,
55    ) -> Result<(), Error>;
56
57    /// Creates a view for the agents.
58    fn trajectory_views(&mut self) -> impl AsRef<[TrajectoryView<'_, Self::Tensor>]>;
59}
60
61/// Coupled runtime unit that binds an agent and sampler together.
62pub struct OnPolicyRuntime<A: Agent, S: Sampler> {
63    /// Trainable agent.
64    pub agent: A,
65    /// Rollout collector.
66    pub sampler: S,
67}
68
69impl<A: Agent, S: Sampler> OnPolicyRuntime<A, S> {
70    /// Collects a fresh set of rollouts using the sampler-facing actor.
71    ///
72    /// # Errors
73    ///
74    /// Returns an error if the sampler cannot collect the rollouts.
75    pub fn collect(&mut self) -> Result<(), Error> {
76        let actor = self.agent.actor();
77        let actor = ActorWrapper::new(actor);
78        self.sampler.collect_rollouts(actor)
79    }
80
81    /// Returns the last collected trajectory containers from the sampler.
82    pub fn trajectory_containers(&mut self) -> impl AsRef<[TrajectoryView<'_, S::Tensor>]> {
83        self.sampler.trajectory_views()
84    }
85
86    /// Adapts the sampler buffers and runs an agent update.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error if the agent cannot learn from the collected trajectories.
91    pub fn learn(&mut self) -> Result<(), Error> {
92        let views = self.sampler.trajectory_views();
93        let buffers = views
94            .as_ref()
95            .iter()
96            .map(TrajectoryViewsWrapper::from_view)
97            .collect::<Result<Vec<_>, _>>()?;
98        self.agent.learn(&buffers)
99    }
100
101    /// Returns the agent-facing actor snapshot.
102    pub fn actor(&self) -> A::Actor {
103        self.agent.actor()
104    }
105}
106
107/// Lifecycle hooks that control an [`OnPolicyAlgorithm`] training loop.
108pub trait OnPolicyAlgorithmHooks {
109    /// Agent type controlled by the training loop.
110    type A: Agent;
111    /// Sampler type controlled by the training loop.
112    type S: Sampler;
113
114    /// Called once before rollout/training starts.
115    fn init_hook(&mut self, runtime: &mut OnPolicyRuntime<Self::A, Self::S>) -> HookResult;
116
117    /// Called after rollouts are collected and before agent learning.
118    fn post_rollout_hook(&mut self, runtime: &mut OnPolicyRuntime<Self::A, Self::S>) -> HookResult;
119
120    /// Called after the agent has learned from the latest rollouts.
121    fn post_training_hook(&mut self, runtime: &mut OnPolicyRuntime<Self::A, Self::S>)
122    -> HookResult;
123
124    /// Called once when the loop exits.
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if end-of-training finalization fails.
129    fn finish_training_hook(
130        &mut self,
131        runtime: &mut OnPolicyRuntime<Self::A, Self::S>,
132    ) -> Result<(), Error>;
133}
134
135/// Generic on-policy training loop combining a runtime with lifecycle hooks.
136pub struct OnPolicyAlgorithm<A: Agent, S: Sampler, H: OnPolicyAlgorithmHooks<A = A, S = S>> {
137    /// Coupled training runtime.
138    pub runtime: OnPolicyRuntime<A, S>,
139    /// Lifecycle hooks.
140    pub hooks: H,
141}
142
143impl<A: Agent, S: Sampler, H: OnPolicyAlgorithmHooks<A = A, S = S>> OnPolicyAlgorithm<A, S, H> {
144    fn training_loop(&mut self) -> Result<(), Error> {
145        return_on_hook_result!(self.hooks.init_hook(&mut self.runtime));
146        loop {
147            self.runtime.collect()?;
148            break_on_hook_result!(self.hooks.post_rollout_hook(&mut self.runtime));
149
150            self.runtime.learn()?;
151            break_on_hook_result!(self.hooks.post_training_hook(&mut self.runtime));
152        }
153        Ok(())
154    }
155}
156
157impl<A: Agent, S: Sampler, H: OnPolicyAlgorithmHooks<A = A, S = S>> OnPolicyAlgorithm<A, S, H> {
158    /// Creates an on-policy algorithm from its runtime and lifecycle hooks.
159    pub fn new(runtime: OnPolicyRuntime<A, S>, hooks: H) -> Self {
160        Self { runtime, hooks }
161    }
162
163    /// Runs training until a hook requests termination.
164    ///
165    /// # Errors
166    ///
167    /// Returns an error if learning fails or a hook reports a deferred failure
168    /// during end-of-training finalization.
169    pub fn train(&mut self) -> Result<(), Error> {
170        let training_result = self.training_loop();
171        let finalization_result = self.hooks.finish_training_hook(&mut self.runtime);
172        training_result.and(finalization_result)
173    }
174}