DUAL_ANNEALING
Overview
The DUAL_ANNEALING function performs global optimization by combining generalized simulated annealing with local search, wrapping scipy.optimize.dual_annealing. It explores the search space using temperature-driven jumps and periodically refines the incumbent solution with deterministic polishing, providing robust performance on multimodal landscapes. This example function is provided as-is without any representation of accuracy.
Usage
To apply the solver in Excel:
=DUAL_ANNEALING(func_expr, bounds, [maxiter], [initial_temp], [restart_temp_ratio], [visit], [accept], [seed], [no_local_search], [x_zero])func_expr(string, required): Objective expression usingx[i]to address variables.bounds(2D list, required): Each row is[min, max]specifying bounds for a variable.maxiter(int, optional, default=1000): Maximum number of global search iterations.initial_temp(float, optional, default=5230.0): Starting temperature controlling exploration scale.restart_temp_ratio(float, optional, default=2e-5): Restart threshold ratio in(0, 1].visit(float, optional, default=2.62): Visiting distribution parameter (must be > 0).accept(float, optional, default=-5.0): Acceptance bias parameter (negative values favor downhill moves).seed(int, optional): Random seed for reproducible results.no_local_search(bool, optional, default=FALSE): Set toTRUEto skip the polishing local optimizer.x_zero(2D list, optional): Initial guess for the search; provide a single row with one value per variable.
The function returns a single-row 2D list with the optimal variables followed by the objective value, or an error message string if inputs are invalid or the solver fails.
Function Expressions
The func_expr parameter accepts mathematical expressions using any of the following functions and constants. All functions are case-sensitive.
| Function/Constant | Description | Example |
|---|---|---|
sin(x) | Sine function (radians) | sin(x[0]) |
cos(x) | Cosine function (radians) | cos(x[1]) |
tan(x) | Tangent function (radians) | tan(x[0]) |
asin(x) or arcsin(x) | Arc sine function (radians) | asin(x[0]) |
acos(x) or arccos(x) | Arc cosine function (radians) | acos(x[1]) |
atan(x) or arctan(x) | Arc tangent function (radians) | atan(x[0]) |
sinh(x) | Hyperbolic sine | sinh(x[0]) |
cosh(x) | Hyperbolic cosine | cosh(x[1]) |
tanh(x) | Hyperbolic tangent | tanh(x[0]) |
exp(x) | Exponential function () | exp(x[0]) |
log(x) or ln(x) | Natural logarithm (base ) | log(x[0]) |
log10(x) | Base-10 logarithm | log10(x[0]) |
sqrt(x) | Square root | sqrt(x[0]) |
abs(x) | Absolute value | abs(x[0] - x[1]) |
pow(x, y) | Power function (x raised to power y) | pow(x[0], 2) |
pi | Mathematical constant π (≈3.14159) | x[0] * pi |
e | Mathematical constant e (≈2.71828) | e**x[0] |
Important Notes:
- All trigonometric functions use radians, not degrees
- Use
**(double asterisk) or^(caret) for exponentiation. Both work equivalently. - For multi-variable functions, use indexed variable notation:
x[0],x[1], etc.
Expression Examples
Common mathematical expressions and their func_expr notation:
- →
"x[0]**2 + x[1]**2"or"x[0]^2 + x[1]^2" - →
"(x[0]-1)**2 + (x[1]+2)**2" - →
"sin(x[0]) + cos(x[1])" - →
"exp(-x[0]**2 - x[1]**2)"or"e^(-x[0]^2 - x[1]^2)"
Examples
Example 1: Basic Quadratic with All Parameters
Inputs:
| func_expr | bounds | maxiter | initial_temp | restart_temp_ratio | visit | accept | seed | no_local_search | x_zero | |
|---|---|---|---|---|---|---|---|---|---|---|
| (x[0]-1)**2 + (x[1]+2)**2 | -5 | 5 | 1500 | 6000 | 1.5E-05 | 2.5 | -4.5 | 101 | FALSE | 0, 0 |
| -5 | 5 |
Excel formula:
=DUAL_ANNEALING("(x[0]-1)**2 + (x[1]+2)**2", {-5,5;-5,5}, 1500, 6000, 0.000015, 2.5, -4.5, 101, FALSE, {0;0})Expected output:
| x₁ | x₂ | Objective |
|---|---|---|
| 1.000 | -2.000 | 0.000 |
This example demonstrates using non-default values for all optional parameters.
Example 2: No Local Search
Inputs:
| func_expr | bounds | seed | no_local_search | maxiter | |
|---|---|---|---|---|---|
| (x[0])**2 + (x[1]-3)**2 | -4 | 4 | 202 | TRUE | 200 |
| 0 | 6 |
Excel formula:
=DUAL_ANNEALING("(x[0])**2 + (x[1]-3)**2", {-4,4;0,6}, , , , , , 202, TRUE, , 200)Expected output:
| x₁ | x₂ | Objective |
|---|---|---|
| 0.037 | 3.024 | 0.002 |
Example 3: Custom Temperature Parameters
Inputs:
| func_expr | bounds | initial_temp | restart_temp_ratio | visit | accept | seed | |
|---|---|---|---|---|---|---|---|
| (x[0]-2)**2 + (x[1]-1)**2 | -6 | 6 | 7000 | 1E-05 | 2.7 | -3.0 | 404 |
| -6 | 6 |
Excel formula:
=DUAL_ANNEALING("(x[0]-2)**2 + (x[1]-1)**2", {-6,6;-6,6}, , 7000, 0.00001, 2.7, -3, 404)Expected output:
| x₁ | x₂ | Objective |
|---|---|---|
| 2.000 | 1.000 | 0.000 |
Example 4: Using an Initial Guess
Inputs:
| func_expr | bounds | x_zero | seed | maxiter | |||
|---|---|---|---|---|---|---|---|
| (x[0]+1)**2 + (x[1]-2)**2 + (x[2]-0.5)**2 | -5 | 5 | -0.5 | 1.5 | 909 | 300 | |
| -5 | 5 | 0.0 | |||||
| -5 | 5 |
Excel formula:
=DUAL_ANNEALING("(x[0]+1)**2 + (x[1]-2)**2 + (x[2]-0.5)**2", {-5,5;-5,5;-5,5}, , , , , , 909, FALSE, {-0.5,1.5,0}, 300)Expected output:
| x₁ | x₂ | x₃ | Objective |
|---|---|---|---|
| -1.000 | 2.000 | 0.500 | 0.000 |
Python Code
import math
from typing import List, Optional
import numpy as np
from scipy.optimize import dual_annealing as scipy_dual_annealing
def dual_annealing(
func_expr: str,
bounds: List[List[float]],
maxiter: int = 1000,
initial_temp: float = 5230.0,
restart_temp_ratio: float = 2e-5,
visit: float = 2.62,
accept: float = -5.0,
seed: Optional[int] = None,
no_local_search: bool = False,
x_zero: Optional[List[List[float]]] = None,
):
"""
Minimize a multivariate function using dual annealing.
Args:
func_expr: Objective expression in terms of `x` (use `x[i]` for components).
bounds: 2D list of [min, max] pairs defining the search domain.
maxiter: Maximum number of global search iterations.
initial_temp: Initial temperature controlling the search scope.
restart_temp_ratio: Temperature ratio triggering restarts.
visit: Parameter governing visiting distribution (must be > 0).
accept: Parameter controlling acceptance probability of new states.
seed: Optional random seed for reproducibility.
no_local_search: If True, skip the local minimization polish stage.
x_zero: Optional initial point as 2D list or list matching the number of variables.
Returns:
list[list[float]] or str: [[x1, x2, ..., objective]] on success, otherwise an informative error message.
This example function is provided as-is without any representation of accuracy.
"""
# Validate func_expr
if not isinstance(func_expr, str):
return "Invalid input: func_expr must be a string."
if 'x' not in func_expr:
return "Invalid input: func_expr must reference variable x (e.g., x[0])."
# Validate bounds
if not isinstance(bounds, list) or len(bounds) == 0:
return "Invalid input: bounds must be a 2D list of [min, max] pairs."
processed_bounds = []
for idx, bound in enumerate(bounds):
if not isinstance(bound, list) or len(bound) != 2:
return "Invalid input: each bound must be a [min, max] pair."
try:
lower = float(bound[0])
upper = float(bound[1])
except (TypeError, ValueError):
return "Invalid input: bounds must contain numeric values."
if lower > upper:
return f"Invalid input: lower bound must not exceed upper bound for variable index {idx}."
processed_bounds.append((lower, upper))
dimension = len(processed_bounds)
# Validate numeric parameters
try:
maxiter = int(maxiter)
except (TypeError, ValueError):
return "Invalid input: maxiter must be an integer."
if maxiter <= 0:
return "Invalid input: maxiter must be positive."
try:
initial_temp = float(initial_temp)
restart_temp_ratio = float(restart_temp_ratio)
visit = float(visit)
accept = float(accept)
except (TypeError, ValueError):
return "Invalid input: initial_temp, restart_temp_ratio, visit, and accept must be numeric."
if initial_temp <= 0:
return "Invalid input: initial_temp must be positive."
if not (0 < restart_temp_ratio <= 1):
return "Invalid input: restart_temp_ratio must be in the interval (0, 1]."
if visit <= 0:
return "Invalid input: visit must be positive."
rng_seed = None
if seed is not None:
try:
rng_seed = int(seed)
except (TypeError, ValueError):
return "Invalid input: seed must be an integer."
if not isinstance(no_local_search, bool):
return "Invalid input: no_local_search must be a boolean."
x0_vector = None
if x_zero is not None:
try:
arr = np.array(x_zero, dtype=float).flatten()
except Exception:
return "Invalid input: x_zero must contain numeric values."
if arr.size != dimension:
return "Invalid input: x_zero length must match number of variables."
x0_vector = arr
def objective(x_vector):
try:
local_x = [float(val) for val in np.atleast_1d(x_vector)]
value = eval(func_expr, {"x": local_x, "math": math, "np": np})
except Exception as exc:
raise ValueError(f"Error evaluating func_expr: {exc}")
try:
result_value = float(value)
except (TypeError, ValueError):
raise ValueError("Objective expression must return a scalar numeric value.")
if math.isnan(result_value) or math.isinf(result_value):
raise ValueError("Objective evaluation produced NaN or infinity.")
return result_value
try:
result = scipy_dual_annealing(
objective,
bounds=processed_bounds,
maxiter=maxiter,
initial_temp=initial_temp,
restart_temp_ratio=restart_temp_ratio,
visit=visit,
accept=accept,
seed=rng_seed,
no_local_search=no_local_search,
x0=x0_vector,
)
except ValueError as exc:
return f"Error during dual annealing: {exc}"
except Exception as exc:
return f"Error during dual annealing: {exc}"
if not result.success:
return f"Dual annealing failed: {result.message}"
if result.x is None or result.fun is None:
return "Dual annealing failed: missing solution data."
try:
solution_vector = [float(val) for val in result.x]
except (TypeError, ValueError):
return "Error converting solution vector to floats."
objective_value = float(result.fun)
return [solution_vector + [objective_value]]