ConfigSpace
                                
                                 ConfigSpace copied to clipboard
                                
                                    ConfigSpace copied to clipboard
                            
                            
                            
                        Create easy way to remove hyperparameters
YAHPOBench has the task id in it's configuration as a hyperparamter which is not something you would like any optimizer to know about. Should be an easy way to remove a hyperparamter by name.
If this violates some conditional where the remove hyperparamter is what is being condition on, i.e. a is active when b is x and we remove b from the space. Then we need to remove this condition too. Likewise for forbidden clauses.
Currently just removing the task id manually and updating the cache as it's not a dependant of anything.
Current hack:
def remove_hyperparameter(name: str, space: ConfigurationSpace) -> None:
    """Removes a hyperparameter from a configuration space
    Essentially undoes the operations done by adding a hyperparamter
    and then runs the same validation checks that is done in ConfigSpace
    NOTE
    ----
    * Doesn't account for conditionals
    Parameters
    ----------
    name : str
        The name of the hyperparamter to remove
    space : ConfigurationSpace
        The space to remove it from
    """
    if name not in space._hyperparameters:
        raise ValueError(f"{name} not in {space}")
    assert name not in space._conditionals, "Can't handle conditionals"
    assert not any(
        name != f.hyperparameter.name for f in space.get_forbiddens()
    ), "Can't handle forbiddens"
    # No idea what this is really for
    root = "__HPOlib_configuration_space_root__"
    # Remove it from children
    if root in space._children and name in space._children[root]:
        del space._children[root][name]
    # Remove it from parents
    if root in space._parents and name in space._parents[root]:
        del space._parents[root][name]
    # Remove it from indices
    if name in space._hyperparameter_idx:
        del space._hyperparameter_idx[name]
        # We re-enumerate the dict
        space._hyperparameter_idx = {
            name: idx for idx, name in enumerate(space._hyperparameter_idx)
        }
    # Finally, remove it from the known parameter
    del space._hyperparameters[name]
    # Update according to what adding does `add_hyperparameter()`
    space._update_cache()
    space._check_default_configuration()  # TODO: Get sporadic failures here?
    space._sort_hyperparameters()
    return
Hey, this would also require to remove it from conditions and forbiddens, or to remove conditions and forbiddens if hyperparameters are in there.
True, good spot and thank you!
I just added the following two assertions for now until I can figure out how to do it properly:
    assert name not in space._conditionals, "Can't handle conditionals"
    assert not any(
        name != f.hyperparameter.name for f in space.get_forbiddens()
    ), "Can't handle forbiddens"
Turns out the above hack is stochastic? The _check_default_configuration seems to randomly fail 1/4 of the time. I just get the error:
mfpbench/yahpo/benchmark.py:94: in __init__
    remove_hyperparameter("OpenML_task_id", space)
mfpbench/util.py:79: in remove_hyperparameter
    space._check_default_configuration()
ConfigSpace/configuration_space.pyx:1068: in ConfigSpace.configuration_space.ConfigurationSpace._check_default_configuration
    ???
ConfigSpace/configuration_space.pyx:1465: in ConfigSpace.configuration_space.Configuration.__init__
    ???
ConfigSpace/configuration_space.pyx:1495: in ConfigSpace.configuration_space.Configuration.is_valid_configuration
    ???
ConfigSpace/c_util.pyx:38: in ConfigSpace.c_util.check_configuration
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
>   ???
E   IndexError: index 7 is out of bounds for axis 0 with size 7
I think I will go with a manual copy over everything except the one param I want removed.
The copy approach for the record:
def remove_hyperparameter(name: str, space: ConfigurationSpace) -> ConfigurationSpace:
    """A new configuration space with the hyperparameter removed
    Essentially copies hp over and fails if there is conditionals or forbiddens
    """
    if name not in space._hyperparameters:
        raise ValueError(f"{name} not in {space}")
    # Copying conditionals only work on objects and not named entities
    # Seeing as we copy objects and don't use the originals, transfering these
    # to the new objects is a bit tedious, possible but not required at this time
    # ... same goes for forbiddens
    assert name not in space._conditionals, "Can't handle conditionals"
    assert not any(
        name != f.hyperparameter.name for f in space.get_forbiddens()
    ), "Can't handle forbiddens"
    hps = [copy(hp) for hp in space.get_hyperparameters() if hp.name != name]
    if isinstance(space.random, np.random.RandomState):
        new_seed = space.random.randint(2 ** 32 - 1)
    else:
        new_seed = copy(space.random)
    new_space = ConfigurationSpace(
        # TODO: not sure if this will have implications, assuming not
        seed=new_seed,
        name=copy(space.name),
        meta=copy(space.meta),
    )
    new_space.add_hyperparameters(hps)
    return new_space
+1 for this feature handling removing conditions and stuff like that!