SHGO
Overview
The SHGO function performs global optimization using the Simplicial Homology Global Optimization algorithm by wrapping scipy.optimize.shgo. It systematically samples the search space with low-discrepancy sequences, refines candidate simplices, and guarantees convergence to the global minimum under mild conditions. This example function is provided as-is without any representation of accuracy.
Usage
To evaluate the optimizer in Excel:
=SHGO(func_expr, bounds, [n], [iters], [sampling_method])func_expr(string, required): Objective expression referencing variables throughx[i].bounds(2D list, required): Each row is[min, max]defining the allowable range for a variable.n(int, optional, default=100): Number of initial sampling points.iters(int, optional, default=1): Number of refinement iterations.sampling_method(string (enum), optional, default="simplicial"): Sampling scheme. Valid options:simplicial,sobol,halton.
The function returns a single-row 2D list containing the optimal variables with the final objective value, or an error message string if validation 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) |
cos(x) | Cosine function (radians) | cos(x) |
tan(x) | Tangent function (radians) | tan(x) |
asin(x) or arcsin(x) | Arc sine function (radians) | asin(x) or arcsin(x) |
acos(x) or arccos(x) | Arc cosine function (radians) | acos(x) or arccos(x) |
atan(x) or arctan(x) | Arc tangent function (radians) | atan(x) or arctan(x) |
sinh(x) | Hyperbolic sine | sinh(x) |
cosh(x) | Hyperbolic cosine | cosh(x) |
tanh(x) | Hyperbolic tangent | tanh(x) |
exp(x) | Exponential function () | exp(x) |
log(x) or ln(x) | Natural logarithm (base ) | log(x) or ln(x) |
log10(x) | Base-10 logarithm | log10(x) |
sqrt(x) | Square root | sqrt(x) |
abs(x) | Absolute value | abs(x) |
pow(x, y) | Power function (x raised to power y) | pow(x, 2) |
pi | Mathematical constant π (≈3.14159) | x * pi |
e | Mathematical constant e (≈2.71828) | e**x |
inf | Positive infinity | x + inf (rarely used) |
Important Notes:
- All trigonometric functions use radians, not degrees
- Use
**(double asterisk) or^(caret) for exponentiation. Both are automatically converted to Python’s**operator. Examples:x[0]**2,x[0]^2,e**x,e^xall work equivalently - For multi-variable functions, use indexed variable notation:
x[0],x[1], etc. - Examples:
"(x[0]-1)**2 + (x[1]+2)^2","sin(x[0]) + cos(x[1])","exp(-x[0]**2 - x[1]^2)"
Expression Examples
Common mathematical expressions and their func_expr notation for multi-variable functions:
- →
"(x[0]-1)**2 + (x[1]+2)**2" - →
"x[0]**2 + x[1]**2"or"x[0]^2 + x[1]^2" - →
"sin(x[0]) + cos(x[1])" - →
"exp(-x[0]**2 - x[1]**2)"or"e^(-x[0]^2 - x[1]^2)" - →
"(x[0]-1)**2 + (x[1]-0.5)**2 + (x[2]+1)**2"
Examples
Example 1: Basic Quadratic with All Parameters
Inputs:
| func_expr | bounds | n | iters | sampling_method | |
|---|---|---|---|---|---|
| (x[0]-1)**2 + (x[1]+2)**2 | -5 | 5 | 120 | 3 | sobol |
| -5 | 5 |
Excel formula:
=SHGO("(x[0]-1)**2 + (x[1]+2)**2", {-5,5;-5,5}, 120, 3, "sobol")Expected output:
| x₁ | x₂ | Objective |
|---|---|---|
| 1.000 | -2.000 | 0.000 |
This example demonstrates using non-default values for all optional parameters.
Example 2: Sobol Sampling with Refinement
Inputs:
| func_expr | bounds | sampling_method | n | iters | |
|---|---|---|---|---|---|
| (x[0]-2)**2 + (x[1]-1)**2 | -6 | 6 | sobol | 50 | 2 |
| -6 | 6 |
Excel formula:
=SHGO("(x[0]-2)**2 + (x[1]-1)**2", {-6,6;-6,6}, 50, 2, "sobol")Expected output:
| x₁ | x₂ | Objective |
|---|---|---|
| 2.000 | 1.000 | 0.000 |
Example 3: Three-Variable Optimization
Inputs:
| func_expr | bounds | n | ||
|---|---|---|---|---|
| (x[0]-1)**2 + (x[1]-0.5)**2 + (x[2]+1)**2 | -4 | 4 | 80 | |
| -4 | 4 | |||
| -4 | 4 |
Excel formula:
=SHGO("(x[0]-1)**2 + (x[1]-0.5)**2 + (x[2]+1)**2", {-4,4;-4,4;-4,4}, 80)Expected output:
| x₁ | x₂ | x₃ | Objective |
|---|---|---|---|
| 1.000 | 0.500 | -1.000 | 0.000 |
Example 4: Halton Sampling Strategy
Inputs:
| func_expr | bounds | sampling_method | n | iters | |
|---|---|---|---|---|---|
| x[0]**2 + 2*x[1]**2 | -3 | 3 | halton | 60 | 3 |
| -3 | 3 |
Excel formula:
=SHGO("x[0]**2 + 2*x[1]**2", {-3,3;-3,3}, 60, 3, "halton")Expected output:
| x₁ | x₂ | Objective |
|---|---|---|
| 0.000 | 0.000 | 0.000 |
Python Code
import math
import re
from typing import List
import numpy as np
from scipy.optimize import shgo as scipy_shgo
def shgo(
func_expr: str,
bounds: List[List[float]],
n: int = 100,
iters: int = 1,
sampling_method: str = 'simplicial',
):
"""Find global minimum using Simplicial Homology Global Optimization.
This function wraps scipy.optimize.shgo. For more information, see:
https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.shgo.html
Args:
func_expr: Objective expression using x[i] for variable access.
bounds: 2D list of [min, max] pairs specifying variable limits.
n: Number of initial sampling points. Defaults to 100.
iters: Number of refinement iterations. Defaults to 1.
sampling_method: Sampling strategy ('simplicial', 'sobol', 'halton'). Defaults to 'simplicial'.
Returns:
[[x1, x2, ..., objective]] on success, or an error message string.
This example function is provided as-is without any representation of accuracy.
"""
if not isinstance(func_expr, str):
return "Invalid input: func_expr must be a string."
if func_expr.strip() == "":
return "Invalid input: func_expr must be a non-empty string."
func_expr = re.sub(r'\^', '**', func_expr)
if "x" not in func_expr:
return "Invalid input: func_expr must reference variable x (e.g., x[0])."
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)
try:
n = int(n)
iters = int(iters)
except (TypeError, ValueError):
return "Invalid input: n and iters must be integers."
if n <= 0:
return "Invalid input: n must be positive."
if iters <= 0:
return "Invalid input: iters must be positive."
valid_methods = {'simplicial', 'sobol', 'halton'}
if sampling_method not in valid_methods:
return f"Invalid input: sampling_method must be one of: {', '.join(sorted(valid_methods))}"
safe_globals = {
"math": math,
"np": np,
"numpy": np,
"__builtins__": {},
}
safe_globals.update({
name: getattr(math, name)
for name in dir(math)
if not name.startswith("_")
})
safe_globals.update({
"sin": np.sin,
"cos": np.cos,
"tan": np.tan,
"asin": np.arcsin,
"arcsin": np.arcsin,
"acos": np.arccos,
"arccos": np.arccos,
"atan": np.arctan,
"arctan": np.arctan,
"sinh": np.sinh,
"cosh": np.cosh,
"tanh": np.tanh,
"exp": np.exp,
"log": np.log,
"ln": np.log,
"log10": np.log10,
"sqrt": np.sqrt,
"abs": np.abs,
"pow": np.power,
"pi": math.pi,
"e": math.e,
})
x0_vector = [np.mean([b[0], b[1]]) for b in processed_bounds]
try:
initial_eval = eval(func_expr, safe_globals, {"x": x0_vector})
float(initial_eval)
except Exception as exc:
return f"Error: Invalid expression at initial guess: {exc}"
def objective(x_vector):
try:
local_x = [float(val) for val in np.atleast_1d(x_vector)]
result = eval(func_expr, safe_globals, {"x": local_x})
numeric_value = float(result)
except Exception as exc:
raise ValueError(f"Error evaluating func_expr: {exc}")
if not math.isfinite(numeric_value):
raise ValueError("Objective evaluation produced NaN or infinity.")
return numeric_value
try:
result = scipy_shgo(
objective,
bounds=processed_bounds,
n=n,
iters=iters,
sampling_method=sampling_method,
)
except ValueError as exc:
return f"Error during SHGO: {exc}"
except Exception as exc:
return f"Error during SHGO: {exc}"
if not result.success:
return f"Error during SHGO: {result.message}"
if result.x is None or result.fun is None:
return "Error during SHGO: missing solution data."
try:
solution_vector = [float(val) for val in np.atleast_1d(result.x)]
except (TypeError, ValueError):
return "Error during SHGO: could not convert solution to floats."
objective_value = float(result.fun)
return [solution_vector + [objective_value]]