/*
Bethe ansatz solution of the Lieb-Liniger model
What this is about
The Lieb-Liniger Hamiltonian describes N bosons on a ring, interacting with a repulsive Dirac delta function potential. This is a 1D interacting quantum many-body problem. It can be solved using the Bethe ansatz technique. Roughly speaking, the eigenfunctions of the N-particle Hamiltonian consist of linear combinations of plane waves, with momenta k_n and coefficients a_...; ... the boundary conditions on the wavefunction translate into a set of N non-linear equations for the k's, (U is the strength of the potential; lambda are integers; different choices of lambda lead to different eigenfunctions)
k_m - lambda_m = sum_n arctan (U/(k_m-k_n))/(pi) .
These are identical to the force-balance equations for a set of N classical particles in 1D with positions k_m, attached to anchors at integer positions lambda_m with harmonic springs (or pendula), and repelling each other with forces arctan (U/(k_m-k_m))/(pi). For the case of a repulsive potential (positive U), the equations can easily be solved by simulating the mechanical system with suitable damping!
Thus:
The problem of finding an eigenstate of an interacting 1D quantum system with short-range interactions
is equivalent to
the problem of finding the ground state of an interacting 1D classical system with long-range interactions.
How to play
- Use the left and right keys to move the black arrow.
- Use the , and . keys (< and >) to move the anchor at the current location.
- If the keys don't work, click on the picture to make sure it has focus (indicated by a blue rectangle).
- Click and drag inside the picture to zoom in and out.
Suggestions to try out
- The applet starts with all the anchors (Bethe quantum numbers, lambda_n) set to zero. This gives the wavevectors for the ground state of the quantum system. Now, press the > key to move one anchor to the right, so that one of the lambda's is now 1. The balls adjust to find a new equilibrium; their new positions represent the Bethe wavevectors in the first excited state of the quantum system.
- Press the Left key and the > key repeatedly to move all anchors to the right, so that all the lambda's are equal to 1. This leads to an equilibrium configuration that is identical to the one with all lambda's equal to 0, except that everything is shifted to the right by 1 unit. You have found an excited state of the quantum system that is identical to the ground state, except for an extra factor of exp(2*pi*i*x). The classical system contains only pairwise repulsive forces, so it is translationally invariant. This is a consequence of the Galilean invariance of the quantum problem.
- Reload the applet to reset the lambda's to 0.
- Free Bose gas: First, turn off the interaction by setting U=0. You will see that all the balls collapse together at the lowest point of the pendulum. Indeed, the ground-state wavefunction of the non-interacting Bose gas is simply a constant, which corresponds to all Bethe wavevectors being zero.
- Press the > key to move one anchor. This drags along one ball, but leaves all the others alone. Thus, the first excited state of the system has a single-particle character.
- Girardeau gas: Now try making the interaction infinitely strong (U=100000) instead. The slider only goes up to 100, so you will have to click on the box and type in 100000. This corresponds to the Girardeau model of impenetrable, 'hard-core' bosons. In the mechanical analog[ue], the bosons repel each other with forces F=1, thereby keeping each other at arm's length -- that is, at distance 1 unit.
- Boson-fermion equivalence:
Girardeau showed that the Bose gas with U=infinity is equivalent to a non-interacting Fermi gas, provided that the number of bosons, N, is odd. You can verify this by keeping U=100000 and changing N. For example, when N=5 and all the lambda's are 0, you will find that k=-2,-1,0,1,2. These k's correspond to the lowest 5 single-particle states for non-interacting particles. Hence, instead of condensing into the state with k=0, the bosons pile up to form a "Bose sea", just like fermions forming a Fermi sea!
- You can also confirm that the excited states are obtained by making single-particle excitations out of the Bose sea. Indeed, moving one anchor causes its ball to move, but leaves every other ball alone -- I just learnt that from my applet!
- Thermodynamic limit: Now for something more complicated. First of all, click and drag inside the picture until the numbers -50 to 50 fit within the top ruler. Now, click and hold the "up" button for N until you have generated N=100 balls. There is now a dense distribution of wavevectors. This corresponds to one of the distributions in
Fig. 2 of the Lieb-Liniger paper (page 1611)
.
-
By tuning U up and down, you can see that the distribution becomes wider or narrower, just as Liniger's results showed. If you are observant, you may also notice that the shape of the distribution changes from a top hat at large U to a peaked function at small U (as Liniger showed). Of course, Liniger accomplished the calculation for N=infinity by solving the inhomogeneous Fredholm equation, rather than by simulating N=100 balls as we do here.
- Excitation energies: Remember to look at the value of the energy E shown in the lower left corner of the applet.
Comments specific to this applet
- I have designed the interface so that you can never make two pendula cross each other. That would lead to wrong answers.
- The forces are calculated based on the horizontal positions of the balls. The vertical positions mean nothing. (I made the applet this way because pendula are easier to draw than springs.)
- I have scaled the k_n and lambda_n to remove factors of 2*pi from the equations. The potential U and energy E are quoted in arbitrary units (here, E is simply the sum of squares of the k's).
- For large N and small U, the simple iterative algorithm may become unstable. This shouldn't be difficult to fix.
- It is a non-trivial matter to generalize the calculation to negative U, because the Bethe ansatz equations develop complex solutions, and the iterative procedure becomes unstable!
- If your screen is too small to view the text and the applet at the same time, you can open another identical browser window. (Duh.)
Credits and references
*/
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.net.URL;
import javax.swing.*;
import javax.swing.event.*; // for changelistener
import javax.swing.JSpinner.NumberEditor;
import java.util.Hashtable; // for label tables for slider
import java.text.DecimalFormat;
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// no need to trigger resize anymore
public class lieb extends JApplet {
LiebPanel panelC;
Container contentPane;
JPanel panelN;
JPanel panelS;
JPanel panelW;
JPanel panelE;
JSpinner spinnerN;
JSpinner spinnerU;
JSlider sliderU;
JLabel labell;
JLabel labelk;
JLabel labelE;
JButton buttonZoomIn,buttonZoomOut;
final int nUmin=0,nUmax=0x7FFFFFF0;
final double scaleU=10.0; // 1000
Hashtable labelTable;
public int nu_from_u(double U) {
return (int)Math.round( (nUmax+1)*Math.atan(U/scaleU)/Math.PI*2 );}
public double u_from_nu(int nU) {
return scaleU*Math.tan(Math.PI/2*nU/(nUmax+1.0d));}
public void addLabel(double U) {
int nU;
//DecimalFormat nf = new DecimalFormat("##0.#####E0");
DecimalFormat nf = new DecimalFormat("##0.###");
nf.setGroupingUsed(false);
nf.setMaximumFractionDigits(0);
nf.setMinimumFractionDigits(0);
nU=nu_from_u(U);
JLabel label1=new JLabel(nf.format(U));
Font font1;
font1 = new Font("MONOSPACED", Font.PLAIN, 12);
label1.setFont(font1);
labelTable.put(new Integer(nU), label1);
}
public void init() {
int n;
int bosonsNMax;
Font font1;
contentPane = getContentPane();
font1 = new Font("MONOSPACED", Font.PLAIN, 12);
panelC = new LiebPanel(); panelC.applet1 = this;
bosonsNMax=panelC.bosonsNMax;
panelC.myInit();
panelE = new JPanel();
panelW = new JPanel();
panelN = new JPanel();
panelS = new JPanel();
contentPane.setLayout(new BorderLayout());
contentPane.add(panelC, BorderLayout.CENTER);
contentPane.add(panelE, BorderLayout.EAST);
contentPane.add(panelW, BorderLayout.WEST);
contentPane.add(panelN, BorderLayout.NORTH);
contentPane.add(panelS, BorderLayout.SOUTH);
// Construct the controls
// Begin with N=3 and U=3.0
spinnerN = new JSpinner(new SpinnerNumberModel(3, 1, bosonsNMax, 1));
spinnerN.setMaximumSize(new Dimension(2000, (int)spinnerN.getPreferredSize().getHeight()));
spinnerU = new JSpinner(new SpinnerNumberModel(3.0d, 0.0, 1.0e8d, 0.1d ));
spinnerU.setPreferredSize(new Dimension(100, (int)spinnerU.getPreferredSize().getHeight()));
spinnerU.setEditor(new NumberEditor(spinnerU, "##0.###")); // Eureka!
sliderU = new JSlider(JSlider.HORIZONTAL, nUmin, nUmax, nUmin);
//sliderU.setMajorTickSpacing(10*nUstep);
//sliderU.setMinorTickSpacing(1*nUstep);
//sliderU.setPaintTicks(true);
labelTable = new Hashtable();
addLabel(0.0d);
addLabel(1.0d);
addLabel(2.0d);
addLabel(3.0d);
addLabel(40d);
addLabel(5.0d);
addLabel(10.0d);
addLabel(20.0d);
addLabel(50.0d);
addLabel(100000.0d);
sliderU.setLabelTable( labelTable );
sliderU.setPaintLabels(true);
sliderU.setPreferredSize(new Dimension(512, (int)sliderU.getPreferredSize().getHeight()));
// Set up the GUI
panelN.add(new JLabel("Bosons, N:")); // Use FlowLayout
panelN.add(spinnerN);
panelN.add(new JLabel("Potential, U:"));
panelN.add(spinnerU);
panelN.add(sliderU);
panelS.setLayout(new BoxLayout(panelS, BoxLayout.PAGE_AXIS));
panelS.add(labell = new JLabel());
labell.setFont(font1); //inherit from parent
panelS.add(labelk = new JLabel());
labelk.setFont(font1); //inherit from parent
panelS.add(labelE = new JLabel());
labelE.setFont(font1); //inherit from parent
// Register listeners
panelC.addMouseListener(panelC);
panelC.addMouseMotionListener(panelC);
spinnerN.addChangeListener(panelC); // when SPINNER changes, tell PANEL
spinnerU.addChangeListener(panelC); // when SPINNER changes, tell PANEL
sliderU.addChangeListener(panelC); // when SLIDER changes, tell PANEL
//Pretend we just clicked the spinners
panelC.stateChanged(new ChangeEvent(spinnerN));
panelC.stateChanged(new ChangeEvent(spinnerU));
panelC.setFocusable(true);
System.out.println (panelC.requestFocusInWindow());
}
}
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
class LiebPanel extends Panel
implements Runnable, ComponentListener, ChangeListener, KeyListener, MouseListener, MouseMotionListener {
lieb applet1;
Thread thread1=null;
Image image1;
Image imAnchor,imBall,imDownArrow;
String message1;
int size0x,size0y;
int size1x,size1y,dx1,dy1,xoff,yoff;
int imx,imy;
final int bosonsNMax=100; // Maximum possible
int bosonsN;
double ll[],kk[],ff[]; //lambda_n, k_n, f_m=sum_n theta_{mn} ...
double uu[];
int delay;
double potentialU=3.0; // adjust with slider
final double dt=0.10; // iteration time step
int lSelected = 0;
double scalex;
//========== My own methods ==========
public int sgn(int i) {return (i>0?1:0)-(i<0?1:0);}
public void myInit() {
try {
imAnchor = applet1.getImage(applet1.getCodeBase(), "anchor.png");
imBall = applet1.getImage(applet1.getCodeBase(), "ball.red.gif");
imDownArrow = applet1.getImage(applet1.getCodeBase(), "downarrow.png");
} catch (Exception e2) {
System.err.println ("Couldn't load images!");
}
int idum;
try {idum=Integer.parseInt(applet1.getParameter("DELAY"));}
catch (Exception e) {idum=100;} delay = idum;
ll = new double[bosonsNMax]; // fix this
kk = new double[bosonsNMax];
ff = new double[bosonsNMax];
uu = new double[bosonsNMax];
bosonsN=5;
int n;
for (n=0; nll[n]) {
double d;
d=ll[m];ll[m]=ll[n];ll[n]=d;
d=kk[m];kk[m]=kk[n];kk[n]=d;
}
}
}
}
public void myPaint(Graphics realg) {
realg.drawImage (image1, 0, 0, null);
}
public void myUpdate() {
final int tickLength=7;
int n,m;
double kmn,f;
int YBEAM=80;
int YLOWER=size1y-32;
int PENDLEN=YLOWER-YBEAM-32;
int X1,Y1,X2,Y2,XD,YD;
int bosonsNNew;
Graphics g;
String str1;
if (image1==null) return;
g=image1.getGraphics();
for (m=0; m 100) {lstep=10; lmin/=10;lmin*=10;}
else if (lmax-lmin > 60) {lstep=5; lmin/=5;lmin*=5;}
else if (lmax-lmin > 30) {lstep=2; lmin/=2;lmin*=2;}
for (l=lmin; l<=lmax; l+=lstep) {
X1 = (int)(size1x/2 + l*scalex);
g.drawLine(X1,YBEAM,X1,YBEAM-tickLength);
str1=Integer.toString(l);
g.drawString(str1, X1-fm.stringWidth(str1)/2, YBEAM+16);
}
for (n=0; nPENDLEN) flagNeedToZoom=true;
YD = (int)Math.sqrt(PENDLEN*PENDLEN - XD*XD); // need to catch excep
Y1 = YBEAM;
Y2 = YBEAM + YD;
g.setColor (Color.black);
g.drawLine(X1,Y1,X2,Y2);
if ((int)ll[n]==llast) {xshf+=2;yshf-=1;}
else {xshf=0;yshf=0;llast=(int)ll[n];}
g.drawImage(imAnchor, X1-imx/2+xshf, Y1-imy+yshf, null); // anchors
g.drawImage(imBall, X2-imx/2, Y2-imy/2, null); // balls
//g.setColor(Color.white);
//g.drawString(Integer.toString(n), X2-3, Y2+3);
}
X1 =(int)( size1x/2 + lSelected*scalex);
Y1 = YBEAM;
g.drawImage(imDownArrow, X1-imx/2, Y1-imy*5/2, null); // balls
if (flagNeedToZoom) {scalex*=0.9d; System.out.println("easdf");}
if (isFocusOwner()) {
g.setColor(Color.blue);
int bord=5;
g.drawRect(0+bord, 0+bord, size1x-1-2*bord, size1y-1-2*bord);
}
}
String rightJustify(String s, int length, char pad) {
StringBuffer buffer = new StringBuffer(s);
while (buffer.length() < length) buffer.insert(0, pad);
return buffer.toString();
}
public void run() {
while (true) {
myUpdate(); repaint();
try {Thread.sleep(delay);} catch (Exception e) {}
}
}
public void paint(Graphics g) {myPaint(g);}
public void update(Graphics g){myPaint(g);}
//========== Implement ComponentListener ==========
public void componentHidden(ComponentEvent e){}
public void componentMoved(ComponentEvent e){}
public void componentShown(ComponentEvent e) {}
public void componentResized(ComponentEvent e) {
size0x=getSize().width; size0y=getSize().height;
size1x=size0x; size1y=size0y;
imx=24; imy=imx;
image1=createImage(size1x, size1y);
imAnchor = imAnchor.getScaledInstance(imx, imy, Image.SCALE_SMOOTH);
imBall = imBall.getScaledInstance(imx, imy, Image.SCALE_SMOOTH);
imDownArrow = imDownArrow.getScaledInstance(imx, imy*3/2, Image.SCALE_SMOOTH);
myUpdate(); repaint();
}
//========== Implement KeyListener ==========
public void keyPressed (KeyEvent e) {
int keycode;
int n;
keycode = e.getKeyCode();
switch (keycode) {
case KeyEvent.VK_RIGHT:
lSelected++;
break;
case KeyEvent.VK_LEFT:
lSelected--;
break;
case KeyEvent.VK_COMMA:
// Find the leftmost anchor at position lSelected,....
for (n=0; n<=bosonsN-1; n++) if (ll[n]==lSelected) break;
if (n<=bosonsN-1) ll[n]--;
lSelected--;
break;
case KeyEvent.VK_PERIOD:
// Find the rightmost anchor at position lSelected, and move it right
for (n=bosonsN-1; n>=0; n--) if (ll[n]==lSelected) break;
if (n>=0) ll[n]++;
lSelected++;
break;
}
}
public void keyReleased (KeyEvent e) {}
public void keyTyped (KeyEvent e) {}
//========== Implement ChangeListener ==========
public void stateChanged(ChangeEvent e) {
if (e.getSource()==applet1.spinnerN) {
bosonsN=Integer.parseInt(applet1.spinnerN.getValue().toString());
myReInit();
myUpdate(); repaint(); // better respond immediately to change of N!
} else if (e.getSource()==applet1.spinnerU) {
potentialU=Float.parseFloat(applet1.spinnerU.getValue().toString());
applet1.sliderU.setValue(applet1.nu_from_u(potentialU));
} else if (e.getSource()==applet1.sliderU) {
int nU=applet1.sliderU.getValue();
applet1.spinnerU.setValue(new Double(applet1.u_from_nu(nU)));
//if (!applet1.sliderU.getValueIsAdjusting()) {
}
}
//========== Implement MouseMotionListener ==========
int dragX,dragY;
public void mouseMoved (MouseEvent e) {}
public void mouseDragged (MouseEvent e) {
int X2=e.getX(),Y2=e.getY();
int dX =X2-dragX;
int dY =X2-dragY;
scalex *= 1.0d + 0.02d*Math.atan(dX);
dragX=X2;dragY=Y2;
}
//========== Implement MouseListener ==========
public void mouseEntered (MouseEvent e) {}
public void mouseExited (MouseEvent e) {}
public void mousePressed (MouseEvent e) {}
public void mouseReleased (MouseEvent e) {}
public void mouseClicked (MouseEvent e) {}
}
//bosonsNNew = (((SpinnerNumberModel)applet1.spinnerN.getValue()).getNumber()).intValue();
/*
SpinnerModel dateModel = dateSpinner.getModel();
if (dateModel instanceof SpinnerDateModel) {
setSeasonalColor(((SpinnerDateModel)dateModel).getDate());
}*/
//DecimalFormat nf = new DecimalFormat("#0.000 ");
/*
public int nu_from_u(double U) {
return (int)Math.round( (nUmax+1)*(1-Math.exp(-U/scaleU)) );}
public double u_from_nu(int nU) {
return scaleU*Math.log(1/(1 - nU/(nUmax+1.0d)));}
*/
/*
public int nu_from_u(double U) {
return (int)Math.round( (nUmax+1)*(uscale*Math.log(U)) );}
public double u_from_nu(int nU) {
return Math.exp(nU/(nUmax+1.0d)/uscale) ;}
*/
/*
public int nu_from_u(double U) {
return (int)Math.round( (nUmax+1)* uscale/(1+1/(Math.log(1+U)))) ;}
public double u_from_nu(int nU) {
double x=nU/(nUmax+1.0);
return Math.exp(1/(uscale/x-1)-1) ;}
*/
/*
for (nU=nUmin; nU<=nUmax; nU+=nUstep) {
labelTable.put( new Integer(nU),
new JLabel(nf.format( u_from_nu(nU) ) ));
}
*/