Repository: scipy/scipy
Pull request: BUG: Set success attribute in OptimizeResult. See #9801
closes gh-9801

Problem statement:
dual_annealing does not set the success attribute in OptimizeResult
```
from scipy.optimize import dual_annealing, rosen
res = dual_annealing(rosen, [(-2, 2), (-2, 2)])
print(res.success)
```

Gives:

```
--------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/miniconda3/envs/dev3/lib/python3.7/site-packages/scipy/optimize/optimize.py in __getattr__(self, name)
    113         try:
--> 114             return self[name]
    115         except KeyError:

KeyError: 'success'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
<ipython-input-4-402eff86d4dd> in <module>
      1 from scipy.optimize import dual_annealing, rosen
      2 res = dual_annealing(rosen, [(-2, 2), (-2, 2)])
----> 3 res.success

~/miniconda3/envs/dev3/lib/python3.7/site-packages/scipy/optimize/optimize.py in __getattr__(self, name)
    114             return self[name]
    115         except KeyError:
--> 116             raise AttributeError(name)
    117 
    118     __setattr__ = dict.__setitem__

AttributeError: success
```

Hints from issue discussion:

Commit message: BUG: Set success attribute in OptimizeResult. See #9801

Patch to review:
diff --git a/scipy/optimize/_dual_annealing.py b/scipy/optimize/_dual_annealing.py
index 1b6ccf2ddcab..aa8948ad4e75 100644
--- a/scipy/optimize/_dual_annealing.py
+++ b/scipy/optimize/_dual_annealing.py
@@ -637,11 +637,16 @@ def dual_annealing(func, bounds, args=(), maxiter=1000,
     # Strategy chain instance
     strategy_chain = StrategyChain(accept, visit_dist, func_wrapper,
                                minimizer_wrapper, rand_state, energy_state)
-    # Run the search loop
     need_to_stop = False
     iteration = 0
     message = []
+    # OptimizeResult object to be returned
+    optimize_res = OptimizeResult()
+    optimize_res.sucess = True
+    optimize_res.status = 0
+    
     t1 = np.exp((visit - 1) * np.log(2.0)) - 1.0
+    # Run the search loop
     while(not need_to_stop):
         for i in range(maxiter):
             # Compute temperature for this step
@@ -661,6 +666,7 @@ def dual_annealing(func, bounds, args=(), maxiter=1000,
             if val is not None:
                 message.append(val)
                 need_to_stop = True
+                optimize_res.success = False
                 break
             # Possible local search at the end of the strategy chain
             if not no_local_search:
@@ -668,16 +674,16 @@ def dual_annealing(func, bounds, args=(), maxiter=1000,
                 if val is not None:
                     message.append(val)
                     need_to_stop = True
+                    optimize_res.success = False
                     break
             iteration += 1
 
-    # Return the OptimizeResult
-    res = OptimizeResult()
-    res.x = energy_state.xbest
-    res.fun = energy_state.ebest
-    res.nit = iteration
-    res.nfev = func_wrapper.nfev
-    res.njev = func_wrapper.ngev
-    res.nhev = func_wrapper.nhev
-    res.message = message
-    return res
+    # Setting the OptimizeResult values
+    optimize_res.x = energy_state.xbest
+    optimize_res.fun = energy_state.ebest
+    optimize_res.nit = iteration
+    optimize_res.nfev = func_wrapper.nfev
+    optimize_res.njev = func_wrapper.ngev
+    optimize_res.nhev = func_wrapper.nhev
+    optimize_res.message = message
+    return optimize_res


Code review comments:
diff_hunk: @@ -637,11 +637,16 @@ def dual_annealing(func, bounds, args=(), maxiter=1000,
     # Strategy chain instance
     strategy_chain = StrategyChain(accept, visit_dist, func_wrapper,
                                minimizer_wrapper, rand_state, energy_state)
-    # Run the search loop
     need_to_stop = False
     iteration = 0
     message = []
+    # OptimizeResult object to be returned
+    optimize_res = OptimizeResult()
+    optimize_res.sucess = True
path: scipy/optimize/_dual_annealing.py
text: @user1:
it's spelled with two `c`'s.

@author:
Fixed and added asserts in existing tests for this.