378 lines
14 KiB
Python
378 lines
14 KiB
Python
from datetime import datetime
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
import torch
|
|
|
|
import ray
|
|
from ray.data._internal.tensor_extensions.utils import create_ragged_ndarray
|
|
from ray.data.context import DataContext
|
|
from ray.data.tests.conftest import * # noqa
|
|
from ray.tests.conftest import * # noqa
|
|
|
|
|
|
class UserObj:
|
|
def __eq__(self, other):
|
|
return isinstance(other, UserObj)
|
|
|
|
|
|
def do_map_batches(data):
|
|
ds = ray.data.range(1)
|
|
ds = ds.map_batches(lambda x: {"output": data})
|
|
return ds.take_batch()["output"]
|
|
|
|
|
|
def assert_structure_equals(a, b):
|
|
assert type(a) is type(b), (type(a), type(b))
|
|
assert type(a[0]) == type(b[0]), (type(a[0]), type(b[0])) # noqa: E721
|
|
assert a.dtype == b.dtype
|
|
assert a.shape == b.shape
|
|
for i in range(len(a)):
|
|
assert np.array_equal(a[i], b[i]), (i, a[i], b[i])
|
|
|
|
|
|
def test_list_of_scalars(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [1, 2, 3]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, np.array([1, 2, 3], dtype=np.int64))
|
|
|
|
|
|
def test_list_of_numpy_scalars(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [np.int64(1), np.int64(2), np.int64(3)]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, np.array([1, 2, 3], dtype=np.int64))
|
|
|
|
|
|
def test_list_of_objects(ray_start_regular_shared, restore_data_context):
|
|
# NOTE: Fallback is enabled by default, this is purely for notational purposes
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = True
|
|
|
|
data = [1, 2, 3, UserObj()]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, np.array([1, 2, 3, UserObj()]))
|
|
|
|
|
|
# Define datetime values with different precisions
|
|
DATETIME_DAY_PRECISION = datetime(year=2024, month=1, day=1)
|
|
DATETIME_HOUR_PRECISION = datetime(year=2024, month=1, day=1, hour=1)
|
|
DATETIME_MIN_PRECISION = datetime(year=2024, month=1, day=1, minute=1)
|
|
DATETIME_SEC_PRECISION = datetime(year=2024, month=1, day=1, second=1)
|
|
DATETIME_MILLISEC_PRECISION = datetime(year=2024, month=1, day=1, microsecond=1000)
|
|
DATETIME_MICROSEC_PRECISION = datetime(year=2024, month=1, day=1, microsecond=1)
|
|
|
|
# Define pandas values for different precisions
|
|
PANDAS_DAY_PRECISION = pd.Timestamp(year=2024, month=1, day=1)
|
|
PANDAS_HOUR_PRECISION = pd.Timestamp(year=2024, month=1, day=1, hour=1)
|
|
PANDAS_MIN_PRECISION = pd.Timestamp(year=2024, month=1, day=1, minute=1)
|
|
PANDAS_SEC_PRECISION = pd.Timestamp(year=2024, month=1, day=1, second=1)
|
|
PANDAS_MILLISEC_PRECISION = pd.Timestamp(year=2024, month=1, day=1, microsecond=1000)
|
|
PANDAS_MICROSEC_PRECISION = pd.Timestamp(year=2024, month=1, day=1, microsecond=1)
|
|
PANDAS_NANOSEC_PRECISION = pd.Timestamp(
|
|
year=2024, month=1, day=1, hour=1, minute=1, second=1
|
|
) + pd.Timedelta(nanoseconds=1)
|
|
|
|
# Define numpy.datetime64 values for comparison
|
|
DATETIME64_DAY_PRECISION = np.datetime64("2024-01-01")
|
|
DATETIME64_HOUR_PRECISION = np.datetime64("2024-01-01T01:00", "s")
|
|
DATETIME64_MIN_PRECISION = np.datetime64("2024-01-01T00:01", "s")
|
|
DATETIME64_SEC_PRECISION = np.datetime64("2024-01-01T00:00:01")
|
|
DATETIME64_MILLISEC_PRECISION = np.datetime64("2024-01-01T00:00:00.001")
|
|
DATETIME64_MICROSEC_PRECISION = np.datetime64("2024-01-01T00:00:00.000001")
|
|
DATETIME64_NANOSEC_PRECISION = np.datetime64("2024-01-01T01:01:01.000000001", "ns")
|
|
|
|
|
|
# Parametrized test to validate datetime values and expected numpy.datetime64 results
|
|
@pytest.mark.parametrize(
|
|
"data,expected_output",
|
|
[
|
|
(
|
|
[DATETIME_DAY_PRECISION],
|
|
np.array([DATETIME64_DAY_PRECISION], dtype="datetime64[s]"),
|
|
),
|
|
([DATETIME_HOUR_PRECISION], np.array([DATETIME64_HOUR_PRECISION])),
|
|
([DATETIME_MIN_PRECISION], np.array([DATETIME64_MIN_PRECISION])),
|
|
([DATETIME_SEC_PRECISION], np.array([DATETIME64_SEC_PRECISION])),
|
|
([DATETIME_MILLISEC_PRECISION], np.array([DATETIME64_MILLISEC_PRECISION])),
|
|
([DATETIME_MICROSEC_PRECISION], np.array([DATETIME64_MICROSEC_PRECISION])),
|
|
(
|
|
[DATETIME_MICROSEC_PRECISION, DATETIME_MILLISEC_PRECISION],
|
|
np.array(
|
|
[DATETIME64_MICROSEC_PRECISION, DATETIME64_MILLISEC_PRECISION],
|
|
dtype="datetime64[us]",
|
|
),
|
|
),
|
|
(
|
|
[DATETIME_SEC_PRECISION, DATETIME_MILLISEC_PRECISION],
|
|
np.array(
|
|
[DATETIME64_SEC_PRECISION, DATETIME64_MILLISEC_PRECISION],
|
|
dtype="datetime64[ms]",
|
|
),
|
|
),
|
|
(
|
|
[DATETIME_DAY_PRECISION, DATETIME_SEC_PRECISION],
|
|
np.array(
|
|
[DATETIME64_DAY_PRECISION, DATETIME64_SEC_PRECISION],
|
|
dtype="datetime64[s]",
|
|
),
|
|
),
|
|
(
|
|
[PANDAS_DAY_PRECISION],
|
|
np.array([DATETIME64_DAY_PRECISION], dtype="datetime64[s]"),
|
|
),
|
|
([PANDAS_HOUR_PRECISION], np.array([DATETIME64_HOUR_PRECISION])),
|
|
([PANDAS_MIN_PRECISION], np.array([DATETIME64_MIN_PRECISION])),
|
|
([PANDAS_SEC_PRECISION], np.array([DATETIME64_SEC_PRECISION])),
|
|
([PANDAS_MILLISEC_PRECISION], np.array([DATETIME64_MILLISEC_PRECISION])),
|
|
([PANDAS_MICROSEC_PRECISION], np.array([DATETIME64_MICROSEC_PRECISION])),
|
|
([PANDAS_NANOSEC_PRECISION], np.array([DATETIME64_NANOSEC_PRECISION])),
|
|
(
|
|
[PANDAS_NANOSEC_PRECISION, PANDAS_MICROSEC_PRECISION],
|
|
np.array(
|
|
[DATETIME64_NANOSEC_PRECISION, DATETIME64_MICROSEC_PRECISION],
|
|
dtype="datetime64[ns]",
|
|
),
|
|
),
|
|
(
|
|
[PANDAS_MICROSEC_PRECISION, PANDAS_MILLISEC_PRECISION],
|
|
np.array(
|
|
[DATETIME64_MICROSEC_PRECISION, DATETIME64_MILLISEC_PRECISION],
|
|
dtype="datetime64[us]",
|
|
),
|
|
),
|
|
(
|
|
[PANDAS_SEC_PRECISION, PANDAS_MILLISEC_PRECISION],
|
|
np.array(
|
|
[DATETIME64_SEC_PRECISION, DATETIME64_MILLISEC_PRECISION],
|
|
dtype="datetime64[ms]",
|
|
),
|
|
),
|
|
(
|
|
[PANDAS_DAY_PRECISION, PANDAS_SEC_PRECISION],
|
|
np.array(
|
|
[DATETIME64_DAY_PRECISION, DATETIME64_SEC_PRECISION],
|
|
dtype="datetime64[s]",
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_list_of_datetimes(
|
|
data, expected_output, ray_start_regular_shared, restore_data_context
|
|
):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, expected_output)
|
|
|
|
|
|
def test_array_like(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = torch.Tensor([1, 2, 3])
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, np.array([1.0, 2.0, 3.0], dtype=np.float32))
|
|
|
|
|
|
def test_list_of_arrays(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [np.array([1, 2, 3]), np.array([4, 5, 6])]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int64))
|
|
|
|
|
|
def test_list_of_array_like(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [torch.Tensor([1, 2, 3]), torch.Tensor([4, 5, 6])]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32))
|
|
|
|
|
|
def test_ragged_tensors_map_batches(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [torch.Tensor([1, 2, 3]), torch.Tensor([1, 2])]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(
|
|
output, create_ragged_ndarray([np.array([1, 2, 3]), np.array([1, 2])])
|
|
)
|
|
|
|
data = [torch.zeros((3, 5, 10)), torch.zeros((3, 8, 8))]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(
|
|
output, create_ragged_ndarray([np.zeros((3, 5, 10)), np.zeros((3, 8, 8))])
|
|
)
|
|
|
|
|
|
def test_scalar_nested_arrays(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [[[1]], [[2]]]
|
|
output = do_map_batches(data)
|
|
|
|
assert_structure_equals(
|
|
output,
|
|
create_ragged_ndarray(
|
|
[np.array([1], dtype=np.object_), np.array([2], dtype=np.object_)]
|
|
),
|
|
)
|
|
|
|
|
|
def test_scalar_lists_not_converted(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [[1, 2], [1, 2]]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(
|
|
output, create_ragged_ndarray([np.array([1, 2]), np.array([1, 2])])
|
|
)
|
|
|
|
data = [[1, 2, 3], [1, 2]]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(
|
|
output, create_ragged_ndarray([np.array([1, 2, 3]), np.array([1, 2])])
|
|
)
|
|
|
|
|
|
def test_scalar_numpy(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = np.int64(1)
|
|
ds = ray.data.range(2, override_num_blocks=1)
|
|
ds = ds.map(lambda x: {"output": data})
|
|
output = ds.take_batch()["output"]
|
|
assert_structure_equals(output, np.array([1, 1], dtype=np.int64))
|
|
|
|
|
|
def test_scalar_arrays(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = np.array([1, 2, 3])
|
|
ds = ray.data.range(2, override_num_blocks=1)
|
|
ds = ds.map(lambda x: {"output": data})
|
|
output = ds.take_batch()["output"]
|
|
assert_structure_equals(output, np.array([[1, 2, 3], [1, 2, 3]], dtype=np.int64))
|
|
|
|
|
|
def test_bytes(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
"""Tests that bytes are converted to object dtype instead of zero-terminated."""
|
|
data = b"\x1a\n\x00\n\x1a"
|
|
ds = ray.data.range(1, override_num_blocks=1)
|
|
ds = ds.map(lambda x: {"output": data})
|
|
output = ds.take_batch()["output"]
|
|
assert_structure_equals(output, np.array([b"\x1a\n\x00\n\x1a"], dtype=object))
|
|
|
|
|
|
def test_uniform_tensors(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = torch.Tensor([1, 2, 3])
|
|
ds = ray.data.range(2, override_num_blocks=1)
|
|
ds = ds.map(lambda x: {"output": data})
|
|
output = ds.take_batch()["output"]
|
|
assert_structure_equals(output, np.array([[1, 2, 3], [1, 2, 3]], dtype=np.float32))
|
|
|
|
|
|
def test_scalar_ragged_arrays(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [np.array([1, 2, 3]), np.array([1, 2])]
|
|
ds = ray.data.range(2, override_num_blocks=1)
|
|
ds = ds.map(lambda x: {"output": data[x["id"]]})
|
|
output = ds.take_batch()["output"]
|
|
|
|
assert_structure_equals(
|
|
output, np.array([np.array([1, 2, 3]), np.array([1, 2])], dtype=object)
|
|
)
|
|
|
|
|
|
def test_ragged_tensors(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [torch.Tensor([1, 2, 3]), torch.Tensor([1, 2])]
|
|
ds = ray.data.range(2, override_num_blocks=1)
|
|
ds = ds.map(lambda x: {"output": data[x["id"]]})
|
|
output = ds.take_batch()["output"]
|
|
assert_structure_equals(
|
|
output, np.array([np.array([1, 2, 3]), np.array([1, 2])], dtype=object)
|
|
)
|
|
|
|
data = [torch.zeros((3, 5, 10)), torch.zeros((3, 8, 8))]
|
|
ds = ray.data.range(2, override_num_blocks=1)
|
|
ds = ds.map(lambda x: {"output": data[x["id"]]})
|
|
output = ds.take_batch()["output"]
|
|
assert_structure_equals(
|
|
output, create_ragged_ndarray([np.zeros((3, 5, 10)), np.zeros((3, 8, 8))])
|
|
)
|
|
|
|
|
|
def test_nested_ragged_arrays(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [
|
|
{"a": [[1], [2, 3]]},
|
|
{"a": [[4, 5], [6]]},
|
|
]
|
|
|
|
def f(row):
|
|
return data[row["id"]]
|
|
|
|
output = ray.data.range(2).map(f).take_all()
|
|
assert output == data
|
|
|
|
|
|
# https://github.com/ray-project/ray/issues/35340
|
|
def test_complex_ragged_arrays(ray_start_regular_shared, restore_data_context):
|
|
# Disable (automatic) fallback to `ArrowPythonObjectType` extension type
|
|
DataContext.get_current().enable_fallback_to_arrow_object_ext_type = False
|
|
|
|
data = [[{"a": 1}, {"a": 2}, {"a": 3}], [{"b": 1}]]
|
|
output = do_map_batches(data)
|
|
|
|
# Assert resulting objects are coerced to appropriate shape, following
|
|
# table's schema
|
|
assert_structure_equals(
|
|
output,
|
|
create_ragged_ndarray(
|
|
[
|
|
np.array(
|
|
[{"a": 1, "b": None}, {"a": 2, "b": None}, {"a": 3, "b": None}]
|
|
),
|
|
np.array([{"a": None, "b": 1}]),
|
|
]
|
|
),
|
|
)
|
|
|
|
data = ["hi", 1, None, [[[[]]]], {"a": [[{"b": 2, "c": UserObj()}]]}, UserObj()]
|
|
output = do_map_batches(data)
|
|
assert_structure_equals(output, create_ragged_ndarray(data))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
sys.exit(pytest.main(["-v", __file__]))
|