{"record":{"id":"cfecf6cf89190fea","repo":"microsoft/qlib","slug":"unsupported-value-to-fill-with-invalid-obj","errorCode":null,"errorMessage":"Unsupported value to fill with invalid: {obj}","messagePattern":"Unsupported value to fill with invalid: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/rl/utils/finite_env.py","lineNumber":55,"sourceCode":"\n\ndef fill_invalid(obj: int | float | bool | T) -> T:\n    if isinstance(obj, (int, float, bool)):\n        return fill_invalid(np.array(obj))\n    if hasattr(obj, \"dtype\"):\n        if isinstance(obj, np.ndarray):\n            if np.issubdtype(obj.dtype, np.floating):\n                return np.full_like(obj, np.nan)\n            return np.full_like(obj, np.iinfo(obj.dtype).max)\n        # dealing with corner cases that numpy number is not supported by tianshou's sharray\n        return fill_invalid(np.array(obj))\n    elif isinstance(obj, dict):\n        return {k: fill_invalid(v) for k, v in obj.items()}\n    elif isinstance(obj, list):\n        return [fill_invalid(v) for v in obj]\n    elif isinstance(obj, tuple):\n        return tuple(fill_invalid(v) for v in obj)\n    raise ValueError(f\"Unsupported value to fill with invalid: {obj}\")\n\n\ndef is_invalid(arr: int | float | bool | T) -> bool:\n    if isinstance(arr, np.ndarray):\n        if np.issubdtype(arr.dtype, np.floating):\n            return np.isnan(arr).all()\n        return cast(bool, cast(np.ndarray, np.iinfo(arr.dtype).max == arr).all())\n    if isinstance(arr, dict):\n        return all(is_invalid(o) for o in arr.values())\n    if isinstance(arr, (list, tuple)):\n        return all(is_invalid(o) for o in arr)\n    if isinstance(arr, (int, float, bool, np.number)):\n        return is_invalid(np.array(arr))\n    return True\n\n\ndef generate_nan_observation(obs_space: gym.Space) -> Any:\n    \"\"\"The NaN observation that indicates the environment receives no seed.","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/rl/utils/finite_env.py#L37-L73","documentation":"ValueError from `fill_invalid` in qlib/rl/utils/finite_env.py:55. The function recursively builds an 'invalid' sentinel sample matching a space's structure: it supports np.ndarray, numpy scalars, dict, list, and tuple; any other type (str, None, custom object, plain python int/float for the array branch) reaches the final raise.","triggerScenarios":"An observation/space sample containing an unsupported leaf: a string id (e.g. stock_id), a None value, a python object, or a bare python int/float that the numpy conversion path can't turn into a supported array (or an empty/zero-d object array).","commonSituations":"Custom observations that embed metadata (dates as str, instrument ids, enums); gym version changes making samples come back as object dtype arrays; observations containing None for optional fields.","solutions":["Keep observation leaves to numeric ndarray/dict/list/tuple structures; move string metadata out of the observation (put it in simulator state or info).","Convert leaves to np.ndarray explicitly in your StateInterpreter (`np.asarray(x, dtype=np.float32)`), including scalars to 0-d/1-element arrays.","Drop None-valued keys or replace them with NaN-filled numeric arrays so fill_invalid can recurse."],"exampleFix":"// before\nobs = {\"feature\": feat, \"stock_id\": \"SH600000\"}  # str leaf -> ValueError\n// after\nobs = {\"feature\": feat, \"stock_id\": np.asarray(600000, dtype=np.int64)}  # numeric leaf","handlingStrategy":"type-guard","validationCode":"import numpy as np\n\ndef leaves_fillable(obj) -> bool:\n    if isinstance(obj, np.ndarray):\n        return obj.dtype == object or np.issubdtype(obj.dtype, np.number) or np.issubdtype(obj.dtype, np.bool_)\n    if isinstance(obj, np.number):\n        return True\n    if isinstance(obj, dict):\n        return all(leaves_fillable(v) for v in obj.values())\n    if isinstance(obj, (list, tuple)):\n        return all(leaves_fillable(v) for v in obj)\n    return False","typeGuard":"def fill_invalid_safe(obj):\n    \"\"\"Pre-convert unsupported leaves so fill_invalid never raises.\"\"\"\n    if isinstance(obj, (str, bytes, type(None))):\n        return np.asarray(0, dtype=np.int64)\n    if isinstance(obj, (int, float, bool)):\n        return np.asarray(obj)\n    if isinstance(obj, dict):\n        return {k: fill_invalid_safe(v) for k, v in obj.items()}\n    if isinstance(obj, (list, tuple)):\n        return type(obj)(fill_invalid_safe(v) for v in obj)\n    return obj","tryCatchPattern":"try:\n    invalid = fill_invalid(obs)\nexcept ValueError as e:\n    if \"Unsupported value to fill with invalid\" in str(e):\n        raise TypeError(f\"observation leaf not fillable: {e}\") from e\n    raise","preventionTips":["Keep observations purely numeric (ndarray/dict/list/tuple of numbers).","Convert str/None metadata out of the observation in the StateInterpreter.","Test fill_invalid on a real sample when adding new observation fields."],"tags":["rl","finite-env","observation","type-validation","sentinel"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}