AuAg2 chain

We want to simulate external forces by the application of geometry constraints. This “COnstraint GEometry to simulate Forces” is known as the COGEF method [Beyer2000].

1S-COGEF

The standard COGEF method constrains two atoms usually by their distance and relaxes all other degrees of freedom. We want to perform this now for a linear Au-Ag-Ag chain using the ASE built in EMT potential. Optimization/relaxation is done with the FIRE (Fast Inertial Relaxation Engine) algorithm. The convergence criterion is that the force on all individual atoms should be less than f_max

We first relax the chain:

from ase import io
from ase.atoms import Atoms
from ase.optimize import FIRE
from ase.calculators.emt import EMT

fmax = 0.01

fname = 'AuAg2.traj'
try:
    image = io.read(fname)
except FileNotFoundError:
    image = Atoms('AuAgAg', positions=((-1, 0, 0), (0, 0, 0), (1, 0, 0)))
    image.calc = EMT()
    FIRE(image).run(fmax=fmax)
    image.write(fname)

Now we pull on the ends (atom indices 0, 2) of the chain:

from ase import io
from ase.calculators.emt import EMT

from cogef import COGEF1D

image = io.read('AuAg2.traj')
image.calc = EMT()

fmax = 0.01
cogef = COGEF1D(0, 2, fmax=fmax)

if not len(cogef.images):  # calculation was done already
    cogef.images = [image]

    stepsize = 0.1
    steps = 30
    cogef.move(stepsize, steps)

which creates the directory cogef_0_2 and writes the file cogef1d_0_2/cogef.traj containing the corresponding trajectory. Observing the trajectory shows that eventually the Ag-Ag bond breaks. The maximum force needed for separation can be either obtained from a numerical derivative of the energy or directly from the forces:

from cogef import COGEF1D
from cogef.units import nN

cogef = COGEF1D(0, 2)  # this reads cogef1d_0_2/cogef.traj if it is there

print('Maximal force: {0:4.2f} nN (from energies),'.format(
    cogef.get_maximum_force(method='use_energies') / nN) +
    ' {0:4.2f} nN (from forces)'.format(
        cogef.get_maximum_force(method='use_forces') / nN))

Dissociation

../../../_images/1scogef.png

The figure shows the force-dependent probability distributions dp/dF for different temperatures. The mean rupture force and standard deviation are defined by the peaks.

In reality, bonds can not withstand much smaller forces due to temperature effects. We may to include finite temperature into consideration. This is done using the Dissociation object that allows to obtain force-dependent ** link for the dissociation file probability distributions \(dp/dF\) as shown in the figure above.

The figure was created with:

import pylab as plt
from cogef.generalized import COGEF1D
from cogef import Dissociation
from cogef.dissociation.barrier import ElectronicBarrier

from cogef.units import nN

cogef = COGEF1D(0, 2)

# The force is assumed to increase uniformly by this loading rate.
alpha_nNs = 10  # [nN/s]
alpha = alpha_nNs * nN

diss = Dissociation(ElectronicBarrier(cogef))

fstep = 0.001
for T in [5, 50, 100, 200, 300, 500]:  # Temperature [K]
    # Automatic limits
    fmin, fmax = diss.get_force_limits(T, alpha, force_step=fstep,
                                       method='electronic')
    dpdf, F = diss.probability_density(
        T, loading_rate=alpha, force_max=fmax, force_min=fmin,
        force_step=fstep)
    p = plt.plot(F / nN, dpdf * nN, 'o-',
                 label='T={0}K'.format(T))
    plt.axvline(x=cogef.get_maximum_force(method='use_energies') / nN,
                ls='--', color='k')

plt.legend(loc=2)
plt.ylim(0, 110)
plt.xlabel('F [nN]')
plt.ylabel('dp/dF [nN$^{-1}$]')
plt.plot()

plt.savefig('1scogef.png')

The peaks define the average rupture force and the standard deviation. These can be obtained using the Dissociation object:

from cogef import COGEF
from cogef import Dissociation
from cogef.units import nN

