Exemple #1
0
def test_single_opt():
    from pyqmc.accumulators import EnergyAccumulator
    from pyscf import lib, gto, scf
    
    import pandas as pd
    from pyqmc.multiplywf import MultiplyWF
    from pyqmc.jastrow import Jastrow2B
    from pyqmc.func3d import GaussianFunction
    from pyqmc.slater import PySCFSlaterRHF
    from pyqmc.multiplywf import MultiplyWF
    from pyqmc.jastrow import Jastrow2B
    
    from pyqmc.mc import initial_guess,vmc
    
    mol = gto.M(atom='Li 0. 0. 0.; Li 0. 0. 1.5', basis='bfd_vtz',ecp='bfd',unit='bohr',verbose=5)
    mf = scf.RHF(mol).run()
    nconf=1000
    nsteps=10

    coords = initial_guess(mol,nconf)
    wf=MultiplyWF(PySCFSlaterRHF(mol,mf),Jastrow2B(mol,
        basis=[GaussianFunction(1.0),GaussianFunction(2.0)]))
    
    vmc(wf,coords,nsteps=nsteps)

    opt_var,wf=optvariance(EnergyAccumulator(mol),wf,coords,['wf2coeff'])
    print('Final variance:',opt_var)
Exemple #2
0
def test_vmc():
    """
    Test that a VMC calculation of a Slater determinant matches Hartree-Fock within error bars.
    """
    nconf = 1000
    mol = gto.M(atom="Li 0. 0. 0.; Li 0. 0. 1.5",
                basis="cc-pvtz",
                unit="bohr",
                verbose=1)

    mf_rhf = scf.RHF(mol).run()
    mf_uhf = scf.UHF(mol).run()
    nsteps = 300
    warmup = 30

    for wf, mf in [
        (PySCFSlater(mol, mf_rhf), mf_rhf),
        (PySCFSlater(mol, mf_uhf), mf_uhf),
    ]:

        # Without blocks
        coords = initial_guess(mol, nconf)
        df, coords = vmc(
            wf,
            coords,
            nsteps=nsteps,
            accumulators={"energy": EnergyAccumulator(mol)},
            verbose=True,
        )

        df = pd.DataFrame(df)
        df = reblock(df["energytotal"][warmup:], 20)
        en = df.mean()
        err = df.sem()
        assert en - mf.energy_tot(
        ) < 5 * err, "pyscf {0}, vmc {1}, err {2}".format(
            mf.energy_tot(), en, err)

        # With blocks
        coords = initial_guess(mol, nconf)
        df, coords = vmc(
            wf,
            coords,
            nblocks=int(nsteps / 30),
            nsteps_per_block=30,
            accumulators={"energy": EnergyAccumulator(mol)},
        )

        df = pd.DataFrame(df)["energytotal"][int(warmup / 30):]
        en = df.mean()
        err = df.sem()
        assert en - mf.energy_tot(
        ) < 5 * err, "pyscf {0}, vmc {1}, err {2}".format(
            mf.energy_tot(), en, err)
Exemple #3
0
def test_vmc():
    """
    Test that a VMC calculation of a Slater determinant matches Hartree-Fock within error bars.
    """
    nconf = 5000
    mol = gto.M(atom="Li 0. 0. 0.; Li 0. 0. 1.5",
                basis="cc-pvtz",
                unit="bohr",
                verbose=1)

    mf_rhf = scf.RHF(mol).run()
    mf_uhf = scf.UHF(mol).run()
    nsteps = 100
    warmup = 30

    for wf, mf in [
        (PySCFSlaterUHF(mol, mf_rhf), mf_rhf),
        (PySCFSlaterUHF(mol, mf_uhf), mf_uhf),
    ]:

        coords = initial_guess(mol, nconf)
        df, coords = vmc(wf,
                         coords,
                         nsteps=nsteps,
                         accumulators={"energy": EnergyAccumulator(mol)})

        df = pd.DataFrame(df)
        en = np.mean(df["energytotal"][warmup:])
        err = np.std(df["energytotal"][warmup:]) / np.sqrt(nsteps - warmup)
        assert en - mf.energy_tot() < 10 * err
Exemple #4
0
def test_accumulator():
    """ Tests that the accumulator gets inserted into the data output correctly.
    """
    import pandas as pd
    from pyqmc.mc import vmc, initial_guess
    from pyscf import gto, scf
    from pyqmc.energy import energy
    from pyqmc.slateruhf import PySCFSlaterUHF
    from pyqmc.accumulators import EnergyAccumulator

    mol = gto.M(atom="Li 0. 0. 0.; Li 0. 0. 1.5",
                basis="cc-pvtz",
                unit="bohr",
                verbose=5)
    mf = scf.RHF(mol).run()
    nconf = 5000
    wf = PySCFSlaterUHF(mol, mf)
    coords = initial_guess(mol, nconf)

    df, coords = vmc(wf,
                     coords,
                     nsteps=30,
                     accumulators={"energy": EnergyAccumulator(mol)})
    df = pd.DataFrame(df)
    eaccum = EnergyAccumulator(mol)
    eaccum_energy = eaccum(coords, wf)
    df = pd.DataFrame(df)
    print(df["energytotal"][29] == np.average(eaccum_energy["total"]))

    assert df["energytotal"][29] == np.average(eaccum_energy["total"])
Exemple #5
0
def test():
    """ Ensure that DMC obtains the exact result for a hydrogen atom """
    from pyscf import lib, gto, scf
    from pyqmc.slater import PySCFSlater
    from pyqmc.jastrowspin import JastrowSpin
    from pyqmc.dmc import limdrift, rundmc
    from pyqmc.mc import vmc
    from pyqmc.accumulators import EnergyAccumulator
    from pyqmc.func3d import CutoffCuspFunction
    from pyqmc.multiplywf import MultiplyWF
    from pyqmc.coord import OpenConfigs
    import pandas as pd

    mol = gto.M(atom="H 0. 0. 0.", basis="sto-3g", unit="bohr", spin=1)
    mf = scf.UHF(mol).run()
    nconf = 1000
    configs = OpenConfigs(np.random.randn(nconf, 1, 3))
    wf1 = PySCFSlater(mol, mf)
    wf = wf1
    wf2 = JastrowSpin(mol, a_basis=[CutoffCuspFunction(5, 0.2)], b_basis=[])
    wf2.parameters["acoeff"] = np.asarray([[[1.0, 0]]])
    wf = MultiplyWF(wf1, wf2)

    dfvmc, configs_ = vmc(
        wf, configs, nsteps=50, accumulators={"energy": EnergyAccumulator(mol)}
    )
    dfvmc = pd.DataFrame(dfvmc)
    print(
        "vmc energy",
        np.mean(dfvmc["energytotal"]),
        np.std(dfvmc["energytotal"]) / np.sqrt(len(dfvmc)),
    )

    warmup = 200
    branchtime = 5
    dfdmc, configs_, weights_ = rundmc(
        wf,
        configs,
        nsteps=4000 + warmup * branchtime,
        branchtime=branchtime,
        accumulators={"energy": EnergyAccumulator(mol)},
        ekey=("energy", "total"),
        tstep=0.01,
        drift_limiter=limdrift,
        verbose=True,
    )

    dfdmc = pd.DataFrame(dfdmc)
    dfdmc.sort_values("step", inplace=True)

    dfprod = dfdmc[dfdmc.step >= warmup]

    rb_summary = reblock.reblock_summary(dfprod[["energytotal", "energyei"]], 20)
    print(rb_summary)
    energy, err = [rb_summary[v]["energytotal"] for v in ("mean", "standard error")]
    assert (
        np.abs(energy + 0.5) < 5 * err
    ), "energy not within {0} of -0.5: energy {1}".format(5 * err, np.mean(energy))
