Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Finite field method

The finite field method denotes the calculation of molecular properties of a given order by means of numerical differentiation of lower-order molecular properties. It is typically applied to the determination of electric properties since it is trivial to incorporate uniform static electric fields in the molecular Hamiltonian and carry out calculations in the presence of these fields.

Compared to analytic derivative techniques, finite field calculations are slower and less accurate but their appeal lies in the ease of implementation.

Energy expansion

In the presence of a uniform static electric field, the molecular energy becomes equal to

E(F)=E0μαFα12ααβFαFβ16βαβγFαFβFγ124γαβγδFαFβFγFδE(F) = E_0 - \mu_{\alpha} F_\alpha - \frac{1}{2} \alpha_{\alpha\beta} F_\alpha F_\beta - \frac{1}{6} \beta_{\alpha\beta\gamma} F_\alpha F_\beta F_\gamma - \frac{1}{24} \gamma_{\alpha\beta\gamma\delta} F_\alpha F_\beta F_\gamma F_\delta - \cdots

where the molecular property tensors μ\mu, α\alpha, β\beta, and γ\gamma are the permanent dipole moment, polarizabilty, first-order hyperpolarizability, and second-order hyperpolarizability, respectively. These molecular properties refer to the isolated system and can thus be identified from derivatives of the energy in the limit of zero field strength:

μα=E(F)FαF=0ααβ(0;0)=2E(F)FαFβF=0βαβγ(0;0,0)=3E(F)FαFβFγF=0γαβγδ(0;0,0,0)=4E(F)FαFβFγFδF=0\begin{align*} \mu_{\alpha} & = - \left. \frac{\partial E(F)}{\partial F_\alpha} \right|_{F = 0} \\ \alpha_{\alpha\beta}(0;0) & = - \left. \frac{\partial^2 E(F)}{\partial F_\alpha \partial F_\beta} \right|_{F = 0} \\ \beta_{\alpha\beta\gamma}(0;0,0) & = - \left. \frac{\partial^3 E(F)}{\partial F_\alpha \partial F_\beta \partial F_\gamma} \right|_{F = 0} \\ \gamma_{\alpha\beta\gamma\delta}(0;0,0,0) & = - \left. \frac{\partial^4 E(F)}{\partial F_\alpha \partial F_\beta \partial F_\gamma \partial F_\delta} \right|_{F = 0} \end{align*}

Dipole moment expansion

It is also possible to define a dipole moment of the system in the presence of the external field. This field-dependent dipole moment takes the form

μαF=μα+ααβFβ+12βαβγFβFγ+16γαβγδFβFγFδ\mu_\alpha^F = \mu_\alpha + \alpha_{\alpha\beta} F_\beta + \frac{1}{2} \beta_{\alpha\beta\gamma} F_\beta F_\gamma + \frac{1}{6} \gamma_{\alpha\beta\gamma\delta} F_\beta F_\gamma F_\delta - \cdots

We then get

ααβ(0;0)=μαFFβF=0βαβγ(0;0,0)=2μαFFβFγF=0γαβγδ(0;0,0,0)=3μαFFβFγFδF=0\begin{align*} \alpha_{\alpha\beta}(0;0) & = \left. \frac{\partial \mu_\alpha^F}{\partial F_\beta } \right|_{F = 0} \\ \beta_{\alpha\beta\gamma}(0;0,0) & = \left. \frac{\partial^2 \mu_\alpha^F}{\partial F_\beta \partial F_\gamma} \right|_{F = 0} \\ \gamma_{\alpha\beta\gamma\delta}(0;0,0,0) & = \left. \frac{\partial^3 \mu_\alpha^F}{\partial F_\beta \partial F_\gamma \partial F_\delta} \right|_{F = 0} \end{align*}

Polarizability expansion

It is also possible to define a polarizability of the system in the presence of the external field. This field-dependent polarizability takes the form

ααβF(ω;ω)=ααβ(ω;ω)+βαβγ(ω;ω,0)Fγ+12γαβγδ(ω;ω,0,0)FγFδ\alpha_{\alpha\beta}^F(-\omega;\omega) = \alpha_{\alpha\beta}(-\omega;\omega) + \beta_{\alpha\beta\gamma}(-\omega;\omega,0) F_\gamma + \frac{1}{2} \gamma_{\alpha\beta\gamma\delta}(-\omega;\omega,0,0) F_\gamma F_\delta - \cdots

We then get