cogef = COGEF(0, 2)
diss = Dissociation(cogef)

LOADING_RATE = 10  # [nN/s]
P = 101325.  # Pressure [Pa]

fstep = 0.003
for T in [5, 50, 100, 200, 300, 500]:  # Temperature [K]
    fmin, fmax = diss.get_force_limits(T, P, LOADING_RATE, force_step=fstep,
                                       method='electronic')
    force, error = diss.rupture_force_and_uncertainty(
        T, P, LOADING_RATE, fmax, fmin, fstep, method='electronic')
    print('Rupture force ({0}K): ({1:4.2f} +- {2:4.2f}) nN'.format(
        T, force / nN, error))

Rupture force calculation can be performed automatically, that is the number of needed steps for molecule stretching is obtained. The loop in the next example stops when there are enough images in order to obtain the rupture force:

 ### Rupture force calculation
 
  T = 300
  fmax = 0.01
  stepsize = 0.1
  max_steps = 100

  image = Atoms('AuAgAg', positions=((-1, 0, 0), (0, 0, 0), (1, 0, 0)))
  image.set_calculator(EMT())
  FIRE(image).run(fmax=fmax)

  cogef = COGEF([image], 0, 2, optimizer=FIRE, fmax=fmax)
  diss = Dissociation(cogef)
  for step in range(max_steps + 1):
      try:
          fmin, fmax = diss.get_force_limits(T, P, LOADING_RATE,
                                             force_step=fstep,
                                             method='electronic')
          break
      except ValueError as msg:
          if diss.error not in [1, 2, 3]:
              raise ValueError(msg)
          assert step < max_steps, 'Step limit reached.'
          # Add one image
          cogef.pull(stepsize, 1, initialize, 'cogef.traj')
  force, error = diss.rupture_force_and_uncertainty(
      T, P, LOADING_RATE, fmax, fmin, fstep, method='electronic')
  # in nN
  force = force / nN
  error = error / nN
  print('Rupture force: ({0:4.3f} +- {1:4.3f}) nN'.format(force, error))

Gibbs energies can be used instead of electronic energies by a vibrational analysis and a calculation based on IdealGasThermo. The difference in rupture force is very small for this example:

from ase.calculators.emt import EMT
from cogef import COGEF
from cogef import Dissociation
from cogef.units import nN

cogef = COGEF(0, 2)

LOADING_RATE = 10  # [nN/s]
T = 300      # Temperature [K]
P = 101325.  # Pressure    [Pa]


def initialize(image, dummy_remove):  # XXX
    image.calc = EMT()
    return image


fstep = 0.003
diss = Dissociation(cogef, initialize, vib_method='frederiksen')
diss.geometry = 'linear'  # Like in class IdealGasThermo

fmin, fmax = diss.get_force_limits(T, P, LOADING_RATE, force_step=fstep,
                                   method='Gibbs')
force, error = diss.rupture_force_and_uncertainty(
    T, P, LOADING_RATE, fmax, fmin, fstep, method='Gibbs')
print('Rupture force (Gibbs): ({0:4.3f} +- '
      '{1:4.3f}) nN'.format(force / nN, error))

Most probable force

Rupture force or the most probable force can also be obtained analytically using Bell barrier or the General barrier. We can calculate the force dependent peak values of rupture force, the distribution and the range of most probable forces analytically.

# creates: mpf.png

from matplotlib import pyplot as plt

from cogef import COGEF1D, MPF
from cogef import Dissociation
from cogef.dissociation.diss_old import Dissociation as Dissociation_old
from cogef.units import nN#, _hplanck, _e
import numpy as np

#h = _hplanck / _e

cogef = COGEF1D(0, 2)
energies, distances = cogef.get_energy_curve()
E = list(energies)
last_intact_bond = np.argmax(E)

diss_old = Dissociation_old(cogef)
cogef.last_intact_bond_image = last_intact_bond

D0 = diss_old.electronic_energy_barrier(0)
print('De=', D0, 'eV')