Exemple #6
0
def test():

    mol = gto.M(atom="Li 0. 0. 0.; Li 0. 0. 1.5",
                basis="sto-3g",
                unit="bohr",
                verbose=0)
    mf = scf.RHF(mol).run()

    # Lowdin orthogonalized AO basis.
    lowdin = lo.orth_ao(mol, "lowdin")

    # MOs in the Lowdin basis.
    mo = solve(lowdin, mf.mo_coeff)

    # make AO to localized orbital coefficients.
    mfobdm = mf.make_rdm1(mo, mf.mo_occ)

    ### Test OBDM calculation.
    nconf = 500
    nsteps = 400
    obdm_steps = 4
    warmup = 15
    wf = PySCFSlaterUHF(mol, mf)
    configs = initial_guess(mol, nconf)
    energy = EnergyAccumulator(mol)
    obdm = OBDMAccumulator(mol=mol, orb_coeff=lowdin, nsweeps=1)
    obdm_up = OBDMAccumulator(mol=mol, orb_coeff=lowdin, nsweeps=1, spin=0)
    obdm_down = OBDMAccumulator(mol=mol, orb_coeff=lowdin, nsweeps=1, spin=1)

    df, coords = vmc(
        wf,
        configs,
        nsteps=nsteps,
        accumulators={
            "energy": energy,
            "obdm": obdm,
            "obdm_up": obdm_up,
            "obdm_down": obdm_down,
        },
    )
    df = DataFrame(df)

    obdm_est = {}
    for k in ["obdm", "obdm_up", "obdm_down"]:
        avg_norm = np.array(df.loc[warmup:,
                                   k + "norm"].values.tolist()).mean(axis=0)
        avg_obdm = np.array(df.loc[warmup:,
                                   k + "value"].values.tolist()).mean(axis=0)
        obdm_est[k] = normalize_obdm(avg_obdm, avg_norm)

    print("Average OBDM(orb,orb)", obdm_est["obdm"].diagonal().round(3))
    print("mf obdm", mfobdm.diagonal().round(3))
    assert np.max(np.abs(obdm_est["obdm"] - mfobdm)) < 0.05
    print(obdm_est["obdm_up"].diagonal().round(3))
    print(obdm_est["obdm_down"].diagonal().round(3))
    assert np.mean(
        np.abs(obdm_est["obdm_up"] + obdm_est["obdm_down"] - mfobdm)) < 0.05
Exemple #7
0
def test():
    """ Ensure that DMC obtains the exact result for a hydrogen atom """
    from pyscf import lib, gto, scf
    from pyqmc.slateruhf import PySCFSlaterUHF
    from pyqmc.jastrowspin import JastrowSpin
    from pyqmc.dmc import limdrift, dmc
    from pyqmc.mc import vmc
    from pyqmc.accumulators import EnergyAccumulator
    from pyqmc.func3d import ExpCuspFunction
    from pyqmc.multiplywf import MultiplyWF
    import pandas as pd

    mol = gto.M(atom='H 0. 0. 0.', basis='sto-3g', unit='bohr', spin=1)
    mf = scf.UHF(mol).run()
    nconf = 1000
    configs = np.random.randn(nconf, 1, 3)
    wf1 = PySCFSlaterUHF(mol, mf)
    wf = wf1
    wf2 = JastrowSpin(mol, a_basis=[ExpCuspFunction(5, .2)], b_basis=[])
    wf2.parameters['acoeff'] = np.asarray([[-1.0, 0]])
    wf = MultiplyWF(wf1, wf2)

    dfvmc, configs_ = vmc(wf,
                          configs,
                          nsteps=50,
                          accumulators={'energy': EnergyAccumulator(mol)})
    dfvmc = pd.DataFrame(dfvmc)
    print('vmc energy', np.mean(dfvmc['energytotal']),
          np.std(dfvmc['energytotal']) / np.sqrt(len(dfvmc)))

    dfdmc, configs_, weights_ = dmc(
        wf,
        configs,
        nsteps=5000,
        branchtime=5,
        accumulators={'energy': EnergyAccumulator(mol)},
        ekey=('energy', 'total'),
        tstep=0.01,
        drift_limiter=limdrift,
        verbose=True)

    dfdmc = pd.DataFrame(dfdmc)
    dfdmc.sort_values('step', inplace=True)

    warmup = 200
    dfprod = dfdmc[dfdmc.step > warmup]

    reblock = pyblock.reblock(dfprod[['energytotal', 'energyei']])
    print(reblock[1])
    dfoptimal = reblock[1][reblock[1][('energytotal', 'optimal block')] != '']
    energy = dfoptimal[('energytotal', 'mean')].values[0]
    err = dfoptimal[('energytotal', 'standard error')].values[0]
    print("energy", energy, "+/-", err)
    assert np.abs(
        energy +
        0.5) < 5 * err, "energy not within {0} of -0.5: energy {1}".format(
            5 * err, np.mean(energy))
Exemple #8
0
def test_updateinternals(wf, configs):
    """
    :parameter wf: a wave function object to be tested
    :parameter configs: electron positions
    :type configs: (nconf, nelec, 3) array
    :returns: max abs errors
    :rtype: dictionary

    """
    import pyqmc.mc as mc

    nconf, ne, ndim = configs.configs.shape
    delta = 1e-2

    iscomplex = 1j if wf.iscomplex else 1
    updatevstest = np.zeros((ne, nconf)) * iscomplex
    recomputevstest = np.zeros((ne, nconf)) * iscomplex
    recomputevsupdate = np.zeros((ne, nconf)) * iscomplex
    wfcopy = copy.copy(wf)
    val1 = wf.recompute(configs)
    for e in range(ne):
        # val1 = wf.recompute(configs)
        epos = configs.make_irreducible(e, configs.configs[:, e, :] + delta)
        ratio = wf.testvalue(e, epos)
        wf.updateinternals(e, epos, configs)
        update = wf.value()
        configs.move(e, epos, [True] * nconf)
        recompute = wfcopy.recompute(configs)
        updatevstest[
            e, :] = update[0] / val1[0] * np.exp(update[1] - val1[1]) - ratio
        recomputevsupdate[e, :] = update[0] / val1[0] * np.exp(
            update[1] -
            val1[1]) - recompute[0] / val1[0] * np.exp(recompute[1] - val1[1])
        recomputevstest[e, :] = (
            recompute[0] / val1[0] * np.exp(recompute[1] - val1[1]) - ratio)
        val1 = recompute

    # Test mask and pgrad
    _, configs = mc.vmc(wf, configs, nblocks=1, nsteps_per_block=1, tstep=2)
    pgradupdate = wf.pgradient()
    wf.recompute(configs)
    pgrad = wf.pgradient()
    pgdict = {
        k: np.max(np.abs(pgu - pgrad[k]))
        for k, pgu in pgradupdate.items() if np.prod(pgu.shape) > 0
    }
    return {
        "updatevstest": np.max(np.abs(updatevstest)),
        "recomputevstest": np.max(np.abs(recomputevstest)),
        "recomputevsupdate": np.max(np.abs(recomputevsupdate)),
        **pgdict,
    }
