Posts
The band structure of graphene
|0> to |1>: Why quantum technology is bound to rise.
Finite differences and second quantization
Dynamically adjustable HoppingAmplitudes using callbacks in TBTK
Retrieving the Hamiltonian from the Model in TBTK
Creating Models with complex geometries using an IndexFilter in TBTK
Direct access to wave function amplitudes and eigenvalues in TBTK
Using TBTK to calculate the density of states (DOS) of a 1D, 2D, and 3D square lattice
TBTK v1.0.0 released
Using TBTK to time C++ code
Introduction to TBTK – A software development kit for quantum mechanical calculations
The second quantum revolution

Dynamically adjustable HoppingAmplitudes using callbacks in TBTK

Kristofer Björnson
(7th of November 2018)

Most recent TBTK release at the time of writing: v1.0.3. (Updated to work with: v2.0.0)

In this blog post we will take a look at how to specify models without necessarily knowing all the model parameters at the point of specification. This can be very useful when the Hamiltonian is time dependent or depends on some parameter that is to be solved for self-consistently.

We will here consider a third situation in which adjustable HoppingAmplitudes can be utilized by setting up a model with a variable potential. We will use this to repeatedly calculate the probability density and eigenvalues for the lowest energy states for seven different types of one-dimensional potentials. Namely, the infinite square well, square well, double square well, harmonic oscillator, double well, and step potential, as well as a potential with two regions with different potential separated by a barrier.

1 Parameters

The parameters that are needed are the number of sites to use for the discretized Schrödinger equation, the number of states for which we are going to calculate the probability density, and the nearest neighbor hopping amplitude t. We define these as follows.

1//Number of sites and states to extract the
2//probability density for.
3const int SIZE_X = 500;
4const int NUM_STATES = 7;
5double t = 1;

We further create an enum that will be used to specify the type of the potential.

1//Enum type for specifying the type of the
2//potential.
3enum PotentialType{
4    InfiniteSquareWell,
5    SquareWell,
6    DoubleSquareWell,
7    HarmonicOscillator,
8    DoubleWell,
9    Step,
10    Barrier
11};
12
13//The type of the potential.
14PotentialType potentialType;

2 Potential function

We also define a potential function that will be responsible for returning the potential at any given point.

1//Function that dynamically returns the current potential on a given site.
2complex<double> potential(int x){
3    switch(potentialType){
4    case InfiniteSquareWell:
5        return infiniteSquareWell(x);
6    case SquareWell:
7        return squareWell(x);
8    case DoubleSquareWell:
9        return doubleSquareWell(x);
10    case HarmonicOscillator:
11        return harmonicOscillator(x);
12    case DoubleWell:
13        return doubleWell(x);
14    case Step:
15        return step(x);
16    case Barrier:
17        return barrier(x);
18    default:
19        Streams::out << ”Error. This should never happen.”;
20        exit(1);
21    }
22}

Depending on the current potential type, this function calls one of several specific potential functions. The definition of these can be seen in the full source code linked at the end of this post.

3 Callback function

Normally a HoppingAmplitude is specified as

1HoppingAmplitude(value, toIndex, formIndex);

However, it is also possible to pass a callback instead of a value as the first argument. This callback will later be called by the HoppingAmplitude to determine the value, passing the toIndex and fromIndex as arguments. That is, the HoppingAmplitude will “call back” later to determine the value.

For a class to classify as a valid callback function, it needs to inherit from HoppingAmplitude::AmplitudeCallback and provide a function that takes two indices as arguments and returns a complex value. In addition, it needs to be able to handle every pair of indices that it will be called with. That is, every pair of indices which it is packed together with into a HoppingAmplitude when the model is specified.

In this example, the position can be read off from either of the indices. In the following implementation, we read it off from the from Index and pass it to the potential function defined above to get the value for the potential.

1//Callback that dynamically returns the current
2//value on a given site.
3class PotentialCallback : public HoppingAmplitude::AmplitudeCallback{
4public:
5    complex<double> getHoppingAmplitude(
6        const Index &to,
7        const Index &from
8    ){
9        return potential(from[0]);
10    }
11} potentialCallback;

In the last line, we instantiate an object of the PotentialCallback called potentialCallback. It is this object that will be used as callback when specifying the model.

4 Model

The model that we will consider is the discretized one-dimensional Schrödinger equation which we specify as follows.111For more details see for example Using TBTK to calculate the density of states (DOS) of a 1D, 2D, and 3D square lattice and Direct access to wave function amplitudes and eigenvalues in TBTK.

