Skip to content

Commit 923144f

Browse files
0.26.13
fun analytical
1 parent b2f024b commit 923144f

5 files changed

Lines changed: 296 additions & 242 deletions

File tree

notebooks/00_spotPython_tests.ipynb

Lines changed: 186 additions & 216 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
77

88
[project]
99
name = "spotpython"
10-
version = "0.26.12"
10+
version = "0.26.13"
1111
authors = [
1212
{ name="T. Bartz-Beielstein", email="tbb@bartzundbartz.de" }
1313
]

src/spotpython/fun/objectivefunctions.py

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class Analytical:
1212
Offset value. Defaults to 0.0.
1313
seed (int):
1414
Seed value for random number generation. Defaults to 126.
15+
fun_control (dict):
16+
Dictionary containing control parameters for the function. Defaults to None.
1517
1618
Notes:
1719
See [Numpy Random Sampling](https://numpy.org/doc/stable/reference/random/index.html#random-quick-start)
@@ -29,12 +31,22 @@ class Analytical:
2931
Dictionary containing control parameters for the function.
3032
"""
3133

32-
def __init__(self, offset: float = 0.0, sigma=0.0, seed: int = 126) -> None:
34+
def __init__(self, offset: float = 0.0, sigma=0.0, seed: int = 126, fun_control=None) -> None:
3335
self.offset = offset
3436
self.sigma = sigma
3537
self.seed = seed
3638
self.rng = default_rng(seed=self.seed)
37-
self.fun_control = {"sigma": sigma, "seed": None, "sel_var": None}
39+
self.fun_control = {"offset": offset, "sigma": self.sigma, "seed": self.seed}
40+
# overwrite fun_control with user input if provided
41+
if fun_control is not None:
42+
self.fun_control = fun_control
43+
# check if fun_control contains offset, sigma and seed, if not, add them
44+
if "offset" not in self.fun_control:
45+
self.fun_control["offset"] = self.offset
46+
if "sigma" not in self.fun_control:
47+
self.fun_control["sigma"] = self.sigma
48+
if "seed" not in self.fun_control:
49+
self.fun_control["seed"] = self.seed
3850

3951
def __repr__(self) -> str:
4052
return f"analytical(offset={self.offset}, sigma={self.sigma}, seed={self.seed})"
@@ -142,19 +154,67 @@ def fun_linear(self, X: np.ndarray, fun_control: Optional[Dict] = None) -> np.nd
142154
dict with entries `sigma` (noise level) and `seed` (random seed).
143155
144156
Returns:
145-
np.ndarray: A 1D numpy array with shape (n,) containing the calculated values.
157+
np.ndarray: A 1D numpy array with shape (n,) containing the calculated values, which were obtained by
158+
summing the weighted input values after subtracting the offset. Noise can be added to the output. An intercept
159+
can be provided by setting the `alpha` key in the `fun_control` dictionary. If the `beta` key is provided, the
160+
weighted sum is computed. If `beta` is not provided, the sum of the input values is computed.
146161
147162
Examples:
148-
>>> from spotpython.fun.objectivefunctions import analytical
163+
>>> from spotpython.fun.objectivefunctions import Analytical
149164
>>> import numpy as np
150-
>>> X = np.array([[1, 2, 3], [4, 5, 6]])
151-
>>> fun = analytical()
152-
>>> fun.fun_linear(X)
153-
array([ 6., 15.])
165+
>>> # Without offset and without noise
166+
>>> user_fun = UserAnalytical()
167+
>>> X = np.array([[0, 0, 0], [1, 1, 1]])
168+
>>> results = user_fun.fun_user_function(X)
169+
>>> print(results)
170+
>>>
171+
>>> # With offset and without noise
172+
>>> user_fun = UserAnalytical(offset=1.0)
173+
>>> X = np.array([[0, 0, 0], [1, 1, 1]])
174+
>>> results = user_fun.fun_user_function(X)
175+
>>> print(results)
176+
>>>
177+
>>> # With offset and noise
178+
>>> user_fun = UserAnalytical(offset=1.0, sigma=0.1, seed=1)
179+
>>> X = np.array([[0, 0, 0], [1, 1, 1]])
180+
>>> results = user_fun.fun_user_function(X)
181+
>>> print(results)
182+
>>>
183+
>>> # Provide alpha (intercept), no beta
184+
>>> fun_control = {"alpha": 10.0}
185+
>>> fun.fun_linear(X, fun_control=fun_control)
186+
>>> array([16., 25.])
187+
>>>
188+
>>> # Provide alpha and beta (weighted sum with intercept)
189+
>>> fun_control = {"alpha": 2.0, "beta": [1.0, 2.0, 3.0]}
190+
>>> fun.fun_linear(X, fun_control=fun_control)
191+
array([14., 32.])
192+
[0. 3.]
193+
[3. 0.]
194+
[3.03455842 0.08216181]
154195
155196
"""
156197
X = self._prepare_input_data(X, fun_control)
157-
y = np.sum(X, axis=1)
198+
offset = np.ones(X.shape[1]) * self.offset
199+
200+
alpha = self.fun_control.get("alpha", 0.0)
201+
beta = self.fun_control.get("beta", None)
202+
if beta is not None:
203+
# check if beta is a numpy array
204+
if not isinstance(beta, np.ndarray):
205+
# convert beta to numpy array of shape (n,), where n is the number of columns in X
206+
beta = np.array(beta)
207+
if beta.shape[0] != X.shape[1]:
208+
raise Exception("beta must have the same number of elements as the number of columns in X")
209+
210+
# Compute the linear response
211+
if beta is not None:
212+
# Weighted sum with intercept
213+
y = alpha + np.dot(X - offset, beta)
214+
else:
215+
# Original behavior: just sum the rows
216+
y = alpha + np.sum(X - offset, axis=1)
217+
158218
return self._add_noise(y)
159219

160220
def fun_sphere(self, X: np.ndarray, fun_control: Optional[Dict] = None) -> np.ndarray:

src/spotpython/light/trainmodel.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from lightning.pytorch.callbacks import ModelCheckpoint
77
from torch.utils.data import DataLoader
88
from captum.attr import IntegratedGradients, DeepLift, KernelShap
9-
import pandas as pd
109
import torch
1110
import os
1211

src/spotpython/utils/effects.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,47 @@ def screeningplan(k, p, xi, r):
4949
return X
5050

5151

52-
def screening(X, objhandle, range_, xi, p, labels, print=False) -> pd.DataFrame:
53-
"""
54-
Screening method for global sensitivity analysis.
52+
def screening(X, fun, xi, p, labels, range=None, print=False) -> pd.DataFrame:
53+
"""Generates a DataFrame with elementary effect screening metrics.
54+
55+
This function calculates the mean and standard deviation of the
56+
elementary effects for a given set of design variables and returns
57+
the results as a Pandas DataFrame.
5558
5659
Args:
57-
X (np.ndarray): Design matrix with shape (n, k), where n is the number of design points and k is the number of design variables.
58-
objhandle (function): Objective function to evaluate the design points.
59-
range_ (np.ndarray): Array with shape (2, k) with the lower and upper bounds for each design variable.
60-
xi (float): Step length.
61-
p (int): Number of levels.
62-
labels (list): List with the names of the design variables.
63-
print (bool): If True, print the results in a table. If False, plot the results.
60+
X (np.ndarray): The screening plan matrix, typically structured
61+
within a [0,1]^k box.
62+
fun: The objective function to evaluate at each
63+
design point in the screening plan.
64+
xi (float): The elementary effect step length factor.
65+
p (int): Number of discrete levels along each dimension.
66+
labels (list of str): A list of variable names corresponding to
67+
the design variables.
68+
range (np.ndarray): A 2xk matrix where the first row contains
69+
lower bounds and the second row contains upper bounds for
70+
each variable.
6471
6572
Returns:
66-
pd.DataFrame: Table with the mean and standard deviation of the elementary effects
67-
68-
73+
pd.DataFrame: A DataFrame containing three columns:
74+
- 'varname': The name of each variable.
75+
- 'mean': The mean of the elementary effects for each variable.
76+
- 'sd': The standard deviation of the elementary effects for
77+
each variable.
78+
79+
Examples:
80+
>>> import numpy as np
81+
>>> from spotpython.fun.objectivefunctions import Analytical
82+
>>> from spotpython.utils.effects import screening
83+
>>>
84+
>>> # Create a small test input with shape (n, 10)
85+
>>> X_test = np.array([
86+
... [0.0]*10,
87+
... [1.0]*10
88+
... ])
89+
>>> fun = Analytical()
90+
>>> labels = ["x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10"]
91+
>>> result = screening(X_test, fun.fun_wingwt, np.array([[0]*10, [1]*10]), 0.1, 3, labels)
92+
>>> print
6993
"""
7094
# Determine the number of design variables (k)
7195
k = X.shape[1]
@@ -75,8 +99,9 @@ def screening(X, objhandle, range_, xi, p, labels, print=False) -> pd.DataFrame:
7599
# Scale each design point to the given range and evaluate the objective function
76100
t = np.zeros(X.shape[0])
77101
for i in range(X.shape[0]):
78-
# X[i, :] = range_[0, :] + X[i, :] * (range_[1, :] - range_[0, :])
79-
t[i] = objhandle(X[i, :])
102+
if range is not None:
103+
X[i, :] = range[0, :] + X[i, :] * (range[1, :] - range[0, :])
104+
t[i] = fun(X[i, :])
80105

81106
# Calculate the elementary effects
82107
F = np.zeros((k, r))

0 commit comments

Comments
 (0)