drlab 0.2.2__tar.gz → 0.2.3__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.
- {drlab-0.2.2/src/drlab.egg-info → drlab-0.2.3}/PKG-INFO +1 -1
- {drlab-0.2.2 → drlab-0.2.3}/pyproject.toml +1 -1
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/__init__.py +1 -1
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/experiments/off_policy_experiment.py +3 -2
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/experiments/on_policy_experiment.py +1 -1
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/off_policy/base.py +4 -4
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/off_policy/dqn.py +6 -4
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/off_policy/sac.py +4 -4
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/on_policy/actor_critic.py +8 -8
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/on_policy/base.py +5 -5
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/on_policy/ppo.py +4 -4
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/on_policy/reinforce.py +4 -4
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/replay/replay_buffer.py +18 -8
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/replay/transition_batch.py +6 -3
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/runners/runner.py +29 -16
- {drlab-0.2.2 → drlab-0.2.3/src/drlab.egg-info}/PKG-INFO +1 -1
- {drlab-0.2.2 → drlab-0.2.3}/tests/test_learners.py +7 -7
- {drlab-0.2.2 → drlab-0.2.3}/tests/test_off_policy_experiment.py +3 -2
- {drlab-0.2.2 → drlab-0.2.3}/tests/test_public_api.py +1 -1
- {drlab-0.2.2 → drlab-0.2.3}/tests/test_replay.py +21 -9
- {drlab-0.2.2 → drlab-0.2.3}/tests/test_runner.py +23 -0
- {drlab-0.2.2 → drlab-0.2.3}/LICENSE +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/README.md +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/setup.cfg +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/controllers/__init__.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/controllers/base.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/controllers/e_greedy.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/controllers/gaussian.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/controllers/greedy.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/controllers/stochastic.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/controllers/warmup.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/experiments/__init__.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/__init__.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/off_policy/__init__.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/off_policy/configs.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/on_policy/__init__.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/learners/on_policy/configs.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/replay/__init__.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab/runners/__init__.py +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab.egg-info/SOURCES.txt +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab.egg-info/dependency_links.txt +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab.egg-info/requires.txt +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/src/drlab.egg-info/top_level.txt +0 -0
- {drlab-0.2.2 → drlab-0.2.3}/tests/test_controllers.py +0 -0
|
@@ -130,7 +130,8 @@ class OffPolicyExperiment:
|
|
|
130
130
|
states=batch.states,
|
|
131
131
|
actions=batch.actions,
|
|
132
132
|
rewards=batch.rewards,
|
|
133
|
-
|
|
133
|
+
terminated=batch.terminated,
|
|
134
|
+
truncated=batch.truncated,
|
|
134
135
|
next_states=batch.next_states,
|
|
135
136
|
returns=batch.returns,
|
|
136
137
|
)
|
|
@@ -142,7 +143,7 @@ class OffPolicyExperiment:
|
|
|
142
143
|
for _ in range(self.grad_repeats):
|
|
143
144
|
mb = self._make_minibatch(batch, last_episode)
|
|
144
145
|
total_loss += self.learner.train(
|
|
145
|
-
mb.rewards, mb.
|
|
146
|
+
mb.rewards, mb.terminated, mb.states, mb.actions, mb.next_states
|
|
146
147
|
)
|
|
147
148
|
|
|
148
149
|
return total_loss / self.grad_repeats
|
|
@@ -88,7 +88,7 @@ class OffPolicyLearner(ABC):
|
|
|
88
88
|
self,
|
|
89
89
|
model: th.nn.Module,
|
|
90
90
|
rewards: th.Tensor,
|
|
91
|
-
|
|
91
|
+
terminated: th.Tensor,
|
|
92
92
|
states: th.Tensor,
|
|
93
93
|
actions: th.Tensor,
|
|
94
94
|
next_states: th.Tensor,
|
|
@@ -102,7 +102,7 @@ class OffPolicyLearner(ABC):
|
|
|
102
102
|
value = regularizer(
|
|
103
103
|
model,
|
|
104
104
|
rewards,
|
|
105
|
-
|
|
105
|
+
terminated,
|
|
106
106
|
states,
|
|
107
107
|
actions,
|
|
108
108
|
next_states,
|
|
@@ -139,9 +139,9 @@ class OffPolicyLearner(ABC):
|
|
|
139
139
|
def train(
|
|
140
140
|
self,
|
|
141
141
|
rewards: th.Tensor,
|
|
142
|
-
|
|
142
|
+
terminated: th.Tensor,
|
|
143
143
|
states: th.Tensor,
|
|
144
144
|
actions: th.Tensor,
|
|
145
145
|
next_states: th.Tensor,
|
|
146
146
|
) -> float:
|
|
147
|
-
pass
|
|
147
|
+
pass
|
|
@@ -58,7 +58,7 @@ class DQNLearner(OffPolicyLearner):
|
|
|
58
58
|
def train(
|
|
59
59
|
self,
|
|
60
60
|
rewards: th.Tensor, # float32, [B,1]
|
|
61
|
-
|
|
61
|
+
terminated: th.Tensor, # bool or float(0/1), [B,1]
|
|
62
62
|
states: th.Tensor, # float32, [B, obs_dim] or [B,C,H,W]
|
|
63
63
|
actions: th.Tensor, # int64, [B,1]
|
|
64
64
|
next_states: th.Tensor, # float32, same as states
|
|
@@ -66,8 +66,10 @@ class DQNLearner(OffPolicyLearner):
|
|
|
66
66
|
self.model.train(True)
|
|
67
67
|
|
|
68
68
|
# Compute targets
|
|
69
|
-
|
|
70
|
-
targets = rewards + self.gamma * (
|
|
69
|
+
not_terminated = 1.0 - terminated.float()
|
|
70
|
+
targets = rewards + self.gamma * (
|
|
71
|
+
not_terminated * self.next_values(next_states)
|
|
72
|
+
)
|
|
71
73
|
pred = self.current_values(states, actions)
|
|
72
74
|
|
|
73
75
|
# Compute loss
|
|
@@ -75,7 +77,7 @@ class DQNLearner(OffPolicyLearner):
|
|
|
75
77
|
reg_loss = self.regularization_loss(
|
|
76
78
|
self.model,
|
|
77
79
|
rewards=rewards,
|
|
78
|
-
|
|
80
|
+
terminated=terminated,
|
|
79
81
|
states=states,
|
|
80
82
|
actions=actions,
|
|
81
83
|
next_states=next_states,
|
|
@@ -90,7 +90,7 @@ class SACLearner(OffPolicyLearner):
|
|
|
90
90
|
def train(
|
|
91
91
|
self,
|
|
92
92
|
rewards: th.Tensor, # float32, [B,1]
|
|
93
|
-
|
|
93
|
+
terminated: th.Tensor, # bool or float(0/1), [B,1]
|
|
94
94
|
states: th.Tensor, # float32, [B, obs_dim] or [B,C,H,W]
|
|
95
95
|
actions: th.Tensor, # float32, [B, action_dim]
|
|
96
96
|
next_states: th.Tensor, # float32, same as states
|
|
@@ -110,9 +110,9 @@ class SACLearner(OffPolicyLearner):
|
|
|
110
110
|
target_q2 = self.critic2_target(next_action_states)
|
|
111
111
|
target_q = th.min(target_q1, target_q2)
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
not_terminated = 1.0 - terminated.float()
|
|
114
114
|
targets = rewards + self.config.gamma * (
|
|
115
|
-
|
|
115
|
+
not_terminated * (target_q - self.alpha.detach() * next_log_probs)
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
action_states = th.cat([states, actions], dim=-1)
|
|
@@ -149,7 +149,7 @@ class SACLearner(OffPolicyLearner):
|
|
|
149
149
|
reg_loss = self.regularization_loss(
|
|
150
150
|
self.actor,
|
|
151
151
|
rewards=rewards,
|
|
152
|
-
|
|
152
|
+
terminated=terminated,
|
|
153
153
|
states=states,
|
|
154
154
|
actions=actions,
|
|
155
155
|
next_states=next_states,
|
|
@@ -28,14 +28,14 @@ class ActorCriticLearner(OnPolicyLearner):
|
|
|
28
28
|
self,
|
|
29
29
|
returns: th.Tensor,
|
|
30
30
|
rewards: th.Tensor,
|
|
31
|
-
|
|
31
|
+
terminated: th.Tensor,
|
|
32
32
|
values: th.Tensor,
|
|
33
33
|
next_values: th.Tensor,
|
|
34
34
|
) -> th.Tensor:
|
|
35
35
|
if self.config.value_targets == ValueTargets.RETURNS:
|
|
36
36
|
targets = returns
|
|
37
37
|
elif self.config.value_targets == ValueTargets.TD:
|
|
38
|
-
targets = rewards + self.config.gamma * (~
|
|
38
|
+
targets = rewards + self.config.gamma * (~terminated * next_values)
|
|
39
39
|
else:
|
|
40
40
|
raise ValueError(f"Unknown value_targets: {self.config.value_targets}")
|
|
41
41
|
return self.config.value_criterion(values, targets)
|
|
@@ -44,13 +44,13 @@ class ActorCriticLearner(OnPolicyLearner):
|
|
|
44
44
|
self,
|
|
45
45
|
returns: th.Tensor,
|
|
46
46
|
rewards: th.Tensor,
|
|
47
|
-
|
|
47
|
+
terminated: th.Tensor,
|
|
48
48
|
values: th.Tensor,
|
|
49
49
|
next_values: th.Tensor
|
|
50
50
|
) -> th.Tensor:
|
|
51
51
|
advantages = None
|
|
52
52
|
if self.config.advantage_bootstrap:
|
|
53
|
-
advantages = rewards + self.config.gamma * (~
|
|
53
|
+
advantages = rewards + self.config.gamma * (~terminated * next_values)
|
|
54
54
|
else:
|
|
55
55
|
advantages = returns
|
|
56
56
|
if self.config.use_bias:
|
|
@@ -74,7 +74,7 @@ class ActorCriticLearner(OnPolicyLearner):
|
|
|
74
74
|
def train(
|
|
75
75
|
self,
|
|
76
76
|
rewards: th.Tensor,
|
|
77
|
-
|
|
77
|
+
terminated: th.Tensor,
|
|
78
78
|
states: th.Tensor,
|
|
79
79
|
actions: th.Tensor,
|
|
80
80
|
next_states: th.Tensor,
|
|
@@ -106,7 +106,7 @@ class ActorCriticLearner(OnPolicyLearner):
|
|
|
106
106
|
advantages = self._advantages(
|
|
107
107
|
returns=returns,
|
|
108
108
|
rewards=rewards,
|
|
109
|
-
|
|
109
|
+
terminated=terminated,
|
|
110
110
|
values=values,
|
|
111
111
|
next_values=next_values,
|
|
112
112
|
)
|
|
@@ -119,7 +119,7 @@ class ActorCriticLearner(OnPolicyLearner):
|
|
|
119
119
|
value_loss = self.config.value_lambda * self._value_loss(
|
|
120
120
|
returns=returns,
|
|
121
121
|
rewards=rewards,
|
|
122
|
-
|
|
122
|
+
terminated=terminated,
|
|
123
123
|
values=values,
|
|
124
124
|
next_values=next_values,
|
|
125
125
|
)
|
|
@@ -128,7 +128,7 @@ class ActorCriticLearner(OnPolicyLearner):
|
|
|
128
128
|
|
|
129
129
|
reg_loss = self._regularization_loss(
|
|
130
130
|
rewards=rewards,
|
|
131
|
-
|
|
131
|
+
terminated=terminated,
|
|
132
132
|
states=states,
|
|
133
133
|
actions=actions,
|
|
134
134
|
next_states=next_states,
|
|
@@ -50,7 +50,7 @@ class OnPolicyLearner:
|
|
|
50
50
|
def _regularization_loss(
|
|
51
51
|
self,
|
|
52
52
|
rewards: th.Tensor,
|
|
53
|
-
|
|
53
|
+
terminated: th.Tensor,
|
|
54
54
|
states: th.Tensor,
|
|
55
55
|
actions: th.Tensor,
|
|
56
56
|
next_states: th.Tensor,
|
|
@@ -59,7 +59,7 @@ class OnPolicyLearner:
|
|
|
59
59
|
return 0.0
|
|
60
60
|
|
|
61
61
|
return sum(
|
|
62
|
-
lam * reg(self.actor, rewards,
|
|
62
|
+
lam * reg(self.actor, rewards, terminated, states, actions, next_states)
|
|
63
63
|
for reg, lam in zip(self.config.regularizers, self.config.reg_lams)
|
|
64
64
|
)
|
|
65
65
|
|
|
@@ -67,7 +67,7 @@ class OnPolicyLearner:
|
|
|
67
67
|
self,
|
|
68
68
|
returns: th.Tensor,
|
|
69
69
|
rewards: th.Tensor,
|
|
70
|
-
|
|
70
|
+
terminated: th.Tensor,
|
|
71
71
|
values: th.Tensor,
|
|
72
72
|
next_values: th.Tensor,
|
|
73
73
|
) -> th.Tensor:
|
|
@@ -106,7 +106,7 @@ class OnPolicyLearner:
|
|
|
106
106
|
self,
|
|
107
107
|
returns: th.Tensor,
|
|
108
108
|
rewards: th.Tensor,
|
|
109
|
-
|
|
109
|
+
terminated: th.Tensor,
|
|
110
110
|
values: th.Tensor,
|
|
111
111
|
next_values: th.Tensor
|
|
112
112
|
) -> th.Tensor:
|
|
@@ -115,7 +115,7 @@ class OnPolicyLearner:
|
|
|
115
115
|
def train(
|
|
116
116
|
self,
|
|
117
117
|
rewards: th.Tensor, # float32, [B,1]
|
|
118
|
-
|
|
118
|
+
terminated: th.Tensor, # bool or float(0/1), [B,1]
|
|
119
119
|
states: th.Tensor, # float32, [B, obs_dim] or [B,C,H,W]
|
|
120
120
|
actions: th.Tensor, # int64, [B,1]
|
|
121
121
|
next_states: th.Tensor, # float32, same as states
|
|
@@ -38,7 +38,7 @@ class PPOLearner(ActorCriticLearner):
|
|
|
38
38
|
def train(
|
|
39
39
|
self,
|
|
40
40
|
rewards: th.Tensor,
|
|
41
|
-
|
|
41
|
+
terminated: th.Tensor,
|
|
42
42
|
states: th.Tensor,
|
|
43
43
|
actions: th.Tensor,
|
|
44
44
|
next_states: th.Tensor,
|
|
@@ -69,7 +69,7 @@ class PPOLearner(ActorCriticLearner):
|
|
|
69
69
|
advantages = self._advantages(
|
|
70
70
|
returns=returns,
|
|
71
71
|
rewards=rewards,
|
|
72
|
-
|
|
72
|
+
terminated=terminated,
|
|
73
73
|
values=old_values,
|
|
74
74
|
next_values=next_values,
|
|
75
75
|
)
|
|
@@ -96,7 +96,7 @@ class PPOLearner(ActorCriticLearner):
|
|
|
96
96
|
value_loss = self.config.value_lambda * self._value_loss(
|
|
97
97
|
returns=returns,
|
|
98
98
|
rewards=rewards,
|
|
99
|
-
|
|
99
|
+
terminated=terminated,
|
|
100
100
|
values=values,
|
|
101
101
|
next_values=next_values,
|
|
102
102
|
)
|
|
@@ -105,7 +105,7 @@ class PPOLearner(ActorCriticLearner):
|
|
|
105
105
|
|
|
106
106
|
reg_loss = self._regularization_loss(
|
|
107
107
|
rewards=rewards,
|
|
108
|
-
|
|
108
|
+
terminated=terminated,
|
|
109
109
|
states=states,
|
|
110
110
|
actions=actions,
|
|
111
111
|
next_states=next_states,
|
|
@@ -18,7 +18,7 @@ class ReinforceLearner(OnPolicyLearner):
|
|
|
18
18
|
self,
|
|
19
19
|
returns: th.Tensor,
|
|
20
20
|
rewards: th.Tensor,
|
|
21
|
-
|
|
21
|
+
terminated: th.Tensor,
|
|
22
22
|
values: th.Tensor | None,
|
|
23
23
|
next_values: th.Tensor | None,
|
|
24
24
|
) -> th.Tensor:
|
|
@@ -34,7 +34,7 @@ class ReinforceLearner(OnPolicyLearner):
|
|
|
34
34
|
def train(
|
|
35
35
|
self,
|
|
36
36
|
rewards: th.Tensor,
|
|
37
|
-
|
|
37
|
+
terminated: th.Tensor,
|
|
38
38
|
states: th.Tensor,
|
|
39
39
|
actions: th.Tensor,
|
|
40
40
|
next_states: th.Tensor,
|
|
@@ -50,7 +50,7 @@ class ReinforceLearner(OnPolicyLearner):
|
|
|
50
50
|
advantages = self._advantages(
|
|
51
51
|
returns=returns,
|
|
52
52
|
rewards=rewards,
|
|
53
|
-
|
|
53
|
+
terminated=terminated,
|
|
54
54
|
values=None,
|
|
55
55
|
next_values=None,
|
|
56
56
|
)
|
|
@@ -60,7 +60,7 @@ class ReinforceLearner(OnPolicyLearner):
|
|
|
60
60
|
entropy_loss = self._entropy_loss(logits)
|
|
61
61
|
reg_loss = self._regularization_loss(
|
|
62
62
|
rewards=rewards,
|
|
63
|
-
|
|
63
|
+
terminated=terminated,
|
|
64
64
|
states=states,
|
|
65
65
|
actions=actions,
|
|
66
66
|
next_states=next_states,
|
|
@@ -27,7 +27,8 @@ class ReplayBuffer:
|
|
|
27
27
|
self.actions = np.zeros((capacity, *action_shape), dtype=action_dtype)
|
|
28
28
|
|
|
29
29
|
self.rewards = np.zeros((capacity, 1), dtype=np.float32)
|
|
30
|
-
self.
|
|
30
|
+
self.terminated = np.zeros((capacity, 1), dtype=np.bool_)
|
|
31
|
+
self.truncated = np.zeros((capacity, 1), dtype=np.bool_)
|
|
31
32
|
self.returns = np.zeros((capacity, 1), dtype=np.float32)
|
|
32
33
|
|
|
33
34
|
self.ptr = 0
|
|
@@ -41,7 +42,8 @@ class ReplayBuffer:
|
|
|
41
42
|
states: np.ndarray, # [B, *obs_shape]
|
|
42
43
|
actions: np.ndarray, # [B] or [B,1]
|
|
43
44
|
rewards: np.ndarray, # [B] or [B,1]
|
|
44
|
-
|
|
45
|
+
terminated: np.ndarray, # [B] or [B,1] (bool)
|
|
46
|
+
truncated: np.ndarray, # [B] or [B,1] (bool)
|
|
45
47
|
next_states: np.ndarray, # [B, *obs_shape]
|
|
46
48
|
returns: np.ndarray # [B, 1]
|
|
47
49
|
|
|
@@ -56,7 +58,8 @@ class ReplayBuffer:
|
|
|
56
58
|
else:
|
|
57
59
|
actions = np.asarray(actions, dtype=np.int64).reshape(B, 1)
|
|
58
60
|
rewards = np.asarray(rewards, dtype=np.float32).reshape(B, 1)
|
|
59
|
-
|
|
61
|
+
terminated = np.asarray(terminated, dtype=np.bool_).reshape(B, 1)
|
|
62
|
+
truncated = np.asarray(truncated, dtype=np.bool_).reshape(B, 1)
|
|
60
63
|
returns = np.asarray(returns, dtype=np.float32).reshape(B, 1)
|
|
61
64
|
|
|
62
65
|
idx = (self.ptr + np.arange(B)) % self.capacity
|
|
@@ -65,7 +68,8 @@ class ReplayBuffer:
|
|
|
65
68
|
self.next_states[idx] = next_states
|
|
66
69
|
self.actions[idx] = actions
|
|
67
70
|
self.rewards[idx] = rewards
|
|
68
|
-
self.
|
|
71
|
+
self.terminated[idx] = terminated
|
|
72
|
+
self.truncated[idx] = truncated
|
|
69
73
|
self.returns[idx] = returns
|
|
70
74
|
|
|
71
75
|
self.ptr = (self.ptr + B) % self.capacity
|
|
@@ -76,9 +80,12 @@ class ReplayBuffer:
|
|
|
76
80
|
next_states = th.from_numpy(self.next_states[:self.size]).to(self.device)
|
|
77
81
|
actions = th.from_numpy(self.actions[:self.size]).to(self.device)
|
|
78
82
|
rewards = th.from_numpy(self.rewards[:self.size]).to(self.device)
|
|
79
|
-
|
|
83
|
+
terminated = th.from_numpy(self.terminated[:self.size]).to(self.device)
|
|
84
|
+
truncated = th.from_numpy(self.truncated[:self.size]).to(self.device)
|
|
80
85
|
returns = th.from_numpy(self.returns[:self.size]).to(self.device)
|
|
81
|
-
return TransitionBatch(
|
|
86
|
+
return TransitionBatch(
|
|
87
|
+
states, actions, rewards, terminated, truncated, next_states, returns
|
|
88
|
+
)
|
|
82
89
|
|
|
83
90
|
def sample(self, batch_size: int) -> TransitionBatch:
|
|
84
91
|
if self.size == 0:
|
|
@@ -90,7 +97,10 @@ class ReplayBuffer:
|
|
|
90
97
|
next_states = th.from_numpy(self.next_states[idx]).to(self.device)
|
|
91
98
|
actions = th.from_numpy(self.actions[idx]).to(self.device) # long already
|
|
92
99
|
rewards = th.from_numpy(self.rewards[idx]).to(self.device) # float32
|
|
93
|
-
|
|
100
|
+
terminated = th.from_numpy(self.terminated[idx]).to(self.device) # bool
|
|
101
|
+
truncated = th.from_numpy(self.truncated[idx]).to(self.device) # bool
|
|
94
102
|
returns = th.from_numpy(self.returns[idx]).to(self.device) # float32
|
|
95
103
|
|
|
96
|
-
return TransitionBatch(
|
|
104
|
+
return TransitionBatch(
|
|
105
|
+
states, actions, rewards, terminated, truncated, next_states, returns
|
|
106
|
+
)
|
|
@@ -8,7 +8,8 @@ class TransitionBatch:
|
|
|
8
8
|
states: th.Tensor | np.ndarray # [B, obs_dim]
|
|
9
9
|
actions: th.Tensor | np.ndarray # [B, 1] (int64/float32)
|
|
10
10
|
rewards: th.Tensor | np.ndarray # [B, 1] (float)
|
|
11
|
-
|
|
11
|
+
terminated: th.Tensor | np.ndarray # [B, 1] (bool), disables bootstrapping
|
|
12
|
+
truncated: th.Tensor | np.ndarray # [B, 1] (bool), ends an episode only
|
|
12
13
|
next_states: th.Tensor | np.ndarray # [B, obs_dim]
|
|
13
14
|
returns: th.Tensor | np.ndarray # [B, 1] (float)
|
|
14
15
|
|
|
@@ -24,7 +25,8 @@ class TransitionBatch:
|
|
|
24
25
|
states=move(self.states),
|
|
25
26
|
actions=move(self.actions),
|
|
26
27
|
rewards=move(self.rewards),
|
|
27
|
-
|
|
28
|
+
terminated=move(self.terminated),
|
|
29
|
+
truncated=move(self.truncated),
|
|
28
30
|
next_states=move(self.next_states),
|
|
29
31
|
returns=move(self.returns),
|
|
30
32
|
)
|
|
@@ -34,7 +36,8 @@ class TransitionBatch:
|
|
|
34
36
|
states=th.cat([self.states, other.states], dim=0),
|
|
35
37
|
actions=th.cat([self.actions, other.actions], dim=0),
|
|
36
38
|
rewards=th.cat([self.rewards, other.rewards], dim=0),
|
|
37
|
-
|
|
39
|
+
terminated=th.cat([self.terminated, other.terminated], dim=0),
|
|
40
|
+
truncated=th.cat([self.truncated, other.truncated], dim=0),
|
|
38
41
|
next_states=th.cat([self.next_states, other.next_states], dim=0),
|
|
39
42
|
returns=th.cat([self.returns, other.returns], dim=0),
|
|
40
43
|
)
|
|
@@ -34,7 +34,8 @@ class Runner:
|
|
|
34
34
|
self.return_last_episode = return_last_episode
|
|
35
35
|
self.ep_len, self.ep_ret = 0, 0.0
|
|
36
36
|
self._ep_states, self._ep_actions, self._ep_rewards = [], [], []
|
|
37
|
-
self.
|
|
37
|
+
self._ep_terminated, self._ep_truncated = [], []
|
|
38
|
+
self._ep_next_states, self._ep_returns = [], []
|
|
38
39
|
|
|
39
40
|
def _discounted_returns(self, rewards: list[float]) -> list[float]:
|
|
40
41
|
out = [0.0] * len(rewards)
|
|
@@ -68,7 +69,8 @@ class Runner:
|
|
|
68
69
|
states,
|
|
69
70
|
actions,
|
|
70
71
|
rewards,
|
|
71
|
-
|
|
72
|
+
terminated,
|
|
73
|
+
truncated,
|
|
72
74
|
next_states,
|
|
73
75
|
returns,
|
|
74
76
|
as_numpy: bool,
|
|
@@ -76,18 +78,22 @@ class Runner:
|
|
|
76
78
|
states = np.asarray(states, dtype=np.float32)
|
|
77
79
|
actions = self._actions_to_array(actions)
|
|
78
80
|
rewards = np.asarray(rewards, dtype=np.float32).reshape(-1, 1)
|
|
79
|
-
|
|
81
|
+
terminated = np.asarray(terminated, dtype=np.bool_).reshape(-1, 1)
|
|
82
|
+
truncated = np.asarray(truncated, dtype=np.bool_).reshape(-1, 1)
|
|
80
83
|
next_states = np.asarray(next_states, dtype=np.float32)
|
|
81
84
|
returns = np.asarray(returns, dtype=np.float32).reshape(-1, 1)
|
|
82
85
|
|
|
83
86
|
if as_numpy:
|
|
84
|
-
return TransitionBatch(
|
|
87
|
+
return TransitionBatch(
|
|
88
|
+
states, actions, rewards, terminated, truncated, next_states, returns
|
|
89
|
+
)
|
|
85
90
|
|
|
86
91
|
return TransitionBatch(
|
|
87
92
|
states=th.from_numpy(states),
|
|
88
93
|
actions=th.from_numpy(actions),
|
|
89
94
|
rewards=th.from_numpy(rewards),
|
|
90
|
-
|
|
95
|
+
terminated=th.from_numpy(terminated),
|
|
96
|
+
truncated=th.from_numpy(truncated),
|
|
91
97
|
next_states=th.from_numpy(next_states),
|
|
92
98
|
returns=th.from_numpy(returns),
|
|
93
99
|
)
|
|
@@ -102,7 +108,9 @@ class Runner:
|
|
|
102
108
|
last_episode = None
|
|
103
109
|
ep_returns, ep_lengths = [], []
|
|
104
110
|
ep_start = 0
|
|
105
|
-
states, actions, rewards
|
|
111
|
+
states, actions, rewards = [], [], []
|
|
112
|
+
terminated_flags, truncated_flags = [], []
|
|
113
|
+
next_states, returns = [], []
|
|
106
114
|
run_one_episode = num_steps <= 0
|
|
107
115
|
|
|
108
116
|
st = 0
|
|
@@ -113,14 +121,15 @@ class Runner:
|
|
|
113
121
|
with th.inference_mode():
|
|
114
122
|
action_t = self.controller.choose(state_t)
|
|
115
123
|
action = self._action_to_env(action_t)
|
|
116
|
-
next_state, reward, terminated, truncated, _ = self.env.step(action)
|
|
117
|
-
|
|
124
|
+
next_state, reward, terminated, truncated, _ = self.env.step(action)
|
|
125
|
+
episode_end = terminated or truncated
|
|
118
126
|
|
|
119
127
|
# 2. Store transition
|
|
120
128
|
states.append(self.state)
|
|
121
129
|
actions.append(action)
|
|
122
130
|
rewards.append(reward)
|
|
123
|
-
|
|
131
|
+
terminated_flags.append(terminated)
|
|
132
|
+
truncated_flags.append(truncated)
|
|
124
133
|
next_states.append(next_state)
|
|
125
134
|
returns.append(0)
|
|
126
135
|
self.ep_ret += reward
|
|
@@ -129,34 +138,37 @@ class Runner:
|
|
|
129
138
|
self._ep_states.append(self.state)
|
|
130
139
|
self._ep_actions.append(action)
|
|
131
140
|
self._ep_rewards.append(reward)
|
|
132
|
-
self.
|
|
141
|
+
self._ep_terminated.append(terminated)
|
|
142
|
+
self._ep_truncated.append(truncated)
|
|
133
143
|
self._ep_next_states.append(next_state)
|
|
134
144
|
self._ep_returns.append(0)
|
|
135
145
|
|
|
136
146
|
# 3. Compute cumlative returns
|
|
137
|
-
if self.calculate_returns and (
|
|
147
|
+
if self.calculate_returns and (episode_end or st == num_steps - 1):
|
|
138
148
|
ep_rewards = rewards[ep_start: st + 1]
|
|
139
149
|
discounted_returns = self._discounted_returns(ep_rewards)
|
|
140
150
|
|
|
141
151
|
returns[ep_start: st + 1] = discounted_returns
|
|
142
|
-
if self.return_last_episode and
|
|
152
|
+
if self.return_last_episode and episode_end:
|
|
143
153
|
self._ep_returns = discounted_returns
|
|
144
154
|
|
|
145
155
|
# 4. Update state
|
|
146
156
|
self.state = next_state
|
|
147
|
-
if
|
|
157
|
+
if episode_end:
|
|
148
158
|
if self.return_last_episode:
|
|
149
159
|
last_episode = self._make_batch(
|
|
150
160
|
self._ep_states,
|
|
151
161
|
self._ep_actions,
|
|
152
162
|
self._ep_rewards,
|
|
153
|
-
self.
|
|
163
|
+
self._ep_terminated,
|
|
164
|
+
self._ep_truncated,
|
|
154
165
|
self._ep_next_states,
|
|
155
166
|
self._ep_returns,
|
|
156
167
|
as_numpy,
|
|
157
168
|
)
|
|
158
169
|
self._ep_states, self._ep_actions, self._ep_rewards = [], [], []
|
|
159
|
-
self.
|
|
170
|
+
self._ep_terminated, self._ep_truncated = [], []
|
|
171
|
+
self._ep_next_states, self._ep_returns = [], []
|
|
160
172
|
ep_start = st + 1
|
|
161
173
|
ep_returns.append(self.ep_ret)
|
|
162
174
|
ep_lengths.append(self.ep_len)
|
|
@@ -173,7 +185,8 @@ class Runner:
|
|
|
173
185
|
states,
|
|
174
186
|
actions,
|
|
175
187
|
rewards,
|
|
176
|
-
|
|
188
|
+
terminated_flags,
|
|
189
|
+
truncated_flags,
|
|
177
190
|
next_states,
|
|
178
191
|
returns,
|
|
179
192
|
as_numpy,
|
|
@@ -47,7 +47,7 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
47
47
|
|
|
48
48
|
loss = learner.train(
|
|
49
49
|
rewards=th.tensor([[1.0], [0.0], [1.0]]),
|
|
50
|
-
|
|
50
|
+
terminated=th.tensor([[False], [True], [False]]),
|
|
51
51
|
states=th.randn(3, 4),
|
|
52
52
|
actions=th.tensor([[0], [1], [0]], dtype=th.long),
|
|
53
53
|
next_states=th.randn(3, 4),
|
|
@@ -90,7 +90,7 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
90
90
|
|
|
91
91
|
loss = learner.train(
|
|
92
92
|
rewards=th.tensor([[1.0], [0.0], [1.0]]),
|
|
93
|
-
|
|
93
|
+
terminated=th.tensor([[False], [True], [False]]),
|
|
94
94
|
states=th.randn(3, obs_dim),
|
|
95
95
|
actions=th.randn(3, action_dim).tanh(),
|
|
96
96
|
next_states=th.randn(3, obs_dim),
|
|
@@ -125,7 +125,7 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
125
125
|
)
|
|
126
126
|
calls = []
|
|
127
127
|
|
|
128
|
-
def actor_l2(model, rewards,
|
|
128
|
+
def actor_l2(model, rewards, terminated, states, actions, next_states):
|
|
129
129
|
calls.append(model)
|
|
130
130
|
return sum(param.square().sum() for param in model.parameters())
|
|
131
131
|
|
|
@@ -145,7 +145,7 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
145
145
|
|
|
146
146
|
learner.train(
|
|
147
147
|
rewards=th.tensor([[1.0], [0.0], [1.0]]),
|
|
148
|
-
|
|
148
|
+
terminated=th.tensor([[False], [True], [False]]),
|
|
149
149
|
states=th.randn(3, obs_dim),
|
|
150
150
|
actions=th.randn(3, action_dim).tanh(),
|
|
151
151
|
next_states=th.randn(3, obs_dim),
|
|
@@ -171,7 +171,7 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
171
171
|
|
|
172
172
|
loss = learner.train(
|
|
173
173
|
rewards=th.tensor([[1.0], [0.5], [0.0]]),
|
|
174
|
-
|
|
174
|
+
terminated=th.tensor([[False], [False], [True]]),
|
|
175
175
|
states=th.randn(3, 4),
|
|
176
176
|
actions=th.tensor([[0], [1], [0]], dtype=th.long),
|
|
177
177
|
next_states=th.randn(3, 4),
|
|
@@ -199,7 +199,7 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
199
199
|
|
|
200
200
|
loss = learner.train(
|
|
201
201
|
rewards=th.tensor([[1.0], [0.5], [0.0]]),
|
|
202
|
-
|
|
202
|
+
terminated=th.tensor([[False], [False], [True]]),
|
|
203
203
|
states=th.randn(3, 4),
|
|
204
204
|
actions=th.tensor([[0], [1], [0]], dtype=th.long),
|
|
205
205
|
next_states=th.randn(3, 4),
|
|
@@ -227,7 +227,7 @@ class LearnerSmokeTest(unittest.TestCase):
|
|
|
227
227
|
|
|
228
228
|
loss = learner.train(
|
|
229
229
|
rewards=th.tensor([[1.0], [0.5], [0.0]]),
|
|
230
|
-
|
|
230
|
+
terminated=th.tensor([[False], [False], [True]]),
|
|
231
231
|
states=th.randn(3, 4),
|
|
232
232
|
actions=th.tensor([[0], [1], [0]], dtype=th.long),
|
|
233
233
|
next_states=th.randn(3, 4),
|
|
@@ -49,7 +49,7 @@ class CountingLearner(OffPolicyLearner):
|
|
|
49
49
|
def train(
|
|
50
50
|
self,
|
|
51
51
|
rewards: th.Tensor,
|
|
52
|
-
|
|
52
|
+
terminated: th.Tensor,
|
|
53
53
|
states: th.Tensor,
|
|
54
54
|
actions: th.Tensor,
|
|
55
55
|
next_states: th.Tensor,
|
|
@@ -63,7 +63,8 @@ def one_transition() -> TransitionBatch:
|
|
|
63
63
|
states=np.asarray([[0.0]], dtype=np.float32),
|
|
64
64
|
actions=np.asarray([[0]], dtype=np.int64),
|
|
65
65
|
rewards=np.asarray([[1.0]], dtype=np.float32),
|
|
66
|
-
|
|
66
|
+
terminated=np.asarray([[False]], dtype=np.bool_),
|
|
67
|
+
truncated=np.asarray([[False]], dtype=np.bool_),
|
|
67
68
|
next_states=np.asarray([[1.0]], dtype=np.float32),
|
|
68
69
|
returns=np.asarray([[1.0]], dtype=np.float32),
|
|
69
70
|
)
|
|
@@ -37,7 +37,7 @@ class PublicAPITest(unittest.TestCase):
|
|
|
37
37
|
"WarmupController",
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
self.assertEqual(drlab.__version__, "0.2.
|
|
40
|
+
self.assertEqual(drlab.__version__, "0.2.3")
|
|
41
41
|
self.assertTrue(expected.issubset(set(drlab.__all__)))
|
|
42
42
|
for name in expected:
|
|
43
43
|
self.assertTrue(hasattr(drlab, name), name)
|
|
@@ -12,7 +12,8 @@ class TransitionBatchTest(unittest.TestCase):
|
|
|
12
12
|
states=th.zeros(2, 3),
|
|
13
13
|
actions=th.zeros(2, 1, dtype=th.long),
|
|
14
14
|
rewards=th.ones(2, 1),
|
|
15
|
-
|
|
15
|
+
terminated=th.zeros(2, 1, dtype=th.bool),
|
|
16
|
+
truncated=th.zeros(2, 1, dtype=th.bool),
|
|
16
17
|
next_states=th.ones(2, 3),
|
|
17
18
|
returns=th.full((2, 1), 2.0),
|
|
18
19
|
)
|
|
@@ -28,7 +29,8 @@ class TransitionBatchTest(unittest.TestCase):
|
|
|
28
29
|
states=np.zeros((2, 3), dtype=np.float32),
|
|
29
30
|
actions=np.zeros((2, 1), dtype=np.int64),
|
|
30
31
|
rewards=np.ones((2, 1), dtype=np.float32),
|
|
31
|
-
|
|
32
|
+
terminated=np.zeros((2, 1), dtype=np.bool_),
|
|
33
|
+
truncated=np.zeros((2, 1), dtype=np.bool_),
|
|
32
34
|
next_states=np.ones((2, 3), dtype=np.float32),
|
|
33
35
|
returns=np.full((2, 1), 2.0, dtype=np.float32),
|
|
34
36
|
)
|
|
@@ -37,7 +39,8 @@ class TransitionBatchTest(unittest.TestCase):
|
|
|
37
39
|
|
|
38
40
|
self.assertEqual(moved.states.dtype, th.float32)
|
|
39
41
|
self.assertEqual(moved.actions.dtype, th.int64)
|
|
40
|
-
self.assertEqual(moved.
|
|
42
|
+
self.assertEqual(moved.terminated.dtype, th.bool)
|
|
43
|
+
self.assertEqual(moved.truncated.dtype, th.bool)
|
|
41
44
|
self.assertTrue(th.equal(moved.returns, th.full((2, 1), 2.0)))
|
|
42
45
|
|
|
43
46
|
def test_cat_concatenates_every_field(self):
|
|
@@ -45,7 +48,8 @@ class TransitionBatchTest(unittest.TestCase):
|
|
|
45
48
|
states=th.zeros(1, 2),
|
|
46
49
|
actions=th.zeros(1, 1, dtype=th.long),
|
|
47
50
|
rewards=th.ones(1, 1),
|
|
48
|
-
|
|
51
|
+
terminated=th.zeros(1, 1, dtype=th.bool),
|
|
52
|
+
truncated=th.ones(1, 1, dtype=th.bool),
|
|
49
53
|
next_states=th.ones(1, 2),
|
|
50
54
|
returns=th.ones(1, 1),
|
|
51
55
|
)
|
|
@@ -53,7 +57,8 @@ class TransitionBatchTest(unittest.TestCase):
|
|
|
53
57
|
states=th.ones(2, 2),
|
|
54
58
|
actions=th.ones(2, 1, dtype=th.long),
|
|
55
59
|
rewards=th.full((2, 1), 2.0),
|
|
56
|
-
|
|
60
|
+
terminated=th.ones(2, 1, dtype=th.bool),
|
|
61
|
+
truncated=th.zeros(2, 1, dtype=th.bool),
|
|
57
62
|
next_states=th.full((2, 2), 3.0),
|
|
58
63
|
returns=th.full((2, 1), 4.0),
|
|
59
64
|
)
|
|
@@ -63,6 +68,8 @@ class TransitionBatchTest(unittest.TestCase):
|
|
|
63
68
|
self.assertEqual(combined.states.shape, (3, 2))
|
|
64
69
|
self.assertEqual(combined.actions.shape, (3, 1))
|
|
65
70
|
self.assertTrue(th.equal(combined.rewards.squeeze(-1), th.tensor([1.0, 2.0, 2.0])))
|
|
71
|
+
episode_ends = combined.terminated | combined.truncated
|
|
72
|
+
self.assertTrue(th.equal(episode_ends, th.ones(3, 1, dtype=th.bool)))
|
|
66
73
|
|
|
67
74
|
|
|
68
75
|
class ReplayBufferTest(unittest.TestCase):
|
|
@@ -75,7 +82,8 @@ class ReplayBufferTest(unittest.TestCase):
|
|
|
75
82
|
states=states,
|
|
76
83
|
actions=np.asarray([0, 1, 0]),
|
|
77
84
|
rewards=np.asarray([1.0, 2.0, 3.0]),
|
|
78
|
-
|
|
85
|
+
terminated=np.asarray([False, False, True]),
|
|
86
|
+
truncated=np.asarray([False, True, False]),
|
|
79
87
|
next_states=next_states,
|
|
80
88
|
returns=np.asarray([6.0, 5.0, 3.0]),
|
|
81
89
|
)
|
|
@@ -87,7 +95,9 @@ class ReplayBufferTest(unittest.TestCase):
|
|
|
87
95
|
self.assertIsInstance(all_data, TransitionBatch)
|
|
88
96
|
self.assertEqual(all_data.states.shape, (3, 2))
|
|
89
97
|
self.assertEqual(all_data.actions.dtype, th.int64)
|
|
90
|
-
self.assertEqual(all_data.
|
|
98
|
+
self.assertEqual(all_data.terminated.dtype, th.bool)
|
|
99
|
+
self.assertEqual(all_data.truncated.dtype, th.bool)
|
|
100
|
+
self.assertTrue(all_data.truncated[1].item())
|
|
91
101
|
self.assertIsInstance(sample, TransitionBatch)
|
|
92
102
|
self.assertEqual(sample.states.shape, (2, 2))
|
|
93
103
|
|
|
@@ -98,7 +108,8 @@ class ReplayBufferTest(unittest.TestCase):
|
|
|
98
108
|
states=np.asarray([[0], [1]], dtype=np.float32),
|
|
99
109
|
actions=np.asarray([0, 1]),
|
|
100
110
|
rewards=np.asarray([0.0, 1.0]),
|
|
101
|
-
|
|
111
|
+
terminated=np.asarray([False, False]),
|
|
112
|
+
truncated=np.asarray([False, False]),
|
|
102
113
|
next_states=np.asarray([[1], [2]], dtype=np.float32),
|
|
103
114
|
returns=np.asarray([1.0, 1.0]),
|
|
104
115
|
)
|
|
@@ -106,7 +117,8 @@ class ReplayBufferTest(unittest.TestCase):
|
|
|
106
117
|
states=np.asarray([[2], [3]], dtype=np.float32),
|
|
107
118
|
actions=np.asarray([0, 1]),
|
|
108
119
|
rewards=np.asarray([2.0, 3.0]),
|
|
109
|
-
|
|
120
|
+
terminated=np.asarray([False, True]),
|
|
121
|
+
truncated=np.asarray([False, False]),
|
|
110
122
|
next_states=np.asarray([[3], [4]], dtype=np.float32),
|
|
111
123
|
returns=np.asarray([5.0, 3.0]),
|
|
112
124
|
)
|
|
@@ -48,6 +48,12 @@ class ThreeStepEnv(gym.Env):
|
|
|
48
48
|
return obs, reward, terminated, truncated, {}
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
class TruncatingEnv(ThreeStepEnv):
|
|
52
|
+
def step(self, action):
|
|
53
|
+
obs, reward, _, _, info = super().step(action)
|
|
54
|
+
return obs, reward, False, self.t >= 3, info
|
|
55
|
+
|
|
56
|
+
|
|
51
57
|
class RunnerTest(unittest.TestCase):
|
|
52
58
|
def test_run_zero_collects_one_complete_episode_with_returns(self):
|
|
53
59
|
env = ThreeStepEnv()
|
|
@@ -65,6 +71,23 @@ class RunnerTest(unittest.TestCase):
|
|
|
65
71
|
self.assertEqual(ep_lengths, [3])
|
|
66
72
|
self.assertTrue(th.equal(batch.returns.squeeze(-1), th.tensor([6.0, 4.0, 2.0])))
|
|
67
73
|
self.assertTrue(th.equal(last_episode.returns.squeeze(-1), th.tensor([6.0, 4.0, 2.0])))
|
|
74
|
+
self.assertTrue(batch.terminated[-1].item())
|
|
75
|
+
self.assertFalse(batch.truncated.any().item())
|
|
76
|
+
|
|
77
|
+
def test_truncation_ends_episode_without_marking_transition_terminal(self):
|
|
78
|
+
env = TruncatingEnv()
|
|
79
|
+
model = FixedLogits([0.0, 1.0])
|
|
80
|
+
controller = GreedyController(model, num_actions=2)
|
|
81
|
+
runner = Runner(env, controller, calculate_returns=False)
|
|
82
|
+
|
|
83
|
+
batch, ep_returns, ep_lengths, last_episode = runner.run(0)
|
|
84
|
+
|
|
85
|
+
self.assertEqual(ep_returns, [6.0])
|
|
86
|
+
self.assertEqual(ep_lengths, [3])
|
|
87
|
+
self.assertIsNotNone(last_episode)
|
|
88
|
+
self.assertFalse(batch.terminated.any().item())
|
|
89
|
+
self.assertTrue(batch.truncated[-1].item())
|
|
90
|
+
self.assertTrue((batch.terminated | batch.truncated)[-1].item())
|
|
68
91
|
|
|
69
92
|
def test_run_positive_steps_can_return_partial_batch(self):
|
|
70
93
|
env = ThreeStepEnv()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|