Here is the code to actually compute the MPSs:
#include "itensor/all.h"
#include "itensor/FermionSiteOddSitesParity.h"
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <map>
#include <random>
#include <chrono>
#include "itensor/util/print_macro.h"
using namespace itensor;
int main()
{
//
// Set up model parameters
//
int PhysicalLatticeLength = 5 ;
int numberOfParticles = 5;
int NumberFermionicSpecies = 2;
int L = PhysicalLatticeLength*NumberFermionicSpecies;
int Lp1by2m1 = (L+1)/2 - 1 ;
println("L is \n");
std::cout << L << " \n ";
auto minW = 1.0 ;
auto maxW = 1.0 ;
auto deltaW = 0.5 ;
int NumberWIndices = floor((maxW - minW)/ deltaW + 1.01);
int NumberOfStates = 9; // 5
double EnergySpectrum[NumberWIndices][NumberOfStates];
double randphysicalonebodypot[PhysicalLatticeLength];
double randonebodypotential[L];
double overlapMatrix[NumberWIndices][NumberOfStates][NumberOfStates];
double globalParityArray[NumberWIndices][NumberOfStates];
int globalNumberTemp;
std::vector<int> globalNumberVector;
// Build the space, conserving Fermionic particle number
auto psites = FermionOddSitePar(L,{"ConserveNf",true, "ConservePfodd", true});
// auto psites = Fermion(L,{"ConserveNf",true});
// Parameters for Iemini et al. Wire Model
double t = 1.0 ; // Tunneling sets energy & time scale
double U = 0.5;
double G = 0.0;
// W will be swept over
//
// Build Hamiltonian and initial MPS state to stick into DMRG
//
for( auto WIndex: range(NumberWIndices) )
{ // start Delta Inex loop
auto W = minW + deltaW*WIndex;
println("About to begin the calculation for W = \n" );
std::cout << W << " \n ";
// Create the operators for building the Hamiltonian
auto ampo = AutoMPO(psites);
// Nearest Neighbor Couplings
for( auto ii:range1(PhysicalLatticeLength-1))
{
// Tunneling for all for both wires species
ampo += -t, "Cdag", NumberFermionicSpecies*ii-1, "C", NumberFermionicSpecies*ii+1; // Wire a
ampo += -t, "Cdag", NumberFermionicSpecies*ii+1, "C", NumberFermionicSpecies*ii-1; // Wire a
ampo += -t, "Cdag", NumberFermionicSpecies*ii-0, "C", NumberFermionicSpecies*ii+2; // Wire b
ampo += -t, "Cdag", NumberFermionicSpecies*ii+2, "C", NumberFermionicSpecies*ii-0; // Wire b
// Potential Term of adjascent sites
ampo += -2*U, "N", NumberFermionicSpecies*ii-1, "N", NumberFermionicSpecies*ii+1;
ampo += -2*U, "N", NumberFermionicSpecies*ii-0, "N", NumberFermionicSpecies*ii+2;
// Nearest-Neighbor Cooper Pair exchange between wires
ampo += W, "Cdag", NumberFermionicSpecies*ii-0, "Cdag", NumberFermionicSpecies*ii+2, "C", NumberFermionicSpecies*ii+1, "C", NumberFermionicSpecies*ii-1;
ampo += W, "Cdag", NumberFermionicSpecies*ii-1, "Cdag", NumberFermionicSpecies*ii+1, "C", NumberFermionicSpecies*ii+2, "C", NumberFermionicSpecies*ii-0;
}
// Single-site Terms
for( auto ii:range1(PhysicalLatticeLength))
{
// One-body term to set zero
ampo += 2*U, "N", NumberFermionicSpecies*ii-1; // na(j)
ampo += 2*U, "N", NumberFermionicSpecies*ii-0; // nb(j)
}
// Generate the Hamiltonian
auto H = toMPO(ampo);
// Generating an initial state over the space specified above
auto state = InitState(psites);
// Set the initial state to be an MPS over the set of sites call psites
//// With defined QNs from FermionSiteOddSitesParity
// Set QNs for total particle number and fixed-parity
for( int ll : range1(L))
{
state.set(ll,"0");
}
for( int ll : range1(numberOfParticles) )
{
state.set(2*ll-1,"1");
}
// Generate the initial MPS to actually use
auto psi0 = state;
//
// Set DMRG properties
//
for( auto ii : range1(L) ) if( ii%2==1 ) state.set(ii,"Occ");
// Set the number of DMRG sweeps
auto sweeps = Sweeps(8);
// Set the maximum bond dimension per sweep
// Setting it low initially allows the computation to be much faster
sweeps.maxdim() = 80, 80, 100, 150, 150, 250, 350, 400;
// Set the cutoff for the bond dimension
sweeps.cutoff() = 1E-6, 1E-6, 1E-8, 1E-8, 1E-10, 1E-10, 1E-10, 1E-11;
// Set the number of Davidson iterations per sweep
sweeps.niter() = 4, 4, 4, 3, 3, 3, 2, 2;
// Set the noise to add to the MPS itself each sweep
sweeps.noise() = 1E-5, 1E-5, 1E-6, 1E-7, 1E-8, 1E-10, 1E-10, 1E-11;
//
// Run the DMRG
//
int nstates = NumberOfStates;
std::vector<MPS> states;
std::vector<double> energies;
// int nn = 0 ;
auto [energy,psi] = dmrg(H,states,psi0,sweeps,"Silent");
//// Calculate the energy and state
//// Well, that was short
states.push_back(psi);
energies.push_back(energy);
// Can save ITensor objects to file, but not generic C data arrays - not great?
writeToFile("psig", psi);
EnergySpectrum[WIndex][0] = energy;
for(int nn = 1; nn < nstates; nn++){
auto [energye,psie] = dmrg(H,states,psi0,sweeps,{"Silent=", false, "Weight=", 5.0});
states.push_back(psie);
energies.push_back(energye);
EnergySpectrum[WIndex][nn] = energye;
}
//
// Post-process the MPS for parity
//
for( int nn : range(NumberOfStates) )
{
psi = states[nn];
psi.noPrime();
// 'gauge' the MPS to site 1
// // any 'position' between 1 and L, inclusive, would work here
psi.position(1);
psidag = dag(psi);
for( int kk =1; kk < L; ++kk){
auto kkSiteInd = siteIndex(psi, kk) ;
if(kk%2==1) psidag.prime(kkSiteInd) ;
}
psidag.prime("Link");
auto lii_1 = leftLinkIndex(psi,1);
psi.prime(lii_1);
auto Par = psi(1);
Par *= op(psites,"F",1) ; // op(psites,"Id",1) - 2*op(psites,"N", 1);
Par *= psidag(1);
for(int kk = 2; kk < L; ++kk)
{
if( kk%2==1 ){ // odd sites, calculate 1-2N
// Need to count the number of Fermions between sites ii and jj
//// including phase for Jordan-Wigner string
Par *= psi(kk);
Par *= op(psites,"F",kk); // this is the operator we need!
// op(psites,"Id",kk) - 2*op(psites,"N", kk);
// J-W string - need to add the "F" argument in between
//// to get proper phase with Fermions
Par *= psidag(kk);
std::cout << " Computed parity operator contribution for an odd site ";
} else { // even sites, use an identity operator
// Need to count the number of Fermions between sites ii and jj
//// including phase for Jordan-Wigner string
Par *= psi(kk);
// Par *= op(psites,"F",kk); // Dont need for commuting operators?
// J-W string - need to add the "F" argument in between
//// to get proper phase with Fermions
Par *= psidag(kk);
std::cout << " Computed parity operator contribution for an even site ";
} // else an even site
std::cout << " Computed parity operator contribution for site " << kk << " \n " << " \n ";
} // over site kk
// Final site
// index linking jj to jj+1 and use an identity operator
auto lL = rightLinkIndex(psi,L);
Par *= prime(psi(L),lL);
// Like above for site ii, prime again at site jj (this time on the RHS)
// Par *= op(psites,"F",L);
Par *= psidag(L);
std::cout << " Computed parity operator contribution for site " << L << " \n ";
std::cout << " About to print the operator Par" << " \n ";
Print(Par);
std::cout << " \n " << " Printed the operator Par" << " \n ";
double Parityresult = elt(Par); // use eltC if expecting complex
globalParityArray[WIndex][nn] = Parityresult;
} // nn state of interest loop
for( int nn : range(NumberOfStates) )
{
auto psinn=states[nn];
for(int mm : range(NumberOfStates))
{
auto psimm=states[mm] ;
overlapMatrix[WIndex][nn][mm]=inner(psinn,psimm);
} // over mm
} // over nn
println("Energies at this parameter set are \n" );
for( int nn : range(NumberOfStates) )
{
std::cout << EnergySpectrum[WIndex][nn] << " \n ";
}
println("Global Parities of Eigenstates at this parameter set are \n" );
for( int nn : range(NumberOfStates) )
{
std::cout << globalParityArray[WIndex][nn] << " \n ";
}
} // WIndex ending loop
string parityFileName = "ParityDoubleWire_L" + str(L) + "_N" + str(numberOfParticles) + ".txt" ;
std::ofstream paritySpectrumFile(parityFileName); // ("EnergySpectrumFile.txt");
paritySpectrumFile << NumberWIndices << NumberOfStates << "\n";
for (int i = 0; i < NumberWIndices; i++)
{
for (int n = 0; n < NumberOfStates; n++)
{
paritySpectrumFile << globalParityArray[i][n] << " ";
}
paritySpectrumFile << "\n";
}
string energyFileName = "EnergiesDoubleWire_L" + str(L) + "_N" + str(numberOfParticles) + ".txt" ;
std::ofstream EnergySpectrumFile(energyFileName); // ("EnergySpectrumFile.txt");
EnergySpectrumFile << NumberWIndices << NumberOfStates << "\n";
for (int i = 0; i < NumberWIndices; i++)
{
for (int n = 0; n < NumberOfStates; n++)
{
EnergySpectrumFile << EnergySpectrum[i][n] << " ";
}
EnergySpectrumFile << "\n";
}
string overlapFileName = "overlapMatrixDoubleWire_L" + str(L) + "_N" + str(numberOfParticles) + ".txt" ;
std::ofstream overlapFile(overlapFileName);
overlapFile << NumberWIndices*NumberOfStates << " " << L << "\n";
for (int i = 0; i < NumberWIndices; i++)
{
for (int nn = 0; nn < NumberOfStates; nn++)
{
for (int mm = 0; mm < NumberOfStates; mm++)
{
overlapFile << overlapMatrix[i][nn][mm] << " ";
} // j
overlapFile << "\n";
} // n
} // i
return 0;
}
The energies printed are noticably out of order, and only some have a defined odd-site (single wire) parity.
Energies at this parameter set are:
-2.2028
-2.22071
-1.46725
-2.21413
-2.20282
-1.95795
-1.95795
-1.62416
-1.69117
Global Parity Measurements of Eigenstates are
-1
1
1
-0.997684
0.997674
0.999984
-0.999984
0.859696
-0.678753
Of course, the energies can be tweaked by playing with the term that is proportional to the overlap between the states, but it doesn't seem to be conserving the parity. Any insight would be greatly appreciated.