Simulated charge stability diagrams for a 2x2 quantum dot system

This example shows how to use qtt.simulation.dotsystem to define a Hubbard-based model system of a 4 quantum dot array in a 2x2 plaquette configuration. Here we will use this model system to reproduce the Fig 1c plot from https://aip.scitation.org/doi/10.1063/1.5025928

[ ]:
%matplotlib inline

import os
import numpy as np
import matplotlib.pyplot as plt

import qtt.simulation.dotsystem as dotsystem

Define some extra helper functions:

[ ]:
def gates_from_det(dot_system, det_values=None):
    """ Sets the correct gate voltages. Run this function after setting the detuning variables."""
    if det_values:
        return np.dot(np.linalg.inv(dot_system.la_matrix), det_values)
    det_values = [getattr(dot_system, 'det%d' % (i + 1)) for i in range(dot_system.ndots)]
    gate_values = np.dot(np.linalg.inv(dot_system.la_matrix), det_values)
    for i in range(dot_system.ndots):
        setattr(dot_system, 'P%d' % (i + 1), gate_values[i])
    return gate_values


def det_from_gates(dot_system, plunger_values=None):
    """ Sets the correct detuning variables that matches the gate combination.
        Run this function after setting the gate voltages.
    """
    if plunger_values:
        return np.dot(dot_system.la_matrix, plunger_values)
    plunger_values = np.array([getattr(dot_system, 'P%d' % (i + 1)) for i in range(dot_system.ndots)])
    det_values = np.dot(dot_system.la_matrix, plunger_values)
    for i in range(dot_system.ndots):
        setattr(dot_system, 'det%d' % (i + 1), det_values[i])
    return det_values


def parse_scan_parameters(dot_system, scan_parameters, scan_steps, scan_range):
    """ Used to parse the input to the simulate_honeycomb function."""
    half_range = scan_range/2
    scan_steps_x, scan_steps_y = scan_steps
    scan_min_max = [[-half_range, half_range, -half_range, half_range],
               [-half_range, -half_range, half_range, half_range]]
    dot_system.makeparamvalues2D(scan_parameters, scan_min_max, scan_steps_x, scan_steps_y)

    if scan_parameters[0].startswith('det'):
        for parameter in dot_system.scan_parameters:
            dot_system.vals2D[pn] += getattr(dot_system, parameter)
            parameters = dot_system.vals2D.copy()
        return parameters

    initial_values = dot_system.getall('det')
    det = [np.zeros(dot_system.vals2D[scan_parameters[0]].shape) for i in range (dot_system.ndots)]
    params = dot_system.vals2D.copy()
    dict_params = {}
    for name in scan_parameters:
        if '{' in name:
            dict_prop = eval(name)
            for name2, prop in dict_prop.items():
                dict_params[name2] = getattr(dot_system, name2) + params[name] * prop
        else:
            dict_params[name] = getattr(dot_system, name) + params[name]
    for step_x in range(scan_steps_x):
        for step_y in range(scan_steps_y):
            for pn, pv in dict_params.items():
                setattr(dot_system, pn, pv[step_x, step_y])
            det_temp = det_from_gates(dot_system)
            for k in range(len(det_temp)):
                det[k][step_x, step_y] = det_temp[k]

    dot_system.setall('det', initial_values)

    dot_system.vals2D = {}
    for i in range(len(det)):
        dot_system.vals2D['det%i' % (i + 1)] = det[i]

    return params


def show_charge_occupation_numbers_on_click(dot_system, x_data, y_data, number_of_clicks=1):
    """ Shows the charge occupation numbers at the clicked points in the plotted charge stability diagram.

    Args:
        dot_system (dot_system): The simulated dot system.
        x_data (np.array): The parsed result data from the independent gate variable.
        y_data (np.array): The parsed result data from the dependent gate variable.
        number_of_clicks (int): The number of times the occupation numbers should be printed.
    """
    mV_minimum_x = x_data.min()
    mV_minimum_y = y_data.min()
    mV_range_x = x_data.max() - mV_minimum_x
    mV_range_y = y_data.max() - mV_minimum_y
    pixels_range_x, pixels_range_y = np.shape(x_data)

    if not 'QTT_UNITTEST' in os.environ:
        for i in range(number_of_clicks):
            mouse_clicks = plt.ginput()
            if mouse_clicks:
                (mV_coordinate_x, mV_coordinate_y) = mouse_clicks[0]

                x_index = int((mV_coordinate_x - mV_minimum_x) / mV_range_x * pixels_range_x)
                y_index = int((mV_coordinate_y - mV_minimum_y) / mV_range_y * pixels_range_y)

                charge_occupation_numbers = str(dot_system.hcgs[y_index, x_index])
                plt.text(mV_coordinate_x, mV_coordinate_y, charge_occupation_numbers, color='white')

