Mismatch of results in two different machine

Hi,
I am trying to reproduce the paper Phase diagram of the extended Bose–Hubbard model. For which I am using the following code

//test.cpp

#include <iostream>
#include <cmath>
#include <iomanip>
#include <cmath>
#include "itensor/all.h"
#include "meshgrid.hpp"

using namespace itensor;
using namespace std;
using namespace meshgen;

// Define the parameters
const Real hop = -1; // hopping parameter
const int L = 50;    // number of lattice sites
const int N = 50;    // number of particles
const double alpha = 0;
const int max_occ = 3; // maximum occupancy in a particular lattice site
const int max_value = 10;
const double h = 0.01;
const int max_size = (max_value / h) + 1;

int main()
{
    vector<double> u_nn(max_size, 0.0);
    linspace(u_nn.begin(), u_nn.end(), 0.0, h);
    // input value

    int c_point = 556;
    // cin >> c_point;

    double V = 0.0;
    double U_ONSITE = 5.0;
    double U_NN = u_nn[c_point];

    // ITensor code

    auto sites = Boson(N, {"MaxOcc", max_occ});

    // Create the Hamiltonian
    auto ampo = AutoMPO(sites);
    for (int i = 1; i <= N; ++i)
    {
        // hopping term
        if (i < N)
        {
            ampo += hop, "Adag", i, "A", i + 1;
            ampo += hop, "A", i, "Adag", i + 1;
        }
        // potential term
        ampo += V * cos(2 * M_PI * alpha * i), "N", i;
        // on-site interaction term
        ampo += (1.0 / 2.0) * U_ONSITE, "Adag", i, "Adag", i, "A", i, "A", i;
        // nearest-neighbor interaction term
        if (i < N)
        {
            ampo += U_NN, "N", i, "N", i + 1;
        }
    }
    auto H = toMPO(ampo);

    vector<int> box(L, 0);
    for (int i = 0; i < N; i++)
    {
        box[i % L] += 1;
    }

    // Create the initial MPS
    auto state = InitState(sites);
    for (int i = 1; i <= L; i++)
    {
        state.set(i, to_string(box[i - 1]));
    }
    auto psi = MPS(state);

    // Set up the DMRG sweeps
    auto sweeps = Sweeps(10);
    sweeps.maxdim() = 10000;
    sweeps.cutoff() = 1E-8;

    // Run the DMRG calculation
    auto [en0, psi0] = dmrg(H, psi, sweeps, {"Quiet=", true});

    auto wfs = vector<MPS>(1);
    wfs.at(0) = psi0;

    //
    // Here the Weight option sets the energy penalty for
    // psi1 having any overlap with psi0
    //
    auto [en1, psi1] = dmrg(H, wfs, psi, sweeps, {"Quiet=", true, "Weight=", 50.0});

    double ng = en1 - en0;
    cout << "ng: " << ng << endl;
    cout << "V: " << V << endl;
    cout << "U_ONSITE: " << U_ONSITE << endl;
    cout << "U_NN: " << U_NN << endl;

    return 0;
}

This produces different results in two different machines. On my laptop, when I am using

g++ -std=c++17 test.cpp -o test -I/home/debamalya/cpp_libraries/ITensor/ -I/home/debamalya/cpp_libraries/meshgen/include/ -L/home/debamalya/cpp_libraries/ITensor/lib -litensor -llapack -lblas

produces output

ng: 0.0212581
V: 0
U_ONSITE: 5
U_NN: 5.56

In a cluster of open PBS, when I am using

/home/debamalya/gcc_compiler/bin/g++ -std=c++17 test.cpp -o test -I/home/debamalya/cpp_libraries/ITensor/ -I/home/debamalya/cpp_libraries/meshgen/include/ -L/home/debamalya/cpp_libraries/ITensor/lib -litensor -llapack -lblas -pthread

produces output

ng: 0.0203305
V: 0
U_ONSITE: 5
U_NN: 5.56

There is a clear difference in ‘ng’. I am confused why there appears to be a difference and which one is correct!

The DMRG algorithm can produce slight variations if it isn’t fully converged. These could be from things like slight differences in the BLAS implementation used, for example. I would recommend trying a larger number of sweeps, and also importantly, using a smaller maxdim in the earlier few sweeps then gradually raising it, to get more robust convergence. Hopefully then in the limit of more sweeps you will see the answers coming much closer together.

Thank you for your reply. I found your suggestion helpful. I have tried with larger sweeps, and a gradual increase of maxdim helped me to get more accurate and almost similar results in both machines. Calculating the ground state does not create much problem, but to calculate the excited state, maxdim and the number of sweeps need to be much larger values.

Glad those changes helped. Yes, for excited states one typically does need to do many more sweeps. I would say about maxdim to be careful not to raise it too large too early. It’s usually a good strategy to do a large number of sweeps at a low maxdim (e.g. 40 sweeps at maxdim=20) to get the rough properties of the state correct, the raise maxdim later by doubling it every two sweeps or so.

