Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trust-radius quasi-Newton optimiser #262

Merged
merged 118 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
4526f2d
stub commit
shoubhikraj Mar 2, 2023
cb9a04c
small changes
shoubhikraj Mar 2, 2023
a25f121
add qa step
shoubhikraj Mar 3, 2023
57d1ab1
add qa step
shoubhikraj Mar 3, 2023
7194085
fix qa step to use cartesian step size
shoubhikraj Mar 6, 2023
8fc24fd
minor updates
shoubhikraj Mar 7, 2023
b174eb1
unfinished updates
shoubhikraj Mar 7, 2023
70ef08e
minor updates
shoubhikraj Mar 7, 2023
896a690
updates to QA
shoubhikraj Mar 8, 2023
a41bbb3
optimiser plot
shoubhikraj Mar 8, 2023
cf1c2cb
optimiser plotting added
shoubhikraj Mar 8, 2023
7518bbc
stub for flowchart update
shoubhikraj Mar 8, 2023
071d037
flowchart update
shoubhikraj Mar 9, 2023
b59a87e
trust update
shoubhikraj Mar 9, 2023
9d9e02d
damping optimiser
shoubhikraj Mar 10, 2023
c635e8a
minor updates
shoubhikraj Mar 10, 2023
3da9b3c
minor updates
shoubhikraj Mar 10, 2023
1f961c3
unfinished tests
shoubhikraj Mar 10, 2023
61dd5ab
minor updates
shoubhikraj Mar 10, 2023
42d05e8
DIC flag to disallow unconverged IBT
shoubhikraj Mar 12, 2023
0233a9b
DIC flag to disallow unconverged IBT
shoubhikraj Mar 12, 2023
56721a5
improvements to coordinates
shoubhikraj Mar 12, 2023
591da9f
improvements to coordinates
shoubhikraj Mar 13, 2023
457a6a9
improvements to coordinates
shoubhikraj Mar 13, 2023
ba6d0a6
damping updates
shoubhikraj Mar 13, 2023
6ea1ea1
TRIM step updates
shoubhikraj Mar 13, 2023
937b487
hessian update tests
shoubhikraj Mar 13, 2023
ae7ac58
Merge remote-tracking branch 'origin/robust-optimiser' into robust-op…
shoubhikraj Mar 13, 2023
7e44768
hessian update tests
shoubhikraj Mar 13, 2023
381efd2
TRIM tests
shoubhikraj Mar 13, 2023
2d86a03
trim corrections
shoubhikraj Mar 14, 2023
ba6971c
trim corrections
shoubhikraj Mar 14, 2023
063da00
stub commit
shoubhikraj Mar 2, 2023
5586fbe
small changes
shoubhikraj Mar 2, 2023
053fbc2
add qa step
shoubhikraj Mar 3, 2023
2709702
add qa step
shoubhikraj Mar 3, 2023
28956df
fix qa step to use cartesian step size
shoubhikraj Mar 6, 2023
5177d13
minor updates
shoubhikraj Mar 7, 2023
59b58a1
unfinished updates
shoubhikraj Mar 7, 2023
96dd915
minor updates
shoubhikraj Mar 7, 2023
67eaa87
updates to QA
shoubhikraj Mar 8, 2023
5d36651
optimiser plot
shoubhikraj Mar 8, 2023
abde7f5
optimiser plotting added
shoubhikraj Mar 8, 2023
fc088c3
stub for flowchart update
shoubhikraj Mar 8, 2023
ef75029
flowchart update
shoubhikraj Mar 9, 2023
341d91a
trust update
shoubhikraj Mar 9, 2023
564d696
damping optimiser
shoubhikraj Mar 10, 2023
881e861
minor updates
shoubhikraj Mar 10, 2023
99b4df0
minor updates
shoubhikraj Mar 10, 2023
6e5e006
unfinished tests
shoubhikraj Mar 10, 2023
b32e1de
hessian update tests
shoubhikraj Mar 13, 2023
22ba298
minor updates
shoubhikraj Mar 10, 2023
b354628
DIC flag to disallow unconverged IBT
shoubhikraj Mar 12, 2023
25e2f72
DIC flag to disallow unconverged IBT
shoubhikraj Mar 12, 2023
a6748b4
improvements to coordinates
shoubhikraj Mar 12, 2023
97ea8e6
improvements to coordinates
shoubhikraj Mar 13, 2023
2543705
improvements to coordinates
shoubhikraj Mar 13, 2023
3dac4e1
damping updates
shoubhikraj Mar 13, 2023
a83bd7e
TRIM step updates
shoubhikraj Mar 13, 2023
7dbc485
hessian update tests
shoubhikraj Mar 13, 2023
724be38
TRIM tests
shoubhikraj Mar 13, 2023
d5fb54f
trim corrections
shoubhikraj Mar 14, 2023
c982b74
trim corrections
shoubhikraj Mar 14, 2023
d206d91
Merge remote-tracking branch 'origin/robust-optimiser' into robust-op…
shoubhikraj Mar 14, 2023
9ffcdb5
tests for trimoptimiser
shoubhikraj Mar 14, 2023
8d766b8
tests for plotting
shoubhikraj Mar 14, 2023
0305385
minor fix
shoubhikraj Mar 14, 2023
2d9878e
unconverged ibt is allowed by default
shoubhikraj Mar 15, 2023
e440e40
add to init
shoubhikraj Mar 15, 2023
0768c34
black
shoubhikraj Mar 15, 2023
3142d3b
reject high energy increase
shoubhikraj Mar 16, 2023
0163d31
print ratio as well
shoubhikraj Mar 16, 2023
77da410
minor update
shoubhikraj Mar 16, 2023
0b7de70
BFGS-SR1 scheme added
shoubhikraj Mar 16, 2023
4bec77a
BFGS-SR1 scheme added
shoubhikraj Mar 16, 2023
d6c9e55
pr updates
shoubhikraj Mar 20, 2023
083169c
pr updates #2
shoubhikraj Mar 20, 2023
21e94cc
rename
shoubhikraj Mar 20, 2023
8183a71
bfgs-sr1-update tests
shoubhikraj Mar 21, 2023
82d7cf2
minor updates
shoubhikraj Mar 21, 2023
37d8549
cartesian optimiser
shoubhikraj Mar 26, 2023
f074ba3
pr updates 3
shoubhikraj Mar 26, 2023
cfae31e
pr updates 4
shoubhikraj Mar 26, 2023
1f6cc70
minor fix
shoubhikraj Mar 26, 2023
665eb75
reimplement damping
shoubhikraj Mar 29, 2023
31088ab
minor fixes
shoubhikraj Apr 1, 2023
07c6047
pr updates, test updates
shoubhikraj Apr 2, 2023
7de9103
resolve merge conflict
shoubhikraj Apr 2, 2023
dce3496
Merge branch 'v1.4.0' into robust-optimiser
shoubhikraj Apr 2, 2023
5eff59b
test updates
shoubhikraj Apr 3, 2023
38fee0c
unfinished updates
shoubhikraj Apr 3, 2023
e729aa0
unfinished updates
shoubhikraj Apr 3, 2023
c267a1f
fixes for tests, change name of RFO trust argument
shoubhikraj Apr 3, 2023
29de8bf
revert RFO and change step size control
shoubhikraj Apr 4, 2023
8338843
fix some tests
shoubhikraj Apr 4, 2023
e3e3473
test updates
shoubhikraj Apr 4, 2023
72f3ca1
test fix
shoubhikraj Apr 4, 2023
dddc6f9
test fix
shoubhikraj Apr 4, 2023
c87ce3f
test updates
shoubhikraj Apr 5, 2023
06d29d2
xyz trajectory writing
shoubhikraj Apr 5, 2023
531ae51
xyz trajectory writing
shoubhikraj Apr 5, 2023
37bbddd
test fix
shoubhikraj Apr 5, 2023
42a6d8a
tests
shoubhikraj Apr 5, 2023
319266f
fix
shoubhikraj Apr 5, 2023
ac79b30
polynomial interpolation: commit
shoubhikraj Apr 8, 2023
516d4e0
polynomial line search
shoubhikraj Apr 9, 2023
fc2d97b
remove line search
shoubhikraj Apr 10, 2023
238f9b6
test fixes
shoubhikraj Apr 10, 2023
6eef4fb
test fixes
shoubhikraj Apr 10, 2023
60dad76
minor fix
shoubhikraj Apr 10, 2023
7ec6824
minor fix
shoubhikraj Apr 11, 2023
c84ffef
pr suggestions
shoubhikraj Apr 11, 2023
d4e3e75
test update
shoubhikraj Apr 11, 2023
e30eb24
changes to TRM
shoubhikraj Apr 12, 2023
34fd312
use hessian eigenbasis
shoubhikraj Apr 12, 2023
bf9d851
Revert "use hessian eigenbasis"
shoubhikraj Apr 13, 2023
83e18b6
Revert "changes to TRM"
shoubhikraj Apr 13, 2023
6437fe3
fix 0 eigenvalue error
shoubhikraj Apr 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions autode/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,11 @@ class CouldNotPlotSmoothProfile(AutodeException):

