User Guide
A step-by-step guide to using XDiag
Installation
Julia
Enter the package mode using ]
in the Julia REPL and type:
That's it!
C++
For using the C++ version, we need to compile the library first. To do so, we first download the code using git,
and then compile the library using CMake,
The resulting library is now installed at /path/to/where/xdiag/should/be/install
. There are various options when compiling, including optimizations which can be used. For more details on the compilation process, we refer to the Compilation guide.
First steps
Writing code
Let us set up our first program using the xdiag
library.
The function say_hello()
prints out a welcome message, which also contains information which exact XDiag version is used. In Julia this is all there is to it.
What is maybe a bit unfamiliar is the try / catch
block in C++. XDiag implements a traceback mechanism for runtime errors, which is activated by the error_trace
function. While not stricly necessary here, it is a good practice to make use of this.
Compilation
In C++, now that the application program is written, we next need to set up the compilation instructions using CMake. To do so we create a second file called CMakeLists.txt
in the same directory.
cmake_minimum_required(VERSION 3.19)
project(hello_world)
find_package(xdiag REQUIRED HINTS "/path/to/where/xdiag/should/be/install")
add_executable(main main.cpp)
target_link_libraries(main PRIVATE xdiag::xdiag)
You should replace /path/to/where/xdiag/should/be/install
with the appropriate directory where your XDiag library is installed after compilation. We then compile the application code,
Hilbert spaces
We are now ready to run our first actual calculation using XDiag. Our immediate goal will be to determine the ground state energy of a \(S=1/2\) Heisenberg model on a 1D chain lattice with periodic boundary conditions,
where \(\mathbf{S}_i = (S^x_i, S^y_i, S^z_i)\) denotes the vector of spin matrices at a given site \(i\). The notation \(\langle i, j\rangle\) refers to summatation over neighboring sites \(i\) and \(j\).
The first thing to define before any computation, is the Hilbert space our model will be defined on. For the present example, we use the Hilbert space class Spinhalf. Further possible Hilbert spaces include tJ and Electron, see Blocks. We consider a chain lattice with \(N=8\) sites and create a \(S=1/2\) Hilbert space:
We would like to know which spin configurations, the Hilbert space is made up of. To do so, we can iterate over the Hilbert space and print out the spin configurations.
This produces an output similar to the following:
Here we already see several things at work. XDiag features a convenient way to write logs in C++, with the Log class. The first argument to Log()
is a format string. In C++ we use the fmt library, to be able to write structured format and format our output. The second argument turns our spins
into a string. spins
is of type ProductState, whose configuration on each site can be individually addressed.
Further, we notice that all \(2^N\) spin configurations are included in this Hilbert space. However, the Heisenberg model conserves the total \(S^z = \sum_i S^z_i\), and thus we could limit ourselves to a block of the Hilbert space, which only contains configurations of a certain magnetization:
This produces an output similar to:
We see that the block now only contains configurations, where the total number of spins pointing up is 4. We can now quickly check the dimension of the Hilbert spaces, and confirm that the dimension of the block is reduced from \(2^8=256\) to \(\begin{pmatrix} 8 \\ 4 \end{pmatrix} = 70\),
which should print:
Here, we introduced another functionality of XDiag in C++, the XDIAG_SHOW macro which can be used for quick debug printing of XDiag objects.
Operators
Next, we define our Hamiltonian. We do so by using an OpSum object. These encode sums of operators of the form
Here, \(\mathcal{O}_i\) denote single operators described as Op objects and \(c_i\) are the coupling constants. These can either be a real or complex number, or a string which later needs to be replaced. THe Hamiltonian of the spin \(S=1/2\) Heisenberg chain is created like this:
We first create an empty OpSum and then add additional terms to it. The first part of the product denotes the coupling constant, here given as a string. Alternatively, one could have directly used real / complex numbers here. The second part of the product is a single Op object. It is created with two inputs:
- The type, here
SdotS
denoting an operator of the form \(\mathbf{S}_i\cdot\mathbf{S}_j\). XDiag features a wide variety of operator types, see Operator types. - An array defining which site the operator lives on. Notice, that in julia we start counting the sites from 1, while in C++ we start counting the sites from 0.
Ground states
Now our Hamiltonian is defined, we can directly compute the ground state and its energy using the function eig0:
Here, e0
is a double precision real number and psi0
is a State object. The eig0 uses an iterative algorithms to compute the ground state by applying the Hamiltonian in a matrix-free manner, and is thus very useful for large system sizes. It is based on a Lanczos algorithm, which can also be called directly using eigs_lanczos which offers more control on the behavior of the algorithm.
Alternatively, we can also perform a full diagonalization by computing the full Hamiltonian matrix using the matrix function.
Notice, that we are using the Armadillo library with the arma
namespace in C++. This library serves as the linear algebra backend of XDiag and can be used to perform further calculations. In Julia, the eigen
and Symmetric
functions are part of the LinearAlgebra
standard library.
Measurements
Finally, we might be interested in measuring some observables of the ground state. We compute the static spin correlation \(S^z_0 S^z_j\):
Here, we are using the inner function to compute an expectation value of the form \(\langle \psi_0 |\mathcal{O}| \psi_0 \rangle\) with \(\mathcal{O} = S^z_0 S^z_j\).
Input / Output
Julia features a variety of packages facilitating input and output of data. For C++, XDiag provides convenient functionality for TOML and HDF5 files.
Reading from TOML
While defining a Hamiltonian or other operators in code as above can be done, it is often preferable to define operators in a file and read it in. In XDiag, we use the TOML language to define input. The Hamiltonian of the \(N=8\) site Heisenberg chain we created above can be written in a TOML file as a list like this:
Interactions = [
['J', 'SdotS', 0, 1],
['J', 'SdotS', 1, 2],
['J', 'SdotS', 2, 3],
['J', 'SdotS', 3, 4],
['J', 'SdotS', 4, 5],
['J', 'SdotS', 5, 6],
['J', 'SdotS', 6, 7],
['J', 'SdotS', 7, 0]
]
The first entry in every list element is the coupling constant J
, the second enty is the type SdotS
, and the following two entries are the sites of the operator. To read in such a Hamiltonian from a toml file we can use the FileToml object together with the read_opsum Function,
XDiag also features the functions read_permutation_group and read_representation to conventiently read PermutationGroup and Representation objects used to describe symmetries from file, see further below.
Writing results to hdf5
After a simulation, we want to store results to file. A standard scientific data format is the hdf5 format. Julia supports input and output to hdf5 with the HDF5.jl package.
For C++ we provide a convenient way of writing results to hdf5 files. In general all numerical data, including scalar real/complex numbers as well as armadillo vectors and matrices can be easily written to file using the FileH5 object.
The second argument "w!"
specifies the access mode to the file. "w!"
is the forced write mode, where preexisting files are overwritten.
Symmetries
Permutations
Key functionality of XDiag comprises symmetry-adapted calculations, in particular with permutation symmtries which can be translation symmetries on a lattice. The Heisenberg chain with periodic boundary conditions is invariant under the translation operator
To represent such symmetries, we use the Permutation object. For example, on the \(8\)-site chain lattice, the symmetry which maps the sites,
can be represented via:
Notice, that also here we start counting from 1 in Julia, and from 0 in C++. Permutation objects can be multiplied, inverted, and raised to a power. We can use them to define a PermutationGroup object in the following way:
Representations
One-dimensional representations of a PermutationGroup are described by the Representation object. As one-dimensional irreducible representations (irreps) are given by their characters \(\chi(g)\) which is simply a list of (complex) numbers for every symmetry element \(g\). A representation can be created by handing the group and the list of characters:
Upon creation of a representation, XDiag checks whether the group axioms as well as the homomorphism property of the characters is fulfilled, i.e.,
Representations can then be used to create symmetry adapted blocks, which can then be diagonalized independently:
Also, permutation groups and representations can be defined in a TOML file and read in. Please refer to the read_permutation_group and read_representation documentation on the specific format required.
Using symmetries, thus, can give enhanced physical insights to the system, but also reduces the computational costs significantly. A common analysis tool for understanding quantum many-body systems is the Tower of states analysis. For an introduction, see e.g.
Studying Continuous Symmetry Breaking using Energy Level Spectroscopy
Alexander Wietek, Michael Schuler, Andreas M. Läuchli
arXiv:1704.08622 [cond-mat.str-el]
DOI: 10.48550/arXiv.1704.08622
Further features
Some further features of XDiag include:
-
Besides the Spinhalf, further blocks include the tJ and Electron
-
Distributed blocks like SpinhalfDistributed and tJDistributed allow for distributed memory parallelization in C++.
-
Real and imaginary time evolutions can be performed using the functions time_evolve, imaginary_time_evolve.
-
Operators can be symmetrized using the symmetrize function.
-
Product states and random states can be created using the functions product_state and random_state.
-
Timing of certain parts of the code in C++ can be conventiently performed using the Timing utilities.