/*

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

Suggestions to try out

  1. 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.
  2. 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.
  3. Reload the applet to reset the lambda's to 0.
  4. 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.
  5. 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.
  6. 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.
  7. 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!
  8. 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!
  9. 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) .
  10. 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.
  11. 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

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) ) )); } */