Usage

Here are a few example usage cases.

Hubbard chain

In the first example, we will work though simulating the repulsive Hubbard model on a 1D chain at half-filling.

The Hubbard Hamiltonian for a 1D chain is given by

\[\hat{H} = -t \sum_{i, \sigma} (\hat{c}^{\dagger}_{i+1,\sigma}\hat{c}^{\phantom \dagger}_{i, \sigma} + {\rm h.c.}) + U \sum_i \hat{n}_{i, \uparrow}\hat{n}_{i, \downarrow} - \mu \sum_{i, \sigma} \hat{n}_{i, \sigma},\]

where $\hat{c}^\dagger_{i, \sigma} \ (\hat{c}^{\phantom \dagger}_{i, \sigma})$ creates (annihilates) a spin $\sigma$ electron on site $i$ in the lattice, and $\hat{n}_{i, \sigma} = \hat{c}^\dagger_{i, \sigma} \hat{c}^{\phantom \dagger}_{i, \sigma}$ is the spin-$\sigma$ electron number operator for site $i$. In the above Hamiltonian $t$ is the nearest neighbor hopping integral and $U > 0$ controls the strength of the on-site Hubbard repulsion. Lastly, we consider the case that the system is half-filled and particle-hole symmetric, which occurs when when the chemical potential is zero, $\mu = 0.0$.

Suppose we want to simulate a half-filled Hubbard chain $(\mu = 0.0)$ of length $L=4$ with $U=1.0$ with a Neél antiferromagnet trial wavefunction and a density-density Jastrow factor.. This is accomplished by running the script associated with this example using the command

> julia hubbard_chain.jl 1 4 1.0 2 2 300 1000 2000 100

Below you will find a heavily commented script that includes details on what each part of the code is doing.

# Import the required packages
using LinearAlgebra
using Random
using Printf

using LatticeUtilities
using VariationalMC

# We define a top-level function for running the VMC simulation.
# Note that the arguments of this function correspond to the command line
# arguments used to run this script.
function run_hubbard_chain_simulation(
    sID, 
    L,
    U,
    nup,
    ndn,
    N_equil,
    N_opts,
    N_updates,
    N_bins; 
    filepath="."
)
    # DEBUG flag which writes information to terminal during the simulation.
    # For efficeincy, this should always be turned off. 
    debug = false

    # Select which parameters in the variational wavefunction will be optimized.
    optimize = (
        # local s-wave pairing
        Δ_0 = false,
        # spin-x (in-plane magnetization)
        Δ_sx = false,
        # spin-z (out-of-plane magnetization)
        Δ_sz = true,
        # (BCS) chemical potential
        μ = false,
        # uniform charge density 
        Δ_cdw = false
    )

    # Specify whether the model will be particle-hole transformed.
    # Note that this is required if adding pair fields to the wavefunction.
    pht = false

    # Construct the foldername the data will be written.
    df_prefix = @sprintf "hubbard_chain_U%.2f_nup%.2f_ndn%.2f_L%d_opt" U, nup, ndn, L

    # Append optimized parameter names to the foldername.
    datafolder_prefix = create_datafolder_prefix(optimize, df_prefix)

    # Initialize an instance of the SimulationInfo type.
    # This type tracks of where the data is written, as well as 
    # which version of Julia and VariationalMC are used in the script. 
    simulation_info = SimulationInfo(
        filepath = filepath, 
        datafolder_prefix = datafolder_prefix,
        sID = sID
    )

    # Initialize the directory the data will be written.
    initialize_datafolder(simulation_info)

    # Initialize a random number generator that will be used throughout the simulation.
    # We seed this function with a randomly sampled number for the
    # global random number generator.
    seed = abs(rand(Int))
    rng = Xoshiro(seed)

    # Set the optimization rate for the VMC simulation.
    dt = 0.03     

    # Set the stabilization factor used in parameter optimization. 
    η = 1e-4    

    # Set the frequency in Monte Carlo steps with which the equal-time Green's function
    # matrix will be recomputed using the numerical stabilization procedure.
    n_stab_W = 50

    # Specify the maximum allowed error in the equal-time Green's function matrix that
    # is corrected by numerical stabilization.
    δW = 1e-3

    # Specify the optimization bin size.
    # The optimization bin size if the number of measurments that are averaged over each time data
    # is written to file during optimization. 
    opt_bin_size = 3000

    # Calculate the simulation bin size.
    # The bin size is the number of measurements that are averaged over each time data is written
    # to file during the simulation.
    bin_size = div(N_updates, N_bins)

    # Initialize a dictionary to store additional information about the simulation.
    # This is a sort of "notebook" for tracking extraneous parameters during the VMC simulation.
    metadata = Dict()
    metadata["N_equil"] = N_equil
    metadata["N_opts"] = N_opts
    metadata["N_updates"] = N_updates
    metadata["N_bins"] = N_bins
    metadata["seed"] = seed
    metadata["pht"] = true
    metadata["δW"] = δW
    metadata["dt"] = dt 
    metadata["opt_flags"] = optimize 
    metadata["avg_acceptance_rate"] = 0.0
    metadata["opt_time"] = 0.0
    metadata["sim_time"] = 0.0
    metadata["total_time"] = 0.0

    #######################
    ### DEFINE THE MODEL ##
    #######################

    # Initialize an instance of the UnitCell type.
    # This struct defines the UnitCell.
    unit_cell = UnitCell(
        lattice_vecs = [[1.0]],
        basis_vecs   = [[0.0]]
    )

    # Initialize an instance of the Lattice type.
    # The struct describes the size of the finite periodic lattice to be simulated.
    # Note that the current version of LatticeUtilities requires 
    # periodic boundary conditions be used.
    lattice = Lattice(
        [L], 
        [true]
    )

    # Define the nearest neighbor bond for a 1D chain.
    bond_x = Bond(
        orbitals = (1,1), 
        displacement = [1]
    )

    # Define the next-nearest neighbor bonds for a 1D chain.
    bond_xp = Bond(
        orbitals = (1,1), 
        displacement = [2]
    )

    # Collect all bond definitions into a single vector.
    # Note that this has the structure [[nearest],[next-nearest]].
    bonds = [[bond_x], [bond_xp]]
    
    # Initialize an instance of the ModelGeometry type.
    # This type helps keep track of all the relevant features of the lattice
    # geometry being simulated, including the defintion of the unit cell,
    # the size of the finite periodic lattice, and all the relevant
    # bond defintions that may arise in the model.
    model_geometry = ModelGeometry(
        unit_cell, 
        lattice, 
        bonds
    )

    ##############################
    ### INITIALIZE MEASUREMENTS ##
    ##############################

    # Initialize the container that measurements will be accumulated into.
    measurement_container = initialize_measurement_container(
        N_opts, 
        opt_bin_size, 
        N_bins, 
        bin_size,
        determinantal_parameters,
        model_geometry
    )

    # Initialize the sub-directories to which the various measurements will be written.
    initialize_measurement_directories(
        simulation_info, 
        measurement_container
    )

    ############################
    ### SET-UP VMC SIMULATION ##
    ############################

    # Determine the total particle density in the canonical ensemble. 
    (density, Np, Ne) = get_particle_density(nup, ndn)

    # Define the nearest neighbor hopping amplitude, setting the energy scale of the system. 
    t = 1.0;

    # Define the next-nearest neighbor hopping amplitude.
    tp = 0.0;

    # Define the non-interacting tight binding model.
    tight_binding_model = TightBindingModel(t, tp)

    # Specify the minimum value of each variational parameter.
    # Note that this is done to avoid open shell issues.
    minabs_vpar = 1e-4;

    # Initialize determinantal variational parameters.
    determinantal_parameters = DeterminantalParameters(
        optimize, 
        tight_binding_model, 
        model_geometry, 
        minabs_vpar, 
        Ne, 
        pht
    )

    # Initialize the (fermionic) particle configuration cache.
    pconfig_cache = nothing

    ###########################
    ### OPTIMIZATION UPDATES ##
    ###########################

    # Record start time for optimization. 
    opt_start_time = time()

    # Iterate over optimization updates.
    for bin in 1:N_opts

        # Initialize the determinantal wavefunction.
        detwf = get_determinantal_wavefunction(
            tight_binding_model, 
            determinantal_parameters, 
            optimize, 
            Ne, 
            nup, 
            ndn, 
            model_geometry, 
            rng,
            pconfig_cache
        )   

        # Iterate over equilibration/thermalization updates.
        for step in 1:N_equil 

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf) = local_fermion_update!(
                detwf, 
                Ne, 
                model_geometry, 
                n_stab_W,
                δW, 
                rng
            )

            # Record the acceptance rate for attempted local updates of the particle configuration.                                                         
            metadata["avg_acceptance_rate"] += acceptance_rate;
        end

        # Iterate over the number of optimization bins.
        for n in 1:opt_bin_size

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf) = local_fermion_update!(
                detwf, 
                Ne, 
                model_geometry, 
                n_stab_W,
                δW, 
                rng
            ) 
                                                                                                        
            # Record the acceptance rate for attempted local updates of the particle configuration                                                       
            metadata["avg_acceptance_rate"] += acceptance_rate;
            
            # Make measurements, with results being recorded in the measurement container. 
            make_measurements!(
                measurement_container, 
                detwf, 
                tight_binding_model, 
                determinantal_parameters, 
                model_geometry, 
                Ne, 
                pht
            )

            # Write measurement for the current bin to file.
            write_measurements!(
                bin, 
                n, 
                measurement_container, 
                simulation_info
            )
        end

        # Record the last particle configuration used in the current bin. 
        pconfig_cache = detwf.pconfig

        # Process the measurement results, calculating error bars for all measurements. 
        # process_measurements(simulation_info, opt_bin_size)

        # Attempt an update to the variational parameters using the Stochastic Reconfiguration procedure. 
        stochastic_reconfiguration!( 
            measurement_container,  
            determinantal_parameters, 
            η, 
            dt, 
            bin, 
            opt_bin_size
        )  
    end     

    # Record end time for optimization.
    opt_end_time = time()

    # Calculate the total time for optimization. 
    metadata["opt_time"] += opt_end_time - opt_start_time

    #########################
    ### SIMULATION UPDATES ##
    #########################

    # Record start time for the simulation.
    sim_start_time = time()

    # Iterate over simulation updates.
    for bin in 1:N_updates

        # Initialize the determinantal wavefunction.
        detwf = get_determinantal_wavefunction(
            tight_binding_model, 
            determinantal_parameters, 
            optimize, 
            Ne, 
            nup, 
            ndn, 
            model_geometry, 
            rng,
            pconfig_cache
        )   

        # Iterate over equilibration/thermalization updates.
        for step in 1:N_equil 

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf) = local_fermion_update!(
                detwf, 
                Ne, 
                model_geometry, 
                n_stab_W,
                δW, 
                rng
            )

            # Record the acceptance rate for attempted local updates of the particle configuration.                                                         
            metadata["avg_acceptance_rate"] += acceptance_rate;
        end

        # Iterate over the number of simulation bins.
        for n in 1:bin_size

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf) = local_fermion_update!(
                detwf, 
                Ne, 
                model_geometry, 
                n_stab_W,
                δW, 
                rng
            ) 
                                                                                                        
            # Record the acceptance rate for attempted local updates of the particle configuration                                                       
            metadata["avg_acceptance_rate"] += acceptance_rate;
            
            # Make measurements, with results being recorded in the measurement container. 
            make_measurements!(
                measurement_container, 
                detwf, 
                tight_binding_model, 
                determinantal_parameters, 
                model_geometry, 
                Ne, 
                pht
            )

            # Write measurement for the current bin to file.
            write_measurements!(
                bin, 
                n, 
                measurement_container, 
                simulation_info
            )
        end

        # Record the last particle configuration used in the current bin. 
        pconfig_cache = detwf.pconfig

        # Process the measurement results, calculating error bars for all measurements. 
        # process_measurements(simulation_info, bin_size)
    end     

    # Record end time for the simulation. 
    sim_end_time = time()

    # Calculate total time for the simulation. 
    metadata["sim_time"] += sim_end_time - sim_start_time

    # Record the total runtime.
    metadata["total_time"] += metadata["opt_time"] + metadata["sim_time"]

    # # Write simulation summary TOML file.
    # save_simulation_info(simulation_info, metadata)

    return nothing
end 

# Only execute if the script is run directly from the command line.
if abspath(PROGRAM_FILE) == @__FILE__

    # Read in the command line arguments.
    sID = parse(Int, ARGS[1]) # simulation ID
    L = parse(Int, ARGS[2])
    U = parse(Float64, ARGS[3])
    nup = parse(Int, ARGS[4])
    ndn = parse(Int, ARGS[5])
    N_equil = parse(Int, ARGS[6])
    N_opts = parse(Int, ARGS[7])
    N_updates = parse(Int, ARGS[8])
    N_bins = parse(Int, ARGS[9])

    # Run the simulation.
    run_hubbard_chain_simulation(sID, L, U, nup, ndn, N_equil, N_opts, N_updates, N_bins)
end

Hubbard chain with MPI (experimental)

In this example we simulate the same system as in example 1, the half-filled Hubbard chain. However in this case, we use a BCS trial wavefunction with a density-density Jastrow factor. A short tes simulation using this script associated with this example can be run as

> mpiexecjl -n 8 julia hubbard_chain_mpi.jl 1 4 1.0 2 2 300 1000 2000 100

(The script is forthcoming.)

Hubbard chain with Jastrow factor

In this example we simulate the same system as in example 1, the half-filled Hubbard chain. However in this case, we use a BCS trial wavefunction with a density-density Jastrow factor. A short tes simulation using this script associated with this example can be run as

> julia hubbard_chain_jastrow.jl 1 4 1.0 2 2 300 1000 2000 100

Below you will find a heavily commented script that includes details on what each part of the code is doing.

# Import the required packages
using LinearAlgebra
using Random
using Printf

using LatticeUtilities
using VariationalMC

