diff --git a/optuna_dashboard/_cached_extra_study_property.py b/optuna_dashboard/_cached_extra_study_property.py index 2f27fa64..1e4fb1a7 100644 --- a/optuna_dashboard/_cached_extra_study_property.py +++ b/optuna_dashboard/_cached_extra_study_property.py @@ -2,12 +2,14 @@ from __future__ import annotations import copy import threading +from typing import Any from typing import List from typing import Optional from typing import Set from typing import Tuple from typing import TYPE_CHECKING +import numpy as np from optuna.distributions import BaseDistribution from optuna.trial import FrozenTrial from optuna.trial import TrialState @@ -84,12 +86,11 @@ class _CachedExtraStudyProperty: self._cursor = next_cursor + def _is_sortable_value(self, v: Any) -> bool: + return not isinstance(v, bool) and isinstance(v, (int, float, np.integer, np.floating)) + def _update_user_attrs(self, trial: FrozenTrial) -> None: - # TODO(c-bata): Support numpy-specific number types. - current_user_attrs = { - k: not isinstance(v, bool) and isinstance(v, (int, float)) - for k, v in trial.user_attrs.items() - } + current_user_attrs = {k: self._is_sortable_value(v) for k, v in trial.user_attrs.items()} for attr_name, current_is_sortable in current_user_attrs.items(): is_sortable = self._union_user_attrs.get(attr_name) if is_sortable is None: diff --git a/optuna_dashboard/_serializer.py b/optuna_dashboard/_serializer.py index b5a3b305..9b0cce64 100644 --- a/optuna_dashboard/_serializer.py +++ b/optuna_dashboard/_serializer.py @@ -104,6 +104,8 @@ def serialize_attrs(attrs: dict[str, Any]) -> list[Attribute]: value = "" elif isinstance(v, str): value = v + elif isinstance(v, (np.floating, np.integer)): + value = v.item() else: value = json.dumps(v) value = value[:MAX_ATTR_LENGTH] if len(value) > MAX_ATTR_LENGTH else value diff --git a/python_tests/test_cached_extra_study_property.py b/python_tests/test_cached_extra_study_property.py index c02006a7..dcd5bc5e 100644 --- a/python_tests/test_cached_extra_study_property.py +++ b/python_tests/test_cached_extra_study_property.py @@ -4,6 +4,7 @@ from typing import Any from unittest import TestCase import warnings +import numpy as np import optuna from optuna import create_trial from optuna.distributions import BaseDistribution @@ -254,11 +255,29 @@ class _CachedExtraStudyPropertyUserAttrs(TestCase): def test_infer_sortable(self) -> None: user_attrs_list: list[dict[str, Any]] = [ - {"a": 1, "b": 1, "c": 1, "d": "a", "e": 1, "f": True}, + { + "a": 1, + "b": 1, + "c": 1, + "d": "a", + "e": 1, + "f": True, + "g": np.float128(1.1), + "h": np.int64(2), + }, {"a": 2, "b": "a", "c": "a", "d": "a"}, {"a": 3, "b": None, "c": 3, "d": "a", "e": 3}, ] - expected = {"a": True, "b": False, "c": False, "d": False, "e": True, "f": False} + expected = { + "a": True, + "b": False, + "c": False, + "d": False, + "e": True, + "f": False, + "g": True, + "h": True, + } trials = [] for user_attrs in user_attrs_list: diff --git a/python_tests/test_serializers.py b/python_tests/test_serializers.py index 0d1546d2..8c64c5d8 100644 --- a/python_tests/test_serializers.py +++ b/python_tests/test_serializers.py @@ -2,6 +2,7 @@ from __future__ import annotations import sys +import numpy as np import optuna from optuna_dashboard._serializer import serialize_attrs from optuna_dashboard._serializer import serialize_study_detail @@ -25,6 +26,32 @@ def test_serialize_dict() -> None: assert len(serialized) <= 1 +def test_serialize_numpy_integer() -> None: + serialized = serialize_attrs( + { + "int8": np.int8(1), + "int16": np.int16(1), + "int32": np.int32(1), + "int64": np.int64(1), + } + ) + assert len(serialized) == 4 + assert all([v["value"] == 1 for v in serialized]) + + +def test_serialize_numpy_floating() -> None: + serialized = serialize_attrs( + { + "float16": np.float16(1.0), + "float32": np.float32(1.0), + "float64": np.float64(1.0), + "float128": np.float128(1.0), + } + ) + assert len(serialized) == 4 + assert all([v["value"] == 1.0 for v in serialized]) + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="BoTorch dropped Python3.7 support") def test_get_study_detail_is_preferential() -> None: storage = optuna.storages.InMemoryStorage()