Fmax_cogef = cogef.get_maximum_force(method='use_energies')  # eV/A
print('Fmax = {0:4.2f} eV/A'.format(Fmax_cogef), '=', Fmax_cogef / nN, 'nN')

mpf = MPF(D0, Fmax_cogef)
fstep = 0.0001

# values for calculation
T = 300
anN = 10  # nN/s
print(' ')
print('For T =', T, 'K and alpha =', anN, 'nN/s')

# ------------------ Relative forces (f = F/Fmax)-----------------------------
fmp_bell = mpf.bell(T_K=T, alpha_nNs=anN)

delf = mpf.width_f(T_K=T)
b1 = (fmp_bell + delf / 2)
b2 = (fmp_bell - delf / 2)

fmp_gen = mpf.general(T_K=T, alpha_nNs=anN)
g1 = (fmp_gen + delf / 2)
g2 = (fmp_gen - delf / 2)

fmp_gen_ver = mpf.general_verify(fmp_gen, T_K=T, alpha_nNs=anN)

#################################################
dpdf_bell, f_dpdf_bell = mpf.bell_pd(T_K=T, alpha_nNs=anN, force_step=fstep,
                                     prob_break_value=0.)
idx_bell = np.argmax(dpdf_bell)
fmp_bell_dpdf = f_dpdf_bell[idx_bell]

################################################
dpdf_gen, f_dpdf_gen = mpf.general_pd(T_K=T, alpha_nNs=anN, force_step=fstep,
                                      prob_break_value=0.)
idx_gen = np.argmax(dpdf_gen)
fmp_gen_dpdf = f_dpdf_gen[idx_gen]

print('        Actual most probable force  (F) values in nN          ')
fmp_bell = round(fmp_bell * Fmax_cogef / nN, 4)
b1 = round(b1 * Fmax_cogef / nN, 4)
b2 = round(b2 * Fmax_cogef / nN, 4)
print('Most probable force_Bell is', fmp_bell, 'nN ranging '
                                               'from', b2, 'to', b1, 'nN')
fmp_bell_dpdf = round(fmp_bell_dpdf * Fmax_cogef / nN, 4)
print('From numerical, maximum force_Bell is', fmp_bell_dpdf, 'nN')

print(' ')
fmp_gen = round(fmp_gen * Fmax_cogef / nN, 4)
g1 = round(g1 * Fmax_cogef / nN, 4)
g2 = round(g2 * Fmax_cogef / nN, 4)
print('Most probable force_General:', fmp_gen, 'nN ranging '
                                               'from', g2, 'nN to', g1, 'nN')
fmp_gen_ver = round(fmp_gen_ver * Fmax_cogef / nN, 4)
print('From verification, MPF_General:', fmp_gen_ver, 'nN')

fmp_gen_dpdf = round(fmp_gen_dpdf * Fmax_cogef / nN, 4)
print('From numerical, maximum force_General is', fmp_gen_dpdf, 'nN')

# numerical values in nN
f_dpdf_bell = f_dpdf_bell * Fmax_cogef / nN
f_dpdf_gen = f_dpdf_gen * Fmax_cogef / nN

fig = plt.figure()
ax = fig.add_subplot()
plt.plot(f_dpdf_bell, dpdf_bell, 'o-', color='orange', label='Bell')
plt.plot(f_dpdf_gen, dpdf_gen, 'o-', color='cornflowerblue', label='General')
plt.axvline(x=b1, ls='--', color='orange')
plt.axvline(x=b2, ls='--', color='orange')
plt.axvline(x=g1, ls='--', color='cornflowerblue')
plt.axvline(x=g2, ls='--', color='cornflowerblue')
plt.xlim(0.60, 1.15)
plt.xlabel('Force (nN)')
plt.ylabel('dp/dF (nN$^{-1}$)')
plt.title(r'$T$={} $K$,  '.format(T) + r'$\alpha$={} $nN/s$'.format(anN))
plt.legend()
plt.savefig('mpf.png')
../../../_images/mpf.png