zack4417 commited on
Commit
6fdb8eb
·
verified ·
1 Parent(s): 7d7201e

Add Relay-Kitchen FDK student + DDPM/EDM teachers (fw0 step80k + comparison archive) (part 4)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/qgpo_classifier.py +77 -0
  2. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/rew_classifiers.py +29 -0
  3. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/base_dataset.py +41 -0
  4. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/block_push_dataset.py +76 -0
  5. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_antmaze_dataset.py +563 -0
  6. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_kitchen_dataset.py +444 -0
  7. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_maze2d_dataset.py +290 -0
  8. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_mujoco_dataset.py +453 -0
  9. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/dataset_utils.py +409 -0
  10. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/imagecodecs.py +1385 -0
  11. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/kitchen_dataset.py +247 -0
  12. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/pusht_dataset.py +233 -0
  13. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/replay_buffer.py +592 -0
  14. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_dataset.py +506 -0
  15. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_rl_dataset.py +450 -0
  16. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/rotation_conversions.py +597 -0
  17. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/__init__.py +5 -0
  18. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/basic.py +106 -0
  19. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/consistency_model.py +428 -0
  20. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddim.py +190 -0
  21. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddpm.py +375 -0
  22. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/diffusionsde.py +987 -0
  23. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver.py +889 -0
  24. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver_discrete.py +598 -0
  25. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edm.py +426 -0
  26. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edmddim.py +84 -0
  27. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/koopman_net.py +427 -0
  28. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/newedm.py +438 -0
  29. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/rectifiedflow.py +632 -0
  30. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/veode.py +73 -0
  31. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpode.py +78 -0
  32. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpsde.py +1099 -0
  33. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/async_vector_env.py +671 -0
  34. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/__init__.py +7 -0
  35. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing.py +1092 -0
  36. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_discontinuous.py +338 -0
  37. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_multimodal.py +802 -0
  38. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/discontinuous_push_oracle.py +70 -0
  39. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/multimodal_push_oracle.py +187 -0
  40. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/oriented_push_oracle.py +258 -0
  41. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/pushing_info.py +35 -0
  42. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/reach_oracle.py +61 -0
  43. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/pose3d.py +70 -0
  44. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/utils_pybullet.py +451 -0
  45. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/xarm_sim_robot.py +230 -0
  46. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/__init__.py +30 -0
  47. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/base.py +156 -0
  48. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_lowdim_wrapper.py +56 -0
  49. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_util.py +51 -0
  50. checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/relay_policy_learning/adept_envs/adept_envs/__init__.py +19 -0
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/qgpo_classifier.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from .base import BaseClassifier
7
+
8
+
9
+ class QGPOClassifier(BaseClassifier):
10
+ """
11
+ QGPOClassifier defines logp(y | x, t) using negative MSE.
12
+ Assuming nn_classifier is a NN used to predict y through x and t, i.e, pred_y = nn_classifier(x, t),
13
+ logp is defined as - temperature * MSE(nn_classifier(x, t), y).
14
+ """
15
+
16
+ def loss(self, x: torch.Tensor, t: torch.Tensor, y: Dict[str, torch.Tensor]):
17
+ """ In-support Contrastive Energy Prediction Loss in https://arxiv.org/pdf/2304.12824
18
+
19
+ Args:
20
+ x: torch.Tensor,
21
+ Noisy support actions. Shape: (batch_size, K, act_dim)
22
+ t: torch.Tensor,
23
+ Diffusion time steps. Shape: (batch_size, )
24
+ y: Dict[str, torch.Tensor],
25
+ {
26
+ "soft_label": torch.Tensor,
27
+ Softmax of Q values. Shape: (batch_size, K, 1)
28
+ "obs": torch.Tensor,
29
+ Observations. Shape: (batch_size, obs_dim)}
30
+
31
+ Returns:
32
+ loss: torch.Tensor
33
+ """
34
+ b, k = x.shape[:2]
35
+
36
+ soft_label, obs = y["soft_label"], y["obs"]
37
+
38
+ f = self.model(x, t.unsqueeze(1).repeat(1, k), obs.unsqueeze(1).repeat(1, k, 1))
39
+
40
+ loss = - (soft_label * F.log_softmax(f, 1)).sum(1).mean()
41
+
42
+ with torch.no_grad():
43
+ f_max = f.max(1)[0].mean().item()
44
+ f_mean = f.mean().item()
45
+ f_min = f.min(1)[0].mean().item()
46
+
47
+ return loss, {"f_max": f_max, "f_mean": f_mean, "f_min": f_min}
48
+
49
+ def update(self, x: torch.Tensor, noise: torch.Tensor, y: Dict[str, torch.Tensor], update_ema: bool = True):
50
+ loss, log = self.loss(x, noise, y)
51
+ self.optim.zero_grad()
52
+ loss.backward()
53
+ if isinstance(self.grad_clip_norm, float):
54
+ grad_norm = torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm).item()
55
+ else:
56
+ grad_norm = None
57
+ self.optim.step()
58
+ if update_ema:
59
+ self.ema_update()
60
+ log.update({"loss": loss.item(), "grad_norm": grad_norm})
61
+ return log
62
+
63
+ def logp(self, x: torch.Tensor, t: torch.Tensor, c: torch.Tensor):
64
+ """
65
+ Args:
66
+ x: torch.Tensor,
67
+ Noisy actions. Shape (batch_size, act_dim)
68
+ t: torch.Tensor,
69
+ Timestep. Shape (batch_size, )
70
+ c: torch.Tensor,
71
+ Observations. Shape (batch_size, obs_dim)
72
+
73
+ Returns:
74
+ f: torch.Tensor,
75
+ Energy prediction. Shape (batch_size, 1)
76
+ """
77
+ return self.model_ema(x, t, c)
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/classifier/rew_classifiers.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from cleandiffuser.nn_classifier import BaseNNClassifier
4
+ from .base import BaseClassifier
5
+
6
+
7
+ class CumRewClassifier(BaseClassifier):
8
+ def __init__(
9
+ self,
10
+ nn_classifier: BaseNNClassifier,
11
+ device: str = "cpu",
12
+ optim_params: Optional[dict] = None,
13
+ ):
14
+ super().__init__(nn_classifier, 0.995, None, optim_params, device)
15
+
16
+ def loss(self, x, noise, R):
17
+ pred_R = self.model(x, noise, None)
18
+ return ((pred_R - R) ** 2).mean()
19
+
20
+ def update(self, x, noise, R):
21
+ self.optim.zero_grad()
22
+ loss = self.loss(x, noise, R)
23
+ loss.backward()
24
+ self.optim.step()
25
+ self.ema_update()
26
+ return {"loss": loss.item()}
27
+
28
+ def logp(self, x, noise, c=None):
29
+ return self.model_ema(x, noise)
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/base_dataset.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import torch.nn
4
+ from torch.utils.data import Dataset
5
+
6
+
7
+ # Adapted from the datasets on: https://github.com/real-stanford/diffusion_policy
8
+
9
+ # Observation Horizon: To|n_obs_steps
10
+ # Action Horizon: Ta|n_action_steps
11
+ # Prediction Horizon: T|horizon
12
+ # To = 3
13
+ # Ta = 4
14
+ # T = 6
15
+ # |o|o|o|
16
+ # | | |a|a|a|a|
17
+ # To = 2
18
+ # Ta = 5
19
+ # T = 6
20
+ # |o|o|
21
+ # | |a|a|a|a|a|
22
+
23
+
24
+ class BaseDataset(Dataset):
25
+ def get_normalizer(self, **kwargs):
26
+ raise NotImplementedError()
27
+
28
+ def __len__(self) -> int:
29
+ return 0
30
+
31
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
32
+ """
33
+ output:
34
+ obs:
35
+ key1: T, Do1 # default key: state
36
+ key2: T, Do2
37
+ action: T, Da
38
+ reward: T, 1
39
+ info:
40
+ """
41
+ raise NotImplementedError()
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/block_push_dataset.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+ import torch
3
+ import numpy as np
4
+ import copy
5
+ import pathlib
6
+ from cleandiffuser.dataset.base_dataset import BaseDataset
7
+ from cleandiffuser.dataset.replay_buffer import ReplayBuffer
8
+ from cleandiffuser.dataset.dataset_utils import SequenceSampler, MinMaxNormalizer, EmptyNormalizer, dict_apply
9
+
10
+ # dev/block_pushing/multimodal_push_seed.zarr
11
+ # ├── data
12
+ # │ ├── action (114962, 2) float32
13
+ # │ └── obs (114962, 16) float32
14
+ # └── meta
15
+ # └── episode_ends (1000,) int64
16
+
17
+ class BlockPushDataset(BaseDataset):
18
+ def __init__(self,
19
+ zarr_path,
20
+ horizon=1,
21
+ pad_before=0,
22
+ pad_after=0,
23
+ obs_keys=['obs', 'action']
24
+ ):
25
+ super().__init__()
26
+ self.replay_buffer = ReplayBuffer.copy_from_path(
27
+ zarr_path, keys=obs_keys)
28
+
29
+ self.sampler = SequenceSampler(
30
+ replay_buffer=self.replay_buffer,
31
+ sequence_length=horizon,
32
+ pad_before=pad_before,
33
+ pad_after=pad_after)
34
+
35
+ self.horizon = horizon
36
+ self.pad_before = pad_before
37
+ self.pad_after = pad_after
38
+
39
+ self.normalizer = self.get_normalizer()
40
+
41
+ def get_normalizer(self):
42
+ state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
43
+ action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
44
+
45
+ return {
46
+ "obs": {
47
+ "state": state_normalizer
48
+ },
49
+ "action": action_normalizer
50
+ }
51
+
52
+ def __str__(self) -> str:
53
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
54
+
55
+ def __len__(self) -> int:
56
+ return len(self.sampler)
57
+
58
+ def _sample_to_data(self, sample):
59
+ state = sample['obs'].astype(np.float32) # (T, 5)
60
+ state = self.normalizer['obs']['state'].normalize(state)
61
+
62
+ action = sample['action'].astype(np.float32) # (T, 2)
63
+ action = self.normalizer['action'].normalize(action)
64
+ data = {
65
+ 'obs': {
66
+ 'state': state, # T, D_o
67
+ },
68
+ 'action': action, # T, D_a
69
+ }
70
+ return data
71
+
72
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
73
+ sample = self.sampler.sample_sequence(idx)
74
+ data = self._sample_to_data(sample)
75
+ torch_data = dict_apply(data, torch.tensor)
76
+ return torch_data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_antmaze_dataset.py ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+ from cleandiffuser.dataset.base_dataset import BaseDataset
7
+ from cleandiffuser.utils import GaussianNormalizer, dict_apply
8
+
9
+
10
+ class D4RLAntmazeDataset(BaseDataset):
11
+ """ **D4RL-Antmaze Sequential Dataset**
12
+
13
+ torch.utils.data.Dataset wrapper for D4RL-Antmaze dataset.
14
+ Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-zero padding.
15
+ Use GaussianNormalizer to normalize the observations as default.
16
+ Each batch contains
17
+ - batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
18
+ - batch["act"], actions of shape (batch_size, horizon, a_dim)
19
+ - batch["rew"], rewards of shape (batch_size, horizon, 1)
20
+ - batch["val"], Monte Carlo return of shape (batch_size, 1)
21
+
22
+ Args:
23
+ dataset: Dict[str, np.ndarray],
24
+ D4RL-Antmaze dataset. Obtained by calling `env.get_dataset()`.
25
+ horizon: int,
26
+ Length of each sequence. Default is 1.
27
+ max_path_length: int,
28
+ Maximum length of the episodes. Default is 1001.
29
+ noreaching_penalty: float,
30
+ Penalty for not reaching the goal. Default is -100.
31
+ discount: float,
32
+ Discount factor. Default is 0.99.
33
+
34
+ Examples:
35
+ >>> env = gym.make("antmaze-medium-play-v2")
36
+ >>> dataset = D4RLAntmazeDataset(env.get_dataset(), horizon=32)
37
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
38
+ >>> batch = next(iter(dataloader))
39
+ >>> obs = batch["obs"]["state"] # (32, 32, 29)
40
+ >>> act = batch["act"] # (32, 32, 8)
41
+ >>> rew = batch["rew"] # (32, 32, 1)
42
+ >>> val = batch["val"] # (32, 1)
43
+
44
+ >>> normalizer = dataset.get_normalizer()
45
+ >>> obs = env.reset()[None, :]
46
+ >>> normed_obs = normalizer.normalize(obs)
47
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
48
+ """
49
+ def __init__(
50
+ self,
51
+ dataset: Dict[str, np.ndarray],
52
+ horizon: int = 1,
53
+ max_path_length: int = 1001,
54
+ noreaching_penalty: float = -100.,
55
+ discount: float = 0.99,
56
+ ):
57
+ super().__init__()
58
+
59
+ observations, actions, rewards, timeouts, terminals = (
60
+ dataset["observations"].astype(np.float32),
61
+ dataset["actions"].astype(np.float32),
62
+ dataset["rewards"].astype(np.float32),
63
+ dataset["timeouts"],
64
+ dataset["terminals"])
65
+ rewards -= 1 # -1 for each step and 0 for reaching the goal
66
+ dones = np.logical_or(timeouts, terminals)
67
+ self.normalizers = {
68
+ "state": GaussianNormalizer(observations)}
69
+ normed_observations = self.normalizers["state"].normalize(observations)
70
+
71
+ self.horizon = horizon
72
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
73
+
74
+ self.indices = []
75
+ self.seq_obs, self.seq_act, self.seq_rew = [], [], []
76
+ self.tml_and_not_timeout = []
77
+
78
+ self.path_lengths, ptr = [], 0
79
+ path_idx = 0
80
+ for i in range(timeouts.shape[0]):
81
+
82
+ if i != 0 and ((dones[i - 1] and not dones[i]) or timeouts[i - 1]):
83
+
84
+ path_length = i - ptr
85
+ self.path_lengths.append(path_length)
86
+
87
+ if terminals[i] and not timeouts[i]:
88
+ self.tml_and_not_timeout.append([path_idx, i - ptr])
89
+
90
+ # 1. agent walks out of the goal
91
+ if path_length < max_path_length:
92
+
93
+ _seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
94
+ _seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
95
+ _seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
96
+
97
+ _seq_obs[:i - ptr] = normed_observations[ptr:i]
98
+ _seq_act[:i - ptr] = actions[ptr:i]
99
+ _seq_rew[:i - ptr] = rewards[ptr:i][:, None]
100
+
101
+ # repeat padding
102
+ _seq_obs[i - ptr:] = normed_observations[i] # repeat last state
103
+ _seq_act[i - ptr:] = 0 # repeat zero action
104
+ _seq_rew[i - ptr:] = 0 # repeat zero reward
105
+
106
+ self.seq_obs.append(_seq_obs)
107
+ self.seq_act.append(_seq_act)
108
+ self.seq_rew.append(_seq_rew)
109
+
110
+ # 2. agent never reaches the goal during the episode
111
+ elif path_length == max_path_length:
112
+
113
+ self.seq_obs.append(normed_observations[ptr:i])
114
+ self.seq_act.append(actions[ptr:i])
115
+ self.seq_rew.append(rewards[ptr:i][:, None])
116
+
117
+ # panelty for not reaching the goal
118
+ self.seq_rew[-1][-1] = noreaching_penalty
119
+
120
+ else:
121
+ raise ValueError(f"path_length: {path_length} > max_path_length: {max_path_length}")
122
+
123
+ max_start = min(self.path_lengths[-1] - 1, max_path_length - horizon)
124
+ self.indices += [(path_idx, start, start + horizon) for start in range(max_start + 1)]
125
+
126
+ ptr = i
127
+ path_idx += 1
128
+
129
+ self.seq_obs = np.array(self.seq_obs)
130
+ self.seq_act = np.array(self.seq_act)
131
+ self.seq_rew = np.array(self.seq_rew)
132
+
133
+ self.seq_val = np.copy(self.seq_rew)
134
+ for i in range(max_path_length - 1):
135
+ self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
136
+ self.tml_and_not_timeout = np.array(self.tml_and_not_timeout, dtype=np.int64)
137
+
138
+ def get_normalizer(self):
139
+ return self.normalizers["state"]
140
+
141
+ def __len__(self):
142
+ return len(self.indices)
143
+
144
+ def __getitem__(self, idx: int):
145
+ path_idx, start, end = self.indices[idx]
146
+
147
+ data = {
148
+ 'obs': {
149
+ 'state': self.seq_obs[path_idx, start:end]},
150
+ 'act': self.seq_act[path_idx, start:end],
151
+ 'rew': self.seq_rew[path_idx, start:end],
152
+ 'val': self.seq_val[path_idx, start],
153
+ }
154
+
155
+ torch_data = dict_apply(data, torch.tensor)
156
+
157
+ return torch_data
158
+
159
+
160
+ class D4RLAntmazeTDDataset(BaseDataset):
161
+ """ **D4RL-Antmaze Transition Dataset**
162
+
163
+ torch.utils.data.Dataset wrapper for D4RL-Antmaze dataset.
164
+ Chunk the dataset into transitions.
165
+ Use GaussianNormalizer to normalize the observations as default.
166
+ Each batch contains
167
+ - batch["obs"]["state"], observation of shape (batch_size, o_dim)
168
+ - batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
169
+ - batch["act"], action of shape (batch_size, a_dim)
170
+ - batch["rew"], reward of shape (batch_size, 1)
171
+ - batch["tml"], terminal of shape (batch_size, 1)
172
+
173
+ Args:
174
+ dataset: Dict[str, np.ndarray],
175
+ D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
176
+ reward_tune: str,
177
+ Reward tuning method. Can be "iql", "cql", "antmaze", or "none". Default is "iql".
178
+
179
+ Examples:
180
+ >>> env = gym.make("antmaze-medium-play-v2")
181
+ >>> dataset = D4RLAntmazeTDDataset(d4rl.qlearning_dataset(env))
182
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
183
+ >>> batch = next(iter(dataloader))
184
+ >>> obs = batch["obs"]["state"] # (32, 29)
185
+ >>> act = batch["act"] # (32, 8)
186
+ >>> rew = batch["rew"] # (32, 1)
187
+ >>> tml = batch["tml"] # (32, 1)
188
+ >>> next_obs = batch["next_obs"]["state"] # (32, 29)
189
+
190
+ >>> normalizer = dataset.get_normalizer()
191
+ >>> obs = env.reset()[None, :]
192
+ >>> normed_obs = normalizer.normalize(obs)
193
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
194
+ """
195
+ def __init__(self, dataset: Dict[str, np.ndarray], reward_tune: str = "iql"):
196
+ super().__init__()
197
+
198
+ observations, actions, next_observations, rewards, terminals = (
199
+ dataset["observations"].astype(np.float32),
200
+ dataset["actions"].astype(np.float32),
201
+ dataset["next_observations"].astype(np.float32),
202
+ dataset["rewards"].astype(np.float32),
203
+ dataset["terminals"].astype(np.float32))
204
+
205
+ if reward_tune == "iql":
206
+ rewards = rewards - 1.
207
+ elif reward_tune == "cql":
208
+ rewards = (rewards - 0.5) * 4.
209
+ elif reward_tune == "antmaze":
210
+ rewards = (rewards - 0.25) * 2.
211
+ elif reward_tune == "none":
212
+ rewards = rewards
213
+ else:
214
+ raise ValueError(f"reward_tune: {reward_tune} is not supported.")
215
+
216
+ self.normalizers = {
217
+ "state": GaussianNormalizer(observations)}
218
+ normed_observations = self.normalizers["state"].normalize(observations)
219
+ normed_next_observations = self.normalizers["state"].normalize(next_observations)
220
+
221
+ self.obs = torch.tensor(normed_observations)
222
+ self.act = torch.tensor(actions)
223
+ self.rew = torch.tensor(rewards)[:, None]
224
+ self.tml = torch.tensor(terminals)[:, None]
225
+ self.next_obs = torch.tensor(normed_next_observations)
226
+
227
+ self.size = self.obs.shape[0]
228
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
229
+
230
+ def get_normalizer(self):
231
+ return self.normalizers["state"]
232
+
233
+ def __len__(self):
234
+ return self.size
235
+
236
+ def __getitem__(self, idx: int):
237
+
238
+ data = {
239
+ 'obs': {
240
+ 'state': self.obs[idx], },
241
+ 'next_obs': {
242
+ 'state': self.next_obs[idx], },
243
+ 'act': self.act[idx],
244
+ 'rew': self.rew[idx],
245
+ 'tml': self.tml[idx], }
246
+
247
+ return data
248
+
249
+
250
+ class MultiHorizonD4RLAntmazeDataset(BaseDataset):
251
+ def __init__(
252
+ self,
253
+ dataset,
254
+ horizons=(10, 20),
255
+ max_path_length=1001,
256
+ noreaching_penalty=-100,
257
+ discount=0.99,
258
+ ):
259
+ super().__init__()
260
+
261
+ observations, actions, rewards, timeouts, terminals = (
262
+ dataset["observations"].astype(np.float32),
263
+ dataset["actions"].astype(np.float32),
264
+ dataset["rewards"].astype(np.float32),
265
+ dataset["timeouts"],
266
+ dataset["terminals"])
267
+ rewards -= 1
268
+ dones = np.logical_or(timeouts, terminals)
269
+ self.normalizers = {
270
+ "state": GaussianNormalizer(observations)}
271
+ normed_observations = self.normalizers["state"].normalize(observations)
272
+
273
+ self.horizons = horizons
274
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
275
+ self.discount = discount ** np.arange(max_path_length, dtype=np.float32)
276
+
277
+ self.indices = [[] for _ in range(len(horizons))]
278
+ self.seq_obs, self.seq_act, self.seq_rew = [], [], []
279
+
280
+ self.path_lengths, ptr = [], 0
281
+ path_idx = 0
282
+ for i in range(timeouts.shape[0]):
283
+
284
+ if i != 0 and ((dones[i - 1] and not dones[i]) or timeouts[i - 1]):
285
+
286
+ path_length = i - ptr
287
+ self.path_lengths.append(path_length)
288
+
289
+ # 1. agent walks out of the goal
290
+ if path_length < max_path_length:
291
+
292
+ _seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
293
+ _seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
294
+ _seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
295
+
296
+ _seq_obs[:i - ptr] = normed_observations[ptr:i]
297
+ _seq_act[:i - ptr] = actions[ptr:i]
298
+ _seq_rew[:i - ptr] = rewards[ptr:i][:, None]
299
+
300
+ # repeat padding
301
+ _seq_obs[i - ptr:] = normed_observations[i] # repeat last state
302
+ _seq_act[i - ptr:] = 0 # repeat zero action
303
+ _seq_rew[i - ptr:] = 0 # repeat zero reward
304
+
305
+ self.seq_obs.append(_seq_obs)
306
+ self.seq_act.append(_seq_act)
307
+ self.seq_rew.append(_seq_rew)
308
+
309
+ # 2. agent never reaches the goal during the episode
310
+ elif path_length == max_path_length:
311
+
312
+ self.seq_obs.append(normed_observations[ptr:i])
313
+ self.seq_act.append(actions[ptr:i])
314
+ self.seq_rew.append(rewards[ptr:i][:, None])
315
+
316
+ # panelty for not reaching the goal
317
+ self.seq_rew[-1][-1] = noreaching_penalty
318
+
319
+ else:
320
+ raise ValueError(f"path_length: {path_length} > max_path_length: {max_path_length}")
321
+
322
+ max_starts = [min(self.path_lengths[-1] - 1, max_path_length - horizon) for horizon in horizons]
323
+ for k in range(len(horizons)):
324
+ self.indices[k] += [(path_idx, start, start + horizons[k]) for start in range(max_starts[k] + 1)]
325
+
326
+ ptr = i
327
+ path_idx += 1
328
+
329
+ self.seq_obs = np.array(self.seq_obs)
330
+ self.seq_act = np.array(self.seq_act)
331
+ self.seq_rew = np.array(self.seq_rew)
332
+
333
+ self.len_each_horizon = [len(indices) for indices in self.indices]
334
+
335
+ def get_normalizer(self):
336
+ return self.normalizers["state"]
337
+
338
+ def __len__(self):
339
+ return max(self.len_each_horizon)
340
+
341
+ def __getitem__(self, idx: int):
342
+
343
+ indices = [
344
+ int(self.len_each_horizon[i] * (idx / self.len_each_horizon[-1])) for i in range(len(self.horizons))]
345
+
346
+ torch_datas = []
347
+
348
+ for i, horizon in enumerate(self.horizons):
349
+
350
+ path_idx, start, end = self.indices[i][indices[i]]
351
+
352
+ rewards = self.seq_rew[path_idx, start:]
353
+ values = (rewards * self.discount[:rewards.shape[0], None]).sum(0)
354
+
355
+ data = {
356
+ 'obs': {
357
+ 'state': self.seq_obs[path_idx, start:end]},
358
+ 'act': self.seq_act[path_idx, start:end],
359
+ 'rew': self.seq_rew[path_idx, start:end],
360
+ 'val': values}
361
+
362
+ torch_data = dict_apply(data, torch.tensor)
363
+
364
+ torch_datas.append({
365
+ "horizon": horizon,
366
+ "data": torch_data,
367
+ })
368
+
369
+ return torch_datas
370
+
371
+ class DV_D4RLAntmazeSeqDataset(BaseDataset):
372
+ """ **D4RL-Antmaze Sequential Dataset**
373
+
374
+ torch.utils.data.Dataset wrapper for D4RL-Antmaze dataset.
375
+ Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-zero padding.
376
+ Use GaussianNormalizer to normalize the observations as default.
377
+ Each batch contains
378
+ - batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
379
+ - batch["act"], actions of shape (batch_size, horizon, a_dim)
380
+ - batch["rew"], rewards of shape (batch_size, horizon, 1)
381
+ - batch["val"], Monte Carlo return of shape (batch_size, 1)
382
+
383
+ Args:
384
+ dataset: Dict[str, np.ndarray],
385
+ D4RL-Antmaze dataset. Obtained by calling `env.get_dataset()`.
386
+ horizon: int,
387
+ Length of each sequence. Default is 1.
388
+ max_path_length: int,
389
+ Maximum length of the episodes. Default is 1001.
390
+ noreaching_penalty: float,
391
+ Penalty for not reaching the goal. Default is -100.
392
+ discount: float,
393
+ Discount factor. Default is 0.99.
394
+
395
+ Examples:
396
+ >>> env = gym.make("antmaze-medium-play-v2")
397
+ >>> dataset = D4RLAntmazeDataset(env.get_dataset(), horizon=32)
398
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
399
+ >>> batch = next(iter(dataloader))
400
+ >>> obs = batch["obs"]["state"] # (32, 32, 29)
401
+ >>> act = batch["act"] # (32, 32, 8)
402
+ >>> rew = batch["rew"] # (32, 32, 1)
403
+ >>> val = batch["val"] # (32, 1)
404
+
405
+ >>> normalizer = dataset.get_normalizer()
406
+ >>> obs = env.reset()[None, :]
407
+ >>> normed_obs = normalizer.normalize(obs)
408
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
409
+ """
410
+ def __init__(
411
+ self,
412
+ dataset: Dict[str, np.ndarray],
413
+ horizon: int = 1,
414
+ max_path_length: int = 1001,
415
+ discount: float = 0.99,
416
+ continous_reward_at_done: bool = False,
417
+ reward_tune: str = "iql",
418
+ center_mapping: bool = True,
419
+ learn_policy: bool = False,
420
+ stride: int = 1,
421
+ only_learn_reached_policy: bool = False,
422
+ ):
423
+ super().__init__()
424
+
425
+ observations, actions, rewards, timeouts, terminals = (
426
+ dataset["observations"].astype(np.float32),
427
+ dataset["actions"].astype(np.float32),
428
+ dataset["rewards"].astype(np.float32),
429
+ dataset["timeouts"].astype(np.float32),
430
+ dataset["terminals"].astype(np.float32))
431
+
432
+ self.learn_policy = learn_policy
433
+ self.stride = stride
434
+
435
+ self.normalizers = {
436
+ "state": GaussianNormalizer(observations)}
437
+ normed_observations = self.normalizers["state"].normalize(observations)
438
+
439
+ self.horizon = horizon
440
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
441
+
442
+ self.indices = []
443
+ self.seq_obs, self.seq_act, self.seq_rew, self.seq_tml = [], [], [], []
444
+
445
+ ptr = 0 # ptr: t=0 pos
446
+ path_idx = 0
447
+
448
+ timeouts_indices = np.where(timeouts == 1)[0]
449
+
450
+ for index in timeouts_indices:
451
+ terminal_index = -1
452
+ assert index - ptr + 1 == max_path_length
453
+ for i in range(ptr, index + 1):
454
+ if terminals[i]:
455
+ terminal_index = i
456
+ break
457
+
458
+ if terminal_index == -1 and self.learn_policy and not only_learn_reached_policy:
459
+ path_length = index - ptr + 1
460
+ assert path_length == max_path_length
461
+
462
+ _seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
463
+ _seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
464
+ _seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
465
+ _seq_tml = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
466
+
467
+ _seq_obs[:path_length] = normed_observations[ptr:index+1]
468
+ _seq_act[:path_length] = actions[ptr:index+1]
469
+ _seq_rew[:path_length] = rewards[ptr:index+1][:, None]
470
+ _seq_tml[:path_length] = terminals[ptr:index+1][:, None]
471
+
472
+ self.seq_obs.append(_seq_obs)
473
+ self.seq_act.append(_seq_act)
474
+ self.seq_rew.append(_seq_rew)
475
+ self.seq_tml.append(_seq_tml)
476
+
477
+ max_start = max_path_length - (horizon - 1) * stride - 1
478
+ self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
479
+ path_idx += 1
480
+
481
+ elif terminal_index != -1:
482
+ path_length = terminal_index - ptr + 1
483
+ assert path_length <= max_path_length
484
+ assert rewards[terminal_index] == 1
485
+
486
+ _seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
487
+ _seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
488
+ _seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
489
+ _seq_tml = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
490
+
491
+ _seq_obs[:path_length] = normed_observations[ptr:terminal_index+1]
492
+ _seq_act[:path_length] = actions[ptr:terminal_index+1]
493
+ _seq_rew[:path_length] = rewards[ptr:terminal_index+1][:, None]
494
+ _seq_tml[:path_length] = terminals[ptr:terminal_index+1][:, None]
495
+
496
+ _seq_obs[path_length:] = normed_observations[terminal_index] # repeat state
497
+ _seq_act[path_length:] = 0 # zero action
498
+ _seq_rew[path_length:] = 1 if continous_reward_at_done else 0
499
+ _seq_tml[path_length:] = 1
500
+
501
+ self.seq_obs.append(_seq_obs)
502
+ self.seq_act.append(_seq_act)
503
+ self.seq_rew.append(_seq_rew)
504
+ self.seq_tml.append(_seq_tml)
505
+
506
+ max_start = path_length - 1
507
+ self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
508
+ path_idx += 1
509
+
510
+ ptr = index + 1
511
+
512
+ self.seq_obs = np.array(self.seq_obs)
513
+ self.seq_act = np.array(self.seq_act)
514
+ self.seq_rew = np.array(self.seq_rew)
515
+ self.seq_tml = np.array(self.seq_tml)
516
+
517
+ if reward_tune == "iql":
518
+ self.seq_rew += -1
519
+ elif reward_tune == "none":
520
+ self.seq_rew = self.seq_rew
521
+ else:
522
+ raise ValueError(f"reward_tune: {reward_tune} is not supported.")
523
+
524
+ self.seq_val = np.copy(self.seq_rew)
525
+ for i in reversed(range(max_path_length - 1)):
526
+ self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
527
+
528
+ print(f"max discounted return: {self.seq_val.max()}")
529
+ print(f"min discounted return: {self.seq_val.min()}")
530
+
531
+ # val \in [-1, 1]
532
+ self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
533
+ if center_mapping:
534
+ self.seq_val = self.seq_val * 2 - 1
535
+ print(f"max normed discounted return: {self.seq_val.max()}")
536
+ print(f"min normed discounted return: {self.seq_val.min()}")
537
+
538
+ def get_normalizer(self):
539
+ return self.normalizers["state"]
540
+
541
+ def __len__(self):
542
+ return len(self.indices)
543
+
544
+ def __getitem__(self, idx: int):
545
+ path_idx, start, end = self.indices[idx]
546
+
547
+ if self.learn_policy:
548
+ horizon_state = self.seq_obs[path_idx, start:end:self.stride].copy()
549
+ horizon_state[:, :2] -= horizon_state[0, :2]
550
+ else:
551
+ horizon_state = self.seq_obs[path_idx, start:end:self.stride]
552
+
553
+ data = {
554
+ 'obs': {'state': horizon_state},
555
+ 'act': self.seq_act[path_idx, start:end:self.stride],
556
+ 'rew': self.seq_rew[path_idx, start:end:self.stride],
557
+ 'val': self.seq_val[path_idx, start],
558
+ 'tml': self.seq_tml[path_idx, start],
559
+ }
560
+
561
+ torch_data = dict_apply(data, torch.tensor)
562
+
563
+ return torch_data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_kitchen_dataset.py ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+ from cleandiffuser.dataset.base_dataset import BaseDataset
7
+ from cleandiffuser.utils import GaussianNormalizer, dict_apply
8
+
9
+
10
+ class D4RLKitchenDataset(BaseDataset):
11
+ """ **D4RL-Kitchen Sequential Dataset**
12
+
13
+ torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
14
+ Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-repeat padding.
15
+ Use GaussianNormalizer to normalize the observations as default.
16
+ Each batch contains
17
+ - batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
18
+ - batch["act"], actions of shape (batch_size, horizon, a_dim)
19
+ - batch["rew"], rewards of shape (batch_size, horizon, 1)
20
+ - batch["val"], Monte Carlo return of shape (batch_size, 1)
21
+
22
+ Args:
23
+ dataset: Dict[str, np.ndarray],
24
+ D4RL-Kitchen dataset. Obtained by calling `env.get_dataset()`.
25
+ horizon: int,
26
+ Length of each sequence. Default is 1.
27
+ max_path_length: int,
28
+ Maximum length of the episodes. Default is 280.
29
+ discount: float,
30
+ Discount factor. Default is 0.99.
31
+
32
+ Examples:
33
+ >>> env = gym.make("kitchen-mixed-v0")
34
+ >>> dataset = D4RLKitchenDataset(env.get_dataset(), horizon=32)
35
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
36
+ >>> batch = next(iter(dataloader))
37
+ >>> obs = batch["obs"]["state"] # (32, 32, 60)
38
+ >>> act = batch["act"] # (32, 32, 9)
39
+ >>> rew = batch["rew"] # (32, 32, 1)
40
+ >>> val = batch["val"] # (32, 1)
41
+
42
+ >>> normalizer = dataset.get_normalizer()
43
+ >>> obs = env.reset()[None, :]
44
+ >>> normed_obs = normalizer.normalize(obs)
45
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
46
+ """
47
+ def __init__(
48
+ self,
49
+ dataset: Dict[str, np.ndarray],
50
+ horizon: int = 1,
51
+ max_path_length: int = 280,
52
+ discount: float = 0.99,
53
+ ):
54
+ super().__init__()
55
+
56
+ observations, actions, rewards, timeouts, terminals = (
57
+ dataset["observations"].astype(np.float32),
58
+ dataset["actions"].astype(np.float32),
59
+ dataset["rewards"].astype(np.float32),
60
+ dataset["timeouts"],
61
+ dataset["terminals"])
62
+ self.normalizers = {
63
+ "state": GaussianNormalizer(observations)}
64
+ normed_observations = self.normalizers["state"].normalize(observations)
65
+
66
+ self.horizon = horizon
67
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
68
+
69
+ self.indices = []
70
+ self.seq_obs, self.seq_act, self.seq_rew = [], [], []
71
+ self.tml_and_not_timeout = []
72
+
73
+ self.path_lengths, ptr = [], 0
74
+ path_idx = 0
75
+ for i in range(timeouts.shape[0]):
76
+ if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
77
+ self.path_lengths.append(i - ptr + 1)
78
+
79
+ if terminals[i] and not timeouts[i]:
80
+ self.tml_and_not_timeout.append([path_idx, i - ptr])
81
+
82
+ _seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
83
+ _seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
84
+ _seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
85
+
86
+ _seq_obs[:i - ptr + 1] = normed_observations[ptr:i + 1]
87
+ _seq_act[:i - ptr + 1] = actions[ptr:i + 1]
88
+ _seq_rew[:i - ptr + 1] = rewards[ptr:i + 1][:, None]
89
+
90
+ # repeat padding
91
+ _seq_obs[i - ptr + 1:] = normed_observations[i] # repeat last state
92
+ _seq_act[i - ptr + 1:] = 0 # repeat zero action
93
+ _seq_rew[i - ptr + 1:] = rewards[i] # repeat last reward
94
+
95
+ self.seq_obs.append(_seq_obs)
96
+ self.seq_act.append(_seq_act)
97
+ self.seq_rew.append(_seq_rew)
98
+
99
+ max_start = min(self.path_lengths[-1] - 1, max_path_length - horizon)
100
+ self.indices += [(path_idx, start, start + horizon) for start in range(max_start + 1)]
101
+
102
+ ptr = i + 1
103
+ path_idx += 1
104
+
105
+ self.seq_obs = np.array(self.seq_obs)
106
+ self.seq_act = np.array(self.seq_act)
107
+ self.seq_rew = np.array(self.seq_rew)
108
+
109
+ self.seq_val = np.copy(self.seq_rew)
110
+ for i in range(max_path_length - 1):
111
+ self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
112
+ self.tml_and_not_timeout = np.array(self.tml_and_not_timeout, dtype=np.int64)
113
+
114
+ def get_normalizer(self):
115
+ return self.normalizers["state"]
116
+
117
+ def __len__(self):
118
+ return len(self.indices)
119
+
120
+ def __getitem__(self, idx: int):
121
+ path_idx, start, end = self.indices[idx]
122
+
123
+ data = {
124
+ 'obs': {
125
+ 'state': self.seq_obs[path_idx, start:end]},
126
+ 'act': self.seq_act[path_idx, start:end],
127
+ 'rew': self.seq_rew[path_idx, start:end],
128
+ 'val': self.seq_val[path_idx, start],
129
+ }
130
+
131
+ torch_data = dict_apply(data, torch.tensor)
132
+
133
+ return torch_data
134
+
135
+
136
+ class D4RLKitchenTDDataset(BaseDataset):
137
+ """ **D4RL-Kitchen Transition Dataset**
138
+
139
+ torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
140
+ Chunk the dataset into transitions.
141
+ Use GaussianNormalizer to normalize the observations as default.
142
+ Each batch contains
143
+ - batch["obs"]["state"], observation of shape (batch_size, o_dim)
144
+ - batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
145
+ - batch["act"], action of shape (batch_size, a_dim)
146
+ - batch["rew"], reward of shape (batch_size, 1)
147
+ - batch["tml"], terminal of shape (batch_size, 1)
148
+
149
+ Args:
150
+ dataset: Dict[str, np.ndarray],
151
+ D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
152
+
153
+ Examples:
154
+ >>> env = gym.make("kitchen-mixed-v0")
155
+ >>> dataset = D4RLKitchenTDDataset(d4rl.qlearning_dataset(env))
156
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
157
+ >>> batch = next(iter(dataloader))
158
+ >>> obs = batch["obs"]["state"] # (32, 60)
159
+ >>> act = batch["act"] # (32, 9)
160
+ >>> rew = batch["rew"] # (32, 1)
161
+ >>> tml = batch["tml"] # (32, 1)
162
+ >>> next_obs = batch["next_obs"]["state"] # (32, 60)
163
+
164
+ >>> normalizer = dataset.get_normalizer()
165
+ >>> obs = env.reset()[None, :]
166
+ >>> normed_obs = normalizer.normalize(obs)
167
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
168
+ """
169
+ def __init__(self, dataset: Dict[str, np.ndarray]):
170
+ super().__init__()
171
+
172
+ observations, actions, next_observations, rewards, terminals = (
173
+ dataset["observations"].astype(np.float32),
174
+ dataset["actions"].astype(np.float32),
175
+ dataset["next_observations"].astype(np.float32),
176
+ dataset["rewards"].astype(np.float32),
177
+ dataset["terminals"].astype(np.float32))
178
+
179
+ self.normalizers = {
180
+ "state": GaussianNormalizer(observations)}
181
+ normed_observations = self.normalizers["state"].normalize(observations)
182
+ normed_next_observations = self.normalizers["state"].normalize(next_observations)
183
+
184
+ self.obs = torch.tensor(normed_observations)
185
+ self.act = torch.tensor(actions)
186
+ self.rew = torch.tensor(rewards)[:, None]
187
+ self.tml = torch.tensor(terminals)[:, None]
188
+ self.next_obs = torch.tensor(normed_next_observations)
189
+
190
+ self.size = self.obs.shape[0]
191
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
192
+
193
+ def get_normalizer(self):
194
+ return self.normalizers["state"]
195
+
196
+ def __len__(self):
197
+ return self.size
198
+
199
+ def __getitem__(self, idx: int):
200
+ data = {
201
+ 'obs': {
202
+ 'state': self.obs[idx], },
203
+ 'next_obs': {
204
+ 'state': self.next_obs[idx], },
205
+ 'act': self.act[idx],
206
+ 'rew': self.rew[idx],
207
+ 'tml': self.tml[idx], }
208
+
209
+ return data
210
+
211
+
212
+ class MultiHorizonD4RLKitchenDataset(BaseDataset):
213
+ def __init__(
214
+ self,
215
+ dataset,
216
+ horizons=(10, 20),
217
+ max_path_length=280,
218
+ discount=0.99,
219
+ ):
220
+ super().__init__()
221
+
222
+ observations, actions, rewards, timeouts, terminals = (
223
+ dataset["observations"].astype(np.float32),
224
+ dataset["actions"].astype(np.float32),
225
+ dataset["rewards"].astype(np.float32),
226
+ dataset["timeouts"],
227
+ dataset["terminals"])
228
+ self.normalizers = {
229
+ "state": GaussianNormalizer(observations)}
230
+ normed_observations = self.normalizers["state"].normalize(observations)
231
+
232
+ self.horizons = horizons
233
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
234
+ self.discount = discount ** np.arange(max_path_length, dtype=np.float32)
235
+
236
+ self.indices = [[] for _ in range(len(horizons))]
237
+ self.seq_obs, self.seq_act, self.seq_rew = [], [], []
238
+
239
+ self.path_lengths, ptr = [], 0
240
+ path_idx = 0
241
+ for i in range(timeouts.shape[0]):
242
+ if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
243
+ self.path_lengths.append(i - ptr + 1)
244
+
245
+ _seq_obs = np.zeros((max_path_length, self.o_dim), dtype=np.float32)
246
+ _seq_act = np.zeros((max_path_length, self.a_dim), dtype=np.float32)
247
+ _seq_rew = np.zeros((max_path_length, 1), dtype=np.float32)
248
+
249
+ _seq_obs[:i - ptr + 1] = normed_observations[ptr:i + 1]
250
+ _seq_act[:i - ptr + 1] = actions[ptr:i + 1]
251
+ _seq_rew[:i - ptr + 1] = rewards[ptr:i + 1][:, None]
252
+
253
+ # repeat padding
254
+ _seq_obs[i - ptr + 1:] = normed_observations[i] # repeat last state
255
+ _seq_act[i - ptr + 1:] = 0 # repeat zero action
256
+ _seq_rew[i - ptr + 1:] = rewards[i] # repeat last reward
257
+
258
+ self.seq_obs.append(_seq_obs)
259
+ self.seq_act.append(_seq_act)
260
+ self.seq_rew.append(_seq_rew)
261
+
262
+ max_starts = [min(self.path_lengths[-1] - 1, max_path_length - horizon) for horizon in horizons]
263
+ for k in range(len(horizons)):
264
+ self.indices[k] += [(path_idx, start, start + horizons[k]) for start in range(max_starts[k] + 1)]
265
+
266
+ ptr = i + 1
267
+ path_idx += 1
268
+
269
+ self.seq_obs = np.array(self.seq_obs)
270
+ self.seq_act = np.array(self.seq_act)
271
+ self.seq_rew = np.array(self.seq_rew)
272
+
273
+ self.len_each_horizon = [len(indices) for indices in self.indices]
274
+
275
+ def get_normalizer(self):
276
+ return self.normalizers["state"]
277
+
278
+ def __len__(self):
279
+ return max(self.len_each_horizon)
280
+
281
+ def __getitem__(self, idx: int):
282
+
283
+ indices = [
284
+ int(self.len_each_horizon[i] * (idx / self.len_each_horizon[-1])) for i in range(len(self.horizons))]
285
+
286
+ torch_datas = []
287
+
288
+ for i, horizon in enumerate(self.horizons):
289
+ path_idx, start, end = self.indices[i][indices[i]]
290
+
291
+ rewards = self.seq_rew[path_idx, start:]
292
+ values = (rewards * self.discount[:rewards.shape[0], None]).sum(0)
293
+
294
+ data = {
295
+ 'obs': {
296
+ 'state': self.seq_obs[path_idx, start:end]},
297
+ 'act': self.seq_act[path_idx, start:end],
298
+ 'rew': self.seq_rew[path_idx, start:end],
299
+ 'val': values}
300
+
301
+ torch_data = dict_apply(data, torch.tensor)
302
+
303
+ torch_datas.append({
304
+ "horizon": horizon,
305
+ "data": torch_data,
306
+ })
307
+
308
+ return torch_datas
309
+
310
+ class DV_D4RLKitchenSeqDataset(BaseDataset):
311
+ """ **D4RL-Kitchen Sequential Dataset**
312
+
313
+ torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
314
+ Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-repeat padding.
315
+ Use GaussianNormalizer to normalize the observations as default.
316
+ Each batch contains
317
+ - batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
318
+ - batch["act"], actions of shape (batch_size, horizon, a_dim)
319
+ - batch["rew"], rewards of shape (batch_size, horizon, 1)
320
+ - batch["val"], Monte Carlo return of shape (batch_size, 1)
321
+
322
+ Args:
323
+ dataset: Dict[str, np.ndarray],
324
+ D4RL-Kitchen dataset. Obtained by calling `env.get_dataset()`.
325
+ horizon: int,
326
+ Length of each sequence. Default is 1.
327
+ max_path_length: int,
328
+ Maximum length of the episodes. Default is 280.
329
+ discount: float,
330
+ Discount factor. Default is 0.99.
331
+
332
+ Examples:
333
+ >>> env = gym.make("kitchen-mixed-v0")
334
+ >>> dataset = D4RLKitchenDataset(env.get_dataset(), horizon=32)
335
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
336
+ >>> batch = next(iter(dataloader))
337
+ >>> obs = batch["obs"]["state"] # (32, 32, 60)
338
+ >>> act = batch["act"] # (32, 32, 9)
339
+ >>> rew = batch["rew"] # (32, 32, 1)
340
+ >>> val = batch["val"] # (32, 1)
341
+
342
+ >>> normalizer = dataset.get_normalizer()
343
+ >>> obs = env.reset()[None, :]
344
+ >>> normed_obs = normalizer.normalize(obs)
345
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
346
+ """
347
+ def __init__(
348
+ self,
349
+ dataset: Dict[str, np.ndarray],
350
+ horizon: int = 1,
351
+ max_path_length: int = 280,
352
+ discount: float = 0.99,
353
+ center_mapping: bool = True,
354
+ stride: int = 1,
355
+ ):
356
+ super().__init__()
357
+
358
+ observations, actions, rewards, timeouts, terminals = (
359
+ dataset["observations"].astype(np.float32),
360
+ dataset["actions"].astype(np.float32),
361
+ dataset["rewards"].astype(np.float32),
362
+ dataset["timeouts"].astype(np.float32),
363
+ dataset["terminals"].astype(np.float32))
364
+ self.stride = stride
365
+
366
+ self.normalizers = {
367
+ "state": GaussianNormalizer(observations)}
368
+ normed_observations = self.normalizers["state"].normalize(observations)
369
+
370
+ self.horizon = horizon
371
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
372
+
373
+ self.indices = []
374
+ self.seq_obs, self.seq_act, self.seq_rew = [], [], []
375
+
376
+ ptr = 0
377
+ path_idx = 0
378
+ for i in range(timeouts.shape[0]):
379
+ if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
380
+ path_length = i - ptr + 1
381
+ assert path_length <= max_path_length
382
+
383
+ _seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
384
+ _seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
385
+ _seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
386
+
387
+ _seq_obs[:path_length] = normed_observations[ptr:i + 1]
388
+ _seq_act[:path_length] = actions[ptr:i + 1]
389
+ _seq_rew[:path_length] = rewards[ptr:i + 1][:, None]
390
+
391
+ # repeat padding
392
+ _seq_obs[path_length:] = normed_observations[i] # repeat last state
393
+ _seq_act[path_length:] = 0
394
+ _seq_rew[path_length:] = rewards[i]
395
+
396
+ self.seq_obs.append(_seq_obs)
397
+ self.seq_act.append(_seq_act)
398
+ self.seq_rew.append(_seq_rew)
399
+
400
+ max_start = path_length - 1
401
+ self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
402
+
403
+ ptr = i + 1
404
+ path_idx += 1
405
+
406
+ self.seq_obs = np.array(self.seq_obs)
407
+ self.seq_act = np.array(self.seq_act)
408
+ self.seq_rew = np.array(self.seq_rew)
409
+
410
+ self.seq_val = np.copy(self.seq_rew)
411
+ for i in reversed(range(max_path_length - 1)):
412
+ self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
413
+
414
+ print(f"max discounted return: {self.seq_val.max()}")
415
+ print(f"min discounted return: {self.seq_val.min()}")
416
+
417
+ # val \in [-1, 1]
418
+ self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
419
+ if center_mapping:
420
+ self.seq_val = self.seq_val * 2 - 1
421
+ print(f"max normed discounted return: {self.seq_val.max()}")
422
+ print(f"min normed discounted return: {self.seq_val.min()}")
423
+
424
+ def get_normalizer(self):
425
+ return self.normalizers["state"]
426
+
427
+ def __len__(self):
428
+ return len(self.indices)
429
+
430
+ def __getitem__(self, idx: int):
431
+ path_idx, start, end = self.indices[idx]
432
+
433
+ horizon_state = self.seq_obs[path_idx, start:end:self.stride]
434
+
435
+ data = {
436
+ 'obs': {'state': horizon_state},
437
+ 'act': self.seq_act[path_idx, start:end:self.stride],
438
+ 'rew': self.seq_rew[path_idx, start:end:self.stride],
439
+ 'val': self.seq_val[path_idx, start],
440
+ }
441
+
442
+ torch_data = dict_apply(data, torch.tensor)
443
+
444
+ return torch_data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_maze2d_dataset.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+ from cleandiffuser.dataset.base_dataset import BaseDataset
7
+ from cleandiffuser.utils import GaussianNormalizer, dict_apply
8
+
9
+ class DV_D4RLMaze2DSeqDataset(BaseDataset):
10
+ """ **D4RL-Maze2D Sequential Dataset**
11
+
12
+ torch.utils.data.Dataset wrapper for D4RL-Maze2D dataset.
13
+ Chunk the dataset into sequences of length `horizon` with obs-repeat/act-zero/reward-zero padding.
14
+ Use GaussianNormalizer to normalize the observations as default.
15
+ Each batch contains
16
+ - batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
17
+ - batch["act"], actions of shape (batch_size, horizon, a_dim)
18
+ - batch["rew"], rewards of shape (batch_size, horizon, 1)
19
+ - batch["val"], Monte Carlo return of shape (batch_size, 1)
20
+
21
+ Args:
22
+ dataset: Dict[str, np.ndarray],
23
+ D4RL-Maze2D dataset. Obtained by calling `env.get_dataset()`.
24
+ horizon: int,
25
+ Length of each sequence. Default is 1.
26
+ max_path_length: int,
27
+ Maximum length of the episodes. Default is 1001.
28
+ noreaching_penalty: float,
29
+ Penalty for not reaching the goal. Default is -100.
30
+ discount: float,
31
+ Discount factor. Default is 0.99.
32
+
33
+ Examples:
34
+ >>> env = gym.make("Maze2D-medium-play-v2")
35
+ >>> dataset = D4RLMaze2DDataset(env.get_dataset(), horizon=32)
36
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
37
+ >>> batch = next(iter(dataloader))
38
+ >>> obs = batch["obs"]["state"] # (32, 32, 29)
39
+ >>> act = batch["act"] # (32, 32, 8)
40
+ >>> rew = batch["rew"] # (32, 32, 1)
41
+ >>> val = batch["val"] # (32, 1)
42
+
43
+ >>> normalizer = dataset.get_normalizer()
44
+ >>> obs = env.reset()[None, :]
45
+ >>> normed_obs = normalizer.normalize(obs)
46
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
47
+ """
48
+ def __init__(
49
+ self,
50
+ dataset: Dict[str, np.ndarray],
51
+ horizon: int = 1,
52
+ max_path_length: int = 800,
53
+ discount: float = 0.99,
54
+ continous_reward_at_done: bool = False,
55
+ reward_tune: str = "iql",
56
+ center_mapping: bool = True,
57
+ learn_policy: bool = False,
58
+ stride: int = 1,
59
+ ):
60
+ super().__init__()
61
+
62
+ self.max_path_length = max_path_length
63
+
64
+ observations, actions, rewards, timeouts = (
65
+ dataset["observations"].astype(np.float32),
66
+ dataset["actions"].astype(np.float32),
67
+ dataset["rewards"].astype(np.float32),
68
+ dataset["timeouts"].astype(np.float32))
69
+ self.learn_policy = learn_policy
70
+ self.stride = stride
71
+
72
+ self.normalizers = {
73
+ "state": GaussianNormalizer(observations)}
74
+ normed_observations = self.normalizers["state"].normalize(observations)
75
+
76
+ self.horizon = horizon
77
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
78
+
79
+ self.indices = []
80
+ self.seq_obs, self.seq_act, self.seq_rew = [], [], []
81
+
82
+ path_idx = 0
83
+
84
+ self.paths = []
85
+ next_end = [-1] * (timeouts.shape[0] + 1)
86
+ next_start = [-1] * (timeouts.shape[0] + 1)
87
+ for index in reversed(range(timeouts.shape[0])):
88
+ if rewards[index] == 1.0:
89
+ next_end[index] = index
90
+ next_start[index] = next_start[index+1]
91
+ else:
92
+ next_end[index] = next_end[index+1]
93
+ next_start[index] = index
94
+
95
+ path_start = next_start[0]
96
+ path_end = next_end[path_start]
97
+
98
+ if self.learn_policy:
99
+ for path_start in range(0, timeouts.shape[0], max_path_length):
100
+ path_end = min(path_start + max_path_length - 1, timeouts.shape[0] - 1)
101
+ path_length = path_end - path_start + 1
102
+
103
+ _seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
104
+ _seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
105
+ _seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
106
+
107
+ _seq_obs[:path_length] = normed_observations[path_start:path_end+1]
108
+ _seq_act[:path_length] = actions[path_start:path_end+1]
109
+ _seq_rew[:path_length] = rewards[path_start:path_end+1][:, None]
110
+
111
+ _seq_obs[path_length:] = normed_observations[path_end] # repeat state
112
+ _seq_act[path_length:] = 0 # zero action
113
+ _seq_rew[path_length:] = 1 if continous_reward_at_done else 0
114
+
115
+ self.seq_obs.append(_seq_obs)
116
+ self.seq_act.append(_seq_act)
117
+ self.seq_rew.append(_seq_rew)
118
+
119
+ max_start = path_length - 1
120
+ self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
121
+ self.paths.append((path_start, path_end))
122
+ path_idx += 1
123
+
124
+ else:
125
+ while path_end != -1:
126
+ path_start = max(path_start, path_end - max_path_length + 1)
127
+ path_length = path_end - path_start + 1
128
+ assert path_length >= 2
129
+
130
+ _seq_obs = np.zeros((max_path_length + (horizon - 1) * stride, self.o_dim), dtype=np.float32)
131
+ _seq_act = np.zeros((max_path_length + (horizon - 1) * stride, self.a_dim), dtype=np.float32)
132
+ _seq_rew = np.zeros((max_path_length + (horizon - 1) * stride, 1), dtype=np.float32)
133
+
134
+ _seq_obs[:path_length] = normed_observations[path_start:path_end+1]
135
+ _seq_act[:path_length] = actions[path_start:path_end+1]
136
+ _seq_rew[:path_length] = rewards[path_start:path_end+1][:, None]
137
+
138
+ _seq_obs[path_length:] = normed_observations[path_end] # repeat state
139
+ _seq_act[path_length:] = 0 # zero action
140
+ _seq_rew[path_length:] = 1 if continous_reward_at_done else 0
141
+
142
+ self.seq_obs.append(_seq_obs)
143
+ self.seq_act.append(_seq_act)
144
+ self.seq_rew.append(_seq_rew)
145
+
146
+ max_start = path_length - 1
147
+ self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
148
+ self.paths.append((path_start, path_end))
149
+ path_idx += 1
150
+
151
+ path_start = next_start[path_end]
152
+ path_end = next_end[path_start]
153
+
154
+ self.seq_obs = np.array(self.seq_obs)
155
+ self.seq_act = np.array(self.seq_act)
156
+ self.seq_rew = np.array(self.seq_rew)
157
+
158
+ if reward_tune == "iql":
159
+ self.seq_rew += -1
160
+ elif reward_tune == "none":
161
+ self.seq_rew = self.seq_rew
162
+ else:
163
+ raise ValueError(f"reward_tune: {reward_tune} is not supported.")
164
+
165
+ self.seq_val = np.copy(self.seq_rew)
166
+ print(self.seq_obs.shape)
167
+ for i in reversed(range(max_path_length - 1)):
168
+ self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
169
+
170
+ print(f"max discounted return: {self.seq_val.max()}")
171
+ print(f"min discounted return: {self.seq_val.min()}")
172
+
173
+ # val \in [-1, 1]
174
+ self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
175
+ if center_mapping:
176
+ self.seq_val = self.seq_val * 2 - 1
177
+ print(f"max normed discounted return: {self.seq_val.max()}")
178
+ print(f"min normed discounted return: {self.seq_val.min()}")
179
+
180
+ def get_normalizer(self):
181
+ return self.normalizers["state"]
182
+
183
+ def __len__(self):
184
+ return len(self.indices)
185
+
186
+ def __getitem__(self, idx: int):
187
+ path_idx, start, end = self.indices[idx]
188
+
189
+ if self.learn_policy:
190
+ horizon_state = self.seq_obs[path_idx, start:end:self.stride].copy()
191
+ horizon_state[:, :2] -= horizon_state[0, :2]
192
+ else:
193
+ horizon_state = self.seq_obs[path_idx, start:end:self.stride]
194
+
195
+ data = {
196
+ 'obs': {'state': horizon_state},
197
+ 'act': self.seq_act[path_idx, start:end:self.stride],
198
+ 'rew': self.seq_rew[path_idx, start:end:self.stride],
199
+ 'val': self.seq_val[path_idx, start],
200
+ }
201
+
202
+ torch_data = dict_apply(data, torch.tensor)
203
+
204
+ return torch_data
205
+
206
+ class D4RLMaze2DTDDataset(BaseDataset):
207
+ """ **D4RL-Kitchen Transition Dataset**
208
+
209
+ torch.utils.data.Dataset wrapper for D4RL-Kitchen dataset.
210
+ Chunk the dataset into transitions.
211
+ Use GaussianNormalizer to normalize the observations as default.
212
+ Each batch contains
213
+ - batch["obs"]["state"], observation of shape (batch_size, o_dim)
214
+ - batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
215
+ - batch["act"], action of shape (batch_size, a_dim)
216
+ - batch["rew"], reward of shape (batch_size, 1)
217
+ - batch["tml"], terminal of shape (batch_size, 1)
218
+
219
+ Args:
220
+ dataset: Dict[str, np.ndarray],
221
+ D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
222
+
223
+ Examples:
224
+ >>> env = gym.make("kitchen-mixed-v0")
225
+ >>> dataset = D4RLKitchenTDDataset(d4rl.qlearning_dataset(env))
226
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
227
+ >>> batch = next(iter(dataloader))
228
+ >>> obs = batch["obs"]["state"] # (32, 60)
229
+ >>> act = batch["act"] # (32, 9)
230
+ >>> rew = batch["rew"] # (32, 1)
231
+ >>> tml = batch["tml"] # (32, 1)
232
+ >>> next_obs = batch["next_obs"]["state"] # (32, 60)
233
+
234
+ >>> normalizer = dataset.get_normalizer()
235
+ >>> obs = env.reset()[None, :]
236
+ >>> normed_obs = normalizer.normalize(obs)
237
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
238
+ """
239
+ def __init__(self, dataset: Dict[str, np.ndarray], reward_tune: str = "iql"):
240
+ super().__init__()
241
+
242
+ observations, actions, next_observations, rewards, terminals = (
243
+ dataset["observations"].astype(np.float32),
244
+ dataset["actions"].astype(np.float32),
245
+ dataset["next_observations"].astype(np.float32),
246
+ dataset["rewards"].astype(np.float32),
247
+ dataset["terminals"].astype(np.float32))
248
+
249
+ if reward_tune == "iql":
250
+ rewards = rewards - 1.
251
+ elif reward_tune == "cql":
252
+ rewards = (rewards - 0.5) * 4.
253
+ elif reward_tune == "antmaze":
254
+ rewards = (rewards - 0.25) * 2.
255
+ elif reward_tune == "none":
256
+ rewards = rewards
257
+ else:
258
+ raise ValueError(f"reward_tune: {reward_tune} is not supported.")
259
+
260
+ self.normalizers = {
261
+ "state": GaussianNormalizer(observations)}
262
+ normed_observations = self.normalizers["state"].normalize(observations)
263
+ normed_next_observations = self.normalizers["state"].normalize(next_observations)
264
+
265
+ self.obs = torch.tensor(normed_observations)
266
+ self.act = torch.tensor(actions)
267
+ self.rew = torch.tensor(rewards)[:, None]
268
+ self.tml = torch.tensor(terminals)[:, None]
269
+ self.next_obs = torch.tensor(normed_next_observations)
270
+
271
+ self.size = self.obs.shape[0]
272
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
273
+
274
+ def get_normalizer(self):
275
+ return self.normalizers["state"]
276
+
277
+ def __len__(self):
278
+ return self.size
279
+
280
+ def __getitem__(self, idx: int):
281
+ data = {
282
+ 'obs': {
283
+ 'state': self.obs[idx], },
284
+ 'next_obs': {
285
+ 'state': self.next_obs[idx], },
286
+ 'act': self.act[idx],
287
+ 'rew': self.rew[idx],
288
+ 'tml': self.tml[idx], }
289
+
290
+ return data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/d4rl_mujoco_dataset.py ADDED
@@ -0,0 +1,453 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+ from cleandiffuser.dataset.base_dataset import BaseDataset
7
+ from cleandiffuser.utils import GaussianNormalizer, dict_apply
8
+
9
+
10
+ def return_reward_range(dataset, max_episode_steps):
11
+ """ Return the range of episodic returns in the D4RL-MuJoCo dataset. """
12
+ returns, lengths = [], []
13
+ ep_ret, ep_len = 0.0, 0
14
+ for r, d in zip(dataset["rewards"], dataset["terminals"]):
15
+ ep_ret += float(r)
16
+ ep_len += 1
17
+ if d or ep_len == max_episode_steps:
18
+ returns.append(ep_ret)
19
+ lengths.append(ep_len)
20
+ ep_ret, ep_len = 0.0, 0
21
+ lengths.append(ep_len) # but still keep track of number of steps
22
+ assert sum(lengths) == len(dataset["rewards"])
23
+ return min(returns), max(returns)
24
+
25
+
26
+ def modify_reward(dataset, max_episode_steps=1000):
27
+ """ Modify the episodic return scale of the D4RL-MuJoCo dataset to be within [0, max_episode_steps]. """
28
+ min_ret, max_ret = return_reward_range(dataset, max_episode_steps)
29
+ dataset["rewards"] /= max_ret - min_ret
30
+ dataset["rewards"] *= max_episode_steps
31
+ return dataset
32
+
33
+
34
+ class D4RLMuJoCoDataset(BaseDataset):
35
+ """ **D4RL-MuJoCo Sequential Dataset**
36
+
37
+ torch.utils.data.Dataset wrapper for D4RL-MuJoCo dataset.
38
+ Chunk the dataset into sequences of length `horizon` without padding.
39
+ Use GaussianNormalizer to normalize the observations as default.
40
+ Each batch contains
41
+ - batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
42
+ - batch["act"], actions of shape (batch_size, horizon, a_dim)
43
+ - batch["rew"], rewards of shape (batch_size, horizon, 1)
44
+ - batch["val"], Monte Carlo return of shape (batch_size, 1)
45
+
46
+ Args:
47
+ dataset: Dict[str, np.ndarray],
48
+ D4RL-MuJoCo dataset. Obtained by calling `env.get_dataset()`.
49
+ terminal_penalty: float,
50
+ Penalty reward for early-terminal states. Default is -100.
51
+ horizon: int,
52
+ Length of each sequence. Default is 1.
53
+ max_path_length: int,
54
+ Maximum length of the episodes. Default is 1000.
55
+ discount: float,
56
+ Discount factor. Default is 0.99.
57
+
58
+ Examples:
59
+ >>> env = gym.make("halfcheetah-medium-expert-v2")
60
+ >>> dataset = D4RLMuJoCoDataset(env.get_dataset(), horizon=32)
61
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
62
+ >>> batch = next(iter(dataloader))
63
+ >>> obs = batch["obs"]["state"] # (32, 32, 17)
64
+ >>> act = batch["act"] # (32, 32, 6)
65
+ >>> rew = batch["rew"] # (32, 32, 1)
66
+ >>> val = batch["val"] # (32, 1)
67
+
68
+ >>> normalizer = dataset.get_normalizer()
69
+ >>> obs = env.reset()[None, :]
70
+ >>> normed_obs = normalizer.normalize(obs)
71
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
72
+ """
73
+ def __init__(
74
+ self,
75
+ dataset: Dict[str, np.ndarray],
76
+ terminal_penalty: float = -100.,
77
+ horizon: int = 1,
78
+ max_path_length: int = 1000,
79
+ discount: float = 0.99,
80
+ ):
81
+ super().__init__()
82
+
83
+ observations, actions, rewards, timeouts, terminals = (
84
+ dataset["observations"].astype(np.float32),
85
+ dataset["actions"].astype(np.float32),
86
+ dataset["rewards"].astype(np.float32),
87
+ dataset["timeouts"],
88
+ dataset["terminals"])
89
+ self.normalizers = {
90
+ "state": GaussianNormalizer(observations)}
91
+ normed_observations = self.normalizers["state"].normalize(observations)
92
+
93
+ self.horizon = horizon
94
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
95
+
96
+ n_paths = np.sum(np.logical_or(terminals, timeouts))
97
+ self.seq_obs = np.zeros((n_paths, max_path_length, self.o_dim), dtype=np.float32)
98
+ self.seq_act = np.zeros((n_paths, max_path_length, self.a_dim), dtype=np.float32)
99
+ self.seq_rew = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
100
+ self.seq_val = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
101
+ self.tml_and_not_timeout = []
102
+ self.indices = []
103
+
104
+ path_lengths, ptr = [], 0
105
+ path_idx = 0
106
+ for i in range(timeouts.shape[0]):
107
+ if timeouts[i] or terminals[i]:
108
+ path_lengths.append(i - ptr + 1)
109
+
110
+ if terminals[i] and not timeouts[i]:
111
+ rewards[i] = terminal_penalty if terminal_penalty is not None else rewards[i]
112
+ self.tml_and_not_timeout.append([path_idx, i - ptr])
113
+
114
+ self.seq_obs[path_idx, :i - ptr + 1] = normed_observations[ptr:i + 1]
115
+ self.seq_act[path_idx, :i - ptr + 1] = actions[ptr:i + 1]
116
+ self.seq_rew[path_idx, :i - ptr + 1] = rewards[ptr:i + 1][:, None]
117
+
118
+ max_start = min(path_lengths[-1] - 1, max_path_length - horizon)
119
+ self.indices += [(path_idx, start, start + horizon) for start in range(max_start + 1)]
120
+
121
+ ptr = i + 1
122
+ path_idx += 1
123
+
124
+ self.seq_val[:, -1] = self.seq_rew[:, -1]
125
+ for i in range(max_path_length - 1):
126
+ self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
127
+ self.path_lengths = np.array(path_lengths)
128
+ self.tml_and_not_timeout = np.array(self.tml_and_not_timeout, dtype=np.int64)
129
+
130
+ def get_normalizer(self):
131
+ return self.normalizers["state"]
132
+
133
+ def __len__(self):
134
+ return len(self.indices)
135
+
136
+ def __getitem__(self, idx: int):
137
+ path_idx, start, end = self.indices[idx]
138
+
139
+ data = {
140
+ 'obs': {
141
+ 'state': self.seq_obs[path_idx, start:end]},
142
+ 'act': self.seq_act[path_idx, start:end],
143
+ 'rew': self.seq_rew[path_idx, start:end],
144
+ 'val': self.seq_val[path_idx, start],
145
+ }
146
+
147
+ torch_data = dict_apply(data, torch.tensor)
148
+
149
+ return torch_data
150
+
151
+
152
+ class D4RLMuJoCoTDDataset(BaseDataset):
153
+ """ **D4RL-MuJoCo Transition Dataset**
154
+
155
+ torch.utils.data.Dataset wrapper for D4RL-MuJoCo dataset.
156
+ Chunk the dataset into transitions.
157
+ Use GaussianNormalizer to normalize the observations as default.
158
+ Each batch contains
159
+ - batch["obs"]["state"], observation of shape (batch_size, o_dim)
160
+ - batch["next_obs"]["state"], next observation of shape (batch_size, o_dim)
161
+ - batch["act"], action of shape (batch_size, a_dim)
162
+ - batch["rew"], reward of shape (batch_size, 1)
163
+ - batch["tml"], terminal of shape (batch_size, 1)
164
+
165
+ Args:
166
+ dataset: Dict[str, np.ndarray],
167
+ D4RL-MuJoCo TD dataset. Obtained by calling `d4rl.qlearning_dataset(env)`.
168
+ normalize_reward: bool,
169
+ Normalize the reward. Default is False.
170
+
171
+ Examples:
172
+ >>> env = gym.make("halfcheetah-medium-expert-v2")
173
+ >>> dataset = D4RLMuJoCoTDDataset(d4rl.qlearning_dataset(env))
174
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
175
+ >>> batch = next(iter(dataloader))
176
+ >>> obs = batch["obs"]["state"] # (32, 17)
177
+ >>> act = batch["act"] # (32, 6)
178
+ >>> rew = batch["rew"] # (32, 1)
179
+ >>> tml = batch["tml"] # (32, 1)
180
+ >>> next_obs = batch["next_obs"]["state"] # (32, 17)
181
+
182
+ >>> normalizer = dataset.get_normalizer()
183
+ >>> obs = env.reset()[None, :]
184
+ >>> normed_obs = normalizer.normalize(obs)
185
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
186
+ """
187
+ def __init__(self, dataset: Dict[str, np.ndarray], normalize_reward: bool = False):
188
+ super().__init__()
189
+ if normalize_reward:
190
+ dataset = modify_reward(dataset, 1000)
191
+
192
+ observations, actions, next_observations, rewards, terminals = (
193
+ dataset["observations"].astype(np.float32),
194
+ dataset["actions"].astype(np.float32),
195
+ dataset["next_observations"].astype(np.float32),
196
+ dataset["rewards"].astype(np.float32),
197
+ dataset["terminals"].astype(np.float32))
198
+
199
+ self.normalizers = {
200
+ "state": GaussianNormalizer(observations)}
201
+ normed_observations = self.normalizers["state"].normalize(observations)
202
+ normed_next_observations = self.normalizers["state"].normalize(next_observations)
203
+
204
+ self.obs = torch.tensor(normed_observations, dtype=torch.float32)
205
+ self.act = torch.tensor(actions, dtype=torch.float32)
206
+ self.rew = torch.tensor(rewards, dtype=torch.float32)[:, None]
207
+ self.tml = torch.tensor(terminals, dtype=torch.float32)[:, None]
208
+ self.next_obs = torch.tensor(normed_next_observations, dtype=torch.float32)
209
+
210
+ self.size = self.obs.shape[0]
211
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
212
+
213
+ def get_normalizer(self):
214
+ return self.normalizers["state"]
215
+
216
+ def __len__(self):
217
+ return self.size
218
+
219
+ def __getitem__(self, idx: int):
220
+ data = {
221
+ 'obs': {
222
+ 'state': self.obs[idx], },
223
+ 'next_obs': {
224
+ 'state': self.next_obs[idx], },
225
+ 'act': self.act[idx],
226
+ 'rew': self.rew[idx],
227
+ 'tml': self.tml[idx], }
228
+
229
+ return data
230
+
231
+
232
+ class MultiHorizonD4RLMuJoCoDataset(BaseDataset):
233
+ def __init__(
234
+ self,
235
+ dataset,
236
+ terminal_penalty=-100,
237
+ horizons=(10, 20),
238
+ max_path_length=1000,
239
+ discount=0.99,
240
+ ):
241
+ super().__init__()
242
+
243
+ observations, actions, rewards, timeouts, terminals = (
244
+ dataset["observations"].astype(np.float32),
245
+ dataset["actions"].astype(np.float32),
246
+ dataset["rewards"].astype(np.float32),
247
+ dataset["timeouts"],
248
+ dataset["terminals"])
249
+ self.normalizers = {
250
+ "state": GaussianNormalizer(observations)}
251
+ normed_observations = self.normalizers["state"].normalize(observations)
252
+
253
+ self.horizons = horizons
254
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
255
+ self.discount = discount ** np.arange(max_path_length, dtype=np.float32)
256
+
257
+ n_paths = np.sum(np.logical_or(terminals, timeouts))
258
+ self.seq_obs = np.zeros((n_paths, max_path_length, self.o_dim), dtype=np.float32)
259
+ self.seq_act = np.zeros((n_paths, max_path_length, self.a_dim), dtype=np.float32)
260
+ self.seq_rew = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
261
+ self.seq_val = np.zeros((n_paths, max_path_length, 1), dtype=np.float32)
262
+ self.indices = [[] for _ in range(len(horizons))]
263
+
264
+ path_lengths, ptr = [], 0
265
+ path_idx = 0
266
+ for i in range(timeouts.shape[0]):
267
+ if timeouts[i] or terminals[i]:
268
+ path_lengths.append(i - ptr + 1)
269
+
270
+ if terminals[i] and not timeouts[i]:
271
+ rewards[i] = terminal_penalty if terminal_penalty is not None else rewards[i]
272
+
273
+ self.seq_obs[path_idx, :i - ptr + 1] = normed_observations[ptr:i + 1]
274
+ self.seq_act[path_idx, :i - ptr + 1] = actions[ptr:i + 1]
275
+ self.seq_rew[path_idx, :i - ptr + 1] = rewards[ptr:i + 1][:, None]
276
+
277
+ max_starts = [min(path_lengths[-1] - 1, max_path_length - horizon) for horizon in horizons]
278
+ for k in range(len(horizons)):
279
+ self.indices[k] += [(path_idx, start, start + horizons[k]) for start in range(max_starts[k] + 1)]
280
+
281
+ ptr = i + 1
282
+ path_idx += 1
283
+
284
+ self.seq_val[:, -1] = self.seq_rew[:, -1]
285
+ for i in range(max_path_length - 1):
286
+ self.seq_val[:, - 2 - i] = self.seq_rew[:, -2 - i] + discount * self.seq_val[:, -1 - i]
287
+ self.path_lengths = np.array(path_lengths)
288
+ self.len_each_horizon = [len(indices) for indices in self.indices]
289
+
290
+ def get_normalizer(self):
291
+ return self.normalizers["state"]
292
+
293
+ def __len__(self):
294
+ return max(self.len_each_horizon)
295
+
296
+ def __getitem__(self, idx: int):
297
+
298
+ indices = [
299
+ int(self.len_each_horizon[i] * (idx / self.len_each_horizon[-1])) for i in range(len(self.horizons))]
300
+
301
+ torch_datas = []
302
+
303
+ for i, horizon in enumerate(self.horizons):
304
+
305
+ path_idx, start, end = self.indices[i][indices[i]]
306
+
307
+ data = {
308
+ 'obs': {
309
+ 'state': self.seq_obs[path_idx, start:end]},
310
+ 'act': self.seq_act[path_idx, start:end],
311
+ 'val': self.seq_val[path_idx, start]}
312
+
313
+ torch_data = dict_apply(data, torch.tensor)
314
+
315
+ torch_datas.append({
316
+ "horizon": horizon,
317
+ "data": torch_data,
318
+ })
319
+
320
+ return torch_datas
321
+
322
+ class DV_D4RLMuJoCoSeqDataset(BaseDataset):
323
+ """ **D4RL-MuJoCo Sequential Dataset**
324
+
325
+ torch.utils.data.Dataset wrapper for D4RL-MuJoCo dataset.
326
+ Chunk the dataset into sequences of length `horizon` without padding.
327
+ Use GaussianNormalizer to normalize the observations as default.
328
+ Each batch contains
329
+ - batch["obs"]["state"], observations of shape (batch_size, horizon, o_dim)
330
+ - batch["act"], actions of shape (batch_size, horizon, a_dim)
331
+ - batch["rew"], rewards of shape (batch_size, horizon, 1)
332
+ - batch["val"], Monte Carlo return of shape (batch_size, 1)
333
+
334
+ Args:
335
+ dataset: Dict[str, np.ndarray],
336
+ D4RL-MuJoCo dataset. Obtained by calling `env.get_dataset()`.
337
+ terminal_penalty: float,
338
+ Penalty reward for early-terminal states. Default is -100.
339
+ horizon: int,
340
+ Length of each sequence. Default is 1.
341
+ max_path_length: int,
342
+ Maximum length of the episodes. Default is 1000.
343
+ discount: float,
344
+ Discount factor. Default is 0.99.
345
+
346
+ Examples:
347
+ >>> env = gym.make("halfcheetah-medium-expert-v2")
348
+ >>> dataset = D4RLMuJoCoDataset(env.get_dataset(), horizon=32)
349
+ >>> dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
350
+ >>> batch = next(iter(dataloader))
351
+ >>> obs = batch["obs"]["state"] # (32, 32, 17)
352
+ >>> act = batch["act"] # (32, 32, 6)
353
+ >>> rew = batch["rew"] # (32, 32, 1)
354
+ >>> val = batch["val"] # (32, 1)
355
+
356
+ >>> normalizer = dataset.get_normalizer()
357
+ >>> obs = env.reset()[None, :]
358
+ >>> normed_obs = normalizer.normalize(obs)
359
+ >>> unnormed_obs = normalizer.unnormalize(normed_obs)
360
+ """
361
+ def __init__(
362
+ self,
363
+ dataset: Dict[str, np.ndarray],
364
+ terminal_penalty: float = -100,
365
+ horizon: int = 1,
366
+ max_path_length: int = 1000,
367
+ discount: float = 0.99,
368
+ center_mapping: bool = True,
369
+ stride: int = 1,
370
+ full_traj_bonus: float = 100,
371
+ ):
372
+ super().__init__()
373
+
374
+ observations, actions, rewards, timeouts, terminals = (
375
+ dataset["observations"].astype(np.float32),
376
+ dataset["actions"].astype(np.float32),
377
+ dataset["rewards"].astype(np.float32),
378
+ dataset["timeouts"].astype(np.float32),
379
+ dataset["terminals"].astype(np.float32))
380
+ self.stride = stride
381
+
382
+ self.normalizers = {
383
+ "state": GaussianNormalizer(observations)}
384
+ normed_observations = self.normalizers["state"].normalize(observations)
385
+
386
+ self.horizon = horizon
387
+ self.o_dim, self.a_dim = observations.shape[-1], actions.shape[-1]
388
+
389
+ n_paths = np.sum(np.logical_or(terminals, timeouts))
390
+ self.seq_obs = np.zeros((n_paths+1, max_path_length, self.o_dim), dtype=np.float32)
391
+ self.seq_act = np.zeros((n_paths+1, max_path_length, self.a_dim), dtype=np.float32)
392
+ self.seq_rew = np.zeros((n_paths+1, max_path_length, 1), dtype=np.float32)
393
+ self.seq_val = np.zeros((n_paths+1, max_path_length, 1), dtype=np.float32)
394
+ self.indices = []
395
+
396
+ ptr = 0
397
+ path_idx = 0
398
+ for i in range(timeouts.shape[0]):
399
+ if timeouts[i] or terminals[i] or i == timeouts.shape[0] - 1:
400
+ path_length = i - ptr + 1
401
+ assert path_length <= max_path_length, f"current path length {path_length}"
402
+
403
+ if terminals[i]:
404
+ rewards[i] = terminal_penalty if terminal_penalty is not None else rewards[i]
405
+
406
+ if path_length == max_path_length:
407
+ rewards[i] = rewards[i] + full_traj_bonus if full_traj_bonus is not None else rewards[i]
408
+
409
+ self.seq_obs[path_idx, :path_length] = normed_observations[ptr:i + 1]
410
+ self.seq_act[path_idx, :path_length] = actions[ptr:i + 1]
411
+ self.seq_rew[path_idx, :path_length] = rewards[ptr:i + 1][:, None]
412
+
413
+ max_start = path_length - (horizon - 1) * stride - 1
414
+ self.indices += [(path_idx, start, start + (horizon - 1) * stride + 1) for start in range(max_start + 1)]
415
+
416
+ ptr = i + 1
417
+ path_idx += 1
418
+
419
+ self.seq_val[:, -1] = self.seq_rew[:, -1]
420
+ for i in reversed(range(max_path_length-1)):
421
+ self.seq_val[:, i] = self.seq_rew[:, i] + discount * self.seq_val[:, i+1]
422
+
423
+ print(f"max discounted return: {self.seq_val.max()}")
424
+ print(f"min discounted return: {self.seq_val.min()}")
425
+
426
+ # val \in [-1, 1]
427
+ self.seq_val = (self.seq_val - self.seq_val.min()) / (self.seq_val.max() - self.seq_val.min())
428
+ if center_mapping:
429
+ self.seq_val = self.seq_val * 2 - 1
430
+ print(f"max normed discounted return: {self.seq_val.max()}")
431
+ print(f"min normed discounted return: {self.seq_val.min()}")
432
+
433
+ def get_normalizer(self):
434
+ return self.normalizers["state"]
435
+
436
+ def __len__(self):
437
+ return len(self.indices)
438
+
439
+ def __getitem__(self, idx: int):
440
+ path_idx, start, end = self.indices[idx]
441
+
442
+ horizon_state = self.seq_obs[path_idx, start:end:self.stride]
443
+
444
+ data = {
445
+ 'obs': {'state': horizon_state},
446
+ 'act': self.seq_act[path_idx, start:end:self.stride],
447
+ 'rew': self.seq_rew[path_idx, start:end:self.stride],
448
+ 'val': self.seq_val[path_idx, start],
449
+ }
450
+
451
+ torch_data = dict_apply(data, torch.tensor)
452
+
453
+ return torch_data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/dataset_utils.py ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Callable, List, Union
2
+ import numpy as np
3
+ import torch
4
+ import scipy.interpolate as interpolate
5
+ import numba
6
+ from cleandiffuser.dataset.replay_buffer import ReplayBuffer
7
+ import cleandiffuser.dataset.rotation_conversions as rc
8
+ import functools
9
+
10
+
11
+ # -----------------------------------------------------------------------------#
12
+ # ------------------------------ SequenceSampler ------------------------------#
13
+ # -----------------------------------------------------------------------------#
14
+
15
+ # Original implemetation: https://github.com/real-stanford/diffusion_policy
16
+ # Observation Horizon: To|n_obs_steps
17
+ # Action Horizon: Ta|n_action_steps
18
+ # Prediction Horizon: T|horizon
19
+ # To = 3
20
+ # Ta = 4
21
+ # T = 6
22
+ # |o|o|o|
23
+ # | | |a|a|a|a|
24
+ # pad_before = 2
25
+ # pad_after = 3
26
+
27
+ @numba.jit(nopython=True)
28
+ def create_indices(
29
+ episode_ends: np.ndarray,
30
+ sequence_length: int,
31
+ pad_before: int = 0, pad_after: int = 0,
32
+ debug: bool = True) -> np.ndarray:
33
+ pad_before = min(max(pad_before, 0), sequence_length - 1)
34
+ pad_after = min(max(pad_after, 0), sequence_length - 1)
35
+
36
+ indices = list()
37
+ for i in range(len(episode_ends)):
38
+ start_idx = 0 # episode start index
39
+ if i > 0:
40
+ start_idx = episode_ends[i - 1]
41
+ end_idx = episode_ends[i] # episode end index
42
+ episode_length = end_idx - start_idx # episode length
43
+
44
+ min_start = -pad_before
45
+ max_start = episode_length - sequence_length + pad_after
46
+
47
+ # range stops one idx before end
48
+ for idx in range(min_start, max_start + 1):
49
+ buffer_start_idx = max(idx, 0) + start_idx
50
+ buffer_end_idx = min(idx + sequence_length, episode_length) + start_idx
51
+ start_offset = buffer_start_idx - (idx + start_idx)
52
+ end_offset = (idx + sequence_length + start_idx) - buffer_end_idx
53
+ sample_start_idx = 0 + start_offset
54
+ sample_end_idx = sequence_length - end_offset
55
+ if debug:
56
+ assert (start_offset >= 0)
57
+ assert (end_offset >= 0)
58
+ assert (sample_end_idx - sample_start_idx) == (buffer_end_idx - buffer_start_idx)
59
+ indices.append([
60
+ buffer_start_idx, buffer_end_idx,
61
+ sample_start_idx, sample_end_idx])
62
+ indices = np.array(indices)
63
+ return indices
64
+
65
+
66
+ class SequenceSampler:
67
+ def __init__(
68
+ self,
69
+ replay_buffer: ReplayBuffer,
70
+ sequence_length: int,
71
+ pad_before: int = 0,
72
+ pad_after: int = 0,
73
+ keys=None,
74
+ key_first_k=dict(),
75
+ zero_padding: bool = False,
76
+ ):
77
+ """
78
+ key_first_k: dict str: int
79
+ Only take first k data from these keys (to improve perf)
80
+ """
81
+ super().__init__()
82
+ assert (sequence_length >= 1)
83
+
84
+ # all keys
85
+ if keys is None:
86
+ keys = list(replay_buffer.keys())
87
+
88
+ episode_ends = replay_buffer.episode_ends[:]
89
+
90
+ # create indices
91
+ # indices (buffer_start_idx, buffer_end_idx, sample_start_idx, sample_end_idx)
92
+ # buffer_start_idx and buffer_end_idx define the actual start and end positions of the sample sequence within the original dataset.
93
+ # sample_start_idx and sample_end_idx define the relative start and end positions within the sample sequence,
94
+ # which is particularly useful when dealing with padding as it can affect the actual length of the sequence.
95
+ indices = create_indices(
96
+ episode_ends=episode_ends,
97
+ sequence_length=sequence_length,
98
+ pad_before=pad_before,
99
+ pad_after=pad_after,
100
+ )
101
+
102
+ self.indices = indices
103
+ self.keys = list(keys) # prevent OmegaConf list performance problem
104
+ self.sequence_length = sequence_length
105
+ self.replay_buffer = replay_buffer
106
+ self.zero_padding = zero_padding
107
+ self.key_first_k = key_first_k
108
+
109
+ def __len__(self):
110
+ return len(self.indices)
111
+
112
+ def sample_sequence(self, idx):
113
+ buffer_start_idx, buffer_end_idx, sample_start_idx, sample_end_idx = self.indices[idx]
114
+ result = dict()
115
+ for key in self.keys:
116
+ input_arr = self.replay_buffer[key]
117
+ # performance optimization, avoid small allocation if possible
118
+ if key not in self.key_first_k:
119
+ sample = input_arr[buffer_start_idx:buffer_end_idx]
120
+ else:
121
+ # performance optimization, only load used obs steps
122
+ n_data = buffer_end_idx - buffer_start_idx
123
+ k_data = min(self.key_first_k[key], n_data)
124
+ # fill value with Nan to catch bugs
125
+ # the non-loaded region should never be used
126
+ sample = np.full((n_data,) + input_arr.shape[1:],
127
+ fill_value=np.nan, dtype=input_arr.dtype)
128
+ sample[:k_data] = input_arr[buffer_start_idx:buffer_start_idx + k_data]
129
+ data = sample
130
+ if (sample_start_idx > 0) or (sample_end_idx < self.sequence_length):
131
+ data = np.zeros(
132
+ shape=(self.sequence_length,) + input_arr.shape[1:],
133
+ dtype=input_arr.dtype)
134
+ if not self.zero_padding:
135
+ if sample_start_idx > 0:
136
+ data[:sample_start_idx] = sample[0]
137
+ if sample_end_idx < self.sequence_length:
138
+ data[sample_end_idx:] = sample[-1]
139
+ data[sample_start_idx:sample_end_idx] = sample
140
+ result[key] = data
141
+ return result
142
+
143
+
144
+ # -----------------------------------------------------------------------------#
145
+ # ---------------------------- Rotation Transformer ---------------------------#
146
+ # -----------------------------------------------------------------------------#
147
+
148
+ class RotationTransformer:
149
+ valid_reps = [
150
+ 'axis_angle',
151
+ 'euler_angles',
152
+ 'quaternion',
153
+ 'rotation_6d',
154
+ 'matrix'
155
+ ]
156
+
157
+ def __init__(self,
158
+ from_rep='axis_angle',
159
+ to_rep='rotation_6d',
160
+ from_convention=None,
161
+ to_convention=None):
162
+ """
163
+ Valid representations
164
+
165
+ Always use matrix as intermediate representation.
166
+ """
167
+ assert from_rep != to_rep
168
+ assert from_rep in self.valid_reps
169
+ assert to_rep in self.valid_reps
170
+ if from_rep == 'euler_angles':
171
+ assert from_convention is not None
172
+ if to_rep == 'euler_angles':
173
+ assert to_convention is not None
174
+
175
+ forward_funcs = list()
176
+ inverse_funcs = list()
177
+
178
+ if from_rep != 'matrix':
179
+ funcs = [
180
+ getattr(rc, f'{from_rep}_to_matrix'),
181
+ getattr(rc, f'matrix_to_{from_rep}')
182
+ ]
183
+ if from_convention is not None:
184
+ funcs = [functools.partial(func, convention=from_convention)
185
+ for func in funcs]
186
+ forward_funcs.append(funcs[0])
187
+ inverse_funcs.append(funcs[1])
188
+
189
+ if to_rep != 'matrix':
190
+ funcs = [
191
+ getattr(rc, f'matrix_to_{to_rep}'),
192
+ getattr(rc, f'{to_rep}_to_matrix')
193
+ ]
194
+ if to_convention is not None:
195
+ funcs = [functools.partial(func, convention=to_convention)
196
+ for func in funcs]
197
+ forward_funcs.append(funcs[0])
198
+ inverse_funcs.append(funcs[1])
199
+
200
+ inverse_funcs = inverse_funcs[::-1]
201
+
202
+ self.forward_funcs = forward_funcs
203
+ self.inverse_funcs = inverse_funcs
204
+
205
+ @staticmethod
206
+ def _apply_funcs(x: Union[np.ndarray, torch.Tensor], funcs: list) -> Union[np.ndarray, torch.Tensor]:
207
+ x_ = x
208
+ if isinstance(x, np.ndarray):
209
+ x_ = torch.tensor(x)
210
+ x_: torch.Tensor
211
+ for func in funcs:
212
+ x_ = func(x_)
213
+ y = x_
214
+ if isinstance(x, np.ndarray):
215
+ y = x_.numpy()
216
+ return y
217
+
218
+ def forward(self, x: Union[np.ndarray, torch.Tensor]
219
+ ) -> Union[np.ndarray, torch.Tensor]:
220
+ return self._apply_funcs(x, self.forward_funcs)
221
+
222
+ def inverse(self, x: Union[np.ndarray, torch.Tensor]
223
+ ) -> Union[np.ndarray, torch.Tensor]:
224
+ return self._apply_funcs(x, self.inverse_funcs)
225
+
226
+
227
+ # -----------------------------------------------------------------------------#
228
+ # --------------------------- multi-field normalizer --------------------------#
229
+ # -----------------------------------------------------------------------------#
230
+
231
+ def empirical_cdf(sample):
232
+ """ https://stackoverflow.com/a/33346366 """
233
+
234
+ # find the unique values and their corresponding counts
235
+ quantiles, counts = np.unique(sample, return_counts=True)
236
+
237
+ # take the cumulative sum of the counts and divide by the sample size to
238
+ # get the cumulative probabilities between 0 and 1
239
+ cumprob = np.cumsum(counts).astype(np.double) / sample.size
240
+
241
+ return quantiles, cumprob
242
+
243
+
244
+ class CDFNormalizer1d:
245
+ """
246
+ CDF normalizer for a single dimension
247
+ """
248
+
249
+ def __init__(self, X):
250
+ assert X.ndim == 1
251
+ self.X = X.astype(np.float32)
252
+ quantiles, cumprob = empirical_cdf(self.X)
253
+ self.fn = interpolate.interp1d(quantiles, cumprob)
254
+ self.inv = interpolate.interp1d(cumprob, quantiles)
255
+ self.xmin, self.xmax = quantiles.min(), quantiles.max()
256
+ self.ymin, self.ymax = cumprob.min(), cumprob.max()
257
+
258
+ def normalize(self, x):
259
+ x = np.clip(x, self.xmin, self.xmax)
260
+ y = self.fn(x)
261
+ y = 2 * y - 1
262
+ return y
263
+
264
+ def unnormalize(self, x, eps=1e-4):
265
+ x = (x + 1) / 2.
266
+ if (x < self.ymin - eps).any() or (x > self.ymax + eps).any():
267
+ print(
268
+ f'''[ dataset/normalization ] Warning: out of range in unnormalize: '''
269
+ f'''[{x.min()}, {x.max()}] | '''
270
+ f'''x : [{self.xmin}, {self.xmax}] | '''
271
+ f'''y: [{self.ymin}, {self.ymax}]''')
272
+ x = np.clip(x, self.ymin, self.ymax)
273
+ y = self.inv(x)
274
+ return y
275
+
276
+
277
+ class CDFNormalizer:
278
+ """
279
+ makes training data uniform (over each dimension) by transforming it with marginal CDFs
280
+ """
281
+
282
+ def __init__(self, X):
283
+ self.X = X.astype(np.float32)
284
+ self.mins, self.maxs = X.min(0), X.max(0)
285
+ self.dim = X.shape[-1]
286
+ self.cdfs = [
287
+ CDFNormalizer1d(self.X[:, i])
288
+ for i in range(self.dim)]
289
+
290
+ def wrap(self, fn_name, x):
291
+ shape = x.shape
292
+ x = x.reshape(-1, self.dim)
293
+ out = np.zeros_like(x)
294
+ for i, cdf in enumerate(self.cdfs):
295
+ fn = getattr(cdf, fn_name)
296
+ out[:, i] = fn(x[:, i])
297
+ return out.reshape(shape)
298
+
299
+ def normalize(self, x):
300
+ return self.wrap('normalize', x)
301
+
302
+ def unnormalize(self, x):
303
+ return self.wrap('unnormalize', x)
304
+
305
+
306
+ class GaussianNormalizer:
307
+ """
308
+ normalizes data to have zero mean and unit variance
309
+ """
310
+
311
+ def __init__(self, X):
312
+ self.X = X.astype(np.float32)
313
+ self.means, self.stds = X.mean(0), X.std(0)
314
+ self.stds[self.stds == 0] = 1.
315
+
316
+ def normalize(self, x):
317
+ return (x - self.means[None,]) / self.stds[None,]
318
+
319
+ def unnormalize(self, x):
320
+ return x * self.stds[None,] + self.means[None,]
321
+
322
+
323
+ class ImageNormalizer:
324
+ """
325
+ normalizes image data from range [0, 1] to [-1, 1].
326
+ """
327
+
328
+ def __init__(self):
329
+ pass
330
+
331
+ def normalize(self, x):
332
+ return x * 2.0 - 1.0
333
+
334
+ def unnormalize(self, x):
335
+ return (x + 1.0) / 2.0
336
+
337
+
338
+ class MinMaxNormalizer:
339
+ """
340
+ normalizes data through maximum and minimum expansion.
341
+ """
342
+
343
+ def __init__(self, X):
344
+ X = X.reshape(-1, X.shape[-1]).astype(np.float32)
345
+ self.min, self.max = np.min(X, axis=0), np.max(X, axis=0)
346
+ self.range = self.max - self.min
347
+ if np.any(self.range == 0):
348
+ self.range = self.max - self.min
349
+ print("Warning: Some features have the same min and max value. These will be set to 0.")
350
+ self.range[self.range == 0] = 1
351
+
352
+ def normalize(self, x):
353
+ x = x.astype(np.float32)
354
+ # nomalize to [0,1]
355
+ nx = (x - self.min) / self.range
356
+ # normalize to [-1, 1]
357
+ nx = nx * 2 - 1
358
+ return nx
359
+
360
+ def unnormalize(self, x):
361
+ x = x.astype(np.float32)
362
+ nx = (x + 1) / 2
363
+ x = nx * self.range + self.min
364
+ return x
365
+
366
+
367
+ class EmptyNormalizer:
368
+ """
369
+ do nothing and change nothing
370
+ """
371
+
372
+ def __init__(self):
373
+ pass
374
+
375
+ def normalize(self, x):
376
+ return x
377
+
378
+ def unnormalize(self, x):
379
+ return x
380
+
381
+
382
+ # -----------------------------------------------------------------------------#
383
+ # ------------------------------- useful tool ---------------------------------#
384
+ # -----------------------------------------------------------------------------#
385
+
386
+ def dict_apply(
387
+ x: Dict[str, torch.Tensor],
388
+ func: Callable[[torch.Tensor], torch.Tensor]
389
+ ) -> Dict[str, torch.Tensor]:
390
+ result = dict()
391
+ for key, value in x.items():
392
+ if isinstance(value, dict):
393
+ result[key] = dict_apply(value, func)
394
+ elif value is None:
395
+ result[key] = None
396
+ else:
397
+ result[key] = func(value)
398
+ return result
399
+
400
+
401
+ def loop_dataloader(dl):
402
+ while True:
403
+ for b in dl:
404
+ yield b
405
+
406
+ def loop_two_dataloaders(dl1, dl2):
407
+ while True:
408
+ for b1, b2 in zip(dl1, dl2):
409
+ yield b1, b2
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/imagecodecs.py ADDED
@@ -0,0 +1,1385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # imagecodecs/numcodecs.py
2
+
3
+ # Copyright (c) 2021-2022, Christoph Gohlke
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # 1. Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ #
12
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ #
16
+ # 3. Neither the name of the copyright holder nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+
32
+ """Additional numcodecs implemented using imagecodecs."""
33
+
34
+ __version__ = '2022.9.26'
35
+
36
+ __all__ = ('register_codecs',)
37
+
38
+ import numpy
39
+ from numcodecs.abc import Codec
40
+ from numcodecs.registry import register_codec, get_codec
41
+
42
+ import imagecodecs
43
+
44
+
45
+ def protective_squeeze(x: numpy.ndarray):
46
+ """
47
+ Squeeze dim only if it's not the last dim.
48
+ Image dim expected to be *, H, W, C
49
+ """
50
+ img_shape = x.shape[-3:]
51
+ if len(x.shape) > 3:
52
+ n_imgs = numpy.prod(x.shape[:-3])
53
+ if n_imgs > 1:
54
+ img_shape = (-1,) + img_shape
55
+ return x.reshape(img_shape)
56
+
57
+ def get_default_image_compressor(**kwargs):
58
+ if imagecodecs.JPEGXL:
59
+ # has JPEGXL
60
+ this_kwargs = {
61
+ 'effort': 3,
62
+ 'distance': 0.3,
63
+ # bug in libjxl, invalid codestream for non-lossless
64
+ # when decoding speed > 1
65
+ 'decodingspeed': 1
66
+ }
67
+ this_kwargs.update(kwargs)
68
+ return JpegXl(**this_kwargs)
69
+ else:
70
+ this_kwargs = {
71
+ 'level': 50
72
+ }
73
+ this_kwargs.update(kwargs)
74
+ return Jpeg2k(**this_kwargs)
75
+
76
+ class Aec(Codec):
77
+ """AEC codec for numcodecs."""
78
+
79
+ codec_id = 'imagecodecs_aec'
80
+
81
+ def __init__(
82
+ self, bitspersample=None, flags=None, blocksize=None, rsi=None
83
+ ):
84
+ self.bitspersample = bitspersample
85
+ self.flags = flags
86
+ self.blocksize = blocksize
87
+ self.rsi = rsi
88
+
89
+ def encode(self, buf):
90
+ return imagecodecs.aec_encode(
91
+ buf,
92
+ bitspersample=self.bitspersample,
93
+ flags=self.flags,
94
+ blocksize=self.blocksize,
95
+ rsi=self.rsi,
96
+ )
97
+
98
+ def decode(self, buf, out=None):
99
+ return imagecodecs.aec_decode(
100
+ buf,
101
+ bitspersample=self.bitspersample,
102
+ flags=self.flags,
103
+ blocksize=self.blocksize,
104
+ rsi=self.rsi,
105
+ out=_flat(out),
106
+ )
107
+
108
+
109
+ class Apng(Codec):
110
+ """APNG codec for numcodecs."""
111
+
112
+ codec_id = 'imagecodecs_apng'
113
+
114
+ def __init__(self, level=None, photometric=None, delay=None):
115
+ self.level = level
116
+ self.photometric = photometric
117
+ self.delay = delay
118
+
119
+ def encode(self, buf):
120
+ buf = protective_squeeze(numpy.asarray(buf))
121
+ return imagecodecs.apng_encode(
122
+ buf,
123
+ level=self.level,
124
+ photometric=self.photometric,
125
+ delay=self.delay,
126
+ )
127
+
128
+ def decode(self, buf, out=None):
129
+ return imagecodecs.apng_decode(buf, out=out)
130
+
131
+
132
+ class Avif(Codec):
133
+ """AVIF codec for numcodecs."""
134
+
135
+ codec_id = 'imagecodecs_avif'
136
+
137
+ def __init__(
138
+ self,
139
+ level=None,
140
+ speed=None,
141
+ tilelog2=None,
142
+ bitspersample=None,
143
+ pixelformat=None,
144
+ numthreads=None,
145
+ index=None,
146
+ ):
147
+ self.level = level
148
+ self.speed = speed
149
+ self.tilelog2 = tilelog2
150
+ self.bitspersample = bitspersample
151
+ self.pixelformat = pixelformat
152
+ self.numthreads = numthreads
153
+ self.index = index
154
+
155
+ def encode(self, buf):
156
+ buf = protective_squeeze(numpy.asarray(buf))
157
+ return imagecodecs.avif_encode(
158
+ buf,
159
+ level=self.level,
160
+ speed=self.speed,
161
+ tilelog2=self.tilelog2,
162
+ bitspersample=self.bitspersample,
163
+ pixelformat=self.pixelformat,
164
+ numthreads=self.numthreads,
165
+ )
166
+
167
+ def decode(self, buf, out=None):
168
+ return imagecodecs.avif_decode(
169
+ buf, index=self.index, numthreads=self.numthreads, out=out
170
+ )
171
+
172
+
173
+ class Bitorder(Codec):
174
+ """Bitorder codec for numcodecs."""
175
+
176
+ codec_id = 'imagecodecs_bitorder'
177
+
178
+ def encode(self, buf):
179
+ return imagecodecs.bitorder_encode(buf)
180
+
181
+ def decode(self, buf, out=None):
182
+ return imagecodecs.bitorder_decode(buf, out=_flat(out))
183
+
184
+
185
+ class Bitshuffle(Codec):
186
+ """Bitshuffle codec for numcodecs."""
187
+
188
+ codec_id = 'imagecodecs_bitshuffle'
189
+
190
+ def __init__(self, itemsize=1, blocksize=0):
191
+ self.itemsize = itemsize
192
+ self.blocksize = blocksize
193
+
194
+ def encode(self, buf):
195
+ return imagecodecs.bitshuffle_encode(
196
+ buf, itemsize=self.itemsize, blocksize=self.blocksize
197
+ ).tobytes()
198
+
199
+ def decode(self, buf, out=None):
200
+ return imagecodecs.bitshuffle_decode(
201
+ buf,
202
+ itemsize=self.itemsize,
203
+ blocksize=self.blocksize,
204
+ out=_flat(out),
205
+ )
206
+
207
+
208
+ class Blosc(Codec):
209
+ """Blosc codec for numcodecs."""
210
+
211
+ codec_id = 'imagecodecs_blosc'
212
+
213
+ def __init__(
214
+ self,
215
+ level=None,
216
+ compressor=None,
217
+ typesize=None,
218
+ blocksize=None,
219
+ shuffle=None,
220
+ numthreads=None,
221
+ ):
222
+ self.level = level
223
+ self.compressor = compressor
224
+ self.typesize = typesize
225
+ self.blocksize = blocksize
226
+ self.shuffle = shuffle
227
+ self.numthreads = numthreads
228
+
229
+ def encode(self, buf):
230
+ buf = protective_squeeze(numpy.asarray(buf))
231
+ return imagecodecs.blosc_encode(
232
+ buf,
233
+ level=self.level,
234
+ compressor=self.compressor,
235
+ typesize=self.typesize,
236
+ blocksize=self.blocksize,
237
+ shuffle=self.shuffle,
238
+ numthreads=self.numthreads,
239
+ )
240
+
241
+ def decode(self, buf, out=None):
242
+ return imagecodecs.blosc_decode(
243
+ buf, numthreads=self.numthreads, out=_flat(out)
244
+ )
245
+
246
+
247
+ class Blosc2(Codec):
248
+ """Blosc2 codec for numcodecs."""
249
+
250
+ codec_id = 'imagecodecs_blosc2'
251
+
252
+ def __init__(
253
+ self,
254
+ level=None,
255
+ compressor=None,
256
+ typesize=None,
257
+ blocksize=None,
258
+ shuffle=None,
259
+ numthreads=None,
260
+ ):
261
+ self.level = level
262
+ self.compressor = compressor
263
+ self.typesize = typesize
264
+ self.blocksize = blocksize
265
+ self.shuffle = shuffle
266
+ self.numthreads = numthreads
267
+
268
+ def encode(self, buf):
269
+ buf = protective_squeeze(numpy.asarray(buf))
270
+ return imagecodecs.blosc2_encode(
271
+ buf,
272
+ level=self.level,
273
+ compressor=self.compressor,
274
+ typesize=self.typesize,
275
+ blocksize=self.blocksize,
276
+ shuffle=self.shuffle,
277
+ numthreads=self.numthreads,
278
+ )
279
+
280
+ def decode(self, buf, out=None):
281
+ return imagecodecs.blosc2_decode(
282
+ buf, numthreads=self.numthreads, out=_flat(out)
283
+ )
284
+
285
+
286
+ class Brotli(Codec):
287
+ """Brotli codec for numcodecs."""
288
+
289
+ codec_id = 'imagecodecs_brotli'
290
+
291
+ def __init__(self, level=None, mode=None, lgwin=None):
292
+ self.level = level
293
+ self.mode = mode
294
+ self.lgwin = lgwin
295
+
296
+ def encode(self, buf):
297
+ return imagecodecs.brotli_encode(
298
+ buf, level=self.level, mode=self.mode, lgwin=self.lgwin
299
+ )
300
+
301
+ def decode(self, buf, out=None):
302
+ return imagecodecs.brotli_decode(buf, out=_flat(out))
303
+
304
+
305
+ class ByteShuffle(Codec):
306
+ """ByteShuffle codec for numcodecs."""
307
+
308
+ codec_id = 'imagecodecs_byteshuffle'
309
+
310
+ def __init__(
311
+ self, shape, dtype, axis=-1, dist=1, delta=False, reorder=False
312
+ ):
313
+ self.shape = tuple(shape)
314
+ self.dtype = numpy.dtype(dtype).str
315
+ self.axis = axis
316
+ self.dist = dist
317
+ self.delta = bool(delta)
318
+ self.reorder = bool(reorder)
319
+
320
+ def encode(self, buf):
321
+ buf = protective_squeeze(numpy.asarray(buf))
322
+ assert buf.shape == self.shape
323
+ assert buf.dtype == self.dtype
324
+ return imagecodecs.byteshuffle_encode(
325
+ buf,
326
+ axis=self.axis,
327
+ dist=self.dist,
328
+ delta=self.delta,
329
+ reorder=self.reorder,
330
+ ).tobytes()
331
+
332
+ def decode(self, buf, out=None):
333
+ if not isinstance(buf, numpy.ndarray):
334
+ buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
335
+ return imagecodecs.byteshuffle_decode(
336
+ buf,
337
+ axis=self.axis,
338
+ dist=self.dist,
339
+ delta=self.delta,
340
+ reorder=self.reorder,
341
+ out=out,
342
+ )
343
+
344
+
345
+ class Bz2(Codec):
346
+ """Bz2 codec for numcodecs."""
347
+
348
+ codec_id = 'imagecodecs_bz2'
349
+
350
+ def __init__(self, level=None):
351
+ self.level = level
352
+
353
+ def encode(self, buf):
354
+ return imagecodecs.bz2_encode(buf, level=self.level)
355
+
356
+ def decode(self, buf, out=None):
357
+ return imagecodecs.bz2_decode(buf, out=_flat(out))
358
+
359
+
360
+ class Cms(Codec):
361
+ """CMS codec for numcodecs."""
362
+
363
+ codec_id = 'imagecodecs_cms'
364
+
365
+ def __init__(self, *args, **kwargs):
366
+ pass
367
+
368
+ def encode(self, buf, out=None):
369
+ # return imagecodecs.cms_transform(buf)
370
+ raise NotImplementedError
371
+
372
+ def decode(self, buf, out=None):
373
+ # return imagecodecs.cms_transform(buf)
374
+ raise NotImplementedError
375
+
376
+
377
+ class Deflate(Codec):
378
+ """Deflate codec for numcodecs."""
379
+
380
+ codec_id = 'imagecodecs_deflate'
381
+
382
+ def __init__(self, level=None, raw=False):
383
+ self.level = level
384
+ self.raw = bool(raw)
385
+
386
+ def encode(self, buf):
387
+ return imagecodecs.deflate_encode(buf, level=self.level, raw=self.raw)
388
+
389
+ def decode(self, buf, out=None):
390
+ return imagecodecs.deflate_decode(buf, out=_flat(out), raw=self.raw)
391
+
392
+
393
+ class Delta(Codec):
394
+ """Delta codec for numcodecs."""
395
+
396
+ codec_id = 'imagecodecs_delta'
397
+
398
+ def __init__(self, shape=None, dtype=None, axis=-1, dist=1):
399
+ self.shape = None if shape is None else tuple(shape)
400
+ self.dtype = None if dtype is None else numpy.dtype(dtype).str
401
+ self.axis = axis
402
+ self.dist = dist
403
+
404
+ def encode(self, buf):
405
+ if self.shape is not None or self.dtype is not None:
406
+ buf = protective_squeeze(numpy.asarray(buf))
407
+ assert buf.shape == self.shape
408
+ assert buf.dtype == self.dtype
409
+ return imagecodecs.delta_encode(
410
+ buf, axis=self.axis, dist=self.dist
411
+ ).tobytes()
412
+
413
+ def decode(self, buf, out=None):
414
+ if self.shape is not None or self.dtype is not None:
415
+ buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
416
+ return imagecodecs.delta_decode(
417
+ buf, axis=self.axis, dist=self.dist, out=out
418
+ )
419
+
420
+
421
+ class Float24(Codec):
422
+ """Float24 codec for numcodecs."""
423
+
424
+ codec_id = 'imagecodecs_float24'
425
+
426
+ def __init__(self, byteorder=None, rounding=None):
427
+ self.byteorder = byteorder
428
+ self.rounding = rounding
429
+
430
+ def encode(self, buf):
431
+ buf = protective_squeeze(numpy.asarray(buf))
432
+ return imagecodecs.float24_encode(
433
+ buf, byteorder=self.byteorder, rounding=self.rounding
434
+ )
435
+
436
+ def decode(self, buf, out=None):
437
+ return imagecodecs.float24_decode(
438
+ buf, byteorder=self.byteorder, out=out
439
+ )
440
+
441
+
442
+ class FloatPred(Codec):
443
+ """Floating Point Predictor codec for numcodecs."""
444
+
445
+ codec_id = 'imagecodecs_floatpred'
446
+
447
+ def __init__(self, shape, dtype, axis=-1, dist=1):
448
+ self.shape = tuple(shape)
449
+ self.dtype = numpy.dtype(dtype).str
450
+ self.axis = axis
451
+ self.dist = dist
452
+
453
+ def encode(self, buf):
454
+ buf = protective_squeeze(numpy.asarray(buf))
455
+ assert buf.shape == self.shape
456
+ assert buf.dtype == self.dtype
457
+ return imagecodecs.floatpred_encode(
458
+ buf, axis=self.axis, dist=self.dist
459
+ ).tobytes()
460
+
461
+ def decode(self, buf, out=None):
462
+ if not isinstance(buf, numpy.ndarray):
463
+ buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
464
+ return imagecodecs.floatpred_decode(
465
+ buf, axis=self.axis, dist=self.dist, out=out
466
+ )
467
+
468
+
469
+ class Gif(Codec):
470
+ """GIF codec for numcodecs."""
471
+
472
+ codec_id = 'imagecodecs_gif'
473
+
474
+ def encode(self, buf):
475
+ buf = protective_squeeze(numpy.asarray(buf))
476
+ return imagecodecs.gif_encode(buf)
477
+
478
+ def decode(self, buf, out=None):
479
+ return imagecodecs.gif_decode(buf, asrgb=False, out=out)
480
+
481
+
482
+ class Heif(Codec):
483
+ """HEIF codec for numcodecs."""
484
+
485
+ codec_id = 'imagecodecs_heif'
486
+
487
+ def __init__(
488
+ self,
489
+ level=None,
490
+ bitspersample=None,
491
+ photometric=None,
492
+ compression=None,
493
+ numthreads=None,
494
+ index=None,
495
+ ):
496
+ self.level = level
497
+ self.bitspersample = bitspersample
498
+ self.photometric = photometric
499
+ self.compression = compression
500
+ self.numthreads = numthreads
501
+ self.index = index
502
+
503
+ def encode(self, buf):
504
+ buf = protective_squeeze(numpy.asarray(buf))
505
+ return imagecodecs.heif_encode(
506
+ buf,
507
+ level=self.level,
508
+ bitspersample=self.bitspersample,
509
+ photometric=self.photometric,
510
+ compression=self.compression,
511
+ numthreads=self.numthreads,
512
+ )
513
+
514
+ def decode(self, buf, out=None):
515
+ return imagecodecs.heif_decode(
516
+ buf,
517
+ index=self.index,
518
+ photometric=self.photometric,
519
+ numthreads=self.numthreads,
520
+ out=out,
521
+ )
522
+
523
+
524
+ class Jetraw(Codec):
525
+ """Jetraw codec for numcodecs."""
526
+
527
+ codec_id = 'imagecodecs_jetraw'
528
+
529
+ def __init__(
530
+ self,
531
+ shape,
532
+ identifier,
533
+ parameters=None,
534
+ verbosity=None,
535
+ errorbound=None,
536
+ ):
537
+ self.shape = shape
538
+ self.identifier = identifier
539
+ self.errorbound = errorbound
540
+ imagecodecs.jetraw_init(parameters, verbosity)
541
+
542
+ def encode(self, buf):
543
+ return imagecodecs.jetraw_encode(
544
+ buf, identifier=self.identifier, errorbound=self.errorbound
545
+ )
546
+
547
+ def decode(self, buf, out=None):
548
+ if out is None:
549
+ out = numpy.empty(self.shape, numpy.uint16)
550
+ return imagecodecs.jetraw_decode(buf, out=out)
551
+
552
+
553
+ class Jpeg(Codec):
554
+ """JPEG codec for numcodecs."""
555
+
556
+ codec_id = 'imagecodecs_jpeg'
557
+
558
+ def __init__(
559
+ self,
560
+ bitspersample=None,
561
+ tables=None,
562
+ header=None,
563
+ colorspace_data=None,
564
+ colorspace_jpeg=None,
565
+ level=None,
566
+ subsampling=None,
567
+ optimize=None,
568
+ smoothing=None,
569
+ ):
570
+ self.tables = tables
571
+ self.header = header
572
+ self.bitspersample = bitspersample
573
+ self.colorspace_data = colorspace_data
574
+ self.colorspace_jpeg = colorspace_jpeg
575
+ self.level = level
576
+ self.subsampling = subsampling
577
+ self.optimize = optimize
578
+ self.smoothing = smoothing
579
+
580
+ def encode(self, buf):
581
+ buf = protective_squeeze(numpy.asarray(buf))
582
+ return imagecodecs.jpeg_encode(
583
+ buf,
584
+ level=self.level,
585
+ colorspace=self.colorspace_data,
586
+ outcolorspace=self.colorspace_jpeg,
587
+ subsampling=self.subsampling,
588
+ optimize=self.optimize,
589
+ smoothing=self.smoothing,
590
+ )
591
+
592
+ def decode(self, buf, out=None):
593
+ out_shape = None
594
+ if out is not None:
595
+ out_shape = out.shape
596
+ out = protective_squeeze(out)
597
+ img = imagecodecs.jpeg_decode(
598
+ buf,
599
+ bitspersample=self.bitspersample,
600
+ tables=self.tables,
601
+ header=self.header,
602
+ colorspace=self.colorspace_jpeg,
603
+ outcolorspace=self.colorspace_data,
604
+ out=out,
605
+ )
606
+ if out_shape is not None:
607
+ img = img.reshape(out_shape)
608
+ return img
609
+
610
+ def get_config(self):
611
+ """Return dictionary holding configuration parameters."""
612
+ config = dict(id=self.codec_id)
613
+ for key in self.__dict__:
614
+ if not key.startswith('_'):
615
+ value = getattr(self, key)
616
+ if value is not None and key in ('header', 'tables'):
617
+ import base64
618
+
619
+ value = base64.b64encode(value).decode()
620
+ config[key] = value
621
+ return config
622
+
623
+ @classmethod
624
+ def from_config(cls, config):
625
+ """Instantiate codec from configuration object."""
626
+ for key in ('header', 'tables'):
627
+ value = config.get(key, None)
628
+ if value is not None and isinstance(value, str):
629
+ import base64
630
+
631
+ config[key] = base64.b64decode(value.encode())
632
+ return cls(**config)
633
+
634
+
635
+ class Jpeg2k(Codec):
636
+ """JPEG 2000 codec for numcodecs."""
637
+
638
+ codec_id = 'imagecodecs_jpeg2k'
639
+
640
+ def __init__(
641
+ self,
642
+ level=None,
643
+ codecformat=None,
644
+ colorspace=None,
645
+ tile=None,
646
+ reversible=None,
647
+ bitspersample=None,
648
+ resolutions=None,
649
+ numthreads=None,
650
+ verbose=0,
651
+ ):
652
+ self.level = level
653
+ self.codecformat = codecformat
654
+ self.colorspace = colorspace
655
+ self.tile = None if tile is None else tuple(tile)
656
+ self.reversible = reversible
657
+ self.bitspersample = bitspersample
658
+ self.resolutions = resolutions
659
+ self.numthreads = numthreads
660
+ self.verbose = verbose
661
+
662
+ def encode(self, buf):
663
+ buf = protective_squeeze(numpy.asarray(buf))
664
+ return imagecodecs.jpeg2k_encode(
665
+ buf,
666
+ level=self.level,
667
+ codecformat=self.codecformat,
668
+ colorspace=self.colorspace,
669
+ tile=self.tile,
670
+ reversible=self.reversible,
671
+ bitspersample=self.bitspersample,
672
+ resolutions=self.resolutions,
673
+ numthreads=self.numthreads,
674
+ verbose=self.verbose,
675
+ )
676
+
677
+ def decode(self, buf, out=None):
678
+ return imagecodecs.jpeg2k_decode(
679
+ buf, verbose=self.verbose, numthreads=self.numthreads, out=out
680
+ )
681
+
682
+
683
+ class JpegLs(Codec):
684
+ """JPEG LS codec for numcodecs."""
685
+
686
+ codec_id = 'imagecodecs_jpegls'
687
+
688
+ def __init__(self, level=None):
689
+ self.level = level
690
+
691
+ def encode(self, buf):
692
+ buf = protective_squeeze(numpy.asarray(buf))
693
+ return imagecodecs.jpegls_encode(buf, level=self.level)
694
+
695
+ def decode(self, buf, out=None):
696
+ return imagecodecs.jpegls_decode(buf, out=out)
697
+
698
+
699
+ class JpegXl(Codec):
700
+ """JPEG XL codec for numcodecs."""
701
+
702
+ codec_id = 'imagecodecs_jpegxl'
703
+
704
+ def __init__(
705
+ self,
706
+ # encode
707
+ level=None,
708
+ effort=None,
709
+ distance=None,
710
+ lossless=None,
711
+ decodingspeed=None,
712
+ photometric=None,
713
+ planar=None,
714
+ usecontainer=None,
715
+ # decode
716
+ index=None,
717
+ keeporientation=None,
718
+ # both
719
+ numthreads=None,
720
+ ):
721
+ """
722
+ Return JPEG XL image from numpy array.
723
+ Float must be in nominal range 0..1.
724
+
725
+ Currently L, LA, RGB, RGBA images are supported in contig mode.
726
+ Extra channels are only supported for grayscale images in planar mode.
727
+
728
+ Parameters
729
+ ----------
730
+ level : Default to None, i.e. not overwriting lossess and decodingspeed options.
731
+ When < 0: Use lossless compression
732
+ When in [0,1,2,3,4]: Sets the decoding speed tier for the provided options.
733
+ Minimum is 0 (slowest to decode, best quality/density), and maximum
734
+ is 4 (fastest to decode, at the cost of some quality/density).
735
+ effort : Default to 3.
736
+ Sets encoder effort/speed level without affecting decoding speed.
737
+ Valid values are, from faster to slower speed: 1:lightning 2:thunder
738
+ 3:falcon 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise.
739
+ Speed: lightning, thunder, falcon, cheetah, hare, wombat, squirrel, kitten, tortoise
740
+ control the encoder effort in ascending order.
741
+ This also affects memory usage: using lower effort will typically reduce memory
742
+ consumption during encoding.
743
+ lightning and thunder are fast modes useful for lossless mode (modular).
744
+ falcon disables all of the following tools.
745
+ cheetah enables coefficient reordering, context clustering, and heuristics for selecting DCT sizes and quantization steps.
746
+ hare enables Gaborish filtering, chroma from luma, and an initial estimate of quantization steps.
747
+ wombat enables error diffusion quantization and full DCT size selection heuristics.
748
+ squirrel (default) enables dots, patches, and spline detection, and full context clustering.
749
+ kitten optimizes the adaptive quantization for a psychovisual metric.
750
+ tortoise enables a more thorough adaptive quantization search.
751
+ distance : Default to 1.0
752
+ Sets the distance level for lossy compression: target max butteraugli distance,
753
+ lower = higher quality. Range: 0 .. 15. 0.0 = mathematically lossless
754
+ (however, use JxlEncoderSetFrameLossless instead to use true lossless,
755
+ as setting distance to 0 alone is not the only requirement).
756
+ 1.0 = visually lossless. Recommended range: 0.5 .. 3.0.
757
+ lossess : Default to False.
758
+ Use lossess encoding.
759
+ decodingspeed : Default to 0.
760
+ Duplicate to level. [0,4]
761
+ photometric : Return JxlColorSpace value.
762
+ Default logic is quite complicated but works most of the time.
763
+ Accepted value:
764
+ int: [-1,3]
765
+ str: ['RGB',
766
+ 'WHITEISZERO', 'MINISWHITE',
767
+ 'BLACKISZERO', 'MINISBLACK', 'GRAY',
768
+ 'XYB', 'KNOWN']
769
+ planar : Enable multi-channel mode.
770
+ Default to false.
771
+ usecontainer :
772
+ Forces the encoder to use the box-based container format (BMFF)
773
+ even when not necessary.
774
+ When using JxlEncoderUseBoxes, JxlEncoderStoreJPEGMetadata or
775
+ JxlEncoderSetCodestreamLevel with level 10, the encoder will
776
+ automatically also use the container format, it is not necessary
777
+ to use JxlEncoderUseContainer for those use cases.
778
+ By default this setting is disabled.
779
+ index : Selectively decode frames for animation.
780
+ Default to 0, decode all frames.
781
+ When set to > 0, decode that frame index only.
782
+ keeporientation :
783
+ Enables or disables preserving of as-in-bitstream pixeldata orientation.
784
+ Some images are encoded with an Orientation tag indicating that the
785
+ decoder must perform a rotation and/or mirroring to the encoded image data.
786
+
787
+ If skip_reorientation is JXL_FALSE (the default): the decoder will apply
788
+ the transformation from the orientation setting, hence rendering the image
789
+ according to its specified intent. When producing a JxlBasicInfo, the decoder
790
+ will always set the orientation field to JXL_ORIENT_IDENTITY (matching the
791
+ returned pixel data) and also align xsize and ysize so that they correspond
792
+ to the width and the height of the returned pixel data.
793
+
794
+ If skip_reorientation is JXL_TRUE: the decoder will skip applying the
795
+ transformation from the orientation setting, returning the image in
796
+ the as-in-bitstream pixeldata orientation. This may be faster to decode
797
+ since the decoder doesnt have to apply the transformation, but can
798
+ cause wrong display of the image if the orientation tag is not correctly
799
+ taken into account by the user.
800
+
801
+ By default, this option is disabled, and the returned pixel data is
802
+ re-oriented according to the images Orientation setting.
803
+ threads : Default to 1.
804
+ If <= 0, use all cores.
805
+ If > 32, clipped to 32.
806
+ """
807
+
808
+ self.level = level
809
+ self.effort = effort
810
+ self.distance = distance
811
+ self.lossless = bool(lossless)
812
+ self.decodingspeed = decodingspeed
813
+ self.photometric = photometric
814
+ self.planar = planar
815
+ self.usecontainer = usecontainer
816
+ self.index = index
817
+ self.keeporientation = keeporientation
818
+ self.numthreads = numthreads
819
+
820
+ def encode(self, buf):
821
+ # TODO: only squeeze all but last dim
822
+ buf = protective_squeeze(numpy.asarray(buf))
823
+ return imagecodecs.jpegxl_encode(
824
+ buf,
825
+ level=self.level,
826
+ effort=self.effort,
827
+ distance=self.distance,
828
+ lossless=self.lossless,
829
+ decodingspeed=self.decodingspeed,
830
+ photometric=self.photometric,
831
+ planar=self.planar,
832
+ usecontainer=self.usecontainer,
833
+ numthreads=self.numthreads,
834
+ )
835
+
836
+ def decode(self, buf, out=None):
837
+ return imagecodecs.jpegxl_decode(
838
+ buf,
839
+ index=self.index,
840
+ keeporientation=self.keeporientation,
841
+ numthreads=self.numthreads,
842
+ out=out,
843
+ )
844
+
845
+
846
+ class JpegXr(Codec):
847
+ """JPEG XR codec for numcodecs."""
848
+
849
+ codec_id = 'imagecodecs_jpegxr'
850
+
851
+ def __init__(
852
+ self,
853
+ level=None,
854
+ photometric=None,
855
+ hasalpha=None,
856
+ resolution=None,
857
+ fp2int=None,
858
+ ):
859
+ self.level = level
860
+ self.photometric = photometric
861
+ self.hasalpha = hasalpha
862
+ self.resolution = resolution
863
+ self.fp2int = fp2int
864
+
865
+ def encode(self, buf):
866
+ buf = protective_squeeze(numpy.asarray(buf))
867
+ return imagecodecs.jpegxr_encode(
868
+ buf,
869
+ level=self.level,
870
+ photometric=self.photometric,
871
+ hasalpha=self.hasalpha,
872
+ resolution=self.resolution,
873
+ )
874
+
875
+ def decode(self, buf, out=None):
876
+ return imagecodecs.jpegxr_decode(buf, fp2int=self.fp2int, out=out)
877
+
878
+
879
+ class Lerc(Codec):
880
+ """LERC codec for numcodecs."""
881
+
882
+ codec_id = 'imagecodecs_lerc'
883
+
884
+ def __init__(self, level=None, version=None, planar=None):
885
+ self.level = level
886
+ self.version = version
887
+ self.planar = bool(planar)
888
+ # TODO: support mask?
889
+ # self.mask = None
890
+
891
+ def encode(self, buf):
892
+ buf = protective_squeeze(numpy.asarray(buf))
893
+ return imagecodecs.lerc_encode(
894
+ buf,
895
+ level=self.level,
896
+ version=self.version,
897
+ planar=self.planar,
898
+ )
899
+
900
+ def decode(self, buf, out=None):
901
+ return imagecodecs.lerc_decode(buf, out=out)
902
+
903
+
904
+ class Ljpeg(Codec):
905
+ """LJPEG codec for numcodecs."""
906
+
907
+ codec_id = 'imagecodecs_ljpeg'
908
+
909
+ def __init__(self, bitspersample=None):
910
+ self.bitspersample = bitspersample
911
+
912
+ def encode(self, buf):
913
+ buf = protective_squeeze(numpy.asarray(buf))
914
+ return imagecodecs.ljpeg_encode(buf, bitspersample=self.bitspersample)
915
+
916
+ def decode(self, buf, out=None):
917
+ return imagecodecs.ljpeg_decode(buf, out=out)
918
+
919
+
920
+ class Lz4(Codec):
921
+ """LZ4 codec for numcodecs."""
922
+
923
+ codec_id = 'imagecodecs_lz4'
924
+
925
+ def __init__(self, level=None, hc=False, header=True):
926
+ self.level = level
927
+ self.hc = hc
928
+ self.header = bool(header)
929
+
930
+ def encode(self, buf):
931
+ return imagecodecs.lz4_encode(
932
+ buf, level=self.level, hc=self.hc, header=self.header
933
+ )
934
+
935
+ def decode(self, buf, out=None):
936
+ return imagecodecs.lz4_decode(buf, header=self.header, out=_flat(out))
937
+
938
+
939
+ class Lz4f(Codec):
940
+ """LZ4F codec for numcodecs."""
941
+
942
+ codec_id = 'imagecodecs_lz4f'
943
+
944
+ def __init__(
945
+ self,
946
+ level=None,
947
+ blocksizeid=False,
948
+ contentchecksum=None,
949
+ blockchecksum=None,
950
+ ):
951
+ self.level = level
952
+ self.blocksizeid = blocksizeid
953
+ self.contentchecksum = contentchecksum
954
+ self.blockchecksum = blockchecksum
955
+
956
+ def encode(self, buf):
957
+ return imagecodecs.lz4f_encode(
958
+ buf,
959
+ level=self.level,
960
+ blocksizeid=self.blocksizeid,
961
+ contentchecksum=self.contentchecksum,
962
+ blockchecksum=self.blockchecksum,
963
+ )
964
+
965
+ def decode(self, buf, out=None):
966
+ return imagecodecs.lz4f_decode(buf, out=_flat(out))
967
+
968
+
969
+ class Lzf(Codec):
970
+ """LZF codec for numcodecs."""
971
+
972
+ codec_id = 'imagecodecs_lzf'
973
+
974
+ def __init__(self, header=True):
975
+ self.header = bool(header)
976
+
977
+ def encode(self, buf):
978
+ return imagecodecs.lzf_encode(buf, header=self.header)
979
+
980
+ def decode(self, buf, out=None):
981
+ return imagecodecs.lzf_decode(buf, header=self.header, out=_flat(out))
982
+
983
+
984
+ class Lzma(Codec):
985
+ """LZMA codec for numcodecs."""
986
+
987
+ codec_id = 'imagecodecs_lzma'
988
+
989
+ def __init__(self, level=None):
990
+ self.level = level
991
+
992
+ def encode(self, buf):
993
+ return imagecodecs.lzma_encode(buf, level=self.level)
994
+
995
+ def decode(self, buf, out=None):
996
+ return imagecodecs.lzma_decode(buf, out=_flat(out))
997
+
998
+
999
+ class Lzw(Codec):
1000
+ """LZW codec for numcodecs."""
1001
+
1002
+ codec_id = 'imagecodecs_lzw'
1003
+
1004
+ def encode(self, buf):
1005
+ return imagecodecs.lzw_encode(buf)
1006
+
1007
+ def decode(self, buf, out=None):
1008
+ return imagecodecs.lzw_decode(buf, out=_flat(out))
1009
+
1010
+
1011
+ class PackBits(Codec):
1012
+ """PackBits codec for numcodecs."""
1013
+
1014
+ codec_id = 'imagecodecs_packbits'
1015
+
1016
+ def __init__(self, axis=None):
1017
+ self.axis = axis
1018
+
1019
+ def encode(self, buf):
1020
+ if not isinstance(buf, (bytes, bytearray)):
1021
+ buf = protective_squeeze(numpy.asarray(buf))
1022
+ return imagecodecs.packbits_encode(buf, axis=self.axis)
1023
+
1024
+ def decode(self, buf, out=None):
1025
+ return imagecodecs.packbits_decode(buf, out=_flat(out))
1026
+
1027
+
1028
+ class Pglz(Codec):
1029
+ """PGLZ codec for numcodecs."""
1030
+
1031
+ codec_id = 'imagecodecs_pglz'
1032
+
1033
+ def __init__(self, header=True, strategy=None):
1034
+ self.header = bool(header)
1035
+ self.strategy = strategy
1036
+
1037
+ def encode(self, buf):
1038
+ return imagecodecs.pglz_encode(
1039
+ buf, strategy=self.strategy, header=self.header
1040
+ )
1041
+
1042
+ def decode(self, buf, out=None):
1043
+ return imagecodecs.pglz_decode(buf, header=self.header, out=_flat(out))
1044
+
1045
+
1046
+ class Png(Codec):
1047
+ """PNG codec for numcodecs."""
1048
+
1049
+ codec_id = 'imagecodecs_png'
1050
+
1051
+ def __init__(self, level=None):
1052
+ self.level = level
1053
+
1054
+ def encode(self, buf):
1055
+ buf = protective_squeeze(numpy.asarray(buf))
1056
+ return imagecodecs.png_encode(buf, level=self.level)
1057
+
1058
+ def decode(self, buf, out=None):
1059
+ return imagecodecs.png_decode(buf, out=out)
1060
+
1061
+
1062
+ class Qoi(Codec):
1063
+ """QOI codec for numcodecs."""
1064
+
1065
+ codec_id = 'imagecodecs_qoi'
1066
+
1067
+ def __init__(self):
1068
+ pass
1069
+
1070
+ def encode(self, buf):
1071
+ buf = protective_squeeze(numpy.asarray(buf))
1072
+ return imagecodecs.qoi_encode(buf)
1073
+
1074
+ def decode(self, buf, out=None):
1075
+ return imagecodecs.qoi_decode(buf, out=out)
1076
+
1077
+
1078
+ class Rgbe(Codec):
1079
+ """RGBE codec for numcodecs."""
1080
+
1081
+ codec_id = 'imagecodecs_rgbe'
1082
+
1083
+ def __init__(self, header=False, shape=None, rle=None):
1084
+ if not header and shape is None:
1085
+ raise ValueError('must specify data shape if no header')
1086
+ if shape and shape[-1] != 3:
1087
+ raise ValueError('invalid shape')
1088
+ self.shape = shape
1089
+ self.header = bool(header)
1090
+ self.rle = None if rle is None else bool(rle)
1091
+
1092
+ def encode(self, buf):
1093
+ buf = protective_squeeze(numpy.asarray(buf))
1094
+ return imagecodecs.rgbe_encode(buf, header=self.header, rle=self.rle)
1095
+
1096
+ def decode(self, buf, out=None):
1097
+ if out is None and not self.header:
1098
+ out = numpy.empty(self.shape, numpy.float32)
1099
+ return imagecodecs.rgbe_decode(
1100
+ buf, header=self.header, rle=self.rle, out=out
1101
+ )
1102
+
1103
+
1104
+ class Rcomp(Codec):
1105
+ """Rcomp codec for numcodecs."""
1106
+
1107
+ codec_id = 'imagecodecs_rcomp'
1108
+
1109
+ def __init__(self, shape, dtype, nblock=None):
1110
+ self.shape = tuple(shape)
1111
+ self.dtype = numpy.dtype(dtype).str
1112
+ self.nblock = nblock
1113
+
1114
+ def encode(self, buf):
1115
+ return imagecodecs.rcomp_encode(buf, nblock=self.nblock)
1116
+
1117
+ def decode(self, buf, out=None):
1118
+ return imagecodecs.rcomp_decode(
1119
+ buf,
1120
+ shape=self.shape,
1121
+ dtype=self.dtype,
1122
+ nblock=self.nblock,
1123
+ out=out,
1124
+ )
1125
+
1126
+
1127
+ class Snappy(Codec):
1128
+ """Snappy codec for numcodecs."""
1129
+
1130
+ codec_id = 'imagecodecs_snappy'
1131
+
1132
+ def encode(self, buf):
1133
+ return imagecodecs.snappy_encode(buf)
1134
+
1135
+ def decode(self, buf, out=None):
1136
+ return imagecodecs.snappy_decode(buf, out=_flat(out))
1137
+
1138
+
1139
+ class Spng(Codec):
1140
+ """SPNG codec for numcodecs."""
1141
+
1142
+ codec_id = 'imagecodecs_spng'
1143
+
1144
+ def __init__(self, level=None):
1145
+ self.level = level
1146
+
1147
+ def encode(self, buf):
1148
+ buf = protective_squeeze(numpy.asarray(buf))
1149
+ return imagecodecs.spng_encode(buf, level=self.level)
1150
+
1151
+ def decode(self, buf, out=None):
1152
+ return imagecodecs.spng_decode(buf, out=out)
1153
+
1154
+
1155
+ class Tiff(Codec):
1156
+ """TIFF codec for numcodecs."""
1157
+
1158
+ codec_id = 'imagecodecs_tiff'
1159
+
1160
+ def __init__(self, index=None, asrgb=None, verbose=0):
1161
+ self.index = index
1162
+ self.asrgb = bool(asrgb)
1163
+ self.verbose = verbose
1164
+
1165
+ def encode(self, buf):
1166
+ # TODO: not implemented
1167
+ buf = protective_squeeze(numpy.asarray(buf))
1168
+ return imagecodecs.tiff_encode(buf)
1169
+
1170
+ def decode(self, buf, out=None):
1171
+ return imagecodecs.tiff_decode(
1172
+ buf,
1173
+ index=self.index,
1174
+ asrgb=self.asrgb,
1175
+ verbose=self.verbose,
1176
+ out=out,
1177
+ )
1178
+
1179
+
1180
+ class Webp(Codec):
1181
+ """WebP codec for numcodecs."""
1182
+
1183
+ codec_id = 'imagecodecs_webp'
1184
+
1185
+ def __init__(self, level=None, lossless=None, method=None, hasalpha=None):
1186
+ self.level = level
1187
+ self.hasalpha = bool(hasalpha)
1188
+ self.method = method
1189
+ self.lossless = lossless
1190
+
1191
+ def encode(self, buf):
1192
+ buf = protective_squeeze(numpy.asarray(buf))
1193
+ return imagecodecs.webp_encode(
1194
+ buf, level=self.level, lossless=self.lossless, method=self.method
1195
+ )
1196
+
1197
+ def decode(self, buf, out=None):
1198
+ return imagecodecs.webp_decode(buf, hasalpha=self.hasalpha, out=out)
1199
+
1200
+
1201
+ class Xor(Codec):
1202
+ """XOR codec for numcodecs."""
1203
+
1204
+ codec_id = 'imagecodecs_xor'
1205
+
1206
+ def __init__(self, shape=None, dtype=None, axis=-1):
1207
+ self.shape = None if shape is None else tuple(shape)
1208
+ self.dtype = None if dtype is None else numpy.dtype(dtype).str
1209
+ self.axis = axis
1210
+
1211
+ def encode(self, buf):
1212
+ if self.shape is not None or self.dtype is not None:
1213
+ buf = protective_squeeze(numpy.asarray(buf))
1214
+ assert buf.shape == self.shape
1215
+ assert buf.dtype == self.dtype
1216
+ return imagecodecs.xor_encode(buf, axis=self.axis).tobytes()
1217
+
1218
+ def decode(self, buf, out=None):
1219
+ if self.shape is not None or self.dtype is not None:
1220
+ buf = numpy.frombuffer(buf, dtype=self.dtype).reshape(*self.shape)
1221
+ return imagecodecs.xor_decode(buf, axis=self.axis, out=_flat(out))
1222
+
1223
+
1224
+ class Zfp(Codec):
1225
+ """ZFP codec for numcodecs."""
1226
+
1227
+ codec_id = 'imagecodecs_zfp'
1228
+
1229
+ def __init__(
1230
+ self,
1231
+ shape=None,
1232
+ dtype=None,
1233
+ strides=None,
1234
+ level=None,
1235
+ mode=None,
1236
+ execution=None,
1237
+ numthreads=None,
1238
+ chunksize=None,
1239
+ header=True,
1240
+ ):
1241
+ if header:
1242
+ self.shape = None
1243
+ self.dtype = None
1244
+ self.strides = None
1245
+ elif shape is None or dtype is None:
1246
+ raise ValueError('invalid shape or dtype')
1247
+ else:
1248
+ self.shape = tuple(shape)
1249
+ self.dtype = numpy.dtype(dtype).str
1250
+ self.strides = None if strides is None else tuple(strides)
1251
+ self.level = level
1252
+ self.mode = mode
1253
+ self.execution = execution
1254
+ self.numthreads = numthreads
1255
+ self.chunksize = chunksize
1256
+ self.header = bool(header)
1257
+
1258
+ def encode(self, buf):
1259
+ buf = protective_squeeze(numpy.asarray(buf))
1260
+ if not self.header:
1261
+ assert buf.shape == self.shape
1262
+ assert buf.dtype == self.dtype
1263
+ return imagecodecs.zfp_encode(
1264
+ buf,
1265
+ level=self.level,
1266
+ mode=self.mode,
1267
+ execution=self.execution,
1268
+ header=self.header,
1269
+ numthreads=self.numthreads,
1270
+ chunksize=self.chunksize,
1271
+ )
1272
+
1273
+ def decode(self, buf, out=None):
1274
+ if self.header:
1275
+ return imagecodecs.zfp_decode(buf, out=out)
1276
+ return imagecodecs.zfp_decode(
1277
+ buf,
1278
+ shape=self.shape,
1279
+ dtype=numpy.dtype(self.dtype),
1280
+ strides=self.strides,
1281
+ numthreads=self.numthreads,
1282
+ out=out,
1283
+ )
1284
+
1285
+
1286
+ class Zlib(Codec):
1287
+ """Zlib codec for numcodecs."""
1288
+
1289
+ codec_id = 'imagecodecs_zlib'
1290
+
1291
+ def __init__(self, level=None):
1292
+ self.level = level
1293
+
1294
+ def encode(self, buf):
1295
+ return imagecodecs.zlib_encode(buf, level=self.level)
1296
+
1297
+ def decode(self, buf, out=None):
1298
+ return imagecodecs.zlib_decode(buf, out=_flat(out))
1299
+
1300
+
1301
+ class Zlibng(Codec):
1302
+ """Zlibng codec for numcodecs."""
1303
+
1304
+ codec_id = 'imagecodecs_zlibng'
1305
+
1306
+ def __init__(self, level=None):
1307
+ self.level = level
1308
+
1309
+ def encode(self, buf):
1310
+ return imagecodecs.zlibng_encode(buf, level=self.level)
1311
+
1312
+ def decode(self, buf, out=None):
1313
+ return imagecodecs.zlibng_decode(buf, out=_flat(out))
1314
+
1315
+
1316
+ class Zopfli(Codec):
1317
+ """Zopfli codec for numcodecs."""
1318
+
1319
+ codec_id = 'imagecodecs_zopfli'
1320
+
1321
+ def encode(self, buf):
1322
+ return imagecodecs.zopfli_encode(buf)
1323
+
1324
+ def decode(self, buf, out=None):
1325
+ return imagecodecs.zopfli_decode(buf, out=_flat(out))
1326
+
1327
+
1328
+ class Zstd(Codec):
1329
+ """ZStandard codec for numcodecs."""
1330
+
1331
+ codec_id = 'imagecodecs_zstd'
1332
+
1333
+ def __init__(self, level=None):
1334
+ self.level = level
1335
+
1336
+ def encode(self, buf):
1337
+ return imagecodecs.zstd_encode(buf, level=self.level)
1338
+
1339
+ def decode(self, buf, out=None):
1340
+ return imagecodecs.zstd_decode(buf, out=_flat(out))
1341
+
1342
+
1343
+ def _flat(out):
1344
+ """Return numpy array as contiguous view of bytes if possible."""
1345
+ if out is None:
1346
+ return None
1347
+ view = memoryview(out)
1348
+ if view.readonly or not view.contiguous:
1349
+ return None
1350
+ return view.cast('B')
1351
+
1352
+
1353
+ def register_codecs(codecs=None, force=False, verbose=True):
1354
+ """Register codecs in this module with numcodecs."""
1355
+ for name, cls in globals().items():
1356
+ if not hasattr(cls, 'codec_id') or name == 'Codec':
1357
+ continue
1358
+ if codecs is not None and cls.codec_id not in codecs:
1359
+ continue
1360
+ try:
1361
+ try:
1362
+ get_codec({'id': cls.codec_id})
1363
+ except TypeError:
1364
+ # registered, but failed
1365
+ pass
1366
+ except ValueError:
1367
+ # not registered yet
1368
+ pass
1369
+ else:
1370
+ if not force:
1371
+ if verbose:
1372
+ log_warning(
1373
+ f'numcodec {cls.codec_id!r} already registered'
1374
+ )
1375
+ continue
1376
+ if verbose:
1377
+ log_warning(f'replacing registered numcodec {cls.codec_id!r}')
1378
+ register_codec(cls)
1379
+
1380
+
1381
+ def log_warning(msg, *args, **kwargs):
1382
+ """Log message with level WARNING."""
1383
+ import logging
1384
+
1385
+ logging.getLogger(__name__).warning(msg, *args, **kwargs)
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/kitchen_dataset.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import pathlib
3
+ from typing import Dict
4
+
5
+ import numpy as np
6
+ import torch
7
+ from tqdm import tqdm
8
+
9
+ from cleandiffuser.dataset.base_dataset import BaseDataset
10
+ from cleandiffuser.dataset.dataset_utils import SequenceSampler, dict_apply
11
+ from cleandiffuser.dataset.replay_buffer import ReplayBuffer
12
+ from cleandiffuser.env.kitchen.kitchen_util import parse_mjl_logs
13
+ from cleandiffuser.utils import EmptyNormalizer, MinMaxNormalizer
14
+
15
+
16
+ class KitchenDataset(BaseDataset):
17
+ def __init__(self,
18
+ dataset_dir,
19
+ horizon=1,
20
+ pad_before=0,
21
+ pad_after=0,
22
+ ):
23
+ super().__init__()
24
+
25
+ data_directory = pathlib.Path(dataset_dir)
26
+ observations = np.load(data_directory / "observations_seq.npy")
27
+ actions = np.load(data_directory / "actions_seq.npy")
28
+ masks = np.load(data_directory / "existence_mask.npy")
29
+
30
+ self.replay_buffer = ReplayBuffer.create_empty_numpy()
31
+ for i in range(len(masks)):
32
+ eps_len = int(masks[i].sum())
33
+ obs = observations[i, :eps_len].astype(np.float32)
34
+ action = actions[i, :eps_len].astype(np.float32)
35
+ data = {
36
+ 'state': obs,
37
+ 'action': action
38
+ }
39
+ self.replay_buffer.add_episode(data)
40
+
41
+ self.sampler = SequenceSampler(
42
+ replay_buffer=self.replay_buffer,
43
+ sequence_length=horizon,
44
+ pad_before=pad_before,
45
+ pad_after=pad_after)
46
+
47
+ self.horizon = horizon
48
+ self.pad_before = pad_before
49
+ self.pad_after = pad_after
50
+
51
+ self.normalizer = self.get_normalizer()
52
+
53
+ def get_normalizer(self):
54
+ state_normalizer = MinMaxNormalizer(
55
+ self.replay_buffer['state'][:]) # (N, obs_dim)
56
+ action_normalizer = MinMaxNormalizer(
57
+ self.replay_buffer['action'][:]) # (N, action_dim)
58
+ return {
59
+ "obs": {
60
+ "state": state_normalizer
61
+ },
62
+ "action": action_normalizer
63
+ }
64
+
65
+ def sample_to_data(self, sample):
66
+ state = sample['state'].astype(np.float32)
67
+ state = self.normalizer['obs']['state'].normalize(state)
68
+
69
+ action = sample['action'].astype(np.float32)
70
+ action = self.normalizer['action'].normalize(action)
71
+ data = {
72
+ 'obs': {
73
+ 'state': state,
74
+ },
75
+ 'action': action,
76
+ }
77
+ return data
78
+
79
+ def __str__(self) -> str:
80
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
81
+
82
+ def __len__(self) -> int:
83
+ return len(self.sampler)
84
+
85
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
86
+ sample = self.sampler.sample_sequence(idx)
87
+ data = self.sample_to_data(sample)
88
+ torch_data = dict_apply(data, torch.tensor)
89
+ return torch_data
90
+
91
+
92
+ class KitchenDatasetV2(BaseDataset):
93
+ def __init__(self,
94
+ dataset_dir,
95
+ horizon=1,
96
+ pad_before=0,
97
+ pad_after=0,
98
+ ):
99
+ super().__init__()
100
+
101
+ data_directory = pathlib.Path(dataset_dir)
102
+ observations = np.load(
103
+ data_directory / "observations_seq.npy").astype(np.float32)
104
+ actions = np.load(
105
+ data_directory / "actions_seq.npy").astype(np.float32)
106
+ masks = np.load(data_directory / "existence_mask.npy").astype(np.int64)
107
+
108
+ self.state_normalizer = MinMaxNormalizer(observations)
109
+ self.action_normalizer = MinMaxNormalizer(actions)
110
+
111
+ normed_observations = self.state_normalizer.normalize(observations)
112
+ normed_actions = self.action_normalizer.normalize(actions)
113
+
114
+ self.replay_buffer = ReplayBuffer.create_empty_numpy()
115
+ for i in range(len(masks)):
116
+ eps_len = int(masks[i].sum())
117
+ obs = normed_observations[i, :eps_len]
118
+ action = normed_actions[i, :eps_len]
119
+ data = {'state': obs, 'action': action}
120
+ self.replay_buffer.add_episode(data)
121
+
122
+ self.sampler = SequenceSampler(
123
+ replay_buffer=self.replay_buffer,
124
+ sequence_length=horizon,
125
+ pad_before=pad_before,
126
+ pad_after=pad_after)
127
+
128
+ self.horizon = horizon
129
+ self.pad_before = pad_before
130
+ self.pad_after = pad_after
131
+
132
+ self.normalizer = self.get_normalizer()
133
+ self.obs_dim, self.act_dim = observations.shape[-1], actions.shape[-1]
134
+
135
+ self.normed_observations = normed_observations[np.where(masks)].reshape(-1, self.obs_dim)
136
+ self.normed_actions = normed_actions[np.where(masks)].reshape(-1, self.act_dim)
137
+ self.observations = observations
138
+ self.actions = actions
139
+
140
+ def get_normalizer(self):
141
+ return {
142
+ "state": self.state_normalizer,
143
+ "action": self.action_normalizer}
144
+
145
+ def __str__(self) -> str:
146
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
147
+
148
+ def __len__(self) -> int:
149
+ return len(self.sampler)
150
+
151
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
152
+ data = self.sampler.sample_sequence(idx)
153
+ torch_data = dict_apply(data, torch.tensor)
154
+ return torch_data
155
+
156
+
157
+ class KitchenMjlDataset(BaseDataset):
158
+ def __init__(self,
159
+ dataset_dir,
160
+ horizon=1,
161
+ pad_before=0,
162
+ pad_after=0,
163
+ abs_action=True,
164
+ robot_noise_ratio=0.1,
165
+ ):
166
+ super().__init__()
167
+
168
+ data_directory = pathlib.Path(dataset_dir)
169
+ robot_pos_noise_amp = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
170
+ 0.1, 0.005, 0.005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005,
171
+ 0.0005, 0.005, 0.005, 0.005, 0.1, 0.1, 0.1, 0.005,
172
+ 0.005, 0.005, 0.1, 0.1, 0.1, 0.005], dtype=np.float32)
173
+ rng = np.random.default_rng(seed=42)
174
+
175
+ data_directory = pathlib.Path(dataset_dir)
176
+ self.replay_buffer = ReplayBuffer.create_empty_numpy()
177
+ for i, mjl_path in enumerate(tqdm(list(data_directory.glob('*/*.mjl')))):
178
+ try:
179
+ data = parse_mjl_logs(str(mjl_path.absolute()), skipamount=40)
180
+ qpos = data['qpos'].astype(np.float32)
181
+ obs = np.concatenate([
182
+ qpos[:, :9],
183
+ qpos[:, -21:],
184
+ np.zeros((len(qpos), 30), dtype=np.float32)
185
+ ], axis=-1)
186
+ if robot_noise_ratio > 0:
187
+ # add observation noise to match real robot
188
+ noise = robot_noise_ratio * robot_pos_noise_amp * rng.uniform(
189
+ low=-1., high=1., size=(obs.shape[0], 30))
190
+ obs[:, :30] += noise
191
+ episode = {
192
+ 'state': obs,
193
+ 'action': data['ctrl'].astype(np.float32)
194
+ }
195
+ self.replay_buffer.add_episode(episode)
196
+ except Exception as e:
197
+ print(i, e)
198
+
199
+ self.sampler = SequenceSampler(
200
+ replay_buffer=self.replay_buffer,
201
+ sequence_length=horizon,
202
+ pad_before=pad_before,
203
+ pad_after=pad_after)
204
+
205
+ self.horizon = horizon
206
+ self.pad_before = pad_before
207
+ self.pad_after = pad_after
208
+
209
+ self.normalizer = self.get_normalizer()
210
+
211
+ def get_normalizer(self):
212
+ state_normalizer = MinMaxNormalizer(
213
+ self.replay_buffer['state'][:]) # (N, obs_dim)
214
+ action_normalizer = MinMaxNormalizer(
215
+ self.replay_buffer['action'][:]) # (N, action_dim)
216
+ return {
217
+ "obs": {
218
+ "state": state_normalizer
219
+ },
220
+ "action": action_normalizer
221
+ }
222
+
223
+ def sample_to_data(self, sample):
224
+ state = sample['state'].astype(np.float32)
225
+ state = self.normalizer['obs']['state'].normalize(state)
226
+
227
+ action = sample['action'].astype(np.float32)
228
+ action = self.normalizer['action'].normalize(action)
229
+ data = {
230
+ 'obs': {
231
+ 'state': state,
232
+ },
233
+ 'action': action,
234
+ }
235
+ return data
236
+
237
+ def __str__(self) -> str:
238
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
239
+
240
+ def __len__(self) -> int:
241
+ return len(self.sampler)
242
+
243
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
244
+ sample = self.sampler.sample_sequence(idx)
245
+ data = self.sample_to_data(sample)
246
+ torch_data = dict_apply(data, torch.tensor)
247
+ return torch_data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/pusht_dataset.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+ import torch
3
+ import numpy as np
4
+ import copy
5
+ from cleandiffuser.dataset.base_dataset import BaseDataset
6
+ from cleandiffuser.dataset.replay_buffer import ReplayBuffer
7
+ from cleandiffuser.dataset.dataset_utils import SequenceSampler, MinMaxNormalizer, ImageNormalizer, dict_apply
8
+
9
+
10
+ # data/pusht_cchi_v7_replay.zarr
11
+ # ├── data
12
+ # │ ├── action (25650, 2) float32
13
+ # │ ├── img (25650, 96, 96, 3) float32
14
+ # │ ├── keypoint (25650, 9, 2) float32
15
+ # │ ├── n_contacts (25650, 1) float32
16
+ # │ └── state (25650, 5) float32
17
+ # └── meta
18
+ # └── episode_ends (206,) int64
19
+ # assert('data' in root)
20
+ # assert('meta' in root)
21
+ # assert('episode_ends' in root['meta'])
22
+
23
+
24
+ class PushTStateDataset(BaseDataset):
25
+ def __init__(self,
26
+ dataset_path,
27
+ obs_keys=["state", "action"],
28
+ horizon=1,
29
+ pad_before=0,
30
+ pad_after=0,
31
+ abs_action=False
32
+ ):
33
+
34
+ super().__init__()
35
+ self.replay_buffer = ReplayBuffer.copy_from_path(
36
+ dataset_path, keys=obs_keys)
37
+
38
+ self.sampler = SequenceSampler(
39
+ replay_buffer=self.replay_buffer,
40
+ sequence_length=horizon,
41
+ pad_before=pad_before,
42
+ pad_after=pad_after)
43
+
44
+ self.horizon = horizon
45
+ self.pad_before = pad_before
46
+ self.pad_after = pad_after
47
+
48
+ self.normalizer = self.get_normalizer()
49
+
50
+ def get_normalizer(self):
51
+ state_normalizer = MinMaxNormalizer(self.replay_buffer['state'][:]) # (N, obs_dim)
52
+ action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
53
+ return {
54
+ "obs": {
55
+ "state": state_normalizer
56
+ },
57
+ "action": action_normalizer
58
+ }
59
+
60
+ def __str__(self) -> str:
61
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
62
+
63
+ def __len__(self) -> int:
64
+ return len(self.sampler)
65
+
66
+ def _sample_to_data(self, sample):
67
+ state = sample['state'].astype(np.float32) # (T, 5)
68
+ state = self.normalizer['obs']['state'].normalize(state)
69
+
70
+ action = sample['action'].astype(np.float32) # (T, 2)
71
+ action = self.normalizer['action'].normalize(action)
72
+ data = {
73
+ 'obs': {
74
+ 'state': state, # T, 5
75
+ },
76
+ 'action': action, # T, 2
77
+ }
78
+ return data
79
+
80
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
81
+ sample = self.sampler.sample_sequence(idx)
82
+ data = self._sample_to_data(sample)
83
+ torch_data = dict_apply(data, torch.tensor)
84
+ return torch_data
85
+
86
+
87
+ class PushTKeypointDataset(BaseDataset):
88
+ def __init__(self,
89
+ dataset_path,
90
+ obs_keys=['keypoint', 'state', 'action'],
91
+ horizon=1,
92
+ pad_before=0,
93
+ pad_after=0,
94
+ abs_action=False
95
+ ):
96
+
97
+ super().__init__()
98
+ self.replay_buffer = ReplayBuffer.copy_from_path(
99
+ dataset_path, keys=obs_keys)
100
+
101
+ self.sampler = SequenceSampler(
102
+ replay_buffer=self.replay_buffer,
103
+ sequence_length=horizon,
104
+ pad_before=pad_before,
105
+ pad_after=pad_after)
106
+
107
+ self.horizon = horizon
108
+ self.pad_before = pad_before
109
+ self.pad_after = pad_after
110
+
111
+ self.normalizer = self.get_normalizer()
112
+
113
+ def get_normalizer(self):
114
+ agent_pos_normalizer = MinMaxNormalizer(self.replay_buffer['state'][:, :2]) # (N, 2)
115
+ keypoint_normalizer = MinMaxNormalizer(self.replay_buffer['keypoint'][:]) # (N, 9, 2)
116
+ action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, 2)
117
+ return {
118
+ "obs": {
119
+ "keypoint": keypoint_normalizer,
120
+ "agent_pos": agent_pos_normalizer
121
+ },
122
+ "action": action_normalizer
123
+ }
124
+
125
+ def __str__(self) -> str:
126
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
127
+
128
+ def __len__(self) -> int:
129
+ return len(self.sampler)
130
+
131
+ def _sample_to_data(self, sample):
132
+ # keypoint
133
+ data_size = sample['keypoint'].shape[0] # (T, 9, 2)
134
+ keypoint = sample['keypoint'].reshape(-1, sample['keypoint'].shape[-1]).astype(np.float32)
135
+ keypoint = self.normalizer['obs']['keypoint'].normalize(keypoint)
136
+ keypoint = keypoint.reshape(data_size, -1) # (T, 18)
137
+
138
+ # agent_pos
139
+ agent_pos = sample['state'][:,:2].astype(np.float32) # (T, 2)
140
+ agent_pos = self.normalizer['obs']['agent_pos'].normalize(agent_pos)
141
+
142
+ # action
143
+ action = sample['action'].astype(np.float32) # (T, 2)
144
+ action = self.normalizer['action'].normalize(action)
145
+
146
+ data = {
147
+ 'obs': {
148
+ 'keypoint': keypoint, # T, 18
149
+ 'agent_pos': agent_pos, # T, 2
150
+ },
151
+ 'action': action, # T, 2
152
+ }
153
+ return data
154
+
155
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
156
+ sample = self.sampler.sample_sequence(idx)
157
+ data = self._sample_to_data(sample)
158
+ torch_data = dict_apply(data, torch.tensor)
159
+ return torch_data
160
+
161
+
162
+ class PushTImageDataset(BaseDataset):
163
+ def __init__(self,
164
+ zarr_path,
165
+ obs_keys=['img', 'state', 'action'],
166
+ horizon=1,
167
+ pad_before=0,
168
+ pad_after=0,
169
+ abs_action=False
170
+ ):
171
+
172
+ super().__init__()
173
+ self.replay_buffer = ReplayBuffer.copy_from_path(
174
+ zarr_path, keys=obs_keys)
175
+
176
+ self.sampler = SequenceSampler(
177
+ replay_buffer=self.replay_buffer,
178
+ sequence_length=horizon,
179
+ pad_before=pad_before,
180
+ pad_after=pad_after)
181
+
182
+ self.horizon = horizon
183
+ self.pad_before = pad_before
184
+ self.pad_after = pad_after
185
+
186
+ self.normalizer = self.get_normalizer()
187
+
188
+ def get_normalizer(self):
189
+ agent_pos_normalizer = MinMaxNormalizer(self.replay_buffer['state'][...,:2])
190
+ image_normalizer = ImageNormalizer()
191
+ action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:])
192
+
193
+ return {
194
+ "obs": {
195
+ "image": image_normalizer,
196
+ "agent_pos": agent_pos_normalizer
197
+ },
198
+ "action": action_normalizer
199
+ }
200
+
201
+ def __str__(self) -> str:
202
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
203
+
204
+ def __len__(self) -> int:
205
+ return len(self.sampler)
206
+
207
+ def _sample_to_data(self, sample):
208
+ # image
209
+ image = np.moveaxis(sample['img'], -1, 1) / 255
210
+ image = self.normalizer['obs']['image'].normalize(image)
211
+
212
+ # agent_pos
213
+ agent_pos = sample['state'][:,:2].astype(np.float32) # (T, 2)
214
+ agent_pos = self.normalizer['obs']['agent_pos'].normalize(agent_pos)
215
+
216
+ # action
217
+ action = sample['action'].astype(np.float32) # (T, 2)
218
+ action = self.normalizer['action'].normalize(action)
219
+
220
+ data = {
221
+ 'obs': {
222
+ 'image': image, # T, 3, 96, 96
223
+ 'agent_pos': agent_pos, # T, 2
224
+ },
225
+ 'action': action, # T, 2
226
+ }
227
+ return data
228
+
229
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
230
+ sample = self.sampler.sample_sequence(idx)
231
+ data = self._sample_to_data(sample)
232
+ torch_data = dict_apply(data, torch.tensor)
233
+ return torch_data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/replay_buffer.py ADDED
@@ -0,0 +1,592 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Union, Dict, Optional
2
+ import os
3
+ import math
4
+ import numbers
5
+ import zarr
6
+ import numcodecs
7
+ import numpy as np
8
+ from functools import cached_property
9
+
10
+
11
+ def check_chunks_compatible(chunks: tuple, shape: tuple):
12
+ assert len(shape) == len(chunks)
13
+ for c in chunks:
14
+ assert isinstance(c, numbers.Integral)
15
+ assert c > 0
16
+
17
+
18
+ def rechunk_recompress_array(group, name,
19
+ chunks=None, chunk_length=None,
20
+ compressor=None, tmp_key='_temp'):
21
+ old_arr = group[name]
22
+ if chunks is None:
23
+ if chunk_length is not None:
24
+ chunks = (chunk_length,) + old_arr.chunks[1:]
25
+ else:
26
+ chunks = old_arr.chunks
27
+ check_chunks_compatible(chunks, old_arr.shape)
28
+
29
+ if compressor is None:
30
+ compressor = old_arr.compressor
31
+
32
+ if (chunks == old_arr.chunks) and (compressor == old_arr.compressor):
33
+ # no change
34
+ return old_arr
35
+
36
+ # rechunk recompress
37
+ group.move(name, tmp_key)
38
+ old_arr = group[tmp_key]
39
+ n_copied, n_skipped, n_bytes_copied = zarr.copy(
40
+ source=old_arr,
41
+ dest=group,
42
+ name=name,
43
+ chunks=chunks,
44
+ compressor=compressor,
45
+ )
46
+ del group[tmp_key]
47
+ arr = group[name]
48
+ return arr
49
+
50
+
51
+ def get_optimal_chunks(shape, dtype,
52
+ target_chunk_bytes=2e6,
53
+ max_chunk_length=None):
54
+ """
55
+ Common shapes
56
+ T,D
57
+ T,N,D
58
+ T,H,W,C
59
+ T,N,H,W,C
60
+ """
61
+ itemsize = np.dtype(dtype).itemsize
62
+ # reversed
63
+ rshape = list(shape[::-1])
64
+ if max_chunk_length is not None:
65
+ rshape[-1] = int(max_chunk_length)
66
+ split_idx = len(shape) - 1
67
+ for i in range(len(shape) - 1):
68
+ this_chunk_bytes = itemsize * np.prod(rshape[:i])
69
+ next_chunk_bytes = itemsize * np.prod(rshape[:i + 1])
70
+ if this_chunk_bytes <= target_chunk_bytes \
71
+ and next_chunk_bytes > target_chunk_bytes:
72
+ split_idx = i
73
+
74
+ rchunks = rshape[:split_idx]
75
+ item_chunk_bytes = itemsize * np.prod(rshape[:split_idx])
76
+ this_max_chunk_length = rshape[split_idx]
77
+ next_chunk_length = min(this_max_chunk_length, math.ceil(
78
+ target_chunk_bytes / item_chunk_bytes))
79
+ rchunks.append(next_chunk_length)
80
+ len_diff = len(shape) - len(rchunks)
81
+ rchunks.extend([1] * len_diff)
82
+ chunks = tuple(rchunks[::-1])
83
+ # print(np.prod(chunks) * itemsize / target_chunk_bytes)
84
+ return chunks
85
+
86
+
87
+ class ReplayBuffer:
88
+ """
89
+ Zarr-based temporal datastructure.
90
+ Assumes first dimension to be time. Only chunk in time dimension.
91
+ """
92
+
93
+ def __init__(self, root: Union[zarr.Group, Dict[str, dict]]):
94
+ """
95
+ Dummy constructor. Use copy_from* and create_from* class methods instead.
96
+ """
97
+ assert ('data' in root)
98
+ assert ('meta' in root)
99
+ assert ('episode_ends' in root['meta'])
100
+ for key, value in root['data'].items():
101
+ assert (value.shape[0] == root['meta']['episode_ends'][-1])
102
+ self.root = root
103
+
104
+ # ============= create constructors ===============
105
+ @classmethod
106
+ def create_empty_zarr(cls, storage=None, root=None):
107
+ if root is None:
108
+ if storage is None:
109
+ storage = zarr.MemoryStore()
110
+ root = zarr.group(store=storage)
111
+ data = root.require_group('data', overwrite=False)
112
+ meta = root.require_group('meta', overwrite=False)
113
+ if 'episode_ends' not in meta:
114
+ episode_ends = meta.zeros('episode_ends', shape=(0,), dtype=np.int64,
115
+ compressor=None, overwrite=False)
116
+ return cls(root=root)
117
+
118
+ @classmethod
119
+ def create_empty_numpy(cls):
120
+ root = {
121
+ 'data': dict(),
122
+ 'meta': {
123
+ 'episode_ends': np.zeros((0,), dtype=np.int64)
124
+ }
125
+ }
126
+ return cls(root=root)
127
+
128
+ @classmethod
129
+ def create_from_group(cls, group, **kwargs):
130
+ if 'data' not in group:
131
+ # create from stratch
132
+ buffer = cls.create_empty_zarr(root=group, **kwargs)
133
+ else:
134
+ # already exist
135
+ buffer = cls(root=group, **kwargs)
136
+ return buffer
137
+
138
+ @classmethod
139
+ def create_from_path(cls, zarr_path, mode='r', **kwargs):
140
+ """
141
+ Open an on-disk zarr directly (for dataset larger than memory).
142
+ Slower.
143
+ """
144
+ group = zarr.open(os.path.expanduser(zarr_path), mode)
145
+ return cls.create_from_group(group, **kwargs)
146
+
147
+ # ============= copy constructors ===============
148
+ @classmethod
149
+ def copy_from_store(cls, src_store, store=None, keys=None,
150
+ chunks: Dict[str, tuple] = dict(),
151
+ compressors: Union[dict, str, numcodecs.abc.Codec] = dict(),
152
+ if_exists='replace',
153
+ **kwargs):
154
+ """
155
+ Load to memory.
156
+ """
157
+ src_root = zarr.group(src_store)
158
+ root = None
159
+ if store is None:
160
+ # numpy backend
161
+ meta = dict()
162
+ for key, value in src_root['meta'].items():
163
+ if len(value.shape) == 0:
164
+ meta[key] = np.array(value)
165
+ else:
166
+ meta[key] = value[:]
167
+
168
+ if keys is None:
169
+ keys = src_root['data'].keys()
170
+ data = dict()
171
+ for key in keys:
172
+ arr = src_root['data'][key]
173
+ data[key] = arr[:]
174
+
175
+ root = {
176
+ 'meta': meta,
177
+ 'data': data
178
+ }
179
+ else:
180
+ root = zarr.group(store=store)
181
+ # copy without recompression
182
+ n_copied, n_skipped, n_bytes_copied = zarr.copy_store(source=src_store, dest=store,
183
+ source_path='/meta', dest_path='/meta',
184
+ if_exists=if_exists)
185
+ data_group = root.create_group('data', overwrite=True)
186
+ if keys is None:
187
+ keys = src_root['data'].keys()
188
+ for key in keys:
189
+ value = src_root['data'][key]
190
+ cks = cls._resolve_array_chunks(
191
+ chunks=chunks, key=key, array=value)
192
+ cpr = cls._resolve_array_compressor(
193
+ compressors=compressors, key=key, array=value)
194
+ if cks == value.chunks and cpr == value.compressor:
195
+ # copy without recompression
196
+ this_path = '/data/' + key
197
+ n_copied, n_skipped, n_bytes_copied = zarr.copy_store(
198
+ source=src_store, dest=store,
199
+ source_path=this_path, dest_path=this_path,
200
+ if_exists=if_exists
201
+ )
202
+ else:
203
+ # copy with recompression
204
+ n_copied, n_skipped, n_bytes_copied = zarr.copy(
205
+ source=value, dest=data_group, name=key,
206
+ chunks=cks, compressor=cpr, if_exists=if_exists
207
+ )
208
+ buffer = cls(root=root)
209
+ return buffer
210
+
211
+ @classmethod
212
+ def copy_from_path(cls, zarr_path, backend=None, store=None, keys=None,
213
+ chunks: Dict[str, tuple] = dict(),
214
+ compressors: Union[dict, str, numcodecs.abc.Codec] = dict(),
215
+ if_exists='replace',
216
+ **kwargs):
217
+ """
218
+ Copy a on-disk zarr to in-memory compressed.
219
+ Recommended
220
+ """
221
+ if backend == 'numpy':
222
+ print('backend argument is deprecated!')
223
+ store = None
224
+ group = zarr.open(os.path.expanduser(zarr_path), 'r')
225
+ return cls.copy_from_store(src_store=group.store, store=store,
226
+ keys=keys, chunks=chunks, compressors=compressors,
227
+ if_exists=if_exists, **kwargs)
228
+
229
+ # ============= save methods ===============
230
+ def save_to_store(self, store,
231
+ chunks: Optional[Dict[str, tuple]] = dict(),
232
+ compressors: Union[str, numcodecs.abc.Codec, dict] = dict(),
233
+ if_exists='replace',
234
+ **kwargs):
235
+
236
+ root = zarr.group(store)
237
+ if self.backend == 'zarr':
238
+ # recompression free copy
239
+ n_copied, n_skipped, n_bytes_copied = zarr.copy_store(
240
+ source=self.root.store, dest=store,
241
+ source_path='/meta', dest_path='/meta', if_exists=if_exists)
242
+ else:
243
+ meta_group = root.create_group('meta', overwrite=True)
244
+ # save meta, no chunking
245
+ for key, value in self.root['meta'].items():
246
+ _ = meta_group.array(
247
+ name=key,
248
+ data=value,
249
+ shape=value.shape,
250
+ chunks=value.shape)
251
+
252
+ # save data, chunk
253
+ data_group = root.create_group('data', overwrite=True)
254
+ for key, value in self.root['data'].items():
255
+ cks = self._resolve_array_chunks(
256
+ chunks=chunks, key=key, array=value)
257
+ cpr = self._resolve_array_compressor(
258
+ compressors=compressors, key=key, array=value)
259
+ if isinstance(value, zarr.Array):
260
+ if cks == value.chunks and cpr == value.compressor:
261
+ # copy without recompression
262
+ this_path = '/data/' + key
263
+ n_copied, n_skipped, n_bytes_copied = zarr.copy_store(
264
+ source=self.root.store, dest=store,
265
+ source_path=this_path, dest_path=this_path, if_exists=if_exists)
266
+ else:
267
+ # copy with recompression
268
+ n_copied, n_skipped, n_bytes_copied = zarr.copy(
269
+ source=value, dest=data_group, name=key,
270
+ chunks=cks, compressor=cpr, if_exists=if_exists
271
+ )
272
+ else:
273
+ # numpy
274
+ _ = data_group.array(
275
+ name=key,
276
+ data=value,
277
+ chunks=cks,
278
+ compressor=cpr
279
+ )
280
+ return store
281
+
282
+ def save_to_path(self, zarr_path,
283
+ chunks: Optional[Dict[str, tuple]] = dict(),
284
+ compressors: Union[str, numcodecs.abc.Codec, dict] = dict(),
285
+ if_exists='replace',
286
+ **kwargs):
287
+ store = zarr.DirectoryStore(os.path.expanduser(zarr_path))
288
+ return self.save_to_store(store, chunks=chunks,
289
+ compressors=compressors, if_exists=if_exists, **kwargs)
290
+
291
+ @staticmethod
292
+ def resolve_compressor(compressor='default'):
293
+ if compressor == 'default':
294
+ compressor = numcodecs.Blosc(cname='lz4', clevel=5,
295
+ shuffle=numcodecs.Blosc.NOSHUFFLE)
296
+ elif compressor == 'disk':
297
+ compressor = numcodecs.Blosc('zstd', clevel=5,
298
+ shuffle=numcodecs.Blosc.BITSHUFFLE)
299
+ return compressor
300
+
301
+ @classmethod
302
+ def _resolve_array_compressor(cls,
303
+ compressors: Union[dict, str, numcodecs.abc.Codec], key, array):
304
+ # allows compressor to be explicitly set to None
305
+ cpr = 'nil'
306
+ if isinstance(compressors, dict):
307
+ if key in compressors:
308
+ cpr = cls.resolve_compressor(compressors[key])
309
+ elif isinstance(array, zarr.Array):
310
+ cpr = array.compressor
311
+ else:
312
+ cpr = cls.resolve_compressor(compressors)
313
+ # backup default
314
+ if cpr == 'nil':
315
+ cpr = cls.resolve_compressor('default')
316
+ return cpr
317
+
318
+ @classmethod
319
+ def _resolve_array_chunks(cls,
320
+ chunks: Union[dict, tuple], key, array):
321
+ cks = None
322
+ if isinstance(chunks, dict):
323
+ if key in chunks:
324
+ cks = chunks[key]
325
+ elif isinstance(array, zarr.Array):
326
+ cks = array.chunks
327
+ elif isinstance(chunks, tuple):
328
+ cks = chunks
329
+ else:
330
+ raise TypeError(f"Unsupported chunks type {type(chunks)}")
331
+ # backup default
332
+ if cks is None:
333
+ cks = get_optimal_chunks(shape=array.shape, dtype=array.dtype)
334
+ # check
335
+ check_chunks_compatible(chunks=cks, shape=array.shape)
336
+ return cks
337
+
338
+ # ============= properties =================
339
+ @cached_property
340
+ def data(self):
341
+ return self.root['data']
342
+
343
+ @cached_property
344
+ def meta(self):
345
+ return self.root['meta']
346
+
347
+ def update_meta(self, data):
348
+ # sanitize data
349
+ np_data = dict()
350
+ for key, value in data.items():
351
+ if isinstance(value, np.ndarray):
352
+ np_data[key] = value
353
+ else:
354
+ arr = np.array(value)
355
+ if arr.dtype == object:
356
+ raise TypeError(f"Invalid value type {type(value)}")
357
+ np_data[key] = arr
358
+
359
+ meta_group = self.meta
360
+ if self.backend == 'zarr':
361
+ for key, value in np_data.items():
362
+ _ = meta_group.array(
363
+ name=key,
364
+ data=value,
365
+ shape=value.shape,
366
+ chunks=value.shape,
367
+ overwrite=True)
368
+ else:
369
+ meta_group.update(np_data)
370
+
371
+ return meta_group
372
+
373
+ @property
374
+ def episode_ends(self):
375
+ return self.meta['episode_ends']
376
+
377
+ def get_episode_idxs(self):
378
+ import numba
379
+ numba.jit(nopython=True)
380
+
381
+ def _get_episode_idxs(episode_ends):
382
+ result = np.zeros((episode_ends[-1],), dtype=np.int64)
383
+ for i in range(len(episode_ends)):
384
+ start = 0
385
+ if i > 0:
386
+ start = episode_ends[i - 1]
387
+ end = episode_ends[i]
388
+ for idx in range(start, end):
389
+ result[idx] = i
390
+ return result
391
+
392
+ return _get_episode_idxs(self.episode_ends)
393
+
394
+ @property
395
+ def backend(self):
396
+ backend = 'numpy'
397
+ if isinstance(self.root, zarr.Group):
398
+ backend = 'zarr'
399
+ return backend
400
+
401
+ # =========== dict-like API ==============
402
+ def __repr__(self) -> str:
403
+ if self.backend == 'zarr':
404
+ return str(self.root.tree())
405
+ else:
406
+ return super().__repr__()
407
+
408
+ def keys(self):
409
+ return self.data.keys()
410
+
411
+ def values(self):
412
+ return self.data.values()
413
+
414
+ def items(self):
415
+ return self.data.items()
416
+
417
+ def __getitem__(self, key):
418
+ return self.data[key]
419
+
420
+ def __contains__(self, key):
421
+ return key in self.data
422
+
423
+ # =========== our API ==============
424
+ @property
425
+ def n_steps(self):
426
+ if len(self.episode_ends) == 0:
427
+ return 0
428
+ return self.episode_ends[-1]
429
+
430
+ @property
431
+ def n_episodes(self):
432
+ return len(self.episode_ends)
433
+
434
+ @property
435
+ def chunk_size(self):
436
+ if self.backend == 'zarr':
437
+ return next(iter(self.data.arrays()))[-1].chunks[0]
438
+ return None
439
+
440
+ @property
441
+ def episode_lengths(self):
442
+ ends = self.episode_ends[:]
443
+ ends = np.insert(ends, 0, 0)
444
+ lengths = np.diff(ends)
445
+ return lengths
446
+
447
+ def add_episode(self,
448
+ data: Dict[str, np.ndarray],
449
+ chunks: Optional[Dict[str, tuple]] = dict(),
450
+ compressors: Union[str, numcodecs.abc.Codec, dict] = dict()):
451
+ assert (len(data) > 0)
452
+ is_zarr = (self.backend == 'zarr')
453
+
454
+ curr_len = self.n_steps
455
+ episode_length = None
456
+ for key, value in data.items():
457
+ assert (len(value.shape) >= 1)
458
+ if episode_length is None:
459
+ episode_length = len(value)
460
+ else:
461
+ assert (episode_length == len(value))
462
+ new_len = curr_len + episode_length
463
+
464
+ for key, value in data.items():
465
+ new_shape = (new_len,) + value.shape[1:]
466
+ # create array
467
+ if key not in self.data:
468
+ if is_zarr:
469
+ cks = self._resolve_array_chunks(
470
+ chunks=chunks, key=key, array=value)
471
+ cpr = self._resolve_array_compressor(
472
+ compressors=compressors, key=key, array=value)
473
+ arr = self.data.zeros(name=key,
474
+ shape=new_shape,
475
+ chunks=cks,
476
+ dtype=value.dtype,
477
+ compressor=cpr)
478
+ else:
479
+ # copy data to prevent modify
480
+ arr = np.zeros(shape=new_shape, dtype=value.dtype)
481
+ self.data[key] = arr
482
+ else:
483
+ arr = self.data[key]
484
+ assert (value.shape[1:] == arr.shape[1:])
485
+ # same method for both zarr and numpy
486
+ if is_zarr:
487
+ arr.resize(new_shape)
488
+ else:
489
+ arr.resize(new_shape, refcheck=False)
490
+ # copy data
491
+ arr[-value.shape[0]:] = value
492
+
493
+ # append to episode ends
494
+ episode_ends = self.episode_ends
495
+ if is_zarr:
496
+ episode_ends.resize(episode_ends.shape[0] + 1)
497
+ else:
498
+ episode_ends.resize(episode_ends.shape[0] + 1, refcheck=False)
499
+ episode_ends[-1] = new_len
500
+
501
+ # rechunk
502
+ if is_zarr:
503
+ if episode_ends.chunks[0] < episode_ends.shape[0]:
504
+ rechunk_recompress_array(self.meta, 'episode_ends',
505
+ chunk_length=int(episode_ends.shape[0] * 1.5))
506
+
507
+ def drop_episode(self):
508
+ is_zarr = (self.backend == 'zarr')
509
+ episode_ends = self.episode_ends[:].copy()
510
+ assert (len(episode_ends) > 0)
511
+ start_idx = 0
512
+ if len(episode_ends) > 1:
513
+ start_idx = episode_ends[-2]
514
+ for key, value in self.data.items():
515
+ new_shape = (start_idx,) + value.shape[1:]
516
+ if is_zarr:
517
+ value.resize(new_shape)
518
+ else:
519
+ value.resize(new_shape, refcheck=False)
520
+ if is_zarr:
521
+ self.episode_ends.resize(len(episode_ends) - 1)
522
+ else:
523
+ self.episode_ends.resize(len(episode_ends) - 1, refcheck=False)
524
+
525
+ def pop_episode(self):
526
+ assert (self.n_episodes > 0)
527
+ episode = self.get_episode(self.n_episodes - 1, copy=True)
528
+ self.drop_episode()
529
+ return episode
530
+
531
+ def extend(self, data):
532
+ self.add_episode(data)
533
+
534
+ def get_episode(self, idx, copy=False):
535
+ idx = list(range(len(self.episode_ends)))[idx]
536
+ start_idx = 0
537
+ if idx > 0:
538
+ start_idx = self.episode_ends[idx - 1]
539
+ end_idx = self.episode_ends[idx]
540
+ result = self.get_steps_slice(start_idx, end_idx, copy=copy)
541
+ return result
542
+
543
+ def get_episode_slice(self, idx):
544
+ start_idx = 0
545
+ if idx > 0:
546
+ start_idx = self.episode_ends[idx - 1]
547
+ end_idx = self.episode_ends[idx]
548
+ return slice(start_idx, end_idx)
549
+
550
+ def get_steps_slice(self, start, stop, step=None, copy=False):
551
+ _slice = slice(start, stop, step)
552
+
553
+ result = dict()
554
+ for key, value in self.data.items():
555
+ x = value[_slice]
556
+ if copy and isinstance(value, np.ndarray):
557
+ x = x.copy()
558
+ result[key] = x
559
+ return result
560
+
561
+ # =========== chunking =============
562
+ def get_chunks(self) -> dict:
563
+ assert self.backend == 'zarr'
564
+ chunks = dict()
565
+ for key, value in self.data.items():
566
+ chunks[key] = value.chunks
567
+ return chunks
568
+
569
+ def set_chunks(self, chunks: dict):
570
+ assert self.backend == 'zarr'
571
+ for key, value in chunks.items():
572
+ if key in self.data:
573
+ arr = self.data[key]
574
+ if value != arr.chunks:
575
+ check_chunks_compatible(chunks=value, shape=arr.shape)
576
+ rechunk_recompress_array(self.data, key, chunks=value)
577
+
578
+ def get_compressors(self) -> dict:
579
+ assert self.backend == 'zarr'
580
+ compressors = dict()
581
+ for key, value in self.data.items():
582
+ compressors[key] = value.compressor
583
+ return compressors
584
+
585
+ def set_compressors(self, compressors: dict):
586
+ assert self.backend == 'zarr'
587
+ for key, value in compressors.items():
588
+ if key in self.data:
589
+ arr = self.data[key]
590
+ compressor = self.resolve_compressor(value)
591
+ if compressor != arr.compressor:
592
+ rechunk_recompress_array(self.data, key, compressor=compressor)
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_dataset.py ADDED
@@ -0,0 +1,506 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+ import torch
3
+ import numpy as np
4
+ import copy
5
+ import pathlib
6
+ import h5py
7
+ import zarr
8
+ from tqdm import tqdm
9
+ import concurrent.futures
10
+ from collections import defaultdict
11
+ from cleandiffuser.dataset.imagecodecs import register_codecs, Jpeg2k
12
+ from cleandiffuser.dataset.base_dataset import BaseDataset
13
+ from cleandiffuser.dataset.replay_buffer import ReplayBuffer
14
+ from cleandiffuser.dataset.dataset_utils import SequenceSampler, EmptyNormalizer, RotationTransformer, dict_apply, \
15
+ MinMaxNormalizer, ImageNormalizer
16
+
17
+ register_codecs()
18
+
19
+
20
+ class RobomimicDataset(BaseDataset):
21
+ def __init__(self,
22
+ dataset_dir,
23
+ horizon=1,
24
+ pad_before=0,
25
+ pad_after=0,
26
+ obs_keys=('object', 'robot0_eef_pos', 'robot0_eef_quat', 'robot0_gripper_qpos'),
27
+ abs_action=False,
28
+ rotation_rep='rotation_6d',
29
+ ):
30
+ super().__init__()
31
+ self.rotation_transformer = RotationTransformer(
32
+ from_rep='axis_angle', to_rep=rotation_rep)
33
+
34
+ self.replay_buffer = ReplayBuffer.create_empty_numpy()
35
+ with h5py.File(dataset_dir) as file:
36
+ demos = file['data']
37
+ for i in tqdm(range(len(demos)), desc="Loading hdf5 to ReplayBuffer"):
38
+ demo = demos[f'demo_{i}']
39
+ episode = _data_to_obs(
40
+ raw_obs=demo['obs'],
41
+ raw_actions=demo['actions'][:].astype(np.float32),
42
+ obs_keys=obs_keys,
43
+ abs_action=abs_action,
44
+ rotation_transformer=self.rotation_transformer)
45
+ self.replay_buffer.add_episode(episode)
46
+
47
+ self.sampler = SequenceSampler(
48
+ replay_buffer=self.replay_buffer,
49
+ sequence_length=horizon,
50
+ pad_before=pad_before,
51
+ pad_after=pad_after)
52
+
53
+ self.horizon = horizon
54
+ self.pad_before = pad_before
55
+ self.pad_after = pad_after
56
+ self.abs_action = abs_action
57
+ self.normalizer = self.get_normalizer()
58
+
59
+ def undo_transform_action(self, action):
60
+ raw_shape = action.shape
61
+ if raw_shape[-1] == 20:
62
+ # dual arm
63
+ action = action.reshape(-1, 2, 10)
64
+
65
+ d_rot = action.shape[-1] - 4
66
+ pos = action[..., :3]
67
+ rot = action[..., 3:3 + d_rot]
68
+ gripper = action[..., [-1]]
69
+ rot = self.rotation_transformer.inverse(rot)
70
+ uaction = np.concatenate([
71
+ pos, rot, gripper
72
+ ], axis=-1)
73
+
74
+ if raw_shape[-1] == 20:
75
+ # dual arm
76
+ uaction = uaction.reshape(*raw_shape[:-1], 14)
77
+
78
+ return uaction
79
+
80
+ def get_normalizer(self):
81
+ if self.abs_action:
82
+ state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
83
+ action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
84
+ else:
85
+ state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
86
+ action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
87
+ return {
88
+ "obs": {
89
+ "state": state_normalizer
90
+ },
91
+ "action": action_normalizer
92
+ }
93
+
94
+ def sample_to_data(self, sample):
95
+ state = sample['obs'].astype(np.float32)
96
+ state = self.normalizer['obs']['state'].normalize(state)
97
+
98
+ action = sample['action'].astype(np.float32)
99
+ action = self.normalizer['action'].normalize(action)
100
+ data = {
101
+ 'obs': {
102
+ 'state': state
103
+ },
104
+ 'action': action,
105
+ }
106
+ return data
107
+
108
+ def __str__(self) -> str:
109
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
110
+
111
+ def __len__(self) -> int:
112
+ return len(self.sampler)
113
+
114
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
115
+ sample = self.sampler.sample_sequence(idx)
116
+ data = self.sample_to_data(sample)
117
+ torch_data = dict_apply(data, torch.tensor)
118
+ return torch_data
119
+
120
+
121
+ def _data_to_obs(raw_obs, raw_actions, obs_keys, abs_action, rotation_transformer):
122
+ obs = np.concatenate([
123
+ raw_obs[key] for key in obs_keys
124
+ ], axis=-1).astype(np.float32)
125
+
126
+ if abs_action:
127
+ is_dual_arm = False
128
+ if raw_actions.shape[-1] == 14:
129
+ # dual arm
130
+ raw_actions = raw_actions.reshape(-1, 2, 7)
131
+ is_dual_arm = True
132
+
133
+ pos = raw_actions[..., :3]
134
+ rot = raw_actions[..., 3:6]
135
+ gripper = raw_actions[..., 6:]
136
+ rot = rotation_transformer.forward(rot)
137
+ raw_actions = np.concatenate([
138
+ pos, rot, gripper
139
+ ], axis=-1).astype(np.float32)
140
+
141
+ if is_dual_arm:
142
+ raw_actions = raw_actions.reshape(-1, 20)
143
+
144
+ data = {
145
+ 'obs': obs,
146
+ 'action': raw_actions
147
+ }
148
+ return data
149
+
150
+
151
+ class RobomimicImageDataset(BaseDataset):
152
+ def __init__(self,
153
+ dataset_dir,
154
+ shape_meta: dict,
155
+ n_obs_steps=None,
156
+ horizon=1,
157
+ pad_before=0,
158
+ pad_after=0,
159
+ abs_action=False,
160
+ rotation_rep='rotation_6d',
161
+ ):
162
+ super().__init__()
163
+ self.rotation_transformer = RotationTransformer(
164
+ from_rep='axis_angle', to_rep=rotation_rep)
165
+
166
+ self.replay_buffer = _convert_robomimic_to_replay(
167
+ store=zarr.MemoryStore(),
168
+ shape_meta=shape_meta,
169
+ dataset_path=dataset_dir,
170
+ abs_action=abs_action,
171
+ rotation_transformer=self.rotation_transformer)
172
+
173
+ rgb_keys = list()
174
+ lowdim_keys = list()
175
+ obs_shape_meta = shape_meta['obs']
176
+ for key, attr in obs_shape_meta.items():
177
+ type = attr.get('type', 'low_dim')
178
+ if type == 'rgb':
179
+ rgb_keys.append(key)
180
+ elif type == 'low_dim':
181
+ lowdim_keys.append(key)
182
+
183
+ key_first_k = dict()
184
+ if n_obs_steps is not None:
185
+ # only take first k obs from images
186
+ for key in rgb_keys + lowdim_keys:
187
+ key_first_k[key] = n_obs_steps
188
+ self.sampler = SequenceSampler(
189
+ replay_buffer=self.replay_buffer,
190
+ sequence_length=horizon,
191
+ pad_before=pad_before,
192
+ pad_after=pad_after,
193
+ key_first_k=key_first_k
194
+ )
195
+
196
+ self.shape_meta = shape_meta
197
+ self.rgb_keys = rgb_keys
198
+ self.lowdim_keys = lowdim_keys
199
+ self.abs_action = abs_action
200
+ self.horizon = horizon
201
+ self.pad_before = pad_before
202
+ self.pad_after = pad_after
203
+ self.n_obs_steps = n_obs_steps
204
+
205
+ self.normalizer = self.get_normalizer()
206
+
207
+ def get_normalizer(self):
208
+ normalizer = defaultdict(dict)
209
+ for key in self.lowdim_keys:
210
+ normalizer['obs'][key] = MinMaxNormalizer(self.replay_buffer[key][:])
211
+ for key in self.rgb_keys:
212
+ normalizer['obs'][key] = ImageNormalizer()
213
+ normalizer['action'] = MinMaxNormalizer(self.replay_buffer['action'][:])
214
+
215
+ return normalizer
216
+
217
+ def __str__(self) -> str:
218
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
219
+
220
+ def __len__(self) -> int:
221
+ return len(self.sampler)
222
+
223
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
224
+ sample = self.sampler.sample_sequence(idx)
225
+
226
+ # obs
227
+ # to save RAM, only return first n_obs_steps of OBS
228
+ # since the rest will be discarded anyway.
229
+ # when self.n_obs_steps is None
230
+ # this slice does nothing (takes all)
231
+ T_slice = slice(self.n_obs_steps)
232
+
233
+ obs_dict = dict()
234
+ for key in self.rgb_keys:
235
+ # move channel last to channel first
236
+ # T,H,W,C
237
+ # convert uint8 image to float32
238
+ obs_dict[key] = np.moveaxis(sample[key][T_slice], -1, 1
239
+ ).astype(np.float32) / 255.
240
+ # T,C,H,W
241
+ del sample[key]
242
+ obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
243
+
244
+ for key in self.lowdim_keys:
245
+ obs_dict[key] = sample[key][T_slice].astype(np.float32)
246
+ del sample[key]
247
+ obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
248
+
249
+ # action
250
+ action = sample['action'].astype(np.float32)
251
+ action = self.normalizer['action'].normalize(action)
252
+
253
+ torch_data = {
254
+ 'obs': dict_apply(obs_dict, torch.tensor),
255
+ 'action': torch.tensor(action)
256
+ }
257
+ return torch_data
258
+
259
+ def undo_transform_action(self, action):
260
+ raw_shape = action.shape
261
+ if raw_shape[-1] == 20:
262
+ # dual arm
263
+ action = action.reshape(-1, 2, 10)
264
+
265
+ d_rot = action.shape[-1] - 4
266
+ pos = action[..., :3]
267
+ rot = action[..., 3:3 + d_rot]
268
+ gripper = action[..., [-1]]
269
+ rot = self.rotation_transformer.inverse(rot)
270
+ uaction = np.concatenate([
271
+ pos, rot, gripper
272
+ ], axis=-1)
273
+
274
+ if raw_shape[-1] == 20:
275
+ # dual arm
276
+ uaction = uaction.reshape(*raw_shape[:-1], 14)
277
+
278
+ return uaction
279
+
280
+
281
+ def _convert_actions(raw_actions, abs_action, rotation_transformer):
282
+ actions = raw_actions
283
+ if abs_action:
284
+ is_dual_arm = False
285
+ if raw_actions.shape[-1] == 14:
286
+ # dual arm
287
+ raw_actions = raw_actions.reshape(-1, 2, 7)
288
+ is_dual_arm = True
289
+
290
+ pos = raw_actions[..., :3]
291
+ rot = raw_actions[..., 3:6]
292
+ gripper = raw_actions[..., 6:]
293
+ rot = rotation_transformer.forward(rot)
294
+ raw_actions = np.concatenate([
295
+ pos, rot, gripper
296
+ ], axis=-1).astype(np.float32)
297
+
298
+ if is_dual_arm:
299
+ raw_actions = raw_actions.reshape(-1, 20)
300
+ actions = raw_actions
301
+ return actions
302
+
303
+
304
+ def _convert_robomimic_to_replay(store, shape_meta, dataset_path, abs_action, rotation_transformer,
305
+ n_workers=None, max_inflight_tasks=None):
306
+ """ Convert Robomimic dataset to ReplayBuffer
307
+
308
+ A ReplayBuffer is a `zarr.Group` or Dict[str, dict] that contains the following keys:
309
+ - data: zarr.Group or Dict[str, dict]
310
+ Contains the data. All data should be stored as numpy arrays with the same length.
311
+ - meta: zarr.Group or Dict[str, dict]
312
+ Contains key "episode_ends", which is a numpy array of shape (n_episodes,) that contains the
313
+ end index of each episode in the data.
314
+
315
+ Args:
316
+ - store: zarr.Store
317
+ zarr.MemoryStore()
318
+ - shape_meta: dict
319
+ Shape metadata of the dataset. Should contain keys 'obs', 'action'.
320
+ For example:
321
+ shape_meta = {
322
+ "action": {"shape": [10, ]},
323
+ "obs": {
324
+ "agentview_image": {"shape": [84, 84, 3], "type": "rgb"},
325
+ "robot0_eef_pos": {"shape": [3, ], "type": "low_dim"},
326
+ }}
327
+ - dataset_path: str
328
+ Path to the Robomimic dataset
329
+ - abs_action: bool
330
+ Whether to use position or velocity control
331
+ - rotation_transformer: RotationTransformer
332
+ Rotation transformer to convert rotation representation
333
+ """
334
+
335
+ """ Dataset structure of Can-PH, as an example:
336
+ - data
337
+ - demo_0
338
+ - actions (118, 7)
339
+ - dones (118, )
340
+ - next_obs
341
+ - agentview_image (118, 84, 84, 3)
342
+ - object (118, 14)
343
+ - robot0_eef_pos (118, 3)
344
+ - robot0_eef_quat
345
+ - robot0_eef_vel_ang
346
+ - robot0_eef_vel_lin
347
+ - robot0_eye_in_hand_image
348
+ - robot0_gripper_qpos
349
+ - robot0_gripper_qvel
350
+ - robot0_joint_pos
351
+ - robot0_joint_pos_cos
352
+ - robot0_joint_pos_sin
353
+ - robot0_joint_vel
354
+ - obs
355
+ ...
356
+ - rewards (118, )
357
+ - states (118, 71)
358
+ - demo_1
359
+ ...(x200 demos)
360
+ - mask
361
+ - 20_percent
362
+ - 20_percent_train
363
+ - 20_percent_valid
364
+ - 50_percent
365
+ - 50_percent_train
366
+ - 50_percent_valid
367
+ - train (180,)
368
+ - valid (20,)
369
+
370
+ Suppose that the `shape_meta` is:
371
+ shape_meta = {
372
+ "action": {"shape": [10, ]},
373
+ "obs": {
374
+ "agentview_image": {
375
+ "shape": [3, 84, 84], "type": "rgb", },
376
+ "robot0_eye_in_hand_image": {
377
+ "shape": [3, 84, 84], "type": "rgb", },
378
+ "robot0_eef_pos": {
379
+ "shape": [3, ], "type": "low_dim", },
380
+ "robot0_eef_quat": {
381
+ "shape": [4, ], "type": "low_dim", },
382
+ "robot0_gripper_qpos": {
383
+ "shape": [2, ], "type": "low_dim", }, }}
384
+ """
385
+
386
+ import multiprocessing
387
+ if n_workers is None:
388
+ n_workers = multiprocessing.cpu_count()
389
+ if max_inflight_tasks is None:
390
+ max_inflight_tasks = n_workers * 5
391
+
392
+ # parse shape_meta
393
+ rgb_keys = list()
394
+ lowdim_keys = list()
395
+ # construct compressors and chunks
396
+ obs_shape_meta = shape_meta['obs']
397
+ for key, attr in obs_shape_meta.items():
398
+ shape = attr['shape']
399
+ type = attr.get('type', 'low_dim')
400
+ if type == 'rgb':
401
+ rgb_keys.append(key)
402
+ elif type == 'low_dim':
403
+ lowdim_keys.append(key)
404
+ # rgb_keys = ['agentview_image', 'robot0_eye_in_hand_image']
405
+ # lowdim_keys = ['robot0_eef_pos', 'robot0_eef_quat', 'robot0_gripper_qpos']
406
+
407
+ # create zarr group
408
+ root = zarr.group(store)
409
+ data_group = root.require_group('data', overwrite=True)
410
+ meta_group = root.require_group('meta', overwrite=True)
411
+
412
+ with h5py.File(dataset_path) as file:
413
+
414
+ # count total steps
415
+ demos = file['data']
416
+ episode_ends = list()
417
+ prev_end = 0
418
+ for i in range(len(demos)):
419
+ demo = demos[f'demo_{i}']
420
+ episode_length = demo['actions'].shape[0]
421
+ episode_end = prev_end + episode_length
422
+ prev_end = episode_end
423
+ episode_ends.append(episode_end)
424
+ n_steps = episode_ends[-1]
425
+ episode_starts = [0] + episode_ends[:-1]
426
+ _ = meta_group.array('episode_ends', episode_ends,
427
+ dtype=np.int64, compressor=None, overwrite=True)
428
+
429
+ # save lowdim data
430
+ for key in tqdm(lowdim_keys + ['action'], desc="Loading lowdim data"):
431
+ data_key = 'obs/' + key
432
+ if key == 'action':
433
+ data_key = 'actions'
434
+ this_data = list()
435
+ for i in range(len(demos)):
436
+ demo = demos[f'demo_{i}']
437
+ this_data.append(demo[data_key][:].astype(np.float32))
438
+ this_data = np.concatenate(this_data, axis=0)
439
+ if key == 'action':
440
+ this_data = _convert_actions(
441
+ raw_actions=this_data,
442
+ abs_action=abs_action,
443
+ rotation_transformer=rotation_transformer
444
+ )
445
+ assert this_data.shape == (n_steps,) + tuple(shape_meta['action']['shape'])
446
+ else:
447
+ assert this_data.shape == (n_steps,) + tuple(shape_meta['obs'][key]['shape'])
448
+ _ = data_group.array(
449
+ name=key,
450
+ data=this_data,
451
+ shape=this_data.shape,
452
+ chunks=this_data.shape,
453
+ compressor=None,
454
+ dtype=this_data.dtype
455
+ )
456
+
457
+ def img_copy(zarr_arr, zarr_idx, hdf5_arr, hdf5_idx):
458
+ try:
459
+ zarr_arr[zarr_idx] = hdf5_arr[hdf5_idx]
460
+ # make sure we can successfully decode
461
+ _ = zarr_arr[zarr_idx]
462
+ return True
463
+ except Exception as e:
464
+ return False
465
+
466
+ with tqdm(total=n_steps * len(rgb_keys), desc="Loading image data", mininterval=1.0) as pbar:
467
+ # one chunk per thread, therefore no synchronization needed
468
+ with concurrent.futures.ThreadPoolExecutor(max_workers=n_workers) as executor:
469
+ futures = set()
470
+ for key in rgb_keys:
471
+ data_key = 'obs/' + key
472
+ shape = tuple(shape_meta['obs'][key]['shape'])
473
+ c, h, w = shape
474
+ this_compressor = Jpeg2k(level=50)
475
+ img_arr = data_group.require_dataset(
476
+ name=key,
477
+ shape=(n_steps, h, w, c),
478
+ chunks=(1, h, w, c),
479
+ compressor=this_compressor,
480
+ dtype=np.uint8
481
+ )
482
+ for episode_idx in range(len(demos)):
483
+ demo = demos[f'demo_{episode_idx}']
484
+ hdf5_arr = demo['obs'][key]
485
+ for hdf5_idx in range(hdf5_arr.shape[0]):
486
+ if len(futures) >= max_inflight_tasks:
487
+ # limit number of inflight tasks
488
+ completed, futures = concurrent.futures.wait(futures,
489
+ return_when=concurrent.futures.FIRST_COMPLETED)
490
+ for f in completed:
491
+ if not f.result():
492
+ raise RuntimeError('Failed to encode image!')
493
+ pbar.update(len(completed))
494
+
495
+ zarr_idx = episode_starts[episode_idx] + hdf5_idx
496
+ futures.add(
497
+ executor.submit(img_copy,
498
+ img_arr, zarr_idx, hdf5_arr, hdf5_idx))
499
+ completed, futures = concurrent.futures.wait(futures)
500
+ for f in completed:
501
+ if not f.result():
502
+ raise RuntimeError('Failed to encode image!')
503
+ pbar.update(len(completed))
504
+
505
+ replay_buffer = ReplayBuffer(root)
506
+ return replay_buffer
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/robomimic_rl_dataset.py ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+ import torch
3
+ import numpy as np
4
+ import copy
5
+ import pathlib
6
+ import h5py
7
+ import zarr
8
+ from tqdm import tqdm
9
+ import concurrent.futures
10
+ from collections import defaultdict
11
+ from cleandiffuser.dataset.imagecodecs import register_codecs, Jpeg2k
12
+ from cleandiffuser.dataset.base_dataset import BaseDataset
13
+ from cleandiffuser.dataset.replay_buffer import ReplayBuffer
14
+ from cleandiffuser.dataset.dataset_utils import SequenceSampler, EmptyNormalizer, RotationTransformer, dict_apply, \
15
+ MinMaxNormalizer, ImageNormalizer
16
+
17
+ register_codecs()
18
+
19
+
20
+ class RobomimicDataset(BaseDataset):
21
+ def __init__(
22
+ self,
23
+ dataset_dir,
24
+ horizon=1,
25
+ pad_before=0,
26
+ pad_after=0,
27
+ obs_keys=['object', 'robot0_eef_pos', 'robot0_eef_quat', 'robot0_gripper_qpos'],
28
+ abs_action=False,
29
+ rotation_rep='rotation_6d',
30
+ ):
31
+ super().__init__()
32
+ self.rotation_transformer = RotationTransformer(
33
+ from_rep='axis_angle', to_rep=rotation_rep)
34
+
35
+ self.replay_buffer = ReplayBuffer.create_empty_numpy()
36
+ with h5py.File(dataset_dir) as file:
37
+ demos = file['data']
38
+ for i in tqdm(range(len(demos)), desc="Loading hdf5 to ReplayBuffer"):
39
+ demo = demos[f'demo_{i}']
40
+ episode = _data_to_obs(
41
+ raw_obs=demo['obs'],
42
+ raw_actions=demo['actions'][:].astype(np.float32),
43
+ rewards=demo['rewards'],
44
+ next_raw_obs=demo['next_obs'],
45
+ dones=demo['dones'],
46
+ obs_keys=obs_keys,
47
+ abs_action=abs_action,
48
+ rotation_transformer=self.rotation_transformer)
49
+ self.replay_buffer.add_episode(episode)
50
+
51
+ self.sampler = SequenceSampler(
52
+ replay_buffer=self.replay_buffer,
53
+ sequence_length=horizon,
54
+ pad_before=pad_before,
55
+ pad_after=pad_after)
56
+
57
+ self.horizon = horizon
58
+ self.pad_before = pad_before
59
+ self.pad_after = pad_after
60
+ self.abs_action = abs_action
61
+ self.normalizer = self.get_normalizer()
62
+
63
+ def undo_transform_action(self, action):
64
+ raw_shape = action.shape
65
+ if raw_shape[-1] == 20:
66
+ # dual arm
67
+ action = action.reshape(-1, 2, 10)
68
+
69
+ d_rot = action.shape[-1] - 4
70
+ pos = action[..., :3]
71
+ rot = action[..., 3:3 + d_rot]
72
+ gripper = action[..., [-1]]
73
+ rot = self.rotation_transformer.inverse(rot)
74
+ uaction = np.concatenate([
75
+ pos, rot, gripper
76
+ ], axis=-1)
77
+
78
+ if raw_shape[-1] == 20:
79
+ # dual arm
80
+ uaction = uaction.reshape(*raw_shape[:-1], 14)
81
+
82
+ return uaction
83
+
84
+ def get_normalizer(self):
85
+ state_normalizer = MinMaxNormalizer(self.replay_buffer['obs'][:]) # (N, obs_dim)
86
+ next_state_normalizer = MinMaxNormalizer(self.replay_buffer['next_obs'][:]) # (N, obs_dim)
87
+ action_normalizer = MinMaxNormalizer(self.replay_buffer['action'][:]) # (N, action_dim)
88
+
89
+ return {
90
+ "obs": {
91
+ "state": state_normalizer
92
+ },
93
+ "next_obs": {
94
+ "state": next_state_normalizer
95
+ },
96
+ "action": action_normalizer,
97
+ "reward": reward_normalizer,
98
+ }
99
+
100
+ def sample_to_data(self, sample):
101
+ state = sample['obs'].astype(np.float32)
102
+ state = self.normalizer['obs']['state'].normalize(state)
103
+
104
+ action = sample['action'].astype(np.float32)
105
+ action = self.normalizer['action'].normalize(action)
106
+
107
+ next_state = sample['next_obs'].astype(np.float32)
108
+ next_state = self.normalizer['next_obs']['state'].normalize(next_state)
109
+
110
+ reward = sample['reward'].astype(np.float32)
111
+ done = sample['done'].astype(np.float32)
112
+
113
+ data = {
114
+ 'obs': {
115
+ 'state': state
116
+ },
117
+ 'next_obs': {
118
+ 'state': next_state
119
+ },
120
+ 'action': action,
121
+ 'reward': reward,
122
+ 'done': done,
123
+ }
124
+ return data
125
+
126
+ def __str__(self) -> str:
127
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
128
+
129
+ def __len__(self) -> int:
130
+ return len(self.sampler)
131
+
132
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
133
+ sample = self.sampler.sample_sequence(idx)
134
+ data = self.sample_to_data(sample)
135
+ torch_data = dict_apply(data, torch.from_numpy)
136
+ return torch_data
137
+
138
+
139
+ def _data_to_obs(raw_obs, raw_actions, rewards, next_raw_obs, dones, obs_keys, abs_action, rotation_transformer):
140
+ # for key in obs_keys:
141
+ # print(key, raw_obs[key].shape)
142
+ # exit()
143
+ obs = np.concatenate([
144
+ raw_obs[key] for key in obs_keys
145
+ ], axis=-1).astype(np.float32)
146
+
147
+ next_obs = np.concatenate([
148
+ next_raw_obs[key] for key in obs_keys
149
+ ], axis=-1).astype(np.float32)
150
+
151
+ if abs_action:
152
+ is_dual_arm = False
153
+ if raw_actions.shape[-1] == 14:
154
+ # dual arm
155
+ raw_actions = raw_actions.reshape(-1, 2, 7)
156
+ is_dual_arm = True
157
+
158
+ pos = raw_actions[..., :3]
159
+ rot = raw_actions[..., 3:6]
160
+ gripper = raw_actions[..., 6:]
161
+ rot = rotation_transformer.forward(rot)
162
+ raw_actions = np.concatenate([
163
+ pos, rot, gripper
164
+ ], axis=-1).astype(np.float32)
165
+
166
+ if is_dual_arm:
167
+ raw_actions = raw_actions.reshape(-1, 20)
168
+
169
+ data = {
170
+ 'obs': obs,
171
+ 'action': raw_actions,
172
+ 'reward': rewards,
173
+ 'next_obs': next_obs,
174
+ 'done': dones,
175
+ }
176
+ return data
177
+
178
+
179
+ class RobomimicImageDataset(BaseDataset):
180
+ def __init__(self,
181
+ dataset_dir,
182
+ shape_meta: dict,
183
+ n_obs_steps=None,
184
+ horizon=1,
185
+ pad_before=0,
186
+ pad_after=0,
187
+ abs_action=False,
188
+ rotation_rep='rotation_6d',
189
+ ):
190
+ super().__init__()
191
+ self.rotation_transformer = RotationTransformer(
192
+ from_rep='axis_angle', to_rep=rotation_rep)
193
+
194
+ self.replay_buffer = _convert_robomimic_to_replay(
195
+ store=zarr.MemoryStore(),
196
+ shape_meta=shape_meta,
197
+ dataset_path=dataset_dir,
198
+ abs_action=abs_action,
199
+ rotation_transformer=self.rotation_transformer)
200
+
201
+ rgb_keys = list()
202
+ lowdim_keys = list()
203
+ obs_shape_meta = shape_meta['obs']
204
+ for key, attr in obs_shape_meta.items():
205
+ type = attr.get('type', 'low_dim')
206
+ if type == 'rgb':
207
+ rgb_keys.append(key)
208
+ elif type == 'low_dim':
209
+ lowdim_keys.append(key)
210
+
211
+ key_first_k = dict()
212
+ if n_obs_steps is not None:
213
+ # only take first k obs from images
214
+ for key in rgb_keys + lowdim_keys:
215
+ key_first_k[key] = n_obs_steps
216
+ self.sampler = SequenceSampler(
217
+ replay_buffer=self.replay_buffer,
218
+ sequence_length=horizon,
219
+ pad_before=pad_before,
220
+ pad_after=pad_after,
221
+ key_first_k=key_first_k
222
+ )
223
+
224
+ self.shape_meta = shape_meta
225
+ self.rgb_keys = rgb_keys
226
+ self.lowdim_keys = lowdim_keys
227
+ self.abs_action = abs_action
228
+ self.horizon = horizon
229
+ self.pad_before = pad_before
230
+ self.pad_after = pad_after
231
+ self.n_obs_steps = n_obs_steps
232
+
233
+ self.normalizer = self.get_normalizer()
234
+
235
+ def get_normalizer(self):
236
+ normalizer = defaultdict(dict)
237
+ for key in self.lowdim_keys:
238
+ normalizer['obs'][key] = MinMaxNormalizer(self.replay_buffer[key][:])
239
+ for key in self.rgb_keys:
240
+ normalizer['obs'][key] = ImageNormalizer()
241
+ normalizer['action'] = MinMaxNormalizer(self.replay_buffer['action'][:])
242
+
243
+ return normalizer
244
+
245
+ def __str__(self) -> str:
246
+ return f"Keys: {self.replay_buffer.keys()} Steps: {self.replay_buffer.n_steps} Episodes: {self.replay_buffer.n_episodes}"
247
+
248
+ def __len__(self) -> int:
249
+ return len(self.sampler)
250
+
251
+ def __getitem__(self, idx: int) -> Dict[str, torch.Tensor]:
252
+ sample = self.sampler.sample_sequence(idx)
253
+
254
+ # obs
255
+ # to save RAM, only return first n_obs_steps of OBS
256
+ # since the rest will be discarded anyway.
257
+ # when self.n_obs_steps is None
258
+ # this slice does nothing (takes all)
259
+ T_slice = slice(self.n_obs_steps)
260
+
261
+ obs_dict = dict()
262
+ for key in self.rgb_keys:
263
+ # move channel last to channel first
264
+ # T,H,W,C
265
+ # convert uint8 image to float32
266
+ obs_dict[key] = np.moveaxis(sample[key][T_slice], -1, 1
267
+ ).astype(np.float32) / 255.
268
+ # T,C,H,W
269
+ del sample[key]
270
+ obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
271
+
272
+ for key in self.lowdim_keys:
273
+ obs_dict[key] = sample[key][T_slice].astype(np.float32)
274
+ del sample[key]
275
+ obs_dict[key] = self.normalizer['obs'][key].normalize(obs_dict[key])
276
+
277
+ # action
278
+ action = sample['action'].astype(np.float32)
279
+ action = self.normalizer['action'].normalize(action)
280
+
281
+ torch_data = {
282
+ 'obs': dict_apply(obs_dict, torch.from_numpy),
283
+ 'action': torch.from_numpy(action)
284
+ }
285
+ return torch_data
286
+
287
+ def undo_transform_action(self, action):
288
+ raw_shape = action.shape
289
+ if raw_shape[-1] == 20:
290
+ # dual arm
291
+ action = action.reshape(-1, 2, 10)
292
+
293
+ d_rot = action.shape[-1] - 4
294
+ pos = action[..., :3]
295
+ rot = action[..., 3:3 + d_rot]
296
+ gripper = action[..., [-1]]
297
+ rot = self.rotation_transformer.inverse(rot)
298
+ uaction = np.concatenate([
299
+ pos, rot, gripper
300
+ ], axis=-1)
301
+
302
+ if raw_shape[-1] == 20:
303
+ # dual arm
304
+ uaction = uaction.reshape(*raw_shape[:-1], 14)
305
+
306
+ return uaction
307
+
308
+
309
+ def _convert_actions(raw_actions, abs_action, rotation_transformer):
310
+ actions = raw_actions
311
+ if abs_action:
312
+ is_dual_arm = False
313
+ if raw_actions.shape[-1] == 14:
314
+ # dual arm
315
+ raw_actions = raw_actions.reshape(-1, 2, 7)
316
+ is_dual_arm = True
317
+
318
+ pos = raw_actions[..., :3]
319
+ rot = raw_actions[..., 3:6]
320
+ gripper = raw_actions[..., 6:]
321
+ rot = rotation_transformer.forward(rot)
322
+ raw_actions = np.concatenate([
323
+ pos, rot, gripper
324
+ ], axis=-1).astype(np.float32)
325
+
326
+ if is_dual_arm:
327
+ raw_actions = raw_actions.reshape(-1, 20)
328
+ actions = raw_actions
329
+ return actions
330
+
331
+
332
+ def _convert_robomimic_to_replay(store, shape_meta, dataset_path, abs_action, rotation_transformer,
333
+ n_workers=None, max_inflight_tasks=None):
334
+ import multiprocessing
335
+ if n_workers is None:
336
+ n_workers = multiprocessing.cpu_count()
337
+ if max_inflight_tasks is None:
338
+ max_inflight_tasks = n_workers * 5
339
+
340
+ # parse shape_meta
341
+ rgb_keys = list()
342
+ lowdim_keys = list()
343
+ # construct compressors and chunks
344
+ obs_shape_meta = shape_meta['obs']
345
+ for key, attr in obs_shape_meta.items():
346
+ shape = attr['shape']
347
+ type = attr.get('type', 'low_dim')
348
+ if type == 'rgb':
349
+ rgb_keys.append(key)
350
+ elif type == 'low_dim':
351
+ lowdim_keys.append(key)
352
+
353
+ root = zarr.group(store)
354
+ data_group = root.require_group('data', overwrite=True)
355
+ meta_group = root.require_group('meta', overwrite=True)
356
+
357
+ with h5py.File(dataset_path) as file:
358
+ # count total steps
359
+ demos = file['data']
360
+ episode_ends = list()
361
+ prev_end = 0
362
+ for i in range(len(demos)):
363
+ demo = demos[f'demo_{i}']
364
+ episode_length = demo['actions'].shape[0]
365
+ episode_end = prev_end + episode_length
366
+ prev_end = episode_end
367
+ episode_ends.append(episode_end)
368
+ n_steps = episode_ends[-1]
369
+ episode_starts = [0] + episode_ends[:-1]
370
+ _ = meta_group.array('episode_ends', episode_ends,
371
+ dtype=np.int64, compressor=None, overwrite=True)
372
+
373
+ # save lowdim data
374
+ for key in tqdm(lowdim_keys + ['action'], desc="Loading lowdim data"):
375
+ data_key = 'obs/' + key
376
+ if key == 'action':
377
+ data_key = 'actions'
378
+ this_data = list()
379
+ for i in range(len(demos)):
380
+ demo = demos[f'demo_{i}']
381
+ this_data.append(demo[data_key][:].astype(np.float32))
382
+ this_data = np.concatenate(this_data, axis=0)
383
+ if key == 'action':
384
+ this_data = _convert_actions(
385
+ raw_actions=this_data,
386
+ abs_action=abs_action,
387
+ rotation_transformer=rotation_transformer
388
+ )
389
+ assert this_data.shape == (n_steps,) + tuple(shape_meta['action']['shape'])
390
+ else:
391
+ assert this_data.shape == (n_steps,) + tuple(shape_meta['obs'][key]['shape'])
392
+ _ = data_group.array(
393
+ name=key,
394
+ data=this_data,
395
+ shape=this_data.shape,
396
+ chunks=this_data.shape,
397
+ compressor=None,
398
+ dtype=this_data.dtype
399
+ )
400
+
401
+ def img_copy(zarr_arr, zarr_idx, hdf5_arr, hdf5_idx):
402
+ try:
403
+ zarr_arr[zarr_idx] = hdf5_arr[hdf5_idx]
404
+ # make sure we can successfully decode
405
+ _ = zarr_arr[zarr_idx]
406
+ return True
407
+ except Exception as e:
408
+ return False
409
+
410
+ with tqdm(total=n_steps * len(rgb_keys), desc="Loading image data", mininterval=1.0) as pbar:
411
+ # one chunk per thread, therefore no synchronization needed
412
+ with concurrent.futures.ThreadPoolExecutor(max_workers=n_workers) as executor:
413
+ futures = set()
414
+ for key in rgb_keys:
415
+ data_key = 'obs/' + key
416
+ shape = tuple(shape_meta['obs'][key]['shape'])
417
+ c, h, w = shape
418
+ this_compressor = Jpeg2k(level=50)
419
+ img_arr = data_group.require_dataset(
420
+ name=key,
421
+ shape=(n_steps, h, w, c),
422
+ chunks=(1, h, w, c),
423
+ compressor=this_compressor,
424
+ dtype=np.uint8
425
+ )
426
+ for episode_idx in range(len(demos)):
427
+ demo = demos[f'demo_{episode_idx}']
428
+ hdf5_arr = demo['obs'][key]
429
+ for hdf5_idx in range(hdf5_arr.shape[0]):
430
+ if len(futures) >= max_inflight_tasks:
431
+ # limit number of inflight tasks
432
+ completed, futures = concurrent.futures.wait(futures,
433
+ return_when=concurrent.futures.FIRST_COMPLETED)
434
+ for f in completed:
435
+ if not f.result():
436
+ raise RuntimeError('Failed to encode image!')
437
+ pbar.update(len(completed))
438
+
439
+ zarr_idx = episode_starts[episode_idx] + hdf5_idx
440
+ futures.add(
441
+ executor.submit(img_copy,
442
+ img_arr, zarr_idx, hdf5_arr, hdf5_idx))
443
+ completed, futures = concurrent.futures.wait(futures)
444
+ for f in completed:
445
+ if not f.result():
446
+ raise RuntimeError('Failed to encode image!')
447
+ pbar.update(len(completed))
448
+
449
+ replay_buffer = ReplayBuffer(root)
450
+ return replay_buffer
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/dataset/rotation_conversions.py ADDED
@@ -0,0 +1,597 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # pyre-unsafe
8
+
9
+ from typing import Optional, Union
10
+
11
+ import torch
12
+ import torch.nn.functional as F
13
+
14
+ Device = Union[str, torch.device]
15
+
16
+ """
17
+ The transformation matrices returned from the functions in this file assume
18
+ the points on which the transformation will be applied are column vectors.
19
+ i.e. the R matrix is structured as
20
+
21
+ R = [
22
+ [Rxx, Rxy, Rxz],
23
+ [Ryx, Ryy, Ryz],
24
+ [Rzx, Rzy, Rzz],
25
+ ] # (3, 3)
26
+
27
+ This matrix can be applied to column vectors by post multiplication
28
+ by the points e.g.
29
+
30
+ points = [[0], [1], [2]] # (3 x 1) xyz coordinates of a point
31
+ transformed_points = R * points
32
+
33
+ To apply the same matrix to points which are row vectors, the R matrix
34
+ can be transposed and pre multiplied by the points:
35
+
36
+ e.g.
37
+ points = [[0, 1, 2]] # (1 x 3) xyz coordinates of a point
38
+ transformed_points = points * R.transpose(1, 0)
39
+ """
40
+
41
+
42
+ def quaternion_to_matrix(quaternions: torch.Tensor) -> torch.Tensor:
43
+ """
44
+ Convert rotations given as quaternions to rotation matrices.
45
+
46
+ Args:
47
+ quaternions: quaternions with real part first,
48
+ as tensor of shape (..., 4).
49
+
50
+ Returns:
51
+ Rotation matrices as tensor of shape (..., 3, 3).
52
+ """
53
+ r, i, j, k = torch.unbind(quaternions, -1)
54
+ # pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`.
55
+ two_s = 2.0 / (quaternions * quaternions).sum(-1)
56
+
57
+ o = torch.stack(
58
+ (
59
+ 1 - two_s * (j * j + k * k),
60
+ two_s * (i * j - k * r),
61
+ two_s * (i * k + j * r),
62
+ two_s * (i * j + k * r),
63
+ 1 - two_s * (i * i + k * k),
64
+ two_s * (j * k - i * r),
65
+ two_s * (i * k - j * r),
66
+ two_s * (j * k + i * r),
67
+ 1 - two_s * (i * i + j * j),
68
+ ),
69
+ -1,
70
+ )
71
+ return o.reshape(quaternions.shape[:-1] + (3, 3))
72
+
73
+
74
+ def _copysign(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
75
+ """
76
+ Return a tensor where each element has the absolute value taken from the,
77
+ corresponding element of a, with sign taken from the corresponding
78
+ element of b. This is like the standard copysign floating-point operation,
79
+ but is not careful about negative 0 and NaN.
80
+
81
+ Args:
82
+ a: source tensor.
83
+ b: tensor whose signs will be used, of the same shape as a.
84
+
85
+ Returns:
86
+ Tensor of the same shape as a with the signs of b.
87
+ """
88
+ signs_differ = (a < 0) != (b < 0)
89
+ return torch.where(signs_differ, -a, a)
90
+
91
+
92
+ def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor:
93
+ """
94
+ Returns torch.sqrt(torch.max(0, x))
95
+ but with a zero subgradient where x is 0.
96
+ """
97
+ ret = torch.zeros_like(x)
98
+ positive_mask = x > 0
99
+ ret[positive_mask] = torch.sqrt(x[positive_mask])
100
+ return ret
101
+
102
+
103
+ def matrix_to_quaternion(matrix: torch.Tensor) -> torch.Tensor:
104
+ """
105
+ Convert rotations given as rotation matrices to quaternions.
106
+
107
+ Args:
108
+ matrix: Rotation matrices as tensor of shape (..., 3, 3).
109
+
110
+ Returns:
111
+ quaternions with real part first, as tensor of shape (..., 4).
112
+ """
113
+ if matrix.size(-1) != 3 or matrix.size(-2) != 3:
114
+ raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
115
+
116
+ batch_dim = matrix.shape[:-2]
117
+ m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind(
118
+ matrix.reshape(batch_dim + (9,)), dim=-1
119
+ )
120
+
121
+ q_abs = _sqrt_positive_part(
122
+ torch.stack(
123
+ [
124
+ 1.0 + m00 + m11 + m22,
125
+ 1.0 + m00 - m11 - m22,
126
+ 1.0 - m00 + m11 - m22,
127
+ 1.0 - m00 - m11 + m22,
128
+ ],
129
+ dim=-1,
130
+ )
131
+ )
132
+
133
+ # we produce the desired quaternion multiplied by each of r, i, j, k
134
+ quat_by_rijk = torch.stack(
135
+ [
136
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
137
+ # `int`.
138
+ torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1),
139
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
140
+ # `int`.
141
+ torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1),
142
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
143
+ # `int`.
144
+ torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1),
145
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
146
+ # `int`.
147
+ torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1),
148
+ ],
149
+ dim=-2,
150
+ )
151
+
152
+ # We floor here at 0.1 but the exact level is not important; if q_abs is small,
153
+ # the candidate won't be picked.
154
+ flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device)
155
+ quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr))
156
+
157
+ # if not for numerical problems, quat_candidates[i] should be same (up to a sign),
158
+ # forall i; we pick the best-conditioned one (with the largest denominator)
159
+ out = quat_candidates[
160
+ F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, :
161
+ ].reshape(batch_dim + (4,))
162
+ return standardize_quaternion(out)
163
+
164
+
165
+ def _axis_angle_rotation(axis: str, angle: torch.Tensor) -> torch.Tensor:
166
+ """
167
+ Return the rotation matrices for one of the rotations about an axis
168
+ of which Euler angles describe, for each value of the angle given.
169
+
170
+ Args:
171
+ axis: Axis label "X" or "Y or "Z".
172
+ angle: any shape tensor of Euler angles in radians
173
+
174
+ Returns:
175
+ Rotation matrices as tensor of shape (..., 3, 3).
176
+ """
177
+
178
+ cos = torch.cos(angle)
179
+ sin = torch.sin(angle)
180
+ one = torch.ones_like(angle)
181
+ zero = torch.zeros_like(angle)
182
+
183
+ if axis == "X":
184
+ R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos)
185
+ elif axis == "Y":
186
+ R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos)
187
+ elif axis == "Z":
188
+ R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one)
189
+ else:
190
+ raise ValueError("letter must be either X, Y or Z.")
191
+
192
+ return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3))
193
+
194
+
195
+ def euler_angles_to_matrix(euler_angles: torch.Tensor, convention: str) -> torch.Tensor:
196
+ """
197
+ Convert rotations given as Euler angles in radians to rotation matrices.
198
+
199
+ Args:
200
+ euler_angles: Euler angles in radians as tensor of shape (..., 3).
201
+ convention: Convention string of three uppercase letters from
202
+ {"X", "Y", and "Z"}.
203
+
204
+ Returns:
205
+ Rotation matrices as tensor of shape (..., 3, 3).
206
+ """
207
+ if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3:
208
+ raise ValueError("Invalid input euler angles.")
209
+ if len(convention) != 3:
210
+ raise ValueError("Convention must have 3 letters.")
211
+ if convention[1] in (convention[0], convention[2]):
212
+ raise ValueError(f"Invalid convention {convention}.")
213
+ for letter in convention:
214
+ if letter not in ("X", "Y", "Z"):
215
+ raise ValueError(f"Invalid letter {letter} in convention string.")
216
+ matrices = [
217
+ _axis_angle_rotation(c, e)
218
+ for c, e in zip(convention, torch.unbind(euler_angles, -1))
219
+ ]
220
+ # return functools.reduce(torch.matmul, matrices)
221
+ return torch.matmul(torch.matmul(matrices[0], matrices[1]), matrices[2])
222
+
223
+
224
+ def _angle_from_tan(
225
+ axis: str, other_axis: str, data, horizontal: bool, tait_bryan: bool
226
+ ) -> torch.Tensor:
227
+ """
228
+ Extract the first or third Euler angle from the two members of
229
+ the matrix which are positive constant times its sine and cosine.
230
+
231
+ Args:
232
+ axis: Axis label "X" or "Y or "Z" for the angle we are finding.
233
+ other_axis: Axis label "X" or "Y or "Z" for the middle axis in the
234
+ convention.
235
+ data: Rotation matrices as tensor of shape (..., 3, 3).
236
+ horizontal: Whether we are looking for the angle for the third axis,
237
+ which means the relevant entries are in the same row of the
238
+ rotation matrix. If not, they are in the same column.
239
+ tait_bryan: Whether the first and third axes in the convention differ.
240
+
241
+ Returns:
242
+ Euler Angles in radians for each matrix in data as a tensor
243
+ of shape (...).
244
+ """
245
+
246
+ i1, i2 = {"X": (2, 1), "Y": (0, 2), "Z": (1, 0)}[axis]
247
+ if horizontal:
248
+ i2, i1 = i1, i2
249
+ even = (axis + other_axis) in ["XY", "YZ", "ZX"]
250
+ if horizontal == even:
251
+ return torch.atan2(data[..., i1], data[..., i2])
252
+ if tait_bryan:
253
+ return torch.atan2(-data[..., i2], data[..., i1])
254
+ return torch.atan2(data[..., i2], -data[..., i1])
255
+
256
+
257
+ def _index_from_letter(letter: str) -> int:
258
+ if letter == "X":
259
+ return 0
260
+ if letter == "Y":
261
+ return 1
262
+ if letter == "Z":
263
+ return 2
264
+ raise ValueError("letter must be either X, Y or Z.")
265
+
266
+
267
+ def matrix_to_euler_angles(matrix: torch.Tensor, convention: str) -> torch.Tensor:
268
+ """
269
+ Convert rotations given as rotation matrices to Euler angles in radians.
270
+
271
+ Args:
272
+ matrix: Rotation matrices as tensor of shape (..., 3, 3).
273
+ convention: Convention string of three uppercase letters.
274
+
275
+ Returns:
276
+ Euler angles in radians as tensor of shape (..., 3).
277
+ """
278
+ if len(convention) != 3:
279
+ raise ValueError("Convention must have 3 letters.")
280
+ if convention[1] in (convention[0], convention[2]):
281
+ raise ValueError(f"Invalid convention {convention}.")
282
+ for letter in convention:
283
+ if letter not in ("X", "Y", "Z"):
284
+ raise ValueError(f"Invalid letter {letter} in convention string.")
285
+ if matrix.size(-1) != 3 or matrix.size(-2) != 3:
286
+ raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
287
+ i0 = _index_from_letter(convention[0])
288
+ i2 = _index_from_letter(convention[2])
289
+ tait_bryan = i0 != i2
290
+ if tait_bryan:
291
+ central_angle = torch.asin(
292
+ matrix[..., i0, i2] * (-1.0 if i0 - i2 in [-1, 2] else 1.0)
293
+ )
294
+ else:
295
+ central_angle = torch.acos(matrix[..., i0, i0])
296
+
297
+ o = (
298
+ _angle_from_tan(
299
+ convention[0], convention[1], matrix[..., i2], False, tait_bryan
300
+ ),
301
+ central_angle,
302
+ _angle_from_tan(
303
+ convention[2], convention[1], matrix[..., i0, :], True, tait_bryan
304
+ ),
305
+ )
306
+ return torch.stack(o, -1)
307
+
308
+
309
+ def random_quaternions(
310
+ n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
311
+ ) -> torch.Tensor:
312
+ """
313
+ Generate random quaternions representing rotations,
314
+ i.e. versors with nonnegative real part.
315
+
316
+ Args:
317
+ n: Number of quaternions in a batch to return.
318
+ dtype: Type to return.
319
+ device: Desired device of returned tensor. Default:
320
+ uses the current device for the default tensor type.
321
+
322
+ Returns:
323
+ Quaternions as tensor of shape (N, 4).
324
+ """
325
+ if isinstance(device, str):
326
+ device = torch.device(device)
327
+ o = torch.randn((n, 4), dtype=dtype, device=device)
328
+ s = (o * o).sum(1)
329
+ o = o / _copysign(torch.sqrt(s), o[:, 0])[:, None]
330
+ return o
331
+
332
+
333
+ def random_rotations(
334
+ n: int, dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
335
+ ) -> torch.Tensor:
336
+ """
337
+ Generate random rotations as 3x3 rotation matrices.
338
+
339
+ Args:
340
+ n: Number of rotation matrices in a batch to return.
341
+ dtype: Type to return.
342
+ device: Device of returned tensor. Default: if None,
343
+ uses the current device for the default tensor type.
344
+
345
+ Returns:
346
+ Rotation matrices as tensor of shape (n, 3, 3).
347
+ """
348
+ quaternions = random_quaternions(n, dtype=dtype, device=device)
349
+ return quaternion_to_matrix(quaternions)
350
+
351
+
352
+ def random_rotation(
353
+ dtype: Optional[torch.dtype] = None, device: Optional[Device] = None
354
+ ) -> torch.Tensor:
355
+ """
356
+ Generate a single random 3x3 rotation matrix.
357
+
358
+ Args:
359
+ dtype: Type to return
360
+ device: Device of returned tensor. Default: if None,
361
+ uses the current device for the default tensor type
362
+
363
+ Returns:
364
+ Rotation matrix as tensor of shape (3, 3).
365
+ """
366
+ return random_rotations(1, dtype, device)[0]
367
+
368
+
369
+ def standardize_quaternion(quaternions: torch.Tensor) -> torch.Tensor:
370
+ """
371
+ Convert a unit quaternion to a standard form: one in which the real
372
+ part is non negative.
373
+
374
+ Args:
375
+ quaternions: Quaternions with real part first,
376
+ as tensor of shape (..., 4).
377
+
378
+ Returns:
379
+ Standardized quaternions as tensor of shape (..., 4).
380
+ """
381
+ return torch.where(quaternions[..., 0:1] < 0, -quaternions, quaternions)
382
+
383
+
384
+ def quaternion_raw_multiply(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
385
+ """
386
+ Multiply two quaternions.
387
+ Usual torch rules for broadcasting apply.
388
+
389
+ Args:
390
+ a: Quaternions as tensor of shape (..., 4), real part first.
391
+ b: Quaternions as tensor of shape (..., 4), real part first.
392
+
393
+ Returns:
394
+ The product of a and b, a tensor of quaternions shape (..., 4).
395
+ """
396
+ aw, ax, ay, az = torch.unbind(a, -1)
397
+ bw, bx, by, bz = torch.unbind(b, -1)
398
+ ow = aw * bw - ax * bx - ay * by - az * bz
399
+ ox = aw * bx + ax * bw + ay * bz - az * by
400
+ oy = aw * by - ax * bz + ay * bw + az * bx
401
+ oz = aw * bz + ax * by - ay * bx + az * bw
402
+ return torch.stack((ow, ox, oy, oz), -1)
403
+
404
+
405
+ def quaternion_multiply(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
406
+ """
407
+ Multiply two quaternions representing rotations, returning the quaternion
408
+ representing their composition, i.e. the versor with nonnegative real part.
409
+ Usual torch rules for broadcasting apply.
410
+
411
+ Args:
412
+ a: Quaternions as tensor of shape (..., 4), real part first.
413
+ b: Quaternions as tensor of shape (..., 4), real part first.
414
+
415
+ Returns:
416
+ The product of a and b, a tensor of quaternions of shape (..., 4).
417
+ """
418
+ ab = quaternion_raw_multiply(a, b)
419
+ return standardize_quaternion(ab)
420
+
421
+
422
+ def quaternion_invert(quaternion: torch.Tensor) -> torch.Tensor:
423
+ """
424
+ Given a quaternion representing rotation, get the quaternion representing
425
+ its inverse.
426
+
427
+ Args:
428
+ quaternion: Quaternions as tensor of shape (..., 4), with real part
429
+ first, which must be versors (unit quaternions).
430
+
431
+ Returns:
432
+ The inverse, a tensor of quaternions of shape (..., 4).
433
+ """
434
+
435
+ scaling = torch.tensor([1, -1, -1, -1], device=quaternion.device)
436
+ return quaternion * scaling
437
+
438
+
439
+ def quaternion_apply(quaternion: torch.Tensor, point: torch.Tensor) -> torch.Tensor:
440
+ """
441
+ Apply the rotation given by a quaternion to a 3D point.
442
+ Usual torch rules for broadcasting apply.
443
+
444
+ Args:
445
+ quaternion: Tensor of quaternions, real part first, of shape (..., 4).
446
+ point: Tensor of 3D points of shape (..., 3).
447
+
448
+ Returns:
449
+ Tensor of rotated points of shape (..., 3).
450
+ """
451
+ if point.size(-1) != 3:
452
+ raise ValueError(f"Points are not in 3D, {point.shape}.")
453
+ real_parts = point.new_zeros(point.shape[:-1] + (1,))
454
+ point_as_quaternion = torch.cat((real_parts, point), -1)
455
+ out = quaternion_raw_multiply(
456
+ quaternion_raw_multiply(quaternion, point_as_quaternion),
457
+ quaternion_invert(quaternion),
458
+ )
459
+ return out[..., 1:]
460
+
461
+
462
+ def axis_angle_to_matrix(axis_angle: torch.Tensor) -> torch.Tensor:
463
+ """
464
+ Convert rotations given as axis/angle to rotation matrices.
465
+
466
+ Args:
467
+ axis_angle: Rotations given as a vector in axis angle form,
468
+ as a tensor of shape (..., 3), where the magnitude is
469
+ the angle turned anticlockwise in radians around the
470
+ vector's direction.
471
+
472
+ Returns:
473
+ Rotation matrices as tensor of shape (..., 3, 3).
474
+ """
475
+ return quaternion_to_matrix(axis_angle_to_quaternion(axis_angle))
476
+
477
+
478
+ def matrix_to_axis_angle(matrix: torch.Tensor) -> torch.Tensor:
479
+ """
480
+ Convert rotations given as rotation matrices to axis/angle.
481
+
482
+ Args:
483
+ matrix: Rotation matrices as tensor of shape (..., 3, 3).
484
+
485
+ Returns:
486
+ Rotations given as a vector in axis angle form, as a tensor
487
+ of shape (..., 3), where the magnitude is the angle
488
+ turned anticlockwise in radians around the vector's
489
+ direction.
490
+ """
491
+ return quaternion_to_axis_angle(matrix_to_quaternion(matrix))
492
+
493
+
494
+ def axis_angle_to_quaternion(axis_angle: torch.Tensor) -> torch.Tensor:
495
+ """
496
+ Convert rotations given as axis/angle to quaternions.
497
+
498
+ Args:
499
+ axis_angle: Rotations given as a vector in axis angle form,
500
+ as a tensor of shape (..., 3), where the magnitude is
501
+ the angle turned anticlockwise in radians around the
502
+ vector's direction.
503
+
504
+ Returns:
505
+ quaternions with real part first, as tensor of shape (..., 4).
506
+ """
507
+ angles = torch.norm(axis_angle, p=2, dim=-1, keepdim=True)
508
+ half_angles = angles * 0.5
509
+ eps = 1e-6
510
+ small_angles = angles.abs() < eps
511
+ sin_half_angles_over_angles = torch.empty_like(angles)
512
+ sin_half_angles_over_angles[~small_angles] = (
513
+ torch.sin(half_angles[~small_angles]) / angles[~small_angles]
514
+ )
515
+ # for x small, sin(x/2) is about x/2 - (x/2)^3/6
516
+ # so sin(x/2)/x is about 1/2 - (x*x)/48
517
+ sin_half_angles_over_angles[small_angles] = (
518
+ 0.5 - (angles[small_angles] * angles[small_angles]) / 48
519
+ )
520
+ quaternions = torch.cat(
521
+ [torch.cos(half_angles), axis_angle * sin_half_angles_over_angles], dim=-1
522
+ )
523
+ return quaternions
524
+
525
+
526
+ def quaternion_to_axis_angle(quaternions: torch.Tensor) -> torch.Tensor:
527
+ """
528
+ Convert rotations given as quaternions to axis/angle.
529
+
530
+ Args:
531
+ quaternions: quaternions with real part first,
532
+ as tensor of shape (..., 4).
533
+
534
+ Returns:
535
+ Rotations given as a vector in axis angle form, as a tensor
536
+ of shape (..., 3), where the magnitude is the angle
537
+ turned anticlockwise in radians around the vector's
538
+ direction.
539
+ """
540
+ norms = torch.norm(quaternions[..., 1:], p=2, dim=-1, keepdim=True)
541
+ half_angles = torch.atan2(norms, quaternions[..., :1])
542
+ angles = 2 * half_angles
543
+ eps = 1e-6
544
+ small_angles = angles.abs() < eps
545
+ sin_half_angles_over_angles = torch.empty_like(angles)
546
+ sin_half_angles_over_angles[~small_angles] = (
547
+ torch.sin(half_angles[~small_angles]) / angles[~small_angles]
548
+ )
549
+ # for x small, sin(x/2) is about x/2 - (x/2)^3/6
550
+ # so sin(x/2)/x is about 1/2 - (x*x)/48
551
+ sin_half_angles_over_angles[small_angles] = (
552
+ 0.5 - (angles[small_angles] * angles[small_angles]) / 48
553
+ )
554
+ return quaternions[..., 1:] / sin_half_angles_over_angles
555
+
556
+
557
+ def rotation_6d_to_matrix(d6: torch.Tensor) -> torch.Tensor:
558
+ """
559
+ Converts 6D rotation representation by Zhou et al. [1] to rotation matrix
560
+ using Gram--Schmidt orthogonalization per Section B of [1].
561
+ Args:
562
+ d6: 6D rotation representation, of size (*, 6)
563
+
564
+ Returns:
565
+ batch of rotation matrices of size (*, 3, 3)
566
+
567
+ [1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
568
+ On the Continuity of Rotation Representations in Neural Networks.
569
+ IEEE Conference on Computer Vision and Pattern Recognition, 2019.
570
+ Retrieved from http://arxiv.org/abs/1812.07035
571
+ """
572
+
573
+ a1, a2 = d6[..., :3], d6[..., 3:]
574
+ b1 = F.normalize(a1, dim=-1)
575
+ b2 = a2 - (b1 * a2).sum(-1, keepdim=True) * b1
576
+ b2 = F.normalize(b2, dim=-1)
577
+ b3 = torch.cross(b1, b2, dim=-1)
578
+ return torch.stack((b1, b2, b3), dim=-2)
579
+
580
+
581
+ def matrix_to_rotation_6d(matrix: torch.Tensor) -> torch.Tensor:
582
+ """
583
+ Converts rotation matrices to 6D rotation representation by Zhou et al. [1]
584
+ by dropping the last row. Note that 6D representation is not unique.
585
+ Args:
586
+ matrix: batch of rotation matrices of size (*, 3, 3)
587
+
588
+ Returns:
589
+ 6D rotation representation, of size (*, 6)
590
+
591
+ [1] Zhou, Y., Barnes, C., Lu, J., Yang, J., & Li, H.
592
+ On the Continuity of Rotation Representations in Neural Networks.
593
+ IEEE Conference on Computer Vision and Pattern Recognition, 2019.
594
+ Retrieved from http://arxiv.org/abs/1812.07035
595
+ """
596
+ batch_dim = matrix.size()[:-2]
597
+ return matrix[..., :2, :].clone().reshape(batch_dim + (6,))
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from .basic import DiffusionModel
2
+ from .diffusionsde import DiscreteDiffusionSDE, ContinuousDiffusionSDE
3
+ from .newedm import ContinuousEDM
4
+ from .rectifiedflow import DiscreteRectifiedFlow, ContinuousRectifiedFlow
5
+ from .consistency_model import ContinuousConsistencyModel
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/basic.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from copy import deepcopy
2
+ from typing import Optional, Union
3
+
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn as nn
7
+
8
+ from cleandiffuser.utils import to_tensor
9
+ from cleandiffuser.classifier import BaseClassifier
10
+ from cleandiffuser.nn_condition import BaseNNCondition, IdentityCondition
11
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
12
+
13
+
14
+ class DiffusionModel:
15
+
16
+ def __init__(
17
+ self,
18
+
19
+ # ----------------- Neural Networks ----------------- #
20
+ # NN backbone for the diffusion model
21
+ nn_diffusion: BaseNNDiffusion,
22
+ # Add a condition-process NN to enable classifier-free-guidance
23
+ nn_condition: Optional[BaseNNCondition] = None,
24
+
25
+ # ----------------- Masks ----------------- #
26
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
27
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
28
+ # Add loss weight
29
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
30
+
31
+ # ------------------ Plugs ---------------- #
32
+ # Add a classifier to enable classifier-guidance
33
+ classifier: Optional[BaseClassifier] = None,
34
+
35
+ # ------------------ Params ---------------- #
36
+ grad_clip_norm: Optional[float] = None,
37
+ diffusion_steps: int = 1000,
38
+ ema_rate: float = 0.995,
39
+ optim_params: Optional[dict] = None,
40
+
41
+ device: Union[torch.device, str] = "cpu"
42
+ ):
43
+ if optim_params is None:
44
+ optim_params = {"lr": 2e-4, "weight_decay": 1e-5}
45
+
46
+ self.device = device
47
+ self.grad_clip_norm = grad_clip_norm
48
+ self.diffusion_steps = diffusion_steps
49
+ self.ema_rate = ema_rate
50
+
51
+ # nn_condition is None means that the model is not conditioned on any input.
52
+ if nn_condition is None:
53
+ nn_condition = IdentityCondition()
54
+
55
+ # In the code implementation of Diffusion models, it is common to maintain an exponential
56
+ # moving average (EMA) version of the model for inference, as it has been observed that
57
+ # this approach can result in more stable generation outcomes.
58
+ self.model = nn.ModuleDict({
59
+ "diffusion": nn_diffusion.to(self.device),
60
+ "condition": nn_condition.to(self.device)})
61
+ self.model_ema = deepcopy(self.model).requires_grad_(False)
62
+
63
+ self.model.train()
64
+ self.model_ema.eval()
65
+
66
+ self.optimizer = torch.optim.AdamW(self.model.parameters(), **optim_params)
67
+
68
+ self.classifier = classifier
69
+
70
+ self.fix_mask = to_tensor(fix_mask, self.device)[None, ] if fix_mask is not None else 0.
71
+ self.loss_weight = to_tensor(loss_weight, self.device)[None, ] if loss_weight is not None else 1.
72
+
73
+ def train(self):
74
+ self.model.train()
75
+ if self.classifier is not None:
76
+ self.classifier.model.train()
77
+
78
+ def eval(self):
79
+ self.model.eval()
80
+ if self.classifier is not None:
81
+ self.classifier.model.eval()
82
+
83
+ def ema_update(self):
84
+ with torch.no_grad():
85
+ for p, p_ema in zip(self.model.parameters(), self.model_ema.parameters()):
86
+ p_ema.data.mul_(self.ema_rate).add_(p.data, alpha=1. - self.ema_rate)
87
+
88
+ def update(self, x0, condition=None, update_ema=True, **kwargs):
89
+ raise NotImplementedError
90
+
91
+ def sample(self, *args, **kwargs):
92
+ raise NotImplementedError
93
+
94
+ def save(self, path: str):
95
+ torch.save({
96
+ "model": self.model.state_dict(),
97
+ "model_ema": self.model_ema.state_dict(),
98
+ }, path)
99
+
100
+ def load(self, path: str):
101
+ checkpoint = torch.load(path, map_location=self.device)
102
+ self.model.load_state_dict(checkpoint["model"])
103
+ self.model_ema.load_state_dict(checkpoint["model_ema"])
104
+
105
+
106
+
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/consistency_model.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Callable, List
2
+ from typing import Optional, Union
3
+
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn as nn
7
+
8
+ from cleandiffuser.diffusion import DiffusionModel
9
+ from cleandiffuser.nn_condition import BaseNNCondition
10
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
11
+ from cleandiffuser.utils import at_least_ndim
12
+ from .newedm import ContinuousEDM
13
+
14
+
15
+ def erf(x):
16
+ a1 = 0.254829592
17
+ a2 = -0.284496736
18
+ a3 = 1.421413741
19
+ a4 = -1.453152027
20
+ a5 = 1.061405429
21
+ p = 0.3275911
22
+
23
+ sign = np.sign(x)
24
+ x = np.abs(x)
25
+ t = 1.0 / (1.0 + p * x)
26
+ y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * np.exp(-x * x)
27
+ return sign * y
28
+
29
+
30
+ def compare_properties(obj1, obj2, properties: List[str]):
31
+ differences = []
32
+ for prop in properties:
33
+ obj1_prop = getattr(obj1, prop)
34
+ obj2_prop = getattr(obj2, prop)
35
+ if isinstance(obj1_prop, torch.Tensor):
36
+ if not torch.allclose(obj1_prop, obj2_prop):
37
+ differences.append(prop)
38
+ elif isinstance(obj1_prop, np.ndarray):
39
+ if not np.allclose(obj1_prop, obj2_prop):
40
+ differences.append(prop)
41
+ else:
42
+ if obj1_prop != obj2_prop:
43
+ differences.append(prop)
44
+ return differences
45
+
46
+
47
+ def pseudo_huber_loss(source: torch.Tensor, target: torch.Tensor, c: float = 0.0):
48
+ return ((source - target) ** 2 + c ** 2).sqrt() - c
49
+
50
+
51
+ class CMCurriculumLogger:
52
+ def __init__(
53
+ self, s0: int = 10, s1: int = 1280, curriculum_cycle: int = 100_000,
54
+ sigma_min: float = 0.002, sigma_max: float = 80., rho: float = 7.,
55
+ P_mean: float = -1.1, P_std: float = 2.0
56
+ ):
57
+ self.Kprime = np.ceil(curriculum_cycle / (np.log2(np.ceil(s1 / s0)) + 1))
58
+ self.Nk = s0
59
+ self.s0, self.s1 = s0, s1
60
+ self.curriculum_cycle = curriculum_cycle
61
+ self.sigma_min, self.sigma_max, self.rho = sigma_min, sigma_max, rho
62
+ self.P_mean, self.P_std = P_mean, P_std
63
+
64
+ self.ceil_k_div_Kprime, self.k = None, None
65
+
66
+ self.update_k(0)
67
+
68
+ def update_k(self, k):
69
+ self.k = k
70
+ if np.ceil(k / self.Kprime) != self.ceil_k_div_Kprime:
71
+ self.ceil_k_div_Kprime = np.ceil(k / self.Kprime)
72
+ self.Nk = int(min(self.s0 * (2 ** self.ceil_k_div_Kprime), self.s1))
73
+
74
+ self.sigmas = ((self.sigma_min ** (1 / self.rho) + np.arange(self.Nk + 1, dtype=np.float32)
75
+ / self.Nk * (self.sigma_max ** (1 / self.rho) - self.sigma_min ** (
76
+ 1 / self.rho))) ** self.rho)
77
+
78
+ self.p_sigmas = (erf((np.log(self.sigmas[1:]) - self.P_mean) / (self.P_std * (2 ** 0.5))) -
79
+ erf((np.log(self.sigmas[:-1]) - self.P_mean) / (self.P_std * (2 ** 0.5))))
80
+ self.p_sigmas = self.p_sigmas / self.p_sigmas.sum()
81
+
82
+ def incremental_update_k(self):
83
+ self.update_k(self.k + 1)
84
+
85
+ @property
86
+ def curriculum_process(self):
87
+ return (self.k % self.curriculum_cycle) / self.curriculum_cycle
88
+
89
+
90
+ class ContinuousConsistencyModel(DiffusionModel):
91
+ """**Continuous-time Consistency Model**
92
+
93
+ The Consistency Model defines a consistency function.
94
+ A consistency function has the property of self-consistency:
95
+ its outputs are consistent for arbitrary pairs of (x_t, t) that belong to the same PF ODE trajectory.
96
+ To learn such a consistency function, the Consistency Model needs to be distilled either from a pre-trained EDM
97
+ or learned directly through consistency training loss.
98
+ This self-consistency property allows the Consistency Model in theory to achieve one-step generation.
99
+
100
+ The current implementation of Consistency Model only supports continuous-time ODEs.
101
+ The sampling steps are required to be greater than 0.
102
+
103
+ Args:
104
+ nn_diffusion: BaseNNDiffusion,
105
+ The neural network backbone for the Diffusion model.
106
+ nn_condition: Optional[BaseNNCondition],
107
+ The neural network backbone for the condition embedding.
108
+
109
+ fix_mask: Union[list, np.ndarray, torch.Tensor],
110
+ Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
111
+ The mask should be in the shape of `x_shape`.
112
+ loss_weight: Union[list, np.ndarray, torch.Tensor],
113
+ Add loss weight. The weight should be in the shape of `x_shape`.
114
+
115
+ classifier: Optional[BaseClassifier],
116
+ The Consistency Model does not support classifier guidance; please set this option to `None`.
117
+
118
+ grad_clip_norm: Optional[float],
119
+ Gradient clipping norm.
120
+ ema_rate: float,
121
+ Exponential moving average rate.
122
+ optim_params: Optional[dict],
123
+ Optimizer parameters.
124
+
125
+ s0: int,
126
+ The minimum number of noise levels. Default: 10.
127
+ s1: int,
128
+ The maximum number of noise levels. Default: 1280.
129
+ data_dim: int,
130
+ The dimension of the data, which affects the `pseudo_huber_constant`.
131
+ As suggested in `improved Consistency Models`, `pseudo_huber_constant` = 0.00054 * np.sqrt(data_dim).
132
+ If `data_dim` is `None`, then `pseudo_huber_constant` = 0.01 will be used.
133
+ P_mean: float,
134
+ Hyperparameter for noise sampling during training. Default: -1.1.
135
+ P_std: float,
136
+ Hyperparameter for noise sampling during training. Default: 2.0.
137
+ sigma_min: float,
138
+ The minimum standard deviation of the noise. Default: 0.002.
139
+ sigma_max: float,
140
+ The maximum standard deviation of the noise. Default: 80.
141
+ sigma_data: float,
142
+ The standard deviation of the data. Default: 0.5.
143
+ rho: float,
144
+ The power of the noise schedule. Default: 7.
145
+ curriculum_cycle: int,
146
+ The cycle of the curriculum process.
147
+ It is best to set `curriculum_cycle` to the number of model training iterations. Default: 100_000.
148
+
149
+ x_max: Optional[torch.Tensor],
150
+ The maximum value for the input data. `None` indicates no constraint.
151
+ x_min: Optional[torch.Tensor],
152
+ The minimum value for the input data. `None` indicates no constraint.
153
+
154
+ device: Union[torch.device, str],
155
+ The device to run the model.
156
+ """
157
+
158
+ def __init__(
159
+ self,
160
+
161
+ # ----------------- Neural Networks ----------------- #
162
+ nn_diffusion: BaseNNDiffusion,
163
+ nn_condition: Optional[BaseNNCondition] = None,
164
+
165
+ # ----------------- Masks ----------------- #
166
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
167
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
168
+ # Add loss weight
169
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
170
+
171
+ # ------------------ Plugins ---------------- #
172
+ # Do not support CG
173
+ classifier=None,
174
+
175
+ # ------------------ Training Params ---------------- #
176
+ grad_clip_norm: Optional[float] = None,
177
+ ema_rate: float = 0.9999,
178
+ optim_params: Optional[dict] = None,
179
+
180
+ # ------------------- Consistency Model Params ------------------- #
181
+ s0: int = 10,
182
+ s1: int = 1280,
183
+ data_dim: int = None,
184
+ P_mean: float = -1.1,
185
+ P_std: float = 2.0,
186
+ sigma_min: float = 0.002,
187
+ sigma_max: float = 80.,
188
+ sigma_data: float = 0.5,
189
+ rho: float = 7.0,
190
+ curriculum_cycle: int = 100_000,
191
+
192
+ x_max: Optional[torch.Tensor] = None,
193
+ x_min: Optional[torch.Tensor] = None,
194
+
195
+ device: Union[torch.device, str] = "cpu"
196
+ ):
197
+ super().__init__(
198
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
199
+ 0, ema_rate, optim_params, device)
200
+
201
+ self.cur_logger = CMCurriculumLogger(
202
+ s0, s1, curriculum_cycle, sigma_min, sigma_max, rho, P_mean, P_std)
203
+
204
+ self.pseudo_huber_constant = 0.01 if data_dim is None else 0.00054 * np.sqrt(data_dim)
205
+
206
+ self.rho = rho
207
+ self.sigma_data, self.sigma_max, self.sigma_min = sigma_data, sigma_max, sigma_min
208
+ self.x_max = x_max.to(device) if isinstance(x_max, torch.Tensor) else x_max
209
+ self.x_min = x_min.to(device) if isinstance(x_min, torch.Tensor) else x_min
210
+
211
+ self.edm = None
212
+ self.distillation_sigmas, self.distillation_N = None, None
213
+
214
+ def prepare_distillation(self, edm: ContinuousEDM, distillation_N: int = 18):
215
+ checklist = [
216
+ "sigma_data", "sigma_max", "sigma_min", "rho", "x_max", "x_min",
217
+ "fix_mask", "loss_weight", "device"]
218
+ differences = compare_properties(self, edm, checklist)
219
+ if len(differences) != 0:
220
+ raise ValueError(f"Properties {differences} are different between the EDM and the Consistency Model.")
221
+ self.edm = edm
222
+ self.model.load_state_dict(edm.model.state_dict())
223
+ self.model_ema.load_state_dict(edm.model_ema.state_dict())
224
+ self.distillation_N = distillation_N
225
+ self.distillation_sigmas = self.training_noise_schedule(distillation_N)
226
+
227
+ @property
228
+ def supported_solvers(self):
229
+ return ["none"]
230
+
231
+ @property
232
+ def clip_pred(self):
233
+ return (self.x_max is not None) or (self.x_min is not None)
234
+
235
+ def training_noise_schedule(self, N):
236
+ sigma = ((self.sigma_min ** (1 / self.rho) + np.arange(N + 1)
237
+ / N * (self.sigma_max ** (1 / self.rho) - self.sigma_min ** (1 / self.rho))) ** self.rho)
238
+ return torch.tensor(sigma, device=self.device, dtype=torch.float32)
239
+
240
+ # ===================== CM Pre-conditioning =======================
241
+ def c_skip(self, sigma):
242
+ return self.sigma_data ** 2 / (self.sigma_data ** 2 + (sigma - self.sigma_min) ** 2)
243
+
244
+ def c_out(self, sigma):
245
+ return (sigma - self.sigma_min) * self.sigma_data / (self.sigma_data ** 2 + sigma ** 2).sqrt()
246
+
247
+ def c_in(self, sigma):
248
+ return 1 / (self.sigma_data ** 2 + sigma ** 2).sqrt()
249
+
250
+ def c_noise(self, sigma):
251
+ return 0.25 * sigma.log()
252
+
253
+ def f(self, x, t, condition=None, model=None):
254
+ c_skip, c_out, c_in, c_noise = self.c_skip(t), self.c_out(t), self.c_in(t), self.c_noise(t)
255
+ if model is None:
256
+ model = self.model
257
+ c_skip, c_in, c_out = (
258
+ at_least_ndim(c_skip, x.dim()), at_least_ndim(c_in, x.dim()), at_least_ndim(c_out, x.dim()))
259
+ pred_x = c_skip * x + c_out * model["diffusion"](c_in * x, c_noise, condition)
260
+ if self.clip_pred:
261
+ pred_x = pred_x.clip(self.x_min, self.x_max)
262
+ return pred_x
263
+
264
+ def distillation_loss(self, x0, condition=None):
265
+
266
+ assert self.edm is not None, "Please call `prepare_distillation` before distillation."
267
+
268
+ idx = torch.randint(self.distillation_N, (x0.shape[0],), device=self.device)
269
+ t_m, t_n = self.distillation_sigmas[idx + 1], self.distillation_sigmas[idx]
270
+ x_m, t_m, eps = self.edm.add_noise(x0, t_m, None)
271
+
272
+ with torch.no_grad():
273
+ condition_vec_cfg = self.edm.model_ema["condition"](condition) if condition is not None else None
274
+ pred, logp = self.edm.guided_sampling(
275
+ x_m, t_m, None, self.edm.model_ema, condition_vec_cfg, 1.0, None, 0.0, False)
276
+ dot_x = (x_m - pred) / at_least_ndim(t_m, x_m.dim())
277
+ delta_t = t_m - t_n
278
+ x_n = x_m - dot_x * at_least_ndim(delta_t, x_m.dim())
279
+ x_n = x_n * (1. - self.fix_mask) + x0 * self.fix_mask
280
+
281
+ condition_vec = self.model["condition"](condition) if condition is not None else None
282
+ pred_x_m = self.f(x_m, t_m, condition_vec, self.model)
283
+ with torch.no_grad():
284
+ condition_vec_ema = self.model_ema["condition"](condition) if condition is not None else None
285
+ pred_x_n = self.f(x_n, t_n, condition_vec_ema, self.model_ema)
286
+
287
+ loss = (((pred_x_n - pred_x_m) ** 2) * (1 - self.fix_mask) *
288
+ self.loss_weight * at_least_ndim((1 / (t_m - t_n)), pred_x_n.dim()))
289
+
290
+ return loss.mean(), None
291
+
292
+ def training_loss(self, x0, condition=None):
293
+
294
+ idx = np.random.choice(self.cur_logger.Nk, size=x0.shape[0], p=self.cur_logger.p_sigmas)
295
+
296
+ # m = n + 1
297
+ sigma_n = torch.tensor(self.cur_logger.sigmas[idx], device=self.device)
298
+ sigma_m = torch.tensor(self.cur_logger.sigmas[idx + 1], device=self.device)
299
+
300
+ eps = torch.randn_like(x0)
301
+
302
+ x_n = x0 + at_least_ndim(sigma_n, x0.dim()) * eps
303
+ x_m = x0 + at_least_ndim(sigma_m, x0.dim()) * eps
304
+
305
+ condition = self.model["condition"](condition) if condition is not None else None
306
+
307
+ pred_x_m = self.f(x_m, sigma_m, condition, self.model)
308
+ with torch.no_grad():
309
+ pred_x_n = self.f(x_n, sigma_n, condition.detach(), self.model)
310
+
311
+ loss = pseudo_huber_loss(pred_x_m, pred_x_n, self.pseudo_huber_constant)
312
+
313
+ unweighted_loss = (loss * (1 - self.fix_mask) * self.loss_weight)
314
+
315
+ cm_loss_weight = at_least_ndim(1 / (sigma_m - sigma_n), x0.dim())
316
+
317
+ return (unweighted_loss * cm_loss_weight).mean(), unweighted_loss.mean().item()
318
+
319
+ def update(self, x0, condition=None, update_ema=True, loss_type="training", **kwargs):
320
+ """ One-step gradient update.
321
+
322
+ Args:
323
+ x0: torch.Tensor,
324
+ Samples from the target distribution.
325
+ condition: Optional,
326
+ Condition of x0. `None` indicates no condition.
327
+ update_ema: bool,
328
+ Whether to update the exponential moving average model.
329
+ loss_type: str,
330
+ The type of loss. `training` or `distillation`.
331
+
332
+ Returns:
333
+ log: dict,
334
+ The log dictionary.
335
+
336
+ Examples:
337
+ >>> model = ContinuousConsistencyModel(...)
338
+ >>> x0 = torch.randn(*x_shape)
339
+ >>> condition = torch.randn(*condition_shape)
340
+ >>> log = model.update(x0, condition, loss_type="training") # training
341
+ >>> log = model.update(x0, condition, loss_type="distillation") # distillation
342
+ """
343
+ if loss_type == "training":
344
+ loss, unweighted_loss = self.training_loss(x0, condition)
345
+ elif loss_type == "distillation":
346
+ loss, unweighted_loss = self.distillation_loss(x0, condition)
347
+ else:
348
+ raise ValueError(f"Unknown loss type: {loss_type}")
349
+
350
+ loss.backward()
351
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
352
+ if self.grad_clip_norm else None
353
+ self.optimizer.step()
354
+ self.optimizer.zero_grad()
355
+
356
+ if update_ema:
357
+ self.ema_update()
358
+
359
+ if loss_type == "training":
360
+ self.cur_logger.incremental_update_k()
361
+
362
+ log = {"loss": loss.item(), "grad_norm": grad_norm, "unweighted_loss": unweighted_loss}
363
+
364
+ return log
365
+
366
+ def sample(
367
+ self,
368
+ # ---------- the known fixed portion ---------- #
369
+ prior: torch.Tensor,
370
+ # ----------------- sampling ----------------- #
371
+ solver: str = "none",
372
+ n_samples: int = 1,
373
+ sample_steps: int = 5,
374
+ sample_step_schedule: Union[str, Callable] = "uniform",
375
+ use_ema: bool = True,
376
+ temperature: float = 1.0,
377
+ # ------------------ guidance ------------------ #
378
+ condition_cfg=None,
379
+ mask_cfg=None,
380
+ w_cfg: float = 0.0,
381
+ condition_cg=None,
382
+ w_cg: float = 0.0,
383
+ # ----------- Diffusion-X sampling ----------
384
+ diffusion_x_sampling_steps: int = 0,
385
+ # ----------- Warm-Starting -----------
386
+ warm_start_reference: Optional[torch.Tensor] = None,
387
+ warm_start_forward_level: float = 0.3,
388
+ # ------------------ others ------------------ #
389
+ requires_grad: bool = False,
390
+ preserve_history: bool = False,
391
+ **kwargs,
392
+ ):
393
+ assert w_cg == 0.0 and condition_cg is None, "Consistency Distillation does not support classifier guidance."
394
+
395
+ log = {
396
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
397
+
398
+ model = self.model if not use_ema else self.model_ema
399
+
400
+ prior = prior.to(self.device)
401
+ xt = torch.randn_like(prior) * self.sigma_max * temperature
402
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
403
+
404
+ if preserve_history:
405
+ log["sample_history"][:, 0] = xt.cpu().numpy()
406
+
407
+ with torch.set_grad_enabled(requires_grad):
408
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
409
+
410
+ # ===================== Sampling Schedule ====================
411
+ sample_step_schedule = ((self.sigma_min ** (1 / self.rho) + torch.arange(sample_steps + 1, device=self.device)
412
+ / sample_steps * (self.sigma_max ** (1 / self.rho) - self.sigma_min ** (1 / self.rho)))
413
+ ** self.rho)
414
+
415
+ t = torch.full((n_samples,), sample_step_schedule[-1], dtype=torch.float32, device=self.device)
416
+ pred_x = self.f(xt, t, condition_vec_cfg, model)
417
+ pred_x = pred_x * (1. - self.fix_mask) + prior * self.fix_mask
418
+
419
+ loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps))
420
+
421
+ for i in reversed(loop_steps):
422
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
423
+ xt = pred_x + (at_least_ndim(t, xt.dim()) ** 2 - self.sigma_min ** 2).sqrt() * torch.randn_like(xt)
424
+
425
+ pred_x = self.f(xt, t, condition_vec_cfg, model)
426
+ pred_x = pred_x * (1. - self.fix_mask) + prior * self.fix_mask
427
+
428
+ return pred_x, log
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddim.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn as nn
7
+
8
+ from cleandiffuser.classifier import BaseClassifier
9
+ from cleandiffuser.nn_condition import BaseNNCondition
10
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
11
+ from cleandiffuser.utils import at_least_ndim
12
+ from .basic import DiffusionModel
13
+
14
+
15
+ class DDIM(DiffusionModel):
16
+
17
+ def __init__(
18
+ self,
19
+
20
+ # ----------------- Neural Networks ----------------- #
21
+ nn_diffusion: BaseNNDiffusion,
22
+ nn_condition: Optional[BaseNNCondition] = None,
23
+
24
+ # ----------------- Masks ----------------- #
25
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
26
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
27
+ # Add loss weight
28
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
29
+
30
+ # ------------------ Plugs ---------------- #
31
+ # Add a classifier to enable classifier-guidance
32
+ classifier: Optional[BaseClassifier] = None,
33
+
34
+ # ------------------ Params ---------------- #
35
+ grad_clip_norm: Optional[float] = None,
36
+ diffusion_steps: int = 1000,
37
+ ema_rate: float = 0.995,
38
+ optim_params: Optional[dict] = None,
39
+
40
+ # ------------------- DPM Params ------------------- #
41
+ noise_schedule: str = "linear", # or cosine
42
+ t_eps: float = 1e-3,
43
+
44
+ device: Union[torch.device, str] = "cpu"
45
+ ):
46
+ super().__init__(
47
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
48
+ diffusion_steps, ema_rate, optim_params, device)
49
+
50
+ self.noise_schedule = noise_schedule
51
+ self.t_eps = t_eps
52
+
53
+ @property
54
+ def t_range(self):
55
+ if self.noise_schedule == "linear":
56
+ return self.t_eps, 1.
57
+ elif self.noise_schedule == "cosine":
58
+ return self.t_eps, 0.9946
59
+ else:
60
+ raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
61
+
62
+ def alpha_schedule(self, t):
63
+ if self.noise_schedule == "linear":
64
+ beta0, beta1 = 0.1, 20
65
+ return (-(beta1-beta0)/4*(t**2) - beta0/2*t).exp()
66
+ elif self.noise_schedule == "cosine":
67
+ s = 0.008
68
+ return ((torch.cos(np.pi/2*(t+s)/(1+s))).log() - np.log(np.cos(np.pi/2*s/(1+s)))).exp()
69
+ else:
70
+ raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Training
74
+
75
+ def add_noise(self, x0, t=None, eps=None):
76
+ if t is None:
77
+ t = torch.rand((x0.shape[0], ), device=self.device)
78
+ t = self.t_range[0] + t * (self.t_range[1] - self.t_range[0])
79
+ eps = torch.randn_like(x0) if eps is None else eps
80
+ alpha = self.alpha_schedule(at_least_ndim(t, x0.dim()))
81
+ sigma = (1 - alpha ** 2).sqrt()
82
+ xt = x0 * alpha + sigma * eps
83
+ xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
84
+ return xt, t, eps
85
+
86
+ def loss(self, x0, condition=None):
87
+ xt, t, eps = self.add_noise(x0)
88
+ condition = self.model["condition"](condition) if condition is not None else None
89
+ loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
90
+ return (loss * self.loss_weight).mean()
91
+
92
+ def update(self, x0, condition=None, **kwargs):
93
+ self.optimizer.zero_grad()
94
+ loss = self.loss(x0, condition)
95
+ loss.backward()
96
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
97
+ if self.grad_clip_norm else None
98
+ self.optimizer.step()
99
+ self.ema_update()
100
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
101
+ return log
102
+
103
+ def update_classifier(self, x0, condition):
104
+ xt, t, eps = self.add_noise(x0)
105
+ log = self.classifier.update(xt, t, condition)
106
+ return log
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Inference
110
+
111
+ def sample(
112
+ self,
113
+ # ---------- the known fixed portion ---------- #
114
+ prior: Optional[torch.Tensor] = None,
115
+ # ----------------- sampling ----------------- #
116
+ n_samples: int = 1,
117
+ sample_steps: int = None,
118
+ use_ema: bool = True,
119
+ solver: str = "euler",
120
+ # ------------------ guidance ------------------ #
121
+ condition_cfg=None,
122
+ mask_cfg=None,
123
+ w_cfg: float = 0.0,
124
+ condition_cg=None,
125
+ w_cg: float = 0.0,
126
+
127
+ preserve_history: bool = False,
128
+ ):
129
+ log, x_history = {}, None
130
+ model = self.model_ema if use_ema else self.model
131
+
132
+ t = torch.linspace(self.t_range[1], self.t_range[0], sample_steps + 1, device=self.device)
133
+ alphas = self.alpha_schedule(t)
134
+ sigmas = (1 - alphas ** 2).sqrt()
135
+ logSNRs = (alphas / sigmas).log()
136
+
137
+ if prior is None:
138
+ xt = torch.randn((n_samples, *self.default_x_shape), device=self.device)
139
+ else:
140
+ xt = torch.randn_like(prior, device=self.device) * 0.5
141
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
142
+
143
+ if preserve_history:
144
+ x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
145
+ x_history[:, 0] = xt.cpu().numpy()
146
+
147
+ with torch.no_grad():
148
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
149
+ condition_vec_cg = condition_cg
150
+
151
+ for i in range(sample_steps):
152
+
153
+ h = logSNRs[i + 1] - logSNRs[i]
154
+
155
+ t_batch = t[i].repeat(n_samples)
156
+
157
+ # ----------------- CFG ----------------- #
158
+ with torch.no_grad():
159
+ if w_cfg != 0.0 and w_cfg != 1.0:
160
+ condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
161
+ eps_theta = model["diffusion"](
162
+ torch.repeat_interleave(xt, 2, dim=0),
163
+ torch.repeat_interleave(t_batch, 2, dim=0),
164
+ condition_vec_cfg)
165
+ eps_theta = w_cfg * eps_theta[:n_samples] + (1. - w_cfg) * eps_theta[n_samples:]
166
+ else:
167
+ eps_theta = model["diffusion"](xt, t_batch, condition_vec_cfg)
168
+ # ----------------- CG ----------------- #
169
+ if self.classifier is not None and w_cg != 0.0:
170
+ log_p, grad = self.classifier.gradients(xt.clone(), t_batch, condition_vec_cg)
171
+ eps_theta = eps_theta - w_cg * sigmas[i] * grad
172
+ else:
173
+ log_p = None
174
+
175
+ # do not change the fixed portion
176
+ xt = alphas[i + 1] / alphas[i] * xt - sigmas[i + 1] * torch.expm1(h) * eps_theta
177
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
178
+
179
+ if preserve_history:
180
+ x_history[:, t] = xt.cpu().numpy()
181
+ log["log_p"] = log_p
182
+
183
+ log["sample_history"] = x_history
184
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
185
+ with torch.no_grad():
186
+ logp = self.classifier.logp(xt, t_batch, condition_vec_cg)
187
+ log["log_p"] = logp
188
+
189
+ return xt, log
190
+
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/ddpm.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from cleandiffuser.utils import (
11
+ at_least_ndim,
12
+ cosine_beta_schedule,
13
+ linear_beta_schedule)
14
+ from .basic import DiffusionModel
15
+
16
+
17
+ class DDPM(DiffusionModel):
18
+
19
+ def __init__(
20
+ self,
21
+
22
+ # ----------------- Neural Networks ----------------- #
23
+ nn_diffusion: BaseNNDiffusion,
24
+ nn_condition: Optional[BaseNNCondition] = None,
25
+
26
+ # ----------------- Masks ----------------- #
27
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
28
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
29
+ # Add loss weight
30
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
31
+
32
+ # ------------------ Plugs ---------------- #
33
+ # Add a classifier to enable classifier-guidance
34
+ classifier: Optional[BaseClassifier] = None,
35
+
36
+ # ------------------ Params ---------------- #
37
+ grad_clip_norm: Optional[float] = None,
38
+ diffusion_steps: int = 1000,
39
+ ema_rate: float = 0.995,
40
+ optim_params: Optional[dict] = None,
41
+ x_max: Optional[torch.Tensor] = None,
42
+ x_min: Optional[torch.Tensor] = None,
43
+
44
+ # ------------------- DPM Params ------------------- #
45
+ predict_noise: bool = True,
46
+ beta_schedule: str = "cosine", # or cosine
47
+ beta_schedule_params: Optional[dict] = None,
48
+
49
+ device: Union[torch.device, str] = "cpu"
50
+ ):
51
+ super().__init__(
52
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
53
+ diffusion_steps, ema_rate, optim_params, device)
54
+
55
+ self.predict_noise = predict_noise
56
+
57
+ if beta_schedule_params is None:
58
+ beta_schedule_params = {}
59
+ beta_schedule_params["T"] = self.diffusion_steps
60
+
61
+ if beta_schedule == "linear":
62
+ beta = linear_beta_schedule(**beta_schedule_params)
63
+ elif beta_schedule == "cosine":
64
+ beta = cosine_beta_schedule(**beta_schedule_params)
65
+ else:
66
+ raise ValueError(f"Unknown beta schedule: {beta_schedule}")
67
+
68
+ self.beta = torch.tensor(beta, device=self.device, dtype=torch.float32)
69
+ self.alpha = 1 - self.beta
70
+ self.bar_alpha = torch.cumprod(self.alpha.clone(), 0)
71
+ self.x_max, self.x_min = x_max, x_min
72
+
73
+ @property
74
+ def clip_pred(self):
75
+ return (self.x_max is not None) or (self.x_min is not None)
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Training
79
+
80
+ def add_noise(self, x0, t=None, eps=None):
81
+ t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device) if t is None else t
82
+ eps = torch.randn_like(x0) if eps is None else eps
83
+ bar_alpha = at_least_ndim(self.bar_alpha[t], x0.dim())
84
+ xt = x0 * bar_alpha.sqrt() + eps * (1 - bar_alpha).sqrt()
85
+ xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
86
+ return xt, t, eps
87
+
88
+ def loss(self, x0, condition=None):
89
+ xt, t, eps = self.add_noise(x0)
90
+ condition = self.model["condition"](condition) if condition is not None else None
91
+ if self.predict_noise:
92
+ loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
93
+ else:
94
+ loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
95
+
96
+ return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
97
+
98
+ def update(self, x0, condition=None, update_ema=True, **kwargs):
99
+ loss = self.loss(x0, condition)
100
+ loss.backward()
101
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
102
+ if self.grad_clip_norm else None
103
+ self.optimizer.step()
104
+ self.optimizer.zero_grad()
105
+ if update_ema: self.ema_update()
106
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
107
+ return log
108
+
109
+ def update_classifier(self, x0, condition):
110
+ xt, t, eps = self.add_noise(x0)
111
+ log = self.classifier.update(xt, t, condition)
112
+ return log
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Inference
116
+
117
+ def predict_function(
118
+ self, x, t, bar_alpha,
119
+ use_ema=False, requires_grad=False,
120
+ # ----------------- CFG ----------------- #
121
+ condition_vec_cfg=None,
122
+ w_cfg: float = 0.0,
123
+ # ----------------- CG ----------------- #
124
+ condition_vec_cg=None,
125
+ w_cg: float = 1.0,
126
+ ):
127
+ b = x.shape[0]
128
+ model = self.model_ema if use_ema else self.model
129
+
130
+ # ----------------- CFG ----------------- #
131
+ with torch.set_grad_enabled(requires_grad):
132
+ if w_cfg != 0.0 and w_cfg != 1.0:
133
+ repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
134
+ condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
135
+ pred = model["diffusion"](
136
+ x.repeat(*repeat_dim), t.repeat(2), condition_vec_cfg)
137
+ pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
138
+ elif w_cfg == 0.0:
139
+ pred = model["diffusion"](x, t, None)
140
+ else:
141
+ pred = model["diffusion"](x, t, condition_vec_cfg)
142
+
143
+ # ----------------- CG ----------------- #
144
+ if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
145
+ log_p, grad = self.classifier.gradients(x.clone(), t, condition_vec_cg)
146
+ if self.predict_noise:
147
+ pred = pred - w_cg * (1 - bar_alpha).sqrt() * grad
148
+ else:
149
+ pred = pred + w_cg * (1 - bar_alpha) / bar_alpha.sqrt() * grad
150
+ else:
151
+ log_p = None
152
+
153
+ if self.predict_noise:
154
+ if self.clip_pred:
155
+ upper_bound = (x - bar_alpha.sqrt() * self.x_min) / (1 - bar_alpha).sqrt() \
156
+ if self.x_min is not None else None
157
+ lower_bound = (x - bar_alpha.sqrt() * self.x_max) / (1 - bar_alpha).sqrt() \
158
+ if self.x_max is not None else None
159
+ pred = pred.clip(lower_bound, upper_bound)
160
+ pred = pred * (1 - self.fix_mask)
161
+ else:
162
+ if self.clip_pred:
163
+ pred = pred.clip(self.x_min, self.x_max)
164
+ pred = pred * (1 - self.fix_mask) + x * self.fix_mask
165
+
166
+ return pred, {"log_p": log_p}
167
+
168
+ def sample(
169
+ self,
170
+ # ---------- the known fixed portion ---------- #
171
+ prior: Optional[torch.Tensor] = None,
172
+ # ----------------- sampling ----------------- #
173
+ n_samples: int = 1,
174
+ sample_steps: int = None,
175
+ use_ema: bool = True,
176
+ temperature: float = 1.0,
177
+ # ------------------ guidance ------------------ #
178
+ condition_cfg=None,
179
+ mask_cfg=None,
180
+ w_cfg: float = 0.0,
181
+ condition_cg=None,
182
+ w_cg: float = 0.0,
183
+ # ------------------ others ------------------ #
184
+ requires_grad: bool = False,
185
+ preserve_history: bool = False,
186
+ **kwargs,
187
+ ):
188
+ # initialize logger
189
+ log = {
190
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
191
+
192
+ # choose the model
193
+ model = self.model_ema if use_ema else self.model
194
+
195
+ # check `sample_steps`
196
+ if sample_steps != self.diffusion_steps:
197
+ import warnings
198
+ warnings.warn(f"sample_steps != diffusion_steps, sample_steps will be set to diffusion_steps.")
199
+ sample_steps = self.diffusion_steps
200
+
201
+ # initialize the samples
202
+ xt = torch.randn_like(prior, device=self.device) * temperature
203
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
204
+ if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
205
+
206
+ # preprocess the conditions
207
+ with torch.set_grad_enabled(requires_grad):
208
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
209
+ condition_vec_cg = condition_cg
210
+
211
+ # enter the sampling loop
212
+ for t in range(self.diffusion_steps - 1, -1, -1):
213
+
214
+ t_batch = torch.tensor(t, device=self.device, dtype=torch.long).repeat(n_samples)
215
+ bar_alpha = self.bar_alpha[t]
216
+ bar_alpha_prev = self.bar_alpha[t - 1] if t > 0 else torch.tensor(1.0, device=self.device)
217
+ alpha = self.alpha[t]
218
+ beta = self.beta[t]
219
+
220
+ # predict eps_theta or x_theta with CG/CFG
221
+ pred_theta, log = self.predict_function(
222
+ xt, t_batch, bar_alpha,
223
+ use_ema=use_ema,
224
+ requires_grad=requires_grad,
225
+ condition_vec_cfg=condition_vec_cfg,
226
+ condition_vec_cg=condition_vec_cg,
227
+ w_cfg=w_cfg, w_cg=w_cg)
228
+
229
+ # one step denoise
230
+ if self.predict_noise:
231
+
232
+ xt = 1 / alpha.sqrt() * (xt - beta / (1 - bar_alpha).sqrt() * pred_theta)
233
+
234
+ else:
235
+
236
+ xt = 1 / (1 - bar_alpha) * (
237
+ alpha.sqrt() * (1 - bar_alpha_prev) * xt +
238
+ beta * bar_alpha_prev.sqrt() * pred_theta)
239
+
240
+ if t != 0:
241
+ xt = xt + (beta * (1 - bar_alpha_prev) / (1 - bar_alpha)).sqrt() * torch.randn_like(xt)
242
+
243
+ # Fix the known portion
244
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
245
+ if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
246
+
247
+ # calculate the final log_p
248
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
249
+ with torch.no_grad():
250
+ logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
251
+ log["log_p"] = logp
252
+
253
+ return xt, log
254
+
255
+
256
+ def sample_x(
257
+ self,
258
+ # ---------- the known fixed portion ---------- #
259
+ prior: Optional[torch.Tensor] = None,
260
+ # ----------------- sampling ----------------- #
261
+ n_samples: int = 1,
262
+ sample_steps: int = None,
263
+ extra_sample_steps: int = 8,
264
+ use_ema: bool = True,
265
+ temperature: float = 1.0,
266
+ # ------------------ guidance ------------------ #
267
+ condition_cfg=None,
268
+ mask_cfg=None,
269
+ w_cfg: float = 0.0,
270
+ condition_cg=None,
271
+ w_cg: float = 0.0,
272
+ # ------------------ others ------------------ #
273
+ requires_grad: bool = False,
274
+ preserve_history: bool = False,
275
+ **kwargs,
276
+ ):
277
+ # initialize logger
278
+ log = {
279
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
280
+
281
+ # choose the model
282
+ model = self.model_ema if use_ema else self.model
283
+
284
+ # check `sample_steps`
285
+ if sample_steps != self.diffusion_steps:
286
+ import warnings
287
+ warnings.warn(f"sample_steps != diffusion_steps, sample_steps will be set to diffusion_steps.")
288
+ sample_steps = self.diffusion_steps
289
+
290
+ # initialize the samples
291
+ xt = torch.randn_like(prior, device=self.device) * temperature
292
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
293
+ if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
294
+
295
+ # preprocess the conditions
296
+ with torch.set_grad_enabled(requires_grad):
297
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
298
+ condition_vec_cg = condition_cg
299
+
300
+ # enter the sampling loop
301
+ for t in range(self.diffusion_steps - 1, -1, -1):
302
+
303
+ t_batch = torch.tensor(t, device=self.device, dtype=torch.long).repeat(n_samples)
304
+ bar_alpha = self.bar_alpha[t]
305
+ bar_alpha_prev = self.bar_alpha[t - 1] if t > 0 else torch.tensor(1.0, device=self.device)
306
+ alpha = self.alpha[t]
307
+ beta = self.beta[t]
308
+
309
+ # predict eps_theta or x_theta with CG/CFG
310
+ pred_theta, log = self.predict_function(
311
+ xt, t_batch, bar_alpha,
312
+ use_ema=use_ema,
313
+ requires_grad=requires_grad,
314
+ condition_vec_cfg=condition_vec_cfg,
315
+ condition_vec_cg=condition_vec_cg,
316
+ w_cfg=w_cfg, w_cg=w_cg)
317
+
318
+ # one step denoise
319
+ if self.predict_noise:
320
+
321
+ xt = 1 / alpha.sqrt() * (xt - beta / (1 - bar_alpha).sqrt() * pred_theta)
322
+
323
+ else:
324
+
325
+ xt = 1 / (1 - bar_alpha) * (
326
+ alpha.sqrt() * (1 - bar_alpha_prev) * xt +
327
+ beta * bar_alpha_prev.sqrt() * pred_theta)
328
+
329
+ if t != 0:
330
+ xt = xt + (beta * (1 - bar_alpha_prev) / (1 - bar_alpha)).sqrt() * torch.randn_like(xt)
331
+
332
+ # Fix the known portion
333
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
334
+ if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
335
+
336
+ if extra_sample_steps > 0:
337
+
338
+ t_batch = torch.tensor(0, device=self.device, dtype=torch.long).repeat(n_samples)
339
+ bar_alpha = self.bar_alpha[0]
340
+ bar_alpha_prev = torch.tensor(1.0, device=self.device)
341
+ alpha = self.alpha[0]
342
+ beta = self.beta[0]
343
+
344
+ for _ in range(extra_sample_steps):
345
+
346
+ # predict eps_theta or x_theta with CG/CFG
347
+ pred_theta, log = self.predict_function(
348
+ xt, t_batch, bar_alpha,
349
+ use_ema=use_ema,
350
+ requires_grad=requires_grad,
351
+ condition_vec_cfg=condition_vec_cfg,
352
+ condition_vec_cg=condition_vec_cg,
353
+ w_cfg=w_cfg, w_cg=w_cg)
354
+
355
+ # one step denoise
356
+ if self.predict_noise:
357
+
358
+ xt = 1 / alpha.sqrt() * (xt - beta / (1 - bar_alpha).sqrt() * pred_theta)
359
+
360
+ else:
361
+
362
+ xt = 1 / (1 - bar_alpha) * (
363
+ alpha.sqrt() * (1 - bar_alpha_prev) * xt +
364
+ beta * bar_alpha_prev.sqrt() * pred_theta)
365
+
366
+ # Fix the known portion
367
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
368
+
369
+ # calculate the final log_p
370
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
371
+ with torch.no_grad():
372
+ logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
373
+ log["log_p"] = logp
374
+
375
+ return xt, log
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/diffusionsde.py ADDED
@@ -0,0 +1,987 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union, Callable, Dict
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from cleandiffuser.utils import (
11
+ at_least_ndim,
12
+ SUPPORTED_NOISE_SCHEDULES, SUPPORTED_DISCRETIZATIONS, SUPPORTED_SAMPLING_STEP_SCHEDULE)
13
+ from .basic import DiffusionModel
14
+
15
+ SUPPORTED_SOLVERS = [
16
+ "ddpm", "ddim",
17
+ "ode_dpmsolver_1", "ode_dpmsolver++_1", "ode_dpmsolver++_2M",
18
+ "sde_dpmsolver_1", "sde_dpmsolver++_1", "sde_dpmsolver++_2M",]
19
+
20
+
21
+ def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
22
+ """
23
+ x_theta = (x - sigma * eps_theta) / alpha
24
+ """
25
+ return (x - sigma * eps_theta) / alpha
26
+
27
+
28
+ def xtheta_to_epstheta(x, alpha, sigma, x_theta):
29
+ """
30
+ eps_theta = (x - alpha * x_theta) / sigma
31
+ """
32
+ return (x - alpha * x_theta) / sigma
33
+
34
+
35
+ class BaseDiffusionSDE(DiffusionModel):
36
+
37
+ def __init__(
38
+ self,
39
+
40
+ # ----------------- Neural Networks ----------------- #
41
+ nn_diffusion: BaseNNDiffusion,
42
+ nn_condition: Optional[BaseNNCondition] = None,
43
+
44
+ # ----------------- Masks ----------------- #
45
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
46
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
47
+ # Add loss weight
48
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
49
+
50
+ # ------------------ Plugins ---------------- #
51
+ # Add a classifier to enable classifier-guidance
52
+ classifier: Optional[BaseClassifier] = None,
53
+
54
+ # ------------------ Training Params ---------------- #
55
+ grad_clip_norm: Optional[float] = None,
56
+ ema_rate: float = 0.995,
57
+ optim_params: Optional[dict] = None,
58
+
59
+ # ------------------- Diffusion Params ------------------- #
60
+ epsilon: float = 1e-3,
61
+
62
+ noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
63
+ noise_schedule_params: Optional[dict] = None,
64
+
65
+ x_max: Optional[torch.Tensor] = None,
66
+ x_min: Optional[torch.Tensor] = None,
67
+
68
+ predict_noise: bool = True,
69
+
70
+ device: Union[torch.device, str] = "cpu"
71
+ ):
72
+ super().__init__(
73
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
74
+ 0, ema_rate, optim_params, device)
75
+
76
+ self.predict_noise = predict_noise
77
+ self.epsilon = epsilon
78
+ self.x_max = x_max.to(device) if isinstance(x_max, torch.Tensor) else x_max
79
+ self.x_min = x_min.to(device) if isinstance(x_min, torch.Tensor) else x_min
80
+
81
+ @property
82
+ def supported_solvers(self):
83
+ return SUPPORTED_SOLVERS
84
+
85
+ @property
86
+ def clip_pred(self):
87
+ return (self.x_max is not None) or (self.x_min is not None)
88
+
89
+ # ==================== Training: Score Matching ======================
90
+
91
+ def add_noise(self, x0, t=None, eps=None):
92
+ raise NotImplementedError
93
+
94
+ def loss(self, x0, condition=None, **kwargs):
95
+
96
+ xt, t, eps = self.add_noise(x0)
97
+
98
+ condition = self.model["condition"](condition) if condition is not None else None
99
+
100
+ if self.predict_noise:
101
+ loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
102
+ else:
103
+ loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
104
+
105
+ loss = loss * self.loss_weight * (1 - self.fix_mask)
106
+
107
+ # find weighted_regression_tensor in kwargs
108
+ weighted_regression_tensor = kwargs.get("weighted_regression_tensor", None)
109
+ if weighted_regression_tensor is not None:
110
+ loss *= weighted_regression_tensor.unsqueeze(-1)
111
+
112
+ return loss.mean()
113
+
114
+ def update(self, x0, condition=None, update_ema=True, **kwargs):
115
+ """One-step gradient update.
116
+ Inputs:
117
+ - x0: torch.Tensor
118
+ Samples from the target distribution.
119
+ - condition: Optional
120
+ Condition of x0. `None` indicates no condition.
121
+ - update_ema: bool
122
+ Whether to update the exponential moving average model.
123
+
124
+ Outputs:
125
+ - log: dict
126
+ The log dictionary.
127
+ """
128
+ loss = self.loss(x0, condition, **kwargs)
129
+
130
+ loss.backward()
131
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
132
+ if self.grad_clip_norm else None
133
+ self.optimizer.step()
134
+ self.optimizer.zero_grad()
135
+
136
+ if update_ema:
137
+ self.ema_update()
138
+
139
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
140
+
141
+ return log
142
+
143
+ def update_classifier(self, x0, condition):
144
+
145
+ xt, t, eps = self.add_noise(x0)
146
+
147
+ log = self.classifier.update(xt, t, condition)
148
+
149
+ return log
150
+
151
+ # ==================== Sampling: Solving SDE/ODE ======================
152
+
153
+ def classifier_guidance(
154
+ self, xt, t, alpha, sigma,
155
+ model, condition=None, w: float = 1.0,
156
+ pred=None):
157
+ """
158
+ Guided Sampling CG:
159
+ bar_eps = eps - w * sigma * grad
160
+ bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
161
+ """
162
+ if pred is None:
163
+ pred = model["diffusion"](xt, t, None)
164
+ if self.classifier is None or w == 0.0:
165
+ return pred, None
166
+ else:
167
+ log_p, grad = self.classifier.gradients(xt.clone(), t, condition)
168
+ if self.predict_noise:
169
+ pred = pred - w * sigma * grad
170
+ else:
171
+ pred = pred + w * ((sigma ** 2) / alpha) * grad
172
+
173
+ return pred, log_p
174
+
175
+ def classifier_free_guidance(
176
+ self, xt, t,
177
+ model, condition=None, w: float = 1.0,
178
+ pred=None, pred_uncond=None,
179
+ requires_grad: bool = False):
180
+ """
181
+ Guided Sampling CFG:
182
+ bar_eps = w * pred + (1 - w) * pred_uncond
183
+ bar_x0 = w * pred + (1 - w) * pred_uncond
184
+ """
185
+ with torch.set_grad_enabled(requires_grad):
186
+ if w != 0.0 and w != 1.0:
187
+ if pred is None or pred_uncond is None:
188
+ b = xt.shape[0]
189
+ repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
190
+ condition = torch.cat([condition, torch.zeros_like(condition)], 0)
191
+ pred_all = model["diffusion"](
192
+ xt.repeat(*repeat_dim), t.repeat(2), condition)
193
+ pred, pred_uncond = pred_all[:b], pred_all[b:]
194
+ elif w == 0.0:
195
+ pred = 0.
196
+ pred_uncond = model["diffusion"](xt, t, None)
197
+ else:
198
+ pred = model["diffusion"](xt, t, condition)
199
+ pred_uncond = 0.
200
+
201
+ if self.predict_noise or not self.predict_noise:
202
+ bar_pred = w * pred + (1 - w) * pred_uncond
203
+ else:
204
+ bar_pred = pred
205
+
206
+ return bar_pred
207
+
208
+ def clip_prediction(self, pred, xt, alpha, sigma):
209
+ """
210
+ Clip the prediction at each sampling step to stablize the generation.
211
+ (xt - alpha * x_max) / sigma <= eps <= (xt - alpha * x_min) / sigma
212
+ x_min <= x0 <= x_max
213
+ """
214
+ if self.predict_noise:
215
+ if self.clip_pred:
216
+ upper_bound = (xt - alpha * self.x_min) / sigma if self.x_min is not None else None
217
+ lower_bound = (xt - alpha * self.x_max) / sigma if self.x_max is not None else None
218
+ pred = pred.clip(lower_bound, upper_bound)
219
+ else:
220
+ if self.clip_pred:
221
+ pred = pred.clip(self.x_min, self.x_max)
222
+
223
+ return pred
224
+
225
+ def guided_sampling(
226
+ self, xt, t, alpha, sigma,
227
+ model,
228
+ condition_cfg=None, w_cfg: float = 0.0,
229
+ condition_cg=None, w_cg: float = 0.0,
230
+ requires_grad: bool = False):
231
+ """
232
+ One-step epsilon/x0 prediction with guidance.
233
+ """
234
+
235
+ pred = self.classifier_free_guidance(
236
+ xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
237
+
238
+ pred, logp = self.classifier_guidance(
239
+ xt, t, alpha, sigma, model, condition_cg, w_cg, pred)
240
+
241
+ return pred, logp
242
+
243
+ def sample(self, *args, **kwargs):
244
+ raise NotImplementedError
245
+
246
+
247
+ class DiscreteDiffusionSDE(BaseDiffusionSDE):
248
+ """Discrete-time Diffusion SDE (VP-SDE)
249
+
250
+ The Diffusion SDE is currently one of the most commonly used formulations of diffusion processes.
251
+ Its training process involves utilizing neural networks to estimate its scaled score function,
252
+ which is used to compute the reverse process. The Diffusion SDE has reverse processes
253
+ in both SDE and ODE forms, sharing the same marginal distribution.
254
+ The first-order discretized forms of both are equivalent to well-known models such as DDPM and DDIM.
255
+ DPM-Solvers have observed the semi-linearity of the reverse process and have computed its exact solution.
256
+
257
+ The DiscreteDiffusionSDE is the discrete-time version of the Diffusion SDE.
258
+ It discretizes the continuous time interval into a finite number of diffusion steps
259
+ and only estimates the score function on these steps.
260
+ Therefore, in sampling, solvers can only work on these learned steps.
261
+ The sampling steps are required to be greater than 1 and less than or equal to the number of diffusion steps.
262
+
263
+ Args:
264
+ - nn_diffusion: BaseNNDiffusion
265
+ The neural network backbone for the Diffusion model.
266
+ - nn_condition: Optional[BaseNNCondition]
267
+ The neural network backbone for the condition embedding.
268
+
269
+ - fix_mask: Union[list, np.ndarray, torch.Tensor]
270
+ Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
271
+ The mask should be in the shape of `x_shape`.
272
+ - loss_weight: Union[list, np.ndarray, torch.Tensor]
273
+ Add loss weight. The weight should be in the shape of `x_shape`.
274
+
275
+ - classifier: Optional[BaseClassifier]
276
+ Add a classifier to enable classifier-guidance.
277
+
278
+ - grad_clip_norm: Optional[float]
279
+ Gradient clipping norm.
280
+ - ema_rate: float
281
+ Exponential moving average rate.
282
+ - optim_params: Optional[dict]
283
+ Optimizer parameters.
284
+
285
+ - epsilon: float
286
+ The minimum time step for the diffusion reverse process.
287
+ In practice, using a very small value instead of `0` can avoid numerical instability.
288
+
289
+ - diffusion_steps: int
290
+ The discretization steps for discrete-time diffusion models.
291
+ - discretization: Union[str, Callable]
292
+ The discretization method for the diffusion steps.
293
+
294
+ - noise_schedule: Union[str, Dict[str, Callable]]
295
+ The noise schedule for the diffusion process. Can be "linear" or "cosine".
296
+ - noise_schedule_params: Optional[dict]
297
+ The parameters for the noise schedule.
298
+
299
+ - x_max: Optional[torch.Tensor]
300
+ The maximum value for the input data. `None` indicates no constraint.
301
+ - x_min: Optional[torch.Tensor]
302
+ The minimum value for the input data. `None` indicates no constraint.
303
+
304
+ - predict_noise: bool
305
+ Whether to predict the noise or the data.
306
+
307
+ - device: Union[torch.device, str]
308
+ The device to run the model.
309
+ """
310
+ def __init__(
311
+ self,
312
+
313
+ # ----------------- Neural Networks ----------------- #
314
+ nn_diffusion: BaseNNDiffusion,
315
+ nn_condition: Optional[BaseNNCondition] = None,
316
+
317
+ # ----------------- Masks ----------------- #
318
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
319
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
320
+ # Add loss weight
321
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
322
+
323
+ # ------------------ Plugins ---------------- #
324
+ # Add a classifier to enable classifier-guidance
325
+ classifier: Optional[BaseClassifier] = None,
326
+
327
+ # ------------------ Training Params ---------------- #
328
+ grad_clip_norm: Optional[float] = None,
329
+ ema_rate: float = 0.995,
330
+ optim_params: Optional[dict] = None,
331
+
332
+ # ------------------- Diffusion Params ------------------- #
333
+ epsilon: float = 1e-3,
334
+
335
+ diffusion_steps: int = 1000,
336
+ discretization: Union[str, Callable] = "uniform",
337
+
338
+ noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
339
+ noise_schedule_params: Optional[dict] = None,
340
+
341
+ x_max: Optional[torch.Tensor] = None,
342
+ x_min: Optional[torch.Tensor] = None,
343
+
344
+ predict_noise: bool = True,
345
+
346
+ device: Union[torch.device, str] = "cpu"
347
+ ):
348
+ super().__init__(
349
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm, ema_rate, optim_params,
350
+ epsilon, noise_schedule, noise_schedule_params, x_max, x_min, predict_noise, device)
351
+
352
+ self.diffusion_steps = diffusion_steps
353
+
354
+ if 1. / diffusion_steps < epsilon:
355
+ raise ValueError("epsilon is too large for the number of diffusion steps")
356
+
357
+ # ================= Discretization =================
358
+ # - Map the continuous range [epsilon, 1.] to the discrete range [0, T-1]
359
+ if isinstance(discretization, str):
360
+ if discretization in SUPPORTED_DISCRETIZATIONS.keys():
361
+ self.t_diffusion = SUPPORTED_DISCRETIZATIONS[discretization](diffusion_steps, epsilon).to(device)
362
+ else:
363
+ Warning(f"Discretization method {discretization} is not supported. "
364
+ f"Using uniform discretization instead.")
365
+ self.t_diffusion = SUPPORTED_DISCRETIZATIONS["uniform"](diffusion_steps, epsilon).to(device)
366
+ elif callable(discretization):
367
+ self.t_diffusion = discretization(diffusion_steps, epsilon).to(device)
368
+ else:
369
+ raise ValueError("discretization must be a callable or a string")
370
+
371
+ # ================= Noise Schedule =================
372
+ if isinstance(noise_schedule, str):
373
+ if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
374
+ self.alpha, self.sigma = SUPPORTED_NOISE_SCHEDULES[noise_schedule]["forward"](
375
+ self.t_diffusion, **(noise_schedule_params or {}))
376
+ else:
377
+ raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
378
+ elif isinstance(noise_schedule, dict):
379
+ self.alpha, self.sigma = noise_schedule["forward"](self.t_diffusion, **(noise_schedule_params or {}))
380
+ else:
381
+ raise ValueError("noise_schedule must be a callable or a string")
382
+
383
+ self.logSNR = torch.log(self.alpha / self.sigma)
384
+
385
+ # ==================== Training: Score Matching ======================
386
+
387
+ def add_noise(self, x0, t=None, eps=None):
388
+
389
+ t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device) if t is None else t
390
+ eps = torch.randn_like(x0) if eps is None else eps
391
+
392
+ alpha, sigma = at_least_ndim(self.alpha[t], x0.dim()), at_least_ndim(self.sigma[t], x0.dim())
393
+
394
+ xt = alpha * x0 + sigma * eps
395
+ xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
396
+
397
+ return xt, t, eps
398
+
399
+ # ==================== Sampling: Solving SDE/ODE ======================
400
+
401
+ def sample(
402
+ self,
403
+ # ---------- the known fixed portion ---------- #
404
+ prior: torch.Tensor,
405
+ # ----------------- sampling ----------------- #
406
+ solver: str = "ddpm",
407
+ n_samples: int = 1,
408
+ sample_steps: int = 5,
409
+ sample_step_schedule: Union[str, Callable] = "uniform",
410
+ use_ema: bool = True,
411
+ temperature: float = 1.0,
412
+ # ------------------ guidance ------------------ #
413
+ condition_cfg=None,
414
+ mask_cfg=None,
415
+ w_cfg: float = 0.0,
416
+ condition_cg=None,
417
+ w_cg: float = 0.0,
418
+ # ----------- Diffusion-X sampling ----------
419
+ diffusion_x_sampling_steps: int = 0,
420
+ # ----------- Warm-Starting -----------
421
+ warm_start_reference: Optional[torch.Tensor] = None,
422
+ warm_start_forward_level: float = 0.3,
423
+ # ------------------ others ------------------ #
424
+ requires_grad: bool = False,
425
+ preserve_history: bool = False,
426
+ # ------------------ Data collection ------------------ #
427
+ collector=None,
428
+ collect_trajectory: bool = False,
429
+ obs_dim: int = None,
430
+ initial_noise: Optional[torch.Tensor] = None,
431
+ return_initial_noise: bool = False,
432
+ **kwargs,
433
+ ):
434
+ """Sampling.
435
+
436
+ Inputs:
437
+ - prior: torch.Tensor
438
+ The known fixed portion of the input data. Should be in the shape of generated data.
439
+ Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
440
+
441
+ - solver: str
442
+ The solver for the reverse process. Check `supported_solvers` property for available solvers.
443
+ - n_samples: int
444
+ The number of samples to generate.
445
+ - sample_steps: int
446
+ The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
447
+ - sample_step_schedule: Union[str, Callable]
448
+ The schedule for the sampling steps.
449
+ - use_ema: bool
450
+ Whether to use the exponential moving average model.
451
+ - temperature: float
452
+ The temperature for sampling.
453
+ - initial_noise: Optional[torch.Tensor]
454
+ Optional standard-normal noise used to initialize the reverse process.
455
+ It is multiplied by ``temperature`` before fixed conditioning is applied.
456
+ Supplying this makes noise/trajectory distillation pairs exactly reproducible.
457
+ - return_initial_noise: bool
458
+ If true, return the exact conditioned initial state as ``log["initial_xt"]``.
459
+
460
+ - condition_cfg: Optional
461
+ Condition for Classifier-free-guidance.
462
+ - mask_cfg: Optional
463
+ Mask for Classifier-guidance.
464
+ - w_cfg: float
465
+ Weight for Classifier-free-guidance.
466
+ - condition_cg: Optional
467
+ Condition for Classifier-guidance.
468
+ - w_cg: float
469
+ Weight for Classifier-guidance.
470
+
471
+ - diffusion_x_sampling_steps: int
472
+ The number of diffusion steps for diffusion-x sampling.
473
+
474
+ - warm_start_reference: Optional[torch.Tensor]
475
+ Reference data for warm-starting sampling. `None` indicates no warm-starting.
476
+ - warm_start_forward_level: float
477
+ The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
478
+
479
+ - requires_grad: bool
480
+ Whether to preserve gradients.
481
+ - preserve_history: bool
482
+ Whether to preserve the sampling history.
483
+
484
+ Outputs:
485
+ - x0: torch.Tensor
486
+ Generated samples. Be in the shape of `(n_samples, *x_shape)`.
487
+ - log: dict
488
+ The log dictionary.
489
+ """
490
+ assert solver in SUPPORTED_SOLVERS, f"Solver {solver} is not supported."
491
+
492
+ # ===================== Initialization =====================
493
+ log = {
494
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
495
+
496
+ model = self.model if not use_ema else self.model_ema
497
+
498
+ prior = prior.to(self.device)
499
+ if isinstance(warm_start_reference, torch.Tensor) and isinstance(initial_noise, torch.Tensor):
500
+ raise ValueError("warm_start_reference and initial_noise are mutually exclusive")
501
+ if isinstance(warm_start_reference, torch.Tensor):
502
+ diffusion_steps = int(warm_start_forward_level * self.diffusion_steps)
503
+ fwd_alpha, fwd_sigma = self.alpha[diffusion_steps], self.sigma[diffusion_steps]
504
+ xt = warm_start_reference * fwd_alpha + fwd_sigma * torch.randn_like(warm_start_reference)
505
+ else:
506
+ diffusion_steps = self.diffusion_steps
507
+ if isinstance(initial_noise, torch.Tensor):
508
+ if initial_noise.shape != prior.shape:
509
+ raise ValueError(
510
+ f"initial_noise shape {tuple(initial_noise.shape)} does not match "
511
+ f"prior shape {tuple(prior.shape)}")
512
+ xt = initial_noise.to(device=prior.device, dtype=prior.dtype) * temperature
513
+ else:
514
+ xt = torch.randn_like(prior) * temperature
515
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
516
+ if return_initial_noise:
517
+ # Clone before denoising: later solver updates must not mutate the saved pair input.
518
+ log["initial_xt"] = xt.detach().clone()
519
+ if preserve_history:
520
+ log["sample_history"][:, 0] = xt.cpu().numpy()
521
+
522
+ with torch.set_grad_enabled(requires_grad):
523
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
524
+ condition_vec_cg = condition_cg
525
+
526
+ # ===================== Sampling Schedule ====================
527
+ if isinstance(sample_step_schedule, str):
528
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
529
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
530
+ diffusion_steps, sample_steps)
531
+ else:
532
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
533
+ elif callable(sample_step_schedule):
534
+ sample_step_schedule = sample_step_schedule(diffusion_steps, sample_steps)
535
+ else:
536
+ raise ValueError("sample_step_schedule must be a callable or a string")
537
+
538
+ alphas = self.alpha[sample_step_schedule]
539
+ sigmas = self.sigma[sample_step_schedule]
540
+ logSNRs = torch.log(alphas / sigmas)
541
+ hs = torch.zeros_like(logSNRs)
542
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
543
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
544
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
545
+
546
+ buffer = []
547
+
548
+ # ===================== Denoising Loop ========================
549
+ loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
550
+ for i in reversed(loop_steps):
551
+
552
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
553
+
554
+ xt_before = xt
555
+
556
+ # guided sampling
557
+ pred, logp = self.guided_sampling(
558
+ xt, t, alphas[i], sigmas[i],
559
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
560
+
561
+ # clip the prediction
562
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
563
+
564
+ # noise & data prediction
565
+ eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
566
+ x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
567
+
568
+ # one-step update
569
+ if solver == "ddpm":
570
+ xt = (
571
+ (alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
572
+ (sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
573
+ if i > 1:
574
+ xt += (stds[i] * torch.randn_like(xt))
575
+
576
+ elif solver == "ddim":
577
+ xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
578
+
579
+ elif solver == "ode_dpmsolver_1":
580
+ xt = (alphas[i - 1] / alphas[i]) * xt - sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta
581
+
582
+ elif solver == "ode_dpmsolver++_1":
583
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
584
+
585
+ elif solver == "ode_dpmsolver++_2M":
586
+ buffer.append(x_theta)
587
+ if i < sample_steps:
588
+ r = hs[i + 1] / hs[i]
589
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
590
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
591
+ else:
592
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
593
+
594
+ elif solver == "sde_dpmsolver_1":
595
+ xt = ((alphas[i - 1] / alphas[i]) * xt -
596
+ 2 * sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta +
597
+ sigmas[i - 1] * torch.expm1(2 * hs[i]).sqrt() * torch.randn_like(xt))
598
+
599
+ elif solver == "sde_dpmsolver++_1":
600
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
601
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
602
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
603
+
604
+ elif solver == "sde_dpmsolver++_2M":
605
+ buffer.append(x_theta)
606
+ if i < sample_steps:
607
+ r = hs[i + 1] / hs[i]
608
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
609
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
610
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * D +
611
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
612
+ else:
613
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
614
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
615
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
616
+
617
+ # fix the known portion, and preserve the sampling history
618
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
619
+ #
620
+ xt_after = xt
621
+
622
+ # === 轨迹采集(关键)===
623
+ if collect_trajectory and collector is not None:
624
+ assert obs_dim is not None, "collect_trajectory 需要传入 obs_dim"
625
+ u = prior[:, 0, :obs_dim] # u = obs0(归一化之后)
626
+ collector.add(xt_before, xt_after, t, u, i_step=i)
627
+
628
+ if preserve_history:
629
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
630
+
631
+ # ================= Post-processing =================
632
+ if self.classifier is not None:
633
+ with torch.no_grad():
634
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
635
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
636
+ log["log_p"] = logp
637
+
638
+ if self.clip_pred:
639
+ xt = xt.clip(self.x_min, self.x_max)
640
+
641
+ return xt, log
642
+
643
+
644
+ class ContinuousDiffusionSDE(BaseDiffusionSDE):
645
+ """Continuous-time Diffusion SDE (VP-SDE)
646
+
647
+ The Diffusion SDE is currently one of the most commonly used formulations of diffusion processes.
648
+ Its training process involves utilizing neural networks to estimate its scaled score function,
649
+ which is used to compute the reverse process. The Diffusion SDE has reverse processes
650
+ in both SDE and ODE forms, sharing the same marginal distribution.
651
+ The first-order discretized forms of both are equivalent to well-known models such as DDPM and DDIM.
652
+ DPM-Solvers have observed the semi-linearity of the reverse process and have computed its exact solution.
653
+
654
+ The ContinuousDiffusionSDE is the continuous-time version of the Diffusion SDE.
655
+ It estimates the score function at any $t\in[0,T]$ and solves the reverse process in continuous time.
656
+ The sampling steps are required to be greater than 1.
657
+
658
+ Args:
659
+ - nn_diffusion: BaseNNDiffusion
660
+ The neural network backbone for the Diffusion model.
661
+ - nn_condition: Optional[BaseNNCondition]
662
+ The neural network backbone for the condition embedding.
663
+
664
+ - fix_mask: Union[list, np.ndarray, torch.Tensor]
665
+ Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
666
+ The mask should be in the shape of `x_shape`.
667
+ - loss_weight: Union[list, np.ndarray, torch.Tensor]
668
+ Add loss weight. The weight should be in the shape of `x_shape`.
669
+
670
+ - classifier: Optional[BaseClassifier]
671
+ Add a classifier to enable classifier-guidance.
672
+
673
+ - grad_clip_norm: Optional[float]
674
+ Gradient clipping norm.
675
+ - ema_rate: float
676
+ Exponential moving average rate.
677
+ - optim_params: Optional[dict]
678
+ Optimizer parameters.
679
+
680
+ - epsilon: float
681
+ The minimum time step for the diffusion reverse process.
682
+ In practice, using a very small value instead of `0` can avoid numerical instability.
683
+
684
+ - noise_schedule: Union[str, Dict[str, Callable]]
685
+ The noise schedule for the diffusion process. Can be "linear" or "cosine".
686
+ - noise_schedule_params: Optional[dict]
687
+ The parameters for the noise schedule.
688
+
689
+ - x_max: Optional[torch.Tensor]
690
+ The maximum value for the input data. `None` indicates no constraint.
691
+ - x_min: Optional[torch.Tensor]
692
+ The minimum value for the input data. `None` indicates no constraint.
693
+
694
+ - predict_noise: bool
695
+ Whether to predict the noise or the data.
696
+
697
+ - device: Union[torch.device, str]
698
+ The device to run the model.
699
+ """
700
+ def __init__(
701
+ self,
702
+
703
+ # ----------------- Neural Networks ----------------- #
704
+ nn_diffusion: BaseNNDiffusion,
705
+ nn_condition: Optional[BaseNNCondition] = None,
706
+
707
+ # ----------------- Masks ----------------- #
708
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
709
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
710
+ # Add loss weight
711
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
712
+
713
+ # ------------------ Plugins ---------------- #
714
+ # Add a classifier to enable classifier-guidance
715
+ classifier: Optional[BaseClassifier] = None,
716
+
717
+ # ------------------ Training Params ---------------- #
718
+ grad_clip_norm: Optional[float] = None,
719
+ ema_rate: float = 0.995,
720
+ optim_params: Optional[dict] = None,
721
+
722
+ # ------------------- Diffusion Params ------------------- #
723
+ epsilon: float = 1e-3,
724
+
725
+ noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
726
+ noise_schedule_params: Optional[dict] = None,
727
+
728
+ x_max: Optional[torch.Tensor] = None,
729
+ x_min: Optional[torch.Tensor] = None,
730
+
731
+ predict_noise: bool = True,
732
+
733
+ device: Union[torch.device, str] = "cpu"
734
+ ):
735
+ super().__init__(
736
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm, ema_rate, optim_params,
737
+ epsilon, noise_schedule, noise_schedule_params, x_max, x_min, predict_noise, device)
738
+
739
+ # ==================== Continuous Time-step Range ====================
740
+ if noise_schedule == "cosine":
741
+ self.t_diffusion = [epsilon, 0.9946]
742
+ else:
743
+ self.t_diffusion = [epsilon, 1.]
744
+
745
+ # ===================== Noise Schedule ======================
746
+ if isinstance(noise_schedule, str):
747
+ if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
748
+ self.noise_schedule_funcs = SUPPORTED_NOISE_SCHEDULES[noise_schedule]
749
+ self.noise_schedule_params = noise_schedule_params
750
+ else:
751
+ raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
752
+ elif isinstance(noise_schedule, dict):
753
+ self.noise_schedule_funcs = noise_schedule
754
+ self.noise_schedule_params = noise_schedule_params
755
+ else:
756
+ raise ValueError("noise_schedule must be a callable or a string")
757
+
758
+ # ==================== Training: Score Matching ======================
759
+
760
+ def add_noise(self, x0, t=None, eps=None):
761
+
762
+ t = (torch.rand((x0.shape[0],), device=self.device) *
763
+ (self.t_diffusion[1] - self.t_diffusion[0]) + self.t_diffusion[0]) if t is None else t
764
+
765
+ eps = torch.randn_like(x0) if eps is None else eps
766
+
767
+ alpha, sigma = self.noise_schedule_funcs["forward"](t, **(self.noise_schedule_params or {}))
768
+ alpha = at_least_ndim(alpha, x0.dim())
769
+ sigma = at_least_ndim(sigma, x0.dim())
770
+
771
+ xt = alpha * x0 + sigma * eps
772
+ xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
773
+
774
+ return xt, t, eps
775
+
776
+ # ==================== Sampling: Solving SDE/ODE ======================
777
+
778
+ def sample(
779
+ self,
780
+ # ---------- the known fixed portion ---------- #
781
+ prior: torch.Tensor,
782
+ # ----------------- sampling ----------------- #
783
+ solver: str = "ddpm",
784
+ n_samples: int = 1,
785
+ sample_steps: int = 5,
786
+ sample_step_schedule: Union[str, Callable] = "uniform_continuous",
787
+ use_ema: bool = True,
788
+ temperature: float = 1.0,
789
+ # ------------------ guidance ------------------ #
790
+ condition_cfg=None,
791
+ mask_cfg=None,
792
+ w_cfg: float = 0.0,
793
+ condition_cg=None,
794
+ w_cg: float = 0.0,
795
+ # ----------- Diffusion-X sampling ----------
796
+ diffusion_x_sampling_steps: int = 0,
797
+ # ----------- Warm-Starting -----------
798
+ warm_start_reference: Optional[torch.Tensor] = None,
799
+ warm_start_forward_level: float = 0.3,
800
+ # ------------------ others ------------------ #
801
+ requires_grad: bool = False,
802
+ preserve_history: bool = False,
803
+ **kwargs,
804
+ ):
805
+ """Sampling.
806
+
807
+ Inputs:
808
+ - prior: torch.Tensor
809
+ The known fixed portion of the input data. Should be in the shape of generated data.
810
+ Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
811
+
812
+ - solver: str
813
+ The solver for the reverse process. Check `supported_solvers` property for available solvers.
814
+ - n_samples: int
815
+ The number of samples to generate.
816
+ - sample_steps: int
817
+ The number of sampling steps. Should be greater than 1.
818
+ - sample_step_schedule: Union[str, Callable]
819
+ The schedule for the sampling steps.
820
+ - use_ema: bool
821
+ Whether to use the exponential moving average model.
822
+ - temperature: float
823
+ The temperature for sampling.
824
+
825
+ - condition_cfg: Optional
826
+ Condition for Classifier-free-guidance.
827
+ - mask_cfg: Optional
828
+ Mask for Classifier-guidance.
829
+ - w_cfg: float
830
+ Weight for Classifier-free-guidance.
831
+ - condition_cg: Optional
832
+ Condition for Classifier-guidance.
833
+ - w_cg: float
834
+ Weight for Classifier-guidance.
835
+
836
+ - diffusion_x_sampling_steps: int
837
+ The number of diffusion steps for diffusion-x sampling.
838
+
839
+ - warm_start_reference: Optional[torch.Tensor]
840
+ Reference data for warm-starting sampling. `None` indicates no warm-starting.
841
+ - warm_start_forward_level: float
842
+ The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
843
+
844
+ - requires_grad: bool
845
+ Whether to preserve gradients.
846
+ - preserve_history: bool
847
+ Whether to preserve the sampling history.
848
+
849
+ Outputs:
850
+ - x0: torch.Tensor
851
+ Generated samples. Be in the shape of `(n_samples, *x_shape)`.
852
+ - log: dict
853
+ The log dictionary.
854
+ """
855
+ assert solver in SUPPORTED_SOLVERS, f"Solver {solver} is not supported."
856
+
857
+ # ===================== Initialization =====================
858
+ log = {
859
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
860
+
861
+ model = self.model if not use_ema else self.model_ema
862
+
863
+ prior = prior.to(self.device)
864
+ if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
865
+ warm_start_forward_level = self.epsilon + warm_start_forward_level * (1. - self.epsilon)
866
+ fwd_alpha, fwd_sigma = self.noise_schedule_funcs["forward"](
867
+ torch.ones((1,), device=self.device) * warm_start_forward_level, **(self.noise_schedule_params or {}))
868
+ xt = warm_start_reference * fwd_alpha + fwd_sigma * torch.randn_like(warm_start_reference)
869
+ else:
870
+ xt = torch.randn_like(prior) * temperature
871
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
872
+ if preserve_history:
873
+ log["sample_history"][:, 0] = xt.cpu().numpy()
874
+
875
+ with torch.set_grad_enabled(requires_grad):
876
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
877
+ condition_vec_cg = condition_cg
878
+
879
+ # ===================== Sampling Schedule ====================
880
+ if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
881
+ t_diffusion = [self.t_diffusion[0], warm_start_forward_level]
882
+ else:
883
+ t_diffusion = self.t_diffusion
884
+ if isinstance(sample_step_schedule, str):
885
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
886
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
887
+ t_diffusion, sample_steps)
888
+ else:
889
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
890
+ elif callable(sample_step_schedule):
891
+ sample_step_schedule = sample_step_schedule(t_diffusion, sample_steps)
892
+ else:
893
+ raise ValueError("sample_step_schedule must be a callable or a string")
894
+
895
+ alphas, sigmas = self.noise_schedule_funcs["forward"](
896
+ sample_step_schedule, **(self.noise_schedule_params or {}))
897
+ logSNRs = torch.log(alphas / sigmas)
898
+ hs = torch.zeros_like(logSNRs)
899
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
900
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
901
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
902
+
903
+ buffer = []
904
+
905
+ # ===================== Denoising Loop ========================
906
+ loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
907
+ for i in reversed(loop_steps):
908
+
909
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
910
+
911
+ # guided sampling
912
+ pred, logp = self.guided_sampling(
913
+ xt, t, alphas[i], sigmas[i],
914
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
915
+
916
+ # clip the prediction
917
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
918
+
919
+ # transform to eps_theta
920
+ eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
921
+ x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
922
+
923
+ # one-step update
924
+ if solver == "ddpm":
925
+ xt = (
926
+ (alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
927
+ (sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
928
+ if i > 1:
929
+ xt += (stds[i] * torch.randn_like(xt))
930
+
931
+ elif solver == "ddim":
932
+ xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
933
+
934
+ elif solver == "ode_dpmsolver_1":
935
+ xt = (alphas[i - 1] / alphas[i]) * xt - sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta
936
+
937
+ elif solver == "ode_dpmsolver++_1":
938
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
939
+
940
+ elif solver == "ode_dpmsolver++_2M":
941
+ buffer.append(x_theta)
942
+ if i < sample_steps:
943
+ r = hs[i + 1] / hs[i]
944
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
945
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
946
+ else:
947
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
948
+
949
+ elif solver == "sde_dpmsolver_1":
950
+ xt = ((alphas[i - 1] / alphas[i]) * xt -
951
+ 2 * sigmas[i - 1] * torch.expm1(hs[i]) * eps_theta +
952
+ sigmas[i - 1] * torch.expm1(2 * hs[i]).sqrt() * torch.randn_like(xt))
953
+
954
+ elif solver == "sde_dpmsolver++_1":
955
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
956
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
957
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
958
+
959
+ elif solver == "sde_dpmsolver++_2M":
960
+ buffer.append(x_theta)
961
+ if i < sample_steps:
962
+ r = hs[i + 1] / hs[i]
963
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
964
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
965
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * D +
966
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
967
+ else:
968
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
969
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
970
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
971
+
972
+ # fix the known portion, and preserve the sampling history
973
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
974
+ if preserve_history:
975
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
976
+
977
+ # ================= Post-processing =================
978
+ if self.classifier is not None and w_cg != 0.:
979
+ with torch.no_grad():
980
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
981
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
982
+ log["log_p"] = logp
983
+
984
+ if self.clip_pred:
985
+ xt = xt.clip(self.x_min, self.x_max)
986
+
987
+ return xt, log
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver.py ADDED
@@ -0,0 +1,889 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from cleandiffuser.utils import at_least_ndim
11
+ from .basic import DiffusionModel
12
+
13
+ SAMPLER_CONFIG = {
14
+
15
+ "ode_dpm_1": {
16
+ "predict_noise": True,
17
+ "order": 1, },
18
+
19
+ "ddim": {
20
+ "predict_noise": True,
21
+ "order": 1, },
22
+
23
+ "sde_dpm_1": {
24
+ "predict_noise": True,
25
+ "order": 1, },
26
+
27
+ "ode_dpmpp_1": {
28
+ "predict_noise": False,
29
+ "order": 1, },
30
+
31
+ "sde_dpmpp_1": {
32
+ "predict_noise": False,
33
+ "order": 1, },
34
+
35
+ "ode_dpm_2": {
36
+ "predict_noise": True,
37
+ "order": 2, },
38
+
39
+ "sde_dpm_2": {
40
+ "predict_noise": True,
41
+ "order": 2, },
42
+
43
+ "ode_dpmpp_2": {
44
+ "predict_noise": False,
45
+ "order": 2, },
46
+
47
+ "sde_dpmpp_2": {
48
+ "predict_noise": False,
49
+ "order": 2, }, }
50
+
51
+
52
+ def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
53
+ """
54
+ x_theta = (x - sigma * eps_theta) / alpha
55
+ """
56
+ return (x - sigma * eps_theta) / alpha
57
+
58
+
59
+ def xtheta_to_epstheta(x, alpha, sigma, x_theta):
60
+ """
61
+ eps_theta = (x - alpha * x_theta) / sigma
62
+ """
63
+ return (x - alpha * x_theta) / sigma
64
+
65
+
66
+ def onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler):
67
+
68
+ if sampler[:-2] == "ode_dpm" or sampler == "ddim":
69
+ xt_next = (
70
+ alphas[i] / alphas[i - 1] * xt -
71
+ sigmas[i] * torch.expm1(h[i]) * pred_theta)
72
+ elif sampler[:-2] == "sde_dpm":
73
+ xt_next = (
74
+ alphas[i] / alphas[i - 1] * xt -
75
+ 2. * sigmas[i] * torch.expm1(h[i]) * pred_theta +
76
+ sigmas[i] * torch.expm1(2. * h[i]).sqrt() * torch.randn_like(xt))
77
+ elif sampler[:-2] == "ode_dpmpp":
78
+ xt_next = (
79
+ sigmas[i] / sigmas[i - 1] * xt -
80
+ alphas[i] * torch.expm1(-h[i]) * pred_theta)
81
+ elif sampler[:-2] == "sde_dpmpp":
82
+ xt_next = (
83
+ sigmas[i] / sigmas[i - 1] * (-h[i]).exp() * xt -
84
+ alphas[i] * torch.expm1(-2. * h[i]) * pred_theta +
85
+ sigmas[i] * (-1. * torch.expm1(-2. * h[i])).sqrt() * torch.randn_like(xt))
86
+ else:
87
+ raise ValueError(f'Unknown sampler: {sampler}.')
88
+
89
+ return xt_next
90
+
91
+
92
+ class DPMSolver(DiffusionModel):
93
+
94
+ def __init__(
95
+ self,
96
+
97
+ # ----------------- Neural Networks ----------------- #
98
+ nn_diffusion: BaseNNDiffusion,
99
+ nn_condition: Optional[BaseNNCondition] = None,
100
+
101
+ # ----------------- Masks ----------------- #
102
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
103
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
104
+ # Add loss weight
105
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
106
+
107
+ # ------------------ Plugs ---------------- #
108
+ # Add a classifier to enable classifier-guidance
109
+ classifier: Optional[BaseClassifier] = None,
110
+
111
+ # ------------------ Params ---------------- #
112
+ grad_clip_norm: Optional[float] = None,
113
+ diffusion_steps: int = 1000,
114
+ ema_rate: float = 0.995,
115
+ optim_params: Optional[dict] = None,
116
+ x_max: Optional[torch.Tensor] = None,
117
+ x_min: Optional[torch.Tensor] = None,
118
+
119
+ # ------------------- DPM Params ------------------- #
120
+ predict_noise: bool = False,
121
+ noise_schedule: str = "linear", # or cosine
122
+ t_eps: float = 1e-3,
123
+
124
+ device: Union[torch.device, str] = "cpu"
125
+ ):
126
+ super().__init__(
127
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
128
+ diffusion_steps, ema_rate, optim_params, device)
129
+
130
+ self.predict_noise = predict_noise
131
+ self.noise_schedule = noise_schedule
132
+ self.t_eps = t_eps
133
+ self.x_max, self.x_min = x_max, x_min
134
+
135
+ @property
136
+ def clip_pred(self):
137
+ return (self.x_max is not None) or (self.x_min is not None)
138
+
139
+ @property
140
+ def supported_samplers(self):
141
+ return list(SAMPLER_CONFIG.keys())
142
+
143
+ @property
144
+ def t_range(self):
145
+ if self.noise_schedule == "linear":
146
+ return self.t_eps, 1.
147
+ elif self.noise_schedule == "cosine":
148
+ return self.t_eps, 0.9946
149
+ else:
150
+ raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
151
+
152
+ def alpha_schedule(self, t):
153
+ if self.noise_schedule == "linear":
154
+ beta0, beta1 = 0.1, 20
155
+ return (-(beta1 - beta0) / 4 * (t ** 2) - beta0 / 2 * t).exp()
156
+ elif self.noise_schedule == "cosine":
157
+ s = 0.008
158
+ return ((torch.cos(np.pi / 2 * (t + s) / (1 + s))).log() - np.log(np.cos(np.pi / 2 * s / (1 + s)))).exp()
159
+ else:
160
+ raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # Training
164
+
165
+ def add_noise(self, x0, t=None, eps=None):
166
+ if t is None:
167
+ t = torch.rand((x0.shape[0],), device=self.device)
168
+ t = self.t_range[0] + t * (self.t_range[1] - self.t_range[0])
169
+ eps = torch.randn_like(x0) if eps is None else eps
170
+ alpha = self.alpha_schedule(at_least_ndim(t, x0.dim()))
171
+ sigma = (1 - alpha ** 2).sqrt()
172
+ xt = x0 * alpha + sigma * eps
173
+ xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
174
+ return xt, t, eps
175
+
176
+ def loss(self, x0, condition=None):
177
+ xt, t, eps = self.add_noise(x0)
178
+ condition = self.model["condition"](condition) if condition is not None else None
179
+
180
+ if self.predict_noise:
181
+ loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
182
+ else:
183
+ loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
184
+
185
+ return (loss * self.loss_weight * (1. - self.fix_mask)).mean()
186
+
187
+ def update(self, x0, condition=None, update_ema=True, **kwargs):
188
+ self.optimizer.zero_grad()
189
+ loss = self.loss(x0, condition)
190
+ loss.backward()
191
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
192
+ if self.grad_clip_norm else None
193
+ self.optimizer.step()
194
+ if update_ema: self.ema_update()
195
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
196
+ return log
197
+
198
+ def update_classifier(self, x0, condition):
199
+ xt, t, eps = self.add_noise(x0)
200
+ log = self.classifier.update(xt, t, condition)
201
+ return log
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Inference
205
+
206
+ def predict_function(
207
+ self, x, t, alpha, sigma,
208
+ use_ema=False, requires_grad=False, predict_noise=False,
209
+ # ----------------- CFG ----------------- #
210
+ condition_vec_cfg=None,
211
+ w_cfg: float = 0.0,
212
+ # ----------------- CG ----------------- #
213
+ condition_vec_cg=None,
214
+ w_cg: float = 1.0,
215
+ ):
216
+ b = x.shape[0]
217
+ model = self.model_ema if use_ema else self.model
218
+
219
+ # ----------------- CFG ----------------- #
220
+ with torch.set_grad_enabled(requires_grad):
221
+ if w_cfg != 0.0 and w_cfg != 1.0:
222
+ repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
223
+ condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
224
+ pred = model["diffusion"](
225
+ x.repeat(*repeat_dim), t.repeat(2), condition_vec_cfg)
226
+ pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
227
+ elif w_cfg == 0.0:
228
+ pred = model["diffusion"](x, t, None)
229
+ else:
230
+ pred = model["diffusion"](x, t, condition_vec_cfg)
231
+
232
+ if self.predict_noise and predict_noise:
233
+ # The network predicts noise, and we need to predict noise here
234
+ pass
235
+ elif self.predict_noise and not predict_noise:
236
+ # The network predicts noise, and we need to predict x0 here
237
+ pred = epstheta_to_xtheta(x, alpha, sigma, pred)
238
+ elif not self.predict_noise and predict_noise:
239
+ # The network predicts x0, and we need to predict noise here
240
+ pred = xtheta_to_epstheta(x, alpha, sigma, pred)
241
+ else:
242
+ # The network predicts x0, and we need to predict x0 here
243
+ pass
244
+
245
+ # ----------------- CG ----------------- #
246
+ if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
247
+ log_p, grad = self.classifier.gradients(x.clone(), t, condition_vec_cg)
248
+ if predict_noise:
249
+ pred = pred - w_cg * sigma * grad
250
+ else:
251
+ pred = pred + w_cg * ((sigma ** 2) / alpha) * grad
252
+ else:
253
+ log_p = None
254
+
255
+ # If the bounds are not None, clip `pred`
256
+ if predict_noise:
257
+ if self.clip_pred:
258
+ upper_bound = (x - alpha * self.x_min) / sigma if self.x_min is not None else None
259
+ lower_bound = (x - alpha * self.x_max) / sigma if self.x_max is not None else None
260
+ pred = pred.clip(lower_bound, upper_bound)
261
+ pred = pred * (1 - self.fix_mask)
262
+ else:
263
+ if self.clip_pred:
264
+ pred = pred.clip(self.x_min, self.x_max)
265
+ pred = pred * (1 - self.fix_mask) + x * self.fix_mask
266
+
267
+ return pred, {"log_p": log_p}
268
+
269
+ # def first_order_sample(
270
+ # self,
271
+ # sampler: str = "ode_dpm_1",
272
+ # # ---------- the known fixed portion ---------- #
273
+ # prior: Optional[torch.Tensor] = None,
274
+ # # ----------------- sampling ----------------- #
275
+ # n_samples: int = 1,
276
+ # sample_steps: int = None,
277
+ # use_ema: bool = True,
278
+ # kappa: float = 1.0,
279
+ # temperature: float = 1.0,
280
+ # # ------------------ guidance ------------------ #
281
+ # condition_cfg=None,
282
+ # mask_cfg=None,
283
+ # w_cfg: float = 0.0,
284
+ # condition_cg=None,
285
+ # w_cg: float = 0.0,
286
+ #
287
+ # requires_grad: bool = False,
288
+ # preserve_history: bool = False,
289
+ # ):
290
+ # log = {
291
+ # "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
292
+ #
293
+ # model = self.model_ema if use_ema else self.model
294
+ #
295
+ # idx = torch.arange(sample_steps + 1)
296
+ # t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
297
+ # idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
298
+ #
299
+ # alphas = self.alpha_schedule(t)
300
+ # sigmas = (1 - alphas ** 2).sqrt()
301
+ # logSNRs = (alphas / sigmas).log()
302
+ # h = torch.zeros_like(logSNRs)
303
+ # h[1:] = logSNRs[1:] - logSNRs[:-1]
304
+ #
305
+ # xt = torch.randn_like(prior, device=self.device) * temperature
306
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
307
+ # if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
308
+ #
309
+ # with torch.set_grad_enabled(requires_grad):
310
+ # condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
311
+ # condition_vec_cg = condition_cg
312
+ #
313
+ # for i in range(1, sample_steps + 1):
314
+ #
315
+ # pred_theta, log = self.predict_function(
316
+ # xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
317
+ # use_ema=use_ema,
318
+ # requires_grad=requires_grad,
319
+ # predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
320
+ # condition_vec_cfg=condition_vec_cfg,
321
+ # condition_vec_cg=condition_vec_cg,
322
+ # w_cfg=w_cfg, w_cg=w_cg)
323
+ #
324
+ # xt = onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler)
325
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
326
+ # if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
327
+ #
328
+ # if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
329
+ # with torch.no_grad():
330
+ # logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
331
+ # log["log_p"] = logp
332
+ #
333
+ # return xt, log
334
+ #
335
+ # def second_order_sample_multistep(
336
+ # self,
337
+ # sampler: str = "ode_dpmpp_2",
338
+ # # ---------- the known fixed portion ---------- #
339
+ # prior: Optional[torch.Tensor] = None,
340
+ # # ----------------- sampling ----------------- #
341
+ # n_samples: int = 1,
342
+ # sample_steps: int = None,
343
+ # use_ema: bool = True,
344
+ # kappa: float = 1.0,
345
+ # temperature: float = 1.0,
346
+ # # ------------------ guidance ------------------ #
347
+ # condition_cfg=None,
348
+ # mask_cfg=None,
349
+ # w_cfg: float = 0.0,
350
+ # condition_cg=None,
351
+ # w_cg: float = 0.0,
352
+ #
353
+ # requires_grad: bool = False,
354
+ # preserve_history: bool = False,
355
+ # ):
356
+ # log = {
357
+ # "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
358
+ #
359
+ # model = self.model_ema if use_ema else self.model
360
+ #
361
+ # idx = torch.arange(sample_steps + 1)
362
+ # t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
363
+ # idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
364
+ #
365
+ # alphas = self.alpha_schedule(t)
366
+ # sigmas = (1 - alphas ** 2).sqrt()
367
+ # logSNRs = (alphas / sigmas).log()
368
+ # h = torch.zeros_like(logSNRs)
369
+ # h[1:] = logSNRs[1:] - logSNRs[:-1]
370
+ #
371
+ # buffer = []
372
+ #
373
+ # xt = torch.randn_like(prior, device=self.device) * temperature
374
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
375
+ # if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
376
+ #
377
+ # with torch.set_grad_enabled(requires_grad):
378
+ # condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
379
+ # condition_vec_cg = condition_cg
380
+ #
381
+ # # pre-update
382
+ #
383
+ # pred_theta, log = self.predict_function(
384
+ # xt, t[0].repeat(n_samples), alphas[0], sigmas[0],
385
+ # use_ema=use_ema,
386
+ # requires_grad=requires_grad,
387
+ # predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
388
+ # condition_vec_cfg=condition_vec_cfg,
389
+ # condition_vec_cg=condition_vec_cg,
390
+ # w_cfg=w_cfg, w_cg=w_cg)
391
+ # buffer.append(pred_theta.clone())
392
+ #
393
+ # xt = onestep_estimate(xt, pred_theta, 1, alphas, sigmas, h, sampler)
394
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
395
+ # if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
396
+ #
397
+ # pred_theta, log = self.predict_function(
398
+ # xt, t[1].repeat(n_samples), alphas[1], sigmas[1],
399
+ # use_ema=use_ema,
400
+ # requires_grad=requires_grad,
401
+ # predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
402
+ # condition_vec_cfg=condition_vec_cfg,
403
+ # condition_vec_cg=condition_vec_cg,
404
+ # w_cfg=w_cfg, w_cg=w_cg)
405
+ # buffer.append(pred_theta.clone())
406
+ #
407
+ # for i in range(2, sample_steps + 1):
408
+ #
409
+ # r = h[i - 1] / h[i]
410
+ #
411
+ # D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
412
+ #
413
+ # xt = onestep_estimate(xt, D, i, alphas, sigmas, h, sampler)
414
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
415
+ # if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
416
+ #
417
+ # if i < sample_steps + 1:
418
+ # pred_theta, log = self.predict_function(
419
+ # xt, t[i].repeat(n_samples), alphas[i], sigmas[i],
420
+ # use_ema=use_ema,
421
+ # requires_grad=requires_grad,
422
+ # predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
423
+ # condition_vec_cfg=condition_vec_cfg,
424
+ # condition_vec_cg=condition_vec_cg,
425
+ # w_cfg=w_cfg, w_cg=w_cg)
426
+ # buffer.append(pred_theta.clone())
427
+ #
428
+ # if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
429
+ # with torch.no_grad():
430
+ # logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
431
+ # log["log_p"] = logp
432
+ #
433
+ # return xt, log
434
+
435
+ def sample(
436
+ self,
437
+ # ---------- the known fixed portion ---------- #
438
+ prior: Optional[torch.Tensor] = None,
439
+ # ----------------- sampling ----------------- #
440
+ n_samples: int = 1,
441
+ sample_steps: int = 5,
442
+ use_ema: bool = True,
443
+ temperature: float = 1.0,
444
+ kappa: float = 1.0,
445
+ sampler: str = "ddim",
446
+ # ------------------ guidance ------------------ #
447
+ condition_cfg=None,
448
+ mask_cfg=None,
449
+ w_cfg: float = 0.0,
450
+ condition_cg=None,
451
+ w_cg: float = 0.0,
452
+ # ------------------ others ------------------ #
453
+ requires_grad: bool = False,
454
+ preserve_history: bool = False,
455
+ **kwargs,
456
+ ):
457
+ assert sampler in self.supported_samplers, f"Sampler '{sampler}' is not supported."
458
+
459
+ # initialize logger
460
+ log = {
461
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
462
+
463
+ # choose the model
464
+ model = self.model_ema if use_ema else self.model
465
+
466
+ # schedule the sampling steps
467
+ idx = torch.arange(sample_steps + 1)
468
+ t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
469
+ idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
470
+
471
+ alphas = self.alpha_schedule(t)
472
+ sigmas = (1 - alphas ** 2).sqrt()
473
+ logSNRs = (alphas / sigmas).log()
474
+ h = torch.zeros_like(logSNRs)
475
+ h[1:] = logSNRs[1:] - logSNRs[:-1]
476
+
477
+ buffer = [] # only for 2nd order samplers
478
+
479
+ # initialize the samples
480
+ xt = torch.randn_like(prior, device=self.device) * temperature
481
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
482
+ if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
483
+
484
+ # preprocess the conditions
485
+ with torch.set_grad_enabled(requires_grad):
486
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
487
+ condition_vec_cg = condition_cg
488
+
489
+ # enter the sampling loop
490
+ for i in range(1, sample_steps + 1):
491
+
492
+ # predict eps_theta or x_theta with CG/CFG
493
+ pred_theta, log = self.predict_function(
494
+ xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
495
+ use_ema=use_ema,
496
+ requires_grad=requires_grad,
497
+ predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
498
+ condition_vec_cfg=condition_vec_cfg,
499
+ condition_vec_cg=condition_vec_cg,
500
+ w_cfg=w_cfg, w_cg=w_cg)
501
+ if SAMPLER_CONFIG[sampler]["order"] == 2: buffer.append(pred_theta.clone())
502
+
503
+ # one step denoise
504
+ if SAMPLER_CONFIG[sampler]["order"] == 2 and i > 1:
505
+
506
+ r = h[i - 1] / h[i]
507
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
508
+
509
+ xt = onestep_estimate(xt, D, i, alphas, sigmas, h, sampler)
510
+
511
+ else:
512
+
513
+ xt = onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler)
514
+
515
+ # fix the known portion, and preserve the sampling history
516
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
517
+ if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy()
518
+
519
+ # calculate the final log_p
520
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
521
+ with torch.no_grad():
522
+ logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
523
+ log["log_p"] = logp
524
+
525
+ # clip xt if bound is not None
526
+ if self.clip_pred:
527
+ xt = xt.clip(self.x_min, self.x_max)
528
+
529
+ return xt, log
530
+
531
+ def sample_x(
532
+ self,
533
+ # ---------- the known fixed portion ---------- #
534
+ prior: Optional[torch.Tensor] = None,
535
+ # ----------------- sampling ----------------- #
536
+ n_samples: int = 1,
537
+ sample_steps: int = 5,
538
+ extra_sample_steps: int = 8,
539
+ use_ema: bool = True,
540
+ temperature: float = 1.0,
541
+ kappa: float = 1.0,
542
+ sampler: str = "ddim",
543
+ # ------------------ guidance ------------------ #
544
+ condition_cfg=None,
545
+ mask_cfg=None,
546
+ w_cfg: float = 0.0,
547
+ condition_cg=None,
548
+ w_cg: float = 0.0,
549
+ # ------------------ others ------------------ #
550
+ requires_grad: bool = False,
551
+ preserve_history: bool = False,
552
+ **kwargs,
553
+ ):
554
+ assert sampler in self.supported_samplers, f"Sampler '{sampler}' is not supported."
555
+
556
+ # initialize logger
557
+ log = {
558
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
559
+
560
+ # choose the model
561
+ model = self.model_ema if use_ema else self.model
562
+
563
+ # schedule the sampling steps
564
+ idx = torch.arange(sample_steps + 1)
565
+ t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
566
+ idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
567
+
568
+ alphas = self.alpha_schedule(t)
569
+ sigmas = (1 - alphas ** 2).sqrt()
570
+ logSNRs = (alphas / sigmas).log()
571
+ h = torch.zeros_like(logSNRs)
572
+ h[1:] = logSNRs[1:] - logSNRs[:-1]
573
+
574
+ buffer = [] # only for 2nd order samplers
575
+
576
+ # initialize the samples
577
+ xt = torch.randn_like(prior, device=self.device) * temperature
578
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
579
+ if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
580
+
581
+ # preprocess the conditions
582
+ with torch.set_grad_enabled(requires_grad):
583
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
584
+ condition_vec_cg = condition_cg
585
+
586
+ # enter the sampling loop
587
+ for i in range(1, sample_steps + 1):
588
+
589
+ # predict eps_theta or x_theta with CG/CFG
590
+ pred_theta, log = self.predict_function(
591
+ xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
592
+ use_ema=use_ema,
593
+ requires_grad=requires_grad,
594
+ predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
595
+ condition_vec_cfg=condition_vec_cfg,
596
+ condition_vec_cg=condition_vec_cg,
597
+ w_cfg=w_cfg, w_cg=w_cg)
598
+ if SAMPLER_CONFIG[sampler]["order"] == 2: buffer.append(pred_theta.clone())
599
+
600
+ # one step denoise
601
+ if SAMPLER_CONFIG[sampler]["order"] == 2 and i > 1:
602
+
603
+ r = h[i - 1] / h[i]
604
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
605
+
606
+ xt = onestep_estimate(xt, D, i, alphas, sigmas, h, sampler)
607
+
608
+ else:
609
+
610
+ xt = onestep_estimate(xt, pred_theta, i, alphas, sigmas, h, sampler)
611
+
612
+ # fix the known portion, and preserve the sampling history
613
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
614
+ if preserve_history: log["sample_history"][:, 1] = xt.cpu().numpy() # TODO: BUG logging
615
+
616
+ # diffusion-X extra sampling steps, only for 1st order solvers
617
+ if SAMPLER_CONFIG[sampler]["order"] == 1 and extra_sample_steps > 0:
618
+ for i in range(extra_sample_steps):
619
+
620
+ # predict eps_theta or x_theta with CG/CFG
621
+ pred_theta, log = self.predict_function(
622
+ xt, t[sample_steps - 1].repeat(n_samples), alphas[sample_steps - 1], sigmas[sample_steps - 1],
623
+ use_ema=use_ema,
624
+ requires_grad=requires_grad,
625
+ predict_noise=SAMPLER_CONFIG[sampler]["predict_noise"],
626
+ condition_vec_cfg=condition_vec_cfg,
627
+ condition_vec_cg=condition_vec_cg,
628
+ w_cfg=w_cfg, w_cg=w_cg)
629
+
630
+ xt = onestep_estimate(xt, pred_theta, sample_steps, alphas, sigmas, h, sampler)
631
+
632
+ # fix the known portion, and preserve the sampling history
633
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
634
+
635
+ # calculate the final log_p
636
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
637
+ with torch.no_grad():
638
+ logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
639
+ log["log_p"] = logp
640
+
641
+ # clip xt if bound is not None
642
+ if self.clip_pred:
643
+ xt = xt.clip(self.x_min, self.x_max)
644
+
645
+ return xt, log
646
+
647
+
648
+
649
+ # def sample_with_ode_dpm_1(
650
+ # self,
651
+ # # ---------- the known fixed portion ---------- #
652
+ # prior: Optional[torch.Tensor] = None,
653
+ # # ----------------- sampling ----------------- #
654
+ # n_samples: int = 1,
655
+ # sample_steps: int = None,
656
+ # use_ema: bool = True,
657
+ # kappa: float = 1.0,
658
+ # temperature: float = 1.0,
659
+ # # ------------------ guidance ------------------ #
660
+ # condition_cfg=None,
661
+ # mask_cfg=None,
662
+ # w_cfg: float = 0.0,
663
+ # condition_cg=None,
664
+ # w_cg: float = 0.0,
665
+ #
666
+ # preserve_history: bool = False,
667
+ # ):
668
+ # log = {
669
+ # "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
670
+ #
671
+ # model = self.model_ema if use_ema else self.model
672
+ #
673
+ # idx = torch.arange(sample_steps + 1)
674
+ # t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
675
+ # idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
676
+ #
677
+ # alphas = self.alpha_schedule(t)
678
+ # sigmas = (1 - alphas ** 2).sqrt()
679
+ # logSNRs = (alphas / sigmas).log()
680
+ # h = torch.zeros_like(logSNRs)
681
+ # h[1:] = logSNRs[1:] - logSNRs[:-1]
682
+ #
683
+ # xt = torch.randn_like(prior, device=self.device) * temperature
684
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
685
+ # if preserve_history: log["sample_history"][:, 0] = xt.cpu().numpy()
686
+ #
687
+ # with torch.no_grad():
688
+ # condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
689
+ # condition_vec_cg = condition_cg
690
+ #
691
+ # for i in range(1, sample_steps + 1):
692
+ #
693
+ # eps_theta, log = self.predict_eps_theta(
694
+ # xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
695
+ # use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
696
+ #
697
+ # xt = alphas[i] / alphas[i - 1] * xt - sigmas[i] * torch.expm1(h[i]) * eps_theta
698
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
699
+ #
700
+ # if preserve_history:
701
+ # x_history[:, i] = xt.cpu().numpy()
702
+ #
703
+ # log["sample_history"] = x_history
704
+ # if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
705
+ # with torch.no_grad():
706
+ # logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
707
+ # log["log_p"] = logp
708
+ #
709
+ # return xt, log
710
+ #
711
+ # def sample_with_sde_dpm_1(
712
+ # self,
713
+ # # ---------- the known fixed portion ---------- #
714
+ # prior: Optional[torch.Tensor] = None,
715
+ # # ----------------- sampling ----------------- #
716
+ # n_samples: int = 1,
717
+ # sample_steps: int = None,
718
+ # use_ema: bool = True,
719
+ # kappa: float = 1.0,
720
+ # temperature: float = 1.0,
721
+ # # ------------------ guidance ------------------ #
722
+ # condition_cfg=None,
723
+ # mask_cfg=None,
724
+ # w_cfg: float = 0.0,
725
+ # condition_cg=None,
726
+ # w_cg: float = 0.0,
727
+ #
728
+ # preserve_history: bool = False,
729
+ # ):
730
+ # log, x_history = {}, None
731
+ # model = self.model_ema if use_ema else self.model
732
+ #
733
+ # idx = torch.arange(sample_steps + 1)
734
+ # t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
735
+ # idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
736
+ #
737
+ # alphas = self.alpha_schedule(t)
738
+ # sigmas = (1 - alphas ** 2).sqrt()
739
+ # logSNRs = (alphas / sigmas).log()
740
+ # h = torch.zeros_like(logSNRs)
741
+ # h[1:] = logSNRs[1:] - logSNRs[:-1]
742
+ #
743
+ # if prior is None:
744
+ # xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
745
+ # else:
746
+ # xt = torch.randn_like(prior, device=self.device) * temperature
747
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
748
+ #
749
+ # if preserve_history:
750
+ # x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
751
+ # x_history[:, 0] = xt.cpu().numpy()
752
+ #
753
+ # with torch.no_grad():
754
+ # condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
755
+ # condition_vec_cg = condition_cg
756
+ #
757
+ # for i in range(1, sample_steps + 1):
758
+ #
759
+ # eps_theta, log = self.predict_eps_theta(
760
+ # xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
761
+ # use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
762
+ #
763
+ # xt = (
764
+ # alphas[i] / alphas[i - 1] * xt -
765
+ # 2. * sigmas[i] * torch.expm1(h[i]) * eps_theta +
766
+ # sigmas[i] * torch.expm1(2. * h[i]).sqrt() * torch.randn_like(xt))
767
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
768
+ #
769
+ # if preserve_history:
770
+ # x_history[:, i] = xt.cpu().numpy()
771
+ #
772
+ # log["sample_history"] = x_history
773
+ # if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
774
+ # with torch.no_grad():
775
+ # logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
776
+ # log["log_p"] = logp
777
+ #
778
+ # return xt, log
779
+ #
780
+ # def sample_with_sde_dpmpp_1(
781
+ # self,
782
+ # # ---------- the known fixed portion ---------- #
783
+ # prior: Optional[torch.Tensor] = None,
784
+ # # ----------------- sampling ----------------- #
785
+ # n_samples: int = 1,
786
+ # sample_steps: int = None,
787
+ # use_ema: bool = True,
788
+ # kappa: float = 1.0,
789
+ # temperature: float = 1.0,
790
+ # # ------------------ guidance ------------------ #
791
+ # condition_cfg=None,
792
+ # mask_cfg=None,
793
+ # w_cfg: float = 0.0,
794
+ # condition_cg=None,
795
+ # w_cg: float = 0.0,
796
+ #
797
+ # preserve_history: bool = False,
798
+ # ):
799
+ # log, x_history = {}, None
800
+ # model = self.model_ema if use_ema else self.model
801
+ #
802
+ # idx = torch.arange(sample_steps + 1)
803
+ # t = (((sample_steps - idx) / sample_steps * self.t_range[1] ** (1 / kappa) +
804
+ # idx / sample_steps * self.t_range[0] ** (1 / kappa)) ** kappa).to(self.device)
805
+ #
806
+ # alphas = self.alpha_schedule(t)
807
+ # sigmas = (1 - alphas ** 2).sqrt()
808
+ # logSNRs = (alphas / sigmas).log()
809
+ # h = torch.zeros_like(logSNRs)
810
+ # h[1:] = logSNRs[1:] - logSNRs[:-1]
811
+ #
812
+ # if prior is None:
813
+ # xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
814
+ # else:
815
+ # xt = torch.randn_like(prior, device=self.device) * temperature
816
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
817
+ #
818
+ # if preserve_history:
819
+ # x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
820
+ # x_history[:, 0] = xt.cpu().numpy()
821
+ #
822
+ # with torch.no_grad():
823
+ # condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
824
+ # condition_vec_cg = condition_cg
825
+ #
826
+ # for i in range(1, sample_steps + 1):
827
+ #
828
+ # x_theta, log = self.predict_x_theta(
829
+ # xt, t[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
830
+ # use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
831
+ #
832
+ # xt = (
833
+ # sigmas[i] / sigmas[i - 1] * (-h[i]).exp() * xt +
834
+ # alphas[i] * (1. - (-2. * h[i]).exp()) * x_theta +
835
+ # sigmas[i] * (1. - (-2. * h[i]).exp()).sqrt() * torch.randn_like(xt))
836
+ # xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
837
+ #
838
+ # if preserve_history:
839
+ # x_history[:, i] = xt.cpu().numpy()
840
+ #
841
+ # log["sample_history"] = x_history
842
+ # if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
843
+ # with torch.no_grad():
844
+ # logp = self.classifier.logp(xt, t[-1].repeat(n_samples), condition_vec_cg)
845
+ # log["log_p"] = logp
846
+ #
847
+ # return xt, log
848
+
849
+ # def sample_with_ode_dpm_2(
850
+ # self,
851
+ # # ---------- the known fixed portion ---------- #
852
+ # prior: Optional[torch.Tensor] = None,
853
+ # # ----------------- sampling ----------------- #
854
+ # n_samples: int = 1,
855
+ # sample_steps: int = None,
856
+ # use_ema: bool = True,
857
+ # kappa: float = 1.0,
858
+ # temperature: float = 1.0,
859
+ # # ------------------ guidance ------------------ #
860
+ # condition_cfg=None,
861
+ # mask_cfg=None,
862
+ # w_cfg: float = 0.0,
863
+ # condition_cg=None,
864
+ # w_cg: float = 0.0,
865
+ #
866
+ # preserve_history: bool = False,
867
+ # ):
868
+ # raise NotImplementedError
869
+
870
+ # def sample_with_ode_dpmpp_1(
871
+ # self,
872
+ # # ---------- the known fixed portion ---------- #
873
+ # prior: Optional[torch.Tensor] = None,
874
+ # # ----------------- sampling ----------------- #
875
+ # n_samples: int = 1,
876
+ # sample_steps: int = 5,
877
+ # use_ema: bool = True,
878
+ # kappa: float = 1.0,
879
+ # temperature: float = 1.0,
880
+ # # ------------------ guidance ------------------ #
881
+ # condition_cfg=None,
882
+ # mask_cfg=None,
883
+ # w_cfg: float = 0.0,
884
+ # condition_cg=None,
885
+ # w_cg: float = 0.0,
886
+ #
887
+ # preserve_history: bool = False,
888
+ # ):
889
+ # raise NotImplementedError
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/dpmsolver_discrete.py ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn as nn
7
+
8
+ from cleandiffuser.classifier import BaseClassifier
9
+ from cleandiffuser.nn_condition import BaseNNCondition
10
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
11
+ from cleandiffuser.utils import at_least_ndim
12
+ from .basic import DiffusionModel
13
+
14
+
15
+ def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
16
+ """
17
+ x_theta = (x - sigma * eps_theta) / alpha
18
+ """
19
+ return (x - sigma * eps_theta) / alpha
20
+
21
+
22
+ def xtheta_to_epstheta(x, alpha, sigma, x_theta):
23
+ """
24
+ eps_theta = (x - alpha * x_theta) / sigma
25
+ """
26
+ return (x - alpha * x_theta) / sigma
27
+
28
+
29
+ class DPMSolverDiscrete(DiffusionModel):
30
+
31
+ def __init__(
32
+ self,
33
+
34
+ # ----------------- Neural Networks ----------------- #
35
+ nn_diffusion: BaseNNDiffusion,
36
+ nn_condition: Optional[BaseNNCondition] = None,
37
+
38
+ # ----------------- Masks ----------------- #
39
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
40
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
41
+ # Add loss weight
42
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
43
+
44
+ # ------------------ Plugs ---------------- #
45
+ # Add a classifier to enable classifier-guidance
46
+ classifier: Optional[BaseClassifier] = None,
47
+
48
+ # ------------------ Params ---------------- #
49
+ grad_clip_norm: Optional[float] = None,
50
+ diffusion_steps: int = 1000,
51
+ ema_rate: float = 0.995,
52
+ optim_params: Optional[dict] = None,
53
+
54
+ # ------------------- DPM Params ------------------- #
55
+ predict_noise: bool = False,
56
+ noise_schedule: str = "linear", # or cosine
57
+ t_eps: float = 1e-3,
58
+
59
+ device: Union[torch.device, str] = "cpu"
60
+ ):
61
+ super().__init__(
62
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
63
+ diffusion_steps, ema_rate, optim_params, device)
64
+
65
+ self.predict_noise = predict_noise
66
+ self.noise_schedule = noise_schedule
67
+ self.t_eps = t_eps
68
+
69
+ if self.noise_schedule == "linear":
70
+ beta0, beta1 = 0.1, 20
71
+ self.t_cont = torch.linspace(t_eps, 1., diffusion_steps, device=device)
72
+ self.alphas = (-(beta1 - beta0) / 4 * (self.t_cont ** 2) - beta0 / 2 * self.t_cont).exp()
73
+ elif self.noise_schedule == "cosine":
74
+ s = 0.008
75
+ self.t_cont = torch.linspace(t_eps, 0.9946, diffusion_steps, device=device)
76
+ self.alphas = ((torch.cos(np.pi / 2 * (self.t_cont + s) / (1 + s))).log()
77
+ - np.log(np.cos(np.pi / 2 * s / (1 + s)))).exp()
78
+ else:
79
+ raise ValueError(f"noise_schedule should be 'linear' or 'cosine', but got {self.noise_schedule}.")
80
+
81
+ self.sigmas = (1 - self.alphas ** 2).sqrt()
82
+ self.logSNRs = (self.alphas / self.sigmas).log()
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # Training
86
+
87
+ def add_noise(self, x0, idx=None, eps=None):
88
+ if idx is None:
89
+ idx = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device)
90
+ eps = torch.randn_like(x0) if eps is None else eps
91
+ alpha = at_least_ndim(self.alphas[idx], x0.dim())
92
+ sigma = at_least_ndim(self.sigmas[idx], x0.dim())
93
+ xt = x0 * alpha + sigma * eps
94
+ xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
95
+ return xt, idx, eps
96
+
97
+ def loss(self, x0, condition=None):
98
+ xt, idx, eps = self.add_noise(x0)
99
+ condition = self.model["condition"](condition) if condition is not None else None
100
+
101
+ if self.predict_noise:
102
+ loss = (self.model["diffusion"](xt, idx, condition) - eps) ** 2
103
+ else:
104
+ loss = (self.model["diffusion"](xt, idx, condition) - x0) ** 2
105
+
106
+ return (loss * self.loss_weight * (1. - self.fix_mask)).mean()
107
+
108
+ def update(self, x0, condition=None, **kwargs):
109
+ self.optimizer.zero_grad()
110
+ loss = self.loss(x0, condition)
111
+ loss.backward()
112
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
113
+ if self.grad_clip_norm else None
114
+ self.optimizer.step()
115
+ self.ema_update()
116
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
117
+ return log
118
+
119
+ def update_classifier(self, x0, condition):
120
+ xt, idx, eps = self.add_noise(x0)
121
+ log = self.classifier.update(xt, idx, condition)
122
+ return log
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Inference
126
+
127
+ def predict_eps_theta(
128
+ self, x, idx, alpha, sigma, use_ema=False,
129
+ # ----------------- CFG ----------------- #
130
+ condition_vec_cfg=None,
131
+ w_cfg: float = 0.0,
132
+ # ----------------- CG ----------------- #
133
+ condition_vec_cg=None,
134
+ w_cg: float = 1.0,
135
+ ):
136
+ b = x.shape[0]
137
+ model = self.model_ema if use_ema else self.model
138
+
139
+ # ----------------- CFG ----------------- #
140
+ with torch.no_grad():
141
+ if w_cfg != 0.0 and w_cfg != 1.0:
142
+ repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
143
+ condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
144
+ pred = model["diffusion"](
145
+ x.repeat(*repeat_dim), idx.repeat(2), condition_vec_cfg)
146
+ pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
147
+ else:
148
+ pred = model["diffusion"](x, idx, condition_vec_cfg)
149
+
150
+ if self.predict_noise:
151
+ eps_theta = pred
152
+ else:
153
+ eps_theta = xtheta_to_epstheta(x, alpha, sigma, pred)
154
+
155
+ # ----------------- CG ----------------- #
156
+ if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
157
+ log_p, grad = self.classifier.gradients(x.clone(), idx, condition_vec_cg)
158
+ eps_theta = eps_theta - w_cg * sigma * grad
159
+ else:
160
+ log_p = None
161
+
162
+ eps_theta = eps_theta * (1 - self.fix_mask)
163
+
164
+ return eps_theta, {"log_p": log_p}
165
+
166
+ def predict_x_theta(
167
+ self, x, idx, alpha, sigma, use_ema=False,
168
+ # ----------------- CFG ----------------- #
169
+ condition_vec_cfg=None,
170
+ w_cfg: float = 0.0,
171
+ # ----------------- CG ----------------- #
172
+ condition_vec_cg=None,
173
+ w_cg: float = 1.0,
174
+ ):
175
+ b = x.shape[0]
176
+ model = self.model_ema if use_ema else self.model
177
+
178
+ # ----------------- CFG ----------------- #
179
+ with torch.no_grad():
180
+ if w_cfg != 0.0 and w_cfg != 1.0:
181
+ repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
182
+ condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
183
+ pred = model["diffusion"](
184
+ x.repeat(*repeat_dim), idx.repeat(2), condition_vec_cfg)
185
+ pred = w_cfg * pred[:b] + (1. - w_cfg) * pred[b:]
186
+ else:
187
+ pred = model["diffusion"](x, idx, condition_vec_cfg)
188
+
189
+ if self.predict_noise:
190
+ x_theta = epstheta_to_xtheta(x, alpha, sigma, pred)
191
+ else:
192
+ x_theta = pred
193
+
194
+ # ----------------- CG ----------------- #
195
+ if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
196
+ log_p, grad = self.classifier.gradients(x.clone(), idx, condition_vec_cg)
197
+ x_theta = x_theta + w_cg * ((sigma ** 2) / alpha) * grad
198
+ else:
199
+ log_p = None
200
+
201
+ x_theta = x_theta * (1 - self.fix_mask) + x * self.fix_mask
202
+
203
+ return x_theta, {"log_p": log_p}
204
+
205
+ def sample_with_ode_dpm_1(
206
+ self,
207
+ # ---------- the known fixed portion ---------- #
208
+ prior: Optional[torch.Tensor] = None,
209
+ # ----------------- sampling ----------------- #
210
+ n_samples: int = 1,
211
+ sample_steps: int = None,
212
+ use_ema: bool = True,
213
+ kappa: float = 1.0,
214
+ temperature: float = 1.0,
215
+ # ------------------ guidance ------------------ #
216
+ condition_cfg=None,
217
+ mask_cfg=None,
218
+ w_cfg: float = 0.0,
219
+ condition_cg=None,
220
+ w_cg: float = 0.0,
221
+
222
+ preserve_history: bool = False,
223
+ ):
224
+ log, x_history = {}, None
225
+ model = self.model_ema if use_ema else self.model
226
+
227
+ idx = torch.arange(sample_steps + 1)
228
+ t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
229
+ idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
230
+ t_sample = (torch.max(t_cond-1/self.diffusion_steps, torch.zeros_like(t_cond))*self.diffusion_steps).long()
231
+
232
+ alphas = self.alphas[t_sample]
233
+ sigmas = self.sigmas[t_sample]
234
+ logSNRs = self.logSNRs[t_sample]
235
+ h = torch.zeros_like(logSNRs)
236
+ h[1:] = logSNRs[1:] - logSNRs[:-1]
237
+
238
+ if prior is None:
239
+ xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
240
+ else:
241
+ xt = torch.randn_like(prior, device=self.device) * temperature
242
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
243
+
244
+ if preserve_history:
245
+ x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
246
+ x_history[:, 0] = xt.cpu().numpy()
247
+
248
+ with torch.no_grad():
249
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
250
+ condition_vec_cg = condition_cg
251
+
252
+ for i in range(1, sample_steps + 1):
253
+
254
+ eps_theta, log = self.predict_eps_theta(
255
+ xt, t_sample[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
256
+ use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
257
+
258
+ xt = alphas[i] / alphas[i - 1] * xt - sigmas[i] * torch.expm1(h[i]) * eps_theta
259
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
260
+
261
+ if preserve_history:
262
+ x_history[:, i] = xt.cpu().numpy()
263
+
264
+ log["sample_history"] = x_history
265
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
266
+ with torch.no_grad():
267
+ logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
268
+ log["log_p"] = logp
269
+
270
+ return xt, log
271
+
272
+ def sample_with_sde_dpm_1(
273
+ self,
274
+ # ---------- the known fixed portion ---------- #
275
+ prior: Optional[torch.Tensor] = None,
276
+ # ----------------- sampling ----------------- #
277
+ n_samples: int = 1,
278
+ sample_steps: int = None,
279
+ use_ema: bool = True,
280
+ kappa: float = 1.0,
281
+ temperature: float = 1.0,
282
+ # ------------------ guidance ------------------ #
283
+ condition_cfg=None,
284
+ mask_cfg=None,
285
+ w_cfg: float = 0.0,
286
+ condition_cg=None,
287
+ w_cg: float = 0.0,
288
+
289
+ preserve_history: bool = False,
290
+ ):
291
+ log, x_history = {}, None
292
+ model = self.model_ema if use_ema else self.model
293
+
294
+ idx = torch.arange(sample_steps + 1)
295
+ t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
296
+ idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
297
+ t_sample = (torch.max(t_cond - 1 / self.diffusion_steps,
298
+ torch.zeros_like(t_cond)) * self.diffusion_steps).long()
299
+
300
+ alphas = self.alphas[t_sample]
301
+ sigmas = self.sigmas[t_sample]
302
+ logSNRs = self.logSNRs[t_sample]
303
+ h = torch.zeros_like(logSNRs)
304
+ h[1:] = logSNRs[1:] - logSNRs[:-1]
305
+
306
+ if prior is None:
307
+ xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
308
+ else:
309
+ xt = torch.randn_like(prior, device=self.device) * temperature
310
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
311
+
312
+ if preserve_history:
313
+ x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
314
+ x_history[:, 0] = xt.cpu().numpy()
315
+
316
+ with torch.no_grad():
317
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
318
+ condition_vec_cg = condition_cg
319
+
320
+ for i in range(1, sample_steps + 1):
321
+
322
+ eps_theta, log = self.predict_eps_theta(
323
+ xt, t_sample[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
324
+ use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
325
+
326
+ xt = (
327
+ alphas[i] / alphas[i - 1] * xt -
328
+ 2. * sigmas[i] * torch.expm1(h[i]) * eps_theta +
329
+ sigmas[i] * torch.expm1(2. * h[i]).sqrt() * torch.randn_like(xt))
330
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
331
+
332
+ if preserve_history:
333
+ x_history[:, i] = xt.cpu().numpy()
334
+
335
+ log["sample_history"] = x_history
336
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
337
+ with torch.no_grad():
338
+ logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
339
+ log["log_p"] = logp
340
+
341
+ return xt, log
342
+
343
+ def sample_with_sde_dpmpp_1(
344
+ self,
345
+ # ---------- the known fixed portion ---------- #
346
+ prior: Optional[torch.Tensor] = None,
347
+ # ----------------- sampling ----------------- #
348
+ n_samples: int = 1,
349
+ sample_steps: int = None,
350
+ use_ema: bool = True,
351
+ kappa: float = 1.0,
352
+ temperature: float = 1.0,
353
+ # ------------------ guidance ------------------ #
354
+ condition_cfg=None,
355
+ mask_cfg=None,
356
+ w_cfg: float = 0.0,
357
+ condition_cg=None,
358
+ w_cg: float = 0.0,
359
+
360
+ preserve_history: bool = False,
361
+ ):
362
+ log, x_history = {}, None
363
+ model = self.model_ema if use_ema else self.model
364
+
365
+ idx = torch.arange(sample_steps + 1)
366
+ t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
367
+ idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
368
+ t_sample = (torch.max(t_cond - 1 / self.diffusion_steps,
369
+ torch.zeros_like(t_cond)) * self.diffusion_steps).long()
370
+
371
+ alphas = self.alphas[t_sample]
372
+ sigmas = self.sigmas[t_sample]
373
+ logSNRs = self.logSNRs[t_sample]
374
+ h = torch.zeros_like(logSNRs)
375
+ h[1:] = logSNRs[1:] - logSNRs[:-1]
376
+
377
+ if prior is None:
378
+ xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
379
+ else:
380
+ xt = torch.randn_like(prior, device=self.device) * temperature
381
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
382
+
383
+ if preserve_history:
384
+ x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
385
+ x_history[:, 0] = xt.cpu().numpy()
386
+
387
+ with torch.no_grad():
388
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
389
+ condition_vec_cg = condition_cg
390
+
391
+ for i in range(1, sample_steps + 1):
392
+
393
+ x_theta, log = self.predict_x_theta(
394
+ xt, t_sample[i - 1].repeat(n_samples), alphas[i - 1], sigmas[i - 1],
395
+ use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
396
+
397
+ xt = (
398
+ sigmas[i] / sigmas[i - 1] * (-h[i]).exp() * xt +
399
+ alphas[i] * (1. - (-2. * h[i]).exp()) * x_theta +
400
+ sigmas[i] * (1. - (-2. * h[i]).exp()).sqrt() * torch.randn_like(xt))
401
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
402
+
403
+ if preserve_history:
404
+ x_history[:, i] = xt.cpu().numpy()
405
+
406
+ log["sample_history"] = x_history
407
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
408
+ with torch.no_grad():
409
+ logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
410
+ log["log_p"] = logp
411
+
412
+ return xt, log
413
+
414
+ def sample_with_ode_dpm_2(
415
+ self,
416
+ # ---------- the known fixed portion ---------- #
417
+ prior: Optional[torch.Tensor] = None,
418
+ # ----------------- sampling ----------------- #
419
+ n_samples: int = 1,
420
+ sample_steps: int = None,
421
+ use_ema: bool = True,
422
+ kappa: float = 1.0,
423
+ temperature: float = 1.0,
424
+ # ------------------ guidance ------------------ #
425
+ condition_cfg=None,
426
+ mask_cfg=None,
427
+ w_cfg: float = 0.0,
428
+ condition_cg=None,
429
+ w_cg: float = 0.0,
430
+
431
+ preserve_history: bool = False,
432
+ ):
433
+ raise NotImplementedError
434
+
435
+ def sample_with_ode_dpmpp_1(
436
+ self,
437
+ # ---------- the known fixed portion ---------- #
438
+ prior: Optional[torch.Tensor] = None,
439
+ # ----------------- sampling ----------------- #
440
+ n_samples: int = 1,
441
+ sample_steps: int = 5,
442
+ use_ema: bool = True,
443
+ kappa: float = 1.0,
444
+ temperature: float = 1.0,
445
+ # ------------------ guidance ------------------ #
446
+ condition_cfg=None,
447
+ mask_cfg=None,
448
+ w_cfg: float = 0.0,
449
+ condition_cg=None,
450
+ w_cg: float = 0.0,
451
+
452
+ preserve_history: bool = False,
453
+ ):
454
+ raise NotImplementedError
455
+
456
+ def sample_with_ode_dpmpp_2(
457
+ self,
458
+ # ---------- the known fixed portion ---------- #
459
+ prior: Optional[torch.Tensor] = None,
460
+ # ----------------- sampling ----------------- #
461
+ n_samples: int = 1,
462
+ sample_steps: int = 5,
463
+ use_ema: bool = True,
464
+ kappa: float = 1.0,
465
+ temperature: float = 1.0,
466
+ # ------------------ guidance ------------------ #
467
+ condition_cfg=None,
468
+ mask_cfg=None,
469
+ w_cfg: float = 0.0,
470
+ condition_cg=None,
471
+ w_cg: float = 0.0,
472
+
473
+ preserve_history: bool = False,
474
+ ):
475
+ log, x_history = {}, None
476
+ model = self.model_ema if use_ema else self.model
477
+
478
+ idx = torch.arange(sample_steps + 1)
479
+ t_cond = (((sample_steps - idx) / sample_steps * 1. ** (1 / kappa) +
480
+ idx / sample_steps * self.t_eps ** (1 / kappa)) ** kappa).to(self.device)
481
+ t_sample = (torch.max(t_cond - 1 / self.diffusion_steps,
482
+ torch.zeros_like(t_cond)) * self.diffusion_steps).long()
483
+
484
+ alphas = self.alphas[t_sample]
485
+ sigmas = self.sigmas[t_sample]
486
+ logSNRs = self.logSNRs[t_sample]
487
+ h = torch.zeros_like(logSNRs)
488
+ h[1:] = logSNRs[1:] - logSNRs[:-1]
489
+ buffer = []
490
+
491
+ if prior is None:
492
+ xt = torch.randn((n_samples, *self.default_x_shape), device=self.device) * temperature
493
+ else:
494
+ xt = torch.randn_like(prior, device=self.device) * temperature
495
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
496
+
497
+ if preserve_history:
498
+ x_history = np.empty((n_samples, sample_steps + 1, *xt.shape))
499
+ x_history[:, 0] = xt.cpu().numpy()
500
+
501
+ with torch.no_grad():
502
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
503
+ condition_vec_cg = condition_cg
504
+
505
+ x_theta_t0, log = self.predict_x_theta(
506
+ xt, t_sample[0].repeat(n_samples), alphas[0], sigmas[0], use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
507
+ x_theta_t0 = x_theta_t0 * (1. - self.fix_mask) + prior * self.fix_mask
508
+ buffer.append(x_theta_t0)
509
+
510
+ xt = sigmas[1] / sigmas[0] * xt - alphas[1] * torch.expm1(-h[1]) * buffer[-1]
511
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
512
+ if preserve_history:
513
+ x_history[:, 1] = xt.cpu().numpy()
514
+
515
+ x_theta_t1, log = self.predict_x_theta(
516
+ xt, t_sample[1].repeat(n_samples), alphas[1], sigmas[1], use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
517
+ x_theta_t1 = x_theta_t1 * (1. - self.fix_mask) + prior * self.fix_mask
518
+ buffer.append(x_theta_t1)
519
+
520
+ for i in range(2, sample_steps + 1):
521
+
522
+ r = h[i - 1] / h[i]
523
+
524
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
525
+
526
+ xt = sigmas[i] / sigmas[i - 1] * xt - alphas[i] * torch.expm1(-h[i]) * D
527
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
528
+
529
+ if i < sample_steps + 1:
530
+ x_theta_ti, log = self.predict_x_theta(
531
+ xt, t_sample[i].repeat(n_samples), alphas[i], sigmas[i],
532
+ use_ema, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg)
533
+ x_theta_ti = x_theta_ti * (1. - self.fix_mask) + prior * self.fix_mask
534
+ buffer.append(x_theta_ti.clone())
535
+
536
+ if preserve_history:
537
+ x_history[:, i] = xt.cpu().numpy()
538
+
539
+ log["sample_history"] = x_history
540
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
541
+ with torch.no_grad():
542
+ logp = self.classifier.logp(xt, t_sample[-1].repeat(n_samples), condition_vec_cg)
543
+ log["log_p"] = logp
544
+
545
+ return xt, log
546
+
547
+ def sample(
548
+ self,
549
+ # ---------- the known fixed portion ---------- #
550
+ prior: Optional[torch.Tensor] = None,
551
+ # ----------------- sampling ----------------- #
552
+ n_samples: int = 1,
553
+ sample_steps: int = 5,
554
+ use_ema: bool = True,
555
+ kappa: float = 1.0,
556
+ temperature: float = 1.0,
557
+ solver: str = "dpmsolver++", # or ddim
558
+ # ------------------ guidance ------------------ #
559
+ condition_cfg=None,
560
+ mask_cfg=None,
561
+ w_cfg: float = 0.0,
562
+ condition_cg=None,
563
+ w_cg: float = 0.0,
564
+
565
+ preserve_history: bool = False,
566
+ ):
567
+ if solver == "ode_dpm_1" or solver == "ddim":
568
+ return self.sample_with_ode_dpm_1(
569
+ prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
570
+ condition_cg, w_cg,
571
+ preserve_history)
572
+ elif solver == "sde_dpm_1":
573
+ return self.sample_with_sde_dpm_1(
574
+ prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
575
+ condition_cg, w_cg,
576
+ preserve_history)
577
+ elif solver == "sde_dpmpp_1":
578
+ return self.sample_with_sde_dpmpp_1(
579
+ prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
580
+ condition_cg, w_cg,
581
+ preserve_history)
582
+ elif solver == "ode_dpm_2":
583
+ return self.sample_with_ode_dpm_2(
584
+ prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
585
+ condition_cg, w_cg,
586
+ preserve_history)
587
+ elif solver == "ode_dpmpp_1":
588
+ return self.sample_with_ode_dpmpp_1(
589
+ prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
590
+ condition_cg, w_cg,
591
+ preserve_history)
592
+ elif solver == "ode_dpmpp_2":
593
+ return self.sample_with_ode_dpmpp_2(
594
+ prior, n_samples, sample_steps, use_ema, kappa, temperature, condition_cfg, mask_cfg, w_cfg,
595
+ condition_cg, w_cg,
596
+ preserve_history)
597
+ else:
598
+ raise ValueError(f"Solver: {solver} has not been implemented yet.")
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edm.py ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn as nn
7
+
8
+ from cleandiffuser.utils import at_least_ndim
9
+ from cleandiffuser.classifier import BaseClassifier
10
+ from cleandiffuser.nn_condition import BaseNNCondition
11
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
12
+ from .basic import DiffusionModel
13
+
14
+
15
+ class EDMArchetecture(DiffusionModel):
16
+
17
+ def __init__(
18
+ self,
19
+
20
+ # ----------------- Neural Networks ----------------- #
21
+ nn_diffusion: BaseNNDiffusion,
22
+ nn_condition: Optional[BaseNNCondition] = None,
23
+
24
+ # ----------------- Masks ----------------- #
25
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
26
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
27
+ # Add loss weight
28
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
29
+
30
+ # ------------------ Plugs ---------------- #
31
+ # Add a classifier to enable classifier-guidance
32
+ classifier: Optional[BaseClassifier] = None,
33
+
34
+ # ------------------ Params ---------------- #
35
+ grad_clip_norm: Optional[float] = None,
36
+ diffusion_steps: int = 1000,
37
+ ema_rate: float = 0.995,
38
+ optim_params: Optional[dict] = None,
39
+
40
+ device: Union[torch.device, str] = "cpu"
41
+ ):
42
+ super().__init__(
43
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
44
+ diffusion_steps, ema_rate, optim_params, device)
45
+
46
+ self.dot_scale_s = None
47
+ self.dot_sigma_s = None
48
+ self.scale_s = None
49
+ self.t_s = None
50
+ self.sigma_s = None
51
+ self.x_weight_s, self.D_weight_s = None, None
52
+ self.sample_steps = None
53
+
54
+ def set_sample_steps(self, N: int):
55
+ raise NotImplementedError
56
+
57
+ def c_skip(self, sigma):
58
+ raise NotImplementedError
59
+
60
+ def c_out(self, sigma):
61
+ raise NotImplementedError
62
+
63
+ def c_in(self, sigma):
64
+ raise NotImplementedError
65
+
66
+ def c_noise(self, sigma):
67
+ raise NotImplementedError
68
+
69
+ def loss_weighting(self, sigma):
70
+ raise NotImplementedError
71
+
72
+ def sample_noise_distribution(self, N):
73
+ raise NotImplementedError
74
+
75
+ def sample_scale_distribution(self, N):
76
+ raise NotImplementedError
77
+
78
+ def D(self, x, sigma, condition=None, use_ema=False):
79
+ """ Prepositioning in EDM """
80
+ c_skip, c_out, c_in, c_noise = self.c_skip(sigma), self.c_out(sigma), self.c_in(sigma), self.c_noise(sigma)
81
+ F = self.model_ema["diffusion"] if use_ema else self.model["diffusion"]
82
+ c_noise = at_least_ndim(c_noise.squeeze(), 1)
83
+ return c_skip * x + c_out * F(c_in * x, c_noise, condition)
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # Training
87
+
88
+ def loss(self, x0, condition=None):
89
+ sigma = self.sample_noise_distribution(x0.shape[0])
90
+ sigma = at_least_ndim(sigma, x0.dim())
91
+ eps = torch.randn_like(x0) * sigma * (1. - self.fix_mask)
92
+ condition = self.model["condition"](condition) if condition is not None else None
93
+ loss = (self.loss_weighting(sigma) * (self.D(x0 + eps, sigma, condition) - x0) ** 2)
94
+ return (loss * self.loss_weight).mean()
95
+
96
+ def update(self, x0, condition=None, **kwargs):
97
+ self.optimizer.zero_grad()
98
+ loss = self.loss(x0, condition)
99
+ loss.backward()
100
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
101
+ if self.grad_clip_norm else None
102
+ self.optimizer.step()
103
+ self.ema_update()
104
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
105
+ return log
106
+
107
+ def update_classifier(self, x0, condition):
108
+ sigma = self.sample_noise_distribution(x0.shape[0])
109
+ sigma = at_least_ndim(sigma, x0.dim())
110
+ noise = self.c_noise(sigma)
111
+ eps = torch.randn_like(x0) * sigma * (1. - self.fix_mask)
112
+ log = self.classifier.update(x0 + eps, at_least_ndim(noise.squeeze(), 1), condition)
113
+ return log
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # Inference
117
+
118
+ def dot_x(
119
+ self, x, i, use_ema=False,
120
+ # ----------------- CFG ----------------- #
121
+ condition_vec_cfg=None,
122
+ w_cfg: float = 0.0,
123
+ # ----------------- CG ----------------- #
124
+ condition_vec_cg=None,
125
+ w_cg: float = 1.0,
126
+ ):
127
+ b = x.shape[0]
128
+ sigma = at_least_ndim(self.sigma_s[i].repeat(b), x.dim())
129
+ noise = self.c_noise(sigma)
130
+ unscale = 1. / self.scale_s[i] * (1. - self.fix_mask) + self.fix_mask
131
+ # ----------------- CFG ----------------- #
132
+ with torch.no_grad():
133
+ if w_cfg != 0.0 and w_cfg != 1.0:
134
+ repeat_dim = [2 if i == 0 else 1 for i in range(x.dim())]
135
+ condition_vec_cfg = torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0)
136
+ D = self.D(
137
+ (x * unscale).repeat(*repeat_dim),
138
+ sigma.repeat(*repeat_dim),
139
+ condition_vec_cfg, use_ema)
140
+ D = w_cfg * D[:b] + (1. - w_cfg) * D[b:]
141
+ elif w_cfg == 0.0:
142
+ D = self.D(x * unscale, sigma, None, use_ema)
143
+ else:
144
+ D = self.D(x * unscale, sigma, condition_vec_cfg, use_ema)
145
+ # ----------------- CG ----------------- #
146
+ if self.classifier is not None and w_cg != 0.0 and condition_vec_cg is not None:
147
+ log_p, grad = self.classifier.gradients(x * unscale,
148
+ at_least_ndim(noise.squeeze(), 1), condition_vec_cg)
149
+ D = D + w_cg * self.scale_s[i] * (sigma ** 2) * grad
150
+ else:
151
+ log_p = None
152
+
153
+ # do not change the fixed portion
154
+ dot_x = self.x_weight_s[i] * x - self.D_weight_s[i] * D
155
+ dot_x = dot_x * (1. - self.fix_mask)
156
+
157
+ return dot_x, {"log_p": log_p}
158
+
159
+ def sample(
160
+ self,
161
+ # ---------- the known fixed portion ---------- #
162
+ prior: Optional[torch.Tensor] = None,
163
+ # ----------------- sampling ----------------- #
164
+ n_samples: int = 1,
165
+ sample_steps: int = 5,
166
+ use_ema: bool = True,
167
+ solver: str = "euler",
168
+ # ------------------ guidance ------------------ #
169
+ condition_cfg=None,
170
+ mask_cfg=None,
171
+ w_cfg: float = 0.0,
172
+ condition_cg=None,
173
+ w_cg: float = 0.0,
174
+
175
+ preserve_history: bool = False,
176
+ **kwargs
177
+ ):
178
+ """
179
+ Sample from the diffusion model.
180
+ ---
181
+ Input:
182
+ - prior: Optional[torch.Tensor] = None
183
+ The known fixed portion of the input data. Should be in the shape of `(n_samples, *x_shape)`.
184
+ Leave the unknown part as `0`. If `None`, which means `fix_mask` is `None`, the model takes no prior.
185
+
186
+ - sample_steps: int = 5
187
+ Number of sampling steps.
188
+
189
+ - use_ema: bool = True
190
+ Whether to use the EMA model. If `False`, you should `eval` the model.
191
+
192
+ - solver: str = "euler"
193
+ The solver to use. Can be either "euler" or "heun".
194
+
195
+ - condition_cfg: Optional[torch.Tensor] = None
196
+ The condition for the CFG. Should be in the shape of `(n_samples, *shape_of_nn_condition_input)`.
197
+ If `None`, the model takes no condition.
198
+
199
+ - mask_cfg: Optional[torch.Tensor] = None
200
+ The mask for the CFG. Should be in the shape of `(n_samples, *shape_of_nn_condition_mask)`.
201
+ Model will ignore the `mask_cfg==0` parts in `condition_cfg`. If `None`, the model takes no mask.
202
+
203
+ - w_cfg: float = 0.0
204
+ The weight for the CFG. If `0.0`, the model takes no CFG.
205
+
206
+ - condition_cg: Optional[torch.Tensor] = None
207
+ The condition for the CG. Should be in the shape of `(n_samples, 1)`.
208
+ If `None`, the model takes no condition.
209
+
210
+ - w_cg: float = 0.0
211
+ The weight for the CG. If `0.0`, the model takes no CG.
212
+
213
+ - preserve_history: bool = False
214
+ Whether to preserve the history of the sampling process. If `True`, the model will return the history.
215
+
216
+ Output:
217
+ - xt: torch.Tensor
218
+ The sampled data. Should be in the shape of `(n_samples, *x_shape)`.
219
+
220
+ - log: dict
221
+ The log of the sampling process. Contains the following keys:
222
+ - "sample_history": np.ndarray
223
+ The history of the sampling process. Should be in the shape of `(n_samples, N + 1, *x_shape)`.
224
+ If `preserve_history` is `False`, this key will not exist.
225
+ - "log_p": torch.Tensor
226
+ The log probability of the sampled data estimated by CG.
227
+ Should be in the shape of `(n_samples,)`.
228
+ """
229
+ if sample_steps != self.sample_steps:
230
+ self.set_sample_steps(sample_steps)
231
+
232
+ N = self.sample_steps
233
+ model = self.model_ema if use_ema else self.model
234
+ log, x_history = {}, None
235
+
236
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
237
+
238
+ if prior is None:
239
+ xt = torch.randn((n_samples, *self.default_x_shape),
240
+ device=self.device) * self.sigma_s[0] * self.scale_s[0]
241
+ else:
242
+ xt = torch.randn_like(prior, device=self.device) * self.sigma_s[0] * self.scale_s[0]
243
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
244
+
245
+ if preserve_history:
246
+ x_history = np.empty((n_samples, N + 1, *xt.shape))
247
+ x_history[:, 0] = xt.cpu().numpy()
248
+
249
+ for i in range(N):
250
+ # tmp_w_cg = w_cg if i < 1 else 0.0
251
+ delta_x, log = self.dot_x(xt, i, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
252
+ delta_t = self.t_s[i] - self.t_s[i + 1]
253
+ x_tp1 = xt - delta_x * delta_t
254
+ if prior is not None:
255
+ x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
256
+ if solver == "heun":
257
+ if i != N - 1 and self.sigma_s[i + 1] > 0.005:
258
+ delta_x_2, log = self.dot_x(x_tp1, i + 1, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
259
+ x_tp1 = xt - (delta_x + delta_x_2) / 2. * delta_t
260
+ if prior is not None:
261
+ x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
262
+
263
+ xt = x_tp1
264
+
265
+ if preserve_history:
266
+ x_history[:, i + 1] = xt.cpu().numpy()
267
+
268
+ log["sample_history"] = x_history
269
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
270
+ with torch.no_grad():
271
+ logp = self.classifier.logp(
272
+ xt, at_least_ndim(self.c_noise(self.sigma_s[-1]).squeeze(), 1), condition_cg)
273
+ log["log_p"] = logp
274
+
275
+ return xt, log
276
+
277
+ def sample_x(
278
+ self,
279
+ # ---------- the known fixed portion ---------- #
280
+ prior: Optional[torch.Tensor] = None,
281
+ # ----------------- sampling ----------------- #
282
+ n_samples: int = 1,
283
+ sample_steps: int = 5,
284
+ extra_sample_steps: int = 8,
285
+ use_ema: bool = True,
286
+ solver: str = "euler",
287
+ # ------------------ guidance ------------------ #
288
+ condition_cfg=None,
289
+ mask_cfg=None,
290
+ w_cfg: float = 0.0,
291
+ condition_cg=None,
292
+ w_cg: float = 0.0,
293
+
294
+ preserve_history: bool = False,
295
+ ):
296
+ if sample_steps != self.sample_steps:
297
+ self.set_sample_steps(sample_steps)
298
+
299
+ N = self.sample_steps
300
+ model = self.model_ema if use_ema else self.model
301
+ log, x_history = {}, None
302
+
303
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
304
+
305
+ if prior is None:
306
+ xt = torch.randn((n_samples, *self.default_x_shape),
307
+ device=self.device) * self.sigma_s[0] * self.scale_s[0]
308
+ else:
309
+ xt = torch.randn_like(prior, device=self.device) * self.sigma_s[0] * self.scale_s[0]
310
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
311
+
312
+ if preserve_history:
313
+ x_history = np.empty((n_samples, N + 1, *xt.shape))
314
+ x_history[:, 0] = xt.cpu().numpy()
315
+
316
+ for i in range(N):
317
+ # tmp_w_cg = w_cg if i < 1 else 0.0
318
+ delta_x, log = self.dot_x(xt, i, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
319
+ delta_t = self.t_s[i] - self.t_s[i + 1]
320
+ x_tp1 = xt - delta_x * delta_t
321
+ if prior is not None:
322
+ x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
323
+ if solver == "heun":
324
+ if i != N - 1 and self.sigma_s[i + 1] > 0.005:
325
+ delta_x_2, log = self.dot_x(x_tp1, i + 1, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
326
+ x_tp1 = xt - (delta_x + delta_x_2) / 2. * delta_t
327
+ if prior is not None:
328
+ x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
329
+
330
+ xt = x_tp1
331
+
332
+ if preserve_history:
333
+ x_history[:, i + 1] = xt.cpu().numpy()
334
+
335
+ if extra_sample_steps > 0:
336
+
337
+ delta_t = self.t_s[N - 1] - self.t_s[N]
338
+
339
+ for _ in range(extra_sample_steps):
340
+
341
+ delta_x, log = self.dot_x(xt, N - 1, use_ema, condition_vec_cfg, w_cfg, condition_cg, w_cg)
342
+ x_tp1 = xt - delta_x * delta_t
343
+ if prior is not None:
344
+ x_tp1 = x_tp1 * (1. - self.fix_mask) + prior * self.fix_mask
345
+
346
+ xt = x_tp1
347
+
348
+ log["sample_history"] = x_history
349
+ if log["log_p"] is None and self.classifier is not None and condition_cg is not None:
350
+ with torch.no_grad():
351
+ logp = self.classifier.logp(
352
+ xt, at_least_ndim(self.c_noise(self.sigma_s[-1]).squeeze(), 1), condition_cg)
353
+ log["log_p"] = logp
354
+
355
+ return xt, log
356
+
357
+
358
+ class EDM(EDMArchetecture):
359
+ def __init__(
360
+ self,
361
+
362
+ # ----------------- Neural Networks ----------------- #
363
+ nn_diffusion: BaseNNDiffusion,
364
+ nn_condition: Optional[BaseNNCondition] = None,
365
+
366
+ # ----------------- Masks ----------------- #
367
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
368
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
369
+ # Add loss weight
370
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
371
+
372
+ # ------------------ Plugs ---------------- #
373
+ # Add a classifier to enable classifier-guidance
374
+ classifier: Optional[BaseClassifier] = None,
375
+
376
+ # ------------------ Params ---------------- #
377
+ grad_clip_norm: Optional[float] = None,
378
+ diffusion_steps: int = 1000,
379
+ ema_rate: float = 0.995,
380
+ optim_params: Optional[dict] = None,
381
+
382
+ # ------------------- EDM Params ------------------- #
383
+ sigma_data: float = 0.5,
384
+ sigma_min: float = 0.002,
385
+ sigma_max: float = 80.,
386
+ rho: float = 7.,
387
+ P_mean: float = -1.2,
388
+ P_std: float = 1.2,
389
+
390
+ device: Union[torch.device, str] = "cpu"
391
+ ):
392
+ super().__init__(
393
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
394
+ diffusion_steps, ema_rate, optim_params, device)
395
+
396
+ self.sigma_data = sigma_data
397
+ self.sigma_min, self.sigma_max, self.rho = sigma_min, sigma_max, rho
398
+ self.P_mean, self.P_std = P_mean, P_std
399
+
400
+ def set_sample_steps(self, N: int):
401
+ self.sample_steps = N
402
+ self.sigma_s = (self.sigma_max ** (1 / self.rho) + torch.arange(N + 1, device=self.device) / N *
403
+ (self.sigma_min ** (1 / self.rho) - self.sigma_max ** (1 / self.rho))) ** self.rho
404
+ self.t_s = self.sigma_s
405
+ self.scale_s = torch.ones_like(self.sigma_s)
406
+ self.dot_sigma_s = torch.ones_like(self.sigma_s)
407
+ self.dot_scale_s = torch.zeros_like(self.sigma_s)
408
+ self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
409
+ self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
410
+
411
+ def c_skip(self, sigma): return self.sigma_data ** 2 / (self.sigma_data ** 2 + sigma ** 2)
412
+
413
+ def c_out(self, sigma): return sigma * self.sigma_data / (self.sigma_data ** 2 + sigma ** 2).sqrt()
414
+
415
+ def c_in(self, sigma): return 1 / (self.sigma_data ** 2 + sigma ** 2).sqrt()
416
+
417
+ def c_noise(self, sigma): return 0.25 * sigma.log()
418
+
419
+ def loss_weighting(self, sigma): return (self.sigma_data ** 2 + sigma ** 2) / ((sigma * self.sigma_data) ** 2)
420
+
421
+ def sample_noise_distribution(self, N):
422
+ log_sigma = torch.randn(N, device=self.device) * self.P_std + self.P_mean
423
+ return log_sigma.exp()
424
+
425
+ def sample_scale_distribution(self, N):
426
+ return torch.ones(N, device=self.device)
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/edmddim.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Union, Optional
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+ from cleandiffuser.classifier import BaseClassifier
7
+ from cleandiffuser.nn_condition import BaseNNCondition
8
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
9
+ from .edm import EDMArchetecture
10
+
11
+
12
+ class EDMDDIM(EDMArchetecture):
13
+ def __init__(
14
+ self,
15
+
16
+ # ----------------- Neural Networks ----------------- #
17
+ nn_diffusion: BaseNNDiffusion,
18
+ nn_condition: Optional[BaseNNCondition] = None,
19
+
20
+ # ----------------- Masks ----------------- #
21
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
22
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
23
+ # Add loss weight
24
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
25
+
26
+ # ------------------ Plugs ---------------- #
27
+ # Add a classifier to enable classifier-guidance
28
+ classifier: Optional[BaseClassifier] = None,
29
+
30
+ # ------------------ Params ---------------- #
31
+ grad_clip_norm: Optional[float] = None,
32
+ diffusion_steps: int = 1000,
33
+ ema_rate: float = 0.995,
34
+ optim_params: Optional[dict] = None,
35
+
36
+ # ------------------- EDM Params ------------------- #
37
+ C1: float = 0.001,
38
+ C2: float = 0.008,
39
+ j0: float = 8,
40
+
41
+ device: Union[torch.device, str] = "cpu"
42
+ ):
43
+ super().__init__(
44
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
45
+ diffusion_steps, ema_rate, optim_params, device)
46
+
47
+ self.C1, self.C2, self.j0 = C1, C2, j0
48
+ self.u = None
49
+
50
+ def set_sample_steps(self, N: int):
51
+ self.sample_steps = N
52
+ bar_alpha = torch.sin(
53
+ torch.arange(self.diffusion_steps + 1, device=self.device) /
54
+ (self.diffusion_steps * (self.C2 + 1)) * np.pi / 2) ** 2
55
+ tmp = torch.max(bar_alpha[:-1] / bar_alpha[1:], torch.tensor(self.C1, device=self.device))
56
+ self.u = torch.empty_like(bar_alpha[:-1])
57
+ for i in range(self.diffusion_steps):
58
+ if i == 0:
59
+ self.u[-1 - i] = (1 / tmp[-1 - i] - 1).sqrt()
60
+ else:
61
+ self.u[-1 - i] = ((self.u[-i] ** 2 + 1) / tmp[-1 - i] - 1).sqrt()
62
+ idx = torch.arange(N, device=self.device)
63
+ self.t_s = self.u[torch.floor(self.j0 + (self.diffusion_steps - 1 - self.j0) /
64
+ (N - 1) * idx + 0.5).long()]
65
+ self.sigma_s = self.t_s
66
+ self.scale_s = torch.ones_like(self.sigma_s) * 1.0
67
+ self.dot_sigma_s = torch.ones_like(self.sigma_s) * 1.0
68
+ self.dot_scale_s = torch.zeros_like(self.sigma_s)
69
+ self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
70
+ self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
71
+
72
+ def c_skip(self, sigma): return torch.ones_like(sigma)
73
+
74
+ def c_out(self, sigma): return -sigma
75
+
76
+ def c_in(self, sigma): return 1 / (1 + sigma ** 2).sqrt()
77
+
78
+ def c_noise(self, sigma): return sigma
79
+
80
+ def loss_weighting(self, sigma): return 1 / (sigma ** 2)
81
+
82
+ def sample_noise_distribution(self, N):
83
+ j = torch.randint(0, self.diffusion_steps, (N, 1), device=self.device)
84
+ return self.u[j]
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/koopman_net.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import Any, Dict, Optional, Tuple
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+
8
+ # =========================
9
+ # Utilities
10
+ # =========================
11
+
12
+
13
+ def load_koopman_checkpoint(path: str, map_location: Any = "cpu") -> Tuple[Dict[str, Any], Dict[str, Any]]:
14
+ """
15
+ Loads a Koopman student checkpoint. Supports legacy flat state_dict or
16
+ wrapped dicts: {"state_dict": ..., "gamma_mode": "z"|"z0"|"obs0", ...}.
17
+ """
18
+ obj = torch.load(path, map_location=map_location)
19
+ if isinstance(obj, dict) and "state_dict" in obj:
20
+ meta = {k: v for k, v in obj.items() if k != "state_dict"}
21
+ return obj["state_dict"], meta
22
+ return obj, {}
23
+
24
+
25
+ def orthogonal_init_(tensor, scale=1.0):
26
+ with torch.no_grad():
27
+ nn.init.orthogonal_(tensor)
28
+ tensor.mul_(scale)
29
+
30
+ def build_encoder(input_dim: int, latent_dim: int) -> nn.Module:
31
+ return nn.Sequential(
32
+ nn.Conv1d(input_dim, 128, kernel_size=5, padding=2),
33
+ nn.ReLU(),
34
+ nn.Conv1d(128, 256, kernel_size=5, padding=2),
35
+ nn.ReLU(),
36
+ nn.AdaptiveAvgPool1d(1),
37
+ nn.Flatten(),
38
+ nn.Linear(256, latent_dim),
39
+ )
40
+
41
+ def build_decoder(latent_dim: int, horizon: int, input_dim: int) -> nn.Module:
42
+ return nn.Sequential(
43
+ nn.Linear(latent_dim, 1024),
44
+ nn.ReLU(),
45
+ nn.Linear(1024, horizon * input_dim),
46
+ )
47
+
48
+ class BaseDistiller(nn.Module):
49
+ def get_spectral_penalty(self) -> torch.Tensor:
50
+ p = next(self.parameters())
51
+ return torch.tensor(0.0, device=p.device)
52
+
53
+ def get_pinv_loss(self) -> torch.Tensor:
54
+ p = next(self.parameters())
55
+ return torch.tensor(0.0, device=p.device)
56
+
57
+ # =========================
58
+ # KDM: static linear
59
+ # =========================
60
+ class KDMKoopmanDistiller(BaseDistiller):
61
+ def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1,
62
+ detach_clean=False, shared_encoder=False, noisy_latent=0.2):
63
+ super().__init__()
64
+ self.horizon = horizon
65
+ self.input_dim = input_dim
66
+ self.latent_dim = latent_dim
67
+ self.num_iters = num_iters
68
+ self.detach_clean = detach_clean
69
+ self.shared_encoder = shared_encoder
70
+ self.noisy_latent = noisy_latent
71
+
72
+ if shared_encoder:
73
+ self.encoder = build_encoder(input_dim, latent_dim)
74
+ else:
75
+ self.encoder_clean = build_encoder(input_dim, latent_dim)
76
+ self.encoder_noise = build_encoder(input_dim, latent_dim)
77
+
78
+ self.koopman = nn.Linear(latent_dim, latent_dim, bias=False)
79
+ orthogonal_init_(self.koopman.weight, scale=0.9)
80
+
81
+ self.decoder = build_decoder(latent_dim, horizon, input_dim)
82
+
83
+ def encode_clean(self, x):
84
+ return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_clean(x.permute(0, 2, 1))
85
+
86
+ def encode_noise(self, x):
87
+ return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_noise(x.permute(0, 2, 1))
88
+
89
+ def decode(self, z):
90
+ return self.decoder(z).view(-1, self.horizon, self.input_dim)
91
+
92
+ def forward(self, x_noise, x_clean=None):
93
+ z_noise = self.encode_noise(x_noise)
94
+ if self.training and self.noisy_latent > 0:
95
+ z_noise = z_noise + torch.randn_like(z_noise) * self.noisy_latent
96
+
97
+ z_pred = z_noise
98
+ for _ in range(self.num_iters):
99
+ z_pred = self.koopman(z_pred)
100
+
101
+ if self.training:
102
+ assert x_clean is not None
103
+ z_clean = self.encode_clean(x_clean)
104
+ z_clean_noisy = z_clean + torch.randn_like(z_clean) * self.noisy_latent if self.noisy_latent > 0 else z_clean
105
+ z_clean_target = z_clean.detach() if self.detach_clean else z_clean
106
+
107
+ x_recon = self.decode(z_clean_noisy)
108
+ x_pred = self.decode(z_pred)
109
+ return z_clean_target, z_pred, x_recon, x_pred
110
+ else:
111
+ return self.decode(z_pred)
112
+
113
+
114
+ # =========================
115
+ # KDM-F: static factorized (Aligned with NeurIPS 2025 FastKoopmanMatrix)
116
+ # =========================
117
+ class StaticFactorizedKoopman(nn.Module):
118
+ """
119
+ KDM-F 风格:严格对齐 NeurIPS 2025 的 FastKoopmanMatrix
120
+ 1. 采用 QR 分解进行正交初始化
121
+ 2. 采用双重指数 exp(-exp(nu)) 严格约束特征值模长在 (0, 1) 内
122
+ """
123
+ def __init__(self, latent_dim: int):
124
+ super().__init__()
125
+ self.d = latent_dim
126
+ scale = 1.0 / math.sqrt(2 * self.d)
127
+
128
+ # 1. QR 分解正交初始化 P_re 和 P_im
129
+ P_re_init = self.random_orthonormal_matrix(self.d) * scale
130
+ P_im_init = self.random_orthonormal_matrix(self.d) * scale
131
+ self.P_re = nn.Parameter(P_re_init)
132
+ self.P_im = nn.Parameter(P_im_init)
133
+
134
+ # P_inv 原版使用标准高斯初始化,依赖 P_inv Loss 在训练中对齐
135
+ self.P_inv_re = nn.Parameter(torch.randn(self.d, self.d) * scale)
136
+ self.P_inv_im = nn.Parameter(torch.randn(self.d, self.d) * scale)
137
+
138
+ # 2. 严格收缩的特征值参数化 (防止动力学发散)
139
+ rmin, rmax = 0.0, 1.0
140
+ max_phase = 6.283 # 2 * pi
141
+
142
+ u1 = torch.rand(self.d)
143
+ u2 = torch.rand(self.d)
144
+
145
+ # nu_log 映射,使得 lambda_mod = exp(-exp(nu_log))
146
+ init_nu = torch.log(-0.5 * torch.log(u1 * (rmax + rmin) * (rmax - rmin) + rmin**2))
147
+ self.nu_log = nn.Parameter(init_nu)
148
+
149
+ # theta_log 映射,使得 phase = exp(theta_log)
150
+ self.theta_log = nn.Parameter(torch.log(max_phase * u2))
151
+
152
+ def random_orthonormal_matrix(self, size):
153
+ """生成随机正交矩阵用于初始化"""
154
+ q, _ = torch.linalg.qr(torch.randn(size, size))
155
+ return q
156
+
157
+ def normalize_rows(self, x, eps=1e-8):
158
+ """行归一化,原版前向传播时使用"""
159
+ return x / (x.norm(dim=-1, keepdim=True) + eps)
160
+
161
+ def to_real_block_matrix(self, re, im):
162
+ """将复数矩阵重组为实数块对角矩阵"""
163
+ top = torch.cat([re, -im], dim=1)
164
+ bottom = torch.cat([im, re], dim=1)
165
+ return torch.cat([top, bottom], dim=0)
166
+
167
+ def forward(self, z):
168
+ # 归一化特征向量
169
+ P_re_norm = self.normalize_rows(self.P_re)
170
+ P_im_norm = self.normalize_rows(self.P_im)
171
+
172
+ P = self.to_real_block_matrix(P_re_norm, P_im_norm)
173
+ P_inv = self.to_real_block_matrix(self.P_inv_re, self.P_inv_im)
174
+
175
+ # 还原严格有界的特征值
176
+ lambda_mod = torch.exp(-torch.exp(self.nu_log))
177
+ phase = torch.exp(self.theta_log)
178
+ lambda_re = lambda_mod * torch.cos(phase)
179
+ lambda_im = lambda_mod * torch.sin(phase)
180
+
181
+ Lambda = self.to_real_block_matrix(torch.diag(lambda_re), torch.diag(lambda_im))
182
+
183
+ # 状态增广并演化
184
+ z_aug = torch.cat([z, torch.zeros_like(z)], dim=-1)
185
+ z_pushed = z_aug @ P_inv.T @ Lambda.T @ P.T
186
+
187
+ # 截断回原维度
188
+ return z_pushed[..., :self.d]
189
+
190
+ def get_spectral_penalty(self):
191
+ # 对应原版 EigenSpecKoopmanLossTypes.OnTheCircle
192
+ # 因为 lambda_mod 已经被双重指数限制在 (0, 1) 之间
193
+ # 谱惩罚的作用是将它们推向单位圆边缘,防止过度收缩导致特征消失
194
+ lambda_mod = torch.exp(-torch.exp(self.nu_log))
195
+ return torch.abs(lambda_mod - 1.0).mean()
196
+
197
+ def get_pinv_loss(self):
198
+ # 逆矩阵对齐损失
199
+ P_re_norm = self.normalize_rows(self.P_re)
200
+ P_im_norm = self.normalize_rows(self.P_im)
201
+ P = self.to_real_block_matrix(P_re_norm, P_im_norm)
202
+ P_inv = self.to_real_block_matrix(self.P_inv_re, self.P_inv_im)
203
+
204
+ I = torch.eye(2 * self.d, device=P.device)
205
+ return F.mse_loss(P_inv @ P, I) + F.mse_loss(P @ P_inv, I)
206
+
207
+ class KDMFactorizedDistiller(BaseDistiller):
208
+ def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1,
209
+ spectral_penalty_coef=0.01, detach_clean=False,
210
+ shared_encoder=False, noisy_latent=0.2):
211
+ super().__init__()
212
+ self.horizon = horizon
213
+ self.input_dim = input_dim
214
+ self.latent_dim = latent_dim
215
+ self.num_iters = num_iters
216
+ self.detach_clean = detach_clean
217
+ self.spectral_penalty_coef = spectral_penalty_coef
218
+ self.shared_encoder = shared_encoder
219
+ self.noisy_latent = noisy_latent
220
+
221
+ if shared_encoder:
222
+ self.encoder = build_encoder(input_dim, latent_dim)
223
+ else:
224
+ self.encoder_clean = build_encoder(input_dim, latent_dim)
225
+ self.encoder_noise = build_encoder(input_dim, latent_dim)
226
+
227
+ self.koopman = StaticFactorizedKoopman(latent_dim)
228
+ self.decoder = build_decoder(latent_dim, horizon, input_dim)
229
+
230
+ def encode_clean(self, x):
231
+ return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_clean(x.permute(0, 2, 1))
232
+
233
+ def encode_noise(self, x):
234
+ return self.encoder(x.permute(0, 2, 1)) if self.shared_encoder else self.encoder_noise(x.permute(0, 2, 1))
235
+
236
+ def decode(self, z):
237
+ return self.decoder(z).view(-1, self.horizon, self.input_dim)
238
+
239
+ def forward(self, x_noise, x_clean=None):
240
+ z_noise = self.encode_noise(x_noise)
241
+ if self.training and self.noisy_latent > 0:
242
+ z_noise = z_noise + torch.randn_like(z_noise) * self.noisy_latent
243
+
244
+ z_pred = z_noise
245
+ for _ in range(self.num_iters):
246
+ z_pred = self.koopman(z_pred)
247
+
248
+ if self.training:
249
+ assert x_clean is not None
250
+ z_clean = self.encode_clean(x_clean)
251
+ z_clean_noisy = z_clean + torch.randn_like(z_clean) * self.noisy_latent if self.noisy_latent > 0 else z_clean
252
+ z_clean_target = z_clean.detach() if self.detach_clean else z_clean
253
+
254
+ x_recon = self.decode(z_clean_noisy)
255
+ x_pred = self.decode(z_pred)
256
+ return z_clean_target, z_pred, x_recon, x_pred
257
+ else:
258
+ return self.decode(z_pred)
259
+
260
+ def get_spectral_penalty(self):
261
+ return self.koopman.get_spectral_penalty() * self.spectral_penalty_coef
262
+
263
+ def get_pinv_loss(self):
264
+ return self.koopman.get_pinv_loss()
265
+
266
+ # =========================
267
+ # Regression baseline
268
+ # =========================
269
+ class ResidualMLPEvolver(nn.Module):
270
+ def __init__(self, latent_dim: int, hidden_dim: int = 1024, depth: int = 2):
271
+ super().__init__()
272
+ layers = []
273
+ d_in = latent_dim
274
+ for i in range(depth):
275
+ d_out = hidden_dim if i < depth - 1 else latent_dim
276
+ layers.append(nn.Linear(d_in, d_out))
277
+ if i < depth - 1:
278
+ layers.append(nn.ReLU())
279
+ d_in = d_out
280
+ self.net = nn.Sequential(*layers)
281
+
282
+ def forward(self, z):
283
+ return z + self.net(z)
284
+
285
+ class RegressionDistiller(BaseDistiller):
286
+ def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1, detach_clean=False):
287
+ super().__init__()
288
+ self.horizon = horizon
289
+ self.input_dim = input_dim
290
+ self.latent_dim = latent_dim
291
+ self.num_iters = num_iters
292
+ self.detach_clean = detach_clean
293
+
294
+ self.encoder = build_encoder(input_dim, latent_dim)
295
+ self.decoder = build_decoder(latent_dim, horizon, input_dim)
296
+ self.evolver = ResidualMLPEvolver(latent_dim, hidden_dim=1024, depth=2)
297
+
298
+ def forward(self, x_noise, x_clean=None):
299
+ z_noise = self.encoder(x_noise.permute(0, 2, 1))
300
+
301
+ z_pred = z_noise
302
+ for _ in range(self.num_iters):
303
+ z_pred = self.evolver(z_pred)
304
+
305
+ if self.training:
306
+ assert x_clean is not None
307
+ z_clean = self.encoder(x_clean.permute(0, 2, 1))
308
+ z_clean_target = z_clean.detach() if self.detach_clean else z_clean
309
+
310
+ x_recon = self.decoder(z_clean).view(-1, self.horizon, self.input_dim)
311
+ x_pred = self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
312
+ return z_clean_target, z_pred, x_recon, x_pred
313
+ else:
314
+ return self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
315
+
316
+ # =========================
317
+ # Ours: dynamic factorized Koopman
318
+ # =========================
319
+ class FactorizedDynamicKoopman(nn.Module):
320
+ """
321
+ Modal gains gamma: eigen_net input is either current latent z (default),
322
+ fixed initial latent z0 (gamma_mode='z0'), or first-timestep obs slice (gamma_mode='obs0').
323
+ """
324
+
325
+ def __init__(self, latent_dim: int, gamma_mode: str = "z", obs_dim: Optional[int] = None):
326
+ super().__init__()
327
+ self.latent_dim = latent_dim
328
+ self.gamma_mode = gamma_mode
329
+ if gamma_mode not in ("z", "z0", "obs0"):
330
+ raise ValueError(f"gamma_mode must be 'z', 'z0', or 'obs0', got {gamma_mode!r}")
331
+ if gamma_mode == "obs0" and obs_dim is None:
332
+ raise ValueError("obs0 gamma_mode requires obs_dim")
333
+
334
+ self.obs_dim = obs_dim
335
+ cond_dim = obs_dim if gamma_mode == "obs0" else latent_dim
336
+
337
+ self.P = nn.Linear(latent_dim, latent_dim, bias=False)
338
+ self.P_inv = nn.Linear(latent_dim, latent_dim, bias=False)
339
+
340
+ nn.init.orthogonal_(self.P.weight)
341
+ self.P_inv.weight.data = self.P.weight.data.T.clone()
342
+
343
+ self.eigen_net = nn.Sequential(
344
+ nn.Linear(cond_dim, 128),
345
+ nn.ReLU(),
346
+ nn.Linear(128, latent_dim)
347
+ )
348
+
349
+ def forward(self, z: torch.Tensor, c_context: Optional[torch.Tensor] = None):
350
+ if self.gamma_mode == "z":
351
+ gamma_in = z
352
+ else:
353
+ if c_context is None:
354
+ raise ValueError(f"gamma_mode={self.gamma_mode!r} requires c_context (z0 or obs0).")
355
+ gamma_in = c_context
356
+ eigen_values = self.eigen_net(gamma_in)
357
+ z_projected = self.P_inv(z)
358
+ z_evolved = z_projected * eigen_values
359
+ z_next = self.P(z_evolved)
360
+ return z_next, eigen_values
361
+
362
+ def get_pinv_loss(self):
363
+ I = torch.eye(self.latent_dim, device=self.P.weight.device)
364
+ return F.mse_loss(self.P.weight @ self.P_inv.weight, I)
365
+
366
+ class KoopmanDistiller(BaseDistiller):
367
+ def __init__(self, input_dim, horizon, latent_dim=512, num_iters=1,
368
+ spectral_penalty_coef=0.01, detach_clean=True,
369
+ gamma_mode: str = "z", obs_dim: Optional[int] = None):
370
+ super().__init__()
371
+ self.horizon = horizon
372
+ self.input_dim = input_dim
373
+ self.latent_dim = latent_dim
374
+ self.num_iters = num_iters
375
+ self.spectral_penalty_coef = spectral_penalty_coef
376
+ self.detach_clean = detach_clean
377
+ self.gamma_mode = gamma_mode
378
+ if gamma_mode == "obs0" and obs_dim is None:
379
+ raise ValueError("KoopmanDistiller: obs0 gamma_mode requires obs_dim")
380
+ self.obs_dim = obs_dim
381
+
382
+ self.encoder = build_encoder(input_dim, latent_dim)
383
+ self.decoder = build_decoder(latent_dim, horizon, input_dim)
384
+ self.koopman = FactorizedDynamicKoopman(latent_dim, gamma_mode=gamma_mode, obs_dim=obs_dim)
385
+
386
+ def gamma_context(self, x_noise: torch.Tensor) -> Optional[torch.Tensor]:
387
+ """Conditioning vector for eigen_net when gamma_mode is z0 or obs0; else None."""
388
+ if self.gamma_mode == "z":
389
+ return None
390
+ if self.gamma_mode == "z0":
391
+ return self.encoder(x_noise.permute(0, 2, 1))
392
+ return x_noise[:, 0, : self.obs_dim]
393
+
394
+ def forward(self, x_noise, x_clean=None):
395
+ z_noise = self.encoder(x_noise.permute(0, 2, 1))
396
+ c_ctx = self.gamma_context(x_noise)
397
+
398
+ all_eigen_values = []
399
+ z_pred = z_noise
400
+ for _ in range(self.num_iters):
401
+ z_pred, eigen_vals = self.koopman(z_pred, c_ctx)
402
+ all_eigen_values.append(eigen_vals)
403
+
404
+ self.current_eigen_values = torch.cat(all_eigen_values, dim=0)
405
+
406
+ if self.training:
407
+ assert x_clean is not None
408
+ z_clean = self.encoder(x_clean.permute(0, 2, 1))
409
+ z_clean_target = z_clean.detach() if self.detach_clean else z_clean
410
+
411
+ x_recon = self.decoder(z_clean).view(-1, self.horizon, self.input_dim)
412
+ x_pred = self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
413
+ return z_clean_target, z_pred, x_recon, x_pred
414
+ else:
415
+ return self.decoder(z_pred).view(-1, self.horizon, self.input_dim)
416
+
417
+ def get_spectral_penalty(self):
418
+ if not hasattr(self, "current_eigen_values"):
419
+ p = next(self.parameters())
420
+ return torch.tensor(0.0, device=p.device)
421
+
422
+ radii = torch.abs(self.current_eigen_values)
423
+ penalty = F.relu(radii - 1.0).mean()
424
+ return penalty * self.spectral_penalty_coef
425
+
426
+ def get_pinv_loss(self):
427
+ return self.koopman.get_pinv_loss()
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/newedm.py ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from cleandiffuser.utils import (
11
+ at_least_ndim)
12
+ from .basic import DiffusionModel
13
+
14
+
15
+ class ContinuousEDM(DiffusionModel):
16
+ """Continuous-time EDM
17
+
18
+ EDM posits that the concepts of `t` in the diffusion process and the noise schedule are equivalent.
19
+ Previous noise schedules can be interpreted as perturbing the data with Gaussian noise
20
+ followed by scaling. EDM sets the standard deviation of noise as `t`, the scale as 1,
21
+ and devises a series of preconditioning steps to aid in model learning.
22
+
23
+ The current implementation of EDM only supports continuous-time ODEs.
24
+ The sampling steps are required to be greater than 1.
25
+
26
+ Args:
27
+ - nn_diffusion: BaseNNDiffusion
28
+ The neural network backbone for the Diffusion model.
29
+ - nn_condition: Optional[BaseNNCondition]
30
+ The neural network backbone for the condition embedding.
31
+
32
+ - fix_mask: Union[list, np.ndarray, torch.Tensor]
33
+ Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
34
+ The mask should be in the shape of `x_shape`.
35
+ - loss_weight: Union[list, np.ndarray, torch.Tensor]
36
+ Add loss weight. The weight should be in the shape of `x_shape`.
37
+
38
+ - classifier: Optional[BaseClassifier]
39
+ Add a classifier to enable classifier-guidance.
40
+
41
+ - grad_clip_norm: Optional[float]
42
+ Gradient clipping norm.
43
+ - ema_rate: float
44
+ Exponential moving average rate.
45
+ - optim_params: Optional[dict]
46
+ Optimizer parameters.
47
+
48
+ - sigma_data: float
49
+ The standard deviation of the data. Default: 0.5.
50
+ - sigma_min: float
51
+ The minimum standard deviation of the noise. Default: 0.002.
52
+ - sigma_max: float
53
+ The maximum standard deviation of the noise. Default: 80.
54
+ - rho: float
55
+ The power of the noise schedule. Default: 7.
56
+ - P_mean: float
57
+ Hyperparameter for noise sampling during training. Default: -1.2.
58
+ - P_std: float
59
+ Hyperparameter for noise sampling during training. Default: 1.2.
60
+
61
+ - x_max: Optional[torch.Tensor]
62
+ The maximum value for the input data. `None` indicates no constraint.
63
+ - x_min: Optional[torch.Tensor]
64
+ The minimum value for the input data. `None` indicates no constraint.
65
+
66
+ - device: Union[torch.device, str]
67
+ The device to run the model.
68
+ """
69
+ def __init__(
70
+ self,
71
+
72
+ # ----------------- Neural Networks ----------------- #
73
+ nn_diffusion: BaseNNDiffusion,
74
+ nn_condition: Optional[BaseNNCondition] = None,
75
+
76
+ # ----------------- Masks ----------------- #
77
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
78
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
79
+ # Add loss weight
80
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
81
+
82
+ # ------------------ Plugins ---------------- #
83
+ # Add a classifier to enable classifier-guidance
84
+ classifier: Optional[BaseClassifier] = None,
85
+
86
+ # ------------------ Training Params ---------------- #
87
+ grad_clip_norm: Optional[float] = None,
88
+ ema_rate: float = 0.995,
89
+ optim_params: Optional[dict] = None,
90
+
91
+ # ------------------- Diffusion Params ------------------- #
92
+ sigma_data: float = 0.5,
93
+ sigma_min: float = 0.002,
94
+ sigma_max: float = 80.,
95
+ rho: float = 7.,
96
+ P_mean: float = -1.2,
97
+ P_std: float = 1.2,
98
+
99
+ x_max: Optional[torch.Tensor] = None,
100
+ x_min: Optional[torch.Tensor] = None,
101
+
102
+ device: Union[torch.device, str] = "cpu"
103
+ ):
104
+ super().__init__(
105
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
106
+ 0, ema_rate, optim_params, device)
107
+
108
+ self.sigma_data, self.sigma_min, self.sigma_max = sigma_data, sigma_min, sigma_max
109
+ self.rho, self.P_mean, self.P_std = rho, P_mean, P_std
110
+
111
+ self.x_max = x_max.to(device) if isinstance(x_max, torch.Tensor) else x_max
112
+ self.x_min = x_min.to(device) if isinstance(x_min, torch.Tensor) else x_min
113
+
114
+ # ==================== Continuous Time-step Range ====================
115
+ self.t_diffusion = [sigma_min, sigma_max]
116
+
117
+ # ======================= Noise Schedule =========================
118
+ # scale(t) = 1., dot_scale(t) = 0.
119
+ # sigma(t) = t , dot_sigma(t) = 1.
120
+
121
+ @property
122
+ def supported_solvers(self):
123
+ return ["euler", "heun"]
124
+
125
+ @property
126
+ def clip_pred(self):
127
+ return (self.x_max is not None) or (self.x_min is not None)
128
+
129
+ # ===================== EDM Pre-conditioning =========================
130
+ def c_skip(self, sigma):
131
+ return self.sigma_data ** 2 / (self.sigma_data ** 2 + sigma ** 2)
132
+
133
+ def c_out(self, sigma):
134
+ return sigma * self.sigma_data / (self.sigma_data ** 2 + sigma ** 2).sqrt()
135
+
136
+ def c_in(self, sigma):
137
+ return 1 / (self.sigma_data ** 2 + sigma ** 2).sqrt()
138
+
139
+ def c_noise(self, sigma):
140
+ return 0.25 * sigma.log()
141
+
142
+ def D(self, x, sigma, condition=None, model=None):
143
+ """ Prepositioning in EDM """
144
+ c_skip, c_out, c_in, c_noise = self.c_skip(sigma), self.c_out(sigma), self.c_in(sigma), self.c_noise(sigma)
145
+ if model is None:
146
+ model = self.model
147
+ c_skip, c_in, c_out = at_least_ndim(c_skip, x.dim()), at_least_ndim(c_in, x.dim()), at_least_ndim(c_out, x.dim())
148
+ return c_skip * x + c_out * model["diffusion"](c_in * x, c_noise, condition)
149
+
150
+ # ==================== Training: Score Matching ======================
151
+
152
+ def add_noise(self, x0, t=None, eps=None):
153
+
154
+ t = (torch.randn((x0.shape[0], ), device=self.device) * self.P_std + self.P_mean).exp() if t is None else t
155
+
156
+ eps = torch.randn_like(x0) if eps is None else eps
157
+
158
+ scale = 1.
159
+ sigma = at_least_ndim(t, x0.dim())
160
+
161
+ xt = scale * x0 + sigma * eps
162
+ xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
163
+
164
+ return xt, t, eps
165
+
166
+ def loss(self, x0, condition=None):
167
+
168
+ xt, t, eps = self.add_noise(x0)
169
+
170
+ condition = self.model["condition"](condition) if condition is not None else None
171
+
172
+ loss = (self.D(xt, t, condition) - x0) ** 2
173
+
174
+ edm_loss_weight = at_least_ndim((t ** 2 + self.sigma_data ** 2) / ((t * self.sigma_data) ** 2), x0.dim())
175
+
176
+ return (loss * self.loss_weight * (1 - self.fix_mask) * edm_loss_weight).mean()
177
+
178
+ def update(self, x0, condition=None, update_ema=True, **kwargs):
179
+ """One-step gradient update.
180
+ Inputs:
181
+ - x0: torch.Tensor
182
+ Samples from the target distribution.
183
+ - condition: Optional
184
+ Condition of x0. `None` indicates no condition.
185
+ - update_ema: bool
186
+ Whether to update the exponential moving average model.
187
+
188
+ Outputs:
189
+ - log: dict
190
+ The log dictionary.
191
+ """
192
+ loss = self.loss(x0, condition)
193
+
194
+ loss.backward()
195
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
196
+ if self.grad_clip_norm else None
197
+ self.optimizer.step()
198
+ self.optimizer.zero_grad()
199
+
200
+ if update_ema:
201
+ self.ema_update()
202
+
203
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
204
+
205
+ return log
206
+
207
+ def update_classifier(self, x0, condition):
208
+
209
+ xt, t, eps = self.add_noise(x0)
210
+
211
+ log = self.classifier.update(xt, t.log() / 4., condition)
212
+
213
+ return log
214
+
215
+ # ==================== Sampling: Solving SDE/ODE ======================
216
+
217
+ def classifier_guidance(
218
+ self, xt, t, sigma,
219
+ model, condition=None, w: float = 1.0,
220
+ pred=None):
221
+ """
222
+ Guided Sampling CG:
223
+ bar_eps = eps - w * sigma * grad
224
+ bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
225
+ """
226
+ if pred is None:
227
+ pred = self.D(xt, t, None, model)
228
+ if self.classifier is None or w == 0.0 or condition is None:
229
+ return pred, None
230
+ else:
231
+ log_p, grad = self.classifier.gradients(
232
+ xt.clone(), t.log() / 4., condition)
233
+ pred = pred + w * (at_least_ndim(sigma, pred.dim()) ** 2) * grad
234
+
235
+ return pred, log_p
236
+
237
+ def classifier_free_guidance(
238
+ self, xt, t,
239
+ model, condition=None, w: float = 1.0,
240
+ pred=None, pred_uncond=None,
241
+ requires_grad: bool = False):
242
+ """
243
+ Guided Sampling CFG:
244
+ bar_eps = w * pred + (1 - w) * pred_uncond
245
+ bar_x0 = w * pred + (1 - w) * pred_uncond
246
+ """
247
+ with torch.set_grad_enabled(requires_grad):
248
+ if w != 0.0 and w != 1.0:
249
+ if pred is None or pred_uncond is None:
250
+ b = xt.shape[0]
251
+ repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
252
+ condition = torch.cat([condition, torch.zeros_like(condition)], 0)
253
+ pred_all = self.D(
254
+ xt.repeat(*repeat_dim),
255
+ t.repeat(2), condition, model)
256
+ pred, pred_uncond = pred_all[:b], pred_all[b:]
257
+ elif w == 0.0:
258
+ pred = 0.
259
+ pred_uncond = self.D(xt, t, None, model)
260
+ else:
261
+ pred = self.D(xt, t, condition, model)
262
+ pred_uncond = 0.
263
+
264
+ bar_pred = w * pred + (1 - w) * pred_uncond
265
+
266
+ return bar_pred
267
+
268
+ def guided_sampling(
269
+ self, xt, t, sigma,
270
+ model,
271
+ condition_cfg=None, w_cfg: float = 0.0,
272
+ condition_cg=None, w_cg: float = 0.0,
273
+ requires_grad: bool = False):
274
+ """
275
+ One-step epsilon/x0 prediction with guidance.
276
+ """
277
+
278
+ pred = self.classifier_free_guidance(
279
+ xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
280
+
281
+ pred, logp = self.classifier_guidance(
282
+ xt, t, sigma, model, condition_cg, w_cg, pred)
283
+
284
+ return pred, logp
285
+
286
+ def sample(
287
+ self,
288
+ # ---------- the known fixed portion ---------- #
289
+ prior: torch.Tensor,
290
+ # ----------------- sampling ----------------- #
291
+ solver: str = "euler", # euler or heun
292
+ n_samples: int = 1,
293
+ sample_steps: int = 5,
294
+ use_ema: bool = True,
295
+ temperature: float = 1.0,
296
+ # ------------------ guidance ------------------ #
297
+ condition_cfg=None,
298
+ mask_cfg=None,
299
+ w_cfg: float = 0.0,
300
+ condition_cg=None,
301
+ w_cg: float = 0.0,
302
+ # ----------- Diffusion-X sampling ----------
303
+ diffusion_x_sampling_steps: int = 0,
304
+ # ----------- Warm-Starting -----------
305
+ warm_start_reference: Optional[torch.Tensor] = None,
306
+ warm_start_forward_level: float = 0.3,
307
+ # ------------------ others ------------------ #
308
+ requires_grad: bool = False,
309
+ preserve_history: bool = False,
310
+ **kwargs,
311
+ ):
312
+ """Sampling.
313
+
314
+ Inputs:
315
+ - prior: torch.Tensor
316
+ The known fixed portion of the input data. Should be in the shape of generated data.
317
+ Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
318
+
319
+ - solver: str
320
+ The solver for the reverse process. Check `supported_solvers` property for available solvers.
321
+ - n_samples: int
322
+ The number of samples to generate.
323
+ - sample_steps: int
324
+ The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
325
+ - sample_step_schedule: Union[str, Callable]
326
+ The schedule for the sampling steps.
327
+ - use_ema: bool
328
+ Whether to use the exponential moving average model.
329
+ - temperature: float
330
+ The temperature for sampling.
331
+
332
+ - condition_cfg: Optional
333
+ Condition for Classifier-free-guidance.
334
+ - mask_cfg: Optional
335
+ Mask for Classifier-guidance.
336
+ - w_cfg: float
337
+ Weight for Classifier-free-guidance.
338
+ - condition_cg: Optional
339
+ Condition for Classifier-guidance.
340
+ - w_cg: float
341
+ Weight for Classifier-guidance.
342
+
343
+ - diffusion_x_sampling_steps: int
344
+ The number of diffusion steps for diffusion-x sampling.
345
+
346
+ - warm_start_reference: Optional[torch.Tensor]
347
+ Reference data for warm-starting sampling. `None` indicates no warm-starting.
348
+ - warm_start_forward_level: float
349
+ The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
350
+
351
+ - requires_grad: bool
352
+ Whether to preserve gradients.
353
+ - preserve_history: bool
354
+ Whether to preserve the sampling history.
355
+
356
+ Outputs:
357
+ - x0: torch.Tensor
358
+ Generated samples. Be in the shape of `(n_samples, *x_shape)`.
359
+ - log: dict
360
+ The log dictionary.
361
+ """
362
+ assert solver in ["euler", "heun"], f"Solver {solver} is not supported. Use 'euler' or 'heun' instead."
363
+
364
+ # ===================== Initialization =====================
365
+ log = {
366
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
367
+
368
+ model = self.model if not use_ema else self.model_ema
369
+
370
+ prior = prior.to(self.device)
371
+ if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
372
+ fwd_sigma = self.sigma_min + (self.sigma_max - self.sigma_min) * warm_start_forward_level
373
+ xt = warm_start_reference + fwd_sigma * torch.randn_like(warm_start_reference)
374
+ else:
375
+ fwd_sigma = self.sigma_max
376
+ xt = torch.randn_like(prior) * self.sigma_max * temperature
377
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
378
+ if preserve_history:
379
+ log["sample_history"][:, 0] = xt.cpu().numpy()
380
+
381
+ with torch.set_grad_enabled(requires_grad):
382
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
383
+ condition_vec_cg = condition_cg
384
+
385
+ # ===================== Sampling Schedule ====================
386
+ sample_step_schedule = ((self.sigma_min ** (1 / self.rho) + torch.arange(sample_steps + 1, device=self.device)
387
+ / sample_steps * (fwd_sigma ** (1 / self.rho) - self.sigma_min ** (1 / self.rho)))
388
+ ** self.rho)
389
+
390
+ sigmas = sample_step_schedule
391
+
392
+ # ===================== Denoising Loop ========================
393
+ loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
394
+ for i in reversed(loop_steps):
395
+
396
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
397
+
398
+ # guided sampling
399
+ pred, logp = self.guided_sampling(
400
+ xt, t, sigmas[i],
401
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
402
+
403
+ # clip the prediction
404
+ if self.clip_pred:
405
+ pred = pred.clip(self.x_min, self.x_max)
406
+
407
+ # one-step update
408
+ dot_x = (xt - pred) / at_least_ndim(sigmas[i], xt.dim())
409
+ delta_t = sample_step_schedule[i] - sample_step_schedule[i - 1]
410
+ x_next = xt - dot_x * delta_t
411
+ x_next = x_next * (1. - self.fix_mask) + prior * self.fix_mask
412
+
413
+ if solver == "heun" and i > 1:
414
+ pred, logp = self.guided_sampling(
415
+ x_next, t / sample_step_schedule[i] * sample_step_schedule[i - 1], sigmas[i - 1],
416
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
417
+ if self.clip_pred:
418
+ pred = pred.clip(self.x_min, self.x_max)
419
+ dot_x_prime = (x_next - pred) / at_least_ndim(sigmas[i - 1], xt.dim())
420
+ x_next = xt - (dot_x + dot_x_prime) / 2. * delta_t
421
+ x_next = x_next * (1. - self.fix_mask) + prior * self.fix_mask
422
+
423
+ xt = x_next
424
+
425
+ if preserve_history:
426
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
427
+
428
+ # ================= Post-processing =================
429
+ if self.classifier is not None:
430
+ with torch.no_grad():
431
+ t = torch.ones((n_samples,), dtype=torch.long, device=self.device) * self.sigma_min
432
+ logp = self.classifier.logp(xt, t.log() / 4., condition_vec_cg)
433
+ log["log_p"] = logp
434
+
435
+ if self.clip_pred:
436
+ xt = xt.clip(self.x_min, self.x_max)
437
+
438
+ return xt, log
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/rectifiedflow.py ADDED
@@ -0,0 +1,632 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union, Callable
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from cleandiffuser.utils import (
11
+ at_least_ndim,
12
+ SUPPORTED_DISCRETIZATIONS, SUPPORTED_SAMPLING_STEP_SCHEDULE)
13
+ from .basic import DiffusionModel
14
+
15
+
16
+ class DiscreteRectifiedFlow(DiffusionModel):
17
+ """Discrete-time Rectified Flow
18
+
19
+ Rectified flow learns a straight flow for transporting between two probability distributions.
20
+ The straight characteristic of this flow enables ODEs to achieve high-quality data generation
21
+ with very few sampling steps (achieving one-step sampling when the ODE is completely straight).
22
+ Rectified flow can continuously optimize model performance by refining the learned ODE through multiple reflow procedures.
23
+ Since it does not require Gaussian to be the source distribution, our implementation supports transformations
24
+ between two arbitrary distributions. However, if not specifically specified during training,
25
+ the model will default to learning the transport from a Gaussian distribution to the target distribution.
26
+
27
+ The DiscreteRectifiedFlow is the discrete-time version of Rectified flow.
28
+ It discretizes the continuous time interval into a finite number of diffusion steps
29
+ and only estimates the score function on these steps.
30
+ Therefore, in sampling, solvers can only work on these learned steps.
31
+ The sampling steps are required to be greater than 1 and less than or equal to the number of diffusion steps.
32
+
33
+ Args:
34
+ - nn_diffusion: BaseNNDiffusion
35
+ The neural network backbone for the Diffusion model.
36
+ - nn_condition: Optional[BaseNNCondition]
37
+ The neural network backbone for the condition embedding.
38
+
39
+ - fix_mask: Union[list, np.ndarray, torch.Tensor]
40
+ Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
41
+ The mask should be in the shape of `x_shape`.
42
+ - loss_weight: Union[list, np.ndarray, torch.Tensor]
43
+ Add loss weight. The weight should be in the shape of `x_shape`.
44
+
45
+ - classifier: Optional[BaseClassifier]
46
+ Add a classifier to enable classifier-guidance.
47
+
48
+ - grad_clip_norm: Optional[float]
49
+ Gradient clipping norm.
50
+ - ema_rate: float
51
+ Exponential moving average rate.
52
+ - optim_params: Optional[dict]
53
+ Optimizer parameters.
54
+
55
+ - diffusion_steps: int
56
+ The discretization steps for discrete-time diffusion models.
57
+ - discretization: Union[str, Callable]
58
+ The discretization method for the diffusion steps.
59
+
60
+ - x_max: Optional[torch.Tensor]
61
+ The maximum value for the input data. `None` indicates no constraint.
62
+ - x_min: Optional[torch.Tensor]
63
+ The minimum value for the input data. `None` indicates no constraint.
64
+
65
+ - device: Union[torch.device, str]
66
+ The device to run the model.
67
+ """
68
+ def __init__(
69
+ self,
70
+
71
+ # ----------------- Neural Networks ----------------- #
72
+ nn_diffusion: BaseNNDiffusion,
73
+ nn_condition: Optional[BaseNNCondition] = None,
74
+
75
+ # ----------------- Masks ----------------- #
76
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
77
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
78
+ # Add loss weight
79
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
80
+
81
+ # ------------------ Plugins ---------------- #
82
+ # Add a classifier to enable classifier-guidance
83
+ classifier: Optional[BaseClassifier] = None,
84
+
85
+ # ------------------ Training Params ---------------- #
86
+ grad_clip_norm: Optional[float] = None,
87
+ ema_rate: float = 0.995,
88
+ optim_params: Optional[dict] = None,
89
+
90
+ # ------------------- Diffusion Params ------------------- #
91
+ diffusion_steps: int = 1000,
92
+
93
+ discretization: Union[str, Callable] = "uniform",
94
+
95
+ x_max: Optional[torch.Tensor] = None,
96
+ x_min: Optional[torch.Tensor] = None,
97
+
98
+ device: Union[torch.device, str] = "cpu"
99
+ ):
100
+ super().__init__(
101
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
102
+ diffusion_steps, ema_rate, optim_params, device)
103
+
104
+ assert classifier is None, "Rectified Flow does not support classifier-guidance."
105
+
106
+ self.x_max, self.x_min = x_max, x_min
107
+
108
+ # ================= Discretization =================
109
+ # - Map the continuous range [0., 1.] to the discrete range [0, T-1]
110
+ if isinstance(discretization, str):
111
+ if discretization in SUPPORTED_DISCRETIZATIONS.keys():
112
+ self.t_diffusion = SUPPORTED_DISCRETIZATIONS[discretization](diffusion_steps, 0.).to(device)
113
+ else:
114
+ Warning(f"Discretization method {discretization} is not supported. "
115
+ f"Using uniform discretization instead.")
116
+ self.t_diffusion = SUPPORTED_DISCRETIZATIONS["uniform"](diffusion_steps, 0.).to(device)
117
+ elif callable(discretization):
118
+ self.t_diffusion = discretization(diffusion_steps, 0.).to(device)
119
+ else:
120
+ raise ValueError("discretization must be a callable or a string")
121
+
122
+ @property
123
+ def supported_solvers(self):
124
+ return ["euler"]
125
+
126
+ @property
127
+ def clip_pred(self):
128
+ return (self.x_max is not None) or (self.x_min is not None)
129
+
130
+ # ==================== Training: Straighten Flow ======================
131
+
132
+ def loss(self, x0, x1=None, condition=None):
133
+
134
+ # x1 is the samples of source distribution.
135
+ # If x1 is None, then we assume x1 is from a standard Gaussian distribution.
136
+ if x1 is None:
137
+ x1 = torch.randn_like(x0)
138
+ else:
139
+ assert x0.shape == x1.shape, "x0 and x1 must have the same shape"
140
+
141
+ t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device)
142
+ t_c = at_least_ndim(self.t_diffusion[t], x0.dim())
143
+
144
+ xt = t_c * x1 + (1 - t_c) * x0
145
+ xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
146
+
147
+ condition = self.model["condition"](condition) if condition is not None else None
148
+
149
+ loss = (self.model["diffusion"](xt, t, condition) - (x0 - x1)) ** 2
150
+
151
+ return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
152
+
153
+ def update(self, x0, condition=None, update_ema=True, x1=None, **kwargs):
154
+ """One-step gradient update.
155
+ Inputs:
156
+ - x0: torch.Tensor
157
+ Samples from the target distribution.
158
+ - condition: Optional
159
+ Condition of x0. `None` indicates no condition.
160
+ - update_ema: bool
161
+ Whether to update the exponential moving average model.
162
+ - x1: torch.Tensor
163
+ Samples from the source distribution. `None` indicates standard Gaussian samples.
164
+
165
+ Outputs:
166
+ - log: dict
167
+ The log dictionary.
168
+ """
169
+ loss = self.loss(x0, x1, condition)
170
+
171
+ loss.backward()
172
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
173
+ if self.grad_clip_norm else None
174
+ self.optimizer.step()
175
+ self.optimizer.zero_grad()
176
+
177
+ if update_ema:
178
+ self.ema_update()
179
+
180
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
181
+
182
+ return log
183
+
184
+ # ==================== Sampling: Solving a straight ODE flow ======================
185
+
186
+ def sample(
187
+ self,
188
+ # ---------- the known fixed portion ---------- #
189
+ prior: torch.Tensor,
190
+ x1: torch.Tensor = None,
191
+ # ----------------- sampling ----------------- #
192
+ n_samples: int = 1,
193
+ sample_steps: int = 5,
194
+ sample_step_schedule: Union[str, Callable] = "uniform",
195
+ use_ema: bool = True,
196
+ temperature: float = 1.0,
197
+ # ------------------ guidance ------------------ #
198
+ condition_cfg=None,
199
+ mask_cfg=None,
200
+ w_cfg: float = 0.0,
201
+ condition_cg=None,
202
+ w_cg: float = 0.0,
203
+ # ----------- Diffusion-X sampling ----------
204
+ diffusion_x_sampling_steps: int = 0,
205
+ # ----------- Warm-Starting -----------
206
+ warm_start_reference: Optional[torch.Tensor] = None,
207
+ warm_start_forward_level: float = 0.3,
208
+ # ------------------ others ------------------ #
209
+ requires_grad: bool = False,
210
+ preserve_history: bool = False,
211
+ **kwargs,
212
+ ):
213
+ """Sampling.
214
+
215
+ Inputs:
216
+ - prior: torch.Tensor
217
+ The known fixed portion of the input data. Should be in the shape of generated data.
218
+ Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
219
+ - x1: torch.Tensor
220
+ The samples from the source distribution. `None` indicates standard Gaussian samples.
221
+
222
+ - n_samples: int
223
+ The number of samples to generate.
224
+ - sample_steps: int
225
+ The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
226
+ - sample_step_schedule: Union[str, Callable]
227
+ The schedule for the sampling steps.
228
+ - use_ema: bool
229
+ Whether to use the exponential moving average model.
230
+ - temperature: float
231
+ The temperature for sampling.
232
+
233
+ - condition_cfg: Optional
234
+ Condition for Classifier-free-guidance.
235
+ - mask_cfg: Optional
236
+ Mask for Classifier-guidance.
237
+ - w_cfg: float
238
+ Weight for Classifier-free-guidance.
239
+ - condition_cg: Optional
240
+ Condition for Classifier-guidance.
241
+ - w_cg: float
242
+ Weight for Classifier-guidance.
243
+
244
+ - diffusion_x_sampling_steps: int
245
+ The number of diffusion steps for diffusion-x sampling.
246
+
247
+ - warm_start_reference: Optional[torch.Tensor]
248
+ Reference data for warm-starting sampling. `None` indicates no warm-starting.
249
+ - warm_start_forward_level: float
250
+ The forward noise level to perturb the reference data. Should be in the range of `[0., 1.]`, where `1` indicates pure noise.
251
+
252
+ - requires_grad: bool
253
+ Whether to preserve gradients.
254
+ - preserve_history: bool
255
+ Whether to preserve the sampling history.
256
+
257
+ Outputs:
258
+ - x0: torch.Tensor
259
+ Generated samples. Be in the shape of `(n_samples, *x_shape)`.
260
+ - log: dict
261
+ The log dictionary.
262
+ """
263
+ assert w_cg == 0.0 and condition_cg is None, "Rectified Flow does not support classifier-guidance."
264
+
265
+ prior = prior.to(self.device)
266
+ if isinstance(warm_start_reference, torch.Tensor):
267
+ diffusion_steps = int(warm_start_forward_level * self.diffusion_steps)
268
+ t_c = at_least_ndim(self.t_diffusion[diffusion_steps], prior.dim())
269
+ x1 = torch.randn_like(prior) * t_c + warm_start_reference * (1 - t_c)
270
+ else:
271
+ diffusion_steps = self.diffusion_steps
272
+ if x1 is None:
273
+ x1 = torch.randn_like(prior) * temperature
274
+ else:
275
+ assert prior.shape == x1.shape, "prior and x1 must have the same shape"
276
+
277
+ # ===================== Initialization =====================
278
+ log = {
279
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
280
+
281
+ model = self.model if not use_ema else self.model_ema
282
+
283
+ xt = x1.clone()
284
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
285
+ if preserve_history:
286
+ log["sample_history"][:, 0] = xt.cpu().numpy()
287
+
288
+ with torch.set_grad_enabled(requires_grad):
289
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
290
+
291
+ # ===================== Sampling Schedule ====================
292
+ if isinstance(sample_step_schedule, str):
293
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
294
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
295
+ diffusion_steps, sample_steps)
296
+ else:
297
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
298
+ elif callable(sample_step_schedule):
299
+ sample_step_schedule = sample_step_schedule(diffusion_steps, sample_steps)
300
+ else:
301
+ raise ValueError("sample_step_schedule must be a callable or a string")
302
+
303
+ # ===================== Denoising Loop ========================
304
+ loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
305
+ for i in reversed(loop_steps):
306
+
307
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
308
+
309
+ start_t, end_t = self.t_diffusion[sample_step_schedule[i]], self.t_diffusion[sample_step_schedule[i - 1]]
310
+ delta_t = start_t - end_t
311
+
312
+ # velocity
313
+ if w_cfg != 0.0 and w_cfg != 1.0 and condition_vec_cfg is not None:
314
+ repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
315
+ vel_all = model["diffusion"](
316
+ xt.repeat(*repeat_dim), t.repeat(2),
317
+ torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0))
318
+ vel_cond, vel_uncond = vel_all.chunk(2, dim=0)
319
+ vel = w_cfg * vel_cond + (1 - w_cfg) * vel_uncond
320
+ elif w_cfg == 0.0 or condition_vec_cfg is None:
321
+ vel = model["diffusion"](xt, t, None)
322
+ else:
323
+ vel = model["diffusion"](xt, t, condition_vec_cfg)
324
+
325
+ # one-step update
326
+ xt = xt + delta_t * vel
327
+
328
+ # fix the known portion, and preserve the sampling history
329
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
330
+ if preserve_history:
331
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
332
+
333
+ # ================= Post-processing =================
334
+ if self.clip_pred:
335
+ xt = xt.clip(self.x_min, self.x_max)
336
+
337
+ return xt, log
338
+
339
+
340
+ class ContinuousRectifiedFlow(DiffusionModel):
341
+ """Continuous-time Rectified Flow
342
+
343
+ Rectified flow learns a straight flow for transporting between two probability distributions.
344
+ The straight characteristic of this flow enables ODEs to achieve high-quality data generation
345
+ with very few sampling steps (achieving one-step sampling when the ODE is completely straight).
346
+ Rectified flow can continuously optimize model performance by refining the learned ODE through multiple reflow procedures.
347
+ Since it does not require Gaussian to be the source distribution, our implementation supports transformations
348
+ between two arbitrary distributions. However, if not specifically specified during training,
349
+ the model will default to learning the transport from a Gaussian distribution to the target distribution.
350
+
351
+ The ContinuousRectifiedFlow is the continuous-time version of Rectified flow.
352
+ It estimates the score function at any $t\in[0,T]$ and solves the reverse process in continuous time.
353
+ The sampling steps are required to be greater than 1.
354
+
355
+ Args:
356
+ - nn_diffusion: BaseNNDiffusion
357
+ The neural network backbone for the Diffusion model.
358
+ - nn_condition: Optional[BaseNNCondition]
359
+ The neural network backbone for the condition embedding.
360
+
361
+ - fix_mask: Union[list, np.ndarray, torch.Tensor]
362
+ Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
363
+ The mask should be in the shape of `x_shape`.
364
+ - loss_weight: Union[list, np.ndarray, torch.Tensor]
365
+ Add loss weight. The weight should be in the shape of `x_shape`.
366
+
367
+ - classifier: Optional[BaseClassifier]
368
+ Add a classifier to enable classifier-guidance.
369
+
370
+ - grad_clip_norm: Optional[float]
371
+ Gradient clipping norm.
372
+ - ema_rate: float
373
+ Exponential moving average rate.
374
+ - optim_params: Optional[dict]
375
+ Optimizer parameters.
376
+
377
+ - x_max: Optional[torch.Tensor]
378
+ The maximum value for the input data. `None` indicates no constraint.
379
+ - x_min: Optional[torch.Tensor]
380
+ The minimum value for the input data. `None` indicates no constraint.
381
+
382
+ - device: Union[torch.device, str]
383
+ The device to run the model.
384
+ """
385
+ def __init__(
386
+ self,
387
+
388
+ # ----------------- Neural Networks ----------------- #
389
+ nn_diffusion: BaseNNDiffusion,
390
+ nn_condition: Optional[BaseNNCondition] = None,
391
+
392
+ # ----------------- Masks ----------------- #
393
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
394
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
395
+ # Add loss weight
396
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
397
+
398
+ # ------------------ Plugins ---------------- #
399
+ # Add a classifier to enable classifier-guidance
400
+ classifier: Optional[BaseClassifier] = None,
401
+
402
+ # ------------------ Training Params ---------------- #
403
+ grad_clip_norm: Optional[float] = None,
404
+ ema_rate: float = 0.995,
405
+ optim_params: Optional[dict] = None,
406
+
407
+ # ------------------- Diffusion Params ------------------- #
408
+ x_max: Optional[torch.Tensor] = None,
409
+ x_min: Optional[torch.Tensor] = None,
410
+
411
+ device: Union[torch.device, str] = "cpu"
412
+ ):
413
+ super().__init__(
414
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
415
+ 0, ema_rate, optim_params, device)
416
+
417
+ assert classifier is None, "Rectified Flow does not support classifier-guidance."
418
+
419
+ self.x_max, self.x_min = x_max, x_min
420
+
421
+ @property
422
+ def supported_solvers(self):
423
+ return ["euler"]
424
+
425
+ @property
426
+ def clip_pred(self):
427
+ return (self.x_max is not None) or (self.x_min is not None)
428
+
429
+ # ==================== Training: Straighten Flow ======================
430
+
431
+ def loss(self, x0, x1=None, condition=None):
432
+
433
+ # x1 is the samples of source distribution.
434
+ # If x1 is None, then we assume x1 is from a standard Gaussian distribution.
435
+ if x1 is None:
436
+ x1 = torch.randn_like(x0)
437
+ else:
438
+ assert x0.shape == x1.shape, "x0 and x1 must have the same shape"
439
+
440
+ t = torch.rand((x0.shape[0],), device=self.device)
441
+
442
+ xt = x0 + at_least_ndim(t, x0.dim()) * (x1 - x0)
443
+
444
+ xt = xt * (1. - self.fix_mask) + x0 * self.fix_mask
445
+
446
+ condition = self.model["condition"](condition) if condition is not None else None
447
+
448
+ loss = (self.model["diffusion"](xt, t, condition) - (x0 - x1)) ** 2
449
+
450
+ return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
451
+
452
+ def update(self, x0, condition=None, update_ema=True, x1=None, **kwargs):
453
+ """One-step gradient update.
454
+ Inputs:
455
+ - x0: torch.Tensor
456
+ Samples from the target distribution.
457
+ - condition: Optional
458
+ Condition of x0. `None` indicates no condition.
459
+ - update_ema: bool
460
+ Whether to update the exponential moving average model.
461
+ - x1: torch.Tensor
462
+ Samples from the source distribution. `None` indicates standard Gaussian samples.
463
+
464
+ Outputs:
465
+ - log: dict
466
+ The log dictionary.
467
+ """
468
+ loss = self.loss(x0, x1, condition)
469
+
470
+ loss.backward()
471
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
472
+ if self.grad_clip_norm else None
473
+ self.optimizer.step()
474
+ self.optimizer.zero_grad()
475
+
476
+ if update_ema:
477
+ self.ema_update()
478
+
479
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
480
+
481
+ return log
482
+
483
+ # ==================== Sampling: Solving a straight ODE flow ======================
484
+
485
+ def sample(
486
+ self,
487
+ # ---------- the known fixed portion ---------- #
488
+ prior: torch.Tensor,
489
+ x1: torch.Tensor = None,
490
+ # ----------------- sampling ----------------- #
491
+ n_samples: int = 1,
492
+ sample_steps: int = 5,
493
+ sample_step_schedule: Union[str, Callable] = "uniform_continuous",
494
+ use_ema: bool = True,
495
+ temperature: float = 1.0,
496
+ # ------------------ guidance ------------------ #
497
+ condition_cfg=None,
498
+ mask_cfg=None,
499
+ w_cfg: float = 0.0,
500
+ condition_cg=None,
501
+ w_cg: float = 0.0,
502
+ # ----------- Diffusion-X sampling ----------
503
+ diffusion_x_sampling_steps: int = 0,
504
+ # ----------- Warm-Starting -----------
505
+ warm_start_reference: Optional[torch.Tensor] = None,
506
+ warm_start_forward_level: float = 0.3,
507
+ # ------------------ others ------------------ #
508
+ requires_grad: bool = False,
509
+ preserve_history: bool = False,
510
+ **kwargs,
511
+ ):
512
+ """Sampling.
513
+
514
+ Inputs:
515
+ - prior: torch.Tensor
516
+ The known fixed portion of the input data. Should be in the shape of generated data.
517
+ Use `torch.zeros((n_samples, *x_shape))` for non-prior sampling.
518
+ - x1: torch.Tensor
519
+ The samples from the source distribution. `None` indicates standard Gaussian samples.
520
+
521
+ - n_samples: int
522
+ The number of samples to generate.
523
+ - sample_steps: int
524
+ The number of sampling steps. Should be greater than 1 and less than or equal to the number of diffusion steps.
525
+ - sample_step_schedule: Union[str, Callable]
526
+ The schedule for the sampling steps.
527
+ - use_ema: bool
528
+ Whether to use the exponential moving average model.
529
+ - temperature: float
530
+ The temperature for sampling.
531
+
532
+ - condition_cfg: Optional
533
+ Condition for Classifier-free-guidance.
534
+ - mask_cfg: Optional
535
+ Mask for Classifier-guidance.
536
+ - w_cfg: float
537
+ Weight for Classifier-free-guidance.
538
+ - condition_cg: Optional
539
+ Condition for Classifier-guidance.
540
+ - w_cg: float
541
+ Weight for Classifier-guidance.
542
+
543
+ - diffusion_x_sampling_steps: int
544
+ The number of diffusion steps for diffusion-x sampling.
545
+
546
+ - requires_grad: bool
547
+ Whether to preserve gradients.
548
+ - preserve_history: bool
549
+ Whether to preserve the sampling history.
550
+
551
+ Outputs:
552
+ - x0: torch.Tensor
553
+ Generated samples. Be in the shape of `(n_samples, *x_shape)`.
554
+ - log: dict
555
+ The log dictionary.
556
+ """
557
+ assert w_cg == 0.0 and condition_cg is None, "Rectified Flow does not support classifier-guidance."
558
+
559
+ prior = prior.to(self.device)
560
+ if isinstance(warm_start_reference, torch.Tensor):
561
+ t_c = torch.ones_like(prior) * warm_start_forward_level
562
+ x1 = torch.randn_like(prior) * t_c + warm_start_reference * (1 - t_c)
563
+ else:
564
+ if x1 is None:
565
+ x1 = torch.randn_like(prior) * temperature
566
+ else:
567
+ assert prior.shape == x1.shape, "prior and x1 must have the same shape"
568
+
569
+ # ===================== Initialization =====================
570
+ log = {
571
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
572
+
573
+ model = self.model if not use_ema else self.model_ema
574
+
575
+ xt = x1.clone()
576
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
577
+ if preserve_history:
578
+ log["sample_history"][:, 0] = xt.cpu().numpy()
579
+
580
+ with torch.set_grad_enabled(requires_grad):
581
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
582
+
583
+ # ===================== Sampling Schedule ====================
584
+ if isinstance(warm_start_reference, torch.Tensor) and warm_start_forward_level > 0.:
585
+ final_t = warm_start_forward_level
586
+ else:
587
+ final_t = 1.
588
+ if isinstance(sample_step_schedule, str):
589
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
590
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
591
+ [0., final_t], sample_steps)
592
+ else:
593
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
594
+ elif callable(sample_step_schedule):
595
+ sample_step_schedule = sample_step_schedule([0., final_t], sample_steps)
596
+ else:
597
+ raise ValueError("sample_step_schedule must be a callable or a string")
598
+
599
+ # ===================== Denoising Loop ========================
600
+ loop_steps = [1] * diffusion_x_sampling_steps + list(range(1, sample_steps + 1))
601
+ for i in reversed(loop_steps):
602
+
603
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
604
+
605
+ delta_t = sample_step_schedule[i] - sample_step_schedule[i - 1]
606
+
607
+ # velocity
608
+ if w_cfg != 0.0 and w_cfg != 1.0 and condition_vec_cfg is not None:
609
+ repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
610
+ vel_all = model["diffusion"](
611
+ xt.repeat(*repeat_dim), t.repeat(2),
612
+ torch.cat([condition_vec_cfg, torch.zeros_like(condition_vec_cfg)], 0))
613
+ vel_cond, vel_uncond = vel_all.chunk(2, dim=0)
614
+ vel = w_cfg * vel_cond + (1 - w_cfg) * vel_uncond
615
+ elif w_cfg == 0.0 or condition_vec_cfg is None:
616
+ vel = model["diffusion"](xt, t, None)
617
+ else:
618
+ vel = model["diffusion"](xt, t, condition_vec_cfg)
619
+
620
+ # one-step update
621
+ xt = xt + delta_t * vel
622
+
623
+ # fix the known portion, and preserve the sampling history
624
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
625
+ if preserve_history:
626
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
627
+
628
+ # ================= Post-processing =================
629
+ if self.clip_pred:
630
+ xt = xt.clip(self.x_min, self.x_max)
631
+
632
+ return xt, log
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/veode.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+ import torch
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from .edm import EDMArchetecture
11
+
12
+
13
+ class VEODE(EDMArchetecture):
14
+ def __init__(
15
+ self,
16
+
17
+ # ----------------- Neural Networks ----------------- #
18
+ nn_diffusion: BaseNNDiffusion,
19
+ nn_condition: Optional[BaseNNCondition] = None,
20
+
21
+ # ----------------- Masks ----------------- #
22
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
23
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
24
+ # Add loss weight
25
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
26
+
27
+ # ------------------ Plugs ---------------- #
28
+ # Add a classifier to enable classifier-guidance
29
+ classifier: Optional[BaseClassifier] = None,
30
+
31
+ # ------------------ Params ---------------- #
32
+ grad_clip_norm: Optional[float] = None,
33
+ diffusion_steps: int = 1000,
34
+ ema_rate: float = 0.995,
35
+ optim_params: Optional[dict] = None,
36
+
37
+ # ------------------- EDM Params ------------------- #
38
+ sigma_min: float = 0.02,
39
+ sigma_max: float = 100.,
40
+
41
+ device: Union[torch.device, str] = "cpu"
42
+ ):
43
+ super().__init__(
44
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
45
+ diffusion_steps, ema_rate, optim_params, device)
46
+
47
+ self.sigma_min, self.sigma_max = sigma_min, sigma_max
48
+
49
+ def set_sample_steps(self, N: int):
50
+ self.sample_steps = N
51
+ self.sigma_s = self.sigma_max * (self.sigma_min / self.sigma_max) ** (
52
+ torch.arange(N, device=self.device) / (N - 1))
53
+ self.t_s = self.sigma_s ** 2
54
+ self.scale_s = torch.ones_like(self.sigma_s) * 1.0
55
+ self.dot_sigma_s = 1 / (2 * self.sigma_s)
56
+ self.dot_scale_s = torch.zeros_like(self.sigma_s)
57
+ self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
58
+ self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
59
+
60
+ def c_skip(self, sigma): return torch.ones_like(sigma)
61
+
62
+ def c_out(self, sigma): return sigma
63
+
64
+ def c_in(self, sigma): return torch.ones_like(sigma)
65
+
66
+ def c_noise(self, sigma): return (0.5 * sigma).log()
67
+
68
+ def loss_weighting(self, sigma): return 1 / (sigma ** 2)
69
+
70
+ def sample_noise_distribution(self, N):
71
+ log_sigma = torch.rand((N, 1), device=self.device) * np.log(self.sigma_max / self.sigma_min) + np.log(
72
+ self.sigma_min)
73
+ return log_sigma.exp()
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpode.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+ import torch
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from .edm import EDMArchetecture
11
+
12
+
13
+ class VPODE(EDMArchetecture):
14
+ def __init__(
15
+ self,
16
+
17
+ # ----------------- Neural Networks ----------------- #
18
+ nn_diffusion: BaseNNDiffusion,
19
+ nn_condition: Optional[BaseNNCondition] = None,
20
+
21
+ # ----------------- Masks ----------------- #
22
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
23
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
24
+ # Add loss weight
25
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
26
+
27
+ # ------------------ Plugs ---------------- #
28
+ # Add a classifier to enable classifier-guidance
29
+ classifier: Optional[BaseClassifier] = None,
30
+
31
+ # ------------------ Params ---------------- #
32
+ grad_clip_norm: Optional[float] = None,
33
+ diffusion_steps: int = 1000,
34
+ ema_rate: float = 0.995,
35
+ optim_params: Optional[dict] = None,
36
+
37
+ # ------------------- EDM Params ------------------- #
38
+ beta_min: float = 0.1,
39
+ beta_max: float = 20.,
40
+ eps_s: float = 1e-3,
41
+ eps_t: float = 1e-5,
42
+
43
+ device: Union[torch.device, str] = "cpu"
44
+ ):
45
+ super().__init__(
46
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
47
+ diffusion_steps, ema_rate, optim_params, device)
48
+
49
+ self.beta_min, self.beta_max = beta_min, beta_max
50
+ self.eps_s, self.eps_t = eps_s, eps_t
51
+ self.beta_d = beta_max - beta_min
52
+
53
+ def set_sample_steps(self, N: int):
54
+ self.sample_steps = N
55
+ self.t_s = torch.arange(N, device=self.device) / (N - 1) * (1e-3 - 1) + 1
56
+ self.sigma_s = ((0.5 * self.beta_d * (self.t_s ** 2) + self.beta_min * self.t_s).exp() - 1.).sqrt()
57
+ self.scale_s = 1 / (1 + self.sigma_s ** 2).sqrt()
58
+ self.dot_sigma_s = (0.5 * (self.sigma_s ** 2 + 1) * (self.beta_d * self.t_s + self.beta_min) / self.sigma_s)
59
+ self.dot_scale_s = -self.sigma_s / (1 + self.sigma_s ** 2) ** 1.5 * self.dot_sigma_s
60
+ self.x_weight_s = (self.dot_sigma_s / self.sigma_s + self.dot_scale_s / self.scale_s)
61
+ self.D_weight_s = self.dot_sigma_s / self.sigma_s * self.scale_s
62
+
63
+ def c_skip(self, sigma): return torch.ones_like(sigma)
64
+
65
+ def c_out(self, sigma): return -sigma
66
+
67
+ def c_in(self, sigma): return 1 / (1 + sigma ** 2).sqrt()
68
+
69
+ def c_noise(self, sigma):
70
+ scale = 1 / (1 + sigma ** 2).sqrt()
71
+ t = ((self.beta_min ** 2 - 4 * self.beta_d * scale.log()).sqrt() - self.beta_min) / self.beta_d
72
+ return ((self.diffusion_steps - 1) * t).long()
73
+
74
+ def loss_weighting(self, sigma): return 1 / (sigma ** 2)
75
+
76
+ def sample_noise_distribution(self, N):
77
+ t = torch.rand((N, 1), device=self.device) * (1 - self.eps_t) + self.eps_t
78
+ return ((0.5 * self.beta_d * t ** 2 + self.beta_min * t).exp() - 1).sqrt()
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/diffusion/vpsde.py ADDED
@@ -0,0 +1,1099 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Union, Callable, Dict
2
+
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn as nn
6
+
7
+ from cleandiffuser.classifier import BaseClassifier
8
+ from cleandiffuser.nn_condition import BaseNNCondition
9
+ from cleandiffuser.nn_diffusion import BaseNNDiffusion
10
+ from cleandiffuser.utils import (
11
+ at_least_ndim,
12
+ SUPPORTED_NOISE_SCHEDULES, SUPPORTED_DISCRETIZATIONS, SUPPORTED_SAMPLING_STEP_SCHEDULE)
13
+ from .basic import DiffusionModel
14
+
15
+ SUPPORTED_SOLVERS = [
16
+ "ddpm", "ddim", "ode_dpmsolver++_1", "sde_dpmsolver++_1", "ode_dpmsolver++_2M"
17
+ ]
18
+
19
+
20
+ def epstheta_to_xtheta(x, alpha, sigma, eps_theta):
21
+ """
22
+ x_theta = (x - sigma * eps_theta) / alpha
23
+ """
24
+ return (x - sigma * eps_theta) / alpha
25
+
26
+
27
+ def xtheta_to_epstheta(x, alpha, sigma, x_theta):
28
+ """
29
+ eps_theta = (x - alpha * x_theta) / sigma
30
+ """
31
+ return (x - alpha * x_theta) / sigma
32
+
33
+
34
+ class DiscreteVPSDE(DiffusionModel):
35
+
36
+ def __init__(
37
+ self,
38
+
39
+ # ----------------- Neural Networks ----------------- #
40
+ nn_diffusion: BaseNNDiffusion,
41
+ nn_condition: Optional[BaseNNCondition] = None,
42
+
43
+ # ----------------- Masks ----------------- #
44
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
45
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
46
+ # Add loss weight
47
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
48
+
49
+ # ------------------ Plugins ---------------- #
50
+ # Add a classifier to enable classifier-guidance
51
+ classifier: Optional[BaseClassifier] = None,
52
+
53
+ # ------------------ Training Params ---------------- #
54
+ grad_clip_norm: Optional[float] = None,
55
+ ema_rate: float = 0.995,
56
+ optim_params: Optional[dict] = None,
57
+
58
+ # ------------------- Diffusion Params ------------------- #
59
+ epsilon: float = 1e-3,
60
+ diffusion_steps: int = 1000,
61
+
62
+ discretization: Union[str, Callable] = "uniform",
63
+ noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
64
+ noise_schedule_params: Optional[dict] = None,
65
+
66
+ x_max: Optional[torch.Tensor] = None,
67
+ x_min: Optional[torch.Tensor] = None,
68
+
69
+ predict_noise: bool = True,
70
+
71
+ device: Union[torch.device, str] = "cpu"
72
+ ):
73
+ super().__init__(
74
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
75
+ diffusion_steps, ema_rate, optim_params, device)
76
+
77
+ self.predict_noise = predict_noise
78
+ self.epsilon = epsilon
79
+ self.x_max, self.x_min = x_max, x_min
80
+
81
+ if 1. / diffusion_steps < epsilon:
82
+ raise ValueError("epsilon is too large for the number of diffusion steps")
83
+
84
+ # ================= Discretization =================
85
+ # - Map the continuous range [epsilon, 1.] to the discrete range [0, T-1]
86
+ if isinstance(discretization, str):
87
+ if discretization in SUPPORTED_DISCRETIZATIONS.keys():
88
+ self.t_diffusion = SUPPORTED_DISCRETIZATIONS[discretization](diffusion_steps, epsilon).to(device)
89
+ else:
90
+ Warning(f"Discretization method {discretization} is not supported. "
91
+ f"Using uniform discretization instead.")
92
+ self.t_diffusion = SUPPORTED_DISCRETIZATIONS["uniform"](diffusion_steps, epsilon).to(device)
93
+ elif callable(discretization):
94
+ self.t_diffusion = discretization(diffusion_steps, epsilon).to(device)
95
+ else:
96
+ raise ValueError("discretization must be a callable or a string")
97
+
98
+ # ================= Noise Schedule =================
99
+ if isinstance(noise_schedule, str):
100
+ if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
101
+ self.alpha, self.sigma = SUPPORTED_NOISE_SCHEDULES[noise_schedule]["forward"](
102
+ self.t_diffusion, **(noise_schedule_params or {}))
103
+ else:
104
+ raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
105
+ elif isinstance(noise_schedule, dict):
106
+ self.alpha, self.sigma = noise_schedule["forward"](self.t_diffusion, **(noise_schedule_params or {}))
107
+ else:
108
+ raise ValueError("noise_schedule must be a callable or a string")
109
+
110
+ self.logSNR = torch.log(self.alpha / self.sigma)
111
+
112
+ # # ================= Extra Steps for DPM-Solver 2 ==================
113
+ # self.inter_logSNR = torch.zeros_like(self.logSNR)
114
+ # self.inter_logSNR[1:] = (self.logSNR[1:] + self.logSNR[:-1]) / 2.
115
+ # # inter_logSNR[0] is not correctly calculated, but it will not be used.
116
+ # if isinstance(noise_schedule, str):
117
+ # if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
118
+ # self.inter_t_diffusion = SUPPORTED_NOISE_SCHEDULES[noise_schedule]["reverse"](
119
+ # logSNR=self.inter_logSNR, **(noise_schedule_params or {}))
120
+ # else:
121
+ # raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
122
+ # elif isinstance(noise_schedule, dict):
123
+ # self.inter_t_diffusion = noise_schedule["reverse"](
124
+ # logSNR=self.inter_logSNR, **(noise_schedule_params or {}))
125
+ # else:
126
+ # raise ValueError("noise_schedule must be a callable or a string")
127
+
128
+ @property
129
+ def clip_pred(self):
130
+ return (self.x_max is not None) or (self.x_min is not None)
131
+
132
+ # ==================== Training: Score Matching ======================
133
+
134
+ def add_noise(self, x0, t=None, eps=None):
135
+
136
+ t = torch.randint(self.diffusion_steps, (x0.shape[0],), device=self.device) if t is None else t
137
+ eps = torch.randn_like(x0) if eps is None else eps
138
+
139
+ alpha, sigma = at_least_ndim(self.alpha[t], x0.dim()), at_least_ndim(self.sigma[t], x0.dim())
140
+
141
+ xt = alpha * x0 + sigma * eps
142
+ xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
143
+
144
+ return xt, t, eps
145
+
146
+ def loss(self, x0, condition=None):
147
+
148
+ xt, t, eps = self.add_noise(x0)
149
+
150
+ condition = self.model["condition"](condition) if condition is not None else None
151
+
152
+ if self.predict_noise:
153
+ loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
154
+ else:
155
+ loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
156
+
157
+ return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
158
+
159
+ def update(self, x0, condition=None, update_ema=True, **kwargs):
160
+
161
+ loss = self.loss(x0, condition)
162
+
163
+ loss.backward()
164
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
165
+ if self.grad_clip_norm else None
166
+ self.optimizer.step()
167
+ self.optimizer.zero_grad()
168
+
169
+ if update_ema:
170
+ self.ema_update()
171
+
172
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
173
+
174
+ return log
175
+
176
+ def update_classifier(self, x0, condition):
177
+
178
+ xt, t, eps = self.add_noise(x0)
179
+
180
+ log = self.classifier.update(xt, t, condition)
181
+
182
+ return log
183
+
184
+ # ==================== Sampling: Solving SDE/ODE ======================
185
+
186
+ def classifier_guidance(
187
+ self, xt, t, alpha, sigma,
188
+ model, condition=None, w: float = 1.0,
189
+ pred=None):
190
+ """
191
+ Guided Sampling CG:
192
+ bar_eps = eps - w * sigma * grad
193
+ bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
194
+ """
195
+ if pred is None:
196
+ pred = model["diffusion"](xt, t, None)
197
+ if self.classifier is None or w == 0.0 or condition is None:
198
+ return pred, None
199
+ else:
200
+ log_p, grad = self.classifier.gradients(xt.clone(), t, condition)
201
+ if self.predict_noise:
202
+ pred = pred - w * sigma * grad
203
+ else:
204
+ pred = pred + w * ((sigma ** 2) / alpha) * grad
205
+
206
+ return pred, log_p
207
+
208
+ def classifier_free_guidance(
209
+ self, xt, t,
210
+ model, condition=None, w: float = 1.0,
211
+ pred=None, pred_uncond=None,
212
+ requires_grad: bool = False):
213
+ """
214
+ Guided Sampling CFG:
215
+ bar_eps = w * pred + (1 - w) * pred_uncond
216
+ bar_x0 = w * pred + (1 - w) * pred_uncond
217
+ """
218
+ with torch.set_grad_enabled(requires_grad):
219
+ if w != 0.0 and w != 1.0:
220
+ if pred is None or pred_uncond is None:
221
+ b = xt.shape[0]
222
+ repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
223
+ condition = torch.cat([condition, torch.zeros_like(condition)], 0)
224
+ pred_all = model["diffusion"](
225
+ xt.repeat(*repeat_dim), t.repeat(2), condition)
226
+ pred, pred_uncond = pred_all[:b], pred_all[b:]
227
+ elif w == 0.0:
228
+ pred = 0.
229
+ pred_uncond = model["diffusion"](xt, t, None)
230
+ else:
231
+ pred = model["diffusion"](xt, t, condition)
232
+ pred_uncond = 0.
233
+
234
+ if self.predict_noise or not self.predict_noise:
235
+ bar_pred = w * pred + (1 - w) * pred_uncond
236
+ else:
237
+ bar_pred = pred
238
+
239
+ return bar_pred
240
+
241
+ def clip_prediction(self, pred, xt, alpha, sigma):
242
+ """
243
+ Clip the prediction at each sampling step to stablize the generation.
244
+ (xt - alpha * x_max) / sigma <= eps <= (xt - alpha * x_min) / sigma
245
+ x_min <= x0 <= x_max
246
+ """
247
+ if self.predict_noise:
248
+ if self.clip_pred:
249
+ upper_bound = (xt - alpha * self.x_min) / sigma if self.x_min is not None else None
250
+ lower_bound = (xt - alpha * self.x_max) / sigma if self.x_max is not None else None
251
+ pred = pred.clip(lower_bound, upper_bound)
252
+ else:
253
+ if self.clip_pred:
254
+ pred = pred.clip(self.x_min, self.x_max)
255
+
256
+ return pred
257
+
258
+ def guided_sampling(
259
+ self, xt, t, alpha, sigma,
260
+ model,
261
+ condition_cfg=None, w_cfg: float = 0.0,
262
+ condition_cg=None, w_cg: float = 0.0,
263
+ requires_grad: bool = False):
264
+ """
265
+ One-step epsilon/x0 prediction with guidance.
266
+ """
267
+
268
+ pred = self.classifier_free_guidance(
269
+ xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
270
+
271
+ pred, logp = self.classifier_guidance(
272
+ xt, t, alpha, sigma, model, condition_cg, w_cg, pred)
273
+
274
+ return pred, logp
275
+
276
+ def ddpm_solver(
277
+ self,
278
+ # ---------- the known fixed portion ---------- #
279
+ prior: torch.Tensor,
280
+ # ----------------- sampling ----------------- #
281
+ n_samples: int = 1,
282
+ sample_steps: int = 5,
283
+ sample_step_schedule: Union[str, Callable] = "uniform",
284
+ use_ema: bool = True,
285
+ temperature: float = 1.0,
286
+ # ------------------ guidance ------------------ #
287
+ condition_cfg=None,
288
+ mask_cfg=None,
289
+ w_cfg: float = 0.0,
290
+ condition_cg=None,
291
+ w_cg: float = 0.0,
292
+ # ------------------ others ------------------ #
293
+ requires_grad: bool = False,
294
+ preserve_history: bool = False,
295
+ **kwargs,
296
+ ):
297
+
298
+ # ===================== Initialization =====================
299
+ log = {
300
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
301
+
302
+ model = self.model if not use_ema else self.model_ema
303
+
304
+ xt = torch.randn_like(prior, device=self.device) * temperature
305
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
306
+ if preserve_history:
307
+ log["sample_history"][:, 0] = xt.cpu().numpy()
308
+
309
+ with torch.set_grad_enabled(requires_grad):
310
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
311
+ condition_vec_cg = condition_cg
312
+
313
+ # ===================== Sampling Schedule ====================
314
+ if isinstance(sample_step_schedule, str):
315
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
316
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
317
+ self.diffusion_steps, sample_steps)
318
+ else:
319
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
320
+ elif callable(sample_step_schedule):
321
+ sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
322
+ else:
323
+ raise ValueError("sample_step_schedule must be a callable or a string")
324
+
325
+ alphas = self.alpha[sample_step_schedule]
326
+ sigmas = self.sigma[sample_step_schedule]
327
+ logSNRs = torch.log(alphas / sigmas)
328
+ hs = torch.zeros_like(logSNRs)
329
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
330
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
331
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
332
+
333
+ # ===================== Denoising Loop ========================
334
+
335
+ for i in reversed(range(1, sample_steps + 1)):
336
+
337
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
338
+
339
+ # guided sampling
340
+ pred, logp = self.guided_sampling(
341
+ xt, t, alphas[i], sigmas[i],
342
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
343
+
344
+ # clip the prediction
345
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
346
+
347
+ # transform to eps_theta
348
+ eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
349
+
350
+ # one-step update
351
+ xt = (
352
+ (alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
353
+ (sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
354
+ if i > 1:
355
+ xt += (stds[i] * torch.randn_like(xt))
356
+
357
+ # fix the known portion, and preserve the sampling history
358
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
359
+ if preserve_history:
360
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
361
+
362
+ # ================= Post-processing =================
363
+ if self.classifier is not None:
364
+ with torch.no_grad():
365
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
366
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
367
+ log["log_p"] = logp
368
+
369
+ if self.clip_pred:
370
+ xt = xt.clip(self.x_min, self.x_max)
371
+
372
+ return xt, log
373
+
374
+ def ddim_solver(
375
+ self,
376
+ # ---------- the known fixed portion ---------- #
377
+ prior: torch.Tensor,
378
+ # ----------------- sampling ----------------- #
379
+ n_samples: int = 1,
380
+ sample_steps: int = 5,
381
+ sample_step_schedule: Union[str, Callable] = "uniform",
382
+ use_ema: bool = True,
383
+ temperature: float = 1.0,
384
+ # ------------------ guidance ------------------ #
385
+ condition_cfg=None,
386
+ mask_cfg=None,
387
+ w_cfg: float = 0.0,
388
+ condition_cg=None,
389
+ w_cg: float = 0.0,
390
+ # ------------------ others ------------------ #
391
+ requires_grad: bool = False,
392
+ preserve_history: bool = False,
393
+ **kwargs,
394
+ ):
395
+
396
+ # ===================== Initialization =====================
397
+ log = {
398
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
399
+
400
+ model = self.model if not use_ema else self.model_ema
401
+
402
+ xt = torch.randn_like(prior, device=self.device) * temperature
403
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
404
+ if preserve_history:
405
+ log["sample_history"][:, 0] = xt.cpu().numpy()
406
+
407
+ with torch.set_grad_enabled(requires_grad):
408
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
409
+ condition_vec_cg = condition_cg
410
+
411
+ # ===================== Sampling Schedule ====================
412
+ if isinstance(sample_step_schedule, str):
413
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
414
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
415
+ self.diffusion_steps, sample_steps)
416
+ else:
417
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
418
+ elif callable(sample_step_schedule):
419
+ sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
420
+ else:
421
+ raise ValueError("sample_step_schedule must be a callable or a string")
422
+
423
+ alphas = self.alpha[sample_step_schedule]
424
+ sigmas = self.sigma[sample_step_schedule]
425
+ logSNRs = torch.log(alphas / sigmas)
426
+ hs = torch.zeros_like(logSNRs)
427
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
428
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
429
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
430
+
431
+ # ===================== Denoising Loop ========================
432
+
433
+ for i in reversed(range(1, sample_steps + 1)):
434
+
435
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
436
+
437
+ # guided sampling
438
+ pred, logp = self.guided_sampling(
439
+ xt, t, alphas[i], sigmas[i],
440
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
441
+
442
+ # clip the prediction
443
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
444
+
445
+ # transform to eps_theta
446
+ eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
447
+
448
+ # one-step update
449
+ xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
450
+
451
+ # fix the known portion, and preserve the sampling history
452
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
453
+ if preserve_history:
454
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
455
+
456
+ # ================= Post-processing =================
457
+ if self.classifier is not None:
458
+ with torch.no_grad():
459
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
460
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
461
+ log["log_p"] = logp
462
+
463
+ if self.clip_pred:
464
+ xt = xt.clip(self.x_min, self.x_max)
465
+
466
+ return xt, log
467
+
468
+ def ode_dpm_solver_pp_1(
469
+ self,
470
+ # ---------- the known fixed portion ---------- #
471
+ prior: torch.Tensor,
472
+ # ----------------- sampling ----------------- #
473
+ n_samples: int = 1,
474
+ sample_steps: int = 5,
475
+ sample_step_schedule: Union[str, Callable] = "uniform",
476
+ use_ema: bool = True,
477
+ temperature: float = 1.0,
478
+ # ------------------ guidance ------------------ #
479
+ condition_cfg=None,
480
+ mask_cfg=None,
481
+ w_cfg: float = 0.0,
482
+ condition_cg=None,
483
+ w_cg: float = 0.0,
484
+ # ------------------ others ------------------ #
485
+ requires_grad: bool = False,
486
+ preserve_history: bool = False,
487
+ **kwargs,
488
+ ):
489
+
490
+ # ===================== Initialization =====================
491
+ log = {
492
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
493
+
494
+ model = self.model if not use_ema else self.model_ema
495
+
496
+ xt = torch.randn_like(prior, device=self.device) * temperature
497
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
498
+ if preserve_history:
499
+ log["sample_history"][:, 0] = xt.cpu().numpy()
500
+
501
+ with torch.set_grad_enabled(requires_grad):
502
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
503
+ condition_vec_cg = condition_cg
504
+
505
+ # ===================== Sampling Schedule ====================
506
+ if isinstance(sample_step_schedule, str):
507
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
508
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
509
+ self.diffusion_steps, sample_steps)
510
+ else:
511
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
512
+ elif callable(sample_step_schedule):
513
+ sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
514
+ else:
515
+ raise ValueError("sample_step_schedule must be a callable or a string")
516
+
517
+ alphas = self.alpha[sample_step_schedule]
518
+ sigmas = self.sigma[sample_step_schedule]
519
+ logSNRs = torch.log(alphas / sigmas)
520
+ hs = torch.zeros_like(logSNRs)
521
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
522
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
523
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
524
+
525
+ # ===================== Denoising Loop ========================
526
+
527
+ for i in reversed(range(1, sample_steps + 1)):
528
+
529
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
530
+
531
+ # guided sampling
532
+ pred, logp = self.guided_sampling(
533
+ xt, t, alphas[i], sigmas[i],
534
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
535
+
536
+ # clip the prediction
537
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
538
+
539
+ # transform to eps_theta
540
+ x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
541
+
542
+ # one-step update
543
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
544
+
545
+ # fix the known portion, and preserve the sampling history
546
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
547
+ if preserve_history:
548
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
549
+
550
+ # ================= Post-processing =================
551
+ if self.classifier is not None:
552
+ with torch.no_grad():
553
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
554
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
555
+ log["log_p"] = logp
556
+
557
+ if self.clip_pred:
558
+ xt = xt.clip(self.x_min, self.x_max)
559
+
560
+ return xt, log
561
+
562
+ def sde_dpm_solver_pp_1(
563
+ self,
564
+ # ---------- the known fixed portion ---------- #
565
+ prior: torch.Tensor,
566
+ # ----------------- sampling ----------------- #
567
+ n_samples: int = 1,
568
+ sample_steps: int = 5,
569
+ sample_step_schedule: Union[str, Callable] = "uniform",
570
+ use_ema: bool = True,
571
+ temperature: float = 1.0,
572
+ # ------------------ guidance ------------------ #
573
+ condition_cfg=None,
574
+ mask_cfg=None,
575
+ w_cfg: float = 0.0,
576
+ condition_cg=None,
577
+ w_cg: float = 0.0,
578
+ # ------------------ others ------------------ #
579
+ requires_grad: bool = False,
580
+ preserve_history: bool = False,
581
+ **kwargs,
582
+ ):
583
+
584
+ # ===================== Initialization =====================
585
+ log = {
586
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
587
+
588
+ model = self.model if not use_ema else self.model_ema
589
+
590
+ xt = torch.randn_like(prior, device=self.device) * temperature
591
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
592
+ if preserve_history:
593
+ log["sample_history"][:, 0] = xt.cpu().numpy()
594
+
595
+ with torch.set_grad_enabled(requires_grad):
596
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
597
+ condition_vec_cg = condition_cg
598
+
599
+ # ===================== Sampling Schedule ====================
600
+ if isinstance(sample_step_schedule, str):
601
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
602
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
603
+ self.diffusion_steps, sample_steps)
604
+ else:
605
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
606
+ elif callable(sample_step_schedule):
607
+ sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
608
+ else:
609
+ raise ValueError("sample_step_schedule must be a callable or a string")
610
+
611
+ alphas = self.alpha[sample_step_schedule]
612
+ sigmas = self.sigma[sample_step_schedule]
613
+ logSNRs = torch.log(alphas / sigmas)
614
+ hs = torch.zeros_like(logSNRs)
615
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
616
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
617
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
618
+
619
+ # ===================== Denoising Loop ========================
620
+
621
+ for i in reversed(range(1, sample_steps + 1)):
622
+
623
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
624
+
625
+ # guided sampling
626
+ pred, logp = self.guided_sampling(
627
+ xt, t, alphas[i], sigmas[i],
628
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
629
+
630
+ # clip the prediction
631
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
632
+
633
+ # transform to eps_theta
634
+ x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
635
+
636
+ # one-step update
637
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
638
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
639
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
640
+
641
+ # fix the known portion, and preserve the sampling history
642
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
643
+ if preserve_history:
644
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
645
+
646
+ # ================= Post-processing =================
647
+ if self.classifier is not None:
648
+ with torch.no_grad():
649
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
650
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
651
+ log["log_p"] = logp
652
+
653
+ if self.clip_pred:
654
+ xt = xt.clip(self.x_min, self.x_max)
655
+
656
+ return xt, log
657
+
658
+ def ode_dpm_solver_pp_2M(
659
+ self,
660
+ # ---------- the known fixed portion ---------- #
661
+ prior: torch.Tensor,
662
+ # ----------------- sampling ----------------- #
663
+ n_samples: int = 1,
664
+ sample_steps: int = 5,
665
+ sample_step_schedule: Union[str, Callable] = "uniform",
666
+ use_ema: bool = True,
667
+ temperature: float = 1.0,
668
+ # ------------------ guidance ------------------ #
669
+ condition_cfg=None,
670
+ mask_cfg=None,
671
+ w_cfg: float = 0.0,
672
+ condition_cg=None,
673
+ w_cg: float = 0.0,
674
+ # ------------------ others ------------------ #
675
+ requires_grad: bool = False,
676
+ preserve_history: bool = False,
677
+ **kwargs,
678
+ ):
679
+
680
+ # ===================== Initialization =====================
681
+ log = {
682
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
683
+
684
+ model = self.model if not use_ema else self.model_ema
685
+
686
+ xt = torch.randn_like(prior, device=self.device) * temperature
687
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
688
+ if preserve_history:
689
+ log["sample_history"][:, 0] = xt.cpu().numpy()
690
+
691
+ with torch.set_grad_enabled(requires_grad):
692
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
693
+ condition_vec_cg = condition_cg
694
+
695
+ # ===================== Sampling Schedule ====================
696
+ if isinstance(sample_step_schedule, str):
697
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
698
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
699
+ self.diffusion_steps, sample_steps)
700
+ else:
701
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
702
+ elif callable(sample_step_schedule):
703
+ sample_step_schedule = sample_step_schedule(self.diffusion_steps, sample_steps)
704
+ else:
705
+ raise ValueError("sample_step_schedule must be a callable or a string")
706
+
707
+ alphas = self.alpha[sample_step_schedule]
708
+ sigmas = self.sigma[sample_step_schedule]
709
+ logSNRs = torch.log(alphas / sigmas)
710
+ hs = torch.zeros_like(logSNRs)
711
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
712
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
713
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
714
+
715
+ buffer = []
716
+
717
+ # ===================== Denoising Loop ========================
718
+
719
+ for i in reversed(range(1, sample_steps + 1)):
720
+
721
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.long, device=self.device)
722
+
723
+ # guided sampling
724
+ pred, logp = self.guided_sampling(
725
+ xt, t, alphas[i], sigmas[i],
726
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
727
+
728
+ # clip the prediction
729
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
730
+
731
+ # transform to x_theta
732
+ x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
733
+
734
+ buffer.append(x_theta)
735
+
736
+ # one-step update
737
+ if i < sample_steps:
738
+ r = hs[i + 1] / hs[i]
739
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
740
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
741
+ else:
742
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
743
+
744
+ # fix the known portion, and preserve the sampling history
745
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
746
+ if preserve_history:
747
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
748
+
749
+ # ================= Post-processing =================
750
+ if self.classifier is not None:
751
+ with torch.no_grad():
752
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
753
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
754
+ log["log_p"] = logp
755
+
756
+ if self.clip_pred:
757
+ xt = xt.clip(self.x_min, self.x_max)
758
+
759
+ return xt, log
760
+
761
+
762
+ class ContinuousVPSDE(DiffusionModel):
763
+
764
+ def __init__(
765
+ self,
766
+
767
+ # ----------------- Neural Networks ----------------- #
768
+ nn_diffusion: BaseNNDiffusion,
769
+ nn_condition: Optional[BaseNNCondition] = None,
770
+
771
+ # ----------------- Masks ----------------- #
772
+ # Fix some portion of the input data, and only allow the diffusion model to complete the rest part.
773
+ fix_mask: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
774
+ # Add loss weight
775
+ loss_weight: Union[list, np.ndarray, torch.Tensor] = None, # be in the shape of `x_shape`
776
+
777
+ # ------------------ Plugins ---------------- #
778
+ # Add a classifier to enable classifier-guidance
779
+ classifier: Optional[BaseClassifier] = None,
780
+
781
+ # ------------------ Training Params ---------------- #
782
+ grad_clip_norm: Optional[float] = None,
783
+ ema_rate: float = 0.995,
784
+ optim_params: Optional[dict] = None,
785
+
786
+ # ------------------- Diffusion Params ------------------- #
787
+ epsilon: float = 1e-3,
788
+
789
+ discretization: Union[str, Callable] = "uniform",
790
+ noise_schedule: Union[str, Dict[str, Callable]] = "cosine",
791
+ noise_schedule_params: Optional[dict] = None,
792
+
793
+ x_max: Optional[torch.Tensor] = None,
794
+ x_min: Optional[torch.Tensor] = None,
795
+
796
+ predict_noise: bool = True,
797
+
798
+ device: Union[torch.device, str] = "cpu"
799
+ ):
800
+ super().__init__(
801
+ nn_diffusion, nn_condition, fix_mask, loss_weight, classifier, grad_clip_norm,
802
+ 0, ema_rate, optim_params, device)
803
+
804
+ self.predict_noise = predict_noise
805
+ self.epsilon = epsilon
806
+ self.x_max, self.x_min = x_max, x_min
807
+
808
+ # ==================== Continuous Time-step Range ====================
809
+ if noise_schedule == "cosine":
810
+ self.t_diffusion = [epsilon, 0.9946]
811
+ else:
812
+ self.t_diffusion = [epsilon, 1.]
813
+
814
+ # ===================== Noise Schedule ======================
815
+ if isinstance(noise_schedule, str):
816
+ if noise_schedule in SUPPORTED_NOISE_SCHEDULES.keys():
817
+ self.noise_schedule_funcs = SUPPORTED_NOISE_SCHEDULES[noise_schedule]
818
+ self.noise_schedule_params = noise_schedule_params
819
+ else:
820
+ raise ValueError(f"Noise schedule {noise_schedule} is not supported.")
821
+ elif isinstance(noise_schedule, dict):
822
+ self.noise_schedule_funcs = noise_schedule
823
+ self.noise_schedule_params = noise_schedule_params
824
+ else:
825
+ raise ValueError("noise_schedule must be a callable or a string")
826
+
827
+ @property
828
+ def clip_pred(self):
829
+ return (self.x_max is not None) or (self.x_min is not None)
830
+
831
+ # ==================== Training: Score Matching ======================
832
+
833
+ def add_noise(self, x0, t=None, eps=None):
834
+
835
+ t = (torch.rand((x0.shape[0],), device=self.device) *
836
+ (self.t_diffusion[1] - self.t_diffusion[0]) + self.t_diffusion[0]) if t is None else t
837
+
838
+ eps = torch.randn_like(x0) if eps is None else eps
839
+
840
+ alpha, sigma = self.noise_schedule_funcs["forward"](t, **(self.noise_schedule_params or {}))
841
+ alpha = at_least_ndim(alpha, x0.dim())
842
+ sigma = at_least_ndim(sigma, x0.dim())
843
+
844
+ xt = alpha * x0 + sigma * eps
845
+ xt = (1. - self.fix_mask) * xt + self.fix_mask * x0
846
+
847
+ return xt, t, eps
848
+
849
+ def loss(self, x0, condition=None):
850
+
851
+ xt, t, eps = self.add_noise(x0)
852
+
853
+ condition = self.model["condition"](condition) if condition is not None else None
854
+
855
+ if self.predict_noise:
856
+ loss = (self.model["diffusion"](xt, t, condition) - eps) ** 2
857
+ else:
858
+ loss = (self.model["diffusion"](xt, t, condition) - x0) ** 2
859
+
860
+ return (loss * self.loss_weight * (1 - self.fix_mask)).mean()
861
+
862
+ def update(self, x0, condition=None, update_ema=True, **kwargs):
863
+
864
+ loss = self.loss(x0, condition)
865
+
866
+ loss.backward()
867
+ grad_norm = nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip_norm) \
868
+ if self.grad_clip_norm else None
869
+ self.optimizer.step()
870
+ self.optimizer.zero_grad()
871
+
872
+ if update_ema:
873
+ self.ema_update()
874
+
875
+ log = {"loss": loss.item(), "grad_norm": grad_norm}
876
+
877
+ return log
878
+
879
+ def update_classifier(self, x0, condition):
880
+
881
+ xt, t, eps = self.add_noise(x0)
882
+
883
+ log = self.classifier.update(xt, t, condition)
884
+
885
+ return log
886
+
887
+ # ==================== Sampling: Solving SDE/ODE ======================
888
+
889
+ def classifier_guidance(
890
+ self, xt, t, alpha, sigma,
891
+ model, condition=None, w: float = 1.0,
892
+ pred=None):
893
+ """
894
+ Guided Sampling CG:
895
+ bar_eps = eps - w * sigma * grad
896
+ bar_x0 = x0 + w * (sigma ** 2) * alpha * grad
897
+ """
898
+ if pred is None:
899
+ pred = model["diffusion"](xt, t, None)
900
+ if self.classifier is None or w == 0.0 or condition is None:
901
+ return pred, None
902
+ else:
903
+ log_p, grad = self.classifier.gradients(xt.clone(), t, condition)
904
+ if self.predict_noise:
905
+ pred = pred - w * sigma * grad
906
+ else:
907
+ pred = pred + w * ((sigma ** 2) / alpha) * grad
908
+
909
+ return pred, log_p
910
+
911
+ def classifier_free_guidance(
912
+ self, xt, t,
913
+ model, condition=None, w: float = 1.0,
914
+ pred=None, pred_uncond=None,
915
+ requires_grad: bool = False):
916
+ """
917
+ Guided Sampling CFG:
918
+ bar_eps = w * pred + (1 - w) * pred_uncond
919
+ bar_x0 = w * pred + (1 - w) * pred_uncond
920
+ """
921
+ with torch.set_grad_enabled(requires_grad):
922
+ if w != 0.0 and w != 1.0:
923
+ if pred is None or pred_uncond is None:
924
+ b = xt.shape[0]
925
+ repeat_dim = [2 if i == 0 else 1 for i in range(xt.dim())]
926
+ condition = torch.cat([condition, torch.zeros_like(condition)], 0)
927
+ pred_all = model["diffusion"](
928
+ xt.repeat(*repeat_dim), t.repeat(2), condition)
929
+ pred, pred_uncond = pred_all[:b], pred_all[b:]
930
+ elif w == 0.0:
931
+ pred = 0.
932
+ pred_uncond = model["diffusion"](xt, t, None)
933
+ else:
934
+ pred = model["diffusion"](xt, t, condition)
935
+ pred_uncond = 0.
936
+
937
+ if self.predict_noise or not self.predict_noise:
938
+ bar_pred = w * pred + (1 - w) * pred_uncond
939
+ else:
940
+ bar_pred = pred
941
+
942
+ return bar_pred
943
+
944
+ def clip_prediction(self, pred, xt, alpha, sigma):
945
+ """
946
+ Clip the prediction at each sampling step to stablize the generation.
947
+ (xt - alpha * x_max) / sigma <= eps <= (xt - alpha * x_min) / sigma
948
+ x_min <= x0 <= x_max
949
+ """
950
+ if self.clip_pred:
951
+ if self.predict_noise:
952
+ upper_bound = (xt - alpha * self.x_min) / sigma if self.x_min is not None else None
953
+ lower_bound = (xt - alpha * self.x_max) / sigma if self.x_max is not None else None
954
+ pred = pred.clip(lower_bound, upper_bound)
955
+ else:
956
+ pred = pred.clip(self.x_min, self.x_max)
957
+ return pred
958
+
959
+ def guided_sampling(
960
+ self, xt, t, alpha, sigma,
961
+ model,
962
+ condition_cfg=None, w_cfg: float = 0.0,
963
+ condition_cg=None, w_cg: float = 0.0,
964
+ requires_grad: bool = False):
965
+ """
966
+ One-step epsilon/x0 prediction with guidance.
967
+ """
968
+
969
+ pred = self.classifier_free_guidance(
970
+ xt, t, model, condition_cfg, w_cfg, None, None, requires_grad)
971
+
972
+ pred, logp = self.classifier_guidance(
973
+ xt, t, alpha, sigma, model, condition_cg, w_cg, pred)
974
+
975
+ return pred, logp
976
+
977
+ def sample(
978
+ self,
979
+ # ---------- the known fixed portion ---------- #
980
+ prior: torch.Tensor,
981
+ # ----------------- sampling ----------------- #
982
+ solver: str = "ddpm",
983
+ n_samples: int = 1,
984
+ sample_steps: int = 5,
985
+ sample_step_schedule: Union[str, Callable] = "uniform_continuous",
986
+ use_ema: bool = True,
987
+ temperature: float = 1.0,
988
+ # ------------------ guidance ------------------ #
989
+ condition_cfg=None,
990
+ mask_cfg=None,
991
+ w_cfg: float = 0.0,
992
+ condition_cg=None,
993
+ w_cg: float = 0.0,
994
+ # ------------------ others ------------------ #
995
+ requires_grad: bool = False,
996
+ preserve_history: bool = False,
997
+ **kwargs,
998
+ ):
999
+ assert solver in SUPPORTED_SOLVERS, f"Solver {solver} is not supported."
1000
+
1001
+ # ===================== Initialization =====================
1002
+ log = {
1003
+ "sample_history": np.empty((n_samples, sample_steps + 1, *prior.shape)) if preserve_history else None, }
1004
+
1005
+ model = self.model if not use_ema else self.model_ema9999
1006
+
1007
+ xt = torch.randn_like(prior, device=self.device) * temperature
1008
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
1009
+ if preserve_history:
1010
+ log["sample_history"][:, 0] = xt.cpu().numpy()
1011
+
1012
+ with torch.set_grad_enabled(requires_grad):
1013
+ condition_vec_cfg = model["condition"](condition_cfg, mask_cfg) if condition_cfg is not None else None
1014
+ condition_vec_cg = condition_cg
1015
+
1016
+ # ===================== Sampling Schedule ====================
1017
+ if isinstance(sample_step_schedule, str):
1018
+ if sample_step_schedule in SUPPORTED_SAMPLING_STEP_SCHEDULE.keys():
1019
+ sample_step_schedule = SUPPORTED_SAMPLING_STEP_SCHEDULE[sample_step_schedule](
1020
+ self.t_diffusion, sample_steps)
1021
+ else:
1022
+ raise ValueError(f"Sampling step schedule {sample_step_schedule} is not supported.")
1023
+ elif callable(sample_step_schedule):
1024
+ sample_step_schedule = sample_step_schedule(self.t_diffusion, sample_steps)
1025
+ else:
1026
+ raise ValueError("sample_step_schedule must be a callable or a string")
1027
+
1028
+ alphas, sigmas = self.noise_schedule_funcs["forward"](
1029
+ sample_step_schedule, **(self.noise_schedule_params or {}))
1030
+ logSNRs = torch.log(alphas / sigmas)
1031
+ hs = torch.zeros_like(logSNRs)
1032
+ hs[1:] = logSNRs[:-1] - logSNRs[1:] # hs[0] is not correctly calculated, but it will not be used.
1033
+ stds = torch.zeros((sample_steps + 1,), device=self.device)
1034
+ stds[1:] = sigmas[:-1] / sigmas[1:] * (1 - (alphas[1:] / alphas[:-1]) ** 2).sqrt()
1035
+
1036
+ buffer = []
1037
+
1038
+ # ===================== Denoising Loop ========================
1039
+
1040
+ for i in reversed(range(1, sample_steps + 1)):
1041
+
1042
+ t = torch.full((n_samples,), sample_step_schedule[i], dtype=torch.float32, device=self.device)
1043
+
1044
+ # guided sampling
1045
+ pred, logp = self.guided_sampling(
1046
+ xt, t, alphas[i], sigmas[i],
1047
+ model, condition_vec_cfg, w_cfg, condition_vec_cg, w_cg, requires_grad)
1048
+
1049
+ # clip the prediction
1050
+ pred = self.clip_prediction(pred, xt, alphas[i], sigmas[i])
1051
+
1052
+ # transform to eps_theta
1053
+ eps_theta = pred if self.predict_noise else xtheta_to_epstheta(xt, alphas[i], sigmas[i], pred)
1054
+ x_theta = pred if not self.predict_noise else epstheta_to_xtheta(xt, alphas[i], sigmas[i], pred)
1055
+
1056
+ # one-step update
1057
+ if solver == "ddpm":
1058
+ xt = (
1059
+ (alphas[i - 1] / alphas[i]) * (xt - sigmas[i] * eps_theta) +
1060
+ (sigmas[i - 1] ** 2 - stds[i] ** 2 + 1e-8).sqrt() * eps_theta)
1061
+ if i > 1:
1062
+ xt += (stds[i] * torch.randn_like(xt))
1063
+
1064
+ elif solver == "ddim":
1065
+ xt = (alphas[i - 1] * ((xt - sigmas[i] * eps_theta) / alphas[i]) + sigmas[i - 1] * eps_theta)
1066
+
1067
+ elif solver == "ode_dpmsolver++_1":
1068
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
1069
+
1070
+ elif solver == "sde_dpmsolver++_1":
1071
+ xt = ((sigmas[i - 1] / sigmas[i]) * (-hs[i]).exp() * xt -
1072
+ alphas[i - 1] * torch.expm1(-2 * hs[i]) * x_theta +
1073
+ sigmas[i - 1] * (-torch.expm1(-2 * hs[i])).sqrt() * torch.randn_like(xt))
1074
+
1075
+ elif solver == "ode_dpmsolver++_2M":
1076
+ buffer.append(x_theta)
1077
+ if i < sample_steps:
1078
+ r = hs[i + 1] / hs[i]
1079
+ D = (1 + 0.5 / r) * buffer[-1] - 0.5 / r * buffer[-2]
1080
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * D
1081
+ else:
1082
+ xt = (sigmas[i - 1] / sigmas[i]) * xt - alphas[i - 1] * torch.expm1(-hs[i]) * x_theta
1083
+
1084
+ # fix the known portion, and preserve the sampling history
1085
+ xt = xt * (1. - self.fix_mask) + prior * self.fix_mask
1086
+ if preserve_history:
1087
+ log["sample_history"][:, sample_steps - i + 1] = xt.cpu().numpy()
1088
+
1089
+ # ================= Post-processing =================
1090
+ if self.classifier is not None:
1091
+ with torch.no_grad():
1092
+ t = torch.zeros((n_samples,), dtype=torch.long, device=self.device)
1093
+ logp = self.classifier.logp(xt, t, condition_vec_cg)
1094
+ log["log_p"] = logp
1095
+
1096
+ if self.clip_pred:
1097
+ xt = xt.clip(self.x_min, self.x_max)
1098
+
1099
+ return xt, log
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/async_vector_env.py ADDED
@@ -0,0 +1,671 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Back ported methods: call, set_attr from v0.26
3
+ Disabled auto-reset after done
4
+ Added render method.
5
+ """
6
+
7
+
8
+ import numpy as np
9
+ import multiprocessing as mp
10
+ import time
11
+ import sys
12
+ from enum import Enum
13
+ from copy import deepcopy
14
+
15
+ from gym import logger
16
+ from gym.vector.vector_env import VectorEnv
17
+ from gym.error import (
18
+ AlreadyPendingCallError,
19
+ NoAsyncCallError,
20
+ ClosedEnvironmentError,
21
+ CustomSpaceError,
22
+ )
23
+ from gym.vector.utils import (
24
+ create_shared_memory,
25
+ create_empty_array,
26
+ write_to_shared_memory,
27
+ read_from_shared_memory,
28
+ concatenate,
29
+ CloudpickleWrapper,
30
+ clear_mpi_env_vars,
31
+ )
32
+
33
+ __all__ = ["AsyncVectorEnv"]
34
+
35
+
36
+ class AsyncState(Enum):
37
+ DEFAULT = "default"
38
+ WAITING_RESET = "reset"
39
+ WAITING_STEP = "step"
40
+ WAITING_CALL = "call"
41
+
42
+
43
+ class AsyncVectorEnv(VectorEnv):
44
+ """Vectorized environment that runs multiple environments in parallel. It
45
+ uses `multiprocessing` processes, and pipes for communication.
46
+ Parameters
47
+ ----------
48
+ env_fns : iterable of callable
49
+ Functions that create the environments.
50
+ observation_space : `gym.spaces.Space` instance, optional
51
+ Observation space of a single environment. If `None`, then the
52
+ observation space of the first environment is taken.
53
+ action_space : `gym.spaces.Space` instance, optional
54
+ Action space of a single environment. If `None`, then the action space
55
+ of the first environment is taken.
56
+ shared_memory : bool (default: `True`)
57
+ If `True`, then the observations from the worker processes are
58
+ communicated back through shared variables. This can improve the
59
+ efficiency if the observations are large (e.g. images).
60
+ copy : bool (default: `True`)
61
+ If `True`, then the `reset` and `step` methods return a copy of the
62
+ observations.
63
+ context : str, optional
64
+ Context for multiprocessing. If `None`, then the default context is used.
65
+ Only available in Python 3.
66
+ daemon : bool (default: `True`)
67
+ If `True`, then subprocesses have `daemon` flag turned on; that is, they
68
+ will quit if the head process quits. However, `daemon=True` prevents
69
+ subprocesses to spawn children, so for some environments you may want
70
+ to have it set to `False`
71
+ worker : function, optional
72
+ WARNING - advanced mode option! If set, then use that worker in a subprocess
73
+ instead of a default one. Can be useful to override some inner vector env
74
+ logic, for instance, how resets on done are handled. Provides high
75
+ degree of flexibility and a high chance to shoot yourself in the foot; thus,
76
+ if you are writing your own worker, it is recommended to start from the code
77
+ for `_worker` (or `_worker_shared_memory`) method below, and add changes
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ env_fns,
83
+ dummy_env_fn=None,
84
+ observation_space=None,
85
+ action_space=None,
86
+ shared_memory=True,
87
+ copy=True,
88
+ context=None,
89
+ daemon=True,
90
+ worker=None,
91
+ ):
92
+ ctx = mp.get_context(context)
93
+ self.env_fns = env_fns
94
+ self.shared_memory = shared_memory
95
+ self.copy = copy
96
+
97
+ # Added dummy_env_fn to fix OpenGL error in Mujoco
98
+ # disable any OpenGL rendering in dummy_env_fn, since it
99
+ # will conflict with OpenGL context in the forked child process
100
+ if dummy_env_fn is None:
101
+ dummy_env_fn = env_fns[0]
102
+ dummy_env = dummy_env_fn()
103
+ self.metadata = dummy_env.metadata
104
+
105
+ if (observation_space is None) or (action_space is None):
106
+ observation_space = observation_space or dummy_env.observation_space
107
+ action_space = action_space or dummy_env.action_space
108
+ dummy_env.close()
109
+ del dummy_env
110
+ super(AsyncVectorEnv, self).__init__(
111
+ num_envs=len(env_fns),
112
+ observation_space=observation_space,
113
+ action_space=action_space,
114
+ )
115
+
116
+ if self.shared_memory:
117
+ try:
118
+ _obs_buffer = create_shared_memory(
119
+ self.single_observation_space, n=self.num_envs, ctx=ctx
120
+ )
121
+ self.observations = read_from_shared_memory(
122
+ _obs_buffer, self.single_observation_space, n=self.num_envs
123
+ )
124
+ except CustomSpaceError:
125
+ raise ValueError(
126
+ "Using `shared_memory=True` in `AsyncVectorEnv` "
127
+ "is incompatible with non-standard Gym observation spaces "
128
+ "(i.e. custom spaces inheriting from `gym.Space`), and is "
129
+ "only compatible with default Gym spaces (e.g. `Box`, "
130
+ "`Tuple`, `Dict`) for batching. Set `shared_memory=False` "
131
+ "if you use custom observation spaces."
132
+ )
133
+ else:
134
+ _obs_buffer = None
135
+ self.observations = create_empty_array(
136
+ self.single_observation_space, n=self.num_envs, fn=np.zeros
137
+ )
138
+
139
+ self.parent_pipes, self.processes = [], []
140
+ self.error_queue = ctx.Queue()
141
+ target = _worker_shared_memory if self.shared_memory else _worker
142
+ target = worker or target
143
+ with clear_mpi_env_vars():
144
+ for idx, env_fn in enumerate(self.env_fns):
145
+ parent_pipe, child_pipe = ctx.Pipe()
146
+ process = ctx.Process(
147
+ target=target,
148
+ name="Worker<{0}>-{1}".format(type(self).__name__, idx),
149
+ args=(
150
+ idx,
151
+ CloudpickleWrapper(env_fn),
152
+ child_pipe,
153
+ parent_pipe,
154
+ _obs_buffer,
155
+ self.error_queue,
156
+ ),
157
+ )
158
+
159
+ self.parent_pipes.append(parent_pipe)
160
+ self.processes.append(process)
161
+
162
+ process.daemon = daemon
163
+ process.start()
164
+ child_pipe.close()
165
+
166
+ self._state = AsyncState.DEFAULT
167
+ self._check_observation_spaces()
168
+
169
+ def seed(self, seeds=None):
170
+ self._assert_is_running()
171
+ if seeds is None:
172
+ seeds = [None for _ in range(self.num_envs)]
173
+ if isinstance(seeds, int):
174
+ seeds = [seeds + i for i in range(self.num_envs)]
175
+ assert len(seeds) == self.num_envs
176
+
177
+ if self._state != AsyncState.DEFAULT:
178
+ raise AlreadyPendingCallError(
179
+ "Calling `seed` while waiting "
180
+ "for a pending call to `{0}` to complete.".format(self._state.value),
181
+ self._state.value,
182
+ )
183
+
184
+ for pipe, seed in zip(self.parent_pipes, seeds):
185
+ pipe.send(("seed", seed))
186
+ _, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
187
+ self._raise_if_errors(successes)
188
+
189
+ def reset_async(self):
190
+ self._assert_is_running()
191
+ if self._state != AsyncState.DEFAULT:
192
+ raise AlreadyPendingCallError(
193
+ "Calling `reset_async` while waiting "
194
+ "for a pending call to `{0}` to complete".format(self._state.value),
195
+ self._state.value,
196
+ )
197
+
198
+ for pipe in self.parent_pipes:
199
+ pipe.send(("reset", None))
200
+ self._state = AsyncState.WAITING_RESET
201
+
202
+ def reset_wait(self, timeout=None):
203
+ """
204
+ Parameters
205
+ ----------
206
+ timeout : int or float, optional
207
+ Number of seconds before the call to `reset_wait` times out. If
208
+ `None`, the call to `reset_wait` never times out.
209
+ Returns
210
+ -------
211
+ observations : sample from `observation_space`
212
+ A batch of observations from the vectorized environment.
213
+ """
214
+ self._assert_is_running()
215
+ if self._state != AsyncState.WAITING_RESET:
216
+ raise NoAsyncCallError(
217
+ "Calling `reset_wait` without any prior " "call to `reset_async`.",
218
+ AsyncState.WAITING_RESET.value,
219
+ )
220
+
221
+ if not self._poll(timeout):
222
+ self._state = AsyncState.DEFAULT
223
+ raise mp.TimeoutError(
224
+ "The call to `reset_wait` has timed out after "
225
+ "{0} second{1}.".format(timeout, "s" if timeout > 1 else "")
226
+ )
227
+
228
+ results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
229
+ self._raise_if_errors(successes)
230
+ self._state = AsyncState.DEFAULT
231
+
232
+ if not self.shared_memory:
233
+ self.observations = concatenate(
234
+ results, self.observations, self.single_observation_space
235
+ )
236
+
237
+ return deepcopy(self.observations) if self.copy else self.observations
238
+
239
+ def step_async(self, actions):
240
+ """
241
+ Parameters
242
+ ----------
243
+ actions : iterable of samples from `action_space`
244
+ List of actions.
245
+ """
246
+ self._assert_is_running()
247
+ if self._state != AsyncState.DEFAULT:
248
+ raise AlreadyPendingCallError(
249
+ "Calling `step_async` while waiting "
250
+ "for a pending call to `{0}` to complete.".format(self._state.value),
251
+ self._state.value,
252
+ )
253
+
254
+ for pipe, action in zip(self.parent_pipes, actions):
255
+ pipe.send(("step", action))
256
+ self._state = AsyncState.WAITING_STEP
257
+
258
+ def step_wait(self, timeout=None):
259
+ """
260
+ Parameters
261
+ ----------
262
+ timeout : int or float, optional
263
+ Number of seconds before the call to `step_wait` times out. If
264
+ `None`, the call to `step_wait` never times out.
265
+ Returns
266
+ -------
267
+ observations : sample from `observation_space`
268
+ A batch of observations from the vectorized environment.
269
+ rewards : `np.ndarray` instance (dtype `np.float_`)
270
+ A vector of rewards from the vectorized environment.
271
+ dones : `np.ndarray` instance (dtype `np.bool_`)
272
+ A vector whose entries indicate whether the episode has ended.
273
+ infos : list of dict
274
+ A list of auxiliary diagnostic information.
275
+ """
276
+ self._assert_is_running()
277
+ if self._state != AsyncState.WAITING_STEP:
278
+ raise NoAsyncCallError(
279
+ "Calling `step_wait` without any prior call " "to `step_async`.",
280
+ AsyncState.WAITING_STEP.value,
281
+ )
282
+
283
+ if not self._poll(timeout):
284
+ self._state = AsyncState.DEFAULT
285
+ raise mp.TimeoutError(
286
+ "The call to `step_wait` has timed out after "
287
+ "{0} second{1}.".format(timeout, "s" if timeout > 1 else "")
288
+ )
289
+
290
+ results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
291
+ self._raise_if_errors(successes)
292
+ self._state = AsyncState.DEFAULT
293
+ observations_list, rewards, dones, infos = zip(*results)
294
+
295
+ if not self.shared_memory:
296
+ self.observations = concatenate(
297
+ observations_list, self.observations, self.single_observation_space
298
+ )
299
+
300
+ return (
301
+ deepcopy(self.observations) if self.copy else self.observations,
302
+ np.array(rewards),
303
+ np.array(dones, dtype=np.bool_),
304
+ infos,
305
+ )
306
+
307
+ def close_extras(self, timeout=None, terminate=False):
308
+ """
309
+ Parameters
310
+ ----------
311
+ timeout : int or float, optional
312
+ Number of seconds before the call to `close` times out. If `None`,
313
+ the call to `close` never times out. If the call to `close` times
314
+ out, then all processes are terminated.
315
+ terminate : bool (default: `False`)
316
+ If `True`, then the `close` operation is forced and all processes
317
+ are terminated.
318
+ """
319
+ timeout = 0 if terminate else timeout
320
+ try:
321
+ if self._state != AsyncState.DEFAULT:
322
+ logger.warn(
323
+ "Calling `close` while waiting for a pending "
324
+ "call to `{0}` to complete.".format(self._state.value)
325
+ )
326
+ function = getattr(self, "{0}_wait".format(self._state.value))
327
+ function(timeout)
328
+ except mp.TimeoutError:
329
+ terminate = True
330
+
331
+ if terminate:
332
+ for process in self.processes:
333
+ if process.is_alive():
334
+ process.terminate()
335
+ else:
336
+ for pipe in self.parent_pipes:
337
+ if (pipe is not None) and (not pipe.closed):
338
+ pipe.send(("close", None))
339
+ for pipe in self.parent_pipes:
340
+ if (pipe is not None) and (not pipe.closed):
341
+ pipe.recv()
342
+
343
+ for pipe in self.parent_pipes:
344
+ if pipe is not None:
345
+ pipe.close()
346
+ for process in self.processes:
347
+ process.join()
348
+
349
+ def _poll(self, timeout=None):
350
+ self._assert_is_running()
351
+ if timeout is None:
352
+ return True
353
+ end_time = time.perf_counter() + timeout
354
+ delta = None
355
+ for pipe in self.parent_pipes:
356
+ delta = max(end_time - time.perf_counter(), 0)
357
+ if pipe is None:
358
+ return False
359
+ if pipe.closed or (not pipe.poll(delta)):
360
+ return False
361
+ return True
362
+
363
+ def _check_observation_spaces(self):
364
+ self._assert_is_running()
365
+ for pipe in self.parent_pipes:
366
+ pipe.send(("_check_observation_space", self.single_observation_space))
367
+ same_spaces, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
368
+ self._raise_if_errors(successes)
369
+ if not all(same_spaces):
370
+ raise RuntimeError(
371
+ "Some environments have an observation space "
372
+ "different from `{0}`. In order to batch observations, the "
373
+ "observation spaces from all environments must be "
374
+ "equal.".format(self.single_observation_space)
375
+ )
376
+
377
+ def _assert_is_running(self):
378
+ if self.closed:
379
+ raise ClosedEnvironmentError(
380
+ "Trying to operate on `{0}`, after a "
381
+ "call to `close()`.".format(type(self).__name__)
382
+ )
383
+
384
+ def _raise_if_errors(self, successes):
385
+ if all(successes):
386
+ return
387
+
388
+ num_errors = self.num_envs - sum(successes)
389
+ assert num_errors > 0
390
+ for _ in range(num_errors):
391
+ index, exctype, value = self.error_queue.get()
392
+ logger.error(
393
+ "Received the following error from Worker-{0}: "
394
+ "{1}: {2}".format(index, exctype.__name__, value)
395
+ )
396
+ logger.error("Shutting down Worker-{0}.".format(index))
397
+ self.parent_pipes[index].close()
398
+ self.parent_pipes[index] = None
399
+
400
+ logger.error("Raising the last exception back to the main process.")
401
+ raise exctype(value)
402
+
403
+ def call_async(self, name: str, *args, **kwargs):
404
+ """Calls the method with name asynchronously and apply args and kwargs to the method.
405
+
406
+ Args:
407
+ name: Name of the method or property to call.
408
+ *args: Arguments to apply to the method call.
409
+ **kwargs: Keyword arguments to apply to the method call.
410
+
411
+ Raises:
412
+ ClosedEnvironmentError: If the environment was closed (if :meth:`close` was previously called).
413
+ AlreadyPendingCallError: Calling `call_async` while waiting for a pending call to complete
414
+ """
415
+ self._assert_is_running()
416
+ if self._state != AsyncState.DEFAULT:
417
+ raise AlreadyPendingCallError(
418
+ "Calling `call_async` while waiting "
419
+ f"for a pending call to `{self._state.value}` to complete.",
420
+ self._state.value,
421
+ )
422
+
423
+ for pipe in self.parent_pipes:
424
+ pipe.send(("_call", (name, args, kwargs)))
425
+ self._state = AsyncState.WAITING_CALL
426
+
427
+ def call_wait(self, timeout = None) -> list:
428
+ """Calls all parent pipes and waits for the results.
429
+
430
+ Args:
431
+ timeout: Number of seconds before the call to `step_wait` times out.
432
+ If `None` (default), the call to `step_wait` never times out.
433
+
434
+ Returns:
435
+ List of the results of the individual calls to the method or property for each environment.
436
+
437
+ Raises:
438
+ NoAsyncCallError: Calling `call_wait` without any prior call to `call_async`.
439
+ TimeoutError: The call to `call_wait` has timed out after timeout second(s).
440
+ """
441
+ self._assert_is_running()
442
+ if self._state != AsyncState.WAITING_CALL:
443
+ raise NoAsyncCallError(
444
+ "Calling `call_wait` without any prior call to `call_async`.",
445
+ AsyncState.WAITING_CALL.value,
446
+ )
447
+
448
+ if not self._poll(timeout):
449
+ self._state = AsyncState.DEFAULT
450
+ raise mp.TimeoutError(
451
+ f"The call to `call_wait` has timed out after {timeout} second(s)."
452
+ )
453
+
454
+ results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
455
+ self._raise_if_errors(successes)
456
+ self._state = AsyncState.DEFAULT
457
+
458
+ return results
459
+
460
+ def call(self, name: str, *args, **kwargs):
461
+ """Call a method, or get a property, from each parallel environment.
462
+
463
+ Args:
464
+ name (str): Name of the method or property to call.
465
+ *args: Arguments to apply to the method call.
466
+ **kwargs: Keyword arguments to apply to the method call.
467
+
468
+ Returns:
469
+ List of the results of the individual calls to the method or property for each environment.
470
+ """
471
+ self.call_async(name, *args, **kwargs)
472
+ return self.call_wait()
473
+
474
+
475
+ def call_each(self, name: str,
476
+ args_list: list=None,
477
+ kwargs_list: list=None,
478
+ timeout = None):
479
+ n_envs = len(self.parent_pipes)
480
+ if args_list is None:
481
+ args_list = [[]] * n_envs
482
+ assert len(args_list) == n_envs
483
+
484
+ if kwargs_list is None:
485
+ kwargs_list = [dict()] * n_envs
486
+ assert len(kwargs_list) == n_envs
487
+
488
+ # send
489
+ self._assert_is_running()
490
+ if self._state != AsyncState.DEFAULT:
491
+ raise AlreadyPendingCallError(
492
+ "Calling `call_async` while waiting "
493
+ f"for a pending call to `{self._state.value}` to complete.",
494
+ self._state.value,
495
+ )
496
+
497
+ for i, pipe in enumerate(self.parent_pipes):
498
+ pipe.send(("_call", (name, args_list[i], kwargs_list[i])))
499
+ self._state = AsyncState.WAITING_CALL
500
+
501
+ # receive
502
+ self._assert_is_running()
503
+ if self._state != AsyncState.WAITING_CALL:
504
+ raise NoAsyncCallError(
505
+ "Calling `call_wait` without any prior call to `call_async`.",
506
+ AsyncState.WAITING_CALL.value,
507
+ )
508
+
509
+ if not self._poll(timeout):
510
+ self._state = AsyncState.DEFAULT
511
+ raise mp.TimeoutError(
512
+ f"The call to `call_wait` has timed out after {timeout} second(s)."
513
+ )
514
+
515
+ results, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
516
+ self._raise_if_errors(successes)
517
+ self._state = AsyncState.DEFAULT
518
+
519
+ return results
520
+
521
+
522
+ def set_attr(self, name: str, values):
523
+ """Sets an attribute of the sub-environments.
524
+
525
+ Args:
526
+ name: Name of the property to be set in each individual environment.
527
+ values: Values of the property to be set to. If ``values`` is a list or
528
+ tuple, then it corresponds to the values for each individual
529
+ environment, otherwise a single value is set for all environments.
530
+
531
+ Raises:
532
+ ValueError: Values must be a list or tuple with length equal to the number of environments.
533
+ AlreadyPendingCallError: Calling `set_attr` while waiting for a pending call to complete.
534
+ """
535
+ self._assert_is_running()
536
+ if not isinstance(values, (list, tuple)):
537
+ values = [values for _ in range(self.num_envs)]
538
+ if len(values) != self.num_envs:
539
+ raise ValueError(
540
+ "Values must be a list or tuple with length equal to the "
541
+ f"number of environments. Got `{len(values)}` values for "
542
+ f"{self.num_envs} environments."
543
+ )
544
+
545
+ if self._state != AsyncState.DEFAULT:
546
+ raise AlreadyPendingCallError(
547
+ "Calling `set_attr` while waiting "
548
+ f"for a pending call to `{self._state.value}` to complete.",
549
+ self._state.value,
550
+ )
551
+
552
+ for pipe, value in zip(self.parent_pipes, values):
553
+ pipe.send(("_setattr", (name, value)))
554
+ _, successes = zip(*[pipe.recv() for pipe in self.parent_pipes])
555
+ self._raise_if_errors(successes)
556
+
557
+ def render(self, *args, **kwargs):
558
+ return self.call('render', *args, **kwargs)
559
+
560
+
561
+
562
+ def _worker(index, env_fn, pipe, parent_pipe, shared_memory, error_queue):
563
+ assert shared_memory is None
564
+ env = env_fn()
565
+ parent_pipe.close()
566
+ try:
567
+ while True:
568
+ command, data = pipe.recv()
569
+ if command == "reset":
570
+ observation = env.reset()
571
+ pipe.send((observation, True))
572
+ elif command == "step":
573
+ observation, reward, done, info = env.step(data)
574
+ # if done:
575
+ # observation = env.reset()
576
+ pipe.send(((observation, reward, done, info), True))
577
+ elif command == "seed":
578
+ env.seed(data)
579
+ pipe.send((None, True))
580
+ elif command == "close":
581
+ pipe.send((None, True))
582
+ break
583
+ elif command == "_call":
584
+ name, args, kwargs = data
585
+ if name in ["reset", "step", "seed", "close"]:
586
+ raise ValueError(
587
+ f"Trying to call function `{name}` with "
588
+ f"`_call`. Use `{name}` directly instead."
589
+ )
590
+ function = getattr(env, name)
591
+ if callable(function):
592
+ pipe.send((function(*args, **kwargs), True))
593
+ else:
594
+ pipe.send((function, True))
595
+ elif command == "_setattr":
596
+ name, value = data
597
+ setattr(env, name, value)
598
+ pipe.send((None, True))
599
+
600
+ elif command == "_check_observation_space":
601
+ pipe.send((data == env.observation_space, True))
602
+ else:
603
+ raise RuntimeError(
604
+ "Received unknown command `{0}`. Must "
605
+ "be one of {`reset`, `step`, `seed`, `close`, "
606
+ "`_check_observation_space`}.".format(command)
607
+ )
608
+ except (KeyboardInterrupt, Exception):
609
+ error_queue.put((index,) + sys.exc_info()[:2])
610
+ pipe.send((None, False))
611
+ finally:
612
+ env.close()
613
+
614
+
615
+ def _worker_shared_memory(index, env_fn, pipe, parent_pipe, shared_memory, error_queue):
616
+ assert shared_memory is not None
617
+ env = env_fn()
618
+ observation_space = env.observation_space
619
+ parent_pipe.close()
620
+ try:
621
+ while True:
622
+ command, data = pipe.recv()
623
+ if command == "reset":
624
+ observation = env.reset()
625
+ write_to_shared_memory(
626
+ index, observation, shared_memory, observation_space
627
+ )
628
+ pipe.send((None, True))
629
+ elif command == "step":
630
+ observation, reward, done, info = env.step(data)
631
+ # if done:
632
+ # observation = env.reset()
633
+ write_to_shared_memory(
634
+ index, observation, shared_memory, observation_space
635
+ )
636
+ pipe.send(((None, reward, done, info), True))
637
+ elif command == "seed":
638
+ env.seed(data)
639
+ pipe.send((None, True))
640
+ elif command == "close":
641
+ pipe.send((None, True))
642
+ break
643
+ elif command == "_call":
644
+ name, args, kwargs = data
645
+ if name in ["reset", "step", "seed", "close"]:
646
+ raise ValueError(
647
+ f"Trying to call function `{name}` with "
648
+ f"`_call`. Use `{name}` directly instead."
649
+ )
650
+ function = getattr(env, name)
651
+ if callable(function):
652
+ pipe.send((function(*args, **kwargs), True))
653
+ else:
654
+ pipe.send((function, True))
655
+ elif command == "_setattr":
656
+ name, value = data
657
+ setattr(env, name, value)
658
+ pipe.send((None, True))
659
+ elif command == "_check_observation_space":
660
+ pipe.send((data == observation_space, True))
661
+ else:
662
+ raise RuntimeError(
663
+ "Received unknown command `{0}`. Must "
664
+ "be one of {`reset`, `step`, `seed`, `close`, "
665
+ "`_check_observation_space`}.".format(command)
666
+ )
667
+ except (KeyboardInterrupt, Exception):
668
+ error_queue.put((index,) + sys.exc_info()[:2])
669
+ pipe.send((None, False))
670
+ finally:
671
+ env.close()
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from gym.envs.registration import register
2
+
3
+ register(
4
+ id='block-push-multi-modal-v0',
5
+ entry_point='cleandiffuser.env.block_pushing.block_pushing_multimodal:BlockPushMultimodal',
6
+ max_episode_steps=350,
7
+ )
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing.py ADDED
@@ -0,0 +1,1092 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Simple block environments for the XArm."""
17
+
18
+ import collections
19
+ import enum
20
+ import math
21
+ import time
22
+ from typing import Dict, List, Optional, Tuple, Union
23
+
24
+ import gym
25
+ from gym import spaces
26
+ from gym.envs import registration
27
+ from cleandiffuser.env.block_pushing.utils import utils_pybullet
28
+ from cleandiffuser.env.block_pushing.utils import xarm_sim_robot
29
+ from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
30
+ from cleandiffuser.env.block_pushing.utils.utils_pybullet import ObjState
31
+ from cleandiffuser.env.block_pushing.utils.utils_pybullet import XarmState
32
+ import numpy as np
33
+ from scipy.spatial import transform
34
+ import pybullet
35
+ import pybullet_utils.bullet_client as bullet_client
36
+
37
+ import matplotlib.pyplot as plt
38
+
39
+ BLOCK_URDF_PATH = "third_party/py/envs/assets/block.urdf"
40
+ PLANE_URDF_PATH = "third_party/bullet/examples/pybullet/gym/pybullet_data/" "plane.urdf"
41
+ WORKSPACE_URDF_PATH = "third_party/py/envs/assets/workspace.urdf"
42
+ ZONE_URDF_PATH = "third_party/py/envs/assets/zone.urdf"
43
+ INSERT_URDF_PATH = "third_party/py/envs/assets/insert.urdf"
44
+
45
+ EFFECTOR_HEIGHT = 0.06
46
+ EFFECTOR_DOWN_ROTATION = transform.Rotation.from_rotvec([0, math.pi, 0])
47
+
48
+ WORKSPACE_BOUNDS = np.array(((0.15, -0.5), (0.7, 0.5)))
49
+
50
+ # Min/max bounds calculated from oracle data using:
51
+ # ibc/environments/board2d_dataset_statistics.ipynb
52
+ # to calculate [mean - 3 * std, mean + 3 * std] using the oracle data.
53
+ # pylint: disable=line-too-long
54
+ ACTION_MIN = np.array([-0.02547718, -0.02090043], np.float32)
55
+ ACTION_MAX = np.array([0.02869084, 0.04272365], np.float32)
56
+ EFFECTOR_TARGET_TRANSLATION_MIN = np.array(
57
+ [0.1774151772260666, -0.6287994794547558], np.float32
58
+ )
59
+ EFFECTOR_TARGET_TRANSLATION_MAX = np.array(
60
+ [0.5654461532831192, 0.5441607423126698], np.float32
61
+ )
62
+ EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MIN = np.array(
63
+ [-0.07369826920330524, -0.11395704373717308], np.float32
64
+ )
65
+ EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MAX = np.array(
66
+ [0.10131562314927578, 0.19391131028532982], np.float32
67
+ )
68
+ EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MIN = np.array(
69
+ [-0.17813862301409245, -0.3309651017189026], np.float32
70
+ )
71
+ EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MAX = np.array(
72
+ [0.23726161383092403, 0.8404090404510498], np.float32
73
+ )
74
+ BLOCK_ORIENTATION_COS_SIN_MIN = np.array(
75
+ [-2.0649861991405487, -0.6154364347457886], np.float32
76
+ )
77
+ BLOCK_ORIENTATION_COS_SIN_MAX = np.array(
78
+ [1.6590178310871124, 1.8811014890670776], np.float32
79
+ )
80
+ TARGET_ORIENTATION_COS_SIN_MIN = np.array(
81
+ [-1.0761439241468906, -0.8846937336493284], np.float32
82
+ )
83
+ TARGET_ORIENTATION_COS_SIN_MAX = np.array(
84
+ [-0.8344330154359341, 0.8786859593819827], np.float32
85
+ )
86
+
87
+ # Hardcoded Pose joints to make sure we don't have surprises from using the
88
+ # IK solver on reset. The joint poses correspond to the Pose with:
89
+ # rotation = rotation3.Rotation3.from_axis_angle([0, 1, 0], math.pi)
90
+ # translation = np.array([0.3, -0.4, 0.07])
91
+ INITIAL_JOINT_POSITIONS = np.array(
92
+ [
93
+ -0.9254632489674508,
94
+ 0.6990770671568564,
95
+ -1.106629064060494,
96
+ 0.0006653351931553931,
97
+ 0.3987969742311386,
98
+ -4.063402065624296,
99
+ ]
100
+ )
101
+
102
+ DEFAULT_CAMERA_POSE = (1.0, 0, 0.75)
103
+ DEFAULT_CAMERA_ORIENTATION = (np.pi / 4, np.pi, -np.pi / 2)
104
+ IMAGE_WIDTH = 320
105
+ IMAGE_HEIGHT = 240
106
+ CAMERA_INTRINSICS = (
107
+ 0.803 * IMAGE_WIDTH, # fx
108
+ 0,
109
+ IMAGE_WIDTH / 2.0, # cx
110
+ 0,
111
+ 0.803 * IMAGE_WIDTH, # fy
112
+ IMAGE_HEIGHT / 2.0, # cy
113
+ 0,
114
+ 0,
115
+ 1,
116
+ )
117
+
118
+ # "Realistic" visuals.
119
+ X_MIN_REAL = 0.15
120
+ X_MAX_REAL = 0.6
121
+ Y_MIN_REAL = -0.3048
122
+ Y_MAX_REAL = 0.3048
123
+ WORKSPACE_BOUNDS_REAL = np.array(((X_MIN_REAL, Y_MIN_REAL), (X_MAX_REAL, Y_MAX_REAL)))
124
+ WORKSPACE_URDF_PATH_REAL = "third_party/py/ibc/environments/assets/workspace_real.urdf"
125
+ CAMERA_POSE_REAL = (0.75, 0, 0.5)
126
+ CAMERA_ORIENTATION_REAL = (np.pi / 5, np.pi, -np.pi / 2)
127
+
128
+ IMAGE_WIDTH_REAL = 320
129
+ IMAGE_HEIGHT_REAL = 180
130
+ CAMERA_INTRINSICS_REAL = (
131
+ 0.803 * IMAGE_WIDTH_REAL, # fx
132
+ 0,
133
+ IMAGE_WIDTH_REAL / 2.0, # cx
134
+ 0,
135
+ 0.803 * IMAGE_WIDTH_REAL, # fy
136
+ IMAGE_HEIGHT_REAL / 2.0, # cy
137
+ 0,
138
+ 0,
139
+ 1,
140
+ )
141
+ # pylint: enable=line-too-long
142
+
143
+
144
+ def build_env_name(task, shared_memory, use_image_obs, use_normalized_env=False):
145
+ """Construct the env name from parameters."""
146
+ if isinstance(task, str):
147
+ task = BlockTaskVariant[task]
148
+ env_name = "Block" + task.value
149
+
150
+ if use_image_obs:
151
+ env_name = env_name + "Rgb"
152
+ if use_normalized_env:
153
+ env_name = env_name + "Normalized"
154
+ if shared_memory:
155
+ env_name = "Shared" + env_name
156
+
157
+ env_name = env_name + "-v0"
158
+
159
+ return env_name
160
+
161
+
162
+ class BlockTaskVariant(enum.Enum):
163
+ REACH = "Reach"
164
+ REACH_NORMALIZED = "ReachNormalized"
165
+ PUSH = "Push"
166
+ PUSH_NORMALIZED = "PushNormalized"
167
+ INSERT = "Insert"
168
+
169
+
170
+ def sleep_spin(sleep_time_sec):
171
+ """Spin wait sleep. Avoids time.sleep accuracy issues on Windows."""
172
+ if sleep_time_sec <= 0:
173
+ return
174
+ t0 = time.perf_counter()
175
+ while time.perf_counter() - t0 < sleep_time_sec:
176
+ pass
177
+
178
+
179
+ class BlockPush(gym.Env):
180
+ """Simple XArm environment for block pushing."""
181
+
182
+ def __init__(
183
+ self,
184
+ control_frequency=10.0,
185
+ task=BlockTaskVariant.PUSH,
186
+ image_size=None,
187
+ shared_memory=False,
188
+ seed=None,
189
+ goal_dist_tolerance=0.01,
190
+ effector_height=None,
191
+ visuals_mode="default",
192
+ abs_action=False
193
+ ):
194
+ """Creates an env instance.
195
+
196
+ Args:
197
+ control_frequency: Control frequency for the arm. Each env step will
198
+ advance the simulation by 1/control_frequency seconds.
199
+ task: enum for which task, see BlockTaskVariant enum.
200
+ image_size: Optional image size (height, width). If None, no image
201
+ observations will be used.
202
+ shared_memory: If True `pybullet.SHARED_MEMORY` is used to connect to
203
+ pybullet. Useful to debug.
204
+ seed: Optional seed for the environment.
205
+ goal_dist_tolerance: float, how far away from the goal to terminate.
206
+ effector_height: float, custom height for end effector.
207
+ visuals_mode: 'default' or 'real'.
208
+ """
209
+ # pybullet.connect(pybullet.GUI)
210
+ # pybullet.resetDebugVisualizerCamera(
211
+ # cameraDistance=1.5,
212
+ # cameraYaw=0,
213
+ # cameraPitch=-40,
214
+ # cameraTargetPosition=[0.55, -0.35, 0.2],
215
+ # )
216
+ if visuals_mode != "default" and visuals_mode != "real":
217
+ raise ValueError("visuals_mode must be `real` or `default`.")
218
+ self._task = task
219
+ self._connection_mode = pybullet.DIRECT
220
+ if shared_memory:
221
+ self._connection_mode = pybullet.SHARED_MEMORY
222
+
223
+ self.goal_dist_tolerance = goal_dist_tolerance
224
+
225
+ self.effector_height = effector_height or EFFECTOR_HEIGHT
226
+
227
+ self._visuals_mode = visuals_mode
228
+ if visuals_mode == "default":
229
+ self._camera_pose = DEFAULT_CAMERA_POSE
230
+ self._camera_orientation = DEFAULT_CAMERA_ORIENTATION
231
+ self.workspace_bounds = WORKSPACE_BOUNDS
232
+ self._image_size = image_size
233
+ self._camera_instrinsics = CAMERA_INTRINSICS
234
+ self._workspace_urdf_path = WORKSPACE_URDF_PATH
235
+ else:
236
+ self._camera_pose = CAMERA_POSE_REAL
237
+ self._camera_orientation = CAMERA_ORIENTATION_REAL
238
+ self.workspace_bounds = WORKSPACE_BOUNDS_REAL
239
+ self._image_size = image_size
240
+ self._camera_instrinsics = CAMERA_INTRINSICS_REAL
241
+ self._workspace_urdf_path = WORKSPACE_URDF_PATH_REAL
242
+
243
+ self.action_space = spaces.Box(low=-0.1, high=0.1, shape=(2,)) # x, y
244
+ self.observation_space = self._create_observation_space(image_size)
245
+
246
+ self._rng = np.random.RandomState(seed=seed)
247
+ self._block_ids = None
248
+ self._previous_state = None
249
+ self._robot = None
250
+ self._workspace_uid = None
251
+ self._target_id = None
252
+ self._target_pose = None
253
+ self._target_effector_pose = None
254
+ self._pybullet_client = None
255
+ self.reach_target_translation = None
256
+ self._setup_pybullet_scene()
257
+ self._saved_state = None
258
+
259
+ assert isinstance(self._pybullet_client, bullet_client.BulletClient)
260
+ self._control_frequency = control_frequency
261
+ self._step_frequency = (
262
+ 1 / self._pybullet_client.getPhysicsEngineParameters()["fixedTimeStep"]
263
+ )
264
+
265
+ self._last_loop_time = None
266
+ self._last_loop_frame_sleep_time = None
267
+ if self._step_frequency % self._control_frequency != 0:
268
+ raise ValueError(
269
+ "Control frequency should be a multiple of the "
270
+ "configured Bullet TimeStep."
271
+ )
272
+ self._sim_steps_per_step = int(self._step_frequency / self._control_frequency)
273
+
274
+ self.rendered_img = None
275
+ self._abs_action = abs_action
276
+
277
+ # Use saved_state and restore to make reset safe as no simulation state has
278
+ # been updated at this state, but the assets are now loaded.
279
+ self.save_state()
280
+ self.reset()
281
+
282
+ @property
283
+ def pybullet_client(self):
284
+ return self._pybullet_client
285
+
286
+ @property
287
+ def robot(self):
288
+ return self._robot
289
+
290
+ @property
291
+ def workspace_uid(self):
292
+ return self._workspace_uid
293
+
294
+ @property
295
+ def target_effector_pose(self):
296
+ return self._target_effector_pose
297
+
298
+ @property
299
+ def target_pose(self):
300
+ return self._target_pose
301
+
302
+ @property
303
+ def control_frequency(self):
304
+ return self._control_frequency
305
+
306
+ @property
307
+ def connection_mode(self):
308
+ return self._connection_mode
309
+
310
+ def save_state(self):
311
+ self._saved_state = self._pybullet_client.saveState()
312
+
313
+ def set_goal_dist_tolerance(self, val):
314
+ self.goal_dist_tolerance = val
315
+
316
+ def get_control_frequency(self):
317
+ return self._control_frequency
318
+
319
+ def compute_state(self):
320
+ return self._compute_state()
321
+
322
+ def get_goal_translation(self):
323
+ """Return the translation component of the goal (2D)."""
324
+ if self._task == BlockTaskVariant.REACH:
325
+ return np.concatenate([self.reach_target_translation, [0]])
326
+ else:
327
+ return self._target_pose.translation if self._target_pose else None
328
+
329
+ def get_obj_ids(self):
330
+ return self._block_ids
331
+
332
+ def _setup_workspace_and_robot(self, end_effector="suction"):
333
+ self._pybullet_client.resetSimulation()
334
+ self._pybullet_client.configureDebugVisualizer(pybullet.COV_ENABLE_GUI, 0)
335
+ self._pybullet_client.setPhysicsEngineParameter(enableFileCaching=0)
336
+ self._pybullet_client.setGravity(0, 0, -9.8)
337
+
338
+ utils_pybullet.load_urdf(
339
+ self._pybullet_client, PLANE_URDF_PATH, basePosition=[0, 0, -0.001]
340
+ )
341
+ self._workspace_uid = utils_pybullet.load_urdf(
342
+ self._pybullet_client,
343
+ self._workspace_urdf_path,
344
+ basePosition=[0.35, 0, 0.0],
345
+ )
346
+
347
+ self._robot = xarm_sim_robot.XArmSimRobot(
348
+ self._pybullet_client,
349
+ initial_joint_positions=INITIAL_JOINT_POSITIONS,
350
+ end_effector=end_effector,
351
+ color="white" if self._visuals_mode == "real" else "default",
352
+ )
353
+
354
+ def _setup_pybullet_scene(self):
355
+ self._pybullet_client = bullet_client.BulletClient(self._connection_mode)
356
+
357
+ # Temporarily disable rendering to speed up loading URDFs.
358
+ pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 0)
359
+
360
+ self._setup_workspace_and_robot()
361
+
362
+ if self._task == BlockTaskVariant.INSERT:
363
+ target_urdf_path = INSERT_URDF_PATH
364
+ else:
365
+ target_urdf_path = ZONE_URDF_PATH
366
+
367
+ self._target_id = utils_pybullet.load_urdf(
368
+ self._pybullet_client, target_urdf_path, useFixedBase=True
369
+ )
370
+ self._block_ids = [
371
+ utils_pybullet.load_urdf(
372
+ self._pybullet_client, BLOCK_URDF_PATH, useFixedBase=False
373
+ )
374
+ ]
375
+
376
+ # Re-enable rendering.
377
+ pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 1)
378
+
379
+ self.step_simulation_to_stabilize()
380
+
381
+ def step_simulation_to_stabilize(self, nsteps=100):
382
+ for _ in range(nsteps):
383
+ self._pybullet_client.stepSimulation()
384
+
385
+ def seed(self, seed=None):
386
+ self._rng = np.random.RandomState(seed=seed)
387
+
388
+ def _set_robot_target_effector_pose(self, pose):
389
+ self._target_effector_pose = pose
390
+ self._robot.set_target_effector_pose(pose)
391
+
392
+ def reset(self, reset_poses=True):
393
+ workspace_center_x = 0.4
394
+
395
+ if reset_poses:
396
+ self._pybullet_client.restoreState(self._saved_state)
397
+
398
+ rotation = transform.Rotation.from_rotvec([0, math.pi, 0])
399
+ translation = np.array([0.3, -0.4, self.effector_height])
400
+ starting_pose = Pose3d(rotation=rotation, translation=translation)
401
+ self._set_robot_target_effector_pose(starting_pose)
402
+
403
+ # Reset block pose.
404
+ block_x = workspace_center_x + self._rng.uniform(low=-0.1, high=0.1)
405
+ block_y = -0.2 + self._rng.uniform(low=-0.15, high=0.15)
406
+ block_translation = np.array([block_x, block_y, 0])
407
+ block_sampled_angle = self._rng.uniform(math.pi)
408
+ block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
409
+
410
+ self._pybullet_client.resetBasePositionAndOrientation(
411
+ self._block_ids[0],
412
+ block_translation.tolist(),
413
+ block_rotation.as_quat().tolist(),
414
+ )
415
+
416
+ # Reset target pose.
417
+ target_x = workspace_center_x + self._rng.uniform(low=-0.10, high=0.10)
418
+ target_y = 0.2 + self._rng.uniform(low=-0.15, high=0.15)
419
+ target_translation = np.array([target_x, target_y, 0.020])
420
+
421
+ target_sampled_angle = math.pi + self._rng.uniform(
422
+ low=-math.pi / 6, high=math.pi / 6
423
+ )
424
+ target_rotation = transform.Rotation.from_rotvec(
425
+ [0, 0, target_sampled_angle]
426
+ )
427
+
428
+ self._pybullet_client.resetBasePositionAndOrientation(
429
+ self._target_id,
430
+ target_translation.tolist(),
431
+ target_rotation.as_quat().tolist(),
432
+ )
433
+ else:
434
+ (
435
+ target_translation,
436
+ target_orientation_quat,
437
+ ) = self._pybullet_client.getBasePositionAndOrientation(self._target_id)
438
+ target_rotation = transform.Rotation.from_quat(target_orientation_quat)
439
+ target_translation = np.array(target_translation)
440
+
441
+ self._target_pose = Pose3d(
442
+ rotation=target_rotation, translation=target_translation
443
+ )
444
+
445
+ if reset_poses:
446
+ self.step_simulation_to_stabilize()
447
+
448
+ state = self._compute_state()
449
+ self._previous_state = state
450
+
451
+ if self._task == BlockTaskVariant.REACH:
452
+ self._compute_reach_target(state)
453
+
454
+ self._init_goal_distance = self._compute_goal_distance(state)
455
+ init_goal_eps = 1e-7
456
+ assert self._init_goal_distance > init_goal_eps
457
+ self.best_fraction_reduced_goal_dist = 0.0
458
+
459
+ return state
460
+
461
+ def _compute_goal_distance(self, state):
462
+ goal_translation = self.get_goal_translation()
463
+ if self._task != BlockTaskVariant.REACH:
464
+ goal_distance = np.linalg.norm(
465
+ state["block_translation"] - goal_translation[0:2]
466
+ )
467
+ else:
468
+ goal_distance = np.linalg.norm(
469
+ state["effector_translation"] - goal_translation[0:2]
470
+ )
471
+ return goal_distance
472
+
473
+ def _compute_reach_target(self, state):
474
+ xy_block = state["block_translation"]
475
+ xy_target = state["target_translation"]
476
+
477
+ xy_block_to_target = xy_target - xy_block
478
+ xy_dir_block_to_target = (xy_block_to_target) / np.linalg.norm(
479
+ xy_block_to_target
480
+ )
481
+ self.reach_target_translation = xy_block + -1 * xy_dir_block_to_target * 0.05
482
+
483
+ def _compute_state(self):
484
+ effector_pose = self._robot.forward_kinematics()
485
+ block_position_and_orientation = (
486
+ self._pybullet_client.getBasePositionAndOrientation(self._block_ids[0])
487
+ )
488
+ block_pose = Pose3d(
489
+ rotation=transform.Rotation.from_quat(block_position_and_orientation[1]),
490
+ translation=block_position_and_orientation[0],
491
+ )
492
+
493
+ def _yaw_from_pose(pose):
494
+ return np.array([pose.rotation.as_euler("xyz", degrees=False)[-1]])
495
+
496
+ obs = collections.OrderedDict(
497
+ block_translation=block_pose.translation[0:2],
498
+ block_orientation=_yaw_from_pose(block_pose),
499
+ effector_translation=effector_pose.translation[0:2],
500
+ effector_target_translation=self._target_effector_pose.translation[0:2],
501
+ target_translation=self._target_pose.translation[0:2],
502
+ target_orientation=_yaw_from_pose(self._target_pose),
503
+ )
504
+ if self._image_size is not None:
505
+ obs["rgb"] = self._render_camera(self._image_size)
506
+ return obs
507
+
508
+ def _step_robot_and_sim(self, action):
509
+ """Steps the robot and pybullet sim."""
510
+ # Compute target_effector_pose by shifting the effector's pose by the
511
+ # action.
512
+ if self._abs_action:
513
+ target_effector_translation = np.array([action[0], action[1], 0])
514
+ else:
515
+ target_effector_translation = np.array(
516
+ self._target_effector_pose.translation
517
+ ) + np.array([action[0], action[1], 0])
518
+
519
+ target_effector_translation[0:2] = np.clip(
520
+ target_effector_translation[0:2],
521
+ self.workspace_bounds[0],
522
+ self.workspace_bounds[1],
523
+ )
524
+ target_effector_translation[-1] = self.effector_height
525
+ target_effector_pose = Pose3d(
526
+ rotation=EFFECTOR_DOWN_ROTATION, translation=target_effector_translation
527
+ )
528
+
529
+ self._set_robot_target_effector_pose(target_effector_pose)
530
+
531
+ # Update sleep time dynamically to stay near real-time.
532
+ frame_sleep_time = 0
533
+ if self._connection_mode == pybullet.SHARED_MEMORY:
534
+ cur_time = time.time()
535
+ if self._last_loop_time is not None:
536
+ # Calculate the total, non-sleeping time from the previous frame, this
537
+ # includes the actual step as well as any compute that happens in the
538
+ # caller thread (model inference, etc).
539
+ compute_time = (
540
+ cur_time
541
+ - self._last_loop_time
542
+ - self._last_loop_frame_sleep_time * self._sim_steps_per_step
543
+ )
544
+ # Use this to calculate the current frame's total sleep time to ensure
545
+ # that env.step runs at policy rate. This is an estimate since the
546
+ # previous frame's compute time may not match the current frame.
547
+ total_sleep_time = max((1 / self._control_frequency) - compute_time, 0)
548
+ # Now spread this out over the inner sim steps. This doesn't change
549
+ # control in any way, but makes the animation appear smooth.
550
+ frame_sleep_time = total_sleep_time / self._sim_steps_per_step
551
+ else:
552
+ # No estimate of the previous frame's compute, assume it is zero.
553
+ frame_sleep_time = 1 / self._step_frequency
554
+
555
+ # Cache end of this loop time, to compute sleep time on next iteration.
556
+ self._last_loop_time = cur_time
557
+ self._last_loop_frame_sleep_time = frame_sleep_time
558
+
559
+ for _ in range(self._sim_steps_per_step):
560
+ if self._connection_mode == pybullet.SHARED_MEMORY:
561
+ sleep_spin(frame_sleep_time)
562
+ self._pybullet_client.stepSimulation()
563
+
564
+ def step(self, action):
565
+ self._step_robot_and_sim(action)
566
+
567
+ state = self._compute_state()
568
+
569
+ goal_distance = self._compute_goal_distance(state)
570
+ fraction_reduced_goal_distance = 1.0 - (
571
+ goal_distance / self._init_goal_distance
572
+ )
573
+ if fraction_reduced_goal_distance > self.best_fraction_reduced_goal_dist:
574
+ self.best_fraction_reduced_goal_dist = fraction_reduced_goal_distance
575
+
576
+ done = False
577
+ reward = self.best_fraction_reduced_goal_dist
578
+
579
+ # Terminate the episode if the block is close enough to the target.
580
+ if goal_distance < self.goal_dist_tolerance:
581
+ reward = 1.0
582
+ done = True
583
+
584
+ return state, reward, done, {}
585
+
586
+ @property
587
+ def succeeded(self):
588
+ state = self._compute_state()
589
+ goal_distance = self._compute_goal_distance(state)
590
+ if goal_distance < self.goal_dist_tolerance:
591
+ return True
592
+ return False
593
+
594
+ @property
595
+ def goal_distance(self):
596
+ state = self._compute_state()
597
+ return self._compute_goal_distance(state)
598
+
599
+ def render(self, mode="rgb_array"):
600
+ if self._image_size is not None:
601
+ image_size = self._image_size
602
+ else:
603
+ # This allows rendering even for state-only obs,
604
+ # for visualization.
605
+ image_size = (IMAGE_HEIGHT, IMAGE_WIDTH)
606
+
607
+ data = self._render_camera(image_size=(image_size[0], image_size[1]))
608
+ if mode == "human":
609
+ if self.rendered_img is None:
610
+ self.rendered_img = plt.imshow(
611
+ np.zeros((image_size[0], image_size[1], 4))
612
+ )
613
+ else:
614
+ self.rendered_img.set_data(data)
615
+ plt.draw()
616
+ plt.pause(0.00001)
617
+ return data
618
+
619
+ def close(self):
620
+ self._pybullet_client.disconnect()
621
+
622
+ def calc_camera_params(self, image_size):
623
+ # Mimic RealSense D415 camera parameters.
624
+ intrinsics = self._camera_instrinsics
625
+
626
+ # Set default camera poses.
627
+ front_position = self._camera_pose
628
+ front_rotation = self._camera_orientation
629
+ front_rotation = self._pybullet_client.getQuaternionFromEuler(front_rotation)
630
+ # Default camera configs.
631
+ zrange = (0.01, 10.0)
632
+
633
+ # OpenGL camera settings.
634
+ lookdir = np.float32([0, 0, 1]).reshape(3, 1)
635
+ updir = np.float32([0, -1, 0]).reshape(3, 1)
636
+ rotation = self._pybullet_client.getMatrixFromQuaternion(front_rotation)
637
+ rotm = np.float32(rotation).reshape(3, 3)
638
+ lookdir = (rotm @ lookdir).reshape(-1)
639
+ updir = (rotm @ updir).reshape(-1)
640
+ lookat = front_position + lookdir
641
+ focal_len = intrinsics[0]
642
+ znear, zfar = zrange
643
+ viewm = self._pybullet_client.computeViewMatrix(front_position, lookat, updir)
644
+ fovh = (image_size[0] / 2) / focal_len
645
+ fovh = 180 * np.arctan(fovh) * 2 / np.pi
646
+
647
+ # Notes: 1) FOV is vertical FOV 2) aspect must be float
648
+ aspect_ratio = image_size[1] / image_size[0]
649
+ projm = self._pybullet_client.computeProjectionMatrixFOV(
650
+ fovh, aspect_ratio, znear, zfar
651
+ )
652
+
653
+ return viewm, projm, front_position, lookat, updir
654
+
655
+ def _render_camera(self, image_size):
656
+ """Render RGB image with RealSense configuration."""
657
+ viewm, projm, _, _, _ = self.calc_camera_params(image_size)
658
+
659
+ # Render with OpenGL camera settings.
660
+ _, _, color, _, _ = self._pybullet_client.getCameraImage(
661
+ width=image_size[1],
662
+ height=image_size[0],
663
+ viewMatrix=viewm,
664
+ projectionMatrix=projm,
665
+ flags=pybullet.ER_SEGMENTATION_MASK_OBJECT_AND_LINKINDEX,
666
+ renderer=pybullet.ER_BULLET_HARDWARE_OPENGL,
667
+ )
668
+
669
+ # Get color image.
670
+ color_image_size = (image_size[0], image_size[1], 4)
671
+ color = np.array(color, dtype=np.uint8).reshape(color_image_size)
672
+ color = color[:, :, :3] # remove alpha channel
673
+
674
+ return color.astype(np.uint8)
675
+
676
+ def _create_observation_space(self, image_size):
677
+ pi2 = math.pi * 2
678
+
679
+ obs_dict = collections.OrderedDict(
680
+ block_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
681
+ block_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
682
+ effector_translation=spaces.Box(
683
+ low=self.workspace_bounds[0] - 0.1, # Small buffer for to IK noise.
684
+ high=self.workspace_bounds[1] + 0.1,
685
+ ), # x,y
686
+ effector_target_translation=spaces.Box(
687
+ low=self.workspace_bounds[0] - 0.1, # Small buffer for to IK noise.
688
+ high=self.workspace_bounds[1] + 0.1,
689
+ ), # x,y
690
+ target_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
691
+ target_orientation=spaces.Box(
692
+ low=-pi2,
693
+ high=pi2,
694
+ shape=(1,),
695
+ ), # theta
696
+ )
697
+ if image_size is not None:
698
+ obs_dict["rgb"] = spaces.Box(
699
+ low=0, high=255, shape=(image_size[0], image_size[1], 3), dtype=np.uint8
700
+ )
701
+ return spaces.Dict(obs_dict)
702
+
703
+ def get_pybullet_state(self):
704
+ """Save pybullet state of the scene.
705
+
706
+ Returns:
707
+ dict containing 'robots', 'robot_end_effectors', 'targets', 'objects',
708
+ each containing a list of ObjState.
709
+ """
710
+ state: Dict[str, List[ObjState]] = {}
711
+
712
+ state["robots"] = [
713
+ XarmState.get_bullet_state(
714
+ self._pybullet_client,
715
+ self.robot.xarm,
716
+ target_effector_pose=self._target_effector_pose,
717
+ goal_translation=self.get_goal_translation(),
718
+ )
719
+ ]
720
+
721
+ state["robot_end_effectors"] = []
722
+ if self.robot.end_effector:
723
+ state["robot_end_effectors"].append(
724
+ ObjState.get_bullet_state(
725
+ self._pybullet_client, self.robot.end_effector
726
+ )
727
+ )
728
+
729
+ state["targets"] = []
730
+ if self._target_id:
731
+ state["targets"].append(
732
+ ObjState.get_bullet_state(self._pybullet_client, self._target_id)
733
+ )
734
+
735
+ state["objects"] = []
736
+ for obj_id in self.get_obj_ids():
737
+ state["objects"].append(
738
+ ObjState.get_bullet_state(self._pybullet_client, obj_id)
739
+ )
740
+
741
+ return state
742
+
743
+ def set_pybullet_state(self, state):
744
+ """Restore pyullet state.
745
+
746
+ WARNING: py_environment wrapper assumes environments aren't reset in their
747
+ constructor and will often reset the environment unintentionally. It is
748
+ always recommended that you call env.reset on the tfagents wrapper before
749
+ playback (replaying pybullet_state).
750
+
751
+ Args:
752
+ state: dict containing 'robots', 'robot_end_effectors', 'targets',
753
+ 'objects', each containing a list of ObjState.
754
+ """
755
+
756
+ assert isinstance(state["robots"][0], XarmState)
757
+ xarm_state: XarmState = state["robots"][0]
758
+ xarm_state.set_bullet_state(self._pybullet_client, self.robot.xarm)
759
+ self._set_robot_target_effector_pose(xarm_state.target_effector_pose)
760
+
761
+ def _set_state_safe(obj_state, obj_id):
762
+ if obj_state is not None:
763
+ assert obj_id is not None, "Cannot set state for missing object."
764
+ obj_state.set_bullet_state(self._pybullet_client, obj_id)
765
+ else:
766
+ assert obj_id is None, f"No state found for obj_id {obj_id}"
767
+
768
+ robot_end_effectors = state["robot_end_effectors"]
769
+ _set_state_safe(
770
+ None if not robot_end_effectors else robot_end_effectors[0],
771
+ self.robot.end_effector,
772
+ )
773
+
774
+ targets = state["targets"]
775
+ _set_state_safe(None if not targets else targets[0], self._target_id)
776
+
777
+ obj_ids = self.get_obj_ids()
778
+ assert len(state["objects"]) == len(obj_ids), "State length mismatch"
779
+ for obj_state, obj_id in zip(state["objects"], obj_ids):
780
+ _set_state_safe(obj_state, obj_id)
781
+
782
+ self.reset(reset_poses=False)
783
+
784
+
785
+ class BlockPushNormalized(gym.Env):
786
+ """Simple XArm environment for block pushing, normalized state and actions."""
787
+
788
+ def __init__(
789
+ self,
790
+ control_frequency=10.0,
791
+ task=BlockTaskVariant.PUSH_NORMALIZED,
792
+ image_size=None,
793
+ shared_memory=False,
794
+ seed=None,
795
+ ):
796
+ """Creates an env instance.
797
+
798
+ Args:
799
+ control_frequency: Control frequency for the arm. Each env step will
800
+ advance the simulation by 1/control_frequency seconds.
801
+ task: enum for which task, see BlockTaskVariant enum.
802
+ image_size: Optional image size (height, width). If None, no image
803
+ observations will be used.
804
+ shared_memory: If True `pybullet.SHARED_MEMORY` is used to connect to
805
+ pybullet. Useful to debug.
806
+ seed: Optional seed for the environment.
807
+ """
808
+ # Map normalized task to unnormalized task.
809
+ if task == BlockTaskVariant.PUSH_NORMALIZED:
810
+ env_task = BlockTaskVariant.PUSH
811
+ elif task == BlockTaskVariant.REACH_NORMALIZED:
812
+ env_task = BlockTaskVariant.REACH
813
+ else:
814
+ raise ValueError("Unsupported task %s" % str(task))
815
+ self._env = BlockPush(
816
+ control_frequency, env_task, image_size, shared_memory, seed
817
+ )
818
+ self.action_space = spaces.Box(low=-1, high=1, shape=(2,))
819
+ self.observation_space = spaces.Dict(
820
+ collections.OrderedDict(
821
+ effector_target_translation=spaces.Box(low=-1, high=1, shape=(2,)),
822
+ effector_target_to_block_translation=spaces.Box(
823
+ low=-1, high=1, shape=(2,)
824
+ ),
825
+ block_orientation_cos_sin=spaces.Box(low=-1, high=1, shape=(2,)),
826
+ effector_target_to_target_translation=spaces.Box(
827
+ low=-1, high=1, shape=(2,)
828
+ ),
829
+ target_orientation_cos_sin=spaces.Box(low=-1, high=1, shape=(2,)),
830
+ )
831
+ )
832
+ self.reset()
833
+
834
+ def get_control_frequency(self):
835
+ return self._env.get_control_frequency()
836
+
837
+ @property
838
+ def reach_target_translation(self):
839
+ return self._env.reach_target_translation
840
+
841
+ def seed(self, seed=None):
842
+ self._env.seed(seed)
843
+
844
+ def reset(self):
845
+ state = self._env.reset()
846
+ return self.calc_normalized_state(state)
847
+
848
+ def step(self, action):
849
+ # The environment is normalized [mean-3*std, mean+3*std] -> [-1, 1].
850
+ action = np.clip(action, a_min=-1.0, a_max=1.0)
851
+ state, reward, done, info = self._env.step(
852
+ self.calc_unnormalized_action(action)
853
+ )
854
+ state = self.calc_normalized_state(state)
855
+ reward = reward * 100 # Keep returns in [0, 100]
856
+ return state, reward, done, info
857
+
858
+ def render(self, mode="rgb_array"):
859
+ return self._env.render(mode)
860
+
861
+ def close(self):
862
+ self._env.close()
863
+
864
+ @staticmethod
865
+ def _normalize(values, values_min, values_max):
866
+ offset = (values_max + values_min) * 0.5
867
+ scale = (values_max - values_min) * 0.5
868
+ return (values - offset) / scale # [min, max] -> [-1, 1]
869
+
870
+ @staticmethod
871
+ def _unnormalize(values, values_min, values_max):
872
+ offset = (values_max + values_min) * 0.5
873
+ scale = (values_max - values_min) * 0.5
874
+ return values * scale + offset # [-1, 1] -> [min, max]
875
+
876
+ @classmethod
877
+ def calc_normalized_action(cls, action):
878
+ return cls._normalize(action, ACTION_MIN, ACTION_MAX)
879
+
880
+ @classmethod
881
+ def calc_unnormalized_action(cls, norm_action):
882
+ return cls._unnormalize(norm_action, ACTION_MIN, ACTION_MAX)
883
+
884
+ @classmethod
885
+ def calc_normalized_state(cls, state):
886
+
887
+ effector_target_translation = cls._normalize(
888
+ state["effector_target_translation"],
889
+ EFFECTOR_TARGET_TRANSLATION_MIN,
890
+ EFFECTOR_TARGET_TRANSLATION_MAX,
891
+ )
892
+
893
+ effector_target_to_block_translation = cls._normalize(
894
+ state["block_translation"] - state["effector_target_translation"],
895
+ EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MIN,
896
+ EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MAX,
897
+ )
898
+ ori = state["block_orientation"][0]
899
+ block_orientation_cos_sin = cls._normalize(
900
+ np.array([math.cos(ori), math.sin(ori)], np.float32),
901
+ BLOCK_ORIENTATION_COS_SIN_MIN,
902
+ BLOCK_ORIENTATION_COS_SIN_MAX,
903
+ )
904
+
905
+ effector_target_to_target_translation = cls._normalize(
906
+ state["target_translation"] - state["effector_target_translation"],
907
+ EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MIN,
908
+ EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MAX,
909
+ )
910
+ ori = state["target_orientation"][0]
911
+ target_orientation_cos_sin = cls._normalize(
912
+ np.array([math.cos(ori), math.sin(ori)], np.float32),
913
+ TARGET_ORIENTATION_COS_SIN_MIN,
914
+ TARGET_ORIENTATION_COS_SIN_MAX,
915
+ )
916
+
917
+ # Note: We do not include effector_translation in the normalized state.
918
+ # This means the unnormalized -> normalized mapping is not invertable.
919
+ return collections.OrderedDict(
920
+ effector_target_translation=effector_target_translation,
921
+ effector_target_to_block_translation=effector_target_to_block_translation,
922
+ block_orientation_cos_sin=block_orientation_cos_sin,
923
+ effector_target_to_target_translation=effector_target_to_target_translation,
924
+ target_orientation_cos_sin=target_orientation_cos_sin,
925
+ )
926
+
927
+ @classmethod
928
+ def calc_unnormalized_state(cls, norm_state):
929
+
930
+ effector_target_translation = cls._unnormalize(
931
+ norm_state["effector_target_translation"],
932
+ EFFECTOR_TARGET_TRANSLATION_MIN,
933
+ EFFECTOR_TARGET_TRANSLATION_MAX,
934
+ )
935
+ # Note: normalized state does not include effector_translation state, this
936
+ # means this component will be missing (and is marked nan).
937
+ effector_translation = np.array([np.nan, np.nan], np.float32)
938
+
939
+ effector_target_to_block_translation = cls._unnormalize(
940
+ norm_state["effector_target_to_block_translation"],
941
+ EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MIN,
942
+ EFFECTOR_TARGET_TO_BLOCK_TRANSLATION_MAX,
943
+ )
944
+ block_translation = (
945
+ effector_target_to_block_translation + effector_target_translation
946
+ )
947
+ ori_cos_sin = cls._unnormalize(
948
+ norm_state["block_orientation_cos_sin"],
949
+ BLOCK_ORIENTATION_COS_SIN_MIN,
950
+ BLOCK_ORIENTATION_COS_SIN_MAX,
951
+ )
952
+ block_orientation = np.array(
953
+ [math.atan2(ori_cos_sin[1], ori_cos_sin[0])], np.float32
954
+ )
955
+
956
+ effector_target_to_target_translation = cls._unnormalize(
957
+ norm_state["effector_target_to_target_translation"],
958
+ EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MIN,
959
+ EFFECTOR_TARGET_TO_TARGET_TRANSLATION_MAX,
960
+ )
961
+ target_translation = (
962
+ effector_target_to_target_translation + effector_target_translation
963
+ )
964
+ ori_cos_sin = cls._unnormalize(
965
+ norm_state["target_orientation_cos_sin"],
966
+ TARGET_ORIENTATION_COS_SIN_MIN,
967
+ TARGET_ORIENTATION_COS_SIN_MAX,
968
+ )
969
+ target_orientation = np.array(
970
+ [math.atan2(ori_cos_sin[1], ori_cos_sin[0])], np.float32
971
+ )
972
+
973
+ return collections.OrderedDict(
974
+ block_translation=block_translation,
975
+ block_orientation=block_orientation,
976
+ effector_translation=effector_translation,
977
+ effector_target_translation=effector_target_translation,
978
+ target_translation=target_translation,
979
+ target_orientation=target_orientation,
980
+ )
981
+
982
+ def get_pybullet_state(self):
983
+ return self._env.get_pybullet_state()
984
+
985
+ def set_pybullet_state(self, state):
986
+ return self._env.set_pybullet_state(state)
987
+
988
+ @property
989
+ def pybullet_client(self):
990
+ return self._env.pybullet_client
991
+
992
+ def calc_camera_params(self, image_size):
993
+ return self._env.calc_camera_params(image_size)
994
+
995
+ def _compute_state(self):
996
+ return self.calc_normalized_state(
997
+ self._env._compute_state()
998
+ ) # pylint: disable=protected-access
999
+
1000
+
1001
+ # Make sure we only register once to allow us to reload the module in colab for
1002
+ # debugging.
1003
+ if "BlockPush-v0" in registration.registry.env_specs:
1004
+ del registration.registry.env_specs["BlockInsert-v0"]
1005
+ del registration.registry.env_specs["BlockPush-v0"]
1006
+ del registration.registry.env_specs["BlockPushNormalized-v0"]
1007
+ del registration.registry.env_specs["BlockPushRgbNormalized-v0"]
1008
+ del registration.registry.env_specs["BlockReach-v0"]
1009
+ del registration.registry.env_specs["BlockReachNormalized-v0"]
1010
+ del registration.registry.env_specs["BlockReachRgbNormalized-v0"]
1011
+ del registration.registry.env_specs["SharedBlockInsert-v0"]
1012
+ del registration.registry.env_specs["SharedBlockPush-v0"]
1013
+ del registration.registry.env_specs["SharedBlockReach-v0"]
1014
+
1015
+ registration.register(
1016
+ id="BlockInsert-v0",
1017
+ entry_point=BlockPush,
1018
+ kwargs=dict(task=BlockTaskVariant.INSERT),
1019
+ max_episode_steps=50,
1020
+ )
1021
+ registration.register(id="BlockPush-v0", entry_point=BlockPush, max_episode_steps=100)
1022
+ registration.register(
1023
+ id="BlockPushNormalized-v0",
1024
+ entry_point=BlockPushNormalized,
1025
+ kwargs=dict(task=BlockTaskVariant.PUSH_NORMALIZED),
1026
+ max_episode_steps=100,
1027
+ )
1028
+ registration.register(
1029
+ id="BlockPushRgb-v0",
1030
+ entry_point=BlockPush,
1031
+ max_episode_steps=100,
1032
+ kwargs=dict(image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)),
1033
+ )
1034
+ registration.register(
1035
+ id="BlockPushRgbNormalized-v0",
1036
+ entry_point=BlockPushNormalized,
1037
+ kwargs=dict(
1038
+ task=BlockTaskVariant.PUSH_NORMALIZED, image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)
1039
+ ),
1040
+ max_episode_steps=100,
1041
+ )
1042
+ registration.register(
1043
+ id="BlockReach-v0",
1044
+ entry_point=BlockPush,
1045
+ kwargs=dict(task=BlockTaskVariant.REACH),
1046
+ max_episode_steps=50,
1047
+ )
1048
+ registration.register(
1049
+ id="BlockReachRgb-v0",
1050
+ entry_point=BlockPush,
1051
+ max_episode_steps=100,
1052
+ kwargs=dict(task=BlockTaskVariant.REACH, image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)),
1053
+ )
1054
+ registration.register(
1055
+ id="BlockReachNormalized-v0",
1056
+ entry_point=BlockPushNormalized,
1057
+ kwargs=dict(task=BlockTaskVariant.REACH_NORMALIZED),
1058
+ max_episode_steps=50,
1059
+ )
1060
+ registration.register(
1061
+ id="BlockReachRgbNormalized-v0",
1062
+ entry_point=BlockPushNormalized,
1063
+ kwargs=dict(
1064
+ task=BlockTaskVariant.REACH_NORMALIZED, image_size=(IMAGE_HEIGHT, IMAGE_WIDTH)
1065
+ ),
1066
+ max_episode_steps=50,
1067
+ )
1068
+
1069
+ registration.register(
1070
+ id="SharedBlockInsert-v0",
1071
+ entry_point=BlockPush,
1072
+ kwargs=dict(task=BlockTaskVariant.INSERT, shared_memory=True),
1073
+ max_episode_steps=50,
1074
+ )
1075
+ registration.register(
1076
+ id="SharedBlockPush-v0",
1077
+ entry_point=BlockPush,
1078
+ kwargs=dict(shared_memory=True),
1079
+ max_episode_steps=100,
1080
+ )
1081
+ registration.register(
1082
+ id="SharedBlockPushNormalized-v0",
1083
+ entry_point=BlockPushNormalized,
1084
+ kwargs=dict(task=BlockTaskVariant.PUSH_NORMALIZED, shared_memory=True),
1085
+ max_episode_steps=100,
1086
+ )
1087
+ registration.register(
1088
+ id="SharedBlockReach-v0",
1089
+ entry_point=BlockPush,
1090
+ kwargs=dict(task=BlockTaskVariant.REACH, shared_memory=True),
1091
+ max_episode_steps=50,
1092
+ )
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_discontinuous.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Discontinuous block pushing."""
17
+ import collections
18
+ import enum
19
+ import math
20
+ from typing import List, Optional
21
+
22
+ from gym import spaces
23
+ from gym.envs import registration
24
+ from cleandiffuser.env.block_pushing import block_pushing
25
+ from cleandiffuser.env.block_pushing.utils import utils_pybullet
26
+ from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
27
+ import numpy as np
28
+ from scipy.spatial import transform
29
+ import pybullet
30
+ import pybullet_utils.bullet_client as bullet_client
31
+
32
+ ZONE2_URDF_PATH = "third_party/py/envs/assets/zone2.urdf"
33
+
34
+ MIN_TARGET_DIST = 0.15
35
+ NUM_RESET_ATTEMPTS = 1000
36
+
37
+
38
+ def build_env_name(task, shared_memory, use_image_obs):
39
+ """Construct the env name from parameters."""
40
+ del task
41
+ env_name = "BlockPushDiscontinuous"
42
+
43
+ if use_image_obs:
44
+ env_name = env_name + "Rgb"
45
+
46
+ if shared_memory:
47
+ env_name = "Shared" + env_name
48
+
49
+ env_name = env_name + "-v0"
50
+
51
+ return env_name
52
+
53
+
54
+ class BlockTaskVariant(enum.Enum):
55
+ REACH = "Reach"
56
+ REACH_NORMALIZED = "ReachNormalized"
57
+ PUSH = "Push"
58
+ PUSH_NORMALIZED = "PushNormalized"
59
+ INSERT = "Insert"
60
+
61
+
62
+ # pytype: skip-file
63
+ class BlockPushDiscontinuous(block_pushing.BlockPush):
64
+ """Discontinuous block pushing."""
65
+
66
+ def __init__(
67
+ self,
68
+ control_frequency=10.0,
69
+ task=BlockTaskVariant.PUSH,
70
+ image_size=None,
71
+ shared_memory=False,
72
+ seed=None,
73
+ goal_dist_tolerance=0.04,
74
+ ):
75
+ super(BlockPushDiscontinuous, self).__init__(
76
+ control_frequency=control_frequency,
77
+ task=task,
78
+ image_size=image_size,
79
+ shared_memory=shared_memory,
80
+ seed=seed,
81
+ goal_dist_tolerance=goal_dist_tolerance,
82
+ )
83
+
84
+ @property
85
+ def target_poses(self):
86
+ return self._target_poses
87
+
88
+ def get_goal_translation(self):
89
+ """Return the translation component of the goal (2D)."""
90
+ if self._target_poses:
91
+ return [i.translation for i in self._target_poses]
92
+ else:
93
+ return None
94
+
95
+ def _setup_pybullet_scene(self):
96
+ self._pybullet_client = bullet_client.BulletClient(self._connection_mode)
97
+
98
+ # Temporarily disable rendering to speed up loading URDFs.
99
+ pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 0)
100
+
101
+ self._setup_workspace_and_robot()
102
+ target_urdf_path = block_pushing.ZONE_URDF_PATH
103
+
104
+ self._target_ids = []
105
+ for _ in [block_pushing.ZONE_URDF_PATH, ZONE2_URDF_PATH]:
106
+ self._target_ids.append(
107
+ utils_pybullet.load_urdf(
108
+ self._pybullet_client, target_urdf_path, useFixedBase=True
109
+ )
110
+ )
111
+ self._block_ids = [
112
+ utils_pybullet.load_urdf(
113
+ self._pybullet_client, block_pushing.BLOCK_URDF_PATH, useFixedBase=False
114
+ )
115
+ ]
116
+
117
+ # Re-enable rendering.
118
+ pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 1)
119
+
120
+ self.step_simulation_to_stabilize()
121
+
122
+ def _reset_target_poses(self, workspace_center_x):
123
+ """Resets target poses."""
124
+ self._target_poses = [None for _ in range(len(self._target_ids))]
125
+
126
+ def _reset_target_pose(idx, avoid=None):
127
+ def _get_random_translation():
128
+ # Choose x,y randomly.
129
+ target_x = workspace_center_x + self._rng.uniform(low=-0.10, high=0.10)
130
+ # Fix ys for this environment.
131
+ if idx == 0:
132
+ target_y = 0
133
+ else:
134
+ target_y = 0.4
135
+ target_translation = np.array([target_x, target_y, 0.020])
136
+ return target_translation
137
+
138
+ if avoid is None:
139
+ target_translation = _get_random_translation()
140
+ else:
141
+ # Reject targets too close to `avoid`.
142
+ for _ in range(NUM_RESET_ATTEMPTS):
143
+ target_translation = _get_random_translation()
144
+ dist = np.linalg.norm(target_translation[0] - avoid[0])
145
+ if dist > MIN_TARGET_DIST:
146
+ break
147
+ target_sampled_angle = math.pi + self._rng.uniform(
148
+ low=-math.pi / 6, high=math.pi / 6
149
+ )
150
+ target_rotation = transform.Rotation.from_rotvec(
151
+ [0, 0, target_sampled_angle]
152
+ )
153
+ self._pybullet_client.resetBasePositionAndOrientation(
154
+ self._target_ids[idx],
155
+ target_translation.tolist(),
156
+ target_rotation.as_quat().tolist(),
157
+ )
158
+ self._target_poses[idx] = Pose3d(
159
+ rotation=target_rotation, translation=target_translation
160
+ )
161
+
162
+ try_idx = 0
163
+ while True:
164
+ # Choose the first target.
165
+ _reset_target_pose(0)
166
+ # Choose the second target, avoiding the first.
167
+ _reset_target_pose(1, avoid=self._target_poses[0].translation)
168
+ dist = np.linalg.norm(
169
+ self._target_poses[0].translation[0]
170
+ - self._target_poses[1].translation[0]
171
+ )
172
+ if dist > MIN_TARGET_DIST:
173
+ break
174
+ try_idx += 1
175
+ if try_idx >= NUM_RESET_ATTEMPTS:
176
+ raise ValueError("could not find matching target")
177
+ assert dist > MIN_TARGET_DIST
178
+
179
+ def reset(self):
180
+ self._pybullet_client.restoreState(self._saved_state)
181
+
182
+ rotation = transform.Rotation.from_rotvec([0, math.pi, 0])
183
+ translation = np.array([0.3, -0.4, block_pushing.EFFECTOR_HEIGHT])
184
+ starting_pose = Pose3d(rotation=rotation, translation=translation)
185
+ self._set_robot_target_effector_pose(starting_pose)
186
+
187
+ workspace_center_x = 0.4
188
+
189
+ # Reset block pose.
190
+ block_x = workspace_center_x + self._rng.uniform(low=-0.1, high=0.1)
191
+ block_y = -0.2 + self._rng.uniform(low=-0.15, high=0.15)
192
+ block_translation = np.array([block_x, block_y, 0])
193
+ block_sampled_angle = self._rng.uniform(math.pi)
194
+ block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
195
+
196
+ self._pybullet_client.resetBasePositionAndOrientation(
197
+ self._block_ids[0],
198
+ block_translation.tolist(),
199
+ block_rotation.as_quat().tolist(),
200
+ )
201
+
202
+ # Reset target pose.
203
+ self._reset_target_poses(workspace_center_x)
204
+
205
+ self.step_simulation_to_stabilize()
206
+ state = self._compute_state()
207
+ self._previous_state = state
208
+ self.min_dist_to_first_goal = np.inf
209
+ self.min_dist_to_second_goal = np.inf
210
+ self.steps = 0
211
+ return state
212
+
213
+ def _compute_goal_distance(self, state):
214
+ # Reward is 1. blocks is inside any target.
215
+ return np.mean([self.min_dist_to_first_goal, self.min_dist_to_second_goal])
216
+
217
+ def _compute_state(self):
218
+ effector_pose = self._robot.forward_kinematics()
219
+ block_position_and_orientation = (
220
+ self._pybullet_client.getBasePositionAndOrientation(self._block_ids[0])
221
+ )
222
+ block_pose = Pose3d(
223
+ rotation=transform.Rotation.from_quat(block_position_and_orientation[1]),
224
+ translation=block_position_and_orientation[0],
225
+ )
226
+
227
+ def _yaw_from_pose(pose):
228
+ return np.array([pose.rotation.as_euler("xyz", degrees=False)[-1]])
229
+
230
+ obs = collections.OrderedDict(
231
+ block_translation=block_pose.translation[0:2],
232
+ block_orientation=_yaw_from_pose(block_pose),
233
+ effector_translation=effector_pose.translation[0:2],
234
+ effector_target_translation=self._target_effector_pose.translation[0:2],
235
+ target_translation=self._target_poses[0].translation[0:2],
236
+ target_orientation=_yaw_from_pose(self._target_poses[0]),
237
+ target2_translation=self._target_poses[1].translation[0:2],
238
+ target2_orientation=_yaw_from_pose(self._target_poses[1]),
239
+ )
240
+ if self._image_size is not None:
241
+ obs["rgb"] = self._render_camera(self._image_size)
242
+ return obs
243
+
244
+ def step(self, action):
245
+ self._step_robot_and_sim(action)
246
+ state = self._compute_state()
247
+ reward = self._get_reward(state)
248
+ done = False
249
+ if reward > 0.0:
250
+ done = True
251
+ # Cache so we can compute success.
252
+ self.state = state
253
+ return state, reward, done, {}
254
+
255
+ def dist(self, state, target):
256
+ # Reward is 1. blocks is inside any target.
257
+ return np.linalg.norm(
258
+ state["block_translation"] - state["%s_translation" % target]
259
+ )
260
+
261
+ def _get_reward(self, state):
262
+ """Reward is 1.0 if agent hits both goals and stays at second."""
263
+ # This also statefully updates these values.
264
+ self.min_dist_to_first_goal = min(
265
+ self.dist(state, "target"), self.min_dist_to_first_goal
266
+ )
267
+ self.min_dist_to_second_goal = min(
268
+ self.dist(state, "target2"), self.min_dist_to_second_goal
269
+ )
270
+
271
+ def _reward(thresh):
272
+ reward_first = True if self.min_dist_to_first_goal < thresh else False
273
+ reward_second = True if self.min_dist_to_second_goal < thresh else False
274
+ return 1.0 if (reward_first and reward_second) else 0.0
275
+
276
+ reward = _reward(self.goal_dist_tolerance)
277
+ return reward
278
+
279
+ @property
280
+ def succeeded(self):
281
+ thresh = self.goal_dist_tolerance
282
+ hit_first = True if self.min_dist_to_first_goal < thresh else False
283
+ hit_second = True if self.min_dist_to_first_goal < thresh else False
284
+ current_distance_to_second = self.dist(self.state, "target2")
285
+ still_at_second = True if current_distance_to_second < thresh else False
286
+ return hit_first and hit_second and still_at_second
287
+
288
+ def _create_observation_space(self, image_size):
289
+ pi2 = math.pi * 2
290
+
291
+ obs_dict = collections.OrderedDict(
292
+ block_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
293
+ block_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
294
+ effector_translation=spaces.Box(
295
+ # Small buffer for to IK noise.
296
+ low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
297
+ high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
298
+ ), # x,y
299
+ effector_target_translation=spaces.Box(
300
+ # Small buffer for to IK noise.
301
+ low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
302
+ high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
303
+ ), # x,y
304
+ target_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
305
+ target_orientation=spaces.Box(
306
+ low=-pi2,
307
+ high=pi2,
308
+ shape=(1,),
309
+ ), # theta
310
+ target2_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
311
+ target2_orientation=spaces.Box(
312
+ low=-pi2,
313
+ high=pi2,
314
+ shape=(1,),
315
+ ), # theta
316
+ )
317
+ if image_size is not None:
318
+ obs_dict["rgb"] = spaces.Box(
319
+ low=0, high=255, shape=(image_size[0], image_size[1], 3), dtype=np.uint8
320
+ )
321
+ return spaces.Dict(obs_dict)
322
+
323
+
324
+ if "BlockPushDiscontinuous-v0" in registration.registry.env_specs:
325
+ del registration.registry.env_specs["BlockPushDiscontinuous-v0"]
326
+
327
+ registration.register(
328
+ id="BlockPushDiscontinuous-v0",
329
+ entry_point=BlockPushDiscontinuous,
330
+ max_episode_steps=200,
331
+ )
332
+
333
+ registration.register(
334
+ id="BlockPushDiscontinuousRgb-v0",
335
+ entry_point=BlockPushDiscontinuous,
336
+ max_episode_steps=200,
337
+ kwargs=dict(image_size=(block_pushing.IMAGE_HEIGHT, block_pushing.IMAGE_WIDTH)),
338
+ )
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/block_pushing_multimodal.py ADDED
@@ -0,0 +1,802 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Multimodal block environments for the XArm."""
17
+
18
+ import collections
19
+ import logging
20
+ import math
21
+ from typing import Dict, List, Optional, Union
22
+ import copy
23
+ import time
24
+
25
+ from gym import spaces
26
+ from gym.envs import registration
27
+ from cleandiffuser.env.block_pushing import block_pushing
28
+ from cleandiffuser.env.block_pushing.utils import utils_pybullet
29
+ from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
30
+ from cleandiffuser.env.block_pushing.utils.utils_pybullet import ObjState
31
+ from cleandiffuser.env.block_pushing.utils.utils_pybullet import XarmState
32
+ import numpy as np
33
+ from scipy.spatial import transform
34
+ import pybullet
35
+ import pybullet_utils.bullet_client as bullet_client
36
+
37
+ # pytype: skip-file
38
+ BLOCK2_URDF_PATH = "third_party/py/envs/assets/block2.urdf"
39
+ ZONE2_URDF_PATH = "third_party/py/envs/assets/zone2.urdf"
40
+
41
+ # When resetting multiple targets, they should all be this far apart.
42
+ MIN_BLOCK_DIST = 0.1
43
+ MIN_TARGET_DIST = 0.12
44
+ # pylint: enable=line-too-long
45
+ NUM_RESET_ATTEMPTS = 1000
46
+
47
+ # Random movement of blocks
48
+ RANDOM_X_SHIFT = 0.1
49
+ RANDOM_Y_SHIFT = 0.15
50
+
51
+ logging.basicConfig(
52
+ level="INFO",
53
+ format="%(asctime)s [%(levelname)s] %(message)s",
54
+ filemode="w",
55
+ )
56
+ logger = logging.getLogger()
57
+
58
+
59
+ def build_env_name(task, shared_memory, use_image_obs):
60
+ """Construct the env name from parameters."""
61
+ del task
62
+ env_name = "BlockPushMultimodal"
63
+
64
+ if use_image_obs:
65
+ env_name = env_name + "Rgb"
66
+
67
+ if shared_memory:
68
+ env_name = "Shared" + env_name
69
+
70
+ env_name = env_name + "-v0"
71
+
72
+ return env_name
73
+
74
+
75
+ class BlockPushEventManager:
76
+ def __init__(self):
77
+ self.event_steps = {
78
+ 'REACH_0': -1,
79
+ 'REACH_1': -1,
80
+ 'TARGET_0_0': -1,
81
+ 'TARGET_0_1': -1,
82
+ 'TARGET_1_0': -1,
83
+ 'TARGET_1_1': -1
84
+ }
85
+
86
+ def reach(self, step, block_id):
87
+ key = f'REACH_{block_id}'
88
+ if self.event_steps[key] < 0:
89
+ self.event_steps[key] = step
90
+
91
+ def target(self, step, block_id, target_id):
92
+ key = f'TARGET_{block_id}_{target_id}'
93
+ if self.event_steps[key] < 0:
94
+ self.event_steps[key] = step
95
+
96
+ def reset(self):
97
+ for key in list(self.event_steps):
98
+ self.event_steps[key] = -1
99
+
100
+ def get_info(self):
101
+ return copy.deepcopy(self.event_steps)
102
+
103
+ class BlockPushMultimodal(block_pushing.BlockPush):
104
+ """2 blocks, 2 targets."""
105
+
106
+ def __init__(
107
+ self,
108
+ control_frequency=10.0,
109
+ task=block_pushing.BlockTaskVariant.PUSH,
110
+ image_size=None,
111
+ shared_memory=False,
112
+ seed=None,
113
+ goal_dist_tolerance=0.05,
114
+ abs_action=False
115
+ ):
116
+ """Creates an env instance.
117
+
118
+ Args:
119
+ control_frequency: Control frequency for the arm. Each env step will
120
+ advance the simulation by 1/control_frequency seconds.
121
+ task: enum for which task, see BlockTaskVariant enum.
122
+ image_size: Optional image size (height, width). If None, no image
123
+ observations will be used.
124
+ shared_memory: If True `pybullet.SHARED_MEMORY` is used to connect to
125
+ pybullet. Useful to debug.
126
+ seed: Optional seed for the environment.
127
+ goal_dist_tolerance: float, how far away from the goal to terminate.
128
+ """
129
+ self._target_ids = None
130
+ self._target_poses = None
131
+ self._event_manager = BlockPushEventManager()
132
+ super(BlockPushMultimodal, self).__init__(
133
+ control_frequency=control_frequency,
134
+ task=task,
135
+ image_size=image_size,
136
+ shared_memory=shared_memory,
137
+ seed=seed,
138
+ goal_dist_tolerance=goal_dist_tolerance,
139
+ )
140
+ self._init_distance = [-1.0, -1.0]
141
+ self._in_target = [[-1.0, -1.0], [-1.0, -1.0]]
142
+ self._first_move = [-1, -1]
143
+ self._step_num = 0
144
+ self._abs_action = abs_action
145
+
146
+ @property
147
+ def target_poses(self):
148
+ return self._target_poses
149
+
150
+ def get_goal_translation(self):
151
+ """Return the translation component of the goal (2D)."""
152
+ if self._target_poses:
153
+ return [i.translation for i in self._target_poses]
154
+ else:
155
+ return None
156
+
157
+ def _setup_pybullet_scene(self):
158
+ self._pybullet_client = bullet_client.BulletClient(self._connection_mode)
159
+
160
+ # Temporarily disable rendering to speed up loading URDFs.
161
+ pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 0)
162
+
163
+ self._setup_workspace_and_robot()
164
+
165
+ self._target_ids = [
166
+ utils_pybullet.load_urdf(self._pybullet_client, i, useFixedBase=True)
167
+ for i in [block_pushing.ZONE_URDF_PATH, ZONE2_URDF_PATH]
168
+ ]
169
+ self._block_ids = []
170
+ for i in [block_pushing.BLOCK_URDF_PATH, BLOCK2_URDF_PATH]:
171
+ self._block_ids.append(
172
+ utils_pybullet.load_urdf(self._pybullet_client, i, useFixedBase=False)
173
+ )
174
+
175
+ # Re-enable rendering.
176
+ pybullet.configureDebugVisualizer(pybullet.COV_ENABLE_RENDERING, 1)
177
+
178
+ self.step_simulation_to_stabilize()
179
+
180
+ def _reset_block_poses(self, workspace_center_x):
181
+ """Resets block poses."""
182
+
183
+ # Helper for choosing random block position.
184
+ def _reset_block_pose(idx, add=0.0, avoid=None):
185
+ def _get_random_translation():
186
+ block_x = (
187
+ workspace_center_x
188
+ + add
189
+ + self._rng.uniform(low=-RANDOM_X_SHIFT, high=RANDOM_X_SHIFT)
190
+ )
191
+ block_y = -0.2 + self._rng.uniform(
192
+ low=-RANDOM_Y_SHIFT, high=RANDOM_Y_SHIFT
193
+ )
194
+ block_translation = np.array([block_x, block_y, 0])
195
+ return block_translation
196
+
197
+ if avoid is None:
198
+ block_translation = _get_random_translation()
199
+ else:
200
+ # Reject targets too close to `avoid`.
201
+ for _ in range(NUM_RESET_ATTEMPTS):
202
+ block_translation = _get_random_translation()
203
+ dist = np.linalg.norm(block_translation[0] - avoid[0])
204
+ # print('block inner try_idx %d, dist %.3f' % (try_idx, dist))
205
+ if dist > MIN_BLOCK_DIST:
206
+ break
207
+ block_sampled_angle = self._rng.uniform(math.pi)
208
+ block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
209
+ self._pybullet_client.resetBasePositionAndOrientation(
210
+ self._block_ids[idx],
211
+ block_translation.tolist(),
212
+ block_rotation.as_quat().tolist(),
213
+ )
214
+ return block_translation
215
+
216
+ # Reject targets too close to `avoid`.
217
+ for _ in range(NUM_RESET_ATTEMPTS):
218
+ # Reset first block.
219
+ b0_translation = _reset_block_pose(0)
220
+ # Reset second block away from first block.
221
+ b1_translation = _reset_block_pose(1, avoid=b0_translation)
222
+ dist = np.linalg.norm(b0_translation[0] - b1_translation[0])
223
+ if dist > MIN_BLOCK_DIST:
224
+ break
225
+ else:
226
+ raise ValueError("could not find matching block")
227
+ assert dist > MIN_BLOCK_DIST
228
+
229
+ def _reset_target_poses(self, workspace_center_x):
230
+ """Resets target poses."""
231
+
232
+ def _reset_target_pose(idx, add=0.0, avoid=None):
233
+ def _get_random_translation():
234
+ # Choose x,y randomly.
235
+ target_x = (
236
+ workspace_center_x
237
+ + add
238
+ + self._rng.uniform(
239
+ low=-0.05 * RANDOM_X_SHIFT, high=0.05 * RANDOM_X_SHIFT
240
+ )
241
+ )
242
+ target_y = 0.2 + self._rng.uniform(
243
+ low=-0.05 * RANDOM_Y_SHIFT, high=0.05 * RANDOM_Y_SHIFT
244
+ )
245
+ target_translation = np.array([target_x, target_y, 0.020])
246
+ return target_translation
247
+
248
+ if avoid is None:
249
+ target_translation = _get_random_translation()
250
+ else:
251
+ # Reject targets too close to `avoid`.
252
+ for _ in range(NUM_RESET_ATTEMPTS):
253
+ target_translation = _get_random_translation()
254
+ dist = np.linalg.norm(target_translation[0] - avoid[0])
255
+ # print('target inner try_idx %d, dist %.3f' % (try_idx, dist))
256
+ if dist > MIN_TARGET_DIST:
257
+ break
258
+ target_sampled_angle = math.pi + self._rng.uniform(
259
+ low=-math.pi / 30, high=math.pi / 30
260
+ )
261
+ target_rotation = transform.Rotation.from_rotvec(
262
+ [0, 0, target_sampled_angle]
263
+ )
264
+ self._pybullet_client.resetBasePositionAndOrientation(
265
+ self._target_ids[idx],
266
+ target_translation.tolist(),
267
+ target_rotation.as_quat().tolist(),
268
+ )
269
+ self._target_poses[idx] = Pose3d(
270
+ rotation=target_rotation, translation=target_translation
271
+ )
272
+
273
+ if self._target_poses is None:
274
+ self._target_poses = [None for _ in range(len(self._target_ids))]
275
+
276
+ for _ in range(NUM_RESET_ATTEMPTS):
277
+ # Choose the first target.
278
+ add = 0.12 * self._rng.choice([-1, 1])
279
+ # Randomly flip the location of the targets.
280
+ _reset_target_pose(0, add=add)
281
+ _reset_target_pose(1, add=-add, avoid=self._target_poses[0].translation)
282
+ dist = np.linalg.norm(
283
+ self._target_poses[0].translation[0]
284
+ - self._target_poses[1].translation[0]
285
+ )
286
+ if dist > MIN_TARGET_DIST:
287
+ break
288
+ else:
289
+ raise ValueError("could not find matching target")
290
+ assert dist > MIN_TARGET_DIST
291
+
292
+ def _reset_object_poses(self, workspace_center_x, workspace_center_y):
293
+ # Reset block poses.
294
+ self._reset_block_poses(workspace_center_x)
295
+
296
+ # Reset target poses.
297
+ self._reset_target_poses(workspace_center_x)
298
+
299
+ self._init_distance = [-1.0, -1.0]
300
+ self._in_target = [[-1.0, -1.0], [-1.0, -1.0]]
301
+ self._step_num = 0
302
+
303
+ def reset(self, reset_poses=True):
304
+ workspace_center_x = 0.4
305
+ workspace_center_y = 0.0
306
+
307
+ if reset_poses:
308
+ self._pybullet_client.restoreState(self._saved_state)
309
+
310
+ rotation = transform.Rotation.from_rotvec([0, math.pi, 0])
311
+ translation = np.array([0.3, -0.4, block_pushing.EFFECTOR_HEIGHT])
312
+ starting_pose = Pose3d(rotation=rotation, translation=translation)
313
+ self._set_robot_target_effector_pose(starting_pose)
314
+ self._reset_object_poses(workspace_center_x, workspace_center_y)
315
+
316
+ # else:
317
+ self._target_poses = [
318
+ self._get_target_pose(idx) for idx in self._target_ids
319
+ ]
320
+
321
+ if reset_poses:
322
+ self.step_simulation_to_stabilize()
323
+
324
+ state = self._compute_state()
325
+ self._previous_state = state
326
+ self._event_manager.reset()
327
+ return state
328
+
329
+ def _get_target_pose(self, idx):
330
+ (
331
+ target_translation,
332
+ target_orientation_quat,
333
+ ) = self._pybullet_client.getBasePositionAndOrientation(idx)
334
+ target_rotation = transform.Rotation.from_quat(target_orientation_quat)
335
+ target_translation = np.array(target_translation)
336
+ return Pose3d(rotation=target_rotation, translation=target_translation)
337
+
338
+ def _compute_reach_target(self, state):
339
+ xy_block = state["block_translation"]
340
+ xy_target = state["target_translation"]
341
+
342
+ xy_block_to_target = xy_target - xy_block
343
+ xy_dir_block_to_target = (xy_block_to_target) / np.linalg.norm(
344
+ xy_block_to_target
345
+ )
346
+ self.reach_target_translation = xy_block + -1 * xy_dir_block_to_target * 0.05
347
+
348
+ def _compute_state(self):
349
+ effector_pose = self._robot.forward_kinematics()
350
+
351
+ def _get_block_pose(idx):
352
+ block_position_and_orientation = (
353
+ self._pybullet_client.getBasePositionAndOrientation(
354
+ self._block_ids[idx]
355
+ )
356
+ )
357
+ block_pose = Pose3d(
358
+ rotation=transform.Rotation.from_quat(
359
+ block_position_and_orientation[1]
360
+ ),
361
+ translation=block_position_and_orientation[0],
362
+ )
363
+ return block_pose
364
+
365
+ block_poses = [_get_block_pose(i) for i in range(len(self._block_ids))]
366
+
367
+ def _yaw_from_pose(pose):
368
+ return np.array([pose.rotation.as_euler("xyz", degrees=False)[-1] % np.pi])
369
+
370
+ obs = collections.OrderedDict(
371
+ block_translation=block_poses[0].translation[0:2],
372
+ block_orientation=_yaw_from_pose(block_poses[0]),
373
+ block2_translation=block_poses[1].translation[0:2],
374
+ block2_orientation=_yaw_from_pose(block_poses[1]),
375
+ effector_translation=effector_pose.translation[0:2],
376
+ effector_target_translation=self._target_effector_pose.translation[0:2],
377
+ target_translation=self._target_poses[0].translation[0:2],
378
+ target_orientation=_yaw_from_pose(self._target_poses[0]),
379
+ target2_translation=self._target_poses[1].translation[0:2],
380
+ target2_orientation=_yaw_from_pose(self._target_poses[1]),
381
+ )
382
+
383
+ for i in range(2):
384
+ new_distance = np.linalg.norm(
385
+ block_poses[i].translation[0:2]
386
+ ) # + np.linalg.norm(_yaw_from_pose(block_poses[i]))
387
+ if self._init_distance[i] == -1:
388
+ self._init_distance[i] = new_distance
389
+ else:
390
+ if self._init_distance[i] != 100:
391
+ if np.abs(new_distance - self._init_distance[i]) > 1e-3:
392
+ logger.info(f"Block {i} moved on step {self._step_num}")
393
+ self._event_manager.reach(step=self._step_num, block_id=i)
394
+ self._init_distance[i] = 100
395
+
396
+ self._step_num += 1
397
+ if self._image_size is not None:
398
+ obs["rgb"] = self._render_camera(self._image_size)
399
+ return obs
400
+
401
+ def step(self, action):
402
+ self._step_robot_and_sim(action)
403
+
404
+ state = self._compute_state()
405
+ done = False
406
+ reward = self._get_reward(state)
407
+ if reward >= 0.5:
408
+ # Terminate the episode if both blocks are close enough to the targets.
409
+ done = True
410
+
411
+ info = self._event_manager.get_info()
412
+ return state, reward, done, info
413
+
414
+ def _step_robot_and_sim(self, action):
415
+ """Steps the robot and pybullet sim."""
416
+ # Compute target_effector_pose by shifting the effector's pose by the
417
+ # action.
418
+ if self._abs_action:
419
+ target_effector_translation = np.array([action[0], action[1], 0])
420
+ else:
421
+ target_effector_translation = np.array(
422
+ self._target_effector_pose.translation
423
+ ) + np.array([action[0], action[1], 0])
424
+
425
+ target_effector_translation[0:2] = np.clip(
426
+ target_effector_translation[0:2],
427
+ self.workspace_bounds[0],
428
+ self.workspace_bounds[1],
429
+ )
430
+ target_effector_translation[-1] = self.effector_height
431
+ target_effector_pose = Pose3d(
432
+ rotation=block_pushing.EFFECTOR_DOWN_ROTATION, translation=target_effector_translation
433
+ )
434
+
435
+ self._set_robot_target_effector_pose(target_effector_pose)
436
+
437
+ # Update sleep time dynamically to stay near real-time.
438
+ frame_sleep_time = 0
439
+ if self._connection_mode == pybullet.SHARED_MEMORY:
440
+ cur_time = time.time()
441
+ if self._last_loop_time is not None:
442
+ # Calculate the total, non-sleeping time from the previous frame, this
443
+ # includes the actual step as well as any compute that happens in the
444
+ # caller thread (model inference, etc).
445
+ compute_time = (
446
+ cur_time
447
+ - self._last_loop_time
448
+ - self._last_loop_frame_sleep_time * self._sim_steps_per_step
449
+ )
450
+ # Use this to calculate the current frame's total sleep time to ensure
451
+ # that env.step runs at policy rate. This is an estimate since the
452
+ # previous frame's compute time may not match the current frame.
453
+ total_sleep_time = max((1 / self._control_frequency) - compute_time, 0)
454
+ # Now spread this out over the inner sim steps. This doesn't change
455
+ # control in any way, but makes the animation appear smooth.
456
+ frame_sleep_time = total_sleep_time / self._sim_steps_per_step
457
+ else:
458
+ # No estimate of the previous frame's compute, assume it is zero.
459
+ frame_sleep_time = 1 / self._step_frequency
460
+
461
+ # Cache end of this loop time, to compute sleep time on next iteration.
462
+ self._last_loop_time = cur_time
463
+ self._last_loop_frame_sleep_time = frame_sleep_time
464
+
465
+ for _ in range(self._sim_steps_per_step):
466
+ if self._connection_mode == pybullet.SHARED_MEMORY:
467
+ block_pushing.sleep_spin(frame_sleep_time)
468
+ self._pybullet_client.stepSimulation()
469
+
470
+ def _get_reward(self, state):
471
+ # Reward is 1. if both blocks are inside targets, but not the same target.
472
+ targets = ["target", "target2"]
473
+
474
+ def _block_target_dist(block, target):
475
+ return np.linalg.norm(
476
+ state["%s_translation" % block] - state["%s_translation" % target]
477
+ )
478
+
479
+ def _closest_target(block):
480
+ # Distances to all targets.
481
+ dists = [_block_target_dist(block, t) for t in targets]
482
+ # Which is closest.
483
+ closest_target = targets[np.argmin(dists)]
484
+ closest_dist = np.min(dists)
485
+ # Is it in the closest target?
486
+ in_target = closest_dist < self.goal_dist_tolerance
487
+ return closest_target, in_target
488
+
489
+ blocks = ["block", "block2"]
490
+
491
+ reward = 0.0
492
+
493
+ for t_i, t in enumerate(targets):
494
+ for b_i, b in enumerate(blocks):
495
+ if self._in_target[t_i][b_i] == -1:
496
+ dist = _block_target_dist(b, t)
497
+ if dist < self.goal_dist_tolerance:
498
+ self._in_target[t_i][b_i] = 0
499
+ logger.info(
500
+ f"Block {b_i} entered target {t_i} on step {self._step_num}"
501
+ )
502
+ self._event_manager.target(step=self._step_num, block_id=b_i, target_id=t_i)
503
+ reward += 0.49
504
+
505
+ b0_closest_target, b0_in_target = _closest_target("block")
506
+ b1_closest_target, b1_in_target = _closest_target("block2")
507
+ # reward = 0.0
508
+ if b0_in_target and b1_in_target and (b0_closest_target != b1_closest_target):
509
+ reward = 0.51
510
+ return reward
511
+
512
+ def _compute_goal_distance(self, state):
513
+ blocks = ["block", "block2"]
514
+
515
+ def _target_block_dist(target, block):
516
+ return np.linalg.norm(
517
+ state["%s_translation" % block] - state["%s_translation" % target]
518
+ )
519
+
520
+ def _closest_block_dist(target):
521
+ dists = [_target_block_dist(target, b) for b in blocks]
522
+ closest_dist = np.min(dists)
523
+ return closest_dist
524
+
525
+ t0_closest_dist = _closest_block_dist("target")
526
+ t1_closest_dist = _closest_block_dist("target2")
527
+ return np.mean([t0_closest_dist, t1_closest_dist])
528
+
529
+ @property
530
+ def succeeded(self):
531
+ state = self._compute_state()
532
+ reward = self._get_reward(state)
533
+ if reward >= 0.5:
534
+ return True
535
+ return False
536
+
537
+ def _create_observation_space(self, image_size):
538
+ pi2 = math.pi * 2
539
+
540
+ obs_dict = collections.OrderedDict(
541
+ block_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
542
+ block_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
543
+ block2_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
544
+ block2_orientation=spaces.Box(low=-pi2, high=pi2, shape=(1,)), # phi
545
+ effector_translation=spaces.Box(
546
+ low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
547
+ high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
548
+ ), # x,y
549
+ effector_target_translation=spaces.Box(
550
+ low=block_pushing.WORKSPACE_BOUNDS[0] - 0.1,
551
+ high=block_pushing.WORKSPACE_BOUNDS[1] + 0.1,
552
+ ), # x,y
553
+ target_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
554
+ target_orientation=spaces.Box(
555
+ low=-pi2,
556
+ high=pi2,
557
+ shape=(1,),
558
+ ), # theta
559
+ target2_translation=spaces.Box(low=-5, high=5, shape=(2,)), # x,y
560
+ target2_orientation=spaces.Box(
561
+ low=-pi2,
562
+ high=pi2,
563
+ shape=(1,),
564
+ ), # theta
565
+ )
566
+ if image_size is not None:
567
+ obs_dict["rgb"] = spaces.Box(
568
+ low=0, high=255, shape=(image_size[0], image_size[1], 3), dtype=np.uint8
569
+ )
570
+ return spaces.Dict(obs_dict)
571
+
572
+ def get_pybullet_state(self):
573
+ """Save pybullet state of the scene.
574
+
575
+ Returns:
576
+ dict containing 'robots', 'robot_end_effectors', 'targets', 'objects',
577
+ each containing a list of ObjState.
578
+ """
579
+ state: Dict[str, List[ObjState]] = {}
580
+
581
+ state["robots"] = [
582
+ XarmState.get_bullet_state(
583
+ self._pybullet_client,
584
+ self.robot.xarm,
585
+ target_effector_pose=self._target_effector_pose,
586
+ goal_translation=None,
587
+ )
588
+ ]
589
+
590
+ state["robot_end_effectors"] = []
591
+ if self.robot.end_effector:
592
+ state["robot_end_effectors"].append(
593
+ ObjState.get_bullet_state(
594
+ self._pybullet_client, self.robot.end_effector
595
+ )
596
+ )
597
+
598
+ state["targets"] = []
599
+ if self._target_ids:
600
+ for target_id in self._target_ids:
601
+ state["targets"].append(
602
+ ObjState.get_bullet_state(self._pybullet_client, target_id)
603
+ )
604
+
605
+ state["objects"] = []
606
+ for obj_id in self.get_obj_ids():
607
+ state["objects"].append(
608
+ ObjState.get_bullet_state(self._pybullet_client, obj_id)
609
+ )
610
+
611
+ return state
612
+
613
+ def set_pybullet_state(self, state):
614
+ """Restore pyullet state.
615
+
616
+ WARNING: py_environment wrapper assumes environments aren't reset in their
617
+ constructor and will often reset the environment unintentionally. It is
618
+ always recommended that you call env.reset on the tfagents wrapper before
619
+ playback (replaying pybullet_state).
620
+
621
+ Args:
622
+ state: dict containing 'robots', 'robot_end_effectors', 'targets',
623
+ 'objects', each containing a list of ObjState.
624
+ """
625
+
626
+ assert isinstance(state["robots"][0], XarmState)
627
+ xarm_state: XarmState = state["robots"][0]
628
+ xarm_state.set_bullet_state(self._pybullet_client, self.robot.xarm)
629
+ self._set_robot_target_effector_pose(xarm_state.target_effector_pose)
630
+
631
+ def _set_state_safe(obj_state, obj_id):
632
+ if obj_state is not None:
633
+ assert obj_id is not None, "Cannot set state for missing object."
634
+ obj_state.set_bullet_state(self._pybullet_client, obj_id)
635
+ else:
636
+ assert obj_id is None, f"No state found for obj_id {obj_id}"
637
+
638
+ robot_end_effectors = state["robot_end_effectors"]
639
+ _set_state_safe(
640
+ None if not robot_end_effectors else robot_end_effectors[0],
641
+ self.robot.end_effector,
642
+ )
643
+
644
+ for target_state, target_id in zip(state["targets"], self._target_ids):
645
+ _set_state_safe(target_state, target_id)
646
+
647
+ obj_ids = self.get_obj_ids()
648
+ assert len(state["objects"]) == len(obj_ids), "State length mismatch"
649
+ for obj_state, obj_id in zip(state["objects"], obj_ids):
650
+ _set_state_safe(obj_state, obj_id)
651
+
652
+ self.reset(reset_poses=False)
653
+
654
+
655
+ class BlockPushHorizontalMultimodal(BlockPushMultimodal):
656
+ def _reset_object_poses(self, workspace_center_x, workspace_center_y):
657
+ # Reset block poses.
658
+ self._reset_block_poses(workspace_center_y)
659
+
660
+ # Reset target poses.
661
+ self._reset_target_poses(workspace_center_y)
662
+
663
+ def _reset_block_poses(self, workspace_center_y):
664
+ """Resets block poses."""
665
+
666
+ # Helper for choosing random block position.
667
+ def _reset_block_pose(idx, add=0.0, avoid=None):
668
+ def _get_random_translation():
669
+ block_x = 0.35 + 0.5 * self._rng.uniform(
670
+ low=-RANDOM_X_SHIFT, high=RANDOM_X_SHIFT
671
+ )
672
+ block_y = (
673
+ workspace_center_y
674
+ + add
675
+ + 0.5 * self._rng.uniform(low=-RANDOM_Y_SHIFT, high=RANDOM_Y_SHIFT)
676
+ )
677
+ block_translation = np.array([block_x, block_y, 0])
678
+ return block_translation
679
+
680
+ if avoid is None:
681
+ block_translation = _get_random_translation()
682
+ else:
683
+ # Reject targets too close to `avoid`.
684
+ for _ in range(NUM_RESET_ATTEMPTS):
685
+ block_translation = _get_random_translation()
686
+ dist = np.linalg.norm(block_translation[0] - avoid[0])
687
+ # print('block inner try_idx %d, dist %.3f' % (try_idx, dist))
688
+ if dist > MIN_BLOCK_DIST:
689
+ break
690
+ block_sampled_angle = self._rng.uniform(math.pi)
691
+ block_rotation = transform.Rotation.from_rotvec([0, 0, block_sampled_angle])
692
+ self._pybullet_client.resetBasePositionAndOrientation(
693
+ self._block_ids[idx],
694
+ block_translation.tolist(),
695
+ block_rotation.as_quat().tolist(),
696
+ )
697
+ return block_translation
698
+
699
+ # Reject targets too close to `avoid`.
700
+ for _ in range(NUM_RESET_ATTEMPTS):
701
+ # Reset first block.
702
+ add = 0.2 * self._rng.choice([-1, 1])
703
+ b0_translation = _reset_block_pose(0, add=add)
704
+ # Reset second block away from first block.
705
+ b1_translation = _reset_block_pose(1, add=-add, avoid=b0_translation)
706
+ dist = np.linalg.norm(b0_translation[0] - b1_translation[0])
707
+ if dist > MIN_BLOCK_DIST:
708
+ break
709
+ else:
710
+ raise ValueError("could not find matching block")
711
+ assert dist > MIN_BLOCK_DIST
712
+
713
+ def _reset_target_poses(self, workspace_center_y):
714
+ """Resets target poses."""
715
+
716
+ def _reset_target_pose(idx, add=0.0, avoid=None):
717
+ def _get_random_translation():
718
+ # Choose x,y randomly.
719
+ target_x = 0.5 + self._rng.uniform(
720
+ low=-0.05 * RANDOM_X_SHIFT, high=0.05 * RANDOM_X_SHIFT
721
+ )
722
+ target_y = (
723
+ workspace_center_y
724
+ + add
725
+ + self._rng.uniform(
726
+ low=-0.05 * RANDOM_Y_SHIFT, high=0.05 * RANDOM_Y_SHIFT
727
+ )
728
+ )
729
+ target_translation = np.array([target_x, target_y, 0.020])
730
+ return target_translation
731
+
732
+ if avoid is None:
733
+ target_translation = _get_random_translation()
734
+ else:
735
+ # Reject targets too close to `avoid`.
736
+ for _ in range(NUM_RESET_ATTEMPTS):
737
+ target_translation = _get_random_translation()
738
+ dist = np.linalg.norm(target_translation[0] - avoid[0])
739
+ # print('target inner try_idx %d, dist %.3f' % (try_idx, dist))
740
+ if dist > MIN_TARGET_DIST:
741
+ break
742
+ target_sampled_angle = math.pi + self._rng.uniform(
743
+ low=-math.pi / 30, high=math.pi / 30
744
+ )
745
+ target_rotation = transform.Rotation.from_rotvec(
746
+ [0, 0, target_sampled_angle]
747
+ )
748
+ self._pybullet_client.resetBasePositionAndOrientation(
749
+ self._target_ids[idx],
750
+ target_translation.tolist(),
751
+ target_rotation.as_quat().tolist(),
752
+ )
753
+ self._target_poses[idx] = Pose3d(
754
+ rotation=target_rotation, translation=target_translation
755
+ )
756
+
757
+ if self._target_poses is None:
758
+ self._target_poses = [None for _ in range(len(self._target_ids))]
759
+
760
+ for _ in range(NUM_RESET_ATTEMPTS):
761
+ # Choose the first target.
762
+ add = 0.2 * self._rng.choice([-1, 1])
763
+ # Randomly flip the location of the targets.
764
+ _reset_target_pose(0, add=add)
765
+ _reset_target_pose(1, add=-add, avoid=self._target_poses[0].translation)
766
+ dist = np.linalg.norm(
767
+ self._target_poses[0].translation[0]
768
+ - self._target_poses[1].translation[0]
769
+ )
770
+ break
771
+ # if dist > MIN_TARGET_DIST:
772
+ # break
773
+ else:
774
+ raise ValueError("could not find matching target")
775
+ # assert dist > MIN_TARGET_DIST
776
+
777
+
778
+ if "BlockPushMultimodal-v0" in registration.registry.env_specs:
779
+ del registration.registry.env_specs["BlockPushMultimodal-v0"]
780
+
781
+ registration.register(
782
+ id="BlockPushMultimodal-v0", entry_point=BlockPushMultimodal, max_episode_steps=350
783
+ )
784
+
785
+ registration.register(
786
+ id="BlockPushMultimodalFlipped-v0",
787
+ entry_point=BlockPushHorizontalMultimodal,
788
+ max_episode_steps=25,
789
+ )
790
+
791
+ registration.register(
792
+ id="SharedBlockPushMultimodal-v0",
793
+ entry_point=BlockPushMultimodal,
794
+ kwargs=dict(shared_memory=True),
795
+ max_episode_steps=350,
796
+ )
797
+ registration.register(
798
+ id="BlockPushMultimodalRgb-v0",
799
+ entry_point=BlockPushMultimodal,
800
+ max_episode_steps=350,
801
+ kwargs=dict(image_size=(block_pushing.IMAGE_HEIGHT, block_pushing.IMAGE_WIDTH)),
802
+ )
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/discontinuous_push_oracle.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Pushes to first target, waits, then pushes to second target."""
17
+
18
+ import cleandiffuser.env.block_pushing.oracles.oriented_push_oracle as oriented_push_oracle_module
19
+ import numpy as np
20
+ from tf_agents.trajectories import policy_step
21
+ from tf_agents.trajectories import time_step as ts
22
+ from tf_agents.typing import types
23
+
24
+ # Only used for debug visualization.
25
+ import pybullet # pylint: disable=unused-import
26
+
27
+
28
+ class DiscontinuousOrientedPushOracle(oriented_push_oracle_module.OrientedPushOracle):
29
+ """Pushes to first target, waits, then pushes to second target."""
30
+
31
+ def __init__(self, env, goal_tolerance=0.04, wait=0):
32
+ super(DiscontinuousOrientedPushOracle, self).__init__(env)
33
+ self._countdown = 0
34
+ self._wait = wait
35
+ self._goal_dist_tolerance = goal_tolerance
36
+
37
+ def reset(self):
38
+ self.phase = "move_to_pre_block"
39
+ self._countdown = 0
40
+
41
+ def _action(self, time_step, policy_state):
42
+ if time_step.is_first():
43
+ self.reset()
44
+ # Move to first target first.
45
+ self._current_target = "target"
46
+ self._has_switched = False
47
+
48
+ def _block_target_dist(block, target):
49
+ dist = np.linalg.norm(
50
+ time_step.observation["%s_translation" % block]
51
+ - time_step.observation["%s_translation" % target]
52
+ )
53
+ return dist
54
+
55
+ d1 = _block_target_dist("block", "target")
56
+ if d1 < self._goal_dist_tolerance and not self._has_switched:
57
+ self._countdown = self._wait
58
+ # If first block has been pushed to first target, switch to second block.
59
+ self._has_switched = True
60
+ self._current_target = "target2"
61
+
62
+ xy_delta = self._get_action_for_block_target(
63
+ time_step, block="block", target=self._current_target
64
+ )
65
+
66
+ if self._countdown > 0:
67
+ xy_delta = np.zeros_like(xy_delta)
68
+ self._countdown -= 1
69
+
70
+ return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/multimodal_push_oracle.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Oracle for multimodal pushing task."""
17
+ import cleandiffuser.env.block_pushing.oracles.oriented_push_oracle as oriented_push_oracle_module
18
+ import numpy as np
19
+ from tf_agents.trajectories import policy_step
20
+ from tf_agents.trajectories import time_step as ts
21
+ from tf_agents.typing import types
22
+
23
+ # Only used for debug visualization.
24
+ import pybullet # pylint: disable=unused-import
25
+
26
+
27
+ class MultimodalOrientedPushOracle(oriented_push_oracle_module.OrientedPushOracle):
28
+ """Oracle for multimodal pushing task."""
29
+
30
+ def __init__(self, env, goal_dist_tolerance=0.04, action_noise_std=0.0):
31
+ super(MultimodalOrientedPushOracle, self).__init__(env)
32
+ self._goal_dist_tolerance = goal_dist_tolerance
33
+ self._action_noise_std = action_noise_std
34
+
35
+ def reset(self):
36
+ self.origin = None
37
+ self.first_preblock = None
38
+ self.phase = "move_to_pre_block"
39
+
40
+ def _get_move_to_preblock(self, xy_pre_block, xy_ee):
41
+ max_step_velocity = 0.3
42
+ # Go 5 cm away from the block, on the line between the block and target.
43
+ xy_delta_to_preblock = xy_pre_block - xy_ee
44
+ diff = np.linalg.norm(xy_delta_to_preblock)
45
+ if diff < 0.001:
46
+ self.phase = "move_to_block"
47
+ if self.first_preblock is None:
48
+ self.first_preblock = np.copy(xy_pre_block)
49
+ xy_delta = xy_delta_to_preblock
50
+ return xy_delta, max_step_velocity
51
+
52
+ def _get_action_for_block_target(self, time_step, block="block", target="target"):
53
+ # Specifying this as velocity makes it independent of control frequency.
54
+ max_step_velocity = 0.35
55
+
56
+ info = self._get_action_info(time_step, block, target)
57
+
58
+ if self.origin is None:
59
+ self.origin = np.copy(info.xy_ee)
60
+
61
+ if self.phase == "move_to_pre_block":
62
+ xy_delta, max_step_velocity = self._get_move_to_preblock(
63
+ info.xy_pre_block, info.xy_ee
64
+ )
65
+
66
+ if self.phase == "return_to_first_preblock":
67
+ max_step_velocity = 0.3
68
+ if self.first_preblock is None:
69
+ self.first_preblock = self.origin
70
+ # Return to the first preblock.
71
+ xy_delta_to_origin = self.first_preblock - info.xy_ee
72
+ diff = np.linalg.norm(xy_delta_to_origin)
73
+ if diff < 0.001:
74
+ self.phase = "return_to_origin"
75
+ xy_delta = xy_delta_to_origin
76
+
77
+ if self.phase == "return_to_origin":
78
+ max_step_velocity = 0.3
79
+ # Go 5 cm away from the block, on the line between the block and target.
80
+ xy_delta_to_origin = self.origin - info.xy_ee
81
+ diff = np.linalg.norm(xy_delta_to_origin)
82
+ if diff < 0.001:
83
+ self.phase = "move_to_pre_block"
84
+ xy_delta = xy_delta_to_origin
85
+
86
+ if self.phase == "move_to_block":
87
+ xy_delta = self._get_move_to_block(
88
+ info.xy_delta_to_nexttoblock,
89
+ info.theta_threshold_to_orient,
90
+ info.theta_error,
91
+ )
92
+
93
+ if self.phase == "push_block":
94
+ xy_delta = self._get_push_block(
95
+ info.theta_error,
96
+ info.theta_threshold_to_orient,
97
+ info.xy_delta_to_touchingblock,
98
+ )
99
+
100
+ orient_circle_diameter = 0.025
101
+
102
+ if self.phase == "orient_block_left" or self.phase == "orient_block_right":
103
+ max_step_velocity = 0.15
104
+
105
+ if self.phase == "orient_block_left":
106
+ xy_delta = self._get_orient_block_left(
107
+ info.xy_dir_block_to_ee,
108
+ orient_circle_diameter,
109
+ info.xy_block,
110
+ info.xy_ee,
111
+ info.theta_error,
112
+ info.theta_threshold_flat_enough,
113
+ )
114
+
115
+ if self.phase == "orient_block_right":
116
+ xy_delta = self._get_orient_block_right(
117
+ info.xy_dir_block_to_ee,
118
+ orient_circle_diameter,
119
+ info.xy_block,
120
+ info.xy_ee,
121
+ info.theta_error,
122
+ info.theta_threshold_flat_enough,
123
+ )
124
+
125
+ if self._action_noise_std != 0.0:
126
+ xy_delta += self._np_random_state.randn(2) * self._action_noise_std
127
+
128
+ max_step_distance = max_step_velocity * (1 / self._env.get_control_frequency())
129
+ length = np.linalg.norm(xy_delta)
130
+ if length > max_step_distance:
131
+ xy_direction = xy_delta / length
132
+ xy_delta = xy_direction * max_step_distance
133
+ return xy_delta
134
+
135
+ def _choose_goal_order(self):
136
+ """Chooses block->target order for multimodal pushing."""
137
+ # Define all possible ((first_block, first_target),
138
+ # (second_block, second_target)).
139
+ possible_orders = [
140
+ (("block", "target"), ("block2", "target2")),
141
+ (("block", "target2"), ("block2", "target")),
142
+ (("block2", "target"), ("block", "target2")),
143
+ (("block2", "target2"), ("block", "target")),
144
+ ]
145
+ # import pdb; pdb.set_trace()
146
+ # result = random.choice(possible_orders)
147
+ result = possible_orders[self._env._rng.choice(len(possible_orders))]
148
+ return result
149
+
150
+ def _action(self, time_step, policy_state):
151
+ if time_step.is_first():
152
+ self.reset()
153
+ (
154
+ (self._first_block, self._first_target),
155
+ (self._second_block, self._second_target),
156
+ ) = self._choose_goal_order()
157
+ self._current_block, self._current_target = (
158
+ self._first_block,
159
+ self._first_target,
160
+ )
161
+ self._has_switched = False
162
+
163
+ def _block_target_dist(block, target):
164
+ dist = np.linalg.norm(
165
+ time_step.observation["%s_translation" % block]
166
+ - time_step.observation["%s_translation" % target]
167
+ )
168
+ return dist
169
+
170
+ if (
171
+ _block_target_dist(self._first_block, self._first_target)
172
+ < self._goal_dist_tolerance
173
+ and not self._has_switched
174
+ ):
175
+ # If first block has been pushed to first target, switch to second block.
176
+ self._current_block, self._current_target = (
177
+ self._second_block,
178
+ self._second_target,
179
+ )
180
+ self._has_switched = True
181
+ self.phase = "return_to_first_preblock"
182
+
183
+ xy_delta = self._get_action_for_block_target(
184
+ time_step, block=self._current_block, target=self._current_target
185
+ )
186
+
187
+ return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/oriented_push_oracle.py ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Oracle for pushing task which orients the block then pushes it."""
17
+
18
+ import cleandiffuser.env.block_pushing.oracles.pushing_info as pushing_info_module
19
+ import numpy as np
20
+ from tf_agents.policies import py_policy
21
+ from tf_agents.trajectories import policy_step
22
+ from tf_agents.trajectories import time_step as ts
23
+ from tf_agents.typing import types
24
+
25
+ # Only used for debug visualization.
26
+ import pybullet # pylint: disable=unused-import
27
+
28
+
29
+ class OrientedPushOracle(py_policy.PyPolicy):
30
+ """Oracle for pushing task which orients the block then pushes it."""
31
+
32
+ def __init__(self, env, action_noise_std=0.0):
33
+ super(OrientedPushOracle, self).__init__(
34
+ env.time_step_spec(), env.action_spec()
35
+ )
36
+ self._env = env
37
+ self._np_random_state = np.random.RandomState(0)
38
+ self.phase = "move_to_pre_block"
39
+ self._action_noise_std = action_noise_std
40
+
41
+ def reset(self):
42
+ self.phase = "move_to_pre_block"
43
+
44
+ def get_theta_from_vector(self, vector):
45
+ return np.arctan2(vector[1], vector[0])
46
+
47
+ def theta_to_rotation2d(self, theta):
48
+ r = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
49
+ return r
50
+
51
+ def rotate(self, theta, xy_dir_block_to_ee):
52
+ rot_2d = self.theta_to_rotation2d(theta)
53
+ return rot_2d @ xy_dir_block_to_ee
54
+
55
+ def _get_action_info(self, time_step, block, target):
56
+ xy_block = time_step.observation["%s_translation" % block][:2]
57
+ theta_block = time_step.observation["%s_orientation" % block]
58
+ xy_target = time_step.observation["%s_translation" % target][:2]
59
+ xy_ee = time_step.observation["effector_target_translation"][:2]
60
+
61
+ xy_block_to_target = xy_target - xy_block
62
+ xy_dir_block_to_target = (xy_block_to_target) / np.linalg.norm(
63
+ xy_block_to_target
64
+ )
65
+ theta_to_target = self.get_theta_from_vector(xy_dir_block_to_target)
66
+
67
+ theta_error = theta_to_target - theta_block
68
+ # Block has 4-way symmetry.
69
+ while theta_error > np.pi / 4:
70
+ theta_error -= np.pi / 2.0
71
+ while theta_error < -np.pi / 4:
72
+ theta_error += np.pi / 2.0
73
+
74
+ xy_pre_block = xy_block + -xy_dir_block_to_target * 0.05
75
+ xy_nexttoblock = xy_block + -xy_dir_block_to_target * 0.03
76
+ xy_touchingblock = xy_block + -xy_dir_block_to_target * 0.01
77
+ xy_delta_to_nexttoblock = xy_nexttoblock - xy_ee
78
+ xy_delta_to_touchingblock = xy_touchingblock - xy_ee
79
+
80
+ xy_block_to_ee = xy_ee - xy_block
81
+ xy_dir_block_to_ee = xy_block_to_ee / np.linalg.norm(xy_block_to_ee)
82
+
83
+ theta_threshold_to_orient = 0.2
84
+ theta_threshold_flat_enough = 0.03
85
+ return pushing_info_module.PushingInfo(
86
+ xy_block=xy_block,
87
+ xy_ee=xy_ee,
88
+ xy_pre_block=xy_pre_block,
89
+ xy_delta_to_nexttoblock=xy_delta_to_nexttoblock,
90
+ xy_delta_to_touchingblock=xy_delta_to_touchingblock,
91
+ xy_dir_block_to_ee=xy_dir_block_to_ee,
92
+ theta_threshold_to_orient=theta_threshold_to_orient,
93
+ theta_threshold_flat_enough=theta_threshold_flat_enough,
94
+ theta_error=theta_error,
95
+ )
96
+
97
+ def _get_move_to_preblock(self, xy_pre_block, xy_ee):
98
+ max_step_velocity = 0.3
99
+ # Go 5 cm away from the block, on the line between the block and target.
100
+ xy_delta_to_preblock = xy_pre_block - xy_ee
101
+ diff = np.linalg.norm(xy_delta_to_preblock)
102
+ if diff < 0.001:
103
+ self.phase = "move_to_block"
104
+ xy_delta = xy_delta_to_preblock
105
+ return xy_delta, max_step_velocity
106
+
107
+ def _get_move_to_block(
108
+ self, xy_delta_to_nexttoblock, theta_threshold_to_orient, theta_error
109
+ ):
110
+ diff = np.linalg.norm(xy_delta_to_nexttoblock)
111
+ if diff < 0.001:
112
+ self.phase = "push_block"
113
+ # If need to re-oorient, then re-orient.
114
+ if theta_error > theta_threshold_to_orient:
115
+ self.phase = "orient_block_left"
116
+ if theta_error < -theta_threshold_to_orient:
117
+ self.phase = "orient_block_right"
118
+ # Otherwise, push into the block.
119
+ xy_delta = xy_delta_to_nexttoblock
120
+ return xy_delta
121
+
122
+ def _get_push_block(
123
+ self, theta_error, theta_threshold_to_orient, xy_delta_to_touchingblock
124
+ ):
125
+ # If need to reorient, go back to move_to_pre_block, move_to_block first.
126
+ if theta_error > theta_threshold_to_orient:
127
+ self.phase = "move_to_pre_block"
128
+ if theta_error < -theta_threshold_to_orient:
129
+ self.phase = "move_to_pre_block"
130
+ xy_delta = xy_delta_to_touchingblock
131
+ return xy_delta
132
+
133
+ def _get_orient_block_left(
134
+ self,
135
+ xy_dir_block_to_ee,
136
+ orient_circle_diameter,
137
+ xy_block,
138
+ xy_ee,
139
+ theta_error,
140
+ theta_threshold_flat_enough,
141
+ ):
142
+ xy_dir_block_to_ee = self.rotate(0.2, xy_dir_block_to_ee)
143
+ xy_block_to_ee = xy_dir_block_to_ee * orient_circle_diameter
144
+ xy_push_left_spot = xy_block + xy_block_to_ee
145
+ xy_delta = xy_push_left_spot - xy_ee
146
+ if theta_error < theta_threshold_flat_enough:
147
+ self.phase = "move_to_pre_block"
148
+ return xy_delta
149
+
150
+ def _get_orient_block_right(
151
+ self,
152
+ xy_dir_block_to_ee,
153
+ orient_circle_diameter,
154
+ xy_block,
155
+ xy_ee,
156
+ theta_error,
157
+ theta_threshold_flat_enough,
158
+ ):
159
+ xy_dir_block_to_ee = self.rotate(-0.2, xy_dir_block_to_ee)
160
+ xy_block_to_ee = xy_dir_block_to_ee * orient_circle_diameter
161
+ xy_push_left_spot = xy_block + xy_block_to_ee
162
+ xy_delta = xy_push_left_spot - xy_ee
163
+ if theta_error > -theta_threshold_flat_enough:
164
+ self.phase = "move_to_pre_block"
165
+ return xy_delta
166
+
167
+ def _get_action_for_block_target(self, time_step, block="block", target="target"):
168
+ # Specifying this as velocity makes it independent of control frequency.
169
+ max_step_velocity = 0.35
170
+ info = self._get_action_info(time_step, block, target)
171
+
172
+ if self.phase == "move_to_pre_block":
173
+ xy_delta, max_step_velocity = self._get_move_to_preblock(
174
+ info.xy_pre_block, info.xy_ee
175
+ )
176
+
177
+ if self.phase == "move_to_block":
178
+ xy_delta = self._get_move_to_block(
179
+ info.xy_delta_to_nexttoblock,
180
+ info.theta_threshold_to_orient,
181
+ info.theta_error,
182
+ )
183
+
184
+ if self.phase == "push_block":
185
+ xy_delta = self._get_push_block(
186
+ info.theta_error,
187
+ info.theta_threshold_to_orient,
188
+ info.xy_delta_to_touchingblock,
189
+ )
190
+
191
+ orient_circle_diameter = 0.025
192
+
193
+ if self.phase == "orient_block_left" or self.phase == "orient_block_right":
194
+ max_step_velocity = 0.15
195
+
196
+ if self.phase == "orient_block_left":
197
+ xy_delta = self._get_orient_block_left(
198
+ info.xy_dir_block_to_ee,
199
+ orient_circle_diameter,
200
+ info.xy_block,
201
+ info.xy_ee,
202
+ info.theta_error,
203
+ info.theta_threshold_flat_enough,
204
+ )
205
+
206
+ if self.phase == "orient_block_right":
207
+ xy_delta = self._get_orient_block_right(
208
+ info.xy_dir_block_to_ee,
209
+ orient_circle_diameter,
210
+ info.xy_block,
211
+ info.xy_ee,
212
+ info.theta_error,
213
+ info.theta_threshold_flat_enough,
214
+ )
215
+
216
+ if self._action_noise_std != 0.0:
217
+ xy_delta += self._np_random_state.randn(2) * self._action_noise_std
218
+
219
+ max_step_distance = max_step_velocity * (1 / self._env.get_control_frequency())
220
+ length = np.linalg.norm(xy_delta)
221
+ if length > max_step_distance:
222
+ xy_direction = xy_delta / length
223
+ xy_delta = xy_direction * max_step_distance
224
+ return xy_delta
225
+
226
+ def _action(self, time_step, policy_state):
227
+ if time_step.is_first():
228
+ self.reset()
229
+ xy_delta = self._get_action_for_block_target(
230
+ time_step, block="block", target="target"
231
+ )
232
+ return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
233
+
234
+
235
+ class OrientedPushNormalizedOracle(py_policy.PyPolicy):
236
+ """Oracle for pushing task which orients the block then pushes it."""
237
+
238
+ def __init__(self, env):
239
+ super(OrientedPushNormalizedOracle, self).__init__(
240
+ env.time_step_spec(), env.action_spec()
241
+ )
242
+ self._oracle = OrientedPushOracle(env)
243
+ self._env = env
244
+
245
+ def reset(self):
246
+ self._oracle.reset()
247
+
248
+ def _action(self, time_step, policy_state):
249
+ time_step = time_step._asdict()
250
+ time_step["observation"] = self._env.calc_unnormalized_state(
251
+ time_step["observation"]
252
+ )
253
+ step = self._oracle._action(
254
+ ts.TimeStep(**time_step), policy_state
255
+ ) # pylint: disable=protected-access
256
+ return policy_step.PolicyStep(
257
+ action=self._env.calc_normalized_action(step.action)
258
+ )
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/pushing_info.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Dataclass holding info needed for pushing oracles."""
17
+ import dataclasses
18
+ from typing import Any
19
+
20
+
21
+ @dataclasses.dataclass
22
+ class PushingInfo:
23
+ """Holds onto info necessary for pushing state machine."""
24
+
25
+ xy_block: Any = None
26
+ xy_ee: Any = None
27
+ xy_pre_block: Any = None
28
+ xy_delta_to_nexttoblock: Any = None
29
+ xy_delta_to_touchingblock: Any = None
30
+ xy_dir_block_to_ee: Any = None
31
+ theta_threshold_to_orient: Any = None
32
+ theta_threshold_flat_enough: Any = None
33
+ theta_error: Any = None
34
+ obstacle_poses: Any = None
35
+ distance_to_target: Any = None
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/oracles/reach_oracle.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Reach oracle."""
17
+ import numpy as np
18
+ from tf_agents.policies import py_policy
19
+ from tf_agents.trajectories import policy_step
20
+ from tf_agents.trajectories import time_step as ts
21
+ from tf_agents.typing import types
22
+
23
+ # Only used for debug visualization.
24
+ import pybullet # pylint: disable=unused-import
25
+
26
+
27
+ class ReachOracle(py_policy.PyPolicy):
28
+ """Oracle for moving to a specific spot relative to the block and target."""
29
+
30
+ def __init__(self, env, block_pushing_oracles_action_std=0.0):
31
+ super(ReachOracle, self).__init__(env.time_step_spec(), env.action_spec())
32
+ self._env = env
33
+ self._np_random_state = np.random.RandomState(0)
34
+ self._block_pushing_oracles_action_std = block_pushing_oracles_action_std
35
+
36
+ def _action(self, time_step, policy_state):
37
+
38
+ # Specifying this as velocity makes it independent of control frequency.
39
+ max_step_velocity = 0.2
40
+
41
+ xy_ee = time_step.observation["effector_target_translation"]
42
+
43
+ # This should be observable from block and target translation,
44
+ # but re-using the computation from the env so that it's only done once, and
45
+ # used for reward / completion computation.
46
+ xy_pre_block = self._env.reach_target_translation
47
+
48
+ xy_delta = xy_pre_block - xy_ee
49
+
50
+ if self._block_pushing_oracles_action_std != 0.0:
51
+ xy_delta += (
52
+ self._np_random_state.randn(2) * self._block_pushing_oracles_action_std
53
+ )
54
+
55
+ max_step_distance = max_step_velocity * (1 / self._env.get_control_frequency())
56
+ length = np.linalg.norm(xy_delta)
57
+ if length > max_step_distance:
58
+ xy_direction = xy_delta / length
59
+ xy_delta = xy_direction * max_step_distance
60
+
61
+ return policy_step.PolicyStep(action=np.asarray(xy_delta, dtype=np.float32))
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/pose3d.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """A simple 6DOF pose container.
17
+ """
18
+
19
+ import dataclasses
20
+ import numpy as np
21
+ from scipy.spatial import transform
22
+
23
+
24
+ class NoCopyAsDict(object):
25
+ """Base class for dataclasses. Avoids a copy in the asdict() call."""
26
+
27
+ def asdict(self):
28
+ """Replacement for dataclasses.asdict.
29
+
30
+ TF Dataset does not handle dataclasses.asdict, which uses copy.deepcopy when
31
+ setting values in the output dict. This causes issues with tf.Dataset.
32
+ Instead, shallow copy contents.
33
+
34
+ Returns:
35
+ dict containing contents of dataclass.
36
+ """
37
+ return {k.name: getattr(self, k.name) for k in dataclasses.fields(self)}
38
+
39
+
40
+ @dataclasses.dataclass
41
+ class Pose3d(NoCopyAsDict):
42
+ """Simple container for translation and rotation."""
43
+
44
+ rotation: transform.Rotation
45
+ translation: np.ndarray
46
+
47
+ @property
48
+ def vec7(self):
49
+ return np.concatenate([self.translation, self.rotation.as_quat()])
50
+
51
+ def serialize(self):
52
+ return {
53
+ "rotation": self.rotation.as_quat().tolist(),
54
+ "translation": self.translation.tolist(),
55
+ }
56
+
57
+ @staticmethod
58
+ def deserialize(data):
59
+ return Pose3d(
60
+ rotation=transform.Rotation.from_quat(data["rotation"]),
61
+ translation=np.array(data["translation"]),
62
+ )
63
+
64
+ def __eq__(self, other):
65
+ return np.array_equal(
66
+ self.rotation.as_quat(), other.rotation.as_quat()
67
+ ) and np.array_equal(self.translation, other.translation)
68
+
69
+ def __ne__(self, other):
70
+ return not self.__eq__(other)
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/utils_pybullet.py ADDED
@@ -0,0 +1,451 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Assortment of utilities to interact with bullet within g3."""
17
+ import dataclasses
18
+ import datetime
19
+ import getpass
20
+ import gzip
21
+ import json
22
+ import os
23
+ import time
24
+ from typing import Any, Dict, List, Optional, Tuple
25
+
26
+ from absl import logging
27
+ from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
28
+ import numpy as np
29
+ from scipy.spatial import transform
30
+ import six
31
+
32
+
33
+ import pybullet
34
+ import pybullet_data
35
+ import pybullet_utils.bullet_client as bullet_client
36
+
37
+ Vec3 = Tuple[float, float, float]
38
+ Vec4 = Tuple[float, float, float, float]
39
+ PYBULLET_STATE_VERSION = 2 # Basic versioning of serialized pybullet state.
40
+
41
+
42
+ # Note about rotation_to_matrix and matrix_to_rotation below:
43
+ # The abstractions below allow us to use older versions of scipy.
44
+ def rotation_to_matrix(rotation):
45
+ if hasattr(rotation, "as_dcm"):
46
+ return rotation.as_dcm()
47
+ else:
48
+ assert hasattr(rotation, "as_matrix")
49
+ return rotation.as_matrix()
50
+
51
+
52
+ def matrix_to_rotation(matrix):
53
+ if hasattr(transform.Rotation, "from_dcm"):
54
+ return transform.Rotation.from_dcm(matrix)
55
+ else:
56
+ assert hasattr(transform.Rotation, "from_matrix")
57
+ return transform.Rotation.from_matrix(matrix)
58
+
59
+
60
+ def load_urdf(pybullet_client, file_path, *args, **kwargs):
61
+ """Loads the given URDF filepath."""
62
+
63
+ # Handles most general file open case.
64
+ try:
65
+ if os.path.exists(file_path):
66
+ return pybullet_client.loadURDF(file_path, *args, **kwargs)
67
+ except pybullet_client.error:
68
+ pass
69
+
70
+ try:
71
+ import pathlib
72
+ asset_path = str(pathlib.Path(__file__).parent.parent.joinpath('assets'))
73
+ if file_path.startswith("third_party/py/envs/assets/"):
74
+ pybullet_client.setAdditionalSearchPath(asset_path)
75
+ file_path = file_path[len("third_party/py/envs/assets/") :]
76
+ if file_path.startswith(
77
+ "third_party/bullet/examples/pybullet/gym/pybullet_data/"
78
+ ):
79
+ pybullet_client.setAdditionalSearchPath(pybullet_data.getDataPath())
80
+ file_path = file_path[55:]
81
+ # logging.info("Loading URDF %s", file_path)
82
+ return pybullet_client.loadURDF(file_path, *args, **kwargs)
83
+ except pybullet.error:
84
+ raise FileNotFoundError("Cannot load the URDF file {}".format(file_path))
85
+
86
+
87
+ def add_visual_sphere(client, center=(0, 0, 0), radius=0.1, rgba=(0.5, 0.5, 0.5, 0.5)):
88
+ """Add a sphere to bullet scene (visual only, no physics).
89
+
90
+ Args:
91
+ client: pybullet client (or pybullet library handle).
92
+ center: Center of sphere.
93
+ radius: Sphere radius.
94
+ rgba: rgba color of sphere.
95
+
96
+ Returns:
97
+ Unique integer bullet id of constructed object.
98
+ """
99
+ vis_obj_id = client.createVisualShape(
100
+ client.GEOM_SPHERE, radius=radius, rgbaColor=rgba
101
+ )
102
+ obj_id = client.createMultiBody(
103
+ baseCollisionShapeIndex=-1, baseVisualShapeIndex=vis_obj_id, basePosition=center
104
+ )
105
+ return obj_id
106
+
107
+
108
+ def pybullet_mat_to_numpy_4x4(pybullet_matrix):
109
+ assert len(pybullet_matrix) == 16, "pybullet matrix should be len 16"
110
+ return np.transpose(np.reshape(np.array(pybullet_matrix, dtype=np.float64), (4, 4)))
111
+
112
+
113
+ def decompose_view_matrix(pybullet_view_matrix):
114
+ """Decompose view matrix into pos + quat format (assumes mat is rigid!)."""
115
+ # It would be MUCH better to use something from bullet, however pybullet does
116
+ # not expose all of the linear algebra library.
117
+ mat = pybullet_mat_to_numpy_4x4(pybullet_view_matrix)
118
+
119
+ # View matrix is now:
120
+ # | R_11 R_12 R_13 t_1 |
121
+ # | R_21 R_22 R_23 t_2 |
122
+ # | R_31 R_32 R_33 t_3 |
123
+ # | 0 0 0 1 |
124
+
125
+ # R is the inverse eye to target at orientation, and t is R * eye.
126
+ mat_view_to_world = np.linalg.inv(mat)
127
+
128
+ # mat_view_to_world is the view to world transform, therefore the translation
129
+ # component of this matrix is simply the world space position (since mat *
130
+ # (0, 0, 0, 1)) is just copying the right column.
131
+ world_xyz_view = np.copy(mat_view_to_world[0:3, 3])
132
+
133
+ mat_view_to_world[0:3, 3] = 0 # Zero out the position change.
134
+ world_quat_view = matrix_to_rotation(mat_view_to_world).as_quat()
135
+
136
+ return world_xyz_view, world_quat_view
137
+
138
+
139
+ def world_obj_to_view(world_xyz_obj, world_quat_obj, camera_view, client):
140
+ """Transform object into view space."""
141
+ world_xyz_view, world_quat_view = decompose_view_matrix(camera_view)
142
+ view_xyz_world, view_quat_world = client.invertTransform(
143
+ world_xyz_view, world_quat_view
144
+ )
145
+ view_xyz_obj, view_quat_obj = client.multiplyTransforms(
146
+ view_xyz_world, view_quat_world, world_xyz_obj, world_quat_obj
147
+ )
148
+
149
+ return view_xyz_obj, view_quat_obj
150
+
151
+
152
+ def image_xy_to_view_ray(xy, cam_width, cam_height, proj_mat_inv):
153
+ """Calculate view-space ray from pixel location."""
154
+ # Recall (from http://www.songho.ca/opengl/gl_projectionmatrix.html):
155
+ # xyzw_clip = M_proj * xyzw_eye, and
156
+ # xyz_ndc = xyzw_clip[0:3] / xwzw_clip[3].
157
+ xyz_ndc = np.array(
158
+ [2.0 * xy[0] / cam_width - 1.0, -(2.0 * xy[1] / cam_height - 1.0), 0]
159
+ ) # in [-1, 1]
160
+ xyzw_clip = np.concatenate([xyz_ndc, [1]])
161
+ xyzw_eye = proj_mat_inv @ xyzw_clip
162
+ origin = np.zeros(3)
163
+ vec = xyzw_eye[:3] / max(np.linalg.norm(xyzw_eye[:3]), 1e-6)
164
+ return origin, vec
165
+
166
+
167
+ def view_ray_to_world_ray(origin, vec, view_mat_inv):
168
+ """Transform view-space ray into world space."""
169
+ origin = view_mat_inv @ np.concatenate([origin, [1]])
170
+ vec = view_mat_inv @ np.concatenate([vec, [0]])
171
+
172
+ return origin[:3], vec[:3]
173
+
174
+
175
+ def ray_to_plane_test(ray_origin, ray_vec, plane_origin, plane_normal):
176
+ """Perform a ray-plane intersection test."""
177
+ ln = np.dot(plane_normal, ray_vec)
178
+ if abs(ln) < np.finfo(np.float32).eps:
179
+ return None
180
+
181
+ # Solve for the intersection fraction t.
182
+ t = np.dot(plane_normal, plane_origin - ray_origin) / ln
183
+ if t >= 0:
184
+ return ray_origin + ray_vec * t
185
+ else:
186
+ return None
187
+
188
+
189
+ def get_workspace(env):
190
+ (
191
+ workspace_origin,
192
+ workspace_quat,
193
+ ) = env.pybullet_client.getBasePositionAndOrientation(env.workspace_uid)
194
+ workspace_normal = rotation_to_matrix(transform.Rotation.from_quat(workspace_quat))[
195
+ 2, 0:3
196
+ ]
197
+
198
+ return workspace_origin, workspace_normal
199
+
200
+
201
+ def reset_camera_pose(env, view_type):
202
+ """Reset camera pose to canonical frame."""
203
+ p = env.pybullet_client
204
+
205
+ if view_type == "POLICY":
206
+ camera_info = p.getDebugVisualizerCamera()
207
+ image_size = (camera_info[0], camera_info[1])
208
+
209
+ viewm, _, front_position, lookat, _ = env.calc_camera_params(image_size)
210
+
211
+ euler = matrix_to_rotation(pybullet_mat_to_numpy_4x4(viewm)[0:3, 0:3]).as_euler(
212
+ "xyz", degrees=False
213
+ )
214
+ pitch = euler[1]
215
+ yaw = -euler[2]
216
+ # The distance is a bit far away (the GL view has higher FOV).
217
+ distance = np.linalg.norm(front_position - lookat) * 0.6
218
+ elif view_type == "TOP_DOWN":
219
+ workspace_origin, _ = get_workspace(env)
220
+ distance = 0.5
221
+ lookat = workspace_origin
222
+ yaw = np.pi / 2
223
+ # Note: pi/2 pitch results in gimble lock and pybullet doesn't support it.
224
+ pitch = -(np.pi / 2 - 1e-5)
225
+ else:
226
+ raise ValueError("unsupported view_type %s" % view_type)
227
+ p.resetDebugVisualizerCamera(
228
+ cameraDistance=distance,
229
+ cameraYaw=360 * yaw / (2.0 * np.pi),
230
+ cameraPitch=360 * pitch / (2.0 * np.pi),
231
+ cameraTargetPosition=lookat,
232
+ )
233
+
234
+
235
+ def _lists_to_tuple(obj):
236
+ if isinstance(obj, list):
237
+ return tuple([_lists_to_tuple(v) for v in obj])
238
+ else:
239
+ return obj
240
+
241
+
242
+ @dataclasses.dataclass
243
+ class ObjState:
244
+ """A container for storing pybullet object state."""
245
+
246
+ obj_id: int
247
+
248
+ # base_pose: (xyz, quat).
249
+ base_pose: Tuple[Vec3, Vec4]
250
+ # base_vel: (vel, ang_vel).
251
+ base_vel: Tuple[Vec3, Vec3]
252
+ joint_info: Any
253
+ joint_state: Any
254
+
255
+ @staticmethod
256
+ def get_bullet_state(client, obj_id):
257
+ """Read Pybullet internal state."""
258
+ base_pose = client.getBasePositionAndOrientation(obj_id)
259
+ base_vel = client.getBaseVelocity(obj_id)
260
+
261
+ joint_info = []
262
+ joint_state = []
263
+ for i in range(client.getNumJoints(obj_id)):
264
+ joint_state.append(client.getJointState(obj_id, i))
265
+ joint_info.append(ObjState._get_joint_info(client, obj_id, i))
266
+
267
+ return ObjState(
268
+ obj_id=obj_id,
269
+ base_pose=base_pose,
270
+ base_vel=base_vel,
271
+ joint_info=tuple(joint_info),
272
+ joint_state=tuple(joint_state),
273
+ )
274
+
275
+ @staticmethod
276
+ def _get_joint_info(client, obj_id, joint_index):
277
+ ji = client.getJointInfo(obj_id, joint_index)
278
+ return tuple([v if not isinstance(v, bytes) else v.decode("utf-8") for v in ji])
279
+
280
+ def set_bullet_state(self, client, obj_id):
281
+ """Hard set the current bullet state."""
282
+ xyz, quat = self.base_pose
283
+ client.resetBasePositionAndOrientation(obj_id, xyz, quat)
284
+ vel, ang_vel = self.base_vel
285
+ client.resetBaseVelocity(obj_id, vel, ang_vel)
286
+
287
+ njoints = client.getNumJoints(obj_id)
288
+ if njoints != len(self.joint_info) or njoints != len(self.joint_state):
289
+ raise ValueError("Incorrect number of joint info state pairs.")
290
+
291
+ for i, (joint_info, joint_state) in enumerate(
292
+ zip(self.joint_info, self.joint_state)
293
+ ):
294
+ joint_index = joint_info[0]
295
+ if joint_index != i:
296
+ raise ValueError("Joint index mismatch.")
297
+
298
+ # Check that the current joint we're trying to restore state for has the
299
+ # same info as the state joint.
300
+ cur_joint_info = ObjState._get_joint_info(client, obj_id, joint_index)
301
+ if cur_joint_info != joint_info:
302
+ raise ValueError(
303
+ "joint_info mismatch %s vs %s (expected)"
304
+ % (str(cur_joint_info), str(joint_info))
305
+ )
306
+ joint_position = joint_state[0]
307
+ joint_velocity = joint_state[1]
308
+ client.resetJointState(
309
+ obj_id, i, targetValue=joint_position, targetVelocity=joint_velocity
310
+ )
311
+
312
+ def serialize(self):
313
+ return {
314
+ "obj_id": self.obj_id,
315
+ "base_pose": self.base_pose,
316
+ "base_vel": self.base_vel,
317
+ "joint_info": self.joint_info,
318
+ "joint_state": self.joint_state,
319
+ }
320
+
321
+ @staticmethod
322
+ def deserialize(data):
323
+ return ObjState(
324
+ obj_id=_lists_to_tuple(data["obj_id"]),
325
+ base_pose=_lists_to_tuple(data["base_pose"]),
326
+ base_vel=_lists_to_tuple(data["base_vel"]),
327
+ joint_info=_lists_to_tuple(data["joint_info"]),
328
+ joint_state=_lists_to_tuple(data["joint_state"]),
329
+ )
330
+
331
+
332
+ @dataclasses.dataclass
333
+ class XarmState(ObjState):
334
+ """A container for storing pybullet robot state."""
335
+
336
+ # The set point of the robot's controller.
337
+ target_effector_pose: Pose3d
338
+ goal_translation: Optional[Vec3]
339
+
340
+ @staticmethod
341
+ def get_bullet_state(client, obj_id, target_effector_pose, goal_translation):
342
+ if goal_translation is not None:
343
+ goal_translation = tuple(goal_translation.tolist())
344
+ return XarmState(
345
+ **dataclasses.asdict(ObjState.get_bullet_state(client, obj_id)),
346
+ target_effector_pose=target_effector_pose,
347
+ goal_translation=goal_translation
348
+ )
349
+
350
+ def serialize(self):
351
+ data = ObjState.serialize(self)
352
+ data["target_effector_pose"] = self.target_effector_pose.serialize()
353
+ if self.goal_translation is not None:
354
+ data["goal_translation"] = self.goal_translation
355
+ else:
356
+ data["goal_translation"] = []
357
+ return data
358
+
359
+ @staticmethod
360
+ def deserialize(data):
361
+ goal_translation = (
362
+ None
363
+ if not data["goal_translation"]
364
+ else _lists_to_tuple(data["goal_translation"])
365
+ )
366
+ return XarmState(
367
+ obj_id=data["obj_id"],
368
+ base_pose=_lists_to_tuple(data["base_pose"]),
369
+ base_vel=_lists_to_tuple(data["base_vel"]),
370
+ joint_info=_lists_to_tuple(data["joint_info"]),
371
+ joint_state=_lists_to_tuple(data["joint_state"]),
372
+ goal_translation=goal_translation,
373
+ target_effector_pose=Pose3d.deserialize(data["target_effector_pose"]),
374
+ )
375
+
376
+
377
+ def _serialize_pybullet_state(pybullet_state):
378
+ """Convert data to POD types."""
379
+ if isinstance(pybullet_state, list):
380
+ return [_serialize_pybullet_state(entry) for entry in pybullet_state]
381
+ elif isinstance(pybullet_state, dict):
382
+ assert "_serialized_obj_name" not in pybullet_state
383
+ return {
384
+ key: _serialize_pybullet_state(value)
385
+ for key, value in pybullet_state.items()
386
+ }
387
+ elif isinstance(pybullet_state, (XarmState, ObjState)):
388
+ return {
389
+ "_serialized_obj_name": type(pybullet_state).__name__,
390
+ "_serialized_data": pybullet_state.serialize(),
391
+ }
392
+ elif isinstance(pybullet_state, int):
393
+ return pybullet_state
394
+ else:
395
+ raise ValueError(
396
+ "Unhandled type for object %s, type %s"
397
+ % (str(pybullet_state), type(pybullet_state))
398
+ )
399
+
400
+
401
+ def _deserialize_pybullet_state(state):
402
+ """Parse data from POD types."""
403
+ if isinstance(state, list):
404
+ return [_deserialize_pybullet_state(item) for item in state]
405
+ elif isinstance(state, dict):
406
+ if "_serialized_obj_name" in state:
407
+ if state["_serialized_obj_name"] == XarmState.__name__:
408
+ return XarmState.deserialize(state["_serialized_data"])
409
+ elif state["_serialized_obj_name"] == ObjState.__name__:
410
+ return ObjState.deserialize(state["_serialized_data"])
411
+ else:
412
+ raise ValueError("Unsupported: %s" % state["_serialized_obj_name"])
413
+ else:
414
+ return {
415
+ key: _deserialize_pybullet_state(value) for key, value in state.items()
416
+ }
417
+ elif isinstance(state, int):
418
+ return state
419
+ else:
420
+ raise ValueError("Unhandled type for object %s" % str(state))
421
+
422
+
423
+ def write_pybullet_state(filename, pybullet_state, task, actions=None):
424
+ """Serialize pybullet state to json file."""
425
+ import torch
426
+ data = {
427
+ "pybullet_state": _serialize_pybullet_state(pybullet_state),
428
+ "state_version": PYBULLET_STATE_VERSION,
429
+ "ts_ms": int(time.mktime(datetime.datetime.now().timetuple())) * 1000,
430
+ "user": getpass.getuser(),
431
+ "task": task,
432
+ "actions": actions if actions is not None else [],
433
+ }
434
+ torch.save(data, filename)
435
+
436
+
437
+ def read_pybullet_state(filename):
438
+ """Deserialize pybullet state from json file."""
439
+ import torch
440
+ data = torch.load(filename)
441
+
442
+ assert isinstance(data, dict)
443
+
444
+ if data["state_version"] != PYBULLET_STATE_VERSION:
445
+ raise ValueError(
446
+ "incompatible state data (version %d, expected %d)"
447
+ % (data["state_version"], PYBULLET_STATE_VERSION)
448
+ )
449
+
450
+ data["pybullet_state"] = _deserialize_pybullet_state(data["pybullet_state"])
451
+ return data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/block_pushing/utils/xarm_sim_robot.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2022 The Reach ML Authors.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """XArm Robot Kinematics."""
17
+ from cleandiffuser.env.block_pushing.utils import utils_pybullet
18
+ from cleandiffuser.env.block_pushing.utils.pose3d import Pose3d
19
+ import numpy as np
20
+ from scipy.spatial import transform
21
+ import pybullet
22
+
23
+ XARM_URDF_PATH = (
24
+ "third_party/bullet/examples/pybullet/gym/pybullet_data/" "xarm/xarm6_robot.urdf"
25
+ )
26
+ SUCTION_URDF_PATH = "third_party/py/envs/assets/suction/" "suction-head-long.urdf"
27
+ CYLINDER_URDF_PATH = "third_party/py/envs/assets/suction/" "cylinder.urdf"
28
+ CYLINDER_REAL_URDF_PATH = "third_party/py/envs/assets/suction/" "cylinder_real.urdf"
29
+ HOME_JOINT_POSITIONS = np.deg2rad([0, -20, -80, 0, 100, -30])
30
+
31
+
32
+ class XArmSimRobot:
33
+ """A simulated PyBullet XArm robot, mostly for forward/inverse kinematics."""
34
+
35
+ def __init__(
36
+ self,
37
+ pybullet_client,
38
+ initial_joint_positions=HOME_JOINT_POSITIONS,
39
+ end_effector="none",
40
+ color="default",
41
+ ):
42
+ self._pybullet_client = pybullet_client
43
+ self.initial_joint_positions = initial_joint_positions
44
+
45
+ if color == "default":
46
+ self.xarm = utils_pybullet.load_urdf(
47
+ pybullet_client, XARM_URDF_PATH, [0, 0, 0]
48
+ )
49
+ else:
50
+ raise ValueError("Unrecognized xarm color %s" % color)
51
+
52
+ # Get revolute joints of robot (skip fixed joints).
53
+ joints = []
54
+ joint_indices = []
55
+ for i in range(self._pybullet_client.getNumJoints(self.xarm)):
56
+ joint_info = self._pybullet_client.getJointInfo(self.xarm, i)
57
+ if joint_info[2] == pybullet.JOINT_REVOLUTE:
58
+ joints.append(joint_info[0])
59
+ joint_indices.append(i)
60
+ # Note examples in pybullet do this, but it is not clear what the
61
+ # benefits are.
62
+ self._pybullet_client.changeDynamics(
63
+ self.xarm, i, linearDamping=0, angularDamping=0
64
+ )
65
+
66
+ self._n_joints = len(joints)
67
+ self._joints = tuple(joints)
68
+ self._joint_indices = tuple(joint_indices)
69
+
70
+ # Move robot to home joint configuration
71
+ self.reset_joints(self.initial_joint_positions)
72
+ self.effector_link = 6
73
+
74
+ if (
75
+ end_effector == "suction"
76
+ or end_effector == "cylinder"
77
+ or end_effector == "cylinder_real"
78
+ ):
79
+ self.end_effector = self._setup_end_effector(end_effector)
80
+ else:
81
+ if end_effector != "none":
82
+ raise ValueError('end_effector "%s" is not supported.' % end_effector)
83
+ self.end_effector = None
84
+
85
+ def _setup_end_effector(self, end_effector):
86
+ """Adds a suction or cylinder end effector."""
87
+ pose = self.forward_kinematics()
88
+ if end_effector == "suction":
89
+ body = utils_pybullet.load_urdf(
90
+ self._pybullet_client,
91
+ SUCTION_URDF_PATH,
92
+ pose.translation,
93
+ pose.rotation.as_quat(),
94
+ )
95
+ elif end_effector == "cylinder":
96
+ body = utils_pybullet.load_urdf(
97
+ self._pybullet_client,
98
+ CYLINDER_URDF_PATH,
99
+ pose.translation,
100
+ pose.rotation.as_quat(),
101
+ )
102
+ elif end_effector == "cylinder_real":
103
+ body = utils_pybullet.load_urdf(
104
+ self._pybullet_client,
105
+ CYLINDER_REAL_URDF_PATH,
106
+ pose.translation,
107
+ pose.rotation.as_quat(),
108
+ )
109
+ else:
110
+ raise ValueError('end_effector "%s" is not supported.' % end_effector)
111
+
112
+ constraint_id = self._pybullet_client.createConstraint(
113
+ parentBodyUniqueId=self.xarm,
114
+ parentLinkIndex=6,
115
+ childBodyUniqueId=body,
116
+ childLinkIndex=-1,
117
+ jointType=pybullet.JOINT_FIXED,
118
+ jointAxis=(0, 0, 0),
119
+ parentFramePosition=(0, 0, 0),
120
+ childFramePosition=(0, 0, 0),
121
+ )
122
+ self._pybullet_client.changeConstraint(constraint_id, maxForce=50)
123
+
124
+ return body
125
+
126
+ def reset_joints(self, joint_values):
127
+ """Sets the position of the Robot's joints.
128
+
129
+ *Note*: This should only be used at the start while not running the
130
+ simulation resetJointState overrides all physics simulation.
131
+
132
+ Args:
133
+ joint_values: Iterable with desired joint positions.
134
+ """
135
+ for i in range(self._n_joints):
136
+ self._pybullet_client.resetJointState(
137
+ self.xarm, self._joints[i], joint_values[i]
138
+ )
139
+
140
+ def get_joints_measured(self):
141
+ joint_states = self._pybullet_client.getJointStates(
142
+ self.xarm, self._joint_indices
143
+ )
144
+ joint_positions = np.array([state[0] for state in joint_states])
145
+ joint_velocities = np.array([state[1] for state in joint_states])
146
+ joint_torques = np.array([state[3] for state in joint_states])
147
+ return joint_positions, joint_velocities, joint_torques
148
+
149
+ def get_joint_positions(self):
150
+ joint_states = self._pybullet_client.getJointStates(
151
+ self.xarm, self._joint_indices
152
+ )
153
+ joint_positions = np.array([state[0] for state in joint_states])
154
+ return joint_positions
155
+
156
+ def forward_kinematics(self):
157
+ """Forward kinematics."""
158
+ effector_state = self._pybullet_client.getLinkState(
159
+ self.xarm, self.effector_link
160
+ )
161
+ return Pose3d(
162
+ translation=np.array(effector_state[0]),
163
+ rotation=transform.Rotation.from_quat(effector_state[1]),
164
+ )
165
+
166
+ def inverse_kinematics(
167
+ self, world_effector_pose, max_iterations=100, residual_threshold=1e-10
168
+ ):
169
+ """Inverse kinematics.
170
+
171
+ Args:
172
+ world_effector_pose: Target Pose3d for the robot's end effector.
173
+ max_iterations: Refine the IK solution until the distance between target
174
+ and actual end effector position is below this threshold, or the
175
+ maxNumIterations is reached. Default is 20 iterations.
176
+ residual_threshold: Refine the IK solution until the distance between
177
+ target and actual end effector position is below this threshold, or the
178
+ maxNumIterations is reached.
179
+
180
+ Returns:
181
+ Numpy array with required joint angles to reach the requested pose.
182
+ """
183
+ return np.array(
184
+ self._pybullet_client.calculateInverseKinematics(
185
+ self.xarm,
186
+ self.effector_link,
187
+ world_effector_pose.translation,
188
+ world_effector_pose.rotation.as_quat(), # as_quat returns xyzw.
189
+ lowerLimits=[-17] * 6,
190
+ upperLimits=[17] * 6,
191
+ jointRanges=[17] * 6,
192
+ restPoses=[0, 0] + self.get_joint_positions()[2:].tolist(),
193
+ maxNumIterations=max_iterations,
194
+ residualThreshold=residual_threshold,
195
+ )
196
+ )
197
+
198
+ def set_target_effector_pose(self, world_effector_pose):
199
+ target_joint_positions = self.inverse_kinematics(world_effector_pose)
200
+ self.set_target_joint_positions(target_joint_positions)
201
+
202
+ def set_target_joint_velocities(self, target_joint_velocities):
203
+ self._pybullet_client.setJointMotorControlArray(
204
+ self.xarm,
205
+ self._joint_indices,
206
+ pybullet.VELOCITY_CONTROL,
207
+ targetVelocities=target_joint_velocities,
208
+ forces=[5 * 240.0] * 6,
209
+ )
210
+
211
+ def set_target_joint_positions(self, target_joint_positions):
212
+ self._pybullet_client.setJointMotorControlArray(
213
+ self.xarm,
214
+ self._joint_indices,
215
+ pybullet.POSITION_CONTROL,
216
+ targetPositions=target_joint_positions,
217
+ forces=[5 * 240.0] * 6,
218
+ )
219
+
220
+ def set_alpha_transparency(self, alpha):
221
+ visual_shape_data = self._pybullet_client.getVisualShapeData(self.xarm)
222
+
223
+ for i in range(self._pybullet_client.getNumJoints(self.xarm)):
224
+ object_id, link_index, _, _, _, _, _, rgba_color = visual_shape_data[i]
225
+ assert object_id == self.xarm, "xarm id mismatch."
226
+ assert link_index == i, "Link visual data was returned out of order."
227
+ rgba_color = list(rgba_color[0:3]) + [alpha]
228
+ self._pybullet_client.changeVisualShape(
229
+ self.xarm, linkIndex=i, rgbaColor=rgba_color
230
+ )
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/__init__.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Environments using kitchen and Franka robot."""
2
+ from gym.envs.registration import register
3
+
4
+ register(
5
+ id="kitchen-microwave-kettle-light-slider-v0",
6
+ entry_point="cleandiffuser.env.kitchen.v0:KitchenMicrowaveKettleLightSliderV0",
7
+ max_episode_steps=280,
8
+ reward_threshold=1.0,
9
+ )
10
+
11
+ register(
12
+ id="kitchen-microwave-kettle-burner-light-v0",
13
+ entry_point="cleandiffuser.env.kitchen.v0:KitchenMicrowaveKettleBottomBurnerLightV0",
14
+ max_episode_steps=280,
15
+ reward_threshold=1.0,
16
+ )
17
+
18
+ register(
19
+ id="kitchen-kettle-microwave-light-slider-v0",
20
+ entry_point="cleandiffuser.env.kitchen.v0:KitchenKettleMicrowaveLightSliderV0",
21
+ max_episode_steps=280,
22
+ reward_threshold=1.0,
23
+ )
24
+
25
+ register(
26
+ id="kitchen-all-v0",
27
+ entry_point="cleandiffuser.env.kitchen.v0:KitchenAllV0",
28
+ max_episode_steps=280,
29
+ reward_threshold=1.0,
30
+ )
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/base.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ # hack to import adept envs
4
+ ADEPT_DIR = os.path.join(os.path.dirname(__file__), 'relay_policy_learning', 'adept_envs')
5
+ sys.path.append(ADEPT_DIR)
6
+
7
+ import logging
8
+ import numpy as np
9
+ import adept_envs
10
+ from adept_envs.franka.kitchen_multitask_v0 import KitchenTaskRelaxV1
11
+
12
+ OBS_ELEMENT_INDICES = {
13
+ "bottom burner": np.array([11, 12]),
14
+ "top burner": np.array([15, 16]),
15
+ "light switch": np.array([17, 18]),
16
+ "slide cabinet": np.array([19]),
17
+ "hinge cabinet": np.array([20, 21]),
18
+ "microwave": np.array([22]),
19
+ "kettle": np.array([23, 24, 25, 26, 27, 28, 29]),
20
+ }
21
+ OBS_ELEMENT_GOALS = {
22
+ "bottom burner": np.array([-0.88, -0.01]),
23
+ "top burner": np.array([-0.92, -0.01]),
24
+ "light switch": np.array([-0.69, -0.05]),
25
+ "slide cabinet": np.array([0.37]),
26
+ "hinge cabinet": np.array([0.0, 1.45]),
27
+ "microwave": np.array([-0.75]),
28
+ "kettle": np.array([-0.23, 0.75, 1.62, 0.99, 0.0, 0.0, -0.06]),
29
+ }
30
+ BONUS_THRESH = 0.3
31
+ logger = logging.getLogger()
32
+
33
+
34
+ class KitchenBase(KitchenTaskRelaxV1):
35
+ # A string of element names. The robot's task is then to modify each of
36
+ # these elements appropriately.
37
+ TASK_ELEMENTS = []
38
+ ALL_TASKS = [
39
+ "bottom burner",
40
+ "top burner",
41
+ "light switch",
42
+ "slide cabinet",
43
+ "hinge cabinet",
44
+ "microwave",
45
+ "kettle",
46
+ ]
47
+ REMOVE_TASKS_WHEN_COMPLETE = True
48
+ TERMINATE_ON_TASK_COMPLETE = True
49
+ TERMINATE_ON_WRONG_COMPLETE = False
50
+ COMPLETE_IN_ANY_ORDER = (
51
+ True # This allows for the tasks to be completed in arbitrary order.
52
+ )
53
+
54
+ def __init__(
55
+ self, dataset_url=None, ref_max_score=None, ref_min_score=None,
56
+ use_abs_action=False,
57
+ **kwargs
58
+ ):
59
+ self.tasks_to_complete = list(self.TASK_ELEMENTS)
60
+ self.goal_masking = True
61
+ super(KitchenBase, self).__init__(use_abs_action=use_abs_action, **kwargs)
62
+
63
+ def set_goal_masking(self, goal_masking=True):
64
+ """Sets goal masking for goal-conditioned approaches (like RPL)."""
65
+ self.goal_masking = goal_masking
66
+
67
+ def _get_task_goal(self, task=None, actually_return_goal=False):
68
+ if task is None:
69
+ task = ["microwave", "kettle", "bottom burner", "light switch"]
70
+ new_goal = np.zeros_like(self.goal)
71
+ if self.goal_masking and not actually_return_goal:
72
+ return new_goal
73
+ for element in task:
74
+ element_idx = OBS_ELEMENT_INDICES[element]
75
+ element_goal = OBS_ELEMENT_GOALS[element]
76
+ new_goal[element_idx] = element_goal
77
+
78
+ return new_goal
79
+
80
+ def reset_model(self):
81
+ self.tasks_to_complete = list(self.TASK_ELEMENTS)
82
+ return super(KitchenBase, self).reset_model()
83
+
84
+ def get_observation(self):
85
+ return self._get_obs()
86
+
87
+ def _get_reward_n_score(self, obs_dict):
88
+ reward_dict, score = super(KitchenBase, self)._get_reward_n_score(obs_dict)
89
+ reward = 0.0
90
+ next_q_obs = obs_dict["qp"]
91
+ next_obj_obs = obs_dict["obj_qp"]
92
+ next_goal = self._get_task_goal(
93
+ task=self.TASK_ELEMENTS, actually_return_goal=True
94
+ ) # obs_dict['goal']
95
+ idx_offset = len(next_q_obs)
96
+ completions = []
97
+ all_completed_so_far = True
98
+ for element in self.tasks_to_complete:
99
+ element_idx = OBS_ELEMENT_INDICES[element]
100
+ distance = np.linalg.norm(
101
+ next_obj_obs[..., element_idx - idx_offset] - next_goal[element_idx]
102
+ )
103
+ complete = distance < BONUS_THRESH
104
+ condition = (
105
+ complete and all_completed_so_far
106
+ if not self.COMPLETE_IN_ANY_ORDER
107
+ else complete
108
+ )
109
+ if condition: # element == self.tasks_to_complete[0]:
110
+ print("Task {} completed!".format(element))
111
+ completions.append(element)
112
+ all_completed_so_far = all_completed_so_far and complete
113
+ if self.REMOVE_TASKS_WHEN_COMPLETE:
114
+ [self.tasks_to_complete.remove(element) for element in completions]
115
+ bonus = float(len(completions))
116
+ reward_dict["bonus"] = bonus
117
+ reward_dict["r_total"] = bonus
118
+ score = bonus
119
+ return reward_dict, score
120
+
121
+ def step(self, a, b=None):
122
+ obs, reward, done, env_info = super(KitchenBase, self).step(a, b=b)
123
+ if self.TERMINATE_ON_TASK_COMPLETE:
124
+ done = not self.tasks_to_complete
125
+ if self.TERMINATE_ON_WRONG_COMPLETE:
126
+ all_goal = self._get_task_goal(task=self.ALL_TASKS)
127
+ for wrong_task in list(set(self.ALL_TASKS) - set(self.TASK_ELEMENTS)):
128
+ element_idx = OBS_ELEMENT_INDICES[wrong_task]
129
+ distance = np.linalg.norm(obs[..., element_idx] - all_goal[element_idx])
130
+ complete = distance < BONUS_THRESH
131
+ if complete:
132
+ done = True
133
+ break
134
+ env_info["completed_tasks"] = set(self.TASK_ELEMENTS) - set(
135
+ self.tasks_to_complete
136
+ )
137
+ return obs, reward, done, env_info
138
+
139
+ def get_goal(self):
140
+ """Loads goal state from dataset for goal-conditioned approaches (like RPL)."""
141
+ raise NotImplementedError
142
+
143
+ def _split_data_into_seqs(self, data):
144
+ """Splits dataset object into list of sequence dicts."""
145
+ seq_end_idxs = np.where(data["terminals"])[0]
146
+ start = 0
147
+ seqs = []
148
+ for end_idx in seq_end_idxs:
149
+ seqs.append(
150
+ dict(
151
+ states=data["observations"][start : end_idx + 1],
152
+ actions=data["actions"][start : end_idx + 1],
153
+ )
154
+ )
155
+ start = end_idx + 1
156
+ return seqs
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_lowdim_wrapper.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Optional, Optional
2
+ import numpy as np
3
+ import gym
4
+ from gym.spaces import Box
5
+ from cleandiffuser.env.kitchen.base import KitchenBase
6
+
7
+ class KitchenLowdimWrapper(gym.Env):
8
+ def __init__(self,
9
+ env: KitchenBase,
10
+ init_qpos: Optional[np.ndarray]=None,
11
+ init_qvel: Optional[np.ndarray]=None,
12
+ render_hw = (240,360)
13
+ ):
14
+ self.env = env
15
+ self.all_tasks = env.ALL_TASKS
16
+ self.init_qpos = init_qpos
17
+ self.init_qvel = init_qvel
18
+ self.render_hw = render_hw
19
+
20
+ @property
21
+ def action_space(self):
22
+ return self.env.action_space
23
+
24
+ @property
25
+ def observation_space(self):
26
+ return self.env.observation_space
27
+
28
+ @property
29
+ def tasks_to_complete(self):
30
+ return self.env.tasks_to_complete
31
+
32
+ def set_init_q(self, init_qpos, init_qvel):
33
+ self.init_qpos = init_qpos
34
+ self.init_qvel = init_qvel
35
+
36
+ def seed(self, seed=None):
37
+ return self.env.seed(seed)
38
+
39
+ def reset(self):
40
+ if self.init_qpos is not None:
41
+ print("Load known initial conditions.")
42
+ # reset anyway to be safe, not very expensive
43
+ _ = self.env.reset()
44
+ # start from known state
45
+ self.env.set_state(self.init_qpos, self.init_qvel)
46
+ obs = self.env.get_observation()
47
+ return obs
48
+ else:
49
+ return self.env.reset()
50
+
51
+ def render(self, mode='rgb_array'):
52
+ h, w = self.render_hw
53
+ return self.env.render(mode=mode, width=w, height=h)
54
+
55
+ def step(self, a):
56
+ return self.env.step(a)
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/kitchen_util.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import struct
2
+ import numpy as np
3
+
4
+ def parse_mjl_logs(read_filename, skipamount):
5
+ with open(read_filename, mode='rb') as file:
6
+ fileContent = file.read()
7
+ headers = struct.unpack('iiiiiii', fileContent[:28])
8
+ nq = headers[0]
9
+ nv = headers[1]
10
+ nu = headers[2]
11
+ nmocap = headers[3]
12
+ nsensordata = headers[4]
13
+ nuserdata = headers[5]
14
+ name_len = headers[6]
15
+ name = struct.unpack(str(name_len) + 's', fileContent[28:28+name_len])[0]
16
+ rem_size = len(fileContent[28 + name_len:])
17
+ num_floats = int(rem_size/4)
18
+ dat = np.asarray(struct.unpack(str(num_floats) + 'f', fileContent[28+name_len:]))
19
+ recsz = 1 + nq + nv + nu + 7*nmocap + nsensordata + nuserdata
20
+ if rem_size % recsz != 0:
21
+ print("ERROR")
22
+ else:
23
+ dat = np.reshape(dat, (int(len(dat)/recsz), recsz))
24
+ dat = dat.T
25
+
26
+ time = dat[0,:][::skipamount] - 0*dat[0, 0]
27
+ qpos = dat[1:nq + 1, :].T[::skipamount, :]
28
+ qvel = dat[nq+1:nq+nv+1,:].T[::skipamount, :]
29
+ ctrl = dat[nq+nv+1:nq+nv+nu+1,:].T[::skipamount,:]
30
+ mocap_pos = dat[nq+nv+nu+1:nq+nv+nu+3*nmocap+1,:].T[::skipamount, :]
31
+ mocap_quat = dat[nq+nv+nu+3*nmocap+1:nq+nv+nu+7*nmocap+1,:].T[::skipamount, :]
32
+ sensordata = dat[nq+nv+nu+7*nmocap+1:nq+nv+nu+7*nmocap+nsensordata+1,:].T[::skipamount,:]
33
+ userdata = dat[nq+nv+nu+7*nmocap+nsensordata+1:,:].T[::skipamount,:]
34
+
35
+ data = dict(nq=nq,
36
+ nv=nv,
37
+ nu=nu,
38
+ nmocap=nmocap,
39
+ nsensordata=nsensordata,
40
+ name=name,
41
+ time=time,
42
+ qpos=qpos,
43
+ qvel=qvel,
44
+ ctrl=ctrl,
45
+ mocap_pos=mocap_pos,
46
+ mocap_quat=mocap_quat,
47
+ sensordata=sensordata,
48
+ userdata=userdata,
49
+ logName = read_filename
50
+ )
51
+ return data
checkpoints/relay_kitchen_fdk_fw0_step80k_20260917/comparison_20260918_cp_eval3/source_original_verified/cleandiffuser/env/kitchen/relay_policy_learning/adept_envs/adept_envs/__init__.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/python
2
+ #
3
+ # Copyright 2020 Google LLC
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import adept_envs.franka
18
+
19
+ from adept_envs.utils.configurable import global_config