Jastrow¶
Jastrow factor is the sum of homogeneous, isotropic electron–electron terms \(u(r_{ij})\), isotropic electron–nucleus terms \(\chi(r_{iI})\) centered on the nuclei, isotropic electron–electron–nucleus terms \(f(r_{ij}, r_{iI}, r_{jI})\), also centered on the nuclei:
Jastrow part of wavefunction is represented by the casino.Jastrow class.
It must be initialized from the configuration files:
from casino.readers import CasinoConfig
from casino.jastrow import Jastrow
config_path = <path to a directory containing input file>
config = CasinoConfig(config_path)
config.read()
jastrow = Jastrow(config)
To prevent code duplication, we need to prepare the necessary intermediate data:
import numpy as np
neu, ned = config.input.neu, config.input.ned
ne = neu + ned
r_e = np.random.uniform(-1, 1, ne * 3).reshape((ne, 3))
atom_positions = config.wfn.atom_positions
e_vectors = np.expand_dims(r_e, 1) - np.expand_dims(r_e, 0)
n_vectors = np.expand_dims(r_e, 0) - np.expand_dims(atom_positions, 1)
e_powers = jastrow.ee_powers(e_vectors)
n_powers = jastrow.en_powers(n_vectors)
r_ij = np.linalg.norm(e_vectors, axis=2)
r_iI = np.linalg.norm(n_vectors, axis=2)
ee_spin_mask = np.ones(shape=(ne, ne), dtype=int)
ee_spin_mask[:neu, :neu] = 0
ee_spin_mask[neu:, neu:] = 2
en_spin_mask = np.zeros(shape=(ne,), dtype=int)
en_spin_mask[neu:] = 1
C = jastrow.trunc
Summary of Methods¶
Jastrow class has a following methods:
Method |
Output |
Shape |
|---|---|---|
\(u(r_{ij})\) |
scalar |
|
\(\chi(r_{iI})\) |
scalar |
|
\(f(r_{ij}, r_{iI}, r_{jI})\) |
scalar |
|
\(\nabla u(r_{ij})\) |
\((3N_e,)\) |
|
\(\nabla \chi(r_{iI})\) |
\((3N_e,)\) |
|
\(\nabla f(r_{ij}, r_{iI}, r_{jI})\) |
\((3N_e,)\) |
|
\(\Delta u(r_{ij})\) |
scalar |
|
\(\Delta \chi(r_{iI})\) |
scalar |
|
\(\Delta f(r_{ij}, r_{iI}, r_{jI})\) |
scalar |
u-term¶
\(u(r_{ij})\) term consists of a complete power expansion in electron-electron distances \(r_{ij}\):
where \(\Theta\) is the Heaviside function. This term goes to zero at the cutoff length \(L_u\) with \(C - 1\) continuous derivatives at. In this expression C determines the behavior at the cutoff length. If \(C = 2\), the gradient of this term is continuous but the second derivative and hence the local energy is discontinuous, and if \(C = 3\) then both the gradient of this term and the local energy are continuous. Electron-electron Kato cusp conditions at \(r_{ij} = 0\) satisfied by constraint:
where \(\Gamma_{ij} = 1/2\) if electrons \(i\) and \(j\) have opposite spins and \(\Gamma_{ij} = 1/4\) if \(i\) and \(j\) have
the same spin.
For certain electron coordinates, \(u\) term can be obtained with casino.Jastrow.u_term() method:
jastrow.u_term(e_powers)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
poly = polyval(r_ij, jastrow.u_parameters.T)
cutoff = np.minimum(r_ij - jastrow.u_cutoff, 0) ** C
np.sum(np.triu(cutoff * np.choose(ee_spin_mask, poly, mode='wrap'), 1))
chi-term¶
\(\chi(r_{iI})\) term consists of a complete power expansion in electron-nucleus distances \(r_{iI}\):
where \(\Theta\) is the Heaviside function. This term goes to zero at the cutoff length \(L_{\chi I}\). The term involving the ionic charge \(Z_I\) enforces the electron–nucleus Kato cusp condition:
if the Slater part of the wave function satisfies the electron–nucleus cusp condition, or if a non-divergent pseudopotential is used, then the Jastrow factor is required to be cuspless at the nuclei, i.e it should satisfy the above equation with \(Z_I = 0\)
For certain electron coordinates, \(\chi\) term can be obtained with casino.Jastrow.chi_term() method:
jastrow.chi_term(n_powers)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
poly = polyval(r_iI, jastrow.chi_parameters[0].T)
cutoff = np.minimum(r_iI - jastrow.chi_cutoff, 0) ** C
np.sum(cutoff[0] * np.choose(en_spin_mask, poly, mode='wrap'))
f-term¶
\(f(r_{ij}, r_{iI}, r_{jI})\) term is the most general expansion of a function of \(r_{ij}\) , \(r_{iI}\) , and \(r_{jI}\) that is cuspless at the coalescence point and goes smoothly to zero when either \(r_{iI}\) or \(r_{jI}\) reach cutoff lengths:
To ensure electron–electron Kato cusp conditions folowing \(2N_{fI}^{eN} + 1\) constraints is applied:
and to ensure electron–nucleus Kato cusp conditions folowing \(N_{fI}^{eN} + N_{fI}^{ee} + 1\) constraints is applied:
If desired, there are \(N_{fI}^{ee}\) constraints imposed to prevent duplication of \(u\) term \((γ_{00nI} = 0 \ \forall n)\) and there are \(N_{fI}^{eI}\) constraints imposed to prevent duplication of \(\chi\) term \((γ_{l00I} = 0 \ \forall l)\) also the Jastrow factor to be symmetric under electron exchanges it is required that \(\gamma_{lmnI} = \gamma_{mlnI} \ \forall I, m, l, n\).
For certain electron coordinates, \(f\) term can be obtained with casino.Jastrow.f_term() method:
jastrow.f_term(e_powers, n_powers)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval3d
r_ijI = np.tile(r_iI[0], (ne, 1))
cutoff = np.minimum(r_iI - jastrow.f_cutoff, 0) ** C
poly = polyval3d(r_ijI, r_ijI.T, r_ij, jastrow.f_parameters[0].T)
np.sum(np.triu(np.outer(cutoff[0], cutoff[0]) * np.choose(ee_spin_mask, poly, mode='wrap'), 1))
u-term gradient¶
Considering that gradient of spherically symmetric function (in 3-D space) is:
There is only two non-zero terms of \(u(r_{ij})\) gradient, i.e. by \(i\)-th or \(j\)-th electron coordinates:
where \(\mathbf{\hat r}_{ij}\) is the unit vector in the direction of the \(\mathbf{r}_{ij}\)
For certain electron coordinates, \(u\) term gradient can be obtained with casino.Jastrow.u_term_gradient() method:
jastrow.u_term_gradient(e_powers, e_vectors)
this is equivalent to (continues from):
import numpy as np
from numpy.polynomial.polynomial import polyval
L = jastrow.u_cutoff
l = np.arange(jastrow.u_parameters.shape[1])
cutoff = np.minimum(r_ij - L, 0) ** C
poly = polyval(r_ij, jastrow.u_parameters.T) * C / (r_ij - L)
poly += polyval(r_ij, (l * jastrow.u_parameters).T) / r_ij
g_ij = np.nan_to_num(cutoff * np.choose(ee_spin_mask, poly, mode='wrap') * e_vectors.T / r_ij)
np.sum(g_ij, axis=1).T.ravel()
chi-term gradient¶
Considering that gradient of spherically symmetric function (in 3-D space) is:
There is only one non-zero term of \(\chi(r_{iI})\) gradient, i.e. by \(i\)-th electron coordinates:
where \(\mathbf{\hat r}_{iI}\) is the unit vector in the direction of the \(\mathbf{r}_{iI}\)
For certain electron coordinates, \(\chi\) term gradient can be obtained with casino.Jastrow.chi_term_gradient() method:
jastrow.chi_term_gradient(n_powers, n_vectors)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
L = jastrow.chi_cutoff
cutoff = np.minimum(r_iI - L, 0) ** C
r_iI = np.linalg.norm(n_vectors, axis=2)
m = np.arange(jastrow.chi_parameters[0].shape[1])
poly = polyval(r_iI, jastrow.chi_parameters[0].T) * C / (r_iI[0] - L[0])
poly += polyval(r_iI, (m * jastrow.chi_parameters[0]).T) / r_iI[0]
(cutoff[0] * np.choose(en_spin_mask, poly, mode='wrap') * n_vectors[0].T / r_iI[0]).T.ravel()
f-term gradient¶
Considering that gradient of spherically symmetric function (in 3-D space) is:
There is only two non-zero terms of \(f(r_{ij}, r_{iI}, r_{jI})\) gradient, i.e. by \(i\)-th or \(j\)-th electron coordinates:
For certain electron coordinates, \(f\) term gradient can be obtained with casino.Jastrow.f_term_gradient() method:
jastrow.f_term_gradient(e_powers, n_powers, e_vectors, n_vectors)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval3d
n = np.expand_dims(np.arange(jastrow.f_parameters[0].shape[1]), axis=(1, 2))
m = np.expand_dims(np.arange(jastrow.f_parameters[0].shape[2]), axis=1)
l = np.arange(jastrow.f_parameters[0].shape[3])
L = jastrow.f_cutoff
cutoff = np.minimum(r_iI - L, 0) ** C
r_ijI = np.tile(r_iI[0], (ne, 1))
poly = polyval3d(r_ijI, r_ijI.T, r_ij, jastrow.f_parameters[0].T)
poly_l = polyval3d(r_ijI, r_ijI.T, r_ij, (l * jastrow.f_parameters[0]).T)
poly_m = polyval3d(r_ijI, r_ijI.T, r_ij, (m * jastrow.f_parameters[0]).T)
poly_n = polyval3d(r_ijI, r_ijI.T, r_ij, (n * jastrow.f_parameters[0]).T)
g_ijI = np.choose(ee_spin_mask, poly, mode='wrap') * C / (r_iI[0] - L[0])
g_ijI += np.choose(ee_spin_mask, poly_l, mode='wrap') / r_iI[0]
g_ijI = np.triu(g_ijI, 1) * np.expand_dims(n_vectors[0].T / r_iI[0], 1)
g_jiI = np.choose(ee_spin_mask, poly, mode='wrap').T * C / (r_iI[0] - L[0])
g_jiI += np.choose(ee_spin_mask, poly_m, mode='wrap').T / r_iI[0]
g_jiI = np.tril(g_jiI, -1) * np.expand_dims(n_vectors[0].T / r_iI[0], 1)
g_ij = np.nan_to_num(np.choose(ee_spin_mask, poly_n / r_ij, mode='wrap') * e_vectors.T / r_ij)
np.sum(np.outer(cutoff[0], cutoff[0]) * (g_ijI + g_jiI + g_ij), axis=1).T
u-term laplacian¶
Considering that Laplace operator of spherically symmetric function (in 3-D space) is:
There is only two non-zero terms of \(u(r_{ij})\) laplacian, i.e. by \(i\)-th or \(j\)-th electron coordinates:
For certain electron coordinates, \(u\) term laplacian can be obtained with casino.Jastrow.u_term_laplacian() method:
jastrow.u_term_laplacian(e_powers)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
L = jastrow.u_cutoff
l = np.arange(jastrow.u_parameters.shape[1])
cutoff = np.minimum(r_ij - jastrow.u_cutoff, 0) ** C
poly = polyval(r_ij, jastrow.u_parameters.T) * C * (C - 1) / (r_ij - L) ** 2
poly += 2 * polyval(r_ij, ((l + 1) * jastrow.u_parameters).T) * C / r_ij / (r_ij - L)
poly += polyval(r_ij, (l * (l + 1) * jastrow.u_parameters).T) / r_ij ** 2
2 * np.sum(np.triu(cutoff * np.choose(ee_spin_mask, poly, mode='wrap'), 1))
chi-term laplacian¶
Considering that Laplace operator of spherically symmetric function (in 3-D space) is:
then \(\chi(r_{iI})\) term laplacian:
For certain electron coordinates, \(\chi\) term laplacian can be obtained with casino.Jastrow.chi_term_laplacian() method:
jastrow.chi_term_laplacian(n_powers)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
L = jastrow.chi_cutoff
m = np.arange(jastrow.chi_parameters[0].shape[1])
cutoff = np.minimum(r_iI - L, 0) ** C
poly = polyval(r_iI, jastrow.chi_parameters[0].T) * C * (C - 1) / (r_iI[0] - L[0]) ** 2
poly += 2 * polyval(r_iI, ((m + 1) * jastrow.chi_parameters[0]).T) * C / r_iI / (r_iI[0] - L[0])
poly += polyval(r_iI, (m * (m + 1) * jastrow.chi_parameters[0]).T) / r_iI ** 2
np.sum(cutoff[0] * np.choose(en_spin_mask, poly, mode='wrap'))
f-term laplacian¶
Considering that Laplace operator of spherically symmetric function (in 3-D space) is:
and \(f\) term is a product of two spherically symmetric functions \(g(r_{ij})\) and \(h(r_{iI})\) so using:
There is only two non-zero terms of \(f(r_{ij}, r_{iI}, r_{jI})\) laplacian, i.e. by \(i\)-th or \(j\)-th electron coordinates:
For certain electron coordinates, \(f\) term laplacian can be obtained with casino.Jastrow.f_term_laplacian() method:
jastrow.f_term_laplacian(e_powers, n_powers, e_vectors, n_vectors)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval3d
n = np.expand_dims(np.arange(jastrow.f_parameters[0].shape[1]), axis=(1, 2))
m = np.expand_dims(np.arange(jastrow.f_parameters[0].shape[2]), axis=1)
l = np.arange(jastrow.f_parameters[0].shape[3])
L = jastrow.f_cutoff
cutoff = np.minimum(r_iI - L, 0) ** C
r_ijI = np.tile(r_iI[0], (ne, 1))
poly = polyval3d(r_ijI, r_ijI.T, r_ij, jastrow.f_parameters[0].T)
poly_l = polyval3d(r_ijI, r_ijI.T, r_ij, ((l + 1) * jastrow.f_parameters[0]).T)
poly_ll = polyval3d(r_ijI, r_ijI.T, r_ij, (l * (l + 1) * jastrow.f_parameters[0]).T)
poly_n = polyval3d(r_ijI, r_ijI.T, r_ij, (n * jastrow.f_parameters[0]).T)
poly_nn = polyval3d(r_ijI, r_ijI.T, r_ij, (n * (n + 1) * jastrow.f_parameters[0]).T)
poly_lm = polyval3d(r_ijI, r_ijI.T, r_ij, (l * m * jastrow.f_parameters[0]).T)
poly_ln = polyval3d(r_ijI, r_ijI.T, r_ij, (l * n * jastrow.f_parameters[0]).T)
l_1 = np.choose(ee_spin_mask, poly, mode='wrap') * C * (C - 1) / (r_iI[0] - L[0]) ** 2
l_1 += 2 * np.choose(ee_spin_mask, poly_l, mode='wrap') * C / (r_iI[0] - L[0]) / r_iI[0]
l_1 += np.choose(ee_spin_mask, poly_ll, mode='wrap') / r_iI[0] ** 2
l_dot = np.choose(ee_spin_mask, poly_n, mode='wrap') * C / (r_iI[0] - L[0]) / r_ij
l_dot += np.choose(ee_spin_mask, poly_ln, mode='wrap') / r_iI[0] / r_ij
l_dot *= -np.einsum('aij,ai->ji', (e_vectors.T / r_ij), (n_vectors[0].T / r_iI[0]))
l_2 = np.choose(ee_spin_mask, poly_nn, mode='wrap') / r_ij ** 2
np.sum(np.outer(cutoff[0], cutoff[0]) * np.nan_to_num(l_1 + 2 * l_dot + l_2))