Files
syfffffjj 07027e8fde 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.
2026-11-02 16:41:07 +08:00

95 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)