class TemplateLoadingFailed(AutodeException):
"""A template file was not in the correct format"""


class OptimiserStepError(AutodeException):
"""Unable to calculate a valid Optimiser step"""


class CoordinateTransformFailed(AutodeException):
"""Internal coordinate to Cartesian transform failed"""
9 changes: 8 additions & 1 deletion autode/opt/coordinates/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,22 @@ def __new__(
arr.B = None # Wilson B matrix
arr.B_T_inv = None # Generalised inverse of B
arr.U = np.eye(len(arr)) # Transform matrix
arr.allow_unconverged_back_transform = True # for internal coords

return arr

def __array_finalize__(self, obj: "OptCoordinates") -> None:
"""See https://numpy.org/doc/stable/user/basics.subclassing.html"""

for attr in ("units", "_e", "_g", "_h", "_h_inv", "U", "B", "B_T_inv"):
# fmt: off
for attr in (
"units", "_e", "_g", "_h",
"_h_inv", "U", "B", "B_T_inv",
"allow_unconverged_back_transform",
):
self.__dict__[attr] = getattr(obj, attr, None)

# fmt: on
return None

@property
Expand Down
11 changes: 5 additions & 6 deletions autode/opt/coordinates/dic.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
InverseDistances,
InternalCoordinates,
)
from autode.exceptions import CoordinateTransformFailed

