drlab 0.2.6__tar.gz → 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {drlab-0.2.6/src/drlab.egg-info → drlab-0.2.8}/PKG-INFO +6 -2
  2. {drlab-0.2.6 → drlab-0.2.8}/README.md +5 -1
  3. {drlab-0.2.6 → drlab-0.2.8}/pyproject.toml +1 -1
  4. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/__init__.py +1 -1
  5. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/off_policy/dqn.py +4 -0
  6. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/off_policy/sac.py +20 -5
  7. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/on_policy/base.py +4 -0
  8. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/replay/replay_buffer.py +5 -0
  9. {drlab-0.2.6 → drlab-0.2.8/src/drlab.egg-info}/PKG-INFO +6 -2
  10. {drlab-0.2.6 → drlab-0.2.8}/tests/test_learners.py +81 -0
  11. {drlab-0.2.6 → drlab-0.2.8}/tests/test_public_api.py +1 -1
  12. {drlab-0.2.6 → drlab-0.2.8}/tests/test_replay.py +34 -0
  13. {drlab-0.2.6 → drlab-0.2.8}/LICENSE +0 -0
  14. {drlab-0.2.6 → drlab-0.2.8}/setup.cfg +0 -0
  15. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/controllers/__init__.py +0 -0
  16. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/controllers/base.py +0 -0
  17. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/controllers/e_greedy.py +0 -0
  18. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/controllers/gaussian.py +0 -0
  19. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/controllers/greedy.py +0 -0
  20. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/controllers/stochastic.py +0 -0
  21. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/controllers/warmup.py +0 -0
  22. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/experiments/__init__.py +0 -0
  23. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/experiments/off_policy_experiment.py +0 -0
  24. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/experiments/on_policy_experiment.py +0 -0
  25. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/__init__.py +0 -0
  26. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/off_policy/__init__.py +0 -0
  27. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/off_policy/base.py +0 -0
  28. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/off_policy/configs.py +0 -0
  29. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/on_policy/__init__.py +0 -0
  30. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/on_policy/actor_critic.py +0 -0
  31. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/on_policy/configs.py +0 -0
  32. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/on_policy/ppo.py +0 -0
  33. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/learners/on_policy/reinforce.py +0 -0
  34. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/replay/__init__.py +0 -0
  35. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/replay/transition_batch.py +0 -0
  36. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/runners/__init__.py +0 -0
  37. {drlab-0.2.6 → drlab-0.2.8}/src/drlab/runners/runner.py +0 -0
  38. {drlab-0.2.6 → drlab-0.2.8}/src/drlab.egg-info/SOURCES.txt +0 -0
  39. {drlab-0.2.6 → drlab-0.2.8}/src/drlab.egg-info/dependency_links.txt +0 -0
  40. {drlab-0.2.6 → drlab-0.2.8}/src/drlab.egg-info/requires.txt +0 -0
  41. {drlab-0.2.6 → drlab-0.2.8}/src/drlab.egg-info/top_level.txt +0 -0
  42. {drlab-0.2.6 → drlab-0.2.8}/tests/test_controllers.py +0 -0
  43. {drlab-0.2.6 → drlab-0.2.8}/tests/test_off_policy_experiment.py +0 -0
  44. {drlab-0.2.6 → drlab-0.2.8}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drlab
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Deep Reinforcement Learning kit for research.
5
5
  Author: Tomas Osarte
6
6
  Keywords: reinforcement-learning,deep-learning,pytorch,gymnasium
@@ -139,11 +139,15 @@ episode returns, episode lengths, and optionally the last completed episode.
139
139
 
140
140
  `ReplayBuffer` stores transitions for off-policy learning. It supports both
141
141
  discrete and continuous action spaces through the configured action shape and
142
- action type.
142
+ action type. Call `reset()` to remove all stored transitions and reuse the
143
+ buffer.
143
144
 
144
145
  `TransitionBatch` is the tensor container passed between runners, replay
145
146
  buffers, experiments, and learners.
146
147
 
148
+ All learners provide `reset_optimizers()` to clear optimizer state without
149
+ changing gradients, optimizer settings, or model parameters.
150
+
147
151
  ## Simple Usage
148
152
 
149
153
  This example trains a DQN agent on `CartPole-v1`:
@@ -102,11 +102,15 @@ episode returns, episode lengths, and optionally the last completed episode.
102
102
 
103
103
  `ReplayBuffer` stores transitions for off-policy learning. It supports both
104
104
  discrete and continuous action spaces through the configured action shape and
105
- action type.
105
+ action type. Call `reset()` to remove all stored transitions and reuse the
106
+ buffer.
106
107
 
107
108
  `TransitionBatch` is the tensor container passed between runners, replay
108
109
  buffers, experiments, and learners.
109
110
 
111
+ All learners provide `reset_optimizers()` to clear optimizer state without
112
+ changing gradients, optimizer settings, or model parameters.
113
+
110
114
  ## Simple Usage
111
115
 
112
116
  This example trains a DQN agent on `CartPole-v1`:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "drlab"
7
- version = "0.2.6"
7
+ version = "0.2.8"
8
8
  description = "Deep Reinforcement Learning kit for research."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -35,7 +35,7 @@ from drlab.learners import (
35
35
  from drlab.replay import ReplayBuffer, TransitionBatch
36
36
  from drlab.runners import Runner
37
37
 
38
- __version__ = "0.2.6"
38
+ __version__ = "0.2.8"
39
39
 
40
40
  __all__ = [
41
41
  "__version__",
@@ -28,6 +28,10 @@ class DQNLearner(OffPolicyLearner):
28
28
  if self.use_target_model:
29
29
  self.target_model = self.make_target_model(self.model)
30
30
 
31
+ def reset_optimizers(self) -> None:
32
+ """Clear the optimizer state."""
33
+ self.optimizer.state.clear()
34
+
31
35
  def _validate_dqn_config(self) -> None:
32
36
  if self.double_q and not self.use_target_model:
33
37
  raise ValueError("double_q=True requires use_target_model=True.")
@@ -63,6 +63,15 @@ class SACLearner(OffPolicyLearner):
63
63
  lr=config.alpha_lr,
64
64
  )
65
65
 
66
+ def reset_optimizers(self) -> None:
67
+ """Clear the state of every optimizer owned by the learner."""
68
+ for optimizer in (
69
+ self.actor_optimizer,
70
+ self.critic_optimizer,
71
+ self.alpha_optimizer,
72
+ ):
73
+ optimizer.state.clear()
74
+
66
75
  @property
67
76
  def alpha(self) -> th.Tensor:
68
77
  return self.log_alpha.exp()
@@ -196,12 +205,18 @@ class SACLearner(OffPolicyLearner):
196
205
  ])
197
206
 
198
207
  loss = actor_loss + critic_loss + alpha_loss
208
+
209
+ # A single batched host transfer avoids five separate device
210
+ # synchronizations (one per .item() call) on every training step.
211
+ actor_v, critic_v, alpha_v, reg_v, total_v = th.stack(
212
+ [policy_loss, critic_loss, alpha_loss, reg_loss, loss]
213
+ ).tolist()
199
214
  self.last_losses = {
200
- "actor": float(policy_loss.item()),
201
- "critic": float(critic_loss.item()),
202
- "alpha": float(alpha_loss.item()),
203
- "regularization": float(reg_loss.item()),
204
- "total": float(loss.item()),
215
+ "actor": actor_v,
216
+ "critic": critic_v,
217
+ "alpha": alpha_v,
218
+ "regularization": reg_v,
219
+ "total": total_v,
205
220
  }
206
221
 
207
222
  return self.last_losses["total"]
@@ -20,6 +20,10 @@ class OnPolicyLearner:
20
20
 
21
21
  self._validate_config()
22
22
 
23
+ def reset_optimizers(self) -> None:
24
+ """Clear the optimizer state."""
25
+ self.optimizer.state.clear()
26
+
23
27
  def _validate_config(self):
24
28
  if len(self.config.regularizers) != len(self.config.reg_lams):
25
29
  raise ValueError("regularizers and reg_lams must have same length.")
@@ -37,6 +37,11 @@ class ReplayBuffer:
37
37
  def __len__(self) -> int:
38
38
  return self.size
39
39
 
40
+ def reset(self) -> None:
41
+ """Remove all transitions from the buffer."""
42
+ self.ptr = 0
43
+ self.size = 0
44
+
40
45
  def add(
41
46
  self,
42
47
  states: np.ndarray, # [B, *obs_shape]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drlab
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Deep Reinforcement Learning kit for research.
5
5
  Author: Tomas Osarte
6
6
  Keywords: reinforcement-learning,deep-learning,pytorch,gymnasium
@@ -139,11 +139,15 @@ episode returns, episode lengths, and optionally the last completed episode.
139
139
 
140
140
  `ReplayBuffer` stores transitions for off-policy learning. It supports both
141
141
  discrete and continuous action spaces through the configured action shape and
142
- action type.
142
+ action type. Call `reset()` to remove all stored transitions and reuse the
143
+ buffer.
143
144
 
144
145
  `TransitionBatch` is the tensor container passed between runners, replay
145
146
  buffers, experiments, and learners.
146
147
 
148
+ All learners provide `reset_optimizers()` to clear optimizer state without
149
+ changing gradients, optimizer settings, or model parameters.
150
+
147
151
  ## Simple Usage
148
152
 
149
153
  This example trains a DQN agent on `CartPole-v1`:
@@ -25,7 +25,88 @@ def parameters_changed(model, before):
25
25
  )
26
26
 
27
27
 
28
+ def populate_optimizer_state(optimizer):
29
+ optimizer.zero_grad(set_to_none=True)
30
+ loss = sum(
31
+ parameter.square().sum()
32
+ for group in optimizer.param_groups
33
+ for parameter in group["params"]
34
+ )
35
+ loss.backward()
36
+ optimizer.step()
37
+
38
+
39
+ def assert_optimizer_state_reset(test_case, optimizer):
40
+ test_case.assertEqual(len(optimizer.state), 0)
41
+ for group in optimizer.param_groups:
42
+ for parameter in group["params"]:
43
+ test_case.assertIsNotNone(parameter.grad)
44
+
45
+
28
46
  class LearnerSmokeTest(unittest.TestCase):
47
+ def test_all_on_policy_learners_reset_their_optimizer(self):
48
+ cases = (
49
+ (ReinforceLearner, ReinforceConfig(num_actions=2), 2),
50
+ (ActorCriticLearner, ActorCriticConfig(num_actions=2), 3),
51
+ (PPOLearner, PPOConfig(num_actions=2), 3),
52
+ )
53
+
54
+ for learner_type, config, output_size in cases:
55
+ with self.subTest(learner=learner_type.__name__):
56
+ actor = th.nn.Linear(4, output_size)
57
+ optimizer = th.optim.Adam(actor.parameters())
58
+ learner = learner_type(actor, optimizer, config)
59
+ populate_optimizer_state(optimizer)
60
+
61
+ learner.reset_optimizers()
62
+
63
+ assert_optimizer_state_reset(self, optimizer)
64
+
65
+ def test_dqn_resets_its_optimizer(self):
66
+ model = th.nn.Linear(4, 2)
67
+ optimizer = th.optim.Adam(model.parameters())
68
+ learner = DQNLearner(
69
+ model,
70
+ optimizer,
71
+ DQNConfig(num_actions=2),
72
+ )
73
+ populate_optimizer_state(optimizer)
74
+
75
+ learner.reset_optimizers()
76
+
77
+ assert_optimizer_state_reset(self, optimizer)
78
+
79
+ def test_sac_resets_all_three_optimizers(self):
80
+ obs_dim = 4
81
+ action_dim = 2
82
+ actor = th.nn.Linear(obs_dim, 2 * action_dim)
83
+ critic1 = th.nn.Linear(obs_dim + action_dim, 1)
84
+ critic2 = th.nn.Linear(obs_dim + action_dim, 1)
85
+ actor_optimizer = th.optim.Adam(actor.parameters())
86
+ critic_optimizer = th.optim.Adam(
87
+ [*critic1.parameters(), *critic2.parameters()]
88
+ )
89
+ learner = SACLearner(
90
+ actor=actor,
91
+ critic1=critic1,
92
+ critic2=critic2,
93
+ actor_optimizer=actor_optimizer,
94
+ critic_optimizer=critic_optimizer,
95
+ config=SACConfig(action_shape=(action_dim,)),
96
+ )
97
+ optimizers = (
98
+ actor_optimizer,
99
+ critic_optimizer,
100
+ learner.alpha_optimizer,
101
+ )
102
+ for optimizer in optimizers:
103
+ populate_optimizer_state(optimizer)
104
+
105
+ learner.reset_optimizers()
106
+
107
+ for optimizer in optimizers:
108
+ assert_optimizer_state_reset(self, optimizer)
109
+
29
110
  def test_soft_target_update_matches_original_arithmetic_exactly(self):
30
111
  source1 = th.nn.Linear(4, 3)
31
112
  source2 = th.nn.Linear(3, 2)
@@ -37,7 +37,7 @@ class PublicAPITest(unittest.TestCase):
37
37
  "WarmupController",
38
38
  }
39
39
 
40
- self.assertEqual(drlab.__version__, "0.2.5")
40
+ self.assertEqual(drlab.__version__, "0.2.8")
41
41
  self.assertTrue(expected.issubset(set(drlab.__all__)))
42
42
  for name in expected:
43
43
  self.assertTrue(hasattr(drlab, name), name)
@@ -134,3 +134,37 @@ class ReplayBufferTest(unittest.TestCase):
134
134
 
135
135
  with self.assertRaisesRegex(ValueError, "empty buffer"):
136
136
  buffer.sample(1)
137
+
138
+ def test_reset_empties_buffer_and_allows_reuse(self):
139
+ buffer = ReplayBuffer(capacity=3, obs_shape=(1,), device="cpu")
140
+ buffer.add(
141
+ states=np.asarray([[0], [1]], dtype=np.float32),
142
+ actions=np.asarray([0, 1]),
143
+ rewards=np.asarray([1.0, 2.0]),
144
+ terminated=np.asarray([False, True]),
145
+ truncated=np.asarray([False, False]),
146
+ next_states=np.asarray([[1], [2]], dtype=np.float32),
147
+ returns=np.asarray([3.0, 2.0]),
148
+ )
149
+
150
+ buffer.reset()
151
+
152
+ self.assertEqual(len(buffer), 0)
153
+ self.assertEqual(buffer.ptr, 0)
154
+ self.assertEqual(buffer.get_all().states.shape, (0, 1))
155
+ with self.assertRaisesRegex(ValueError, "empty buffer"):
156
+ buffer.sample(1)
157
+
158
+ buffer.add(
159
+ states=np.asarray([[4]], dtype=np.float32),
160
+ actions=np.asarray([1]),
161
+ rewards=np.asarray([5.0]),
162
+ terminated=np.asarray([False]),
163
+ truncated=np.asarray([True]),
164
+ next_states=np.asarray([[5]], dtype=np.float32),
165
+ returns=np.asarray([5.0]),
166
+ )
167
+
168
+ self.assertEqual(len(buffer), 1)
169
+ self.assertEqual(buffer.ptr, 1)
170
+ self.assertEqual(buffer.get_all().states.item(), 4.0)
File without changes
File without changes
File without changes
File without changes
File without changes