Overlaps between R/U/GHF#
# Force the local gqcpy to be imported
import sys
sys.path.insert(0, '../../build/gqcpy/')
import gqcpy
import numpy as np
import numpy.random as rand
np.set_printoptions(precision=3, linewidth=120)
In this notebook, we will explore the multi-configurational behaviour of UHF and GHF, by projecting the UHF and GHF ONVs onto the full ONV basis expressed with respect to the RHF orbitals.
Projecting UHF#
H2#
As an introductory example, let’s start by examining H2. We’ll set up an unrestricted spin-orbital basis, where the alpha- and beta-coefficient matrices are equal. Then, we expect the projection of the UHF ONV onto the ONV basis expressed with respect to the RHF spin-orbitals to yield a linear combination consisting of exactly one 1, and the rest 0.
molecule = gqcpy.Molecule.HChain(2, 1.0)
N = molecule.numberOfElectrons()
r_spinor_basis = gqcpy.RSpinOrbitalBasis_d(molecule, "STO-3G")
K = r_spinor_basis.numberOfSpatialOrbitals()
S = r_spinor_basis.quantize(gqcpy.OverlapOperator())
sq_hamiltonian = r_spinor_basis.quantize(gqcpy.FQMolecularHamiltonian(molecule)) # 'sq' for 'second-quantized'
rhf_environment = gqcpy.RHFSCFEnvironment_d.WithCoreGuess(N, sq_hamiltonian, S)
rhf_solver = gqcpy.RHFSCFSolver_d.Plain()
rhf_objective = gqcpy.DiagonalRHFFockMatrixObjective_d(sq_hamiltonian) # use the default threshold of 1.0e-08
rhf_parameters = gqcpy.RHF_d.optimize(rhf_objective, rhf_solver, rhf_environment).groundStateParameters()
After having done the RHF calculation, we are able to construct restricted and unrestricted spin-orbital bases.
r_spinor_basis.transform(rhf_parameters.expansion())
u_spinor_basis = gqcpy.USpinOrbitalBasis_d.FromRestricted(r_spinor_basis)
We are now in the position to calculate the expansion of the UHF determinant in the full spin-resolved ONV basis expressed with respect to the RHF spin-orbitals.
uhf_determinant = gqcpy.SpinResolvedONV.UHF(2, 1, 1)
Projecting this determinant is done by constructing a LinearExpansion
from an ONV projection:
expansion = gqcpy.LinearExpansion_SpinResolved.FromONVProjection(uhf_determinant, r_spinor_basis, u_spinor_basis)
def print_function(coefficient, onv):
print("{:7.4f}".format(coefficient), onv)
expansion.forEach(print_function)
1.0000 10|10
-0.0000 10|01
-0.0000 01|10
0.0000 01|01
H4#
As a more interesting example, we’ll project the H4 UHF triplet state onto the basis of ONVs expressed with respect to the RHF spin-orbitals.
molecule = gqcpy.Molecule.HRingFromDistance(4, 1.0, 0)
N = molecule.numberOfElectrons()
N_P = N//2
N_alpha = N//2
N_beta = N//2
internuclear_repulsion_energy = gqcpy.NuclearRepulsionOperator(molecule.nuclearFramework()).value()
Using Xeno’s code, we find the following coefficient matrices for the RHF solution:
C = [[-0.27745359, -0.8505133, 0.85051937, 2.02075317],
[-0.27745362, -0.85051937, -0.8505133, -2.02075317],
[-0.27745359, 0.8505133, -0.85051937, 2.02075317],
[-0.27745362, 0.85051937, 0.8505133, -2.02075317]]
C = np.array(C)
and the UHF solution:
C_alpha = [[-1.75646828e-01, -1.20606646e-06, 1.20281173e+00, 2.03213486e+00],
[-3.78560533e-01, -1.20281173e+00, -1.20606647e-06, -2.00427438e+00],
[-1.75646828e-01, 1.20606646e-06, -1.20281173e+00, 2.03213486e+00],
[-3.78560533e-01, 1.20281173e+00, 1.20606646e-06, -2.00427438e+00]]
C_alpha = np.array(C_alpha)
C_beta = [[-3.78560533e-01, 1.20281173e+00, 1.21724557e-06, 2.00427438e+00],
[-1.75646828e-01, 1.21724558e-06, -1.20281173e+00, -2.03213486e+00],
[-3.78560533e-01, -1.20281173e+00, -1.21724558e-06, 2.00427438e+00],
[-1.75646828e-01, -1.21724558e-06, 1.20281173e+00, -2.03213486e+00]]
C_beta = np.array(C_beta)
Let’s now set up the corresponding restricted and unrestricted spin-orbital bases.
r_spinor_basis = gqcpy.RSpinOrbitalBasis_d(molecule, "STO-3G")
r_spinor_basis.transform(gqcpy.RTransformation_d(C))
u_spinor_basis = gqcpy.USpinOrbitalBasis_d(molecule, "STO-3G")
u_spinor_basis.transform(gqcpy.UTransformation_d(gqcpy.UTransformationComponent_d(C_alpha), gqcpy.UTransformationComponent_d(C_beta)))
We can now construct the UHF spin-resolved ONV:
uhf_determinant = gqcpy.SpinResolvedONV.UHF(4, 2, 2)
and project it onto the spin-resolved basis of ONVs expressed with respect to the orthonormal restricted spin-orbitals:
expansion = gqcpy.LinearExpansion_SpinResolved.FromONVProjection(uhf_determinant, r_spinor_basis, u_spinor_basis)
expansion.forEach(print_function)
-0.4987 1100|1100
0.4987 1100|1010
0.0000 1100|0110
0.0000 1100|1001
-0.0251 1100|0101
0.0251 1100|0011
-0.4987 1010|1100
0.4987 1010|1010
0.0000 1010|0110
0.0000 1010|1001
-0.0251 1010|0101
0.0251 1010|0011
-0.0000 0110|1100
0.0000 0110|1010
0.0000 0110|0110
0.0000 0110|1001
-0.0000 0110|0101
0.0000 0110|0011
0.0000 1001|1100
-0.0000 1001|1010
-0.0000 1001|0110
-0.0000 1001|1001
0.0000 1001|0101
-0.0000 1001|0011
0.0251 0101|1100
-0.0251 0101|1010
-0.0000 0101|0110
-0.0000 0101|1001
0.0013 0101|0101
-0.0013 0101|0011
0.0251 0011|1100
-0.0251 0011|1010
-0.0000 0011|0110
-0.0000 0011|1001
0.0013 0011|0101
-0.0013 0011|0011
GHF on UHF projection#
Let us start by performing an Unrestricted Hartree-Fock and a Generalized Hartree-Fock calculation on H3. Both should yield different results as H3 posseses a fully spin-symmetry broken solution.
molecule = gqcpy.Molecule.HRingFromDistance(3, 1.889, 0) # create a neutral molecule
N = molecule.numberOfElectrons()
N_alpha = N//2 + 1
N_beta = N//2
spinor_basis = gqcpy.USpinOrbitalBasis_d(molecule, "STO-3G")
S = spinor_basis.quantize(gqcpy.OverlapOperator())
sq_hamiltonian = spinor_basis.quantize(gqcpy.FQMolecularHamiltonian(molecule)) # 'sq' for 'second-quantized'
UHF_environment = gqcpy.UHFSCFEnvironment_d.WithCoreGuess(N_alpha, N_beta, sq_hamiltonian, S)
UHF_solver = gqcpy.UHFSCFSolver_d.Plain(threshold=1.0e-04, maximum_number_of_iterations=1000) # the system is not converging very rapidly
uqc_structure = gqcpy.UHF_d.optimize(UHF_solver, UHF_environment)
print(uqc_structure.groundStateEnergy() + gqcpy.NuclearRepulsionOperator(molecule.nuclearFramework()).value())
uhf_parameters = uqc_structure.groundStateParameters()
-1.3358471555881106
We now have a UHF solution with the wanted parameters saved in a variable. We can now do the same for GHF.
basis = gqcpy.GSpinorBasis_d(molecule, "STO-3G")
gS = basis.quantize(gqcpy.OverlapOperator())
gsq_hamiltonian = basis.quantize(gqcpy.FQMolecularHamiltonian(molecule))
# Generate a random guess in order to find a true GHF solution.
rand.seed(2)
K = spinor_basis.numberOfSpinors()
random_matrix = np.random.rand(K, K)
random_matrix_transpose = random_matrix.T
symmetric_random_matrix = random_matrix + random_matrix_transpose
_, guess = np.linalg.eigh(symmetric_random_matrix)
GHF_environment = gqcpy.GHFSCFEnvironment_d(N, gsq_hamiltonian, gS, gqcpy.GTransformation_d(guess))
GHF_solver = gqcpy.GHFSCFSolver_d.Plain(1.0e-08, 4000)
gqc_structure = gqcpy.GHF_d.optimize(GHF_solver, GHF_environment)
print(gqc_structure.groundStateEnergy() + gqcpy.NuclearRepulsionOperator(molecule.nuclearFramework()).value())
ghf_parameters = gqc_structure.groundStateParameters()
-1.3403026284286064
In order to create a projection, we will need to transform the respective spino(bital) bases to MO basis, as well as transform the UHF basis to a spinor representation.
basis.transform(ghf_parameters.expansion())
spinor_basis.transform(uhf_parameters.expansion())
spinor_basis = gqcpy.GSpinorBasis_d.FromUnrestricted(spinor_basis)
The final ingredient we need is a representation of the GHF determinant.
GHF_determinant = gqcpy.SpinUnresolvedONV.GHF(K, N, ghf_parameters.orbitalEnergies())
We can now create the projection and print it.
expansion = gqcpy.LinearExpansion_SpinUnresolved.FromONVProjection(GHF_determinant, spinor_basis, basis)
expansion.forEach(print_function)
0.0334 111000
-0.2384 110100
-0.6027 101100
-0.0989 011100
0.0976 110010
0.2098 101010
0.0339 011010
0.2638 100110
0.0469 010110
0.0090 001110
0.0780 110001
0.1133 101001
0.0276 011001
0.6000 100101
0.0341 010101
-0.1627 001101
-0.1593 100011
0.0014 010011
0.0583 001011
0.0802 000111