Exemple #9
0
def test_pbc(li_cubic_ccecp):
    from pyqmc import supercell
    import scipy

    mol, mf = li_cubic_ccecp

    # S = np.ones((3, 3)) - np.eye(3)
    S = np.identity(3)
    mol = supercell.get_supercell(mol, S)
    kpts = supercell.get_supercell_kpts(mol)[:2]
    kdiffs = mf.kpts[np.newaxis] - kpts[:, np.newaxis]
    kinds = np.nonzero(np.linalg.norm(kdiffs, axis=-1) < 1e-12)[1]

    # Lowdin orthogonalized AO basis.
    # lowdin = lo.orth_ao(mol, "lowdin")
    loiao = lo.iao.iao(mol.original_cell, mf.mo_coeff, kpts=kpts)
    occs = [mf.mo_occ[k] for k in kinds]
    coefs = [mf.mo_coeff[k] for k in kinds]
    ovlp = mf.get_ovlp()[kinds]
    lowdin = [lo.vec_lowdin(l, o) for l, o in zip(loiao, ovlp)]
    lreps = [np.linalg.multi_dot([l.T, o, c]) for l, o, c in zip(lowdin, ovlp, coefs)]

    # make AO to localized orbital coefficients.
    mfobdm = [np.einsum("ij,j,kj->ik", l.conj(), o, l) for l, o in zip(lreps, occs)]

    ### Test OBDM calculation.
    nconf = 500
    nsteps = 100
    warmup = 6
    wf = Slater(mol, mf)
    configs = initial_guess(mol, nconf)
    obdm_dict = dict(mol=mol, orb_coeff=lowdin, kpts=kpts, nsweeps=4, warmup=10)
    obdm = OBDMAccumulator(**obdm_dict)

    df, coords = vmc(
        wf,
        configs,
        nsteps=nsteps,
        accumulators={"obdm": obdm},  # , "obdm_up": obdm_up, "obdm_down": obdm_down},
        verbose=True,
    )

    obdm_est = {}
    for k in ["obdm"]:  # , "obdm_up", "obdm_down"]:
        avg_norm = np.mean(df[k + "norm"][warmup:], axis=0)
        avg_obdm = np.mean(df[k + "value"][warmup:], axis=0)
        obdm_est[k] = normalize_obdm(avg_obdm, avg_norm)

    mfobdm = scipy.linalg.block_diag(*mfobdm)

    mae = np.mean(np.abs(obdm_est["obdm"] - mfobdm))
    assert mae < 0.05, f"mae {mae}"
Exemple #10
0
def test_ecp():

    mol = gto.M(atom='C 0. 0. 0.', ecp='bfd', basis='bfd_vtz')
    mf = scf.RHF(mol).run()
    nconf=5000
    wf=PySCFSlaterUHF(mol,mf)
    coords = initial_guess(mol,nconf)
    df,coords=vmc(wf,coords,nsteps=100,accumulators={'energy':EnergyAccumulator(mol)} )
    df=pd.DataFrame(df)
    warmup=30
    print('mean field',mf.energy_tot(),'vmc estimation', np.mean(df['energytotal'][warmup:]),np.std(df['energytotal'][warmup:]))
    
    assert abs(mf.energy_tot()-np.mean(df['energytotal'][warmup:])) <= np.std(df['energytotal'][warmup:])
Exemple #11
0
def test():

    mol = gto.M(atom="Li 0. 0. 0.; Li 0. 0. 1.5",
                basis="sto-3g",
                unit="bohr",
                verbose=0)
    mf = scf.RHF(mol).run()

    # Lowdin orthogonalized AO basis.
    lowdin = lo.orth_ao(mol, "lowdin")

    # MOs in the Lowdin basis.
    mo = solve(lowdin, mf.mo_coeff)

    # make AO to localized orbital coefficients.
    mfobdm = mf.make_rdm1(mo, mf.mo_occ)

    ### Test OBDM calculation.
    nconf = 500
    nsteps = 400
    warmup = 15
    wf = Slater(mol, mf)
    configs = initial_guess(mol, nconf)
    obdm_dict = dict(mol=mol, orb_coeff=lowdin, nsweeps=5, warmup=15)
    obdm = OBDMAccumulator(**obdm_dict)
    obdm_up = OBDMAccumulator(**obdm_dict, spin=0)
    obdm_down = OBDMAccumulator(**obdm_dict, spin=1)

    df, coords = vmc(
        wf,
        configs,
        nsteps=nsteps,
        accumulators={
            "obdm": obdm,
            "obdm_up": obdm_up,
            "obdm_down": obdm_down
        },
    )
    obdm_est = {}
    for k in ["obdm", "obdm_up", "obdm_down"]:
        avg_norm = np.mean(df[k + "norm"][warmup:], axis=0)
        avg_obdm = np.mean(df[k + "value"][warmup:], axis=0)
        obdm_est[k] = normalize_obdm(avg_obdm, avg_norm)

    assert np.mean(
        np.abs(obdm_est["obdm_up"] + obdm_est["obdm_down"] - mfobdm)) < 0.05
Exemple #12
0
def genconfigs(n):
    """
  Generate configurations and weights corresponding
  to the highest determinant in MD expansion
  """

    import os
    os.system('mkdir -p vmc/')

    mol, mf, mc, wf, to_opt, freeze = wavefunction(return_mf=True)
    #Sample from the wave function which we're taking pderiv relative to
    mf.mo_coeff = mc.mo_coeff
    mf.mo_occ *= 0
    mf.mo_occ[wf.wf1._det_occup[0][-1]] = 2
    wfp = PySCFSlaterUHF(mol, mf)

    #Lots of configurations
    coords = pyqmc.initial_guess(mol, 100000)

    eacc = EnergyAccumulator(mol)
    transform = LinearTransform(wf.parameters, to_opt, freeze)
    pgrad_bare = PGradTransform(eacc, transform, 0)

    #Lots of steps
    warmup = 10
    for i in range(n + warmup + 1):
        df, coords = vmc(wfp, coords, nsteps=1)

        print(i)
        if (i > warmup):
            coords.configs.dump('vmc/coords' + str(i - warmup) + '.pickle')

            val = wf.recompute(coords)
            valp = wfp.value()

            d = pgrad_bare(coords, wf)

            data = {
                'dpH': np.array(d['dpH'])[:, -1],
                'dppsi': np.array(d['dppsi'])[:, -1],
                'en': np.array(d['total']),
                "wfval": val[1],
                "wfpval": valp[1]
            }
            pd.DataFrame(data).to_json('vmc/evals' + str(i - warmup) + '.json')
    return -1
Exemple #13
0
def vmcparsl(wf, lastrun, nsteps, accumulators, stepoffset=0):
    import os
    os.environ["MKL_NUM_THREADS"] = "1"
    os.environ["NUMEXPR_NUM_THREADS"] = "1"
    os.environ["OMP_NUM_THREADS"] = "1"

    from pyqmc.mc import vmc
    import copy
    import numpy as np

    print("running")
    df, coords = vmc(copy.copy(wf),
                     np.asarray(lastrun[1]).copy(),
                     nsteps=nsteps,
                     accumulators=copy.copy(accumulators),
                     stepoffset=stepoffset)
    return df, coords.tolist()
Exemple #14
0
def test_accumulator(C2_ccecp_rhf):
    """Tests that the accumulator gets inserted into the data output correctly."""
    mol, mf = C2_ccecp_rhf
    nconf = 500
    wf = Slater(mol, mf)
    coords = initial_guess(mol, nconf)

    df, coords = vmc(wf,
                     coords,
                     nsteps=30,
                     accumulators={"energy": EnergyAccumulator(mol)})
    df = pd.DataFrame(df)
    eaccum = EnergyAccumulator(mol)
    eaccum_energy = eaccum(coords, wf)
    df = pd.DataFrame(df)
    print(df["energyke"][29] == np.average(eaccum_energy["ke"]))

    assert df["energyke"][29] == np.average(eaccum_energy["ke"])
Exemple #15
0
def test_accumulator():
    """Tests that the accumulator gets inserted into the data output correctly."""
    mol = gto.M(atom="Li 0. 0. 0.; Li 0. 0. 1.5",
                basis="cc-pvtz",
                unit="bohr",
                verbose=5)
    mf = scf.RHF(mol).run()
    nconf = 5000
    wf = Slater(mol, mf)
    coords = initial_guess(mol, nconf)

    df, coords = vmc(wf,
                     coords,
                     nsteps=30,
                     accumulators={"energy": EnergyAccumulator(mol)})
    df = pd.DataFrame(df)
    eaccum = EnergyAccumulator(mol)
    eaccum_energy = eaccum(coords, wf)
    df = pd.DataFrame(df)
    print(df["energytotal"][29] == np.average(eaccum_energy["total"]))

    assert df["energytotal"][29] == np.average(eaccum_energy["total"])