βαβγ(ω;ω,0)=ααβF(ω;ω)FγF=0γαβγδ(ω;ω,0,0)=2ααβF(ω;ω)FγFδF=0\begin{align*} \beta_{\alpha\beta\gamma}(-\omega;\omega,0) & = \left. \frac{\partial \alpha_{\alpha\beta}^F(-\omega;\omega)}{\partial F_\gamma} \right|_{F = 0} \\ \gamma_{\alpha\beta\gamma\delta}(-\omega;\omega,0,0) & = \left. \frac{\partial^2 \alpha_{\alpha\beta}^F(-\omega;\omega)}{\partial F_\gamma \partial F_\delta} \right|_{F = 0} \end{align*}

Hyperpolarizability expansion

It is also possible to define a hyperpolarizability of the system in the presence of the external field. This field-dependent hyperpolarizability takes the form

βαβγF(ωσ;ω1,ω2)=βαβγ(ωσ;ω1,ω2)+γαβγδ(ωσ;ω1,ω2,0)Fδ\beta_{\alpha\beta\gamma}^F(-\omega_\sigma;\omega_1, \omega_2) = \beta_{\alpha\beta\gamma}(-\omega_\sigma;\omega_1,\omega_2) + \gamma_{\alpha\beta\gamma\delta}(-\omega_\sigma;\omega_1,\omega_2,0) F_\delta - \cdots

We then get

γαβγδ(ωσ;ω1,ω2,0)=βαβγF(ωσ;ω1,ω2)FδF=0\begin{align*} \gamma_{\alpha\beta\gamma\delta}(-\omega_\sigma;\omega_1,\omega_2,0) & = \left. \frac{\partial \beta_{\alpha\beta\gamma}^F(-\omega_\sigma;\omega_1,\omega_2)}{\partial F_\delta} \right|_{F = 0} \end{align*}

where ωσ=ω1+ω2\omega_\sigma = \omega_1+ \omega_2.

Numerical example

Let us illustrate the finite field method with practical example.

import numpy as np
import veloxchem as vlx

We define a water molecule with the dipole moment aligned along the positive z-axis.

h2o_xyz = """3

O    0.000000000000        0.000000000000        0.000000000000
H    0.000000000000        0.740848095288        0.582094932012
H    0.000000000000       -0.740848095288        0.582094932012
"""

molecule = vlx.Molecule.read_xyz_string(h2o_xyz)
basis = vlx.MolecularBasis.read(molecule, "def2-svpd", ostream=None)

Setting up drivers for:

  • SCF optimization

  • first-order properties (for now an explicit function, will be changed later)

  • linear response function (real)

  • quadratic response function (complex)

  • cubic response function (complex)

Source
scf_drv = vlx.ScfRestrictedDriver()
scf_drv.ostream.mute()

scf_drv.xcfun = "b3lyp"
Source
element_charges = {"H": 1.0, "O": 8.0}

# electric-dipole
dipole_drv = vlx.ElectricDipoleIntegralsDriver()

dipole_mats = dipole_drv.compute(molecule, basis)

mu_x = -1.0 * dipole_mats.x_to_numpy()
mu_y = -1.0 * dipole_mats.y_to_numpy()
mu_z = -1.0 * dipole_mats.z_to_numpy()


def dipmom(D):
    mu_e = np.zeros(3)
    mu_n = np.zeros(3)

    # electronic part
    mu_e[0] = np.einsum("ab, ab", D, mu_x)
    mu_e[1] = np.einsum("ab, ab", D, mu_y)
    mu_e[2] = np.einsum("ab, ab", D, mu_z)

    # nuclear part
    for A, molecule_atom_label in enumerate(molecule.get_labels()):
        R_A = np.array(molecule.get_atom_coordinates(A))
        Z_A = element_charges[molecule_atom_label]

        mu_n += Z_A * R_A

    # total dipole moment
    mu = mu_e + mu_n

    return mu[2]
Source
lrf_drv = vlx.LinearResponseSolver()

lrf_drv.ostream.mute()

lrf_drv.a_operator = "electric dipole"
lrf_drv.b_operator = "electric dipole"

lrf_drv.a_components = "z"
lrf_drv.b_components = "z"

lrf_drv.frequencies = [0.0]
Source
qrf_drv = vlx.QuadraticResponseDriver()

qrf_drv.ostream.mute()

qrf_drv.a_component = "z"
qrf_drv.b_component = "z"
qrf_drv.c_component = "z"

qrf_drv.b_frequencies = [0.0]
qrf_drv.c_frequencies = [0.0]

qrf_drv.damping = 0.0
Source
crf_drv = vlx.CubicResponseDriver()

crf_drv.ostream.mute()

crf_drv.a_component = "z"
crf_drv.b_component = "z"
crf_drv.c_component = "z"
crf_drv.d_component = "z"

