|
26 | 26 | from numpy import spacing |
27 | 27 | from numpy import append |
28 | 28 | from numpy import min, max |
| 29 | +from spotpython.utils.convert import get_shape, set_shape |
29 | 30 | from spotpython.utils.init import fun_control_init, optimizer_control_init, surrogate_control_init, design_control_init |
30 | 31 | from spotpython.utils.compare import selectNew |
31 | 32 | from spotpython.utils.aggregate import aggregate_mean_var, select_distant_points |
@@ -1032,6 +1033,44 @@ def initialize_design_matrix(self, X_start=None) -> None: |
1032 | 1033 |
|
1033 | 1034 | self.X = repair_non_numeric(X0, self.var_type) |
1034 | 1035 |
|
| 1036 | + def _mo2so(self, y_mo) -> None: |
| 1037 | + """ |
| 1038 | + Converts multi-objective values to a single-objective value by applying a user-defined |
| 1039 | + function from ``fun_control['fun_mo2so']``. If no user-defined function is given, the |
| 1040 | + values in the first objective row are used. |
| 1041 | +
|
| 1042 | + This method is called after the objective function evaluation (i.e., after ``self.fun()``). |
| 1043 | + It typically returns a 1D array with the single-objective values. |
| 1044 | +
|
| 1045 | + Args: |
| 1046 | + y_mo (numpy.ndarray): |
| 1047 | + A 2D array of shape (m, n), where ``m`` is |
| 1048 | + the number of objectives and ``n`` is the number of data points. |
| 1049 | +
|
| 1050 | + Returns: |
| 1051 | + numpy.ndarray: |
| 1052 | + A 1D array of shape (n,) with single-objective values if ``m > 1``. If only one |
| 1053 | + objective is present (``m == 1``), no transformation is performed. |
| 1054 | +
|
| 1055 | + """ |
| 1056 | + n, k = get_shape(y_mo) |
| 1057 | + # Ensure that y_mo is a (n, k) numpy array |
| 1058 | + y_mo = np.atleast_2d(y_mo) |
| 1059 | + m = y_mo.shape[0] # Number of objectives |
| 1060 | + if m > 1: |
| 1061 | + if self.fun_control["fun_mo2so"] is not None: |
| 1062 | + y0 = self.fun_control["fun_mo2so"](y_mo) |
| 1063 | + else: |
| 1064 | + # Select the first row of an (m, k) array |
| 1065 | + y0 = y_mo[0, :] |
| 1066 | + else: |
| 1067 | + if k is None: |
| 1068 | + y0 = y_mo.flatten() |
| 1069 | + else: |
| 1070 | + y0 = y_mo # Keep as 2D array for single-objective case |
| 1071 | + |
| 1072 | + return y0 |
| 1073 | + |
1035 | 1074 | def evaluate_initial_design(self) -> None: |
1036 | 1075 | """ |
1037 | 1076 | Evaluate the initial design. |
@@ -1084,7 +1123,10 @@ def evaluate_initial_design(self) -> None: |
1084 | 1123 | logger.debug("In Spot() evaluate_initial_design(), before calling self.fun: X_all: %s", X_all) |
1085 | 1124 | logger.debug("In Spot() evaluate_initial_design(), before calling self.fun: fun_control: %s", self.fun_control) |
1086 | 1125 |
|
1087 | | - self.y = self.fun(X=X_all, fun_control=self.fun_control) |
| 1126 | + y_mo = self.fun(X=X_all, fun_control=self.fun_control) |
| 1127 | + # Convert multi-objective values to single-objective values |
| 1128 | + # TODO: Store y_mo in self.y_mo (append new values) |
| 1129 | + self.y = self._mo2so(y_mo) |
1088 | 1130 | self.y = apply_penalty_NA(self.y, self.fun_control["penalty_NA"], verbosity=self.verbosity) |
1089 | 1131 | logger.debug("In Spot() evaluate_initial_design(), after calling self.fun: self.y: %s", self.y) |
1090 | 1132 |
|
@@ -1411,7 +1453,11 @@ def update_design(self) -> None: |
1411 | 1453 | self.fun_control["seed"], |
1412 | 1454 | ) |
1413 | 1455 | # (S-18): Evaluating New Solutions: |
1414 | | - y0 = self.fun(X=X_all, fun_control=self.fun_control) |
| 1456 | + y_mo = self.fun(X=X_all, fun_control=self.fun_control) |
| 1457 | + # Convert multi-objective values to single-objective values: |
| 1458 | + # TODO: Store y_mo in self.y_mo (append new values) |
| 1459 | + y0 = self._mo2so(y_mo) |
| 1460 | + |
1415 | 1461 | y0 = apply_penalty_NA(y0, self.fun_control["penalty_NA"], verbosity=self.verbosity) |
1416 | 1462 | X0, y0 = remove_nan(X0, y0, stop_on_zero_return=False) |
1417 | 1463 | # Append New Solutions (only if they are not nan): |
@@ -1644,7 +1690,10 @@ def generate_random_point(self): |
1644 | 1690 | X_all = self.to_all_dim_if_needed(X0) |
1645 | 1691 | logger.debug("In Spot() generate_random_point(), before calling self.fun: X_all: %s", X_all) |
1646 | 1692 | logger.debug("In Spot() generate_random_point(), before calling self.fun: fun_control: %s", self.fun_control) |
1647 | | - y0 = self.fun(X=X_all, fun_control=self.fun_control) |
| 1693 | + # Convert multi-objective values to single-objective values |
| 1694 | + # TODO: Store y_mo in self.y_mo (append new values) |
| 1695 | + y_mo = self.fun(X=X_all, fun_control=self.fun_control) |
| 1696 | + y0 = self._mo2so(y_mo) |
1648 | 1697 | y0 = apply_penalty_NA(y0, self.fun_control["penalty_NA"], verbosity=self.verbosity) |
1649 | 1698 | X0, y0 = remove_nan(X0, y0, stop_on_zero_return=False) |
1650 | 1699 | return X0, y0 |
@@ -1979,7 +2028,10 @@ def plot_model(self, y_min=None, y_max=None) -> None: |
1979 | 2028 | """ |
1980 | 2029 | if self.k == 1: |
1981 | 2030 | X_test = np.linspace(self.lower[0], self.upper[0], 100) |
1982 | | - y_test = self.fun(X=X_test.reshape(-1, 1), fun_control=self.fun_control) |
| 2031 | + y_mo = self.fun(X=X_test.reshape(-1, 1), fun_control=self.fun_control) |
| 2032 | + # convert multi-objective values to single-objective values |
| 2033 | + # TODO: Store y_mo in self.y_mo (append new values) |
| 2034 | + y_test = self._mo2so(y_mo) |
1983 | 2035 | y_test = apply_penalty_NA(y_test, self.fun_control["penalty_NA"], verbosity=self.verbosity) |
1984 | 2036 | if isinstance(self.surrogate, Kriging): |
1985 | 2037 | y_hat = self.surrogate.predict(X_test[:, np.newaxis], return_val="y") |
|
0 commit comments