# We define a top-level function for running the VMC simulation.
# Note that the arguments of this function correspond to the command line
# arguments used to run this script.
function run_hubbard_chain_simulation(
    sID, 
    L,
    U,
    nup,
    ndn,
    N_equil,
    N_opts,
    N_updates,
    N_bins; 
    filepath="."
)
    # DEBUG flag which writes information to terminal during the simulation.
    # For efficeincy, this should always be turned off. 
    debug = false

    # Select which parameters in the variational wavefunction will be optimized.
    optimize = (
        # local s-wave pairing
        Δ_0 = true,
        # spin-x (in-plane magnetization)
        Δ_sx = false,
        # spin-z (out-of-plane magnetization)
        Δ_sz = true,
        # (BCS) chemical potential
        μ = true,
        # uniform charge density 
        Δ_cdw = false,
        # density-density Jastrow 
        djastrow = true
    )

    # Specify whether the model will be particle-hole transformed.
    # Note that this is required if adding pair fields to the wavefunction.
    pht = true

    # Construct the foldername the data will be written.
    df_prefix = @sprintf "hubbard_chain_U%.2f_nup%.2f_ndn%.2f_L%d_opt" U, nup, ndn, L

    # Append optimized parameter names to the foldername.
    datafolder_prefix = create_datafolder_prefix(optimize, df_prefix)

    # Initialize an instance of the SimulationInfo type.
    # This type tracks of where the data is written, as well as 
    # which version of Julia and VariationalMC are used in the script. 
    simulation_info = SimulationInfo(
        filepath = filepath, 
        datafolder_prefix = datafolder_prefix,
        sID = sID
    )

    # Initialize the directory the data will be written.
    initialize_datafolder(simulation_info)

    # Initialize a random number generator that will be used throughout the simulation.
    # We seed this function with a randomly sampled number for the
    # global random number generator.
    seed = abs(rand(Int))
    rng = Xoshiro(seed)

    # Set the optimization rate for the VMC simulation.
    dt = 0.03     

    # Set the stabilization factor used in parameter optimization. 
    η = 1e-4    

    # Set the frequency in Monte Carlo steps with which the equal-time Green's function
    # matrix will be recomputed using the numerical stabilization procedure.
    n_stab_W = 50

    # Set the frequency in Monte Carlo steps with which the Jastrow T vector will be
    # recomputed using the numerical stabilization procedure.
    n_stab_T = 50

    # Specify the maximum allowed error in the equal-time Green's function matrix that
    # is corrected by numerical stabilization.
    δW = 1e-3

    # Specify the maximum allowed error in the Jastrow T vector that is corrected 
    # by numerical stabilization.
    δT = 1e-3

    # Specify the optimization bin size.
    # The optimization bin size if the number of measurments that are averaged over each time data
    # is written to file during optimization. 
    opt_bin_size = 3000

    # Calculate the simulation bin size.
    # The bin size is the number of measurements that are averaged over each time data is written
    # to file during the simulation.
    bin_size = div(N_updates, N_bins)

    # Initialize a dictionary to store additional information about the simulation.
    # This is a sort of "notebook" for tracking extraneous parameters during the VMC simulation.
    metadata = Dict()
    metadata["N_equil"] = N_equil
    metadata["N_opts"] = N_opts
    metadata["N_updates"] = N_updates
    metadata["N_bins"] = N_bins
    metadata["seed"] = seed
    metadata["pht"] = true
    metadata["δW"] = δW
    metadata["dt"] = dt 
    metadata["opt_flags"] = optimize 
    metadata["avg_acceptance_rate"] = 0.0
    metadata["opt_time"] = 0.0
    metadata["sim_time"] = 0.0
    metadata["total_time"] = 0.0

    #######################
    ### DEFINE THE MODEL ##
    #######################

    # Initialize an instance of the UnitCell type.
    # This struct defines the UnitCell.
    unit_cell = UnitCell(
        lattice_vecs = [[1.0]],
        basis_vecs   = [[0.0]]
    )

    # Initialize an instance of the Lattice type.
    # The struct describes the size of the finite periodic lattice to be simulated.
    # Note that the current version of LatticeUtilities requires 
    # periodic boundary conditions be used.
    lattice = Lattice(
        [L], 
        [true]
    )

    # Define the nearest neighbor bond for a 1D chain.
    bond_x = Bond(
        orbitals = (1,1), 
        displacement = [1]
    )

    # Define the next-nearest neighbor bonds for a 1D chain.
    bond_xp = Bond(
        orbitals = (1,1), 
        displacement = [2]
    )

    # Collect all bond definitions into a single vector.
    # Note that this has the structure [[nearest],[next-nearest]].
    bonds = [[bond_x], [bond_xp]]
    
    # Initialize an instance of the ModelGeometry type.
    # This type helps keep track of all the relevant features of the lattice
    # geometry being simulated, including the defintion of the unit cell,
    # the size of the finite periodic lattice, and all the relevant
    # bond defintions that may arise in the model.
    model_geometry = ModelGeometry(
        unit_cell, 
        lattice, 
        bonds
    )

    ##############################
    ### INITIALIZE MEASUREMENTS ##
    ##############################

    # Initialize the container that measurements will be accumulated into.
    measurement_container = initialize_measurement_container(
        N_opts, 
        opt_bin_size, 
        N_bins, 
        bin_size,
        determinantal_parameters,
        model_geometry
    )

    # Initialize the sub-directories to which the various measurements will be written.
    initialize_measurement_directories(
        simulation_info, 
        measurement_container
    )

    ############################
    ### SET-UP VMC SIMULATION ##
    ############################

    # Determine the total particle density in the canonical ensemble. 
    (density, Np, Ne) = get_particle_density(nup, ndn)

    # Define the nearest neighbor hopping amplitude, setting the energy scale of the system. 
    t = 1.0;

    # Define the next-nearest neighbor hopping amplitude.
    tp = 0.0;

    # Define the non-interacting tight binding model.
    tight_binding_model = TightBindingModel(t, tp)

    # Specify the minimum value of each variational parameter.
    # Note that this is done to avoid open shell issues.
    minabs_vpar = 1e-4;

    # Initialize determinantal variational parameters.
    determinantal_parameters = DeterminantalParameters(
        optimize, 
        tight_binding_model, 
        model_geometry, 
        minabs_vpar, 
        Ne, 
        pht
    )

    # Initialize density-density Jastrow variational parameters. 
    djastrow_parameters = JastrowParameters(
        "e-den-den",
        optimize, 
        model_geometry,
        rng
    )

    # Initialize the (fermionic) particle configuration cache.
    pconfig_cache = nothing

    ###########################
    ### OPTIMIZATION UPDATES ##
    ###########################

    # Record start time for optimization. 
    opt_start_time = time()

    # Iterate over optimization updates.
    for bin in 1:N_opts

        # Initialize the determinantal wavefunction.
        detwf = get_determinantal_wavefunction(
            tight_binding_model, 
            determinantal_parameters, 
            optimize, 
            Ne, 
            nup, 
            ndn, 
            model_geometry, 
            rng,
            pconfig_cache
        )   

        # Initialize the density-density Jastrow factor.
        djas_factor = get_jastrow_factor(
            djastrow_parameters,
            detwf,
            model_geometry,
            pht
        )

        # Iterate over equilibration/thermalization updates.
        for step in 1:N_equil 

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf, djas_factor) = local_fermion_update!(
                detwf, 
                djas_factor,
                djastrow_parameters,
                Ne, 
                model_geometry, 
                pht,
                n_stab_W,
                n_stab_T,
                δW, 
                δT,
                rng
            )

            # Record the acceptance rate for attempted local updates of the particle configuration.                                                         
            metadata["avg_acceptance_rate"] += acceptance_rate;
        end

        # Iterate over the number of optimization bins.
        for n in 1:opt_bin_size

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf, djas_factor) = local_fermion_update!(
                detwf, 
                djas_factor,
                djastrow_parameters,
                Ne, 
                model_geometry, 
                pht,
                n_stab_W,
                n_stab_T,
                δW, 
                δT,
                rng
            ) 
                                                                                                        
            # Record the acceptance rate for attempted local updates of the particle configuration                                                       
            metadata["avg_acceptance_rate"] += acceptance_rate;
            
            # Make measurements, with results being recorded in the measurement container. 
            make_measurements!(
                measurement_container, 
                detwf, 
                tight_binding_model, 
                djas_factor,
                djastrow_parameters,
                determinantal_parameters, 
                model_geometry, 
                Ne, 
                pht
            )

            # Write measurement for the current bin to file.
            write_measurements!(
                bin, 
                n, 
                measurement_container, 
                simulation_info
            )
        end

        # Record the last particle configuration used in the current bin. 
        pconfig_cache = detwf.pconfig

        # Process the measurement results, calculating error bars for all measurements. 
        # process_measurements(simulation_info, opt_bin_size)

        # Attempt an update to the variational parameters using the Stochastic Reconfiguration procedure. 
        stochastic_reconfiguration!( 
            measurement_container,  
            determinantal_parameters, 
            djastrow_parameters,
            η, 
            dt, 
            bin, 
            opt_bin_size
        )  
    end     

    # Record end time for optimization.
    opt_end_time = time()

    # Calculate the total time for optimization. 
    metadata["opt_time"] += opt_end_time - opt_start_time

    #########################
    ### SIMULATION UPDATES ##
    #########################

    # Record start time for the simulation.
    sim_start_time = time()

    # Iterate over simulation updates.
    for bin in 1:N_updates

        # Initialize the determinantal wavefunction.
        detwf = get_determinantal_wavefunction(
            tight_binding_model, 
            determinantal_parameters, 
            optimize, 
            Ne, 
            nup, 
            ndn, 
            model_geometry, 
            rng,
            pconfig_cache
        )   

        # Initialize the density-density Jastrow factor.
        djas_factor = get_jastrow_factor(
            djastrow_parameters,
            detwf,
            model_geometry,
            pht
        )

        # Iterate over equilibration/thermalization updates.
        for step in 1:N_equil 

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf, djas_factor) = local_fermion_update!(
                detwf, 
                Ne, 
                model_geometry, 
                n_stab_W,
                δW, 
                rng
            )

            # Record the acceptance rate for attempted local updates of the particle configuration.                                                         
            metadata["avg_acceptance_rate"] += acceptance_rate;
        end

        # Iterate over the number of simulation bins.
        for n in 1:bin_size

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf) = local_fermion_update!(
                detwf, 
                djas_factor,
                djastrow_parameters,
                Ne, 
                model_geometry, 
                pht,
                n_stab_W,
                n_stab_T,
                δW, 
                δT,
                rng
            ) 
                                                                                                        
            # Record the acceptance rate for attempted local updates of the particle configuration                                                       
            metadata["avg_acceptance_rate"] += acceptance_rate;
            
            # Make measurements, with results being recorded in the measurement container. 
            make_measurements!(
                measurement_container, 
                detwf, 
                tight_binding_model, 
                djas_factor,
                djastrow_parameters,
                determinantal_parameters, 
                model_geometry, 
                Ne, 
                pht
            )

            # Write measurement for the current bin to file.
            write_measurements!(
                bin, 
                n, 
                measurement_container, 
                simulation_info
            )
        end

        # Record the last particle configuration used in the current bin. 
        pconfig_cache = detwf.pconfig

        # Process the measurement results, calculating error bars for all measurements. 
        # process_measurements(simulation_info, bin_size)
    end     

    # Record end time for the simulation. 
    sim_end_time = time()

    # Calculate total time for the simulation. 
    metadata["sim_time"] += sim_end_time - sim_start_time

    # Record the total runtime.
    metadata["total_time"] += metadata["opt_time"] + metadata["sim_time"]

    # # Write simulation summary TOML file.
    # save_simulation_info(simulation_info, metadata)

    return nothing
end 

# Only execute if the script is run directly from the command line.
if abspath(PROGRAM_FILE) == @__FILE__

    # Read in the command line arguments.
    sID = parse(Int, ARGS[1]) # simulation ID
    L = parse(Int, ARGS[2])
    U = parse(Float64, ARGS[3])
    nup = parse(Int, ARGS[4])
    ndn = parse(Int, ARGS[5])
    N_equil = parse(Int, ARGS[6])
    N_opts = parse(Int, ARGS[7])
    N_updates = parse(Int, ARGS[8])
    N_bins = parse(Int, ARGS[9])

    # Run the simulation.
    run_hubbard_chain_simulation(sID, L, U, nup, ndn, N_equil, N_opts, N_updates, N_bins)
end

Square Hubbard model with Jastrow factor

In this next example, we will work though simulating the repulsive Hubbard model on a 2D square lattice at half-filling.

The Hubbard Hamiltonian for a 2D square lattice is given by

\[\hat{H} = -t \sum_{\langle i,j\rangle, \sigma} (\hat{c}^{\dagger}_{j,\sigma}\hat{c}^{\phantom \dagger}_{i, \sigma} + {\rm h.c.}) + U \sum_i \hat{n}_{i, \uparrow}\hat{n}_{i, \downarrow} - \mu \sum_{i, \sigma} \hat{n}_{i, \sigma},\]

