Simulation#
Circuit-level model hierarchy#
SAX simulations expand unmodeled assemblies until they reach cells with declared models. Model declarations therefore define hierarchy boundaries instead of relying on a fixed depth.
Coupled resonators declare their own models so the coupling is preserved. Chip assemblies remain unmodeled and expand to these boundaries and other modeled primitives.
For new components, attach the compact model through schematic_function and keep its
port_order consistent with the layout ports.
AEDT and COMSOL simulation utilities.
This package provides class-based interfaces for setting up HFSS simulations (eigenmode and driven modal) and Q3D Extractor parasitic extractions from gdsfactory components, plus MPh-based COMSOL geometry and study builders.
The AEDT wrappers use the PyAEDT library to interface with Ansys HFSS and Q3D Extractor.
HFSS workflow:
Prepare a component with
prepare_component_for_aedt()Export to GDS and import into HFSS with
qpdk.simulation.hfss.HFSS.import_component()Configure simulation setup (e.g. Eigenmode or Driven) manually via PyAEDT
Extract results with
qpdk.simulation.hfss.HFSS.get_eigenmode_results()orqpdk.simulation.hfss.HFSS.get_sparameter_results()
Q3D Extractor workflow:
Prepare a component with
prepare_component_for_aedt()Export to GDS and import into Q3D with
qpdk.simulation.q3d.Q3D.import_component()Assign signal nets with
qpdk.simulation.q3d.Q3D.assign_nets_from_ports()Configure Q3D setup and analyze
Extract capacitance matrix with
qpdk.simulation.q3d.Q3D.get_capacitance_matrix()
COMSOL workflow:
Extract metal polygons and optional feed ports with
prepare_comsol_layout()Build a 3D air/silicon model with sheet metal via
build_comsol_sheet_model()Add a CPW RF or qubit electrostatic study with
add_cpw_rf_study()oradd_capacitance_study()Mesh it, optionally refined at the metal plane, with
refine_metal_plane_mesh(), or with absolute sizes at the metal viapin_absolute_mesh_sizes()
The steps above are also available as one chainable class:
COMSOL builds a model through
create_sheet() or
create_metal(), holds the layout, and
offers a method per study and mesh helper, so neither the model nor the layout
has to be passed again. Solving, saving, and evaluating are MPh’s own methods.
Note
The AEDT wrappers require uv sync --extra hfss. The COMSOL builders
require uv sync --extra comsol and a local COMSOL installation. Only
COMSOL imports MPh, and it is
exposed lazily, so the layout and helper modules and this package stay
importable without it.
Example
>>> from ansys.aedt.core import Hfss
>>> from qpdk.simulation import HFSS, prepare_component_for_aedt
>>> from qpdk.cells import resonator
>>> comp = resonator(length=4000, meanders=4)
>>> prepared_comp = prepare_component_for_aedt(comp)
>>> hfss_app = Hfss(project="resonator_sim", solution_type="Eigenmode")
>>> hfss_sim = HFSS(hfss_app)
>>> hfss_sim.import_component(prepared_comp)
References
PyAEDT documentation: https://aedt.docs.pyansys.com/
HFSS import_gds_3d: https://aedt.docs.pyansys.com/version/stable/API/_autosummary/ansys.aedt.core.hfss.Hfss.import_gds_3d.html
Q3D Extractor: https://aedt.docs.pyansys.com/version/stable/API/_autosummary/ansys.aedt.core.q3d.Q3d.html
MPh: MPh-py/MPh
Common#
Neutral geometry preparation shared by the AEDT and COMSOL exporters.
This module is pure gdsfactory geometry: it turns a QPDK mask into the physical metal a field solver actually sees. It has no PyAEDT, MPh, or COMSOL imports so both exporters can depend on it.
- qpdk.simulation.layout.prepare_metal_layout(component, margin_draw=0.0, margin_etch=0.0, *, name=None)[source]#
Fold a QPDK mask into positive metal geometry for a field solver.
Applies the additive metals to the etch layer, inverts the mask polarity around the component bounding box, grows the ground by
margin_draw, drops the etch layers, and re-adds the original ports.- Parameters:
- Returns:
A copy of the component prepared for simulation.
- Return type:
Component
Base AEDT simulation utilities using PyAEDT.
This module provides shared helper functions and a base class for AEDT simulations (HFSS, Q3D, Q2D) from gdsfactory components.
- class qpdk.simulation.aedt_base.AEDTBase(*args, **kwargs)[source]#
Bases:
objectBase class for AEDT simulations.
- add_substrate(component, thickness=500.0, material='silicon', name='Substrate')[source]#
Add a substrate box below the component geometry.
- property modeler#
The AEDT modeler instance.
- qpdk.simulation.aedt_base.add_materials_to_aedt(app)[source]#
Add QPDK materials to the PyAEDT application.
- Parameters:
app (Hfss | Q2d | Q3d)
- Return type:
None
- qpdk.simulation.aedt_base.detach_desktop_logging(app)[source]#
Stop PyAEDT reading the AEDT desktop on every log message.
Logger._log_on_desktoptests the desktop before it testssettings.enable_desktop_logs, so the setting PyAEDT turns off itself in non-graphical mode does not prevent the read. The read goes throughDesktop.odesktop, whose failure path releases the gRPC plugin’s AEDT handle, so a stray log message can end a session that AEDT is still running. Dropping the logger’s reference to the desktop is whatDesktop.release_desktopdoes at the end of a session anyway.PyAEDT has one logger per process and every desktop construction points it back at that desktop, so this is process-global and needs calling after each
Hfss,Q3dorQ2dis built. Messages the logger reads out of the desktop, such asget_messages(aedt_messages=True), stop working afterwards.- Parameters:
app (Any) – The AEDT application (an
Hfss,Q3dorQ2dinstance).- Return type:
None
- qpdk.simulation.aedt_base.export_component_to_gds_temp(component, gds_path=None, prefix='qpdk_aedt_')[source]#
Context manager for exporting a component to a temporary GDS file.
The GDS is written without the layout’s context info, where gdsfactory keeps its ports, cross-sections and settings. AEDT needs the geometry only, and on AEDT 2026 R1 the importer fails on a file that carries the info: the import returns success, the AEDT process is gone moments later, and every later call in the session reports a dead desktop. A consumer that reads the file back with gdsfactory rather than handing it to AEDT gets no ports from it.
- qpdk.simulation.aedt_base.fit_view(app)[source]#
Frame the model in a graphical AEDT session, and do nothing when headless.
modeler.fit_allis a view operation. A non-graphical session has no view, and on AEDT 2026 R1 the failed call loses the gRPC channel: the geometry is fine, but every later call in the session reports a dead desktop.The guard reads the flag the session was constructed with, not what the session itself reports, so attaching to an existing headless session still needs
non_graphicalset orPYAEDT_NON_GRAPHICALexported. A released session has no desktop left and raises.- Parameters:
app (Any) – The AEDT application (an
Hfss,Q3dorQ2dinstance).- Return type:
None
- qpdk.simulation.aedt_base.layer_stack_to_gds_mapping(layer_stack=None, thickness_override=None)[source]#
Convert a LayerStack to HFSS/Q3D GDS import mapping dictionary.
Vacuum levels (
material == "vacuum") are skipped: the AEDT background region is already vacuum and any vacuum box is added explicitly withqpdk.simulation.hfss.HFSS.add_air_region(), so importing vacuum as GDS geometry is redundant. This also resolves the Substrate/Vacuum collision onLAYER.SIM_AREA(98, 0).pyaedt’s
import_gds_3dkeys the mapping on GDS layer number only — the GDS datatype is not part of the key — so two levels sharing a layer number (e.g.LAYER.AB_DRAW= (10, 0) andLAYER.AB_VIA= (10, 1)) are merged into a single entry when they share a material and their z-spans join into one box; otherwise an error is raised rather than silently importing wrong geometry. Levels sharing a layer number are processed in ascendingzminorder so the result does not depend on the layer stack’s insertion order.- Returns:
Dictionary mapping layer number to (elevation, thickness) tuple.
- Raises:
ValueError – If two levels share a GDS layer number but cannot be merged into a single (elevation, thickness) entry because their materials differ (regardless of
thickness_override) or, withthickness_override=None, their z-spans do not join into a single box.- Parameters:
layer_stack (LayerStack | None)
thickness_override (float | None)
- Return type:
- qpdk.simulation.aedt_base.object_names_to_materials(object_names, layer_stack)[source]#
Map imported object names to AEDT material names based on the layer stack.
Resolves each object to its
LayerLeveleither by parsing thesignal<layer_number>GDS import names (same convention asrename_imported_objects()) or by matching the (renamed) object name against layer stack names. Metal levels (materials with infinite relative permittivity inmaterial_properties) map to"pec"; dielectric levels map to their layer stack material name.- Returns:
Dictionary mapping object names to AEDT material names.
- Raises:
ValueError – If an object cannot be resolved to a layer stack level, or its level material is not registered in
material_properties. Failing closed avoids silently leaving objects with the project default material, which would make extracted results wrong rather than failed.- Parameters:
- Return type:
- qpdk.simulation.aedt_base.prepare_component_for_aedt(component, margin_draw=0.0, margin_etch=0.0, *, name=None)[source]#
Prepare a component for AEDT simulation export.
Thin backward-compatible wrapper around
qpdk.simulation.layout.prepare_metal_layout(), keeping the historical<component>_aedtname for the initial preparation cell.
HFSS#
HFSS simulation utilities using PyAEDT.
- class qpdk.simulation.hfss.HFSS(*args, **kwargs)[source]#
Bases:
AEDTBaseHFSS simulation wrapper.
Provides high-level methods for importing components into HFSS, setting up simulation regions, and extracting results.
- add_air_region(component, height=500.0, substrate_thickness=500.0, pec_boundary=False, name='AirRegion')[source]#
Add an air region (vacuum box) around the component.
- Parameters:
component (Component) – The component to create air region around.
height (float) – Height above the component in micrometers.
substrate_thickness (float) – Depth below surface for the region.
pec_boundary (bool) – If True, assign PerfectE boundary conditions to outer faces.
name (str) – Name of the created region object.
- Returns:
Name of the created region object.
- Return type:
- add_lumped_ports(ports, cpw_gap, cpw_width)[source]#
Add lumped ports to HFSS at given port locations.
- get_eigenmode_results(setup_name='EigenmodeSetup')[source]#
Extract eigenmode simulation results.
- Parameters:
setup_name (str) – Name of the setup to get results from.
- Returns:
frequencies: List of eigenmode frequencies in GHz
q_factors: List of Q factors for each mode
- Return type:
Dictionary containing
- get_sparameter_results(setup_name='DrivenSetup', sweep_name='FrequencySweep')[source]#
Extract S-parameter results from a driven simulation.
- import_component(component, layer_stack=None, *, import_as_sheets=False, units='um', gds_path=None)[source]#
Import a gdsfactory component into HFSS.
- Parameters:
component (Component) – The gdsfactory component to import.
layer_stack (LayerStack | None) – LayerStack defining thickness and elevation for each layer. If None, uses QPDK’s default LAYER_STACK.
import_as_sheets (bool) – If True, imports metals as 2D sheets (zero thickness) and assigns PerfectE boundary to them. If False, imports as 3D objects with thickness from layer_stack and assigns PerfectE boundary to their surfaces.
units (str) – Length units for the geometry (default: “um” for micrometers).
gds_path (str | Path | None) – Optional path to write the GDS file. If None, uses a temporary file.
- Returns:
True if import was successful, False otherwise.
- Return type:
- class qpdk.simulation.hfss.LumpedPortConfig[source]#
Bases:
TypedDictConfiguration for defining a lumped port rectangle in HFSS.
- qpdk.simulation.hfss.lumped_port_rectangle_from_cpw(center, orientation, cpw_gap, cpw_width)[source]#
Calculates parameters for a lumped port based on its orientation.
- Parameters:
- Returns:
A dictionary containing ‘origin’, ‘sizes’, and ‘integration_line’ for HFSS.
- Raises:
ValueError – If port orientation is not a multiple of 90 degrees.
- Return type:
Q3D and Q2D#
Q3D and Q2D simulation utilities using PyAEDT.
- class qpdk.simulation.q3d.Q2D(*args, **kwargs)[source]#
Bases:
AEDTBaseQ2D simulation wrapper.
Provides methods for 2D cross-sectional impedance extraction.
- create_2d_from_cross_section(cross_section, layer_stack=None, *, ground_width=None, units='um')[source]#
Create a 2D model from a CPW cross-section for impedance extraction.
Builds the cross-sectional geometry of a coplanar waveguide in Ansys Q2D (2D Extractor).
- Parameters:
cross_section (CrossSectionSpec) – A gdsfactory cross-section specification describing the CPW geometry (width and gap).
layer_stack (LayerStack | None) – LayerStack defining substrate and conductor properties. If None, uses QPDK’s default
LAYER_STACK.ground_width (float | None) – Width of each coplanar ground plane in µm. If None, defaults to 10× the CPW gap.
units (str) – Length units for the Q2D geometry (default
"um").
- Returns:
Dictionary with keys
"signal","gnd_left","gnd_right","substrate"mapping to the created Q2D object names.- Raises:
ValueError – If cross-section mapping fails or dimensions are invalid.
- Return type:
- class qpdk.simulation.q3d.Q3D(*args, **kwargs)[source]#
Bases:
AEDTBaseQ3D Extractor simulation wrapper.
Provides methods for importing components into Q3D and performing parasitic capacitance/inductance extraction.
- assign_nets_from_ports(ports, conductor_objects)[source]#
Assign Q3D signal nets based on gdsfactory port locations.
For each gdsfactory port, finds the conductor object whose bounding-box center is nearest to the port center and assigns it as a Q3D signal net.
- Parameters:
ports (Ports) – Collection of gdsfactory ports defining signal locations.
conductor_objects (list[str]) – List of conductor object names created by
import_component().
- Returns:
List of assigned signal net names (one per port).
- Return type:
- get_capacitance_matrix(setup_name='Q3DSetup')[source]#
Extract the capacitance matrix from a Q3D Extractor simulation.
Retrieves all capacitance matrix entries (e.g.
C(o1,o1),C(o1,o2)) from the solved Q3D setup.- Parameters:
setup_name (str) – Name of the analysis setup.
- Returns:
DataFrame with one column per capacitance expression containing the extracted values in Farads.
- Return type:
DataFrame
- import_component(component, layer_stack=None, *, units='um', gds_path=None)[source]#
Import a gdsfactory component into Q3D Extractor.
Imports the component’s GDS geometry into a Q3D Extractor project, mapping each GDS layer to a 3D conductor at the appropriate elevation and thickness from the layer stack.
- Parameters:
component (Component) – The gdsfactory component to import.
layer_stack (LayerStack | None) – LayerStack defining thickness and elevation for each layer. If None, uses QPDK’s default LAYER_STACK.
units (str) – Length units for the geometry (default: “um” for micrometers).
gds_path (str | Path | None) – Optional path to write the GDS file. If None, uses a temporary file.
- Returns:
List of newly created conductor object names in Q3D.
- Raises:
RuntimeError – If GDS import fails.
- Return type:
COMSOL#
The builders below return a plain MPh model. Study and local refinement helpers take
that model and its layout; absolute mesh helpers use the model and named selections.
COMSOL wraps the two in one object: it subclasses
mph.Model, so MPh solves, saves, and evaluates it as usual, and it holds the
layout, so a setup reads as a chain.
Build with
create_sheet()orcreate_metal()Add a study with
add_cpw_rf_study()oradd_capacitance_study()Mesh it with
refine_metal_plane_mesh(),pin_absolute_mesh_sizes(), orpin_absolute_edge_mesh_sizes()Solve, save, and evaluate with MPh’s own
solve,save, andevaluate
Constructing the class needs the comsol extra; geometry and result helpers remain
importable without it.
An MPh model with QPDK layout, study, and mesh methods.
COMSOL keeps the extracted layout with the model while retaining MPh’s
solve, save, and evaluation API. Import it from qpdk.simulation.comsol or
qpdk.simulation after installing the optional comsol extra.
- class qpdk.simulation.comsol.model.COMSOL(model, layout)[source]#
Bases:
ModelAn MPh model holding the QPDK layout its geometry was built from.
Use
create_sheet()orcreate_metal()to build one. The study and mesh methods then use the stored layout. Study methods return the model for chaining; mesh methods return the resulting element count.- Parameters:
model (mph.Model)
layout (ComsolLayout)
- add_capacitance_study(*, conductors, terminal, grounds, voltage_v=1.0, mesh_size=7)[source]#
Add an unsolved electrostatic capacitance study.
Calls
add_capacitance_study()on this model and its layout.- Parameters:
conductors (tuple[tuple[str, Point], ...]) – One
(tag, point)pair per metal face. The tag names the face selection created oncomp1, and the point in µm on the sheet plane sits well inside that face.terminal (str) – Tag of the conductor to drive with the voltage terminal.
grounds (tuple[str, ...]) – Tags of the conductors to ground. Together with
terminalthey name every conductor exactly once.voltage_v (float) – Terminal voltage in V, positive.
mesh_size (int) – COMSOL mesh size, an integer from 1 (finest) to 9 (coarsest).
- Returns:
This model, so the call chains.
- Return type:
Self
- add_cpw_rf_study(*, cpw_gap_um, frequency_ghz=7.5, mesh_size=8, effective_index_shift=2.5)[source]#
Add an unsolved CPW full-wave study.
Calls
add_cpw_rf_study()on this model and its layout. The model has to be a sheet model whose layout was extracted withcrop_to_feed_ports=True.- Parameters:
cpw_gap_um (float) – Width of the etch gap between the centre conductor and the ground at the ports, in µm.
frequency_ghz (float) – Boundary mode analysis reference and initial frequency in GHz.
mesh_size (int) – COMSOL mesh size, an integer from 1 (finest) to 9 (coarsest).
effective_index_shift (float) – Effective-index shift both boundary mode analyses search around, positive and finite.
- Returns:
This model, so the call chains.
- Return type:
- classmethod create_metal(client, layout, *, metal_thickness_um=0.2, name='QPDK metal')[source]#
Build a metal model, with the polygons extruded to a thickness.
The layout-agnostic geometry milestone: no physics, materials, ports, or studies, and the feed ports are not used.
- Parameters:
client (mph.Client) – A connected
mph.Client.layout (ComsolLayout) – Extracted metal polygons and feed ports in µm.
metal_thickness_um (float) – Extrusion height in µm, strictly positive.
name (str) – Name of the COMSOL model.
- Returns:
The built model, holding the layout it was built from. The builder validates the thickness and refuses a layout with no polygons.
- Return type:
Self
- classmethod create_sheet(client, layout, name, *, substrate_thickness_um=200.0, air_height_um=200.0, lateral_margin_um=0.0, silicon_relative_permittivity=11.45)[source]#
Build a sheet model, with the metal as faces on the z = 0 interface.
- Parameters:
client (mph.Client) – A connected
mph.Client.layout (ComsolLayout) – Extracted metal polygons and bounding box in µm.
name (str) – Name of the COMSOL model.
substrate_thickness_um (float) – Silicon thickness below the interface, in µm.
air_height_um (float) – Air height above the interface, in µm.
lateral_margin_um (float) – Margin around the layout bounding box for both blocks, in µm.
silicon_relative_permittivity (float) – Relative permittivity of the silicon block, positive and finite. Defaults to the QPDK technology value for Si.
- Returns:
The built model, holding the layout it was built from. The builder validates the thicknesses, the margin, the permittivity, and the resulting geometry, and a layout with holes needs the Design Module licence.
- Return type:
Self
- pin_absolute_edge_mesh_sizes(*, edge_selection, global_hmax_um, global_hmin_um, edge_hmax_um, edge_hmin_um)[source]#
Mesh at absolute element sizes on a named edge selection.
Calls
pin_absolute_edge_mesh_sizes()on this model and its layout.- Parameters:
edge_selection (str) – Name of the edge selection to pin the local sizes on.
global_hmax_um (float) – Largest element size in µm away from the edges.
global_hmin_um (float) – Smallest element size in µm away from the edges.
edge_hmax_um (float) – Largest element size in µm on the selected edges.
edge_hmin_um (float) – Smallest element size in µm on the selected edges.
- Returns:
The number of mesh elements the pinned sequence built.
- Return type:
- pin_absolute_mesh_sizes(*, global_hmax_um, global_hmin_um, face_sizes, hgrad=None, hcurve=None, hnarrow=None)[source]#
Mesh at absolute element sizes.
Calls
pin_absolute_mesh_sizes()on this model and its layout.- Parameters:
global_hmax_um (float) – Largest element size in µm away from the metal.
global_hmin_um (float) – Smallest element size in µm away from the metal.
face_sizes (Mapping[str, tuple[float, float]]) –
(hmax, hmin)element sizes in µm keyed by the name of a face selection oncomp1.hgrad (float | None) – Maximum element growth rate for the global sizes.
hcurve (float | None) – Curvature resolution for the global sizes, in elements per radian.
hnarrow (float | None) – Narrow region resolution for the global sizes.
- Returns:
The number of mesh elements the pinned sequence built.
- Return type:
- refine_metal_plane_mesh(passes, *, z_half_um=20.0, refine_box=None)[source]#
Mesh and refine around the metal plane.
Calls
refine_metal_plane_mesh()on this model and its layout.- Parameters:
passes (int) – Number of refinement passes, a non-negative integer.
z_half_um (float) – Half-height in µm of the refine box above and below the metal plane.
refine_box (ComsolBoundingBox | None) – x/y bounds in µm to refine instead of the layout bounding box.
- Returns:
The number of mesh elements after meshing.
- Return type:
Extract a gdsfactory component into COMSOL-ready metal polygons and ports.
This module is pure geometry: it converts a QPDK M1_ETCH mask into physical
M1_DRAW metal polygons (in micrometres) plus optional feed-port metadata so a
separate COMSOL model builder can consume it. Layouts that are not fed (e.g. an
eigenmode qubit cell) are extracted with feed_ports=None and carry no feed
metadata. No COMSOL, MPh, or PyAEDT imports live here.
The negative-mask trick lives in the neutral
prepare_metal_layout(): it folds the additive
metal into the etch layer, then inverts the mask around the component bounding
box and applies the ground margin. The result exchanged here has positive
M1_DRAW metal, no etch layers, and the original ports re-added.
Optionally the prepared metal can be cropped at the two feed-port planes so a component whose CPW conductors and both etch-gap strips reach those planes ends in open CPW cross sections instead of solid ground.
- class qpdk.simulation.comsol.layout.ComsolBoundingBox(*, xmin, ymin, xmax, ymax)[source]#
Bases:
objectAxis-aligned bounding box of the prepared ground region, in µm.
- class qpdk.simulation.comsol.layout.ComsolFeedPort(*, name, center, width, orientation)[source]#
Bases:
objectA feed port selected for the COMSOL model, in µm and degrees.
- class qpdk.simulation.comsol.layout.ComsolLayout(*, polygons, feed_ports, bbox)[source]#
Bases:
objectExtracted COMSOL geometry and feed ports for one component.
All coordinates are in micrometres in the component’s own frame.
feed_portsis empty for layouts extracted withfeed_ports=None.Note
The feed ports are reported as-is. Whether each one actually sits on the outer boundary of the prepared ground region is not asserted here: after the mask inversion the ground plane surrounds the feeds, so a port center can legitimately lie inside the bounding box. A model builder that needs the port to coincide with a ground boundary must check that against
polygonsandbboxitself. The exception is an extraction made withcrop_to_feed_ports=True, where the two feed planes bound the metal and each feed center lies on a face ofbbox.- Parameters:
polygons (tuple[ComsolPolygon, ...])
feed_ports (tuple[ComsolFeedPort, ...])
bbox (ComsolBoundingBox)
- class qpdk.simulation.comsol.layout.ComsolPolygon(*, outline, holes=())[source]#
Bases:
objectOne physical metal polygon in µm.
outlineis the outer boundary (vertices in order, first vertex not repeated).holesare inner boundaries, i.e. etched negative space enclosed by metal.A single simple COMSOL polygon point list cannot encode a hole, so a consumer that only accepts plain outlines must either subtract
holesitself or reject polygons whereholesis non-empty.outlinealone is the complete shape only whenholesis empty.
- qpdk.simulation.comsol.layout.prepare_comsol_layout(component, feed_ports=('coupling_o1', 'coupling_o2'), ground_margin=100.0, *, crop_to_feed_ports=False)[source]#
Extract M1_DRAW metal polygons and optional feed ports from a component.
- Parameters:
component (Component) – The gdsfactory component to extract. It must contain an M1_ETCH mask; positive M1_DRAW shapes alone do not define the gaps. The original ports are preserved.
feed_ports (tuple[str, str] | None) – Names of exactly two distinct ports to expose as feeds, or
Nonefor an unfed layout, in which case the result carries no feed ports.Nonefits layouts with no CPW feedline, e.g. an eigenmode qubit cell.ground_margin (float) – Positive margin in µm added around the component bounding box to form the ground plane.
crop_to_feed_ports (bool) – When
True, cut the prepared metal back to the two feed-port planes so each external face shows an open CPW cross section instead of the extended ground. Requires two feed ports that are axis-aligned, opposite, outward-facing, and share their transverse coordinate (left/right along x, or bottom/top along y). Cropping is refused unless every M1_DRAW and M1_ETCH polygon of the input component lies between the planes and the etch gaps reach both planes, so no resonator metal is cut and no face is shorted. The planes snap outwards to the database grid, so the returnedbboxcan exceed the port span by less than one database unit. Defaults toFalse, which leaves the prepared ground untouched.
- Returns:
A
ComsolLayoutwith metal polygons (holes preserved), the selected feed ports (empty whenfeed_portsisNone), and the prepared bounding box, all in µm.- Raises:
ValueError – If
ground_marginis not positive and finite, iffeed_portsis supplied but does not name two distinct available ports, if a feed port is not cardinal or has non-finite coordinates or width, if the component has no M1_ETCH mask or carries geometry on unsupported fabrication layers, if no M1_DRAW metal remains, or ifcrop_to_feed_portsis set but the feeds are not a valid opposite pair or the component does not fit between the planes.- Return type:
Build unsolved extruded metal geometry from an extracted layout.
COMSOL extends MPh’s model with QPDK
layout, study, and mesh methods and builds a model through this builder; the
sheet builder for RF and electrostatic studies lives in
qpdk.simulation.comsol.sheet.
- qpdk.simulation.comsol.metal.build_comsol_metal_model(client, layout, *, metal_thickness_um=0.2, name='QPDK metal')[source]#
Create a COMSOL 3D model holding the layout’s metal as extruded polygons.
Works for any extracted layout, fed or not: the feed ports are not used here. Each polygon outline becomes a solid polygon on work plane
wp1; each hole becomes a second polygon subtracted by its ownDifferencefeature. The finished work plane is extruded tometal_thickness_umbyext1.- Parameters:
client (mph.Client) – A connected
mph.Client.layout (ComsolLayout) – Extracted metal polygons and feed ports in µm.
metal_thickness_um (float) – Extrusion height in µm, strictly positive.
name (str) – Name of the COMSOL model.
- Returns:
no physics, materials, ports, or studies have been added, and the model has not been saved.
- Return type:
The MPh model. Geometry only
- Raises:
ValueError – If
metal_thickness_umis not positive and finite, or the layout has no polygons to extrude.
Build air and silicon domains with QPDK metal sheets at their interface.
The metal polygons are faces at z = 0. Layouts without holes use a work
plane and Form Union. Layouts with holes use the Design Module’s
ProjectToFaces to imprint each outline and hole onto the interface; the
builder checks that those faces survived. Air and silicon get named selections
and materials. Physics and studies are added separately by
add_cpw_rf_study() or
add_capacitance_study().
- qpdk.simulation.comsol.sheet.AIR_SELECTION = 'air'#
Domain selection tags, reused by the physics added on top of this model.
- qpdk.simulation.comsol.sheet.SILICON_RELATIVE_PERMITTIVITY = 11.45#
Canonical relative permittivity of the silicon substrate and of the air above it, from the materials of the QPDK layer stack’s substrate and vacuum levels.
- qpdk.simulation.comsol.sheet.build_comsol_sheet_model(client, layout, name, *, substrate_thickness_um=200.0, air_height_um=200.0, lateral_margin_um=0.0, silicon_relative_permittivity=11.45)[source]#
Create a COMSOL 3D model holding the layout’s metal as interface sheets.
The air and silicon blocks touch at
z = 0and span the layout bounding box grown bylateral_margin_um. A layout without holes puts the metal polygons on a work plane and lets Form Union leave one face per polygon. A layout with holes keeps the interface between the blocks and projects a construction work plane’s faces onto it, so the model ends up with two dielectric domains and one face per metal and per hole region.A layout with holes is built with the Design Module’s
ProjectToFacesfeature and the CAD kernel geometry representation, so it needs the Design Module license; a layout without holes needs neither.- Parameters:
client (mph.Client) – A connected
mph.Client.layout (ComsolLayout) – Extracted metal polygons and bounding box in µm.
name (str) – Name of the COMSOL model.
substrate_thickness_um (float) – Silicon thickness below the interface, in µm.
air_height_um (float) – Air height above the interface, in µm.
lateral_margin_um (float) – Margin around the layout bounding box for both blocks, in µm.
silicon_relative_permittivity (float) – Relative permittivity of the silicon block, positive and finite. Defaults to the QPDK technology value for Si.
- Returns:
The MPh model, with geometry, selections, and materials only.
- Raises:
ValueError – If a thickness is not positive and finite, if the margin is negative or not finite, if the permittivity is not positive and finite, if the layout has no polygons, or, for a layout with holes, if the built geometry does not carry one face per metal polygon with the holes still cut out of the metal.
- Return type:
mph.Model
Add an unsolved CPW full-wave study to a COMSOL model that uses metal sheets.
The model comes from
build_comsol_sheet_model(): air and silicon
meeting at z = 0 with the layout’s metal imprinted on that plane as faces.
This module makes those faces perfect electric conductors, turns the two feed
cross sections into numeric ports, and adds a mesh sequence and a frequency
study. Nothing is meshed or solved here.
The port faces and the integration line are found from the feed ports and the
bounding box of the ComsolLayout, never
from hard-coded entity IDs, so a layout whose feeds do not sit on opposite
bounding-box faces is refused instead of quietly porting the wrong plane.
What a solve gives is the S-parameters of the CPW section between the two
planes: emw.S21dB is the through response at frequency_ghz,
emw.S11dB the reflection. It is not an eigenmode solve, so it says nothing
about a qubit hanging off the feedline.
- qpdk.simulation.comsol.rf.INPUT_FACES_SELECTION = 'input_faces'#
Named selections for the two port cross sections, low side first.
- qpdk.simulation.comsol.rf.INPUT_GAP_SELECTION = 'input_gap'#
Named selections for the integration line of each port, low side first.
- qpdk.simulation.comsol.rf.METAL_FACES_SELECTION = 'metal_faces'#
Named selection holding every metal sheet face, for the PEC boundary.
- qpdk.simulation.comsol.rf.add_cpw_rf_study(model, layout, *, cpw_gap_um, frequency_ghz=7.5, mesh_size=8, effective_index_shift=2.5)[source]#
Add an unsolved CPW full-wave study to a sheet model.
- Parameters:
model (mph.Model) – The MPh model from
build_comsol_sheet_model().layout (ComsolLayout) – The same layout the model was built from, extracted with
crop_to_feed_ports=Trueso both feed planes are open CPW cross sections on the bounding box.cpw_gap_um (float) – Width of the etch gap between the centre conductor and the ground at the ports, in µm. The voltage integration line spans it.
frequency_ghz (float) – Boundary mode analysis reference and initial frequency in GHz.
mesh_size (int) – COMSOL mesh size, an integer from 1 (finest) to 9 (coarsest).
effective_index_shift (float) – Effective-index shift both boundary mode analyses search around, positive and finite. The default 2.5 suits the silicon/air CPW section and reproduces the saved examples.
- Returns:
The same model, with the metal and port selections, the EMW interface, the mesh sequence, and the frequency study added. It has not been meshed or solved and has not been saved.
- Raises:
ValueError – If the gap is not positive and finite, if the frequency is not positive and finite, if the mesh size is not an integer in 1 to 9, if the effective-index shift is not positive and finite, if the layout does not carry two opposite outward-facing feeds on the bounding box, or if a selection does not resolve to the faces or edges the layout implies.
- Return type:
mph.Model
Add an electrostatic capacitance study to a COMSOL model that uses metal sheets.
The model comes from
build_comsol_sheet_model(), where the metal
is a set of faces on the silicon/air interface. This module picks those faces out
with points inside them, drives one of them with a voltage terminal, grounds the
others, and adds a mesh sequence and a stationary study. Nothing is solved here,
and nothing here is specific to a device: the caller names the conductors, which
one is driven, and which are grounded.
A solve gives electrostatic capacitance: es.C11 is the capacitance of the
driven conductor to the grounded rest of the chip, and the stored energy agrees
with it, since \(2 W_\text{e} / V^2\) equals es.C11. It is not an RF Josephson
eigenmode, and turning it into a qubit frequency needs an \(L_\text{J}\) picked
outside COMSOL: that LC estimate is not an eigensolve.
- qpdk.simulation.comsol.capacitance.add_capacitance_study(model, layout, *, conductors, terminal, grounds, voltage_v=1.0, mesh_size=7)[source]#
Add an unsolved electrostatic capacitance study to a sheet model.
- Parameters:
model (mph.Model) – The MPh model from
build_comsol_sheet_model().layout (ComsolLayout) – The metal polygons used to build that model. It must hold exactly one polygon per conductor; a metal polygon that no point names is refused rather than left as a dielectric interface.
conductors (tuple[tuple[str, Point], ...]) – One
(tag, point)pair per metal face. The tag names the face selection created oncomp1, and the point in µm on the sheet plane has to sit inside that face. It centres a small box, so it has to sit clear of the face’s edges.terminal (str) – Tag of the conductor to drive with the voltage terminal.
grounds (tuple[str, ...]) – Tags of the conductors to ground, in the order their Ground features are tagged. Together with
terminalthey have to name every conductor exactly once.voltage_v (float) – Terminal voltage in V, positive.
mesh_size (int) – COMSOL mesh size, an integer from 1 (finest) to 9 (coarsest).
- Returns:
The same model, with the selections, physics, mesh, and study added and nothing solved. Once the caller runs them,
es.C11agrees with2*es.intWe/voltage_v**2.- Raises:
ValueError – If the voltage is not positive and finite, if the mesh size is not an integer in 1 to 9, if a point is not a finite (x, y) pair, if the conductor tags do not name a terminal and a ground each, or if the layout, the polygons, or the points do not assign exactly one distinct metal face to each conductor.
- Return type:
mph.Model
- qpdk.simulation.comsol.capacitance.add_electrostatics(component, *, terminal, grounds, voltage_v)[source]#
Add Electrostatics with one voltage terminal and a ground per selection.
The interface covers every domain, so the default Free Space feature keeps the air, and a Charge Conservation feature on silicon uses that domain’s material permittivity through the default
epsilonr_mat.- Parameters:
component (Any) – The Java component to add the physics to, usually
comp1.terminal (str) – Name of the face selection to drive with the voltage source.
grounds (tuple[str, ...]) – Names of the face selections to ground, one Ground feature each. The features are tagged
gnd1,gnd2, and so on, in this order.voltage_v (float) – Terminal voltage in V, positive.
- Return type:
None
Mesh helpers for COMSOL sheet models.
Both COMSOL notebooks build a study on the same sheet geometry and then want one more thing from the mesh: the element count after a local refinement at the metal plane, to check that a solved quantity has stopped moving with mesh size. The sequence is the same in both, so it lives here once.
The study builders (add_cpw_rf_study() and
add_capacitance_study()) leave
mesh1 physics-controlled. Running it once realizes that sizing; a Refine
feature then switches the sequence to user-controlled, and the second run meshes
the refined box.
pin_absolute_mesh_sizes() is for the other kind of study, where the mesh
must not follow the domain at all. A physics-controlled mesh scales its element
sizes with the longest dimension of the domain, so a series that varies the
distance to the boundary would vary the near-metal resolution with it.
pin_absolute_edge_mesh_sizes() is the same idea one dimension down: the
local sizes go on a named edge selection instead of conductor faces, for a study
whose resolution has to follow a trace outline rather than cover a whole face.
- qpdk.simulation.comsol.mesh.DEFAULT_SIZE_TAG = 'size'#
Tag of the default
Sizefeature a physics-controlled build leaves in the sequence, and the one feature pinning the global sizes requires to be there. The mesh chapter of the COMSOL programming reference warns that the contents of a physics-controlled sequence may change between versions, so it is checked at run time rather than assumed.
- qpdk.simulation.comsol.mesh.EDGE_SIZE_TAG = 'size_edges'#
Tag of the
Sizefeaturepin_absolute_edge_mesh_sizes()creates for the named edge selection. It sits between the default size and the generator for the same reason every other size does.
- qpdk.simulation.comsol.mesh.FREE_TET_TAG = 'ftet_fixed'#
Tag of the generator this module creates, the only one left in the sequence. A
Sizefeature only affects the operation features after it, so this has to follow every size.
- qpdk.simulation.comsol.mesh.GENERATED_FREE_TET_TAG = 'ftet1'#
FreeTet generator COMSOL generates for a physics-controlled sequence. It does not survive an edit of the sequence on a 6.3 build, but exactly when it goes is undocumented, so it is swept either way. Nothing may depend on it being there.
- qpdk.simulation.comsol.mesh.pin_absolute_edge_mesh_sizes(model, *, edge_selection, global_hmax_um, global_hmin_um, edge_hmax_um, edge_hmin_um)[source]#
Mesh a sheet model with absolute element sizes on a named edge selection.
The edge counterpart of
pin_absolute_mesh_sizes(), for the case where the resolution has to follow an outline instead of covering a face: the bulk stays at the global sizes and oneSizefeature pins the edge sizes onedge_selection, which the caller has already created oncomp1. Both sizes are absolute in µm, so the edge resolution no longer moves with the domain. The sequence is left user-controlled, as there.The sequence is built the same way and refused if it comes out any other way, including an
edge_selectionthat resolves to no edge, which would leave the local size applying to nothing.- Parameters:
model (mph.Model) – A model carrying
comp1/mesh1from a study builder, with a named edge selection already created oncomp1.edge_selection (str) – Name of the edge selection to pin the local sizes on.
global_hmax_um (float) – Largest element size in µm away from the edges, positive and finite.
global_hmin_um (float) – Smallest element size in µm away from the edges.
edge_hmax_um (float) – Largest element size in µm on the selected edges.
edge_hmin_um (float) – Smallest element size in µm on the selected edges.
- Returns:
The number of mesh elements the pinned sequence built.
- Raises:
ValueError – If a size is not positive and finite, or if an
hminis larger than thehmaxit belongs to.RuntimeError – If the physics-controlled build left no default
sizefeature, the edge selection resolved to no edge, the sequence came out in an order the sizes would not apply in, the sequence is still physics-controlled afterwards, or COMSOL reported no usable element count.
- Return type:
- qpdk.simulation.comsol.mesh.pin_absolute_mesh_sizes(model, *, global_hmax_um, global_hmin_um, face_sizes, hgrad=None, hcurve=None, hnarrow=None)[source]#
Mesh a sheet model with absolute element sizes at the metal.
A physics-controlled mesh scales its sizes with the longest dimension of the domain, so the near-metal resolution moves whenever the domain does. This helper runs that mesh once to materialise the sequence, then pins every size to an absolute value in µm: the default
Sizefeature takes the global sizes, and oneSizefeature per entry offace_sizestakes the local sizes on the named face selection that entry is keyed by. The sequence is left user-controlled, so a later build or solve cannot re-derive the sizing.A
Sizefeature only affects the operation features after it, so the face sizes have to sit between the default size and the generator. The construction builds that order and refuses to mesh if the sequence comes out any other way, rather than silently meshing something else. The generated FreeTet of the physics-controlled sequence does not survive an edit, and when it goes is undocumented, so it is swept before and after the explicit generator is created, leaving exactly one generator. TheSizefeature properties are in the COMSOL 6.3 API reference, https://doc.comsol.com/6.3/doc/com.comsol.help.comsol/comsol_api_mesh.49.099.html- Parameters:
model (mph.Model) – A model carrying
comp1/mesh1fromadd_capacitance_study(), including the face selections that study creates.global_hmax_um (float) – Largest element size in µm away from the metal, positive and finite.
global_hmin_um (float) – Smallest element size in µm away from the metal.
face_sizes (Mapping[str, tuple[float, float]]) –
(hmax, hmin)element sizes in µm keyed by the name of a face selection oncomp1, oneSizefeature each, taggedsize_followed by the selection name.hgrad (float | None) – Maximum element growth rate for the global sizes.
hcurve (float | None) – Curvature resolution for the global sizes, in elements per radian.
hnarrow (float | None) – Narrow region resolution for the global sizes.
- Returns:
The number of mesh elements the pinned sequence built.
- Raises:
ValueError – If a mesh size is invalid or a minimum exceeds its maximum.
RuntimeError – If the physics-controlled build left no default
sizefeature, the sequence came out in an order the sizes would not apply in, the sequence is still physics-controlled afterwards, or COMSOL reported no usable element count.
- Return type:
- qpdk.simulation.comsol.mesh.refine_metal_plane_mesh(model, layout, passes, *, z_half_um=20.0, refine_box=None)[source]#
Mesh a sheet model and refine the mesh around the metal plane.
The mesh is run once at the study builder’s physics-controlled size, then refined
passestimes inside a box: it spansrefine_boxin x and y and ±``z_half_um`` in z, so the metal plane and the fields just off it are refined while the bulk air and silicon stay coarse. Passrefine_boxwhen the layout’s own box is much larger than the part that matters; a few refinement passes over a whole 3.5 mm ground box cost elements for nothing when the device is 1 mm across.- Parameters:
model (mph.Model) – A model carrying
comp1/mesh1fromadd_cpw_rf_study()oradd_capacitance_study().layout (ComsolLayout) – The layout the model was built from. Its bounding box sets the refine box unless
refine_boxis given.passes (int) – Number of refinement passes, a non-negative integer.
0meshes at the physics-controlled size without refining.z_half_um (float) – Half-height in µm of the refine box above and below the metal plane, positive and finite.
refine_box (ComsolBoundingBox | None) – x/y bounds in µm to refine instead of the layout bounding box.
Noneuses the layout’s own box.
- Returns:
The number of mesh elements after meshing.
- Raises:
ValueError – If
passesis not a non-negative integer, or ifz_half_umis not positive and finite.- Return type: