Skip to content

Series.__array_ufunc__ can't handle UInt output unless _all_ input have type UInt? #23513

@jongbinjung

Description

@jongbinjung

Checks

  • I have checked that this issue has not already been reported.
  • I have confirmed this bug exists on the latest version of Polars.

Reproducible example

import numpy as np
import polars as pl
from numba import float64, guvectorize, int64, uint64  # using numba to create some example ufuncs

# Random positive floats
rs = np.random.RandomState(420)
N = 100
x = pl.Series(rs.uniform(100, 200, N))
y = pl.Series(rs.uniform(100, 200, N))


# 1. Inputs and outputs are all uint64; works as expected
@guvectorize([(uint64[:], uint64[:], uint64[:])], "(n),(n)->(n)")
def a(arr, arr2, result):
    for i in range(len(arr)):
        result[i] = np.uint(arr[i] + arr2[i])


a(x.cast(pl.UInt64), y.cast(pl.UInt64))
# => Works as expected, returns a Series of uint64


# 2. Inputs are int64, output is uint64; breaks if dtype is not explicitly specified
@guvectorize([(int64[:], int64[:], uint64[:])], "(n),(n)->(n)")
def b(arr, arr2, result):
    for i in range(len(arr)):
        result[i] = np.uint(arr[i] + arr2[i])


b(x.cast(pl.Int64), y.cast(pl.Int64))
# => TypeError: No loop matching the specified signature and casting was found for ufunc b

b(x.cast(pl.Int64), y.cast(pl.Int64), dtype="L")
# => Works as expected, returns a Series of uint64


# 3. Inputs are float64, so no casting necessary, but output is uint64; breaks like the previous case
@guvectorize([(float64[:], float64[:], uint64[:])], "(n),(n)->(n)")
def c(arr, arr2, result):
    for i in range(len(arr)):
        result[i] = np.uint(arr[i] + arr2[i])


c(x, y)
# => TypeError: No loop matching the specified signature and casting was found for ufunc b

c(x, y, dtype="L")
# => Works as expected, returns a Series of uint64


# 4. Maybe it works if at least _one_ input is uint64?
@guvectorize([(uint64[:], float64[:], uint64[:])], "(n),(n)->(n)")
def d(arr, arr2, result):
    for i in range(len(arr)):
        result[i] = np.uint(arr[i] + arr2[i])


d(x.cast(pl.UInt64), y)
# => Still no luck ...

d(x.cast(pl.UInt64), y, dtype="L")
# => Works as expected, returns a Series of uint64

Log output

N/A

Issue description

It seems like a ufunc that returns an UInt type will not have the return type set correctly, and fail with a

TypeError: No loop matching the specified signature and casting was found for ufunc {function_name}

unless dtype is set explicitly. (See Reproducible examples above)

I haven't tried an exhaustive combination of input/output types, so not entirely sure if it's just for UInts, but suspect it might have to do with how the dtype value is inferred here?:

# Get the first ufunc dtype from all possible ufunc dtypes for which
# the input arguments can be safely cast to that ufunc dtype.
for dtype_ufunc in dtypes_ufunc:
if np.can_cast(dtype_char_minimum, dtype_ufunc):
dtype_char_minimum = dtype_ufunc
break
# Override minimum dtype if requested.
dtype_char = (
np.dtype(kwargs.pop("dtype")).char
if "dtype" in kwargs
else dtype_char_minimum
)

For context, this came up for me when I was trying to apply a function from the h3 library that takes latitude, longitude coordinates as doubles, and returns some geographical index as UInt64.

Expected behavior

The ufunc works without having to explicitly set the dtype.

Installed versions

--------Version info---------
Polars:              1.31.0
Index type:          UInt32
Platform:            macOS-15.5-arm64-arm-64bit
Python:              3.11.12 (main, Apr  8 2025, 14:15:29) [Clang 16.0.0 (clang-1600.0.26.6)]
LTS CPU:             False

----Optional dependencies----
Azure CLI            <not installed>
adbc_driver_manager  <not installed>
altair               <not installed>
azure.identity       <not installed>
boto3                <not installed>
cloudpickle          <not installed>
connectorx           <not installed>
deltalake            <not installed>
fastexcel            <not installed>
fsspec               <not installed>
gevent               <not installed>
google.auth          2.40.1
great_tables         <not installed>
matplotlib           <not installed>
numpy                2.2.5
openpyxl             <not installed>
pandas               2.2.3
polars_cloud         <not installed>
pyarrow              20.0.0
pydantic             2.11.4
pyiceberg            <not installed>
sqlalchemy           <not installed>
torch                <not installed>
xlsx2csv             <not installed>
xlsxwriter           <not installed>

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds triageAwaiting prioritization by a maintainerpythonRelated to Python Polars

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions