Python cheatsheet

[designed specifically for understanding and modifying simple-dmrg]

For a programmer, the standard, online Python tutorial is quite nice. Below, we try to mention a few things so that you can get acquainted with the simple-dmrg code as quickly as possible.

Python includes a few powerful internal data structures (lists, tuples, and dictionaries), and we use numpy (numeric python) and scipy (additional “scientific” python routines) for linear algebra.

Basics

Unlike many languages where blocks are denoted by braces or special end statements, blocks in python are denoted by indentation level. Thus indentation and whitespace are significant in a python program.

It is possible to execute python directly from the commandline:

$ python

This will bring you into python’s real-eval-print loop (REPL). From here, you can experiment with various commands and expressions. The examples below are taken from the REPL, and include the prompts (“>>>” and “...”) one would see there.

Lists, tuples, and loops

The basic sequence data types in python are lists and tuples.

A list can be constructed literally:

>>> x_list = [2, 3, 5, 7]

and a number of operations can be performed on it:

>>> len(x_list)
4

>>> x_list.append(11)
>>> x_list
[2, 3, 5, 7, 11]

>>> x_list[0]
2

>>> x_list[0] = 0
>>> x_list
[0, 3, 5, 7, 11]

Note, in particular, that python uses indices counting from zero, like C (but unlike Fortran and Matlab).

A tuple in python acts very similarly to a list, but once it is constructed it cannot be modified. It is constructed using parentheses instead of brackets:

>>> x_tuple = (2, 3, 5, 7)

Lists and tuples can contain any data type, and the data type of the elements need not be consistent:

>>> x = ["hello", 4, 8, (23, 12)]

It is also possible to get a subset of a list (e.g. the first three elements) by using Python’s slice notation:

>>> x = [2, 3, 5, 7, 11]
>>> x[:3]
[2, 3, 5]

Looping over lists and tuples

Looping over a list or tuple is quite straightforward:

>>> x_list = [5, 7, 9, 11]
>>> for x in x_list:
...     print(x)
...
5
7
9
11

If you wish to have the corresponding indices for each element of the list, the enumerate() function will provide this:

>>> x_list = [5, 7, 9, 11]
>>> for i, x in enumerate(x_list):
...     print(i, x)
...
0 5
1 7
2 9
3 11

If you have two (or more) parallel arrays with the same number of elements and you want to loop over each of them at once, use the zip() function:

>>> x_list = [2, 3, 5, 7]
>>> y_list = [12, 13, 14, 15]
>>> for x, y in zip(x_list, y_list):
...     print(x, y)
...
2 12
3 13
5 14
7 15

There is a syntactic shortcut for transforming a list into a new one, known as a list comprehension:

>>> primes = [2, 3, 5, 7]
>>> doubled_primes = [2 * x for x in primes]
>>> doubled_primes
[4, 6, 10, 14]

Dictionaries

Dictionaries are python’s powerful mapping data type. A number, string, or even a tuple can be a key, and any data type can be the corresponding value.

Literal construction syntax:

>>> d = {2: "two", 3: "three"}

Lookup syntax:

>>> d[2]
'two'
>>> d[3]
'three'

Modifying (or creating) elements:

>>> d[4] = "four"
>>> d
{2: 'two', 3: 'three', 4: 'four'}

The method get() is another way to lookup an element, but returns the special value None if the key does not exist (instead of raising an error):

>>> d.get(2)
'two'
>>> d.get(4)

Looping over dictionaries

Looping over the keys of a dictionary:

>>> d = {2: "two", 3: "three"}
>>> for key in d:
...     print(key)
...
2
3

Looping over the values of a dictionary:

>>> d = {2: "two", 3: "three"}
>>> for value in d.values():
...     print(value)
...
two
three

Looping over the keys and values, together:

>>> d = {2: "two", 3: "three"}
>>> for key, value in d.items():
...     print(key, value)
...
2 two
3 three

Functions

Function definition in python uses the def keyword:

>>> def f(x):
...     y = x + 2
...     return 2 * y + x
...

Function calling uses parentheses, along with any arguments to be passed:

>>> f(2)
10
>>> f(3)
13

When calling a function, it is also possibly to specify the arguments by name (e.g. x=4):

>>> f(x=4)
16

An alternative syntax for writing a one-line function is to use python’s lambda keyword:

>>> g = lambda x: 3 * x
>>> g(5)
15

numpy arrays

numpy provides a multi-dimensional array type. Unlike lists and tuples, numpy arrays have fixed size and hold values of a single data type. This allows the program to perform operations on large arrays very quickly.

Literal construction of a 2x2 matrix:

>>> np.array([[1, 2], [3, 4]], dtype='d')
array([[ 1.,  2.],
       [ 3.,  4.]])

Note that dtype='d' specifies that the type of the array should be double-precision (real) floating point.

It is also possibly to construct an array of all zeros:

>>> np.zeros([3, 4], dtype='d')
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])

And then elements can be added one-by-one:

>>> x = np.zeros([3, 4], dtype='d')
>>> x[1, 2] = 12
>>> x[1, 3] = 18
>>> x
array([[  0.,   0.,   0.,   0.],
       [  0.,   0.,  12.,  18.],
       [  0.,   0.,   0.,   0.]])

It is possible to access a given row or column by index:

>>> x[1, :]
array([  0.,   0.,  12.,  18.])
>>> x[:, 2]
array([  0.,  12.,   0.])

or to access multiple columns (or rows) at once:

>>> col_indices = [2, 1, 3]
>>> x[:, col_indices]
array([[  0.,   0.,   0.],
       [ 12.,   0.,  18.],
       [  0.,   0.,   0.]])

For matrix-vector (or matrix-matrix) multiplication use the np.dot() function:

>>> np.dot(m, v)

Warning

One tricky thing about numpy arrays is that they do not act as matrices by default. In fact, if you multiply two numpy arrays, python will attempt to multiply them element-wise!

To take an inner product, you will need to take the transpose-conjugate of the left vector yourself:

>>> np.dot(v1.conjugate().transpose(), v2)

Array storage order

Although a numpy array acts as a multi-dimensional object, it is actually stored in memory as a one-dimensional contiguous array. Roughly speaking, the elements can either be stored column-by-column (“column major”, or “Fortran-style”) or row-by-row (“row major”, or “C-style”). As long as we understand the underlying storage order of an array, we can reshape it to have different dimensions. In particular, the logic for taking a partial trace in simple-dmrg uses this reshaping to make the system and environment basis elements correspond to the rows and columns of the matrix, respectively. Then, only a simple matrix multiplication is required to find the reduced density matrix.

Mathematical constants

numpy also provides a variety of mathematical constants:

>>> np.pi
3.141592653589793
>>> np.e
2.718281828459045

Experimentation and getting help

As mentioned above, python’s REPL can be quite useful for experimentation and getting familiar with the language. Another thing we can do is to import the simple-dmrg code directly into the REPL so that we can experiment with it directly. The line:

>>> from simple_dmrg_01_infinite_system import *

will execute all lines except the ones within the block that says:

if __name__ == "__main__":

So if we want to use the finite system algorithm, we can (assuming our source tree is in the PYTHONPATH, which should typically include the current directory):

$ python
>>> from simple_dmrg_04_eigenstate_prediction import *
>>> finite_system_algorithm(L=10, m_warmup=8, m_sweep_list=[8, 8, 8])

It is also possible to get help in the REPL by using python’s built-in help() function on various objects, functions, and types:

>>> help(sum)   # help on python's sum function

>>> help([])    # python list methods
>>> help({})    # python dict methods

>>> help({}.setdefault)   # help on a specific dict method

>>> import numpy as np
>>> help(np.log)          # natural logarithm
>>> help(np.linalg.eigh)  # eigensolver for hermitian matrices