Source code for vayesta.dmet.sdp_sc
import numpy as np
from vayesta.dmet import cvxpy as cp
[docs]def perform_SDP_fit(nelec, fock, impurity_projectors, target_rdms, ovlp, log):
"""Given all required information about the system, generate the correlation potential reproducing the local DM
via a semidefinite program, as described in doi.org/10.1103/PhysRevB.102.085123. Initially use SCS solver, though
others (eg. MOSEK) could be used if this runs into issues.
Note that initially we will only have a single impurity in each symmetry class, until the fragmentation routines are
updated to include symmetry.
Parameters
----------
nelec: integer
Number of electrons in the system.
fock: np.array
Fock matrix of the full system, in the spatial orbital basis.
impurity_projectors: list of list of np.array
For each class of symmetry-equivalent impurities, projection from the full spatial orbital basis to the impurity
set.
target_rdms: list of np.array
Impurity-local one-body density matrices for each class of impurities.
ovlp: np.array
Overlap matrix of the AOs, so we can implicitly transform into the PAO basis.
"""
# print("Target RDMs:")
# print(target_rdms)
# print([x.trace() for x in target_rdms])
# First calculate the number of different symmetry-eqivalent orbital sets for each class of impurity (this is the
# symmetry factor, elsewhere in the code).
nimp = [x[0].shape[1] for x in impurity_projectors]
nsym = [len(x) for x in impurity_projectors]
log.info("Number of site fragments: %s" % nimp)
log.info("Number of symmetry-related fragments: %s" % nsym)
z = cp.Variable(fock.shape, PSD=True)
us = [cp.Variable((i, i), symmetric=True) for i in nimp]
alpha = cp.Variable(1)
# We have the coefficients of the impurity orbitals in the nonorthogonal AO basis, C.
# The coefficients of the AOs in the impurity orbitals is then equal to S @ C.T
AO_in_imps = [[aproj.T @ ovlp for aproj in curr_proj] for curr_proj in impurity_projectors]
# Check this is correctly orthogonal.
# print([x[0].T @ y[0].T for (x,y) in zip(impurity_projectors, AO_in_imps)])
# Want to construct the full correlation potential, in the AO basis.
utot = sum(
[cp.matmul(aproj.T, cp.matmul(us[i], aproj)) for i, curr_proj in enumerate(AO_in_imps) for aproj in curr_proj]
)
# import scipy
# c_pao = np.linalg.inv(scipy.linalg.sqrtm(ovlp))
# First constraint in AO basis required for solution, second enforces tracelessness of Vcorr.
constraints = [
fock + utot + z - alpha * ovlp >> 0,
sum([cp.trace(x) for x in us]) == 0,
]
# Object function computed implicitly in PAO basis; partially use the fragment basis thanks to the invariance of the
# trace to orthogonal rotations (if you're using nonorthogonal fragment orbitals this will need a bit more thought).
objective = cp.Minimize(
sum([cp.trace(rdm1 @ ux) * ni for ni, rdm1, ux in zip(nsym, target_rdms, us)])
- alpha * nelec
+ cp.trace(cp.matmul(z, np.linalg.inv(ovlp)))
)
prob = cp.Problem(objective, constraints)
solval = prob.solve(solver=cp.SCS, eps=1e-8)
msg = "SDP fitting completed. Status= %s" % prob.status
if not prob.status in [cp.OPTIMAL]: # , cp.OPTIMAL_INACCURATE]:
log.warning(msg)
else:
log.info(msg)
# Report the result of the optimisation.
return utot.value