String method (flake.string_method)

String method for minimum energy path (MEP) search.

Two usage modes

2D translational \((x_\mathrm{cm}, y_\mathrm{cm})\) — fixed orientation: pre-rotate the cluster before calling:

from flake.cluster import rotate
pos_rot = rotate(pos, theta_deg)
result  = find_mep(pos_rot, calc_en_f, en_params,
                   p0=[x0, y0], p1=[x1, y1])

result['points'] has shape (n_pt, 2).

3D roto-translational \((x_\mathrm{cm}, y_\mathrm{cm}, \theta)\) — pos in reference frame; the method rotates internally:

result = find_mep(pos, calc_en_f, en_params,
                  p0=[x0, y0, th0], p1=[x1, y1, th1],
                  scale=[lx, ly, ltheta])

result['points'] has shape (n_pt, 3); \(\theta\) is in degrees. Typical scales: \(\lambda_x = \lambda_y =\) substrate lattice spacing; \(\lambda_\theta = 60°\) for 6-fold contact, \(30°\) for 12-fold.

Gradient and string step

The gradient of \(E\) with respect to path coordinates is:

\[\nabla_\mathbf{p} E = \left(-F_x,\; -F_y\right) \quad \text{(2D)} \qquad\text{or}\qquad \left(-F_x,\; -F_y,\; -\tau\right) \quad \text{(3D)}\]

Each interior point steps along \(-\nabla E\):

\[\mathbf{p}_i \;\leftarrow\; \mathbf{p}_i + dt \cdot (-\nabla_{\mathbf{p}_i} E)\]

Scale parameter

scale affects only arc-length reparametrization, not the gradient step. It ensures coordinates with different physical units contribute equally:

\[ds^2 = \sum_i \left(\frac{dp_i}{\lambda_i}\right)^2\]

Pass scale=None (default) for pure 2D: all coordinates share the same units.

Algorithm

Reference: E, Ren, Vanden-Eijnden, J. Chem. Phys. 126, 164103 (2007).

  1. Initialise \(n_\mathrm{pt}\) points on a straight line from \(p_0\) to \(p_1\).

  2. Each interior point steps along the gradient: \(\mathbf{p}_i \mathrel{+}= dt \cdot \nabla_i(-E)\).

  3. Reparametrize to equal arc-length (scaled, linear interpolation).

  4. Repeat until convergence or max_steps.

Classes: StringPath, StringPotential

Function: find_mep

class flake.string_method.StringPath(p0, p1, n_pt, fix_ends=True, scale=None)

Bases: object

A discrete path in dim-dimensional configuration space.

Attributes:

points: (n_pt, dim) float64 ndarray – current path coordinates. fix_ends: bool – if True, endpoints are frozen during steps. scale: (dim,) float64 ndarray – coordinate scales for arc length.

property arc_length

Total arc length of the path in the scaled metric.

reparametrize()

Redistribute points to equal arc-length spacing (scaled metric).

Linear interpolation is used to avoid cubic overshoot near saddle points, which can prevent convergence.

The scale is applied only to arc-length computation; interpolation is performed on the unscaled coordinates.

Raises:

RuntimeError: if the path has collapsed to a point.

step(gradients, dt)

Move each interior point along the gradient, then reparametrize.

Args:

gradients: (n_pt, dim) ndarray – -dE/dp at each point. dt: float – step size.

class flake.string_method.StringPotential(pos, calc_en_f, en_params)

Bases: object

Evaluate substrate energy and gradient along a path.

Args:

pos: (N, 2) ndarray – cluster positions (reference frame, theta=0). calc_en_f: callable – total energy function from substrate_from_params. en_params: list – extra arguments for calc_en_f.

evaluate(path_points, n_jobs=1)

Compute energy and gradient (-dE/dp) at every point along the path.

The dimension of path_points determines the evaluation mode:

dim=2: translational (x_cm, y_cm); pos already oriented by caller. dim=3: roto-translational (x_cm, y_cm, theta_deg); pos rotated here.

Args:

path_points: (n_pt, dim) ndarray – path coordinates. n_jobs: int – joblib parallel workers.

Returns:

energies: (n_pt,) ndarray gradients: (n_pt, dim) ndarray – (-dE/dx, -dE/dy) for dim=2;

(-dE/dx, -dE/dy, -dE/dtheta) = (Fx, Fy, tau) for dim=3.

flake.string_method.find_mep(pos, calc_en_f, en_params, p0, p1, n_pt=100, max_steps=3000, dt=0.0001, fix_ends=True, tol=1e-08, scale=None, n_jobs=1)

Find the minimum energy path between p0 and p1.

The dimension of the search is inferred from len(p0):

dim=2: translational (x_cm, y_cm); caller pre-rotates pos if theta != 0. dim=3: roto-translational (x_cm, y_cm, theta_deg); pos is the reference

frame (theta=0); rotation is applied internally at each path point.

Args:

pos: (N, 2) ndarray – cluster positions (reference frame). calc_en_f: callable – total energy function. en_params: list – extra arguments for calc_en_f. p0: (dim,) array-like – start point. p1: (dim,) array-like – end point. n_pt: int – number of points along path. max_steps: int – maximum gradient-descent iterations. dt: float – step size. fix_ends: bool – freeze endpoints (default True). tol: float – convergence: max pointwise displacement

between iterations < tol.

scale: (dim,) array-like or None – coordinate scales for the

arc-length reparametrization. Default None (ones).

n_jobs: int – parallel workers for potential eval.

Returns:
dict with keys:

‘points’ : (n_pt, dim) – final path in configuration space. ‘energy’ : (n_pt,) – energy along final path. ‘gradient’ : (n_pt, dim) – -dE/dp along final path. ‘converged’: bool ‘n_steps’ : int – actual iterations taken. ‘barrier’ : float – max(energy) - min(energy). ‘dim’ : int – 2 or 3.