17  User-Specified Functions: Extending the Analytical Class

This chapter illustrates how user-specified functions can be optimized and analyzed with the spotpython package by extending the Analytical class.

import numpy as np
from spotpython.fun.objectivefunctions import Analytical
from spotpython.utils.init import fun_control_init, surrogate_control_init
from spotpython.spot import spot

17.0.1 The Objective Function: User Specified

We will use an analytical objective function, i.e., a function that can be described by a (closed) formula: \[ f(x) = \sum_i^k x_i^4. \]

This function is continuous, convex and unimodal. The global minimum is \[ f(x) = 0, \text{at } x = (0,0, \ldots, 0). \]

  • The Analytical class can be extended as follows:
from typing import Optional, Dict

class UserAnalytical(Analytical):
    def fun_user_function(self, X: np.ndarray, fun_control: Optional[Dict] = None) -> np.ndarray:
        """
        Custom new function: f(x) = x^4
        
        Args:
            X (np.ndarray): Input data as a 2D array.
            fun_control (Optional[Dict]): Control parameters for the function.
        
        Returns:
            np.ndarray: Computed values with optional noise.
        
        Examples:
            >>> import numpy as np
            >>> X = np.array([[1, 2, 3], [4, 5, 6]])
            >>> fun = UserAnalytical()
            >>> fun.fun_user_function(X)
        """
        X = self._prepare_input_data(X, fun_control)
     
        offset = np.ones(X.shape[1]) * self.offset
        y = np.sum((X - offset) **4, axis=1) 

        # Add noise if specified in fun_control
        return self._add_noise(y)
user_fun = UserAnalytical()
X = np.array([[0, 0, 0], [1, 1, 1]])
results = user_fun.fun_user_function(X)
print(results)
[0. 3.]
user_fun = UserAnalytical(offset=1.0)
X = np.array([[0, 0, 0], [1, 1, 1]])
results = user_fun.fun_user_function(X)
print(results)
[3. 0.]
user_fun = UserAnalytical(sigma=1.0)
X = np.array([[0, 0, 0], [1, 1, 1]])
results = user_fun.fun_user_function(X)
print(results)
[0.06691138 3.11495313]
user_fun = UserAnalytical().fun_user_function
fun_control = fun_control_init(
              PREFIX="USER",              
              lower = -1.0*np.ones(2),
              upper = np.ones(2),
              var_name=["User Pressure", "User Temp"],
              TENSORBOARD_CLEAN=True,
              tensorboard_log=True)
spot_user = spot.Spot(fun=user_fun,
                  fun_control=fun_control)
spot_user.run()
Moving TENSORBOARD_PATH: runs/ to TENSORBOARD_PATH_OLD: runs_OLD/runs_2024_12_14_20_34_18
Created spot_tensorboard_path: runs/spot_logs/USER_maans08_2024-12-14_20-34-18 for SummaryWriter()
spotpython tuning: 3.715394917589437e-05 [#######---] 73.33% 
spotpython tuning: 3.715394917589437e-05 [########--] 80.00% 
spotpython tuning: 3.715394917589437e-05 [#########-] 86.67% 
spotpython tuning: 3.715394917589437e-05 [#########-] 93.33% 
spotpython tuning: 1.3840535984457729e-05 [##########] 100.00% Done...
<spotpython.spot.spot.Spot at 0x12ca69e80>

17.0.2 Results

_ = spot_user.print_results()
min y: 1.3840535984457729e-05
User Pressure: 0.060781734267030006
User Temp: 0.020927320560756916
spot_user.plot_progress()

17.0.3 A Contour Plot

We can select two dimensions, say \(i=0\) and \(j=1\), and generate a contour plot as follows.

Note:

We have specified identical min_z and max_z values to generate comparable plots.

spot_user.plot_contour(i=0, j=1, min_z=0, max_z=2.25)

  • The variable importance:
_ = spot_user.print_importance()
User Pressure:  77.95830652608618
User Temp:  100.0
spot_user.plot_importance()

17.1 Jupyter Notebook

Note