Efficient Objective Function
Summary
The simulation of the objective function is often the most demanding part of an experiment, especially when simulating with an efficient optimizer. This guide demonstrates how to accelerate simulation with EvoBandits using Numba
, an open source JIT compiler, using a simple example.
Efficient Objective Function
The example optimizes the configuration of a simple test function, which is referred to as Test Problem 4 (TP4) and explained in detail in the GMAB Paper by Preil & Krapp (2025). With regard to the numba acceleration, it can be seen as a lightweight example representing more complex objectives that might leverage full Numba capabilities such as parallelization or GPU acceleration.
from numba import njit
import numpy as np
@njit
def tp4_func(action_vector: np.ndarray, seed: int = -1) -> float:
... # simulation logic
return res
Reproducibility with numba
Since numba-compiled functions cannot use a global RNG seed at this time, the optimizer must generate and pass a new seed for each evaluation of the objective if results shall be reproduced:
- As shown above,
tp4_func
must accept a parameterseed
. In this instance, a sentinel value of-1
is used instead ofNone
to denote an unseeded run, as this avoids a more complex and explicit numba setup that handles an optional function argument. - A seed must be passed to the
Study
on initialization to set up a seeded experiment. The Study will then automatically generate an independent seed from the range of non-negative integers for each evaluation and pass it totp4_func
.
Simulation
EvoBandits treats Numba-compiled and pure Python functions equivalently. For details on the configuration and execution of the optimization, please refer to the Reference and other examples on this page.
if __name__ == "__main__":
# Model a five-dimensional instance of TP4.
params = {"action_vector": IntParam(-100, 100, 5)}
n_trials = 20_000
n_runs = 10
# Execute optimization
study = Study(seed=42)
study.optimize(tp4_func, params, n_trials, n_runs=n_runs)
print("Best solution found during optimization: ", study.best_value)
print("Mean result:", study.mean_value)
print("Best configuration: ", study.best_params)
References
Preil, D., & Krapp, M. (2024). Genetic Multi-Armed Bandits: A Reinforcement Learning Inspired Approach for Simulation Optimization. IEEE Transactions on Evolutionary Computation.
Try it yourself!
You can find the full example in the evobandits.examples
module, available on GitHub.
Note
Running the examples requires additional dependencies, such as Numba and NumPy, which can be installed using:
Expand to copy examples/demo\Study.py
# Copyright 2025 EvoBandits
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import numpy as np
from evobandits import IntParam, Study
from numba import njit
# Constants
BETA_1 = 300
BETA_2 = 500
GAMMA_1 = 0.001
GAMMA_2 = 0.005
EPS_1 = -38
EPS_2 = 56
@njit
def tp4_func(action_vector: np.ndarray, seed: int = -1) -> float:
"""
Compute the function value for Test Problem 4 (TP4) from Preil & Krapp, 2025.
Args:
action_vector: A list of integers to calculate the TP4 function value with.
seed: Optional random seed for reproducibility. Must be a positive integer.
Defaults to -1, which acts as a sentinel value indicating no seeding.
Note: -1 is used because Numba does not support None as a default argument.
Source:
D. Preil and M. Krapp, "Genetic Multi-Armed Bandits: A Reinforcement Learning Inspired
Approach for Simulation Optimization," in IEEE Transactions on Evolutionary Computation,
vol. 29, no. 2, pp. 360-374, April 2025, doi: 10.1109/TEVC.2024.3524505.
"""
res = 0.0
n = len(action_vector)
for i in range(n):
val = action_vector[i]
res += BETA_1 * np.exp(-GAMMA_1 * (val - EPS_1) ** 2) + BETA_2 * np.exp(
-GAMMA_2 * (val - EPS_2) ** 2
)
# Simulate Gaussian noise with std = 100 * len(action_vector)
if seed != -1:
np.random.seed(seed)
res += np.random.normal(0.0, 100.0 * n)
# Negate result to model a minimization problem
res = -res
return res
if __name__ == "__main__":
# Model a five-dimensional instance of TP4.
params = {"action_vector": IntParam(-100, 100, 5)}
n_trials = 20_000
n_runs = 10
# Execute optimization
study = Study(seed=42)
study.optimize(tp4_func, params, n_trials, n_runs=n_runs)
print("Best solution found during optimization: ", study.best_value)
print("Mean result:", study.mean_value)
print("Best configuration: ", study.best_params)