pythonscipyscipy-optimize-minimize

Scipy Optimize Function with Single Variable


I am trying to use SciPy's minimize function to maximize the results of my function f. However, every time I run the script, I get the following error message.

Exception has occurred: TypeError
'float' object is not iterable
  File "C:\Users\DonzaJ\OneDrive - LiRo Group\Desktop\Personal Files\Python Code\Fence Angle Optimizer.py", line 24, in <module>
    theta_final = spo.minimize(f, theta, method = 'SLSQP', options ={'disp':True}, bounds = bdns)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'float' object is not iterable  

Here is the script that I have set up so far.

import scipy.optimize as spo
import math as m
import numpy as np

# Define Constants
fenceLength = 7.5 #ft
pointLoad = 500 #lbs
ex2 = 2 #in
ey2 = 4 #in

# Define Initial Variables
theta = np.ndarray(45.0) #degrees

# Define bounds for theta
bdns = (0.0, 90.0)

def f(X, *args):
    ex1 = (fenceLength/2)*m.cos(m.radians(theta))
    ey1 = (fenceLength/2)*m.sin(m.radians(theta))
    Y = m.sqrt(((pointLoad*(ey1+ey2))**2 + (pointLoad*(ex1+ex2))**2)) * -1
    return Y

# Call minimizer 
theta_final = spo.minimize(f, theta, method = 'SLSQP', options ={'disp':True}, bounds = bdns)

Can anyone explain to me how I can fix this and what I am doing wrong?


Solution

  • You have a couple of things wrong with your code.

    1. np.ndarray(45.0) does not make a numpy array, you need to do something like np.array([45.0]). But, considering you only have one variable, you can just set theta0 = 45.0.
    2. As mentioned in another answer, the bounds should be a sequence of pairs, meaning you should define it as bnds = [(0.0, 90.0)].
    3. Your f function needs to use the variable you are passing it, otherwise the optimizer will see no change in the results and assume it is already at a minimum. In other words, you should have ex1 = (fenceLength/2)*m.cos(m.radians(X)).
    4. Your theta_final will not take the optimal value but will be an OptimizerResult, which you need to get the value from.

    Below is the updated code that removes the numpy import, since it is unnecessary, makes the necessary changes outlined above, and updates some of the notation to be more standard.

    from scipy.optimize import minimize
    import math
    
    # Define Constants
    fenceLength = 7.5 #ft
    pointLoad = 500 #lbs
    ex2 = 2 #in
    ey2 = 4 #in
    
    # Define Initial Variables
    theta0 = 45.0 #degrees
    
    # Define bounds for theta
    bdns = [(0.0, 90.0)]
    
    def f(X, *args):
        ex1 = (fenceLength/2)*math.cos(math.radians(X))
        ey1 = (fenceLength/2)*math.sin(math.radians(X))
        Y = -math.sqrt(((pointLoad*(ey1+ey2))**2 + (pointLoad*(ex1+ex2))**2))
        return Y
    
    # Call minimizer 
    res = minimize(f, theta0, method = "SLSQP", options={"disp":True}, bounds=bdns)
    theta = res.x[0]
    print(f"{theta = } deg")
    

    Output:

    Optimization terminated successfully    (Exit mode 0)
                Current function value: -4111.067977477818
                Iterations: 4
                Function evaluations: 8
                Gradient evaluations: 4
    theta = 63.43457272808145 deg