Skip to content

Commit

Permalink
fix: Refrains from using np.ndarray for nested objects (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
eddiebergman authored Jul 24, 2024
1 parent 5c1974b commit 938c702
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 5 deletions.
5 changes: 2 additions & 3 deletions src/ConfigSpace/api/types/categorical.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from __future__ import annotations

from collections.abc import Sequence
from typing import Literal, Union, overload
from typing_extensions import TypeAlias
from typing import Literal, TypeVar, overload

from ConfigSpace.hyperparameters import CategoricalHyperparameter, OrdinalHyperparameter
from ConfigSpace.types import NotSet, _NotSet

# We only accept these types in `items`
T: TypeAlias = Union[str, int, float]
T = TypeVar("T")


# ordered False -> CategoricalHyperparameter
Expand Down
3 changes: 3 additions & 0 deletions src/ConfigSpace/hyperparameters/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,13 @@ def __init__(
# This can fail with a ValueError if the choices contain arbitrary objects
# that are list like.
seq_choices = np.asarray(choices)
if seq_choices.ndim != 1:
raise ValueError

# NOTE: Unfortunatly, numpy will promote number types to str
# if there are string types in the array, where we'd rather
# stick to object type in that case. Hence the manual...
print(seq_choices)
if seq_choices.dtype.kind in {"U", "S"} and not all(
isinstance(choice, str) for choice in choices
):
Expand Down
9 changes: 9 additions & 0 deletions src/ConfigSpace/hyperparameters/hp_components.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Protocol, TypeVar
from typing_extensions import override
Expand Down Expand Up @@ -202,6 +203,8 @@ def to_vector(self, value: Array[Any]) -> Array[f64]:

@override
def legal_value(self, value: Array[Any]) -> Mask:
print(value)
print(value, self.seq)
if self._lookup is not None:
return np.array([v in self._lookup for v in value], dtype=np.bool_)

Expand Down Expand Up @@ -513,6 +516,12 @@ def to_vector(self, value: ObjectArray) -> Array[f64]:

@override
def to_value(self, vector: Array[f64]) -> ObjectArray:
if isinstance(self.value, Sequence) and not isinstance(self.value, str):
# We have to convert it into a numpy array of objects carefully
# https://stackoverflow.com/a/47389566/5332072
_v = np.empty(len(vector), dtype=object)
_v[:] = [self.value] * len(vector)
return _v
return np.full_like(vector, self.value, dtype=object)

@override
Expand Down
2 changes: 1 addition & 1 deletion src/ConfigSpace/hyperparameters/hyperparameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def __init__(
self._vector_dist = vector_dist
self._transformer = transformer
self._neighborhood = neighborhood
self._neighborhood_size = neighborhood_size
self._neighborhood_size = neighborhood_size # type: ignore
self._value_cast = value_cast

if not self.legal_value(self.default_value):
Expand Down
4 changes: 3 additions & 1 deletion src/ConfigSpace/hyperparameters/ordinal.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ def __init__(
# This can fail with a ValueError if the choices contain arbitrary objects
# that are list like.
seq_choices = np.asarray(sequence)
if seq_choices.ndim != 1:
raise ValueError

# NOTE: Unfortunatly, numpy will promote number types to str
# if there are string types in the array, where we'd rather
Expand Down Expand Up @@ -228,7 +230,7 @@ def pdf_values(self, values: Sequence[Any] | Array[Any]) -> Array[f64]:
if values.ndim != 1:
raise ValueError("Method pdf expects a one-dimensional numpy array")

vector = self.to_vector(values)
vector = self.to_vector(values) # type: ignore
return self.pdf_vector(vector)

if self._contains_sequence_as_value:
Expand Down
16 changes: 16 additions & 0 deletions test/test_hyperparameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def test_constant():
# Test getting the size
for constant in (c1, c2, c3, c4, c5, c1_meta, c6, c7, c8):
assert constant.size == 1
_ = str(constant) # Ensure str repr works

with pytest.raises(ValueError):
_ = Constant("value", np.array([1, 2]))
Expand Down Expand Up @@ -268,6 +269,7 @@ def test_uniformfloat():
# Test get_size
for float_hp in (f1, f3, f4, f5):
assert np.isinf(float_hp.size)
_ = str(float_hp) # Ensure str repr works


def test_uniformfloat_to_integer():
Expand Down Expand Up @@ -575,6 +577,7 @@ def test_normalfloat():
# Test get_size
for float_hp in (f1, f2, f6):
assert np.isinf(float_hp.size)
_ = str(float_hp) # Ensure str repr works

with pytest.raises(ValueError):
_ = NormalFloatHyperparameter(
Expand Down Expand Up @@ -2147,6 +2150,11 @@ def test_categorical():
assert f5.size == 1000
assert f6.size == 2

fn = CategoricalHyperparameter("param", [("a", "b"), [0, 1]])
assert fn.default_value == ("a", "b")
assert fn.legal_value(("a", "b"))
_ = str(fn)


def test_cat_equal():
# Test that weights are properly normalized and compared
Expand Down Expand Up @@ -2756,6 +2764,14 @@ def test_ordinal_is_legal():
assert not f1.legal_vector("Hahaha") # type: ignore


def test_ordinal_nested_lists_prints_correctly():
f1 = OrdinalHyperparameter(
"temp",
[["freezing", "cold", "warm", "hot"], ["a", "b"]],
)
_ = str(f1)


def test_ordinal_check_order():
f1 = OrdinalHyperparameter("temp", ["freezing", "cold", "warm", "hot"])
assert f1.check_order("freezing", "cold")
Expand Down

0 comments on commit 938c702

Please sign in to comment.