dualrate-physics / code /dualrate /normalization.py
AlexWortega's picture
Add results/, code/, slim 10k-OOD checkpoint; link GitHub repo in card
7e86b86 verified
Raw History Blame Contribute Delete
1.91 kB
"""State and delta normalization for the flow expert.
State layout per object: [px, py, vx, vy, angle, angvel].
States are affine-normalized; deltas (flow targets) are scaled to ~unit magnitude.
"""
import math
import numpy as np
import torch
def wrap_angle(a):
"""Wrap angle(s) to [-pi, pi). Works on numpy arrays and torch tensors."""
if isinstance(a, torch.Tensor):
return torch.remainder(a + math.pi, 2 * math.pi) - math.pi
return np.mod(a + math.pi, 2 * math.pi) - math.pi
class StateNormalizer:
def __init__(self, state_offset, state_scale, delta_scale):
self.state_offset = np.asarray(state_offset, dtype=np.float32)
self.state_scale = np.asarray(state_scale, dtype=np.float32)
self.delta_scale = np.asarray(delta_scale, dtype=np.float32)
def _cast(self, ref, arr):
if isinstance(ref, torch.Tensor):
return torch.as_tensor(arr, dtype=ref.dtype, device=ref.device)
return arr
def norm_state(self, s):
return (s - self._cast(s, self.state_offset)) / self._cast(s, self.state_scale)
def denorm_state(self, s):
return s * self._cast(s, self.state_scale) + self._cast(s, self.state_offset)
def norm_delta(self, d):
return d / self._cast(d, self.delta_scale)
def denorm_delta(self, d):
return d * self._cast(d, self.delta_scale)
@staticmethod
def compute_delta(cur, nxt):
"""next - cur with the angle component wrapped to [-pi, pi)."""
d = nxt - cur
d[..., 4] = wrap_angle(d[..., 4])
return d
def state_dict(self):
return {
"state_offset": self.state_offset.tolist(),
"state_scale": self.state_scale.tolist(),
"delta_scale": self.delta_scale.tolist(),
}
@classmethod
def from_config(cls, cfg):
return cls(cfg.state_offset, cfg.state_scale, cfg.delta_scale)