Thanks for your suggestion. But I am a little confused. If I set sweeps = Sweeps(50);, then I need to set 50 maxdim values, or the last value of maxdim will be taken for the rest of the sweeps. For example, for 50 sweeps, I can choose something like

        sweeps = Sweeps(50);
        sweeps.maxdim() = 100, 600, 1100, 1600, 2100, 2600, 3100, 3600, 4100, 4600, 5100, 5600, 6100, 6600, 7100, 7600, 8100, 8600, 9100, 9600, 10100, 10600, 11100, 11600, 12100, 12600, 13100, 13600, 14100, 14600, 15100, 15600, 16100, 16600, 17100, 17600, 18100, 18600, 19100, 19600, 20100, 20600, 21100, 21600, 22100, 22600, 23100, 23600, 24100, 24600;
        sweeps.niter() = 6;
        sweeps.noise() = 0.0;
        sweeps.cutoff() = 1E-8;

.
Do “e.g. 40 sweeps at maxdim=20” means that I need to set one value as maxdim=20 for “sweeps = Sweeps(40)”?

Yes, so one drawback of the C++ interface is that you have to specify the maxdim() values by hand. In the Julia version you can pass arrays of these values, and Julia offers helpful syntax for generating such arrays, so it is easier and more flexible there.

But basically what I meant is something like this (for excited states or other challenging cases):

sweeps = Sweeps(40);
sweeps.maxdim() = 10,10,10,10,20,20,20,20,20,20,20,20,20,20,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,80,80,160,160,300,300,600,600; // I think that's 40 values...

If you stop the list of maxdim’s early, it will just use the last value in the list for the remaining sweeps.

You could make a small helper script to generate these tables of numbers if you want. Also in the future we could think about ways to support other input to Sweeps objects but we’ve mostly been focused on adding new features to the Julia version where such things are easier to do and have a bigger payoff.

Thanks for the explanation.
I am a beginner to DMRG, and for the learning purpose, I have started with the paper( Phase diagram of the extended Bose–Hubbard model), which I intend to reproduce using ITensor. The model extended Bose-Hubbard Hamiltonian, which has the form
H=-J\sum_ib_i^{\dagger}b_i+h.c+\frac{U}{2}\sum_in_i(n_i-1)+V\sum_in_in_{i+1}.
The calculation has been done with J=1, the maximum number of particles per site n_{max}=3 and the average filling \bar{n}=1. The neutral gap and charge gap are the easiest to calculate here. For this purpose I have used the following code

// test.cpp
#include <iostream>
#include <cmath>
#include <iomanip>
#include <cmath>
#include "itensor/all.h"
#include "meshgrid.hpp"

using namespace itensor;
using namespace std;
using namespace meshgen;

const int precision = 10;
const complex<double> z(0, 1);

const double hop = -1;
const int N = 50;      // number of particle
const int L = 50;      // number of lattice sites
const int max_occ = 3; // maximum occupancy per site

const int max_value = 10;
const double h = 0.01;
const int max_size = (max_value / h) + 1;

class hamiltonian
{
private:
    int L, N, max_occ;

    double U_ONSITE, U_NN;

    MPS psi;
    MPO H;
    Sweeps sweeps;

public:
    Real en0, en1;
    MPS psi0, psi1;

    // number of lattice sites
    // number of particles
    // maximum occupancy
    // onsite interaction
    // nearest neighour interaction

    hamiltonian(int m, int n, int o, double j, double k) : U_ONSITE(j), U_NN(k), L(m), N(n), max_occ(o)
    {
        auto sites = Boson(N, {"MaxOcc", max_occ});

        // Create the Hamiltonian
        auto ampo = AutoMPO(sites);
        for (int i = 1; i <= N; ++i)
        {
            // hopping term
            if (i < N)
            {
                ampo += hop, "Adag", i, "A", i + 1;
                ampo += hop, "A", i, "Adag", i + 1;
            }
            // on-site interaction term
            ampo += (1.0 / 2.0) * U_ONSITE, "Adag", i, "Adag", i, "A", i, "A", i;
            // nearest-neighbor interaction term
            if (i < N)
            {
                ampo += U_NN, "N", i, "N", i + 1;
            }
        }
        H = toMPO(ampo);

        vector<int> box(L, 0);
        for (int i = 0; i < N; i++)
        {
            box[i % L] += 1;
        }

        // Create the initial MPS
        auto state = InitState(sites);
        for (int i = 1; i <= L; i++)
        {
            state.set(i, to_string(box[i - 1]));
        }
        psi = MPS(state);
    }

    void ground_state()
    {
        sweeps = Sweeps(20);
        sweeps.maxdim() = 10, 20, 30, 40, 50, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300, 330, 360, 400;
        sweeps.niter() = 6;
        sweeps.noise() = 0.0;
        sweeps.cutoff() = 1E-8;

        // Run the DMRG calculation
        auto [en_g, psi_g] = dmrg(H, psi, sweeps, {"Quiet=", true});
        en0 = en_g;
        psi0 = psi_g;
    }