where $\hat{c}^\dagger_{i, \sigma} \ (\hat{c}^{\phantom \dagger}_{i, \sigma})$ creates (annihilates) a spin $\sigma$ electron on site $i$ in the lattice, and $\hat{n}_{i, \sigma} = \hat{c}^\dagger_{i, \sigma} \hat{c}^{\phantom \dagger}_{i, \sigma}$ is the spin-$\sigma$ electron number operator for site $i$. In the above Hamiltonian $t$ is the nearest neighbor hopping integral and $U > 0$ controls the strength of the on-site Hubbard repulsion. Lastly, we consider the case that the system is half-filled and particle-hole symmetric, which occurs when when the chemical potential is zero, $\mu = 0.0$.

Suppose we want to simulate a half-filled square Hubbard $(\mu = 0.0)$ of size $N=4\times 4$ with $U=1.0$ with a uniform $d$-wave trial wavefunction and a density-density Jastrow factor. This is accomplished by running the script associated with this example using the command

> julia hubbard_square_jastrow.jl 1 4 1.0 2 2 300 1000 2000 100

Below you will find a heavily commented script that includes details on what each part of the code is doing.

# Import the required packages
using LinearAlgebra
using Random
using Printf

using LatticeUtilities
using VariationalMC

# We define a top-level function for running the VMC simulation.
# Note that the arguments of this function correspond to the command line
# arguments used to run this script.
function run_hubbard_square_simulation(
    sID, 
    L,
    U,
    nup,
    ndn,
    N_equil,
    N_opts,
    N_updates,
    N_bins; 
    filepath="."
)
    # DEBUG flag which writes information to terminal during the simulation.
    # For efficeincy, this should always be turned off. 
    debug = false

    # Select which parameters in the variational wavefunction will be optimized.
    optimize = (
        # local s-wave pairing
        Δ_0 = false,
        # site-dependent s-wave pairing  
        Δ_spd = false,
        # local d-wave pairing
        Δ_d = true,
        # site-dependent d-wave pairing 
        Δ_dpd = false,          
        # pairing momentum
        q_p = false,
        # spin-x (in-plane magnetization)
        Δ_sx = false,
        # spin-z (out-of-plane magnetization)
        Δ_sz = true,
        # site-dependent spin density
        Δ_ssd = false,
        # (BCS) chemical potential
        μ = false,
        # uniform charge density 
        Δ_cdw = false,
        # site-dependent charge density
        Δ_csd = false,
        # density-density Jastrow 
        djastrow = true,
    )

    # Specify whether the model will be particle-hole transformed.
    # Note that this is required if adding pair fields to the wavefunction.
    pht = true

    # Construct the foldername the data will be written.
    df_prefix = @sprintf "hubbard_chain_U%.2f_nup%.2f_ndn%.2f_L%d_opt" U, nup, ndn, L

    # Append optimized parameter names to the foldername.
    datafolder_prefix = create_datafolder_prefix(optimize, df_prefix)

    # Initialize an instance of the SimulationInfo type.
    # This type tracks of where the data is written, as well as 
    # which version of Julia and VariationalMC are used in the script. 
    simulation_info = SimulationInfo(
        filepath = filepath, 
        datafolder_prefix = datafolder_prefix,
        sID = sID
    )

    # Initialize the directory the data will be written.
    initialize_datafolder(simulation_info)

    # Initialize a random number generator that will be used throughout the simulation.
    # We seed this function with a randomly sampled number for the
    # global random number generator.
    seed = abs(rand(Int))
    rng = Xoshiro(seed)

    # Set the optimization rate for the VMC simulation.
    dt = 0.03     

    # Set the stabilization factor used in parameter optimization. 
    η = 1e-4    

    # Set the frequency in Monte Carlo steps with which the equal-time Green's function
    # matrix will be recomputed using the numerical stabilization procedure.
    n_stab_W = 50

    # Set the frequency in Monte Carlo steps with which the Jastrow T vector will be
    # recomputed using the numerical stabilization procedure.
    n_stab_T = 50

    # Specify the maximum allowed error in the equal-time Green's function matrix that
    # is corrected by numerical stabilization.
    δW = 1e-3

    # Specify the maximum allowed error in the Jastrow T vector that is corrected 
    # by numerical stabilization.
    δT = 1e-3

    # Specify the optimization bin size.
    # The optimization bin size if the number of measurments that are averaged over each time data
    # is written to file during optimization. 
    opt_bin_size = 3000

    # Calculate the simulation bin size.
    # The bin size is the number of measurements that are averaged over each time data is written
    # to file during the simulation.
    bin_size = div(N_updates, N_bins)

    # Initialize a dictionary to store additional information about the simulation.
    # This is a sort of "notebook" for tracking extraneous parameters during the VMC simulation.
    metadata = Dict()
    metadata["N_equil"] = N_equil
    metadata["N_opts"] = N_opts
    metadata["N_updates"] = N_updates
    metadata["N_bins"] = N_bins
    metadata["seed"] = seed
    metadata["pht"] = true
    metadata["δW"] = δW
    metadata["dt"] = dt 
    metadata["opt_flags"] = optimize 
    metadata["avg_acceptance_rate"] = 0.0
    metadata["opt_time"] = 0.0
    metadata["sim_time"] = 0.0
    metadata["total_time"] = 0.0

    #######################
    ### DEFINE THE MODEL ##
    #######################

    # Initialize an instance of the UnitCell type.
    # This struct defines the UnitCell.
    unit_cell = UnitCell(
        lattice_vecs = [[1.0, 0.0], [0.0, 1.0]],
        basis_vecs   = [[0.0, 0.0]]
    )

    # Initialize an instance of the Lattice type.
    # The struct describes the size of the finite periodic lattice to be simulated.
    # Note that the current version of LatticeUtilities requires 
    # periodic boundary conditions be used.
    lattice = Lattice(
        [L, L], 
        [true, true]
    )

    # Define the nearest neighbor x-bond for a square lattice.
    bond_x = Bond(
        orbitals = (1,1), 
        displacement = [1]
    )

    # Define the nearest neighbor y-bond for a square lattice.
    bond_y = Bond(
        orbitals = (1,1), 
        displacement = [0,1]
    )

    # Define the next-nearest neighbor bonds for a square lattice.
    bond_xy = Bond(
        orbitals = (1,1), 
        displacement = [1,1]
    )

    # Define the next-nearest neighbor bonds for a square lattice.
    bond_yx = Bond(
        orbitals = (1,1), 
        displacement = [1,-1]
    )

    # Collect all bond definitions into a single vector.
    # Note that this has the structure [[nearest],[next-nearest]].
    bonds = [[bond_x, bond_y], [bond_xy, bond_yx]]
    
    # Initialize an instance of the ModelGeometry type.
    # This type helps keep track of all the relevant features of the lattice
    # geometry being simulated, including the defintion of the unit cell,
    # the size of the finite periodic lattice, and all the relevant
    # bond defintions that may arise in the model.
    model_geometry = ModelGeometry(
        unit_cell, 
        lattice, 
        bonds
    )

    ##############################
    ### INITIALIZE MEASUREMENTS ##
    ##############################

    # Initialize the container that measurements will be accumulated into.
    measurement_container = initialize_measurement_container(
        N_opts, 
        opt_bin_size, 
        N_bins, 
        bin_size,
        determinantal_parameters,
        model_geometry
    )

    # Initialize the sub-directories to which the various measurements will be written.
    initialize_measurement_directories(
        simulation_info, 
        measurement_container
    )

    ############################
    ### SET-UP VMC SIMULATION ##
    ############################

    # Determine the total particle density in the canonical ensemble. 
    (density, Np, Ne) = get_particle_density(nup, ndn)

    # Define the nearest neighbor hopping amplitude, setting the energy scale of the system. 
    t = 1.0;

    # Define the next-nearest neighbor hopping amplitude.
    tp = 0.0;

    # Define the non-interacting tight binding model.
    tight_binding_model = TightBindingModel(t, tp)

    # Specify the minimum value of each variational parameter.
    # Note that this is done to avoid open shell issues.
    minabs_vpar = 1e-4;

    # Initialize determinantal variational parameters.
    determinantal_parameters = DeterminantalParameters(
        optimize, 
        tight_binding_model, 
        model_geometry, 
        minabs_vpar, 
        Ne, 
        pht
    )

    # Initialize density-density Jastrow variational parameters. 
    djastrow_parameters = JastrowParameters(
        "e-den-den",
        optimize, 
        model_geometry,
        rng
    )

    # Initialize the (fermionic) particle configuration cache.
    pconfig_cache = nothing

    ###########################
    ### OPTIMIZATION UPDATES ##
    ###########################

    # Record start time for optimization. 
    opt_start_time = time()

    # Iterate over optimization updates.
    for bin in 1:N_opts

        # Initialize the determinantal wavefunction.
        detwf = get_determinantal_wavefunction(
            tight_binding_model, 
            determinantal_parameters, 
            optimize, 
            Ne, 
            nup, 
            ndn, 
            model_geometry, 
            rng,
            pconfig_cache
        )   

        # Initialize the density-density Jastrow factor.
        djas_factor = get_jastrow_factor(
            djastrow_parameters,
            detwf,
            model_geometry,
            pht
        )

        # Iterate over equilibration/thermalization updates.
        for step in 1:N_equil 

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf, djas_factor) = local_fermion_update!(
                detwf, 
                djas_factor,
                djastrow_parameters,
                Ne, 
                model_geometry, 
                pht,
                n_stab_W,
                n_stab_T,
                δW, 
                δT,
                rng
            )

            # Record the acceptance rate for attempted local updates of the particle configuration.                                                         
            metadata["avg_acceptance_rate"] += acceptance_rate;
        end

        # Iterate over the number of optimization bins.
        for n in 1:opt_bin_size

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf, djas_factor) = local_fermion_update!(
                detwf, 
                djas_factor,
                djastrow_parameters,
                Ne, 
                model_geometry, 
                pht,
                n_stab_W,
                n_stab_T,
                δW, 
                δT,
                rng
            ) 
                                                                                                        
            # Record the acceptance rate for attempted local updates of the particle configuration                                                       
            metadata["avg_acceptance_rate"] += acceptance_rate;
            
            # Make measurements, with results being recorded in the measurement container. 
            make_measurements!(
                measurement_container, 
                detwf, 
                tight_binding_model, 
                djas_factor,
                djastrow_parameters,
                determinantal_parameters, 
                model_geometry, 
                Ne, 
                pht
            )

            # Write measurement for the current bin to file.
            write_measurements!(
                bin, 
                n, 
                measurement_container, 
                simulation_info
            )
        end

        # Record the last particle configuration used in the current bin. 
        pconfig_cache = detwf.pconfig

        # Process the measurement results, calculating error bars for all measurements. 
        # process_measurements(simulation_info, opt_bin_size)

        # Attempt an update to the variational parameters using the Stochastic Reconfiguration procedure. 
        stochastic_reconfiguration!( 
            measurement_container,  
            determinantal_parameters, 
            djastrow_parameters,
            η, 
            dt, 
            bin, 
            opt_bin_size
        )  
    end     

    # Record end time for optimization.
    opt_end_time = time()

    # Calculate the total time for optimization. 
    metadata["opt_time"] += opt_end_time - opt_start_time

    #########################
    ### SIMULATION UPDATES ##
    #########################

    # Record start time for the simulation.
    sim_start_time = time()

    # Iterate over simulation updates.
    for bin in 1:N_updates

        # Initialize the determinantal wavefunction.
        detwf = get_determinantal_wavefunction(
            tight_binding_model, 
            determinantal_parameters, 
            optimize, 
            Ne, 
            nup, 
            ndn, 
            model_geometry, 
            rng,
            pconfig_cache
        )   

        # Initialize the density-density Jastrow factor.
        djas_factor = get_jastrow_factor(
            djastrow_parameters,
            detwf,
            model_geometry,
            pht
        )

        # Iterate over equilibration/thermalization updates.
        for step in 1:N_equil 

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf, djas_factor) = local_fermion_update!(
                detwf, 
                Ne, 
                model_geometry, 
                n_stab_W,
                δW, 
                rng
            )

            # Record the acceptance rate for attempted local updates of the particle configuration.                                                         
            metadata["avg_acceptance_rate"] += acceptance_rate;
        end

        # Iterate over the number of simulation bins.
        for n in 1:bin_size

            # Attempt to update the fermionic particle configuration.
            (acceptance_rate, detwf) = local_fermion_update!(
                detwf, 
                djas_factor,
                djastrow_parameters,
                Ne, 
                model_geometry, 
                pht,
                n_stab_W,
                n_stab_T,
                δW, 
                δT,
                rng
            ) 
                                                                                                        
            # Record the acceptance rate for attempted local updates of the particle configuration                                                       
            metadata["avg_acceptance_rate"] += acceptance_rate;
            
            # Make measurements, with results being recorded in the measurement container. 
            make_measurements!(
                measurement_container, 
                detwf, 
                tight_binding_model, 
                djas_factor,
                djastrow_parameters,
                determinantal_parameters, 
                model_geometry, 
                Ne, 
                pht
            )

            # Write measurement for the current bin to file.
            write_measurements!(
                bin, 
                n, 
                measurement_container, 
                simulation_info
            )
        end

        # Record the last particle configuration used in the current bin. 
        pconfig_cache = detwf.pconfig

        # Process the measurement results, calculating error bars for all measurements. 
        # process_measurements(simulation_info, bin_size)
    end     

    # Record end time for the simulation. 
    sim_end_time = time()

    # Calculate total time for the simulation. 
    metadata["sim_time"] += sim_end_time - sim_start_time

    # Record the total runtime.
    metadata["total_time"] += metadata["opt_time"] + metadata["sim_time"]

    # # Write simulation summary TOML file.
    # save_simulation_info(simulation_info, metadata)

    return nothing
end 

# Only execute if the script is run directly from the command line.
if abspath(PROGRAM_FILE) == @__FILE__

    # Read in the command line arguments.
    sID = parse(Int, ARGS[1]) # simulation ID
    L = parse(Int, ARGS[2])
    U = parse(Float64, ARGS[3])
    nup = parse(Int, ARGS[4])
    ndn = parse(Int, ARGS[5])
    N_equil = parse(Int, ARGS[6])
    N_opts = parse(Int, ARGS[7])
    N_updates = parse(Int, ARGS[8])
    N_bins = parse(Int, ARGS[9])

    # Run the simulation.
    run_hubbard_square_simulation(sID, L, U, nup, ndn, N_equil, N_opts, N_updates, N_bins)
end