diff --git a/.coveragerc b/.coveragerc index ae1312cf6..e33a113f6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -11,4 +11,4 @@ omit = */tests/* */examples/* */__init__.py - */setup.py \ No newline at end of file + */setup.py diff --git a/.github/workflows/tests_coverage.yml b/.github/workflows/tests_coverage.yml index 4da5a6947..4407ddc93 100644 --- a/.github/workflows/tests_coverage.yml +++ b/.github/workflows/tests_coverage.yml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install -r requirements.txt numpy==1.26.4 ConfigSpace==0.6.1 + pip install -r requirements.txt numpy==1.26.4 ConfigSpace==0.6.1 adsg-core==1.1.1 git+https://github.com/SMTorg/smt-design-space-ext pip list pip install -e . diff --git a/requirements.txt b/requirements.txt index d8cb72d99..85d2089b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ pytest-xdist # allows running parallel testing with pytest -n pytest-cov # allows to get coverage report ruff # format and lint code jenn >= 1.0.2, <2.0 -egobox ~= 0.20.0 +egobox ~= 0.20.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 78c37d647..ae64ef838 100644 --- a/setup.py +++ b/setup.py @@ -107,6 +107,8 @@ "smt.sampling_methods", "smt.utils", "smt.applications", + "smt.design_space", + "smt.kernels", ], install_requires=[ "scikit-learn", @@ -118,9 +120,6 @@ "numba": [ # pip install smt[numba] "numba~=0.56.4", ], - "cs": [ # pip install smt[cs] - "ConfigSpace~=0.6.1", - ], "gpx": ["egobox~=0.22"], # pip install smt[gpx] }, python_requires=">=3.9", diff --git a/smt/__init__.py b/smt/__init__.py index 2614ce9d9..ad074fa58 100644 --- a/smt/__init__.py +++ b/smt/__init__.py @@ -1 +1,14 @@ __version__ = "2.7.0" + +__all__ = [ + "surrogate_models", + "kernels", + "design_space", + "applications", + "examples", + "sampling_methods", + "utils", + "tests", + "src", + "problems", +] diff --git a/smt/applications/ego.py b/smt/applications/ego.py index b00966d22..e1cc6e694 100644 --- a/smt/applications/ego.py +++ b/smt/applications/ego.py @@ -19,10 +19,7 @@ ) from smt.sampling_methods import LHS from smt.surrogate_models import GEKPLS, GPX, KPLS, KPLSK, KRG, MGP -from smt.utils.design_space import ( - BaseDesignSpace, - DesignSpace, -) +from smt.design_space import BaseDesignSpace, DesignSpace class Evaluator(object): diff --git a/smt/applications/mfk.py b/smt/applications/mfk.py index 76e271e08..9baba0c39 100644 --- a/smt/applications/mfk.py +++ b/smt/applications/mfk.py @@ -23,7 +23,7 @@ MixIntKernelType, compute_n_param, ) -from smt.utils.design_space import ensure_design_space +from smt.design_space import ensure_design_space from smt.utils.kriging import ( componentwise_distance, compute_X_cont, diff --git a/smt/applications/mixed_integer.py b/smt/applications/mixed_integer.py index c1e76ad9a..bb083e5e0 100644 --- a/smt/applications/mixed_integer.py +++ b/smt/applications/mixed_integer.py @@ -12,7 +12,8 @@ from smt.surrogate_models.krg_based import KrgBased, MixIntKernelType from smt.surrogate_models.surrogate_model import SurrogateModel from smt.utils.checks import ensure_2d_array -from smt.utils.design_space import ( + +from smt.design_space import ( BaseDesignSpace, CategoricalVariable, ensure_design_space, diff --git a/smt/applications/tests/test_ego.py b/smt/applications/tests/test_ego.py index 0e563c5b7..d34133d39 100644 --- a/smt/applications/tests/test_ego.py +++ b/smt/applications/tests/test_ego.py @@ -11,7 +11,7 @@ import numpy as np -import smt.utils.design_space as ds +import smt.design_space as ds from smt.applications import EGO from smt.applications.ego import Evaluator from smt.applications.mixed_integer import ( diff --git a/smt/applications/tests/test_mfk_mfkpls_mixed.py b/smt/applications/tests/test_mfk_mfkpls_mixed.py index 4604b9d98..bed656d01 100644 --- a/smt/applications/tests/test_mfk_mfkpls_mixed.py +++ b/smt/applications/tests/test_mfk_mfkpls_mixed.py @@ -32,9 +32,9 @@ KRG, MixIntKernelType, ) -from smt.utils.design_space import ( - CategoricalVariable, +from smt.design_space import ( DesignSpace, + CategoricalVariable, FloatVariable, IntegerVariable, ) diff --git a/smt/applications/tests/test_mixed_integer.py b/smt/applications/tests/test_mixed_integer.py index feffdbd2f..112d290de 100644 --- a/smt/applications/tests/test_mixed_integer.py +++ b/smt/applications/tests/test_mixed_integer.py @@ -17,7 +17,16 @@ except ImportError: NO_MATPLOTLIB = True -import smt.utils.design_space as ds +import smt.design_space as ds +from smt.design_space import ( + HAS_CONFIG_SPACE, + DesignSpace, + CategoricalVariable, + FloatVariable, + IntegerVariable, + OrdinalVariable, +) + from smt.applications.mixed_integer import ( MixedIntegerContext, MixedIntegerKrigingModel, @@ -33,14 +42,6 @@ MixHrcKernelType, MixIntKernelType, ) -from smt.utils.design_space import ( - HAS_CONFIG_SPACE, - CategoricalVariable, - DesignSpace, - FloatVariable, - IntegerVariable, - OrdinalVariable, -) class TestMixedInteger(unittest.TestCase): @@ -474,10 +475,10 @@ def run_mixed_integer_lhs_example(self): from smt.applications.mixed_integer import MixedIntegerSamplingMethod from smt.sampling_methods import LHS - from smt.utils.design_space import ( - CategoricalVariable, - DesignSpace, + from smt.design_space import ( FloatVariable, + DesignSpace, + CategoricalVariable, ) float_var = FloatVariable(0, 4) @@ -507,7 +508,7 @@ def run_mixed_integer_qp_example(self): from smt.applications.mixed_integer import MixedIntegerSurrogateModel from smt.surrogate_models import QP - from smt.utils.design_space import DesignSpace, IntegerVariable + from smt.design_space import DesignSpace, IntegerVariable xt = np.array([0.0, 1.0, 2.0, 3.0, 4.0]) yt = np.array([0.0, 1.0, 1.5, 0.5, 1.0]) @@ -539,7 +540,7 @@ def run_mixed_integer_context_example(self): from smt.applications.mixed_integer import MixedIntegerContext from smt.surrogate_models import KRG - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, @@ -747,7 +748,7 @@ def run_mixed_discrete_design_space_example(self): from smt.applications.mixed_integer import MixedIntegerSamplingMethod from smt.sampling_methods import LHS - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, @@ -798,7 +799,7 @@ def run_hierarchical_design_space_example(self): ) from smt.sampling_methods import LHS from smt.surrogate_models import KRG, MixHrcKernelType, MixIntKernelType - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, @@ -1099,7 +1100,7 @@ def run_hierarchical_variables_Goldstein(self): ) from smt.sampling_methods import LHS from smt.surrogate_models import KRG, MixHrcKernelType, MixIntKernelType - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, @@ -2005,7 +2006,7 @@ def run_mixed_gower_example(self): MixedIntegerKrigingModel, ) from smt.surrogate_models import KRG, MixIntKernelType - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, @@ -2136,7 +2137,7 @@ def run_mixed_cs_example(self): MixedIntegerKrigingModel, ) from smt.surrogate_models import KRG, MixIntKernelType - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, @@ -2265,7 +2266,7 @@ def run_mixed_homo_gaussian_example(self): from smt.applications.mixed_integer import MixedIntegerKrigingModel from smt.surrogate_models import KRG, MixIntKernelType - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, @@ -2394,7 +2395,7 @@ def run_mixed_homo_hyp_example(self): from smt.applications.mixed_integer import MixedIntegerKrigingModel from smt.surrogate_models import KRG, MixIntKernelType - from smt.utils.design_space import ( + from smt.design_space import ( CategoricalVariable, DesignSpace, FloatVariable, diff --git a/smt/design_space/__init__.py b/smt/design_space/__init__.py new file mode 100644 index 000000000..fa0ef40c8 --- /dev/null +++ b/smt/design_space/__init__.py @@ -0,0 +1,55 @@ +import importlib + +spec = importlib.util.find_spec("smt_design_space") +if spec: + HAS_DESIGN_SPACE_EXT = True + HAS_CONFIG_SPACE = True + HAS_ADSG = True +else: + HAS_DESIGN_SPACE_EXT = False + HAS_CONFIG_SPACE = False + HAS_ADSG = False + + +if HAS_DESIGN_SPACE_EXT: + from smt_design_space.design_space import ( + CategoricalVariable, + DesignSpace, + BaseDesignSpace, + FloatVariable, + IntegerVariable, + OrdinalVariable, + ensure_design_space, + ) + +else: + from smt.design_space.design_space import ( + CategoricalVariable, + DesignSpace, + FloatVariable, + IntegerVariable, + OrdinalVariable, + ensure_design_space, + BaseDesignSpace, + ) + +if HAS_DESIGN_SPACE_EXT: + from smt_design_space.design_space import DesignSpaceGraph +else: + + class DesignSpaceGraph: + pass + + +__all__ = [ + "HAS_DESIGN_SPACE_EXT", + "HAS_CONFIG_SPACE", + "HAS_ADSG", + "BaseDesignSpace", + "DesignSpace", + "FloatVariable", + "IntegerVariable", + "OrdinalVariable", + "CategoricalVariable", + "ensure_design_space", +] diff --git a/smt/utils/design_space.py b/smt/design_space/design_space.py similarity index 62% rename from smt/utils/design_space.py rename to smt/design_space/design_space.py index 19e878af9..fee836194 100644 --- a/smt/utils/design_space.py +++ b/smt/design_space/design_space.py @@ -4,43 +4,42 @@ This package is distributed under New BSD license. """ +import importlib +import numpy as np +from smt.sampling_methods.lhs import LHS from typing import List, Optional, Sequence, Tuple, Union -import numpy as np +spec = importlib.util.find_spec("smt_design_space") +if spec: + HAS_DESIGN_SPACE_EXT = True + HAS_CONFIG_SPACE = True + HAS_ADSG = True +else: + HAS_DESIGN_SPACE_EXT = False + HAS_CONFIG_SPACE = False + HAS_ADSG = False +spec = importlib.util.find_spec("adsg-core") +if spec: + HAS_ADSG = True +else: + HAS_ADSG = False +spec = importlib.util.find_spec("configspace") +if spec: + HAS_CONFIG_SPACE = True +else: + HAS_CONFIG_SPACE = False -from smt.sampling_methods import LHS - -try: - from ConfigSpace import ( - CategoricalHyperparameter, - Configuration, - ConfigurationSpace, - EqualsCondition, - ForbiddenAndConjunction, - ForbiddenEqualsClause, - ForbiddenInClause, - ForbiddenLessThanRelation, - InCondition, - OrdinalHyperparameter, - UniformFloatHyperparameter, - UniformIntegerHyperparameter, - ) - from ConfigSpace.exceptions import ForbiddenValueError - from ConfigSpace.util import get_random_neighbor - HAS_CONFIG_SPACE = True +class Configuration: + pass -except ImportError: - HAS_CONFIG_SPACE = False - class Configuration: - pass +class ConfigurationSpace: + pass - class ConfigurationSpace: - pass - class UniformIntegerHyperparameter: - pass +class UniformIntegerHyperparameter: + pass def ensure_design_space(xt=None, xlimits=None, design_space=None) -> "BaseDesignSpace": @@ -194,11 +193,13 @@ class BaseDesignSpace: (`correct_get_acting`), as usually these operations are tightly related. """ - def __init__(self, design_variables: List[DesignVariable] = None): + def __init__( + self, design_variables: List[DesignVariable] = None, random_state=None + ): self._design_variables = design_variables self._is_cat_mask = None self._is_conditionally_acting_mask = None - self.seed = None + self.seed = random_state self.has_valcons_ord_int = False @property @@ -567,11 +568,19 @@ def _round_equally_distributed(x_cont, lower: int, upper: int): x_stretched = (x_cont - lower) * ((diff + 0.9999) / (diff + 1e-16)) - 0.5 return np.round(x_stretched) + lower - """IMPLEMENT FUNCTIONS BELOW""" + def _to_seed(self, random_state=None): + seed = None + if isinstance(random_state, int): + seed = random_state + elif isinstance(random_state, np.random.RandomState): + seed = random_state.get_state()[1][0] + return seed def _get_design_variables(self) -> List[DesignVariable]: """Return the design variables defined in this design space if not provided upon initialization of the class""" + """IMPLEMENT FUNCTIONS BELOW""" + def _is_conditionally_acting(self) -> np.ndarray: """ Return for each design variable whether it is conditionally acting or not. A design variable is conditionally @@ -630,7 +639,9 @@ def __repr__(self): def raise_config_space(): - raise RuntimeError("Dependencies are not installed, run: pip install smt[cs]") + raise RuntimeError( + "Dependencies are not installed, please install smt_design_space." + ) class DesignSpace(BaseDesignSpace): @@ -663,13 +674,6 @@ class DesignSpace(BaseDesignSpace): >>> ds.declare_decreed_var(decreed_var=1, meta_var=0, meta_value='A') # Activate x1 if x0 == A - Decreed variables can be chained (however no cycles and no "diamonds" are supported): - Note: only if ConfigSpace is installed! pip install smt[cs] - >>> ds.declare_decreed_var(decreed_var=2, meta_var=1, meta_value=['C', 'D']) # Activate x2 if x1 == C or D - - If combinations of values between two variables are not allowed, this can be done using a value constraint: - Note: only if ConfigSpace is installed! pip install smt[cs] - >>> ds.add_value_constraint(var1=0, value1='A', var2=2, value2=[0, 1]) # Forbid x0 == A && x2 == 0 or 1 After defining everything correctly, you can then use the design space object to correct design vectors and get information about which design variables are acting: @@ -733,59 +737,12 @@ def _is_num(val): design_variables = converted_dvs self.random_state = random_state # For testing - - self._cs = None - self._cs_cate = None - if HAS_CONFIG_SPACE: - cs_vars = {} - cs_vars_cate = {} - self.isinteger = False - for i, dv in enumerate(design_variables): - name = f"x{i}" - if isinstance(dv, FloatVariable): - cs_vars[name] = UniformFloatHyperparameter( - name, lower=dv.lower, upper=dv.upper - ) - cs_vars_cate[name] = UniformFloatHyperparameter( - name, lower=dv.lower, upper=dv.upper - ) - elif isinstance(dv, IntegerVariable): - cs_vars[name] = FixedIntegerParam( - name, lower=dv.lower, upper=dv.upper - ) - listvalues = [] - for i in range(int(dv.upper - dv.lower + 1)): - listvalues.append(str(int(i + dv.lower))) - cs_vars_cate[name] = CategoricalHyperparameter( - name, choices=listvalues - ) - self.isinteger = True - elif isinstance(dv, OrdinalVariable): - cs_vars[name] = OrdinalHyperparameter(name, sequence=dv.values) - cs_vars_cate[name] = CategoricalHyperparameter( - name, choices=dv.values - ) - - elif isinstance(dv, CategoricalVariable): - cs_vars[name] = CategoricalHyperparameter(name, choices=dv.values) - cs_vars_cate[name] = CategoricalHyperparameter( - name, choices=dv.values - ) - - else: - raise ValueError(f"Unknown variable type: {dv!r}") - seed = self._to_seed(random_state) - - self._cs = NoDefaultConfigurationSpace(space=cs_vars, seed=seed) - ## Fix to make constraints work correctly with either IntegerVariable or OrdinalVariable - ## ConfigSpace is malfunctioning - self._cs_cate = NoDefaultConfigurationSpace(space=cs_vars_cate, seed=seed) - + seed = self.random_state # dict[int, dict[any, list[int]]]: {meta_var_idx: {value: [decreed_var_idx, ...], ...}, ...} self._meta_vars = {} self._is_decreed = np.zeros((len(design_variables),), dtype=bool) - super().__init__(design_variables) + super().__init__(design_variables=design_variables, random_state=seed) def declare_decreed_var( self, decreed_var: int, meta_var: int, meta_value: VarValueType @@ -803,75 +760,30 @@ def declare_decreed_var( - The value or list of values that the meta variable can have to activate the decreed var """ - # ConfigSpace implementation - if self._cs is not None: - # Get associated parameters - decreed_param = self._get_param(decreed_var) - meta_param = self._get_param(meta_var) - - # Add a condition that checks for equality (if single value given) or in-collection (if sequence given) - if isinstance(meta_value, Sequence): - condition = InCondition(decreed_param, meta_param, meta_value) - else: - condition = EqualsCondition(decreed_param, meta_param, meta_value) - - ## Fix to make constraints work correctly with either IntegerVariable or OrdinalVariable - ## ConfigSpace is malfunctioning - self._cs.add_condition(condition) - decreed_param = self._get_param2(decreed_var) - meta_param = self._get_param2(meta_var) - # Add a condition that checks for equality (if single value given) or in-collection (if sequence given) - if isinstance(meta_value, Sequence): - try: - condition = InCondition( - decreed_param, - meta_param, - list(np.atleast_1d(np.array(meta_value, dtype=str))), - ) - except ValueError: - condition = InCondition( - decreed_param, - meta_param, - list(np.atleast_1d(np.array(meta_value, dtype=float))), - ) - else: - try: - condition = EqualsCondition( - decreed_param, meta_param, str(meta_value) - ) - except ValueError: - condition = EqualsCondition(decreed_param, meta_param, meta_value) - - self._cs_cate.add_condition(condition) - - # Simplified implementation - else: - # Variables cannot be both meta and decreed at the same time - if self._is_decreed[meta_var]: - raise RuntimeError( - f"Variable cannot be both meta and decreed ({meta_var})!" - ) + # Variables cannot be both meta and decreed at the same time + if self._is_decreed[meta_var]: + raise RuntimeError( + f"Variable cannot be both meta and decreed ({meta_var})!" + ) - # Variables can only be decreed by one meta var - if self._is_decreed[decreed_var]: - raise RuntimeError(f"Variable is already decreed: {decreed_var}") + # Variables can only be decreed by one meta var + if self._is_decreed[decreed_var]: + raise RuntimeError(f"Variable is already decreed: {decreed_var}") - # Define meta-decreed relationship - if meta_var not in self._meta_vars: - self._meta_vars[meta_var] = {} + # Define meta-decreed relationship + if meta_var not in self._meta_vars: + self._meta_vars[meta_var] = {} - meta_var_obj = self.design_variables[meta_var] - for value in ( - meta_value if isinstance(meta_value, Sequence) else [meta_value] - ): - encoded_value = value - if isinstance(meta_var_obj, (OrdinalVariable, CategoricalVariable)): - if value in meta_var_obj.values: - encoded_value = meta_var_obj.values.index(value) + meta_var_obj = self.design_variables[meta_var] + for value in meta_value if isinstance(meta_value, Sequence) else [meta_value]: + encoded_value = value + if isinstance(meta_var_obj, (OrdinalVariable, CategoricalVariable)): + if value in meta_var_obj.values: + encoded_value = meta_var_obj.values.index(value) - if encoded_value not in self._meta_vars[meta_var]: - self._meta_vars[meta_var][encoded_value] = [] - self._meta_vars[meta_var][encoded_value].append(decreed_var) + if encoded_value not in self._meta_vars[meta_var]: + self._meta_vars[meta_var][encoded_value] = [] + self._meta_vars[meta_var][encoded_value].append(decreed_var) # Mark as decreed (conditionally acting) self._is_decreed[decreed_var] = True @@ -893,87 +805,13 @@ def add_value_constraint( value2: int | str | list[int|str] - Value or values that the second variable is checked against """ - if self._cs is None: - raise_config_space() - # Get parameters - param1 = self._get_param(var1) - param2 = self._get_param(var2) - mixint_types = (UniformIntegerHyperparameter, OrdinalHyperparameter) - self.has_valcons_ord_int = isinstance(param1, mixint_types) or isinstance( - param2, mixint_types - ) - if not (isinstance(param1, UniformFloatHyperparameter)) and not ( - isinstance(param2, UniformFloatHyperparameter) - ): - # Add forbidden clauses - if isinstance(value1, Sequence): - clause1 = ForbiddenInClause(param1, value1) - else: - clause1 = ForbiddenEqualsClause(param1, value1) - - if isinstance(value2, Sequence): - clause2 = ForbiddenInClause(param2, value2) - else: - clause2 = ForbiddenEqualsClause(param2, value2) - - constraint_clause = ForbiddenAndConjunction(clause1, clause2) - self._cs.add_forbidden_clause(constraint_clause) - else: - if value1 in [">", "<"] and value2 in [">", "<"] and value1 != value2: - if value1 == "<": - constraint_clause = ForbiddenLessThanRelation(param1, param2) - self._cs.add_forbidden_clause(constraint_clause) - else: - constraint_clause = ForbiddenLessThanRelation(param2, param1) - self._cs.add_forbidden_clause(constraint_clause) - else: - raise ValueError("Bad definition of DesignSpace.") - - ## Fix to make constraints work correctly with either IntegerVariable or OrdinalVariable - ## ConfigSpace is malfunctioning - # Get parameters - param1 = self._get_param2(var1) - param2 = self._get_param2(var2) - # Add forbidden clauses - if not (isinstance(param1, UniformFloatHyperparameter)) and not ( - isinstance(param2, UniformFloatHyperparameter) - ): - if isinstance(value1, Sequence): - clause1 = ForbiddenInClause( - param1, list(np.atleast_1d(np.array(value1, dtype=str))) - ) - else: - clause1 = ForbiddenEqualsClause(param1, str(value1)) - - if isinstance(value2, Sequence): - try: - clause2 = ForbiddenInClause( - param2, list(np.atleast_1d(np.array(value2, dtype=str))) - ) - except ValueError: - clause2 = ForbiddenInClause( - param2, list(np.atleast_1d(np.array(value2, dtype=float))) - ) - else: - try: - clause2 = ForbiddenEqualsClause(param2, str(value2)) - except ValueError: - clause2 = ForbiddenEqualsClause(param2, value2) - - constraint_clause = ForbiddenAndConjunction(clause1, clause2) - self._cs_cate.add_forbidden_clause(constraint_clause) + raise_config_space() def _get_param(self, idx): - try: - return self._cs.get_hyperparameter(f"x{idx}") - except KeyError: - raise KeyError(f"Variable not found: {idx}") + raise KeyError(f"Variable not found: {idx}") def _get_param2(self, idx): - try: - return self._cs_cate.get_hyperparameter(f"x{idx}") - except KeyError: - raise KeyError(f"Variable not found: {idx}") + raise KeyError(f"Variable not found: {idx}") @property def _cs_var_idx(self): @@ -983,10 +821,7 @@ def _cs_var_idx(self): This property contains the indices of the params in the ConfigurationSpace. """ - names = self._cs.get_hyperparameter_names() - return np.array( - [names.index(f"x{ix}") for ix in range(len(self.design_variables))] - ) + raise_config_space() @property def _inv_cs_var_idx(self): @@ -994,9 +829,7 @@ def _inv_cs_var_idx(self): See _cs_var_idx. This function returns the opposite mapping: the positions of our design variables for each param. """ - return np.array( - [int(param[1:]) for param in self._cs.get_hyperparameter_names()] - ) + raise_config_space() def _is_conditionally_acting(self) -> np.ndarray: # Decreed variables are the conditionally acting variables @@ -1005,20 +838,6 @@ def _is_conditionally_acting(self) -> np.ndarray: def _correct_get_acting(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """Correct and impute design vectors""" x = x.astype(float) - if self._cs is not None: - # Normalize value according to what ConfigSpace expects - self._normalize_x(x) - - # Get corrected Configuration objects by mapping our design vectors - # to the ordering of the ConfigurationSpace - inv_cs_var_idx = self._inv_cs_var_idx - configs = [] - for xi in x: - configs.append(self._get_correct_config(xi[inv_cs_var_idx])) - - # Convert Configuration objects to design vectors and get the is_active matrix - return self._configs_to_x(configs) - # Simplified implementation # Correct discrete variables x_corr = x.copy() @@ -1039,14 +858,6 @@ def _correct_get_acting(self, x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: return x_corr, is_acting - def _to_seed(self, random_state=None): - seed = None - if isinstance(random_state, int): - seed = random_state - elif isinstance(random_state, np.random.RandomState): - seed = random_state.get_state()[1][0] - return seed - def _sample_valid_x( self, n: int, random_state=None ) -> Tuple[np.ndarray, np.ndarray]: @@ -1056,141 +867,32 @@ def _sample_valid_x( if self.random_state is None: self.random_state = random_state - if self._cs is not None: - # Sample Configuration objects - if self.seed is None: - seed = self._to_seed(random_state) - self.seed = seed - self._cs.seed(self.seed) - if self.seed is not None: - self.seed += 1 - configs = self._cs.sample_configuration(n) - if n == 1: - configs = [configs] - # Convert Configuration objects to design vectors and get the is_active matrix - return self._configs_to_x(configs) - - else: - if self.sampler is None: - self.sampler = LHS( - xlimits=x_limits_unfolded, - random_state=random_state, - criterion="ese", - ) - x = self.sampler(n) - # Fold and cast to discrete - x, _ = self.fold_x(x) - self._normalize_x(x, cs_normalize=False) - # Get acting information and impute - return self.correct_get_acting(x) + if self.sampler is None: + self.sampler = LHS( + xlimits=x_limits_unfolded, + random_state=random_state, + criterion="ese", + ) + x = self.sampler(n) + # Fold and cast to discrete + x, _ = self.fold_x(x) + self._normalize_x(x, cs_normalize=False) + # Get acting information and impute + return self.correct_get_acting(x) def _get_correct_config(self, vector: np.ndarray) -> Configuration: - config = Configuration(self._cs, vector=vector) - - # Unfortunately we cannot directly ask which parameters SHOULD be active - # https://github.com/automl/ConfigSpace/issues/253#issuecomment-1513216665 - # Therefore, we temporarily fix it with a very dirty workaround: catch the error raised in check_configuration - # to find out which parameters should be inactive - while True: - try: - ## Fix to make constraints work correctly with either IntegerVariable or OrdinalVariable - ## ConfigSpace is malfunctioning - if self.isinteger and self.has_valcons_ord_int: - vector2 = np.copy(vector) - self._cs_denormalize_x_ordered(np.atleast_2d(vector2)) - indvec = 0 - for hp in self._cs_cate: - if ( - (str(self._cs.get_hyperparameter(hp)).split()[2]) - == "UniformInteger," - and ( - str(self._cs_cate.get_hyperparameter(hp)).split()[2][:3] - ) - == "Cat" - and not (np.isnan(vector2[indvec])) - ): - vector2[indvec] = int(vector2[indvec]) - int( - str(self._cs_cate.get_hyperparameter(hp)).split()[4][ - 1:-1 - ] - ) - indvec += 1 - self._normalize_x_no_integer(np.atleast_2d(vector2)) - config2 = Configuration(self._cs_cate, vector=vector2) - config2.is_valid_configuration() - - config.is_valid_configuration() - return config - - except ValueError as e: - error_str = str(e) - if "Inactive hyperparameter" in error_str: - # Deduce which parameter is inactive - inactive_param_name = error_str.split("'")[1] - param_idx = self._cs.get_idx_by_hyperparameter_name( - inactive_param_name - ) - - # Modify the vector and create a new Configuration - vector = config.get_array().copy() - vector[param_idx] = np.nan - config = Configuration(self._cs, vector=vector) - - # At this point, the parameter active statuses are set correctly, so we only need to correct the - # configuration to one that does not violate the forbidden clauses - elif isinstance(e, ForbiddenValueError): - if self.seed is None: - seed = self._to_seed(self.random_state) - self.seed = seed - if not (self.has_valcons_ord_int): - return get_random_neighbor(config, seed=self.seed) - else: - vector = config.get_array().copy() - indvec = 0 - vector2 = np.copy(vector) - ## Fix to make constraints work correctly with either IntegerVariable or OrdinalVariable - ## ConfigSpace is malfunctioning - for hp in self._cs_cate: - if ( - str(self._cs_cate.get_hyperparameter(hp)).split()[2][:3] - ) == "Cat" and not (np.isnan(vector2[indvec])): - vector2[indvec] = int(vector2[indvec]) - indvec += 1 - - config2 = Configuration(self._cs_cate, vector=vector2) - config3 = get_random_neighbor(config2, seed=self.seed) - vector3 = config3.get_array().copy() - config4 = Configuration(self._cs, vector=vector3) - return config4 - else: - raise + raise_config_space() def _configs_to_x( self, configs: List["Configuration"] ) -> Tuple[np.ndarray, np.ndarray]: - x = np.zeros((len(configs), len(self.design_variables))) - is_acting = np.zeros(x.shape, dtype=bool) - if len(configs) == 0: - return x, is_acting - - cs_var_idx = self._cs_var_idx - for i, config in enumerate(configs): - x[i, :] = config.get_array()[cs_var_idx] - - # De-normalize continuous and integer variables - self._cs_denormalize_x(x) - - # Set is_active flags and impute x - is_acting = np.isfinite(x) - self._impute_non_acting(x, is_acting) - - return x, is_acting + raise_config_space() def _impute_non_acting(self, x: np.ndarray, is_acting: np.ndarray): for i, dv in enumerate(self.design_variables): if isinstance(dv, FloatVariable): # Impute continuous variables to the mid of their bounds - x[~is_acting[:, i], i] = 0.5 * (dv.upper - dv.lower) + x[~is_acting[:, i], i] = 0.5 * (dv.upper - dv.lower) + dv.lower else: # Impute discrete variables to their lower bounds @@ -1220,21 +922,7 @@ def _normalize_x(self, x: np.ndarray, cs_normalize=True): ) def _normalize_x_no_integer(self, x: np.ndarray, cs_normalize=True): - ordereddesign_variables = [ - self.design_variables[i] for i in self._inv_cs_var_idx - ] - for i, dv in enumerate(ordereddesign_variables): - if isinstance(dv, FloatVariable): - if cs_normalize: - x[:, i] = np.clip( - (x[:, i] - dv.lower) / (dv.upper - dv.lower + 1e-16), 0, 1 - ) - - elif isinstance(dv, (OrdinalVariable, CategoricalVariable)): - # To ensure equal distribution of continuous values to discrete values, we first stretch-out the - # continuous values to extend to 0.5 beyond the integer limits and then round. This ensures that the - # values at the limits get a large-enough share of the continuous values - x[:, i] = self._round_equally_distributed(x[:, i], dv.lower, dv.upper) + raise_config_space() def _cs_denormalize_x(self, x: np.ndarray): for i, dv in enumerate(self.design_variables): @@ -1267,36 +955,3 @@ def __str__(self): def __repr__(self): return f"{self.__class__.__name__}({self.design_variables!r})" - - -class NoDefaultConfigurationSpace(ConfigurationSpace): - """ConfigurationSpace that supports no default configuration""" - - def get_default_configuration(self, *args, **kwargs): - raise NotImplementedError - - def _check_default_configuration(self, *args, **kwargs): - pass - - -class FixedIntegerParam(UniformIntegerHyperparameter): - def get_neighbors( - self, - value: float, - rs: np.random.RandomState, - number: int = 4, - transform: bool = False, - std: float = 0.2, - ) -> List[int]: - # Temporary fix until https://github.com/automl/ConfigSpace/pull/313 is released - center = self._transform(value) - lower, upper = self.lower, self.upper - if upper - lower - 1 < number: - neighbors = sorted(set(range(lower, upper + 1)) - {center}) - if transform: - return neighbors - return self._inverse_transform(np.asarray(neighbors)).tolist() - - return super().get_neighbors( - value, rs, number=number, transform=transform, std=std - ) diff --git a/smt/utils/test/test_design_space.py b/smt/design_space/tests/test_design_space.py similarity index 78% rename from smt/utils/test/test_design_space.py rename to smt/design_space/tests/test_design_space.py index 60cbd4515..5dc18314e 100644 --- a/smt/utils/test/test_design_space.py +++ b/smt/design_space/tests/test_design_space.py @@ -8,17 +8,36 @@ import numpy as np -import smt.utils.design_space as ds from smt.sampling_methods import LHS -from smt.utils.design_space import ( - HAS_CONFIG_SPACE, - BaseDesignSpace, - CategoricalVariable, - DesignSpace, - FloatVariable, - IntegerVariable, - OrdinalVariable, -) +import os + +if not (os.getenv("RUN_PLAIN_DESIGN_SPACE_TEST")): + HAS_ADSG = False + HAS_DESIGN_SPACE_EXT = False + HAS_CONFIG_SPACE = False + import smt.design_space.design_space as ds + from smt.design_space.design_space import ( + BaseDesignSpace, + CategoricalVariable, + FloatVariable, + IntegerVariable, + OrdinalVariable, + DesignSpace, + ) +else: + import smt.design_space as ds + from smt.design_space import ( + HAS_CONFIG_SPACE, + HAS_ADSG, + HAS_DESIGN_SPACE_EXT, + BaseDesignSpace, + CategoricalVariable, + FloatVariable, + IntegerVariable, + OrdinalVariable, + DesignSpace, + DesignSpaceGraph, + ) @contextlib.contextmanager @@ -340,7 +359,7 @@ def test_design_space_hierarchical(self): CategoricalVariable(["A", "B", "C"]), # x0 CategoricalVariable(["E", "F"]), # x1 IntegerVariable(0, 1), # x2 - FloatVariable(0, 1), # x3 + FloatVariable(0.1, 1), # x3 ], random_state=42, ) @@ -372,14 +391,14 @@ def test_design_space_hierarchical(self): [0, 1, 0, 0.75], [0, 1, 1, 0.25], [0, 1, 1, 0.75], - [1, 0, 0, 0.5], - [1, 0, 1, 0.5], - [1, 1, 0, 0.5], - [1, 1, 1, 0.5], - [2, 0, 0, 0.5], - [2, 0, 1, 0.5], - [2, 1, 0, 0.5], - [2, 1, 1, 0.5], + [1, 0, 0, 0.55], + [1, 0, 1, 0.55], + [1, 1, 0, 0.55], + [1, 1, 1, 0.55], + [2, 0, 0, 0.55], + [2, 0, 1, 0.55], + [2, 1, 0, 0.55], + [2, 1, 1, 0.55], ] ), ) @@ -410,11 +429,11 @@ def test_design_space_hierarchical(self): x_sampled, is_acting_sampled = ds.sample_valid_x(100, random_state=42) assert x_sampled.shape == (100, 4) x_sampled[is_acting_sampled[:, 3], 3] = np.round( - x_sampled[is_acting_sampled[:, 3], 3] + x_sampled[is_acting_sampled[:, 3], 3], 4 ) x_corr, is_acting_corr = ds.correct_get_acting(x_sampled) - self.assertTrue(np.all(x_corr == x_sampled)) + self.assertTrue(np.sum(np.abs(x_corr - x_sampled)) < 1e-12) self.assertTrue(np.all(is_acting_corr == is_acting_sampled)) seen_x = set() @@ -422,7 +441,10 @@ def test_design_space_hierarchical(self): for i, xi in enumerate(x_sampled): seen_x.add(tuple(xi)) seen_is_acting.add(tuple(is_acting_sampled[i, :])) - assert len(seen_x) == 16 + if HAS_ADSG: + assert len(seen_x) == 49 + else: + assert len(seen_x) == 42 assert len(seen_is_acting) == 2 @unittest.skipIf( @@ -583,7 +605,6 @@ def _is_conditionally_acting(self) -> np.ndarray: ds.declare_decreed_var( decreed_var=3, meta_var=0, meta_value="A" ) # Activate x3 if x0 == A - self.assertRaises( RuntimeError, lambda: ds.sample_valid_x(10, random_state=42) ) @@ -680,6 +701,162 @@ def test_restrictive_value_constraint_categorical(self): x_cartesian2, ) + @unittest.skipIf( + not (HAS_DESIGN_SPACE_EXT), + "Architecture Design Space Graph or ConfigSpace not installed.", + ) + def test_adsg_to_legacy(self): + from adsg_core import BasicADSG, NamedNode, DesignVariableNode + from smt_design_space.design_space import ensure_design_space + from adsg_core import GraphProcessor + + # Create the ADSG + adsg = BasicADSG() + ndv = 13 + # Create nodes + n = [NamedNode(f"N{i}") for i in range(ndv)] + n = [ + NamedNode("MLP"), + NamedNode("Learning_rate"), + NamedNode("Activation_function"), + NamedNode("Optimizer"), + NamedNode("Decay"), + NamedNode("Power_update"), + NamedNode("Average_start"), + NamedNode("Running_Average_1"), + NamedNode("Running_Average_2"), + NamedNode("Numerical_Stability"), + NamedNode("Nb_layers"), + NamedNode("Layer_1"), + NamedNode("Layer_2"), + NamedNode("Layer_3"), # NamedNode("Dropout"), + NamedNode("ASGD"), + NamedNode("Adam"), + NamedNode("20...40"), + NamedNode("40"), + NamedNode("45"), + NamedNode("20...40"), + NamedNode("40"), + NamedNode("45"), + NamedNode("20...40"), + NamedNode("40"), + NamedNode("45"), + ] + adsg.add_node(n[1]) + adsg.add_node(n[2]) + adsg.add_edges( + [ + (n[3], n[10]), + (n[14], n[4]), + (n[14], n[5]), + (n[14], n[6]), + (n[15], n[7]), + (n[15], n[8]), + (n[15], n[9]), + ] + ) + adsg.add_selection_choice("Optimizer_Choice", n[3], [n[14], n[15]]) + adsg.add_selection_choice("#layers", n[10], [n[11], n[12], n[13]]) + a = [] + for i in range(3): + a.append(NamedNode(str(25 + 5 * i))) + b = a.copy() + b.append(n[17]) + b.append(n[18]) + choicel1 = adsg.add_selection_choice("#neurons_1", n[11], b) + adsg.add_edges([(n[12], choicel1), (n[13], choicel1)]) + + a = [] + for i in range(3): + a.append(NamedNode(str(25 + 5 * i))) + b = a.copy() + b.append(n[20]) + b.append(n[21]) + choicel1 = adsg.add_selection_choice("#neurons_2", n[12], b) + adsg.add_edges([(n[13], choicel1)]) + + a = [] + for i in range(3): + a.append(NamedNode(str(25 + 5 * i))) + b = a.copy() + b.append(n[23]) + b.append(n[24]) + choicel1 = adsg.add_selection_choice("#neurons_3", n[13], b) + + adsg.add_incompatibility_constraint([n[15], n[13]]) + adsg.add_incompatibility_constraint([n[14], n[17]]) + adsg.add_incompatibility_constraint([n[14], n[18]]) + adsg.add_incompatibility_constraint([n[14], n[20]]) + adsg.add_incompatibility_constraint([n[14], n[21]]) + adsg.add_incompatibility_constraint([n[14], n[23]]) + adsg.add_incompatibility_constraint([n[14], n[24]]) + start_nodes = set() + start_nodes.add(n[3]) + start_nodes.add(n[2]) + start_nodes.add(n[1]) + adsg.add_edges( + [ + (n[1], DesignVariableNode("x0", bounds=(0, 1))), + (n[4], DesignVariableNode("x1", bounds=(0, 1))), + (n[5], DesignVariableNode("x2", bounds=(0, 1))), + (n[6], DesignVariableNode("x3", bounds=(0, 1))), + (n[7], DesignVariableNode("x4", bounds=(0, 1))), + (n[8], DesignVariableNode("x5", bounds=(0, 1))), + (n[9], DesignVariableNode("x6", bounds=(0, 1))), + ] + ) + adsg.add_selection_choice( + "Activation_Choice", + n[2], + [NamedNode("ReLU"), NamedNode("Sigmoid"), NamedNode("Tanh")], + ) + adsg = adsg.set_start_nodes(start_nodes) + adsg.render() + gp = GraphProcessor(adsg) + gp.get_statistics() + design_space = ensure_design_space(design_space=adsg) + np.testing.assert_array_equal( + np.array( + [ + False, + False, + False, + True, + True, + True, + False, + True, + True, + True, + True, + True, + True, + ] + ), + design_space.is_conditionally_acting, + ) + design_space2 = DesignSpaceGraph(adsg=adsg) + np.testing.assert_array_equal( + np.array( + [ + False, + False, + False, + True, + True, + True, + False, + True, + True, + True, + True, + True, + True, + ] + ), + design_space2.is_conditionally_acting, + ) + if __name__ == "__main__": unittest.main() diff --git a/smt/design_space/tests/test_design_space_local.py b/smt/design_space/tests/test_design_space_local.py new file mode 100644 index 000000000..61a775e9d --- /dev/null +++ b/smt/design_space/tests/test_design_space_local.py @@ -0,0 +1,21 @@ +import unittest +import os + +os.environ["RUN_PLAIN_DESIGN_SPACE_TEST"] = "1" +from test_design_space import Test # Import the existing test class + + +class TestDSLocal(Test): + def test_design_variables(self): + os.environ["RUN_PLAIN_DESIGN_SPACE_TEST"] = "1" + # Load all tests from test_design_space.py + loader = unittest.TestLoader() + suite = loader.discover(start_dir=".", pattern="test_design_space.py") + # Run the tests + runner = unittest.TextTestRunner() + runner.run(suite) + del os.environ["RUN_PLAIN_DESIGN_SPACE_TEST"] + + +if __name__ == "__main__": + unittest.main() diff --git a/smt/problems/hierarchical_goldstein.py b/smt/problems/hierarchical_goldstein.py index 43166d23a..301cb6055 100644 --- a/smt/problems/hierarchical_goldstein.py +++ b/smt/problems/hierarchical_goldstein.py @@ -10,12 +10,13 @@ import numpy as np from smt.problems.problem import Problem -from smt.utils.design_space import ( - CategoricalVariable, - DesignSpace, + +from smt.design_space import ( + OrdinalVariable, FloatVariable, IntegerVariable, - OrdinalVariable, + DesignSpace, + CategoricalVariable, ) diff --git a/smt/problems/mixed_cantilever_beam.py b/smt/problems/mixed_cantilever_beam.py index 4c410b47e..1e3ae91f1 100644 --- a/smt/problems/mixed_cantilever_beam.py +++ b/smt/problems/mixed_cantilever_beam.py @@ -10,7 +10,11 @@ import numpy as np from smt.problems.problem import Problem -from smt.utils.design_space import CategoricalVariable, DesignSpace, FloatVariable +from smt.design_space import ( + FloatVariable, + DesignSpace, + CategoricalVariable, +) class MixedCantileverBeam(Problem): diff --git a/smt/problems/neural_network.py b/smt/problems/neural_network.py index 849f9b39d..a78323cf2 100644 --- a/smt/problems/neural_network.py +++ b/smt/problems/neural_network.py @@ -11,12 +11,12 @@ import numpy as np from smt.problems.problem import Problem -from smt.utils.design_space import ( - CategoricalVariable, - DesignSpace, +from smt.design_space import ( + OrdinalVariable, FloatVariable, IntegerVariable, - OrdinalVariable, + DesignSpace, + CategoricalVariable, ) diff --git a/smt/problems/problem.py b/smt/problems/problem.py index 5bda81c9c..799413101 100644 --- a/smt/problems/problem.py +++ b/smt/problems/problem.py @@ -11,7 +11,11 @@ import numpy as np from smt.utils.checks import ensure_2d_array -from smt.utils.design_space import BaseDesignSpace, DesignSpace + +from smt.design_space import ( + BaseDesignSpace, + DesignSpace, +) from smt.utils.options_dictionary import OptionsDictionary diff --git a/smt/surrogate_models/__init__.py b/smt/surrogate_models/__init__.py index 8c963d5ca..8b774a6e6 100644 --- a/smt/surrogate_models/__init__.py +++ b/smt/surrogate_models/__init__.py @@ -10,12 +10,13 @@ from .sgp import SGP from .krg_based import MixIntKernelType -from smt.utils.design_space import ( + +from smt.design_space import ( DesignSpace, + CategoricalVariable, FloatVariable, IntegerVariable, OrdinalVariable, - CategoricalVariable, ) from smt.utils.kriging import MixHrcKernelType diff --git a/smt/surrogate_models/gpx.py b/smt/surrogate_models/gpx.py index 6ee0581b6..fc2a1aa85 100644 --- a/smt/surrogate_models/gpx.py +++ b/smt/surrogate_models/gpx.py @@ -1,7 +1,8 @@ import numpy as np from smt.surrogate_models.surrogate_model import SurrogateModel -from smt.utils.design_space import ( + +from smt.design_space import ( BaseDesignSpace, ensure_design_space, ) diff --git a/smt/surrogate_models/krg.py b/smt/surrogate_models/krg.py index 69ec35dab..cddd921ae 100644 --- a/smt/surrogate_models/krg.py +++ b/smt/surrogate_models/krg.py @@ -9,7 +9,6 @@ from smt.kernels import Kernel - class KRG(KrgBased): name = "Kriging" diff --git a/smt/surrogate_models/krg_based.py b/smt/surrogate_models/krg_based.py index 262275eb7..de11a38c2 100644 --- a/smt/surrogate_models/krg_based.py +++ b/smt/surrogate_models/krg_based.py @@ -26,11 +26,12 @@ ) from smt.kernels.kernels import _Constant from smt.utils.checks import check_support, ensure_2d_array -from smt.utils.design_space import ( +from smt.design_space import ( BaseDesignSpace, CategoricalVariable, ensure_design_space, ) + from smt.utils.kriging import ( MixHrcKernelType, differences, diff --git a/smt/surrogate_models/tests/test_surrogate_model_examples.py b/smt/surrogate_models/tests/test_surrogate_model_examples.py index a36753910..90cd49232 100644 --- a/smt/surrogate_models/tests/test_surrogate_model_examples.py +++ b/smt/surrogate_models/tests/test_surrogate_model_examples.py @@ -249,7 +249,10 @@ def test_mixed_int_krg(self): from smt.applications.mixed_integer import MixedIntegerKrigingModel from smt.surrogate_models import KRG - from smt.utils.design_space import DesignSpace, IntegerVariable + from smt.design_space import ( + DesignSpace, + IntegerVariable, + ) xt = np.array([0.0, 2.0, 3.0]) yt = np.array([0.0, 1.5, 0.9]) diff --git a/smt/tests/test_derivs.py b/smt/tests/test_derivs.py index 9aeb6c7c8..72a6b9886 100644 --- a/smt/tests/test_derivs.py +++ b/smt/tests/test_derivs.py @@ -13,7 +13,11 @@ from smt.applications import MFK from smt.problems import Sphere from smt.sampling_methods import LHS -from smt.utils.design_space import DesignSpace + +from smt.design_space import ( + DesignSpace, +) + from smt.utils.misc import compute_rms_error from smt.utils.silence import Silence from smt.utils.sm_test_case import SMTestCase diff --git a/smt/tests/test_extrap.py b/smt/tests/test_extrap.py index 9167017eb..452163b26 100644 --- a/smt/tests/test_extrap.py +++ b/smt/tests/test_extrap.py @@ -11,7 +11,10 @@ from smt.problems import Sphere from smt.sampling_methods import LHS -from smt.utils.design_space import DesignSpace + +from smt.design_space import ( + DesignSpace, +) from smt.utils.silence import Silence from smt.utils.sm_test_case import SMTestCase diff --git a/smt/tests/test_low_dim.py b/smt/tests/test_low_dim.py index f9c2b8ac0..6caa9fc65 100644 --- a/smt/tests/test_low_dim.py +++ b/smt/tests/test_low_dim.py @@ -13,7 +13,10 @@ from smt.problems import Sphere, TensorProduct from smt.sampling_methods import LHS from smt.surrogate_models import LS, QP -from smt.utils.design_space import DesignSpace + +from smt.design_space import ( + DesignSpace, +) from smt.utils.misc import compute_rms_error from smt.utils.silence import Silence from smt.utils.sm_test_case import SMTestCase diff --git a/smt/tests/test_output_derivs.py b/smt/tests/test_output_derivs.py index 738ba04b5..f200150cd 100644 --- a/smt/tests/test_output_derivs.py +++ b/smt/tests/test_output_derivs.py @@ -12,7 +12,10 @@ from smt.problems import Sphere from smt.sampling_methods import FullFactorial -from smt.utils.design_space import DesignSpace + +from smt.design_space import ( + DesignSpace, +) from smt.utils.silence import Silence from smt.utils.sm_test_case import SMTestCase diff --git a/smt/tests/test_training_derivs.py b/smt/tests/test_training_derivs.py index 689f68d8a..e47a7a298 100644 --- a/smt/tests/test_training_derivs.py +++ b/smt/tests/test_training_derivs.py @@ -12,7 +12,10 @@ from smt.problems import Sphere, TensorProduct from smt.sampling_methods import FullFactorial -from smt.utils.design_space import DesignSpace + +from smt.design_space import ( + DesignSpace, +) from smt.utils.misc import compute_rms_error from smt.utils.silence import Silence from smt.utils.sm_test_case import SMTestCase diff --git a/smt/utils/kriging.py b/smt/utils/kriging.py index e7f7d9c65..3bc7bfbb0 100644 --- a/smt/utils/kriging.py +++ b/smt/utils/kriging.py @@ -12,7 +12,10 @@ from sklearn.cross_decomposition import PLSRegression as pls from sklearn.metrics.pairwise import check_pairwise_arrays -from smt.utils.design_space import CategoricalVariable + +from smt.design_space import ( + CategoricalVariable, +) USE_NUMBA_JIT = int(os.getenv("USE_NUMBA_JIT", 0)) prange = range diff --git a/tutorial/Misc/SMT_CoopCompKRG.ipynb b/tutorial/Misc/SMT_CoopCompKRG.ipynb index 65d1e3a1c..9c0b22918 100644 --- a/tutorial/Misc/SMT_CoopCompKRG.ipynb +++ b/tutorial/Misc/SMT_CoopCompKRG.ipynb @@ -83,10 +83,10 @@ "yt = prob(xt)\n", "np.random.seed(1)\n", "\n", - "#Chosen point to compare the different models\n", - "xpoint = (-5 + np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]))/10.\n", + "# Chosen point to compare the different models\n", + "xpoint = (-5 + np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]])) / 10.0\n", "ypoint = prob(xpoint)\n", - "print('Exact value of the point', ypoint)" + "print(\"Exact value of the point\", ypoint)" ] }, { @@ -192,12 +192,9 @@ " sm1.train(active_coop_comp, comp_var)\n", "\n", "\n", - "\n", "# Prediction as for ordinary Kriging at the chosen point\n", - "print('Value at the chosen point', sm1.predict_values(xpoint))\n", - "print('Variance at the chosen point', sm1.predict_variances(xpoint))\n", - "\n", - "\n" + "print(\"Value at the chosen point\", sm1.predict_values(xpoint))\n", + "print(\"Variance at the chosen point\", sm1.predict_variances(xpoint))" ] }, { @@ -253,8 +250,8 @@ "sm2.train()\n", "\n", "# Prediction for ordinary Kriging\n", - "print('Value at the chosen point', sm2.predict_values(xpoint))\n", - "print('Variance at the chosen point', sm2.predict_variances(xpoint))\n" + "print(\"Value at the chosen point\", sm2.predict_values(xpoint))\n", + "print(\"Variance at the chosen point\", sm2.predict_variances(xpoint))" ] }, { @@ -316,8 +313,8 @@ "print(\"\\n The model automatically choose \" + str(ncomp) + \" components.\")\n", "\n", "## You can predict a 10-dimension point from the 3-dimensional model\n", - "print('Value at the chosen point', sm3.predict_values(xpoint))\n", - "print('Variance at the chosen point', sm3.predict_variances(xpoint))" + "print(\"Value at the chosen point\", sm3.predict_values(xpoint))\n", + "print(\"Variance at the chosen point\", sm3.predict_variances(xpoint))" ] }, { @@ -427,7 +424,7 @@ "source": [ "# Construction of the validation points to plot the different model predictions\n", "ntest = 200\n", - "sampling = LHS(xlimits=prob.xlimits, random_state=41)\n", + "sampling = LHS(xlimits=prob.xlimits, random_state=41)\n", "xtest = sampling(ntest)\n", "ytest = prob(xtest)\n", "\n", diff --git a/tutorial/MixedInteger/SMT_DesignSpace_example.ipynb b/tutorial/MixedInteger/SMT_DesignSpace_example.ipynb deleted file mode 100644 index 082d8d1bc..000000000 --- a/tutorial/MixedInteger/SMT_DesignSpace_example.ipynb +++ /dev/null @@ -1,395 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "view-in-github" - }, - "source": [ - "\"Open" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mDCpNW2-mu3w" - }, - "source": [ - "
\n", - " \n", - "This tutorial describes how to use de DesignSpace within the SMT toolbox. \n", - "
\n", - " \n", - " May 2024 - `SMT version 2.5.1`\n", - " \n", - " Jasper Bussemaker (DLR), Paul Saves, and Nathalie BARTOLI (ONERA/DTIS/M2CI)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "

Some updates

\n", - "
    - Manipulation of mixed DOE (continuous, integer, categorical and hierarchical variables)
\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "gDeEbi7nmu32" - }, - "source": [ - "

\n", - "To use SMT models, please follow this link : https://github.com/SMTorg/SMT/blob/master/README.md. The documentation is available here: http://smt.readthedocs.io/en/latest/\n", - "

\n", - "\n", - "The reference paper is available \n", - "here https://www.sciencedirect.com/science/article/pii/S0965997818309360?via%3Dihub \n", - "\n", - "or as a preprint: http://mdolab.engin.umich.edu/content/python-surrogate-modeling-framework-derivatives" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "uU32V-7bmu33" - }, - "source": [ - "For mixed integer with continuous relaxation, the reference paper is available here https://www.sciencedirect.com/science/article/pii/S0925231219315619" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "EweBFT9Ap8Ay", - "outputId": "4c016eb0-6a75-46f7-d2db-c4ff17ec284a" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: smt in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (2.6.2)\n", - "Requirement already satisfied: scikit-learn in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from smt) (1.4.0)\n", - "Requirement already satisfied: pyDOE3 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from smt) (1.0.3)\n", - "Requirement already satisfied: scipy in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from smt) (1.11.3)\n", - "Requirement already satisfied: jenn in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from smt) (1.0.6)\n", - "Requirement already satisfied: jsonpointer>=2.4 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jenn->smt) (3.0.0)\n", - "Requirement already satisfied: jsonschema>=4.22 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jenn->smt) (4.22.0)\n", - "Requirement already satisfied: orjson>=3.9 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jenn->smt) (3.10.6)\n", - "Requirement already satisfied: numpy>=1.22 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jenn->smt) (1.23.5)\n", - "Requirement already satisfied: joblib>=1.2.0 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from scikit-learn->smt) (1.4.2)\n", - "Requirement already satisfied: threadpoolctl>=2.0.0 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from scikit-learn->smt) (3.5.0)\n", - "Requirement already satisfied: attrs>=22.2.0 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jsonschema>=4.22->jenn->smt) (23.1.0)\n", - "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jsonschema>=4.22->jenn->smt) (2023.12.1)\n", - "Requirement already satisfied: referencing>=0.28.4 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jsonschema>=4.22->jenn->smt) (0.35.1)\n", - "Requirement already satisfied: rpds-py>=0.7.1 in /stck/psaves/miniconda3/envs/newenv1/lib/python3.9/site-packages (from jsonschema>=4.22->jenn->smt) (0.18.1)\n" - ] - } - ], - "source": [ - "# to install smt\n", - "!pip install smt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "If you use hierarchical variables and the size of your doe greater than 30 points, you may leverage the `numba` JIT compiler to speed up the computation\n", - "To do so:\n", - " \n", - " - install numba library\n", - " \n", - " `pip install numba`\n", - " \n", - " \n", - " - and define the environment variable `USE_NUMBA_JIT = 1` (unset or 0 if you do not want to use numba) \n", - " \n", - " - Linux: export USE_NUMBA_JIT = 1\n", - " \n", - " - Windows: set USE_NUMBA_JIT = 1\n", - "\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from smt.utils.design_space import (\n", - " DesignSpace,\n", - " FloatVariable,\n", - " IntegerVariable,\n", - " OrdinalVariable,\n", - " CategoricalVariable,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "aggEHXHmmu4I" - }, - "source": [ - "# Manipulate DOE with mixed, categorical & hierarchical variables" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "dvVSDE-omu4I" - }, - "source": [ - "4 variables \n", - " - 1 categorical variable with 2 labels ['A', 'B'] # x0 categorical: A or B; order is not relevant\n", - " - 1 ordinal variable with 3 levels ['C', 'D', 'E']), # x1 ordinal: C, D or E; order is relevant\n", - " - 1 integer variable [0,2]: 3 possibilities: 0, 1, 2\n", - " - 1 continuous variable $\\in [0, 1]$\n", - " \n", - " \n", - " **Posssibility to have hierarchical variable: x1 exists only if x0 = 'A'**" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of design variables 4\n" - ] - } - ], - "source": [ - "# Instantiate the design space with all its design variables:\n", - "\n", - "ds = DesignSpace(\n", - " [\n", - " CategoricalVariable(\n", - " [\"A\", \"B\"]\n", - " ), # x0 categorical: A or B; order is not relevant\n", - " OrdinalVariable([\"C\", \"D\", \"E\"]), # x1 ordinal: C, D or E; order is relevant\n", - " IntegerVariable(0, 2), # x2 integer between 0 and 2 (inclusive): 0, 1, 2\n", - " FloatVariable(0, 1), # c3 continuous between 0 and 1\n", - " ]\n", - ")\n", - "\n", - "print(\"Number of design variables\", len(ds.design_variables))\n", - "# You can define decreed variables (conditional activation):\n", - "ds.declare_decreed_var(\n", - " decreed_var=1, meta_var=0, meta_value=\"A\"\n", - ") # Activate x1 if x0 == A" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Data encoded: \n", - " [[0. 2. 1. 0.50141714]\n", - " [1. 0. 1. 0.48517298]\n", - " [0. 1. 2. 0.83539973]\n", - " [0. 0. 1. 0.65900747]\n", - " [0. 2. 1. 0.5664969 ]]\n", - "Data in initial space: \n", - " [['A', 'E', 1.0, 0.5014171417556681], ['B', 'C', 1.0, 0.48517297677504534], ['A', 'D', 2.0, 0.835399731580032], ['A', 'C', 1.0, 0.6590074713239295], ['A', 'E', 1.0, 0.5664968989411066]]\n" - ] - } - ], - "source": [ - "## To give some examples\n", - "# It is also possible to randomly sample design vectors conforming to the constraints:\n", - "n = 5\n", - "x_sampled, is_acting_sampled = ds.sample_valid_x(5)\n", - "\n", - "print(\"Data encoded: \\n\", x_sampled)\n", - "print(\"Data in initial space: \\n\", ds.decode_values(x_sampled))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Which variables are active \n", - " [[ True True True True]\n", - " [ True False True True]\n", - " [ True True True True]\n", - " [ True True True True]\n", - " [ True True True True]]\n" - ] - } - ], - "source": [ - "# After defining everything correctly, you can then use the design space object\n", - "# to correct design vectors and get information about which design variables are acting:\n", - "x_corr, is_acting = ds.correct_get_acting(x_sampled)\n", - "print(\"Which variables are active \\n\", is_acting)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Limits of each variable \n", - " [['A', 'B'], ['0', '1', '2'], (0, 2), (0, 1)]\n", - "Continuous bounds with the encoding done (4 variables now) \n", - " [[0 1]\n", - " [0 2]\n", - " [0 2]\n", - " [0 1]]\n", - "Continuous bounds with the unfolded encoding done (5 variables now)\n", - " [[0. 1.]\n", - " [0. 1.]\n", - " [0. 2.]\n", - " [0. 2.]\n", - " [0. 1.]]\n" - ] - } - ], - "source": [ - "# If needed, it is possible to get the legacy design space definition format:\n", - "xlimits = ds.get_x_limits()\n", - "cont_bounds = ds.get_num_bounds()\n", - "unfolded_cont_bounds = ds.get_unfolded_num_bounds()\n", - "print(\"Limits of each variable \\n\", xlimits)\n", - "print(\"Continuous bounds with the encoding done (4 variables now) \\n\", cont_bounds)\n", - "print(\n", - " \"Continuous bounds with the unfolded encoding done (5 variables now)\\n\",\n", - " unfolded_cont_bounds,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Manipulate DOE with continuous variables" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of design variables = 3 or 3\n" - ] - } - ], - "source": [ - "# You can also instantiate a purely-continuous design space from bounds directly:\n", - "continuous_design_space = DesignSpace([(0, 1), (0, 2), (0.5, 5.5)])\n", - "print(\n", - " \"Number of design variables =\",\n", - " continuous_design_space.n_dv,\n", - " \" or \",\n", - " len(continuous_design_space.design_variables),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "x_sampled_cont, is_acting_sampled_cont = continuous_design_space.sample_valid_x(5)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Data encoded: \n", - " [[0.31711655 0.1616406 2.58306429]\n", - " [0.49960756 0.62928794 2.96869517]\n", - " [0.11611917 0.63034393 2.74785997]\n", - " [0.44327097 0.3821734 3.78969088]\n", - " [0.32158259 1.88724976 1.06429242]]\n", - "Is_acting: \n", - " [[ True True True]\n", - " [ True True True]\n", - " [ True True True]\n", - " [ True True True]\n", - " [ True True True]]\n" - ] - } - ], - "source": [ - "print(\"Data encoded: \\n\", x_sampled_cont)\n", - "print(\"Is_acting: \\n\", is_acting_sampled_cont)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "colab": { - "include_colab_link": true, - "name": "SMT_DesignSpace_example.ipynb", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.18" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/tutorial/README.md b/tutorial/README.md index 0b930db30..55b104680 100644 --- a/tutorial/README.md +++ b/tutorial/README.md @@ -72,10 +72,6 @@ These tutorials introduce to use the opensource Surrogate Modeling Toolbox where [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/SMTorg/smt/blob/master/tutorial/MixedInteger/RunTestCases_Paper_SMT_v2.ipynb) -### DesignSpace to variables (continuous, discrete, categorical, hierarchical) - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/SMTorg/smt/blob/master/tutorial/MixedInteger/SMT_DesignSpace_example.ipynb) - ### Mixed-Integer Gaussian Process and Bayesian Optimization to solve unconstrained problems with mixed variables (continuous, discrete, categorical) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/SMTorg/smt/blob/master/tutorial/MixedInteger/SMT_MixedInteger.ipynb)