Exemple #16
0
def test_vmc(C2_ccecp_rhf):
    """
    Test that a VMC calculation of a Slater determinant matches Hartree-Fock within error bars.
    """
    mol, mf = C2_ccecp_rhf
    nconf = 500
    nsteps = 300
    warmup = 30

    wf = Slater(mol, mf)
    coords = initial_guess(mol, nconf)
    df, coords = vmc(
        wf,
        coords,
        nblocks=int(nsteps / 30),
        nsteps_per_block=30,
        accumulators={"energy": EnergyAccumulator(mol)},
    )

    df = pd.DataFrame(df)["energytotal"][int(warmup / 30):]
    en = df.mean()
    err = df.sem()
    assert en - mf.energy_tot(
    ) < 5 * err, "pyscf {0}, vmc {1}, err {2}".format(mf.energy_tot(), en, err)
Exemple #17
0
def test_ecp():

    mol = gto.M(atom="C 0. 0. 0.", ecp="bfd", basis="bfd_vtz")
    mf = scf.RHF(mol).run()
    nconf = 5000
    wf = Slater(mol, mf)
    coords = initial_guess(mol, nconf)
    df, coords = vmc(wf,
                     coords,
                     nsteps=100,
                     accumulators={"energy": EnergyAccumulator(mol)})
    df = pd.DataFrame(df)
    warmup = 30
    print(
        "mean field",
        mf.energy_tot(),
        "vmc estimation",
        np.mean(df["energytotal"][warmup:]),
        np.std(df["energytotal"][warmup:]),
    )

    assert abs(mf.energy_tot() -
               np.mean(df["energytotal"][warmup:])) <= np.std(
                   df["energytotal"][warmup:])
Exemple #18
0
def test(atom="He", total_spin=0, total_charge=0, scf_basis="sto-3g"):
    mol = gto.M(
        atom="%s 0. 0. 0.; %s 0. 0. 1.5" % (atom, atom),
        basis=scf_basis,
        unit="bohr",
        verbose=4,
        spin=total_spin,
        charge=total_charge,
    )
    mf = scf.UHF(mol).run()
    # Intrinsic Atomic Orbitals
    iaos = make_separate_spin_iaos(
        mol, mf, np.array([i for i in range(mol.natm)]), iao_basis="minao"
    )
    # iaos=make_combined_spin_iaos(mol,mf,np.array([i for i in range(mol.natm)]),iao_basis='minao')
    # MOs in the IAO basis
    mo = reps_combined_spin_iaos(
        iaos,
        mf,
        np.einsum("i,j->ji", np.arange(mf.mo_coeff[0].shape[1]), np.array([1, 1])),
    )
    # Mean-field obdm in IAO basis
    mfobdm = mf.make_rdm1(mo, mf.mo_occ)
    # Mean-field tbdm in IAO basis
    mftbdm = singledet_tbdm(mf, mfobdm)

    ### Test TBDM calculation.
    # VMC params
    nconf = 500
    n_vmc_steps = 400
    vmc_tstep = 0.3
    vmc_warmup = 30
    # TBDM params
    tbdm_sweeps = 4
    tbdm_tstep = 0.5

    wf = PySCFSlater(mol, mf)  # Single-Slater (no jastrow) wf
    configs = initial_guess(mol, nconf)
    energy = EnergyAccumulator(mol)
    obdm_up = OBDMAccumulator(mol=mol, orb_coeff=iaos[0], nsweeps=tbdm_sweeps, spin=0)
    obdm_down = OBDMAccumulator(mol=mol, orb_coeff=iaos[1], nsweeps=tbdm_sweeps, spin=1)
    tbdm_upup = TBDMAccumulator(
        mol=mol, orb_coeff=iaos, nsweeps=tbdm_sweeps, tstep=tbdm_tstep, spin=(0, 0)
    )
    tbdm_updown = TBDMAccumulator(
        mol=mol, orb_coeff=iaos, nsweeps=tbdm_sweeps, tstep=tbdm_tstep, spin=(0, 1)
    )
    tbdm_downup = TBDMAccumulator(
        mol=mol, orb_coeff=iaos, nsweeps=tbdm_sweeps, tstep=tbdm_tstep, spin=(1, 0)
    )
    tbdm_downdown = TBDMAccumulator(
        mol=mol, orb_coeff=iaos, nsweeps=tbdm_sweeps, tstep=tbdm_tstep, spin=(1, 1)
    )

    print("VMC...")
    df, coords = vmc(
        wf,
        configs,
        nsteps=n_vmc_steps,
        tstep=vmc_tstep,
        accumulators={
            "energy": energy,
            "obdm_up": obdm_up,
            "obdm_down": obdm_down,
            "tbdm_upup": tbdm_upup,
            "tbdm_updown": tbdm_updown,
            "tbdm_downup": tbdm_downup,
            "tbdm_downdown": tbdm_downdown,
        },
        verbose=True,
    )

    # Compares obdm from QMC and MF
    obdm_est = {}
    for k in ["obdm_up", "obdm_down"]:
        avg_norm = np.mean(df[k + "norm"][vmc_warmup:], axis=0)
        avg_obdm = np.mean(df[k + "value"][vmc_warmup:], axis=0)
        obdm_est[k] = normalize_obdm(avg_obdm, avg_norm)
    qmcobdm = np.array([obdm_est["obdm_up"], obdm_est["obdm_down"]])
    print("\nComparing QMC and MF obdm:")
    for s in [0, 1]:
        # print('QMC obdm[%d]:\n'%s,qmcobdm[s])
        # print('MF obdm[%d]:\n'%s,mfobdm[s])
        print("diff[%d]:\n" % s, qmcobdm[s] - mfobdm[s])

    # Compares tbdm from QMC and MF
    avg_norm = {}
    avg_tbdm = {}
    tbdm_est = {}
    for t in ["tbdm_upup", "tbdm_updown", "tbdm_downup", "tbdm_downdown"]:
        for k in df.keys():
            if k.startswith(t + "norm_"):
                avg_norm[k.split("_")[-1]] = np.mean(df[k][vmc_warmup:], axis=0)
            if k.startswith(t + "value"):
                avg_tbdm[k.split("_")[-1]] = np.mean(df[k][vmc_warmup:], axis=0)
    for k in avg_tbdm:
        tbdm_est[k] = normalize_tbdm(
            avg_tbdm[k].reshape(2, 2, 2, 2), avg_norm["a"], avg_norm["b"]
        )
    qmctbdm = np.array(
        [
            [tbdm_est["upupvalue"], tbdm_est["updownvalue"]],
            [tbdm_est["downupvalue"], tbdm_est["downdownvalue"]],
        ]
    )
    print("\nComparing QMC and MF tbdm:")
    for sa, sb in [[0, 0], [0, 1], [1, 0], [1, 1]]:
        # print('QMC tbdm[%d,%d]:\n'%(sa,sb),qmctbdm[sa,sb])
        # print('MF tbdm[%d,%d]:\n'%(sa,sb),mftbdm[sa,sb])
        diff = qmctbdm[sa, sb] - mftbdm[sa, sb]
        print("diff[%d,%d]:\n" % (sa, sb), diff)
        assert np.max(np.abs(diff)) < 0.05
