Instructions to use ZibinDong/ActionCodec2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ZibinDong/ActionCodec2 with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("ZibinDong/ActionCodec2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Publish audited ActionCodec2 release files
Browse files- runtime/physical/semantic.py +377 -0
- runtime/physical/trajectory.py +130 -0
- runtime/processing_actioncodec2.py +429 -0
- runtime/routing/__init__.py +1 -0
- runtime/routing/cache.py +41 -0
- runtime/routing/defaults.py +609 -0
- runtime/routing/presets.py +802 -0
- runtime/routing/routing.py +745 -0
- runtime/tokenization/__init__.py +1 -0
- runtime/tokenization/bpe.py +397 -0
- runtime/tokenization/bpe_training.py +235 -0
- runtime/tokenization/bpe_vocabulary.py +159 -0
- runtime/tokenization/generation.py +122 -0
- runtime/tokenization/grammar.py +209 -0
- runtime/tokenization/setbpe_spatial.py +177 -0
- runtime/tokenization/setbpe_spatial_training.py +311 -0
- runtime/tokenization/setbpe_temporal.py +258 -0
- runtime/tokenization/setbpe_temporal_training.py +649 -0
- runtime/training/__init__.py +1 -0
- runtime/training/fitting.py +179 -0
- runtime/training/training.py +698 -0
- setup.py +17 -0
- variants/extended-1st-10mm-16k/config.json +4 -0
- variants/extended-1st-10mm-16k/processor_config.json +11 -0
- variants/extended-1st-10mm-16k/profiles/eef/bpe.json +0 -0
- variants/extended-1st-10mm-16k/profiles/eef/config.json +4 -0
- variants/extended-1st-10mm-16k/profiles/eef/config.yaml +74 -0
- variants/extended-1st-10mm-16k/profiles/eef/primitives.npz +3 -0
- variants/extended-1st-10mm-16k/profiles/eef/processor_config.json +3 -0
- variants/extended-1st-10mm-16k/profiles/joint/bpe.json +0 -0
- variants/extended-1st-10mm-16k/profiles/joint/config.json +4 -0
- variants/extended-1st-10mm-16k/profiles/joint/config.yaml +59 -0
- variants/extended-1st-10mm-16k/profiles/joint/primitives.npz +3 -0
- variants/extended-1st-10mm-16k/profiles/joint/processor_config.json +3 -0
- variants/extended-1st-10mm-16k/router_config.yaml +346 -0
- variants/extended-1st-10mm-1k/config.json +4 -0
- variants/extended-1st-10mm-1k/processor_config.json +11 -0
- variants/extended-1st-10mm-1k/profiles/eef/bpe.json +4222 -0
- variants/extended-1st-10mm-1k/profiles/eef/config.json +4 -0
- variants/extended-1st-10mm-1k/profiles/eef/config.yaml +74 -0
runtime/physical/semantic.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Canonical physical preprocessing for routed ActionCodec2 profiles."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from scipy.interpolate import CubicSpline, PchipInterpolator
|
| 9 |
+
from scipy.spatial.transform import Rotation, RotationSpline
|
| 10 |
+
|
| 11 |
+
from ._so3 import euler_xyz_to_quaternion as _euler_xyz_to_quaternion
|
| 12 |
+
from ._so3 import multiply_quaternions as _quaternion_multiply
|
| 13 |
+
from ._so3 import normalize_quaternion as _normalize_quaternion
|
| 14 |
+
from ._so3 import quaternion_to_rotvec as _quaternion_to_rotvec
|
| 15 |
+
from ._so3 import rotvec_to_quaternion as _rotvec_to_quaternion
|
| 16 |
+
|
| 17 |
+
_INTERPOLATION_METHODS = {"linear", "pchip", "cubic"}
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _validate_rate(value: float, name: str) -> float:
|
| 21 |
+
rate = float(value)
|
| 22 |
+
if not math.isfinite(rate) or rate <= 0.0:
|
| 23 |
+
raise ValueError(f"{name} must be positive and finite")
|
| 24 |
+
return rate
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _quaternion_slerp(
|
| 28 |
+
left: np.ndarray, right: np.ndarray, weight: np.ndarray
|
| 29 |
+
) -> np.ndarray:
|
| 30 |
+
|
| 31 |
+
first = _normalize_quaternion(np.asarray(left, dtype=np.float64))
|
| 32 |
+
second = _normalize_quaternion(np.asarray(right, dtype=np.float64))
|
| 33 |
+
dot = np.sum(first * second, axis=1)
|
| 34 |
+
flip = dot < 0.0
|
| 35 |
+
second[flip] *= -1.0
|
| 36 |
+
dot = np.clip(np.abs(dot), 0.0, 1.0)
|
| 37 |
+
output = np.empty_like(first)
|
| 38 |
+
near = dot > 0.9995
|
| 39 |
+
output[near] = first[near] + weight[near, None] * (second[near] - first[near])
|
| 40 |
+
regular = ~near
|
| 41 |
+
if np.any(regular):
|
| 42 |
+
angle = np.arccos(dot[regular])
|
| 43 |
+
denominator = np.sin(angle)
|
| 44 |
+
w = weight[regular]
|
| 45 |
+
output[regular] = (
|
| 46 |
+
np.sin((1.0 - w) * angle)[:, None] / denominator[:, None] * first[regular]
|
| 47 |
+
+ np.sin(w * angle)[:, None] / denominator[:, None] * second[regular]
|
| 48 |
+
)
|
| 49 |
+
return _normalize_quaternion(output)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def resampled_horizon(length: int, *, source_hz: float, target_hz: float) -> int:
|
| 53 |
+
"Return the target control-step count for a regular source horizon."
|
| 54 |
+
if (
|
| 55 |
+
isinstance(length, bool)
|
| 56 |
+
or not isinstance(length, (int, np.integer))
|
| 57 |
+
or length < 1
|
| 58 |
+
):
|
| 59 |
+
raise ValueError("a trajectory must contain at least one sample")
|
| 60 |
+
source_rate = _validate_rate(source_hz, "source_hz")
|
| 61 |
+
target_rate = _validate_rate(target_hz, "target_hz")
|
| 62 |
+
scaled = int(math.floor(int(length) * target_rate / source_rate + 0.5))
|
| 63 |
+
return max(1, scaled)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _target_times(
|
| 67 |
+
length: int,
|
| 68 |
+
source_hz: float,
|
| 69 |
+
target_hz: float,
|
| 70 |
+
*,
|
| 71 |
+
target_length: int | None,
|
| 72 |
+
) -> np.ndarray:
|
| 73 |
+
count = (
|
| 74 |
+
resampled_horizon(length, source_hz=source_hz, target_hz=target_hz)
|
| 75 |
+
if target_length is None
|
| 76 |
+
else target_length
|
| 77 |
+
)
|
| 78 |
+
if isinstance(count, bool) or not isinstance(count, (int, np.integer)) or count < 1:
|
| 79 |
+
raise ValueError("target_length must be a positive integer")
|
| 80 |
+
return np.arange(int(count), dtype=np.float64) / target_hz
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _guarded_natural_cubic(
|
| 84 |
+
source_time: np.ndarray,
|
| 85 |
+
values: np.ndarray,
|
| 86 |
+
target_time: np.ndarray,
|
| 87 |
+
) -> np.ndarray:
|
| 88 |
+
|
| 89 |
+
sampled = np.asarray(
|
| 90 |
+
CubicSpline(source_time, values, axis=0, bc_type="natural")(target_time),
|
| 91 |
+
dtype=np.float64,
|
| 92 |
+
)
|
| 93 |
+
lower = np.min(values, axis=0)
|
| 94 |
+
upper = np.max(values, axis=0)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
tolerance = 1e-9 + 0.05 * (upper - lower)
|
| 98 |
+
fallback_axes = np.any(
|
| 99 |
+
(sampled < lower[None] - tolerance[None])
|
| 100 |
+
| (sampled > upper[None] + tolerance[None]),
|
| 101 |
+
axis=0,
|
| 102 |
+
)
|
| 103 |
+
if np.any(fallback_axes):
|
| 104 |
+
sampled[:, fallback_axes] = PchipInterpolator(
|
| 105 |
+
source_time, values[:, fallback_axes], axis=0
|
| 106 |
+
)(target_time)
|
| 107 |
+
return sampled
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def resample_uniform(
|
| 111 |
+
values: np.ndarray,
|
| 112 |
+
*,
|
| 113 |
+
source_hz: float,
|
| 114 |
+
target_hz: float,
|
| 115 |
+
kind: str = "linear",
|
| 116 |
+
target_length: int | None = None,
|
| 117 |
+
method: str = "linear",
|
| 118 |
+
) -> np.ndarray:
|
| 119 |
+
"Resample one regular control-horizon trajectory onto a target clock."
|
| 120 |
+
source_rate = _validate_rate(source_hz, "source_hz")
|
| 121 |
+
target_rate = _validate_rate(target_hz, "target_hz")
|
| 122 |
+
array = np.asarray(values, dtype=np.float64)
|
| 123 |
+
if array.ndim != 2 or len(array) < 1 or not np.all(np.isfinite(array)):
|
| 124 |
+
raise ValueError("values must be a non-empty finite [T, D] array")
|
| 125 |
+
if kind not in {"linear", "hold", "pose_xyzw", "pose_euler_xyz"}:
|
| 126 |
+
raise ValueError("unsupported resampling kind")
|
| 127 |
+
if method not in _INTERPOLATION_METHODS:
|
| 128 |
+
raise ValueError(f"method must be one of {sorted(_INTERPOLATION_METHODS)}")
|
| 129 |
+
if kind == "pose_xyzw" and array.shape[1] != 7:
|
| 130 |
+
raise ValueError("pose_xyzw values must have shape [T, 7]")
|
| 131 |
+
if kind == "pose_euler_xyz" and array.shape[1] != 6:
|
| 132 |
+
raise ValueError("pose_euler_xyz values must have shape [T, 6]")
|
| 133 |
+
standard_length = resampled_horizon(
|
| 134 |
+
len(array), source_hz=source_rate, target_hz=target_rate
|
| 135 |
+
)
|
| 136 |
+
if source_rate == target_rate and target_length in {None, standard_length}:
|
| 137 |
+
if kind == "pose_euler_xyz":
|
| 138 |
+
array = np.concatenate(
|
| 139 |
+
(array[:, :3], _euler_xyz_to_quaternion(array[:, 3:6])), axis=1
|
| 140 |
+
)
|
| 141 |
+
return np.asarray(array, dtype=np.float32)
|
| 142 |
+
|
| 143 |
+
target_time = _target_times(
|
| 144 |
+
len(array), source_rate, target_rate, target_length=target_length
|
| 145 |
+
)
|
| 146 |
+
source_time = np.arange(len(array), dtype=np.float64) / source_rate
|
| 147 |
+
interpolation_time = np.minimum(target_time, source_time[-1])
|
| 148 |
+
source_position = target_time * source_rate
|
| 149 |
+
left = np.minimum(np.floor(source_position).astype(np.int64), len(array) - 1)
|
| 150 |
+
right = np.minimum(left + 1, len(array) - 1)
|
| 151 |
+
weight = source_position - left
|
| 152 |
+
if kind == "hold":
|
| 153 |
+
result = array[left]
|
| 154 |
+
else:
|
| 155 |
+
pose = kind in {"pose_xyzw", "pose_euler_xyz"}
|
| 156 |
+
continuous = array[:, :3] if pose else array
|
| 157 |
+
if len(array) == 1:
|
| 158 |
+
sampled = np.repeat(continuous, len(target_time), axis=0)
|
| 159 |
+
elif method == "pchip":
|
| 160 |
+
sampled = PchipInterpolator(source_time, continuous, axis=0)(
|
| 161 |
+
interpolation_time
|
| 162 |
+
)
|
| 163 |
+
elif method == "cubic":
|
| 164 |
+
sampled = _guarded_natural_cubic(
|
| 165 |
+
source_time, continuous, interpolation_time
|
| 166 |
+
)
|
| 167 |
+
else:
|
| 168 |
+
sampled = array[left, : continuous.shape[1]] + weight[:, None] * (
|
| 169 |
+
array[right, : continuous.shape[1]] - array[left, : continuous.shape[1]]
|
| 170 |
+
)
|
| 171 |
+
if not pose:
|
| 172 |
+
result = sampled
|
| 173 |
+
else:
|
| 174 |
+
quaternion = (
|
| 175 |
+
array[:, 3:7]
|
| 176 |
+
if kind == "pose_xyzw"
|
| 177 |
+
else _euler_xyz_to_quaternion(array[:, 3:6])
|
| 178 |
+
)
|
| 179 |
+
if len(array) == 1:
|
| 180 |
+
rotation = np.repeat(quaternion, len(target_time), axis=0)
|
| 181 |
+
elif method == "linear":
|
| 182 |
+
rotation = _quaternion_slerp(
|
| 183 |
+
quaternion[left], quaternion[right], weight
|
| 184 |
+
)
|
| 185 |
+
else:
|
| 186 |
+
rotation = RotationSpline(source_time, Rotation.from_quat(quaternion))(
|
| 187 |
+
interpolation_time
|
| 188 |
+
).as_quat()
|
| 189 |
+
result = np.concatenate((sampled, rotation), axis=1)
|
| 190 |
+
return np.asarray(result, dtype=np.float32)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def eef_pose_absolute(poses: np.ndarray) -> np.ndarray:
|
| 194 |
+
"""Convert canonical ``xyz + xyzw`` state to ``xyz + rotation_vector``."""
|
| 195 |
+
values = np.asarray(poses, dtype=np.float64)
|
| 196 |
+
if values.ndim != 2 or values.shape[1] != 7 or not np.all(np.isfinite(values)):
|
| 197 |
+
raise ValueError("poses must be a finite [T, 7] array")
|
| 198 |
+
return np.asarray(
|
| 199 |
+
np.concatenate((values[:, :3], _quaternion_to_rotvec(values[:, 3:7])), axis=1),
|
| 200 |
+
dtype=np.float32,
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def eef_pose_components(poses: np.ndarray, representation: str) -> np.ndarray:
|
| 205 |
+
"""Convert canonical ``xyz + xyzw`` poses to one registered source layout."""
|
| 206 |
+
values = np.asarray(poses, dtype=np.float64)
|
| 207 |
+
if values.ndim != 2 or values.shape[1] != 7 or not np.all(np.isfinite(values)):
|
| 208 |
+
raise ValueError("poses must be a finite [T, 7] array")
|
| 209 |
+
quaternion = _normalize_quaternion(values[:, 3:7])
|
| 210 |
+
if representation == "rotation_vector":
|
| 211 |
+
rotation = _quaternion_to_rotvec(quaternion)
|
| 212 |
+
elif representation == "euler_xyz":
|
| 213 |
+
rotation = Rotation.from_quat(quaternion).as_euler("xyz")
|
| 214 |
+
elif representation == "quaternion_xyzw":
|
| 215 |
+
rotation = quaternion
|
| 216 |
+
else:
|
| 217 |
+
raise ValueError("unsupported EEF rotation representation")
|
| 218 |
+
return np.asarray(
|
| 219 |
+
np.concatenate((values[:, :3], rotation), axis=1), dtype=np.float32
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def eef_pose_from_components(
|
| 224 |
+
position: np.ndarray,
|
| 225 |
+
rotation: np.ndarray,
|
| 226 |
+
representation: str,
|
| 227 |
+
) -> np.ndarray:
|
| 228 |
+
"Build canonical ``xyz + xyzw`` poses from an explicit source layout."
|
| 229 |
+
translation = np.asarray(position, dtype=np.float64)
|
| 230 |
+
orientation = np.asarray(rotation, dtype=np.float64)
|
| 231 |
+
if translation.ndim != 2 or translation.shape[1] != 3 or len(translation) < 1:
|
| 232 |
+
raise ValueError("position must have non-empty shape [T, 3]")
|
| 233 |
+
if len(orientation) != len(translation) or not np.all(np.isfinite(translation)):
|
| 234 |
+
raise ValueError("position and rotation must have matching finite trajectories")
|
| 235 |
+
if representation == "rotation_vector":
|
| 236 |
+
if orientation.ndim != 2 or orientation.shape[1] != 3:
|
| 237 |
+
raise ValueError("rotation_vector must have shape [T, 3]")
|
| 238 |
+
quaternion = _rotvec_to_quaternion(orientation)
|
| 239 |
+
elif representation == "euler_xyz":
|
| 240 |
+
if orientation.ndim != 2 or orientation.shape[1] != 3:
|
| 241 |
+
raise ValueError("euler_xyz must have shape [T, 3]")
|
| 242 |
+
quaternion = _euler_xyz_to_quaternion(orientation)
|
| 243 |
+
elif representation == "quaternion_xyzw":
|
| 244 |
+
if orientation.ndim != 2 or orientation.shape[1] != 4:
|
| 245 |
+
raise ValueError("quaternion_xyzw must have shape [T, 4]")
|
| 246 |
+
quaternion = _normalize_quaternion(orientation)
|
| 247 |
+
else:
|
| 248 |
+
raise ValueError("unsupported EEF rotation representation")
|
| 249 |
+
if not np.all(np.isfinite(orientation)):
|
| 250 |
+
raise ValueError("position and rotation must be finite")
|
| 251 |
+
return np.asarray(
|
| 252 |
+
np.concatenate((translation, quaternion), axis=1), dtype=np.float32
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def eef_pose_local_absolute(
|
| 257 |
+
poses: np.ndarray, *, initial_pose: np.ndarray | None = None
|
| 258 |
+
) -> np.ndarray:
|
| 259 |
+
"Express measured poses relative to an explicit or inferred initial pose."
|
| 260 |
+
values = np.asarray(poses, dtype=np.float64)
|
| 261 |
+
if (
|
| 262 |
+
values.ndim != 2
|
| 263 |
+
or values.shape[1] != 7
|
| 264 |
+
or len(values) < 1
|
| 265 |
+
or not np.all(np.isfinite(values))
|
| 266 |
+
):
|
| 267 |
+
raise ValueError("poses must be a finite non-empty [T, 7] array")
|
| 268 |
+
if initial_pose is None:
|
| 269 |
+
initial = values[:1]
|
| 270 |
+
else:
|
| 271 |
+
initial = np.asarray(initial_pose, dtype=np.float64)
|
| 272 |
+
if initial.shape == (7,):
|
| 273 |
+
initial = initial[None]
|
| 274 |
+
if initial.shape != (1, 7) or not np.all(np.isfinite(initial)):
|
| 275 |
+
raise ValueError("initial_pose must be a finite [7] pose")
|
| 276 |
+
quaternion = _normalize_quaternion(values[:, 3:7])
|
| 277 |
+
initial_quaternion = _normalize_quaternion(initial[:, 3:7])
|
| 278 |
+
inverse_start = initial_quaternion.copy()
|
| 279 |
+
inverse_start[:, :3] *= -1.0
|
| 280 |
+
relative = _quaternion_multiply(
|
| 281 |
+
np.broadcast_to(inverse_start, quaternion.shape), quaternion
|
| 282 |
+
)
|
| 283 |
+
return np.asarray(
|
| 284 |
+
np.concatenate(
|
| 285 |
+
(values[:, :3] - initial[:, :3], _quaternion_to_rotvec(relative)), axis=1
|
| 286 |
+
),
|
| 287 |
+
dtype=np.float32,
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def eef_pose_deltas(poses: np.ndarray) -> np.ndarray:
|
| 292 |
+
"Differentiate a local ``xyz + xyzw`` path from zero/identity."
|
| 293 |
+
values = np.asarray(poses, dtype=np.float64)
|
| 294 |
+
if (
|
| 295 |
+
values.ndim != 2
|
| 296 |
+
or values.shape[1] != 7
|
| 297 |
+
or len(values) < 1
|
| 298 |
+
or not np.all(np.isfinite(values))
|
| 299 |
+
):
|
| 300 |
+
raise ValueError("poses must be a finite non-empty [T, 7] array")
|
| 301 |
+
quaternion = _normalize_quaternion(values[:, 3:7])
|
| 302 |
+
previous_position = np.concatenate((np.zeros((1, 3)), values[:-1, :3]), axis=0)
|
| 303 |
+
identity = np.asarray([[0.0, 0.0, 0.0, 1.0]], dtype=np.float64)
|
| 304 |
+
previous_quaternion = np.concatenate((identity, quaternion[:-1]), axis=0)
|
| 305 |
+
inverse_previous = previous_quaternion.copy()
|
| 306 |
+
inverse_previous[:, :3] *= -1.0
|
| 307 |
+
increment = _quaternion_multiply(inverse_previous, quaternion)
|
| 308 |
+
return np.asarray(
|
| 309 |
+
np.concatenate(
|
| 310 |
+
(values[:, :3] - previous_position, _quaternion_to_rotvec(increment)),
|
| 311 |
+
axis=1,
|
| 312 |
+
),
|
| 313 |
+
dtype=np.float32,
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def eef_pose_from_local_absolute(
|
| 318 |
+
poses: np.ndarray, *, initial_pose: np.ndarray
|
| 319 |
+
) -> np.ndarray:
|
| 320 |
+
"""Attach an external initial pose to a local ``xyz + xyzw`` path."""
|
| 321 |
+
values = np.asarray(poses, dtype=np.float64)
|
| 322 |
+
initial = np.asarray(initial_pose, dtype=np.float64)
|
| 323 |
+
if (
|
| 324 |
+
values.ndim != 2
|
| 325 |
+
or values.shape[1] != 7
|
| 326 |
+
or len(values) < 1
|
| 327 |
+
or not np.all(np.isfinite(values))
|
| 328 |
+
):
|
| 329 |
+
raise ValueError("poses must be a finite non-empty [T, 7] array")
|
| 330 |
+
if initial.shape == (7,):
|
| 331 |
+
initial = initial[None]
|
| 332 |
+
if initial.shape != (1, 7) or not np.all(np.isfinite(initial)):
|
| 333 |
+
raise ValueError("initial_pose must be a finite [7] pose")
|
| 334 |
+
local_quaternion = _normalize_quaternion(values[:, 3:7])
|
| 335 |
+
initial_quaternion = _normalize_quaternion(initial[:, 3:7])
|
| 336 |
+
quaternion = _quaternion_multiply(
|
| 337 |
+
np.broadcast_to(initial_quaternion, local_quaternion.shape), local_quaternion
|
| 338 |
+
)
|
| 339 |
+
return np.asarray(
|
| 340 |
+
np.concatenate((values[:, :3] + initial[:, :3], quaternion), axis=1),
|
| 341 |
+
dtype=np.float32,
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
def integrate_eef_deltas(deltas: np.ndarray) -> np.ndarray:
|
| 346 |
+
"Integrate ``[dx,dy,dz,body-rotvec]`` from a zero/identity pose."
|
| 347 |
+
values = np.asarray(deltas, dtype=np.float64)
|
| 348 |
+
if values.ndim != 2 or values.shape[1] != 6 or not np.all(np.isfinite(values)):
|
| 349 |
+
raise ValueError("deltas must be a finite [T, 6] array")
|
| 350 |
+
if len(values) < 1:
|
| 351 |
+
raise ValueError("deltas must contain at least one step")
|
| 352 |
+
translation = np.cumsum(values[:, :3], axis=0)
|
| 353 |
+
increments = _rotvec_to_quaternion(values[:, 3:6])
|
| 354 |
+
orientation = np.empty((len(values), 4), dtype=np.float64)
|
| 355 |
+
current = np.asarray([[0.0, 0.0, 0.0, 1.0]], dtype=np.float64)
|
| 356 |
+
for index, increment in enumerate(increments):
|
| 357 |
+
current = _quaternion_multiply(current, increment[None])
|
| 358 |
+
orientation[index] = current[0]
|
| 359 |
+
return np.asarray(
|
| 360 |
+
np.concatenate((translation, orientation), axis=1), dtype=np.float32
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def binarize_gripper_actions(
|
| 365 |
+
values: np.ndarray,
|
| 366 |
+
*,
|
| 367 |
+
threshold: float,
|
| 368 |
+
open_is_high: bool,
|
| 369 |
+
) -> np.ndarray:
|
| 370 |
+
"""Map one controller gripper channel to canonical ``0=closed, 1=open``."""
|
| 371 |
+
array = np.asarray(values, dtype=np.float32)
|
| 372 |
+
if array.ndim != 2 or array.shape[1] != 1 or not np.all(np.isfinite(array)):
|
| 373 |
+
raise ValueError("gripper action must be a finite [T, 1] array")
|
| 374 |
+
if not math.isfinite(threshold):
|
| 375 |
+
raise ValueError("gripper threshold must be finite")
|
| 376 |
+
opened = array >= threshold if open_is_high else array <= threshold
|
| 377 |
+
return opened.astype(np.float32)
|
runtime/physical/trajectory.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Variable-width trajectory contract shared by statistics and fitting."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from collections.abc import Sequence
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from ..common._validation import active_dimensions
|
| 11 |
+
from .configuration import ActionCodec2Config
|
| 12 |
+
|
| 13 |
+
__all__ = ["ActiveTrajectory", "TrajectoryLike", "prepare_trajectory"]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass(frozen=True)
|
| 17 |
+
class ActiveTrajectory:
|
| 18 |
+
"One complete trajectory over an explicit subset of codebook dimensions."
|
| 19 |
+
|
| 20 |
+
values: np.ndarray
|
| 21 |
+
active_dims: tuple[int, ...]
|
| 22 |
+
previous_first_order: np.ndarray | None = None
|
| 23 |
+
|
| 24 |
+
def __init__(
|
| 25 |
+
self,
|
| 26 |
+
values: np.ndarray,
|
| 27 |
+
active_dims: Sequence[int],
|
| 28 |
+
*,
|
| 29 |
+
previous_first_order: np.ndarray | None = None,
|
| 30 |
+
) -> None:
|
| 31 |
+
object.__setattr__(self, "values", np.asarray(values))
|
| 32 |
+
object.__setattr__(self, "active_dims", tuple(active_dims))
|
| 33 |
+
previous = (
|
| 34 |
+
None
|
| 35 |
+
if previous_first_order is None
|
| 36 |
+
else np.asarray(previous_first_order, dtype=np.float32)
|
| 37 |
+
)
|
| 38 |
+
if previous is not None and (
|
| 39 |
+
previous.shape != (len(self.active_dims),)
|
| 40 |
+
or not np.all(np.isfinite(previous))
|
| 41 |
+
):
|
| 42 |
+
raise ValueError(
|
| 43 |
+
"previous_first_order must be finite with shape [D_active]"
|
| 44 |
+
)
|
| 45 |
+
object.__setattr__(self, "previous_first_order", previous)
|
| 46 |
+
|
| 47 |
+
@classmethod
|
| 48 |
+
def from_padded(
|
| 49 |
+
cls,
|
| 50 |
+
values: np.ndarray,
|
| 51 |
+
*,
|
| 52 |
+
active_dims: Sequence[int],
|
| 53 |
+
previous_first_order: np.ndarray | None = None,
|
| 54 |
+
) -> ActiveTrajectory:
|
| 55 |
+
"Drop engineering-only dense padding before the semantic boundary."
|
| 56 |
+
array = np.asarray(values)
|
| 57 |
+
dims = tuple(int(value) for value in active_dims)
|
| 58 |
+
if array.ndim != 2 or not dims or min(dims) < 0 or max(dims) >= array.shape[1]:
|
| 59 |
+
raise ValueError(
|
| 60 |
+
"padded values and active_dims do not define a valid trajectory"
|
| 61 |
+
)
|
| 62 |
+
previous = (
|
| 63 |
+
None
|
| 64 |
+
if previous_first_order is None
|
| 65 |
+
else np.take(previous_first_order, dims)
|
| 66 |
+
)
|
| 67 |
+
return cls(np.take(array, dims, axis=1), dims, previous_first_order=previous)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def trajectory_boundary(
|
| 71 |
+
trajectory: TrajectoryLike, config: ActionCodec2Config, active_dims: tuple[int, ...]
|
| 72 |
+
) -> np.ndarray | None:
|
| 73 |
+
"""Expand explicit compact state to ``[D]`` for fitting and validation."""
|
| 74 |
+
previous = getattr(trajectory, "previous_first_order", None)
|
| 75 |
+
active = set(active_dims)
|
| 76 |
+
required = any(
|
| 77 |
+
c.primitive_order == 2 and active.intersection(c.dims)
|
| 78 |
+
for c in config.physical.components.values()
|
| 79 |
+
)
|
| 80 |
+
if previous is None and required:
|
| 81 |
+
raise ValueError(
|
| 82 |
+
"second-order trajectories require explicit previous_first_order"
|
| 83 |
+
)
|
| 84 |
+
if previous is None and not any(
|
| 85 |
+
c.primitive_order == 2 for c in config.physical.components.values()
|
| 86 |
+
):
|
| 87 |
+
return None
|
| 88 |
+
dense = np.zeros(config.physical.input_dim, dtype=np.float32)
|
| 89 |
+
if previous is not None:
|
| 90 |
+
dense[list(active_dims)] = previous
|
| 91 |
+
return dense
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
TrajectoryLike = np.ndarray | ActiveTrajectory
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def prepare_trajectory(
|
| 98 |
+
trajectory: TrajectoryLike,
|
| 99 |
+
*,
|
| 100 |
+
dimension: int,
|
| 101 |
+
index: int,
|
| 102 |
+
dtype: np.dtype | type = np.float32,
|
| 103 |
+
) -> tuple[np.ndarray, tuple[int, ...]]:
|
| 104 |
+
"Validate a trajectory and return compact values plus global dimension ids."
|
| 105 |
+
if isinstance(trajectory, ActiveTrajectory):
|
| 106 |
+
raw = trajectory.values
|
| 107 |
+
active_dims = trajectory.active_dims
|
| 108 |
+
else:
|
| 109 |
+
raw = trajectory
|
| 110 |
+
array = np.asarray(raw)
|
| 111 |
+
active_dims = tuple(range(array.shape[1])) if array.ndim == 2 else ()
|
| 112 |
+
|
| 113 |
+
values = np.asarray(raw, dtype=dtype)
|
| 114 |
+
if values.ndim != 2 or len(values) < 1 or values.shape[1] < 1:
|
| 115 |
+
raise ValueError(
|
| 116 |
+
f"trajectory {index} must have non-empty shape [T, D_active], got {values.shape}"
|
| 117 |
+
)
|
| 118 |
+
if len(active_dims) != values.shape[1]:
|
| 119 |
+
raise ValueError(
|
| 120 |
+
f"trajectory {index} active_dims has {len(active_dims)} entries for "
|
| 121 |
+
f"width {values.shape[1]}"
|
| 122 |
+
)
|
| 123 |
+
if any(isinstance(item, bool) or not isinstance(item, int) for item in active_dims):
|
| 124 |
+
raise ValueError(f"trajectory {index} active_dims must contain integers")
|
| 125 |
+
active_dims = active_dimensions(
|
| 126 |
+
active_dims, dimension, subject=f"trajectory {index} active_dims"
|
| 127 |
+
)
|
| 128 |
+
if not np.all(np.isfinite(values)):
|
| 129 |
+
raise ValueError(f"trajectory {index} contains a non-finite value")
|
| 130 |
+
return np.ascontiguousarray(values), active_dims
|
runtime/processing_actioncodec2.py
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hugging Face processor over the shared ActionCodec2 frontend implementation."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from collections.abc import Mapping
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
from transformers.feature_extraction_utils import BatchFeature
|
| 13 |
+
from transformers.processing_utils import ProcessorMixin
|
| 14 |
+
|
| 15 |
+
from .routing.defaults import CodecBuilder, ProfileBundleBuilder, builtin_action_spaces
|
| 16 |
+
from .integration.hf import (
|
| 17 |
+
PROCESSOR_CONFIG_NAME,
|
| 18 |
+
ROUTER_ALLOW_PATTERNS,
|
| 19 |
+
ActionCodec2FrontendHFConfig,
|
| 20 |
+
resolve_pretrained_directory,
|
| 21 |
+
save_huggingface_metadata,
|
| 22 |
+
save_model_card,
|
| 23 |
+
upload_pretrained_directory,
|
| 24 |
+
)
|
| 25 |
+
from .routing.presets import PresetConfig
|
| 26 |
+
from .routing.routing import ActionCodec2Router
|
| 27 |
+
from .frontend.tokenizer import ActionCodec2Tokenizer
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _saved_metadata(directory: Path) -> dict[str, Any]:
|
| 31 |
+
metadata = directory / PROCESSOR_CONFIG_NAME
|
| 32 |
+
return (
|
| 33 |
+
json.loads(metadata.read_text(encoding="utf-8")) if metadata.is_file() else {}
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _fitted_status(profiles) -> str:
|
| 38 |
+
status = ", ".join(sorted(profiles)) or "none; call codec.fit(episodes)"
|
| 39 |
+
return f"Fitted profiles: {status}\n"
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class ActionCodec2(ActionCodec2Tokenizer, ProcessorMixin):
|
| 43 |
+
"Action-space-bound processor with lossless token serialization."
|
| 44 |
+
|
| 45 |
+
model_type = ActionCodec2FrontendHFConfig.model_type
|
| 46 |
+
model_input_names = ["input_ids"]
|
| 47 |
+
|
| 48 |
+
attributes = []
|
| 49 |
+
|
| 50 |
+
def __init__(
|
| 51 |
+
self,
|
| 52 |
+
backend: ActionCodec2Router | None = None,
|
| 53 |
+
*,
|
| 54 |
+
action_space: str,
|
| 55 |
+
primitive_order: int = 1,
|
| 56 |
+
bpe_scheme: str | None = None,
|
| 57 |
+
profile_configs: Mapping[str, object] | None = None,
|
| 58 |
+
) -> None:
|
| 59 |
+
fresh = backend is None
|
| 60 |
+
if fresh:
|
| 61 |
+
backend = CodecBuilder(
|
| 62 |
+
primitive_order=primitive_order,
|
| 63 |
+
bpe_scheme=bpe_scheme,
|
| 64 |
+
profile_configs=profile_configs,
|
| 65 |
+
).build()
|
| 66 |
+
elif (
|
| 67 |
+
primitive_order != 1
|
| 68 |
+
or bpe_scheme is not None
|
| 69 |
+
or profile_configs is not None
|
| 70 |
+
):
|
| 71 |
+
raise ValueError(
|
| 72 |
+
"profile construction options cannot accompany an existing backend"
|
| 73 |
+
)
|
| 74 |
+
super().__init__(backend, action_space=action_space)
|
| 75 |
+
ProcessorMixin.__init__(self)
|
| 76 |
+
self._fitted_profiles = frozenset() if fresh else frozenset(backend.profiles)
|
| 77 |
+
self.training_report = None
|
| 78 |
+
self._source_directory = Path(__file__).parent
|
| 79 |
+
|
| 80 |
+
def __dir__(self):
|
| 81 |
+
inapplicable = {
|
| 82 |
+
"action_tokenizer",
|
| 83 |
+
"attributes",
|
| 84 |
+
"audio_tokenizer",
|
| 85 |
+
"chat_template",
|
| 86 |
+
"check_argument_for_proper_class",
|
| 87 |
+
"encode_kwargs",
|
| 88 |
+
"feature_extractor_class",
|
| 89 |
+
"from_args_and_dict",
|
| 90 |
+
"get_possibly_dynamic_module",
|
| 91 |
+
"get_processor_dict",
|
| 92 |
+
"model_input_names",
|
| 93 |
+
"model_type",
|
| 94 |
+
"optional_attributes",
|
| 95 |
+
"optional_call_args",
|
| 96 |
+
"apply_chat_template",
|
| 97 |
+
"post_process_image_text_to_text",
|
| 98 |
+
"pad_action_id",
|
| 99 |
+
"bos_blk_id",
|
| 100 |
+
"parts_meta",
|
| 101 |
+
"BAR_SENTINEL_TOKENS",
|
| 102 |
+
"push_to_hub",
|
| 103 |
+
"register_for_auto_class",
|
| 104 |
+
"tokenizer_class",
|
| 105 |
+
"to_dict",
|
| 106 |
+
"to_json_file",
|
| 107 |
+
"to_json_string",
|
| 108 |
+
"valid_processor_kwargs",
|
| 109 |
+
"validate_init_kwargs",
|
| 110 |
+
}
|
| 111 |
+
return [name for name in super().__dir__() if name not in inapplicable]
|
| 112 |
+
|
| 113 |
+
def __repr__(self) -> str:
|
| 114 |
+
return (
|
| 115 |
+
f"ActionCodec2(action_space={self.action_space.name!r}, "
|
| 116 |
+
f"D={self.action_dim}, vocab={self.action_tokenizer.vocab_size})"
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
@staticmethod
|
| 120 |
+
def builtin_action_spaces():
|
| 121 |
+
"""Return the immutable named layout definitions shipped in the package."""
|
| 122 |
+
return builtin_action_spaces()
|
| 123 |
+
|
| 124 |
+
@classmethod
|
| 125 |
+
def from_profiles(cls, joint, eef, *, action_space: str, token_budgets=None):
|
| 126 |
+
"Bind an artifact assembled from existing joint/EEF profile directories."
|
| 127 |
+
router = ProfileBundleBuilder(joint, eef, token_budgets=token_budgets).build()
|
| 128 |
+
return cls(router, action_space=action_space)
|
| 129 |
+
|
| 130 |
+
def logits_processor(
|
| 131 |
+
self,
|
| 132 |
+
horizon: int,
|
| 133 |
+
*,
|
| 134 |
+
fps: float,
|
| 135 |
+
prompt_length: int,
|
| 136 |
+
eos_token_id: int,
|
| 137 |
+
token_offset: int = 0,
|
| 138 |
+
pad_token_id=None,
|
| 139 |
+
):
|
| 140 |
+
"Create a Transformers LogitsProcessor for a fixed action horizon."
|
| 141 |
+
from .tokenization.generation import ActionCodec2LogitsProcessor
|
| 142 |
+
|
| 143 |
+
return ActionCodec2LogitsProcessor(
|
| 144 |
+
self,
|
| 145 |
+
horizon,
|
| 146 |
+
fps=fps,
|
| 147 |
+
prompt_length=prompt_length,
|
| 148 |
+
eos_token_id=eos_token_id,
|
| 149 |
+
token_offset=token_offset,
|
| 150 |
+
pad_token_id=pad_token_id,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
def batch_decode(
|
| 154 |
+
self,
|
| 155 |
+
token_rows,
|
| 156 |
+
*,
|
| 157 |
+
fps=None,
|
| 158 |
+
current_state=None,
|
| 159 |
+
previous_first_order=None,
|
| 160 |
+
errors="raise",
|
| 161 |
+
):
|
| 162 |
+
"Decode independent token rows, including different valid horizons."
|
| 163 |
+
self._require_fitted()
|
| 164 |
+
if errors not in ("raise", "return"):
|
| 165 |
+
raise ValueError("errors must be 'raise' or 'return'")
|
| 166 |
+
rows = list(token_rows)
|
| 167 |
+
current = (
|
| 168 |
+
None
|
| 169 |
+
if current_state is None
|
| 170 |
+
else np.asarray(
|
| 171 |
+
current_state.detach().cpu()
|
| 172 |
+
if isinstance(current_state, torch.Tensor)
|
| 173 |
+
else current_state
|
| 174 |
+
)
|
| 175 |
+
)
|
| 176 |
+
if current is not None and current.shape != (len(rows), self.action_dim):
|
| 177 |
+
raise ValueError(
|
| 178 |
+
f"current_state must have shape {(len(rows), self.action_dim)}"
|
| 179 |
+
)
|
| 180 |
+
previous = self._numpy_boundary(previous_first_order)
|
| 181 |
+
if previous is not None and any(
|
| 182 |
+
np.ndim(v) != 2 or len(v) != len(rows) for v in previous.values()
|
| 183 |
+
):
|
| 184 |
+
raise ValueError(
|
| 185 |
+
"previous_first_order arrays must have batch dimension equal to token rows"
|
| 186 |
+
)
|
| 187 |
+
result = []
|
| 188 |
+
for index, row in enumerate(rows):
|
| 189 |
+
try:
|
| 190 |
+
decoded = self.decode(
|
| 191 |
+
[row],
|
| 192 |
+
fps=fps,
|
| 193 |
+
current_state=None
|
| 194 |
+
if current is None
|
| 195 |
+
else current[index : index + 1],
|
| 196 |
+
previous_first_order=None
|
| 197 |
+
if previous is None
|
| 198 |
+
else {k: v[index : index + 1] for k, v in previous.items()},
|
| 199 |
+
)
|
| 200 |
+
except ValueError as error:
|
| 201 |
+
error = ValueError(f"token row {index}: {error}")
|
| 202 |
+
if errors == "raise":
|
| 203 |
+
raise error from None
|
| 204 |
+
decoded = error
|
| 205 |
+
result.append(decoded)
|
| 206 |
+
return result
|
| 207 |
+
|
| 208 |
+
@property
|
| 209 |
+
def fitted_profiles(self) -> frozenset[str]:
|
| 210 |
+
"""Names of profiles fitted on data; persisted by save_pretrained()."""
|
| 211 |
+
return self._fitted_profiles
|
| 212 |
+
|
| 213 |
+
@property
|
| 214 |
+
def is_fitted(self) -> bool:
|
| 215 |
+
"""Whether every profile used by the selected action space is fitted."""
|
| 216 |
+
return all(
|
| 217 |
+
c.space in self.fitted_profiles for c in self.action_space.components
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
def _require_fitted(self):
|
| 221 |
+
missing = {c.space for c in self.action_space.components} - self.fitted_profiles
|
| 222 |
+
if missing:
|
| 223 |
+
raise RuntimeError(
|
| 224 |
+
f"Unfitted profiles {sorted(missing)}; call codec.fit(episodes) first"
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
def _describe_action_spaces(self, action_dim: int | None = None) -> str:
|
| 228 |
+
return _fitted_status(self.fitted_profiles) + super()._describe_action_spaces(
|
| 229 |
+
action_dim
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
def for_action_space(self, name: str) -> ActionCodec2:
|
| 233 |
+
"""Share current weights with a new binding; subsequent fit replaces only that instance."""
|
| 234 |
+
codec = super().for_action_space(name)
|
| 235 |
+
codec._fitted_profiles = self.fitted_profiles
|
| 236 |
+
codec.training_report = self.training_report
|
| 237 |
+
codec._source_directory = self._source_directory
|
| 238 |
+
return codec
|
| 239 |
+
|
| 240 |
+
def fit(
|
| 241 |
+
self,
|
| 242 |
+
episodes,
|
| 243 |
+
*,
|
| 244 |
+
fps: float | None = None,
|
| 245 |
+
current_state=None,
|
| 246 |
+
previous_first_order=None,
|
| 247 |
+
bootstrap_context: bool = False,
|
| 248 |
+
backend: str = "auto",
|
| 249 |
+
threads: int = 0,
|
| 250 |
+
batch_size: int = 256,
|
| 251 |
+
progress: bool = False,
|
| 252 |
+
max_cell_bytes: int | None = None,
|
| 253 |
+
work_dir: str | Path | None = None,
|
| 254 |
+
) -> ActionCodec2:
|
| 255 |
+
"Fit physical vocabularies from actions in declared source layouts."
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
from .training.fitting import ActionCodec2Fitter
|
| 259 |
+
|
| 260 |
+
router, report = ActionCodec2Fitter(
|
| 261 |
+
self, bootstrap_context=bootstrap_context
|
| 262 |
+
).fit(
|
| 263 |
+
episodes,
|
| 264 |
+
work_dir=work_dir,
|
| 265 |
+
metadata=dict(
|
| 266 |
+
fps=fps,
|
| 267 |
+
current_state=current_state,
|
| 268 |
+
previous_first_order=previous_first_order,
|
| 269 |
+
),
|
| 270 |
+
backend=backend,
|
| 271 |
+
threads=threads,
|
| 272 |
+
batch_size=batch_size,
|
| 273 |
+
progress=progress,
|
| 274 |
+
max_cell_bytes=max_cell_bytes,
|
| 275 |
+
)
|
| 276 |
+
fitted = self.fitted_profiles | report["profiles"].keys()
|
| 277 |
+
replacement = type(self)(router, action_space=self.action_space.name)
|
| 278 |
+
replacement._fitted_profiles = frozenset(fitted)
|
| 279 |
+
replacement.training_report = report
|
| 280 |
+
replacement._source_directory = self._source_directory
|
| 281 |
+
replacement._warned_default_fps = self._warned_default_fps
|
| 282 |
+
self.__dict__.update(replacement.__dict__)
|
| 283 |
+
return self
|
| 284 |
+
|
| 285 |
+
def _encode_action_indices(self, action, encode_kwargs=None):
|
| 286 |
+
self._require_fitted()
|
| 287 |
+
return super()._encode_action_indices(action, encode_kwargs)
|
| 288 |
+
|
| 289 |
+
def _decode_action_indices(self, action_indices, **kwargs):
|
| 290 |
+
self._require_fitted()
|
| 291 |
+
return super()._decode_action_indices(action_indices, **kwargs)
|
| 292 |
+
|
| 293 |
+
def grammar(self, horizon: int, *, fps: float | None = None):
|
| 294 |
+
self._require_fitted()
|
| 295 |
+
return super().grammar(horizon, fps=fps)
|
| 296 |
+
|
| 297 |
+
def final_first_order(
|
| 298 |
+
self, action_token_ids, *, previous_first_order, executed_steps=None
|
| 299 |
+
):
|
| 300 |
+
self._require_fitted()
|
| 301 |
+
return super().final_first_order(
|
| 302 |
+
action_token_ids,
|
| 303 |
+
previous_first_order=previous_first_order,
|
| 304 |
+
executed_steps=executed_steps,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
@classmethod
|
| 308 |
+
def describe_pretrained(
|
| 309 |
+
cls,
|
| 310 |
+
pretrained_model_name_or_path: str | Path,
|
| 311 |
+
*,
|
| 312 |
+
action_dim: int | None = None,
|
| 313 |
+
**kwargs: Any,
|
| 314 |
+
) -> str:
|
| 315 |
+
"Inspect a local/Hub artifact before choosing an action space."
|
| 316 |
+
directory = resolve_pretrained_directory(
|
| 317 |
+
pretrained_model_name_or_path,
|
| 318 |
+
allow_patterns=ROUTER_ALLOW_PATTERNS,
|
| 319 |
+
**kwargs,
|
| 320 |
+
)
|
| 321 |
+
router = ActionCodec2Router.from_pretrained(directory)
|
| 322 |
+
metadata = _saved_metadata(directory)
|
| 323 |
+
return _fitted_status(
|
| 324 |
+
metadata.get("fitted_profiles", router.profiles)
|
| 325 |
+
) + router.describe_action_spaces(
|
| 326 |
+
selected=metadata.get("action_space"), action_dim=action_dim
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
@classmethod
|
| 330 |
+
def from_pretrained(
|
| 331 |
+
cls,
|
| 332 |
+
pretrained_model_name_or_path: str | Path,
|
| 333 |
+
*,
|
| 334 |
+
action_space: str | Path | PresetConfig | Mapping[str, object] | None = None,
|
| 335 |
+
**kwargs: Any,
|
| 336 |
+
) -> ActionCodec2:
|
| 337 |
+
"Load a local/Hub artifact and restore or override its action space."
|
| 338 |
+
directory = resolve_pretrained_directory(
|
| 339 |
+
pretrained_model_name_or_path,
|
| 340 |
+
allow_patterns=ROUTER_ALLOW_PATTERNS,
|
| 341 |
+
**kwargs,
|
| 342 |
+
)
|
| 343 |
+
if action_space is None:
|
| 344 |
+
action_space = _saved_metadata(directory).get("action_space")
|
| 345 |
+
if action_space is None:
|
| 346 |
+
raise ValueError(
|
| 347 |
+
"action_space is required when loading an unbound router artifact. "
|
| 348 |
+
"First inspect its layouts with print(ActionCodec2.describe_pretrained(path)), "
|
| 349 |
+
"then pass action_space='NAME'; shape alone cannot identify physical meaning."
|
| 350 |
+
)
|
| 351 |
+
codec = super().from_pretrained(directory, action_space=action_space)
|
| 352 |
+
metadata = _saved_metadata(directory)
|
| 353 |
+
fitted = metadata.get("fitted_profiles", list(codec.action_tokenizer.profiles))
|
| 354 |
+
if not isinstance(fitted, list) or any(
|
| 355 |
+
not isinstance(name, str) or name not in codec.action_tokenizer.profiles
|
| 356 |
+
for name in fitted
|
| 357 |
+
):
|
| 358 |
+
raise ValueError("invalid fitted_profiles in processor_config.json")
|
| 359 |
+
codec._fitted_profiles = frozenset(fitted)
|
| 360 |
+
codec._source_directory = directory
|
| 361 |
+
report_path = directory / "fit_report.json"
|
| 362 |
+
if report_path.is_file():
|
| 363 |
+
codec.training_report = json.loads(report_path.read_text(encoding="utf-8"))
|
| 364 |
+
return codec
|
| 365 |
+
|
| 366 |
+
def to_dict(self, legacy_serialization: bool = True) -> dict[str, object]:
|
| 367 |
+
"Return lightweight discovery metadata."
|
| 368 |
+
del legacy_serialization
|
| 369 |
+
return {
|
| 370 |
+
"processor_class": type(self).__name__,
|
| 371 |
+
"model_type": self.model_type,
|
| 372 |
+
"action_space": self.action_space.name,
|
| 373 |
+
"fitted_profiles": sorted(self.fitted_profiles),
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
def save_pretrained(
|
| 377 |
+
self, save_directory: str | Path, push_to_hub: bool = False, **kwargs: Any
|
| 378 |
+
) -> list[str]:
|
| 379 |
+
"""Save the complete router and the selected action-space binding."""
|
| 380 |
+
if not push_to_hub and kwargs:
|
| 381 |
+
raise TypeError(
|
| 382 |
+
f"unused save_pretrained keyword(s): {', '.join(sorted(kwargs))}"
|
| 383 |
+
)
|
| 384 |
+
from .integration.exporting import ArtifactExporter
|
| 385 |
+
|
| 386 |
+
directory = Path(save_directory)
|
| 387 |
+
save_model_card(directory, str(self))
|
| 388 |
+
files = self.action_tokenizer.save_pretrained(directory, include_code=False)
|
| 389 |
+
files.extend(
|
| 390 |
+
ArtifactExporter(
|
| 391 |
+
directory, source_directory=self._source_directory
|
| 392 |
+
).export()
|
| 393 |
+
)
|
| 394 |
+
save_huggingface_metadata(
|
| 395 |
+
directory,
|
| 396 |
+
**self.to_dict(),
|
| 397 |
+
auto_map={"AutoProcessor": "processing_actioncodec2.ActionCodec2"},
|
| 398 |
+
)
|
| 399 |
+
report_path = directory / "fit_report.json"
|
| 400 |
+
if self.training_report is not None:
|
| 401 |
+
report_path.write_text(
|
| 402 |
+
json.dumps(self.training_report, indent=2) + "\n", encoding="utf-8"
|
| 403 |
+
)
|
| 404 |
+
files.append(str(report_path))
|
| 405 |
+
elif report_path.exists():
|
| 406 |
+
report_path.unlink()
|
| 407 |
+
if push_to_hub:
|
| 408 |
+
upload_pretrained_directory(directory, **kwargs)
|
| 409 |
+
return files
|
| 410 |
+
|
| 411 |
+
def __call__(
|
| 412 |
+
self,
|
| 413 |
+
action: torch.Tensor | np.ndarray,
|
| 414 |
+
*,
|
| 415 |
+
fps: float | None = None,
|
| 416 |
+
current_state: torch.Tensor | np.ndarray | None = None,
|
| 417 |
+
previous_first_order: Mapping[str, torch.Tensor | np.ndarray] | None = None,
|
| 418 |
+
encode_kwargs: dict | None = None,
|
| 419 |
+
return_tensors: str | None = None,
|
| 420 |
+
) -> BatchFeature:
|
| 421 |
+
|
| 422 |
+
rows = self.encode(
|
| 423 |
+
action,
|
| 424 |
+
fps=fps,
|
| 425 |
+
current_state=current_state,
|
| 426 |
+
previous_first_order=previous_first_order,
|
| 427 |
+
encode_kwargs=encode_kwargs,
|
| 428 |
+
)
|
| 429 |
+
return BatchFeature({"input_ids": rows}, tensor_type=return_tensors)
|
runtime/routing/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Internal routing components for ActionCodec2."""
|
runtime/routing/cache.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Small per-instance LRU caches; no global references to fitted codecs."""
|
| 2 |
+
|
| 3 |
+
from collections import OrderedDict
|
| 4 |
+
from collections.abc import MutableMapping
|
| 5 |
+
from threading import RLock
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class BoundedCache(MutableMapping):
|
| 9 |
+
"Evict least-recently-used entries above a fixed capacity."
|
| 10 |
+
|
| 11 |
+
def __init__(self, capacity):
|
| 12 |
+
if type(capacity) is not int or capacity < 1:
|
| 13 |
+
raise ValueError("cache capacity must be a positive integer")
|
| 14 |
+
self.capacity = capacity
|
| 15 |
+
self._items = OrderedDict()
|
| 16 |
+
self._lock = RLock()
|
| 17 |
+
|
| 18 |
+
def __getitem__(self, key):
|
| 19 |
+
with self._lock:
|
| 20 |
+
value = self._items[key]
|
| 21 |
+
self._items.move_to_end(key)
|
| 22 |
+
return value
|
| 23 |
+
|
| 24 |
+
def __setitem__(self, key, value):
|
| 25 |
+
with self._lock:
|
| 26 |
+
self._items[key] = value
|
| 27 |
+
self._items.move_to_end(key)
|
| 28 |
+
while len(self._items) > self.capacity:
|
| 29 |
+
self._items.popitem(last=False)
|
| 30 |
+
|
| 31 |
+
def __delitem__(self, key):
|
| 32 |
+
with self._lock:
|
| 33 |
+
del self._items[key]
|
| 34 |
+
|
| 35 |
+
def __iter__(self):
|
| 36 |
+
with self._lock:
|
| 37 |
+
return iter(tuple(self._items))
|
| 38 |
+
|
| 39 |
+
def __len__(self):
|
| 40 |
+
with self._lock:
|
| 41 |
+
return len(self._items)
|
runtime/routing/defaults.py
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Package-owned configuration templates for fresh ActionCodec2 instances."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from collections.abc import Mapping
|
| 6 |
+
|
| 7 |
+
from ..tokenization.bpe import BPESerializer, SetBPESerializer
|
| 8 |
+
from ..physical.configuration import ActionCodec2Config
|
| 9 |
+
from ..physical.physical_processor import ActionCodec2Processor
|
| 10 |
+
from .presets import PresetConfig
|
| 11 |
+
from .routing import ActionCodec2Router
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class CodecBuilder:
|
| 15 |
+
"Build unfitted physical profiles from packaged defaults or explicit configs."
|
| 16 |
+
|
| 17 |
+
def __init__(self, *, primitive_order=1, bpe_scheme=None, profile_configs=None):
|
| 18 |
+
if type(primitive_order) is not int or primitive_order not in (1, 2):
|
| 19 |
+
raise ValueError("primitive_order must be 1 or 2")
|
| 20 |
+
if bpe_scheme not in (None, "set_bpe", "bpe"):
|
| 21 |
+
raise ValueError("bpe_scheme must be 'set_bpe' or 'bpe'")
|
| 22 |
+
if profile_configs is not None and not isinstance(profile_configs, Mapping):
|
| 23 |
+
raise TypeError(
|
| 24 |
+
"profile_configs must map 'joint'/'eef' to physical configurations"
|
| 25 |
+
)
|
| 26 |
+
self.profile_configs = dict(profile_configs or {})
|
| 27 |
+
if set(self.profile_configs) - {"joint", "eef"}:
|
| 28 |
+
raise ValueError("profile_configs keys must be 'joint' or 'eef'")
|
| 29 |
+
self.primitive_order = primitive_order
|
| 30 |
+
self.bpe_scheme = bpe_scheme
|
| 31 |
+
|
| 32 |
+
def _config(self, name: str) -> ActionCodec2Config:
|
| 33 |
+
suffix = "" if self.primitive_order == 1 else "_second_order_pretrain_v2"
|
| 34 |
+
raw = self.profile_configs.get(name, _PROFILE_DEFAULTS[f"{name}{suffix}"])
|
| 35 |
+
if isinstance(raw, ActionCodec2Config):
|
| 36 |
+
config = raw
|
| 37 |
+
elif isinstance(raw, Mapping):
|
| 38 |
+
config = ActionCodec2Config.from_dict(raw)
|
| 39 |
+
else:
|
| 40 |
+
config = ActionCodec2Config.from_yaml(raw)
|
| 41 |
+
if config.train is None:
|
| 42 |
+
raise ValueError(f"{name} profile config requires a train section")
|
| 43 |
+
if self.bpe_scheme is not None:
|
| 44 |
+
payload = config.to_dict()
|
| 45 |
+
payload["set_bpe"]["scheme"] = self.bpe_scheme
|
| 46 |
+
if self.bpe_scheme == "bpe":
|
| 47 |
+
payload["train"]["bpe"]["spatial_vocab_size"] = payload["train"]["bpe"][
|
| 48 |
+
"vocab_size"
|
| 49 |
+
]
|
| 50 |
+
config = ActionCodec2Config.from_dict(payload)
|
| 51 |
+
return config
|
| 52 |
+
|
| 53 |
+
def build(self) -> ActionCodec2Router:
|
| 54 |
+
"""Create primitive-only profiles; the public facade tracks fitted state."""
|
| 55 |
+
processors = {}
|
| 56 |
+
budgets = {}
|
| 57 |
+
for name in ("joint", "eef"):
|
| 58 |
+
config = self._config(name)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
from ..training.training import build_primitives
|
| 62 |
+
|
| 63 |
+
primitives = build_primitives(config)
|
| 64 |
+
if config.set_bpe.scheme == "bpe":
|
| 65 |
+
serializer = BPESerializer(
|
| 66 |
+
bins=primitives.bins, lookahead=config.set_bpe.lookahead, merges=()
|
| 67 |
+
)
|
| 68 |
+
else:
|
| 69 |
+
serializer = SetBPESerializer(
|
| 70 |
+
bins=primitives.bins,
|
| 71 |
+
lookahead=config.set_bpe.lookahead,
|
| 72 |
+
spatial_merges=(),
|
| 73 |
+
temporal_merges=(),
|
| 74 |
+
)
|
| 75 |
+
processors[name] = ActionCodec2Processor(config, primitives, serializer)
|
| 76 |
+
budgets[name] = config.train.bpe.vocab_size
|
| 77 |
+
presets = list(builtin_action_spaces().values())
|
| 78 |
+
return ActionCodec2Router.from_processors(
|
| 79 |
+
processors, token_budgets=budgets, presets=presets
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def builtin_action_spaces():
|
| 84 |
+
"""Return a read-only name-to-preset mapping shipped in the installed package."""
|
| 85 |
+
from types import MappingProxyType
|
| 86 |
+
|
| 87 |
+
presets = [PresetConfig.from_dict(value) for value in _PRESET_DEFAULTS.values()]
|
| 88 |
+
return MappingProxyType({preset.name: preset for preset in presets})
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class ProfileBundleBuilder:
|
| 92 |
+
"Assemble two existing physical artifacts using their recorded vocabulary budgets."
|
| 93 |
+
|
| 94 |
+
def __init__(self, joint, eef, *, token_budgets=None):
|
| 95 |
+
self.paths = {"joint": joint, "eef": eef}
|
| 96 |
+
self.budgets = dict(token_budgets or {})
|
| 97 |
+
if self.budgets.keys() - self.paths.keys():
|
| 98 |
+
raise ValueError("token_budgets accepts only 'joint' and 'eef'")
|
| 99 |
+
|
| 100 |
+
def build(self):
|
| 101 |
+
processors = {
|
| 102 |
+
name: ActionCodec2Processor.from_pretrained(path)
|
| 103 |
+
for name, path in self.paths.items()
|
| 104 |
+
}
|
| 105 |
+
budgets = {
|
| 106 |
+
name: self.budgets.get(
|
| 107 |
+
name,
|
| 108 |
+
processor.config.train.bpe.vocab_size
|
| 109 |
+
if processor.config.train is not None
|
| 110 |
+
else processor.vocab_size,
|
| 111 |
+
)
|
| 112 |
+
for name, processor in processors.items()
|
| 113 |
+
}
|
| 114 |
+
return ActionCodec2Router.from_processors(
|
| 115 |
+
processors,
|
| 116 |
+
token_budgets=budgets,
|
| 117 |
+
presets=list(builtin_action_spaces().values()),
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
_PROFILE_DEFAULTS = {
|
| 122 |
+
"eef": {
|
| 123 |
+
"model_type": "actioncodec2_unified",
|
| 124 |
+
"config_version": 1,
|
| 125 |
+
"physical": {
|
| 126 |
+
"input_dim": 7,
|
| 127 |
+
"components": {
|
| 128 |
+
"translation_m": {
|
| 129 |
+
"dims": {"start": 0, "stop": 3},
|
| 130 |
+
"space": "additive",
|
| 131 |
+
"input_order": 0,
|
| 132 |
+
"primitive_order": 1,
|
| 133 |
+
"input_transform": {"scale": 1.0, "clip": False},
|
| 134 |
+
"constraints": {
|
| 135 |
+
"order_0": {"min": -2.0, "max": 2.0},
|
| 136 |
+
"order_1": {"min": -0.08, "max": 0.08},
|
| 137 |
+
},
|
| 138 |
+
"distortion_limit": 0.005,
|
| 139 |
+
"primitive_selection": "smooth",
|
| 140 |
+
},
|
| 141 |
+
"orientation_so3": {
|
| 142 |
+
"dims": {"start": 3, "stop": 6},
|
| 143 |
+
"space": "so3",
|
| 144 |
+
"input_order": 0,
|
| 145 |
+
"primitive_order": 1,
|
| 146 |
+
"input_transform": {"scale": 1.0, "clip": False},
|
| 147 |
+
"constraints": {
|
| 148 |
+
"order_0": {
|
| 149 |
+
"min": -3.141592653589793,
|
| 150 |
+
"max": 3.141592653589793,
|
| 151 |
+
},
|
| 152 |
+
"order_1": {"min": -0.25, "max": 0.25},
|
| 153 |
+
},
|
| 154 |
+
"distortion_limit": 0.02,
|
| 155 |
+
"primitive_selection": "smooth",
|
| 156 |
+
"representation": "rotation_vector",
|
| 157 |
+
"multiplication": "right",
|
| 158 |
+
},
|
| 159 |
+
"gripper_open_command": {
|
| 160 |
+
"dims": [6],
|
| 161 |
+
"space": "additive",
|
| 162 |
+
"input_order": 0,
|
| 163 |
+
"primitive_order": 0,
|
| 164 |
+
"input_transform": {"scale": 1.0, "clip": True},
|
| 165 |
+
"constraints": {"order_0": {"min": 0.0, "max": 1.0}},
|
| 166 |
+
"distortion_limit": 0.5,
|
| 167 |
+
"primitive_selection": "nearest",
|
| 168 |
+
},
|
| 169 |
+
},
|
| 170 |
+
},
|
| 171 |
+
"set_bpe": {"lookahead": 8, "allow_cross_component_merge": False},
|
| 172 |
+
"train": {
|
| 173 |
+
"primitives": {
|
| 174 |
+
"zero_primitive_components": ["translation_m", "orientation_so3"]
|
| 175 |
+
},
|
| 176 |
+
"bpe": {"spatial_vocab_size": 2048, "vocab_size": 4096},
|
| 177 |
+
"quality_control": {"hard_order_1_scale": 3.0, "min_segment_length": 2},
|
| 178 |
+
},
|
| 179 |
+
},
|
| 180 |
+
"eef_second_order_pretrain_v2": {
|
| 181 |
+
"model_type": "actioncodec2_unified",
|
| 182 |
+
"config_version": 1,
|
| 183 |
+
"physical": {
|
| 184 |
+
"input_dim": 7,
|
| 185 |
+
"components": {
|
| 186 |
+
"translation_m": {
|
| 187 |
+
"dims": {"start": 0, "stop": 3},
|
| 188 |
+
"space": "additive",
|
| 189 |
+
"input_order": 0,
|
| 190 |
+
"primitive_order": 2,
|
| 191 |
+
"input_transform": {"scale": 1.0, "clip": False},
|
| 192 |
+
"constraints": {
|
| 193 |
+
"order_0": {"min": -2.0, "max": 2.0},
|
| 194 |
+
"order_2": {"min": -0.025, "max": 0.025},
|
| 195 |
+
"order_1": {"min": -0.08, "max": 0.08},
|
| 196 |
+
},
|
| 197 |
+
"distortion_limit": 0.005,
|
| 198 |
+
"primitive_selection": "smooth",
|
| 199 |
+
},
|
| 200 |
+
"orientation_so3": {
|
| 201 |
+
"dims": {"start": 3, "stop": 6},
|
| 202 |
+
"space": "so3",
|
| 203 |
+
"input_order": 0,
|
| 204 |
+
"primitive_order": 2,
|
| 205 |
+
"input_transform": {"scale": 1.0, "clip": False},
|
| 206 |
+
"constraints": {
|
| 207 |
+
"order_0": {
|
| 208 |
+
"min": -3.141592653589793,
|
| 209 |
+
"max": 3.141592653589793,
|
| 210 |
+
},
|
| 211 |
+
"order_2": {"min": -0.08, "max": 0.08},
|
| 212 |
+
"order_1": {"min": -0.25, "max": 0.25},
|
| 213 |
+
},
|
| 214 |
+
"distortion_limit": 0.02,
|
| 215 |
+
"primitive_selection": "smooth",
|
| 216 |
+
"representation": "rotation_vector",
|
| 217 |
+
"multiplication": "right",
|
| 218 |
+
},
|
| 219 |
+
"gripper_open_command": {
|
| 220 |
+
"dims": [6],
|
| 221 |
+
"space": "additive",
|
| 222 |
+
"input_order": 0,
|
| 223 |
+
"primitive_order": 0,
|
| 224 |
+
"input_transform": {"scale": 1.0, "clip": True},
|
| 225 |
+
"constraints": {"order_0": {"min": 0.0, "max": 1.0}},
|
| 226 |
+
"distortion_limit": 0.5,
|
| 227 |
+
"primitive_selection": "nearest",
|
| 228 |
+
},
|
| 229 |
+
},
|
| 230 |
+
},
|
| 231 |
+
"set_bpe": {"lookahead": 8, "allow_cross_component_merge": False},
|
| 232 |
+
"train": {
|
| 233 |
+
"primitives": {
|
| 234 |
+
"zero_primitive_components": ["translation_m", "orientation_so3"]
|
| 235 |
+
},
|
| 236 |
+
"bpe": {"spatial_vocab_size": 2048, "vocab_size": 4096},
|
| 237 |
+
"quality_control": {"hard_order_1_scale": 3.0, "min_segment_length": 2},
|
| 238 |
+
},
|
| 239 |
+
},
|
| 240 |
+
"joint": {
|
| 241 |
+
"model_type": "actioncodec2_unified",
|
| 242 |
+
"config_version": 1,
|
| 243 |
+
"physical": {
|
| 244 |
+
"input_dim": 8,
|
| 245 |
+
"components": {
|
| 246 |
+
"generalized_joint_position": {
|
| 247 |
+
"dims": {"start": 0, "stop": 7},
|
| 248 |
+
"space": "additive",
|
| 249 |
+
"input_order": 0,
|
| 250 |
+
"primitive_order": 1,
|
| 251 |
+
"input_transform": {"scale": 1.0, "clip": False},
|
| 252 |
+
"constraints": {
|
| 253 |
+
"order_0": {
|
| 254 |
+
"min": -6.283185307179586,
|
| 255 |
+
"max": 6.283185307179586,
|
| 256 |
+
},
|
| 257 |
+
"order_1": {"min": -0.35, "max": 0.35},
|
| 258 |
+
},
|
| 259 |
+
"distortion_limit": [
|
| 260 |
+
0.004,
|
| 261 |
+
0.004,
|
| 262 |
+
0.006,
|
| 263 |
+
0.0075,
|
| 264 |
+
0.015,
|
| 265 |
+
0.02,
|
| 266 |
+
0.02,
|
| 267 |
+
],
|
| 268 |
+
"primitive_selection": "smooth",
|
| 269 |
+
},
|
| 270 |
+
"gripper_open_command": {
|
| 271 |
+
"dims": [7],
|
| 272 |
+
"space": "additive",
|
| 273 |
+
"input_order": 0,
|
| 274 |
+
"primitive_order": 0,
|
| 275 |
+
"input_transform": {"scale": 1.0, "clip": True},
|
| 276 |
+
"constraints": {"order_0": {"min": 0.0, "max": 1.0}},
|
| 277 |
+
"distortion_limit": 0.5,
|
| 278 |
+
"primitive_selection": "nearest",
|
| 279 |
+
},
|
| 280 |
+
},
|
| 281 |
+
},
|
| 282 |
+
"set_bpe": {"lookahead": 8, "allow_cross_component_merge": False},
|
| 283 |
+
"train": {
|
| 284 |
+
"primitives": {"zero_primitive_components": ["generalized_joint_position"]},
|
| 285 |
+
"bpe": {"spatial_vocab_size": 2048, "vocab_size": 4096},
|
| 286 |
+
"quality_control": {"hard_order_1_scale": 3.0, "min_segment_length": 2},
|
| 287 |
+
},
|
| 288 |
+
},
|
| 289 |
+
"joint_second_order_pretrain_v2": {
|
| 290 |
+
"model_type": "actioncodec2_unified",
|
| 291 |
+
"config_version": 1,
|
| 292 |
+
"physical": {
|
| 293 |
+
"input_dim": 8,
|
| 294 |
+
"components": {
|
| 295 |
+
"generalized_joint_position": {
|
| 296 |
+
"dims": {"start": 0, "stop": 7},
|
| 297 |
+
"space": "additive",
|
| 298 |
+
"input_order": 0,
|
| 299 |
+
"primitive_order": 2,
|
| 300 |
+
"input_transform": {"scale": 1.0, "clip": False},
|
| 301 |
+
"constraints": {
|
| 302 |
+
"order_0": {
|
| 303 |
+
"min": -6.283185307179586,
|
| 304 |
+
"max": 6.283185307179586,
|
| 305 |
+
},
|
| 306 |
+
"order_2": {"min": -0.25, "max": 0.25},
|
| 307 |
+
"order_1": {"min": -0.35, "max": 0.35},
|
| 308 |
+
},
|
| 309 |
+
"distortion_limit": [
|
| 310 |
+
0.004,
|
| 311 |
+
0.004,
|
| 312 |
+
0.006,
|
| 313 |
+
0.0075,
|
| 314 |
+
0.015,
|
| 315 |
+
0.02,
|
| 316 |
+
0.02,
|
| 317 |
+
],
|
| 318 |
+
"primitive_selection": "smooth",
|
| 319 |
+
},
|
| 320 |
+
"gripper_open_command": {
|
| 321 |
+
"dims": [7],
|
| 322 |
+
"space": "additive",
|
| 323 |
+
"input_order": 0,
|
| 324 |
+
"primitive_order": 0,
|
| 325 |
+
"input_transform": {"scale": 1.0, "clip": True},
|
| 326 |
+
"constraints": {"order_0": {"min": 0.0, "max": 1.0}},
|
| 327 |
+
"distortion_limit": 0.5,
|
| 328 |
+
"primitive_selection": "nearest",
|
| 329 |
+
},
|
| 330 |
+
},
|
| 331 |
+
},
|
| 332 |
+
"set_bpe": {"lookahead": 8, "allow_cross_component_merge": False},
|
| 333 |
+
"train": {
|
| 334 |
+
"primitives": {"zero_primitive_components": ["generalized_joint_position"]},
|
| 335 |
+
"bpe": {"spatial_vocab_size": 2048, "vocab_size": 4096},
|
| 336 |
+
"quality_control": {"hard_order_1_scale": 3.0, "min_segment_length": 2},
|
| 337 |
+
},
|
| 338 |
+
},
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
_PRESET_DEFAULTS = {
|
| 342 |
+
"dual_eef_abs": {
|
| 343 |
+
"name": "dual_eef_abs",
|
| 344 |
+
"input_dim": 14,
|
| 345 |
+
"input": "absolute",
|
| 346 |
+
"source_hz": 15.0,
|
| 347 |
+
"target_hz": 15.0,
|
| 348 |
+
"interpolation": "cubic",
|
| 349 |
+
"components": {
|
| 350 |
+
"left_arm": {
|
| 351 |
+
"space": "eef",
|
| 352 |
+
"position_dims": [0, 1, 2],
|
| 353 |
+
"rotation_dims": [3, 4, 5],
|
| 354 |
+
"rotation_format": "rotation_vector",
|
| 355 |
+
"gripper_dim": 6,
|
| 356 |
+
"gripper_threshold": 0.8,
|
| 357 |
+
"gripper_open_is_high": True,
|
| 358 |
+
},
|
| 359 |
+
"right_arm": {
|
| 360 |
+
"space": "eef",
|
| 361 |
+
"position_dims": [7, 8, 9],
|
| 362 |
+
"rotation_dims": [10, 11, 12],
|
| 363 |
+
"rotation_format": "rotation_vector",
|
| 364 |
+
"gripper_dim": 13,
|
| 365 |
+
"gripper_threshold": 0.8,
|
| 366 |
+
"gripper_open_is_high": True,
|
| 367 |
+
},
|
| 368 |
+
},
|
| 369 |
+
},
|
| 370 |
+
"dual_eef_delta": {
|
| 371 |
+
"name": "dual_eef_delta",
|
| 372 |
+
"input_dim": 14,
|
| 373 |
+
"input": "delta",
|
| 374 |
+
"source_hz": 15.0,
|
| 375 |
+
"target_hz": 15.0,
|
| 376 |
+
"interpolation": "cubic",
|
| 377 |
+
"components": {
|
| 378 |
+
"left_arm": {
|
| 379 |
+
"space": "eef",
|
| 380 |
+
"position_dims": [0, 1, 2],
|
| 381 |
+
"rotation_dims": [3, 4, 5],
|
| 382 |
+
"rotation_format": "rotation_vector",
|
| 383 |
+
"gripper_dim": 6,
|
| 384 |
+
"gripper_threshold": 0.8,
|
| 385 |
+
"gripper_open_is_high": True,
|
| 386 |
+
},
|
| 387 |
+
"right_arm": {
|
| 388 |
+
"space": "eef",
|
| 389 |
+
"position_dims": [7, 8, 9],
|
| 390 |
+
"rotation_dims": [10, 11, 12],
|
| 391 |
+
"rotation_format": "rotation_vector",
|
| 392 |
+
"gripper_dim": 13,
|
| 393 |
+
"gripper_threshold": 0.8,
|
| 394 |
+
"gripper_open_is_high": True,
|
| 395 |
+
},
|
| 396 |
+
},
|
| 397 |
+
},
|
| 398 |
+
"dual_joint6_abs": {
|
| 399 |
+
"name": "dual_joint6_abs",
|
| 400 |
+
"input_dim": 14,
|
| 401 |
+
"input": "absolute",
|
| 402 |
+
"source_hz": 15.0,
|
| 403 |
+
"target_hz": 15.0,
|
| 404 |
+
"interpolation": "cubic",
|
| 405 |
+
"components": {
|
| 406 |
+
"left_arm": {
|
| 407 |
+
"space": "joint",
|
| 408 |
+
"joint_dims": [0, 1, 2, 3, 4, 5],
|
| 409 |
+
"gripper_dim": 6,
|
| 410 |
+
"gripper_threshold": 0.8,
|
| 411 |
+
"gripper_open_is_high": True,
|
| 412 |
+
},
|
| 413 |
+
"right_arm": {
|
| 414 |
+
"space": "joint",
|
| 415 |
+
"joint_dims": [7, 8, 9, 10, 11, 12],
|
| 416 |
+
"gripper_dim": 13,
|
| 417 |
+
"gripper_threshold": 0.8,
|
| 418 |
+
"gripper_open_is_high": True,
|
| 419 |
+
},
|
| 420 |
+
},
|
| 421 |
+
},
|
| 422 |
+
"dual_joint6_delta": {
|
| 423 |
+
"name": "dual_joint6_delta",
|
| 424 |
+
"input_dim": 14,
|
| 425 |
+
"input": "delta",
|
| 426 |
+
"source_hz": 15.0,
|
| 427 |
+
"target_hz": 15.0,
|
| 428 |
+
"interpolation": "cubic",
|
| 429 |
+
"components": {
|
| 430 |
+
"left_arm": {
|
| 431 |
+
"space": "joint",
|
| 432 |
+
"joint_dims": [0, 1, 2, 3, 4, 5],
|
| 433 |
+
"gripper_dim": 6,
|
| 434 |
+
"gripper_threshold": 0.8,
|
| 435 |
+
"gripper_open_is_high": True,
|
| 436 |
+
},
|
| 437 |
+
"right_arm": {
|
| 438 |
+
"space": "joint",
|
| 439 |
+
"joint_dims": [7, 8, 9, 10, 11, 12],
|
| 440 |
+
"gripper_dim": 13,
|
| 441 |
+
"gripper_threshold": 0.8,
|
| 442 |
+
"gripper_open_is_high": True,
|
| 443 |
+
},
|
| 444 |
+
},
|
| 445 |
+
},
|
| 446 |
+
"dual_joint7_abs": {
|
| 447 |
+
"name": "dual_joint7_abs",
|
| 448 |
+
"input_dim": 16,
|
| 449 |
+
"input": "absolute",
|
| 450 |
+
"source_hz": 15.0,
|
| 451 |
+
"target_hz": 15.0,
|
| 452 |
+
"interpolation": "cubic",
|
| 453 |
+
"components": {
|
| 454 |
+
"left_arm": {
|
| 455 |
+
"space": "joint",
|
| 456 |
+
"joint_dims": [0, 1, 2, 3, 4, 5, 6],
|
| 457 |
+
"gripper_dim": 7,
|
| 458 |
+
"gripper_threshold": 0.8,
|
| 459 |
+
"gripper_open_is_high": True,
|
| 460 |
+
},
|
| 461 |
+
"right_arm": {
|
| 462 |
+
"space": "joint",
|
| 463 |
+
"joint_dims": [8, 9, 10, 11, 12, 13, 14],
|
| 464 |
+
"gripper_dim": 15,
|
| 465 |
+
"gripper_threshold": 0.8,
|
| 466 |
+
"gripper_open_is_high": True,
|
| 467 |
+
},
|
| 468 |
+
},
|
| 469 |
+
},
|
| 470 |
+
"dual_joint7_delta": {
|
| 471 |
+
"name": "dual_joint7_delta",
|
| 472 |
+
"input_dim": 16,
|
| 473 |
+
"input": "delta",
|
| 474 |
+
"source_hz": 15.0,
|
| 475 |
+
"target_hz": 15.0,
|
| 476 |
+
"interpolation": "cubic",
|
| 477 |
+
"components": {
|
| 478 |
+
"left_arm": {
|
| 479 |
+
"space": "joint",
|
| 480 |
+
"joint_dims": [0, 1, 2, 3, 4, 5, 6],
|
| 481 |
+
"gripper_dim": 7,
|
| 482 |
+
"gripper_threshold": 0.8,
|
| 483 |
+
"gripper_open_is_high": True,
|
| 484 |
+
},
|
| 485 |
+
"right_arm": {
|
| 486 |
+
"space": "joint",
|
| 487 |
+
"joint_dims": [8, 9, 10, 11, 12, 13, 14],
|
| 488 |
+
"gripper_dim": 15,
|
| 489 |
+
"gripper_threshold": 0.8,
|
| 490 |
+
"gripper_open_is_high": True,
|
| 491 |
+
},
|
| 492 |
+
},
|
| 493 |
+
},
|
| 494 |
+
"single_eef_abs": {
|
| 495 |
+
"name": "single_eef_abs",
|
| 496 |
+
"input_dim": 7,
|
| 497 |
+
"input": "absolute",
|
| 498 |
+
"source_hz": 15.0,
|
| 499 |
+
"target_hz": 15.0,
|
| 500 |
+
"interpolation": "cubic",
|
| 501 |
+
"components": {
|
| 502 |
+
"arm": {
|
| 503 |
+
"space": "eef",
|
| 504 |
+
"position_dims": [0, 1, 2],
|
| 505 |
+
"rotation_dims": [3, 4, 5],
|
| 506 |
+
"rotation_format": "rotation_vector",
|
| 507 |
+
"gripper_dim": 6,
|
| 508 |
+
"gripper_threshold": 0.8,
|
| 509 |
+
"gripper_open_is_high": True,
|
| 510 |
+
}
|
| 511 |
+
},
|
| 512 |
+
},
|
| 513 |
+
"single_eef_delta": {
|
| 514 |
+
"name": "single_eef_delta",
|
| 515 |
+
"input_dim": 7,
|
| 516 |
+
"input": "delta",
|
| 517 |
+
"source_hz": 15.0,
|
| 518 |
+
"target_hz": 15.0,
|
| 519 |
+
"interpolation": "cubic",
|
| 520 |
+
"components": {
|
| 521 |
+
"arm": {
|
| 522 |
+
"space": "eef",
|
| 523 |
+
"position_dims": [0, 1, 2],
|
| 524 |
+
"rotation_dims": [3, 4, 5],
|
| 525 |
+
"rotation_format": "rotation_vector",
|
| 526 |
+
"gripper_dim": 6,
|
| 527 |
+
"gripper_threshold": 0.8,
|
| 528 |
+
"gripper_open_is_high": True,
|
| 529 |
+
}
|
| 530 |
+
},
|
| 531 |
+
},
|
| 532 |
+
"single_joint6_abs": {
|
| 533 |
+
"name": "single_joint6_abs",
|
| 534 |
+
"input_dim": 7,
|
| 535 |
+
"input": "absolute",
|
| 536 |
+
"source_hz": 15.0,
|
| 537 |
+
"target_hz": 15.0,
|
| 538 |
+
"interpolation": "cubic",
|
| 539 |
+
"components": {
|
| 540 |
+
"arm": {
|
| 541 |
+
"space": "joint",
|
| 542 |
+
"joint_dims": [0, 1, 2, 3, 4, 5],
|
| 543 |
+
"gripper_dim": 6,
|
| 544 |
+
"gripper_threshold": 0.8,
|
| 545 |
+
"gripper_open_is_high": True,
|
| 546 |
+
}
|
| 547 |
+
},
|
| 548 |
+
},
|
| 549 |
+
"single_joint6_abs_continuous_gripper": {
|
| 550 |
+
"name": "single_joint6_abs_continuous_gripper",
|
| 551 |
+
"input_dim": 6,
|
| 552 |
+
"input": "absolute",
|
| 553 |
+
"source_hz": 15.0,
|
| 554 |
+
"target_hz": 15.0,
|
| 555 |
+
"interpolation": "cubic",
|
| 556 |
+
"components": {"arm": {"space": "joint", "joint_dims": [0, 1, 2, 3, 4, 5]}},
|
| 557 |
+
},
|
| 558 |
+
"single_joint6_delta": {
|
| 559 |
+
"name": "single_joint6_delta",
|
| 560 |
+
"input_dim": 7,
|
| 561 |
+
"input": "delta",
|
| 562 |
+
"source_hz": 15.0,
|
| 563 |
+
"target_hz": 15.0,
|
| 564 |
+
"interpolation": "cubic",
|
| 565 |
+
"components": {
|
| 566 |
+
"arm": {
|
| 567 |
+
"space": "joint",
|
| 568 |
+
"joint_dims": [0, 1, 2, 3, 4, 5],
|
| 569 |
+
"gripper_dim": 6,
|
| 570 |
+
"gripper_threshold": 0.8,
|
| 571 |
+
"gripper_open_is_high": True,
|
| 572 |
+
}
|
| 573 |
+
},
|
| 574 |
+
},
|
| 575 |
+
"single_joint7_abs": {
|
| 576 |
+
"name": "single_joint7_abs",
|
| 577 |
+
"input_dim": 8,
|
| 578 |
+
"input": "absolute",
|
| 579 |
+
"source_hz": 15.0,
|
| 580 |
+
"target_hz": 15.0,
|
| 581 |
+
"interpolation": "cubic",
|
| 582 |
+
"components": {
|
| 583 |
+
"arm": {
|
| 584 |
+
"space": "joint",
|
| 585 |
+
"joint_dims": [0, 1, 2, 3, 4, 5, 6],
|
| 586 |
+
"gripper_dim": 7,
|
| 587 |
+
"gripper_threshold": 0.8,
|
| 588 |
+
"gripper_open_is_high": True,
|
| 589 |
+
}
|
| 590 |
+
},
|
| 591 |
+
},
|
| 592 |
+
"single_joint7_delta": {
|
| 593 |
+
"name": "single_joint7_delta",
|
| 594 |
+
"input_dim": 8,
|
| 595 |
+
"input": "delta",
|
| 596 |
+
"source_hz": 15.0,
|
| 597 |
+
"target_hz": 15.0,
|
| 598 |
+
"interpolation": "cubic",
|
| 599 |
+
"components": {
|
| 600 |
+
"arm": {
|
| 601 |
+
"space": "joint",
|
| 602 |
+
"joint_dims": [0, 1, 2, 3, 4, 5, 6],
|
| 603 |
+
"gripper_dim": 7,
|
| 604 |
+
"gripper_threshold": 0.8,
|
| 605 |
+
"gripper_open_is_high": True,
|
| 606 |
+
}
|
| 607 |
+
},
|
| 608 |
+
},
|
| 609 |
+
}
|
runtime/routing/presets.py
ADDED
|
@@ -0,0 +1,802 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""User-facing action layouts compiled into shared joint/EEF trajectories."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import re
|
| 6 |
+
from collections.abc import Mapping, Sequence
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import yaml
|
| 12 |
+
|
| 13 |
+
from ..common._validation import optional_finite_array, validate_dimension_partition
|
| 14 |
+
from ..physical.configuration import (
|
| 15 |
+
ConfigError,
|
| 16 |
+
_integer,
|
| 17 |
+
_mapping,
|
| 18 |
+
_number,
|
| 19 |
+
_reject_unknown,
|
| 20 |
+
_required,
|
| 21 |
+
_UniqueKeyLoader,
|
| 22 |
+
)
|
| 23 |
+
from ..physical.semantic import (
|
| 24 |
+
binarize_gripper_actions,
|
| 25 |
+
eef_pose_absolute,
|
| 26 |
+
eef_pose_components,
|
| 27 |
+
eef_pose_deltas,
|
| 28 |
+
eef_pose_from_components,
|
| 29 |
+
eef_pose_from_local_absolute,
|
| 30 |
+
eef_pose_local_absolute,
|
| 31 |
+
integrate_eef_deltas,
|
| 32 |
+
resample_uniform,
|
| 33 |
+
)
|
| 34 |
+
from ..physical.trajectory import ActiveTrajectory
|
| 35 |
+
|
| 36 |
+
_NAME = re.compile(r"[a-z][a-z0-9_]*\Z")
|
| 37 |
+
_SPACES = {"eef", "joint"}
|
| 38 |
+
_INPUTS = {"absolute", "delta"}
|
| 39 |
+
_ROTATIONS = {"rotation_vector", "euler_xyz", "quaternion_xyzw"}
|
| 40 |
+
_INTERPOLATIONS = {"linear", "pchip", "cubic"}
|
| 41 |
+
_GRIPPER_DIM = {"eef": 6, "joint": 7}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _format_dims(dims: Sequence[int]) -> str:
|
| 45 |
+
values = tuple(dims)
|
| 46 |
+
if len(values) == 1:
|
| 47 |
+
return str(values[0])
|
| 48 |
+
if values == tuple(range(values[0], values[-1] + 1)):
|
| 49 |
+
return f"{values[0]}..{values[-1]}"
|
| 50 |
+
return ",".join(map(str, values))
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _dims(value: object, path: str) -> tuple[int, ...]:
|
| 54 |
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes, bytearray)):
|
| 55 |
+
raise ConfigError(f"{path} must be a list of dimensions")
|
| 56 |
+
output = tuple(
|
| 57 |
+
_integer(item, f"{path}[{index}]") for index, item in enumerate(value)
|
| 58 |
+
)
|
| 59 |
+
if not output:
|
| 60 |
+
raise ConfigError(f"{path} must not be empty")
|
| 61 |
+
if len(set(output)) != len(output):
|
| 62 |
+
raise ConfigError(f"{path} contains a repeated dimension")
|
| 63 |
+
return output
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@dataclass(frozen=True)
|
| 67 |
+
class ComponentSpec:
|
| 68 |
+
"One independently encoded arm/body component in an input tensor."
|
| 69 |
+
|
| 70 |
+
name: str
|
| 71 |
+
space: str
|
| 72 |
+
joint_dims: tuple[int, ...] | None = None
|
| 73 |
+
position_dims: tuple[int, ...] | None = None
|
| 74 |
+
rotation_dims: tuple[int, ...] | None = None
|
| 75 |
+
rotation_format: str = "rotation_vector"
|
| 76 |
+
gripper_dim: int | None = None
|
| 77 |
+
gripper_threshold: float | None = None
|
| 78 |
+
gripper_open_is_high: bool = True
|
| 79 |
+
|
| 80 |
+
def __post_init__(self) -> None:
|
| 81 |
+
if not isinstance(self.name, str) or _NAME.fullmatch(self.name) is None:
|
| 82 |
+
raise ConfigError(f"component name must match {_NAME.pattern!r}")
|
| 83 |
+
if self.space not in _SPACES:
|
| 84 |
+
raise ConfigError(f"component {self.name!r}.space must be joint or eef")
|
| 85 |
+
if self.rotation_format not in _ROTATIONS:
|
| 86 |
+
raise ConfigError(
|
| 87 |
+
f"component {self.name!r}.rotation_format must be one of {sorted(_ROTATIONS)}"
|
| 88 |
+
)
|
| 89 |
+
for label, dims in (
|
| 90 |
+
("joint_dims", self.joint_dims),
|
| 91 |
+
("position_dims", self.position_dims),
|
| 92 |
+
("rotation_dims", self.rotation_dims),
|
| 93 |
+
):
|
| 94 |
+
if dims is not None:
|
| 95 |
+
_dims(dims, f"component {self.name}.{label}")
|
| 96 |
+
self._validate_motion_dims()
|
| 97 |
+
raw_dims = self.source_dims
|
| 98 |
+
if self.gripper_dim is not None:
|
| 99 |
+
_integer(self.gripper_dim, f"component {self.name}.gripper_dim")
|
| 100 |
+
if self.gripper_dim in raw_dims:
|
| 101 |
+
raise ConfigError(
|
| 102 |
+
f"component {self.name!r}.gripper_dim overlaps motion dims"
|
| 103 |
+
)
|
| 104 |
+
if not isinstance(self.gripper_open_is_high, bool):
|
| 105 |
+
raise ConfigError(
|
| 106 |
+
f"component {self.name!r}.gripper_open_is_high must be boolean"
|
| 107 |
+
)
|
| 108 |
+
threshold = self.gripper_threshold
|
| 109 |
+
if threshold is None:
|
| 110 |
+
threshold = 0.8 if self.gripper_open_is_high else 0.2
|
| 111 |
+
object.__setattr__(self, "gripper_threshold", threshold)
|
| 112 |
+
_number(threshold, f"component {self.name}.gripper_threshold")
|
| 113 |
+
|
| 114 |
+
def _validate_motion_dims(self) -> None:
|
| 115 |
+
if self.space == "joint":
|
| 116 |
+
if self.joint_dims is None or not 1 <= len(self.joint_dims) <= 7:
|
| 117 |
+
raise ConfigError(
|
| 118 |
+
f"joint component {self.name!r} requires 1..7 joint_dims"
|
| 119 |
+
)
|
| 120 |
+
if self.position_dims is not None or self.rotation_dims is not None:
|
| 121 |
+
raise ConfigError(
|
| 122 |
+
f"joint component {self.name!r} cannot define EEF dims"
|
| 123 |
+
)
|
| 124 |
+
return
|
| 125 |
+
if self.joint_dims is not None:
|
| 126 |
+
raise ConfigError(f"EEF component {self.name!r} cannot define joint_dims")
|
| 127 |
+
if self.position_dims is None or len(self.position_dims) != 3:
|
| 128 |
+
raise ConfigError(
|
| 129 |
+
f"EEF component {self.name!r} requires three position_dims"
|
| 130 |
+
)
|
| 131 |
+
expected = 4 if self.rotation_format == "quaternion_xyzw" else 3
|
| 132 |
+
if self.rotation_dims is None or len(self.rotation_dims) != expected:
|
| 133 |
+
raise ConfigError(
|
| 134 |
+
f"EEF component {self.name!r} requires {expected} rotation_dims for "
|
| 135 |
+
f"{self.rotation_format}"
|
| 136 |
+
)
|
| 137 |
+
if set(self.position_dims).intersection(self.rotation_dims):
|
| 138 |
+
raise ConfigError(f"EEF component {self.name!r} reuses a pose dimension")
|
| 139 |
+
|
| 140 |
+
@property
|
| 141 |
+
def source_dims(self) -> tuple[int, ...]:
|
| 142 |
+
"""All source motion columns in canonical component order."""
|
| 143 |
+
if self.space == "joint":
|
| 144 |
+
assert self.joint_dims is not None
|
| 145 |
+
return self.joint_dims
|
| 146 |
+
assert self.position_dims is not None and self.rotation_dims is not None
|
| 147 |
+
return (*self.position_dims, *self.rotation_dims)
|
| 148 |
+
|
| 149 |
+
@property
|
| 150 |
+
def all_source_dims(self) -> tuple[int, ...]:
|
| 151 |
+
"""Motion plus optional binary-gripper source columns."""
|
| 152 |
+
return (
|
| 153 |
+
self.source_dims
|
| 154 |
+
if self.gripper_dim is None
|
| 155 |
+
else (*self.source_dims, self.gripper_dim)
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
@property
|
| 159 |
+
def active_dims(self) -> tuple[int, ...]:
|
| 160 |
+
"""Shared-profile slots emitted by this component."""
|
| 161 |
+
motion_width = len(self.joint_dims or ()) if self.space == "joint" else 6
|
| 162 |
+
dims = tuple(range(motion_width))
|
| 163 |
+
return dims if self.gripper_dim is None else (*dims, _GRIPPER_DIM[self.space])
|
| 164 |
+
|
| 165 |
+
@classmethod
|
| 166 |
+
def from_dict(cls, name: str, payload: object, *, path: str) -> ComponentSpec:
|
| 167 |
+
values = _mapping(payload, path)
|
| 168 |
+
allowed = {
|
| 169 |
+
"space",
|
| 170 |
+
"joint_dims",
|
| 171 |
+
"position_dims",
|
| 172 |
+
"rotation_dims",
|
| 173 |
+
"rotation_format",
|
| 174 |
+
"gripper_dim",
|
| 175 |
+
"gripper_threshold",
|
| 176 |
+
"gripper_open_is_high",
|
| 177 |
+
}
|
| 178 |
+
_reject_unknown(values, allowed, path)
|
| 179 |
+
gripper_dim = values.get("gripper_dim")
|
| 180 |
+
if gripper_dim is not None:
|
| 181 |
+
gripper_dim = _integer(gripper_dim, f"{path}.gripper_dim")
|
| 182 |
+
return cls(
|
| 183 |
+
name=name,
|
| 184 |
+
space=str(_required(values, "space", path)),
|
| 185 |
+
joint_dims=None
|
| 186 |
+
if "joint_dims" not in values
|
| 187 |
+
else _dims(values["joint_dims"], f"{path}.joint_dims"),
|
| 188 |
+
position_dims=None
|
| 189 |
+
if "position_dims" not in values
|
| 190 |
+
else _dims(values["position_dims"], f"{path}.position_dims"),
|
| 191 |
+
rotation_dims=None
|
| 192 |
+
if "rotation_dims" not in values
|
| 193 |
+
else _dims(values["rotation_dims"], f"{path}.rotation_dims"),
|
| 194 |
+
rotation_format=str(values.get("rotation_format", "rotation_vector")),
|
| 195 |
+
gripper_dim=gripper_dim,
|
| 196 |
+
gripper_threshold=(
|
| 197 |
+
None
|
| 198 |
+
if values.get("gripper_threshold") is None
|
| 199 |
+
else _number(values["gripper_threshold"], f"{path}.gripper_threshold")
|
| 200 |
+
),
|
| 201 |
+
gripper_open_is_high=values.get("gripper_open_is_high", True),
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
def to_dict(self) -> dict[str, object]:
|
| 205 |
+
payload: dict[str, object] = {"space": self.space}
|
| 206 |
+
if self.space == "joint":
|
| 207 |
+
payload["joint_dims"] = list(self.joint_dims or ())
|
| 208 |
+
else:
|
| 209 |
+
payload.update(
|
| 210 |
+
{
|
| 211 |
+
"position_dims": list(self.position_dims or ()),
|
| 212 |
+
"rotation_dims": list(self.rotation_dims or ()),
|
| 213 |
+
"rotation_format": self.rotation_format,
|
| 214 |
+
}
|
| 215 |
+
)
|
| 216 |
+
if self.gripper_dim is not None:
|
| 217 |
+
payload.update(
|
| 218 |
+
{
|
| 219 |
+
"gripper_dim": self.gripper_dim,
|
| 220 |
+
"gripper_threshold": self.gripper_threshold,
|
| 221 |
+
"gripper_open_is_high": self.gripper_open_is_high,
|
| 222 |
+
}
|
| 223 |
+
)
|
| 224 |
+
return payload
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@dataclass(frozen=True)
|
| 228 |
+
class PresetConfig:
|
| 229 |
+
"A dataset- and embodiment-independent physical ``[B,T,D]`` contract."
|
| 230 |
+
|
| 231 |
+
name: str
|
| 232 |
+
input_dim: int
|
| 233 |
+
input: str
|
| 234 |
+
components: tuple[ComponentSpec, ...]
|
| 235 |
+
source_hz: float = 15.0
|
| 236 |
+
target_hz: float = 15.0
|
| 237 |
+
interpolation: str = "cubic"
|
| 238 |
+
|
| 239 |
+
def __post_init__(self) -> None:
|
| 240 |
+
if not isinstance(self.name, str) or _NAME.fullmatch(self.name) is None:
|
| 241 |
+
raise ConfigError(f"preset.name must match {_NAME.pattern!r}")
|
| 242 |
+
_integer(self.input_dim, "preset.input_dim", positive=True)
|
| 243 |
+
if self.input not in _INPUTS:
|
| 244 |
+
raise ConfigError("preset.input must be absolute or delta")
|
| 245 |
+
if not self.components:
|
| 246 |
+
raise ConfigError("preset.components must not be empty")
|
| 247 |
+
if not all(
|
| 248 |
+
isinstance(component, ComponentSpec) for component in self.components
|
| 249 |
+
):
|
| 250 |
+
raise ConfigError("preset.components must contain ComponentSpec values")
|
| 251 |
+
names = [component.name for component in self.components]
|
| 252 |
+
if len(set(names)) != len(names):
|
| 253 |
+
raise ConfigError("preset.components contains a duplicate name")
|
| 254 |
+
self._validate_source_partition()
|
| 255 |
+
for rate, path in (
|
| 256 |
+
(self.source_hz, "source_hz"),
|
| 257 |
+
(self.target_hz, "target_hz"),
|
| 258 |
+
):
|
| 259 |
+
checked = _number(rate, f"preset.{path}")
|
| 260 |
+
if checked <= 0.0:
|
| 261 |
+
raise ConfigError(f"preset.{path} must be positive and finite")
|
| 262 |
+
if self.interpolation not in _INTERPOLATIONS:
|
| 263 |
+
raise ConfigError(
|
| 264 |
+
f"preset.interpolation must be one of {sorted(_INTERPOLATIONS)}"
|
| 265 |
+
)
|
| 266 |
+
if self.input == "delta" and any(
|
| 267 |
+
component.space == "eef" and component.rotation_format != "rotation_vector"
|
| 268 |
+
for component in self.components
|
| 269 |
+
):
|
| 270 |
+
raise ConfigError(
|
| 271 |
+
"EEF delta components require rotation_format=rotation_vector"
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
def _validate_source_partition(self) -> None:
|
| 275 |
+
validate_dimension_partition(
|
| 276 |
+
(
|
| 277 |
+
(component.name, component.all_source_dims)
|
| 278 |
+
for component in self.components
|
| 279 |
+
),
|
| 280 |
+
self.input_dim,
|
| 281 |
+
label="input dim",
|
| 282 |
+
error_type=ConfigError,
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
@classmethod
|
| 286 |
+
def from_dict(cls, payload: object, *, path: str = "preset") -> PresetConfig:
|
| 287 |
+
values = _mapping(payload, path)
|
| 288 |
+
allowed = {
|
| 289 |
+
"name",
|
| 290 |
+
"input_dim",
|
| 291 |
+
"input",
|
| 292 |
+
"source_hz",
|
| 293 |
+
"target_hz",
|
| 294 |
+
"interpolation",
|
| 295 |
+
"components",
|
| 296 |
+
}
|
| 297 |
+
_reject_unknown(values, allowed, path)
|
| 298 |
+
raw_components = _mapping(
|
| 299 |
+
_required(values, "components", path), f"{path}.components"
|
| 300 |
+
)
|
| 301 |
+
components = tuple(
|
| 302 |
+
ComponentSpec.from_dict(name, raw, path=f"{path}.components.{name}")
|
| 303 |
+
for name, raw in raw_components.items()
|
| 304 |
+
)
|
| 305 |
+
return cls(
|
| 306 |
+
name=str(_required(values, "name", path)),
|
| 307 |
+
input_dim=_integer(
|
| 308 |
+
_required(values, "input_dim", path), f"{path}.input_dim", positive=True
|
| 309 |
+
),
|
| 310 |
+
input=str(_required(values, "input", path)),
|
| 311 |
+
components=components,
|
| 312 |
+
source_hz=_number(values.get("source_hz", 15.0), f"{path}.source_hz"),
|
| 313 |
+
target_hz=_number(values.get("target_hz", 15.0), f"{path}.target_hz"),
|
| 314 |
+
interpolation=str(values.get("interpolation", "cubic")),
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
@classmethod
|
| 318 |
+
def from_yaml(cls, path: str | Path) -> PresetConfig:
|
| 319 |
+
"""Load one strict user preset without executing arbitrary code."""
|
| 320 |
+
source = Path(path)
|
| 321 |
+
try:
|
| 322 |
+
payload = yaml.load(
|
| 323 |
+
source.read_text(encoding="utf-8"), Loader=_UniqueKeyLoader
|
| 324 |
+
)
|
| 325 |
+
except yaml.YAMLError as error:
|
| 326 |
+
raise ConfigError(f"cannot parse {source}: {error}") from error
|
| 327 |
+
return cls.from_dict(payload, path=str(source))
|
| 328 |
+
|
| 329 |
+
def save_yaml(self, path: str | Path) -> Path:
|
| 330 |
+
"""Save the complete reproducible preset and return its path."""
|
| 331 |
+
target = Path(path)
|
| 332 |
+
target.parent.mkdir(parents=True, exist_ok=True)
|
| 333 |
+
target.write_text(
|
| 334 |
+
yaml.safe_dump(self.to_dict(), sort_keys=False), encoding="utf-8"
|
| 335 |
+
)
|
| 336 |
+
return target
|
| 337 |
+
|
| 338 |
+
def to_dict(self) -> dict[str, object]:
|
| 339 |
+
payload = {
|
| 340 |
+
"name": self.name,
|
| 341 |
+
"input_dim": self.input_dim,
|
| 342 |
+
"input": self.input,
|
| 343 |
+
"source_hz": self.source_hz,
|
| 344 |
+
"target_hz": self.target_hz,
|
| 345 |
+
"interpolation": self.interpolation,
|
| 346 |
+
"components": {
|
| 347 |
+
component.name: component.to_dict() for component in self.components
|
| 348 |
+
},
|
| 349 |
+
}
|
| 350 |
+
return payload
|
| 351 |
+
|
| 352 |
+
def summary(self) -> str:
|
| 353 |
+
"""Return a compact action-space label for discovery UIs."""
|
| 354 |
+
spaces = "+".join(
|
| 355 |
+
dict.fromkeys(component.space for component in self.components)
|
| 356 |
+
)
|
| 357 |
+
return f"{self.name} (D={self.input_dim}, {self.input}, {spaces}, {self.source_hz:g} Hz input)"
|
| 358 |
+
|
| 359 |
+
def describe(self) -> str:
|
| 360 |
+
"""Describe every source tensor column and its physical interpretation."""
|
| 361 |
+
motion = "position" if self.input == "absolute" else "per-step delta"
|
| 362 |
+
lines = [self.summary()]
|
| 363 |
+
for component in self.components:
|
| 364 |
+
if component.space == "joint":
|
| 365 |
+
assert component.joint_dims is not None
|
| 366 |
+
lines.append(
|
| 367 |
+
f" dims {_format_dims(component.joint_dims)}: {component.name} "
|
| 368 |
+
f"generalized-joint {motion} [rad or registered canonical unit]"
|
| 369 |
+
)
|
| 370 |
+
else:
|
| 371 |
+
assert component.position_dims is not None
|
| 372 |
+
assert component.rotation_dims is not None
|
| 373 |
+
lines.append(
|
| 374 |
+
f" dims {_format_dims(component.position_dims)}: {component.name} "
|
| 375 |
+
f"EEF translation {motion} (x, y, z in listed order) [m]"
|
| 376 |
+
)
|
| 377 |
+
rotation_motion = (
|
| 378 |
+
"orientation" if self.input == "absolute" else "increment"
|
| 379 |
+
)
|
| 380 |
+
rotation_unit = (
|
| 381 |
+
"unit xyzw quaternion"
|
| 382 |
+
if component.rotation_format == "quaternion_xyzw"
|
| 383 |
+
else "rad"
|
| 384 |
+
)
|
| 385 |
+
lines.append(
|
| 386 |
+
f" dims {_format_dims(component.rotation_dims)}: {component.name} EEF "
|
| 387 |
+
f"{rotation_motion} [{component.rotation_format}, {rotation_unit}]"
|
| 388 |
+
)
|
| 389 |
+
if self.input == "delta":
|
| 390 |
+
lines.append(
|
| 391 |
+
" rotation_vector = axis * angle [rad], right-multiplicative "
|
| 392 |
+
"body-frame increment: R_next = R_previous @ Exp(rotvec)"
|
| 393 |
+
)
|
| 394 |
+
elif component.rotation_format == "rotation_vector":
|
| 395 |
+
lines.append(
|
| 396 |
+
" rotation_vector = axis * angle [rad], not Euler angles"
|
| 397 |
+
)
|
| 398 |
+
elif component.rotation_format == "euler_xyz":
|
| 399 |
+
lines.append(" euler_xyz = extrinsic x, y, z Euler angles [rad]")
|
| 400 |
+
if component.gripper_dim is not None:
|
| 401 |
+
comparison = ">=" if component.gripper_open_is_high else "<="
|
| 402 |
+
lines.append(
|
| 403 |
+
f" dim {component.gripper_dim}: {component.name} gripper command "
|
| 404 |
+
f"(open if value {comparison} {component.gripper_threshold:g}, otherwise closed; "
|
| 405 |
+
"decoded as absolute command: 1=open, 0=closed)"
|
| 406 |
+
)
|
| 407 |
+
else:
|
| 408 |
+
lines.append(
|
| 409 |
+
f" {component.name}: no binary gripper channel; all listed columns are motion"
|
| 410 |
+
)
|
| 411 |
+
if self.input == "absolute":
|
| 412 |
+
lines.append(
|
| 413 |
+
" encode/decode require the same current_state (B,D), immediately before row 0"
|
| 414 |
+
)
|
| 415 |
+
else:
|
| 416 |
+
lines.append(
|
| 417 |
+
" encode/decode: omit current_state; deltas are per-step increments, not velocities"
|
| 418 |
+
)
|
| 419 |
+
lines.append(
|
| 420 |
+
f" resampling: {self.source_hz:g} Hz default -> {self.target_hz:g} Hz codec "
|
| 421 |
+
f"clock ({self.interpolation})"
|
| 422 |
+
)
|
| 423 |
+
return "\n".join(lines)
|
| 424 |
+
|
| 425 |
+
def canonicalize(
|
| 426 |
+
self,
|
| 427 |
+
values: np.ndarray,
|
| 428 |
+
*,
|
| 429 |
+
source_hz: float | None = None,
|
| 430 |
+
current_state: np.ndarray | None = None,
|
| 431 |
+
) -> dict[str, list[ActiveTrajectory]]:
|
| 432 |
+
"Convert a batch into local paths for the two shared profiles."
|
| 433 |
+
array = np.asarray(values, dtype=np.float32)
|
| 434 |
+
if array.ndim != 3 or len(array) < 1 or array.shape[1] < 1:
|
| 435 |
+
raise ValueError("preset input must have non-empty shape [B, T, D]")
|
| 436 |
+
if array.shape[2] != self.input_dim:
|
| 437 |
+
raise ValueError(
|
| 438 |
+
f"preset {self.name!r} expects input_dim={self.input_dim}, got {array.shape[2]}"
|
| 439 |
+
)
|
| 440 |
+
if not np.all(np.isfinite(array)):
|
| 441 |
+
raise ValueError("preset input contains a non-finite value")
|
| 442 |
+
states: list[np.ndarray | None]
|
| 443 |
+
if current_state is None:
|
| 444 |
+
states = [None] * len(array)
|
| 445 |
+
else:
|
| 446 |
+
state_array = optional_finite_array(
|
| 447 |
+
current_state, shape=(len(array), self.input_dim)
|
| 448 |
+
)
|
| 449 |
+
assert state_array is not None
|
| 450 |
+
states = list(state_array)
|
| 451 |
+
return {
|
| 452 |
+
component.name: [
|
| 453 |
+
self._canonicalize_component(
|
| 454 |
+
episode,
|
| 455 |
+
component,
|
| 456 |
+
source_hz=self.source_hz if source_hz is None else source_hz,
|
| 457 |
+
current_state=state,
|
| 458 |
+
)
|
| 459 |
+
for episode, state in zip(array, states, strict=True)
|
| 460 |
+
]
|
| 461 |
+
for component in self.components
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
def canonicalize_episode(
|
| 465 |
+
self,
|
| 466 |
+
values: np.ndarray,
|
| 467 |
+
*,
|
| 468 |
+
source_hz: float | None = None,
|
| 469 |
+
current_state: np.ndarray | None = None,
|
| 470 |
+
) -> dict[str, ActiveTrajectory]:
|
| 471 |
+
"""Canonicalize one variable-length episode and optional ``(D,)`` anchor."""
|
| 472 |
+
episode = np.asarray(values, dtype=np.float32)
|
| 473 |
+
if episode.ndim != 2 or len(episode) < 1 or episode.shape[1] != self.input_dim:
|
| 474 |
+
raise ValueError(
|
| 475 |
+
f"preset {self.name!r} expects a non-empty [T, {self.input_dim}] episode"
|
| 476 |
+
)
|
| 477 |
+
if not np.all(np.isfinite(episode)):
|
| 478 |
+
raise ValueError("preset input contains a non-finite value")
|
| 479 |
+
state = optional_finite_array(current_state, shape=(self.input_dim,))
|
| 480 |
+
return {
|
| 481 |
+
component.name: self._canonicalize_component(
|
| 482 |
+
episode,
|
| 483 |
+
component,
|
| 484 |
+
source_hz=self.source_hz if source_hz is None else source_hz,
|
| 485 |
+
current_state=state,
|
| 486 |
+
)
|
| 487 |
+
for component in self.components
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
def profile_trajectories(
|
| 491 |
+
self, values: np.ndarray, *, current_state: np.ndarray | None = None
|
| 492 |
+
) -> dict[str, list[ActiveTrajectory]]:
|
| 493 |
+
"""Canonicalize one episode and group its components by joint/EEF profile."""
|
| 494 |
+
canonical = self.canonicalize_episode(values, current_state=current_state)
|
| 495 |
+
output: dict[str, list[ActiveTrajectory]] = {}
|
| 496 |
+
for component in self.components:
|
| 497 |
+
output.setdefault(component.space, []).append(canonical[component.name])
|
| 498 |
+
return output
|
| 499 |
+
|
| 500 |
+
def _canonicalize_component(
|
| 501 |
+
self,
|
| 502 |
+
episode: np.ndarray,
|
| 503 |
+
component: ComponentSpec,
|
| 504 |
+
*,
|
| 505 |
+
source_hz: float,
|
| 506 |
+
current_state: np.ndarray | None,
|
| 507 |
+
) -> ActiveTrajectory:
|
| 508 |
+
if component.space == "joint":
|
| 509 |
+
motion = self._joint_motion(
|
| 510 |
+
episode, component, source_hz=source_hz, current_state=current_state
|
| 511 |
+
)
|
| 512 |
+
else:
|
| 513 |
+
motion = self._eef_motion(
|
| 514 |
+
episode, component, source_hz=source_hz, current_state=current_state
|
| 515 |
+
)
|
| 516 |
+
if component.gripper_dim is None:
|
| 517 |
+
return ActiveTrajectory(motion, component.active_dims)
|
| 518 |
+
gripper = resample_uniform(
|
| 519 |
+
episode[:, component.gripper_dim : component.gripper_dim + 1],
|
| 520 |
+
source_hz=source_hz,
|
| 521 |
+
target_hz=self.target_hz,
|
| 522 |
+
kind="hold",
|
| 523 |
+
method=self.interpolation,
|
| 524 |
+
)
|
| 525 |
+
binary = binarize_gripper_actions(
|
| 526 |
+
gripper,
|
| 527 |
+
threshold=component.gripper_threshold,
|
| 528 |
+
open_is_high=component.gripper_open_is_high,
|
| 529 |
+
)
|
| 530 |
+
if len(binary) != len(motion):
|
| 531 |
+
raise RuntimeError("motion and gripper produced different target horizons")
|
| 532 |
+
return ActiveTrajectory(
|
| 533 |
+
np.concatenate((motion, binary), axis=1), component.active_dims
|
| 534 |
+
)
|
| 535 |
+
|
| 536 |
+
def _joint_motion(
|
| 537 |
+
self,
|
| 538 |
+
episode: np.ndarray,
|
| 539 |
+
component: ComponentSpec,
|
| 540 |
+
*,
|
| 541 |
+
source_hz: float,
|
| 542 |
+
current_state: np.ndarray | None,
|
| 543 |
+
) -> np.ndarray:
|
| 544 |
+
assert component.joint_dims is not None
|
| 545 |
+
source = np.take(episode, component.joint_dims, axis=1)
|
| 546 |
+
if self.input == "delta":
|
| 547 |
+
source = np.cumsum(source, axis=0, dtype=np.float32)
|
| 548 |
+
anchor = np.zeros((1, source.shape[1]), dtype=np.float32)
|
| 549 |
+
else:
|
| 550 |
+
raw_anchor = (
|
| 551 |
+
source[:1]
|
| 552 |
+
if current_state is None
|
| 553 |
+
else np.take(current_state, component.joint_dims)[None]
|
| 554 |
+
)
|
| 555 |
+
unwrapped = np.unwrap(np.concatenate((raw_anchor, source), axis=0), axis=0)
|
| 556 |
+
anchor = np.asarray(unwrapped[:1], dtype=np.float32)
|
| 557 |
+
source = np.asarray(unwrapped[1:], dtype=np.float32)
|
| 558 |
+
sampled = resample_uniform(
|
| 559 |
+
source,
|
| 560 |
+
source_hz=source_hz,
|
| 561 |
+
target_hz=self.target_hz,
|
| 562 |
+
kind="linear",
|
| 563 |
+
method=self.interpolation,
|
| 564 |
+
)
|
| 565 |
+
return np.asarray(sampled - anchor, dtype=np.float32)
|
| 566 |
+
|
| 567 |
+
def _eef_motion(
|
| 568 |
+
self,
|
| 569 |
+
episode: np.ndarray,
|
| 570 |
+
component: ComponentSpec,
|
| 571 |
+
*,
|
| 572 |
+
source_hz: float,
|
| 573 |
+
current_state: np.ndarray | None,
|
| 574 |
+
) -> np.ndarray:
|
| 575 |
+
assert (
|
| 576 |
+
component.position_dims is not None and component.rotation_dims is not None
|
| 577 |
+
)
|
| 578 |
+
position = np.take(episode, component.position_dims, axis=1)
|
| 579 |
+
rotation = np.take(episode, component.rotation_dims, axis=1)
|
| 580 |
+
if self.input == "delta":
|
| 581 |
+
pose = integrate_eef_deltas(np.concatenate((position, rotation), axis=1))
|
| 582 |
+
pose = resample_uniform(
|
| 583 |
+
pose,
|
| 584 |
+
source_hz=source_hz,
|
| 585 |
+
target_hz=self.target_hz,
|
| 586 |
+
kind="pose_xyzw",
|
| 587 |
+
method=self.interpolation,
|
| 588 |
+
)
|
| 589 |
+
return eef_pose_absolute(pose)
|
| 590 |
+
pose = eef_pose_from_components(position, rotation, component.rotation_format)
|
| 591 |
+
if current_state is None:
|
| 592 |
+
initial_pose = pose[0]
|
| 593 |
+
else:
|
| 594 |
+
initial_pose = eef_pose_from_components(
|
| 595 |
+
np.take(current_state, component.position_dims)[None],
|
| 596 |
+
np.take(current_state, component.rotation_dims)[None],
|
| 597 |
+
component.rotation_format,
|
| 598 |
+
)[0]
|
| 599 |
+
pose = resample_uniform(
|
| 600 |
+
pose,
|
| 601 |
+
source_hz=source_hz,
|
| 602 |
+
target_hz=self.target_hz,
|
| 603 |
+
kind="pose_xyzw",
|
| 604 |
+
method=self.interpolation,
|
| 605 |
+
)
|
| 606 |
+
return eef_pose_local_absolute(pose, initial_pose=initial_pose)
|
| 607 |
+
|
| 608 |
+
|
| 609 |
+
def format_action_spaces(
|
| 610 |
+
presets: Mapping[str, PresetConfig],
|
| 611 |
+
*,
|
| 612 |
+
selected: str | None = None,
|
| 613 |
+
action_dim: int | None = None,
|
| 614 |
+
second_order_dims: Mapping[str, Sequence[int]] | None = None,
|
| 615 |
+
) -> str:
|
| 616 |
+
"Explain registered tensor layouts without inferring semantics from shape."
|
| 617 |
+
if action_dim is not None:
|
| 618 |
+
action_dim = _integer(action_dim, "action_dim", positive=True)
|
| 619 |
+
if not presets:
|
| 620 |
+
return "No action spaces are registered."
|
| 621 |
+
candidates = sorted(
|
| 622 |
+
(
|
| 623 |
+
preset
|
| 624 |
+
for preset in presets.values()
|
| 625 |
+
if action_dim is None or preset.input_dim == action_dim
|
| 626 |
+
),
|
| 627 |
+
key=lambda preset: (preset.input_dim, preset.name),
|
| 628 |
+
)
|
| 629 |
+
lines = [
|
| 630 |
+
"ActionCodec2 — action layout guide",
|
| 631 |
+
f"Selected action_space: {selected or 'none (choose a layout before encoding)'}",
|
| 632 |
+
"",
|
| 633 |
+
"Input: NumPy arrays or Torch tensors, (T,D) or (B,T,D).",
|
| 634 |
+
" T = timesteps, D = columns per action, B = batch size; column indices start at 0.",
|
| 635 |
+
" Any positive T is accepted. A batch needs equal T; encode unequal-length episodes separately.",
|
| 636 |
+
" decode returns (B,T,D), including B=1 for a single episode; values are quantized, not lossless.",
|
| 637 |
+
"",
|
| 638 |
+
"How to choose:",
|
| 639 |
+
" joint = robot joint coordinates; EEF = end-effector (tool) position and orientation.",
|
| 640 |
+
" absolute = target coordinates; delta = per-step motion increments, not velocities.",
|
| 641 |
+
" Match column order, units, rotation convention, gripper commands and controller fps.",
|
| 642 |
+
" Shape alone cannot identify physical meaning: layouts sharing D are only candidates.",
|
| 643 |
+
" Consult your dataset/controller documentation if these meanings are unknown.",
|
| 644 |
+
" Supply physical units, not normalized [-1,1] features; convert degrees to radians.",
|
| 645 |
+
" Non-radian joint coordinates need an explicit mapping to the registered canonical units.",
|
| 646 |
+
" Pass the actual sampling rate as fps=...; defaults and codec rates appear below.",
|
| 647 |
+
" Resampling can change decoded T; token coverage determines the returned length.",
|
| 648 |
+
"",
|
| 649 |
+
f"Registered configurations: {len(candidates)} shown / {len(presets)} total"
|
| 650 |
+
+ (
|
| 651 |
+
""
|
| 652 |
+
if action_dim is None
|
| 653 |
+
else f" (D={action_dim} candidates, not automatic selection)"
|
| 654 |
+
),
|
| 655 |
+
]
|
| 656 |
+
if not candidates:
|
| 657 |
+
widths = ", ".join(
|
| 658 |
+
map(str, sorted({preset.input_dim for preset in presets.values()}))
|
| 659 |
+
)
|
| 660 |
+
lines.extend(
|
| 661 |
+
[
|
| 662 |
+
f" No registered action space matches D={action_dim}. Available D: {widths}.",
|
| 663 |
+
" Confirm your column meanings and register a custom layout; do not pad/truncate to force a match.",
|
| 664 |
+
]
|
| 665 |
+
)
|
| 666 |
+
for preset in candidates:
|
| 667 |
+
name = preset.name
|
| 668 |
+
marker = " [selected]" if name == selected else ""
|
| 669 |
+
description = preset.describe().splitlines()
|
| 670 |
+
lines.extend(["", f"- {description[0]}{marker}"])
|
| 671 |
+
lines.extend(f" {line.strip()}" for line in description[1:])
|
| 672 |
+
if second_order_dims is not None:
|
| 673 |
+
boundaries = [
|
| 674 |
+
f"{component.name!r}: (B,{len(component.active_dims)})"
|
| 675 |
+
for component in preset.components
|
| 676 |
+
if set(component.active_dims).intersection(
|
| 677 |
+
second_order_dims.get(component.space, ())
|
| 678 |
+
)
|
| 679 |
+
]
|
| 680 |
+
lines.append(
|
| 681 |
+
" previous_first_order: "
|
| 682 |
+
+ (
|
| 683 |
+
"required in encode/decode, {" + ", ".join(boundaries) + "}; "
|
| 684 |
+
f"reconstructed increments at {preset.target_hz:g} Hz, B=1 for a single episode"
|
| 685 |
+
if boundaries
|
| 686 |
+
else "not required by this artifact for this layout"
|
| 687 |
+
)
|
| 688 |
+
)
|
| 689 |
+
lines.extend(
|
| 690 |
+
[
|
| 691 |
+
"",
|
| 692 |
+
"Next steps:",
|
| 693 |
+
" Load: ActionCodec2.from_pretrained(path, action_space='NAME')",
|
| 694 |
+
" Switch: codec = codec.for_action_space('NAME')",
|
| 695 |
+
" Filter: codec.print_action_spaces(action_dim=D)",
|
| 696 |
+
" Config: codec.action_spaces['NAME'].to_dict() # exact settings for a listed layout",
|
| 697 |
+
" Custom: codec.register_action_space(preset) # explicit column definitions, then switch",
|
| 698 |
+
" Custom layouts can reorder columns; each joint component supports 1..7 coordinates.",
|
| 699 |
+
" EEF absolute rotations support rotation_vector, euler_xyz or quaternion_xyzw (unit x,y,z,w).",
|
| 700 |
+
" EEF delta rotations require rotation_vector with the convention shown above.",
|
| 701 |
+
" For absolute layouts, current_state is (B,D), even when actions are (T,D).",
|
| 702 |
+
]
|
| 703 |
+
)
|
| 704 |
+
return "\n".join(lines)
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
def resample_canonical_trajectory(
|
| 708 |
+
trajectory: ActiveTrajectory,
|
| 709 |
+
component: ComponentSpec,
|
| 710 |
+
*,
|
| 711 |
+
source_hz: float,
|
| 712 |
+
target_hz: float,
|
| 713 |
+
target_length: int | None = None,
|
| 714 |
+
method: str = "linear",
|
| 715 |
+
) -> ActiveTrajectory:
|
| 716 |
+
"""Rate-convert one decoded canonical component without changing slots."""
|
| 717 |
+
values = np.asarray(trajectory.values, dtype=np.float32)
|
| 718 |
+
motion_width = len(component.joint_dims or ()) if component.space == "joint" else 6
|
| 719 |
+
motion = values[:, :motion_width]
|
| 720 |
+
if component.space == "eef":
|
| 721 |
+
pose = eef_pose_from_components(
|
| 722 |
+
motion[:, :3], motion[:, 3:6], "rotation_vector"
|
| 723 |
+
)
|
| 724 |
+
sampled_pose = resample_uniform(
|
| 725 |
+
pose,
|
| 726 |
+
source_hz=source_hz,
|
| 727 |
+
target_hz=target_hz,
|
| 728 |
+
kind="pose_xyzw",
|
| 729 |
+
target_length=target_length,
|
| 730 |
+
method=method,
|
| 731 |
+
)
|
| 732 |
+
sampled_motion = eef_pose_absolute(sampled_pose)
|
| 733 |
+
else:
|
| 734 |
+
sampled_motion = resample_uniform(
|
| 735 |
+
motion,
|
| 736 |
+
source_hz=source_hz,
|
| 737 |
+
target_hz=target_hz,
|
| 738 |
+
kind="linear",
|
| 739 |
+
target_length=target_length,
|
| 740 |
+
method=method,
|
| 741 |
+
)
|
| 742 |
+
if component.gripper_dim is None:
|
| 743 |
+
return ActiveTrajectory(sampled_motion, trajectory.active_dims)
|
| 744 |
+
gripper = resample_uniform(
|
| 745 |
+
values[:, motion_width : motion_width + 1],
|
| 746 |
+
source_hz=source_hz,
|
| 747 |
+
target_hz=target_hz,
|
| 748 |
+
kind="hold",
|
| 749 |
+
target_length=target_length,
|
| 750 |
+
method=method,
|
| 751 |
+
)
|
| 752 |
+
return ActiveTrajectory(
|
| 753 |
+
np.concatenate((sampled_motion, gripper), axis=1), trajectory.active_dims
|
| 754 |
+
)
|
| 755 |
+
|
| 756 |
+
|
| 757 |
+
def externalize_canonical_trajectory(
|
| 758 |
+
trajectory: ActiveTrajectory,
|
| 759 |
+
component: ComponentSpec,
|
| 760 |
+
*,
|
| 761 |
+
current_state: np.ndarray | None,
|
| 762 |
+
) -> np.ndarray:
|
| 763 |
+
"Return deltas, or absolute values when an external anchor is supplied."
|
| 764 |
+
values = np.asarray(trajectory.values, dtype=np.float32)
|
| 765 |
+
motion_width = len(component.joint_dims or ()) if component.space == "joint" else 6
|
| 766 |
+
motion = values[:, :motion_width]
|
| 767 |
+
if component.space == "joint":
|
| 768 |
+
if current_state is None:
|
| 769 |
+
previous = np.concatenate((np.zeros_like(motion[:1]), motion[:-1]), axis=0)
|
| 770 |
+
output_motion = motion - previous
|
| 771 |
+
else:
|
| 772 |
+
assert component.joint_dims is not None
|
| 773 |
+
output_motion = motion + np.take(current_state, component.joint_dims)[None]
|
| 774 |
+
else:
|
| 775 |
+
local_pose = eef_pose_from_components(
|
| 776 |
+
motion[:, :3], motion[:, 3:6], "rotation_vector"
|
| 777 |
+
)
|
| 778 |
+
if current_state is None:
|
| 779 |
+
output_motion = eef_pose_deltas(local_pose)
|
| 780 |
+
else:
|
| 781 |
+
assert (
|
| 782 |
+
component.position_dims is not None
|
| 783 |
+
and component.rotation_dims is not None
|
| 784 |
+
)
|
| 785 |
+
initial_pose = eef_pose_from_components(
|
| 786 |
+
np.take(current_state, component.position_dims)[None],
|
| 787 |
+
np.take(current_state, component.rotation_dims)[None],
|
| 788 |
+
component.rotation_format,
|
| 789 |
+
)[0]
|
| 790 |
+
output_motion = eef_pose_components(
|
| 791 |
+
eef_pose_from_local_absolute(local_pose, initial_pose=initial_pose),
|
| 792 |
+
component.rotation_format,
|
| 793 |
+
)
|
| 794 |
+
if component.gripper_dim is None:
|
| 795 |
+
return np.asarray(output_motion, dtype=np.float32)
|
| 796 |
+
gripper = values[:, motion_width : motion_width + 1]
|
| 797 |
+
if not component.gripper_open_is_high:
|
| 798 |
+
gripper = 1.0 - gripper
|
| 799 |
+
return np.asarray(
|
| 800 |
+
np.concatenate((output_motion, gripper), axis=1),
|
| 801 |
+
dtype=np.float32,
|
| 802 |
+
)
|
runtime/routing/routing.py
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Required-preset routing over the two shared ActionCodec2 processors."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import re
|
| 6 |
+
from collections.abc import Mapping, Sequence
|
| 7 |
+
from dataclasses import asdict, dataclass
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from types import MappingProxyType
|
| 10 |
+
from typing import TYPE_CHECKING
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import yaml
|
| 14 |
+
|
| 15 |
+
from ..common._validation import optional_finite_array
|
| 16 |
+
from .cache import BoundedCache
|
| 17 |
+
from ..physical.configuration import (
|
| 18 |
+
ConfigError,
|
| 19 |
+
_integer,
|
| 20 |
+
_mapping,
|
| 21 |
+
_reject_unknown,
|
| 22 |
+
_required,
|
| 23 |
+
_UniqueKeyLoader,
|
| 24 |
+
)
|
| 25 |
+
from ..integration.hf import (
|
| 26 |
+
ROUTER_ALLOW_PATTERNS,
|
| 27 |
+
ROUTER_CONFIG_NAME,
|
| 28 |
+
ROUTER_MODEL_TYPE,
|
| 29 |
+
resolve_pretrained_directory,
|
| 30 |
+
save_huggingface_metadata,
|
| 31 |
+
save_model_card,
|
| 32 |
+
)
|
| 33 |
+
from ..physical.physical_processor import ActionCodec2Processor
|
| 34 |
+
from .presets import (
|
| 35 |
+
ComponentSpec,
|
| 36 |
+
PresetConfig,
|
| 37 |
+
externalize_canonical_trajectory,
|
| 38 |
+
format_action_spaces,
|
| 39 |
+
resample_canonical_trajectory,
|
| 40 |
+
)
|
| 41 |
+
from ..physical.semantic import resampled_horizon
|
| 42 |
+
from ..physical.trajectory import ActiveTrajectory
|
| 43 |
+
|
| 44 |
+
if TYPE_CHECKING:
|
| 45 |
+
from .processing_actioncodec2 import ActionCodec2
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
ROUTER_CONFIG_VERSION = 3
|
| 49 |
+
_NAME = re.compile(r"[a-z][a-z0-9_]*\Z")
|
| 50 |
+
_PROFILES = {"eef", "joint"}
|
| 51 |
+
_PROFILE_WIDTHS = {"eef": 7, "joint": 8}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _current_state_rows(
|
| 55 |
+
current_state: np.ndarray | None, *, count: int, input_dim: int
|
| 56 |
+
) -> list[np.ndarray | None]:
|
| 57 |
+
if current_state is None:
|
| 58 |
+
return [None] * count
|
| 59 |
+
values = optional_finite_array(current_state, shape=(count, input_dim))
|
| 60 |
+
assert values is not None
|
| 61 |
+
return list(values)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@dataclass(frozen=True)
|
| 65 |
+
class ProfileSpec:
|
| 66 |
+
"""Token namespace metadata for one shared physical processor."""
|
| 67 |
+
|
| 68 |
+
name: str
|
| 69 |
+
token_offset: int
|
| 70 |
+
token_budget: int
|
| 71 |
+
artifact_fingerprint: str
|
| 72 |
+
|
| 73 |
+
def __post_init__(self) -> None:
|
| 74 |
+
if _NAME.fullmatch(self.name) is None:
|
| 75 |
+
raise ConfigError(f"profile.name must match {_NAME.pattern!r}")
|
| 76 |
+
_integer(self.token_offset, "profile.token_offset")
|
| 77 |
+
_integer(self.token_budget, "profile.token_budget", positive=True)
|
| 78 |
+
if re.fullmatch(r"[0-9a-f]{64}", self.artifact_fingerprint) is None:
|
| 79 |
+
raise ConfigError(
|
| 80 |
+
"profile.artifact_fingerprint must be a SHA-256 hex digest"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
@classmethod
|
| 84 |
+
def from_dict(cls, payload: object, *, path: str) -> ProfileSpec:
|
| 85 |
+
values = _mapping(payload, path)
|
| 86 |
+
allowed = {"name", "token_offset", "token_budget", "artifact_fingerprint"}
|
| 87 |
+
_reject_unknown(values, allowed, path)
|
| 88 |
+
return cls(
|
| 89 |
+
name=str(_required(values, "name", path)),
|
| 90 |
+
token_offset=_integer(
|
| 91 |
+
_required(values, "token_offset", path), f"{path}.token_offset"
|
| 92 |
+
),
|
| 93 |
+
token_budget=_integer(
|
| 94 |
+
_required(values, "token_budget", path),
|
| 95 |
+
f"{path}.token_budget",
|
| 96 |
+
positive=True,
|
| 97 |
+
),
|
| 98 |
+
artifact_fingerprint=str(_required(values, "artifact_fingerprint", path)),
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
def to_dict(self) -> dict[str, object]:
|
| 102 |
+
return asdict(self)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
@dataclass(frozen=True)
|
| 106 |
+
class _ProfileRuntime:
|
| 107 |
+
spec: ProfileSpec
|
| 108 |
+
processor: ActionCodec2Processor
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@dataclass(frozen=True)
|
| 112 |
+
class _Segment:
|
| 113 |
+
component: ComponentSpec
|
| 114 |
+
profile: _ProfileRuntime
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class RoutedActionCodec2Grammar:
|
| 118 |
+
"""Constrained grammar over a preset's ordered, namespaced segments."""
|
| 119 |
+
|
| 120 |
+
def __init__(
|
| 121 |
+
self, router: ActionCodec2Router, *, preset: str, horizon: int
|
| 122 |
+
) -> None:
|
| 123 |
+
horizon = _integer(horizon, "horizon", positive=True)
|
| 124 |
+
self.segments = router._segments(router.preset(preset))
|
| 125 |
+
self.vocab_size = router.vocab_size
|
| 126 |
+
self._grammars = tuple(
|
| 127 |
+
segment.profile.processor.grammar(
|
| 128 |
+
horizon, active_dims=segment.component.active_dims
|
| 129 |
+
)
|
| 130 |
+
for segment in self.segments
|
| 131 |
+
)
|
| 132 |
+
initial = self._normalize_state(0, self._grammars[0]._initial_state())
|
| 133 |
+
self._initial_state = initial
|
| 134 |
+
self._state_cache = BoundedCache(256)
|
| 135 |
+
self._state_cache[()] = initial
|
| 136 |
+
|
| 137 |
+
def _normalize_state(
|
| 138 |
+
self, segment_index: int, state: tuple[int, ...]
|
| 139 |
+
) -> tuple[int, tuple[int, ...]]:
|
| 140 |
+
while segment_index < len(self.segments) and self._grammars[
|
| 141 |
+
segment_index
|
| 142 |
+
]._state_is_complete(state):
|
| 143 |
+
segment_index += 1
|
| 144 |
+
if segment_index < len(self.segments):
|
| 145 |
+
state = self._grammars[segment_index]._initial_state()
|
| 146 |
+
return segment_index, state
|
| 147 |
+
|
| 148 |
+
def _advance_routed_state(
|
| 149 |
+
self,
|
| 150 |
+
routed_state: tuple[int, tuple[int, ...]],
|
| 151 |
+
token: int,
|
| 152 |
+
) -> tuple[int, tuple[int, ...]] | None:
|
| 153 |
+
segment_index, state = routed_state
|
| 154 |
+
if segment_index == len(self.segments):
|
| 155 |
+
return None
|
| 156 |
+
segment = self.segments[segment_index]
|
| 157 |
+
local_token = token - segment.profile.spec.token_offset
|
| 158 |
+
if not 0 <= local_token < segment.profile.processor.vocab_size:
|
| 159 |
+
return None
|
| 160 |
+
next_state = self._grammars[segment_index]._advance(state, local_token)
|
| 161 |
+
if next_state is None:
|
| 162 |
+
return None
|
| 163 |
+
return self._normalize_state(segment_index, next_state)
|
| 164 |
+
|
| 165 |
+
def _consume(self, tokens: Sequence[int]) -> tuple[int, tuple[int, ...]] | None:
|
| 166 |
+
checked: list[int] = []
|
| 167 |
+
for raw_token in tokens:
|
| 168 |
+
if isinstance(raw_token, bool) or not isinstance(
|
| 169 |
+
raw_token, (int, np.integer)
|
| 170 |
+
):
|
| 171 |
+
return None
|
| 172 |
+
checked.append(int(raw_token))
|
| 173 |
+
key = tuple(checked)
|
| 174 |
+
if not key:
|
| 175 |
+
return self._initial_state
|
| 176 |
+
missing = object()
|
| 177 |
+
cached = self._state_cache.get(key, missing)
|
| 178 |
+
if cached is not missing:
|
| 179 |
+
return cached
|
| 180 |
+
parent = key[:-1]
|
| 181 |
+
state = self._state_cache.get(parent, missing)
|
| 182 |
+
if state is missing:
|
| 183 |
+
state = self._initial_state
|
| 184 |
+
for token in parent:
|
| 185 |
+
if state is None:
|
| 186 |
+
break
|
| 187 |
+
state = self._advance_routed_state(state, token)
|
| 188 |
+
self._state_cache[parent] = state
|
| 189 |
+
if state is not None:
|
| 190 |
+
state = self._advance_routed_state(state, key[-1])
|
| 191 |
+
self._state_cache[key] = state
|
| 192 |
+
return state
|
| 193 |
+
|
| 194 |
+
def next_token_mask(self, tokens: Sequence[int]) -> np.ndarray:
|
| 195 |
+
"""Return a global namespace mask for the next preset token."""
|
| 196 |
+
state = self._consume(tokens)
|
| 197 |
+
if state is None:
|
| 198 |
+
raise ValueError("token prefix is not legal for this routed preset")
|
| 199 |
+
segment_index, local_state = state
|
| 200 |
+
output = np.zeros(self.vocab_size, dtype=bool)
|
| 201 |
+
if segment_index == len(self.segments):
|
| 202 |
+
return output
|
| 203 |
+
segment = self.segments[segment_index]
|
| 204 |
+
mask = self._grammars[segment_index]._next_token_mask_from_state(local_state)
|
| 205 |
+
start = segment.profile.spec.token_offset
|
| 206 |
+
output[start : start + len(mask)] = mask
|
| 207 |
+
return output
|
| 208 |
+
|
| 209 |
+
def is_complete(self, tokens: Sequence[int]) -> bool:
|
| 210 |
+
"""Whether the prefix completes every segment exactly once."""
|
| 211 |
+
state = self._consume(tokens)
|
| 212 |
+
return state is not None and state[0] == len(self.segments)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
class ActionCodec2Router:
|
| 216 |
+
"Route heterogeneous dataset states into physical token namespaces."
|
| 217 |
+
|
| 218 |
+
model_type = ROUTER_MODEL_TYPE
|
| 219 |
+
|
| 220 |
+
def __init__(
|
| 221 |
+
self,
|
| 222 |
+
profiles: Sequence[tuple[ProfileSpec, ActionCodec2Processor]],
|
| 223 |
+
presets: Sequence[PresetConfig],
|
| 224 |
+
) -> None:
|
| 225 |
+
runtimes: dict[str, _ProfileRuntime] = {}
|
| 226 |
+
expected_offset = 0
|
| 227 |
+
for spec, processor in profiles:
|
| 228 |
+
if spec.name in runtimes:
|
| 229 |
+
raise ConfigError(f"duplicate physical profile {spec.name!r}")
|
| 230 |
+
if spec.token_offset != expected_offset:
|
| 231 |
+
raise ConfigError(
|
| 232 |
+
"profile token namespaces must be contiguous and ordered"
|
| 233 |
+
)
|
| 234 |
+
if processor.vocab_size > spec.token_budget:
|
| 235 |
+
raise ConfigError(
|
| 236 |
+
f"profile {spec.name!r} uses {processor.vocab_size} tokens, "
|
| 237 |
+
f"exceeding budget {spec.token_budget}"
|
| 238 |
+
)
|
| 239 |
+
if processor.artifact_fingerprint != spec.artifact_fingerprint:
|
| 240 |
+
raise ConfigError(
|
| 241 |
+
f"profile {spec.name!r} artifact fingerprint does not match its processor"
|
| 242 |
+
)
|
| 243 |
+
if processor.config.set_bpe.allow_cross_component_merge:
|
| 244 |
+
raise ConfigError(
|
| 245 |
+
"allow_cross_component_merge=true is not supported by the v1 router; "
|
| 246 |
+
"a component-aware Set-BPE serializer and grammar are required"
|
| 247 |
+
)
|
| 248 |
+
runtimes[spec.name] = _ProfileRuntime(spec, processor)
|
| 249 |
+
expected_offset += spec.token_budget
|
| 250 |
+
if not runtimes or not set(runtimes) <= _PROFILES:
|
| 251 |
+
raise ConfigError(
|
| 252 |
+
"router requires joint and/or eef physical profiles"
|
| 253 |
+
)
|
| 254 |
+
for name, runtime in runtimes.items():
|
| 255 |
+
if runtime.processor.action_dim != _PROFILE_WIDTHS[name]:
|
| 256 |
+
raise ConfigError(
|
| 257 |
+
f"profile {name!r} must have width {_PROFILE_WIDTHS[name]}, "
|
| 258 |
+
f"got {runtime.processor.action_dim}"
|
| 259 |
+
)
|
| 260 |
+
self.profiles = MappingProxyType(runtimes)
|
| 261 |
+
self._source_directory = Path(__file__).resolve().parent.parent
|
| 262 |
+
self._grammar_cache = BoundedCache(8)
|
| 263 |
+
self._action_spaces: dict[str, PresetConfig] = {}
|
| 264 |
+
for preset in presets:
|
| 265 |
+
self.register_action_space(preset)
|
| 266 |
+
self.vocab_size = expected_offset
|
| 267 |
+
|
| 268 |
+
@property
|
| 269 |
+
def action_spaces(self) -> Mapping[str, PresetConfig]:
|
| 270 |
+
"""Read-only registered built-in and user-created action spaces."""
|
| 271 |
+
return MappingProxyType(self._action_spaces)
|
| 272 |
+
|
| 273 |
+
def __str__(self) -> str:
|
| 274 |
+
return self.describe_action_spaces()
|
| 275 |
+
|
| 276 |
+
def describe_action_spaces(
|
| 277 |
+
self, *, selected: str | None = None, action_dim: int | None = None
|
| 278 |
+
) -> str:
|
| 279 |
+
"""Explain all layouts, or source-width candidates, using this artifact's state requirements."""
|
| 280 |
+
second_order_dims = {
|
| 281 |
+
name: tuple(
|
| 282 |
+
dimension
|
| 283 |
+
for component in runtime.processor.config.physical.components.values()
|
| 284 |
+
if component.primitive_order == 2
|
| 285 |
+
for dimension in component.dims
|
| 286 |
+
)
|
| 287 |
+
for name, runtime in self.profiles.items()
|
| 288 |
+
}
|
| 289 |
+
return format_action_spaces(
|
| 290 |
+
self.action_spaces,
|
| 291 |
+
selected=selected,
|
| 292 |
+
action_dim=action_dim,
|
| 293 |
+
second_order_dims=second_order_dims,
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
@classmethod
|
| 297 |
+
def from_processors(
|
| 298 |
+
cls,
|
| 299 |
+
processors: Mapping[str, ActionCodec2Processor],
|
| 300 |
+
*,
|
| 301 |
+
token_budgets: Mapping[str, int],
|
| 302 |
+
presets: Sequence[PresetConfig],
|
| 303 |
+
) -> ActionCodec2Router:
|
| 304 |
+
"""Assign stable contiguous namespaces in canonical joint/eef order."""
|
| 305 |
+
if set(processors) != set(token_budgets):
|
| 306 |
+
raise ConfigError("processors and token_budgets must share keys")
|
| 307 |
+
if set(processors) != _PROFILES:
|
| 308 |
+
raise ConfigError(
|
| 309 |
+
"processors must contain exactly the joint and eef profiles"
|
| 310 |
+
)
|
| 311 |
+
offset = 0
|
| 312 |
+
entries = []
|
| 313 |
+
for name in ("joint", "eef"):
|
| 314 |
+
processor = processors[name]
|
| 315 |
+
budget = _integer(
|
| 316 |
+
token_budgets[name], f"token_budgets.{name}", positive=True
|
| 317 |
+
)
|
| 318 |
+
spec = ProfileSpec(
|
| 319 |
+
name,
|
| 320 |
+
offset,
|
| 321 |
+
budget,
|
| 322 |
+
processor.artifact_fingerprint,
|
| 323 |
+
)
|
| 324 |
+
entries.append((spec, processor))
|
| 325 |
+
offset += budget
|
| 326 |
+
return cls(entries, presets)
|
| 327 |
+
|
| 328 |
+
def preset(self, name: str) -> PresetConfig:
|
| 329 |
+
"""Resolve one preset or fail with the available names."""
|
| 330 |
+
try:
|
| 331 |
+
return self._action_spaces[name]
|
| 332 |
+
except KeyError as error:
|
| 333 |
+
raise KeyError(
|
| 334 |
+
f"unknown preset {name!r}; available: {sorted(self._action_spaces)}"
|
| 335 |
+
) from error
|
| 336 |
+
|
| 337 |
+
def register_action_space(
|
| 338 |
+
self,
|
| 339 |
+
preset: PresetConfig | Mapping[str, object] | str | Path,
|
| 340 |
+
) -> PresetConfig:
|
| 341 |
+
"Register a validated action-space config, mapping, or YAML file."
|
| 342 |
+
if isinstance(preset, Mapping):
|
| 343 |
+
preset = PresetConfig.from_dict(preset)
|
| 344 |
+
elif isinstance(preset, (str, Path)):
|
| 345 |
+
preset = PresetConfig.from_yaml(preset)
|
| 346 |
+
if not isinstance(preset, PresetConfig):
|
| 347 |
+
raise TypeError(
|
| 348 |
+
"register_action_space expects PresetConfig, a mapping, or a preset YAML path"
|
| 349 |
+
)
|
| 350 |
+
existing = self._action_spaces.get(preset.name)
|
| 351 |
+
if existing is not None and existing != preset:
|
| 352 |
+
raise ConfigError(
|
| 353 |
+
f"action-space name {preset.name!r} is already registered differently"
|
| 354 |
+
)
|
| 355 |
+
self._action_spaces[preset.name] = preset
|
| 356 |
+
return preset
|
| 357 |
+
|
| 358 |
+
def _segments(self, preset: PresetConfig) -> tuple[_Segment, ...]:
|
| 359 |
+
return tuple(
|
| 360 |
+
_Segment(component, self.profiles[component.space])
|
| 361 |
+
for component in preset.components
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
def encode_batch(
|
| 365 |
+
self,
|
| 366 |
+
inputs: np.ndarray,
|
| 367 |
+
*,
|
| 368 |
+
preset: str,
|
| 369 |
+
fps: float | None = None,
|
| 370 |
+
current_state: np.ndarray | None = None,
|
| 371 |
+
previous_first_order: Mapping[str, np.ndarray] | None = None,
|
| 372 |
+
) -> list[list[int]]:
|
| 373 |
+
"Encode one regular-rate ``[B,T,D]`` tensor at ``fps``."
|
| 374 |
+
preset_config = self.preset(preset)
|
| 375 |
+
self._validate_boundary_keys(previous_first_order, preset_config)
|
| 376 |
+
canonical = preset_config.canonicalize(
|
| 377 |
+
inputs, source_hz=fps, current_state=current_state
|
| 378 |
+
)
|
| 379 |
+
batch = len(next(iter(canonical.values())))
|
| 380 |
+
rows: list[list[int]] = [[] for _ in range(batch)]
|
| 381 |
+
for segment in self._segments(preset_config):
|
| 382 |
+
values = canonical[segment.component.name]
|
| 383 |
+
streams = segment.profile.processor.encode_batch(
|
| 384 |
+
np.stack([trajectory.values for trajectory in values]),
|
| 385 |
+
active_dims=segment.component.active_dims,
|
| 386 |
+
previous_first_order=None
|
| 387 |
+
if previous_first_order is None
|
| 388 |
+
else previous_first_order.get(segment.component.name),
|
| 389 |
+
)
|
| 390 |
+
offset = segment.profile.spec.token_offset
|
| 391 |
+
for row, stream in zip(rows, streams, strict=True):
|
| 392 |
+
row.extend(offset + token for token in stream)
|
| 393 |
+
return rows
|
| 394 |
+
|
| 395 |
+
def route_trajectories(
|
| 396 |
+
self,
|
| 397 |
+
inputs: np.ndarray,
|
| 398 |
+
*,
|
| 399 |
+
preset: str,
|
| 400 |
+
current_state: np.ndarray | None = None,
|
| 401 |
+
fps: float | None = None,
|
| 402 |
+
previous_first_order: Mapping[str, np.ndarray] | None = None,
|
| 403 |
+
) -> dict[str, list[ActiveTrajectory]]:
|
| 404 |
+
"Expose canonical complete trajectories grouped by physical profile."
|
| 405 |
+
preset_config = self.preset(preset)
|
| 406 |
+
self._validate_boundary_keys(previous_first_order, preset_config)
|
| 407 |
+
canonical = preset_config.canonicalize(
|
| 408 |
+
inputs, current_state=current_state, source_hz=fps
|
| 409 |
+
)
|
| 410 |
+
output: dict[str, list[ActiveTrajectory]] = {}
|
| 411 |
+
for component in preset_config.components:
|
| 412 |
+
trajectories = canonical[component.name]
|
| 413 |
+
processor = self.profiles[component.space].processor
|
| 414 |
+
previous = (
|
| 415 |
+
None
|
| 416 |
+
if previous_first_order is None
|
| 417 |
+
else previous_first_order.get(component.name)
|
| 418 |
+
)
|
| 419 |
+
dense = processor._dense_previous_first_order(
|
| 420 |
+
previous, component.active_dims, len(trajectories)
|
| 421 |
+
)
|
| 422 |
+
rows = (
|
| 423 |
+
[None] * len(trajectories)
|
| 424 |
+
if dense is None
|
| 425 |
+
else np.take(dense, component.active_dims, axis=1)
|
| 426 |
+
)
|
| 427 |
+
output.setdefault(component.space, []).extend(
|
| 428 |
+
ActiveTrajectory(t.values, t.active_dims, previous_first_order=p)
|
| 429 |
+
for t, p in zip(trajectories, rows, strict=True)
|
| 430 |
+
)
|
| 431 |
+
return output
|
| 432 |
+
|
| 433 |
+
def _validate_boundary_keys(
|
| 434 |
+
self,
|
| 435 |
+
previous_first_order: Mapping[str, np.ndarray] | None,
|
| 436 |
+
preset: PresetConfig,
|
| 437 |
+
) -> None:
|
| 438 |
+
if previous_first_order is None:
|
| 439 |
+
previous_first_order = {}
|
| 440 |
+
if not isinstance(previous_first_order, Mapping):
|
| 441 |
+
raise ValueError(
|
| 442 |
+
"previous_first_order must map component names to canonical [B,D_active] boundaries"
|
| 443 |
+
)
|
| 444 |
+
unknown = set(previous_first_order) - {c.name for c in preset.components}
|
| 445 |
+
if unknown:
|
| 446 |
+
raise ValueError(
|
| 447 |
+
f"unknown previous_first_order component(s): {sorted(unknown)}"
|
| 448 |
+
)
|
| 449 |
+
required = {
|
| 450 |
+
c.name: f"(B, {len(c.active_dims)})"
|
| 451 |
+
for c in preset.components
|
| 452 |
+
if any(
|
| 453 |
+
p.primitive_order == 2 and set(c.active_dims).intersection(p.dims)
|
| 454 |
+
for p in self.profiles[
|
| 455 |
+
c.space
|
| 456 |
+
].processor.config.physical.components.values()
|
| 457 |
+
)
|
| 458 |
+
}
|
| 459 |
+
missing = set(required) - previous_first_order.keys()
|
| 460 |
+
if missing:
|
| 461 |
+
raise ValueError(
|
| 462 |
+
"second-order tokenization requires previous_first_order with arrays "
|
| 463 |
+
f"{required}; missing {sorted(missing)}. Use canonical {preset.target_hz:g} Hz "
|
| 464 |
+
"reconstructed increments. codec.first_order_template(batch_size) explicitly "
|
| 465 |
+
"creates zeros; use zeros only if the preceding increment is known to be zero."
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
def final_first_order(
|
| 469 |
+
self,
|
| 470 |
+
streams: Sequence[Sequence[int]],
|
| 471 |
+
*,
|
| 472 |
+
preset: str,
|
| 473 |
+
previous_first_order: Mapping[str, np.ndarray],
|
| 474 |
+
executed_steps: int | None = None,
|
| 475 |
+
) -> dict[str, np.ndarray]:
|
| 476 |
+
"Return component ``[B,D_active]`` boundaries at the canonical clock."
|
| 477 |
+
preset_config = self.preset(preset)
|
| 478 |
+
if previous_first_order is None:
|
| 479 |
+
raise ValueError("final_first_order requires previous_first_order")
|
| 480 |
+
self._validate_boundary_keys(previous_first_order, preset_config)
|
| 481 |
+
segments = self._segments(preset_config)
|
| 482 |
+
steps = self._batch_steps(streams, segments)
|
| 483 |
+
split = [self._split_stream(stream, segments, steps) for stream in streams]
|
| 484 |
+
output = {}
|
| 485 |
+
for index, segment in enumerate(segments):
|
| 486 |
+
processor = segment.profile.processor
|
| 487 |
+
if not processor.requires_previous_first_order:
|
| 488 |
+
continue
|
| 489 |
+
output[segment.component.name] = processor.final_first_order(
|
| 490 |
+
[row[index] for row in split],
|
| 491 |
+
previous_first_order=previous_first_order.get(segment.component.name),
|
| 492 |
+
active_dims=segment.component.active_dims,
|
| 493 |
+
executed_steps=executed_steps,
|
| 494 |
+
)
|
| 495 |
+
return output
|
| 496 |
+
|
| 497 |
+
def _split_stream(
|
| 498 |
+
self, tokens: Sequence[int], segments: Sequence[_Segment], steps: int
|
| 499 |
+
) -> list[list[int]]:
|
| 500 |
+
cursor = 0
|
| 501 |
+
output = []
|
| 502 |
+
for segment in segments:
|
| 503 |
+
grammar = segment.profile.processor.grammar(
|
| 504 |
+
steps, active_dims=segment.component.active_dims
|
| 505 |
+
)
|
| 506 |
+
state = grammar._initial_state()
|
| 507 |
+
local: list[int] = []
|
| 508 |
+
while cursor < len(tokens) and not grammar._state_is_complete(state):
|
| 509 |
+
raw_token = tokens[cursor]
|
| 510 |
+
if isinstance(raw_token, bool) or not isinstance(
|
| 511 |
+
raw_token, (int, np.integer)
|
| 512 |
+
):
|
| 513 |
+
raise ValueError(f"tokens[{cursor}] must be an integer")
|
| 514 |
+
token = int(raw_token) - segment.profile.spec.token_offset
|
| 515 |
+
if not 0 <= token < segment.profile.processor.vocab_size:
|
| 516 |
+
raise ValueError(
|
| 517 |
+
"token belongs to the wrong routed profile namespace"
|
| 518 |
+
)
|
| 519 |
+
next_state = grammar._advance(state, token)
|
| 520 |
+
if next_state is None:
|
| 521 |
+
raise ValueError(
|
| 522 |
+
f"Action token stream has invalid coverage for component {segment.component.name!r}. Constrain generation with codec.grammar(H, fps=...).next_token_mask(prefix)."
|
| 523 |
+
)
|
| 524 |
+
state = next_state
|
| 525 |
+
local.append(token)
|
| 526 |
+
cursor += 1
|
| 527 |
+
if not grammar._state_is_complete(state):
|
| 528 |
+
raise ValueError(
|
| 529 |
+
f"Incomplete action token stream for component {segment.component.name!r}. Check codec.grammar(H, fps=...).is_complete(tokens); discard or regenerate this row."
|
| 530 |
+
)
|
| 531 |
+
output.append(local)
|
| 532 |
+
if cursor != len(tokens):
|
| 533 |
+
raise ValueError(
|
| 534 |
+
"Action token stream has extra tokens after complete coverage; remove model prompt/EOS/padding before codec.decode()."
|
| 535 |
+
)
|
| 536 |
+
return output
|
| 537 |
+
|
| 538 |
+
def _token_cell_count(self, raw_token: object, *, index: int) -> int:
|
| 539 |
+
|
| 540 |
+
if isinstance(raw_token, bool) or not isinstance(raw_token, (int, np.integer)):
|
| 541 |
+
raise ValueError(f"tokens[{index}] must be an integer")
|
| 542 |
+
token = int(raw_token)
|
| 543 |
+
for runtime in self.profiles.values():
|
| 544 |
+
local = token - runtime.spec.token_offset
|
| 545 |
+
if 0 <= local < runtime.processor.vocab_size:
|
| 546 |
+
return int(runtime.processor.serializer.token_cell_counts[local])
|
| 547 |
+
raise ValueError(f"tokens[{index}] is outside the routed profile vocabularies")
|
| 548 |
+
|
| 549 |
+
def _decoded_steps(
|
| 550 |
+
self, tokens: Sequence[int], segments: Sequence[_Segment]
|
| 551 |
+
) -> int:
|
| 552 |
+
|
| 553 |
+
if not tokens:
|
| 554 |
+
raise ValueError("token stream must not be empty")
|
| 555 |
+
cells_per_step = sum(len(segment.component.active_dims) for segment in segments)
|
| 556 |
+
represented_cells = sum(
|
| 557 |
+
self._token_cell_count(token, index=index)
|
| 558 |
+
for index, token in enumerate(tokens)
|
| 559 |
+
)
|
| 560 |
+
steps, remainder = divmod(represented_cells, cells_per_step)
|
| 561 |
+
if steps < 1 or remainder:
|
| 562 |
+
raise ValueError(
|
| 563 |
+
"Incomplete action token stream: "
|
| 564 |
+
f"{cells_per_step - remainder} more coordinate values are needed to finish a step. "
|
| 565 |
+
"Constrain generation with codec.grammar(H, fps=...).next_token_mask(prefix) "
|
| 566 |
+
"and check is_complete(tokens) for the intended H before decoding. "
|
| 567 |
+
"Use codec.batch_decode(..., errors='return') to isolate invalid rows; "
|
| 568 |
+
"do not pad truncated tokens with zeros."
|
| 569 |
+
)
|
| 570 |
+
return steps
|
| 571 |
+
|
| 572 |
+
def _batch_steps(
|
| 573 |
+
self,
|
| 574 |
+
streams: Sequence[Sequence[int]],
|
| 575 |
+
segments: Sequence[_Segment],
|
| 576 |
+
) -> int:
|
| 577 |
+
|
| 578 |
+
if not streams:
|
| 579 |
+
raise ValueError("streams must not be empty")
|
| 580 |
+
lengths = [self._decoded_steps(stream, segments) for stream in streams]
|
| 581 |
+
if len(set(lengths)) != 1:
|
| 582 |
+
raise ValueError(
|
| 583 |
+
f"all token streams must decode to the same length, got {lengths}; use codec.batch_decode(...) for independent variable-length outputs"
|
| 584 |
+
)
|
| 585 |
+
return lengths[0]
|
| 586 |
+
|
| 587 |
+
def decode_batch(
|
| 588 |
+
self,
|
| 589 |
+
streams: Sequence[Sequence[int]],
|
| 590 |
+
*,
|
| 591 |
+
preset: str,
|
| 592 |
+
fps: float | None = None,
|
| 593 |
+
current_state: np.ndarray | None = None,
|
| 594 |
+
previous_first_order: Mapping[str, np.ndarray] | None = None,
|
| 595 |
+
) -> dict[str, np.ndarray]:
|
| 596 |
+
"Decode rows to deltas, or absolute components with ``current_state``."
|
| 597 |
+
preset_config = self.preset(preset)
|
| 598 |
+
self._validate_boundary_keys(previous_first_order, preset_config)
|
| 599 |
+
segments = self._segments(preset_config)
|
| 600 |
+
internal_steps = self._batch_steps(streams, segments)
|
| 601 |
+
states = _current_state_rows(
|
| 602 |
+
current_state, count=len(streams), input_dim=preset_config.input_dim
|
| 603 |
+
)
|
| 604 |
+
output_fps = preset_config.source_hz if fps is None else fps
|
| 605 |
+
output_steps = resampled_horizon(
|
| 606 |
+
internal_steps,
|
| 607 |
+
source_hz=preset_config.target_hz,
|
| 608 |
+
target_hz=output_fps,
|
| 609 |
+
)
|
| 610 |
+
output: dict[str, list[np.ndarray]] = {
|
| 611 |
+
component.name: [] for component in preset_config.components
|
| 612 |
+
}
|
| 613 |
+
split_streams = [
|
| 614 |
+
self._split_stream(stream, segments, internal_steps) for stream in streams
|
| 615 |
+
]
|
| 616 |
+
for segment_index, segment in enumerate(segments):
|
| 617 |
+
decoded_batch = segment.profile.processor.decode_batch(
|
| 618 |
+
[split[segment_index] for split in split_streams],
|
| 619 |
+
active_dims=segment.component.active_dims,
|
| 620 |
+
previous_first_order=None
|
| 621 |
+
if previous_first_order is None
|
| 622 |
+
else previous_first_order.get(segment.component.name),
|
| 623 |
+
)
|
| 624 |
+
for decoded, state in zip(decoded_batch, states, strict=True):
|
| 625 |
+
restored = resample_canonical_trajectory(
|
| 626 |
+
ActiveTrajectory(decoded, segment.component.active_dims),
|
| 627 |
+
segment.component,
|
| 628 |
+
source_hz=preset_config.target_hz,
|
| 629 |
+
target_hz=output_fps,
|
| 630 |
+
target_length=output_steps,
|
| 631 |
+
method=preset_config.interpolation,
|
| 632 |
+
)
|
| 633 |
+
output[segment.component.name].append(
|
| 634 |
+
externalize_canonical_trajectory(
|
| 635 |
+
restored, segment.component, current_state=state
|
| 636 |
+
)
|
| 637 |
+
)
|
| 638 |
+
return {name: np.stack(values) for name, values in output.items()}
|
| 639 |
+
|
| 640 |
+
def grammar(self, *, preset: str, horizon: int) -> RoutedActionCodec2Grammar:
|
| 641 |
+
"""Build the global constrained grammar for one preset and target horizon."""
|
| 642 |
+
horizon = _integer(horizon, "horizon", positive=True)
|
| 643 |
+
self.preset(preset)
|
| 644 |
+
key = (preset, horizon)
|
| 645 |
+
grammar = self._grammar_cache.get(key)
|
| 646 |
+
if grammar is None:
|
| 647 |
+
grammar = RoutedActionCodec2Grammar(self, preset=preset, horizon=horizon)
|
| 648 |
+
self._grammar_cache[key] = grammar
|
| 649 |
+
return grammar
|
| 650 |
+
|
| 651 |
+
@property
|
| 652 |
+
def new_action_tokens(self) -> list[str]:
|
| 653 |
+
"""Stable physical token names, including reserved profile budget slots."""
|
| 654 |
+
return [
|
| 655 |
+
f"<action_{name}_{index:04d}>"
|
| 656 |
+
for name, runtime in self.profiles.items()
|
| 657 |
+
for index in range(runtime.spec.token_budget)
|
| 658 |
+
]
|
| 659 |
+
|
| 660 |
+
def save_pretrained(
|
| 661 |
+
self, directory: str | Path, *, include_code: bool = True
|
| 662 |
+
) -> list[str]:
|
| 663 |
+
"""Save one manifest plus isolated physical processor subdirectories."""
|
| 664 |
+
root = Path(directory)
|
| 665 |
+
files = []
|
| 666 |
+
for runtime in self.profiles.values():
|
| 667 |
+
files.extend(
|
| 668 |
+
runtime.processor.save_pretrained(root / "profiles" / runtime.spec.name)
|
| 669 |
+
)
|
| 670 |
+
manifest = {
|
| 671 |
+
"model_type": self.model_type,
|
| 672 |
+
"config_version": ROUTER_CONFIG_VERSION,
|
| 673 |
+
"profiles": [runtime.spec.to_dict() for runtime in self.profiles.values()],
|
| 674 |
+
"presets": [preset.to_dict() for preset in self.action_spaces.values()],
|
| 675 |
+
}
|
| 676 |
+
path = root / ROUTER_CONFIG_NAME
|
| 677 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 678 |
+
path.write_text(yaml.safe_dump(manifest, sort_keys=False), encoding="utf-8")
|
| 679 |
+
metadata = save_huggingface_metadata(
|
| 680 |
+
root,
|
| 681 |
+
model_type=self.model_type,
|
| 682 |
+
processor_class=type(self).__name__,
|
| 683 |
+
**(
|
| 684 |
+
{"auto_map": {"AutoProcessor": "processing_actioncodec2.ActionCodec2"}}
|
| 685 |
+
if include_code
|
| 686 |
+
else {}
|
| 687 |
+
),
|
| 688 |
+
)
|
| 689 |
+
card = save_model_card(root, str(self))
|
| 690 |
+
if include_code:
|
| 691 |
+
from ..integration.exporting import ArtifactExporter
|
| 692 |
+
|
| 693 |
+
files.extend(
|
| 694 |
+
ArtifactExporter(root, source_directory=self._source_directory).export()
|
| 695 |
+
)
|
| 696 |
+
return [str(path), str(card), *map(str, metadata), *files]
|
| 697 |
+
|
| 698 |
+
@classmethod
|
| 699 |
+
def from_pretrained(
|
| 700 |
+
cls,
|
| 701 |
+
pretrained_model_name_or_path: str | Path,
|
| 702 |
+
**kwargs: object,
|
| 703 |
+
) -> ActionCodec2Router | ActionCodec2:
|
| 704 |
+
"Load a router, or bind its public processor when given ``action_space``."
|
| 705 |
+
if "action_space" in kwargs:
|
| 706 |
+
from .processing_actioncodec2 import ActionCodec2
|
| 707 |
+
|
| 708 |
+
return ActionCodec2.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 709 |
+
root = resolve_pretrained_directory(
|
| 710 |
+
pretrained_model_name_or_path,
|
| 711 |
+
allow_patterns=ROUTER_ALLOW_PATTERNS,
|
| 712 |
+
**kwargs,
|
| 713 |
+
)
|
| 714 |
+
path = root / ROUTER_CONFIG_NAME
|
| 715 |
+
payload = _mapping(
|
| 716 |
+
yaml.load(path.read_text(encoding="utf-8"), Loader=_UniqueKeyLoader),
|
| 717 |
+
"router",
|
| 718 |
+
)
|
| 719 |
+
_reject_unknown(
|
| 720 |
+
payload, {"model_type", "config_version", "profiles", "presets"}, "router"
|
| 721 |
+
)
|
| 722 |
+
if payload.get("model_type") != ROUTER_MODEL_TYPE:
|
| 723 |
+
raise ConfigError(f"router.model_type must be {ROUTER_MODEL_TYPE!r}")
|
| 724 |
+
if payload.get("config_version") != ROUTER_CONFIG_VERSION:
|
| 725 |
+
raise ConfigError(
|
| 726 |
+
f"unsupported router config_version {payload.get('config_version')!r}"
|
| 727 |
+
)
|
| 728 |
+
raw_profiles = payload.get("profiles")
|
| 729 |
+
raw_presets = payload.get("presets")
|
| 730 |
+
if not isinstance(raw_profiles, list) or not isinstance(raw_presets, list):
|
| 731 |
+
raise ConfigError("router.profiles and router.presets must be lists")
|
| 732 |
+
profiles = []
|
| 733 |
+
for index, raw in enumerate(raw_profiles):
|
| 734 |
+
spec = ProfileSpec.from_dict(raw, path=f"router.profiles[{index}]")
|
| 735 |
+
processor = ActionCodec2Processor.from_pretrained(
|
| 736 |
+
root / "profiles" / spec.name
|
| 737 |
+
)
|
| 738 |
+
profiles.append((spec, processor))
|
| 739 |
+
presets = [
|
| 740 |
+
PresetConfig.from_dict(raw, path=f"router.presets[{index}]")
|
| 741 |
+
for index, raw in enumerate(raw_presets)
|
| 742 |
+
]
|
| 743 |
+
router = cls(profiles, presets)
|
| 744 |
+
router._source_directory = root
|
| 745 |
+
return router
|
runtime/tokenization/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Internal tokenization components for ActionCodec2."""
|
runtime/tokenization/bpe.py
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Strict ``bpe.json`` I/O for both tokenization schemes."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from collections.abc import Mapping, Sequence
|
| 7 |
+
from numbers import Integral
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
|
| 12 |
+
from ..common._validation import active_dimensions
|
| 13 |
+
from .bpe_vocabulary import BPEVocabulary
|
| 14 |
+
from ..physical.configuration import ActionCodec2Config, ConfigError
|
| 15 |
+
from .setbpe_spatial import SpatialVocabulary
|
| 16 |
+
from .setbpe_temporal import TemporalVocabulary
|
| 17 |
+
|
| 18 |
+
BPE_NAME = "bpe.json"
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _integer(value: object, path: str, *, positive: bool = False) -> int:
|
| 22 |
+
if isinstance(value, bool) or not isinstance(value, int):
|
| 23 |
+
raise ConfigError(f"{path} must be an integer")
|
| 24 |
+
minimum = 1 if positive else 0
|
| 25 |
+
if value < minimum:
|
| 26 |
+
qualifier = "positive" if positive else "non-negative"
|
| 27 |
+
raise ConfigError(f"{path} must be {qualifier}")
|
| 28 |
+
return int(value)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _rules(payload: object, width: int, path: str) -> tuple[tuple[int, ...], ...]:
|
| 32 |
+
|
| 33 |
+
if not isinstance(payload, Sequence) or isinstance(
|
| 34 |
+
payload, (str, bytes, bytearray)
|
| 35 |
+
):
|
| 36 |
+
raise ConfigError(f"{path} must be a list")
|
| 37 |
+
output = []
|
| 38 |
+
for rank, raw_rule in enumerate(payload):
|
| 39 |
+
rule_path = f"{path}[{rank}]"
|
| 40 |
+
if not isinstance(raw_rule, Sequence) or isinstance(
|
| 41 |
+
raw_rule, (str, bytes, bytearray)
|
| 42 |
+
):
|
| 43 |
+
raise ConfigError(f"{rule_path} must be a list of {width} integers")
|
| 44 |
+
if len(raw_rule) != width:
|
| 45 |
+
raise ConfigError(f"{rule_path} must contain exactly {width} integers")
|
| 46 |
+
output.append(
|
| 47 |
+
tuple(
|
| 48 |
+
_integer(value, f"{rule_path}[{index}]")
|
| 49 |
+
for index, value in enumerate(raw_rule)
|
| 50 |
+
)
|
| 51 |
+
)
|
| 52 |
+
return tuple(output)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _unique_json_object(pairs: list[tuple[str, object]]) -> dict[str, object]:
|
| 56 |
+
output: dict[str, object] = {}
|
| 57 |
+
for key, value in pairs:
|
| 58 |
+
if key in output:
|
| 59 |
+
raise ConfigError(f"{BPE_NAME} contains duplicate field {key!r}")
|
| 60 |
+
output[key] = value
|
| 61 |
+
return output
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _validate_rule_references(
|
| 65 |
+
*,
|
| 66 |
+
atom_count: int,
|
| 67 |
+
lookahead: int,
|
| 68 |
+
spatial_merges: tuple[tuple[int, int], ...],
|
| 69 |
+
temporal_merges: tuple[tuple[int, int, int], ...],
|
| 70 |
+
) -> None:
|
| 71 |
+
|
| 72 |
+
for rank, (left, right) in enumerate(spatial_merges):
|
| 73 |
+
child = atom_count + rank
|
| 74 |
+
if left >= child or right >= child:
|
| 75 |
+
raise ConfigError(
|
| 76 |
+
f"spatial_merges[{rank}] must reference already-created tokens"
|
| 77 |
+
)
|
| 78 |
+
spatial_size = atom_count + len(spatial_merges)
|
| 79 |
+
for rank, (left, right, offset) in enumerate(temporal_merges):
|
| 80 |
+
child = spatial_size + rank
|
| 81 |
+
if left >= child or right >= child:
|
| 82 |
+
raise ConfigError(
|
| 83 |
+
f"temporal_merges[{rank}] must reference already-created tokens"
|
| 84 |
+
)
|
| 85 |
+
if not 1 <= offset < lookahead:
|
| 86 |
+
raise ConfigError(
|
| 87 |
+
f"temporal_merges[{rank}] offset must be positive and smaller than lookahead"
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _read_payload(directory: str | Path) -> dict[str, object]:
|
| 92 |
+
|
| 93 |
+
path = Path(directory) / BPE_NAME
|
| 94 |
+
try:
|
| 95 |
+
return json.loads(
|
| 96 |
+
path.read_text(encoding="utf-8"), object_pairs_hook=_unique_json_object
|
| 97 |
+
)
|
| 98 |
+
except OSError:
|
| 99 |
+
raise
|
| 100 |
+
except json.JSONDecodeError as error:
|
| 101 |
+
raise ConfigError(f"cannot parse {path}: {error}") from error
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _dense_cells(
|
| 105 |
+
cells: np.ndarray,
|
| 106 |
+
bins: Sequence[int],
|
| 107 |
+
active_dims: Sequence[int] | None,
|
| 108 |
+
) -> tuple[np.ndarray, tuple[int, ...] | None]:
|
| 109 |
+
|
| 110 |
+
raw = np.asarray(cells)
|
| 111 |
+
if raw.dtype.kind not in {"i", "u"}:
|
| 112 |
+
raise ValueError("cells must contain integer indices")
|
| 113 |
+
values = np.asarray(raw, dtype=np.int64)
|
| 114 |
+
if values.ndim != 2 or len(values) < 1 or values.shape[1] < 1:
|
| 115 |
+
raise ValueError("cells must have non-empty shape [T, D_active]")
|
| 116 |
+
if active_dims is None:
|
| 117 |
+
if values.shape[1] > len(bins):
|
| 118 |
+
raise ValueError(f"cells width exceeds codebook dimension {len(bins)}")
|
| 119 |
+
if values.shape[1] < len(bins):
|
| 120 |
+
dimensions = tuple(range(values.shape[1]))
|
| 121 |
+
dense = np.full((len(values), len(bins)), -1, dtype=np.int64)
|
| 122 |
+
dense[:, : values.shape[1]] = values
|
| 123 |
+
values = dense
|
| 124 |
+
else:
|
| 125 |
+
dimensions = None
|
| 126 |
+
else:
|
| 127 |
+
dimensions = active_dimensions(active_dims, len(bins))
|
| 128 |
+
if len(dimensions) != values.shape[1]:
|
| 129 |
+
raise ValueError(
|
| 130 |
+
"active_dims must have one entry per compact cell coordinate"
|
| 131 |
+
)
|
| 132 |
+
if np.any(values < 0):
|
| 133 |
+
raise ValueError("compact cells must not contain -1")
|
| 134 |
+
dense = np.full((len(values), len(bins)), -1, dtype=np.int64)
|
| 135 |
+
dense[:, dimensions] = values
|
| 136 |
+
values = dense
|
| 137 |
+
counts = np.asarray(tuple(int(value) for value in bins), dtype=np.int64)
|
| 138 |
+
if np.any(values < -1) or np.any(values >= counts[None, :]):
|
| 139 |
+
raise ValueError("cells contain an invalid active or absent index")
|
| 140 |
+
return values, dimensions
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class SetBPESerializer:
|
| 144 |
+
"Lossless mapping between ``[T,D]`` physical cells and token IDs."
|
| 145 |
+
|
| 146 |
+
scheme = "set_bpe"
|
| 147 |
+
|
| 148 |
+
def __init__(
|
| 149 |
+
self,
|
| 150 |
+
*,
|
| 151 |
+
bins: Sequence[int],
|
| 152 |
+
lookahead: int,
|
| 153 |
+
spatial_merges: Sequence[Sequence[int]],
|
| 154 |
+
temporal_merges: Sequence[Sequence[int]],
|
| 155 |
+
) -> None:
|
| 156 |
+
self.lookahead = _integer(lookahead, "lookahead", positive=True)
|
| 157 |
+
self.bins = tuple(
|
| 158 |
+
_integer(value, f"bins[{index}]", positive=True)
|
| 159 |
+
for index, value in enumerate(bins)
|
| 160 |
+
)
|
| 161 |
+
if not self.bins:
|
| 162 |
+
raise ConfigError("bins must contain at least one cell slot")
|
| 163 |
+
self.spatial_merges = _rules(spatial_merges, 2, "spatial_merges")
|
| 164 |
+
self.temporal_merges = _rules(temporal_merges, 3, "temporal_merges")
|
| 165 |
+
_validate_rule_references(
|
| 166 |
+
atom_count=sum(self.bins),
|
| 167 |
+
lookahead=self.lookahead,
|
| 168 |
+
spatial_merges=self.spatial_merges,
|
| 169 |
+
temporal_merges=self.temporal_merges,
|
| 170 |
+
)
|
| 171 |
+
try:
|
| 172 |
+
self.spatial = SpatialVocabulary(self.bins, self.spatial_merges)
|
| 173 |
+
self.vocabulary = TemporalVocabulary(
|
| 174 |
+
self.spatial, self.lookahead, self.temporal_merges
|
| 175 |
+
)
|
| 176 |
+
except ValueError as error:
|
| 177 |
+
raise ConfigError(str(error)) from error
|
| 178 |
+
self.patterns = self.vocabulary.patterns
|
| 179 |
+
self.root = self.vocabulary.root
|
| 180 |
+
self.vocab_size = self.vocabulary.vocab_size
|
| 181 |
+
self.token_cell_counts = np.asarray(
|
| 182 |
+
[
|
| 183 |
+
sum(
|
| 184 |
+
int(self.spatial.support[spatial_token]).bit_count()
|
| 185 |
+
for _, spatial_token in self.patterns[token]
|
| 186 |
+
)
|
| 187 |
+
for token in range(self.vocab_size)
|
| 188 |
+
],
|
| 189 |
+
dtype=np.int64,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
def encode(
|
| 193 |
+
self,
|
| 194 |
+
cells: np.ndarray,
|
| 195 |
+
*,
|
| 196 |
+
active_dims: Sequence[int] | None = None,
|
| 197 |
+
) -> list[int]:
|
| 198 |
+
"Serialize one non-empty trajectory without emitting absent slots."
|
| 199 |
+
values, _ = _dense_cells(cells, self.bins, active_dims)
|
| 200 |
+
return self.vocabulary.encode(values)
|
| 201 |
+
|
| 202 |
+
def decode(
|
| 203 |
+
self,
|
| 204 |
+
tokens: Sequence[int],
|
| 205 |
+
*,
|
| 206 |
+
active_dims: Sequence[int] | None = None,
|
| 207 |
+
) -> np.ndarray:
|
| 208 |
+
"""Recover dense cells, writing ``-1`` in semantically absent slots."""
|
| 209 |
+
if len(tokens) < 1:
|
| 210 |
+
raise ValueError("token stream must not be empty")
|
| 211 |
+
checked = []
|
| 212 |
+
for index, token in enumerate(tokens):
|
| 213 |
+
if isinstance(token, bool) or not isinstance(token, Integral):
|
| 214 |
+
raise ValueError(f"tokens[{index}] must be an integer")
|
| 215 |
+
value = int(token)
|
| 216 |
+
if not 0 <= value < self.vocab_size:
|
| 217 |
+
raise ValueError(f"tokens[{index}] is outside the vocabulary")
|
| 218 |
+
checked.append(value)
|
| 219 |
+
return self.vocabulary.decode(checked, active_dims=active_dims)
|
| 220 |
+
|
| 221 |
+
def to_dict(self) -> dict[str, object]:
|
| 222 |
+
"""Return the intentionally minimal, path-independent BPE artifact."""
|
| 223 |
+
return {
|
| 224 |
+
"spatial_merges": [list(rule) for rule in self.spatial_merges],
|
| 225 |
+
"temporal_merges": [list(rule) for rule in self.temporal_merges],
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
@classmethod
|
| 229 |
+
def from_dict(
|
| 230 |
+
cls,
|
| 231 |
+
payload: object,
|
| 232 |
+
*,
|
| 233 |
+
bins: Sequence[int],
|
| 234 |
+
lookahead: int,
|
| 235 |
+
) -> SetBPESerializer:
|
| 236 |
+
if not isinstance(payload, Mapping) or not all(
|
| 237 |
+
isinstance(key, str) for key in payload
|
| 238 |
+
):
|
| 239 |
+
raise ConfigError(f"{BPE_NAME} must contain one JSON object")
|
| 240 |
+
allowed = {"spatial_merges", "temporal_merges"}
|
| 241 |
+
unknown = sorted(set(payload) - allowed)
|
| 242 |
+
if unknown:
|
| 243 |
+
raise ConfigError(
|
| 244 |
+
f"{BPE_NAME} contains unknown field(s): {', '.join(unknown)}"
|
| 245 |
+
)
|
| 246 |
+
missing = sorted(allowed - set(payload))
|
| 247 |
+
if missing:
|
| 248 |
+
raise ConfigError(f"{BPE_NAME} is missing field(s): {', '.join(missing)}")
|
| 249 |
+
return cls(
|
| 250 |
+
bins=bins,
|
| 251 |
+
lookahead=lookahead,
|
| 252 |
+
spatial_merges=payload["spatial_merges"],
|
| 253 |
+
temporal_merges=payload["temporal_merges"],
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
@classmethod
|
| 257 |
+
def from_pretrained(
|
| 258 |
+
cls,
|
| 259 |
+
directory: str | Path,
|
| 260 |
+
*,
|
| 261 |
+
bins: Sequence[int],
|
| 262 |
+
lookahead: int,
|
| 263 |
+
) -> SetBPESerializer:
|
| 264 |
+
return cls.from_dict(_read_payload(directory), bins=bins, lookahead=lookahead)
|
| 265 |
+
|
| 266 |
+
def save_pretrained(self, directory: str | Path) -> Path:
|
| 267 |
+
"""Write ``bpe.json`` and return its path."""
|
| 268 |
+
path = Path(directory) / BPE_NAME
|
| 269 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 270 |
+
path.write_text(json.dumps(self.to_dict(), indent=2) + "\n", encoding="utf-8")
|
| 271 |
+
return path
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
class BPESerializer:
|
| 275 |
+
"Lossless mapping between ``[T,D]`` physical cells and plain-BPE ids."
|
| 276 |
+
|
| 277 |
+
scheme = "bpe"
|
| 278 |
+
|
| 279 |
+
def __init__(
|
| 280 |
+
self,
|
| 281 |
+
*,
|
| 282 |
+
bins: Sequence[int],
|
| 283 |
+
lookahead: int,
|
| 284 |
+
merges: Sequence[Sequence[int]],
|
| 285 |
+
) -> None:
|
| 286 |
+
self.lookahead = _integer(lookahead, "lookahead", positive=True)
|
| 287 |
+
self.bins = tuple(
|
| 288 |
+
_integer(value, f"bins[{index}]", positive=True)
|
| 289 |
+
for index, value in enumerate(bins)
|
| 290 |
+
)
|
| 291 |
+
if not self.bins:
|
| 292 |
+
raise ConfigError("bins must contain at least one cell slot")
|
| 293 |
+
self.merges = _rules(merges, 2, "merges")
|
| 294 |
+
atom_count = sum(self.bins)
|
| 295 |
+
for rank, (left, right) in enumerate(self.merges):
|
| 296 |
+
child = atom_count + rank
|
| 297 |
+
if left >= child or right >= child:
|
| 298 |
+
raise ConfigError(
|
| 299 |
+
f"merges[{rank}] must reference already-created tokens"
|
| 300 |
+
)
|
| 301 |
+
try:
|
| 302 |
+
self.vocabulary = BPEVocabulary(self.bins, self.merges)
|
| 303 |
+
except ValueError as error:
|
| 304 |
+
raise ConfigError(str(error)) from error
|
| 305 |
+
self.max_token_cells = self.lookahead * len(self.bins)
|
| 306 |
+
self.token_cell_counts = self.vocabulary.token_cells
|
| 307 |
+
oversized = int(np.count_nonzero(self.token_cell_counts > self.max_token_cells))
|
| 308 |
+
if oversized:
|
| 309 |
+
raise ConfigError(
|
| 310 |
+
f"{oversized} merge rule(s) cover more than the {self.max_token_cells} "
|
| 311 |
+
"primitive cells allowed by lookahead"
|
| 312 |
+
)
|
| 313 |
+
self.vocab_size = self.vocabulary.vocab_size
|
| 314 |
+
|
| 315 |
+
def encode(
|
| 316 |
+
self,
|
| 317 |
+
cells: np.ndarray,
|
| 318 |
+
*,
|
| 319 |
+
active_dims: Sequence[int] | None = None,
|
| 320 |
+
) -> list[int]:
|
| 321 |
+
"Serialize one non-empty trajectory without emitting absent slots."
|
| 322 |
+
values, _ = _dense_cells(cells, self.bins, active_dims)
|
| 323 |
+
return self.vocabulary.encode(values)
|
| 324 |
+
|
| 325 |
+
def decode(
|
| 326 |
+
self,
|
| 327 |
+
tokens: Sequence[int],
|
| 328 |
+
*,
|
| 329 |
+
active_dims: Sequence[int] | None = None,
|
| 330 |
+
) -> np.ndarray:
|
| 331 |
+
"""Recover dense cells, writing ``-1`` in semantically absent slots."""
|
| 332 |
+
if len(tokens) < 1:
|
| 333 |
+
raise ValueError("token stream must not be empty")
|
| 334 |
+
checked = []
|
| 335 |
+
for index, token in enumerate(tokens):
|
| 336 |
+
if isinstance(token, bool) or not isinstance(token, Integral):
|
| 337 |
+
raise ValueError(f"tokens[{index}] must be an integer")
|
| 338 |
+
value = int(token)
|
| 339 |
+
if not 0 <= value < self.vocab_size:
|
| 340 |
+
raise ValueError(f"tokens[{index}] is outside the vocabulary")
|
| 341 |
+
checked.append(value)
|
| 342 |
+
return self.vocabulary.decode(checked, active_dims=active_dims)
|
| 343 |
+
|
| 344 |
+
def to_dict(self) -> dict[str, object]:
|
| 345 |
+
"""Return the intentionally minimal, path-independent BPE artifact."""
|
| 346 |
+
return {"merges": [list(rule) for rule in self.merges]}
|
| 347 |
+
|
| 348 |
+
@classmethod
|
| 349 |
+
def from_dict(
|
| 350 |
+
cls,
|
| 351 |
+
payload: object,
|
| 352 |
+
*,
|
| 353 |
+
bins: Sequence[int],
|
| 354 |
+
lookahead: int,
|
| 355 |
+
) -> BPESerializer:
|
| 356 |
+
if not isinstance(payload, Mapping) or not all(
|
| 357 |
+
isinstance(key, str) for key in payload
|
| 358 |
+
):
|
| 359 |
+
raise ConfigError(f"{BPE_NAME} must contain one JSON object")
|
| 360 |
+
unknown = sorted(set(payload) - {"merges"})
|
| 361 |
+
if unknown:
|
| 362 |
+
raise ConfigError(
|
| 363 |
+
f"{BPE_NAME} contains unknown field(s): {', '.join(unknown)}"
|
| 364 |
+
)
|
| 365 |
+
if "merges" not in payload:
|
| 366 |
+
raise ConfigError(f"{BPE_NAME} is missing field(s): merges")
|
| 367 |
+
return cls(bins=bins, lookahead=lookahead, merges=payload["merges"])
|
| 368 |
+
|
| 369 |
+
@classmethod
|
| 370 |
+
def from_pretrained(
|
| 371 |
+
cls,
|
| 372 |
+
directory: str | Path,
|
| 373 |
+
*,
|
| 374 |
+
bins: Sequence[int],
|
| 375 |
+
lookahead: int,
|
| 376 |
+
) -> BPESerializer:
|
| 377 |
+
return cls.from_dict(_read_payload(directory), bins=bins, lookahead=lookahead)
|
| 378 |
+
|
| 379 |
+
def save_pretrained(self, directory: str | Path) -> Path:
|
| 380 |
+
"""Write ``bpe.json`` and return its path."""
|
| 381 |
+
path = Path(directory) / BPE_NAME
|
| 382 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 383 |
+
path.write_text(json.dumps(self.to_dict(), indent=2) + "\n", encoding="utf-8")
|
| 384 |
+
return path
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
SERIALIZERS: dict[str, type[SetBPESerializer] | type[BPESerializer]] = {
|
| 388 |
+
SetBPESerializer.scheme: SetBPESerializer,
|
| 389 |
+
BPESerializer.scheme: BPESerializer,
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def serializer_class(
|
| 394 |
+
config: ActionCodec2Config,
|
| 395 |
+
) -> type[SetBPESerializer] | type[BPESerializer]:
|
| 396 |
+
"""Return the serializer the configured tokenization scheme requires."""
|
| 397 |
+
return SERIALIZERS[config.set_bpe.scheme]
|
runtime/tokenization/bpe_training.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Plain-BPE fitting implementations."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import heapq
|
| 6 |
+
from collections import defaultdict
|
| 7 |
+
from collections.abc import Iterable, Iterator, Sequence
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
from .bpe_vocabulary import BPEVocabulary
|
| 12 |
+
from ..backend.native_backend import require_native, resolve_backend
|
| 13 |
+
|
| 14 |
+
__all__ = ["train_bpe", "cell_chunks"]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def cell_chunks(
|
| 18 |
+
episodes: Iterable[np.ndarray],
|
| 19 |
+
*,
|
| 20 |
+
max_chunk_rows: int = 1_000_000,
|
| 21 |
+
) -> Iterator[tuple[np.ndarray, np.ndarray]]:
|
| 22 |
+
"Yield ``(cells, episode_offsets)`` blocks for the native trainer."
|
| 23 |
+
chunks = getattr(episodes, "chunks", None)
|
| 24 |
+
offsets = getattr(episodes, "offsets", None)
|
| 25 |
+
if getattr(episodes, "_actioncodec2_validated", False) and chunks is not None:
|
| 26 |
+
for index, (codes, bounds) in enumerate(zip(chunks, offsets, strict=True)):
|
| 27 |
+
if codes is None:
|
| 28 |
+
raise RuntimeError("cell corpus chunk has already been consumed")
|
| 29 |
+
yield (
|
| 30 |
+
np.ascontiguousarray(codes),
|
| 31 |
+
np.ascontiguousarray(bounds, dtype=np.int64),
|
| 32 |
+
)
|
| 33 |
+
episodes.release_chunk(index)
|
| 34 |
+
return
|
| 35 |
+
pending: list[np.ndarray] = []
|
| 36 |
+
lengths: list[int] = []
|
| 37 |
+
rows = 0
|
| 38 |
+
|
| 39 |
+
def flush() -> tuple[np.ndarray, np.ndarray]:
|
| 40 |
+
block = np.concatenate(pending, axis=0)
|
| 41 |
+
bounds = np.concatenate(([0], np.cumsum(lengths, dtype=np.int64)))
|
| 42 |
+
pending.clear()
|
| 43 |
+
lengths.clear()
|
| 44 |
+
return np.ascontiguousarray(block), bounds
|
| 45 |
+
|
| 46 |
+
for episode in episodes:
|
| 47 |
+
values = np.asarray(episode)
|
| 48 |
+
if values.dtype.kind not in {"i", "u"}:
|
| 49 |
+
raise ValueError("episodes must contain integer cell indices")
|
| 50 |
+
if values.ndim != 2 or len(values) < 1:
|
| 51 |
+
raise ValueError("every episode must have non-empty shape [T, D]")
|
| 52 |
+
pending.append(values)
|
| 53 |
+
lengths.append(len(values))
|
| 54 |
+
rows += len(values)
|
| 55 |
+
if rows >= max_chunk_rows:
|
| 56 |
+
yield flush()
|
| 57 |
+
rows = 0
|
| 58 |
+
if pending:
|
| 59 |
+
yield flush()
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _flatten(
|
| 63 |
+
chunks: Iterable[tuple[np.ndarray, np.ndarray]], bins: Sequence[int]
|
| 64 |
+
) -> tuple[list[list[int]], int]:
|
| 65 |
+
|
| 66 |
+
counts = np.asarray(tuple(int(value) for value in bins), dtype=np.int64)
|
| 67 |
+
offsets = np.concatenate(([0], np.cumsum(counts)))
|
| 68 |
+
streams: list[list[int]] = []
|
| 69 |
+
actions = 0
|
| 70 |
+
for codes, bounds in chunks:
|
| 71 |
+
values = np.asarray(codes, dtype=np.int64)
|
| 72 |
+
if values.ndim != 2 or values.shape[1] != len(counts):
|
| 73 |
+
raise ValueError("cells must have one column per bin count")
|
| 74 |
+
if np.any(values < -1) or np.any(values >= counts[None, :]):
|
| 75 |
+
raise ValueError("cells must contain -1 or an in-range cell index")
|
| 76 |
+
for start, stop in zip(bounds[:-1], bounds[1:], strict=True):
|
| 77 |
+
episode = values[int(start) : int(stop)]
|
| 78 |
+
if not len(episode):
|
| 79 |
+
raise ValueError("every episode must be non-empty")
|
| 80 |
+
support = episode >= 0
|
| 81 |
+
if np.any(support != support[:1]):
|
| 82 |
+
raise ValueError(
|
| 83 |
+
"active dimensions must remain constant within an episode"
|
| 84 |
+
)
|
| 85 |
+
present = np.flatnonzero(support[0])
|
| 86 |
+
if not len(present):
|
| 87 |
+
raise ValueError(
|
| 88 |
+
"every control step needs at least one active coordinate"
|
| 89 |
+
)
|
| 90 |
+
shifted = episode[:, present] + offsets[present][None, :]
|
| 91 |
+
streams.append(shifted.reshape(-1).tolist())
|
| 92 |
+
actions += len(episode)
|
| 93 |
+
if not streams:
|
| 94 |
+
raise ValueError("BPE training requires at least one complete trajectory")
|
| 95 |
+
return streams, actions
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _train_bpe_python(
|
| 99 |
+
chunks: Iterable[tuple[np.ndarray, np.ndarray]],
|
| 100 |
+
bins: Sequence[int],
|
| 101 |
+
*,
|
| 102 |
+
vocab_size: int,
|
| 103 |
+
max_token_cells: int,
|
| 104 |
+
progress: bool = False,
|
| 105 |
+
) -> tuple[BPEVocabulary, dict[str, object]]:
|
| 106 |
+
|
| 107 |
+
atoms = int(sum(int(value) for value in bins))
|
| 108 |
+
if vocab_size < atoms:
|
| 109 |
+
raise ValueError(
|
| 110 |
+
f"vocab_size {vocab_size} is below the {atoms} primitive atoms"
|
| 111 |
+
)
|
| 112 |
+
if max_token_cells < 1:
|
| 113 |
+
raise ValueError("max_token_cells must be positive")
|
| 114 |
+
streams, actions = _flatten(chunks, bins)
|
| 115 |
+
initial_length = sum(len(stream) for stream in streams)
|
| 116 |
+
length = initial_length
|
| 117 |
+
|
| 118 |
+
counts: dict[tuple[int, int], int] = defaultdict(int)
|
| 119 |
+
carriers: dict[tuple[int, int], set[int]] = defaultdict(set)
|
| 120 |
+
for index, stream in enumerate(streams):
|
| 121 |
+
for pair in zip(stream[:-1], stream[1:], strict=True):
|
| 122 |
+
counts[pair] += 1
|
| 123 |
+
carriers[pair].add(index)
|
| 124 |
+
heap = [(-value, pair) for pair, value in counts.items()]
|
| 125 |
+
heapq.heapify(heap)
|
| 126 |
+
|
| 127 |
+
token_cells = [1] * atoms
|
| 128 |
+
merges: list[tuple[int, int]] = []
|
| 129 |
+
oversized: set[tuple[int, int]] = set()
|
| 130 |
+
observed_cells = 1
|
| 131 |
+
while len(merges) + atoms < vocab_size and heap:
|
| 132 |
+
negative, pair = heapq.heappop(heap)
|
| 133 |
+
frequency = counts.get(pair, 0)
|
| 134 |
+
if frequency <= 0 or frequency != -negative or pair in oversized:
|
| 135 |
+
continue
|
| 136 |
+
span = token_cells[pair[0]] + token_cells[pair[1]]
|
| 137 |
+
if span > max_token_cells:
|
| 138 |
+
oversized.add(pair)
|
| 139 |
+
continue
|
| 140 |
+
child = atoms + len(merges)
|
| 141 |
+
token_cells.append(span)
|
| 142 |
+
observed_cells = max(observed_cells, span)
|
| 143 |
+
touched: set[tuple[int, int]] = set()
|
| 144 |
+
for index in sorted(carriers.pop(pair, ())):
|
| 145 |
+
stream = streams[index]
|
| 146 |
+
output: list[int] = []
|
| 147 |
+
position = 0
|
| 148 |
+
while position < len(stream):
|
| 149 |
+
if (
|
| 150 |
+
position + 1 < len(stream)
|
| 151 |
+
and stream[position] == pair[0]
|
| 152 |
+
and stream[position + 1] == pair[1]
|
| 153 |
+
):
|
| 154 |
+
if output:
|
| 155 |
+
counts[(output[-1], pair[0])] -= 1
|
| 156 |
+
touched.add((output[-1], pair[0]))
|
| 157 |
+
counts[(output[-1], child)] += 1
|
| 158 |
+
carriers[(output[-1], child)].add(index)
|
| 159 |
+
touched.add((output[-1], child))
|
| 160 |
+
if position + 2 < len(stream):
|
| 161 |
+
follower = stream[position + 2]
|
| 162 |
+
counts[(pair[1], follower)] -= 1
|
| 163 |
+
touched.add((pair[1], follower))
|
| 164 |
+
counts[(child, follower)] += 1
|
| 165 |
+
carriers[(child, follower)].add(index)
|
| 166 |
+
touched.add((child, follower))
|
| 167 |
+
counts[pair] -= 1
|
| 168 |
+
length -= 1
|
| 169 |
+
output.append(child)
|
| 170 |
+
position += 2
|
| 171 |
+
else:
|
| 172 |
+
output.append(stream[position])
|
| 173 |
+
position += 1
|
| 174 |
+
streams[index] = output
|
| 175 |
+
merges.append(pair)
|
| 176 |
+
for member in touched:
|
| 177 |
+
value = counts.get(member, 0)
|
| 178 |
+
if value > 0:
|
| 179 |
+
heapq.heappush(heap, (-value, member))
|
| 180 |
+
if progress and len(merges) % 256 == 0:
|
| 181 |
+
print(
|
| 182 |
+
f" bpe merge {len(merges):>5} tokens/action {length / actions:.4f}",
|
| 183 |
+
flush=True,
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
vocabulary = BPEVocabulary(tuple(int(value) for value in bins), tuple(merges))
|
| 187 |
+
report: dict[str, object] = {
|
| 188 |
+
"episodes": len(streams),
|
| 189 |
+
"actions": actions,
|
| 190 |
+
"atoms": atoms,
|
| 191 |
+
"merges": len(merges),
|
| 192 |
+
"vocab_size": vocabulary.vocab_size,
|
| 193 |
+
"cells": initial_length,
|
| 194 |
+
"tokens_per_action_initial": initial_length / actions,
|
| 195 |
+
"tokens_per_action": length / actions,
|
| 196 |
+
"compression": initial_length / length,
|
| 197 |
+
"max_token_cells": int(max_token_cells),
|
| 198 |
+
"max_token_cells_observed": observed_cells,
|
| 199 |
+
"oversized_pairs_skipped": len(oversized),
|
| 200 |
+
"trainer": "python_sparse_exact",
|
| 201 |
+
}
|
| 202 |
+
return vocabulary, report
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def train_bpe(
|
| 206 |
+
episodes: Iterable[np.ndarray],
|
| 207 |
+
bins: Sequence[int],
|
| 208 |
+
*,
|
| 209 |
+
vocab_size: int,
|
| 210 |
+
max_token_cells: int,
|
| 211 |
+
backend: str = "auto",
|
| 212 |
+
progress: bool = False,
|
| 213 |
+
threads: int = 0,
|
| 214 |
+
) -> tuple[BPEVocabulary, dict[str, object]]:
|
| 215 |
+
"Fit plain BPE on the row-major flattened primitive-cell stream."
|
| 216 |
+
if resolve_backend(backend) == "python":
|
| 217 |
+
return _train_bpe_python(
|
| 218 |
+
cell_chunks(episodes),
|
| 219 |
+
bins,
|
| 220 |
+
vocab_size=vocab_size,
|
| 221 |
+
max_token_cells=max_token_cells,
|
| 222 |
+
progress=progress,
|
| 223 |
+
)
|
| 224 |
+
merges, report = require_native().train_bpe(
|
| 225 |
+
cell_chunks(episodes),
|
| 226 |
+
[int(count) for count in bins],
|
| 227 |
+
int(vocab_size),
|
| 228 |
+
int(max_token_cells),
|
| 229 |
+
int(threads),
|
| 230 |
+
)
|
| 231 |
+
vocabulary = BPEVocabulary(
|
| 232 |
+
tuple(int(count) for count in bins),
|
| 233 |
+
tuple((int(left), int(right)) for left, right in merges),
|
| 234 |
+
)
|
| 235 |
+
return vocabulary, dict(report)
|
runtime/tokenization/bpe_vocabulary.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Runtime plain-BPE vocabulary over the row-major flattened cell stream."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import heapq
|
| 6 |
+
from collections.abc import Sequence
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
from ..common._validation import active_dimensions
|
| 12 |
+
|
| 13 |
+
__all__ = ["BPEVocabulary"]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class BPEVocabulary:
|
| 18 |
+
"""Primitive atoms plus ordered, adjacency-based merge rules."""
|
| 19 |
+
|
| 20 |
+
bins: tuple[int, ...]
|
| 21 |
+
merges: tuple[tuple[int, int], ...]
|
| 22 |
+
|
| 23 |
+
def __post_init__(self) -> None:
|
| 24 |
+
if not self.bins or any(count < 1 for count in self.bins):
|
| 25 |
+
raise ValueError("every dimension needs at least one bin")
|
| 26 |
+
self.offsets = np.concatenate(
|
| 27 |
+
([0], np.cumsum(np.asarray(self.bins, dtype=np.int64)))
|
| 28 |
+
)
|
| 29 |
+
self.atom_count = int(self.offsets[-1])
|
| 30 |
+
self.vocab_size = self.atom_count + len(self.merges)
|
| 31 |
+
atom_dims = np.repeat(
|
| 32 |
+
np.arange(len(self.bins), dtype=np.int64),
|
| 33 |
+
np.asarray(self.bins, dtype=np.int64),
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
expansions: list[tuple[int, ...]] = [
|
| 38 |
+
(token,) for token in range(self.atom_count)
|
| 39 |
+
]
|
| 40 |
+
for rank, (left, right) in enumerate(self.merges):
|
| 41 |
+
token = self.atom_count + rank
|
| 42 |
+
if not 0 <= left < token or not 0 <= right < token:
|
| 43 |
+
raise ValueError("a merge rule must reference already-created tokens")
|
| 44 |
+
expansions.append(expansions[left] + expansions[right])
|
| 45 |
+
self.expansions = tuple(expansions)
|
| 46 |
+
self.token_dims = tuple(
|
| 47 |
+
tuple(int(atom_dims[atom]) for atom in expansion)
|
| 48 |
+
for expansion in expansions
|
| 49 |
+
)
|
| 50 |
+
self.token_cells = np.asarray(
|
| 51 |
+
[len(expansion) for expansion in expansions], dtype=np.int64
|
| 52 |
+
)
|
| 53 |
+
self._rules = {
|
| 54 |
+
(int(left), int(right)): (self.atom_count + rank, rank)
|
| 55 |
+
for rank, (left, right) in enumerate(self.merges)
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
@property
|
| 59 |
+
def dimension(self) -> int:
|
| 60 |
+
return len(self.bins)
|
| 61 |
+
|
| 62 |
+
def atom(self, dimension: int, value: int) -> int:
|
| 63 |
+
return int(self.offsets[dimension]) + int(value)
|
| 64 |
+
|
| 65 |
+
def _flatten(self, values: np.ndarray, dimensions: tuple[int, ...]) -> list[int]:
|
| 66 |
+
|
| 67 |
+
counts = np.asarray(self.bins, dtype=np.int64)
|
| 68 |
+
if np.any(values < -1) or np.any(values >= counts[None, :]):
|
| 69 |
+
raise ValueError("cells contain an invalid active or absent index")
|
| 70 |
+
support = values >= 0
|
| 71 |
+
if np.any(support != support[:1]):
|
| 72 |
+
raise ValueError(
|
| 73 |
+
"active dimensions must remain constant within a trajectory"
|
| 74 |
+
)
|
| 75 |
+
present = tuple(int(dim) for dim in np.flatnonzero(support[0]))
|
| 76 |
+
if present != dimensions:
|
| 77 |
+
raise ValueError("cells do not activate exactly the requested dimensions")
|
| 78 |
+
if not present:
|
| 79 |
+
raise ValueError("every control step needs at least one active coordinate")
|
| 80 |
+
active = values[:, list(present)]
|
| 81 |
+
base = np.asarray([int(self.offsets[dim]) for dim in present], dtype=np.int64)
|
| 82 |
+
return (active + base[None, :]).reshape(-1).tolist()
|
| 83 |
+
|
| 84 |
+
def encode(self, cells: np.ndarray) -> list[int]:
|
| 85 |
+
"Apply the merges greedily in creation-rank order to one trajectory."
|
| 86 |
+
values = np.asarray(cells, dtype=np.int64)
|
| 87 |
+
if values.ndim != 2 or len(values) < 1 or values.shape[1] < 1:
|
| 88 |
+
raise ValueError("cells must have non-empty shape [T, D]")
|
| 89 |
+
dimensions = tuple(int(dim) for dim in np.flatnonzero(values[0] >= 0))
|
| 90 |
+
stream = self._flatten(values, dimensions)
|
| 91 |
+
count = len(stream)
|
| 92 |
+
previous = list(range(-1, count - 1))
|
| 93 |
+
following = list(range(1, count + 1))
|
| 94 |
+
following[-1] = -1
|
| 95 |
+
alive = [True] * count
|
| 96 |
+
rules = self._rules
|
| 97 |
+
heap: list[tuple[int, int]] = []
|
| 98 |
+
for index in range(count - 1):
|
| 99 |
+
rule = rules.get((stream[index], stream[index + 1]))
|
| 100 |
+
if rule is not None:
|
| 101 |
+
heap.append((rule[1], index))
|
| 102 |
+
heapq.heapify(heap)
|
| 103 |
+
while heap:
|
| 104 |
+
rank, index = heapq.heappop(heap)
|
| 105 |
+
if not alive[index]:
|
| 106 |
+
continue
|
| 107 |
+
follower = following[index]
|
| 108 |
+
if follower < 0 or not alive[follower]:
|
| 109 |
+
continue
|
| 110 |
+
rule = rules.get((stream[index], stream[follower]))
|
| 111 |
+
if rule is None or rule[1] != rank:
|
| 112 |
+
continue
|
| 113 |
+
stream[index] = rule[0]
|
| 114 |
+
alive[follower] = False
|
| 115 |
+
after = following[follower]
|
| 116 |
+
following[index] = after
|
| 117 |
+
if after >= 0:
|
| 118 |
+
previous[after] = index
|
| 119 |
+
successor = rules.get((stream[index], stream[after]))
|
| 120 |
+
if successor is not None:
|
| 121 |
+
heapq.heappush(heap, (successor[1], index))
|
| 122 |
+
before = previous[index]
|
| 123 |
+
if before >= 0:
|
| 124 |
+
predecessor = rules.get((stream[before], stream[index]))
|
| 125 |
+
if predecessor is not None:
|
| 126 |
+
heapq.heappush(heap, (predecessor[1], before))
|
| 127 |
+
return [stream[index] for index in range(count) if alive[index]]
|
| 128 |
+
|
| 129 |
+
def decode(
|
| 130 |
+
self,
|
| 131 |
+
tokens: Sequence[int],
|
| 132 |
+
*,
|
| 133 |
+
active_dims: Sequence[int] | None = None,
|
| 134 |
+
) -> np.ndarray:
|
| 135 |
+
"""Expand tokens to dense cells, writing ``-1`` in absent coordinates."""
|
| 136 |
+
dimensions = active_dimensions(active_dims, self.dimension)
|
| 137 |
+
atoms: list[int] = []
|
| 138 |
+
for index, raw_token in enumerate(tokens):
|
| 139 |
+
token = int(raw_token)
|
| 140 |
+
if not 0 <= token < self.vocab_size:
|
| 141 |
+
raise ValueError(f"tokens[{index}] is outside the vocabulary")
|
| 142 |
+
atoms.extend(self.expansions[token])
|
| 143 |
+
width = len(dimensions)
|
| 144 |
+
if not atoms or len(atoms) % width:
|
| 145 |
+
raise ValueError(
|
| 146 |
+
"token stream does not cover a whole number of routed control steps"
|
| 147 |
+
)
|
| 148 |
+
values = np.asarray(atoms, dtype=np.int64).reshape(-1, width)
|
| 149 |
+
starts = np.asarray(
|
| 150 |
+
[int(self.offsets[dim]) for dim in dimensions], dtype=np.int64
|
| 151 |
+
)
|
| 152 |
+
stops = np.asarray(
|
| 153 |
+
[int(self.offsets[dim + 1]) for dim in dimensions], dtype=np.int64
|
| 154 |
+
)
|
| 155 |
+
if np.any(values < starts[None, :]) or np.any(values >= stops[None, :]):
|
| 156 |
+
raise ValueError("token stream does not follow the routed dimension order")
|
| 157 |
+
cells = np.full((len(values), self.dimension), -1, dtype=np.int64)
|
| 158 |
+
cells[:, list(dimensions)] = values - starts[None, :]
|
| 159 |
+
return cells
|
runtime/tokenization/generation.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Explicit action-token constraints for Hugging Face generate()."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from transformers import LogitsProcessor
|
| 7 |
+
|
| 8 |
+
from ..routing.cache import BoundedCache
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ActionCodec2LogitsProcessor(LogitsProcessor):
|
| 12 |
+
"Constrain generated action IDs, then allow EOS only at complete coverage."
|
| 13 |
+
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
codec,
|
| 17 |
+
horizon,
|
| 18 |
+
*,
|
| 19 |
+
fps,
|
| 20 |
+
prompt_length,
|
| 21 |
+
eos_token_id,
|
| 22 |
+
token_offset=0,
|
| 23 |
+
pad_token_id=None,
|
| 24 |
+
):
|
| 25 |
+
for name, value in {
|
| 26 |
+
"prompt_length": prompt_length,
|
| 27 |
+
"eos_token_id": eos_token_id,
|
| 28 |
+
"token_offset": token_offset,
|
| 29 |
+
}.items():
|
| 30 |
+
if type(value) is not int or value < 0:
|
| 31 |
+
raise ValueError(f"{name} must be a nonnegative integer")
|
| 32 |
+
if pad_token_id is not None and (
|
| 33 |
+
type(pad_token_id) is not int or pad_token_id < 0
|
| 34 |
+
):
|
| 35 |
+
raise ValueError("pad_token_id must be a nonnegative integer")
|
| 36 |
+
self.grammar = codec.grammar(horizon, fps=fps)
|
| 37 |
+
self.prompt_length = prompt_length
|
| 38 |
+
self.eos_token_id = eos_token_id
|
| 39 |
+
self.pad_token_id = pad_token_id
|
| 40 |
+
self.token_offset = token_offset
|
| 41 |
+
stop = token_offset + self.grammar.vocab_size
|
| 42 |
+
if token_offset <= eos_token_id < stop:
|
| 43 |
+
raise ValueError("eos_token_id must be outside the action token interval")
|
| 44 |
+
if pad_token_id is not None and token_offset <= pad_token_id < stop:
|
| 45 |
+
raise ValueError("pad_token_id must be outside the action token interval")
|
| 46 |
+
self._mask_cache = BoundedCache(64)
|
| 47 |
+
|
| 48 |
+
def _state(self, row):
|
| 49 |
+
if self.eos_token_id in row:
|
| 50 |
+
end = row.index(self.eos_token_id)
|
| 51 |
+
if any(t not in (self.eos_token_id, self.pad_token_id) for t in row[end:]):
|
| 52 |
+
raise ValueError(
|
| 53 |
+
"only EOS/padding may follow completed action generation"
|
| 54 |
+
)
|
| 55 |
+
tokens = [t - self.token_offset for t in row[:end]]
|
| 56 |
+
if not self.grammar.is_complete(tokens):
|
| 57 |
+
raise ValueError("EOS occurred before action coverage was complete")
|
| 58 |
+
else:
|
| 59 |
+
tokens = [t - self.token_offset for t in row]
|
| 60 |
+
state = self.grammar._consume(tokens)
|
| 61 |
+
if state is None:
|
| 62 |
+
raise ValueError(
|
| 63 |
+
"generated prefix violates the action grammar; check prompt_length and token_offset"
|
| 64 |
+
)
|
| 65 |
+
return state
|
| 66 |
+
|
| 67 |
+
def _mask(self, state, scores):
|
| 68 |
+
key = (state, str(scores.device), scores.shape[-1])
|
| 69 |
+
mask = self._mask_cache.get(key)
|
| 70 |
+
if mask is None:
|
| 71 |
+
mask = torch.zeros(scores.shape[-1], device=scores.device, dtype=torch.bool)
|
| 72 |
+
index, local = state
|
| 73 |
+
if index == len(self.grammar.segments):
|
| 74 |
+
mask[self.eos_token_id] = True
|
| 75 |
+
else:
|
| 76 |
+
segment = self.grammar.segments[index]
|
| 77 |
+
allowed = self.grammar._grammars[index]._next_token_mask_from_state(
|
| 78 |
+
local
|
| 79 |
+
)
|
| 80 |
+
start = self.token_offset + segment.profile.spec.token_offset
|
| 81 |
+
mask[start : start + len(allowed)] = torch.as_tensor(
|
| 82 |
+
allowed, device=scores.device
|
| 83 |
+
)
|
| 84 |
+
self._mask_cache[key] = mask
|
| 85 |
+
return mask
|
| 86 |
+
|
| 87 |
+
def __call__(
|
| 88 |
+
self, input_ids: torch.LongTensor, scores: torch.FloatTensor
|
| 89 |
+
) -> torch.FloatTensor:
|
| 90 |
+
if (
|
| 91 |
+
input_ids.ndim != 2
|
| 92 |
+
or scores.ndim != 2
|
| 93 |
+
or input_ids.shape[0] != scores.shape[0]
|
| 94 |
+
):
|
| 95 |
+
raise ValueError(
|
| 96 |
+
"expected input_ids(B,L) and scores(B,V) with the same batch size"
|
| 97 |
+
)
|
| 98 |
+
if input_ids.shape[1] < self.prompt_length:
|
| 99 |
+
raise ValueError("prompt_length exceeds the supplied input length")
|
| 100 |
+
required = max(
|
| 101 |
+
self.token_offset + self.grammar.vocab_size,
|
| 102 |
+
self.eos_token_id + 1,
|
| 103 |
+
0 if self.pad_token_id is None else self.pad_token_id + 1,
|
| 104 |
+
)
|
| 105 |
+
if scores.shape[1] < required:
|
| 106 |
+
raise ValueError(
|
| 107 |
+
"model vocabulary does not cover configured action/EOS/padding IDs"
|
| 108 |
+
)
|
| 109 |
+
rows = input_ids[:, self.prompt_length :].detach().cpu().tolist()
|
| 110 |
+
output = scores.clone()
|
| 111 |
+
groups = {}
|
| 112 |
+
for index, row in enumerate(rows):
|
| 113 |
+
groups.setdefault(self._state(row), []).append(index)
|
| 114 |
+
for state, indices in groups.items():
|
| 115 |
+
output[indices] = scores[indices].masked_fill(
|
| 116 |
+
~self._mask(state, scores), -torch.inf
|
| 117 |
+
)
|
| 118 |
+
if not torch.isfinite(output).any(dim=1).all():
|
| 119 |
+
raise ValueError(
|
| 120 |
+
"all legal action logits were suppressed; check other generation processors"
|
| 121 |
+
)
|
| 122 |
+
return output
|
runtime/tokenization/grammar.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Constrained-generation grammars for the two ActionCodec2 BPE schemes."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from collections.abc import Sequence
|
| 6 |
+
from numbers import Integral
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from ..common._validation import active_dimensions
|
| 11 |
+
from .bpe import BPESerializer, SetBPESerializer
|
| 12 |
+
from ..routing.cache import BoundedCache
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _PrefixGrammar:
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def next_token_mask(self, tokens: Sequence[int]) -> np.ndarray:
|
| 19 |
+
"""Return a boolean vocabulary mask for the next generated token."""
|
| 20 |
+
state = self._state(tokens)
|
| 21 |
+
if state is None:
|
| 22 |
+
raise ValueError("token prefix is not a legal ActionCodec2 stream")
|
| 23 |
+
return self._next_token_mask_from_state(state)
|
| 24 |
+
|
| 25 |
+
def is_complete(self, tokens: Sequence[int]) -> bool:
|
| 26 |
+
"""Whether ``tokens`` is exactly one complete trajectory of this horizon."""
|
| 27 |
+
state = self._state(tokens)
|
| 28 |
+
return state is not None and self._state_is_complete(state)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class ActionCodec2Grammar(_PrefixGrammar):
|
| 32 |
+
"Prefix grammar for one exact, fixed-horizon rooted token stream."
|
| 33 |
+
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
serializer: SetBPESerializer,
|
| 37 |
+
*,
|
| 38 |
+
horizon: int,
|
| 39 |
+
active_dims: Sequence[int] | None = None,
|
| 40 |
+
) -> None:
|
| 41 |
+
if isinstance(horizon, bool) or not isinstance(horizon, int) or horizon < 1:
|
| 42 |
+
raise ValueError("horizon must be a positive integer")
|
| 43 |
+
self.horizon = horizon
|
| 44 |
+
self.vocab_size = serializer.vocab_size
|
| 45 |
+
dims = active_dimensions(active_dims, serializer.spatial.dimension)
|
| 46 |
+
self._full = sum(1 << dimension for dimension in dims)
|
| 47 |
+
self._root_anchor = np.asarray(
|
| 48 |
+
[
|
| 49 |
+
serializer.spatial.anchor[serializer.root[token]]
|
| 50 |
+
for token in range(self.vocab_size)
|
| 51 |
+
],
|
| 52 |
+
dtype=np.int64,
|
| 53 |
+
)
|
| 54 |
+
self._patterns = tuple(
|
| 55 |
+
tuple(
|
| 56 |
+
(offset, int(serializer.spatial.support[spatial_token]))
|
| 57 |
+
for offset, spatial_token in serializer.patterns[token]
|
| 58 |
+
)
|
| 59 |
+
for token in range(self.vocab_size)
|
| 60 |
+
)
|
| 61 |
+
self._mask_cache = BoundedCache(64)
|
| 62 |
+
self._state_cache = BoundedCache(256)
|
| 63 |
+
self._state_cache[()] = self._initial_state()
|
| 64 |
+
|
| 65 |
+
def _advance(self, state: tuple[int, ...], token: int) -> tuple[int, ...] | None:
|
| 66 |
+
if not 0 <= token < self.vocab_size:
|
| 67 |
+
return None
|
| 68 |
+
coverage = list(state)
|
| 69 |
+
front = next(
|
| 70 |
+
(index for index, mask in enumerate(coverage) if mask != self._full),
|
| 71 |
+
self.horizon,
|
| 72 |
+
)
|
| 73 |
+
if front == self.horizon:
|
| 74 |
+
return None
|
| 75 |
+
uncovered = self._full & ~coverage[front]
|
| 76 |
+
expected_anchor = int(uncovered & -uncovered).bit_length() - 1
|
| 77 |
+
if int(self._root_anchor[token]) != expected_anchor:
|
| 78 |
+
return None
|
| 79 |
+
for offset, support in self._patterns[token]:
|
| 80 |
+
step = front + offset
|
| 81 |
+
if step >= self.horizon or coverage[step] & support:
|
| 82 |
+
return None
|
| 83 |
+
coverage[step] |= support
|
| 84 |
+
return tuple(coverage)
|
| 85 |
+
|
| 86 |
+
def _initial_state(self) -> tuple[int, ...]:
|
| 87 |
+
|
| 88 |
+
return (0,) * self.horizon
|
| 89 |
+
|
| 90 |
+
def _state_is_complete(self, state: tuple[int, ...]) -> bool:
|
| 91 |
+
|
| 92 |
+
return all(mask == self._full for mask in state)
|
| 93 |
+
|
| 94 |
+
def _state(self, tokens: Sequence[int]) -> tuple[int, ...] | None:
|
| 95 |
+
checked: list[int] = []
|
| 96 |
+
for raw_token in tokens:
|
| 97 |
+
if isinstance(raw_token, bool) or not isinstance(raw_token, Integral):
|
| 98 |
+
return None
|
| 99 |
+
checked.append(int(raw_token))
|
| 100 |
+
key = tuple(checked)
|
| 101 |
+
if not key:
|
| 102 |
+
return self._initial_state()
|
| 103 |
+
missing = object()
|
| 104 |
+
cached = self._state_cache.get(key, missing)
|
| 105 |
+
if cached is not missing:
|
| 106 |
+
return cached
|
| 107 |
+
parent = key[:-1]
|
| 108 |
+
state = self._state_cache.get(parent, missing)
|
| 109 |
+
if state is missing:
|
| 110 |
+
state = self._initial_state()
|
| 111 |
+
for token in parent:
|
| 112 |
+
state = self._advance(state, token)
|
| 113 |
+
if state is None:
|
| 114 |
+
break
|
| 115 |
+
self._state_cache[parent] = state
|
| 116 |
+
if state is not None:
|
| 117 |
+
state = self._advance(state, key[-1])
|
| 118 |
+
self._state_cache[key] = state
|
| 119 |
+
return state
|
| 120 |
+
|
| 121 |
+
def _next_token_mask_from_state(self, state: tuple[int, ...]) -> np.ndarray:
|
| 122 |
+
|
| 123 |
+
if self._state_is_complete(state):
|
| 124 |
+
return np.zeros(self.vocab_size, dtype=bool)
|
| 125 |
+
cached = self._mask_cache.get(state)
|
| 126 |
+
if cached is not None:
|
| 127 |
+
return cached.copy()
|
| 128 |
+
front = next(index for index, mask in enumerate(state) if mask != self._full)
|
| 129 |
+
uncovered = self._full & ~state[front]
|
| 130 |
+
expected_anchor = int(uncovered & -uncovered).bit_length() - 1
|
| 131 |
+
mask = self._root_anchor == expected_anchor
|
| 132 |
+
for token in np.flatnonzero(mask):
|
| 133 |
+
for offset, support in self._patterns[int(token)]:
|
| 134 |
+
step = front + offset
|
| 135 |
+
if step >= self.horizon or state[step] & support:
|
| 136 |
+
mask[token] = False
|
| 137 |
+
break
|
| 138 |
+
self._mask_cache[state] = mask
|
| 139 |
+
return mask.copy()
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class ActionCodec2BPEGrammar(_PrefixGrammar):
|
| 143 |
+
"Prefix grammar for one exact, fixed-horizon plain-BPE token stream."
|
| 144 |
+
|
| 145 |
+
def __init__(
|
| 146 |
+
self,
|
| 147 |
+
serializer: BPESerializer,
|
| 148 |
+
*,
|
| 149 |
+
horizon: int,
|
| 150 |
+
active_dims: Sequence[int] | None = None,
|
| 151 |
+
) -> None:
|
| 152 |
+
if isinstance(horizon, bool) or not isinstance(horizon, int) or horizon < 1:
|
| 153 |
+
raise ValueError("horizon must be a positive integer")
|
| 154 |
+
self.horizon = horizon
|
| 155 |
+
self.vocab_size = serializer.vocab_size
|
| 156 |
+
dims = active_dimensions(active_dims, serializer.vocabulary.dimension)
|
| 157 |
+
self._width = len(dims)
|
| 158 |
+
self._cells = horizon * self._width
|
| 159 |
+
self._token_cells = np.asarray(serializer.token_cell_counts, dtype=np.int64)
|
| 160 |
+
token_dims = serializer.vocabulary.token_dims
|
| 161 |
+
self._phase = np.zeros((self._width, self.vocab_size), dtype=bool)
|
| 162 |
+
for phase in range(self._width):
|
| 163 |
+
for token in range(self.vocab_size):
|
| 164 |
+
expansion = token_dims[token]
|
| 165 |
+
self._phase[phase, token] = all(
|
| 166 |
+
dimension == dims[(phase + offset) % self._width]
|
| 167 |
+
for offset, dimension in enumerate(expansion)
|
| 168 |
+
)
|
| 169 |
+
self._mask_cache = BoundedCache(64)
|
| 170 |
+
|
| 171 |
+
def _initial_state(self) -> tuple[int, ...]:
|
| 172 |
+
return (0,)
|
| 173 |
+
|
| 174 |
+
def _state_is_complete(self, state: tuple[int, ...]) -> bool:
|
| 175 |
+
return state[0] == self._cells
|
| 176 |
+
|
| 177 |
+
def _advance(self, state: tuple[int, ...], token: int) -> tuple[int, ...] | None:
|
| 178 |
+
if not 0 <= token < self.vocab_size:
|
| 179 |
+
return None
|
| 180 |
+
covered = state[0]
|
| 181 |
+
length = int(self._token_cells[token])
|
| 182 |
+
if covered + length > self._cells:
|
| 183 |
+
return None
|
| 184 |
+
if not self._phase[covered % self._width, token]:
|
| 185 |
+
return None
|
| 186 |
+
return (covered + length,)
|
| 187 |
+
|
| 188 |
+
def _state(self, tokens: Sequence[int]) -> tuple[int, ...] | None:
|
| 189 |
+
state: tuple[int, ...] | None = self._initial_state()
|
| 190 |
+
for raw_token in tokens:
|
| 191 |
+
if isinstance(raw_token, bool) or not isinstance(raw_token, Integral):
|
| 192 |
+
return None
|
| 193 |
+
assert state is not None
|
| 194 |
+
state = self._advance(state, int(raw_token))
|
| 195 |
+
if state is None:
|
| 196 |
+
return None
|
| 197 |
+
return state
|
| 198 |
+
|
| 199 |
+
def _next_token_mask_from_state(self, state: tuple[int, ...]) -> np.ndarray:
|
| 200 |
+
covered = state[0]
|
| 201 |
+
if covered == self._cells:
|
| 202 |
+
return np.zeros(self.vocab_size, dtype=bool)
|
| 203 |
+
cached = self._mask_cache.get(covered)
|
| 204 |
+
if cached is None:
|
| 205 |
+
cached = self._phase[covered % self._width] & (
|
| 206 |
+
self._token_cells <= self._cells - covered
|
| 207 |
+
)
|
| 208 |
+
self._mask_cache[covered] = cached
|
| 209 |
+
return cached.copy()
|
runtime/tokenization/setbpe_spatial.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Runtime spatial Set-BPE vocabulary and lossless row serialization."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import heapq
|
| 6 |
+
from collections.abc import Iterable, Sequence
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
from ..common._validation import active_dimensions
|
| 12 |
+
|
| 13 |
+
__all__ = ["SpatialVocabulary", "aggregate_records"]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def aggregate_records(
|
| 17 |
+
codes: np.ndarray, bins: Sequence[int] | None = None
|
| 18 |
+
) -> tuple[np.ndarray, np.ndarray]:
|
| 19 |
+
"Lossless micro-aggregation of identical quantized actions."
|
| 20 |
+
values = np.asarray(codes, dtype=np.int64)
|
| 21 |
+
if values.ndim != 2:
|
| 22 |
+
raise ValueError("codes must have shape [U, D]")
|
| 23 |
+
if bins is not None:
|
| 24 |
+
dimensions = tuple(int(count) for count in bins)
|
| 25 |
+
if len(dimensions) != values.shape[1]:
|
| 26 |
+
raise ValueError("bins must have one entry per code coordinate")
|
| 27 |
+
counts = np.asarray(dimensions, dtype=np.int64)
|
| 28 |
+
valid = values >= 0
|
| 29 |
+
if np.any(values < -1) or np.any(values >= counts[None, :]):
|
| 30 |
+
raise ValueError("codes must contain -1 or an in-range cell index")
|
| 31 |
+
cardinality = int(np.prod(np.asarray(dimensions, dtype=object)))
|
| 32 |
+
if np.all(valid) and cardinality <= np.iinfo(np.int64).max:
|
| 33 |
+
keys = np.ravel_multi_index(values.T, dimensions)
|
| 34 |
+
unique_keys, counts = np.unique(keys, return_counts=True)
|
| 35 |
+
unique = np.stack(np.unravel_index(unique_keys, dimensions), axis=1)
|
| 36 |
+
return unique.astype(np.int64, copy=False), counts.astype(np.int64)
|
| 37 |
+
unique, counts = np.unique(values, axis=0, return_counts=True)
|
| 38 |
+
return unique, counts.astype(np.int64)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class SpatialVocabulary:
|
| 43 |
+
"""Atoms, merge rules and the decoder metadata they induce."""
|
| 44 |
+
|
| 45 |
+
bins: tuple[int, ...]
|
| 46 |
+
merges: tuple[tuple[int, int], ...]
|
| 47 |
+
|
| 48 |
+
def __post_init__(self) -> None:
|
| 49 |
+
if not self.bins or any(count < 1 for count in self.bins):
|
| 50 |
+
raise ValueError("every dimension needs at least one bin")
|
| 51 |
+
self.offsets = np.concatenate(
|
| 52 |
+
([0], np.cumsum(np.asarray(self.bins, dtype=np.int64)))
|
| 53 |
+
)
|
| 54 |
+
self.atom_count = int(self.offsets[-1])
|
| 55 |
+
self.vocab_size = self.atom_count + len(self.merges)
|
| 56 |
+
dimension = len(self.bins)
|
| 57 |
+
support = np.zeros(self.vocab_size, dtype=np.int64)
|
| 58 |
+
expansion = np.full((self.vocab_size, dimension), -1, dtype=np.int64)
|
| 59 |
+
for index, count in enumerate(self.bins):
|
| 60 |
+
for value in range(count):
|
| 61 |
+
token = int(self.offsets[index]) + value
|
| 62 |
+
support[token] = 1 << index
|
| 63 |
+
expansion[token, index] = value
|
| 64 |
+
for rank, (left, right) in enumerate(self.merges):
|
| 65 |
+
token = self.atom_count + rank
|
| 66 |
+
if left >= token or right >= token:
|
| 67 |
+
raise ValueError("a merge rule must reference already-created tokens")
|
| 68 |
+
if support[left] & support[right]:
|
| 69 |
+
raise ValueError("a merge rule joins two overlapping supports")
|
| 70 |
+
support[token] = support[left] | support[right]
|
| 71 |
+
merged = np.maximum(expansion[left], expansion[right])
|
| 72 |
+
expansion[token] = merged
|
| 73 |
+
self.support = support
|
| 74 |
+
self.expansion = expansion
|
| 75 |
+
self.anchor = np.asarray(
|
| 76 |
+
[int(mask & -mask).bit_length() - 1 for mask in support], dtype=np.int64
|
| 77 |
+
)
|
| 78 |
+
self.full_support = (1 << dimension) - 1
|
| 79 |
+
self._rules: dict[tuple[int, int], tuple[int, int]] = {}
|
| 80 |
+
for rank, (left, right) in enumerate(self.merges):
|
| 81 |
+
key = (min(left, right), max(left, right))
|
| 82 |
+
self._rules[key] = (self.atom_count + rank, rank)
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def dimension(self) -> int:
|
| 86 |
+
return len(self.bins)
|
| 87 |
+
|
| 88 |
+
def atom(self, dimension: int, value: int) -> int:
|
| 89 |
+
return int(self.offsets[dimension]) + int(value)
|
| 90 |
+
|
| 91 |
+
def encode(
|
| 92 |
+
self,
|
| 93 |
+
code: Sequence[int],
|
| 94 |
+
*,
|
| 95 |
+
active_dims: Sequence[int] | None = None,
|
| 96 |
+
) -> list[int]:
|
| 97 |
+
"Deterministic online Set-BPE encoding (Algorithm 8)."
|
| 98 |
+
compact = [int(value) for value in code]
|
| 99 |
+
if active_dims is None:
|
| 100 |
+
if len(compact) != self.dimension:
|
| 101 |
+
raise ValueError(
|
| 102 |
+
f"a dense quantized action must have {self.dimension} slots"
|
| 103 |
+
)
|
| 104 |
+
pairs = enumerate(compact)
|
| 105 |
+
else:
|
| 106 |
+
dimensions = active_dimensions(active_dims, self.dimension)
|
| 107 |
+
if len(dimensions) != len(compact):
|
| 108 |
+
raise ValueError(
|
| 109 |
+
"active_dims must have one entry per compact coordinate"
|
| 110 |
+
)
|
| 111 |
+
pairs = zip(dimensions, compact, strict=True)
|
| 112 |
+
tokens = set()
|
| 113 |
+
for index, value in pairs:
|
| 114 |
+
if value == -1:
|
| 115 |
+
if active_dims is not None:
|
| 116 |
+
raise ValueError("compact coordinates must not contain -1")
|
| 117 |
+
continue
|
| 118 |
+
if not 0 <= value < self.bins[index]:
|
| 119 |
+
raise ValueError(f"bin {value} is out of range for dimension {index}")
|
| 120 |
+
tokens.add(self.atom(index, value))
|
| 121 |
+
if not tokens:
|
| 122 |
+
raise ValueError(
|
| 123 |
+
"a quantized action must contain at least one active coordinate"
|
| 124 |
+
)
|
| 125 |
+
heap: list[tuple[int, int, int, int]] = []
|
| 126 |
+
ordered = sorted(tokens)
|
| 127 |
+
for position, left in enumerate(ordered):
|
| 128 |
+
for right in ordered[position + 1 :]:
|
| 129 |
+
rule = self._rules.get((left, right))
|
| 130 |
+
if rule is not None:
|
| 131 |
+
heapq.heappush(heap, (rule[1], rule[0], left, right))
|
| 132 |
+
while heap:
|
| 133 |
+
_, child, left, right = heapq.heappop(heap)
|
| 134 |
+
if left not in tokens or right not in tokens:
|
| 135 |
+
continue
|
| 136 |
+
tokens.discard(left)
|
| 137 |
+
tokens.discard(right)
|
| 138 |
+
for other in tokens:
|
| 139 |
+
key = (min(child, other), max(child, other))
|
| 140 |
+
rule = self._rules.get(key)
|
| 141 |
+
if rule is not None:
|
| 142 |
+
heapq.heappush(heap, (rule[1], rule[0], key[0], key[1]))
|
| 143 |
+
tokens.add(child)
|
| 144 |
+
return self.serialize(tokens)
|
| 145 |
+
|
| 146 |
+
def serialize(self, tokens: Iterable[int]) -> list[int]:
|
| 147 |
+
"Canonical order for autoregressive supervision."
|
| 148 |
+
return sorted(tokens, key=lambda token: (int(self.anchor[token]), int(token)))
|
| 149 |
+
|
| 150 |
+
def decode(
|
| 151 |
+
self,
|
| 152 |
+
tokens: Sequence[int],
|
| 153 |
+
*,
|
| 154 |
+
active_dims: Sequence[int] | None = None,
|
| 155 |
+
) -> np.ndarray:
|
| 156 |
+
"""Expand tokens to a dense row, using ``-1`` for absent coordinates."""
|
| 157 |
+
code = np.full(self.dimension, -1, dtype=np.int64)
|
| 158 |
+
covered = 0
|
| 159 |
+
for token in tokens:
|
| 160 |
+
value = int(token)
|
| 161 |
+
if not 0 <= value < self.vocab_size:
|
| 162 |
+
raise ValueError(f"token {value} is outside the vocabulary")
|
| 163 |
+
if covered & int(self.support[value]):
|
| 164 |
+
raise ValueError("token supports overlap; this is not a valid action")
|
| 165 |
+
covered |= int(self.support[value])
|
| 166 |
+
local = self.expansion[value]
|
| 167 |
+
code = np.where(local >= 0, local, code)
|
| 168 |
+
if active_dims is None:
|
| 169 |
+
expected = self.full_support
|
| 170 |
+
else:
|
| 171 |
+
dimensions = active_dimensions(active_dims, self.dimension)
|
| 172 |
+
expected = sum(1 << dimension for dimension in dimensions)
|
| 173 |
+
if covered != expected:
|
| 174 |
+
raise ValueError(
|
| 175 |
+
"token set does not exactly cover the requested active dimensions"
|
| 176 |
+
)
|
| 177 |
+
return code
|
runtime/tokenization/setbpe_spatial_training.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Spatial Set-BPE fitting implementations."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import heapq
|
| 6 |
+
from collections.abc import Sequence
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from ..backend.native_backend import require_native, resolve_backend
|
| 11 |
+
from .setbpe_spatial import SpatialVocabulary
|
| 12 |
+
|
| 13 |
+
__all__ = ["train_spatial"]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class _SparseSpatialTrainer:
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
codes: np.ndarray,
|
| 22 |
+
weights: np.ndarray,
|
| 23 |
+
bins: Sequence[int],
|
| 24 |
+
) -> None:
|
| 25 |
+
self.bins = tuple(int(value) for value in bins)
|
| 26 |
+
self.dimension = len(self.bins)
|
| 27 |
+
self.records = np.asarray(codes, dtype=np.int64)
|
| 28 |
+
self.weights = np.asarray(weights, dtype=np.int64)
|
| 29 |
+
if self.records.ndim != 2 or self.records.shape[1] != self.dimension:
|
| 30 |
+
raise ValueError("codes must have shape [U, D] matching the bin counts")
|
| 31 |
+
if self.weights.shape != (self.records.shape[0],):
|
| 32 |
+
raise ValueError("weights must have one entry per record")
|
| 33 |
+
counts = np.asarray(self.bins, dtype=np.int64)
|
| 34 |
+
if np.any(self.records < -1) or np.any(self.records >= counts[None, :]):
|
| 35 |
+
raise ValueError("codes must contain -1 or an in-range cell index")
|
| 36 |
+
if np.any(np.all(self.records < 0, axis=1)):
|
| 37 |
+
raise ValueError("every record must contain at least one active coordinate")
|
| 38 |
+
self.count = int(self.records.shape[0])
|
| 39 |
+
self.offsets = np.concatenate(
|
| 40 |
+
([0], np.cumsum(np.asarray(self.bins, dtype=np.int64)))
|
| 41 |
+
)
|
| 42 |
+
self.atom_count = int(self.offsets[-1])
|
| 43 |
+
|
| 44 |
+
self.occurrence: list[np.ndarray] = []
|
| 45 |
+
self.support: list[int] = []
|
| 46 |
+
self.anchor: list[int] = []
|
| 47 |
+
self.frequency: list[int] = []
|
| 48 |
+
for index in range(self.dimension):
|
| 49 |
+
column = self.records[:, index]
|
| 50 |
+
for value in range(self.bins[index]):
|
| 51 |
+
members = np.flatnonzero(column == value).astype(np.int64)
|
| 52 |
+
self._append_token(members, 1 << index, index)
|
| 53 |
+
self._scratch = np.zeros(self.count, dtype=bool)
|
| 54 |
+
self.merges: list[tuple[int, int]] = []
|
| 55 |
+
self.statistics = {
|
| 56 |
+
"sparse_updates": 0,
|
| 57 |
+
"sparse_heap_rebuilds": 0,
|
| 58 |
+
"sparse_pair_types_peak": 0,
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
self.member = np.where(
|
| 62 |
+
self.records >= 0,
|
| 63 |
+
self.offsets[:-1][None, :] + self.records,
|
| 64 |
+
-1,
|
| 65 |
+
)
|
| 66 |
+
self._slot_pairs = tuple(
|
| 67 |
+
(first, second)
|
| 68 |
+
for first in range(self.dimension)
|
| 69 |
+
for second in range(first + 1, self.dimension)
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
self._sparse_counts: dict[int, int] | None = None
|
| 74 |
+
self._sparse_heap: list[tuple[int, int, int]] = []
|
| 75 |
+
|
| 76 |
+
def _append_token(self, members: np.ndarray, support: int, anchor: int) -> int:
|
| 77 |
+
token = len(self.occurrence)
|
| 78 |
+
self.occurrence.append(members)
|
| 79 |
+
self.support.append(support)
|
| 80 |
+
self.anchor.append(anchor)
|
| 81 |
+
self.frequency.append(int(self.weights[members].sum()) if members.size else 0)
|
| 82 |
+
return token
|
| 83 |
+
|
| 84 |
+
def _intersect(self, left: np.ndarray, right: np.ndarray) -> np.ndarray:
|
| 85 |
+
if left.size > right.size:
|
| 86 |
+
left, right = right, left
|
| 87 |
+
if left.size == 0:
|
| 88 |
+
return left
|
| 89 |
+
self._scratch[left] = True
|
| 90 |
+
result = right[self._scratch[right]]
|
| 91 |
+
self._scratch[left] = False
|
| 92 |
+
return result
|
| 93 |
+
|
| 94 |
+
@staticmethod
|
| 95 |
+
def _sparse_key(left: int, right: int) -> int:
|
| 96 |
+
low, high = (left, right) if left < right else (right, left)
|
| 97 |
+
return (int(low) << 32) | int(high)
|
| 98 |
+
|
| 99 |
+
@staticmethod
|
| 100 |
+
def _sparse_pair(key: int) -> tuple[int, int]:
|
| 101 |
+
return key >> 32, key & 0xFFFFFFFF
|
| 102 |
+
|
| 103 |
+
def build_sparse_index(self) -> None:
|
| 104 |
+
"""Materialize exact counters only for currently observed pairs once."""
|
| 105 |
+
if self._sparse_counts is not None:
|
| 106 |
+
return
|
| 107 |
+
width = len(self.occurrence)
|
| 108 |
+
counts = np.zeros(width * width, dtype=np.int64)
|
| 109 |
+
for first, second in self._slot_pairs:
|
| 110 |
+
left = self.member[:, first]
|
| 111 |
+
right = self.member[:, second]
|
| 112 |
+
valid = (left >= 0) & (right >= 0)
|
| 113 |
+
if not valid.any():
|
| 114 |
+
continue
|
| 115 |
+
low = np.minimum(left[valid], right[valid])
|
| 116 |
+
high = np.maximum(left[valid], right[valid])
|
| 117 |
+
np.add.at(counts, low * width + high, self.weights[valid])
|
| 118 |
+
observed = np.flatnonzero(counts)
|
| 119 |
+
sparse: dict[int, int] = {}
|
| 120 |
+
heap: list[tuple[int, int, int]] = []
|
| 121 |
+
for packed in observed:
|
| 122 |
+
left, right = divmod(int(packed), width)
|
| 123 |
+
key = self._sparse_key(left, right)
|
| 124 |
+
value = int(counts[packed])
|
| 125 |
+
sparse[key] = value
|
| 126 |
+
heap.append((-value, left, right))
|
| 127 |
+
heapq.heapify(heap)
|
| 128 |
+
self._sparse_counts = sparse
|
| 129 |
+
self._sparse_heap = heap
|
| 130 |
+
self.statistics["sparse_pair_types_peak"] = len(sparse)
|
| 131 |
+
|
| 132 |
+
def sparse_best_pair(self) -> tuple[int, int, int] | None:
|
| 133 |
+
"""Return the exact winner from the observed-pair max heap."""
|
| 134 |
+
if self._sparse_counts is None:
|
| 135 |
+
self.build_sparse_index()
|
| 136 |
+
assert self._sparse_counts is not None
|
| 137 |
+
while self._sparse_heap:
|
| 138 |
+
negative, left, right = heapq.heappop(self._sparse_heap)
|
| 139 |
+
value = self._sparse_counts.get(self._sparse_key(left, right), 0)
|
| 140 |
+
if value == -negative:
|
| 141 |
+
return left, right, value
|
| 142 |
+
return None
|
| 143 |
+
|
| 144 |
+
def _update_sparse_index(
|
| 145 |
+
self,
|
| 146 |
+
left: int,
|
| 147 |
+
right: int,
|
| 148 |
+
child: int,
|
| 149 |
+
rows: np.ndarray,
|
| 150 |
+
members: np.ndarray,
|
| 151 |
+
mass: int,
|
| 152 |
+
) -> None:
|
| 153 |
+
if self._sparse_counts is None:
|
| 154 |
+
return
|
| 155 |
+
counts = self._sparse_counts
|
| 156 |
+
heap = self._sparse_heap
|
| 157 |
+
updates = 0
|
| 158 |
+
|
| 159 |
+
def change(key: int, delta: int) -> None:
|
| 160 |
+
nonlocal updates
|
| 161 |
+
new = counts.get(key, 0) + delta
|
| 162 |
+
if new < 0:
|
| 163 |
+
pair = self._sparse_pair(key)
|
| 164 |
+
raise RuntimeError(
|
| 165 |
+
f"sparse spatial pair counter became negative for {pair}"
|
| 166 |
+
)
|
| 167 |
+
if new:
|
| 168 |
+
counts[key] = new
|
| 169 |
+
pair = self._sparse_pair(key)
|
| 170 |
+
heapq.heappush(heap, (-new, pair[0], pair[1]))
|
| 171 |
+
else:
|
| 172 |
+
counts.pop(key, None)
|
| 173 |
+
updates += 1
|
| 174 |
+
|
| 175 |
+
change(self._sparse_key(left, right), -mass)
|
| 176 |
+
other_mass = np.zeros(len(self.occurrence), dtype=np.int64)
|
| 177 |
+
weights = self.weights[members]
|
| 178 |
+
for slot in range(rows.shape[1]):
|
| 179 |
+
values = rows[:, slot]
|
| 180 |
+
valid = (values >= 0) & (values != left) & (values != right)
|
| 181 |
+
if valid.any():
|
| 182 |
+
np.add.at(other_mass, values[valid], weights[valid])
|
| 183 |
+
for raw_other in np.flatnonzero(other_mass):
|
| 184 |
+
other = int(raw_other)
|
| 185 |
+
value = int(other_mass[other])
|
| 186 |
+
change(self._sparse_key(left, other), -value)
|
| 187 |
+
change(self._sparse_key(right, other), -value)
|
| 188 |
+
change(self._sparse_key(child, other), value)
|
| 189 |
+
|
| 190 |
+
self.statistics["sparse_updates"] += updates
|
| 191 |
+
self.statistics["sparse_pair_types_peak"] = max(
|
| 192 |
+
self.statistics["sparse_pair_types_peak"], len(counts)
|
| 193 |
+
)
|
| 194 |
+
if len(heap) > 2 * len(counts) + 65_536:
|
| 195 |
+
self._sparse_heap = [
|
| 196 |
+
(-value, *self._sparse_pair(key)) for key, value in counts.items()
|
| 197 |
+
]
|
| 198 |
+
heapq.heapify(self._sparse_heap)
|
| 199 |
+
self.statistics["sparse_heap_rebuilds"] += 1
|
| 200 |
+
|
| 201 |
+
def merge(self, left: int, right: int) -> int:
|
| 202 |
+
"""Apply one exact incremental merge update."""
|
| 203 |
+
members = self._intersect(self.occurrence[left], self.occurrence[right])
|
| 204 |
+
mass = int(self.weights[members].sum()) if members.size else 0
|
| 205 |
+
rows = self.member[members] if members.size else self.member[:0].copy()
|
| 206 |
+
self.occurrence[left] = np.setdiff1d(
|
| 207 |
+
self.occurrence[left], members, assume_unique=True
|
| 208 |
+
)
|
| 209 |
+
self.occurrence[right] = np.setdiff1d(
|
| 210 |
+
self.occurrence[right], members, assume_unique=True
|
| 211 |
+
)
|
| 212 |
+
self.frequency[left] -= mass
|
| 213 |
+
self.frequency[right] -= mass
|
| 214 |
+
token = self._append_token(
|
| 215 |
+
members,
|
| 216 |
+
self.support[left] | self.support[right],
|
| 217 |
+
min(self.anchor[left], self.anchor[right]),
|
| 218 |
+
)
|
| 219 |
+
self._update_sparse_index(left, right, token, rows, members, mass)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
if members.size:
|
| 223 |
+
rows[rows == right] = -1
|
| 224 |
+
rows[rows == left] = token
|
| 225 |
+
self.member[members] = rows
|
| 226 |
+
self.merges.append((left, right))
|
| 227 |
+
return token
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def _train_spatial_python(
|
| 231 |
+
codes: np.ndarray,
|
| 232 |
+
weights: np.ndarray,
|
| 233 |
+
bins: Sequence[int],
|
| 234 |
+
*,
|
| 235 |
+
vocab_size: int,
|
| 236 |
+
progress: bool = False,
|
| 237 |
+
) -> tuple[SpatialVocabulary, dict[str, object]]:
|
| 238 |
+
|
| 239 |
+
trainer = _SparseSpatialTrainer(codes, weights, bins)
|
| 240 |
+
if vocab_size < trainer.atom_count:
|
| 241 |
+
raise ValueError(
|
| 242 |
+
f"vocab_size {vocab_size} is below the {trainer.atom_count} primitive atoms"
|
| 243 |
+
)
|
| 244 |
+
total_weight = int(trainer.weights.sum())
|
| 245 |
+
active_width = np.sum(trainer.records >= 0, axis=1, dtype=np.int64)
|
| 246 |
+
initial = int(np.dot(trainer.weights, active_width))
|
| 247 |
+
length = initial
|
| 248 |
+
trainer.build_sparse_index()
|
| 249 |
+
for step in range(vocab_size - trainer.atom_count):
|
| 250 |
+
winner = trainer.sparse_best_pair()
|
| 251 |
+
if winner is None:
|
| 252 |
+
break
|
| 253 |
+
left, right, frequency = winner
|
| 254 |
+
trainer.merge(left, right)
|
| 255 |
+
length -= frequency
|
| 256 |
+
if progress and step % 256 == 0:
|
| 257 |
+
print(
|
| 258 |
+
f" merge {step:>5} tokens/action {length / total_weight:.4f}",
|
| 259 |
+
flush=True,
|
| 260 |
+
)
|
| 261 |
+
vocabulary = SpatialVocabulary(
|
| 262 |
+
tuple(int(value) for value in bins), tuple(trainer.merges)
|
| 263 |
+
)
|
| 264 |
+
report = {
|
| 265 |
+
"records": int(trainer.count),
|
| 266 |
+
"weight": total_weight,
|
| 267 |
+
"atoms": trainer.atom_count,
|
| 268 |
+
"merges": len(trainer.merges),
|
| 269 |
+
"vocab_size": vocabulary.vocab_size,
|
| 270 |
+
"tokens_per_action_initial": initial / total_weight,
|
| 271 |
+
"tokens_per_action": length / total_weight,
|
| 272 |
+
"compression": initial / length,
|
| 273 |
+
"sparse_updates": trainer.statistics["sparse_updates"],
|
| 274 |
+
"sparse_heap_rebuilds": trainer.statistics["sparse_heap_rebuilds"],
|
| 275 |
+
"sparse_pair_types_peak": trainer.statistics["sparse_pair_types_peak"],
|
| 276 |
+
}
|
| 277 |
+
return vocabulary, report
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def train_spatial(
|
| 281 |
+
codes: np.ndarray,
|
| 282 |
+
weights: np.ndarray,
|
| 283 |
+
bins: Sequence[int],
|
| 284 |
+
*,
|
| 285 |
+
vocab_size: int,
|
| 286 |
+
backend: str = "auto",
|
| 287 |
+
progress: bool = False,
|
| 288 |
+
**kwargs: object,
|
| 289 |
+
) -> tuple[SpatialVocabulary, dict[str, object]]:
|
| 290 |
+
"Fit spatial Set-BPE, preferring the native kernel when it is built."
|
| 291 |
+
if resolve_backend(backend) == "python":
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
kwargs.pop("threads", None)
|
| 295 |
+
return _train_spatial_python(
|
| 296 |
+
codes, weights, bins, vocab_size=vocab_size, progress=progress, **kwargs
|
| 297 |
+
)
|
| 298 |
+
values = np.ascontiguousarray(codes, dtype=np.int32)
|
| 299 |
+
masses = np.ascontiguousarray(weights, dtype=np.int64)
|
| 300 |
+
merges, report = require_native().train_spatial(
|
| 301 |
+
values,
|
| 302 |
+
masses,
|
| 303 |
+
[int(count) for count in bins],
|
| 304 |
+
int(vocab_size),
|
| 305 |
+
int(kwargs.get("threads", 0)),
|
| 306 |
+
)
|
| 307 |
+
vocabulary = SpatialVocabulary(
|
| 308 |
+
tuple(int(count) for count in bins),
|
| 309 |
+
tuple((int(left), int(right)) for left, right in merges),
|
| 310 |
+
)
|
| 311 |
+
return vocabulary, dict(report)
|
runtime/tokenization/setbpe_temporal.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Runtime rooted-temporal Set-BPE vocabulary and lossless serialization."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import heapq
|
| 6 |
+
from collections.abc import Sequence
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
from ..common._validation import active_dimensions
|
| 12 |
+
from .setbpe_spatial import SpatialVocabulary
|
| 13 |
+
|
| 14 |
+
__all__ = ["TemporalVocabulary"]
|
| 15 |
+
|
| 16 |
+
Rule = tuple[int, int, int]
|
| 17 |
+
Pattern = tuple[tuple[int, int], ...]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _active_support(active_dims: Sequence[int] | None, dimension: int) -> int:
|
| 21 |
+
dims = active_dimensions(active_dims, dimension)
|
| 22 |
+
return sum(1 << value for value in dims)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _validate_episode(values: np.ndarray, spatial: SpatialVocabulary) -> int:
|
| 26 |
+
if values.ndim != 2 or values.shape[1] != spatial.dimension or len(values) < 1:
|
| 27 |
+
raise ValueError(
|
| 28 |
+
f"every episode must have non-empty shape [T, {spatial.dimension}], got {values.shape}"
|
| 29 |
+
)
|
| 30 |
+
counts = np.asarray(spatial.bins, dtype=np.int64)
|
| 31 |
+
if np.any(values < -1) or np.any(values >= counts[None, :]):
|
| 32 |
+
raise ValueError("episodes must contain -1 or an in-range cell index")
|
| 33 |
+
support = values >= 0
|
| 34 |
+
if np.any(np.sum(support, axis=1) == 0):
|
| 35 |
+
raise ValueError("every timestep needs at least one active coordinate")
|
| 36 |
+
if np.any(support != support[:1]):
|
| 37 |
+
raise ValueError("active dimensions must remain constant within an episode")
|
| 38 |
+
return sum(1 << int(value) for value in np.flatnonzero(support[0]))
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class TemporalVocabulary:
|
| 43 |
+
"""A spatial vocabulary plus rooted, forward-only temporal merge rules."""
|
| 44 |
+
|
| 45 |
+
spatial: SpatialVocabulary
|
| 46 |
+
lookahead: int
|
| 47 |
+
merges: tuple[Rule, ...]
|
| 48 |
+
|
| 49 |
+
def __post_init__(self) -> None:
|
| 50 |
+
if self.lookahead < 1:
|
| 51 |
+
raise ValueError("lookahead must be positive")
|
| 52 |
+
self.patterns: list[Pattern] = [
|
| 53 |
+
((0, token),) for token in range(self.spatial.vocab_size)
|
| 54 |
+
]
|
| 55 |
+
self.spans: list[int] = [1] * self.spatial.vocab_size
|
| 56 |
+
self.root: list[int] = list(range(self.spatial.vocab_size))
|
| 57 |
+
self.support_masks: list[tuple[int, ...]] = [
|
| 58 |
+
(int(self.spatial.support[token]),)
|
| 59 |
+
for token in range(self.spatial.vocab_size)
|
| 60 |
+
]
|
| 61 |
+
self._rules: dict[Rule, tuple[int, int]] = {}
|
| 62 |
+
for rank, rule in enumerate(self.merges):
|
| 63 |
+
left, right, offset = (int(value) for value in rule)
|
| 64 |
+
child = self.spatial.vocab_size + rank
|
| 65 |
+
if left >= child or right >= child or left < 0 or right < 0:
|
| 66 |
+
raise ValueError(
|
| 67 |
+
"a temporal merge must reference already-created tokens"
|
| 68 |
+
)
|
| 69 |
+
pattern, masks = _merge_pattern(
|
| 70 |
+
self.patterns[left],
|
| 71 |
+
self.patterns[right],
|
| 72 |
+
offset,
|
| 73 |
+
self.lookahead,
|
| 74 |
+
self.spatial,
|
| 75 |
+
)
|
| 76 |
+
self.patterns.append(pattern)
|
| 77 |
+
self.spans.append(len(masks))
|
| 78 |
+
self.root.append(self.root[left])
|
| 79 |
+
self.support_masks.append(masks)
|
| 80 |
+
key = (left, right, offset)
|
| 81 |
+
if key in self._rules:
|
| 82 |
+
raise ValueError("duplicate temporal merge rule")
|
| 83 |
+
self._rules[key] = (child, rank)
|
| 84 |
+
|
| 85 |
+
@property
|
| 86 |
+
def vocab_size(self) -> int:
|
| 87 |
+
return self.spatial.vocab_size + len(self.merges)
|
| 88 |
+
|
| 89 |
+
def encode(self, codes: np.ndarray) -> list[int]:
|
| 90 |
+
"""Encode a dense episode; ``-1`` slots are semantically absent."""
|
| 91 |
+
values = np.asarray(codes, dtype=np.int64)
|
| 92 |
+
_validate_episode(values, self.spatial)
|
| 93 |
+
|
| 94 |
+
at: list[dict[int, int]] = [dict() for _ in range(values.shape[0])]
|
| 95 |
+
tokens: list[int] = []
|
| 96 |
+
anchors: list[int] = []
|
| 97 |
+
active: list[bool] = []
|
| 98 |
+
for anchor, row in enumerate(values):
|
| 99 |
+
for token in self.spatial.encode(row):
|
| 100 |
+
instance = len(tokens)
|
| 101 |
+
tokens.append(token)
|
| 102 |
+
anchors.append(anchor)
|
| 103 |
+
active.append(True)
|
| 104 |
+
at[anchor][token] = instance
|
| 105 |
+
|
| 106 |
+
frontier: list[tuple[int, int, int, int, int, int]] = []
|
| 107 |
+
|
| 108 |
+
def offer(left_instance: int, right_instance: int) -> None:
|
| 109 |
+
left = tokens[left_instance]
|
| 110 |
+
right = tokens[right_instance]
|
| 111 |
+
offset = anchors[right_instance] - anchors[left_instance]
|
| 112 |
+
rule = self._rules.get((left, right, offset))
|
| 113 |
+
if rule is not None:
|
| 114 |
+
child, rank = rule
|
| 115 |
+
heapq.heappush(
|
| 116 |
+
frontier,
|
| 117 |
+
(
|
| 118 |
+
rank,
|
| 119 |
+
anchors[left_instance],
|
| 120 |
+
int(self.spatial.anchor[self.root[child]]),
|
| 121 |
+
left_instance,
|
| 122 |
+
right_instance,
|
| 123 |
+
child,
|
| 124 |
+
),
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
for anchor, left_items in enumerate(at):
|
| 128 |
+
for offset in range(1, min(self.lookahead, len(at) - anchor)):
|
| 129 |
+
for left_instance in left_items.values():
|
| 130 |
+
for right_instance in at[anchor + offset].values():
|
| 131 |
+
offer(left_instance, right_instance)
|
| 132 |
+
|
| 133 |
+
while frontier:
|
| 134 |
+
_, anchor, _, left_instance, right_instance, child = heapq.heappop(frontier)
|
| 135 |
+
if not active[left_instance] or not active[right_instance]:
|
| 136 |
+
continue
|
| 137 |
+
if anchors[left_instance] != anchor:
|
| 138 |
+
continue
|
| 139 |
+
rule = (
|
| 140 |
+
tokens[left_instance],
|
| 141 |
+
tokens[right_instance],
|
| 142 |
+
anchors[right_instance] - anchor,
|
| 143 |
+
)
|
| 144 |
+
resolved = self._rules.get(rule)
|
| 145 |
+
if resolved is None or resolved[0] != child:
|
| 146 |
+
continue
|
| 147 |
+
|
| 148 |
+
active[left_instance] = False
|
| 149 |
+
active[right_instance] = False
|
| 150 |
+
del at[anchor][tokens[left_instance]]
|
| 151 |
+
del at[anchors[right_instance]][tokens[right_instance]]
|
| 152 |
+
new_instance = len(tokens)
|
| 153 |
+
tokens.append(child)
|
| 154 |
+
anchors.append(anchor)
|
| 155 |
+
active.append(True)
|
| 156 |
+
at[anchor][child] = new_instance
|
| 157 |
+
|
| 158 |
+
for offset in range(1, self.lookahead):
|
| 159 |
+
future = anchor + offset
|
| 160 |
+
if future < len(at):
|
| 161 |
+
for other in at[future].values():
|
| 162 |
+
offer(new_instance, other)
|
| 163 |
+
past = anchor - offset
|
| 164 |
+
if past >= 0:
|
| 165 |
+
for other in at[past].values():
|
| 166 |
+
offer(other, new_instance)
|
| 167 |
+
|
| 168 |
+
ordered: list[tuple[int, int, int]] = []
|
| 169 |
+
for anchor, instances in enumerate(at):
|
| 170 |
+
for token, instance in instances.items():
|
| 171 |
+
if active[instance]:
|
| 172 |
+
ordered.append(
|
| 173 |
+
(anchor, int(self.spatial.anchor[self.root[token]]), token)
|
| 174 |
+
)
|
| 175 |
+
ordered.sort()
|
| 176 |
+
return [token for _, _, token in ordered]
|
| 177 |
+
|
| 178 |
+
def decode(
|
| 179 |
+
self,
|
| 180 |
+
stream: Sequence[int],
|
| 181 |
+
*,
|
| 182 |
+
active_dims: Sequence[int] | None = None,
|
| 183 |
+
) -> np.ndarray:
|
| 184 |
+
"""Decode ids using the route's requested active-dimension support."""
|
| 185 |
+
if not stream:
|
| 186 |
+
return np.empty((0, self.spatial.dimension), dtype=np.int64)
|
| 187 |
+
full = _active_support(active_dims, self.spatial.dimension)
|
| 188 |
+
coverage: list[int] = [0]
|
| 189 |
+
spatial_tokens: list[list[int]] = [[]]
|
| 190 |
+
front = 0
|
| 191 |
+
for raw_token in stream:
|
| 192 |
+
token = int(raw_token)
|
| 193 |
+
if not 0 <= token < self.vocab_size:
|
| 194 |
+
raise ValueError(f"temporal token {token} is outside the vocabulary")
|
| 195 |
+
while front < len(coverage) and coverage[front] == full:
|
| 196 |
+
front += 1
|
| 197 |
+
if front == len(coverage):
|
| 198 |
+
coverage.append(0)
|
| 199 |
+
spatial_tokens.append([])
|
| 200 |
+
|
| 201 |
+
uncovered = full & ~coverage[front]
|
| 202 |
+
expected_anchor = int(uncovered & -uncovered).bit_length() - 1
|
| 203 |
+
actual_anchor = int(self.spatial.anchor[self.root[token]])
|
| 204 |
+
if actual_anchor != expected_anchor:
|
| 205 |
+
raise ValueError(
|
| 206 |
+
"temporal stream violates root order at the earliest incomplete step"
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
for offset, spatial_token in self.patterns[token]:
|
| 210 |
+
step = front + offset
|
| 211 |
+
while step >= len(coverage):
|
| 212 |
+
coverage.append(0)
|
| 213 |
+
spatial_tokens.append([])
|
| 214 |
+
support = int(self.spatial.support[spatial_token])
|
| 215 |
+
if coverage[step] & support:
|
| 216 |
+
raise ValueError(
|
| 217 |
+
"temporal stream contains overlapping spatial support"
|
| 218 |
+
)
|
| 219 |
+
coverage[step] |= support
|
| 220 |
+
spatial_tokens[step].append(spatial_token)
|
| 221 |
+
|
| 222 |
+
if any(mask != full for mask in coverage):
|
| 223 |
+
raise ValueError("temporal stream ended with an incomplete control step")
|
| 224 |
+
return np.stack(
|
| 225 |
+
[
|
| 226 |
+
self.spatial.decode(tokens, active_dims=active_dims)
|
| 227 |
+
for tokens in spatial_tokens
|
| 228 |
+
]
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _merge_pattern(
|
| 233 |
+
left: Pattern,
|
| 234 |
+
right: Pattern,
|
| 235 |
+
offset: int,
|
| 236 |
+
lookahead: int,
|
| 237 |
+
spatial: SpatialVocabulary,
|
| 238 |
+
) -> tuple[Pattern, tuple[int, ...]]:
|
| 239 |
+
if not 1 <= offset < lookahead:
|
| 240 |
+
raise ValueError("a temporal merge offset must be in [1, lookahead)")
|
| 241 |
+
shifted = tuple((time + offset, token) for time, token in right)
|
| 242 |
+
pattern = tuple(sorted((*left, *shifted)))
|
| 243 |
+
span = 1 + max(time for time, _ in pattern)
|
| 244 |
+
if span > lookahead:
|
| 245 |
+
raise ValueError("temporal merge exceeds the lookahead horizon")
|
| 246 |
+
masks = [0] * span
|
| 247 |
+
roots = 0
|
| 248 |
+
for time, token in pattern:
|
| 249 |
+
support = int(spatial.support[token])
|
| 250 |
+
if masks[time] & support:
|
| 251 |
+
raise ValueError("temporal merge joins overlapping spatial supports")
|
| 252 |
+
masks[time] |= support
|
| 253 |
+
roots += int(time == 0)
|
| 254 |
+
if roots != 1:
|
| 255 |
+
raise ValueError(
|
| 256 |
+
"a rooted temporal token must have exactly one time-zero spatial token"
|
| 257 |
+
)
|
| 258 |
+
return pattern, tuple(masks)
|
runtime/tokenization/setbpe_temporal_training.py
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Temporal Set-BPE fitting implementations."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import heapq
|
| 6 |
+
from array import array
|
| 7 |
+
from collections import Counter
|
| 8 |
+
from collections.abc import Iterable, Iterator, Sequence
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
|
| 12 |
+
from ..backend.native_backend import require_native, resolve_backend
|
| 13 |
+
from .setbpe_spatial import SpatialVocabulary
|
| 14 |
+
from .setbpe_temporal import (
|
| 15 |
+
Pattern,
|
| 16 |
+
Rule,
|
| 17 |
+
TemporalVocabulary,
|
| 18 |
+
_merge_pattern,
|
| 19 |
+
_validate_episode,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
__all__ = ["train_temporal"]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class _SparseTemporalTrainer:
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def __init__(
|
| 29 |
+
self,
|
| 30 |
+
episodes: Sequence[np.ndarray],
|
| 31 |
+
spatial: SpatialVocabulary,
|
| 32 |
+
lookahead: int,
|
| 33 |
+
max_vocab_size: int,
|
| 34 |
+
) -> None:
|
| 35 |
+
if lookahead < 1:
|
| 36 |
+
raise ValueError("lookahead must be positive")
|
| 37 |
+
self.spatial = spatial
|
| 38 |
+
self.lookahead = lookahead
|
| 39 |
+
self.max_vocab_size = max_vocab_size
|
| 40 |
+
self.patterns: list[Pattern] = [
|
| 41 |
+
((0, token),) for token in range(spatial.vocab_size)
|
| 42 |
+
]
|
| 43 |
+
self.spans: list[int] = [1] * spatial.vocab_size
|
| 44 |
+
self.masks: list[tuple[int, ...]] = [
|
| 45 |
+
(int(spatial.support[token]),) for token in range(spatial.vocab_size)
|
| 46 |
+
]
|
| 47 |
+
self._mask_table = np.zeros((max_vocab_size, lookahead), dtype=np.uint64)
|
| 48 |
+
self._mask_table[: spatial.vocab_size, 0] = spatial.support.astype(
|
| 49 |
+
np.uint64, copy=False
|
| 50 |
+
)
|
| 51 |
+
self.merges: list[Rule] = []
|
| 52 |
+
self.tokens = array("I")
|
| 53 |
+
self.anchors = array("I")
|
| 54 |
+
self.slots = array("I")
|
| 55 |
+
total_anchors = sum(len(episode) + lookahead for episode in episodes)
|
| 56 |
+
self.member = np.full((total_anchors, spatial.dimension), -1, dtype=np.int32)
|
| 57 |
+
self.occupied = np.zeros(total_anchors, dtype=np.uint64)
|
| 58 |
+
self._slot_mask = (1 << spatial.dimension) - 1
|
| 59 |
+
self.occurrences: list[dict[int, int]] = [
|
| 60 |
+
dict() for _ in range(spatial.vocab_size)
|
| 61 |
+
]
|
| 62 |
+
self.episode_count = len(episodes)
|
| 63 |
+
self.action_count = sum(len(episode) for episode in episodes)
|
| 64 |
+
self.initial_count = 0
|
| 65 |
+
self.counts: dict[int, int] = {}
|
| 66 |
+
self.self_versions: dict[int, int] = {}
|
| 67 |
+
self.heap: list[tuple[int, int, int, int, int, bool]] = []
|
| 68 |
+
self.self_cache: dict[int, tuple[int, list[tuple[int, int]]]] = {}
|
| 69 |
+
self._legal_table = np.zeros(
|
| 70 |
+
(lookahead, max_vocab_size, max_vocab_size), dtype=np.uint8
|
| 71 |
+
)
|
| 72 |
+
for offset in range(1, lookahead):
|
| 73 |
+
self._legal_table[offset, : spatial.vocab_size, : spatial.vocab_size] = 2
|
| 74 |
+
self.statistics = Counter()
|
| 75 |
+
self._active_count = 0
|
| 76 |
+
|
| 77 |
+
self._load_episodes(episodes)
|
| 78 |
+
self.counts = self._initial_pair_counts()
|
| 79 |
+
self.heap = self._initial_heap()
|
| 80 |
+
self.statistics["initial_edge_types"] = len(self.counts)
|
| 81 |
+
self.statistics["initial_edges"] = sum(self.counts.values())
|
| 82 |
+
|
| 83 |
+
def _load_episodes(self, episodes: Sequence[np.ndarray]) -> None:
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
base = 0
|
| 87 |
+
encoding_cache: dict[bytes, tuple[int, ...]] = {}
|
| 88 |
+
for episode in episodes:
|
| 89 |
+
values = np.asarray(episode, dtype=np.int64)
|
| 90 |
+
_validate_episode(values, self.spatial)
|
| 91 |
+
needed = base + len(values) + self.lookahead
|
| 92 |
+
for time, row in enumerate(values):
|
| 93 |
+
cache_key = row.tobytes()
|
| 94 |
+
encoded = encoding_cache.get(cache_key)
|
| 95 |
+
if encoded is None:
|
| 96 |
+
encoded = tuple(self.spatial.encode(row))
|
| 97 |
+
encoding_cache[cache_key] = encoded
|
| 98 |
+
anchor = base + time
|
| 99 |
+
for token in encoded:
|
| 100 |
+
self._new_instance(token, anchor)
|
| 101 |
+
self.initial_count += 1
|
| 102 |
+
base = needed
|
| 103 |
+
|
| 104 |
+
def _initial_pair_counts(self) -> dict[int, int]:
|
| 105 |
+
|
| 106 |
+
base_vocab = self.spatial.vocab_size
|
| 107 |
+
dense_size = base_vocab * base_vocab * self.lookahead
|
| 108 |
+
if dense_size <= 1 << 25:
|
| 109 |
+
dense_counts = np.zeros(dense_size, dtype=np.int64)
|
| 110 |
+
for offset in range(1, self.lookahead):
|
| 111 |
+
left_rows = self.member[:-offset]
|
| 112 |
+
right_rows = self.member[offset:]
|
| 113 |
+
for left_slot in range(self.spatial.dimension):
|
| 114 |
+
left = left_rows[:, left_slot]
|
| 115 |
+
for right_slot in range(self.spatial.dimension):
|
| 116 |
+
right = right_rows[:, right_slot]
|
| 117 |
+
valid = (left >= 0) & (right >= 0)
|
| 118 |
+
if not valid.any():
|
| 119 |
+
continue
|
| 120 |
+
packed = (
|
| 121 |
+
left[valid].astype(np.int64) * base_vocab + right[valid]
|
| 122 |
+
) * self.lookahead + offset
|
| 123 |
+
np.add.at(dense_counts, packed, 1)
|
| 124 |
+
initial_counts: dict[int, int] = {}
|
| 125 |
+
for raw_key in np.flatnonzero(dense_counts):
|
| 126 |
+
pair, offset = divmod(int(raw_key), self.lookahead)
|
| 127 |
+
left, right = divmod(pair, base_vocab)
|
| 128 |
+
packed_key = (
|
| 129 |
+
left * self.max_vocab_size + right
|
| 130 |
+
) * self.lookahead + offset
|
| 131 |
+
initial_counts[packed_key] = int(dense_counts[raw_key])
|
| 132 |
+
return initial_counts
|
| 133 |
+
return self._initial_pair_counts_sparse()
|
| 134 |
+
|
| 135 |
+
def _initial_pair_counts_sparse(self) -> dict[int, int]:
|
| 136 |
+
|
| 137 |
+
fallback: Counter[int] = Counter()
|
| 138 |
+
for anchor, left_row in enumerate(self.member):
|
| 139 |
+
left_tokens = left_row[left_row >= 0]
|
| 140 |
+
for offset in range(1, self.lookahead):
|
| 141 |
+
target = anchor + offset
|
| 142 |
+
if target >= len(self.member):
|
| 143 |
+
break
|
| 144 |
+
right_tokens = self.member[target]
|
| 145 |
+
right_tokens = right_tokens[right_tokens >= 0]
|
| 146 |
+
for raw_left in left_tokens:
|
| 147 |
+
for raw_right in right_tokens:
|
| 148 |
+
packed_key = (
|
| 149 |
+
int(raw_left) * self.max_vocab_size + int(raw_right)
|
| 150 |
+
) * self.lookahead + offset
|
| 151 |
+
fallback[packed_key] += 1
|
| 152 |
+
return dict(fallback)
|
| 153 |
+
|
| 154 |
+
def _initial_heap(self) -> list[tuple[int, int, int, int, int, bool]]:
|
| 155 |
+
heap: list[tuple[int, int, int, int, int, bool]] = []
|
| 156 |
+
for packed_key, count in self.counts.items():
|
| 157 |
+
pair, offset = divmod(packed_key, self.lookahead)
|
| 158 |
+
left, right = divmod(pair, self.max_vocab_size)
|
| 159 |
+
heap.append((-count, left, right, offset, 0, False))
|
| 160 |
+
heapq.heapify(heap)
|
| 161 |
+
return heap
|
| 162 |
+
|
| 163 |
+
def _new_instance(self, token: int, anchor: int) -> int:
|
| 164 |
+
while token >= len(self.occurrences):
|
| 165 |
+
self.occurrences.append({})
|
| 166 |
+
if anchor in self.occurrences[token]:
|
| 167 |
+
raise RuntimeError("a temporal type cannot occur twice at one anchor")
|
| 168 |
+
occupied = int(self.occupied[anchor])
|
| 169 |
+
available = self._slot_mask ^ occupied
|
| 170 |
+
if available == 0:
|
| 171 |
+
raise RuntimeError("temporal anchor exceeded the spatial token-width bound")
|
| 172 |
+
lowest = available & -available
|
| 173 |
+
slot = lowest.bit_length() - 1
|
| 174 |
+
self.occupied[anchor] = occupied | lowest
|
| 175 |
+
instance = len(self.tokens)
|
| 176 |
+
self.tokens.append(token)
|
| 177 |
+
self.anchors.append(anchor)
|
| 178 |
+
self.slots.append(slot)
|
| 179 |
+
self.member[anchor, slot] = token
|
| 180 |
+
self._active_count += 1
|
| 181 |
+
self.occurrences[token][anchor] = instance
|
| 182 |
+
return instance
|
| 183 |
+
|
| 184 |
+
def _initialize_legality(self, child: int) -> None:
|
| 185 |
+
|
| 186 |
+
count = child + 1
|
| 187 |
+
spans = np.asarray(self.spans[:count], dtype=np.int64)
|
| 188 |
+
masks = self._mask_table[:count]
|
| 189 |
+
child_masks = self._mask_table[child]
|
| 190 |
+
child_span = self.spans[child]
|
| 191 |
+
for offset in range(1, self.lookahead):
|
| 192 |
+
overlap = np.zeros(count, dtype=bool)
|
| 193 |
+
for relative in range(self.lookahead - offset):
|
| 194 |
+
overlap |= (child_masks[offset + relative] & masks[:, relative]) != 0
|
| 195 |
+
legal = (offset + spans <= self.lookahead) & ~overlap
|
| 196 |
+
self._legal_table[offset, child, :count] = np.where(legal, 2, 1)
|
| 197 |
+
|
| 198 |
+
overlap.fill(False)
|
| 199 |
+
for relative in range(min(child_span, self.lookahead - offset)):
|
| 200 |
+
overlap |= (masks[:, offset + relative] & child_masks[relative]) != 0
|
| 201 |
+
legal = (offset + child_span <= self.lookahead) & ~overlap
|
| 202 |
+
self._legal_table[offset, :count, child] = np.where(legal, 2, 1)
|
| 203 |
+
|
| 204 |
+
def _entry(
|
| 205 |
+
self, packed_key: int, priority: int, certified: bool
|
| 206 |
+
) -> tuple[int, int, int, int, int, bool]:
|
| 207 |
+
pair, offset = divmod(packed_key, self.lookahead)
|
| 208 |
+
left, right = divmod(pair, self.max_vocab_size)
|
| 209 |
+
return (
|
| 210 |
+
-priority,
|
| 211 |
+
left,
|
| 212 |
+
right,
|
| 213 |
+
offset,
|
| 214 |
+
self.self_versions.get(packed_key, 0) if left == right else 0,
|
| 215 |
+
certified,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
def _select(self, key: Rule) -> list[tuple[int, int]]:
|
| 219 |
+
left, right, offset = key
|
| 220 |
+
left_occurrences = self.occurrences[left]
|
| 221 |
+
right_occurrences = self.occurrences[right]
|
| 222 |
+
if left != right:
|
| 223 |
+
return [
|
| 224 |
+
(left_instance, right_occurrences[anchor + offset])
|
| 225 |
+
for anchor, left_instance in left_occurrences.items()
|
| 226 |
+
if anchor + offset in right_occurrences
|
| 227 |
+
]
|
| 228 |
+
selected: list[tuple[int, int]] = []
|
| 229 |
+
consumed: set[int] = set()
|
| 230 |
+
for anchor in sorted(left_occurrences):
|
| 231 |
+
target = anchor + offset
|
| 232 |
+
if (
|
| 233 |
+
anchor in consumed
|
| 234 |
+
or target in consumed
|
| 235 |
+
or target not in right_occurrences
|
| 236 |
+
):
|
| 237 |
+
continue
|
| 238 |
+
selected.append((left_occurrences[anchor], right_occurrences[target]))
|
| 239 |
+
consumed.add(anchor)
|
| 240 |
+
consumed.add(target)
|
| 241 |
+
return selected
|
| 242 |
+
|
| 243 |
+
def best(self) -> tuple[Rule, list[tuple[int, int]]] | None:
|
| 244 |
+
while self.heap:
|
| 245 |
+
negative, left, right, offset, version, certified = heapq.heappop(self.heap)
|
| 246 |
+
self.statistics["heap_pops"] += 1
|
| 247 |
+
key = (left, right, offset)
|
| 248 |
+
packed_key = (left * self.max_vocab_size + right) * self.lookahead + offset
|
| 249 |
+
current_count = self.counts.get(packed_key, 0)
|
| 250 |
+
if current_count <= 0:
|
| 251 |
+
continue
|
| 252 |
+
if left == right:
|
| 253 |
+
current_version = self.self_versions.get(packed_key, 0)
|
| 254 |
+
stale = current_version != version
|
| 255 |
+
else:
|
| 256 |
+
stale = current_count != -negative
|
| 257 |
+
if stale:
|
| 258 |
+
heapq.heappush(self.heap, self._entry(packed_key, current_count, False))
|
| 259 |
+
continue
|
| 260 |
+
if left == right and not certified:
|
| 261 |
+
matches = self._select(key)
|
| 262 |
+
self.self_cache[packed_key] = (version, matches)
|
| 263 |
+
heapq.heappush(self.heap, self._entry(packed_key, len(matches), True))
|
| 264 |
+
self.statistics["self_certifications"] += 1
|
| 265 |
+
continue
|
| 266 |
+
if certified:
|
| 267 |
+
cached = self.self_cache.get(packed_key)
|
| 268 |
+
if cached is None or cached[0] != version:
|
| 269 |
+
continue
|
| 270 |
+
matches = cached[1]
|
| 271 |
+
else:
|
| 272 |
+
matches = self._select(key)
|
| 273 |
+
gain = -negative
|
| 274 |
+
if len(matches) != gain:
|
| 275 |
+
raise RuntimeError(
|
| 276 |
+
"sparse temporal counter disagrees with exact matching"
|
| 277 |
+
)
|
| 278 |
+
return key, matches
|
| 279 |
+
return None
|
| 280 |
+
|
| 281 |
+
def _accumulate_incident(
|
| 282 |
+
self,
|
| 283 |
+
instances: Sequence[int] | set[int],
|
| 284 |
+
multiplier: int,
|
| 285 |
+
deltas: Counter[int],
|
| 286 |
+
) -> None:
|
| 287 |
+
|
| 288 |
+
grouped: dict[int, list[int]] = {}
|
| 289 |
+
for instance in instances:
|
| 290 |
+
grouped.setdefault(self.tokens[instance], []).append(self.anchors[instance])
|
| 291 |
+
width = len(self.patterns)
|
| 292 |
+
total_anchors = self.member.shape[0]
|
| 293 |
+
vocab_width = self.max_vocab_size
|
| 294 |
+
lookahead = self.lookahead
|
| 295 |
+
for token, raw_anchors in grouped.items():
|
| 296 |
+
anchors = np.asarray(raw_anchors, dtype=np.int64)
|
| 297 |
+
for offset in range(1, self.lookahead):
|
| 298 |
+
future_anchors = anchors[anchors + offset < total_anchors] + offset
|
| 299 |
+
if future_anchors.size:
|
| 300 |
+
values = self.member[future_anchors].reshape(-1)
|
| 301 |
+
values = values[values >= 0]
|
| 302 |
+
if values.size:
|
| 303 |
+
counts = np.bincount(values, minlength=width)
|
| 304 |
+
neighbors = np.flatnonzero(counts)
|
| 305 |
+
neighbors = neighbors[
|
| 306 |
+
self._legal_table[offset, token, neighbors] == 2
|
| 307 |
+
]
|
| 308 |
+
for raw_neighbor in neighbors:
|
| 309 |
+
neighbor = int(raw_neighbor)
|
| 310 |
+
packed_key = (
|
| 311 |
+
token * vocab_width + neighbor
|
| 312 |
+
) * lookahead + offset
|
| 313 |
+
deltas[packed_key] += multiplier * int(counts[neighbor])
|
| 314 |
+
past_anchors = anchors[anchors >= offset] - offset
|
| 315 |
+
if past_anchors.size:
|
| 316 |
+
values = self.member[past_anchors].reshape(-1)
|
| 317 |
+
values = values[values >= 0]
|
| 318 |
+
if values.size:
|
| 319 |
+
counts = np.bincount(values, minlength=width)
|
| 320 |
+
neighbors = np.flatnonzero(counts)
|
| 321 |
+
neighbors = neighbors[
|
| 322 |
+
self._legal_table[offset, neighbors, token] == 2
|
| 323 |
+
]
|
| 324 |
+
for raw_neighbor in neighbors:
|
| 325 |
+
neighbor = int(raw_neighbor)
|
| 326 |
+
packed_key = (
|
| 327 |
+
neighbor * vocab_width + token
|
| 328 |
+
) * lookahead + offset
|
| 329 |
+
deltas[packed_key] += multiplier * int(counts[neighbor])
|
| 330 |
+
|
| 331 |
+
def _correct_internal_edges(
|
| 332 |
+
self,
|
| 333 |
+
instances: Sequence[int] | set[int],
|
| 334 |
+
correction: int,
|
| 335 |
+
deltas: Counter[int],
|
| 336 |
+
) -> None:
|
| 337 |
+
|
| 338 |
+
by_anchor: dict[int, list[int]] = {}
|
| 339 |
+
for instance in instances:
|
| 340 |
+
by_anchor.setdefault(self.anchors[instance], []).append(
|
| 341 |
+
self.tokens[instance]
|
| 342 |
+
)
|
| 343 |
+
vocab_width = self.max_vocab_size
|
| 344 |
+
lookahead = self.lookahead
|
| 345 |
+
for anchor, left_tokens in by_anchor.items():
|
| 346 |
+
for offset in range(1, self.lookahead):
|
| 347 |
+
right_tokens = by_anchor.get(anchor + offset)
|
| 348 |
+
if right_tokens is None:
|
| 349 |
+
continue
|
| 350 |
+
for left in left_tokens:
|
| 351 |
+
for right in right_tokens:
|
| 352 |
+
if self._legal_table[offset, left, right] == 2:
|
| 353 |
+
packed_key = (
|
| 354 |
+
left * vocab_width + right
|
| 355 |
+
) * lookahead + offset
|
| 356 |
+
deltas[packed_key] += correction
|
| 357 |
+
|
| 358 |
+
def _apply_deltas(self, deltas: Counter[int], selected: Rule) -> None:
|
| 359 |
+
|
| 360 |
+
counts = self.counts
|
| 361 |
+
self_versions = self.self_versions
|
| 362 |
+
heap = self.heap
|
| 363 |
+
updates = 0
|
| 364 |
+
vocab_width = self.max_vocab_size
|
| 365 |
+
lookahead = self.lookahead
|
| 366 |
+
selected_key = (selected[0] * vocab_width + selected[1]) * lookahead + selected[
|
| 367 |
+
2
|
| 368 |
+
]
|
| 369 |
+
for packed_key, delta in deltas.items():
|
| 370 |
+
previous = counts.get(packed_key, 0)
|
| 371 |
+
count = previous + delta
|
| 372 |
+
if count < 0:
|
| 373 |
+
raise RuntimeError("temporal edge counter became negative")
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
pair = packed_key // lookahead
|
| 377 |
+
is_self = pair % (vocab_width + 1) == 0
|
| 378 |
+
if is_self:
|
| 379 |
+
version = self_versions.get(packed_key, 0) + 1
|
| 380 |
+
self_versions[packed_key] = version
|
| 381 |
+
self.self_cache.pop(packed_key, None)
|
| 382 |
+
else:
|
| 383 |
+
version = 0
|
| 384 |
+
if count:
|
| 385 |
+
counts[packed_key] = count
|
| 386 |
+
if previous == 0 or packed_key == selected_key:
|
| 387 |
+
pair, offset = divmod(packed_key, lookahead)
|
| 388 |
+
left, right = divmod(pair, vocab_width)
|
| 389 |
+
heapq.heappush(heap, (-count, left, right, offset, version, False))
|
| 390 |
+
else:
|
| 391 |
+
counts.pop(packed_key, None)
|
| 392 |
+
updates += abs(delta)
|
| 393 |
+
self.statistics["counter_updates"] += updates
|
| 394 |
+
|
| 395 |
+
def commit(self, key: Rule, matches: Sequence[tuple[int, int]]) -> None:
|
| 396 |
+
left, right, offset = key
|
| 397 |
+
pattern, masks = _merge_pattern(
|
| 398 |
+
self.patterns[left],
|
| 399 |
+
self.patterns[right],
|
| 400 |
+
offset,
|
| 401 |
+
self.lookahead,
|
| 402 |
+
self.spatial,
|
| 403 |
+
)
|
| 404 |
+
child = len(self.patterns)
|
| 405 |
+
self.patterns.append(pattern)
|
| 406 |
+
self.spans.append(len(masks))
|
| 407 |
+
self.masks.append(masks)
|
| 408 |
+
self.occurrences.append({})
|
| 409 |
+
self.merges.append(key)
|
| 410 |
+
self._mask_table[child, : len(masks)] = masks
|
| 411 |
+
self._initialize_legality(child)
|
| 412 |
+
|
| 413 |
+
removed = {instance for pair in matches for instance in pair}
|
| 414 |
+
if len(removed) != 2 * len(matches):
|
| 415 |
+
raise RuntimeError("selected temporal matches overlap")
|
| 416 |
+
deltas: Counter[int] = Counter()
|
| 417 |
+
self._accumulate_incident(removed, -1, deltas)
|
| 418 |
+
self._correct_internal_edges(removed, 1, deltas)
|
| 419 |
+
|
| 420 |
+
for instance in removed:
|
| 421 |
+
token = self.tokens[instance]
|
| 422 |
+
anchor = self.anchors[instance]
|
| 423 |
+
self.member[anchor, self.slots[instance]] = -1
|
| 424 |
+
self._active_count -= 1
|
| 425 |
+
self.occupied[anchor] = int(self.occupied[anchor]) & ~(
|
| 426 |
+
1 << self.slots[instance]
|
| 427 |
+
)
|
| 428 |
+
del self.occurrences[token][anchor]
|
| 429 |
+
|
| 430 |
+
children = [
|
| 431 |
+
self._new_instance(child, self.anchors[left_instance])
|
| 432 |
+
for left_instance, _ in matches
|
| 433 |
+
]
|
| 434 |
+
self._accumulate_incident(children, 1, deltas)
|
| 435 |
+
self._correct_internal_edges(children, -1, deltas)
|
| 436 |
+
self._apply_deltas(deltas, key)
|
| 437 |
+
|
| 438 |
+
@property
|
| 439 |
+
def active_count(self) -> int:
|
| 440 |
+
return self._active_count
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def _prepare_episodes(
|
| 444 |
+
episodes: Iterable[np.ndarray], spatial: SpatialVocabulary
|
| 445 |
+
) -> Sequence[np.ndarray]:
|
| 446 |
+
if getattr(episodes, "_actioncodec2_validated", False):
|
| 447 |
+
if not isinstance(episodes, Sequence) or len(episodes) == 0:
|
| 448 |
+
raise ValueError("validated temporal corpus must be a non-empty sequence")
|
| 449 |
+
return episodes
|
| 450 |
+
prepared: list[np.ndarray] = []
|
| 451 |
+
for episode in episodes:
|
| 452 |
+
values = np.asarray(episode)
|
| 453 |
+
if values.dtype.kind not in {"i", "u"}:
|
| 454 |
+
raise ValueError("episodes must contain integer cell indices")
|
| 455 |
+
_validate_episode(values, spatial)
|
| 456 |
+
prepared.append(np.ascontiguousarray(values))
|
| 457 |
+
if not prepared or sum(len(episode) for episode in prepared) == 0:
|
| 458 |
+
raise ValueError("temporal training requires at least one non-empty episode")
|
| 459 |
+
return prepared
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def _train_temporal_python(
|
| 463 |
+
episodes: Iterable[np.ndarray],
|
| 464 |
+
spatial: SpatialVocabulary,
|
| 465 |
+
*,
|
| 466 |
+
vocab_size: int,
|
| 467 |
+
lookahead: int,
|
| 468 |
+
progress: bool = False,
|
| 469 |
+
) -> tuple[TemporalVocabulary, dict[str, object]]:
|
| 470 |
+
|
| 471 |
+
prepared = _prepare_episodes(episodes, spatial)
|
| 472 |
+
if vocab_size < spatial.vocab_size:
|
| 473 |
+
raise ValueError(
|
| 474 |
+
f"vocab_size {vocab_size} is below the {spatial.vocab_size} spatial tokens"
|
| 475 |
+
)
|
| 476 |
+
trainer = _SparseTemporalTrainer(prepared, spatial, lookahead, vocab_size)
|
| 477 |
+
length = trainer.initial_count
|
| 478 |
+
for step in range(vocab_size - spatial.vocab_size):
|
| 479 |
+
winner = trainer.best()
|
| 480 |
+
if winner is None:
|
| 481 |
+
break
|
| 482 |
+
key, matches = winner
|
| 483 |
+
if not matches:
|
| 484 |
+
break
|
| 485 |
+
trainer.commit(key, matches)
|
| 486 |
+
length -= len(matches)
|
| 487 |
+
if progress and step % 64 == 0:
|
| 488 |
+
print(
|
| 489 |
+
f" temporal merge {step:>5} tokens/action {length / trainer.action_count:.4f}",
|
| 490 |
+
flush=True,
|
| 491 |
+
)
|
| 492 |
+
if length != trainer.active_count:
|
| 493 |
+
raise RuntimeError("temporal token accounting disagrees with active tiling")
|
| 494 |
+
vocabulary = TemporalVocabulary(spatial, lookahead, tuple(trainer.merges))
|
| 495 |
+
report: dict[str, object] = {
|
| 496 |
+
"episodes": trainer.episode_count,
|
| 497 |
+
"actions": trainer.action_count,
|
| 498 |
+
"lookahead": lookahead,
|
| 499 |
+
"spatial_vocab_size": spatial.vocab_size,
|
| 500 |
+
"merges": len(trainer.merges),
|
| 501 |
+
"vocab_size": vocabulary.vocab_size,
|
| 502 |
+
"tokens_per_action_initial": trainer.initial_count / trainer.action_count,
|
| 503 |
+
"tokens_per_action": length / trainer.action_count,
|
| 504 |
+
"compression": trainer.initial_count / length,
|
| 505 |
+
"trainer": "sparse_exact",
|
| 506 |
+
**{key: int(value) for key, value in trainer.statistics.items()},
|
| 507 |
+
}
|
| 508 |
+
return vocabulary, report
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def _flatten_instances_native(
|
| 512 |
+
episodes: Sequence[np.ndarray],
|
| 513 |
+
spatial: SpatialVocabulary,
|
| 514 |
+
lookahead: int,
|
| 515 |
+
threads: int,
|
| 516 |
+
) -> tuple[Iterator[tuple[np.ndarray, np.ndarray]], int, int]:
|
| 517 |
+
|
| 518 |
+
if getattr(episodes, "_actioncodec2_validated", False):
|
| 519 |
+
return _flatten_cell_corpus(episodes, spatial, lookahead, threads)
|
| 520 |
+
actions = sum(len(episode) for episode in episodes)
|
| 521 |
+
total_anchors = actions + len(episodes) * lookahead
|
| 522 |
+
max_batch_actions = 1_000_000
|
| 523 |
+
merges = np.asarray(spatial.merges, dtype=np.int32).reshape(-1, 2)
|
| 524 |
+
|
| 525 |
+
def generate() -> Iterator[tuple[np.ndarray, np.ndarray]]:
|
| 526 |
+
pending: list[np.ndarray] = []
|
| 527 |
+
pending_anchors: list[np.ndarray] = []
|
| 528 |
+
pending_actions = 0
|
| 529 |
+
base = 0
|
| 530 |
+
|
| 531 |
+
def encode() -> tuple[np.ndarray, np.ndarray]:
|
| 532 |
+
codes = np.concatenate(pending, axis=0).astype(np.int32, copy=False)
|
| 533 |
+
row_anchors = np.concatenate(pending_anchors)
|
| 534 |
+
tokens, anchors = require_native().spatial_encode_instances(
|
| 535 |
+
np.ascontiguousarray(codes),
|
| 536 |
+
np.ascontiguousarray(row_anchors),
|
| 537 |
+
[int(count) for count in spatial.bins],
|
| 538 |
+
np.ascontiguousarray(merges),
|
| 539 |
+
int(threads),
|
| 540 |
+
)
|
| 541 |
+
return np.asarray(tokens, dtype=np.int32), np.asarray(
|
| 542 |
+
anchors, dtype=np.int64
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
for episode in episodes:
|
| 546 |
+
length = len(episode)
|
| 547 |
+
pending.append(episode)
|
| 548 |
+
pending_anchors.append(np.arange(base, base + length, dtype=np.int64))
|
| 549 |
+
pending_actions += length
|
| 550 |
+
base += length + lookahead
|
| 551 |
+
if pending_actions >= max_batch_actions:
|
| 552 |
+
yield encode()
|
| 553 |
+
pending.clear()
|
| 554 |
+
pending_anchors.clear()
|
| 555 |
+
pending_actions = 0
|
| 556 |
+
if pending:
|
| 557 |
+
yield encode()
|
| 558 |
+
|
| 559 |
+
return generate(), total_anchors, actions
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
def _flatten_cell_corpus(
|
| 563 |
+
episodes: Sequence[np.ndarray],
|
| 564 |
+
spatial: SpatialVocabulary,
|
| 565 |
+
lookahead: int,
|
| 566 |
+
threads: int,
|
| 567 |
+
) -> tuple[Iterator[tuple[np.ndarray, np.ndarray]], int, int]:
|
| 568 |
+
|
| 569 |
+
actions = sum(len(codes) for codes in episodes.chunks if codes is not None)
|
| 570 |
+
total_anchors = actions + len(episodes) * lookahead
|
| 571 |
+
merges = np.asarray(spatial.merges, dtype=np.int32).reshape(-1, 2)
|
| 572 |
+
chunks = episodes.chunks
|
| 573 |
+
offsets_by_chunk = episodes.offsets
|
| 574 |
+
|
| 575 |
+
def generate() -> Iterator[tuple[np.ndarray, np.ndarray]]:
|
| 576 |
+
base = 0
|
| 577 |
+
for chunk_index, (codes, offsets) in enumerate(
|
| 578 |
+
zip(chunks, offsets_by_chunk, strict=True)
|
| 579 |
+
):
|
| 580 |
+
if codes is None:
|
| 581 |
+
raise RuntimeError("cell corpus chunk has already been consumed")
|
| 582 |
+
lengths = np.diff(offsets)
|
| 583 |
+
row_anchors = (
|
| 584 |
+
base
|
| 585 |
+
+ np.arange(len(codes), dtype=np.int64)
|
| 586 |
+
+ np.repeat(
|
| 587 |
+
np.arange(len(lengths), dtype=np.int64) * lookahead, lengths
|
| 588 |
+
)
|
| 589 |
+
)
|
| 590 |
+
tokens, anchors = require_native().spatial_encode_instances(
|
| 591 |
+
np.ascontiguousarray(codes),
|
| 592 |
+
row_anchors,
|
| 593 |
+
[int(count) for count in spatial.bins],
|
| 594 |
+
np.ascontiguousarray(merges),
|
| 595 |
+
int(threads),
|
| 596 |
+
)
|
| 597 |
+
episodes.release_chunk(chunk_index)
|
| 598 |
+
yield (
|
| 599 |
+
np.asarray(tokens, dtype=np.int32),
|
| 600 |
+
np.asarray(anchors, dtype=np.int64),
|
| 601 |
+
)
|
| 602 |
+
base += len(codes) + len(lengths) * lookahead
|
| 603 |
+
|
| 604 |
+
return generate(), total_anchors, actions
|
| 605 |
+
|
| 606 |
+
|
| 607 |
+
def train_temporal(
|
| 608 |
+
episodes: Iterable[np.ndarray],
|
| 609 |
+
spatial: SpatialVocabulary,
|
| 610 |
+
*,
|
| 611 |
+
vocab_size: int,
|
| 612 |
+
lookahead: int,
|
| 613 |
+
backend: str = "auto",
|
| 614 |
+
progress: bool = False,
|
| 615 |
+
threads: int = 0,
|
| 616 |
+
) -> tuple[TemporalVocabulary, dict[str, object]]:
|
| 617 |
+
"Fit rooted temporal Set-BPE on top of a frozen spatial vocabulary."
|
| 618 |
+
prepared = _prepare_episodes(episodes, spatial)
|
| 619 |
+
if resolve_backend(backend) == "python":
|
| 620 |
+
return _train_temporal_python(
|
| 621 |
+
prepared,
|
| 622 |
+
spatial,
|
| 623 |
+
vocab_size=vocab_size,
|
| 624 |
+
lookahead=lookahead,
|
| 625 |
+
progress=progress,
|
| 626 |
+
)
|
| 627 |
+
episode_count = len(prepared)
|
| 628 |
+
instance_chunks, total_anchors, actions = _flatten_instances_native(
|
| 629 |
+
prepared, spatial, lookahead, threads
|
| 630 |
+
)
|
| 631 |
+
merges, report = require_native().train_temporal(
|
| 632 |
+
instance_chunks,
|
| 633 |
+
int(total_anchors),
|
| 634 |
+
np.ascontiguousarray(spatial.support, dtype=np.uint64),
|
| 635 |
+
np.ascontiguousarray(spatial.anchor, dtype=np.int32),
|
| 636 |
+
int(spatial.dimension),
|
| 637 |
+
int(lookahead),
|
| 638 |
+
int(vocab_size),
|
| 639 |
+
int(actions),
|
| 640 |
+
int(threads),
|
| 641 |
+
)
|
| 642 |
+
vocabulary = TemporalVocabulary(
|
| 643 |
+
spatial,
|
| 644 |
+
int(lookahead),
|
| 645 |
+
tuple(tuple(int(value) for value in rule) for rule in merges),
|
| 646 |
+
)
|
| 647 |
+
payload = dict(report)
|
| 648 |
+
payload["episodes"] = episode_count
|
| 649 |
+
return vocabulary, payload
|
runtime/training/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Internal training components for ActionCodec2."""
|
runtime/training/fitting.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Replayable, bounded source staging for the public ActionCodec2.fit interface."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from collections.abc import Mapping
|
| 6 |
+
from contextlib import ExitStack
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from tempfile import TemporaryDirectory
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
from ..physical.boundary import bootstrap_second_order_trajectory
|
| 14 |
+
from ..routing.routing import ActionCodec2Router
|
| 15 |
+
from ..frontend.tokenizer import _numpy
|
| 16 |
+
from .training import ProfileTrainer
|
| 17 |
+
from ..physical.trajectory import ActiveTrajectory
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TrajectorySpool:
|
| 21 |
+
"Stage canonical episodes in two sequential files, without retaining source data."
|
| 22 |
+
|
| 23 |
+
def __init__(self, directory):
|
| 24 |
+
self.directory = Path(directory)
|
| 25 |
+
self.counts = {}
|
| 26 |
+
self._stack = ExitStack()
|
| 27 |
+
self._writers = {}
|
| 28 |
+
|
| 29 |
+
def __enter__(self):
|
| 30 |
+
return self
|
| 31 |
+
|
| 32 |
+
def __exit__(self, *exc):
|
| 33 |
+
return self._stack.__exit__(*exc)
|
| 34 |
+
|
| 35 |
+
def append(self, profile, trajectory):
|
| 36 |
+
if profile not in self._writers:
|
| 37 |
+
self._writers[profile] = self._stack.enter_context(
|
| 38 |
+
(self.directory / f"{profile}.npy").open("wb")
|
| 39 |
+
)
|
| 40 |
+
stream = self._writers[profile]
|
| 41 |
+
for value in (
|
| 42 |
+
trajectory.values,
|
| 43 |
+
trajectory.active_dims,
|
| 44 |
+
[]
|
| 45 |
+
if trajectory.previous_first_order is None
|
| 46 |
+
else trajectory.previous_first_order,
|
| 47 |
+
):
|
| 48 |
+
np.save(stream, np.asarray(value), allow_pickle=False)
|
| 49 |
+
self.counts[profile] = self.counts.get(profile, 0) + 1
|
| 50 |
+
|
| 51 |
+
def trajectories(self, profile):
|
| 52 |
+
self._writers[profile].flush()
|
| 53 |
+
with (self.directory / f"{profile}.npy").open("rb") as stream:
|
| 54 |
+
for _ in range(self.counts[profile]):
|
| 55 |
+
values = np.load(stream, allow_pickle=False)
|
| 56 |
+
dims = tuple(int(d) for d in np.load(stream, allow_pickle=False))
|
| 57 |
+
previous = np.load(stream, allow_pickle=False)
|
| 58 |
+
yield ActiveTrajectory(
|
| 59 |
+
values,
|
| 60 |
+
dims,
|
| 61 |
+
previous_first_order=previous if previous.size else None,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class ActionCodec2Fitter:
|
| 66 |
+
"Route user layouts and fit replacement profiles without mutating the caller."
|
| 67 |
+
|
| 68 |
+
def __init__(self, codec, *, bootstrap_context=False):
|
| 69 |
+
if not isinstance(bootstrap_context, bool):
|
| 70 |
+
raise TypeError("bootstrap_context must be a boolean")
|
| 71 |
+
self.codec = codec
|
| 72 |
+
self.router = codec.action_tokenizer
|
| 73 |
+
self.bootstrap_context = bootstrap_context
|
| 74 |
+
|
| 75 |
+
def _route(self, record):
|
| 76 |
+
unknown = set(record) - {
|
| 77 |
+
"actions",
|
| 78 |
+
"action_space",
|
| 79 |
+
"fps",
|
| 80 |
+
"current_state",
|
| 81 |
+
"previous_first_order",
|
| 82 |
+
}
|
| 83 |
+
if unknown:
|
| 84 |
+
raise ValueError(f"unknown training record fields: {sorted(unknown)}")
|
| 85 |
+
if "actions" not in record:
|
| 86 |
+
raise ValueError("training record requires an 'actions' array")
|
| 87 |
+
preset = self.router.preset(
|
| 88 |
+
record.get("action_space", self.codec.action_space.name)
|
| 89 |
+
)
|
| 90 |
+
values = _numpy(torch.as_tensor(record["actions"]))
|
| 91 |
+
if values.ndim == 2:
|
| 92 |
+
values = values[None]
|
| 93 |
+
if values.ndim != 3 or values.shape[0] == 0 or values.shape[1] == 0:
|
| 94 |
+
raise ValueError(
|
| 95 |
+
"training actions must have nonempty shape (T,D) or (B,T,D)"
|
| 96 |
+
)
|
| 97 |
+
current = _numpy(record.get("current_state"))
|
| 98 |
+
if preset.input == "absolute" and current is None:
|
| 99 |
+
raise ValueError(
|
| 100 |
+
f"absolute action space {preset.name!r} requires current_state(B,D)"
|
| 101 |
+
)
|
| 102 |
+
if preset.input == "delta" and current is not None:
|
| 103 |
+
raise ValueError(f"delta action space {preset.name!r}: omit current_state")
|
| 104 |
+
previous = self.codec._numpy_boundary(record.get("previous_first_order"))
|
| 105 |
+
kwargs = dict(
|
| 106 |
+
preset=preset.name,
|
| 107 |
+
fps=self.codec._source_fps(record.get("fps"), preset),
|
| 108 |
+
current_state=current,
|
| 109 |
+
)
|
| 110 |
+
if not self.bootstrap_context:
|
| 111 |
+
return self.router.route_trajectories(
|
| 112 |
+
values, previous_first_order=previous, **kwargs
|
| 113 |
+
)
|
| 114 |
+
if previous is not None:
|
| 115 |
+
raise ValueError(
|
| 116 |
+
"bootstrap_context and previous_first_order are mutually exclusive"
|
| 117 |
+
)
|
| 118 |
+
canonical = preset.canonicalize(
|
| 119 |
+
values, source_hz=kwargs["fps"], current_state=current
|
| 120 |
+
)
|
| 121 |
+
routed = {}
|
| 122 |
+
for component in preset.components:
|
| 123 |
+
processor = self.router.profiles[component.space].processor
|
| 124 |
+
trajectories = canonical[component.name]
|
| 125 |
+
if processor.requires_previous_first_order:
|
| 126 |
+
trajectories = [
|
| 127 |
+
bootstrap_second_order_trajectory(t, processor.config)
|
| 128 |
+
for t in trajectories
|
| 129 |
+
]
|
| 130 |
+
routed.setdefault(component.space, []).extend(trajectories)
|
| 131 |
+
return routed
|
| 132 |
+
|
| 133 |
+
def fit(self, episodes, *, work_dir=None, metadata=None, **options):
|
| 134 |
+
"""Fit observed profiles from arrays or records, replaying staged episodes per profile."""
|
| 135 |
+
metadata = {k: v for k, v in (metadata or {}).items() if v is not None}
|
| 136 |
+
if isinstance(episodes, (np.ndarray, torch.Tensor, Mapping)):
|
| 137 |
+
episodes = (episodes,)
|
| 138 |
+
reports = {}
|
| 139 |
+
processors = {
|
| 140 |
+
name: runtime.processor for name, runtime in self.router.profiles.items()
|
| 141 |
+
}
|
| 142 |
+
records = 0
|
| 143 |
+
with TemporaryDirectory(prefix="actioncodec2-fit-", dir=work_dir) as directory:
|
| 144 |
+
with TrajectorySpool(directory) as spool:
|
| 145 |
+
for episode in episodes:
|
| 146 |
+
record = (
|
| 147 |
+
dict(episode)
|
| 148 |
+
if isinstance(episode, Mapping)
|
| 149 |
+
else {"actions": episode}
|
| 150 |
+
)
|
| 151 |
+
duplicates = set(record).intersection(metadata)
|
| 152 |
+
if duplicates:
|
| 153 |
+
raise ValueError(
|
| 154 |
+
f"training arguments provided twice: {sorted(duplicates)}"
|
| 155 |
+
)
|
| 156 |
+
record.update(metadata)
|
| 157 |
+
for profile, trajectories in self._route(record).items():
|
| 158 |
+
for trajectory in trajectories:
|
| 159 |
+
spool.append(profile, trajectory)
|
| 160 |
+
records += 1
|
| 161 |
+
if not spool.counts:
|
| 162 |
+
raise ValueError("fit requires at least one nonempty episode")
|
| 163 |
+
for name in spool.counts:
|
| 164 |
+
processors[name], reports[name] = ProfileTrainer(
|
| 165 |
+
processors[name].config
|
| 166 |
+
).fit(spool.trajectories(name), **options)
|
| 167 |
+
router = ActionCodec2Router.from_processors(
|
| 168 |
+
processors,
|
| 169 |
+
token_budgets={
|
| 170 |
+
name: runtime.spec.token_budget
|
| 171 |
+
for name, runtime in self.router.profiles.items()
|
| 172 |
+
},
|
| 173 |
+
presets=list(self.router.action_spaces.values()),
|
| 174 |
+
)
|
| 175 |
+
return router, {
|
| 176 |
+
"records": records,
|
| 177 |
+
"bootstrap_context": self.bootstrap_context,
|
| 178 |
+
"profiles": reports,
|
| 179 |
+
}
|
runtime/training/training.py
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"Whole-trajectory primitive construction and BPE fitting."
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
import time
|
| 7 |
+
from bisect import bisect_right
|
| 8 |
+
from collections import defaultdict
|
| 9 |
+
from collections.abc import Iterable, Sequence
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
from ..common._validation import broadcast_vector
|
| 14 |
+
from ..tokenization.bpe import BPESerializer, SetBPESerializer
|
| 15 |
+
from ..tokenization.bpe_training import train_bpe
|
| 16 |
+
from ..physical.configuration import ActionCodec2Config, ConfigError
|
| 17 |
+
from ..physical.physical_codec import PhysicalCodec
|
| 18 |
+
from ..physical.physical_processor import ActionCodec2Processor
|
| 19 |
+
from ..physical.primitives import PrimitiveStore
|
| 20 |
+
from ..tokenization.setbpe_spatial import aggregate_records
|
| 21 |
+
from ..tokenization.setbpe_spatial_training import train_spatial
|
| 22 |
+
from ..tokenization.setbpe_temporal_training import train_temporal
|
| 23 |
+
from ..physical.trajectory import (
|
| 24 |
+
TrajectoryLike,
|
| 25 |
+
prepare_trajectory,
|
| 26 |
+
trajectory_boundary,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _zero_anchored_grid(
|
| 31 |
+
lower: np.float32, upper: np.float32, spacing: np.float32
|
| 32 |
+
) -> np.ndarray:
|
| 33 |
+
|
| 34 |
+
below = int(np.ceil(-lower / spacing))
|
| 35 |
+
above = int(np.ceil(upper / spacing))
|
| 36 |
+
negative = np.linspace(lower, np.float32(0.0), below + 1, dtype=np.float32)[:-1]
|
| 37 |
+
positive = np.linspace(np.float32(0.0), upper, above + 1, dtype=np.float32)
|
| 38 |
+
return np.concatenate((negative, positive))
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _additive_axis(
|
| 42 |
+
lower: np.float32,
|
| 43 |
+
upper: np.float32,
|
| 44 |
+
distortion_limit: np.float32,
|
| 45 |
+
*,
|
| 46 |
+
zero_primitive: bool,
|
| 47 |
+
) -> np.ndarray:
|
| 48 |
+
|
| 49 |
+
width = np.float32(upper - lower)
|
| 50 |
+
if width == 0.0:
|
| 51 |
+
return np.asarray([lower], dtype=np.float32)
|
| 52 |
+
spacing = np.float32(2.0) * distortion_limit
|
| 53 |
+
if zero_primitive and lower < 0.0 < upper:
|
| 54 |
+
return _zero_anchored_grid(lower, upper, spacing)
|
| 55 |
+
count = 1 + int(np.ceil(width / spacing))
|
| 56 |
+
return np.linspace(lower, upper, count, dtype=np.float32)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def build_primitives(config: ActionCodec2Config) -> PrimitiveStore:
|
| 60 |
+
"Derive primitive arrays and force an exact zero on every axis."
|
| 61 |
+
if config.train is None:
|
| 62 |
+
raise ConfigError("config.train is required to build primitives")
|
| 63 |
+
output: list[np.ndarray | None] = [None] * config.physical.input_dim
|
| 64 |
+
for component in config.physical.components.values():
|
| 65 |
+
width = len(component.dims)
|
| 66 |
+
if component.space == "additive":
|
| 67 |
+
domain = component.constraints[f"order_{component.primitive_order}"]
|
| 68 |
+
lower = broadcast_vector(
|
| 69 |
+
domain.minimum, width, dtype=np.dtype(np.float32), name="minimum"
|
| 70 |
+
)
|
| 71 |
+
upper = broadcast_vector(
|
| 72 |
+
domain.maximum, width, dtype=np.dtype(np.float32), name="maximum"
|
| 73 |
+
)
|
| 74 |
+
epsilon = broadcast_vector(
|
| 75 |
+
component.distortion_limit,
|
| 76 |
+
width,
|
| 77 |
+
dtype=np.dtype(np.float32),
|
| 78 |
+
name="distortion_limit",
|
| 79 |
+
)
|
| 80 |
+
if component.primitive_order == 2:
|
| 81 |
+
|
| 82 |
+
lower = lower - 2 * epsilon
|
| 83 |
+
upper = upper + 2 * epsilon
|
| 84 |
+
axes = [
|
| 85 |
+
_additive_axis(
|
| 86 |
+
lower[index],
|
| 87 |
+
upper[index],
|
| 88 |
+
epsilon[index],
|
| 89 |
+
zero_primitive=True,
|
| 90 |
+
)
|
| 91 |
+
for index in range(width)
|
| 92 |
+
]
|
| 93 |
+
else:
|
| 94 |
+
domain = component.constraints[f"order_{component.primitive_order}"]
|
| 95 |
+
radius_dtype = (
|
| 96 |
+
np.float64
|
| 97 |
+
if component.representation == "rotation_vector"
|
| 98 |
+
else np.float32
|
| 99 |
+
)
|
| 100 |
+
lower = broadcast_vector(
|
| 101 |
+
domain.minimum, width, dtype=np.dtype(radius_dtype), name="minimum"
|
| 102 |
+
)
|
| 103 |
+
upper = broadcast_vector(
|
| 104 |
+
domain.maximum, width, dtype=np.dtype(radius_dtype), name="maximum"
|
| 105 |
+
)
|
| 106 |
+
corner = np.maximum(np.abs(lower), np.abs(upper))
|
| 107 |
+
source_radius = (
|
| 108 |
+
float(np.linalg.norm(corner))
|
| 109 |
+
if component.representation == "rotation_vector"
|
| 110 |
+
else float(np.sum(corner, dtype=np.float32))
|
| 111 |
+
)
|
| 112 |
+
source_radius = min(math.pi, source_radius)
|
| 113 |
+
epsilon = float(component.distortion_limit)
|
| 114 |
+
allowance = (3 if component.primitive_order == 2 else 1) * epsilon
|
| 115 |
+
cover_radius = float(min(math.pi, source_radius + allowance))
|
| 116 |
+
maximum_spacing = 2.0 * epsilon / math.sqrt(3.0)
|
| 117 |
+
count = int(math.ceil(2.0 * cover_radius / maximum_spacing)) + 1
|
| 118 |
+
if count % 2 == 0:
|
| 119 |
+
count += 1
|
| 120 |
+
axis = np.linspace(-cover_radius, cover_radius, count, dtype=np.float64)
|
| 121 |
+
if count % 2 == 1:
|
| 122 |
+
axis[count // 2] = 0.0
|
| 123 |
+
axes = [axis.copy() for _ in range(width)]
|
| 124 |
+
for dimension, axis in zip(component.dims, axes, strict=True):
|
| 125 |
+
output[dimension] = axis
|
| 126 |
+
if any(axis is None for axis in output):
|
| 127 |
+
raise RuntimeError(
|
| 128 |
+
"internal error: primitive construction left an unassigned dimension"
|
| 129 |
+
)
|
| 130 |
+
store = PrimitiveStore([axis for axis in output if axis is not None])
|
| 131 |
+
missing_zero = [
|
| 132 |
+
dimension
|
| 133 |
+
for dimension, axis in enumerate(store.values)
|
| 134 |
+
if not np.any(np.asarray(axis) == 0)
|
| 135 |
+
]
|
| 136 |
+
if missing_zero:
|
| 137 |
+
raise ConfigError(
|
| 138 |
+
"every fitted primitive axis must contain exact zero; missing dimension(s) "
|
| 139 |
+
f"{missing_zero}. Ensure each configured domain contains zero."
|
| 140 |
+
)
|
| 141 |
+
return store
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def _validate_component_support(
|
| 145 |
+
active_dims: tuple[int, ...], config: ActionCodec2Config, *, index: int
|
| 146 |
+
) -> None:
|
| 147 |
+
active = set(active_dims)
|
| 148 |
+
for name, component in config.physical.components.items():
|
| 149 |
+
present = active.intersection(component.dims)
|
| 150 |
+
if component.space == "so3" and present and present != set(component.dims):
|
| 151 |
+
raise ValueError(
|
| 152 |
+
f"trajectory {index} activates only part of SO(3) component {name!r}"
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _length_buckets(
|
| 157 |
+
pending: Sequence[tuple[np.ndarray, tuple[int, ...], np.ndarray | None]],
|
| 158 |
+
*,
|
| 159 |
+
maximum_ratio: float = 1.25,
|
| 160 |
+
) -> list[list[int]]:
|
| 161 |
+
|
| 162 |
+
ordered = sorted(range(len(pending)), key=lambda index: len(pending[index][0]))
|
| 163 |
+
buckets: list[list[int]] = []
|
| 164 |
+
current: list[int] = []
|
| 165 |
+
shortest = 0
|
| 166 |
+
for index in ordered:
|
| 167 |
+
length = len(pending[index][0])
|
| 168 |
+
if current and length > shortest * maximum_ratio:
|
| 169 |
+
buckets.append(current)
|
| 170 |
+
current = []
|
| 171 |
+
if not current:
|
| 172 |
+
shortest = length
|
| 173 |
+
current.append(index)
|
| 174 |
+
if current:
|
| 175 |
+
buckets.append(current)
|
| 176 |
+
return buckets
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
class _CellCorpus(Sequence[np.ndarray]):
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
_actioncodec2_validated = True
|
| 183 |
+
|
| 184 |
+
def __init__(self, chunks: list[np.ndarray], offsets: list[np.ndarray]) -> None:
|
| 185 |
+
self.chunks: list[np.ndarray | None] = list(chunks)
|
| 186 |
+
self.offsets = tuple(offsets)
|
| 187 |
+
counts = np.asarray([len(value) - 1 for value in offsets], dtype=np.int64)
|
| 188 |
+
self._episode_ends = np.cumsum(counts)
|
| 189 |
+
|
| 190 |
+
def __len__(self) -> int:
|
| 191 |
+
return int(self._episode_ends[-1]) if len(self._episode_ends) else 0
|
| 192 |
+
|
| 193 |
+
def __getitem__(self, index: int) -> np.ndarray:
|
| 194 |
+
if not isinstance(index, int):
|
| 195 |
+
raise TypeError("cell corpus indices must be integers")
|
| 196 |
+
if index < 0:
|
| 197 |
+
index += len(self)
|
| 198 |
+
if not 0 <= index < len(self):
|
| 199 |
+
raise IndexError(index)
|
| 200 |
+
chunk = bisect_right(self._episode_ends, index)
|
| 201 |
+
previous = 0 if chunk == 0 else int(self._episode_ends[chunk - 1])
|
| 202 |
+
local = index - previous
|
| 203 |
+
start, stop = self.offsets[chunk][local : local + 2]
|
| 204 |
+
values = self.chunks[chunk]
|
| 205 |
+
if values is None:
|
| 206 |
+
raise RuntimeError("cell corpus chunk has already been consumed")
|
| 207 |
+
return values[int(start) : int(stop)]
|
| 208 |
+
|
| 209 |
+
def __iter__(self):
|
| 210 |
+
for chunk, offsets in zip(self.chunks, self.offsets, strict=True):
|
| 211 |
+
if chunk is None:
|
| 212 |
+
raise RuntimeError("cell corpus chunk has already been consumed")
|
| 213 |
+
for start, stop in zip(offsets[:-1], offsets[1:], strict=True):
|
| 214 |
+
yield chunk[int(start) : int(stop)]
|
| 215 |
+
|
| 216 |
+
def release_chunk(self, index: int) -> None:
|
| 217 |
+
"""Release a replayed chunk while retaining compact episode metadata."""
|
| 218 |
+
self.chunks[index] = None
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def _quantize_pending_batch(
|
| 222 |
+
pending: Sequence[tuple[np.ndarray, tuple[int, ...], np.ndarray | None]],
|
| 223 |
+
physical: PhysicalCodec,
|
| 224 |
+
cell_dtype: type[np.signedinteger],
|
| 225 |
+
) -> list[np.ndarray]:
|
| 226 |
+
|
| 227 |
+
result: list[np.ndarray | None] = [None] * len(pending)
|
| 228 |
+
dimension = physical.config.physical.input_dim
|
| 229 |
+
for indices in _length_buckets(pending):
|
| 230 |
+
length = max(len(pending[index][0]) for index in indices)
|
| 231 |
+
batch = np.zeros((len(indices), length, dimension), dtype=np.float32)
|
| 232 |
+
active_by_item: list[tuple[int, ...]] = []
|
| 233 |
+
for local, index in enumerate(indices):
|
| 234 |
+
trajectory, active_dims, _ = pending[index]
|
| 235 |
+
item = batch[local, : len(trajectory)]
|
| 236 |
+
item[:, active_dims] = trajectory
|
| 237 |
+
active_by_item.append(active_dims)
|
| 238 |
+
boundaries = [pending[index][2] for index in indices]
|
| 239 |
+
previous = (
|
| 240 |
+
None
|
| 241 |
+
if all(value is None for value in boundaries)
|
| 242 |
+
else np.stack(
|
| 243 |
+
[
|
| 244 |
+
np.zeros(dimension, dtype=np.float32) if value is None else value
|
| 245 |
+
for value in boundaries
|
| 246 |
+
]
|
| 247 |
+
)
|
| 248 |
+
)
|
| 249 |
+
encoded = physical.encode_batch(batch, previous_first_order=previous).astype(
|
| 250 |
+
cell_dtype, copy=False
|
| 251 |
+
)
|
| 252 |
+
for local, index in enumerate(indices):
|
| 253 |
+
item_length = len(pending[index][0])
|
| 254 |
+
dense = np.full((item_length, dimension), -1, dtype=cell_dtype)
|
| 255 |
+
dense[:, active_by_item[local]] = encoded[local][
|
| 256 |
+
:item_length, active_by_item[local]
|
| 257 |
+
]
|
| 258 |
+
result[index] = dense
|
| 259 |
+
if any(value is None for value in result):
|
| 260 |
+
raise RuntimeError("internal error: trajectory batch was not fully quantized")
|
| 261 |
+
return [value for value in result if value is not None]
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def _quantize_trajectories(
|
| 265 |
+
trajectories: Iterable[TrajectoryLike],
|
| 266 |
+
physical: PhysicalCodec,
|
| 267 |
+
*,
|
| 268 |
+
batch_size: int,
|
| 269 |
+
max_cell_bytes: int | None,
|
| 270 |
+
) -> tuple[_CellCorpus, int, int, dict[int, int]]:
|
| 271 |
+
|
| 272 |
+
if batch_size < 1:
|
| 273 |
+
raise ValueError("batch_size must be positive")
|
| 274 |
+
cell_chunks: list[np.ndarray] = []
|
| 275 |
+
offset_chunks: list[np.ndarray] = []
|
| 276 |
+
cell_parts: list[np.ndarray] = []
|
| 277 |
+
cell_lengths: list[int] = []
|
| 278 |
+
chunk_actions = 0
|
| 279 |
+
action_count = 0
|
| 280 |
+
active_value_count = 0
|
| 281 |
+
arity_counts: dict[int, int] = defaultdict(int)
|
| 282 |
+
source_index = 0
|
| 283 |
+
retained_cell_bytes = 0
|
| 284 |
+
cell_dtype = np.int16 if max(physical.bins) <= np.iinfo(np.int16).max else np.int32
|
| 285 |
+
iterator = iter(trajectories)
|
| 286 |
+
|
| 287 |
+
def flush_cells() -> None:
|
| 288 |
+
nonlocal chunk_actions, retained_cell_bytes
|
| 289 |
+
if not cell_parts:
|
| 290 |
+
return
|
| 291 |
+
chunk = np.concatenate(cell_parts, axis=0)
|
| 292 |
+
projected = retained_cell_bytes + chunk.nbytes
|
| 293 |
+
if max_cell_bytes is not None and projected > max_cell_bytes:
|
| 294 |
+
raise MemoryError(
|
| 295 |
+
"quantized ActionCodec2 corpus would retain at least "
|
| 296 |
+
f"{projected / 2**30:.2f} GiB, exceeding the configured "
|
| 297 |
+
f"{max_cell_bytes / 2**30:.2f} GiB limit; use stratified fitting, "
|
| 298 |
+
"lower coverage, or explicitly raise --max-cell-memory-gib"
|
| 299 |
+
)
|
| 300 |
+
cell_chunks.append(chunk)
|
| 301 |
+
retained_cell_bytes = projected
|
| 302 |
+
offset_chunks.append(
|
| 303 |
+
np.concatenate(([0], np.cumsum(cell_lengths, dtype=np.int64)))
|
| 304 |
+
)
|
| 305 |
+
cell_parts.clear()
|
| 306 |
+
cell_lengths.clear()
|
| 307 |
+
chunk_actions = 0
|
| 308 |
+
|
| 309 |
+
while True:
|
| 310 |
+
pending: list[tuple[np.ndarray, tuple[int, ...], np.ndarray | None]] = []
|
| 311 |
+
for _ in range(batch_size):
|
| 312 |
+
try:
|
| 313 |
+
raw = next(iterator)
|
| 314 |
+
except StopIteration:
|
| 315 |
+
break
|
| 316 |
+
trajectory, active_dims = prepare_trajectory(
|
| 317 |
+
raw,
|
| 318 |
+
index=source_index,
|
| 319 |
+
dimension=physical.config.physical.input_dim,
|
| 320 |
+
dtype=np.float32,
|
| 321 |
+
)
|
| 322 |
+
_validate_component_support(
|
| 323 |
+
active_dims, physical.config, index=source_index
|
| 324 |
+
)
|
| 325 |
+
source_index += 1
|
| 326 |
+
action_count += len(trajectory)
|
| 327 |
+
active_value_count += len(trajectory) * len(active_dims)
|
| 328 |
+
arity_counts[len(active_dims)] += 1
|
| 329 |
+
pending.append(
|
| 330 |
+
(
|
| 331 |
+
trajectory,
|
| 332 |
+
active_dims,
|
| 333 |
+
trajectory_boundary(raw, physical.config, active_dims),
|
| 334 |
+
)
|
| 335 |
+
)
|
| 336 |
+
if not pending:
|
| 337 |
+
break
|
| 338 |
+
|
| 339 |
+
for value in _quantize_pending_batch(pending, physical, cell_dtype):
|
| 340 |
+
cell_parts.append(value)
|
| 341 |
+
cell_lengths.append(len(value))
|
| 342 |
+
chunk_actions += len(value)
|
| 343 |
+
if chunk_actions >= 1_000_000:
|
| 344 |
+
flush_cells()
|
| 345 |
+
flush_cells()
|
| 346 |
+
cells = _CellCorpus(cell_chunks, offset_chunks)
|
| 347 |
+
if not cells:
|
| 348 |
+
raise ValueError("training requires at least one complete trajectory")
|
| 349 |
+
return cells, action_count, active_value_count, dict(sorted(arity_counts.items()))
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def _aggregate_trajectory_records(
|
| 353 |
+
cells: Sequence[np.ndarray], bins: Sequence[int], action_count: int
|
| 354 |
+
) -> tuple[np.ndarray, np.ndarray]:
|
| 355 |
+
|
| 356 |
+
del action_count
|
| 357 |
+
dimension = len(tuple(bins))
|
| 358 |
+
widths = tuple(int(count).bit_length() for count in bins)
|
| 359 |
+
if sum(widths) <= 64:
|
| 360 |
+
return _aggregate_packed_records(cells, bins, widths)
|
| 361 |
+
counts_by_row: dict[bytes, int] = defaultdict(int)
|
| 362 |
+
pending: list[np.ndarray] = []
|
| 363 |
+
pending_rows = 0
|
| 364 |
+
chunk_rows = 1_000_000
|
| 365 |
+
|
| 366 |
+
def flush() -> None:
|
| 367 |
+
nonlocal pending_rows
|
| 368 |
+
if not pending:
|
| 369 |
+
return
|
| 370 |
+
block = (
|
| 371 |
+
np.asarray(pending[0], dtype=np.int32)
|
| 372 |
+
if len(pending) == 1
|
| 373 |
+
else np.concatenate(pending, axis=0).astype(np.int32, copy=False)
|
| 374 |
+
)
|
| 375 |
+
unique, counts = aggregate_records(block, bins)
|
| 376 |
+
for row, count in zip(unique.astype(np.int32, copy=False), counts, strict=True):
|
| 377 |
+
counts_by_row[row.tobytes()] += int(count)
|
| 378 |
+
pending.clear()
|
| 379 |
+
pending_rows = 0
|
| 380 |
+
|
| 381 |
+
for trajectory in cells:
|
| 382 |
+
pending.append(np.asarray(trajectory, dtype=np.int32))
|
| 383 |
+
pending_rows += len(trajectory)
|
| 384 |
+
if pending_rows >= chunk_rows:
|
| 385 |
+
flush()
|
| 386 |
+
flush()
|
| 387 |
+
keys = sorted(counts_by_row)
|
| 388 |
+
records = np.frombuffer(b"".join(keys), dtype=np.int32).reshape(-1, dimension)
|
| 389 |
+
weights = np.fromiter(
|
| 390 |
+
(counts_by_row[key] for key in keys), dtype=np.int64, count=len(keys)
|
| 391 |
+
)
|
| 392 |
+
return records, weights
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def _aggregate_packed_records(
|
| 396 |
+
cells: Sequence[np.ndarray], bins: Sequence[int], widths: Sequence[int]
|
| 397 |
+
) -> tuple[np.ndarray, np.ndarray]:
|
| 398 |
+
|
| 399 |
+
counts_by_key: dict[int, int] = defaultdict(int)
|
| 400 |
+
pending: list[np.ndarray] = []
|
| 401 |
+
pending_rows = 0
|
| 402 |
+
chunk_rows = 1_000_000
|
| 403 |
+
|
| 404 |
+
def flush() -> None:
|
| 405 |
+
nonlocal pending_rows
|
| 406 |
+
if not pending:
|
| 407 |
+
return
|
| 408 |
+
block = pending[0] if len(pending) == 1 else np.concatenate(pending, axis=0)
|
| 409 |
+
keys = np.zeros(len(block), dtype=np.uint64)
|
| 410 |
+
shift = 0
|
| 411 |
+
for dimension, width in enumerate(widths):
|
| 412 |
+
encoded = np.asarray(block[:, dimension], dtype=np.int64) + 1
|
| 413 |
+
keys |= encoded.astype(np.uint64) << np.uint64(shift)
|
| 414 |
+
shift += width
|
| 415 |
+
unique, counts = np.unique(keys, return_counts=True)
|
| 416 |
+
for key, count in zip(unique, counts, strict=True):
|
| 417 |
+
counts_by_key[int(key)] += int(count)
|
| 418 |
+
pending.clear()
|
| 419 |
+
pending_rows = 0
|
| 420 |
+
|
| 421 |
+
for trajectory in cells:
|
| 422 |
+
pending.append(trajectory)
|
| 423 |
+
pending_rows += len(trajectory)
|
| 424 |
+
if pending_rows >= chunk_rows:
|
| 425 |
+
flush()
|
| 426 |
+
flush()
|
| 427 |
+
|
| 428 |
+
keys = np.fromiter(sorted(counts_by_key), dtype=np.uint64, count=len(counts_by_key))
|
| 429 |
+
records = np.empty((len(keys), len(tuple(bins))), dtype=np.int32)
|
| 430 |
+
shift = 0
|
| 431 |
+
for dimension, width in enumerate(widths):
|
| 432 |
+
mask = np.uint64((1 << width) - 1)
|
| 433 |
+
records[:, dimension] = ((keys >> np.uint64(shift)) & mask).astype(np.int32) - 1
|
| 434 |
+
shift += width
|
| 435 |
+
weights = np.fromiter(
|
| 436 |
+
(counts_by_key[int(key)] for key in keys), dtype=np.int64, count=len(keys)
|
| 437 |
+
)
|
| 438 |
+
return records, weights
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
def _fit_report(
|
| 442 |
+
config: ActionCodec2Config,
|
| 443 |
+
primitives: PrimitiveStore,
|
| 444 |
+
codec: ActionCodec2Processor,
|
| 445 |
+
*,
|
| 446 |
+
trajectory_count: int,
|
| 447 |
+
actions: int,
|
| 448 |
+
active_values: int,
|
| 449 |
+
arity_counts: dict[int, int],
|
| 450 |
+
cell_dtype: str,
|
| 451 |
+
unique_records: int,
|
| 452 |
+
spatial_report: dict[str, object],
|
| 453 |
+
temporal_report: dict[str, object],
|
| 454 |
+
timestamps: tuple[float, float, float, float, float, float],
|
| 455 |
+
) -> dict[str, object]:
|
| 456 |
+
started, primitive_done, quantized, aggregated, spatial_done, finished = timestamps
|
| 457 |
+
scheme = config.set_bpe.scheme
|
| 458 |
+
stages: dict[str, object] = (
|
| 459 |
+
{"bpe": temporal_report}
|
| 460 |
+
if scheme == "bpe"
|
| 461 |
+
else {"spatial": spatial_report, "temporal": temporal_report}
|
| 462 |
+
)
|
| 463 |
+
return {
|
| 464 |
+
"trajectories": trajectory_count,
|
| 465 |
+
"actions": actions,
|
| 466 |
+
"active_values": active_values,
|
| 467 |
+
"trajectory_arities": {str(key): value for key, value in arity_counts.items()},
|
| 468 |
+
"bpe_boundary": "complete_component_trajectory",
|
| 469 |
+
"bpe_training_domain": "quantized_primitive_cells",
|
| 470 |
+
"physical_boundary": (
|
| 471 |
+
"explicit_previous_first_order"
|
| 472 |
+
if codec.requires_previous_first_order
|
| 473 |
+
else "zero_position_identity_orientation"
|
| 474 |
+
),
|
| 475 |
+
"component_primitive_orders": {
|
| 476 |
+
name: component.primitive_order
|
| 477 |
+
for name, component in config.physical.components.items()
|
| 478 |
+
},
|
| 479 |
+
"allow_cross_component_merge": config.set_bpe.allow_cross_component_merge,
|
| 480 |
+
"lookahead": config.set_bpe.lookahead,
|
| 481 |
+
"physical_bins": list(primitives.bins),
|
| 482 |
+
"cell_dtype": cell_dtype,
|
| 483 |
+
"unique_spatial_records": unique_records,
|
| 484 |
+
"primitive_atoms": sum(primitives.bins),
|
| 485 |
+
"tokenization_scheme": scheme,
|
| 486 |
+
**stages,
|
| 487 |
+
"vocab_size": codec.vocab_size,
|
| 488 |
+
"tokens_per_action": float(temporal_report["tokens_per_action"]),
|
| 489 |
+
"seconds": {
|
| 490 |
+
"primitives": primitive_done - started,
|
| 491 |
+
"quantize": quantized - primitive_done,
|
| 492 |
+
"aggregate": aggregated - quantized,
|
| 493 |
+
"spatial_train": spatial_done - aggregated,
|
| 494 |
+
"spatial": spatial_done - quantized,
|
| 495 |
+
"temporal": finished - spatial_done,
|
| 496 |
+
"total": finished - started,
|
| 497 |
+
},
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
def _fit_set_bpe(
|
| 502 |
+
cells: Sequence[np.ndarray],
|
| 503 |
+
config: ActionCodec2Config,
|
| 504 |
+
primitives: PrimitiveStore,
|
| 505 |
+
actions: int,
|
| 506 |
+
*,
|
| 507 |
+
backend: str,
|
| 508 |
+
threads: int,
|
| 509 |
+
progress: bool,
|
| 510 |
+
) -> tuple[SetBPESerializer, dict[str, object], dict[str, object], int, float, float]:
|
| 511 |
+
records, weights = _aggregate_trajectory_records(cells, primitives.bins, actions)
|
| 512 |
+
aggregated = time.perf_counter()
|
| 513 |
+
bpe = config.train.bpe if config.train is not None else None
|
| 514 |
+
assert bpe is not None
|
| 515 |
+
spatial, spatial_report = train_spatial(
|
| 516 |
+
records,
|
| 517 |
+
weights,
|
| 518 |
+
primitives.bins,
|
| 519 |
+
vocab_size=bpe.spatial_vocab_size,
|
| 520 |
+
backend=backend,
|
| 521 |
+
threads=threads,
|
| 522 |
+
progress=progress,
|
| 523 |
+
)
|
| 524 |
+
spatial_done = time.perf_counter()
|
| 525 |
+
vocabulary, temporal_report = train_temporal(
|
| 526 |
+
cells,
|
| 527 |
+
spatial,
|
| 528 |
+
vocab_size=bpe.vocab_size,
|
| 529 |
+
lookahead=config.set_bpe.lookahead,
|
| 530 |
+
backend=backend,
|
| 531 |
+
threads=threads,
|
| 532 |
+
progress=progress,
|
| 533 |
+
)
|
| 534 |
+
serializer = SetBPESerializer(
|
| 535 |
+
bins=primitives.bins,
|
| 536 |
+
lookahead=config.set_bpe.lookahead,
|
| 537 |
+
spatial_merges=spatial.merges,
|
| 538 |
+
temporal_merges=vocabulary.merges,
|
| 539 |
+
)
|
| 540 |
+
return (
|
| 541 |
+
serializer,
|
| 542 |
+
spatial_report,
|
| 543 |
+
temporal_report,
|
| 544 |
+
len(records),
|
| 545 |
+
aggregated,
|
| 546 |
+
spatial_done,
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
def _fit_bpe(
|
| 551 |
+
cells: Sequence[np.ndarray],
|
| 552 |
+
config: ActionCodec2Config,
|
| 553 |
+
primitives: PrimitiveStore,
|
| 554 |
+
*,
|
| 555 |
+
backend: str,
|
| 556 |
+
threads: int,
|
| 557 |
+
progress: bool,
|
| 558 |
+
) -> tuple[BPESerializer, dict[str, object], dict[str, object], int, float, float]:
|
| 559 |
+
|
| 560 |
+
started = time.perf_counter()
|
| 561 |
+
bpe = config.train.bpe if config.train is not None else None
|
| 562 |
+
assert bpe is not None
|
| 563 |
+
vocabulary, report = train_bpe(
|
| 564 |
+
cells,
|
| 565 |
+
primitives.bins,
|
| 566 |
+
vocab_size=bpe.vocab_size,
|
| 567 |
+
max_token_cells=config.set_bpe.lookahead * config.physical.input_dim,
|
| 568 |
+
backend=backend,
|
| 569 |
+
threads=threads,
|
| 570 |
+
progress=progress,
|
| 571 |
+
)
|
| 572 |
+
serializer = BPESerializer(
|
| 573 |
+
bins=primitives.bins,
|
| 574 |
+
lookahead=config.set_bpe.lookahead,
|
| 575 |
+
merges=vocabulary.merges,
|
| 576 |
+
)
|
| 577 |
+
return serializer, {}, report, 0, started, started
|
| 578 |
+
|
| 579 |
+
|
| 580 |
+
def _validate_fit_options(
|
| 581 |
+
config: ActionCodec2Config, max_cell_bytes: int | None
|
| 582 |
+
) -> None:
|
| 583 |
+
if config.train is None:
|
| 584 |
+
raise ConfigError("config.train is required for fitting")
|
| 585 |
+
if max_cell_bytes is not None and (
|
| 586 |
+
isinstance(max_cell_bytes, bool)
|
| 587 |
+
or not isinstance(max_cell_bytes, int)
|
| 588 |
+
or max_cell_bytes < 1
|
| 589 |
+
):
|
| 590 |
+
raise ValueError("max_cell_bytes must be a positive integer or None")
|
| 591 |
+
if config.set_bpe.allow_cross_component_merge:
|
| 592 |
+
raise ConfigError(
|
| 593 |
+
"allow_cross_component_merge=true requires the future multi-component "
|
| 594 |
+
"corpus representation; v1 fitting supports the default isolated boundary only"
|
| 595 |
+
)
|
| 596 |
+
if config.set_bpe.scheme == "bpe" and config.train.bpe.spatial_vocab_size != (
|
| 597 |
+
config.train.bpe.vocab_size
|
| 598 |
+
):
|
| 599 |
+
raise ConfigError(
|
| 600 |
+
"set_bpe.scheme=bpe has a single merge stage; set "
|
| 601 |
+
"train.bpe.spatial_vocab_size equal to train.bpe.vocab_size to make the "
|
| 602 |
+
"unused split explicit"
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
class ProfileTrainer:
|
| 607 |
+
"Internal whole-trajectory fitter for one physical profile."
|
| 608 |
+
|
| 609 |
+
def __init__(self, config: ActionCodec2Config) -> None:
|
| 610 |
+
self.config = config
|
| 611 |
+
|
| 612 |
+
def fit(
|
| 613 |
+
self,
|
| 614 |
+
trajectories: Iterable[TrajectoryLike],
|
| 615 |
+
*,
|
| 616 |
+
backend: str = "auto",
|
| 617 |
+
threads: int = 0,
|
| 618 |
+
batch_size: int = 256,
|
| 619 |
+
progress: bool = False,
|
| 620 |
+
max_cell_bytes: int | None = None,
|
| 621 |
+
) -> tuple[ActionCodec2Processor, dict[str, object]]:
|
| 622 |
+
"Fit primitives and the configured tokenization scheme on whole trajectories."
|
| 623 |
+
config = self.config
|
| 624 |
+
_validate_fit_options(config, max_cell_bytes)
|
| 625 |
+
started = time.perf_counter()
|
| 626 |
+
primitives = build_primitives(config)
|
| 627 |
+
primitive_done = time.perf_counter()
|
| 628 |
+
|
| 629 |
+
atoms = sum(primitives.bins)
|
| 630 |
+
bpe = config.train.bpe
|
| 631 |
+
if bpe.spatial_vocab_size < atoms:
|
| 632 |
+
raise ConfigError(
|
| 633 |
+
f"train.bpe.spatial_vocab_size must be at least the {atoms} primitive atoms"
|
| 634 |
+
)
|
| 635 |
+
|
| 636 |
+
physical = PhysicalCodec(config, primitives)
|
| 637 |
+
cells, actions, active_values, arity_counts = _quantize_trajectories(
|
| 638 |
+
trajectories,
|
| 639 |
+
physical,
|
| 640 |
+
batch_size=batch_size,
|
| 641 |
+
max_cell_bytes=max_cell_bytes,
|
| 642 |
+
)
|
| 643 |
+
trajectory_count = len(cells)
|
| 644 |
+
first_cell_dtype = str(cells[0].dtype)
|
| 645 |
+
quantized = time.perf_counter()
|
| 646 |
+
|
| 647 |
+
if config.set_bpe.scheme == "bpe":
|
| 648 |
+
fitted = _fit_bpe(
|
| 649 |
+
cells,
|
| 650 |
+
config,
|
| 651 |
+
primitives,
|
| 652 |
+
backend=backend,
|
| 653 |
+
threads=threads,
|
| 654 |
+
progress=progress,
|
| 655 |
+
)
|
| 656 |
+
else:
|
| 657 |
+
fitted = _fit_set_bpe(
|
| 658 |
+
cells,
|
| 659 |
+
config,
|
| 660 |
+
primitives,
|
| 661 |
+
actions,
|
| 662 |
+
backend=backend,
|
| 663 |
+
threads=threads,
|
| 664 |
+
progress=progress,
|
| 665 |
+
)
|
| 666 |
+
(
|
| 667 |
+
serializer,
|
| 668 |
+
spatial_report,
|
| 669 |
+
temporal_report,
|
| 670 |
+
unique_records,
|
| 671 |
+
aggregated,
|
| 672 |
+
spatial_done,
|
| 673 |
+
) = fitted
|
| 674 |
+
codec = ActionCodec2Processor(config, primitives, serializer)
|
| 675 |
+
finished = time.perf_counter()
|
| 676 |
+
|
| 677 |
+
report = _fit_report(
|
| 678 |
+
config,
|
| 679 |
+
primitives,
|
| 680 |
+
codec,
|
| 681 |
+
trajectory_count=trajectory_count,
|
| 682 |
+
actions=actions,
|
| 683 |
+
active_values=active_values,
|
| 684 |
+
arity_counts=arity_counts,
|
| 685 |
+
cell_dtype=first_cell_dtype,
|
| 686 |
+
unique_records=unique_records,
|
| 687 |
+
spatial_report=spatial_report,
|
| 688 |
+
temporal_report=temporal_report,
|
| 689 |
+
timestamps=(
|
| 690 |
+
started,
|
| 691 |
+
primitive_done,
|
| 692 |
+
quantized,
|
| 693 |
+
aggregated,
|
| 694 |
+
spatial_done,
|
| 695 |
+
finished,
|
| 696 |
+
),
|
| 697 |
+
)
|
| 698 |
+
return codec, report
|
setup.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import setup
|
| 2 |
+
from pybind11.setup_helpers import Pybind11Extension, build_ext
|
| 3 |
+
|
| 4 |
+
native = "actioncodec2_kernels"
|
| 5 |
+
setup(
|
| 6 |
+
ext_modules=[Pybind11Extension(
|
| 7 |
+
"actioncodec2_kernels._actioncodec2_native",
|
| 8 |
+
sources=[f"{native}/{name}.cpp" for name in (
|
| 9 |
+
"module", "physical", "so3", "setbpe", "resample", "reconstruct", "grammar"
|
| 10 |
+
)],
|
| 11 |
+
include_dirs=[native],
|
| 12 |
+
depends=[f"{native}/{name}.hpp" for name in ("parallel", "quaternion")],
|
| 13 |
+
extra_compile_args=["-O3", "-fvisibility=hidden", "-ffp-contract=off"],
|
| 14 |
+
cxx_std=17,
|
| 15 |
+
)],
|
| 16 |
+
cmdclass={"build_ext": build_ext},
|
| 17 |
+
)
|
variants/extended-1st-10mm-16k/config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "actioncodec2",
|
| 3 |
+
"processor_class": "ActionCodec2"
|
| 4 |
+
}
|
variants/extended-1st-10mm-16k/processor_config.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"processor_class": "ActionCodec2",
|
| 3 |
+
"action_space": "single_eef_delta",
|
| 4 |
+
"fitted_profiles": [
|
| 5 |
+
"eef",
|
| 6 |
+
"joint"
|
| 7 |
+
],
|
| 8 |
+
"auto_map": {
|
| 9 |
+
"AutoProcessor": "processing_actioncodec2.ActionCodec2"
|
| 10 |
+
}
|
| 11 |
+
}
|
variants/extended-1st-10mm-16k/profiles/eef/bpe.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
variants/extended-1st-10mm-16k/profiles/eef/config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "actioncodec2_unified",
|
| 3 |
+
"processor_class": "ActionCodec2Processor"
|
| 4 |
+
}
|
variants/extended-1st-10mm-16k/profiles/eef/config.yaml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_type: actioncodec2_unified
|
| 2 |
+
config_version: 1
|
| 3 |
+
physical:
|
| 4 |
+
input_dim: 7
|
| 5 |
+
components:
|
| 6 |
+
translation_m:
|
| 7 |
+
dims:
|
| 8 |
+
start: 0
|
| 9 |
+
stop: 3
|
| 10 |
+
space: additive
|
| 11 |
+
input_order: 0
|
| 12 |
+
primitive_order: 1
|
| 13 |
+
input_transform:
|
| 14 |
+
scale: 1.0
|
| 15 |
+
clip: false
|
| 16 |
+
constraints:
|
| 17 |
+
order_0:
|
| 18 |
+
min: -2.0
|
| 19 |
+
max: 2.0
|
| 20 |
+
order_1:
|
| 21 |
+
min: -0.08
|
| 22 |
+
max: 0.08
|
| 23 |
+
distortion_limit: 0.01
|
| 24 |
+
primitive_selection: smooth
|
| 25 |
+
orientation_so3:
|
| 26 |
+
dims:
|
| 27 |
+
start: 3
|
| 28 |
+
stop: 6
|
| 29 |
+
space: so3
|
| 30 |
+
input_order: 0
|
| 31 |
+
primitive_order: 1
|
| 32 |
+
input_transform:
|
| 33 |
+
scale: 1.0
|
| 34 |
+
clip: false
|
| 35 |
+
constraints:
|
| 36 |
+
order_0:
|
| 37 |
+
min: -3.141592653589793
|
| 38 |
+
max: 3.141592653589793
|
| 39 |
+
order_1:
|
| 40 |
+
min: -0.25
|
| 41 |
+
max: 0.25
|
| 42 |
+
distortion_limit: 0.04
|
| 43 |
+
primitive_selection: smooth
|
| 44 |
+
representation: rotation_vector
|
| 45 |
+
multiplication: right
|
| 46 |
+
gripper_open_command:
|
| 47 |
+
dims:
|
| 48 |
+
- 6
|
| 49 |
+
space: additive
|
| 50 |
+
input_order: 0
|
| 51 |
+
primitive_order: 0
|
| 52 |
+
input_transform:
|
| 53 |
+
scale: 1.0
|
| 54 |
+
clip: true
|
| 55 |
+
constraints:
|
| 56 |
+
order_0:
|
| 57 |
+
min: 0.0
|
| 58 |
+
max: 1.0
|
| 59 |
+
distortion_limit: 0.5
|
| 60 |
+
primitive_selection: nearest
|
| 61 |
+
set_bpe:
|
| 62 |
+
lookahead: 8
|
| 63 |
+
allow_cross_component_merge: false
|
| 64 |
+
train:
|
| 65 |
+
primitives:
|
| 66 |
+
zero_primitive_components:
|
| 67 |
+
- translation_m
|
| 68 |
+
- orientation_so3
|
| 69 |
+
bpe:
|
| 70 |
+
spatial_vocab_size: 1024
|
| 71 |
+
vocab_size: 16384
|
| 72 |
+
quality_control:
|
| 73 |
+
hard_order_1_scale: 3.0
|
| 74 |
+
min_segment_length: 2
|
variants/extended-1st-10mm-16k/profiles/eef/primitives.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6d99d92d73fbfe2d96ff317f0eef2d0d2ba6420ac8049c47ae0cc387913854bf
|
| 3 |
+
size 2510
|
variants/extended-1st-10mm-16k/profiles/eef/processor_config.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"processor_class": "ActionCodec2Processor"
|
| 3 |
+
}
|
variants/extended-1st-10mm-16k/profiles/joint/bpe.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
variants/extended-1st-10mm-16k/profiles/joint/config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "actioncodec2_unified",
|
| 3 |
+
"processor_class": "ActionCodec2Processor"
|
| 4 |
+
}
|
variants/extended-1st-10mm-16k/profiles/joint/config.yaml
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_type: actioncodec2_unified
|
| 2 |
+
config_version: 1
|
| 3 |
+
physical:
|
| 4 |
+
input_dim: 8
|
| 5 |
+
components:
|
| 6 |
+
generalized_joint_position:
|
| 7 |
+
dims:
|
| 8 |
+
start: 0
|
| 9 |
+
stop: 7
|
| 10 |
+
space: additive
|
| 11 |
+
input_order: 0
|
| 12 |
+
primitive_order: 1
|
| 13 |
+
input_transform:
|
| 14 |
+
scale: 1.0
|
| 15 |
+
clip: false
|
| 16 |
+
constraints:
|
| 17 |
+
order_0:
|
| 18 |
+
min: -6.283185307179586
|
| 19 |
+
max: 6.283185307179586
|
| 20 |
+
order_1:
|
| 21 |
+
min: -0.35
|
| 22 |
+
max: 0.35
|
| 23 |
+
distortion_limit:
|
| 24 |
+
- 0.008
|
| 25 |
+
- 0.008
|
| 26 |
+
- 0.012
|
| 27 |
+
- 0.015
|
| 28 |
+
- 0.03
|
| 29 |
+
- 0.04
|
| 30 |
+
- 0.04
|
| 31 |
+
primitive_selection: smooth
|
| 32 |
+
gripper_open_command:
|
| 33 |
+
dims:
|
| 34 |
+
- 7
|
| 35 |
+
space: additive
|
| 36 |
+
input_order: 0
|
| 37 |
+
primitive_order: 0
|
| 38 |
+
input_transform:
|
| 39 |
+
scale: 1.0
|
| 40 |
+
clip: true
|
| 41 |
+
constraints:
|
| 42 |
+
order_0:
|
| 43 |
+
min: 0.0
|
| 44 |
+
max: 1.0
|
| 45 |
+
distortion_limit: 0.5
|
| 46 |
+
primitive_selection: nearest
|
| 47 |
+
set_bpe:
|
| 48 |
+
lookahead: 8
|
| 49 |
+
allow_cross_component_merge: false
|
| 50 |
+
train:
|
| 51 |
+
primitives:
|
| 52 |
+
zero_primitive_components:
|
| 53 |
+
- generalized_joint_position
|
| 54 |
+
bpe:
|
| 55 |
+
spatial_vocab_size: 1024
|
| 56 |
+
vocab_size: 16384
|
| 57 |
+
quality_control:
|
| 58 |
+
hard_order_1_scale: 3.0
|
| 59 |
+
min_segment_length: 2
|
variants/extended-1st-10mm-16k/profiles/joint/primitives.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7424271014df93fbf66a292a1b04aa0f8b999c7c3cf06f965278149d0e41f3e7
|
| 3 |
+
size 2834
|
variants/extended-1st-10mm-16k/profiles/joint/processor_config.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"processor_class": "ActionCodec2Processor"
|
| 3 |
+
}
|
variants/extended-1st-10mm-16k/router_config.yaml
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_type: actioncodec2_router
|
| 2 |
+
config_version: 3
|
| 3 |
+
profiles:
|
| 4 |
+
- name: joint
|
| 5 |
+
token_offset: 0
|
| 6 |
+
token_budget: 16384
|
| 7 |
+
artifact_fingerprint: 309e93d0fa330d19b4e597b5b82ef76cce820f536d6f23a31d103e746ab29e86
|
| 8 |
+
- name: eef
|
| 9 |
+
token_offset: 16384
|
| 10 |
+
token_budget: 16384
|
| 11 |
+
artifact_fingerprint: 10eec18e85e63394e59a20ca958f20af761b72575b5b8c9403bc230f4acaba28
|
| 12 |
+
presets:
|
| 13 |
+
- name: single_eef_abs
|
| 14 |
+
input_dim: 7
|
| 15 |
+
input: absolute
|
| 16 |
+
source_hz: 15.0
|
| 17 |
+
target_hz: 15.0
|
| 18 |
+
interpolation: cubic
|
| 19 |
+
components:
|
| 20 |
+
arm:
|
| 21 |
+
space: eef
|
| 22 |
+
position_dims:
|
| 23 |
+
- 0
|
| 24 |
+
- 1
|
| 25 |
+
- 2
|
| 26 |
+
rotation_dims:
|
| 27 |
+
- 3
|
| 28 |
+
- 4
|
| 29 |
+
- 5
|
| 30 |
+
rotation_format: rotation_vector
|
| 31 |
+
gripper_dim: 6
|
| 32 |
+
gripper_threshold: 0.8
|
| 33 |
+
gripper_open_is_high: true
|
| 34 |
+
- name: single_joint6_abs
|
| 35 |
+
input_dim: 7
|
| 36 |
+
input: absolute
|
| 37 |
+
source_hz: 15.0
|
| 38 |
+
target_hz: 15.0
|
| 39 |
+
interpolation: cubic
|
| 40 |
+
components:
|
| 41 |
+
arm:
|
| 42 |
+
space: joint
|
| 43 |
+
joint_dims:
|
| 44 |
+
- 0
|
| 45 |
+
- 1
|
| 46 |
+
- 2
|
| 47 |
+
- 3
|
| 48 |
+
- 4
|
| 49 |
+
- 5
|
| 50 |
+
gripper_dim: 6
|
| 51 |
+
gripper_threshold: 0.8
|
| 52 |
+
gripper_open_is_high: true
|
| 53 |
+
- name: single_joint7_abs
|
| 54 |
+
input_dim: 8
|
| 55 |
+
input: absolute
|
| 56 |
+
source_hz: 15.0
|
| 57 |
+
target_hz: 15.0
|
| 58 |
+
interpolation: cubic
|
| 59 |
+
components:
|
| 60 |
+
arm:
|
| 61 |
+
space: joint
|
| 62 |
+
joint_dims:
|
| 63 |
+
- 0
|
| 64 |
+
- 1
|
| 65 |
+
- 2
|
| 66 |
+
- 3
|
| 67 |
+
- 4
|
| 68 |
+
- 5
|
| 69 |
+
- 6
|
| 70 |
+
gripper_dim: 7
|
| 71 |
+
gripper_threshold: 0.8
|
| 72 |
+
gripper_open_is_high: true
|
| 73 |
+
- name: single_eef_delta
|
| 74 |
+
input_dim: 7
|
| 75 |
+
input: delta
|
| 76 |
+
source_hz: 15.0
|
| 77 |
+
target_hz: 15.0
|
| 78 |
+
interpolation: cubic
|
| 79 |
+
components:
|
| 80 |
+
arm:
|
| 81 |
+
space: eef
|
| 82 |
+
position_dims:
|
| 83 |
+
- 0
|
| 84 |
+
- 1
|
| 85 |
+
- 2
|
| 86 |
+
rotation_dims:
|
| 87 |
+
- 3
|
| 88 |
+
- 4
|
| 89 |
+
- 5
|
| 90 |
+
rotation_format: rotation_vector
|
| 91 |
+
gripper_dim: 6
|
| 92 |
+
gripper_threshold: 0.8
|
| 93 |
+
gripper_open_is_high: true
|
| 94 |
+
- name: single_joint6_delta
|
| 95 |
+
input_dim: 7
|
| 96 |
+
input: delta
|
| 97 |
+
source_hz: 15.0
|
| 98 |
+
target_hz: 15.0
|
| 99 |
+
interpolation: cubic
|
| 100 |
+
components:
|
| 101 |
+
arm:
|
| 102 |
+
space: joint
|
| 103 |
+
joint_dims:
|
| 104 |
+
- 0
|
| 105 |
+
- 1
|
| 106 |
+
- 2
|
| 107 |
+
- 3
|
| 108 |
+
- 4
|
| 109 |
+
- 5
|
| 110 |
+
gripper_dim: 6
|
| 111 |
+
gripper_threshold: 0.8
|
| 112 |
+
gripper_open_is_high: true
|
| 113 |
+
- name: single_joint7_delta
|
| 114 |
+
input_dim: 8
|
| 115 |
+
input: delta
|
| 116 |
+
source_hz: 15.0
|
| 117 |
+
target_hz: 15.0
|
| 118 |
+
interpolation: cubic
|
| 119 |
+
components:
|
| 120 |
+
arm:
|
| 121 |
+
space: joint
|
| 122 |
+
joint_dims:
|
| 123 |
+
- 0
|
| 124 |
+
- 1
|
| 125 |
+
- 2
|
| 126 |
+
- 3
|
| 127 |
+
- 4
|
| 128 |
+
- 5
|
| 129 |
+
- 6
|
| 130 |
+
gripper_dim: 7
|
| 131 |
+
gripper_threshold: 0.8
|
| 132 |
+
gripper_open_is_high: true
|
| 133 |
+
- name: dual_eef_abs
|
| 134 |
+
input_dim: 14
|
| 135 |
+
input: absolute
|
| 136 |
+
source_hz: 15.0
|
| 137 |
+
target_hz: 15.0
|
| 138 |
+
interpolation: cubic
|
| 139 |
+
components:
|
| 140 |
+
left_arm:
|
| 141 |
+
space: eef
|
| 142 |
+
position_dims:
|
| 143 |
+
- 0
|
| 144 |
+
- 1
|
| 145 |
+
- 2
|
| 146 |
+
rotation_dims:
|
| 147 |
+
- 3
|
| 148 |
+
- 4
|
| 149 |
+
- 5
|
| 150 |
+
rotation_format: rotation_vector
|
| 151 |
+
gripper_dim: 6
|
| 152 |
+
gripper_threshold: 0.8
|
| 153 |
+
gripper_open_is_high: true
|
| 154 |
+
right_arm:
|
| 155 |
+
space: eef
|
| 156 |
+
position_dims:
|
| 157 |
+
- 7
|
| 158 |
+
- 8
|
| 159 |
+
- 9
|
| 160 |
+
rotation_dims:
|
| 161 |
+
- 10
|
| 162 |
+
- 11
|
| 163 |
+
- 12
|
| 164 |
+
rotation_format: rotation_vector
|
| 165 |
+
gripper_dim: 13
|
| 166 |
+
gripper_threshold: 0.8
|
| 167 |
+
gripper_open_is_high: true
|
| 168 |
+
- name: dual_joint6_abs
|
| 169 |
+
input_dim: 14
|
| 170 |
+
input: absolute
|
| 171 |
+
source_hz: 15.0
|
| 172 |
+
target_hz: 15.0
|
| 173 |
+
interpolation: cubic
|
| 174 |
+
components:
|
| 175 |
+
left_arm:
|
| 176 |
+
space: joint
|
| 177 |
+
joint_dims:
|
| 178 |
+
- 0
|
| 179 |
+
- 1
|
| 180 |
+
- 2
|
| 181 |
+
- 3
|
| 182 |
+
- 4
|
| 183 |
+
- 5
|
| 184 |
+
gripper_dim: 6
|
| 185 |
+
gripper_threshold: 0.8
|
| 186 |
+
gripper_open_is_high: true
|
| 187 |
+
right_arm:
|
| 188 |
+
space: joint
|
| 189 |
+
joint_dims:
|
| 190 |
+
- 7
|
| 191 |
+
- 8
|
| 192 |
+
- 9
|
| 193 |
+
- 10
|
| 194 |
+
- 11
|
| 195 |
+
- 12
|
| 196 |
+
gripper_dim: 13
|
| 197 |
+
gripper_threshold: 0.8
|
| 198 |
+
gripper_open_is_high: true
|
| 199 |
+
- name: dual_joint7_abs
|
| 200 |
+
input_dim: 16
|
| 201 |
+
input: absolute
|
| 202 |
+
source_hz: 15.0
|
| 203 |
+
target_hz: 15.0
|
| 204 |
+
interpolation: cubic
|
| 205 |
+
components:
|
| 206 |
+
left_arm:
|
| 207 |
+
space: joint
|
| 208 |
+
joint_dims:
|
| 209 |
+
- 0
|
| 210 |
+
- 1
|
| 211 |
+
- 2
|
| 212 |
+
- 3
|
| 213 |
+
- 4
|
| 214 |
+
- 5
|
| 215 |
+
- 6
|
| 216 |
+
gripper_dim: 7
|
| 217 |
+
gripper_threshold: 0.8
|
| 218 |
+
gripper_open_is_high: true
|
| 219 |
+
right_arm:
|
| 220 |
+
space: joint
|
| 221 |
+
joint_dims:
|
| 222 |
+
- 8
|
| 223 |
+
- 9
|
| 224 |
+
- 10
|
| 225 |
+
- 11
|
| 226 |
+
- 12
|
| 227 |
+
- 13
|
| 228 |
+
- 14
|
| 229 |
+
gripper_dim: 15
|
| 230 |
+
gripper_threshold: 0.8
|
| 231 |
+
gripper_open_is_high: true
|
| 232 |
+
- name: dual_eef_delta
|
| 233 |
+
input_dim: 14
|
| 234 |
+
input: delta
|
| 235 |
+
source_hz: 15.0
|
| 236 |
+
target_hz: 15.0
|
| 237 |
+
interpolation: cubic
|
| 238 |
+
components:
|
| 239 |
+
left_arm:
|
| 240 |
+
space: eef
|
| 241 |
+
position_dims:
|
| 242 |
+
- 0
|
| 243 |
+
- 1
|
| 244 |
+
- 2
|
| 245 |
+
rotation_dims:
|
| 246 |
+
- 3
|
| 247 |
+
- 4
|
| 248 |
+
- 5
|
| 249 |
+
rotation_format: rotation_vector
|
| 250 |
+
gripper_dim: 6
|
| 251 |
+
gripper_threshold: 0.8
|
| 252 |
+
gripper_open_is_high: true
|
| 253 |
+
right_arm:
|
| 254 |
+
space: eef
|
| 255 |
+
position_dims:
|
| 256 |
+
- 7
|
| 257 |
+
- 8
|
| 258 |
+
- 9
|
| 259 |
+
rotation_dims:
|
| 260 |
+
- 10
|
| 261 |
+
- 11
|
| 262 |
+
- 12
|
| 263 |
+
rotation_format: rotation_vector
|
| 264 |
+
gripper_dim: 13
|
| 265 |
+
gripper_threshold: 0.8
|
| 266 |
+
gripper_open_is_high: true
|
| 267 |
+
- name: dual_joint6_delta
|
| 268 |
+
input_dim: 14
|
| 269 |
+
input: delta
|
| 270 |
+
source_hz: 15.0
|
| 271 |
+
target_hz: 15.0
|
| 272 |
+
interpolation: cubic
|
| 273 |
+
components:
|
| 274 |
+
left_arm:
|
| 275 |
+
space: joint
|
| 276 |
+
joint_dims:
|
| 277 |
+
- 0
|
| 278 |
+
- 1
|
| 279 |
+
- 2
|
| 280 |
+
- 3
|
| 281 |
+
- 4
|
| 282 |
+
- 5
|
| 283 |
+
gripper_dim: 6
|
| 284 |
+
gripper_threshold: 0.8
|
| 285 |
+
gripper_open_is_high: true
|
| 286 |
+
right_arm:
|
| 287 |
+
space: joint
|
| 288 |
+
joint_dims:
|
| 289 |
+
- 7
|
| 290 |
+
- 8
|
| 291 |
+
- 9
|
| 292 |
+
- 10
|
| 293 |
+
- 11
|
| 294 |
+
- 12
|
| 295 |
+
gripper_dim: 13
|
| 296 |
+
gripper_threshold: 0.8
|
| 297 |
+
gripper_open_is_high: true
|
| 298 |
+
- name: dual_joint7_delta
|
| 299 |
+
input_dim: 16
|
| 300 |
+
input: delta
|
| 301 |
+
source_hz: 15.0
|
| 302 |
+
target_hz: 15.0
|
| 303 |
+
interpolation: cubic
|
| 304 |
+
components:
|
| 305 |
+
left_arm:
|
| 306 |
+
space: joint
|
| 307 |
+
joint_dims:
|
| 308 |
+
- 0
|
| 309 |
+
- 1
|
| 310 |
+
- 2
|
| 311 |
+
- 3
|
| 312 |
+
- 4
|
| 313 |
+
- 5
|
| 314 |
+
- 6
|
| 315 |
+
gripper_dim: 7
|
| 316 |
+
gripper_threshold: 0.8
|
| 317 |
+
gripper_open_is_high: true
|
| 318 |
+
right_arm:
|
| 319 |
+
space: joint
|
| 320 |
+
joint_dims:
|
| 321 |
+
- 8
|
| 322 |
+
- 9
|
| 323 |
+
- 10
|
| 324 |
+
- 11
|
| 325 |
+
- 12
|
| 326 |
+
- 13
|
| 327 |
+
- 14
|
| 328 |
+
gripper_dim: 15
|
| 329 |
+
gripper_threshold: 0.8
|
| 330 |
+
gripper_open_is_high: true
|
| 331 |
+
- name: single_joint6_abs_continuous_gripper
|
| 332 |
+
input_dim: 6
|
| 333 |
+
input: absolute
|
| 334 |
+
source_hz: 15.0
|
| 335 |
+
target_hz: 15.0
|
| 336 |
+
interpolation: cubic
|
| 337 |
+
components:
|
| 338 |
+
arm:
|
| 339 |
+
space: joint
|
| 340 |
+
joint_dims:
|
| 341 |
+
- 0
|
| 342 |
+
- 1
|
| 343 |
+
- 2
|
| 344 |
+
- 3
|
| 345 |
+
- 4
|
| 346 |
+
- 5
|
variants/extended-1st-10mm-1k/config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "actioncodec2",
|
| 3 |
+
"processor_class": "ActionCodec2"
|
| 4 |
+
}
|
variants/extended-1st-10mm-1k/processor_config.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"processor_class": "ActionCodec2",
|
| 3 |
+
"action_space": "single_eef_delta",
|
| 4 |
+
"fitted_profiles": [
|
| 5 |
+
"eef",
|
| 6 |
+
"joint"
|
| 7 |
+
],
|
| 8 |
+
"auto_map": {
|
| 9 |
+
"AutoProcessor": "processing_actioncodec2.ActionCodec2"
|
| 10 |
+
}
|
| 11 |
+
}
|
variants/extended-1st-10mm-1k/profiles/eef/bpe.json
ADDED
|
@@ -0,0 +1,4222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"spatial_merges": [
|
| 3 |
+
[
|
| 4 |
+
4,
|
| 5 |
+
13
|
| 6 |
+
],
|
| 7 |
+
[
|
| 8 |
+
22,
|
| 9 |
+
84
|
| 10 |
+
],
|
| 11 |
+
[
|
| 12 |
+
38,
|
| 13 |
+
61
|
| 14 |
+
],
|
| 15 |
+
[
|
| 16 |
+
98,
|
| 17 |
+
99
|
| 18 |
+
],
|
| 19 |
+
[
|
| 20 |
+
100,
|
| 21 |
+
101
|
| 22 |
+
],
|
| 23 |
+
[
|
| 24 |
+
96,
|
| 25 |
+
102
|
| 26 |
+
],
|
| 27 |
+
[
|
| 28 |
+
97,
|
| 29 |
+
102
|
| 30 |
+
],
|
| 31 |
+
[
|
| 32 |
+
96,
|
| 33 |
+
100
|
| 34 |
+
],
|
| 35 |
+
[
|
| 36 |
+
61,
|
| 37 |
+
96
|
| 38 |
+
],
|
| 39 |
+
[
|
| 40 |
+
38,
|
| 41 |
+
96
|
| 42 |
+
],
|
| 43 |
+
[
|
| 44 |
+
22,
|
| 45 |
+
98
|
| 46 |
+
],
|
| 47 |
+
[
|
| 48 |
+
84,
|
| 49 |
+
98
|
| 50 |
+
],
|
| 51 |
+
[
|
| 52 |
+
97,
|
| 53 |
+
100
|
| 54 |
+
],
|
| 55 |
+
[
|
| 56 |
+
13,
|
| 57 |
+
99
|
| 58 |
+
],
|
| 59 |
+
[
|
| 60 |
+
4,
|
| 61 |
+
99
|
| 62 |
+
],
|
| 63 |
+
[
|
| 64 |
+
62,
|
| 65 |
+
107
|
| 66 |
+
],
|
| 67 |
+
[
|
| 68 |
+
37,
|
| 69 |
+
106
|
| 70 |
+
],
|
| 71 |
+
[
|
| 72 |
+
39,
|
| 73 |
+
106
|
| 74 |
+
],
|
| 75 |
+
[
|
| 76 |
+
97,
|
| 77 |
+
101
|
| 78 |
+
],
|
| 79 |
+
[
|
| 80 |
+
60,
|
| 81 |
+
107
|
| 82 |
+
],
|
| 83 |
+
[
|
| 84 |
+
83,
|
| 85 |
+
108
|
| 86 |
+
],
|
| 87 |
+
[
|
| 88 |
+
85,
|
| 89 |
+
108
|
| 90 |
+
],
|
| 91 |
+
[
|
| 92 |
+
21,
|
| 93 |
+
109
|
| 94 |
+
],
|
| 95 |
+
[
|
| 96 |
+
5,
|
| 97 |
+
111
|
| 98 |
+
],
|
| 99 |
+
[
|
| 100 |
+
23,
|
| 101 |
+
109
|
| 102 |
+
],
|
| 103 |
+
[
|
| 104 |
+
3,
|
| 105 |
+
111
|
| 106 |
+
],
|
| 107 |
+
[
|
| 108 |
+
14,
|
| 109 |
+
112
|
| 110 |
+
],
|
| 111 |
+
[
|
| 112 |
+
12,
|
| 113 |
+
112
|
| 114 |
+
],
|
| 115 |
+
[
|
| 116 |
+
61,
|
| 117 |
+
97
|
| 118 |
+
],
|
| 119 |
+
[
|
| 120 |
+
62,
|
| 121 |
+
96
|
| 122 |
+
],
|
| 123 |
+
[
|
| 124 |
+
38,
|
| 125 |
+
60
|
| 126 |
+
],
|
| 127 |
+
[
|
| 128 |
+
61,
|
| 129 |
+
116
|
| 130 |
+
],
|
| 131 |
+
[
|
| 132 |
+
3,
|
| 133 |
+
13
|
| 134 |
+
],
|
| 135 |
+
[
|
| 136 |
+
5,
|
| 137 |
+
13
|
| 138 |
+
],
|
| 139 |
+
[
|
| 140 |
+
101,
|
| 141 |
+
113
|
| 142 |
+
],
|
| 143 |
+
[
|
| 144 |
+
101,
|
| 145 |
+
117
|
| 146 |
+
],
|
| 147 |
+
[
|
| 148 |
+
60,
|
| 149 |
+
96
|
| 150 |
+
],
|
| 151 |
+
[
|
| 152 |
+
22,
|
| 153 |
+
83
|
| 154 |
+
],
|
| 155 |
+
[
|
| 156 |
+
101,
|
| 157 |
+
114
|
| 158 |
+
],
|
| 159 |
+
[
|
| 160 |
+
22,
|
| 161 |
+
85
|
| 162 |
+
],
|
| 163 |
+
[
|
| 164 |
+
101,
|
| 165 |
+
115
|
| 166 |
+
],
|
| 167 |
+
[
|
| 168 |
+
23,
|
| 169 |
+
84
|
| 170 |
+
],
|
| 171 |
+
[
|
| 172 |
+
4,
|
| 173 |
+
14
|
| 174 |
+
],
|
| 175 |
+
[
|
| 176 |
+
4,
|
| 177 |
+
12
|
| 178 |
+
],
|
| 179 |
+
[
|
| 180 |
+
21,
|
| 181 |
+
84
|
| 182 |
+
],
|
| 183 |
+
[
|
| 184 |
+
38,
|
| 185 |
+
62
|
| 186 |
+
],
|
| 187 |
+
[
|
| 188 |
+
105,
|
| 189 |
+
120
|
| 190 |
+
],
|
| 191 |
+
[
|
| 192 |
+
105,
|
| 193 |
+
122
|
| 194 |
+
],
|
| 195 |
+
[
|
| 196 |
+
105,
|
| 197 |
+
118
|
| 198 |
+
],
|
| 199 |
+
[
|
| 200 |
+
105,
|
| 201 |
+
119
|
| 202 |
+
],
|
| 203 |
+
[
|
| 204 |
+
105,
|
| 205 |
+
121
|
| 206 |
+
],
|
| 207 |
+
[
|
| 208 |
+
105,
|
| 209 |
+
123
|
| 210 |
+
],
|
| 211 |
+
[
|
| 212 |
+
105,
|
| 213 |
+
125
|
| 214 |
+
],
|
| 215 |
+
[
|
| 216 |
+
105,
|
| 217 |
+
124
|
| 218 |
+
],
|
| 219 |
+
[
|
| 220 |
+
116,
|
| 221 |
+
128
|
| 222 |
+
],
|
| 223 |
+
[
|
| 224 |
+
37,
|
| 225 |
+
126
|
| 226 |
+
],
|
| 227 |
+
[
|
| 228 |
+
97,
|
| 229 |
+
128
|
| 230 |
+
],
|
| 231 |
+
[
|
| 232 |
+
39,
|
| 233 |
+
126
|
| 234 |
+
],
|
| 235 |
+
[
|
| 236 |
+
37,
|
| 237 |
+
129
|
| 238 |
+
],
|
| 239 |
+
[
|
| 240 |
+
37,
|
| 241 |
+
127
|
| 242 |
+
],
|
| 243 |
+
[
|
| 244 |
+
39,
|
| 245 |
+
129
|
| 246 |
+
],
|
| 247 |
+
[
|
| 248 |
+
39,
|
| 249 |
+
127
|
| 250 |
+
],
|
| 251 |
+
[
|
| 252 |
+
23,
|
| 253 |
+
98
|
| 254 |
+
],
|
| 255 |
+
[
|
| 256 |
+
21,
|
| 257 |
+
98
|
| 258 |
+
],
|
| 259 |
+
[
|
| 260 |
+
39,
|
| 261 |
+
134
|
| 262 |
+
],
|
| 263 |
+
[
|
| 264 |
+
110,
|
| 265 |
+
120
|
| 266 |
+
],
|
| 267 |
+
[
|
| 268 |
+
37,
|
| 269 |
+
134
|
| 270 |
+
],
|
| 271 |
+
[
|
| 272 |
+
110,
|
| 273 |
+
119
|
| 274 |
+
],
|
| 275 |
+
[
|
| 276 |
+
110,
|
| 277 |
+
118
|
| 278 |
+
],
|
| 279 |
+
[
|
| 280 |
+
97,
|
| 281 |
+
143
|
| 282 |
+
],
|
| 283 |
+
[
|
| 284 |
+
116,
|
| 285 |
+
143
|
| 286 |
+
],
|
| 287 |
+
[
|
| 288 |
+
3,
|
| 289 |
+
99
|
| 290 |
+
],
|
| 291 |
+
[
|
| 292 |
+
110,
|
| 293 |
+
121
|
| 294 |
+
],
|
| 295 |
+
[
|
| 296 |
+
5,
|
| 297 |
+
99
|
| 298 |
+
],
|
| 299 |
+
[
|
| 300 |
+
110,
|
| 301 |
+
125
|
| 302 |
+
],
|
| 303 |
+
[
|
| 304 |
+
110,
|
| 305 |
+
124
|
| 306 |
+
],
|
| 307 |
+
[
|
| 308 |
+
60,
|
| 309 |
+
97
|
| 310 |
+
],
|
| 311 |
+
[
|
| 312 |
+
105,
|
| 313 |
+
130
|
| 314 |
+
],
|
| 315 |
+
[
|
| 316 |
+
113,
|
| 317 |
+
122
|
| 318 |
+
],
|
| 319 |
+
[
|
| 320 |
+
115,
|
| 321 |
+
124
|
| 322 |
+
],
|
| 323 |
+
[
|
| 324 |
+
105,
|
| 325 |
+
131
|
| 326 |
+
],
|
| 327 |
+
[
|
| 328 |
+
114,
|
| 329 |
+
125
|
| 330 |
+
],
|
| 331 |
+
[
|
| 332 |
+
110,
|
| 333 |
+
122
|
| 334 |
+
],
|
| 335 |
+
[
|
| 336 |
+
110,
|
| 337 |
+
123
|
| 338 |
+
],
|
| 339 |
+
[
|
| 340 |
+
105,
|
| 341 |
+
140
|
| 342 |
+
],
|
| 343 |
+
[
|
| 344 |
+
83,
|
| 345 |
+
160
|
| 346 |
+
],
|
| 347 |
+
[
|
| 348 |
+
135,
|
| 349 |
+
141
|
| 350 |
+
],
|
| 351 |
+
[
|
| 352 |
+
62,
|
| 353 |
+
97
|
| 354 |
+
],
|
| 355 |
+
[
|
| 356 |
+
85,
|
| 357 |
+
160
|
| 358 |
+
],
|
| 359 |
+
[
|
| 360 |
+
85,
|
| 361 |
+
161
|
| 362 |
+
],
|
| 363 |
+
[
|
| 364 |
+
83,
|
| 365 |
+
161
|
| 366 |
+
],
|
| 367 |
+
[
|
| 368 |
+
117,
|
| 369 |
+
120
|
| 370 |
+
],
|
| 371 |
+
[
|
| 372 |
+
23,
|
| 373 |
+
85
|
| 374 |
+
],
|
| 375 |
+
[
|
| 376 |
+
14,
|
| 377 |
+
169
|
| 378 |
+
],
|
| 379 |
+
[
|
| 380 |
+
12,
|
| 381 |
+
171
|
| 382 |
+
],
|
| 383 |
+
[
|
| 384 |
+
12,
|
| 385 |
+
169
|
| 386 |
+
],
|
| 387 |
+
[
|
| 388 |
+
23,
|
| 389 |
+
83
|
| 390 |
+
],
|
| 391 |
+
[
|
| 392 |
+
137,
|
| 393 |
+
141
|
| 394 |
+
],
|
| 395 |
+
[
|
| 396 |
+
14,
|
| 397 |
+
171
|
| 398 |
+
],
|
| 399 |
+
[
|
| 400 |
+
137,
|
| 401 |
+
140
|
| 402 |
+
],
|
| 403 |
+
[
|
| 404 |
+
21,
|
| 405 |
+
85
|
| 406 |
+
],
|
| 407 |
+
[
|
| 408 |
+
3,
|
| 409 |
+
12
|
| 410 |
+
],
|
| 411 |
+
[
|
| 412 |
+
3,
|
| 413 |
+
14
|
| 414 |
+
],
|
| 415 |
+
[
|
| 416 |
+
139,
|
| 417 |
+
141
|
| 418 |
+
],
|
| 419 |
+
[
|
| 420 |
+
21,
|
| 421 |
+
83
|
| 422 |
+
],
|
| 423 |
+
[
|
| 424 |
+
130,
|
| 425 |
+
139
|
| 426 |
+
],
|
| 427 |
+
[
|
| 428 |
+
131,
|
| 429 |
+
142
|
| 430 |
+
],
|
| 431 |
+
[
|
| 432 |
+
5,
|
| 433 |
+
12
|
| 434 |
+
],
|
| 435 |
+
[
|
| 436 |
+
130,
|
| 437 |
+
142
|
| 438 |
+
],
|
| 439 |
+
[
|
| 440 |
+
131,
|
| 441 |
+
139
|
| 442 |
+
],
|
| 443 |
+
[
|
| 444 |
+
141,
|
| 445 |
+
142
|
| 446 |
+
],
|
| 447 |
+
[
|
| 448 |
+
131,
|
| 449 |
+
135
|
| 450 |
+
],
|
| 451 |
+
[
|
| 452 |
+
5,
|
| 453 |
+
14
|
| 454 |
+
],
|
| 455 |
+
[
|
| 456 |
+
131,
|
| 457 |
+
137
|
| 458 |
+
],
|
| 459 |
+
[
|
| 460 |
+
130,
|
| 461 |
+
137
|
| 462 |
+
],
|
| 463 |
+
[
|
| 464 |
+
135,
|
| 465 |
+
140
|
| 466 |
+
],
|
| 467 |
+
[
|
| 468 |
+
101,
|
| 469 |
+
159
|
| 470 |
+
],
|
| 471 |
+
[
|
| 472 |
+
101,
|
| 473 |
+
157
|
| 474 |
+
],
|
| 475 |
+
[
|
| 476 |
+
37,
|
| 477 |
+
174
|
| 478 |
+
],
|
| 479 |
+
[
|
| 480 |
+
114,
|
| 481 |
+
118
|
| 482 |
+
],
|
| 483 |
+
[
|
| 484 |
+
130,
|
| 485 |
+
135
|
| 486 |
+
],
|
| 487 |
+
[
|
| 488 |
+
115,
|
| 489 |
+
119
|
| 490 |
+
],
|
| 491 |
+
[
|
| 492 |
+
113,
|
| 493 |
+
123
|
| 494 |
+
],
|
| 495 |
+
[
|
| 496 |
+
39,
|
| 497 |
+
174
|
| 498 |
+
],
|
| 499 |
+
[
|
| 500 |
+
117,
|
| 501 |
+
121
|
| 502 |
+
],
|
| 503 |
+
[
|
| 504 |
+
113,
|
| 505 |
+
118
|
| 506 |
+
],
|
| 507 |
+
[
|
| 508 |
+
101,
|
| 509 |
+
162
|
| 510 |
+
],
|
| 511 |
+
[
|
| 512 |
+
60,
|
| 513 |
+
116
|
| 514 |
+
],
|
| 515 |
+
[
|
| 516 |
+
101,
|
| 517 |
+
164
|
| 518 |
+
],
|
| 519 |
+
[
|
| 520 |
+
139,
|
| 521 |
+
140
|
| 522 |
+
],
|
| 523 |
+
[
|
| 524 |
+
113,
|
| 525 |
+
119
|
| 526 |
+
],
|
| 527 |
+
[
|
| 528 |
+
140,
|
| 529 |
+
142
|
| 530 |
+
],
|
| 531 |
+
[
|
| 532 |
+
117,
|
| 533 |
+
119
|
| 534 |
+
],
|
| 535 |
+
[
|
| 536 |
+
115,
|
| 537 |
+
123
|
| 538 |
+
],
|
| 539 |
+
[
|
| 540 |
+
39,
|
| 541 |
+
185
|
| 542 |
+
],
|
| 543 |
+
[
|
| 544 |
+
114,
|
| 545 |
+
121
|
| 546 |
+
],
|
| 547 |
+
[
|
| 548 |
+
40,
|
| 549 |
+
106
|
| 550 |
+
],
|
| 551 |
+
[
|
| 552 |
+
117,
|
| 553 |
+
118
|
| 554 |
+
],
|
| 555 |
+
[
|
| 556 |
+
113,
|
| 557 |
+
121
|
| 558 |
+
],
|
| 559 |
+
[
|
| 560 |
+
37,
|
| 561 |
+
185
|
| 562 |
+
],
|
| 563 |
+
[
|
| 564 |
+
62,
|
| 565 |
+
116
|
| 566 |
+
],
|
| 567 |
+
[
|
| 568 |
+
36,
|
| 569 |
+
106
|
| 570 |
+
],
|
| 571 |
+
[
|
| 572 |
+
117,
|
| 573 |
+
123
|
| 574 |
+
],
|
| 575 |
+
[
|
| 576 |
+
114,
|
| 577 |
+
123
|
| 578 |
+
],
|
| 579 |
+
[
|
| 580 |
+
125,
|
| 581 |
+
153
|
| 582 |
+
],
|
| 583 |
+
[
|
| 584 |
+
115,
|
| 585 |
+
118
|
| 586 |
+
],
|
| 587 |
+
[
|
| 588 |
+
114,
|
| 589 |
+
119
|
| 590 |
+
],
|
| 591 |
+
[
|
| 592 |
+
120,
|
| 593 |
+
154
|
| 594 |
+
],
|
| 595 |
+
[
|
| 596 |
+
124,
|
| 597 |
+
155
|
| 598 |
+
],
|
| 599 |
+
[
|
| 600 |
+
63,
|
| 601 |
+
107
|
| 602 |
+
],
|
| 603 |
+
[
|
| 604 |
+
139,
|
| 605 |
+
175
|
| 606 |
+
],
|
| 607 |
+
[
|
| 608 |
+
115,
|
| 609 |
+
121
|
| 610 |
+
],
|
| 611 |
+
[
|
| 612 |
+
142,
|
| 613 |
+
178
|
| 614 |
+
],
|
| 615 |
+
[
|
| 616 |
+
59,
|
| 617 |
+
107
|
| 618 |
+
],
|
| 619 |
+
[
|
| 620 |
+
114,
|
| 621 |
+
122
|
| 622 |
+
],
|
| 623 |
+
[
|
| 624 |
+
63,
|
| 625 |
+
96
|
| 626 |
+
],
|
| 627 |
+
[
|
| 628 |
+
115,
|
| 629 |
+
122
|
| 630 |
+
],
|
| 631 |
+
[
|
| 632 |
+
115,
|
| 633 |
+
120
|
| 634 |
+
],
|
| 635 |
+
[
|
| 636 |
+
59,
|
| 637 |
+
96
|
| 638 |
+
],
|
| 639 |
+
[
|
| 640 |
+
105,
|
| 641 |
+
183
|
| 642 |
+
],
|
| 643 |
+
[
|
| 644 |
+
114,
|
| 645 |
+
120
|
| 646 |
+
],
|
| 647 |
+
[
|
| 648 |
+
119,
|
| 649 |
+
155
|
| 650 |
+
],
|
| 651 |
+
[
|
| 652 |
+
118,
|
| 653 |
+
153
|
| 654 |
+
],
|
| 655 |
+
[
|
| 656 |
+
105,
|
| 657 |
+
186
|
| 658 |
+
],
|
| 659 |
+
[
|
| 660 |
+
105,
|
| 661 |
+
187
|
| 662 |
+
],
|
| 663 |
+
[
|
| 664 |
+
105,
|
| 665 |
+
188
|
| 666 |
+
],
|
| 667 |
+
[
|
| 668 |
+
113,
|
| 669 |
+
124
|
| 670 |
+
],
|
| 671 |
+
[
|
| 672 |
+
122,
|
| 673 |
+
167
|
| 674 |
+
],
|
| 675 |
+
[
|
| 676 |
+
105,
|
| 677 |
+
184
|
| 678 |
+
],
|
| 679 |
+
[
|
| 680 |
+
113,
|
| 681 |
+
125
|
| 682 |
+
],
|
| 683 |
+
[
|
| 684 |
+
119,
|
| 685 |
+
154
|
| 686 |
+
],
|
| 687 |
+
[
|
| 688 |
+
137,
|
| 689 |
+
182
|
| 690 |
+
],
|
| 691 |
+
[
|
| 692 |
+
37,
|
| 693 |
+
225
|
| 694 |
+
],
|
| 695 |
+
[
|
| 696 |
+
39,
|
| 697 |
+
225
|
| 698 |
+
],
|
| 699 |
+
[
|
| 700 |
+
118,
|
| 701 |
+
154
|
| 702 |
+
],
|
| 703 |
+
[
|
| 704 |
+
139,
|
| 705 |
+
178
|
| 706 |
+
],
|
| 707 |
+
[
|
| 708 |
+
117,
|
| 709 |
+
125
|
| 710 |
+
],
|
| 711 |
+
[
|
| 712 |
+
113,
|
| 713 |
+
203
|
| 714 |
+
],
|
| 715 |
+
[
|
| 716 |
+
135,
|
| 717 |
+
178
|
| 718 |
+
],
|
| 719 |
+
[
|
| 720 |
+
117,
|
| 721 |
+
124
|
| 722 |
+
],
|
| 723 |
+
[
|
| 724 |
+
142,
|
| 725 |
+
175
|
| 726 |
+
],
|
| 727 |
+
[
|
| 728 |
+
135,
|
| 729 |
+
182
|
| 730 |
+
],
|
| 731 |
+
[
|
| 732 |
+
105,
|
| 733 |
+
193
|
| 734 |
+
],
|
| 735 |
+
[
|
| 736 |
+
137,
|
| 737 |
+
175
|
| 738 |
+
],
|
| 739 |
+
[
|
| 740 |
+
137,
|
| 741 |
+
178
|
| 742 |
+
],
|
| 743 |
+
[
|
| 744 |
+
39,
|
| 745 |
+
238
|
| 746 |
+
],
|
| 747 |
+
[
|
| 748 |
+
105,
|
| 749 |
+
191
|
| 750 |
+
],
|
| 751 |
+
[
|
| 752 |
+
135,
|
| 753 |
+
175
|
| 754 |
+
],
|
| 755 |
+
[
|
| 756 |
+
105,
|
| 757 |
+
196
|
| 758 |
+
],
|
| 759 |
+
[
|
| 760 |
+
118,
|
| 761 |
+
167
|
| 762 |
+
],
|
| 763 |
+
[
|
| 764 |
+
105,
|
| 765 |
+
195
|
| 766 |
+
],
|
| 767 |
+
[
|
| 768 |
+
105,
|
| 769 |
+
201
|
| 770 |
+
],
|
| 771 |
+
[
|
| 772 |
+
82,
|
| 773 |
+
108
|
| 774 |
+
],
|
| 775 |
+
[
|
| 776 |
+
105,
|
| 777 |
+
192
|
| 778 |
+
],
|
| 779 |
+
[
|
| 780 |
+
119,
|
| 781 |
+
167
|
| 782 |
+
],
|
| 783 |
+
[
|
| 784 |
+
37,
|
| 785 |
+
238
|
| 786 |
+
],
|
| 787 |
+
[
|
| 788 |
+
86,
|
| 789 |
+
108
|
| 790 |
+
],
|
| 791 |
+
[
|
| 792 |
+
139,
|
| 793 |
+
182
|
| 794 |
+
],
|
| 795 |
+
[
|
| 796 |
+
113,
|
| 797 |
+
183
|
| 798 |
+
],
|
| 799 |
+
[
|
| 800 |
+
119,
|
| 801 |
+
153
|
| 802 |
+
],
|
| 803 |
+
[
|
| 804 |
+
118,
|
| 805 |
+
155
|
| 806 |
+
],
|
| 807 |
+
[
|
| 808 |
+
121,
|
| 809 |
+
154
|
| 810 |
+
],
|
| 811 |
+
[
|
| 812 |
+
142,
|
| 813 |
+
182
|
| 814 |
+
],
|
| 815 |
+
[
|
| 816 |
+
121,
|
| 817 |
+
153
|
| 818 |
+
],
|
| 819 |
+
[
|
| 820 |
+
113,
|
| 821 |
+
186
|
| 822 |
+
],
|
| 823 |
+
[
|
| 824 |
+
114,
|
| 825 |
+
184
|
| 826 |
+
],
|
| 827 |
+
[
|
| 828 |
+
115,
|
| 829 |
+
197
|
| 830 |
+
],
|
| 831 |
+
[
|
| 832 |
+
124,
|
| 833 |
+
159
|
| 834 |
+
],
|
| 835 |
+
[
|
| 836 |
+
117,
|
| 837 |
+
122
|
| 838 |
+
],
|
| 839 |
+
[
|
| 840 |
+
122,
|
| 841 |
+
157
|
| 842 |
+
],
|
| 843 |
+
[
|
| 844 |
+
115,
|
| 845 |
+
191
|
| 846 |
+
],
|
| 847 |
+
[
|
| 848 |
+
105,
|
| 849 |
+
208
|
| 850 |
+
],
|
| 851 |
+
[
|
| 852 |
+
59,
|
| 853 |
+
97
|
| 854 |
+
],
|
| 855 |
+
[
|
| 856 |
+
125,
|
| 857 |
+
157
|
| 858 |
+
],
|
| 859 |
+
[
|
| 860 |
+
139,
|
| 861 |
+
199
|
| 862 |
+
],
|
| 863 |
+
[
|
| 864 |
+
40,
|
| 865 |
+
127
|
| 866 |
+
],
|
| 867 |
+
[
|
| 868 |
+
36,
|
| 869 |
+
127
|
| 870 |
+
],
|
| 871 |
+
[
|
| 872 |
+
122,
|
| 873 |
+
159
|
| 874 |
+
],
|
| 875 |
+
[
|
| 876 |
+
22,
|
| 877 |
+
82
|
| 878 |
+
],
|
| 879 |
+
[
|
| 880 |
+
141,
|
| 881 |
+
194
|
| 882 |
+
],
|
| 883 |
+
[
|
| 884 |
+
120,
|
| 885 |
+
155
|
| 886 |
+
],
|
| 887 |
+
[
|
| 888 |
+
110,
|
| 889 |
+
187
|
| 890 |
+
],
|
| 891 |
+
[
|
| 892 |
+
114,
|
| 893 |
+
193
|
| 894 |
+
],
|
| 895 |
+
[
|
| 896 |
+
120,
|
| 897 |
+
153
|
| 898 |
+
],
|
| 899 |
+
[
|
| 900 |
+
110,
|
| 901 |
+
188
|
| 902 |
+
],
|
| 903 |
+
[
|
| 904 |
+
22,
|
| 905 |
+
86
|
| 906 |
+
],
|
| 907 |
+
[
|
| 908 |
+
110,
|
| 909 |
+
204
|
| 910 |
+
],
|
| 911 |
+
[
|
| 912 |
+
114,
|
| 913 |
+
192
|
| 914 |
+
],
|
| 915 |
+
[
|
| 916 |
+
110,
|
| 917 |
+
184
|
| 918 |
+
],
|
| 919 |
+
[
|
| 920 |
+
121,
|
| 921 |
+
155
|
| 922 |
+
],
|
| 923 |
+
[
|
| 924 |
+
40,
|
| 925 |
+
134
|
| 926 |
+
],
|
| 927 |
+
[
|
| 928 |
+
123,
|
| 929 |
+
155
|
| 930 |
+
],
|
| 931 |
+
[
|
| 932 |
+
110,
|
| 933 |
+
197
|
| 934 |
+
],
|
| 935 |
+
[
|
| 936 |
+
124,
|
| 937 |
+
162
|
| 938 |
+
],
|
| 939 |
+
[
|
| 940 |
+
141,
|
| 941 |
+
202
|
| 942 |
+
],
|
| 943 |
+
[
|
| 944 |
+
117,
|
| 945 |
+
204
|
| 946 |
+
],
|
| 947 |
+
[
|
| 948 |
+
115,
|
| 949 |
+
213
|
| 950 |
+
],
|
| 951 |
+
[
|
| 952 |
+
115,
|
| 953 |
+
196
|
| 954 |
+
],
|
| 955 |
+
[
|
| 956 |
+
130,
|
| 957 |
+
198
|
| 958 |
+
],
|
| 959 |
+
[
|
| 960 |
+
139,
|
| 961 |
+
200
|
| 962 |
+
],
|
| 963 |
+
[
|
| 964 |
+
125,
|
| 965 |
+
164
|
| 966 |
+
],
|
| 967 |
+
[
|
| 968 |
+
37,
|
| 969 |
+
253
|
| 970 |
+
],
|
| 971 |
+
[
|
| 972 |
+
137,
|
| 973 |
+
200
|
| 974 |
+
],
|
| 975 |
+
[
|
| 976 |
+
135,
|
| 977 |
+
205
|
| 978 |
+
],
|
| 979 |
+
[
|
| 980 |
+
131,
|
| 981 |
+
194
|
| 982 |
+
],
|
| 983 |
+
[
|
| 984 |
+
137,
|
| 985 |
+
199
|
| 986 |
+
],
|
| 987 |
+
[
|
| 988 |
+
113,
|
| 989 |
+
120
|
| 990 |
+
],
|
| 991 |
+
[
|
| 992 |
+
4,
|
| 993 |
+
15
|
| 994 |
+
],
|
| 995 |
+
[
|
| 996 |
+
121,
|
| 997 |
+
167
|
| 998 |
+
],
|
| 999 |
+
[
|
| 1000 |
+
114,
|
| 1001 |
+
195
|
| 1002 |
+
],
|
| 1003 |
+
[
|
| 1004 |
+
117,
|
| 1005 |
+
206
|
| 1006 |
+
],
|
| 1007 |
+
[
|
| 1008 |
+
37,
|
| 1009 |
+
256
|
| 1010 |
+
],
|
| 1011 |
+
[
|
| 1012 |
+
135,
|
| 1013 |
+
199
|
| 1014 |
+
],
|
| 1015 |
+
[
|
| 1016 |
+
120,
|
| 1017 |
+
162
|
| 1018 |
+
],
|
| 1019 |
+
[
|
| 1020 |
+
140,
|
| 1021 |
+
198
|
| 1022 |
+
],
|
| 1023 |
+
[
|
| 1024 |
+
123,
|
| 1025 |
+
167
|
| 1026 |
+
],
|
| 1027 |
+
[
|
| 1028 |
+
113,
|
| 1029 |
+
190
|
| 1030 |
+
],
|
| 1031 |
+
[
|
| 1032 |
+
117,
|
| 1033 |
+
187
|
| 1034 |
+
],
|
| 1035 |
+
[
|
| 1036 |
+
130,
|
| 1037 |
+
202
|
| 1038 |
+
],
|
| 1039 |
+
[
|
| 1040 |
+
135,
|
| 1041 |
+
210
|
| 1042 |
+
],
|
| 1043 |
+
[
|
| 1044 |
+
113,
|
| 1045 |
+
207
|
| 1046 |
+
],
|
| 1047 |
+
[
|
| 1048 |
+
117,
|
| 1049 |
+
188
|
| 1050 |
+
],
|
| 1051 |
+
[
|
| 1052 |
+
137,
|
| 1053 |
+
210
|
| 1054 |
+
],
|
| 1055 |
+
[
|
| 1056 |
+
36,
|
| 1057 |
+
126
|
| 1058 |
+
],
|
| 1059 |
+
[
|
| 1060 |
+
36,
|
| 1061 |
+
134
|
| 1062 |
+
],
|
| 1063 |
+
[
|
| 1064 |
+
110,
|
| 1065 |
+
209
|
| 1066 |
+
],
|
| 1067 |
+
[
|
| 1068 |
+
142,
|
| 1069 |
+
205
|
| 1070 |
+
],
|
| 1071 |
+
[
|
| 1072 |
+
135,
|
| 1073 |
+
200
|
| 1074 |
+
],
|
| 1075 |
+
[
|
| 1076 |
+
40,
|
| 1077 |
+
126
|
| 1078 |
+
],
|
| 1079 |
+
[
|
| 1080 |
+
39,
|
| 1081 |
+
253
|
| 1082 |
+
],
|
| 1083 |
+
[
|
| 1084 |
+
110,
|
| 1085 |
+
211
|
| 1086 |
+
],
|
| 1087 |
+
[
|
| 1088 |
+
114,
|
| 1089 |
+
201
|
| 1090 |
+
],
|
| 1091 |
+
[
|
| 1092 |
+
142,
|
| 1093 |
+
200
|
| 1094 |
+
],
|
| 1095 |
+
[
|
| 1096 |
+
124,
|
| 1097 |
+
154
|
| 1098 |
+
],
|
| 1099 |
+
[
|
| 1100 |
+
125,
|
| 1101 |
+
154
|
| 1102 |
+
],
|
| 1103 |
+
[
|
| 1104 |
+
120,
|
| 1105 |
+
164
|
| 1106 |
+
],
|
| 1107 |
+
[
|
| 1108 |
+
139,
|
| 1109 |
+
205
|
| 1110 |
+
],
|
| 1111 |
+
[
|
| 1112 |
+
118,
|
| 1113 |
+
157
|
| 1114 |
+
],
|
| 1115 |
+
[
|
| 1116 |
+
123,
|
| 1117 |
+
154
|
| 1118 |
+
],
|
| 1119 |
+
[
|
| 1120 |
+
115,
|
| 1121 |
+
227
|
| 1122 |
+
],
|
| 1123 |
+
[
|
| 1124 |
+
123,
|
| 1125 |
+
153
|
| 1126 |
+
],
|
| 1127 |
+
[
|
| 1128 |
+
119,
|
| 1129 |
+
159
|
| 1130 |
+
],
|
| 1131 |
+
[
|
| 1132 |
+
2,
|
| 1133 |
+
13
|
| 1134 |
+
],
|
| 1135 |
+
[
|
| 1136 |
+
130,
|
| 1137 |
+
194
|
| 1138 |
+
],
|
| 1139 |
+
[
|
| 1140 |
+
39,
|
| 1141 |
+
256
|
| 1142 |
+
],
|
| 1143 |
+
[
|
| 1144 |
+
137,
|
| 1145 |
+
205
|
| 1146 |
+
],
|
| 1147 |
+
[
|
| 1148 |
+
142,
|
| 1149 |
+
210
|
| 1150 |
+
],
|
| 1151 |
+
[
|
| 1152 |
+
131,
|
| 1153 |
+
202
|
| 1154 |
+
],
|
| 1155 |
+
[
|
| 1156 |
+
119,
|
| 1157 |
+
162
|
| 1158 |
+
],
|
| 1159 |
+
[
|
| 1160 |
+
140,
|
| 1161 |
+
190
|
| 1162 |
+
],
|
| 1163 |
+
[
|
| 1164 |
+
6,
|
| 1165 |
+
13
|
| 1166 |
+
],
|
| 1167 |
+
[
|
| 1168 |
+
122,
|
| 1169 |
+
153
|
| 1170 |
+
],
|
| 1171 |
+
[
|
| 1172 |
+
131,
|
| 1173 |
+
198
|
| 1174 |
+
],
|
| 1175 |
+
[
|
| 1176 |
+
4,
|
| 1177 |
+
11
|
| 1178 |
+
],
|
| 1179 |
+
[
|
| 1180 |
+
122,
|
| 1181 |
+
155
|
| 1182 |
+
],
|
| 1183 |
+
[
|
| 1184 |
+
118,
|
| 1185 |
+
164
|
| 1186 |
+
],
|
| 1187 |
+
[
|
| 1188 |
+
63,
|
| 1189 |
+
97
|
| 1190 |
+
],
|
| 1191 |
+
[
|
| 1192 |
+
131,
|
| 1193 |
+
190
|
| 1194 |
+
],
|
| 1195 |
+
[
|
| 1196 |
+
123,
|
| 1197 |
+
157
|
| 1198 |
+
],
|
| 1199 |
+
[
|
| 1200 |
+
110,
|
| 1201 |
+
183
|
| 1202 |
+
],
|
| 1203 |
+
[
|
| 1204 |
+
110,
|
| 1205 |
+
208
|
| 1206 |
+
],
|
| 1207 |
+
[
|
| 1208 |
+
123,
|
| 1209 |
+
159
|
| 1210 |
+
],
|
| 1211 |
+
[
|
| 1212 |
+
110,
|
| 1213 |
+
192
|
| 1214 |
+
],
|
| 1215 |
+
[
|
| 1216 |
+
141,
|
| 1217 |
+
190
|
| 1218 |
+
],
|
| 1219 |
+
[
|
| 1220 |
+
123,
|
| 1221 |
+
162
|
| 1222 |
+
],
|
| 1223 |
+
[
|
| 1224 |
+
142,
|
| 1225 |
+
199
|
| 1226 |
+
],
|
| 1227 |
+
[
|
| 1228 |
+
24,
|
| 1229 |
+
84
|
| 1230 |
+
],
|
| 1231 |
+
[
|
| 1232 |
+
121,
|
| 1233 |
+
157
|
| 1234 |
+
],
|
| 1235 |
+
[
|
| 1236 |
+
115,
|
| 1237 |
+
229
|
| 1238 |
+
],
|
| 1239 |
+
[
|
| 1240 |
+
110,
|
| 1241 |
+
229
|
| 1242 |
+
],
|
| 1243 |
+
[
|
| 1244 |
+
110,
|
| 1245 |
+
186
|
| 1246 |
+
],
|
| 1247 |
+
[
|
| 1248 |
+
121,
|
| 1249 |
+
164
|
| 1250 |
+
],
|
| 1251 |
+
[
|
| 1252 |
+
125,
|
| 1253 |
+
167
|
| 1254 |
+
],
|
| 1255 |
+
[
|
| 1256 |
+
113,
|
| 1257 |
+
201
|
| 1258 |
+
],
|
| 1259 |
+
[
|
| 1260 |
+
140,
|
| 1261 |
+
194
|
| 1262 |
+
],
|
| 1263 |
+
[
|
| 1264 |
+
124,
|
| 1265 |
+
167
|
| 1266 |
+
],
|
| 1267 |
+
[
|
| 1268 |
+
118,
|
| 1269 |
+
159
|
| 1270 |
+
],
|
| 1271 |
+
[
|
| 1272 |
+
139,
|
| 1273 |
+
210
|
| 1274 |
+
],
|
| 1275 |
+
[
|
| 1276 |
+
110,
|
| 1277 |
+
196
|
| 1278 |
+
],
|
| 1279 |
+
[
|
| 1280 |
+
113,
|
| 1281 |
+
227
|
| 1282 |
+
],
|
| 1283 |
+
[
|
| 1284 |
+
110,
|
| 1285 |
+
218
|
| 1286 |
+
],
|
| 1287 |
+
[
|
| 1288 |
+
15,
|
| 1289 |
+
112
|
| 1290 |
+
],
|
| 1291 |
+
[
|
| 1292 |
+
110,
|
| 1293 |
+
195
|
| 1294 |
+
],
|
| 1295 |
+
[
|
| 1296 |
+
123,
|
| 1297 |
+
164
|
| 1298 |
+
],
|
| 1299 |
+
[
|
| 1300 |
+
110,
|
| 1301 |
+
206
|
| 1302 |
+
],
|
| 1303 |
+
[
|
| 1304 |
+
110,
|
| 1305 |
+
212
|
| 1306 |
+
],
|
| 1307 |
+
[
|
| 1308 |
+
119,
|
| 1309 |
+
157
|
| 1310 |
+
],
|
| 1311 |
+
[
|
| 1312 |
+
110,
|
| 1313 |
+
213
|
| 1314 |
+
],
|
| 1315 |
+
[
|
| 1316 |
+
141,
|
| 1317 |
+
198
|
| 1318 |
+
],
|
| 1319 |
+
[
|
| 1320 |
+
11,
|
| 1321 |
+
112
|
| 1322 |
+
],
|
| 1323 |
+
[
|
| 1324 |
+
130,
|
| 1325 |
+
190
|
| 1326 |
+
],
|
| 1327 |
+
[
|
| 1328 |
+
119,
|
| 1329 |
+
164
|
| 1330 |
+
],
|
| 1331 |
+
[
|
| 1332 |
+
121,
|
| 1333 |
+
159
|
| 1334 |
+
],
|
| 1335 |
+
[
|
| 1336 |
+
118,
|
| 1337 |
+
162
|
| 1338 |
+
],
|
| 1339 |
+
[
|
| 1340 |
+
121,
|
| 1341 |
+
162
|
| 1342 |
+
],
|
| 1343 |
+
[
|
| 1344 |
+
110,
|
| 1345 |
+
203
|
| 1346 |
+
],
|
| 1347 |
+
[
|
| 1348 |
+
114,
|
| 1349 |
+
208
|
| 1350 |
+
],
|
| 1351 |
+
[
|
| 1352 |
+
155,
|
| 1353 |
+
197
|
| 1354 |
+
],
|
| 1355 |
+
[
|
| 1356 |
+
110,
|
| 1357 |
+
193
|
| 1358 |
+
],
|
| 1359 |
+
[
|
| 1360 |
+
153,
|
| 1361 |
+
184
|
| 1362 |
+
],
|
| 1363 |
+
[
|
| 1364 |
+
2,
|
| 1365 |
+
12
|
| 1366 |
+
],
|
| 1367 |
+
[
|
| 1368 |
+
20,
|
| 1369 |
+
84
|
| 1370 |
+
],
|
| 1371 |
+
[
|
| 1372 |
+
140,
|
| 1373 |
+
202
|
| 1374 |
+
],
|
| 1375 |
+
[
|
| 1376 |
+
110,
|
| 1377 |
+
207
|
| 1378 |
+
],
|
| 1379 |
+
[
|
| 1380 |
+
157,
|
| 1381 |
+
201
|
| 1382 |
+
],
|
| 1383 |
+
[
|
| 1384 |
+
110,
|
| 1385 |
+
191
|
| 1386 |
+
],
|
| 1387 |
+
[
|
| 1388 |
+
120,
|
| 1389 |
+
167
|
| 1390 |
+
],
|
| 1391 |
+
[
|
| 1392 |
+
159,
|
| 1393 |
+
227
|
| 1394 |
+
],
|
| 1395 |
+
[
|
| 1396 |
+
117,
|
| 1397 |
+
207
|
| 1398 |
+
],
|
| 1399 |
+
[
|
| 1400 |
+
38,
|
| 1401 |
+
310
|
| 1402 |
+
],
|
| 1403 |
+
[
|
| 1404 |
+
124,
|
| 1405 |
+
234
|
| 1406 |
+
],
|
| 1407 |
+
[
|
| 1408 |
+
24,
|
| 1409 |
+
85
|
| 1410 |
+
],
|
| 1411 |
+
[
|
| 1412 |
+
114,
|
| 1413 |
+
209
|
| 1414 |
+
],
|
| 1415 |
+
[
|
| 1416 |
+
114,
|
| 1417 |
+
124
|
| 1418 |
+
],
|
| 1419 |
+
[
|
| 1420 |
+
114,
|
| 1421 |
+
183
|
| 1422 |
+
],
|
| 1423 |
+
[
|
| 1424 |
+
113,
|
| 1425 |
+
212
|
| 1426 |
+
],
|
| 1427 |
+
[
|
| 1428 |
+
117,
|
| 1429 |
+
212
|
| 1430 |
+
],
|
| 1431 |
+
[
|
| 1432 |
+
115,
|
| 1433 |
+
212
|
| 1434 |
+
],
|
| 1435 |
+
[
|
| 1436 |
+
115,
|
| 1437 |
+
125
|
| 1438 |
+
],
|
| 1439 |
+
[
|
| 1440 |
+
110,
|
| 1441 |
+
201
|
| 1442 |
+
],
|
| 1443 |
+
[
|
| 1444 |
+
125,
|
| 1445 |
+
239
|
| 1446 |
+
],
|
| 1447 |
+
[
|
| 1448 |
+
2,
|
| 1449 |
+
111
|
| 1450 |
+
],
|
| 1451 |
+
[
|
| 1452 |
+
110,
|
| 1453 |
+
227
|
| 1454 |
+
],
|
| 1455 |
+
[
|
| 1456 |
+
113,
|
| 1457 |
+
218
|
| 1458 |
+
],
|
| 1459 |
+
[
|
| 1460 |
+
115,
|
| 1461 |
+
186
|
| 1462 |
+
],
|
| 1463 |
+
[
|
| 1464 |
+
113,
|
| 1465 |
+
209
|
| 1466 |
+
],
|
| 1467 |
+
[
|
| 1468 |
+
119,
|
| 1469 |
+
221
|
| 1470 |
+
],
|
| 1471 |
+
[
|
| 1472 |
+
114,
|
| 1473 |
+
218
|
| 1474 |
+
],
|
| 1475 |
+
[
|
| 1476 |
+
122,
|
| 1477 |
+
154
|
| 1478 |
+
],
|
| 1479 |
+
[
|
| 1480 |
+
117,
|
| 1481 |
+
229
|
| 1482 |
+
],
|
| 1483 |
+
[
|
| 1484 |
+
117,
|
| 1485 |
+
211
|
| 1486 |
+
],
|
| 1487 |
+
[
|
| 1488 |
+
115,
|
| 1489 |
+
211
|
| 1490 |
+
],
|
| 1491 |
+
[
|
| 1492 |
+
117,
|
| 1493 |
+
209
|
| 1494 |
+
],
|
| 1495 |
+
[
|
| 1496 |
+
117,
|
| 1497 |
+
208
|
| 1498 |
+
],
|
| 1499 |
+
[
|
| 1500 |
+
6,
|
| 1501 |
+
111
|
| 1502 |
+
],
|
| 1503 |
+
[
|
| 1504 |
+
115,
|
| 1505 |
+
187
|
| 1506 |
+
],
|
| 1507 |
+
[
|
| 1508 |
+
114,
|
| 1509 |
+
207
|
| 1510 |
+
],
|
| 1511 |
+
[
|
| 1512 |
+
117,
|
| 1513 |
+
218
|
| 1514 |
+
],
|
| 1515 |
+
[
|
| 1516 |
+
114,
|
| 1517 |
+
188
|
| 1518 |
+
],
|
| 1519 |
+
[
|
| 1520 |
+
101,
|
| 1521 |
+
234
|
| 1522 |
+
],
|
| 1523 |
+
[
|
| 1524 |
+
125,
|
| 1525 |
+
216
|
| 1526 |
+
],
|
| 1527 |
+
[
|
| 1528 |
+
154,
|
| 1529 |
+
187
|
| 1530 |
+
],
|
| 1531 |
+
[
|
| 1532 |
+
101,
|
| 1533 |
+
239
|
| 1534 |
+
],
|
| 1535 |
+
[
|
| 1536 |
+
118,
|
| 1537 |
+
216
|
| 1538 |
+
],
|
| 1539 |
+
[
|
| 1540 |
+
114,
|
| 1541 |
+
203
|
| 1542 |
+
],
|
| 1543 |
+
[
|
| 1544 |
+
113,
|
| 1545 |
+
211
|
| 1546 |
+
],
|
| 1547 |
+
[
|
| 1548 |
+
23,
|
| 1549 |
+
86
|
| 1550 |
+
],
|
| 1551 |
+
[
|
| 1552 |
+
115,
|
| 1553 |
+
203
|
| 1554 |
+
],
|
| 1555 |
+
[
|
| 1556 |
+
118,
|
| 1557 |
+
237
|
| 1558 |
+
],
|
| 1559 |
+
[
|
| 1560 |
+
120,
|
| 1561 |
+
221
|
| 1562 |
+
],
|
| 1563 |
+
[
|
| 1564 |
+
120,
|
| 1565 |
+
216
|
| 1566 |
+
],
|
| 1567 |
+
[
|
| 1568 |
+
24,
|
| 1569 |
+
83
|
| 1570 |
+
],
|
| 1571 |
+
[
|
| 1572 |
+
124,
|
| 1573 |
+
221
|
| 1574 |
+
],
|
| 1575 |
+
[
|
| 1576 |
+
36,
|
| 1577 |
+
174
|
| 1578 |
+
],
|
| 1579 |
+
[
|
| 1580 |
+
154,
|
| 1581 |
+
188
|
| 1582 |
+
],
|
| 1583 |
+
[
|
| 1584 |
+
119,
|
| 1585 |
+
232
|
| 1586 |
+
],
|
| 1587 |
+
[
|
| 1588 |
+
115,
|
| 1589 |
+
206
|
| 1590 |
+
],
|
| 1591 |
+
[
|
| 1592 |
+
115,
|
| 1593 |
+
204
|
| 1594 |
+
],
|
| 1595 |
+
[
|
| 1596 |
+
20,
|
| 1597 |
+
85
|
| 1598 |
+
],
|
| 1599 |
+
[
|
| 1600 |
+
162,
|
| 1601 |
+
229
|
| 1602 |
+
],
|
| 1603 |
+
[
|
| 1604 |
+
157,
|
| 1605 |
+
183
|
| 1606 |
+
],
|
| 1607 |
+
[
|
| 1608 |
+
114,
|
| 1609 |
+
204
|
| 1610 |
+
],
|
| 1611 |
+
[
|
| 1612 |
+
86,
|
| 1613 |
+
160
|
| 1614 |
+
],
|
| 1615 |
+
[
|
| 1616 |
+
153,
|
| 1617 |
+
192
|
| 1618 |
+
],
|
| 1619 |
+
[
|
| 1620 |
+
124,
|
| 1621 |
+
232
|
| 1622 |
+
],
|
| 1623 |
+
[
|
| 1624 |
+
24,
|
| 1625 |
+
109
|
| 1626 |
+
],
|
| 1627 |
+
[
|
| 1628 |
+
38,
|
| 1629 |
+
394
|
| 1630 |
+
],
|
| 1631 |
+
[
|
| 1632 |
+
159,
|
| 1633 |
+
186
|
| 1634 |
+
],
|
| 1635 |
+
[
|
| 1636 |
+
115,
|
| 1637 |
+
218
|
| 1638 |
+
],
|
| 1639 |
+
[
|
| 1640 |
+
40,
|
| 1641 |
+
174
|
| 1642 |
+
],
|
| 1643 |
+
[
|
| 1644 |
+
122,
|
| 1645 |
+
247
|
| 1646 |
+
],
|
| 1647 |
+
[
|
| 1648 |
+
114,
|
| 1649 |
+
211
|
| 1650 |
+
],
|
| 1651 |
+
[
|
| 1652 |
+
113,
|
| 1653 |
+
193
|
| 1654 |
+
],
|
| 1655 |
+
[
|
| 1656 |
+
113,
|
| 1657 |
+
213
|
| 1658 |
+
]
|
| 1659 |
+
],
|
| 1660 |
+
"temporal_merges": [
|
| 1661 |
+
[
|
| 1662 |
+
103,
|
| 1663 |
+
103,
|
| 1664 |
+
2
|
| 1665 |
+
],
|
| 1666 |
+
[
|
| 1667 |
+
104,
|
| 1668 |
+
104,
|
| 1669 |
+
1
|
| 1670 |
+
],
|
| 1671 |
+
[
|
| 1672 |
+
512,
|
| 1673 |
+
512,
|
| 1674 |
+
4
|
| 1675 |
+
],
|
| 1676 |
+
[
|
| 1677 |
+
513,
|
| 1678 |
+
513,
|
| 1679 |
+
2
|
| 1680 |
+
],
|
| 1681 |
+
[
|
| 1682 |
+
514,
|
| 1683 |
+
514,
|
| 1684 |
+
1
|
| 1685 |
+
],
|
| 1686 |
+
[
|
| 1687 |
+
512,
|
| 1688 |
+
103,
|
| 1689 |
+
4
|
| 1690 |
+
],
|
| 1691 |
+
[
|
| 1692 |
+
515,
|
| 1693 |
+
515,
|
| 1694 |
+
4
|
| 1695 |
+
],
|
| 1696 |
+
[
|
| 1697 |
+
103,
|
| 1698 |
+
103,
|
| 1699 |
+
4
|
| 1700 |
+
],
|
| 1701 |
+
[
|
| 1702 |
+
513,
|
| 1703 |
+
104,
|
| 1704 |
+
2
|
| 1705 |
+
],
|
| 1706 |
+
[
|
| 1707 |
+
104,
|
| 1708 |
+
104,
|
| 1709 |
+
2
|
| 1710 |
+
],
|
| 1711 |
+
[
|
| 1712 |
+
103,
|
| 1713 |
+
512,
|
| 1714 |
+
4
|
| 1715 |
+
],
|
| 1716 |
+
[
|
| 1717 |
+
103,
|
| 1718 |
+
103,
|
| 1719 |
+
6
|
| 1720 |
+
],
|
| 1721 |
+
[
|
| 1722 |
+
512,
|
| 1723 |
+
103,
|
| 1724 |
+
6
|
| 1725 |
+
],
|
| 1726 |
+
[
|
| 1727 |
+
515,
|
| 1728 |
+
104,
|
| 1729 |
+
4
|
| 1730 |
+
],
|
| 1731 |
+
[
|
| 1732 |
+
513,
|
| 1733 |
+
104,
|
| 1734 |
+
3
|
| 1735 |
+
],
|
| 1736 |
+
[
|
| 1737 |
+
515,
|
| 1738 |
+
513,
|
| 1739 |
+
4
|
| 1740 |
+
],
|
| 1741 |
+
[
|
| 1742 |
+
104,
|
| 1743 |
+
104,
|
| 1744 |
+
3
|
| 1745 |
+
],
|
| 1746 |
+
[
|
| 1747 |
+
103,
|
| 1748 |
+
133,
|
| 1749 |
+
2
|
| 1750 |
+
],
|
| 1751 |
+
[
|
| 1752 |
+
103,
|
| 1753 |
+
132,
|
| 1754 |
+
2
|
| 1755 |
+
],
|
| 1756 |
+
[
|
| 1757 |
+
103,
|
| 1758 |
+
136,
|
| 1759 |
+
2
|
| 1760 |
+
],
|
| 1761 |
+
[
|
| 1762 |
+
515,
|
| 1763 |
+
520,
|
| 1764 |
+
4
|
| 1765 |
+
],
|
| 1766 |
+
[
|
| 1767 |
+
103,
|
| 1768 |
+
138,
|
| 1769 |
+
2
|
| 1770 |
+
],
|
| 1771 |
+
[
|
| 1772 |
+
103,
|
| 1773 |
+
144,
|
| 1774 |
+
2
|
| 1775 |
+
],
|
| 1776 |
+
[
|
| 1777 |
+
104,
|
| 1778 |
+
513,
|
| 1779 |
+
2
|
| 1780 |
+
],
|
| 1781 |
+
[
|
| 1782 |
+
512,
|
| 1783 |
+
132,
|
| 1784 |
+
4
|
| 1785 |
+
],
|
| 1786 |
+
[
|
| 1787 |
+
512,
|
| 1788 |
+
133,
|
| 1789 |
+
4
|
| 1790 |
+
],
|
| 1791 |
+
[
|
| 1792 |
+
103,
|
| 1793 |
+
145,
|
| 1794 |
+
2
|
| 1795 |
+
],
|
| 1796 |
+
[
|
| 1797 |
+
103,
|
| 1798 |
+
146,
|
| 1799 |
+
2
|
| 1800 |
+
],
|
| 1801 |
+
[
|
| 1802 |
+
103,
|
| 1803 |
+
147,
|
| 1804 |
+
2
|
| 1805 |
+
],
|
| 1806 |
+
[
|
| 1807 |
+
514,
|
| 1808 |
+
517,
|
| 1809 |
+
1
|
| 1810 |
+
],
|
| 1811 |
+
[
|
| 1812 |
+
103,
|
| 1813 |
+
148,
|
| 1814 |
+
2
|
| 1815 |
+
],
|
| 1816 |
+
[
|
| 1817 |
+
512,
|
| 1818 |
+
136,
|
| 1819 |
+
4
|
| 1820 |
+
],
|
| 1821 |
+
[
|
| 1822 |
+
138,
|
| 1823 |
+
138,
|
| 1824 |
+
3
|
| 1825 |
+
],
|
| 1826 |
+
[
|
| 1827 |
+
512,
|
| 1828 |
+
144,
|
| 1829 |
+
4
|
| 1830 |
+
],
|
| 1831 |
+
[
|
| 1832 |
+
103,
|
| 1833 |
+
149,
|
| 1834 |
+
2
|
| 1835 |
+
],
|
| 1836 |
+
[
|
| 1837 |
+
514,
|
| 1838 |
+
517,
|
| 1839 |
+
3
|
| 1840 |
+
],
|
| 1841 |
+
[
|
| 1842 |
+
512,
|
| 1843 |
+
145,
|
| 1844 |
+
4
|
| 1845 |
+
],
|
| 1846 |
+
[
|
| 1847 |
+
150,
|
| 1848 |
+
136,
|
| 1849 |
+
1
|
| 1850 |
+
],
|
| 1851 |
+
[
|
| 1852 |
+
132,
|
| 1853 |
+
132,
|
| 1854 |
+
2
|
| 1855 |
+
],
|
| 1856 |
+
[
|
| 1857 |
+
512,
|
| 1858 |
+
138,
|
| 1859 |
+
4
|
| 1860 |
+
],
|
| 1861 |
+
[
|
| 1862 |
+
512,
|
| 1863 |
+
147,
|
| 1864 |
+
4
|
| 1865 |
+
],
|
| 1866 |
+
[
|
| 1867 |
+
512,
|
| 1868 |
+
146,
|
| 1869 |
+
4
|
| 1870 |
+
],
|
| 1871 |
+
[
|
| 1872 |
+
103,
|
| 1873 |
+
151,
|
| 1874 |
+
2
|
| 1875 |
+
],
|
| 1876 |
+
[
|
| 1877 |
+
133,
|
| 1878 |
+
133,
|
| 1879 |
+
3
|
| 1880 |
+
],
|
| 1881 |
+
[
|
| 1882 |
+
517,
|
| 1883 |
+
514,
|
| 1884 |
+
1
|
| 1885 |
+
],
|
| 1886 |
+
[
|
| 1887 |
+
103,
|
| 1888 |
+
150,
|
| 1889 |
+
2
|
| 1890 |
+
],
|
| 1891 |
+
[
|
| 1892 |
+
136,
|
| 1893 |
+
136,
|
| 1894 |
+
3
|
| 1895 |
+
],
|
| 1896 |
+
[
|
| 1897 |
+
512,
|
| 1898 |
+
149,
|
| 1899 |
+
4
|
| 1900 |
+
],
|
| 1901 |
+
[
|
| 1902 |
+
513,
|
| 1903 |
+
513,
|
| 1904 |
+
3
|
| 1905 |
+
],
|
| 1906 |
+
[
|
| 1907 |
+
512,
|
| 1908 |
+
148,
|
| 1909 |
+
4
|
| 1910 |
+
],
|
| 1911 |
+
[
|
| 1912 |
+
520,
|
| 1913 |
+
104,
|
| 1914 |
+
4
|
| 1915 |
+
],
|
| 1916 |
+
[
|
| 1917 |
+
145,
|
| 1918 |
+
132,
|
| 1919 |
+
1
|
| 1920 |
+
],
|
| 1921 |
+
[
|
| 1922 |
+
151,
|
| 1923 |
+
138,
|
| 1924 |
+
1
|
| 1925 |
+
],
|
| 1926 |
+
[
|
| 1927 |
+
104,
|
| 1928 |
+
152,
|
| 1929 |
+
1
|
| 1930 |
+
],
|
| 1931 |
+
[
|
| 1932 |
+
144,
|
| 1933 |
+
133,
|
| 1934 |
+
1
|
| 1935 |
+
],
|
| 1936 |
+
[
|
| 1937 |
+
132,
|
| 1938 |
+
145,
|
| 1939 |
+
1
|
| 1940 |
+
],
|
| 1941 |
+
[
|
| 1942 |
+
512,
|
| 1943 |
+
150,
|
| 1944 |
+
4
|
| 1945 |
+
],
|
| 1946 |
+
[
|
| 1947 |
+
104,
|
| 1948 |
+
156,
|
| 1949 |
+
1
|
| 1950 |
+
],
|
| 1951 |
+
[
|
| 1952 |
+
512,
|
| 1953 |
+
151,
|
| 1954 |
+
4
|
| 1955 |
+
],
|
| 1956 |
+
[
|
| 1957 |
+
146,
|
| 1958 |
+
146,
|
| 1959 |
+
2
|
| 1960 |
+
],
|
| 1961 |
+
[
|
| 1962 |
+
104,
|
| 1963 |
+
158,
|
| 1964 |
+
1
|
| 1965 |
+
],
|
| 1966 |
+
[
|
| 1967 |
+
147,
|
| 1968 |
+
147,
|
| 1969 |
+
2
|
| 1970 |
+
],
|
| 1971 |
+
[
|
| 1972 |
+
163,
|
| 1973 |
+
152,
|
| 1974 |
+
1
|
| 1975 |
+
],
|
| 1976 |
+
[
|
| 1977 |
+
517,
|
| 1978 |
+
132,
|
| 1979 |
+
6
|
| 1980 |
+
],
|
| 1981 |
+
[
|
| 1982 |
+
149,
|
| 1983 |
+
149,
|
| 1984 |
+
4
|
| 1985 |
+
],
|
| 1986 |
+
[
|
| 1987 |
+
136,
|
| 1988 |
+
136,
|
| 1989 |
+
4
|
| 1990 |
+
],
|
| 1991 |
+
[
|
| 1992 |
+
517,
|
| 1993 |
+
133,
|
| 1994 |
+
6
|
| 1995 |
+
],
|
| 1996 |
+
[
|
| 1997 |
+
138,
|
| 1998 |
+
138,
|
| 1999 |
+
4
|
| 2000 |
+
],
|
| 2001 |
+
[
|
| 2002 |
+
144,
|
| 2003 |
+
133,
|
| 2004 |
+
2
|
| 2005 |
+
],
|
| 2006 |
+
[
|
| 2007 |
+
172,
|
| 2008 |
+
156,
|
| 2009 |
+
1
|
| 2010 |
+
],
|
| 2011 |
+
[
|
| 2012 |
+
148,
|
| 2013 |
+
148,
|
| 2014 |
+
4
|
| 2015 |
+
],
|
| 2016 |
+
[
|
| 2017 |
+
513,
|
| 2018 |
+
152,
|
| 2019 |
+
2
|
| 2020 |
+
],
|
| 2021 |
+
[
|
| 2022 |
+
104,
|
| 2023 |
+
165,
|
| 2024 |
+
1
|
| 2025 |
+
],
|
| 2026 |
+
[
|
| 2027 |
+
104,
|
| 2028 |
+
166,
|
| 2029 |
+
1
|
| 2030 |
+
],
|
| 2031 |
+
[
|
| 2032 |
+
519,
|
| 2033 |
+
144,
|
| 2034 |
+
2
|
| 2035 |
+
],
|
| 2036 |
+
[
|
| 2037 |
+
173,
|
| 2038 |
+
158,
|
| 2039 |
+
1
|
| 2040 |
+
],
|
| 2041 |
+
[
|
| 2042 |
+
517,
|
| 2043 |
+
147,
|
| 2044 |
+
6
|
| 2045 |
+
],
|
| 2046 |
+
[
|
| 2047 |
+
517,
|
| 2048 |
+
136,
|
| 2049 |
+
6
|
| 2050 |
+
],
|
| 2051 |
+
[
|
| 2052 |
+
517,
|
| 2053 |
+
138,
|
| 2054 |
+
6
|
| 2055 |
+
],
|
| 2056 |
+
[
|
| 2057 |
+
517,
|
| 2058 |
+
146,
|
| 2059 |
+
6
|
| 2060 |
+
],
|
| 2061 |
+
[
|
| 2062 |
+
513,
|
| 2063 |
+
156,
|
| 2064 |
+
2
|
| 2065 |
+
],
|
| 2066 |
+
[
|
| 2067 |
+
104,
|
| 2068 |
+
163,
|
| 2069 |
+
1
|
| 2070 |
+
],
|
| 2071 |
+
[
|
| 2072 |
+
104,
|
| 2073 |
+
168,
|
| 2074 |
+
1
|
| 2075 |
+
],
|
| 2076 |
+
[
|
| 2077 |
+
103,
|
| 2078 |
+
103,
|
| 2079 |
+
1
|
| 2080 |
+
],
|
| 2081 |
+
[
|
| 2082 |
+
519,
|
| 2083 |
+
132,
|
| 2084 |
+
2
|
| 2085 |
+
],
|
| 2086 |
+
[
|
| 2087 |
+
517,
|
| 2088 |
+
145,
|
| 2089 |
+
6
|
| 2090 |
+
],
|
| 2091 |
+
[
|
| 2092 |
+
177,
|
| 2093 |
+
177,
|
| 2094 |
+
2
|
| 2095 |
+
],
|
| 2096 |
+
[
|
| 2097 |
+
133,
|
| 2098 |
+
133,
|
| 2099 |
+
2
|
| 2100 |
+
],
|
| 2101 |
+
[
|
| 2102 |
+
104,
|
| 2103 |
+
170,
|
| 2104 |
+
1
|
| 2105 |
+
],
|
| 2106 |
+
[
|
| 2107 |
+
513,
|
| 2108 |
+
158,
|
| 2109 |
+
2
|
| 2110 |
+
],
|
| 2111 |
+
[
|
| 2112 |
+
517,
|
| 2113 |
+
144,
|
| 2114 |
+
6
|
| 2115 |
+
],
|
| 2116 |
+
[
|
| 2117 |
+
519,
|
| 2118 |
+
133,
|
| 2119 |
+
2
|
| 2120 |
+
],
|
| 2121 |
+
[
|
| 2122 |
+
179,
|
| 2123 |
+
179,
|
| 2124 |
+
2
|
| 2125 |
+
],
|
| 2126 |
+
[
|
| 2127 |
+
145,
|
| 2128 |
+
132,
|
| 2129 |
+
2
|
| 2130 |
+
],
|
| 2131 |
+
[
|
| 2132 |
+
149,
|
| 2133 |
+
149,
|
| 2134 |
+
5
|
| 2135 |
+
],
|
| 2136 |
+
[
|
| 2137 |
+
513,
|
| 2138 |
+
168,
|
| 2139 |
+
2
|
| 2140 |
+
],
|
| 2141 |
+
[
|
| 2142 |
+
520,
|
| 2143 |
+
152,
|
| 2144 |
+
3
|
| 2145 |
+
],
|
| 2146 |
+
[
|
| 2147 |
+
515,
|
| 2148 |
+
521,
|
| 2149 |
+
4
|
| 2150 |
+
],
|
| 2151 |
+
[
|
| 2152 |
+
132,
|
| 2153 |
+
132,
|
| 2154 |
+
3
|
| 2155 |
+
],
|
| 2156 |
+
[
|
| 2157 |
+
517,
|
| 2158 |
+
148,
|
| 2159 |
+
6
|
| 2160 |
+
],
|
| 2161 |
+
[
|
| 2162 |
+
519,
|
| 2163 |
+
146,
|
| 2164 |
+
2
|
| 2165 |
+
],
|
| 2166 |
+
[
|
| 2167 |
+
519,
|
| 2168 |
+
147,
|
| 2169 |
+
2
|
| 2170 |
+
],
|
| 2171 |
+
[
|
| 2172 |
+
138,
|
| 2173 |
+
151,
|
| 2174 |
+
1
|
| 2175 |
+
],
|
| 2176 |
+
[
|
| 2177 |
+
136,
|
| 2178 |
+
150,
|
| 2179 |
+
1
|
| 2180 |
+
],
|
| 2181 |
+
[
|
| 2182 |
+
513,
|
| 2183 |
+
163,
|
| 2184 |
+
2
|
| 2185 |
+
],
|
| 2186 |
+
[
|
| 2187 |
+
520,
|
| 2188 |
+
156,
|
| 2189 |
+
3
|
| 2190 |
+
],
|
| 2191 |
+
[
|
| 2192 |
+
519,
|
| 2193 |
+
145,
|
| 2194 |
+
2
|
| 2195 |
+
],
|
| 2196 |
+
[
|
| 2197 |
+
517,
|
| 2198 |
+
149,
|
| 2199 |
+
6
|
| 2200 |
+
],
|
| 2201 |
+
[
|
| 2202 |
+
176,
|
| 2203 |
+
176,
|
| 2204 |
+
2
|
| 2205 |
+
],
|
| 2206 |
+
[
|
| 2207 |
+
513,
|
| 2208 |
+
170,
|
| 2209 |
+
2
|
| 2210 |
+
],
|
| 2211 |
+
[
|
| 2212 |
+
520,
|
| 2213 |
+
158,
|
| 2214 |
+
3
|
| 2215 |
+
],
|
| 2216 |
+
[
|
| 2217 |
+
133,
|
| 2218 |
+
144,
|
| 2219 |
+
1
|
| 2220 |
+
],
|
| 2221 |
+
[
|
| 2222 |
+
148,
|
| 2223 |
+
148,
|
| 2224 |
+
5
|
| 2225 |
+
],
|
| 2226 |
+
[
|
| 2227 |
+
152,
|
| 2228 |
+
163,
|
| 2229 |
+
1
|
| 2230 |
+
],
|
| 2231 |
+
[
|
| 2232 |
+
513,
|
| 2233 |
+
165,
|
| 2234 |
+
2
|
| 2235 |
+
],
|
| 2236 |
+
[
|
| 2237 |
+
180,
|
| 2238 |
+
168,
|
| 2239 |
+
1
|
| 2240 |
+
],
|
| 2241 |
+
[
|
| 2242 |
+
133,
|
| 2243 |
+
144,
|
| 2244 |
+
2
|
| 2245 |
+
],
|
| 2246 |
+
[
|
| 2247 |
+
513,
|
| 2248 |
+
166,
|
| 2249 |
+
2
|
| 2250 |
+
],
|
| 2251 |
+
[
|
| 2252 |
+
156,
|
| 2253 |
+
156,
|
| 2254 |
+
3
|
| 2255 |
+
],
|
| 2256 |
+
[
|
| 2257 |
+
520,
|
| 2258 |
+
168,
|
| 2259 |
+
3
|
| 2260 |
+
],
|
| 2261 |
+
[
|
| 2262 |
+
136,
|
| 2263 |
+
136,
|
| 2264 |
+
2
|
| 2265 |
+
],
|
| 2266 |
+
[
|
| 2267 |
+
520,
|
| 2268 |
+
163,
|
| 2269 |
+
3
|
| 2270 |
+
],
|
| 2271 |
+
[
|
| 2272 |
+
158,
|
| 2273 |
+
158,
|
| 2274 |
+
3
|
| 2275 |
+
],
|
| 2276 |
+
[
|
| 2277 |
+
138,
|
| 2278 |
+
138,
|
| 2279 |
+
2
|
| 2280 |
+
],
|
| 2281 |
+
[
|
| 2282 |
+
132,
|
| 2283 |
+
145,
|
| 2284 |
+
2
|
| 2285 |
+
],
|
| 2286 |
+
[
|
| 2287 |
+
157,
|
| 2288 |
+
157,
|
| 2289 |
+
2
|
| 2290 |
+
],
|
| 2291 |
+
[
|
| 2292 |
+
519,
|
| 2293 |
+
136,
|
| 2294 |
+
2
|
| 2295 |
+
],
|
| 2296 |
+
[
|
| 2297 |
+
519,
|
| 2298 |
+
138,
|
| 2299 |
+
2
|
| 2300 |
+
],
|
| 2301 |
+
[
|
| 2302 |
+
150,
|
| 2303 |
+
136,
|
| 2304 |
+
2
|
| 2305 |
+
],
|
| 2306 |
+
[
|
| 2307 |
+
166,
|
| 2308 |
+
166,
|
| 2309 |
+
2
|
| 2310 |
+
],
|
| 2311 |
+
[
|
| 2312 |
+
519,
|
| 2313 |
+
148,
|
| 2314 |
+
2
|
| 2315 |
+
],
|
| 2316 |
+
[
|
| 2317 |
+
520,
|
| 2318 |
+
170,
|
| 2319 |
+
3
|
| 2320 |
+
],
|
| 2321 |
+
[
|
| 2322 |
+
145,
|
| 2323 |
+
145,
|
| 2324 |
+
2
|
| 2325 |
+
],
|
| 2326 |
+
[
|
| 2327 |
+
165,
|
| 2328 |
+
165,
|
| 2329 |
+
2
|
| 2330 |
+
],
|
| 2331 |
+
[
|
| 2332 |
+
104,
|
| 2333 |
+
172,
|
| 2334 |
+
1
|
| 2335 |
+
],
|
| 2336 |
+
[
|
| 2337 |
+
146,
|
| 2338 |
+
146,
|
| 2339 |
+
3
|
| 2340 |
+
],
|
| 2341 |
+
[
|
| 2342 |
+
514,
|
| 2343 |
+
144,
|
| 2344 |
+
1
|
| 2345 |
+
],
|
| 2346 |
+
[
|
| 2347 |
+
164,
|
| 2348 |
+
164,
|
| 2349 |
+
2
|
| 2350 |
+
],
|
| 2351 |
+
[
|
| 2352 |
+
147,
|
| 2353 |
+
147,
|
| 2354 |
+
3
|
| 2355 |
+
],
|
| 2356 |
+
[
|
| 2357 |
+
151,
|
| 2358 |
+
138,
|
| 2359 |
+
2
|
| 2360 |
+
],
|
| 2361 |
+
[
|
| 2362 |
+
104,
|
| 2363 |
+
173,
|
| 2364 |
+
1
|
| 2365 |
+
],
|
| 2366 |
+
[
|
| 2367 |
+
132,
|
| 2368 |
+
103,
|
| 2369 |
+
2
|
| 2370 |
+
],
|
| 2371 |
+
[
|
| 2372 |
+
104,
|
| 2373 |
+
181,
|
| 2374 |
+
1
|
| 2375 |
+
],
|
| 2376 |
+
[
|
| 2377 |
+
519,
|
| 2378 |
+
149,
|
| 2379 |
+
2
|
| 2380 |
+
],
|
| 2381 |
+
[
|
| 2382 |
+
519,
|
| 2383 |
+
151,
|
| 2384 |
+
2
|
| 2385 |
+
],
|
| 2386 |
+
[
|
| 2387 |
+
517,
|
| 2388 |
+
150,
|
| 2389 |
+
6
|
| 2390 |
+
],
|
| 2391 |
+
[
|
| 2392 |
+
519,
|
| 2393 |
+
150,
|
| 2394 |
+
2
|
| 2395 |
+
],
|
| 2396 |
+
[
|
| 2397 |
+
521,
|
| 2398 |
+
152,
|
| 2399 |
+
1
|
| 2400 |
+
],
|
| 2401 |
+
[
|
| 2402 |
+
517,
|
| 2403 |
+
151,
|
| 2404 |
+
6
|
| 2405 |
+
],
|
| 2406 |
+
[
|
| 2407 |
+
144,
|
| 2408 |
+
103,
|
| 2409 |
+
2
|
| 2410 |
+
],
|
| 2411 |
+
[
|
| 2412 |
+
520,
|
| 2413 |
+
165,
|
| 2414 |
+
3
|
| 2415 |
+
],
|
| 2416 |
+
[
|
| 2417 |
+
133,
|
| 2418 |
+
103,
|
| 2419 |
+
2
|
| 2420 |
+
],
|
| 2421 |
+
[
|
| 2422 |
+
146,
|
| 2423 |
+
103,
|
| 2424 |
+
2
|
| 2425 |
+
],
|
| 2426 |
+
[
|
| 2427 |
+
136,
|
| 2428 |
+
103,
|
| 2429 |
+
2
|
| 2430 |
+
],
|
| 2431 |
+
[
|
| 2432 |
+
162,
|
| 2433 |
+
162,
|
| 2434 |
+
2
|
| 2435 |
+
],
|
| 2436 |
+
[
|
| 2437 |
+
147,
|
| 2438 |
+
103,
|
| 2439 |
+
2
|
| 2440 |
+
],
|
| 2441 |
+
[
|
| 2442 |
+
520,
|
| 2443 |
+
166,
|
| 2444 |
+
3
|
| 2445 |
+
],
|
| 2446 |
+
[
|
| 2447 |
+
138,
|
| 2448 |
+
103,
|
| 2449 |
+
2
|
| 2450 |
+
],
|
| 2451 |
+
[
|
| 2452 |
+
515,
|
| 2453 |
+
156,
|
| 2454 |
+
4
|
| 2455 |
+
],
|
| 2456 |
+
[
|
| 2457 |
+
149,
|
| 2458 |
+
103,
|
| 2459 |
+
2
|
| 2460 |
+
],
|
| 2461 |
+
[
|
| 2462 |
+
145,
|
| 2463 |
+
103,
|
| 2464 |
+
2
|
| 2465 |
+
],
|
| 2466 |
+
[
|
| 2467 |
+
148,
|
| 2468 |
+
103,
|
| 2469 |
+
2
|
| 2470 |
+
],
|
| 2471 |
+
[
|
| 2472 |
+
514,
|
| 2473 |
+
144,
|
| 2474 |
+
3
|
| 2475 |
+
],
|
| 2476 |
+
[
|
| 2477 |
+
104,
|
| 2478 |
+
180,
|
| 2479 |
+
1
|
| 2480 |
+
],
|
| 2481 |
+
[
|
| 2482 |
+
515,
|
| 2483 |
+
152,
|
| 2484 |
+
4
|
| 2485 |
+
],
|
| 2486 |
+
[
|
| 2487 |
+
117,
|
| 2488 |
+
117,
|
| 2489 |
+
2
|
| 2490 |
+
],
|
| 2491 |
+
[
|
| 2492 |
+
521,
|
| 2493 |
+
152,
|
| 2494 |
+
3
|
| 2495 |
+
],
|
| 2496 |
+
[
|
| 2497 |
+
159,
|
| 2498 |
+
159,
|
| 2499 |
+
2
|
| 2500 |
+
],
|
| 2501 |
+
[
|
| 2502 |
+
136,
|
| 2503 |
+
150,
|
| 2504 |
+
2
|
| 2505 |
+
],
|
| 2506 |
+
[
|
| 2507 |
+
513,
|
| 2508 |
+
172,
|
| 2509 |
+
2
|
| 2510 |
+
],
|
| 2511 |
+
[
|
| 2512 |
+
515,
|
| 2513 |
+
158,
|
| 2514 |
+
4
|
| 2515 |
+
],
|
| 2516 |
+
[
|
| 2517 |
+
114,
|
| 2518 |
+
114,
|
| 2519 |
+
2
|
| 2520 |
+
],
|
| 2521 |
+
[
|
| 2522 |
+
113,
|
| 2523 |
+
113,
|
| 2524 |
+
2
|
| 2525 |
+
],
|
| 2526 |
+
[
|
| 2527 |
+
514,
|
| 2528 |
+
133,
|
| 2529 |
+
1
|
| 2530 |
+
],
|
| 2531 |
+
[
|
| 2532 |
+
115,
|
| 2533 |
+
115,
|
| 2534 |
+
2
|
| 2535 |
+
],
|
| 2536 |
+
[
|
| 2537 |
+
513,
|
| 2538 |
+
173,
|
| 2539 |
+
2
|
| 2540 |
+
],
|
| 2541 |
+
[
|
| 2542 |
+
515,
|
| 2543 |
+
163,
|
| 2544 |
+
4
|
| 2545 |
+
],
|
| 2546 |
+
[
|
| 2547 |
+
138,
|
| 2548 |
+
151,
|
| 2549 |
+
2
|
| 2550 |
+
],
|
| 2551 |
+
[
|
| 2552 |
+
514,
|
| 2553 |
+
512,
|
| 2554 |
+
5
|
| 2555 |
+
],
|
| 2556 |
+
[
|
| 2557 |
+
155,
|
| 2558 |
+
155,
|
| 2559 |
+
2
|
| 2560 |
+
],
|
| 2561 |
+
[
|
| 2562 |
+
513,
|
| 2563 |
+
181,
|
| 2564 |
+
2
|
| 2565 |
+
],
|
| 2566 |
+
[
|
| 2567 |
+
156,
|
| 2568 |
+
172,
|
| 2569 |
+
1
|
| 2570 |
+
],
|
| 2571 |
+
[
|
| 2572 |
+
144,
|
| 2573 |
+
144,
|
| 2574 |
+
3
|
| 2575 |
+
],
|
| 2576 |
+
[
|
| 2577 |
+
151,
|
| 2578 |
+
151,
|
| 2579 |
+
3
|
| 2580 |
+
],
|
| 2581 |
+
[
|
| 2582 |
+
177,
|
| 2583 |
+
177,
|
| 2584 |
+
3
|
| 2585 |
+
],
|
| 2586 |
+
[
|
| 2587 |
+
158,
|
| 2588 |
+
158,
|
| 2589 |
+
4
|
| 2590 |
+
],
|
| 2591 |
+
[
|
| 2592 |
+
152,
|
| 2593 |
+
152,
|
| 2594 |
+
3
|
| 2595 |
+
],
|
| 2596 |
+
[
|
| 2597 |
+
132,
|
| 2598 |
+
132,
|
| 2599 |
+
4
|
| 2600 |
+
],
|
| 2601 |
+
[
|
| 2602 |
+
515,
|
| 2603 |
+
170,
|
| 2604 |
+
4
|
| 2605 |
+
],
|
| 2606 |
+
[
|
| 2607 |
+
179,
|
| 2608 |
+
179,
|
| 2609 |
+
3
|
| 2610 |
+
],
|
| 2611 |
+
[
|
| 2612 |
+
251,
|
| 2613 |
+
251,
|
| 2614 |
+
2
|
| 2615 |
+
],
|
| 2616 |
+
[
|
| 2617 |
+
520,
|
| 2618 |
+
172,
|
| 2619 |
+
3
|
| 2620 |
+
],
|
| 2621 |
+
[
|
| 2622 |
+
521,
|
| 2623 |
+
156,
|
| 2624 |
+
3
|
| 2625 |
+
],
|
| 2626 |
+
[
|
| 2627 |
+
515,
|
| 2628 |
+
526,
|
| 2629 |
+
4
|
| 2630 |
+
],
|
| 2631 |
+
[
|
| 2632 |
+
514,
|
| 2633 |
+
144,
|
| 2634 |
+
5
|
| 2635 |
+
],
|
| 2636 |
+
[
|
| 2637 |
+
513,
|
| 2638 |
+
180,
|
| 2639 |
+
2
|
| 2640 |
+
],
|
| 2641 |
+
[
|
| 2642 |
+
515,
|
| 2643 |
+
168,
|
| 2644 |
+
4
|
| 2645 |
+
],
|
| 2646 |
+
[
|
| 2647 |
+
520,
|
| 2648 |
+
173,
|
| 2649 |
+
3
|
| 2650 |
+
],
|
| 2651 |
+
[
|
| 2652 |
+
149,
|
| 2653 |
+
149,
|
| 2654 |
+
3
|
| 2655 |
+
],
|
| 2656 |
+
[
|
| 2657 |
+
514,
|
| 2658 |
+
133,
|
| 2659 |
+
3
|
| 2660 |
+
],
|
| 2661 |
+
[
|
| 2662 |
+
150,
|
| 2663 |
+
103,
|
| 2664 |
+
2
|
| 2665 |
+
],
|
| 2666 |
+
[
|
| 2667 |
+
168,
|
| 2668 |
+
168,
|
| 2669 |
+
3
|
| 2670 |
+
],
|
| 2671 |
+
[
|
| 2672 |
+
153,
|
| 2673 |
+
153,
|
| 2674 |
+
2
|
| 2675 |
+
],
|
| 2676 |
+
[
|
| 2677 |
+
520,
|
| 2678 |
+
181,
|
| 2679 |
+
3
|
| 2680 |
+
],
|
| 2681 |
+
[
|
| 2682 |
+
247,
|
| 2683 |
+
247,
|
| 2684 |
+
2
|
| 2685 |
+
],
|
| 2686 |
+
[
|
| 2687 |
+
148,
|
| 2688 |
+
148,
|
| 2689 |
+
3
|
| 2690 |
+
],
|
| 2691 |
+
[
|
| 2692 |
+
520,
|
| 2693 |
+
180,
|
| 2694 |
+
3
|
| 2695 |
+
],
|
| 2696 |
+
[
|
| 2697 |
+
521,
|
| 2698 |
+
170,
|
| 2699 |
+
3
|
| 2700 |
+
],
|
| 2701 |
+
[
|
| 2702 |
+
176,
|
| 2703 |
+
176,
|
| 2704 |
+
3
|
| 2705 |
+
],
|
| 2706 |
+
[
|
| 2707 |
+
158,
|
| 2708 |
+
173,
|
| 2709 |
+
1
|
| 2710 |
+
],
|
| 2711 |
+
[
|
| 2712 |
+
515,
|
| 2713 |
+
165,
|
| 2714 |
+
4
|
| 2715 |
+
],
|
| 2716 |
+
[
|
| 2717 |
+
146,
|
| 2718 |
+
146,
|
| 2719 |
+
4
|
| 2720 |
+
],
|
| 2721 |
+
[
|
| 2722 |
+
521,
|
| 2723 |
+
168,
|
| 2724 |
+
3
|
| 2725 |
+
],
|
| 2726 |
+
[
|
| 2727 |
+
133,
|
| 2728 |
+
133,
|
| 2729 |
+
4
|
| 2730 |
+
],
|
| 2731 |
+
[
|
| 2732 |
+
166,
|
| 2733 |
+
166,
|
| 2734 |
+
3
|
| 2735 |
+
],
|
| 2736 |
+
[
|
| 2737 |
+
154,
|
| 2738 |
+
154,
|
| 2739 |
+
2
|
| 2740 |
+
],
|
| 2741 |
+
[
|
| 2742 |
+
151,
|
| 2743 |
+
103,
|
| 2744 |
+
2
|
| 2745 |
+
],
|
| 2746 |
+
[
|
| 2747 |
+
165,
|
| 2748 |
+
165,
|
| 2749 |
+
3
|
| 2750 |
+
],
|
| 2751 |
+
[
|
| 2752 |
+
514,
|
| 2753 |
+
132,
|
| 2754 |
+
1
|
| 2755 |
+
],
|
| 2756 |
+
[
|
| 2757 |
+
515,
|
| 2758 |
+
166,
|
| 2759 |
+
4
|
| 2760 |
+
],
|
| 2761 |
+
[
|
| 2762 |
+
147,
|
| 2763 |
+
147,
|
| 2764 |
+
4
|
| 2765 |
+
],
|
| 2766 |
+
[
|
| 2767 |
+
521,
|
| 2768 |
+
163,
|
| 2769 |
+
3
|
| 2770 |
+
],
|
| 2771 |
+
[
|
| 2772 |
+
145,
|
| 2773 |
+
145,
|
| 2774 |
+
3
|
| 2775 |
+
],
|
| 2776 |
+
[
|
| 2777 |
+
150,
|
| 2778 |
+
150,
|
| 2779 |
+
3
|
| 2780 |
+
],
|
| 2781 |
+
[
|
| 2782 |
+
514,
|
| 2783 |
+
133,
|
| 2784 |
+
5
|
| 2785 |
+
],
|
| 2786 |
+
[
|
| 2787 |
+
512,
|
| 2788 |
+
512,
|
| 2789 |
+
1
|
| 2790 |
+
],
|
| 2791 |
+
[
|
| 2792 |
+
189,
|
| 2793 |
+
189,
|
| 2794 |
+
2
|
| 2795 |
+
],
|
| 2796 |
+
[
|
| 2797 |
+
512,
|
| 2798 |
+
103,
|
| 2799 |
+
1
|
| 2800 |
+
],
|
| 2801 |
+
[
|
| 2802 |
+
152,
|
| 2803 |
+
163,
|
| 2804 |
+
2
|
| 2805 |
+
],
|
| 2806 |
+
[
|
| 2807 |
+
521,
|
| 2808 |
+
158,
|
| 2809 |
+
3
|
| 2810 |
+
],
|
| 2811 |
+
[
|
| 2812 |
+
156,
|
| 2813 |
+
156,
|
| 2814 |
+
4
|
| 2815 |
+
],
|
| 2816 |
+
[
|
| 2817 |
+
105,
|
| 2818 |
+
105,
|
| 2819 |
+
2
|
| 2820 |
+
],
|
| 2821 |
+
[
|
| 2822 |
+
167,
|
| 2823 |
+
167,
|
| 2824 |
+
2
|
| 2825 |
+
],
|
| 2826 |
+
[
|
| 2827 |
+
234,
|
| 2828 |
+
234,
|
| 2829 |
+
2
|
| 2830 |
+
],
|
| 2831 |
+
[
|
| 2832 |
+
514,
|
| 2833 |
+
512,
|
| 2834 |
+
3
|
| 2835 |
+
],
|
| 2836 |
+
[
|
| 2837 |
+
522,
|
| 2838 |
+
132,
|
| 2839 |
+
2
|
| 2840 |
+
],
|
| 2841 |
+
[
|
| 2842 |
+
144,
|
| 2843 |
+
144,
|
| 2844 |
+
4
|
| 2845 |
+
],
|
| 2846 |
+
[
|
| 2847 |
+
216,
|
| 2848 |
+
216,
|
| 2849 |
+
2
|
| 2850 |
+
],
|
| 2851 |
+
[
|
| 2852 |
+
515,
|
| 2853 |
+
172,
|
| 2854 |
+
4
|
| 2855 |
+
],
|
| 2856 |
+
[
|
| 2857 |
+
517,
|
| 2858 |
+
517,
|
| 2859 |
+
1
|
| 2860 |
+
],
|
| 2861 |
+
[
|
| 2862 |
+
522,
|
| 2863 |
+
136,
|
| 2864 |
+
2
|
| 2865 |
+
],
|
| 2866 |
+
[
|
| 2867 |
+
170,
|
| 2868 |
+
170,
|
| 2869 |
+
5
|
| 2870 |
+
],
|
| 2871 |
+
[
|
| 2872 |
+
110,
|
| 2873 |
+
110,
|
| 2874 |
+
2
|
| 2875 |
+
],
|
| 2876 |
+
[
|
| 2877 |
+
515,
|
| 2878 |
+
173,
|
| 2879 |
+
4
|
| 2880 |
+
],
|
| 2881 |
+
[
|
| 2882 |
+
522,
|
| 2883 |
+
147,
|
| 2884 |
+
2
|
| 2885 |
+
],
|
| 2886 |
+
[
|
| 2887 |
+
103,
|
| 2888 |
+
176,
|
| 2889 |
+
2
|
| 2890 |
+
],
|
| 2891 |
+
[
|
| 2892 |
+
522,
|
| 2893 |
+
146,
|
| 2894 |
+
2
|
| 2895 |
+
],
|
| 2896 |
+
[
|
| 2897 |
+
522,
|
| 2898 |
+
133,
|
| 2899 |
+
2
|
| 2900 |
+
],
|
| 2901 |
+
[
|
| 2902 |
+
522,
|
| 2903 |
+
138,
|
| 2904 |
+
2
|
| 2905 |
+
],
|
| 2906 |
+
[
|
| 2907 |
+
514,
|
| 2908 |
+
145,
|
| 2909 |
+
5
|
| 2910 |
+
],
|
| 2911 |
+
[
|
| 2912 |
+
515,
|
| 2913 |
+
180,
|
| 2914 |
+
4
|
| 2915 |
+
],
|
| 2916 |
+
[
|
| 2917 |
+
239,
|
| 2918 |
+
239,
|
| 2919 |
+
2
|
| 2920 |
+
],
|
| 2921 |
+
[
|
| 2922 |
+
521,
|
| 2923 |
+
165,
|
| 2924 |
+
3
|
| 2925 |
+
],
|
| 2926 |
+
[
|
| 2927 |
+
514,
|
| 2928 |
+
148,
|
| 2929 |
+
1
|
| 2930 |
+
],
|
| 2931 |
+
[
|
| 2932 |
+
521,
|
| 2933 |
+
166,
|
| 2934 |
+
3
|
| 2935 |
+
],
|
| 2936 |
+
[
|
| 2937 |
+
163,
|
| 2938 |
+
152,
|
| 2939 |
+
2
|
| 2940 |
+
],
|
| 2941 |
+
[
|
| 2942 |
+
136,
|
| 2943 |
+
136,
|
| 2944 |
+
5
|
| 2945 |
+
],
|
| 2946 |
+
[
|
| 2947 |
+
168,
|
| 2948 |
+
180,
|
| 2949 |
+
1
|
| 2950 |
+
],
|
| 2951 |
+
[
|
| 2952 |
+
522,
|
| 2953 |
+
144,
|
| 2954 |
+
2
|
| 2955 |
+
],
|
| 2956 |
+
[
|
| 2957 |
+
517,
|
| 2958 |
+
512,
|
| 2959 |
+
1
|
| 2960 |
+
],
|
| 2961 |
+
[
|
| 2962 |
+
515,
|
| 2963 |
+
181,
|
| 2964 |
+
4
|
| 2965 |
+
],
|
| 2966 |
+
[
|
| 2967 |
+
132,
|
| 2968 |
+
132,
|
| 2969 |
+
5
|
| 2970 |
+
],
|
| 2971 |
+
[
|
| 2972 |
+
514,
|
| 2973 |
+
149,
|
| 2974 |
+
1
|
| 2975 |
+
],
|
| 2976 |
+
[
|
| 2977 |
+
151,
|
| 2978 |
+
151,
|
| 2979 |
+
4
|
| 2980 |
+
],
|
| 2981 |
+
[
|
| 2982 |
+
514,
|
| 2983 |
+
146,
|
| 2984 |
+
1
|
| 2985 |
+
],
|
| 2986 |
+
[
|
| 2987 |
+
514,
|
| 2988 |
+
147,
|
| 2989 |
+
1
|
| 2990 |
+
],
|
| 2991 |
+
[
|
| 2992 |
+
514,
|
| 2993 |
+
136,
|
| 2994 |
+
1
|
| 2995 |
+
],
|
| 2996 |
+
[
|
| 2997 |
+
156,
|
| 2998 |
+
172,
|
| 2999 |
+
2
|
| 3000 |
+
],
|
| 3001 |
+
[
|
| 3002 |
+
237,
|
| 3003 |
+
237,
|
| 3004 |
+
2
|
| 3005 |
+
],
|
| 3006 |
+
[
|
| 3007 |
+
138,
|
| 3008 |
+
138,
|
| 3009 |
+
5
|
| 3010 |
+
],
|
| 3011 |
+
[
|
| 3012 |
+
149,
|
| 3013 |
+
149,
|
| 3014 |
+
2
|
| 3015 |
+
],
|
| 3016 |
+
[
|
| 3017 |
+
514,
|
| 3018 |
+
138,
|
| 3019 |
+
1
|
| 3020 |
+
],
|
| 3021 |
+
[
|
| 3022 |
+
152,
|
| 3023 |
+
152,
|
| 3024 |
+
2
|
| 3025 |
+
],
|
| 3026 |
+
[
|
| 3027 |
+
158,
|
| 3028 |
+
165,
|
| 3029 |
+
1
|
| 3030 |
+
],
|
| 3031 |
+
[
|
| 3032 |
+
512,
|
| 3033 |
+
176,
|
| 3034 |
+
4
|
| 3035 |
+
],
|
| 3036 |
+
[
|
| 3037 |
+
514,
|
| 3038 |
+
132,
|
| 3039 |
+
5
|
| 3040 |
+
],
|
| 3041 |
+
[
|
| 3042 |
+
148,
|
| 3043 |
+
148,
|
| 3044 |
+
6
|
| 3045 |
+
],
|
| 3046 |
+
[
|
| 3047 |
+
104,
|
| 3048 |
+
104,
|
| 3049 |
+
4
|
| 3050 |
+
],
|
| 3051 |
+
[
|
| 3052 |
+
156,
|
| 3053 |
+
166,
|
| 3054 |
+
1
|
| 3055 |
+
],
|
| 3056 |
+
[
|
| 3057 |
+
514,
|
| 3058 |
+
149,
|
| 3059 |
+
5
|
| 3060 |
+
],
|
| 3061 |
+
[
|
| 3062 |
+
103,
|
| 3063 |
+
189,
|
| 3064 |
+
2
|
| 3065 |
+
],
|
| 3066 |
+
[
|
| 3067 |
+
150,
|
| 3068 |
+
150,
|
| 3069 |
+
4
|
| 3070 |
+
],
|
| 3071 |
+
[
|
| 3072 |
+
146,
|
| 3073 |
+
146,
|
| 3074 |
+
5
|
| 3075 |
+
],
|
| 3076 |
+
[
|
| 3077 |
+
525,
|
| 3078 |
+
152,
|
| 3079 |
+
5
|
| 3080 |
+
],
|
| 3081 |
+
[
|
| 3082 |
+
165,
|
| 3083 |
+
158,
|
| 3084 |
+
1
|
| 3085 |
+
],
|
| 3086 |
+
[
|
| 3087 |
+
177,
|
| 3088 |
+
177,
|
| 3089 |
+
4
|
| 3090 |
+
],
|
| 3091 |
+
[
|
| 3092 |
+
170,
|
| 3093 |
+
170,
|
| 3094 |
+
6
|
| 3095 |
+
],
|
| 3096 |
+
[
|
| 3097 |
+
147,
|
| 3098 |
+
147,
|
| 3099 |
+
5
|
| 3100 |
+
],
|
| 3101 |
+
[
|
| 3102 |
+
514,
|
| 3103 |
+
148,
|
| 3104 |
+
5
|
| 3105 |
+
],
|
| 3106 |
+
[
|
| 3107 |
+
522,
|
| 3108 |
+
145,
|
| 3109 |
+
2
|
| 3110 |
+
],
|
| 3111 |
+
[
|
| 3112 |
+
179,
|
| 3113 |
+
136,
|
| 3114 |
+
2
|
| 3115 |
+
],
|
| 3116 |
+
[
|
| 3117 |
+
525,
|
| 3118 |
+
156,
|
| 3119 |
+
5
|
| 3120 |
+
],
|
| 3121 |
+
[
|
| 3122 |
+
521,
|
| 3123 |
+
173,
|
| 3124 |
+
3
|
| 3125 |
+
],
|
| 3126 |
+
[
|
| 3127 |
+
166,
|
| 3128 |
+
156,
|
| 3129 |
+
1
|
| 3130 |
+
],
|
| 3131 |
+
[
|
| 3132 |
+
133,
|
| 3133 |
+
133,
|
| 3134 |
+
5
|
| 3135 |
+
],
|
| 3136 |
+
[
|
| 3137 |
+
221,
|
| 3138 |
+
221,
|
| 3139 |
+
2
|
| 3140 |
+
],
|
| 3141 |
+
[
|
| 3142 |
+
158,
|
| 3143 |
+
168,
|
| 3144 |
+
1
|
| 3145 |
+
],
|
| 3146 |
+
[
|
| 3147 |
+
148,
|
| 3148 |
+
148,
|
| 3149 |
+
2
|
| 3150 |
+
],
|
| 3151 |
+
[
|
| 3152 |
+
232,
|
| 3153 |
+
232,
|
| 3154 |
+
2
|
| 3155 |
+
],
|
| 3156 |
+
[
|
| 3157 |
+
514,
|
| 3158 |
+
138,
|
| 3159 |
+
5
|
| 3160 |
+
],
|
| 3161 |
+
[
|
| 3162 |
+
145,
|
| 3163 |
+
145,
|
| 3164 |
+
4
|
| 3165 |
+
],
|
| 3166 |
+
[
|
| 3167 |
+
117,
|
| 3168 |
+
117,
|
| 3169 |
+
3
|
| 3170 |
+
],
|
| 3171 |
+
[
|
| 3172 |
+
514,
|
| 3173 |
+
136,
|
| 3174 |
+
5
|
| 3175 |
+
],
|
| 3176 |
+
[
|
| 3177 |
+
525,
|
| 3178 |
+
163,
|
| 3179 |
+
5
|
| 3180 |
+
],
|
| 3181 |
+
[
|
| 3182 |
+
222,
|
| 3183 |
+
222,
|
| 3184 |
+
2
|
| 3185 |
+
],
|
| 3186 |
+
[
|
| 3187 |
+
521,
|
| 3188 |
+
181,
|
| 3189 |
+
3
|
| 3190 |
+
],
|
| 3191 |
+
[
|
| 3192 |
+
514,
|
| 3193 |
+
512,
|
| 3194 |
+
1
|
| 3195 |
+
],
|
| 3196 |
+
[
|
| 3197 |
+
152,
|
| 3198 |
+
104,
|
| 3199 |
+
1
|
| 3200 |
+
],
|
| 3201 |
+
[
|
| 3202 |
+
166,
|
| 3203 |
+
168,
|
| 3204 |
+
1
|
| 3205 |
+
],
|
| 3206 |
+
[
|
| 3207 |
+
165,
|
| 3208 |
+
168,
|
| 3209 |
+
1
|
| 3210 |
+
],
|
| 3211 |
+
[
|
| 3212 |
+
514,
|
| 3213 |
+
147,
|
| 3214 |
+
5
|
| 3215 |
+
],
|
| 3216 |
+
[
|
| 3217 |
+
104,
|
| 3218 |
+
152,
|
| 3219 |
+
2
|
| 3220 |
+
],
|
| 3221 |
+
[
|
| 3222 |
+
514,
|
| 3223 |
+
146,
|
| 3224 |
+
5
|
| 3225 |
+
],
|
| 3226 |
+
[
|
| 3227 |
+
149,
|
| 3228 |
+
149,
|
| 3229 |
+
6
|
| 3230 |
+
],
|
| 3231 |
+
[
|
| 3232 |
+
514,
|
| 3233 |
+
151,
|
| 3234 |
+
1
|
| 3235 |
+
],
|
| 3236 |
+
[
|
| 3237 |
+
521,
|
| 3238 |
+
180,
|
| 3239 |
+
3
|
| 3240 |
+
],
|
| 3241 |
+
[
|
| 3242 |
+
144,
|
| 3243 |
+
144,
|
| 3244 |
+
2
|
| 3245 |
+
],
|
| 3246 |
+
[
|
| 3247 |
+
525,
|
| 3248 |
+
158,
|
| 3249 |
+
5
|
| 3250 |
+
],
|
| 3251 |
+
[
|
| 3252 |
+
172,
|
| 3253 |
+
156,
|
| 3254 |
+
2
|
| 3255 |
+
],
|
| 3256 |
+
[
|
| 3257 |
+
514,
|
| 3258 |
+
145,
|
| 3259 |
+
1
|
| 3260 |
+
],
|
| 3261 |
+
[
|
| 3262 |
+
522,
|
| 3263 |
+
148,
|
| 3264 |
+
2
|
| 3265 |
+
],
|
| 3266 |
+
[
|
| 3267 |
+
514,
|
| 3268 |
+
150,
|
| 3269 |
+
1
|
| 3270 |
+
],
|
| 3271 |
+
[
|
| 3272 |
+
113,
|
| 3273 |
+
113,
|
| 3274 |
+
3
|
| 3275 |
+
],
|
| 3276 |
+
[
|
| 3277 |
+
103,
|
| 3278 |
+
179,
|
| 3279 |
+
2
|
| 3280 |
+
],
|
| 3281 |
+
[
|
| 3282 |
+
163,
|
| 3283 |
+
104,
|
| 3284 |
+
1
|
| 3285 |
+
],
|
| 3286 |
+
[
|
| 3287 |
+
114,
|
| 3288 |
+
114,
|
| 3289 |
+
3
|
| 3290 |
+
],
|
| 3291 |
+
[
|
| 3292 |
+
115,
|
| 3293 |
+
115,
|
| 3294 |
+
3
|
| 3295 |
+
],
|
| 3296 |
+
[
|
| 3297 |
+
157,
|
| 3298 |
+
157,
|
| 3299 |
+
3
|
| 3300 |
+
],
|
| 3301 |
+
[
|
| 3302 |
+
165,
|
| 3303 |
+
152,
|
| 3304 |
+
1
|
| 3305 |
+
],
|
| 3306 |
+
[
|
| 3307 |
+
155,
|
| 3308 |
+
155,
|
| 3309 |
+
3
|
| 3310 |
+
],
|
| 3311 |
+
[
|
| 3312 |
+
166,
|
| 3313 |
+
152,
|
| 3314 |
+
1
|
| 3315 |
+
],
|
| 3316 |
+
[
|
| 3317 |
+
144,
|
| 3318 |
+
512,
|
| 3319 |
+
2
|
| 3320 |
+
],
|
| 3321 |
+
[
|
| 3322 |
+
220,
|
| 3323 |
+
220,
|
| 3324 |
+
2
|
| 3325 |
+
],
|
| 3326 |
+
[
|
| 3327 |
+
290,
|
| 3328 |
+
290,
|
| 3329 |
+
2
|
| 3330 |
+
],
|
| 3331 |
+
[
|
| 3332 |
+
177,
|
| 3333 |
+
138,
|
| 3334 |
+
2
|
| 3335 |
+
],
|
| 3336 |
+
[
|
| 3337 |
+
156,
|
| 3338 |
+
168,
|
| 3339 |
+
1
|
| 3340 |
+
],
|
| 3341 |
+
[
|
| 3342 |
+
132,
|
| 3343 |
+
512,
|
| 3344 |
+
2
|
| 3345 |
+
],
|
| 3346 |
+
[
|
| 3347 |
+
133,
|
| 3348 |
+
512,
|
| 3349 |
+
2
|
| 3350 |
+
],
|
| 3351 |
+
[
|
| 3352 |
+
164,
|
| 3353 |
+
164,
|
| 3354 |
+
3
|
| 3355 |
+
],
|
| 3356 |
+
[
|
| 3357 |
+
525,
|
| 3358 |
+
168,
|
| 3359 |
+
5
|
| 3360 |
+
],
|
| 3361 |
+
[
|
| 3362 |
+
514,
|
| 3363 |
+
150,
|
| 3364 |
+
5
|
| 3365 |
+
],
|
| 3366 |
+
[
|
| 3367 |
+
525,
|
| 3368 |
+
170,
|
| 3369 |
+
5
|
| 3370 |
+
],
|
| 3371 |
+
[
|
| 3372 |
+
158,
|
| 3373 |
+
152,
|
| 3374 |
+
1
|
| 3375 |
+
],
|
| 3376 |
+
[
|
| 3377 |
+
514,
|
| 3378 |
+
151,
|
| 3379 |
+
5
|
| 3380 |
+
],
|
| 3381 |
+
[
|
| 3382 |
+
132,
|
| 3383 |
+
176,
|
| 3384 |
+
2
|
| 3385 |
+
],
|
| 3386 |
+
[
|
| 3387 |
+
521,
|
| 3388 |
+
172,
|
| 3389 |
+
3
|
| 3390 |
+
],
|
| 3391 |
+
[
|
| 3392 |
+
294,
|
| 3393 |
+
294,
|
| 3394 |
+
2
|
| 3395 |
+
],
|
| 3396 |
+
[
|
| 3397 |
+
147,
|
| 3398 |
+
512,
|
| 3399 |
+
2
|
| 3400 |
+
],
|
| 3401 |
+
[
|
| 3402 |
+
148,
|
| 3403 |
+
144,
|
| 3404 |
+
2
|
| 3405 |
+
],
|
| 3406 |
+
[
|
| 3407 |
+
522,
|
| 3408 |
+
149,
|
| 3409 |
+
2
|
| 3410 |
+
],
|
| 3411 |
+
[
|
| 3412 |
+
103,
|
| 3413 |
+
136,
|
| 3414 |
+
4
|
| 3415 |
+
],
|
| 3416 |
+
[
|
| 3417 |
+
173,
|
| 3418 |
+
158,
|
| 3419 |
+
2
|
| 3420 |
+
],
|
| 3421 |
+
[
|
| 3422 |
+
146,
|
| 3423 |
+
512,
|
| 3424 |
+
2
|
| 3425 |
+
],
|
| 3426 |
+
[
|
| 3427 |
+
165,
|
| 3428 |
+
104,
|
| 3429 |
+
1
|
| 3430 |
+
],
|
| 3431 |
+
[
|
| 3432 |
+
103,
|
| 3433 |
+
132,
|
| 3434 |
+
4
|
| 3435 |
+
],
|
| 3436 |
+
[
|
| 3437 |
+
103,
|
| 3438 |
+
138,
|
| 3439 |
+
4
|
| 3440 |
+
],
|
| 3441 |
+
[
|
| 3442 |
+
130,
|
| 3443 |
+
130,
|
| 3444 |
+
2
|
| 3445 |
+
],
|
| 3446 |
+
[
|
| 3447 |
+
144,
|
| 3448 |
+
148,
|
| 3449 |
+
2
|
| 3450 |
+
],
|
| 3451 |
+
[
|
| 3452 |
+
166,
|
| 3453 |
+
104,
|
| 3454 |
+
1
|
| 3455 |
+
],
|
| 3456 |
+
[
|
| 3457 |
+
156,
|
| 3458 |
+
152,
|
| 3459 |
+
1
|
| 3460 |
+
],
|
| 3461 |
+
[
|
| 3462 |
+
179,
|
| 3463 |
+
179,
|
| 3464 |
+
4
|
| 3465 |
+
],
|
| 3466 |
+
[
|
| 3467 |
+
519,
|
| 3468 |
+
132,
|
| 3469 |
+
6
|
| 3470 |
+
],
|
| 3471 |
+
[
|
| 3472 |
+
145,
|
| 3473 |
+
145,
|
| 3474 |
+
5
|
| 3475 |
+
],
|
| 3476 |
+
[
|
| 3477 |
+
189,
|
| 3478 |
+
189,
|
| 3479 |
+
3
|
| 3480 |
+
],
|
| 3481 |
+
[
|
| 3482 |
+
527,
|
| 3483 |
+
152,
|
| 3484 |
+
6
|
| 3485 |
+
],
|
| 3486 |
+
[
|
| 3487 |
+
153,
|
| 3488 |
+
153,
|
| 3489 |
+
3
|
| 3490 |
+
],
|
| 3491 |
+
[
|
| 3492 |
+
524,
|
| 3493 |
+
132,
|
| 3494 |
+
4
|
| 3495 |
+
],
|
| 3496 |
+
[
|
| 3497 |
+
519,
|
| 3498 |
+
148,
|
| 3499 |
+
6
|
| 3500 |
+
],
|
| 3501 |
+
[
|
| 3502 |
+
522,
|
| 3503 |
+
151,
|
| 3504 |
+
2
|
| 3505 |
+
],
|
| 3506 |
+
[
|
| 3507 |
+
522,
|
| 3508 |
+
150,
|
| 3509 |
+
2
|
| 3510 |
+
],
|
| 3511 |
+
[
|
| 3512 |
+
159,
|
| 3513 |
+
159,
|
| 3514 |
+
3
|
| 3515 |
+
],
|
| 3516 |
+
[
|
| 3517 |
+
514,
|
| 3518 |
+
103,
|
| 3519 |
+
5
|
| 3520 |
+
],
|
| 3521 |
+
[
|
| 3522 |
+
163,
|
| 3523 |
+
170,
|
| 3524 |
+
1
|
| 3525 |
+
],
|
| 3526 |
+
[
|
| 3527 |
+
519,
|
| 3528 |
+
144,
|
| 3529 |
+
6
|
| 3530 |
+
],
|
| 3531 |
+
[
|
| 3532 |
+
138,
|
| 3533 |
+
512,
|
| 3534 |
+
2
|
| 3535 |
+
],
|
| 3536 |
+
[
|
| 3537 |
+
162,
|
| 3538 |
+
162,
|
| 3539 |
+
3
|
| 3540 |
+
],
|
| 3541 |
+
[
|
| 3542 |
+
519,
|
| 3543 |
+
136,
|
| 3544 |
+
6
|
| 3545 |
+
],
|
| 3546 |
+
[
|
| 3547 |
+
156,
|
| 3548 |
+
104,
|
| 3549 |
+
1
|
| 3550 |
+
],
|
| 3551 |
+
[
|
| 3552 |
+
181,
|
| 3553 |
+
181,
|
| 3554 |
+
3
|
| 3555 |
+
],
|
| 3556 |
+
[
|
| 3557 |
+
103,
|
| 3558 |
+
177,
|
| 3559 |
+
2
|
| 3560 |
+
],
|
| 3561 |
+
[
|
| 3562 |
+
158,
|
| 3563 |
+
104,
|
| 3564 |
+
1
|
| 3565 |
+
],
|
| 3566 |
+
[
|
| 3567 |
+
151,
|
| 3568 |
+
151,
|
| 3569 |
+
5
|
| 3570 |
+
],
|
| 3571 |
+
[
|
| 3572 |
+
519,
|
| 3573 |
+
133,
|
| 3574 |
+
6
|
| 3575 |
+
],
|
| 3576 |
+
[
|
| 3577 |
+
148,
|
| 3578 |
+
512,
|
| 3579 |
+
2
|
| 3580 |
+
],
|
| 3581 |
+
[
|
| 3582 |
+
136,
|
| 3583 |
+
512,
|
| 3584 |
+
2
|
| 3585 |
+
],
|
| 3586 |
+
[
|
| 3587 |
+
103,
|
| 3588 |
+
133,
|
| 3589 |
+
4
|
| 3590 |
+
],
|
| 3591 |
+
[
|
| 3592 |
+
176,
|
| 3593 |
+
132,
|
| 3594 |
+
2
|
| 3595 |
+
],
|
| 3596 |
+
[
|
| 3597 |
+
168,
|
| 3598 |
+
168,
|
| 3599 |
+
2
|
| 3600 |
+
],
|
| 3601 |
+
[
|
| 3602 |
+
519,
|
| 3603 |
+
149,
|
| 3604 |
+
6
|
| 3605 |
+
],
|
| 3606 |
+
[
|
| 3607 |
+
519,
|
| 3608 |
+
146,
|
| 3609 |
+
6
|
| 3610 |
+
],
|
| 3611 |
+
[
|
| 3612 |
+
525,
|
| 3613 |
+
172,
|
| 3614 |
+
5
|
| 3615 |
+
],
|
| 3616 |
+
[
|
| 3617 |
+
313,
|
| 3618 |
+
313,
|
| 3619 |
+
2
|
| 3620 |
+
],
|
| 3621 |
+
[
|
| 3622 |
+
526,
|
| 3623 |
+
163,
|
| 3624 |
+
2
|
| 3625 |
+
],
|
| 3626 |
+
[
|
| 3627 |
+
521,
|
| 3628 |
+
104,
|
| 3629 |
+
4
|
| 3630 |
+
],
|
| 3631 |
+
[
|
| 3632 |
+
136,
|
| 3633 |
+
136,
|
| 3634 |
+
6
|
| 3635 |
+
],
|
| 3636 |
+
[
|
| 3637 |
+
170,
|
| 3638 |
+
170,
|
| 3639 |
+
4
|
| 3640 |
+
],
|
| 3641 |
+
[
|
| 3642 |
+
524,
|
| 3643 |
+
133,
|
| 3644 |
+
4
|
| 3645 |
+
],
|
| 3646 |
+
[
|
| 3647 |
+
519,
|
| 3648 |
+
147,
|
| 3649 |
+
6
|
| 3650 |
+
],
|
| 3651 |
+
[
|
| 3652 |
+
525,
|
| 3653 |
+
165,
|
| 3654 |
+
5
|
| 3655 |
+
],
|
| 3656 |
+
[
|
| 3657 |
+
525,
|
| 3658 |
+
173,
|
| 3659 |
+
5
|
| 3660 |
+
],
|
| 3661 |
+
[
|
| 3662 |
+
138,
|
| 3663 |
+
138,
|
| 3664 |
+
6
|
| 3665 |
+
],
|
| 3666 |
+
[
|
| 3667 |
+
166,
|
| 3668 |
+
158,
|
| 3669 |
+
1
|
| 3670 |
+
],
|
| 3671 |
+
[
|
| 3672 |
+
41,
|
| 3673 |
+
41,
|
| 3674 |
+
2
|
| 3675 |
+
],
|
| 3676 |
+
[
|
| 3677 |
+
527,
|
| 3678 |
+
156,
|
| 3679 |
+
6
|
| 3680 |
+
],
|
| 3681 |
+
[
|
| 3682 |
+
524,
|
| 3683 |
+
147,
|
| 3684 |
+
4
|
| 3685 |
+
],
|
| 3686 |
+
[
|
| 3687 |
+
240,
|
| 3688 |
+
240,
|
| 3689 |
+
2
|
| 3690 |
+
],
|
| 3691 |
+
[
|
| 3692 |
+
149,
|
| 3693 |
+
512,
|
| 3694 |
+
2
|
| 3695 |
+
],
|
| 3696 |
+
[
|
| 3697 |
+
154,
|
| 3698 |
+
154,
|
| 3699 |
+
3
|
| 3700 |
+
],
|
| 3701 |
+
[
|
| 3702 |
+
103,
|
| 3703 |
+
146,
|
| 3704 |
+
4
|
| 3705 |
+
],
|
| 3706 |
+
[
|
| 3707 |
+
525,
|
| 3708 |
+
180,
|
| 3709 |
+
5
|
| 3710 |
+
],
|
| 3711 |
+
[
|
| 3712 |
+
163,
|
| 3713 |
+
513,
|
| 3714 |
+
1
|
| 3715 |
+
],
|
| 3716 |
+
[
|
| 3717 |
+
519,
|
| 3718 |
+
138,
|
| 3719 |
+
6
|
| 3720 |
+
],
|
| 3721 |
+
[
|
| 3722 |
+
150,
|
| 3723 |
+
150,
|
| 3724 |
+
5
|
| 3725 |
+
],
|
| 3726 |
+
[
|
| 3727 |
+
524,
|
| 3728 |
+
146,
|
| 3729 |
+
4
|
| 3730 |
+
],
|
| 3731 |
+
[
|
| 3732 |
+
532,
|
| 3733 |
+
152,
|
| 3734 |
+
7
|
| 3735 |
+
],
|
| 3736 |
+
[
|
| 3737 |
+
189,
|
| 3738 |
+
133,
|
| 3739 |
+
2
|
| 3740 |
+
],
|
| 3741 |
+
[
|
| 3742 |
+
132,
|
| 3743 |
+
146,
|
| 3744 |
+
2
|
| 3745 |
+
],
|
| 3746 |
+
[
|
| 3747 |
+
524,
|
| 3748 |
+
145,
|
| 3749 |
+
4
|
| 3750 |
+
],
|
| 3751 |
+
[
|
| 3752 |
+
163,
|
| 3753 |
+
152,
|
| 3754 |
+
3
|
| 3755 |
+
],
|
| 3756 |
+
[
|
| 3757 |
+
525,
|
| 3758 |
+
181,
|
| 3759 |
+
5
|
| 3760 |
+
],
|
| 3761 |
+
[
|
| 3762 |
+
217,
|
| 3763 |
+
217,
|
| 3764 |
+
2
|
| 3765 |
+
],
|
| 3766 |
+
[
|
| 3767 |
+
156,
|
| 3768 |
+
156,
|
| 3769 |
+
2
|
| 3770 |
+
],
|
| 3771 |
+
[
|
| 3772 |
+
524,
|
| 3773 |
+
144,
|
| 3774 |
+
4
|
| 3775 |
+
],
|
| 3776 |
+
[
|
| 3777 |
+
146,
|
| 3778 |
+
132,
|
| 3779 |
+
2
|
| 3780 |
+
],
|
| 3781 |
+
[
|
| 3782 |
+
147,
|
| 3783 |
+
133,
|
| 3784 |
+
2
|
| 3785 |
+
],
|
| 3786 |
+
[
|
| 3787 |
+
514,
|
| 3788 |
+
132,
|
| 3789 |
+
3
|
| 3790 |
+
],
|
| 3791 |
+
[
|
| 3792 |
+
158,
|
| 3793 |
+
173,
|
| 3794 |
+
2
|
| 3795 |
+
],
|
| 3796 |
+
[
|
| 3797 |
+
145,
|
| 3798 |
+
149,
|
| 3799 |
+
2
|
| 3800 |
+
],
|
| 3801 |
+
[
|
| 3802 |
+
35,
|
| 3803 |
+
35,
|
| 3804 |
+
2
|
| 3805 |
+
],
|
| 3806 |
+
[
|
| 3807 |
+
105,
|
| 3808 |
+
105,
|
| 3809 |
+
3
|
| 3810 |
+
],
|
| 3811 |
+
[
|
| 3812 |
+
242,
|
| 3813 |
+
242,
|
| 3814 |
+
2
|
| 3815 |
+
],
|
| 3816 |
+
[
|
| 3817 |
+
527,
|
| 3818 |
+
163,
|
| 3819 |
+
6
|
| 3820 |
+
],
|
| 3821 |
+
[
|
| 3822 |
+
219,
|
| 3823 |
+
219,
|
| 3824 |
+
2
|
| 3825 |
+
],
|
| 3826 |
+
[
|
| 3827 |
+
103,
|
| 3828 |
+
147,
|
| 3829 |
+
4
|
| 3830 |
+
],
|
| 3831 |
+
[
|
| 3832 |
+
236,
|
| 3833 |
+
236,
|
| 3834 |
+
2
|
| 3835 |
+
],
|
| 3836 |
+
[
|
| 3837 |
+
514,
|
| 3838 |
+
136,
|
| 3839 |
+
3
|
| 3840 |
+
],
|
| 3841 |
+
[
|
| 3842 |
+
104,
|
| 3843 |
+
167,
|
| 3844 |
+
1
|
| 3845 |
+
],
|
| 3846 |
+
[
|
| 3847 |
+
314,
|
| 3848 |
+
314,
|
| 3849 |
+
2
|
| 3850 |
+
],
|
| 3851 |
+
[
|
| 3852 |
+
526,
|
| 3853 |
+
168,
|
| 3854 |
+
2
|
| 3855 |
+
],
|
| 3856 |
+
[
|
| 3857 |
+
132,
|
| 3858 |
+
149,
|
| 3859 |
+
2
|
| 3860 |
+
],
|
| 3861 |
+
[
|
| 3862 |
+
151,
|
| 3863 |
+
151,
|
| 3864 |
+
2
|
| 3865 |
+
],
|
| 3866 |
+
[
|
| 3867 |
+
527,
|
| 3868 |
+
158,
|
| 3869 |
+
6
|
| 3870 |
+
],
|
| 3871 |
+
[
|
| 3872 |
+
132,
|
| 3873 |
+
147,
|
| 3874 |
+
2
|
| 3875 |
+
],
|
| 3876 |
+
[
|
| 3877 |
+
148,
|
| 3878 |
+
133,
|
| 3879 |
+
2
|
| 3880 |
+
],
|
| 3881 |
+
[
|
| 3882 |
+
514,
|
| 3883 |
+
138,
|
| 3884 |
+
3
|
| 3885 |
+
],
|
| 3886 |
+
[
|
| 3887 |
+
527,
|
| 3888 |
+
168,
|
| 3889 |
+
6
|
| 3890 |
+
],
|
| 3891 |
+
[
|
| 3892 |
+
525,
|
| 3893 |
+
166,
|
| 3894 |
+
5
|
| 3895 |
+
],
|
| 3896 |
+
[
|
| 3897 |
+
176,
|
| 3898 |
+
145,
|
| 3899 |
+
2
|
| 3900 |
+
],
|
| 3901 |
+
[
|
| 3902 |
+
361,
|
| 3903 |
+
361,
|
| 3904 |
+
2
|
| 3905 |
+
],
|
| 3906 |
+
[
|
| 3907 |
+
519,
|
| 3908 |
+
145,
|
| 3909 |
+
6
|
| 3910 |
+
],
|
| 3911 |
+
[
|
| 3912 |
+
146,
|
| 3913 |
+
136,
|
| 3914 |
+
2
|
| 3915 |
+
],
|
| 3916 |
+
[
|
| 3917 |
+
132,
|
| 3918 |
+
136,
|
| 3919 |
+
2
|
| 3920 |
+
],
|
| 3921 |
+
[
|
| 3922 |
+
527,
|
| 3923 |
+
170,
|
| 3924 |
+
6
|
| 3925 |
+
],
|
| 3926 |
+
[
|
| 3927 |
+
180,
|
| 3928 |
+
168,
|
| 3929 |
+
2
|
| 3930 |
+
],
|
| 3931 |
+
[
|
| 3932 |
+
146,
|
| 3933 |
+
133,
|
| 3934 |
+
2
|
| 3935 |
+
],
|
| 3936 |
+
[
|
| 3937 |
+
165,
|
| 3938 |
+
165,
|
| 3939 |
+
4
|
| 3940 |
+
],
|
| 3941 |
+
[
|
| 3942 |
+
170,
|
| 3943 |
+
104,
|
| 3944 |
+
1
|
| 3945 |
+
],
|
| 3946 |
+
[
|
| 3947 |
+
82,
|
| 3948 |
+
82,
|
| 3949 |
+
2
|
| 3950 |
+
],
|
| 3951 |
+
[
|
| 3952 |
+
246,
|
| 3953 |
+
246,
|
| 3954 |
+
2
|
| 3955 |
+
],
|
| 3956 |
+
[
|
| 3957 |
+
132,
|
| 3958 |
+
138,
|
| 3959 |
+
2
|
| 3960 |
+
],
|
| 3961 |
+
[
|
| 3962 |
+
532,
|
| 3963 |
+
156,
|
| 3964 |
+
7
|
| 3965 |
+
],
|
| 3966 |
+
[
|
| 3967 |
+
147,
|
| 3968 |
+
144,
|
| 3969 |
+
2
|
| 3970 |
+
],
|
| 3971 |
+
[
|
| 3972 |
+
158,
|
| 3973 |
+
158,
|
| 3974 |
+
2
|
| 3975 |
+
],
|
| 3976 |
+
[
|
| 3977 |
+
514,
|
| 3978 |
+
148,
|
| 3979 |
+
3
|
| 3980 |
+
],
|
| 3981 |
+
[
|
| 3982 |
+
526,
|
| 3983 |
+
170,
|
| 3984 |
+
2
|
| 3985 |
+
],
|
| 3986 |
+
[
|
| 3987 |
+
345,
|
| 3988 |
+
345,
|
| 3989 |
+
2
|
| 3990 |
+
],
|
| 3991 |
+
[
|
| 3992 |
+
514,
|
| 3993 |
+
149,
|
| 3994 |
+
3
|
| 3995 |
+
],
|
| 3996 |
+
[
|
| 3997 |
+
147,
|
| 3998 |
+
138,
|
| 3999 |
+
2
|
| 4000 |
+
],
|
| 4001 |
+
[
|
| 4002 |
+
517,
|
| 4003 |
+
512,
|
| 4004 |
+
3
|
| 4005 |
+
],
|
| 4006 |
+
[
|
| 4007 |
+
163,
|
| 4008 |
+
163,
|
| 4009 |
+
5
|
| 4010 |
+
],
|
| 4011 |
+
[
|
| 4012 |
+
514,
|
| 4013 |
+
150,
|
| 4014 |
+
3
|
| 4015 |
+
],
|
| 4016 |
+
[
|
| 4017 |
+
223,
|
| 4018 |
+
223,
|
| 4019 |
+
2
|
| 4020 |
+
],
|
| 4021 |
+
[
|
| 4022 |
+
166,
|
| 4023 |
+
166,
|
| 4024 |
+
4
|
| 4025 |
+
],
|
| 4026 |
+
[
|
| 4027 |
+
523,
|
| 4028 |
+
147,
|
| 4029 |
+
4
|
| 4030 |
+
],
|
| 4031 |
+
[
|
| 4032 |
+
136,
|
| 4033 |
+
146,
|
| 4034 |
+
2
|
| 4035 |
+
],
|
| 4036 |
+
[
|
| 4037 |
+
526,
|
| 4038 |
+
152,
|
| 4039 |
+
2
|
| 4040 |
+
],
|
| 4041 |
+
[
|
| 4042 |
+
132,
|
| 4043 |
+
148,
|
| 4044 |
+
2
|
| 4045 |
+
],
|
| 4046 |
+
[
|
| 4047 |
+
165,
|
| 4048 |
+
156,
|
| 4049 |
+
1
|
| 4050 |
+
],
|
| 4051 |
+
[
|
| 4052 |
+
206,
|
| 4053 |
+
206,
|
| 4054 |
+
2
|
| 4055 |
+
],
|
| 4056 |
+
[
|
| 4057 |
+
524,
|
| 4058 |
+
136,
|
| 4059 |
+
4
|
| 4060 |
+
],
|
| 4061 |
+
[
|
| 4062 |
+
514,
|
| 4063 |
+
145,
|
| 4064 |
+
3
|
| 4065 |
+
],
|
| 4066 |
+
[
|
| 4067 |
+
366,
|
| 4068 |
+
366,
|
| 4069 |
+
2
|
| 4070 |
+
],
|
| 4071 |
+
[
|
| 4072 |
+
519,
|
| 4073 |
+
151,
|
| 4074 |
+
6
|
| 4075 |
+
],
|
| 4076 |
+
[
|
| 4077 |
+
136,
|
| 4078 |
+
133,
|
| 4079 |
+
2
|
| 4080 |
+
],
|
| 4081 |
+
[
|
| 4082 |
+
104,
|
| 4083 |
+
168,
|
| 4084 |
+
2
|
| 4085 |
+
],
|
| 4086 |
+
[
|
| 4087 |
+
222,
|
| 4088 |
+
222,
|
| 4089 |
+
3
|
| 4090 |
+
],
|
| 4091 |
+
[
|
| 4092 |
+
523,
|
| 4093 |
+
132,
|
| 4094 |
+
4
|
| 4095 |
+
],
|
| 4096 |
+
[
|
| 4097 |
+
524,
|
| 4098 |
+
138,
|
| 4099 |
+
4
|
| 4100 |
+
],
|
| 4101 |
+
[
|
| 4102 |
+
170,
|
| 4103 |
+
152,
|
| 4104 |
+
1
|
| 4105 |
+
],
|
| 4106 |
+
[
|
| 4107 |
+
40,
|
| 4108 |
+
40,
|
| 4109 |
+
2
|
| 4110 |
+
],
|
| 4111 |
+
[
|
| 4112 |
+
181,
|
| 4113 |
+
181,
|
| 4114 |
+
4
|
| 4115 |
+
],
|
| 4116 |
+
[
|
| 4117 |
+
173,
|
| 4118 |
+
173,
|
| 4119 |
+
3
|
| 4120 |
+
],
|
| 4121 |
+
[
|
| 4122 |
+
328,
|
| 4123 |
+
328,
|
| 4124 |
+
2
|
| 4125 |
+
],
|
| 4126 |
+
[
|
| 4127 |
+
145,
|
| 4128 |
+
512,
|
| 4129 |
+
2
|
| 4130 |
+
],
|
| 4131 |
+
[
|
| 4132 |
+
517,
|
| 4133 |
+
103,
|
| 4134 |
+
3
|
| 4135 |
+
],
|
| 4136 |
+
[
|
| 4137 |
+
514,
|
| 4138 |
+
545,
|
| 4139 |
+
1
|
| 4140 |
+
],
|
| 4141 |
+
[
|
| 4142 |
+
526,
|
| 4143 |
+
156,
|
| 4144 |
+
2
|
| 4145 |
+
],
|
| 4146 |
+
[
|
| 4147 |
+
150,
|
| 4148 |
+
150,
|
| 4149 |
+
2
|
| 4150 |
+
],
|
| 4151 |
+
[
|
| 4152 |
+
517,
|
| 4153 |
+
176,
|
| 4154 |
+
6
|
| 4155 |
+
],
|
| 4156 |
+
[
|
| 4157 |
+
514,
|
| 4158 |
+
146,
|
| 4159 |
+
3
|
| 4160 |
+
],
|
| 4161 |
+
[
|
| 4162 |
+
167,
|
| 4163 |
+
167,
|
| 4164 |
+
3
|
| 4165 |
+
],
|
| 4166 |
+
[
|
| 4167 |
+
146,
|
| 4168 |
+
144,
|
| 4169 |
+
2
|
| 4170 |
+
],
|
| 4171 |
+
[
|
| 4172 |
+
532,
|
| 4173 |
+
168,
|
| 4174 |
+
7
|
| 4175 |
+
],
|
| 4176 |
+
[
|
| 4177 |
+
138,
|
| 4178 |
+
177,
|
| 4179 |
+
2
|
| 4180 |
+
],
|
| 4181 |
+
[
|
| 4182 |
+
149,
|
| 4183 |
+
149,
|
| 4184 |
+
7
|
| 4185 |
+
],
|
| 4186 |
+
[
|
| 4187 |
+
103,
|
| 4188 |
+
103,
|
| 4189 |
+
3
|
| 4190 |
+
],
|
| 4191 |
+
[
|
| 4192 |
+
316,
|
| 4193 |
+
316,
|
| 4194 |
+
2
|
| 4195 |
+
],
|
| 4196 |
+
[
|
| 4197 |
+
519,
|
| 4198 |
+
150,
|
| 4199 |
+
6
|
| 4200 |
+
],
|
| 4201 |
+
[
|
| 4202 |
+
532,
|
| 4203 |
+
163,
|
| 4204 |
+
7
|
| 4205 |
+
],
|
| 4206 |
+
[
|
| 4207 |
+
532,
|
| 4208 |
+
158,
|
| 4209 |
+
7
|
| 4210 |
+
],
|
| 4211 |
+
[
|
| 4212 |
+
514,
|
| 4213 |
+
151,
|
| 4214 |
+
3
|
| 4215 |
+
],
|
| 4216 |
+
[
|
| 4217 |
+
138,
|
| 4218 |
+
147,
|
| 4219 |
+
2
|
| 4220 |
+
]
|
| 4221 |
+
]
|
| 4222 |
+
}
|
variants/extended-1st-10mm-1k/profiles/eef/config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "actioncodec2_unified",
|
| 3 |
+
"processor_class": "ActionCodec2Processor"
|
| 4 |
+
}
|
variants/extended-1st-10mm-1k/profiles/eef/config.yaml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_type: actioncodec2_unified
|
| 2 |
+
config_version: 1
|
| 3 |
+
physical:
|
| 4 |
+
input_dim: 7
|
| 5 |
+
components:
|
| 6 |
+
translation_m:
|
| 7 |
+
dims:
|
| 8 |
+
start: 0
|
| 9 |
+
stop: 3
|
| 10 |
+
space: additive
|
| 11 |
+
input_order: 0
|
| 12 |
+
primitive_order: 1
|
| 13 |
+
input_transform:
|
| 14 |
+
scale: 1.0
|
| 15 |
+
clip: false
|
| 16 |
+
constraints:
|
| 17 |
+
order_0:
|
| 18 |
+
min: -2.0
|
| 19 |
+
max: 2.0
|
| 20 |
+
order_1:
|
| 21 |
+
min: -0.08
|
| 22 |
+
max: 0.08
|
| 23 |
+
distortion_limit: 0.01
|
| 24 |
+
primitive_selection: smooth
|
| 25 |
+
orientation_so3:
|
| 26 |
+
dims:
|
| 27 |
+
start: 3
|
| 28 |
+
stop: 6
|
| 29 |
+
space: so3
|
| 30 |
+
input_order: 0
|
| 31 |
+
primitive_order: 1
|
| 32 |
+
input_transform:
|
| 33 |
+
scale: 1.0
|
| 34 |
+
clip: false
|
| 35 |
+
constraints:
|
| 36 |
+
order_0:
|
| 37 |
+
min: -3.141592653589793
|
| 38 |
+
max: 3.141592653589793
|
| 39 |
+
order_1:
|
| 40 |
+
min: -0.25
|
| 41 |
+
max: 0.25
|
| 42 |
+
distortion_limit: 0.04
|
| 43 |
+
primitive_selection: smooth
|
| 44 |
+
representation: rotation_vector
|
| 45 |
+
multiplication: right
|
| 46 |
+
gripper_open_command:
|
| 47 |
+
dims:
|
| 48 |
+
- 6
|
| 49 |
+
space: additive
|
| 50 |
+
input_order: 0
|
| 51 |
+
primitive_order: 0
|
| 52 |
+
input_transform:
|
| 53 |
+
scale: 1.0
|
| 54 |
+
clip: true
|
| 55 |
+
constraints:
|
| 56 |
+
order_0:
|
| 57 |
+
min: 0.0
|
| 58 |
+
max: 1.0
|
| 59 |
+
distortion_limit: 0.5
|
| 60 |
+
primitive_selection: nearest
|
| 61 |
+
set_bpe:
|
| 62 |
+
lookahead: 8
|
| 63 |
+
allow_cross_component_merge: false
|
| 64 |
+
train:
|
| 65 |
+
primitives:
|
| 66 |
+
zero_primitive_components:
|
| 67 |
+
- translation_m
|
| 68 |
+
- orientation_so3
|
| 69 |
+
bpe:
|
| 70 |
+
spatial_vocab_size: 512
|
| 71 |
+
vocab_size: 1024
|
| 72 |
+
quality_control:
|
| 73 |
+
hard_order_1_scale: 3.0
|
| 74 |
+
min_segment_length: 2
|