Detecting optimization failure

Is there a built-in method or variable that indicates geometry optimization success/failure?

At the moment, I read the RMS gradient and compare against the convergence criterion but wonder if a boolean method/var already exists.

If it fails, it throws an error (not really catch-able) or prints to the screen “Optimizer: Optimization failed!”. Of failed optimizations, the former occurs much more often, I’d expect. I can’t think right off how to even trigger the latter. If you’d like, you can set a PSIvariable to the optimization iteration (or to the negative for failure) as below, and test on that. The value can be got through get_variable('OPTIMIZATION CYCLE') (note that it’ll be a double, not an int). If that proves useful, can absorb into codebase.

--- a/share/python/driver.py
+++ b/share/python/driver.py
@@ -1182,6 +1182,7 @@ def optimize(name, **kwargs):
             if psi4.get_option('OPTKING', 'OPT_TYPE') == 'IRC':
                 thisenergy = old_thisenergy
             print('Optimizer: Optimization complete!')
+            psi4.set_variable('OPTIMIZATION CYCLE', n)
             psi4.print_out('\n    Final optimized geometry and variables:\n')
             moleculeclone.print_in_input_format()
             # Check if user wants to see the intcos; if so, don't delete them.
@@ -1212,6 +1213,7 @@ def optimize(name, **kwargs):
 
         elif optking_rval == psi4.PsiReturnType.Failure:
             print('Optimizer: Optimization failed!')
+            psi4.set_variable('OPTIMIZATION CYCLE', -1 * n)
             if (psi4.get_option('OPTKING', 'KEEP_INTCOS') == False):
                 psi4.opt_clean()
             molecule.set_geometry(moleculeclone.geometry())

Any way that leaves an immediate clue is good.

Normally, I’d fidget about proliferation of variables but your fix has the strong merits of unambiguity and brevity.

Yes, please include it. I’ll patch my local stuff accordingly.

An alternative is for optimize() to return both energy and the gradient matrix (as does a certain Pacific Northwest app) and let the developer decide.

Thanks much.

Ah, if gradient would serve you better, you can have it.

e, w = optimize('hf', return_wfn=True)
w.gradient().print_out()  # prints gradient mat outfile
print w.gradient().rms()  # print rms of gradient mat screen

We don’t want to return E & G directly to avoid too many return signatures

# simple returns
E = energy(...)
E = optimize(...)
E = frequency(...)
G = gradient(...)  # called by optimize()
H = hessian(...)  # called by frequency()

# power user returns
E, wfn = energy(..., return_wfn=True)
E, wfn = optimize(..., return_wfn=True)
E, wfn = frequency(..., return_wfn=True)
G, wfn = gradient(..., return_wfn=True)  # called by optimize()
H, wfn = hessian(..., return_wfn=True)  # called by frequency()

That’s approximately how I did it.
Psi4 allows this expression:

Grms = psi4.get_gradient().rms()

Ideally, the optimize() doc page would explicitly state the return_wfn argument option.

Thanks for all your help.

Preferentially use the Wavefunction.gradient().rms() syntax. We’re moving away from global variables, and the global psi4.get_gradient() is legacy for some particular operations (i.e., communicating with the optimizer).

Yes, the return_wfn bit hasn’t permeated the docs yet. It will next Tuesday, though. I have in mind a lot of the items that need updating, but if you come across anything confusing or old in the docs, feel free to post to the forum.