Repository: automl/DEHB

Issue:
No support for Constant type in vector_to_configspace() and configspace_to_vector()
Hello,

I've been working with DEHB on my dataset and came across an issue with the processing of ConfigSpace in the `de.py` file. Specifically, in the functions `vector_to_configspace()` and `configspace_to_vector()`, there seems to be no option or support for handling the ConfigSpace `Constant` type.

Is this an oversight or intentional? If it's the latter, should I avoid including the `Constant` type in my ConfigSpace or is there a recommended workaround?

Thanks for your assistance!

Discussion:

Patch:
diff --git a/src/dehb/optimizers/de.py b/src/dehb/optimizers/de.py
index 4c27166..d1227f5 100644
--- a/src/dehb/optimizers/de.py
+++ b/src/dehb/optimizers/de.py
@@ -160,6 +160,8 @@ class DEBase():
             elif type(hyper) == ConfigSpace.CategoricalHyperparameter:
                 ranges = np.arange(start=0, stop=1, step=1/len(hyper.choices))
                 param_value = hyper.choices[np.where((vector[i] < ranges) == False)[0][-1]]
+            elif type(hyper) == ConfigSpace.Constant:
+                param_value = hyper.default_value
             else:  # handles UniformFloatHyperparameter & UniformIntegerHyperparameter
                 # rescaling continuous values
                 if hyper.log:
@@ -199,6 +201,8 @@ class DEBase():
             elif type(hyper) == ConfigSpace.CategoricalHyperparameter:
                 nlevels = len(hyper.choices)
                 vector[i] = hyper.choices.index(config[name]) / nlevels
+            elif type(hyper) == ConfigSpace.Constant:
+                vector[i] = 0 # set constant to 0, so that it wont be affected by mutation
             else:
                 bounds = (hyper.lower, hyper.upper)
                 param_value = config[name]


Test patch:
diff --git a/tests/test_de.py b/tests/test_de.py
new file mode 100644
index 0000000..f64457b
--- /dev/null
+++ b/tests/test_de.py
@@ -0,0 +1,120 @@
+import typing
+
+import ConfigSpace
+import pytest
+from src.dehb.optimizers.de import DEBase
+
+def create_toy_DEBase(configspace: ConfigSpace.ConfigurationSpace):
+    """Creates a toy DEBase instance for conversion tests.
+
+    Args:
+        configspace (ConfigurationSpace): Searchspace to use
+
+    Returns:
+        DEBase: DEBase object for testing
+    """
+    dim = len(configspace.get_hyperparameters())
+    return DEBase(f=lambda: 1, cs=configspace, dimensions=dim, pop_size=10, max_age=5,
+                  mutation_factor=0.5, crossover_prob=0.5, strategy="rand1_bin", budget=1)
+
+class TestConversion():
+    """Class that bundles all ConfigSpace/vector conversion tests.
+
+    These tests include conversion tests for constant, categorical, ordinal,
+    float and integer hyperparameters.
+    """
+    def test_constant(self):
+        """Test for constant hyperparameter."""
+        cs = ConfigSpace.ConfigurationSpace(
+            space={
+                "test_const": "constant",
+            },
+        )
+
+        de = create_toy_DEBase(cs)
+
+        test_config = cs.sample_configuration()
+
+        vector = de.configspace_to_vector(test_config)
+        assert vector[0] == 0
+
+        converted_conf = de.vector_to_configspace(vector)
+        assert converted_conf == test_config
+
+    def test_categorical(self):
+        """Test for categorical hyperparameter."""
+        cs = ConfigSpace.ConfigurationSpace(
+            space={
+                "test_categorical": ["a", "b", "c"],
+            },
+        )
+
+        de = create_toy_DEBase(cs)
+
+        test_config = cs.sample_configuration()
+
+        vector = de.configspace_to_vector(test_config)
+
+        assert vector[0] <= 1
+        assert vector[0] >= 0
+
+        converted_conf = de.vector_to_configspace(vector)
+        assert converted_conf == test_config
+
+    def test_ordinal(self):
+        """Test for ordinal hyperparameter."""
+        cs = ConfigSpace.ConfigurationSpace()
+        cs.add_hyperparameter(
+            ConfigSpace.OrdinalHyperparameter("test_ordinal", sequence=[10, 20, 30]))
+
+        de = create_toy_DEBase(cs)
+
+        test_config = cs.sample_configuration()
+
+        vector = de.configspace_to_vector(test_config)
+
+        assert vector[0] <= 1
+        assert vector[0] >= 0
+
+        converted_conf = de.vector_to_configspace(vector)
+        assert converted_conf == test_config
+
+    def test_float(self):
+        """Test for float hyperparameter."""
+        cs = ConfigSpace.ConfigurationSpace(
+            space={
+                "test_float": (1.0, 10.0),
+            },
+        )
+
+        de = create_toy_DEBase(cs)
+
+        test_config = cs.sample_configuration()
+
+        vector = de.configspace_to_vector(test_config)
+
+        assert vector[0] <= 1
+        assert vector[0] >= 0
+
+        converted_conf = de.vector_to_configspace(vector)
+        assert converted_conf == test_config
+
+    def test_integer(self):
+        """Test for integer hyperparameter."""
+        cs = ConfigSpace.ConfigurationSpace(
+            space={
+                "test_int": (1, 10),
+            },
+        )
+
+        de = create_toy_DEBase(cs)
+
+        test_config = cs.sample_configuration()
+
+        vector = de.configspace_to_vector(test_config)
+
+        assert vector[0] <= 1
+        assert vector[0] >= 0
+
+        converted_conf = de.vector_to_configspace(vector)
+        assert converted_conf == test_config
\ No newline at end of file