Backflow¶
Backflow displacement is the sum of homogeneous, isotropic electron–electron terms \(\eta(r_{ij})\), isotropic electron–nucleus terms \(\mu(r_{iI})\) centered on the nuclei, isotropic electron–electron–nucleus terms \(\Phi(r_{iI}, r_{jI}, r_{ij})\), \(\Theta(r_{iI}, r_{jI}, r_{ij})\), also centered on the nuclei:
When all-electron atoms are present, the electron-nucleus Kato cusp conditions cannot be fulfilled unless the backflow displacement at the nuclear position is zero. This can be obtained by applying smooth cutoffs around such atoms. An artificial multiplicative cutoff function \(g(r_{iI})\) is applied to all contributions to the backflow displacement of particle \(i\) that do not depend on the distance \(r_{iI}\) to the all-electron atom \(I\). This includes the homogeneous backflow term displacement like \(\eta(r_{ij})\mathbf{r}_{ij}\) and the inhomogeneous contributions centered on other atom \(J \neq I\) like \(\Phi(r_{iJ}, r_{jJ}, r_{ij})\mathbf{r}_{ij}\) or \(\Theta(r_{iJ}, r_{jJ}, r_{ij})\mathbf{r}_{ij}\). The \(g(r_{iI})\) function used is of the form:
Backflow part of wavefunction is represented by the casino.Backflow class.
It must be initialized from the configuration files:
from casino.readers import CasinoConfig
from casino.backflow import Backflow
config_path = <path to a directory containing input file>
config = CasinoConfig(config_path)
config.read()
backflow = Backflow(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 = backflow.ee_powers(e_vectors)
n_powers = backflow.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 = backflow.trunc
Summary of Methods¶
Backflow class has a following methods:
Method |
Output |
Shape |
|---|---|---|
\(\eta(r_{ij})\mathbf{r}_{ij}\) |
\((3N_e,)\) |
|
\(\mu(r_{ij})\mathbf{r}_{ij}\) |
\((3N_e,)\) |
|
\(\Phi(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{ij} + \Theta(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{iI}\) |
\((3N_e,)\) |
|
\(\nabla \eta(r_{ij})\mathbf{r}_{ij}\) |
\((3N_e, 3N_e)\) |
|
\(\nabla \mu(r_{ij})\mathbf{r}_{ij}\) |
\((3N_e, 3N_e)\) |
|
\(\nabla (\Phi(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{ij} + \Theta(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{iI})\) |
\((3N_e, 3N_e)\) |
|
\(\Delta \eta(r_{ij})\mathbf{r}_{ij}\) |
\((3N_e,)\) |
|
\(\Delta \mu(r_{ij})\mathbf{r}_{ij}\) |
\((3N_e,)\) |
|
\(\Delta (\Phi(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{ij} + \Theta(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{iI})\) |
\((3N_e,)\) |
eta-term¶
\(\eta(r_{ij})\mathbf{r}_{ij}\) consists of a complete power expansion in electron-electron distances \(r_{ij}\):
where \(\Theta\) is the Heaviside function. Electron-electron Kato cusp conditions at \(r_{ij} = 0\) satisfied by constraint for spin-like electrons only:
For certain electron coordinates, \(\eta\) term can be obtained with casino.Backflow.eta_term() method:
backflow.eta_term(e_powers, e_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
poly = polyval(r_ij, backflow.eta_parameters.T)
cutoff = np.maximum(1 - r_ij / backflow.eta_cutoff[0], 0) ** C
np.fill_diagonal(cutoff, 0)
eta = np.expand_dims(cutoff * np.choose(ee_spin_mask, poly, mode='wrap'), -1)
np.sum(-e_vectors * eta, axis=0)
mu-term¶
\(\mu(r_{iI})\mathbf{r}_{iI}\) term consists of a complete power expansion in electron-nucleus distances \(r_{iI}\):
where \(\Theta\) is the Heaviside function. The electron-nucleus Kato cusp conditions at \(r_{iI} = 0\) satisfied if
for all atoms, and in addition,
for all-electron atoms.
For certain electron coordinates, \(\mu\) term can be obtained with casino.Backflow.mu_term() method:
backflow.mu_term(n_powers, n_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
poly = polyval(r_iI, backflow.mu_parameters[0].T)
cutoff = np.maximum(1 - r_iI / backflow.mu_cutoff, 0) ** C
n_vectors * np.expand_dims(cutoff[0] * np.choose(en_spin_mask, poly, mode='wrap'), -1)
phi-term¶
\(\Phi(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{ij}\) and \(\Theta(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{iI}\) terms describe two-electron backflow displacements in terms of \(r_{ij}\) , \(r_{iI}\) , and \(r_{jI}\) and vectors \(\mathbf{r}_{ij}\) , \(\mathbf{r}_{iI}\):
where
and \(\Theta\) is the Heaviside function. To ensure electron–electron Kato cusp conditions folowing \(3(N_{\Phi I}^{ee} + N_{\Phi I}^{en} + 1)\) constraints is applied:
another \(2N_{\Phi I}^{en} + 1\) constraints from the electron-electron Kato cusp conditions:
and extra \(2N_{\Phi I}^{en} + 1\) constraints for spin-like electrons:
for all-electron atoms there are \(4(N_{\Phi I}^{ee} + N_{\Phi I}^{en})+2\) constraints on \(\phi_{klm}\)
for all-electron atoms there are \(3(N_{\Phi I}^{ee} + N_{\Phi I}^{en})+2\) constraints on \(\theta_{klm}\)
For certain electron coordinates, \(\Phi\) and \(\Theta\) terms can be obtained with casino.Backflow.phi_term() method:
backflow.phi_term(e_powers, n_powers, e_vectors, n_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval3d
r_ijI = np.tile(r_iI[0], (ne, 1))
cutoff = np.maximum(1 - r_iI/backflow.phi_cutoff, 0) ** C
phi_poly = polyval3d(r_ijI, r_ijI.T, r_ij, backflow.phi_parameters[0].T)
theta_poly = polyval3d(r_ijI, r_ijI.T, r_ij, backflow.theta_parameters[0].T)
phi = np.outer(cutoff[0], cutoff[0]) * np.choose(ee_spin_mask, phi_poly, mode='wrap')
theta = np.outer(cutoff[0], cutoff[0]) * np.choose(ee_spin_mask, theta_poly, mode='wrap')
np.fill_diagonal(theta, 0)
np.sum(-e_vectors * np.expand_dims(phi, -1) + n_vectors * np.expand_dims(theta, -1), axis=0)
eta-term gradient¶
Considering that vector gradient of spherically symmetric vector function (in 3-D space) is:
There is only two non-zero terms of \(\eta(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, \(\eta\) term gradient can be obtained with casino.Backflow.eta_term_gradient() method:
backflow.eta_term_gradient(e_powers, e_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
L = backflow.eta_cutoff
k = np.arange(backflow.eta_parameters.shape[1])
cutoff = np.maximum(1 - r_ij / backflow.eta_cutoff[0], 0) ** C
np.fill_diagonal(cutoff, 0)
poly = polyval(r_ij, backflow.eta_parameters.T)
poly_k = polyval(r_ij, (k * backflow.eta_parameters).T)
unit_e_vectors = np.nan_to_num(e_vectors/np.expand_dims(r_ij, -1))
t1 = cutoff * np.choose(ee_spin_mask, poly, mode='wrap')
t2 = cutoff * np.choose(ee_spin_mask, poly * C / (r_ij - L) + poly_k / r_ij, mode='wrap')
tt1 = np.einsum('ij,ab->aibj', np.eye(3), np.diag(np.sum(t1, axis=0)) - t1)
np.fill_diagonal(t2, 0)
tt2 = np.einsum('abi,abj,ab->aibj', e_vectors, unit_e_vectors, -t2)
(tt1 + tt2).reshape(ne*3, ne*3)
mu-term gradient¶
Considering that vector gradient of spherically symmetric vector function (in 3-D space) is:
There is only one non-zero term of \(\mu(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, \(\mu\) term gradient can be obtained with casino.Backflow.mu_term_gradient() method:
backflow.mu_term_gradient(n_powers, n_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
L = backflow.mu_cutoff
k = np.arange(backflow.mu_parameters[0].shape[1])
cutoff = np.maximum(1 - r_iI / L, 0) ** C
poly = polyval(r_iI, backflow.mu_parameters[0].T)
poly_k = polyval(r_iI, (k * backflow.mu_parameters[0]).T)
unit_n_vectors = n_vectors/np.expand_dims(r_iI, -1)
t1 = cutoff[0] * np.choose(en_spin_mask, poly, mode='wrap')
t2 = cutoff[0] * np.choose(en_spin_mask, poly * C / (r_iI - L) + poly_k / r_iI, mode='wrap')
tt1 = np.einsum('ij,ab,Ia->aibj', np.eye(3), np.eye(ne), t1)
tt2 = np.einsum('Iai,Iaj,ab,Ia->aibj', n_vectors, unit_n_vectors, np.eye(ne), t2)
(tt1 + tt2).reshape(ne*3, ne*3)
phi-term gradient¶
Considering that vector gradient of spherically symmetric vector function (in 3-D space) is:
There is only two non-zero terms of \(\Phi(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{ij}\) gradient, i.e. by \(i\)-th:
or \(j\)-th electron coordinates:
There is only two non-zero terms of \(\Theta(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{iI}\) gradient, i.e. by \(i\)-th:
or \(j\)-th electron coordinates:
where
where \(\mathbf{\hat r}_{ij}\) is the unit vector in the direction of the \(\mathbf{r}_{ij}\) and \(\mathbf{\hat r}_{iI}\) is the unit vector in the direction of the \(\mathbf{r}_{iI}\)
For certain electron coordinates, \(\phi\) term gradient can be obtained with casino.Backflow.phi_term_gradient() method:
backflow.phi_term_gradient(e_powers, n_powers, e_vectors, n_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval3d
eta-term laplacian¶
Considering that vector laplacian of spherically symmetric vector function (in 3-D space) is:
There is only two non-zero terms of \(\eta(r_{ij})\mathbf{r}_{ij}\) laplacian, i.e. by \(i\)-th or \(j\)-th electron coordinates:
For certain electron coordinates, \(\eta\) laplacian term can be obtained with casino.Backflow.eta_term_laplacian() method:
backflow.eta_term_laplacian(e_powers, e_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
mu-term laplacian¶
Considering that vector laplacian of spherically symmetric vector function (in 3-D space) is:
There is only one non-zero term of \(\mu(r_{iI})\mathbf{r}_{iI}\) laplacian, i.e. by \(i\)-th electron coordinates:
For certain electron coordinates, \(\mu\) term laplacian can be obtained with casino.Backflow.mu_term_laplacian() method:
backflow.mu_term_laplacian(n_powers, n_vectors)[1]
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval
phi-term laplacian¶
Considering that gradient of spherically symmetric function (in 3-D space) is:
and laplacian of spherically symmetric vector function (in 3-D space) is:
and \(\Phi\) term addent is a product of \(f=r_{ij}^m\), \(g=(1−r_{iI}/L_{\Phi I})^С r_{iI}^k\), \(h=(1−r_{jI}/L_{\Phi I})^С r_{jI}^l\), \(\phi_{klmI}\) and \(\mathbf{r}_{ij}\) so using:
There is only two non-zero terms of \(\Phi(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{ij}\) laplacian, i.e. by \(i\)-th:
or \(j\)-th electron coordinates:
\(\Theta\) term addent is a product of \(f=r_{ij}^m\), \(g=(1−r_{iI}/L_{\Phi I})^С r_{iI}^k\), \(h=(1−r_{jI}/L_{\Phi I})^С r_{jI}^l\), \(\theta_{klmI}\) and \(\mathbf{r}_{iI}\) so using:
There is only two non-zero terms of \(\Theta(r_{iI}, r_{jI}, r_{ij})\mathbf{r}_{iI}\) laplacian, i.e. by \(i\)-th:
or \(j\)-th electron coordinates:
where
For certain electron coordinates, \(\phi\) term laplacian can be obtained with casino.Backflow.phi_term_laplacian() method:
backflow.phi_term_laplacian(e_powers, n_powers, e_vectors, n_vectors)
this is equivalent to (continues from):
from numpy.polynomial.polynomial import polyval3d