Exemple #19
0
def test():
    from pyscf import gto, scf, lo
    from numpy.linalg import solve
    from pyqmc.slater import PySCFSlaterRHF
    from pyqmc.mc import initial_guess, vmc
    from pyqmc.accumulators import EnergyAccumulator
    from pandas import DataFrame

    ### Generate some basic objects.
    # Simple Li2 run.
    mol = gto.M(atom='Li 0. 0. 0.; Li 0. 0. 1.5',
                basis='sto-3g',
                unit='bohr',
                verbose=0)
    mf = scf.RHF(mol).run()

    # Lowdin orthogonalized AO basis.
    lowdin = lo.orth_ao(mol, 'lowdin')

    # MOs in the Lowdin basis.
    mo = solve(lowdin, mf.mo_coeff)

    # make AO to localized orbital coefficients.
    mfobdm = mf.make_rdm1(mo, mf.mo_occ)

    #print(mfobdm.diagonal().round(2))

    ### Test one-body sampler.
    #test_sample_onebody(mol,lowdin,mf,nsample=int(1e4))
    #test_sample_onebody(mol,lowdin,mf,nsample=int(4e4))
    #test_sample_onebody(mol,lowdin,mf,nsample=int(1e5))

    ### Test OBDM calculation.
    nconf = 500
    nsteps = 400
    obdm_steps = 2
    warmup = 15
    wf = PySCFSlaterRHF(mol, mf)
    configs = initial_guess(mol, nconf)
    energy = EnergyAccumulator(mol)
    obdm = OBDMAccumulator(mol=mol, orb_coeff=mf.mo_coeff, nstep=obdm_steps)
    df, coords = vmc(wf,
                     configs,
                     nsteps=nsteps,
                     accumulators={
                         'energy': energy,
                         'obdm': obdm
                     })
    df = DataFrame(df)
    df['obdm'] = df[['obdmvalue','obdmnorm']]\
        .apply(lambda x:normalize_obdm(x['obdmvalue'],x['obdmnorm']),axis=1)
    print(df[['obdmvalue', 'obdmnorm',
              'obdm']].applymap(lambda x: x.ravel()[0]))
    avg_norm = np.array(df.loc[warmup:,
                               'obdmnorm'].values.tolist()).mean(axis=0)
    avg_obdm = np.array(df.loc[warmup:, 'obdm'].values.tolist()).mean(axis=0)
    std_obdm = np.array(
        df.loc[warmup:, 'obdm'].values.tolist()).std(axis=0) / nsteps**0.5
    print("Average norm(orb)", avg_norm)
    print("Average OBDM(orb,orb)", avg_obdm.diagonal().round(3))
    print("OBDM error (orb,orb)",
          std_obdm.diagonal().round(
              3))  # Note this needs reblocking to be accurate.
    print("AO occupation", mfobdm[0, 0])
    print('mean field', mf.energy_tot(), 'vmc estimation',
          np.mean(df['energytotal'][warmup:]),
          np.std(df['energytotal'][warmup:]))
Exemple #20
0
def test_pbc():
    from pyscf.pbc import gto, scf
    from pyqmc import PySCFSlaterUHF, PySCFSlaterPBC
    from pyqmc import slaterpbc
    import scipy

    lvecs = (np.ones((3, 3)) - np.eye(3)) * 2.0
    mol = gto.M(
        atom="H 0. 0. -{0}; H 0. 0. {0}".format(0.7),
        basis="sto-3g",
        unit="bohr",
        verbose=0,
        a=lvecs,
    )
    mf = scf.KRHF(mol, kpts=mol.make_kpts((2, 2, 2)))
    mf = mf.run()

    S = np.ones((3, 3)) - 2 * np.eye(3)
    mol = slaterpbc.get_supercell(mol, S)
    kpts = slaterpbc.get_supercell_kpts(mol)[:2]
    kdiffs = mf.kpts[np.newaxis] - kpts[:, np.newaxis]
    kinds = np.nonzero(np.linalg.norm(kdiffs, axis=-1) < 1e-12)[1]

    # Lowdin orthogonalized AO basis.
    # lowdin = lo.orth_ao(mol, "lowdin")
    loiao = lo.iao.iao(mol.original_cell, mf.mo_coeff, kpts=kpts)
    occs = [mf.mo_occ[k] for k in kinds]
    coefs = [mf.mo_coeff[k] for k in kinds]
    ovlp = mf.get_ovlp()[kinds]
    lowdin = [lo.vec_lowdin(l, o) for l, o in zip(loiao, ovlp)]
    lreps = [np.linalg.multi_dot([l.T, o, c]) for l, o, c in zip(lowdin, ovlp, coefs)]

    # make AO to localized orbital coefficients.
    mfobdm = [np.einsum("ij,j,kj->ik", l.conj(), o, l) for l, o in zip(lreps, occs)]

    ### Test OBDM calculation.
    nconf = 800
    nsteps = 50
    warmup = 6
    wf = PySCFSlaterPBC(mol, mf)
    configs = initial_guess(mol, nconf)
    obdm_dict = dict(mol=mol, orb_coeff=lowdin, kpts=kpts, nsweeps=4, warmup=10)
    obdm = OBDMAccumulator(**obdm_dict)
    obdm_up = OBDMAccumulator(**obdm_dict, spin=0)
    obdm_down = OBDMAccumulator(**obdm_dict, spin=1)

    df, coords = vmc(
        wf,
        configs,
        nsteps=nsteps,
        accumulators={"obdm": obdm, "obdm_up": obdm_up, "obdm_down": obdm_down},
        verbose=True,
    )
    df = DataFrame(df)

    obdm_est = {}
    for k in ["obdm", "obdm_up", "obdm_down"]:
        avg_norm = np.array(df.loc[warmup:, k + "norm"].values.tolist()).mean(axis=0)
        avg_obdm = np.array(df.loc[warmup:, k + "value"].values.tolist()).mean(axis=0)
        obdm_est[k] = normalize_obdm(avg_obdm, avg_norm)

    print("Average OBDM(orb,orb)", obdm_est["obdm"].round(3))
    mfobdm = scipy.linalg.block_diag(*mfobdm)
    print("mf obdm", mfobdm.round(3))
    max_abs_err = np.max(np.abs(obdm_est["obdm"] - mfobdm))
    assert max_abs_err < 0.05, "max abs err {0}".format(max_abs_err)
    print(obdm_est["obdm_up"].diagonal().round(3))
    print(obdm_est["obdm_down"].diagonal().round(3))
    mae = np.mean(np.abs(obdm_est["obdm_up"] + obdm_est["obdm_down"] - mfobdm))
    maup = np.mean(np.abs(obdm_est["obdm_up"]))
    madn = np.mean(np.abs(obdm_est["obdm_down"]))
    mamf = np.mean(np.abs(mfobdm))
    assert mae < 0.05, "mae {0}\n maup {1}\n madn {2}\n mamf {3}".format(
        mae, maup, madn, mamf
    )
Exemple #21
0
def rundmc(
    wf,
    configs,
    weights=None,
    tstep=0.01,
    nsteps=1000,
    branchtime=5,
    stepoffset=0,
    branchcut_start=3,
    branchcut_stop=6,
    drift_limiter=limdrift,
    verbose=False,
    accumulators=None,
    ekey=("energy", "total"),
    propagate=dmc_propagate,
    feedback=1.0,
    hdf_file=None,
    client=None,
    npartitions=None,
    **kwargs,
):
    """
    Run DMC

    Args:
      wf: A Wave function-like class. recompute(), gradient(), and updateinternals() are used, as well as anything (such as laplacian() ) used by accumulators

      configs: (nconfig, nelec, 3) - initial coordinates to start calculation.

      weights: (nconfig,) - initial weights to start calculation, defaults to uniform.

      nsteps: number of DMC steps to take

      tstep: Time step for move proposals. Introduces time step error.

      branchtime: number of steps to take between branching

      accumulators: A dictionary of functor objects that take in (coords,wf) and return a dictionary of quantities to be averaged. np.mean(quantity,axis=0) should give the average over configurations. If none, a default energy accumulator will be used.

      ekey: tuple of strings; energy is needed for DMC weights. Access total energy by accumulators[ekey[0]](configs, wf)[ekey[1]

      verbose: Print out step information

      drift_limiter: a function that takes a gradient and a cutoff and returns an adjusted gradient

      stepoffset: If continuing a run, what to start the step numbering at.

    Returns: (df,coords,weights)
      df: A list of dictionaries nstep long that contains all results from the accumulators.

      coords: The final coordinates from this calculation.

      weights: The final weights from this calculation

    """
    # Restart from HDF file
    if hdf_file is not None and os.path.isfile(hdf_file):
        with h5py.File(hdf_file, "r") as hdf:
            stepoffset = hdf["step"][-1] + 1
            configs.load_hdf(hdf)
            weights = np.array(hdf["weights"])
            eref = hdf["eref"][-1]
            esigma = hdf["esigma"][-1]
            if verbose:
                print("Restarted calculation")
    else:
        warmup = 2
        df, configs = mc.vmc(
            wf,
            configs,
            accumulators=accumulators,
            client=client,
            npartitions=npartitions,
            verbose=verbose,
        )
        en = df[ekey[0] + ekey[1]][warmup:]
        eref = np.mean(en).real
        esigma = np.sqrt(np.var(en) * np.mean(df["nconfig"]))
        if verbose:
            print("eref start", eref, "esigma", esigma)

    nconfig = configs.configs.shape[0]
    if weights is None:
        weights = np.ones(nconfig)

    npropagate = int(np.ceil(nsteps / branchtime))
    df = []
    for step in range(npropagate):
        if client is None:
            df_, configs, weights = dmc_propagate(
                wf,
                configs,
                weights,
                tstep,
                branchcut_start * esigma,
                branchcut_stop * esigma,
                eref=eref,
                nsteps=branchtime,
                accumulators=accumulators,
                ekey=ekey,
                drift_limiter=drift_limiter,
                **kwargs,
            )
        else:
            df_, configs, weights = dmc_propagate_parallel(
                wf,
                configs,
                weights,
                client,
                npartitions,
                tstep,
                branchcut_start * esigma,
                branchcut_stop * esigma,
                eref=eref,
                nsteps=branchtime,
                accumulators=accumulators,
                ekey=ekey,
                drift_limiter=drift_limiter,
                **kwargs,
            )

        df_["eref"] = eref
        df_["step"] = step + stepoffset
        df_["esigma"] = esigma
        df_["tstep"] = tstep
        df_["weight_std"] = np.std(weights)
        df_["nsteps"] = branchtime

        dmc_file(hdf_file, df_, {}, configs, weights)
        # print(df_)
        df.append(df_)
        eref = (df_[ekey[0] + ekey[1]] - feedback * np.log(np.mean(weights))).real
        configs, weights = branch(configs, weights)
        if verbose:
            print(
                "energy",
                df_[ekey[0] + ekey[1]],
                "eref",
                df_["eref"],
                "sigma(w)",
                df_["weight_std"],
            )

    df_ret = {}
    for k in df[0].keys():
        df_ret[k] = np.asarray([d[k] for d in df])
    return df_ret, configs, weights
Exemple #22
0
def test():
    """ 
    Tests that the multi-slater wave function value, gradient and 
    parameter gradient evaluations are working correctly. Also 
    checks that VMC energy matches energy calculated in PySCF
    """
    mol = gto.M(atom="Li 0. 0. 0.; H 0. 0. 1.5",
                basis="cc-pvtz",
                unit="bohr",
                spin=0)
    epsilon = 1e-4
    delta = 1e-5
    nsteps = 200
    warmup = 10
    for mf in [scf.RHF(mol).run(), scf.ROHF(mol).run(), scf.UHF(mol).run()]:
        # Test same number of elecs
        mc = mcscf.CASCI(mf, ncas=4, nelecas=(1, 1))
        mc.kernel()
        wf = MultiSlater(mol, mf, mc)

        nconf = 10

        nelec = np.sum(mol.nelec)
        epos = initial_guess(mol, nconf)

        for k, item in testwf.test_updateinternals(wf, epos).items():
            assert item < epsilon
        assert testwf.test_wf_gradient(wf, epos, delta=delta)[0] < epsilon
        assert testwf.test_wf_laplacian(wf, epos, delta=delta)[0] < epsilon
        assert testwf.test_wf_pgradient(wf, epos, delta=delta)[0] < epsilon

        # Test same number of elecs
        mc = mcscf.CASCI(mf, ncas=4, nelecas=(1, 1))
        mc.kernel()
        wf = pyqmc.default_msj(mol, mf, mc)[0]

        nelec = np.sum(mol.nelec)
        epos = initial_guess(mol, nconf)

        for k, item in testwf.test_updateinternals(wf, epos).items():
            assert item < epsilon
        assert testwf.test_wf_gradient(wf, epos, delta=delta)[0] < epsilon
        assert testwf.test_wf_laplacian(wf, epos, delta=delta)[0] < epsilon
        assert testwf.test_wf_pgradient(wf, epos, delta=delta)[0] < epsilon

        # Test different number of elecs
        mc = mcscf.CASCI(mf, ncas=4, nelecas=(2, 0))
        mc.kernel()
        wf = MultiSlater(mol, mf, mc)

        nelec = np.sum(mol.nelec)
        epos = initial_guess(mol, nconf)

        for k, item in testwf.test_updateinternals(wf, epos).items():
            assert item < epsilon
        assert testwf.test_wf_gradient(wf, epos, delta=delta)[0] < epsilon
        assert testwf.test_wf_laplacian(wf, epos, delta=delta)[0] < epsilon
        assert testwf.test_wf_pgradient(wf, epos, delta=delta)[0] < epsilon

        # Quick VMC test
        nconf = 1000
        coords = initial_guess(mol, nconf)
        df, coords = vmc(wf,
                         coords,
                         nsteps=nsteps,
                         accumulators={"energy": EnergyAccumulator(mol)})

        df = pd.DataFrame(df)
        df = reblock(df["energytotal"][warmup:], 20)
        en = df.mean()
        err = df.sem()
        assert en - mc.e_tot < 5 * err