1//Create the Model.
2Model model;
3for(unsigned int x = 0; x < SIZE_X; x++){
4    //Kinetic terms.
5    model << HoppingAmplitude(
6        2*t,
7        {x},
8        {x}
9    );
10    if(x + 1 < SIZE_X){
11        model << HoppingAmplitude(
12            -t,
13            {x + 1},
14            {x}
15        ) + HC;
16    }
17
18    //Potential term.
19    model << HoppingAmplitude(
20        potentialCallback,
21        {x},
22        {x}
23    );
24}
25model.construct();

The kinetic terms result from discretizing the Laplace operator and letting t=2/2mdx2, while the potential term has been added by passing the callback potentialCallback as the first parameter to the HoppingAmplitude.

5 Set up the Solver and PropertyExtractor

We next set up the Solver and PropertyExtractor as follows.

1//Setup the Solver and PropertyExtractor.
2Solver::Diagonalizer solver;
3solver.setModel(model);
4PropertyExtractor::Diagonalizer
5    propertyExtractor(solver);

6 The main loop

In the main loop, we will carry out the same calculation for each potential. We therefore begin by creating a list of potential types to perform the calculations for, and a list of filenames to which to save the results.

1//List of potentials to run the calculation
2//for.
3vector<PotentialType> potentialTypes = {
4    InfiniteSquareWell,
5    SquareWell,
6    DoubleSquareWell,
7    HarmonicOscillator,
8    DoubleWell,
9    Step,
10    Barrier
11};
12
13//List of filenames to save the results to.
14vector<string> filenames = {
15    ”figures/InfiniteSquareWell.png”,
16    ”figures/SquareWell.png”,
17    ”figures/DoubleSquareWell.png”,
18    ”figures/HarmonicOscillator.png”,
19    ”figures/DoubleWell.png”,
20    ”figures/Step.png”,
21    ”figures/Barrier.png”
22};

The main loop itself is as follows.

1//Run the calculation and plot the result for
2//each potential.
3for(
4    unsigned int n = 0;
5    n < potentialTypes.size();
6    n++
7){
8    //Set the current potential.
9    potentialType = potentialTypes[n];
10
11    //Run the solver.
12    solver.run();
13
14    //Calculate the probability density
15    //for the first NUM_STATES.
16    Array<double> probabilityDensities(
17        {NUM_STATES, SIZE_X}
18    );
19    for(
20        unsigned int state = 0;
21        state < NUM_STATES;
22        state++
23    ){
24        for(
25            unsigned int x = 0;
26            x < (unsigned int)SIZE_X;
27            x++
28        ){
29            complex<double> amplitude
30                = propertyExtractor.getAmplitude(
31                    state,
32                    {x}
33                );
34            probabilityDensities[{state, x}]
35                = pow(abs(amplitude), 2);
36        }
37    }
38
39    //Calculate the eigenvalues.
40    Property::EigenValues eigenValues
41        = propertyExtractor.getEigenValues();
42
43    //Plot the results.
44    plot(
45        probabilityDensities,
46        eigenValues,
47        filenames[n]
48    );
49}

Here we loop over each type of potential and set the global parameter potentialType to the current potential in line 9. The solver is then executed, which results in the HoppingAmplitudes being requested internally, which triggers the callback functions to be called and return the values for the current potential. Next, the probability density is calculated2, followed by the calculation of the eigenvalues.

Finally, the results are plotted and saved to file. The plotting routine involves significant post processing of the data and the full code for achieving this can be found in the code linked below. In particular, the probability densities are first rescaled such that NUM_STATES probability densities can be stacked evenly on top of each other without overlapping or extending beyond the plotting window. Then the probability densities are shifted by the respective eigenvalue of the corresponding state to simultaneously convey information about the probability density and the eigenvalues in relation to the potential.

7 Results

Below the results are presented. The thick red line indicates the potential, while the black lines are the probability densities. Each probability density has been offset by the corresponding states eigenvalue to put its energy in relation to the potential. The eigenvalues are also plotted with thin red lines. In agreement with the basic principles of quantum mechanics, we see how the states are oscillating in the regions where the potential is smaller than the energy of the state, while they decay exponentially into the regions where the potential is larger.

7.1 Infinite square well

[Uncaptioned image]

7.2 Square well

[Uncaptioned image]

7.3 Double square well

[Uncaptioned image]

7.4 Harmonic oscillator

[Uncaptioned image]

7.5 Double well

[Uncaptioned image]

7.6 Step

[Uncaptioned image]

7.7 Barrier

[Uncaptioned image]

8 Full code

Full code is available in src/main.cpp in the project 2018_11_07 of the Second Tech code package. See the README for instructions on how to build and run.