Initialize the model system with the experimental parameters

[ ]:
def initialize_two_by_two_system():
    """ Creates the two by two quantum model. The parameters are set according to the experimental setup."""
    two_by_two = dotsystem.TwoXTwo()

    # cross-capacitance matrix and lever arms
    #                                     P1      P2     P3     P4
    cross_capacitance_matrix = np.array([[ 1.00,  0.45,  0.54,  0.87],  # Dot 1
                                         [ 0.65,  1.00,  0.47,  0.50],  # Dot 2
                                         [ 0.17,  0.47,  1.00,  0.24],  # Dot 3
                                         [ 0.44,  0.35,  0.88,  1.00]]) # Dot 4

    det_to_plunger = np.array([0.039 * np.ones(4), 0.041 * np.ones(4),
                               0.054 * np.ones(4), 0.031 * np.ones(4)]) # meV/mV

    two_by_two.la_matrix = cross_capacitance_matrix * det_to_plunger

    # All the following values in meV
    # On-site interaction per dot
    two_by_two.osC1 = 2.5
    two_by_two.osC2 = 2.3
    two_by_two.osC3 = 3
    two_by_two.osC4 = 1.8

    # Intersite interaction per pairs of dots
    two_by_two.isC1 = 0.47 # 1-2
    two_by_two.isC2 = 0.35 # 2-3
    two_by_two.isC3 = 0.43 # 3-4
    two_by_two.isC4 = 0.30 # 4-1
    two_by_two.isC5 = 0.28 # 1-3
    two_by_two.isC6 = 0.18 # 2-4

    # Tunnel coupling per pairs of dots
    two_by_two.tun1 = 0.02 # 1-2
    two_by_two.tun2 = 0.02 # 2-3
    two_by_two.tun3 = 0.02 # 3-4
    two_by_two.tun4 = 0.02 # 4-1

    # Energy offsets per dot (0 is the boundary for adding 1 electron)
    two_by_two.det1 = 1
    two_by_two.det2 = 1
    two_by_two.det3 = 0
    two_by_two.det4 = 0

    gate_voltages = gates_from_det(two_by_two) # This adds the gate voltages (tbt.P#, in mV) that result in the above detuning
    print('Current gate voltages: P1={:.2f} mV, P2={:.2f} mV, P3={:.2f} mV, P4={:.2f} mV'.format(*gate_voltages))
    return two_by_two

Run a 2D gate scan simulation and plot the charge stability diagram

[ ]:
two_by_two = initialize_two_by_two_system()

scan_parameters = ['P2', 'P4']
parameter_x, parameter_y = scan_parameters
scan_steps = [61, 61]
scan_range = 150

parsed_results = parse_scan_parameters(two_by_two, scan_parameters, scan_steps, scan_range)
two_by_two.simulatehoneycomb()

x_values = parsed_results[parameter_x]
y_values = parsed_results[parameter_y]

plt.figure()
plt.pcolor(x_values, y_values, two_by_two.honeycomb)
plt.xlabel("{0} (mV)".format(parameter_x))
plt.ylabel("{0} (mV)".format(parameter_y))
_ = plt.title('Charge stability diagram')

If you would like to check the charge occupation states at different points in the charge stability diagram, you can do that using the method below. The module matplotlib is set to interactive mode using %pylab tk. This will show up a new window that allows for clicking functionality.

[ ]:
if not 'QTT_UNITTEST' in os.environ:
    %pylab tk

two_by_two = initialize_two_by_two_system()

scan_parameters = ['P2', 'P4']
parameter_x, parameter_y = scan_parameters
scan_steps = [61, 61]
scan_range = 150

parsed_results = parse_scan_parameters(two_by_two, scan_parameters, scan_steps, scan_range)
two_by_two.simulatehoneycomb()

x_values = parsed_results[parameter_x]
y_values = parsed_results[parameter_y]

plt.figure()
plt.pcolor(x_values, y_values, two_by_two.honeycomb)
plt.xlabel("{0} (mV)".format(parameter_x))
plt.ylabel("{0} (mV)".format(parameter_y))
_ = plt.title('Charge stability diagram')

show_charge_occupation_numbers_on_click(two_by_two, x_values, y_values, number_of_clicks=4)
[ ]: