physicausal 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,25 @@
1
+ from .causal.validator import CausalValidator
2
+ from .envs.pendulum import PendulumEnv, PendulumState
3
+ from .envs.simple_push import ObjectState, SimplePushEnv
4
+ from .models.beta_tc_vae import BetaTCVAE
5
+ from .models.vae import BetaVAE, WorldModel
6
+ from .training.agent import CausalLearningAgent, ReplayBuffer, train
7
+ from .utils.tracker import NoOpTracker, Tracker
8
+
9
+ __all__ = [
10
+ "BetaTCVAE",
11
+ "BetaVAE",
12
+ "CausalLearningAgent",
13
+ "CausalValidator",
14
+ "NoOpTracker",
15
+ "ObjectState",
16
+ "PendulumEnv",
17
+ "PendulumState",
18
+ "ReplayBuffer",
19
+ "SimplePushEnv",
20
+ "Tracker",
21
+ "WorldModel",
22
+ "train",
23
+ ]
24
+
25
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ """
2
+ 基准测试适配器,支持与其他因果世界模型工具的数据格式互转。
3
+ """
@@ -0,0 +1,142 @@
1
+ """
2
+ CausalVerse data format adapter.
3
+
4
+ CausalVerse (NeurIPS 2025 Spotlight) is a comprehensive causal representation learning benchmark,
5
+ containing 10 domains, 1024x1024 images, 300M+ video frames.
6
+
7
+ Its data structure:
8
+ - images: (T, H, W, 3) RGB sequence
9
+ - factors: dict of ground-truth causal factors
10
+ - actions: (T-1, action_dim) action sequence
11
+
12
+ This adapter provides:
13
+ 1. Converting CausalVerse video data to physicausal (obs, action, next_obs) format
14
+ 2. Converting physicausal evaluation results to CausalVerse standard metrics (MCC, R2)
15
+
16
+ Note: CausalVerse requires additional installation (not a physicausal core dependency).
17
+ """
18
+
19
+ from collections.abc import Callable
20
+ from typing import Any
21
+
22
+ import numpy as np
23
+
24
+
25
+ def convert_causalverse_sequence(
26
+ images: np.ndarray,
27
+ actions: np.ndarray,
28
+ factors: dict[str, np.ndarray] | None = None,
29
+ resize_fn: Callable | None = None,
30
+ ) -> list[tuple[dict, np.ndarray, dict]]:
31
+ """
32
+ Convert CausalVerse video sequence to physicausal triplet list.
33
+
34
+ Parameters
35
+ ----------
36
+ images : np.ndarray
37
+ Image sequence, shape (T, H, W, 3).
38
+ actions : np.ndarray
39
+ Action sequence, shape (T-1, action_dim).
40
+ factors : dict, optional
41
+ Ground-truth causal factors for validation.
42
+ resize_fn : callable, optional
43
+ Image resize function for dimensionality reduction.
44
+
45
+ Returns
46
+ -------
47
+ data : list
48
+ physicausal-format (obs, action, next_obs) list.
49
+ """
50
+ T = images.shape[0]
51
+ data = []
52
+
53
+ for t in range(T - 1):
54
+ obs_b = _image_to_obs(images[t], factors, t, resize_fn)
55
+ obs_a = _image_to_obs(images[t + 1], factors, t + 1, resize_fn)
56
+ action = np.array(actions[t], dtype=np.float32)
57
+ data.append((obs_b, action, obs_a))
58
+
59
+ return data
60
+
61
+
62
+ def _image_to_obs(
63
+ image: np.ndarray,
64
+ factors: dict | None,
65
+ frame_idx: int,
66
+ resize_fn: Callable | None,
67
+ ) -> dict:
68
+ """Convert single frame to physicausal observation format."""
69
+ if resize_fn is not None:
70
+ image = resize_fn(image)
71
+
72
+ visual = image.flatten().astype(np.float32) / 255.0
73
+
74
+ obs: dict[str, Any] = {"visual": visual}
75
+
76
+ if factors is not None:
77
+ # Extract factor values for current frame
78
+ for key, values in factors.items():
79
+ if frame_idx < len(values):
80
+ obs[key] = values[frame_idx]
81
+
82
+ return obs
83
+
84
+
85
+ def compute_causalverse_metrics(
86
+ zs: np.ndarray,
87
+ ground_truth_factors: np.ndarray,
88
+ ) -> dict[str, float]:
89
+ """
90
+ Compute CausalVerse standard metrics.
91
+
92
+ Parameters
93
+ ----------
94
+ zs : np.ndarray
95
+ Latent variables, shape (n_samples, latent_dim).
96
+ ground_truth_factors : np.ndarray
97
+ Ground-truth causal factors, shape (n_samples, n_factors).
98
+
99
+ Returns
100
+ -------
101
+ metrics : dict
102
+ Contains MCC (Mean Correlation Coefficient) and R2.
103
+ """
104
+ from scipy import stats
105
+
106
+ n_factors = ground_truth_factors.shape[1]
107
+ n_dims = zs.shape[1]
108
+
109
+ # Compute correlation matrix for all (factor, latent_dim) pairs
110
+ corr_matrix = np.zeros((n_factors, n_dims))
111
+ for i in range(n_factors):
112
+ for j in range(n_dims):
113
+ r, _ = stats.pearsonr(ground_truth_factors[:, i], zs[:, j])
114
+ corr_matrix[i, j] = abs(r)
115
+
116
+ # MCC: optimal one-to-one matching between factors and latent dims
117
+ # (Hungarian algorithm on the |correlation| matrix, standard in CRL literature)
118
+ from scipy.optimize import linear_sum_assignment
119
+
120
+ rows, cols = linear_sum_assignment(-corr_matrix)
121
+ matched_corrs = [corr_matrix[i, j] for i, j in zip(rows, cols)]
122
+
123
+ mcc = np.mean(matched_corrs) if matched_corrs else 0.0
124
+
125
+ # R2: linear regression fit
126
+ from sklearn.linear_model import LinearRegression
127
+ from sklearn.metrics import r2_score
128
+
129
+ r2s = []
130
+ for i in range(n_factors):
131
+ reg = LinearRegression()
132
+ reg.fit(zs, ground_truth_factors[:, i])
133
+ pred = reg.predict(zs)
134
+ r2s.append(r2_score(ground_truth_factors[:, i], pred))
135
+
136
+ avg_r2 = np.mean(r2s) if r2s else 0.0
137
+
138
+ return {
139
+ "mcc": float(mcc),
140
+ "r2": float(avg_r2),
141
+ "matched_corrs": matched_corrs,
142
+ }
@@ -0,0 +1,122 @@
1
+ """
2
+ CausalWorld data format adapter.
3
+
4
+ CausalWorld (https://github.com/rr-learning/CausalWorld) uses PyBullet
5
+ for robot manipulation simulation. Its data structure is:
6
+ - observations: dict with 'goal', 'mask', 'rgb', 'depth'
7
+ - actions: continuous action vector
8
+ - rewards: scalar reward
9
+
10
+ This adapter provides:
11
+ 1. Converting CausalWorld data to physicausal format
12
+ 2. Converting physicausal evaluation results to CausalWorld metrics
13
+
14
+ Note: CausalWorld requires additional installation (not a physicausal core dependency).
15
+ """
16
+
17
+ from collections.abc import Callable
18
+ from typing import Any
19
+
20
+ import numpy as np
21
+
22
+
23
+ def convert_causalworld_triplet(
24
+ obs_before: dict,
25
+ action: np.ndarray,
26
+ obs_after: dict,
27
+ extract_visual_fn: Callable | None = None,
28
+ ) -> tuple[dict, np.ndarray, dict]:
29
+ """
30
+ Convert one CausalWorld (obs, action, next_obs) to physicausal format.
31
+
32
+ Parameters
33
+ ----------
34
+ obs_before : dict
35
+ CausalWorld observation containing 'rgb', 'depth', 'mask', etc.
36
+ action : np.ndarray
37
+ Action vector.
38
+ obs_after : dict
39
+ Next observation.
40
+ extract_visual_fn : callable, optional
41
+ Custom visual feature extraction function. Defaults to flattening RGB.
42
+
43
+ Returns
44
+ -------
45
+ (obs_before_fmt, action_fmt, obs_after_fmt) : tuple
46
+ physicausal-format triplet.
47
+ """
48
+ if extract_visual_fn is None:
49
+ # Default: flatten RGB image as visual feature
50
+ def extract_visual_fn(obs: dict) -> np.ndarray:
51
+ rgb = obs.get("rgb", obs.get("observation", {}).get("rgb"))
52
+ if rgb is None:
53
+ raise ValueError("Cannot find 'rgb' in observation")
54
+ return rgb.flatten().astype(np.float32) / 255.0
55
+
56
+ obs_b_fmt = {
57
+ "visual": extract_visual_fn(obs_before),
58
+ "position": obs_before.get("position", np.zeros(3)),
59
+ }
60
+ obs_a_fmt = {
61
+ "visual": extract_visual_fn(obs_after),
62
+ "position": obs_after.get("position", np.zeros(3)),
63
+ }
64
+
65
+ return obs_b_fmt, np.array(action, dtype=np.float32), obs_a_fmt
66
+
67
+
68
+ def causalworld_to_physicausal(
69
+ dataset: list[dict],
70
+ extract_visual_fn: Callable | None = None,
71
+ ) -> list[tuple[dict, np.ndarray, dict]]:
72
+ """
73
+ Batch convert CausalWorld dataset to physicausal format.
74
+
75
+ Parameters
76
+ ----------
77
+ dataset : list[dict]
78
+ CausalWorld dataset, each item is {"obs": ..., "action": ..., "next_obs": ...}.
79
+
80
+ Returns
81
+ -------
82
+ data : list
83
+ physicausal-format data list.
84
+ """
85
+ data = []
86
+ for item in dataset:
87
+ obs_b = item.get("obs", item.get("observation"))
88
+ action = item.get("action")
89
+ obs_a = item.get("next_obs", item.get("next_observation"))
90
+ triplet = convert_causalworld_triplet(obs_b, action, obs_a, extract_visual_fn)
91
+ data.append(triplet)
92
+ return data
93
+
94
+
95
+ def compute_causalworld_metrics(
96
+ validator_results: dict[str, Any],
97
+ ) -> dict[str, float]:
98
+ """
99
+ Convert physicausal validation results to CausalWorld-style metrics.
100
+
101
+ CausalWorld uses the following metrics:
102
+ - success_rate: task success rate
103
+ - intervention_success: intervention success rate
104
+ - disentanglement_score: disentanglement score
105
+
106
+ This function provides approximate mapping.
107
+ """
108
+ results = validator_results.get("results", {})
109
+
110
+ # Compute average causal score
111
+ causal_scores = [
112
+ res["best_abs_corr"] for res in results.values() if res.get("is_causal")
113
+ ]
114
+
115
+ avg_corr = np.mean(causal_scores) if causal_scores else 0.0
116
+ n_causal = sum(1 for r in results.values() if r.get("is_causal"))
117
+
118
+ return {
119
+ "disentanglement_score": avg_corr,
120
+ "n_causal_properties": n_causal,
121
+ "total_properties": len(results),
122
+ }
@@ -0,0 +1,4 @@
1
+ from .intervention import bootstrap_ci
2
+ from .validator import CausalValidator
3
+
4
+ __all__ = ["CausalValidator", "bootstrap_ci"]
@@ -0,0 +1,47 @@
1
+ """
2
+ 因果干预与评估工具。
3
+
4
+ 此模块保留向后兼容的 API,主要功能已迁移到 CausalValidator。
5
+ """
6
+
7
+ from collections.abc import Callable
8
+
9
+ import numpy as np
10
+
11
+
12
+ def bootstrap_ci(
13
+ data: np.ndarray,
14
+ statistic_fn: Callable[[np.ndarray], float],
15
+ n_bootstrap: int = 1000,
16
+ alpha: float = 0.05,
17
+ seed: int = 42,
18
+ ) -> tuple[float, float]:
19
+ """
20
+ Bootstrap 置信区间估计。
21
+
22
+ Parameters
23
+ ----------
24
+ data : np.ndarray
25
+ 原始样本数据。
26
+ statistic_fn : callable
27
+ 统计量计算函数,输入数组返回标量。
28
+ n_bootstrap : int
29
+ Bootstrap 重采样次数。
30
+ alpha : float
31
+ 显著性水平。
32
+ seed : int
33
+ 随机种子。
34
+
35
+ Returns
36
+ -------
37
+ (lower, upper) : 置信区间上下界。
38
+ """
39
+ rng = np.random.RandomState(seed)
40
+ n = len(data)
41
+ stats_ = np.zeros(n_bootstrap)
42
+ for i in range(n_bootstrap):
43
+ sample = data[rng.randint(0, n, n)]
44
+ stats_[i] = statistic_fn(sample)
45
+ lower = np.percentile(stats_, 100 * alpha / 2)
46
+ upper = np.percentile(stats_, 100 * (1 - alpha / 2))
47
+ return float(lower), float(upper)
@@ -0,0 +1,315 @@
1
+ from typing import TYPE_CHECKING, Any
2
+
3
+ import numpy as np
4
+ import torch
5
+ from scipy import stats
6
+ from sklearn.feature_selection import mutual_info_regression
7
+
8
+ if TYPE_CHECKING:
9
+ from ..models.base import CausalWorldModel
10
+
11
+
12
+ class CausalValidator:
13
+ """
14
+ Causal validator: evaluates whether a world model's latent variables capture real physical causal properties.
15
+
16
+ Supports the following validation methods:
17
+ 1. Pearson correlation + permutation test (statistical significance)
18
+ 2. Mutual information (non-linear association)
19
+ 3. Do-operator intervention (causal consistency)
20
+ 4. Sensitivity analysis (gradient attribution)
21
+ 5. DCS (Disentanglement Completeness Score)
22
+ """
23
+
24
+ def __init__(self, model: "CausalWorldModel", env):
25
+ self.model = model
26
+ self.env = env
27
+
28
+ # ------------------------------------------------------------------
29
+ # Encoding
30
+ # ------------------------------------------------------------------
31
+
32
+ def encode_objects(
33
+ self, objects: list, batch_size: int = 64
34
+ ) -> np.ndarray:
35
+ """
36
+ Encode a list of objects into a latent variable matrix.
37
+
38
+ Each object is probed with a standardized interaction (``env.probe``) whose
39
+ response is a deterministic function of the object's physical properties.
40
+ The probe observations are encoded and averaged into a single latent vector.
41
+
42
+ For probabilistic models the posterior mean (``encode_dist``) is used instead
43
+ of a sampled ``z``, avoiding sampling noise in the correlation estimates.
44
+
45
+ Returns
46
+ -------
47
+ zs : np.ndarray
48
+ shape (n_objects, latent_dim)
49
+ """
50
+ device = next(self.model.parameters()).device
51
+ zs: list[np.ndarray] = []
52
+
53
+ for i in range(0, len(objects), batch_size):
54
+ batch = objects[i : i + batch_size]
55
+ batch_zs: list[np.ndarray] = []
56
+ for obj in batch:
57
+ obj_idx = self.env.objects.index(obj)
58
+ obs_list = self.env.probe(obj_idx)
59
+ obs_t = torch.tensor(
60
+ np.stack([o["visual"] for o in obs_list]),
61
+ dtype=torch.float32,
62
+ device=device,
63
+ )
64
+ with torch.no_grad():
65
+ if hasattr(self.model, "encode_dist"):
66
+ mu, _ = self.model.encode_dist(obs_t)
67
+ z_obj = mu.mean(dim=0)
68
+ else:
69
+ z_obj = self.model.encode(obs_t).mean(dim=0)
70
+ batch_zs.append(z_obj.cpu().numpy())
71
+ zs.append(np.stack(batch_zs, axis=0))
72
+
73
+ return np.concatenate(zs, axis=0)
74
+
75
+ # ------------------------------------------------------------------
76
+ # Single property validation
77
+ # ------------------------------------------------------------------
78
+
79
+ def test_property(
80
+ self,
81
+ objects: list,
82
+ property_name: str = "mass",
83
+ batch_size: int = 64,
84
+ n_perm: int = 5000,
85
+ ) -> dict[str, Any]:
86
+ """
87
+ Test association between latent variables and a physical property.
88
+
89
+ Returns
90
+ -------
91
+ dict containing:
92
+ - best_dim: index of most correlated latent dimension
93
+ - best_corr: Pearson r
94
+ - best_abs_corr: |r|
95
+ - best_pvalue: two-tailed p
96
+ - permutation_pvalue: permutation test p
97
+ - best_mi: mutual information
98
+ - is_causal: passes causal threshold (|r| > 0.5 and perm_p < 0.05)
99
+ - all_corrs: list of correlations for all dimensions
100
+ """
101
+ zs = self.encode_objects(objects, batch_size=batch_size)
102
+ properties = np.array(
103
+ [getattr(obj, property_name) for obj in objects], dtype=np.float64
104
+ )
105
+
106
+ n_dims = zs.shape[1]
107
+ correlations = []
108
+ for i in range(n_dims):
109
+ r, p = stats.pearsonr(zs[:, i], properties)
110
+ correlations.append((i, float(r), float(abs(r)), float(p)))
111
+
112
+ # Sort by |r|
113
+ correlations.sort(key=lambda x: x[2], reverse=True)
114
+ best = correlations[0]
115
+
116
+ perm_p = self._permutation_test(zs[:, best[0]], properties, n_perm=n_perm)
117
+
118
+ n_neighbors = min(3, max(1, len(objects) - 1))
119
+ mi = mutual_info_regression(
120
+ zs[:, best[0] : best[0] + 1], properties, n_neighbors=n_neighbors
121
+ )[0]
122
+
123
+ return {
124
+ "property": property_name,
125
+ "best_dim": best[0],
126
+ "best_corr": best[1],
127
+ "best_abs_corr": best[2],
128
+ "best_pvalue": best[3],
129
+ "permutation_pvalue": perm_p,
130
+ "best_mi": float(mi),
131
+ "is_causal": best[2] > 0.5 and perm_p < 0.05,
132
+ "n_objects": len(objects),
133
+ "n_dims": n_dims,
134
+ "all_corrs": correlations,
135
+ }
136
+
137
+ def test_multiple_properties(
138
+ self,
139
+ objects: list,
140
+ properties: list[str],
141
+ batch_size: int = 64,
142
+ n_perm: int = 5000,
143
+ ) -> dict[str, Any]:
144
+ """Validate multiple physical properties in batch."""
145
+ results = {}
146
+ for prop in properties:
147
+ results[prop] = self.test_property(
148
+ objects, prop, batch_size, n_perm
149
+ )
150
+ return {"results": results, "properties": properties}
151
+
152
+ # ------------------------------------------------------------------
153
+ # Do-operator intervention
154
+ # ------------------------------------------------------------------
155
+
156
+ def intervene(
157
+ self,
158
+ obj,
159
+ latent_idx: int,
160
+ value: float,
161
+ action: list[float],
162
+ ) -> dict[str, Any]:
163
+ """
164
+ Perform do-operator intervention on latent variable.
165
+
166
+ Parameters
167
+ ----------
168
+ obj : ObjectState
169
+ Target object.
170
+ latent_idx : int
171
+ Latent dimension index to intervene on (note: not latent_dim size).
172
+ value : float
173
+ Intervention value.
174
+ action : list[float]
175
+ Action vector.
176
+
177
+ Returns
178
+ -------
179
+ dict containing predicted displacement, original/intervened latents, etc.
180
+ """
181
+ device = next(self.model.parameters()).device
182
+
183
+ with torch.no_grad():
184
+ obs = self.env._get_observation(obj)
185
+ obs_t = torch.tensor(
186
+ obs["visual"], dtype=torch.float32, device=device
187
+ ).unsqueeze(0)
188
+ z_orig = self.model.encode(obs_t).squeeze(0).cpu().numpy()
189
+
190
+ z_int = z_orig.copy()
191
+ z_int[latent_idx] = float(value)
192
+
193
+ with torch.no_grad():
194
+ z_t = torch.tensor(
195
+ z_int, dtype=torch.float32, device=device
196
+ ).unsqueeze(0)
197
+ a_t = torch.tensor(
198
+ action, dtype=torch.float32, device=device
199
+ ).unsqueeze(0)
200
+ pred_obs = self.model.decode(z_t, a_t)
201
+
202
+ pred_disp = float(
203
+ torch.norm(
204
+ pred_obs.squeeze()[:3]
205
+ - torch.tensor(obj.position, device=device)
206
+ ).item()
207
+ )
208
+
209
+ return {
210
+ "predicted_displacement": pred_disp,
211
+ "z_orig": z_orig,
212
+ "z_intervened": z_int,
213
+ "intervention_dim": latent_idx,
214
+ "intervention_value": value,
215
+ }
216
+
217
+ def sweep_interventions(
218
+ self, obj, action: list[float], values: list[float] | None = None
219
+ ) -> list[dict]:
220
+ """
221
+ Sweep interventions across all latent dimensions.
222
+
223
+ Parameters
224
+ ----------
225
+ values : list[float], optional
226
+ Intervention value for each dimension. Defaults to 2.0 for all.
227
+ """
228
+ n_dims = self.model.latent_dim
229
+ if values is None:
230
+ values = [2.0] * n_dims
231
+ return [
232
+ self.intervene(obj, d, values[d], action) for d in range(n_dims)
233
+ ]
234
+
235
+ # ------------------------------------------------------------------
236
+ # Sensitivity analysis
237
+ # ------------------------------------------------------------------
238
+
239
+ def sensitivity_analysis(self, obj) -> np.ndarray:
240
+ """
241
+ Compute gradient sensitivity of each latent dimension to predicted displacement.
242
+
243
+ Returns
244
+ -------
245
+ grads : np.ndarray
246
+ shape (latent_dim,), mean absolute gradient per dimension.
247
+ """
248
+ device = next(self.model.parameters()).device
249
+ obs = self.env._get_observation(obj)
250
+ obs_t = torch.tensor(
251
+ obs["visual"], dtype=torch.float32, device=device
252
+ ).unsqueeze(0)
253
+
254
+ with torch.no_grad():
255
+ z = self.model.encode(obs_t).clone().detach().requires_grad_(True)
256
+
257
+ action = torch.zeros(1, self.model.action_dim, device=device)
258
+ pred = self.model.decode(z, action)
259
+ loss = torch.norm(pred[:, :3] - obs_t[:, :3])
260
+ loss.backward()
261
+
262
+ return z.grad.abs().squeeze(0).cpu().numpy()
263
+
264
+ # ------------------------------------------------------------------
265
+ # DCS (Disentanglement Completeness Score)
266
+ # ------------------------------------------------------------------
267
+
268
+ def compute_dcs(
269
+ self, objects: list, property_name: str = "mass"
270
+ ) -> float:
271
+ """
272
+ Compute Disentanglement Completeness Score (DCS).
273
+
274
+ DCS = 1 - (second_best_correlation / best_correlation)
275
+
276
+ Value closer to 1 means the property is more concentrated in a single latent dimension.
277
+ This is a lightweight custom proxy for disentanglement (not the literature-standard DCI).
278
+ """
279
+ result = self.test_property(objects, property_name)
280
+ all_corrs = result["all_corrs"]
281
+ if len(all_corrs) < 2:
282
+ return 0.0
283
+
284
+ # Top two by |r|
285
+ best_abs = all_corrs[0][2]
286
+ second_abs = all_corrs[1][2]
287
+
288
+ if best_abs < 1e-8:
289
+ return 0.0
290
+
291
+ dcs = 1.0 - (second_abs / best_abs)
292
+ return float(max(0.0, dcs))
293
+
294
+ # ------------------------------------------------------------------
295
+ # Internal utilities
296
+ # ------------------------------------------------------------------
297
+
298
+ def _permutation_test(
299
+ self, z_col: np.ndarray, props: np.ndarray, n_perm: int = 5000
300
+ ) -> float:
301
+ """Permutation test for Pearson correlation significance."""
302
+ observed_r = abs(float(np.corrcoef(z_col, props)[0, 1]))
303
+ if np.isnan(observed_r):
304
+ return 1.0
305
+
306
+ rng = np.random.RandomState(42)
307
+ null = np.array(
308
+ [
309
+ abs(
310
+ float(np.corrcoef(z_col, rng.permutation(props))[0, 1])
311
+ )
312
+ for _ in range(n_perm)
313
+ ]
314
+ )
315
+ return float(max((null >= observed_r).mean(), 1.0 / n_perm))
@@ -0,0 +1,4 @@
1
+ from .pendulum import PendulumEnv, PendulumState
2
+ from .simple_push import ObjectState, SimplePushEnv
3
+
4
+ __all__ = ["ObjectState", "PendulumEnv", "PendulumState", "SimplePushEnv"]