Exemple #23
0
def rundmc(
    wf,
    configs,
    weights=None,
    tstep=0.01,
    nsteps=1000,
    branchtime=5,
    stepoffset=0,
    branchcut_start=10,
    verbose=False,
    accumulators=None,
    ekey=("energy", "total"),
    feedback=1.0,
    hdf_file=None,
    continue_from=None,
    client=None,
    npartitions=None,
    **kwargs,
):
    """
    Run DMC

    :parameter wf: A Wave function-like class. recompute(), gradient(), and updateinternals() are used, as well as anything (such as laplacian() ) used by accumulators
    :parameter configs: (nconfig, nelec, 3) - initial coordinates to start calculation.
    :parameter weights: (nconfig,) - initial weights to start calculation, defaults to uniform.
    :parameter nsteps: number of DMC steps to take
    :parameter tstep: Time step for move proposals. Introduces time step error.
    :parameter branchtime: number of steps to take between branching
    :parameter accumulators: A dictionary of functor objects that take in (coords,wf) and return a dictionary of quantities to be averaged. np.mean(quantity,axis=0) should give the average over configurations. If none, a default energy accumulator will be used.
    :parameter ekey: tuple of strings; energy is needed for DMC weights. Access total energy by accumulators[ekey[0]](configs, wf)[ekey[1]
    :parameter verbose: Print out step information
    :parameter stepoffset: If continuing a run, what to start the step numbering at.
    :returns: (df,coords,weights)
      df: A list of dictionaries nstep long that contains all results from the accumulators.

      coords: The final coordinates from this calculation.

      weights: The final weights from this calculation

    """
    # Don't continue onto a file that's already there.
    if continue_from is not None and hdf_file is not None and os.path.isfile(
            hdf_file):
        raise RuntimeError(
            f"continue_from is set but hdf_file={hdf_file} already exists! Delete or rename {hdf_file} and try again."
        )

    # Restart if hdf_file is there
    if continue_from is None and hdf_file is not None and os.path.isfile(
            hdf_file):
        continue_from = hdf_file

    # Now we should be sure that there is a file
    # to continue from, if given.
    if continue_from is not None:
        with h5py.File(continue_from, "r") as hdf:
            stepoffset = hdf["step"][-1] + 1
            configs.load_hdf(hdf)
            weights = np.array(hdf["weights"])
            if "e_trial" not in hdf.keys():
                raise ValueError(
                    "Did not find e_trial in the restart file. This may mean that you are trying to restart from a different version of DMC"
                )
            e_trial = hdf["e_trial"][-1]
            e_est = hdf["e_est"][-1]
            esigma = hdf["esigma"][-1]
            if verbose:
                print(
                    f"Restarting calculation {continue_from} from step {stepoffset}"
                )
    else:
        warmup = 2
        df, configs = mc.vmc(
            wf,
            configs,
            accumulators={ekey[0]: accumulators[ekey[0]]},
            client=client,
            npartitions=npartitions,
            verbose=verbose,
        )
        en = df[ekey[0] + ekey[1]][warmup:]
        eref = np.mean(en).real
        e_trial = eref
        e_est = eref
        esigma = np.sqrt(np.var(en) * np.mean(df["nconfig"]))
        if verbose:
            print("eref start", eref, "esigma", esigma)

    nconfig = configs.configs.shape[0]
    if weights is None:
        weights = np.ones(nconfig)

    npropagate = int(np.ceil(nsteps / branchtime))
    df = []
    for step in range(npropagate):
        if client is None:
            df_, configs, weights = dmc_propagate(
                wf,
                configs,
                weights,
                tstep,
                branchcut_start * esigma,
                e_trial=e_trial,
                e_est=e_est,
                nsteps=branchtime,
                accumulators=accumulators,
                ekey=ekey,
                **kwargs,
            )
        else:
            df_, configs, weights = dmc_propagate_parallel(
                wf,
                configs,
                weights,
                client,
                npartitions,
                tstep,
                branchcut_start * esigma,
                e_trial=e_trial,
                e_est=e_est,
                nsteps=branchtime,
                accumulators=accumulators,
                ekey=ekey,
                **kwargs,
            )

        df_["e_trial"] = e_trial
        df_["e_est"] = e_est
        df_["step"] = step + stepoffset
        df_["esigma"] = esigma
        df_["tstep"] = tstep
        df_["weight_std"] = np.std(weights)
        df_["nsteps"] = branchtime

        dmc_file(hdf_file, df_, {}, configs, weights)
        df.append(df_)
        e_est = estimate_energy(hdf_file, df, ekey)
        e_trial = e_est - feedback * np.log(np.mean(weights)).real
        configs, weights = branch(configs, weights)
        if verbose:
            print(
                "energy",
                df_[ekey[0] + ekey[1]],
                "e_trial",
                e_trial,
                "e_est",
                e_est,
                "sigma(w)",
                df_["weight_std"],
            )

    df_ret = {k: np.asarray([d[k] for d in df]) for k in df[0].keys()}
    return df_ret, configs, weights
