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

[1]:
%matplotlib inline

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

import qtt.simulation.dotsystem as dotsystem

Define some extra helper functions:

[2]:
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

[3]:
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

[4]:
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, shading='auto')
plt.xlabel("{0} (mV)".format(parameter_x))
plt.ylabel("{0} (mV)".format(parameter_y))
_ = plt.title('Charge stability diagram')
Current gate voltages: P1=35.83 mV, P2=11.19 mV, P3=-8.40 mV, P4=-12.29 mV
simulatehoneycomb: 0/61
simulatehoneycomb: 4/61
simulatehoneycomb: 8/61
simulatehoneycomb: 12/61
simulatehoneycomb: 16/61
simulatehoneycomb: 20/61
simulatehoneycomb: 24/61
simulatehoneycomb: 28/61
simulatehoneycomb: 32/61
simulatehoneycomb: 36/61
simulatehoneycomb: 40/61
simulatehoneycomb: 44/61
simulatehoneycomb: 48/61
simulatehoneycomb: 52/61
simulatehoneycomb: 56/61
simulatehoneycomb: 60/61
simulatehoneycomb: 17.52 [s] (multiprocess False)
../../_images/notebooks_simulation_example_2x2_tuning_8_1.png

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.

[5]:
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, shading='auto')
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)
Populating the interactive namespace from numpy and matplotlib
Current gate voltages: P1=35.83 mV, P2=11.19 mV, P3=-8.40 mV, P4=-12.29 mV
simulatehoneycomb: 0/61
simulatehoneycomb: 4/61
simulatehoneycomb: 8/61
simulatehoneycomb: 12/61
simulatehoneycomb: 16/61
simulatehoneycomb: 20/61
simulatehoneycomb: 24/61
simulatehoneycomb: 28/61
simulatehoneycomb: 32/61
simulatehoneycomb: 36/61
simulatehoneycomb: 40/61
simulatehoneycomb: 44/61
simulatehoneycomb: 48/61
simulatehoneycomb: 52/61
simulatehoneycomb: 56/61
simulatehoneycomb: 60/61
simulatehoneycomb: 18.50 [s] (multiprocess False)
---------------------------------------------------------------------------
TclError                                  Traceback (most recent call last)
d:\dev\qtt_release\env\lib\site-packages\matplotlib\blocking_input.py in __call__(self, n, timeout)
     91             # Start event loop.
---> 92             self.fig.canvas.start_event_loop(timeout=timeout)
     93         finally:  # Run even on exception like ctrl-c.

d:\dev\qtt_release\env\lib\site-packages\matplotlib\backend_bases.py in start_event_loop(self, timeout)
   2287         while self._looping and counter * timestep < timeout:
-> 2288             self.flush_events()
   2289             time.sleep(timestep)

d:\dev\qtt_release\env\lib\site-packages\matplotlib\backends\_backend_tk.py in flush_events(self)
    404         # docstring inherited
--> 405         self._master.update()
    406

~\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py in update(self)
   1176         """Enter event loop until all pending events have been processed by Tcl."""
-> 1177         self.tk.call('update')
   1178     def update_idletasks(self):

TclError: can't invoke "update" command: application has been destroyed

During handling of the above exception, another exception occurred:

TclError                                  Traceback (most recent call last)
d:\dev\qtt_release\env\lib\site-packages\matplotlib\backend_bases.py in _wait_cursor_for_draw_cm(self)
   2784             try:
-> 2785                 self.set_cursor(cursors.WAIT)
   2786                 yield

d:\dev\qtt_release\env\lib\site-packages\matplotlib\backends\_backend_tk.py in set_cursor(self, cursor)
    543         window = self.canvas.get_tk_widget().master
--> 544         window.configure(cursor=cursord[cursor])
    545         window.update_idletasks()

