Note
Go to the end to download the full example code.
Gated Quantum Dot
The aim of this tutorial is to demonstrate how the builder can be used to accelerate the generation of meshes appropriate for modelling semiconductor nanodevices. Here, the demonstration is made for the following gated quantum-dot system:
Fig. 16 gated_dot
The left-hand side of the above figure shows the layout for this example device, which can be found here. This layout file is in .gds format and can be visualized and modified using, e.g. KLayout. In the above picture, the red rectangle represents the simulation domain, while the blue rectangles represent metallic gates deposited on top of the chip that are used to control the confinement potential used to achieve confinement of single charge carriers in the x-y plane.
The right-hand side of the above figure illustrates the heterostructure
stack, i.e., the multiple layers of materials used to confine charge
carriers in the z direction. Each layer will be labeled differently to
represent the role it plays in the heterostructure, and will be assigned
a certain number of mesh layers depending on how accurate the simulation
should be in each region. In this example, the mismatch between
conduction band edges of GaAs and AlGaAs is used to form a Barrier
isolating electrons in the substrate from the Cap region, and an
n-doped layer is used to bend the conduction band edge and form a
triangular confinement potential in the region indicated as 2DEG in
the above figure. Finally, a Spacer region is used to isolate the
quantum dot formed in the 2DEG from the dopants.
Setup
Header
To generate the mesh for the gated quantum dot using the Builder, we start by importing two relevant modules
from qtcad.builder import Builder, MeshAlgorithm3D
from pathlib import Path
import sys
save_dir = Path(sys.argv[0]).parent
The first import is the qtcad.builder.Builder, while the second is
the standard python library pathlib which will be used to
facilitate referencing separate files. The show_gui variable
determines whether to show the Gmsh GUI or not. Here we set it to
False as this script is run by the documentation generator.
Constants
Next we define some constants that will be useful in setting up the device in the subsequent steps. All lengths are given in nanometers.
# Mesh characteristic lengths
char_len = 50 # Note that this is very low
dot_char_len = char_len / 2
# Thickness of each material layer
cap_thick = 10
barrier_thick = 15
dopant_thick = 5
spacer_thick = 5
two_deg_thick = 5
substrate_thick = 100 - two_deg_thick
Initialization
The Builder is based on the principle of taking a two dimensional shape and creating or editing three dimensional geometry through extrusion and etching, as well as using these shapes to define surfaces. Thus our first step will be to load an OASIS layout file with the two dimensional shapes we are going to use.
In order to do that, we initialize the instance and load the layout
we are going to use. Each qtcad.builder.Builder instance
corresponds to one Gmsh process and multiple instances can coexist.
The layout can be imported as an OASIS file saved with the .oas
extension. In this example we will load the gds file found
gated_qd.oas. For
example, in KLayout, this is done using File > Save As by
selecting OASIS files in the drop-down menu 'Type' under the
file name field.
builder = Builder(name="Quantum Dot").load_layout(
save_dir / "layouts" / "gated_qd.oas", cell_name="builder"
)
In the constructor we provided a name for the model that we are
going to create. Then we instructed the Builder to load the layout
file. By default, all layers from the cell named builder are
loaded (see qtcad.builder.MaskContainer.load_layout for
details). Each layer becomes a qtcad.builder.Mask mask object
containing multiple qtcad.builder.Polygon` objects. The shape
names are read from the "name" user property. Alternatively,
they can be set using text labels (as has been done in
Advanced features). Using
qtcad.builder.Builder.use_mask we will later select a
particular mask. By default, this selects all shapes within that
mask for subsequent operations. If we would like to be more
specific, we can use qtcad.builder.Builder.use_shapes to
select one or multiple shapes. The names of the physical surfaces
and volumes that qtcad.builder.Builder creates are then
derived from the names of the shapes. In general, the names of the
physical groups are automatically generated base on the shape names
(qtcad.builder.Builder.group_from_shape, if the shape has no
name then one will be generated), on the name of the mask
(qtcad.builder.Builder.group_from_mask) or on a given string
(qtcad.builder.Builder.set_group_name). For extrusions, the
base, top, and side surfaces will bear the name of the volume the
bound with the suffixes "_base", "_top", and "_side",
respectively.
Using qtcad.builder.Builder.print_mask_tree, we can print out
the result of loading the layout file.
builder.print_mask_tree()
Layout
├── Mask 1 "dot-region"
│ └── 0 Polygon "dot_region"
├── Mask 4 "gates"
│ ├── 0 Polygon "top_gate_2"
│ ├── 1 Polygon "top_gate_1"
│ ├── 2 Polygon "bottom_gate_1"
│ ├── 3 Polygon "bottom_gate_3"
│ ├── 4 Polygon "bottom_gate_2"
│ └── 5 Polygon "top_gate_3"
└── Mask 0 "footprint"
└── 0 Polygon "footprint"
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
We can also view the shapes in the masks using
qtcad.builder.Builder.view_shapes:
builder.use_mask("gates").view_shapes(save="out/layout_gates.svg", angles=(0, 0, 90))
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
In this case, we have loaded three Masks. The first contains the
shapes that we will later use to define the electrostatic gate
surfaces on the device. Second, the "footprint" mask contains a
simple rectangle with the outline of the device. Lastly, the
"dot-region" mask contains the outline of the region that we will
later declare as the “dot-region” where no classical charges are
allowed.
Creating the heterostructure
Now that we have loaded our layouts, we will define the create the
basic layer structure of the device as shown in Fig. 16.
In order to accomplish this, we will extrude the rectangle in the
"footprint" mask.
The Builder (qtcad.builder.Builder) uses the builder pattern
meaning that most operations are implemented as chained method calls.
To create the heterostructure stack we then run the following code:
(
builder.set_mesh_size(char_len)
.set_min_elements(1)
.use_mask("footprint")
#
# Substrate
#
.set_group_name("substrate")
.extrude(substrate_thick)
#
# Electron confinement region
#
.set_group_name("two_deg")
.extrude(two_deg_thick)
#
# Spacer
#
.set_group_name("spacer")
.extrude(spacer_thick)
#
# Dopant
#
.set_group_name("dopant")
.extrude(dopant_thick)
#
# Barrier
#
.set_group_name("barrier")
.extrude(barrier_thick)
#
# Cap
#
.set_group_name("cap")
.extrude(cap_thick)
)
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
Above, wrapping the code in parenthesis allows to put the chained
method calls on separate lines. In the second line we tell the
Builder to use the characteristic length char_len when meshing
the subsequently created volumes. Calling
qtcad.builder.Builder.set_min_elements tells the Builder to
ensure that there are will be at least two mesh cells along the
narrowest dimension of the created entities (surfaces or volumes).
In the fourth line, we tell the Builder to use the mask named
"footprint" for the upcoming operations. This will automatically
select the only shape in this mask for the subsequent operations.
Here, this is a simple rectangle with the appropriate dimensions (see
Fig. 17).
Fig. 17 The footprint layout.
Subsequently, we save the current z coordinate for subsequent
use. The z coordinate can be set with
qtcad.builder.Builder.set_z_from_group or
qtcad.builder.Builder.set_z_from_group and will be
automatically advanced with each extrusion. Then, we tell builder to
name the created surfaces/volumes based on the string
"substrate" (rather than the name of the shape, see also
Selection of shapes and naming modes) and extrude this
shape by substrate_thick in line ten to create a volume named
substrate__dg_vol. This process is then repeated with all
subsequent layers. (Another way to change the name in this case
would be to use qtcad.builder.Builder.rename_shape.)
At any point we can call qtcad.builder.Builder.view to open a Gmsh
GUI window with the current model.
dg.view(True, volume_labels=True)
If we pass True as the first argument, the view process will
pause the execution of the main process. Otherwise, execution will
continue while the window is open. As can be seen in
stack, each created volume derives it’s name from the name
of the shape in the mask we set above. In this script we we use the
save option to protduce an svg figure.
builder.view(
surfaces=False,
volume_labels=True,
angles=(-90, 0, 85),
save="out/heterostructure.svg",
zoom=0.8,
)
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
Creating a dot region
The volume that will later be used to model the quantum features of
the device will be a subregion of the total volume which has to be
designated by its own physical groups. Furthermore, we typically
desire a finer mesh in this region. In our case the dot region spans
from the bottom of the substrate to the top of the spacer and will be
created by simply extruding the first shape of the dot_region
between the appropriate z coordinates.
To create the dot region we run the following:
dot_height = builder.get_z_from_group("spacer") - builder.get_z_from_group(
"substrate", bottom=True
)
(
builder.set_z_from_group("substrate", bottom=True)
.overlay_mode()
.set_mesh_size(dot_char_len)
.minimum_mesh_size()
.use_mask("dot-region")
.group_from_shape()
.extrude(dot_height)
)
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
In line one we compute the total height of the dot region. We then
set the Builder z-coordinate to the bottom of the substrate
in line three. In line four, we switch the Builder to
overlay mode, meaning that newly created volumes will be named
according to the scheme [name of intersecting volume].[name of
newly created volume] (see also
qtcad.builder.Builder.FragmentationMode). This will allow us to
disambiguate the different heterostructure layers of the dot
region. Then we instruct the generator to take the minimum of the
mesh size set with qtcad.builder.Builder.set_mesh_size using
qtcad.builder.Builder.minimum_mesh_size and the mesh sizes already present
in our heterostructure. Subsequently, we select the mask
"dot-region" (see Fig. 18) and subsequently tell
builder to derive the names of the created volume from the names of
the shapes in the mask. Finally, we extrude the dot region.
Fig. 18 The dot region layout.
Below we can see the result of these operations. Note that the part of
the dot region intersecting with the substrate is called
substrate.dot_region.
builder.view(
surfaces=False,
volume_labels=True,
angles=(-82, 0, 90),
save="out/model_dr.svg",
zoom=2,
)
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
Adding the gate surfaces
Lastly, we add the surfaces that will be used to define the boundary conditions later on. These surfaces are the back gate at the bottom of the substrate and the control gates on the top of the device.
To add the surfaces we execute the following:
(
#
# Back Gate
#
builder.set_mesh_size(char_len)
.use_mask("footprint")
.use_shape("footprint")
.displace_mode()
.set_group_name("ohmic_bnd")
.set_z_from_group("substrate", bottom=True)
.add_surface()
#
# Control Gates
#
.use_mask("gates")
.set_mesh_size(dot_char_len)
.set_z_from_group("cap")
.group_from_shape()
.add_surface()
)
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
First, we select the coarser characteristic length and select the mask
"footprint" as well as the the shape "footprint" within that
mask (the mask now contains multiple shapes due to our copying and
renaming while building the heterostructure). We then rename the
selected shape create the back gate at the bottom. Subsequently we
switch back to the displace_mode so that the names of the surfaces
we create will be directly derived from the shape names (see also
qtcad.builder.Builder.displace_mode). Finally we create the back
gate surface the call to add_surface in line eight. Then, we use
the the mask "gates" (see Fig. 19) to at the
control gates at the bottom of the device. As we haven’t selected
specific shapes within that mask, all shapes will be used at once to
create the surfaces for the control gates.
Fig. 19 The gates layout.
Fig. 20 The user property used to configure the names of shapes.
This results in the following (only showing the surfaces we just created):
builder.view(
surface_labels=True,
surfaces=True,
angles=(-80, 0, 10),
save="out/surfs.svg",
zoom=0.8,
groups=[
"ohmic_bnd",
"top_gate_1",
"top_gate_2",
"top_gate_3",
"bottom_gate_1",
"bottom_gate_2",
"bottom_gate_3",
],
)
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
We see, that each gate is named according to the name of the shape.
Meshing
Finally, we can save the mesh generated from the instructions above to
the disk using the qtcad.builder.Builder.mesh method:
builder.mesh(3, algorithm3d=MeshAlgorithm3D.HXT, show_gmsh_output=True).write(
save_dir / "meshes/gated_dot.msh"
)
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/util/decorators.py:33: UserWarning: Encountered disconnected volumes: 3D Meshing 9 volumes with 2 connected components
return func(self, *args, **kwargs)
nodes: 0%| | 0/82346 [00:00<?, ?it/s]
nodes: 0%| | 0/82346 [00:00<?, ?it/s]
nodes: 100%|██████████| 82346/82346 [00:00<00:00, 4991316.92it/s]
nodes: 100%|██████████| 82346/82346 [00:00<00:00, 4912235.03it/s]
nodes: 100%|██████████| 82346/82346 [00:00<00:00, 4858885.49it/s]
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
Here the first argument sets the meshing dimension and the save
input gives the path and name of the output mesh we wish to save. By
default, the output mesh will not be saved. However here we have
specified that the output mesh should be saved to the meshes/
directory under the name gated_dot.msh. We refer to
qtcad.builder.Builder.mesh for further details.
The resulting mesh is shown below. Note how thinner layers use smaller mesh-sizes, as does the dot-region.
builder.view(
surfaces=False,
volume_labels=True,
angles=(-90, 0, 85),
save="out/mesh.png",
)
builder.view(
surfaces=False,
volume_labels=True,
angles=(-90, 0, 85),
save="out/mesh_dr.png",
groups=["substrate.dot_region", "two_deg.dot_region", "spacer.dot_region"],
)
<qtcad.builder.builder.Builder object at 0x7f5996b46270>
Total running time of the script: (1 minutes 0.520 seconds)