0

I have been trying to perform some global optimization with SciPy optimizer SHGO and I've had issues with the sampling metod 'sobol'. Specifically, I get an error of the type: TypeError: <lambda>() takes 1 positional argument but 3 were given.

The thing that makes me suspect it may be related with something wrong with the sampling method per se is that the error doesn't happen when I use simplicial as sampling method or a different (still constrained) optimization algorithm such as SLSQP.

Here's the code, I'm interested in understanding if someone else can reproduce this error.

import numpy as np
from numpy import linalg as la
import scipy as sc
import scipy.optimize as opt

print("numpy version: ", np.__version__)
print("scipy version: ", sc.__version__)

# d-dim Shannon entropy
def ShEntr(x):
    s=0
    for p_i in x:
        if p_i > 1e-16 and p_i < 1-1e-16:
            s = s - p_i*np.log2(p_i)
    return s

# Function to be optimized
def fun(y, k, g):
    p,c = y
    mat = [[p,c],[c,1-p]]
    return -ShEntr(la.eigvals(mat))   #- sign in front of it due to the fact that scipy.otimize is a minimization

# Optimization parameters
x0 = [0.5,0.2] # Starting point for SLSQP
boun = [(0,1),(0,1)] # Optimization parameters bounds
cons = ({'type':'ineq', 'fun': lambda y: y[0]-y[0]**2-y[1]**2}) # Optimization parameters constraints

## Optimization
# SLSQP
res = opt.minimize(fun, x0, (0.3,0.3), method='SLSQP', constraints=cons, bounds=boun, options={'maxiter':10000})
print("SLSQP: ", -res.fun)
# SHGO: simplicial
res = opt.shgo(fun, bounds=boun, args=(0.3,0.3), n = 30, iters = 5, sampling_method = 'simplicial', constraints=cons)
print("SHGO simplicial: ", -res.fun)
# SHGO: sobol
res = opt.shgo(fun, bounds=boun, args=(0.3,0.3), n = 30, iters = 5, sampling_method = 'sobol', constraints=cons)
print("SHGO sobol: ", -res.fun)

And here's what I get

numpy version:  1.20.3
scipy version:  1.7.3
SLSQP:  0.9999999999999942
SHGO simplicial:  1.0
Traceback (most recent call last):
  File "... path /test.py", line 36, in <module>
    res = opt.shgo(fun, bounds=boun, args=(0.3,0.3), n = 30, iters = 5, sampling_method = 'sobol', constraints=cons)
  File "... path \Anaconda3\lib\site-packages\scipy\optimize\_shgo.py", line 419, in shgo
    shc.construct_complex()
  File "... path \Anaconda3\lib\site-packages\scipy\optimize\_shgo.py", line 733, in construct_complex
    self.iterate()
  File "... path \Anaconda3\lib\site-packages\scipy\optimize\_shgo.py", line 876, in iterate
    self.iterate_complex()
  File "... path \Anaconda3\lib\site-packages\scipy\optimize\_shgo.py", line 912, in iterate_delaunay
    self.sampled_surface(infty_cons_sampl=self.infty_cons_sampl)
  File "... path \Anaconda3\lib\site-packages\scipy\optimize\_shgo.py", line 1243, in sampled_surface
    self.fun_ref()
  File "... path \Anaconda3\lib\site-packages\scipy\optimize\_shgo.py", line 1356, in fun_ref
    if g(self.C[i, :], *self.args) < 0.0:
TypeError: <lambda>() takes 1 positional argument but 3 were given

P.S.: I understand that the function fun(y, k, g) here doesn't need args k and g, but I just (over)simplified the original function I was using (that needs k and g) to reproduce the error.

Sasche
  • 3
  • 1

2 Answers2

0

g(self.C[i, :], *self.args) is how scipy is passing a search variable and the args tuple to your function. I think your fun(y,k,g) is handling that fine.

But I think the error is in calling one of the constraint or boundary functions. They all have to have the same signature - with a 2 term args that means 3 variables.

if g(self.C[i, :], *self.args) < 0.0: testing some function result against 0.0.

hpaulj
  • 221,503
  • 14
  • 230
  • 353
  • Thanks for the quick answer! After additional research I found out that this is actually an unsolved (yet) known bug of scipy SHGO, discussed for instance in issues [15514](https://github.com/scipy/scipy/issues/15514), [16506](https://github.com/scipy/scipy/pull/16506), [17549](https://github.com/scipy/scipy/issues/17549) and [18235](https://github.com/scipy/scipy/issues/18235) and is related to how the algorithm feeds the `args` to the function as you pointed out. A workaround to this bug is described [here](https://stackoverflow.com/a/72810072/21921370): use a wrapper instead of `args`. – Sasche May 18 '23 at 23:16
0

For the sake of completeness I report the workaround as suggested here

Instead of feeding opt.shgo with fun and args=(k,g) just define a new lambda function and feed it to shgo:

func = lambda x,a=a,b=b: fun(x,a,b)
res = opt.shgo(func, bounds=boun, n = 30, iters = 5, sampling_method = 'sobol', constraints=cons) 
Sasche
  • 3
  • 1