~\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py in configure(self, cnf, **kw)
   1484         """
-> 1485         return self._configure('configure', cnf, kw)
   1486     config = configure

~\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py in _configure(self, cmd, cnf, kw)
   1475             return self._getconfigure1(_flatten((self._w, cmd, '-'+cnf)))
-> 1476         self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
   1477     # These used to be defined in Widget:

TclError: invalid command name "."

During handling of the above exception, another exception occurred:

TclError                                  Traceback (most recent call last)
<ipython-input-5-3b8135c8f477> in <module>
     21 _ = plt.title('Charge stability diagram')
     22
---> 23 show_charge_occupation_numbers_on_click(two_by_two, x_values, y_values, number_of_clicks=4)

<ipython-input-2-144fae537620> in show_charge_occupation_numbers_on_click(dot_system, x_data, y_data, number_of_clicks)
     82     if not 'QTT_UNITTEST' in os.environ:
     83         for i in range(number_of_clicks):
---> 84             mouse_clicks = plt.ginput()
     85             if mouse_clicks:
     86                 (mV_coordinate_x, mV_coordinate_y) = mouse_clicks[0]

d:\dev\qtt_release\env\lib\site-packages\matplotlib\pyplot.py in ginput(n, timeout, show_clicks, mouse_add, mouse_pop, mouse_stop)
   2320         n=n, timeout=timeout, show_clicks=show_clicks,
   2321         mouse_add=mouse_add, mouse_pop=mouse_pop,
-> 2322         mouse_stop=mouse_stop)
   2323
   2324

d:\dev\qtt_release\env\lib\site-packages\matplotlib\figure.py in ginput(self, n, timeout, show_clicks, mouse_add, mouse_pop, mouse_stop)
   2329                                                   mouse_stop=mouse_stop)
   2330         return blocking_mouse_input(n=n, timeout=timeout,
-> 2331                                     show_clicks=show_clicks)
   2332
   2333     def waitforbuttonpress(self, timeout=-1):

d:\dev\qtt_release\env\lib\site-packages\matplotlib\blocking_input.py in __call__(self, n, timeout, show_clicks)
    261         self.clicks = []
    262         self.marks = []
--> 263         BlockingInput.__call__(self, n=n, timeout=timeout)
    264         return self.clicks
    265

d:\dev\qtt_release\env\lib\site-packages\matplotlib\blocking_input.py in __call__(self, n, timeout)
     93         finally:  # Run even on exception like ctrl-c.
     94             # Disconnect the callbacks.
---> 95             self.cleanup()
     96         # Return the events in this case.
     97         return self.events

d:\dev\qtt_release\env\lib\site-packages\matplotlib\blocking_input.py in cleanup(self, event)
    250                 mark.remove()
    251             self.marks = []
--> 252             self.fig.canvas.draw()
    253         # Call base class to remove callbacks.
    254         BlockingInput.cleanup(self)

d:\dev\qtt_release\env\lib\site-packages\matplotlib\backends\backend_tkagg.py in draw(self)
      7 class FigureCanvasTkAgg(FigureCanvasAgg, FigureCanvasTk):
      8     def draw(self):
----> 9         super(FigureCanvasTkAgg, self).draw()
     10         _backend_tk.blit(self._tkphoto, self.renderer._renderer, (0, 1, 2, 3))
     11         self._master.update_idletasks()

d:\dev\qtt_release\env\lib\site-packages\matplotlib\backends\backend_agg.py in draw(self)
    390         with RendererAgg.lock, \
    391              (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
--> 392               else nullcontext()):
    393             self.figure.draw(self.renderer)
    394             # A GUI class may be need to update a window using this draw, so

~\AppData\Local\Programs\Python\Python36\lib\contextlib.py in __enter__(self)
     79     def __enter__(self):
     80         try:
---> 81             return next(self.gen)
     82         except StopIteration:
     83             raise RuntimeError("generator didn't yield") from None

d:\dev\qtt_release\env\lib\site-packages\matplotlib\backend_bases.py in _wait_cursor_for_draw_cm(self)
   2786                 yield
   2787             finally:
-> 2788                 self.set_cursor(self._lastCursor)
   2789         else:
   2790             yield

d:\dev\qtt_release\env\lib\site-packages\matplotlib\backends\_backend_tk.py in set_cursor(self, cursor)
    542     def set_cursor(self, cursor):
    543         window = self.canvas.get_tk_widget().master
--> 544         window.configure(cursor=cursord[cursor])
    545         window.update_idletasks()
    546

~\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py in configure(self, cnf, **kw)
   1483         the allowed keyword arguments call the method keys.
   1484         """
-> 1485         return self._configure('configure', cnf, kw)
   1486     config = configure
   1487     def cget(self, key):

~\AppData\Local\Programs\Python\Python36\lib\tkinter\__init__.py in _configure(self, cmd, cnf, kw)
   1474         if isinstance(cnf, str):
   1475             return self._getconfigure1(_flatten((self._w, cmd, '-'+cnf)))
-> 1476         self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
   1477     # These used to be defined in Widget:
   1478     def configure(self, cnf=None, **kw):

TclError: invalid command name "."
ERROR:tornado.application:Exception in callback functools.partial(<function Kernel.enter_eventloop.<locals>.advance_eventloop at 0x00000223CF62FC80>)
Traceback (most recent call last):
  File "d:\dev\qtt_release\env\lib\site-packages\tornado\ioloop.py", line 743, in _run_callback
    ret = callback()
  File "d:\dev\qtt_release\env\lib\site-packages\ipykernel\kernelbase.py", line 314, in advance_eventloop
    eventloop(self)
  File "d:\dev\qtt_release\env\lib\site-packages\ipykernel\eventloops.py", line 232, in loop_tk
    app.tk.createfilehandler(stream.getsockopt(zmq.FD), READABLE, notifier)
AttributeError: '_tkinter.tkapp' object has no attribute 'createfilehandler'
[ ]: