feat: huoyi M20 smooth/bridge training and stability docs

Current code snapshot: smooth/bridge task configs, smooth random terrain,
roll/pitch stability rewards, M20 URDF updates, eval profile and stability
training documentation.
This commit is contained in:
syfffffjj
2026-11-02 16:41:07 +08:00
commit 07027e8fde
91 changed files with 13047 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
# Copyright (c) 2025 Deep Robotics
# SPDX-License-Identifier: BSD 3-Clause
"""按正则列表解析名字,与 IsaacLab 的 ``resolve_matching_names`` 语义一致,
但**匹配不到不抛异常**。
IsaacLab 的实现用 ``re.fullmatch`` 逐个比对,并且:某个名字同时命中多个正则会报错,
某个正则一个都没命中也报错。对环境配置来说这种严格是有用的(写错了应该立刻发现),
但评价代码不该因为「这个机器人没有轮子」就崩掉——那时正确行为是跳过依赖轮子的指标。
这个模块是纯逻辑,不依赖 isaaclab,因此可以单独测试。
"""
from __future__ import annotations
import re
from typing import Sequence
class NameResolution:
"""一次名字解析的结果。"""
__slots__ = ("indices", "names", "unmatched_patterns", "ambiguous_names")
def __init__(
self,
indices: list[int],
names: list[str],
unmatched_patterns: list[str],
ambiguous_names: list[tuple[str, list[str]]],
) -> None:
self.indices = indices
self.names = names
#: 一个都没命中的正则。
self.unmatched_patterns = unmatched_patterns
#: 同时命中多个正则的名字,以及命中的正则。
self.ambiguous_names = ambiguous_names
def __bool__(self) -> bool:
return bool(self.indices)
def __len__(self) -> int:
return len(self.indices)
def __repr__(self) -> str:
return (
f"NameResolution({len(self.indices)} 项, "
f"未命中={self.unmatched_patterns}, 多重命中={self.ambiguous_names})"
)
def resolve_matching_indices(patterns: Sequence[str], names: Sequence[str]) -> NameResolution:
"""把正则列表解析成 ``names`` 中的下标。
行为与 IsaacLab 的 ``resolve_matching_names`` 一致之处:用 ``re.fullmatch``;
返回顺序遵循 ``patterns`` 的顺序。
不同之处:不抛异常,而是把「未命中」和「多重命中」记在返回值里,由调用方决定
是警告还是忽略。
Args:
patterns: 正则列表,例如 ``("base_link", ".*_knee")``。
names: 候选名字,例如 ``articulation.body_names``。
"""
hits: dict[int, list[str]] = {}
pattern_hits: dict[str, int] = {pattern: 0 for pattern in patterns}
for index, name in enumerate(names):
for pattern in patterns:
try:
matched = re.fullmatch(pattern, name) is not None
except re.error as exc: # 正则本身写错,属于配置错误,应当立刻暴露
raise ValueError(f"机器人描述里的正则 {pattern!r} 无法编译:{exc}") from exc
if matched:
hits.setdefault(index, []).append(pattern)
pattern_hits[pattern] += 1
indices: list[int] = []
resolved_names: list[str] = []
ambiguous: list[tuple[str, list[str]]] = []
taken: set[int] = set()
# 按 patterns 顺序输出,跳过已被更早的正则取走的下标
for pattern in patterns:
for index, matched_patterns in hits.items():
if pattern not in matched_patterns or index in taken:
continue
taken.add(index)
indices.append(index)
resolved_names.append(names[index])
if len(matched_patterns) > 1:
ambiguous.append((names[index], list(matched_patterns)))
unmatched = [pattern for pattern, count in pattern_hits.items() if count == 0]
return NameResolution(indices, resolved_names, unmatched, ambiguous)