microsoft/qlib · error · ValueError

Unsupported value to fill with invalid: {obj}

Error message

Unsupported value to fill with invalid: {obj}

What it means

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.

Source

Thrown at qlib/rl/utils/finite_env.py:55


def fill_invalid(obj: int | float | bool | T) -> T:
    if isinstance(obj, (int, float, bool)):
        return fill_invalid(np.array(obj))
    if hasattr(obj, "dtype"):
        if isinstance(obj, np.ndarray):
            if np.issubdtype(obj.dtype, np.floating):
                return np.full_like(obj, np.nan)
            return np.full_like(obj, np.iinfo(obj.dtype).max)
        # dealing with corner cases that numpy number is not supported by tianshou's sharray
        return fill_invalid(np.array(obj))
    elif isinstance(obj, dict):
        return {k: fill_invalid(v) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [fill_invalid(v) for v in obj]
    elif isinstance(obj, tuple):
        return tuple(fill_invalid(v) for v in obj)
    raise ValueError(f"Unsupported value to fill with invalid: {obj}")


def is_invalid(arr: int | float | bool | T) -> bool:
    if isinstance(arr, np.ndarray):
        if np.issubdtype(arr.dtype, np.floating):
            return np.isnan(arr).all()
        return cast(bool, cast(np.ndarray, np.iinfo(arr.dtype).max == arr).all())
    if isinstance(arr, dict):
        return all(is_invalid(o) for o in arr.values())
    if isinstance(arr, (list, tuple)):
        return all(is_invalid(o) for o in arr)
    if isinstance(arr, (int, float, bool, np.number)):
        return is_invalid(np.array(arr))
    return True


def generate_nan_observation(obs_space: gym.Space) -> Any:
    """The NaN observation that indicates the environment receives no seed.

View on GitHub (pinned to 79633dd950)

Solutions

  1. Keep observation leaves to numeric ndarray/dict/list/tuple structures; move string metadata out of the observation (put it in simulator state or info).
  2. Convert leaves to np.ndarray explicitly in your StateInterpreter (`np.asarray(x, dtype=np.float32)`), including scalars to 0-d/1-element arrays.
  3. Drop None-valued keys or replace them with NaN-filled numeric arrays so fill_invalid can recurse.

Example fix

// before
obs = {"feature": feat, "stock_id": "SH600000"}  # str leaf -> ValueError
// after
obs = {"feature": feat, "stock_id": np.asarray(600000, dtype=np.int64)}  # numeric leaf
Defensive patterns

Strategy: type-guard

Validate before calling

import numpy as np

def leaves_fillable(obj) -> bool:
    if isinstance(obj, np.ndarray):
        return obj.dtype == object or np.issubdtype(obj.dtype, np.number) or np.issubdtype(obj.dtype, np.bool_)
    if isinstance(obj, np.number):
        return True
    if isinstance(obj, dict):
        return all(leaves_fillable(v) for v in obj.values())
    if isinstance(obj, (list, tuple)):
        return all(leaves_fillable(v) for v in obj)
    return False

Type guard

def fill_invalid_safe(obj):
    """Pre-convert unsupported leaves so fill_invalid never raises."""
    if isinstance(obj, (str, bytes, type(None))):
        return np.asarray(0, dtype=np.int64)
    if isinstance(obj, (int, float, bool)):
        return np.asarray(obj)
    if isinstance(obj, dict):
        return {k: fill_invalid_safe(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple)):
        return type(obj)(fill_invalid_safe(v) for v in obj)
    return obj

Try / catch

try:
    invalid = fill_invalid(obs)
except ValueError as e:
    if "Unsupported value to fill with invalid" in str(e):
        raise TypeError(f"observation leaf not fillable: {e}") from e
    raise

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/cfecf6cf89190fea. Report an issue: GitHub.