""" 环境层通用工具 ============= 提供数组拼接、索引采样、tensor→numpy 转换等辅助功能。 """ from typing import Dict, Iterable, List, Optional, Union import numpy as np from numpy import ndarray from torch import Tensor from torch_geometric.data.data import BaseData def save_concatenate( arrays: Iterable[np.ndarray], *args, **kwargs ) -> Optional[np.ndarray]: """ 安全拼接多个数组。自动过滤 None 值,空列表返回 None。 Args: arrays: 要拼接的数组列表(可能包含 None) Returns: 拼接后的数组;若全为 None 则返回 None Example: >>> result = save_concatenate([arr1, None, arr2], axis=1) """ arrays = [array for array in arrays if array is not None] if len(arrays) == 0: return None return np.concatenate(arrays, *args, **kwargs) class IndexSampler: """ 随机索引采样器 — 用于循环缓冲区中随机抽取 PDE 实例。 内部维护一个随机排列的索引数组,每次调用 next() 返回一个索引。 遍历完所有索引后自动重新洗牌。 Example: >>> sampler = IndexSampler(100, np.random.RandomState(42)) >>> idx = sampler.next() # 随机抽取一个索引 """ def __init__(self, size: int, random_state: np.random.RandomState): self._size = size self._indices = np.arange(size) self._random_state = random_state self._reset() def next(self) -> int: """返回下一个随机索引,到底后自动洗牌重排。""" if self._position == self._size: self._reset() index = self._indices[self._position] self._position += 1 return index def _reset(self): self._position = 0 self._random_state.shuffle(self._indices) def __len__(self): return self._size def detach( tensor: Union[Tensor, Dict[str, Tensor], List[Tensor]], ) -> Union[ndarray, Dict[str, ndarray], List[ndarray], BaseData]: """ 将 PyTorch tensor 安全转换为 numpy 数组(自动处理 GPU→CPU)。 Args: tensor: PyTorch tensor、tensor 字典或 tensor 列表 Returns: 对应的 numpy 数组 Example: >>> action_np = detach(actions_tensor) # → np.ndarray """ if isinstance(tensor, dict): return {key: detach(value) for key, value in tensor.items()} elif isinstance(tensor, list): return [detach(value) for value in tensor] if tensor.is_cuda: return tensor.cpu().detach().numpy() else: return tensor.detach().numpy()