Note
Go to the end to download the full example code.
Advanced features
This tutorial walks through the creation of an imaginary device with
overlapping gates to demonstrate some more advanced features like
qtcad.builder.Builder.grow and qtcad.builder.Builder.pad_volume.
Fig. 21 The device that will be created in this tutorial.
We begin by importing the necessary modules and defining some characteristic lengths and layer thicknesses.
# sphinx_gallery_thumbnail_path = 'auto_examples/tutorials/images/sphx_glr_advanced_features_010.png'
from qtcad.builder import Builder, MeshAlgorithm3D
from pathlib import Path
import sys
script_dir = Path(sys.argv[0]).resolve().parent
# Mesh characteristic lengths
char_len = 50 # Note that this is very low and for demonstration purposes only
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 = 10
substrate_thick = 100 - two_deg_thick
dot_height = substrate_thick + two_deg_thick + spacer_thick
gate_thick = 10
gate_padding = 5
Next, we initialize a new instance of qtcad.builer.Builder and load our
photomask layout from an oasis file
(demo_dot.oas).
Fig. 22 The photomask layout for the device.
Note
In contrast to Gated Quantum Dot the shape names are defined
through text labels in the layout file. For layers containing
only one shape, it might be simpler to use
qtcad.builder.Builder.group_from_mask and omit naming the shapes.
builder = (
Builder(name="Demo")
.load_layout(script_dir / "layouts" / "demo_dot.oas")
.print_mask_tree()
)
Layout
├── Mask 4 "dot_region"
│ └── 0 Polygon "dot_region"
├── Mask 2 "footprint"
│ └── 0 Polygon "footprint"
└── Mask 3 "gates"
├── 0 Polygon "gate_2"
└── 1 Polygon "gate_1"
Above, we see that the layout contains three masks. The “footprint” mask defines the overall device footprint which we will later use to clip the volumes we create. The “gates” mask contains two rectangles which we will use as a basis to create gates later on. Finally, there is the “dot_region” mask which we will use to identify a region of the device that’s going to contain the quantum dot, i.e. charges confined in three dimensions, and in which all classical charges are excluded.
Next, we set the mesh size we are going to use for the subsequent operations.
builder.set_mesh_size(char_len)
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Bottom Gates
Now that the masks have been added, we are ready to create the gate volumes and surfaces. We will create four sets of gates in total. Horizontal and vertical lower gates, and horizontal and vertical upper gates.
To that end, we copy the “gates” mask and transform the shapes contained therein by rotating them 90 degrees and renaming them to “vertical_<original_name>”.
(
builder.use_mask("gates")
.copy_mask(
"vertical",
transformer=lambda shape: shape.named("vertical_" + shape.name),
rotate=90,
)
.print_mask_tree()
.use_mask(["gates", "vertical"])
.view_shapes(save="figs/advanced_features_gates_shapes.svg")
)
Layout
├── Mask 4 "dot_region"
│ └── 0 Polygon "dot_region"
├── Mask 2 "footprint"
│ └── 0 Polygon "footprint"
├── Mask 3 "gates"
│ ├── 0 Polygon "gate_2"
│ └── 1 Polygon "gate_1"
└── Mask 5 "vertical"
├── 0 Polygon "vertical_gate_2"
└── 1 Polygon "vertical_gate_1"
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Next, we create the bottom set of horizontal gates, again by copying and renaming the “gates” mask.
(
builder.use_mask("gates")
.copy_mask(
"bottom_horizontal",
transformer=lambda shape: shape.named("bottom_" + shape.name),
)
.set_z(substrate_thick - 2 * (gate_padding + gate_thick))
.extrude(gate_thick)
.view(
volume_labels=True,
surfaces=True,
save="figs/advanced_features_gates.svg",
)
)
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
If we show the surface labels, we see that the bottom, side and top of the created volumes receive distinct labels.
builder.view(
volume_labels=False,
surface_labels=True,
angles=(-45, 0, 45),
save="figs/advanced_features_gates_surfs.svg",
)
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Now, we create the vertical set of gates.
(
builder.use_mask("vertical")
.copy_mask(
"bottom_vertical",
transformer=lambda shape: shape.named("bottom_" + shape.name),
)
.set_z(offset=2 * gate_padding)
.extrude(gate_thick)
.quick_mesh()
.view(
volume_labels=True,
surfaces=True,
angles=(-85, 0, 45),
save="figs/advanced_features_gates_vertical_raw.png", # caption: The gates before adding padding.;
show_volume_faces=True,
)
.view(
volume_labels=True,
surfaces=True,
angles=(-90, 0, 45),
save="figs/advanced_features_gates_vertical.png", # caption: Side view showing the gap between the vertical and horizontal gates.;
show_volume_faces=True,
)
)
Fig. 23 The gates before adding padding.
Fig. 24 Side view showing the gap between the vertical and horizontal gates.
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Above, we used qtcad.builder.Builder.quick_mesh to generate a quick preview
of the mesh and show_volume_faces=True to highlight the faces of
the volumes. The colors indicate the physical group of the volumes.
Next, we’ll add an “oxide” padding layer around the gates. We will
use qtcad.builder.Builder.fill_mode to ensure that the padding does not
affect the groups the surfaces of the gates are assigned to. The
qtcad.builder.Builder.use_mask method selects the “footprint” mask and the
shape contained therein which will be used to clip the padding
volumes (see also Padding).
- Builder.pad_volume(group: GroupSpecifier, thickness: float, padding_suffix: str = '_padding', noclip: bool = False, polygonize: bool = True) Self
[🏗️ Operation] Add a padding layer of
thicknessaround the volumes in volume group with namegroup_name. The newly created padding volume will be namedgroup_name + padding_suffixand clipped horizontally by the currently selected shapes unlessnoclipisTrue.- Parameters:
group – The group(s) to pad. See
get_groupfor details on the allowed types.thickness – The padding thickness.
padding_suffix – Suffix to append to the group name for the new volume.
noclip – If
True, no horizontal clipping will occur. Default isFalse.polygonize – If
True, the newly created volumes will be recreated from their hull meshes to avoid problems with curved OpenCASCADE solids (default isTrue).
(
builder.use_mask("footprint")
.fill_mode()
.pad_volume(
lambda group: "gate" in group.name,
gate_padding,
padding_suffix="_padding",
)
.quick_mesh()
.view(
volume_labels=True,
surfaces=True,
angles=(-85, 0, 45),
save="figs/advanced_features_gates_padding_raw.png",
show_volume_faces=True,
)
)
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
The above operation generates a lot of groups (all ending in
_padding as configured above). To reduce clutter and make the
assignment of materials easier, we will merge all padding groups
groups using qtcad.builder.Builder.merge_groups.
- Builder.merge_groups(groups: GroupSpecifier, into: str) Self
[⚡ Utility] Merge multiple physical groups
groupsinto the (potentially new) groupnew.- Parameters:
groups – Group(s) to merge. See
get_groupfor details.into – The name of a new or existing group to merge into.
(
builder.merge_groups(
lambda group: "padding" in group.name and group.dim == 3, "padding"
)
.merge_groups(
lambda group: "padding" in group.name and group.dim == 2, "padding_surfaces"
)
.quick_mesh()
.view(
volume_labels=True,
surfaces=True,
angles=(-85, 0, 45),
save="figs/advanced_features_gates_padding.png",
show_volume_faces=True,
)
)
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Heterostructure
Next we will create the heterostructure layers that confine
particles in the quantum dot along the growth direction, similar
to Gated Quantum Dot. We will use qtcad.builder.Builder.fill_mode to create
the volume around the gates without altering them. We also use
qtcad.builder.Builder.set_min_elements to make sure the final mesh will be
fine enough to resolve the layer structure. In actual applications
the argument should be larger than two.
(
builder.use_mask("footprint")
.fill_mode()
.set_z(0)
# .set_min_elements(2)
.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)
#
# Caps
#
.set_group_name("cap")
.extrude(cap_thick)
.quick_mesh()
.view(
volume_labels=True,
surfaces=True,
angles=(-85, 0, 45),
show_volume_faces=True,
)
)
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Dot region
Finally, we will create the quantum dot region. We will use
qtcad.builder.Builder.set_z_from_group to position the bottom of the dot
region at the bottom of the substrate layer. We then use
qtcad.builder.Builder.overlay_mode to create the dot region while
maintaining the layer structure (see Extrusion and fragmentation modes).
We use qtcad.builder.Builder.group_from_mask to name the shape contained
in the “dot_region” mask accordingly.
(
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_mask()
.extrude(dot_height)
.quick_mesh()
.view(
volume_labels=True,
surfaces=True,
angles=(-45, 0, 45),
save="figs/advanced_features_dot_region.png", # caption: The dot region;
show_volume_faces=True,
groups=lambda g: "dot_region" in g.name,
)
)
Fig. 25 The dot region
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Top gates
Now we will add the upper set of gates. These are similar to the
bottom gates, but we will position them on top of the cap layer. To
achieve this, we will copy the mask containing the shapes of the
Horizontal gates and use qtcad.builder.Builder.deposit to create the gate
volumes atop the device. (Note that extrude would have the same
effect in this case.) We are using qtcad.builder.Builder.displace_mode to
make sure the bottom surfaces of the created volumes retain their
names.
- Builder.deposit(height: float, noclip: bool = False) Self
[🏗️ Operation] Deposit the currently selected shape(s) into the model up from the current z coordinate by
heightlength units. This is done by adding new material atop the topmost surfaces of the model. Ifnoclip=True, the new material is deposited on the whole top surface of the model, rather than clipped by the currently selected shapes.- Parameters:
height – The distance by which to deposit the shape(s) up into the model.
noclip – Whether to deposit the shape without horizontally clipping to the current shapes.
(
builder.use_mask("gates")
.set_mesh_size(char_len)
.displace_mode()
.group_from_shape()
.copy_mask(
"top_horizontal",
transformer=lambda shape: shape.named("top_horizontal_" + shape.name),
)
.deposit(gate_thick)
.quick_mesh()
.view(
volume_labels=True,
surfaces=True,
angles=(-45, 0, 45),
save="figs/advanced_features_top_gates_raw.png", # caption: The top gates before adding padding.;
show_volume_faces=True,
)
)
Fig. 26 The top gates before adding padding.
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
As before, we add a padding layer around the horizontal top gates
using qtcad.builder.Builder.pad_volume.
(
builder.use_mask("footprint")
.fill_mode()
.pad_volume(
lambda group: group.dim == 3 and "top_horizontal" in group.name,
thickness=gate_padding,
)
.merge_groups(lambda group: "padding" in group.name and group.dim == 3, "padding")
.merge_groups(
lambda group: "padding" in group.name and group.dim == 2, "padding_surfaces"
)
.quick_mesh()
.view(
volume_labels=True,
surfaces=True,
angles=(-85, 0, 45),
save="figs/advanced_features_top_gates.png", # caption: The top gates with padding.;
show_volume_faces=True,
)
)
Fig. 27 The top gates with padding.
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Finally, we use qtcad.builder.Builder.grow (see Deposition and Growth) to create
the top vertical gates.
- Builder.grow(thickness: float, noclip: bool = False, exact: bool = False, num_tries: int = 10, i_know_what_im_doing: bool = False, polygonize: bool = True) Self
[🏗️ Operation] Emulate a physical ‘growth’ operation to add material to the current topmost volumes. In contrast to
deposit, this will not only add material to the top of the volumes but also to their sides. The currently active shapes are used to horizontally clip the new volumes unlessnoclipisTrue.The resulting surfaces are labelled similar to the
extrudecommand. However, there is some ambiguity as to what surfaces are to be considered to be on top and what surfaces are on the side. Here, we choose surfaces whose (average) normal vector projected on the x-y plane is 10% larger than its projection on the z-axis to be on the “side”. Surfaces can be created/renamed usingassign_to_group,add_surfaceordeposit_surface.Warning
Due to problems in OpenCASCADE, this operation might fail. If it does it may help to retry with a different thickness or
polygonize=False. The warning can be suppressed withi_know_what_im_doing=True.- Parameters:
thickness – The distance by which to grow the selected shapes. Must be positive.
noclip – If
True, do not clip the created volumes horizontally.exact – If
exact=Truedo not try to slightly adjust the growth thickness in case growth fails and crash straightaway.num_tries – If
exact=False, try up tonum_triestimes to successively slightly reduce the thickness (by 0.1% each time) and retry.i_know_what_im_doing – If
False, a warning will be issued because this operation is known to be somewhat unstable due to problems in OpenCASCADE.polygonize – If
True, the newly created volumes will be recreated from their hull meshes to avoid problems with curved OpenCASCADE solids (default isTrue).
(
builder.use_mask("vertical")
.displace_mode()
.copy_mask(
"top_vertical",
transformer=lambda shape: shape.named("top_" + shape.name),
)
.grow(gate_thick)
.quick_mesh()
.view(
volume_labels=False,
surfaces=False,
angles=(-85, 0, 45),
save="figs/advanced_features_top_gates_vertical.png", # caption: The top vertical gates created using the grow operation.;
show_volume_faces=True,
)
)
Fig. 28 The top vertical gates created using the grow operation.
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/builder.py:379: UserWarning: Due to problems in OpenCASCADE, the grow operation might fail.
If it does it may help to retry with a different thickness or
.``polygonize=False``.
warnings.warn(
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/builder.py:3366: UserWarning: Intersection - BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection
to_grow, merged_boxes = self._get_top_volume(offset)
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/builder.py:399: UserWarning: Difference - BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning
self._grow_shape(shape, thickness, exact, num_tries, polygonize)
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/builder.py:3366: UserWarning: Intersection - BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection BOPAlgo_AlertAcquiredSelfIntersection
to_grow, merged_boxes = self._get_top_volume(offset)
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/builder.py:399: UserWarning: Difference - BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning BOPAlgo_AlertBadPositioning
self._grow_shape(shape, thickness, exact, num_tries, polygonize)
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Mesh generation
We create the final mesh using the default Delaunay algorithm with
qtcad.builder.Builder.mesh, enabling output from Gmsh and conformity
checking. Due to the complexity of the geometry, we will see some
warnings “ill shaped tets”. For applications, the HXT algorithm is
preferable, but takes considerably more time on this model.
(
builder.mesh(
algorithm3d=MeshAlgorithm3D.Delaunay,
show_gmsh_output=True,
check_connectivity=True,
)
.view(
volume_labels=True,
surfaces=True,
angles=(-45, 0, 45),
save="figs/advanced_features_final_mesh.png", # caption: The final mesh of the device.;
)
.view(
volume_labels=True,
surfaces=True,
angles=(-45, 0, 45),
save="figs/advanced_features_final_mesh_dot_region.png", # caption: The final mesh highlighting the dot region.;
groups=lambda g: "dot_region" in g.name,
)
)
Fig. 29 The final mesh of the device.
Fig. 30 The final mesh highlighting the dot region.
/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 27 volumes with 8 connected components
return func(self, *args, **kwargs)
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/util/decorators.py:33: UserWarning: 5 ill-shaped tets are still in the mesh
return func(self, *args, **kwargs)
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/util/decorators.py:33: UserWarning: 24 ill-shaped tets are still in the mesh
return func(self, *args, **kwargs)
nodes: 0%| | 0/5175 [00:00<?, ?it/s]
nodes: 0%| | 0/5175 [00:00<?, ?it/s]
nodes: 100%|██████████| 5175/5175 [00:00<00:00, 1674679.67it/s]
nodes: 100%|██████████| 5175/5175 [00:00<00:00, 1617401.13it/s]
nodes: 100%|██████████| 5175/5175 [00:00<00:00, 1572977.98it/s]
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
We also export the mesh and geometry to disk in the msh and xao formats, respectively, for later use.
builder.write(script_dir / "meshes" / "demo.msh")
builder.write(script_dir / "meshes" / "demo.xao")
<qtcad.builder.builder.Builder object at 0x7fd295d896a0>
Total running time of the script: (0 minutes 58.998 seconds)