# SPDX-License-Identifier: MPL-2.0
#
# ODAT-SE -- an open framework for data analysis
# Copyright (C) 2020- The University of Tokyo
#
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
import numpy as np
import odatse
import odatse.solver.function
[docs]
def quadratics(xs: np.ndarray) -> float:
"""
Quadratic (sphere) function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the quadratic function.
Notes
-----
It has one global minimum f(xs)=0 at xs = [0,0,...,0].
"""
return np.sum(xs * xs)
[docs]
def quartics(xs: np.ndarray) -> float:
"""
Quartic function with two global minima.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the quartic function.
Notes
-----
It has two global minima f(xs)=0 at xs = [1,1,...,1] and [-1,-1,...,-1].
It has one saddle point f(0,0,...,0) = 1.0.
"""
return np.mean((xs - 1.0) ** 2) * np.mean((xs + 1.0) ** 2)
[docs]
def ackley(xs: np.ndarray) -> float:
"""
Ackley's function in arbitrary dimension
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of Ackley's function.
Notes
-----
It has one global minimum f(xs)=0 at xs=[0,0,...,0].
It has many local minima.
"""
a = np.mean(xs ** 2, axis = 0)
a = 20 * np.exp(-0.2 * np.sqrt(a))
b = np.cos(2.0 * np.pi * xs)
b = np.exp(np.mean(b, axis = 0))
return 20.0 + np.exp(1.0) - a - b
[docs]
def alpine(xs: np.ndarray) -> float:
"""
Alpine function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Alpine function.
Notes
-----
It has a global minimum f(x)=0 at x=[0,0,...,0].
"""
return np.sum(np.abs((xs*np.sin(xs))+(0.1*xs)),axis=0)
[docs]
def exponential(xs: np.ndarray) -> float:
"""
Exponential function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Exponential function.
Notes
-----
It has a global minimum f(x)=-1 at x=[0,0,...,0].
"""
return -np.exp(-0.5*np.sum(xs**2,axis=0))
[docs]
def griewank(xs: np.ndarray) -> float:
"""
Griewank function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Griewank function.
Notes
-----
It has a global minimum f(x)=0 at x=[0,0,...,0].
"""
return 1+(np.sum(xs**2,axis=0)/4000)+np.prod(np.cos(xs/np.sqrt((np.arange(xs.shape[0])+1))),axis=0)
[docs]
def himmelblau(xs: np.ndarray) -> float:
"""
Himmelblau's function.
Parameters
----------
xs : np.ndarray
Input array of shape (2,).
Returns
-------
float
The calculated value of Himmelblau's function.
Notes
-----
It has four global minima f(xs) = 0 at
xs=[3,2], [-2.805118..., 3.131312...], [-3.779310..., -3.2831860], and [3.584428..., -1.848126...].
"""
if xs.shape[0] != 2:
raise RuntimeError(
f"ERROR: himmelblau expects d=2 input, but receives d={xs.shape[0]} one"
)
return (xs[0] ** 2 + xs[1] - 11.0) ** 2 + (xs[0] + xs[1] ** 2 - 7.0) ** 2
[docs]
def michalewicz(xs: np.ndarray) -> float:
"""
Michalewicz function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Michalewicz function.
Notes
-----
The global minimum value and location depend on the dimension. There are d! local minima.
For d=2, it has a global minimum f(x)=-1.8013 at x=[2.2051, 1.5698].
For d=5, it has a global minimum f(x)=-4.6876.
For d=10, it has a global minimum f(x)=-9.6602.
"""
return -np.sum(np.sin(xs)*(np.sin(((np.arange(xs.shape[0])+1))*(xs**2)/np.pi)**(2*10)),axis=0)
[docs]
def qing(xs: np.ndarray) -> float:
"""
Qing function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Qing function.
Notes
-----
It has a global minimum f(x)=0 at x=[+/-sqrt(n), ...], where n runs from 1 to d.
"""
return np.sum(((xs**2)-((np.arange(xs.shape[0])+1)))**2,axis=0)
[docs]
def rastrigin(xs: np.ndarray) -> float:
"""
Rastrigin function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Rastrigin function.
Notes
-----
It has a global minimum f(x)=0 at x=[0,0,...,0].
"""
return (xs.shape[0]*10)+np.sum(xs**2-(10*np.cos(2*np.pi*xs)),axis=0)
[docs]
def rosenbrock(xs: np.ndarray) -> float:
"""
Rosenbrock's function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of Rosenbrock's function.
Notes
-----
It has one global minimum f(xs) = 0 at xs=[1,1,...,1].
"""
return np.sum(100.0 * (xs[1:] - xs[:-1] ** 2) ** 2 + (1.0 - xs[:-1]) ** 2, axis = 0)
[docs]
def schaffer(xs: np.ndarray) -> float:
"""
Schaffer function (generalized).
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Schaffer function.
"""
a=(xs[:-1]**2)+(xs[1:]**2)
return np.sum(0.5+(((np.sin(a)**2)-0.5)/((1+(0.001*a))**2)),axis=0)
[docs]
def schwefel(xs: np.ndarray) -> float:
"""
Schwefel function.
Parameters
----------
xs : np.ndarray
Input array.
Returns
-------
float
The calculated value of the Schwefel function.
Notes
-----
It has a global minimum f(x)=0 at x=[420.9687..., ...].
"""
return (xs.shape[0]*418.9829)-np.sum(xs*np.sin(np.sqrt(np.abs(xs))),axis=0)
[docs]
def linear_regression_test(xs: np.ndarray) -> float:
"""
Negative log likelihood of linear regression with Gaussian noise N(0,sigma)
y = ax + b
trained by xdata = [1, 2, 3, 4, 5, 6] and ydata = [1, 3, 2, 4, 3, 5].
Model parameters (a, b, sigma) are corresponding to xs as the following,
a = xs[0], b = xs[1], log(sigma**2) = xs[2]
It has a global minimum f(xs) = 1.005071.. at
xs = [0.628571..., 0.8, -0.664976...].
Parameters
----------
xs : np.ndarray
Input array of model parameters.
Returns
-------
float
The negative log likelihood of the linear regression model.
"""
if xs.shape[0] != 3:
raise RuntimeError(
f"ERROR: regression expects d=3 input, but receives d={xs.shape[0]} one"
)
xdata = np.array([1, 2, 3, 4, 5, 6])
ydata = np.array([1, 3, 2, 4, 3, 5])
n = len(ydata)
return 0.5 * (
n * xs[2] + np.sum((xs[0] * xdata + xs[1] - ydata) ** 2) / np.exp(xs[2])
)
[docs]
class Solver(odatse.solver.function.Solver):
"""Function Solver with pre-defined benchmark functions"""
x: np.ndarray
fx: float
[docs]
def __init__(self, info: odatse.Info) -> None:
"""
Initialize the solver.
Parameters
----------
info: Info
Information object containing solver configuration.
"""
super().__init__(info)
self._name = "analytical"
function_name = info.solver.get("function_name", "quadratics")
if function_name == "quadratics":
self.set_function(quadratics)
elif function_name == "quartics":
self.set_function(quartics)
elif function_name == "ackley":
self.set_function(ackley)
elif function_name == "alpine":
self.set_function(alpine)
elif function_name == "exponential":
self.set_function(exponential)
elif function_name == "griewank":
self.set_function(griewank)
elif function_name == "himmelblau":
dimension = self.dimension
if int(dimension) != 2:
raise RuntimeError(
f"ERROR: himmelblau works only with dimension=2 but input is dimension={dimension}"
)
self.set_function(himmelblau)
elif function_name == "michalewicz":
self.set_function(michalewicz)
elif function_name == "qing":
self.set_function(qing)
elif function_name == "rastrigin":
self.set_function(rastrigin)
elif function_name == "rosenbrock":
self.set_function(rosenbrock)
elif function_name == "schaffer":
self.set_function(schaffer)
elif function_name == "schwefel":
self.set_function(schwefel)
elif function_name == "linear_regression_test":
dimension = self.dimension
if int(dimension) != 3:
raise RuntimeError(
f"ERROR: regression works only with dimension=3 but input is dimension={dimension}"
)
self.set_function(linear_regression_test)
else:
raise RuntimeError(f"ERROR: Unknown function, {function_name}")