diff --git a/src/ConfigSpace/configuration_space.py b/src/ConfigSpace/configuration_space.py index 9652e385..fbea8808 100644 --- a/src/ConfigSpace/configuration_space.py +++ b/src/ConfigSpace/configuration_space.py @@ -895,6 +895,19 @@ def substitute_hyperparameters_in_forbiddens( return new_forbiddens + def __copy__(self) -> ConfigurationSpace: + """Creates a copy of the ConfigurationSpace.""" + cs_copy = ConfigurationSpace( + name=self.name, + meta=self.meta.copy(), + space={hp.name: copy.copy(hp) for hp in self.values()}, + ) + for condition in self.conditions: + cs_copy.add(copy.copy(condition)) + for forbidden in self.forbidden_clauses: + cs_copy.add(copy.copy(forbidden)) + return cs_copy + def __eq__(self, other: Any) -> bool: """Override the default Equals behavior.""" if isinstance(other, self.__class__): diff --git a/test/test_configuration_space.py b/test/test_configuration_space.py index e6429c55..708a990f 100644 --- a/test/test_configuration_space.py +++ b/test/test_configuration_space.py @@ -1482,3 +1482,30 @@ def test_configuration_space_can_be_made_with_sequence_of_hyperparameters() -> N assert len(cs) == 2 assert "a" in cs assert "b" in cs + + +def test_copy() -> None: + import copy + + cs = ConfigurationSpace( + name="myspace", + meta={"hello": "world"}, + space=[ + Float("a", (1.0, 10.0)), + Integer("b", (1, 10)), + CategoricalHyperparameter("c", ["a", "b", "c"]), + ], + ) + cs.add(EqualsCondition(cs["b"], cs["c"], "a")) + cs.add(ForbiddenEqualsClause(cs["a"], 4.2)) + + cs_copy = copy.copy(cs) + assert cs == cs_copy + + # Verify that removing components from the copy + cs_copy.remove(cs["a"]) + assert cs != cs_copy + + # Re adding the hyperparameter will not bring forbidden back + cs_copy.add(cs["a"]) + assert cs != cs_copy