Model
At a glance
When studying lattice Hamiltonians numerically, keeping track of indices and observables can be tedious and error prone. This is especially true when data is stored using linear indices for computational efficiency. The Model class helps organize calculations and serves as a unified entry point to Aleph's scientific computing methods for lattice Hamiltonians.
As described here, a term in a Hamiltonian on a Lattice, with neighbourhoods can always be written in the form,
where , are operators acting on a single site of the lattice, , is the jth displacement defining the neighbourhood, , and is a position dependent coefficient. A model then contains the following data,
- An instance of Lattice specifying the geometry.
- A collection of NeighbourhoodRules used to define terms on the lattice.
- A collection of interactions corresponding to the Hamiltonian.
- A collection of named collections of interactions corresponding to operators.
Creating a Model
To make this concrete, let's make the transverse field Ising model on a square lattice. To construct a model from scratch we begin with a Lattice,
var my_model = model(lattice("square", [4,4]))
The lattice stored in a model can be accessed by calling,
my_model.lattice()
but cannot be modified once the model has been created.
Neighbourhood Rules
In order to add interactions we first have to specify the kinds of neighbourhoods that an interaction can act on. We can add a neighbourhood rule directly to the model using,
my_model.add(neighbourhood_rule("nn_x", [1,0]))
my_model.add(neighbourhood_rule("nn_y", [0,1]))
Coefficients
Coefficients are the named parameters that control the relative strength of interactions in a model. All coefficients that can be added to a model are complex numbers, or
are functions of the coordinates of the sites on which the interaction acts that returns a complex number. In order for a model to be translated into
an algorithmic representation, all the coefficients referenced by its interactions must be specified. Coefficients can be created on their own via the
coefficient factory, and then added to the model via,
my_model.add(coefficient("J", 1.0 + 0.0i))
To add a coefficient with a spatial dependence we can use a function instead of a value. As an example, let's add a magnetic field that is spatially staggered:
def h_func(Complex h_value, coords)
{
if(integer(sum(coords[0].primitive_vector())) % 2 == 0) {
return h_value
}
return -h_value
}
We can then use bind to set the magnitude of ,
my_model.add(coefficient("h", bind(h_func(0.0, _))))
Once a coefficient is added, its value can be changed with the set_coefficient method.
To sweep through a number of different values of , for example,
var hmin = 0.0
var hmax = 1.0
var nvals = 10
var dh = (hmax - hmin) / real(nvals)
for (step : [1...nvals]) {
my_model.set_coefficient("h", bind(h_func(hmin + dh*step, _)))
// analysis ...
}
Interactions
Now that we've added both neighbourhood rules and coefficients we can add the interactions that define the model.
Analogous to the definition of coefficients and neighbourhood rules we can add an interaction with the interaction
factory,
my_model.add(interaction("J", [Z, Z], "nn_x"))
my_model.add(interaction("J", [Z, Z], "nn_y"))
We can similary add the transverse field (staggered in this case),
my_model.add(interaction("h", X, "onsite"))
We didn't have to define the "onsite" NeighbourhoodRule. This is because all models understand how to process "onsite" by default. An unamed constant can also be used as a coefficient, for example,
my_model.add(interaction(1.0+0.0i, Y, "onsite"));
This approach can be useful when a term is not expected to change, but is discouraged if multiple interactions share the same coefficient.
Dispatching to Algorithms
Constructing an OperatorSum
Models can be used to construct explicit operator sums for the purposes of algebraic manipulation, or to feed into algorithmic frameworks that accept an operator sum,
var model_operator_sum = operator_sum(model)
This is a good entry point for any state vector based approach.
Constructing a Matrix Product Operator
Similary, it's possible to send a model to a matrix product operator (MPO) suitable for use in density matrix renormalization group (DMRG).
var model_mpo = mpo(model)
For a more complete example refer to the DMRG tutorial.
Observables
Like the Hamiltonian, many observables are represented as sums over interactions. In this example we consider the magnetization,
my_model.add_observable("zmag", interaction(1.0, Z, "onsite"))
Observables are also named for later reference. We could have also reference a predefined coefficient for the interaction. Once an observable has been added, we can continue to add interactions to the observable. For instance, to compute the sum of nearest neighbour correlations we could write
my_model.add_observable("corrsum", interaction(1.0, [Z, Z], "nn_x"))
my_model.add_observable("corrsum", interaction(1.0, [Z, Z], "nn_y"))
The observables can then be converted to a form suitable for analysis using one of the various conversion methods. For example, we can write,
var oberservable_ops = my_model.observable_to_opsum()
to get a dictionary of the observables converted to operator sums.