_max_back_transform_iterations = 20

Expand Down Expand Up @@ -239,8 +240,10 @@ def iadd(
f"Final RMS(s) = {rms_s:.8f}"
)
x_k = x_1
if self._allow_unconverged_back_transform:
break
if not self.allow_unconverged_back_transform:
raise CoordinateTransformFailed(
"DIC->Cart iterative back-transform did not converge"
)

s_k = np.matmul(self.U.T, self.primitives(x_k))
self.B = np.matmul(self.U.T, self.primitives.B)
Expand All @@ -251,10 +254,6 @@ def iadd(

return self

@property
def _allow_unconverged_back_transform(self) -> bool:
return True

@property
def active_indexes(self) -> List[int]:
"""A list of indexes for the active modes in this coordinate set"""
Expand Down
2 changes: 2 additions & 0 deletions autode/opt/optimisers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from autode.opt.optimisers.rfo import RFOptimiser
from autode.opt.optimisers.prfo import PRFOptimiser
from autode.opt.optimisers.crfo import CRFOptimiser
from autode.opt.optimisers.trm import HybridTRMOptimiser
from autode.opt.optimisers.steepest_descent import (
CartesianSDOptimiser,
DIC_SD_Optimiser,
Expand All @@ -16,4 +17,5 @@
"CRFOptimiser",
"CartesianSDOptimiser",
"DIC_SD_Optimiser",
"HybridTRMOptimiser",
]
80 changes: 78 additions & 2 deletions autode/opt/optimisers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import numpy as np

from abc import ABC, abstractmethod
from collections import UserList
from typing import Union, Optional, Callable, Any
from autode.log import logger
from autode.utils import NumericStringDict
Expand All @@ -10,6 +11,7 @@
from autode.opt.coordinates.base import OptCoordinates
from autode.opt.optimisers.hessian_update import NullUpdate
from autode.exceptions import CalculationException
from autode.plotting import plot_optimiser_profile


class BaseOptimiser(ABC):
Expand Down Expand Up @@ -66,7 +68,7 @@ def __init__(
self._maxiter = int(maxiter)
self._n_cores: int = Config.n_cores

self._history = _OptimiserHistory()
self._history = OptimiserHistory()

self._coords = coords
self._species: Optional["autode.species.Species"] = None
Expand Down Expand Up @@ -769,8 +771,82 @@ def _best_hessian_updater(self) -> "HessianUpdater":
"suitable update strategies"
)

def plot_optimisation(
self,
filename: Optional[str] = None,
plot_energy: bool = True,
plot_rms_grad: bool = True,
) -> None:
"""
Draw the plot of the energies and/or rms_gradients of
the optimisation so far

----------------------------------------------------------------------
Args:
filename (str): Name of the file to plot
plot_energy (bool): Whether to plot energy
plot_rms_grad (bool): Whether to plot RMS of gradient
"""
if self.iteration < 1:
logger.warning("Less than 2 points, cannot draw optimisation plot")
return None

if not self.converged:
logger.warning(
"Optimisation is not converged, drawing a plot "
"of optimiser profile until current iteration"
)

filename = (
f"{self._species.name}_opt_plot.pdf"
if filename is None
else str(filename)
)

plot_optimiser_profile(
self._history,
filename=filename,
plot_energy=plot_energy,
plot_rms_grad=plot_rms_grad,
)
return None

def print_geometries(self, filename: Optional[str] = None) -> None:
"""
Write the trajectory of the optimiser in .xyz format

Args:
filename: Name of the trajectory file (xyz)
"""
assert self._species is not None
if self.iteration < 1:
logger.warning(
"Optimiser did no steps, not saving .xyz trajectory"
)
return None

filename = (
f"{self._species.name}_opt.trj.xyz"
if filename is None
else str(filename)
)

if os.path.isfile(filename):
logger.warning(f"File {filename} exists, overwriting...")
os.remove(filename)

tmp_spc = self._species.new_species()
for coord in self._history:
tmp_spc.coordinates = coord.to("cart")
tmp_spc.energy = coord.e
tmp_spc.print_xyz_file(
filename=filename,
append=True,
)
return None


class _OptimiserHistory(list):
class OptimiserHistory(UserList):
t-young31 marked this conversation as resolved.
Show resolved Hide resolved
"""Sequential history of coordinates"""

@property
Expand Down
2 changes: 1 addition & 1 deletion autode/opt/optimisers/crfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def _g_norm(self) -> GradientRMS:

def _initialise_run(self) -> None:
"""Initialise the optimisation"""
logger.info("Initialising constrained optimisation")
logger.info("Initialising optimisation")

self._build_internal_coordinates()
self._coords.update_h_from_cart_h(self._low_level_cart_hessian)
Expand Down
125 changes: 125 additions & 0 deletions autode/opt/optimisers/hessian_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,5 +451,130 @@ def conditions_met(self) -> bool:
return True


class FlowchartUpdate(HessianUpdater):
"""
A hybrid update scheme combining BFGS, SR1 and PSB Hessian update
formulae. Proposed in A. B. Birkholz and H. B. Schlegel in
Theor. Chem. Acc., 135 (84), 2016. This implementation is slightly
modified.
"""

def __repr__(self):
return "Flowchart"

@property
def _updated_h(self) -> np.ndarray:
"""
Flowchart (or FlowPSB) Hessian update scheme, that dynamically
switches between BFGS and SR1 depending on some criteria.

Alternatively switches to PSB update as a fallback if none of
the criteria are satisfied. Notation follows A. B. Birkholz,
H. B. Schlegel, Theor. Chem. Acc., 135 (84), 2016.

Returns:
(np.ndarray): H
"""
z = self.y - np.matmul(self.h, self.s)
sr1_criteria = np.dot(z, self.s) / (
np.linalg.norm(z) * np.linalg.norm(self.s)
)
bfgs_criteria = np.dot(self.y, self.s) / (
np.linalg.norm(self.y) * np.linalg.norm(self.s)
)
if sr1_criteria < -0.1:
h_new = self.h + np.outer(z, z) / np.dot(z, self.s)
return h_new
elif bfgs_criteria > 0.1:
bfgs_delta_h = np.outer(self.y, self.y) / np.dot(self.y, self.s)
bfgs_delta_h -= np.linalg.multi_dot(
(self.h, self.s.reshape(-1, 1), self.s.reshape(1, -1), self.h)
) / np.linalg.multi_dot(
(self.s.flatten(), self.h, self.s.flatten())
)
h_new = self.h + bfgs_delta_h
return h_new
else:
# Notation copied from Bofill update
G_i_1 = self.h # G_{i-1}
dE_i = self.y - np.dot(G_i_1, self.s) # ΔE_i = Δg_i - G_{i-1}Δx_i
dxTdg = np.dot(self.s, self.y)
G_i_PSB = (
G_i_1
+ (
(np.outer(dE_i, self.s) + np.outer(self.s, dE_i))
/ np.dot(self.s, self.s)
)
- (
(
(dxTdg - np.linalg.multi_dot((self.s, G_i_1, self.s)))
* np.outer(self.s, self.s)
)
/ np.dot(self.s, self.s) ** 2
)
)
return G_i_PSB

@property
def _updated_h_inv(self) -> np.ndarray:
"""Flowchart update is only available for Hessian"""
return np.linalg.inv(self._updated_h)

@property
def conditions_met(self) -> bool:
"""
Flowchart update does not have any conditions, as
update scheme is dynamically selected
"""
return True


class BFGSSR1Update(HessianUpdater):
"""
Interpolates between BFGS and SR1 update in a fashion similar
to Bofill updates, but suited for minimisations. Proposed by
Farkas and Schlegel in J. Chem. Phys., 111, 1999, 10806
"""

def __repr__(self):
return "BFGS-SR1"

@property
def _updated_h(self) -> np.ndarray:
shoubhikraj marked this conversation as resolved.
Show resolved Hide resolved
"""
Hybrid BFGS and SR1 update. The mixing parameter phi is defined
as the square root of the (1 - phi_Bofill) used in Bofill update.

Returns:
(np.ndarray): The updated hessian
"""
bfgs_delta_h = np.outer(self.y, self.y) / np.dot(self.y, self.s)
bfgs_delta_h -= np.linalg.multi_dot(
(self.h, self.s.reshape(-1, 1), self.s.reshape(1, -1), self.h)
) / np.linalg.multi_dot((self.s.flatten(), self.h, self.s.flatten()))

y_hs = self.y - np.matmul(self.h, self.s)
sr1_delta_h = np.outer(y_hs, y_hs) / np.dot(y_hs, self.s)

# definition according to Farkas, Schlegel, J Chem. Phys., 111, 1999
# NOTE: this phi is (1 - original_phi_bofill)
phi = np.dot(self.s, y_hs) ** 2 / (
np.dot(self.s, self.s) * np.dot(y_hs, y_hs)
)
sqrt_phi = np.sqrt(phi)
logger.info(f"BFGS-SR1 update: ϕ = {sqrt_phi:.4f}")
return self.h + sqrt_phi * sr1_delta_h + (1 - sqrt_phi) * bfgs_delta_h

@property
def _updated_h_inv(self) -> np.ndarray:
"""For BFGS-SR1 update, only hessian is available"""
return np.linalg.inv(self._updated_h)

@property
def conditions_met(self) -> bool:
"""No conditions need to be satisfied for BFGS-SR1 update"""
return True


def _ensure_hermitian(matrix: np.ndarray) -> np.ndarray:
return (matrix + matrix.T) / 2.0
9 changes: 6 additions & 3 deletions autode/opt/optimisers/rfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ def _take_step_within_trust_radius(
if len(delta_s) == 0: # No need to sanitise a null step
return 0.0

new_coords = self._coords + factor * delta_s
self._coords.allow_unconverged_back_transform = True
step = factor * delta_s
new_coords = self._coords + step
t-young31 marked this conversation as resolved.
Show resolved Hide resolved
cartesian_delta = new_coords.to("cart") - self._coords.to("cart")
max_component = np.max(np.abs(cartesian_delta))

Expand All @@ -121,7 +123,8 @@ def _take_step_within_trust_radius(
# Note because the transformation is not linear this will not
# generate a step exactly max(∆x) ≡ α, but is empirically close
factor = self.alpha / max_component
new_coords = self._coords + self.alpha / max_component * delta_s
step = factor * delta_s

self._coords = new_coords
self._coords.allow_unconverged_back_transform = False
self._coords = self._coords + step
return factor
Loading