From 9f964f00344ef9be1e99ce25a26274f5ff2620ba Mon Sep 17 00:00:00 2001 From: Dennis Jabs Date: Wed, 15 Nov 2023 11:18:36 +0100 Subject: [PATCH] D. Jabs: - Optimized imports - Now shape can only be a tuple and not an int anymore --- PyHyperparameterSpace/configuration.py | 5 +- PyHyperparameterSpace/dist/abstract_dist.py | 2 - PyHyperparameterSpace/dist/categorical.py | 2 +- PyHyperparameterSpace/dist/continuous.py | 1 + PyHyperparameterSpace/hp/abstract_hp.py | 22 ++-- PyHyperparameterSpace/hp/categorical.py | 71 ++++++----- PyHyperparameterSpace/hp/constant.py | 56 ++++----- PyHyperparameterSpace/hp/continuous.py | 129 +++++++++----------- PyHyperparameterSpace/space.py | 5 +- tests/hp/test_categorical.py | 26 +++- tests/hp/test_constant.py | 5 + tests/hp/test_float.py | 7 +- tests/hp/test_integer.py | 7 +- tests/test_configuration.py | 20 +-- tests/test_space.py | 15 ++- 15 files changed, 194 insertions(+), 179 deletions(-) diff --git a/PyHyperparameterSpace/configuration.py b/PyHyperparameterSpace/configuration.py index 98f134a..4c3e948 100644 --- a/PyHyperparameterSpace/configuration.py +++ b/PyHyperparameterSpace/configuration.py @@ -1,7 +1,4 @@ -from typing import Mapping, Iterator, Any, Union -import numpy as np -import yaml -import json +from typing import Any, Iterator, Mapping class HyperparameterConfiguration(Mapping[str, Any]): diff --git a/PyHyperparameterSpace/dist/abstract_dist.py b/PyHyperparameterSpace/dist/abstract_dist.py index 12fb2ef..8d44ec8 100644 --- a/PyHyperparameterSpace/dist/abstract_dist.py +++ b/PyHyperparameterSpace/dist/abstract_dist.py @@ -1,6 +1,4 @@ from abc import ABC, abstractmethod -from typing import Any, Union, Iterable -import numpy as np class Distribution(ABC): diff --git a/PyHyperparameterSpace/dist/categorical.py b/PyHyperparameterSpace/dist/categorical.py index 391b857..99206f4 100644 --- a/PyHyperparameterSpace/dist/categorical.py +++ b/PyHyperparameterSpace/dist/categorical.py @@ -1,6 +1,6 @@ from typing import Union -import numpy as np +import numpy as np from PyHyperparameterSpace.dist.abstract_dist import Distribution diff --git a/PyHyperparameterSpace/dist/continuous.py b/PyHyperparameterSpace/dist/continuous.py index a5475db..59526aa 100644 --- a/PyHyperparameterSpace/dist/continuous.py +++ b/PyHyperparameterSpace/dist/continuous.py @@ -1,4 +1,5 @@ from typing import Union + import numpy as np from PyHyperparameterSpace.dist.abstract_dist import Distribution diff --git a/PyHyperparameterSpace/hp/abstract_hp.py b/PyHyperparameterSpace/hp/abstract_hp.py index b433673..df5f1e6 100644 --- a/PyHyperparameterSpace/hp/abstract_hp.py +++ b/PyHyperparameterSpace/hp/abstract_hp.py @@ -1,9 +1,7 @@ from abc import ABC, abstractmethod -from typing import Union, Iterable, Any -import numpy as np +from typing import Any, Iterable, Union -from PyHyperparameterSpace.dist.abstract_dist import Distribution -from PyHyperparameterSpace.dist.continuous import Normal +import numpy as np class Hyperparameter(ABC): @@ -25,7 +23,7 @@ def __init__( self, name: str, default: Any = None, - shape: Union[int, tuple[int, ...], None] = None, + shape: Union[tuple[int, ...], None] = None, ): if isinstance(default, list): default = np.array(default) @@ -55,10 +53,10 @@ def get_default(self) -> Any: """ return self._default - def get_shape(self) -> Union[int, tuple[int, ...], None]: + def get_shape(self) -> tuple[int, ...]: """ Returns: - Union[int, tuple[int, ...], None]: + tuple[int, ...]: Shape of the hyperparameter """ return self._shape @@ -104,7 +102,7 @@ def _is_legal_default(self, default: Any) -> bool: pass @abstractmethod - def _check_shape(self, shape: Union[int, tuple[int, ...], None]) -> Union[int, tuple[int, ...], None]: + def _check_shape(self, shape: Union[tuple[int, ...], None]) -> tuple[int, ...]: """ Checks if the given shape is legal. A shape is called legal if it fulfills the format (...) - (dim1, dim2, ...) @@ -118,13 +116,13 @@ def _check_shape(self, shape: Union[int, tuple[int, ...], None]) -> Union[int, t Shape to check Returns: - Union[int, tuple[int, ...], None]: + tuple[int, ...]: Legal shape """ pass @abstractmethod - def _is_legal_shape(self, shape: Union[int, tuple[int, ...], None]) -> bool: + def _is_legal_shape(self, shape: tuple[int, ...]) -> bool: """ Returns true if the given shape fulfills the format (...) - (dim1, dim2, ...) @@ -134,7 +132,7 @@ def _is_legal_shape(self, shape: Union[int, tuple[int, ...], None]) -> bool: and has the same dimension as the given default value. Args: - shape (Union[int, tuple[int, ...], None]): + shape (tuple[int, ...]): Shape to check Returns: @@ -206,7 +204,7 @@ def __setstate__(self, state): def _get_sample_size( cls, size: Union[int, None] = None, - shape: Union[int, tuple[int, ...]] = None, + shape: Union[tuple[int, ...], None] = None, ) -> Union[int, tuple[int], None]: """ Returns the resulting shape of the sample, according to size and shape. diff --git a/PyHyperparameterSpace/hp/categorical.py b/PyHyperparameterSpace/hp/categorical.py index afcfe70..a26436b 100644 --- a/PyHyperparameterSpace/hp/categorical.py +++ b/PyHyperparameterSpace/hp/categorical.py @@ -1,4 +1,5 @@ -from typing import Union, Iterable, Any +from typing import Any, Union + import numpy as np from PyHyperparameterSpace.dist.abstract_dist import Distribution @@ -29,7 +30,7 @@ def __init__( name: str, choices: list[Any], default: Union[Any, None] = None, - shape: Union[int, tuple[int, ...], None] = None, + shape: Union[tuple[int, ...], None] = None, distribution: Union[Distribution, None] = None, ): if isinstance(choices, list): @@ -76,7 +77,7 @@ def _check_choices(self, choices: list[Any]) -> list[Any]: if self._is_legal_choices(choices): return choices else: - raise Exception(f"Illegal choices {choices}!") + raise Exception(f"Illegal choices {choices}. The argument should be a list or an np.ndarray!") def _is_legal_choices(self, choices: Union[list[Any], None]) -> bool: """ @@ -96,53 +97,49 @@ def _is_legal_choices(self, choices: Union[list[Any], None]) -> bool: return False def _check_default(self, default: Union[Any, None]) -> Any: - if default is None: - if self._distribution is not None: - # Case: Take the option with the highest probability as default value - return self._choices[np.argmax(self._distribution.weights)] - else: - # Case: Take the first option as default value - return self._choices[0] + if default is None and self._distribution is not None: + # Case: Take the option with the highest probability as default value + return self._choices[np.argmax(self._distribution.weights)] + elif default is None and self._distribution is None: + # Case: Take the first option as default value + return self._choices[0] elif self._is_legal_default(default): + # Case: default is given and legal return default else: - raise Exception(f"Illegal default {default}!") + # Case: default is illegal + raise Exception(f"Illegal default {default}. The argument should be given in choices!") def _is_legal_default(self, default: Union[Any, None]) -> bool: if isinstance(default, (list, np.ndarray)): + # Case: default is a vector/matrix value return any(np.array_equal(default, choice) for choice in self._choices) else: + # Case: default is a single value return default in self._choices - def _check_shape(self, shape: Union[int, tuple[int, ...], None]) -> Union[int, tuple[int, ...], None]: - if shape is None: - # Case: Adjust the shape according to the given default value - if self._default is None or isinstance(self._default, (int, float, bool, str)): - # Case: default value is not given or is single dimensional - return (1,) - elif isinstance(self._default, np.ndarray): - # Case: default value is multidimensional - return self._default.shape + def _check_shape(self, shape: Union[tuple[int, ...], None]) -> tuple[int, ...]: + if shape is None and isinstance(self._choices[0], (int, float, bool, str, np.int_, np.float_, np.str_, np.bool_)): + # Case: shape is not given and values in choices are single dimensional + return 1, + elif shape is None and isinstance(self._choices[0], np.ndarray): + # Case: shape is not given and values in choices are multidimensional + return self._choices[0].shape elif self._is_legal_shape(shape): + # Case: shape is given and legal return shape else: + # Case: shape is illegal raise Exception(f"Illegal shape {shape}!") - def _is_legal_shape(self, shape: Union[int, tuple[int, ...]]) -> bool: - if shape == 1 or shape == (1,): - # Case: shape refers to single dimensional - if isinstance(self._default, (int, float, bool, str)): - return True - elif isinstance(shape, int): - # Case: shape refers to array-like dimensional - if isinstance(self._default, np.ndarray) and shape == len(self._default) and self._default.ndim == 1: - # Case: default is array-like dimensional - return True - elif isinstance(shape, tuple) and all(isinstance(s, int) for s in shape): - # Case: shape refers to multidimensional - if isinstance(self._default, np.ndarray) and shape == self._default.shape: - # Case: default value is multidimensional - return True + def _is_legal_shape(self, shape: tuple[int, ...]) -> bool: + if shape == (1,) and isinstance(self._choices[0], (int, float, bool, str, np.int_, np.float_, np.str_, np.bool_)): + # Case: shape and values in choices refers to single dimensional + return True + elif isinstance(shape, tuple) and all(isinstance(s, int) for s in shape) and \ + isinstance(self._choices[0], np.ndarray) and shape == self._choices[0].shape: + # Case: shape and values in choices refers to multidimensional + return True return False def _check_distribution(self, distribution: Union[Distribution, None]) -> Distribution: @@ -165,7 +162,7 @@ def _check_distribution(self, distribution: Union[Distribution, None]) -> Distri # Case: Distribution is given and legal return distribution else: - raise Exception(f"Illegal distribution {distribution}!") + raise Exception(f"Illegal distribution {distribution}. The argument should be a class of Choice(...)!") def _is_legal_distribution(self, distribution: Distribution) -> bool: """ @@ -227,7 +224,7 @@ def __hash__(self) -> int: return hash(self.__repr__()) def __repr__(self) -> str: - text = f"Categorical({self._name}, choices={self._choices}, default={self._default}, distribution={self._distribution})" + text = f"Categorical({self._name}, choices={self._choices}, default={self._default}, shape={self._shape}, distribution={self._distribution})" return text def __getstate__(self) -> dict: diff --git a/PyHyperparameterSpace/hp/constant.py b/PyHyperparameterSpace/hp/constant.py index 3d676cf..f2b0933 100644 --- a/PyHyperparameterSpace/hp/constant.py +++ b/PyHyperparameterSpace/hp/constant.py @@ -1,7 +1,7 @@ -from typing import Union, Iterable, Any +from typing import Any, Union + import numpy as np -from PyHyperparameterSpace.dist.abstract_dist import Distribution from PyHyperparameterSpace.hp.abstract_hp import Hyperparameter @@ -10,7 +10,7 @@ class Constant(Hyperparameter): Class to represent a constant hyperparameter, where the given default value does not get changed by the sampling procedure. - Attributes: + Args: name (str): Name of the hyperparameter @@ -25,7 +25,7 @@ def __init__( self, name: str, default: Any, - shape: Union[int, tuple[int, ...], None] = None, + shape: Union[tuple[int, ...], None] = None, ): super().__init__(name=name, shape=shape, default=default) @@ -33,43 +33,35 @@ def _check_default(self, default: Any) -> Any: if self._is_legal_default(default): return default else: - raise Exception(f"Illegal default value {default}") + raise Exception(f"Illegal default {default}. The argument should be given!") def _is_legal_default(self, default: Any) -> bool: if default is None: return False return True - def _check_shape(self, shape: Union[int, tuple[int, ...], None]) -> Union[int, tuple[int, ...]]: - if shape is None: - # Case: Adjust the shape according to the given default value - if isinstance(self._default, (int, float, bool, str)): - # Case: default is single dimensional - return (1,) - elif isinstance(self._default, np.ndarray): - # Case: default is multidimensional - return self._default.shape + def _check_shape(self, shape: Union[tuple[int, ...], None]) -> Union[tuple[int, ...]]: + if shape is None and isinstance(self._default, (int, float, bool, str, np.int_, np.float_, np.str_, np.bool_)): + # Case: shape is not given and default is single dimensional + return 1, + elif shape is None and isinstance(self._default, np.ndarray): + # Case: shape is not given and default is multidimensional + return self._default.shape elif self._is_legal_shape(shape): + # Case: shape is given and has to be checked return shape else: - raise Exception(f"Illegal shape {shape}!") - - def _is_legal_shape(self, shape: Union[int, tuple[int, ...]]) -> bool: - if shape == 1 or shape == (1,): - # Case: shape refers to single dimensional - if isinstance(self._default, (int, float, bool, str)): - # Case: default is single dimensional - return True - elif isinstance(shape, int): - # Case: shape refers to array-like dimensional - if isinstance(self._default, np.ndarray) and shape == len(self._default) and self._default.ndim == 1: - # Case: default is array-like dimensional - return True - elif isinstance(shape, tuple) and all(isinstance(s, int) for s in shape): - # Case: shape refers to multi-dimensional - if isinstance(self._default, np.ndarray) and shape == self._default.shape: - # Case: default is multi-dimensional - return True + # Case: shape is not valid + raise Exception(f"Illegal shape {shape}. The argument should be given in the format (dim1, ...)!") + + def _is_legal_shape(self, shape: tuple[int, ...]) -> bool: + if shape == (1,) and isinstance(self._default, (int, float, bool, str, np.int_, np.float_, np.str_, np.bool_)): + # Case: shape and default refers to single dimensional + return True + elif isinstance(shape, tuple) and all(isinstance(s, int) for s in shape) and \ + isinstance(self._default, np.ndarray) and shape == self._default.shape: + # Case: shape and default refers to multidimensional + return True return False def sample(self, random: np.random.RandomState, size: Union[int, None] = None) -> Any: diff --git a/PyHyperparameterSpace/hp/continuous.py b/PyHyperparameterSpace/hp/continuous.py index b28e922..33500de 100644 --- a/PyHyperparameterSpace/hp/continuous.py +++ b/PyHyperparameterSpace/hp/continuous.py @@ -1,10 +1,11 @@ -from typing import Union, Iterable, Any from abc import ABC, abstractmethod +from typing import Any, Union + import numpy as np +from PyHyperparameterSpace.dist.abstract_dist import Distribution from PyHyperparameterSpace.dist.continuous import MatrixNormal, MultivariateNormal, Normal, Uniform from PyHyperparameterSpace.hp.abstract_hp import Hyperparameter -from PyHyperparameterSpace.dist.abstract_dist import Distribution class Continuous(Hyperparameter, ABC): @@ -33,7 +34,7 @@ def __init__( name: str, bounds: Union[tuple[int, int], tuple[float, float]], default: Any = None, - shape: Union[int, tuple[int, ...], None] = None, + shape: Union[tuple[int, ...], None] = None, distribution: Union[Distribution, None] = Uniform(), ): # First set the variables @@ -180,7 +181,7 @@ def __init__( name: str, bounds: Union[tuple[float, float], tuple[int, int]], default: Union[int, float, list, np.ndarray] = None, - shape: Union[int, tuple[int, ...], None] = None, + shape: Union[tuple[int, ...], None] = None, distribution: Distribution = Uniform(), ): if isinstance(default, list): @@ -193,11 +194,11 @@ def _check_bounds(self, bounds: Union[tuple[float, float], tuple[int, int]]) \ if self._is_legal_bounds(bounds): return bounds else: - raise Exception(f"Illegal bounds {bounds}!") + raise Exception(f"Illegal bounds {bounds}. The argument should have the format (lower, upper), where lower < upper!") def _is_legal_bounds(self, bounds: Union[tuple[float, float], tuple[int, int]]): if isinstance(bounds, tuple) and len(bounds) == 2 and \ - all(isinstance(b, (float, int)) for b in bounds) and bounds[0] < bounds[1]: + all(isinstance(b, (float, int, np.int_, np.float_)) for b in bounds) and bounds[0] < bounds[1]: return True else: return False @@ -214,15 +215,15 @@ def _check_default(self, default: Union[int, float, np.ndarray]) -> Union[int, f elif self._is_legal_default(default): return default else: - raise Exception(f"Illegal default value {default}!") + raise Exception(f"Illegal default {default}. The argument should be in between the bounds (lower, upper)!") def _is_legal_default(self, default: Any) -> bool: - if not isinstance(default, (float, int)) and \ + if not isinstance(default, (float, int, np.int_, np.float_)) and \ not (isinstance(default, np.ndarray) and np.issubdtype(default.dtype, np.floating)) and \ not (isinstance(default, np.ndarray) and np.issubdtype(default.dtype, np.integer)): # Case: default is not in the right format return False - if isinstance(default, (float, int)): + if isinstance(default, (float, int, np.int_, np.float_)): # Case: default is single dimensional return self.lb <= default < self.ub elif isinstance(default, np.ndarray): @@ -230,43 +231,35 @@ def _is_legal_default(self, default: Any) -> bool: return np.all((default >= self.lb) & (default < self.ub)) return False - def _check_shape(self, shape: Union[int, tuple[int, ...]]) -> Union[int, tuple[int, ...]]: - if shape is None: - # Case: Adjust the shape according to the given default value - if self._default is None or isinstance(self._default, (float, int)): - # Case: default is not given or single dimensional - return (1,) - elif isinstance(self._default, np.ndarray): - # Case: default is multidimensional - return self._default.shape + def _check_shape(self, shape: Union[tuple[int, ...], None]) -> tuple[int, ...]: + if shape is None and isinstance(self._default, (float, int, np.int_, np.float_)): + # Case: shape is not given and default is single dimensional + return 1, + elif shape is None and isinstance(self._default, np.ndarray): + # Case: shape is not given and default is multidimensional + return self._default.shape elif self._is_legal_shape(shape): + # Case: shape is given and legal return shape else: - raise Exception(f"Illegal shape {shape}!") - - def _is_legal_shape(self, shape: Union[int, tuple[int, ...]]) -> bool: - if shape == 1 or shape == (1,): - # Case: shape refers to single dimensional - if isinstance(self._default, (float, int)): - # Case: default is single dimensional - return True - elif isinstance(shape, int): - # Case: shape refers to array-like dimensional - if isinstance(self._default, np.ndarray) and shape == len(self._default) and self._default.ndim == 1: - # Case: default is array-like dimensional - return True - elif isinstance(shape, tuple) and all(isinstance(s, int) for s in shape): - # Case: shape refers to multidimensional - if isinstance(self._default, np.ndarray) and shape == self._default.shape: - # Case: default is multidimensional - return True + # Case: shape is illegal + raise Exception(f"Illegal shape {shape}. The argument should have the right format (dim1, ...)!") + + def _is_legal_shape(self, shape: tuple[int, ...]) -> bool: + if shape == (1,) and isinstance(self._default, (float, int, np.int_, np.float_)): + # Case: shape and default refers to single dimensional + return True + elif isinstance(shape, tuple) and all(isinstance(s, int) for s in shape) and \ + isinstance(self._default, np.ndarray) and shape == self._default.shape: + # Case: shape and default refers to multidimensional + return True return False def _check_distribution(self, distribution: Union[Distribution, None]) -> Union[Distribution, None]: if self._is_legal_distribution(distribution): return distribution else: - raise Exception(f"Illegal distribution {distribution}!") + raise Exception(f"Illegal distribution {distribution}. The argument should be in class of MatrixNormal(...), MultivariateNormal(...), Normal(...) or Uniform(...)!") def _is_legal_distribution(self, distribution: Union[Distribution, None]) -> bool: if isinstance(distribution, MatrixNormal): @@ -349,7 +342,7 @@ def sample(self, random: np.random.RandomState, size: Union[int, None] = None) - sample = random.uniform(low=self.lb, high=self.ub, size=sample_size) return sample else: - raise Exception("Unknown Probability Distribution!") + raise Exception("Unknown Distribution!") def valid_configuration(self, value: Any) -> bool: if isinstance(value, (list, np.ndarray)): @@ -400,20 +393,20 @@ def __init__( name: str, bounds: Union[tuple[int, int]], default: Union[int, list, np.ndarray, None] = None, - shape: Union[int, tuple[int, ...], None] = None, + shape: Union[tuple[int, ...], None] = None, distribution: Distribution = Uniform(), ): super().__init__(name=name, shape=shape, bounds=bounds, default=default, distribution=distribution) - def _check_bounds(self, bounds: Union[tuple[int, int], None]) -> Union[tuple[int], tuple[float], None]: + def _check_bounds(self, bounds: tuple[int, int]) -> tuple[int, int]: if self._is_legal_bounds(bounds): return bounds else: raise Exception(f"Illegal bounds {bounds}!") - def _is_legal_bounds(self, bounds: Union[tuple[int], tuple[float], None]) -> bool: + def _is_legal_bounds(self, bounds: tuple[int, int]) -> bool: if isinstance(bounds, tuple) and len(bounds) == 2 and \ - all(isinstance(b, int) for b in bounds) and bounds[0] < bounds[1]: + all(isinstance(b, (int, np.int_)) for b in bounds) and bounds[0] < bounds[1]: return True else: return False @@ -439,50 +432,40 @@ def _is_legal_default(self, default: Union[int, np.ndarray]) -> bool: not (isinstance(default, np.ndarray) and np.issubdtype(default.dtype, np.integer)): # Case: default is not in the right format! return False - if isinstance(default, int): + if isinstance(default, (int, np.int_)): # Case: default is single dimensional return self.lb <= default <= self.ub else: # Case: default is multidimensional return np.all((default >= self.lb) & (default <= self.ub)) - def _check_shape(self, shape: Union[int, tuple[int, ...]]) -> Union[int, tuple[int, ...]]: - if shape is None: - # Case: Adjust the shape according to the given default value - if self._default is None or isinstance(self._default, int): - # Case: default is not given or single dimensional - return (1,) - elif isinstance(self._default, np.ndarray): - # Case: default is multidimensional - return self._default.shape + def _check_shape(self, shape: Union[tuple[int, ...], None]) -> tuple[int, ...]: + if shape is None and isinstance(self._default, (int, np.int_)): + # Case: shape is not given and default is single dimensional + return 1, + elif shape is None and isinstance(self._default, np.ndarray): + # Case: shape is not given and default is multidimensional + return self._default.shape elif self._is_legal_shape(shape): + # Case: shape is given return shape else: - raise Exception(f"Illegal shape {shape}!") - - def _is_legal_shape(self, shape: Union[int, tuple[int, ...]]) -> bool: - if shape == 1 or shape == (1,): - # Check if shape has the right format for the default value - if isinstance(self._default, int): - # Case: default is single dimensional - return True - elif isinstance(shape, int): - # Check if shape has the right format for the default value - if isinstance(self._default, np.ndarray) and shape == len(self._default) and self._default.ndim == 1: - # Case: default is array-like dimensional - return True - elif isinstance(shape, tuple) and all(isinstance(s, int) for s in shape): - # Check if shape is in the right format for the default value - if isinstance(self._default, np.ndarray) and shape == self._default.shape: - # Case: default is multidimensional - return True + raise Exception(f"Illegal shape {shape}. The argument should be in the format (lower, upper), where lower < upper!") + + def _is_legal_shape(self, shape: tuple[int, ...]) -> bool: + if shape == (1,) and isinstance(self._default, (int, np.int_)): + # Case: shape and default refers to single dimensional + return True + elif isinstance(shape, tuple) and all(isinstance(s, (int, np.int_)) for s in shape) and \ + isinstance(self._default, np.ndarray) and shape == self._default.shape: + return True return False def _check_distribution(self, distribution: Union[Distribution, None]) -> Union[Distribution, None]: if self._is_legal_distribution(distribution): return distribution else: - raise Exception(f"Illegal distribution {distribution}") + raise Exception(f"Illegal distribution {distribution}. The argument should have the class Uniform(...)!") def _is_legal_distribution(self, distribution: Union[Distribution, None]) -> bool: if isinstance(distribution, Uniform): @@ -496,14 +479,14 @@ def sample(self, random: np.random.RandomState, size: Union[int, None] = None) - sample = random.randint(low=self.lb, high=self.ub, size=sample_size) return sample else: - raise Exception("Unknown Probability Distribution!") + raise Exception("Unknown Distribution!") def valid_configuration(self, value: Any) -> bool: if isinstance(value, (list, np.ndarray)): # Case: Value is multi-dimensional value = np.array(value) return np.all((self.lb <= value) & (value < self.ub)) and self._shape == value.shape - elif isinstance(value, int): + elif isinstance(value, (int, np.int_)): # Case: value is single-dimensional return self.lb <= value < self.ub else: diff --git a/PyHyperparameterSpace/space.py b/PyHyperparameterSpace/space.py index 954387b..72ffc07 100644 --- a/PyHyperparameterSpace/space.py +++ b/PyHyperparameterSpace/space.py @@ -1,8 +1,9 @@ -from typing import Mapping, Iterator, Any, Union, Iterable +from typing import Any, Iterable, Iterator, Mapping, Union + import numpy as np -from PyHyperparameterSpace.hp.abstract_hp import Hyperparameter from PyHyperparameterSpace.configuration import HyperparameterConfiguration +from PyHyperparameterSpace.hp.abstract_hp import Hyperparameter class HyperparameterConfigurationSpace(Mapping[str, Hyperparameter]): diff --git a/tests/hp/test_categorical.py b/tests/hp/test_categorical.py index c542566..f9292c2 100644 --- a/tests/hp/test_categorical.py +++ b/tests/hp/test_categorical.py @@ -1,9 +1,11 @@ +import os import unittest + import numpy as np import yaml -from PyHyperparameterSpace.hp.categorical import Categorical from PyHyperparameterSpace.dist.categorical import Choice +from PyHyperparameterSpace.hp.categorical import Categorical class TestCategorical(unittest.TestCase): @@ -46,6 +48,9 @@ def setUp(self): # Test with all options self.hp5 = Categorical(name=self.name, choices=self.choices5, default=self.default5, distribution=self.distribution2, shape=self.shape5) + # Test with no shape and default given + self.hp6 = Categorical(name=self.name, choices=self.choices5, default=None, distribution=self.distribution2, + shape=None) def test_name(self): """ @@ -56,6 +61,7 @@ def test_name(self): self.assertEqual(self.name, self.hp3._name) self.assertEqual(self.name, self.hp4._name) self.assertEqual(self.name, self.hp5._name) + self.assertEqual(self.name, self.hp6._name) def test_shape(self): """ @@ -66,6 +72,7 @@ def test_shape(self): self.assertEqual(self.shape, self.hp3._shape) self.assertEqual(self.shape, self.hp4._shape) self.assertEqual(self.shape5, self.hp5._shape) + self.assertEqual(self.shape5, self.hp6._shape) def test_choices(self): """ @@ -76,6 +83,7 @@ def test_choices(self): self.assertTrue(np.array_equal(self.choices, self.hp3._choices)) self.assertTrue(np.array_equal(self.choices, self.hp4._choices)) self.assertTrue(np.array_equal(self.choices5, self.hp5._choices)) + self.assertTrue(np.array_equal(self.choices5, self.hp6._choices)) def test_default(self): """ @@ -86,6 +94,7 @@ def test_default(self): self.assertEqual(self.default_X1, self.hp3._default) self.assertEqual(self.default_X1, self.hp4._default) self.assertTrue(np.all(self.default5 == self.hp5._default)) + self.assertTrue(np.all(self.default5 == self.hp6._default)) def test_distribution(self): """ @@ -96,6 +105,7 @@ def test_distribution(self): self.assertIsInstance(self.hp3._distribution, Choice) self.assertIsInstance(self.hp4._distribution, Choice) self.assertIsInstance(self.hp5._distribution, Choice) + self.assertIsInstance(self.hp6._distribution, Choice) def test_get_name(self): """ @@ -106,6 +116,7 @@ def test_get_name(self): self.assertEqual(self.name, self.hp3.get_name()) self.assertEqual(self.name, self.hp4.get_name()) self.assertEqual(self.name, self.hp5.get_name()) + self.assertEqual(self.name, self.hp6.get_name()) def test_get_default(self): """ @@ -116,6 +127,7 @@ def test_get_default(self): self.assertEqual(self.default_X1, self.hp3.get_default()) self.assertEqual(self.default_X1, self.hp4.get_default()) self.assertTrue(np.all(self.default5 == self.hp5.get_default())) + self.assertTrue(np.all(self.default5 == self.hp6.get_default())) def test_get_shape(self): """ @@ -126,6 +138,7 @@ def test_get_shape(self): self.assertEqual(self.shape, self.hp3.get_shape()) self.assertEqual(self.shape, self.hp4.get_shape()) self.assertEqual(self.shape5, self.hp5.get_shape()) + self.assertEqual(self.shape5, self.hp6.get_shape()) def test_get_choices(self): """ @@ -136,6 +149,7 @@ def test_get_choices(self): self.assertTrue(np.array_equal(self.choices, self.hp3.get_choices())) self.assertTrue(np.array_equal(self.choices, self.hp4.get_choices())) self.assertTrue(np.array_equal(self.choices5, self.hp5.get_choices())) + self.assertTrue(np.array_equal(self.choices5, self.hp6.get_choices())) def test_get_distribution(self): """ @@ -146,6 +160,7 @@ def test_get_distribution(self): self.assertIsInstance(self.hp3.get_distribution(), Choice) self.assertIsInstance(self.hp4.get_distribution(), Choice) self.assertIsInstance(self.hp5.get_distribution(), Choice) + self.assertIsInstance(self.hp6.get_distribution(), Choice) def test_change_distribution(self): """ @@ -178,6 +193,10 @@ def test_sample(self): self.assertEqual(self.size, len(sample5)) self.assertTrue(s in self.choices for s in sample5) + sample6 = self.hp5.sample(random=self.random, size=self.size) + self.assertEqual(self.size, len(sample6)) + self.assertTrue(s in self.choices for s in sample6) + def test_valid_configuration(self): """ Tests the method valid_configuration(). @@ -194,6 +213,7 @@ def test_eq(self): self.assertNotEqual(self.hp, self.hp3) self.assertNotEqual(self.hp, self.hp4) self.assertNotEqual(self.hp, self.hp5) + self.assertNotEqual(self.hp, self.hp6) def test_hash(self): """ @@ -204,6 +224,7 @@ def test_hash(self): self.assertNotEqual(hash(self.hp), hash(self.hp3)) self.assertNotEqual(hash(self.hp), hash(self.hp4)) self.assertNotEqual(hash(self.hp), hash(self.hp5)) + self.assertNotEqual(hash(self.hp), hash(self.hp6)) def test_set_get_state(self): """ @@ -220,6 +241,9 @@ def test_set_get_state(self): # Check if they are equal self.assertEqual(hp, self.hp) + # Delete the yaml file + os.remove("test_data.yaml") + if __name__ == '__main__': unittest.main() diff --git a/tests/hp/test_constant.py b/tests/hp/test_constant.py index 797192f..67e3fe4 100644 --- a/tests/hp/test_constant.py +++ b/tests/hp/test_constant.py @@ -1,4 +1,6 @@ +import os import unittest + import numpy as np import yaml @@ -143,6 +145,9 @@ def test_set_get_state(self): # Check if they are equal self.assertEqual(hp, self.hp) + # Delete the yaml file + os.remove("test_data.yaml") + if __name__ == '__main__': unittest.main() diff --git a/tests/hp/test_float.py b/tests/hp/test_float.py index 4e51ac8..3fbc06b 100644 --- a/tests/hp/test_float.py +++ b/tests/hp/test_float.py @@ -1,9 +1,11 @@ +import os import unittest + import numpy as np import yaml -from PyHyperparameterSpace.hp.continuous import Float from PyHyperparameterSpace.dist.continuous import MatrixNormal, MultivariateNormal, Normal, Uniform +from PyHyperparameterSpace.hp.continuous import Float class TestFloat(unittest.TestCase): @@ -336,6 +338,9 @@ def test_set_get_state(self): # Check if they are equal self.assertEqual(hp, self.normal_hp) + # Delete the yaml file + os.remove("test_data.yaml") + if __name__ == '__main__': unittest.main() diff --git a/tests/hp/test_integer.py b/tests/hp/test_integer.py index ad78983..8ea945d 100644 --- a/tests/hp/test_integer.py +++ b/tests/hp/test_integer.py @@ -1,9 +1,11 @@ +import os import unittest + import numpy as np import yaml -from PyHyperparameterSpace.hp.continuous import Integer from PyHyperparameterSpace.dist.continuous import Uniform +from PyHyperparameterSpace.hp.continuous import Integer class TestInteger(unittest.TestCase): @@ -202,6 +204,9 @@ def test_set_get_state(self): # Check if they are equal self.assertEqual(hp, self.hp) + # Delete the yaml file + os.remove("test_data.yaml") + if __name__ == '__main__': unittest.main() diff --git a/tests/test_configuration.py b/tests/test_configuration.py index a7d65cf..31ff0eb 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1,13 +1,14 @@ +import os import unittest -import numpy as np + import yaml -from PyHyperparameterSpace.space import HyperparameterConfigurationSpace -from PyHyperparameterSpace.hp.continuous import Float, Integer -from PyHyperparameterSpace.hp.categorical import Categorical -from PyHyperparameterSpace.hp.constant import Constant from PyHyperparameterSpace.configuration import HyperparameterConfiguration from PyHyperparameterSpace.dist.categorical import Choice +from PyHyperparameterSpace.hp.categorical import Categorical +from PyHyperparameterSpace.hp.constant import Constant +from PyHyperparameterSpace.hp.continuous import Float, Integer +from PyHyperparameterSpace.space import HyperparameterConfigurationSpace class TestHyperparameterConfiguration(unittest.TestCase): @@ -18,11 +19,11 @@ class TestHyperparameterConfiguration(unittest.TestCase): def setUp(self) -> None: self.cs = HyperparameterConfigurationSpace( values={ - "X1": Float("X1", bounds=(-10.5, 10.5), default=2.25, shape=(1,)), + "X1": Float("X1", bounds=(-10.5, 10.5), default=2.25), "X2": Categorical("X2", choices=[True, False], default=True), - "X3": Integer("X3", bounds=(-10, 10), default=-5, shape=(1,)), + "X3": Integer("X3", bounds=(-10, 10), default=-5), "X4": Categorical("X4", choices=["attr1", "attr2", "attr3"], default="attr1", distribution=Choice([0.3, 0.4, 0.3])), - "X5": Constant("X5", default="X_Const", shape=(1,)), + "X5": Constant("X5", default="X_Const"), }, seed=0, ) @@ -105,6 +106,9 @@ def test_set_get_state(self): # Check if they are equal self.assertEqual(cfg, self.cfg) + # Delete the yaml file + os.remove("test_data.yaml") + if __name__ == '__main__': unittest.main() diff --git a/tests/test_space.py b/tests/test_space.py index 3258a08..9905e1d 100644 --- a/tests/test_space.py +++ b/tests/test_space.py @@ -1,14 +1,16 @@ +import os import unittest + import numpy as np import yaml -from PyHyperparameterSpace.space import HyperparameterConfigurationSpace -from PyHyperparameterSpace.space import HyperparameterConfiguration -from PyHyperparameterSpace.hp.continuous import Float, Integer +from PyHyperparameterSpace.dist.categorical import Choice +from PyHyperparameterSpace.dist.continuous import Normal from PyHyperparameterSpace.hp.categorical import Categorical from PyHyperparameterSpace.hp.constant import Constant -from PyHyperparameterSpace.dist.continuous import Normal, Uniform -from PyHyperparameterSpace.dist.categorical import Choice +from PyHyperparameterSpace.hp.continuous import Float, Integer +from PyHyperparameterSpace.space import HyperparameterConfiguration +from PyHyperparameterSpace.space import HyperparameterConfigurationSpace class TestHyperparameterConfigurationSpace(unittest.TestCase): @@ -155,6 +157,9 @@ def test_set_get_state(self): # Check if they are equal self.assertEqual(cs, self.cs) + # Delete the yaml file + os.remove("test_data.yaml") + if __name__ == '__main__': unittest.main()