Slater determinant¶
The Slater determinant component of the wavefunction is implemented in the casino.Slater class.
This class provides methods to compute the value, gradient, Laplacian, Hessian, and Tressian of a multi-determinant Slater wavefunction.
It must be initialized from the configuration files:
from casino.readers import CasinoConfig
from casino.slater import Slater
config_path = <path to a directory containing input file>
config = CasinoConfig(config_path)
config.read()
slater = Slater(config, cusp=None)
Summary of Methods¶
Slater class has a following methods:
Method |
Output |
Shape |
|---|---|---|
\(A^\uparrow, A^\downarrow\) |
\((N^\uparrow_e, MO^\uparrow), (N^\downarrow_e, MO^\downarrow)\) |
|
\(G^\uparrow, G^\downarrow\) |
\((N^\uparrow_e, MO^\uparrow, 3), (N^\downarrow_e, MO^\downarrow, 3)\) |
|
\(L^\uparrow, L^\downarrow\) |
\((N^\uparrow_e, MO^\uparrow), (N^\downarrow_e, MO^\downarrow)\) |
|
\(H^\uparrow, H^\downarrow\) |
\((N^\uparrow_e, MO^\uparrow, 3, 3), (N^\downarrow_e, MO^\downarrow, 3, 3)\) |
|
\(T^\uparrow, T^\downarrow\) |
\((N^\uparrow_e, MO^\uparrow, 3, 3, 3), (N^\downarrow_e, MO^\downarrow, 3, 3, 3)\) |
|
\(\Psi(r)\) |
\(scalar\) |
|
\(\nabla \Psi(r)/\Psi(r)\) |
\((3N_e,)\) |
|
\(\Delta \Psi(r)/\Psi(r)\) |
\(scalar\) |
|
\(\nabla^2 \Psi(r)/\Psi(r)\) |
\((3N_e, 3N_e)\) |
|
\(\nabla^3 \Psi(r)/\Psi(r)\) |
\((3N_e, 3N_e, 3N_e)\) |
value matrix¶
In quantum chemistry, molecular orbitals (MOs) are normally expanded in a set of atom-centered basis functions, or localized atomic orbitals (AOs):
where \(\mathbf{r}=\{r_{1}...r_{N}\}\) are the coordinates of the N spin-up and spin-down electrons, \(\mathbf{R}_\alpha\) denotes the atomic position center of basis function \(\chi_\alpha\), and the expansion coefficients \(c_{\alpha p}\) are known as molecular orbital (MO) coefficients, also to avoid overflows and underflows a normalization coefficient is multiplied:
In multi-determinant case \(p\) indexes should includes an occupied plus virtual MOs to span for excited states. Therefore it is reasonable to
define electrons \(\times\) (occupied + virtual MOs) matrix \(\mathcal{A}\).
For a system described by a spin-independent Hamiltonian, the spatial and spin degrees of freedom are separable and we can split \(\mathcal{A}_{ip}\)
into two: \(\mathcal{A}^\uparrow_{ip}\) for spin-up and \(\mathcal{A}^\downarrow_{ip}\) for spin-down electrons.
For certain electron coordinates, the values of these matrices can be obtained with casino.Slater.value_matrix() method:
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
n_vectors = np.expand_dims(r_e, 0) - np.expand_dims(atom_positions, 1)
A_up, A_down = slater.value_matrix(n_vectors)
the inverse matrix will be needed to calculate the gradient, laplacian, hesian and tressian:
inv_A_up = np.linalg.inv(A_up)
inv_A_down = np.linalg.inv(A_down)
gradient matrix¶
Consider the gradient operator for \(i\)-th electron:
It is easy to check that:
hence all non-zero values compose the matrix of vectors: \((x, y, z)\) indexed by \(a \in (x, y, z)\):
In multi-determinant case \(p\) indexes should includes an occupied plus virtual MOs to span for excited states. Therefore it is reasonable to
define electrons \(\times\) (occupied + virtual MOs) matrix \(\mathcal{G}_{ip}\).
For a system described by a spin-independent Hamiltonian, the spatial and spin degrees of freedom are separable and we can split \(\mathcal{G}_{ip}\)
into two: \(\mathcal{G}^\uparrow_{ip}\) for spin-up and \(\mathcal{G}^\downarrow_{ip}\) for spin-down electrons.
For certain electron coordinates, the values of these matrices can be obtained with casino.Slater.gradient_matrix() method:
G_up, G_down = slater.gradient_matrix(n_vectors)
laplacian matrix¶
Consider the laplacian operator for \(i\)-th electron:
It is easy to check that:
hence all non-zero values compose the matrix of scalars:
In multi-determinant case \(p\) indexes should includes an occupied plus virtual MOs to span for excited states. Therefore it is reasonable to
define electrons \(\times\) (occupied + virtual MOs) matrix \(\mathcal{L}_{ip}\).
For a system described by a spin-independent Hamiltonian, the spatial and spin degrees of freedom are separable and we can split \(\mathcal{L}_{ip}\)
into two: \(\mathcal{L}^\uparrow_{ip}\) for spin-up and \(\mathcal{L}^\downarrow_{ip}\) for spin-down electrons.
For certain electron coordinates, the values of these matrices can be obtained with casino.Slater.laplacian_matrix() method:
L_up, L_down = slater.laplacian_matrix(n_vectors)
hessian matrix¶
Consider the hessian operator for \(i\)-th electron:
It is easy to check that:
hence all non-zero values compose the matrix of hessians: \((x, y, z) \otimes (x, y, z)\) indexed by \(a,b \in (x, y, z)\):
In multi-determinant case \(p\) indexes should includes an occupied plus virtual MOs to span for excited states. Therefore it is reasonable to
define electrons \(\times\) (occupied + virtual MOs) matrix \(\mathcal{H}_{ip}\).
For a system described by a spin-independent Hamiltonian, the spatial and spin degrees of freedom are separable and we can split \(\mathcal{H}_{ip}\)
into two: \(\mathcal{H}^\uparrow_{ip}\) for spin-up and \(\mathcal{H}^\downarrow_{ip}\) for spin-down electrons.
For certain electron coordinates, the values of these matrices can be obtained with casino.Slater.hessian_matrix() method:
H_up, H_down = slater.hessian_matrix(n_vectors)
tressian matrix¶
Consider the tressian operator for \(i\)-th electron:
It is easy to check that:
hence all non-zero values compose the matrix of tressians: \((x, y, z) \otimes (x, y, z) \otimes (x, y, z)\) indexed by \(a,b,c \in (x, y, z)\):
In multi-determinant case \(p\) indexes should includes an occupied plus virtual MOs to span for excited states. Therefore it is reasonable to
define electrons \(\times\) (occupied + virtual MOs) matrix \(\mathcal{T}_{ip}\).
For a system described by a spin-independent Hamiltonian, the spatial and spin degrees of freedom are separable and we can split \(\mathcal{T}_{ip}\)
into two: \(\mathcal{T}^\uparrow_{ip}\) for spin-up and \(\mathcal{T}^\downarrow_{ip}\) for spin-down electrons.
For certain electron coordinates, the values of these matrices can be obtained with casino.Slater.tressian_matrix() method:
T_up, T_down = slater.tressian_matrix(n_vectors)
value¶
Consider contribution of single Slater determinant:
we can get the value of multideterminant wavefunction:
and \(\mathbf{r}=\{r_{1}...r_{N}\}\) are the coordinates of the N spin-up and spin-down electrons.
For certain electron coordinates, the value can be obtained with casino.Slater.value() method:
value = slater.value(n_vectors)
gradient¶
Consider Slater determinant gradien by \(i\)-th electron coordinates:
to express the trace through sum using equality:
notice that the \(\nabla_{e_i} A\) has the only one non-zero \(row_i(\nabla_{e_i} A) = row_i(G)\):
expand gradient vector over \(i\):
and get gradient of multideterminant wavefunction:
where \(\mathbf{r}=\{r_{1}...r_{N}\}\) are the coordinates of the N spin-up and spin-down electrons
For certain electron coordinates, the gradient vector can be obtained with casino.Slater.gradient() method:
slater.gradient(n_vectors)
this is equivalent to (continues from):
G_up, G_down = slater.gradient_matrix(n_vectors)
tr_grad_u = np.einsum('ij,jia->ia', inv_A_up, G_up).reshape(neu * 3)
tr_grad_d = np.einsum('ij,jia->ia', inv_A_down, G_down).reshape(ned * 3)
np.concatenate((tr_grad_u, tr_grad_d))
laplacian¶
Consider Slater determinant laplacian by \(i\)-th electron coordinates:
to express the trace through sum using equality:
notice that the \(\Delta_{e_i} A\) has the only one non-zero \(row_i(\Delta_{e_i} A) = row_i(L)\):
sum laplacian over \(i\):
and get laplacian of multideterminant wavefunction:
where \(\mathbf{r}=\{r_{1}...r_{N}\}\) are the coordinates of the N spin-up and spin-down electrons
For certain electron coordinates, the laplacian can be obtained with casino.Slater.laplacian() method:
slater.laplacian(n_vectors)
this is equivalent to (continues from):
L_up, L_down = slater.laplacian_matrix(n_vectors)
lap_up = np.einsum('ij,ji', inv_A_up, L_up)
lap_down = np.einsum('ij,ji', inv_A_down, L_down)
lap_up + lap_down
hessian¶
Consider Slater determinant hessian by \(i\)-th and \(j\)-th electrons coordinates:
to express the trace through sum using equality:
notice that the \(\nabla_{e_i} A\) has the only one non-zero \(row_i(\nabla_{e_i} A) = row_i(G)\) and the \(\nabla_{e_i} \nabla_{e_i} A\) has only non-zero \(row_i(\nabla_{e_i} \nabla_{e_i} A) = row_i(H)\):
expand gradient vectors and hessian tensor over \(i\) and \(j\) (with Kronecker delta \(\delta_{ij}\)):
we can get hessian of multideterminant wavefunction:
where \(\mathbf{r}=\{r_{1}...r_{N}\}\) are the coordinates of the N spin-up and spin-down electrons
For certain electron coordinates, the hessian matrix can be obtained with casino.Slater.hessian() method:
slater.hessian(n_vectors)[0]
this is equivalent to (continues from):
G_up, G_down = slater.gradient_matrix(n_vectors)
tr_grad_u = np.einsum('ij,jia->ia', inv_A_up, G_up).reshape(neu * 3)
tr_grad_d = np.einsum('ij,jib->ib', inv_A_down, G_down).reshape(ned * 3)
mul_grad_u = np.einsum('ij,jka->ika', inv_A_up, G_up)
mul_grad_d = np.einsum('ij,jkb->ikb', inv_A_down, G_down)
grad = np.concatenate((tr_grad_u, tr_grad_d))
H_up, H_down = slater.hessian_matrix(n_vectors)
tr_hess_u = np.einsum('ij,jiab->iab', inv_A_up, H_up)
tr_hess_d = np.einsum('ij,jiab->iab', inv_A_down, H_down)
hess_u = np.einsum('ij,iab->iajb', np.eye(neu), tr_hess_u)
hess_d = np.einsum('ij,iab->iajb', np.eye(ned), tr_hess_d)
hess_u -= np.einsum('ijb,jia->iajb', mul_grad_u, mul_grad_u)
hess_d -= np.einsum('ijb,jia->iajb', mul_grad_d, mul_grad_d)
hess = np.zeros((ne * 3, ne * 3))
hess[:neu * 3, :neu * 3] = hess_u.reshape(neu * 3, neu * 3)
hess[neu * 3:, neu * 3:] = hess_d.reshape(ned * 3, ned * 3)
hess += np.outer(grad, grad)
tressian¶
Consider Slater determinant tressian by \(i\)-th, \(j\)-th and \(k\)-th electrons coordinates:
noting that:
to express the trace through sum using equalities:
notice that the \(\nabla_i A\) has only non-zero \(row_i(\nabla_i A) = row_i(G)\) and the \(\nabla_i \nabla_i A\) has only non-zero \(row_i(\nabla_i \nabla_i A) = row_i(H)\) and the \(\nabla_i \nabla_i \nabla_i A\) has only non-zero \(row_i(\nabla_i \nabla_i \nabla_i A) = row_i(T)\) and expand gradient vectors, hessian and tressian tensors over \(i\), \(j\), \(k\):
we can get tressian of multideterminant wavefunction:
where \(\mathbf{r}=\{r_{1}...r_{N}\}\) are the coordinates of the N spin-up and spin-down electrons
For certain electron coordinates, the tressian metrix can be obtained with casino.Slater.tressian() method:
slater.tressian(n_vectors)[0]
this is equivalent to (continues from):
G_up, G_down = slater.gradient_matrix(n_vectors)
tr_grad_u = np.einsum('ij,jia->ia', inv_A_up, G_up).reshape(neu * 3)
tr_grad_d = np.einsum('ij,jib->ib', inv_A_down, G_down).reshape(ned * 3)
grad = np.concatenate((tr_grad_u, tr_grad_d))
H_up, H_down = slater.hessian_matrix(n_vectors)
tr_hess_u = np.einsum('ij,jiab->iab', inv_A_up, H_up)
tr_hess_d = np.einsum('ij,jiab->iab', inv_A_down, H_down)
mul_grad_u = np.einsum('ik,kja->ija', inv_A_up, G_up)
mul_grad_d = np.einsum('ik,kjb->ijb', inv_A_down, G_down)
hess_u = np.einsum('ij,iab->iajb', np.eye(neu), tr_hess_u)
hess_d = np.einsum('ij,iab->iajb', np.eye(ned), tr_hess_d)
hess_u -= np.einsum('ijb,jia->iajb', mul_grad_u, mul_grad_u)
hess_d -= np.einsum('ijb,jia->iajb', mul_grad_d, mul_grad_d)
hess = np.zeros((ne * 3, ne * 3))
hess[:neu * 3, :neu * 3] = hess_u.reshape(neu * 3, neu * 3)
hess[neu * 3:, neu * 3:] = hess_d.reshape(ned * 3, ned * 3)
hess += np.outer(grad, grad)
T_up, T_down = slater.tressian_matrix(n_vectors)
tr_tress_u = np.einsum('ij,jiabc->iabc', inv_A_up, T_up)
tr_tress_d = np.einsum('ij,jiabc->iabc', inv_A_down, T_down)
mul_hess_u = np.einsum('ik,kjab->iajb', inv_A_up, H_up)
mul_hess_d = np.einsum('ik,kjab->iajb', inv_A_down, H_down)
tress_u = np.einsum('ij,jk,iabc->iajbkc', np.eye(neu), np.eye(neu), tr_tress_u)
tress_d = np.einsum('ij,jk,iabc->iajbkc', np.eye(ned), np.eye(ned), tr_tress_d)
tress_u -= np.einsum('ij,kajb,jkc->iajbkc', np.eye(neu), mul_hess_u, mul_grad_u)
tress_u -= np.einsum('ki,jaic,ijb->iajbkc', np.eye(neu), mul_hess_u, mul_grad_u)
tress_u -= np.einsum('jk,ibkc,kia->iajbkc', np.eye(neu), mul_hess_u, mul_grad_u)
tress_d -= np.einsum('ij,kajb,jkc->iajbkc', np.eye(ned), mul_hess_d, mul_grad_d)
tress_d -= np.einsum('ki,jaic,ijb->iajbkc', np.eye(ned), mul_hess_d, mul_grad_d)
tress_d -= np.einsum('jk,ibkc,kia->iajbkc', np.eye(ned), mul_hess_d, mul_grad_d)
tress_u += 2 * np.einsum('jia,kjb,ikc->iajbkc', mul_grad_u, mul_grad_u, mul_grad_u)
tress_d += 2 * np.einsum('jia,kjb,ikc->iajbkc', mul_grad_d, mul_grad_d, mul_grad_d)
# tress_u += np.einsum('kia,ijb,jkc->iajbkc', mul_grad_u, mul_grad_u, mul_grad_u)
# tress_d += np.einsum('kia,ijb,jkc->iajbkc', mul_grad_d, mul_grad_d, mul_grad_d)
tress = np.zeros((ne * 3, ne * 3, ne * 3))
tress[:neu * 3, :neu * 3, :neu * 3] = tress_u.reshape(neu * 3, neu * 3, neu * 3)
tress[neu * 3:, neu * 3:, neu * 3:] = tress_d.reshape(ned * 3, ned * 3, ned * 3)
tress += (
np.einsum('i,jk->ijk', grad, hess) +
np.einsum('k,ij->ijk', grad, hess) +
np.einsum('j,ki->ijk', grad, hess) -
2 * np.einsum('i,j,k->ijk', grad, grad, grad)
)