pola-rs/polars · error

only ufuncs that return one 1D array are supported

Error message

only ufuncs that return one 1D array are supported

What it means

Raised in Series.__array_ufunc__ when the numpy ufunc being applied has more than one output array (ufunc.nout != 1). Polars' dispatch only supports single-output elementwise ufuncs, because it routes the computation through a single Rust apply_ufunc_ kernel that must map one input Series to one output Series.

Source

Thrown at py-polars/src/polars/series/series.py:1632

                raise RuntimeError(msg)

            arr = arr.__array__(dtype)

        return arr

    def __array_ufunc__(
        self, ufunc: np.ufunc, method: str_, *inputs: Any, **kwargs: Any
    ) -> Series:
        """Numpy universal functions."""
        if self._s.n_chunks() > 1:
            self._s.rechunk(in_place=True)

        s = self._s

        if method == "__call__":
            if ufunc.nout != 1:
                msg = "only ufuncs that return one 1D array are supported"
                raise NotImplementedError(msg)

            args: list[int | float | np.ndarray[Any, Any]] = []
            for arg in inputs:
                if isinstance(arg, (int, float, np.ndarray)):
                    args.append(arg)
                elif isinstance(arg, Series):
                    phys_arg = arg.to_physical()
                    if phys_arg._s.n_chunks() > 1:
                        phys_arg._s.rechunk(in_place=True)
                    args.append(phys_arg._s.to_numpy_view())  # type: ignore[arg-type]
                else:
                    msg = f"unsupported type {qualified_type_name(arg)!r} for {arg!r}"
                    raise TypeError(msg)

            # Get minimum dtype needed to be able to cast all input arguments to the
            # same dtype.
            dtype_char_minimum: str = np.result_type(*args).char

View on GitHub (pinned to df599052da)

Solutions

  1. Replace with two single-output operations: np.divmod(s, k) -> `(s // k, s % k)`; np.modf(s) -> `(s - s.cast(pl.Float64).floor(), s.floor())`; np.frexp(s) -> use `np.frexp(s.to_numpy())`.
  2. Detach to numpy when you truly need multi-output: `mantissa, exponent = np.frexp(s.to_numpy())`.
  3. Check ufunc.nout before generic dispatch in library code: `if ufunc.nout != 1: fall back to to_numpy()`.

Example fix

// before
q, r = np.divmod(s, 7)  # NotImplementedError

// after
q, r = s // 7, s % 7
# or
q, r = np.divmod(s.to_numpy(), 7)
Defensive patterns

Strategy: type-guard

Validate before calling

if getattr(ufunc, 'nout', 1) != 1:
    result = ufunc(*[a.to_numpy() if isinstance(a, pl.Series) else a for a in args])
else:
    result = ufunc(*args)

Type guard

def is_single_output_ufunc(ufunc: np.ufunc) -> bool:
    return ufunc.nout == 1

Try / catch

try:
    q, r = np.divmod(s, k)
except NotImplementedError:
    q, r = s // k, s % k

Prevention

When it happens

Trigger: `np.divmod(s, 2)` (nout=2), `np.modf(s)` (nout=2), `np.frexp(s)` (nout=2) on a Series. Single-output ufuncs like np.exp/np.add are unaffected; the check fires only for method == '__call__' with multi-output ufuncs.

Common situations: Quotient/remainder computed together via np.divmod; mantissa/exponent splits via np.frexp in signal processing; fractional/int part splits via np.modf - all called on Series inside pandas-style pipelines.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/f7eb784141c2e68d. Report an issue: GitHub.