crf_drv.b_frequencies = [0.0]
crf_drv.c_frequencies = [0.0]
crf_drv.d_frequencies = [0.0]

crf_drv.damping = 0.0

Numerical differentiation

Up to fourth-order numerical derivatives are calculated with use of a one-dimensional five-point stencil.

def numerical_derivatives(f):
    # numerical differentiation based on the five-point stencil

    d1 = (-f[2 * h] + 8 * f[h] - 8 * f[-h] + f[-2 * h]) / (12 * h)
    d2 = (-f[2 * h] + 16 * f[h] - 30 * f[0.0] + 16 * f[-h] - f[-2 * h]) / (12 * h**2)
    d3 = (f[2 * h] - 2 * f[h] + 2 * f[-h] - f[-2 * h]) / (2 * h**3)
    d4 = (f[2 * h] - 4 * f[h] + 6 * f[0.0] - 4 * f[-h] + f[-2 * h]) / h**4

    return (d1, d2, d3, d4)

Field-dependent molecular properties

The following static molecular properties are determined as functions of the strength, FF, of the electric field applied along the positive z-axis:

  • electronic energy, E(F)E(F)

  • dipole moment, μzF\mu_z^F

  • polarizability, αzzF(0;0)\alpha_{zz}^F(0;0)

  • first-order hyperpolarizaiblity, βzzzF(0;0,0)\beta_{zzz}^F(0;0,0)

  • second-order hyperpolarizaiblity, γzzzzF(0;0,0,0)\gamma_{zzzz}^F(0;0,0,0)

E = {}
mu = {}
alpha = {}
beta = {}
gamma = {}

h = 0.001
field_strengths = np.linspace(-2, 2, 5) * h # five-point stencil

for F in field_strengths:
    scf_drv.electric_field = [0.0, 0.0, F]
    scf_results = scf_drv.compute(molecule, basis)

    E[F] = scf_results["scf_energy"]

    mu[F] = dipmom(scf_results["D_alpha"] + scf_results["D_beta"])

    lrf_results = lrf_drv.compute(molecule, basis, scf_results)
    alpha[F] = -lrf_results["response_functions"][("z", "z", 0.0)]

    qrf_results = qrf_drv.compute(molecule, basis, scf_results)
    beta[F] = qrf_results[('qrf', 0.0, 0.0)].real

    crf_results = crf_drv.compute(molecule, basis, scf_results)
    gamma[F] = -crf_results[('crf', 0.0, 0.0, 0.0)].real

Calculate derivatives

dE = numerical_derivatives(E)
dmu = numerical_derivatives(mu)
dalpha = numerical_derivatives(alpha)
dbeta = numerical_derivatives(beta)

Results

print("-"*50)
print(f"{'Molecular properties':>40s}")
print(" "*10 + "-"*40)
print(f"{'Method':10s}{'mu':>10s}{'alpha':>10s}{'beta':>10s}{'gamma':>10s}")
print("-"*50)
print("Analytic derivatives")
print(f"{'':10s}{mu[0.0]:10.6f}{alpha[0.0]:10.5f}{beta[0.0]:10.5f}{gamma[0.0]:10.3f}")
print("\nNumerical differentiation")
print(f"{' -energy':10s}{-dE[0]:10.6f}{-dE[1]:10.5f}{-dE[2]:10.5f}{-dE[3]:10.3f}")
print(f"{' -mu':10s}{'':10s}{dmu[0]:10.5f}{dmu[1]:10.5f}{dmu[2]:10.3f}")
print(f"{' -alpha':10s}{'':10s}{'':10s}{dalpha[0]:10.5f}{dalpha[1]:10.3f}")
print(f"{' -beta':10s}{'':10s}{'':10s}{'':10s}{dbeta[0]:10.3f}")
print("-"*50)
--------------------------------------------------
                    Molecular properties
          ----------------------------------------
Method            mu     alpha      beta     gamma
--------------------------------------------------
Analytic derivatives
            0.744762   9.34779  -6.73492   999.154

Numerical differentiation
 -energy    0.744763   9.34779  -6.71204  1046.544
 -mu                   9.34780  -6.73527   999.809
 -alpha                         -6.73499   999.151
 -beta                                     999.159
--------------------------------------------------

Using the results obtained with analytic derivatives as reference, we note that up to second-order derivatives are quite stable with the chosen thresholds of convergence (in the SCF optimization and response equation solvers) and strength of the applied field. Fourth-order derivatives are not reliable as seen in the result for γzzzz(0;0,0,0)\gamma_{zzzz}(0;0,0,0) based on differentiation of the energy.