Exemple #24
0
def optimize_orthogonal(
    wfs,
    coords,
    pgrad,
    Starget=None,
    forcing=None,
    tstep=0.1,
    max_iterations=30,
    warmup=1,
    warmup_options=None,
    Ntarget=0.5,
    max_step=10.0,
    hdf_file=None,
    linemin=True,
    step_offset=0,
    Ntol=0.05,
    weight_boundaries=0.3,
    sample_options=None,
    correlated_options=None,
    client=None,
    npartitions=None,
    verbose=True,
):
    r"""
    Minimize

    .. math:: f(p_f) = E_f + \sum_i \lambda_{i=0}^{f-1} |S_{fi} - S_{fi}^*|^2

    Where

    .. math:: N_i = \langle \Psi_i | \Psi_i \rangle

    .. math:: S_{fi} = \frac{\langle \Psi_f | \Psi_i \rangle}{\sqrt{N_f N_i}}

    The \*'d and lambda values are respectively targets and forcings. f is the final wave function in the wave function array.
    We only optimize the parameters of the final wave function, so all 'p' values here represent a parameter in the final wave function.

    **Important arguments**

        :wfs: a list of wave function objects. The last one is optimized; the rest are kept fixed and used as orthogonalization references

        :coords: A Coord set

        :pgrad: A Pgradient object

        :tstep: Maximum timestep for line minimization, or timestep when line minimization is off

        :max_iterations: Number of optimization steps to take

        :warmup_options: a dictionary of options for warm up vmc

        :Starget: An array-like of length len(wfs)-1, which indicates the target overlap for each reference wave function.

        :forcing: An array-like of length len(wfs)-1, which gives the penalty (lambda) for each reference wave function

        :hdf_file: A string that gives the filename to save the optimization data in.

    **Arguments for experts**

        Other arguments should not be changed unless you know what you're doing.

    **Details about the implementation**

    The derivatives are:

    .. math:: \partial_p N_f = 2 Re \langle \partial_p \Psi_f | \Psi_f \rangle

    .. math::  \langle \partial_p \Psi_f | \Psi_i \rangle = \int{ \frac{ \Psi_i\partial_p \Psi_f^*}{\rho} \frac{\rho}{\int \rho} }

    .. math:: \partial_p S_{fi} = \frac{\langle \partial_p \Psi_f | \Psi_i \rangle}{\sqrt{N_f N_i}} - \frac{\langle \Psi_f | \Psi_i \rangle}{2\sqrt{N_f N_i}} \frac{\partial_p N_f}{N_f}

    In this implementation, we set

    .. math:: \rho = \sum_i |\Psi_i|^2

    Note that in the definition of N there is an arbitrary normalization of rho. The targets are set relative to the normalization of the reference wave functions.

    Some implementation notes regarding the normalization:

    It's important for the normalization of the wave functions to be similar; otherwise the weights of one dominate and only one of the wave functions gets sampled.
    Some notes:

     * One could modify the relative weights in the definition of rho, but it's more convenient to output wave functions that are normalized with respect to each other.
     * Adding a penalty to the normalization turns out to be fairly unstable.
     * Moves to reduce the overlap and energy tend to change the normalization a lot (keep in mind that both the determinant and Jastrow parts can have gauge degrees of freedom). This can lead into a tailspin effect pretty quickly.

    In this implementation, we handle this using three techniques:

     * Most importantly, the cost function derivative is orthogonalized to the derivative of the normalization.
     * In the line minimization, if the normalization deviates too far from 0.5 relative to the reference wave function, we do not consider the move. The correlated sampling is unreliable in that situation anyway.
     * The wave function is renormalized if its normalization deviates too far from 0.5 relative to the first wave function.
    """

    # Restart
    if hdf_file is not None and os.path.isfile(hdf_file):
        with h5py.File(hdf_file, "r") as hdf:
            if "wf" in hdf.keys():
                grp = hdf["wf"]
                for k in grp.keys():
                    wfs[-1].parameters[k] = np.array(grp[k])
            if "iteration" in hdf.keys():
                step_offset = np.max(hdf["iteration"][...]) + 1

    parameters = pgrad.transform.serialize_parameters(wfs[-1].parameters)

    if Starget is None:
        Starget = np.zeros(len(wfs) - 1)
    if forcing is None:
        forcing = np.ones(len(wfs) - 1)
    Starget = np.asarray(Starget)
    forcing = np.asarray(forcing)
    if len(forcing) != len(wfs) - 1:
        raise AttributeError(
            "forcing should be an array of length equal to the wfs minus 1: " +
            str(len(forcing)))
    attr = dict(
        tstep=tstep,
        max_iterations=max_iterations,
        forcing=forcing,
        warmup=warmup,
        Starget=Starget,
        Ntarget=Ntarget,
        max_step=max_step,
    )
    conditioner = pyqmc.linemin.sd_update

    if sample_options is None:
        sample_options = {}
    if correlated_options is None:
        correlated_options = {}

    if client is None:
        sampler = sample_overlap
        correlated_sampler = correlated_sample
    else:
        sampler = dist_sample_overlap
        correlated_sampler = dist_correlated_sample
        sample_options["client"] = client
        sample_options["npartitions"] = npartitions
        correlated_options["client"] = client
        correlated_options["npartitions"] = npartitions

    # warm up to equilibrate the configurations before running optimization
    if warmup_options is None:
        warmup_options = dict(nblocks=1, nsteps=10)
    if "tstep" not in warmup_options and "tstep" in sample_options:
        warmup_options["tstep"] = sample_options["tstep"]

    data, coords = mc.vmc(wfs[-1],
                          coords,
                          accumulators={},
                          client=client,
                          npartitions=npartitions,
                          **warmup_options)

    # One set of configurations for every wave function
    allcoords = [coords.copy() for _ in wfs[:-1]]
    dtype = np.complex if wfs[-1].iscomplex else np.float

    for step in range(max_iterations):
        # we iterate until the normalization is reasonable
        # One could potentially save a little time here by not computing the gradients
        # every time, but typically we don't have to renormalize if the moves are good

        # Memory efficient implementation
        nwf = len(wfs)
        normalization = np.zeros(nwf - 1)
        total_energy = 0
        # energy_derivative = np.zeros(len(parameters))
        N_derivative = np.zeros(len(parameters))
        condition = np.zeros((len(parameters), len(parameters)))
        overlaps = np.zeros(nwf - 1, dtype=dtype)
        overlap_derivatives = np.zeros((nwf - 1, len(parameters)), dtype=dtype)

        while True:
            return_data, _ = sampler([wfs[0], wfs[-1]], allcoords[0], pgrad,
                                     **sample_options)
            tmp_deriv = evaluate(return_data, warmup)
            N = tmp_deriv["N"][-1]

            if verbose:
                print("Normalization", N, flush=True)
            if abs(N - Ntarget) < Ntol:
                normalization[0] = tmp_deriv["N"][-1]
                total_energy += tmp_deriv["total"] / (nwf - 1)
                energy_derivative = tmp_deriv["energy_derivative"] / (nwf - 1)
                N_derivative += tmp_deriv["N_derivative"] / (nwf - 1)
                condition += tmp_deriv["condition"] / (nwf - 1)
                overlaps[0] = tmp_deriv["S"][-1, 0]
                overlap_derivatives[0] = tmp_deriv["S_derivative"][0, :]
                break
            else:
                renormalize([wfs[0], wfs[-1]], N)
                parameters = pgrad.transform.serialize_parameters(
                    wfs[-1].parameters)

        for i, wf in enumerate(wfs[1:-1]):
            return_data, _ = sampler([wf, wfs[-1]], allcoords[i + 1], pgrad,
                                     **sample_options)
            deriv_data = evaluate(return_data, warmup)
            normalization[i + 1] = deriv_data["N"][-1]
            total_energy += deriv_data["total"] / (nwf - 1)
            energy_derivative += deriv_data["energy_derivative"] / (nwf - 1)
            N_derivative += deriv_data["N_derivative"] / (nwf - 1)
            condition += deriv_data["condition"] / (nwf - 1)
            overlaps[i + 1] = deriv_data["S"][-1, 0]
            overlap_derivatives[i + 1] = deriv_data["S_derivative"][0, :]
        if verbose:
            print("normalization", normalization)

        delta = overlaps - Starget
        delta_phase = delta / np.abs(delta)
        overlap_derivative = np.einsum(
            "j,jk->k",
            2.0 * forcing * np.abs(delta),
            np.real(overlap_derivatives / delta_phase[:, np.newaxis]),
        )

        total_derivative = energy_derivative + overlap_derivative

        if verbose:
            print("############################# iteration ", step)
            format_str = "{:<15}" * 2 + "{:<20.3}" * 2
            print(format_str.format("Quantity", "wf", "val", "|g|"))
            print(
                format_str.format(
                    "energy",
                    len(wfs) - 1,
                    total_energy,
                    np.linalg.norm(energy_derivative),
                ))
            print(
                format_str.format("norm",
                                  len(wfs) - 1, N,
                                  np.linalg.norm(N_derivative)))
            for i in range(len(wfs) - 1):
                print(
                    format_str.format(
                        "overlap",
                        i,
                        overlaps[i],
                        np.linalg.norm(overlap_derivatives[i]),
                    ),
                    flush=True,
                )

        # Use SR to condition the derivatives
        total_derivative, N_derivative = np.einsum(
            "ij,dj->di",
            np.linalg.inv(condition + 0.1 * np.eye(condition.shape[0])),
            [total_derivative, N_derivative],
        )

        # Try to move in the projection that doesn't change the norm
        # Here we project out the norm derivative
        if np.linalg.norm(N_derivative) > 1e-8:
            total_derivative -= (np.dot(total_derivative, N_derivative) *
                                 N_derivative /
                                 (np.linalg.norm(N_derivative))**2)

        deriv_norm = np.linalg.norm(total_derivative)
        if deriv_norm > max_step:
            total_derivative = total_derivative * max_step / deriv_norm

        test_tsteps = np.linspace(-0.1 * tstep, tstep, 11)
        test_parameters = [
            parameters + conditioner(total_derivative, condition, x)
            for x in test_tsteps
        ]
        data = []
        for icoord, wf in zip(allcoords, wfs):
            data.append(
                correlated_sampler([wf, wfs[-1]], icoord, test_parameters,
                                   pgrad, **correlated_options))
        line_data = {}
        for k in data[0].keys():
            line_data[k] = np.asarray([x[k] for x in data])

        yfit = []
        xfit = []
        overlap_cost = (
            forcing[:, np.newaxis] *
            np.abs(line_data["overlap"][:, :, 0] - Starget[:, np.newaxis])**2)
        cost = np.mean(line_data["total"], axis=0) + np.sum(overlap_cost,
                                                            axis=0)
        mask = (np.abs(line_data["weight"] - 1.0) > weight_boundaries) & (
            np.abs(line_data["weight"]) > weight_boundaries)
        mask = np.all(mask, axis=0)
        if verbose:
            print("tsteps", test_tsteps)
            print("cost", cost)
            print("overlap cost", overlap_cost)
            print("mask", mask)
        xfit = test_tsteps[mask]
        yfit = cost[mask]

        if verbose:
            print("|total_derivative|", np.linalg.norm(total_derivative))
        if len(xfit) > 2:
            min_tstep = pyqmc.linemin.stable_fit(xfit, yfit)
            if verbose:
                print("chose to move", min_tstep, flush=True)
            parameters = parameters + conditioner(total_derivative, condition,
                                                  min_tstep)
        else:
            print("WARNING: did not find valid moves. Reducing the timestep")
            tstep *= 0.5

        for k, it in pgrad.transform.deserialize(parameters).items():
            wfs[-1].parameters[k] = it

        save_data = {
            "energy": total_energy,
            "overlap": overlaps,
            "gradient": total_derivative,
            "N": N,
            "parameters": parameters,
            "iteration": step + step_offset,
            "normalization": normalization,
            "overlap_derivatives": overlap_derivatives,
            "energy_derivative": energy_derivative,
            "line_tsteps": test_tsteps,
            "line_cost": cost,
            "line_norm": line_data["weight"],
        }

        ortho_hdf(hdf_file, save_data, attr, coords, wfs[-1].parameters)

    return wfs