    void excited_state()
    {
        sweeps = Sweeps(50);
        sweeps.maxdim() = 10, 10, 10, 10, 10, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 160, 160, 160, 160, 160, 300, 300, 600, 600, 1200, 1200, 2400, 2400, 4800, 4800;
        sweeps.niter() = 6;
        sweeps.noise() = 0.0;
        sweeps.cutoff() = 1E-8;

        auto wfs = vector<MPS>(1);
        wfs.at(0) = psi0;
        auto [en_e, psi_e] = dmrg(H, wfs, psi, sweeps, {"Quiet=", true, "Weight=", 50.0});
        en1 = en_e;
        psi1 = psi_e;
    }
};

// number of lattice sites
// number of particles
// maximum occupancy
// onsite interaction
// nearest neighour interaction

class nutral_gap : public hamiltonian
{

public:
    double ng;
    nutral_gap(int m, int n, int o, double j, double k) : hamiltonian(m, n, o, j, k)
    {
        ground_state();
        excited_state();
        ng = en1 - en0;
    }
};

class charge_gap
{
public:
    double cg;
    charge_gap(int m, int n, int o, double j, double k)
    {
        hamiltonian Hl(m, n, o, j, k);
        hamiltonian Hll(m - 1, n - 1, o, j, k);
        hamiltonian HlL(m + 1, n + 1, o, j, k);
        Hl.ground_state();
        Hll.ground_state();
        HlL.ground_state();

        cg = HlL.en0 + Hll.en0 - (2 * Hl.en0);
    }
};

int main()
{
    vector<double> u_nn(max_size, 0.0);
    linspace(u_nn.begin(), u_nn.end(), 0.0, h);
    // input value

    int c_point;
    cin >> c_point;

    double U_ONSITE = 5.0;
    double U_NN = u_nn[c_point];

    nutral_gap NG(L, N, max_occ, U_ONSITE, U_NN);
    charge_gap CG(L, N, max_occ, U_ONSITE, U_NN);

    double ng = NG.ng;
    double cg = CG.cg;

    cout << setprecision(precision) << "ng: " << ng << endl;
    cout << setprecision(precision) << "cg: " << cg << endl;
    cout << "U_ONSITE: " << U_ONSITE << endl;
    cout << "U_NN: " << U_NN << endl;

    // const int column_width = 20;

    // ofstream file;

    // file.open("nutral_gap/" + to_string(c_point) + ".txt");
    // if (file.is_open())
    // {
    //     file << left << setw(column_width) << "V" << left << setw(column_width) << "U_ONSITE" << left << setw(column_width) << "U_NN";
    //     file << left << setw(column_width) << "nutral_gap";
    //     file << left << setw(column_width) << "C_POINT" << endl;

    //     file << left << setw(column_width) << V << left << setw(column_width) << U_ONSITE << left << setw(column_width) << U_NN;
    //     file << left << setw(column_width) << ng;
    //     file << left << setw(column_width) << c_point << endl;
    // }
    // file.close();

    // file.open("charge_gap/" + to_string(c_point) + ".txt");
    // if (file.is_open())
    // {
    //     file << left << setw(column_width) << "V" << left << setw(column_width) << "U_ONSITE" << left << setw(column_width) << "U_NN";
    //     file << left << setw(column_width) << "charge_gap";
    //     file << left << setw(column_width) << "C_POINT" << endl;

    //     file << left << setw(column_width) << V << left << setw(column_width) << U_ONSITE << left << setw(column_width) << U_NN;
    //     file << left << setw(column_width) << cg;
    //     file << left << setw(column_width) << c_point << endl;
    // }
    // file.close();
    return 0;
}

For each different value of c_point, one can obtain each point of the graph. The U in the Hamiltonian is same U_ONSITE in code, and similarly V in the Hamiltonian is same as U_NN in code. What I obtained is the following.

nutral_gap

I have no idea why the results do not resemble Figure 6 of that paper! The charge gap is also not matching, and it got negative values. It would be very much helpful if you put a comment or a suggestion.

Since this is a different question, could you please post it as a new topic? Also, generally we like to keep the questions mostly focused on the usage of the ITensor software (meaning how to use it to code methods and debug codes, etc.) but we do sometimes give advice about DMRG and related methods at a general level.

For questions about why certain numbers aren’t coming out the right way, it can be a lot of different things. The main one is whether or not your results are properly converged, so it can be important to vary the number of sweeps, the cutoff and maxdim, etc. The other main issues are usually about understanding the particularities of the system you are studying, like whether a gap is sensitive to boundary conditions, how strong finite-size effects might be (i.e. how much results depend on system size), etc.

Thank you for your reply.
I have posted a new topic for the same. If possible, to help this particular question type which I have newly posted, will be a great help to me. I understand that there might be some subtlety in the question, which might not be completely related to the ITensor itself but to other Physical things of the system itself. But if I get confident that my code implementation is correct and everything related to the code is correct, then I can try to connect to the author of that paper for more understanding. As per the suggestion, I have tried different approaches like changing the sweeps, maxdim etc, but the output does not change. On the other hand, they have explicitly mentioned WOPBC in their code implementation. I am trying to match the result for 50 particles in 50 lattice sites, which they have already done.