#
"""
Variables
from pyaedt.HFSS import HFSS
with HFSS as hfss:
hfss["dim"] = "1mm" # design variable
hfss["$dim"] = "1mm" # project variable
Modeler
# Create a box, assign variables, and assign materials.
from pyaedt.hfss import Hfss
with Hfss as hfss:
hfss.modeler.create_box([0, 0, 0], [10, "dim", 10],
"mybox", "aluminum")
"""
from qiskit_metal.renderers.renderer_ansys_pyaedt.pyaedt_base import QPyaedt
from qiskit_metal.renderers.renderer_ansys.ansys_renderer import QAnsysRenderer
from qiskit_metal.draw.utility import to_vec3D, to_vec3D_list
from qiskit_metal.toolbox_metal.parsing import parse_entry, parse_units
from qiskit_metal import Dict
from typing import List, Tuple, Union
import pandas as pd
import shapely
from qiskit_metal import config
if not config.is_building_docs():
from qiskit_metal.toolbox_python.utility_functions import get_clean_name
[docs]
class QHFSSPyaedt(QPyaedt):
"""Subclass of pyaedt renderer for running HFSS simulations.
QPyaedt Default Options:
"""
default_setup = Dict(
drivenmodal=Dict(
name="Setup",
freq_ghz="5.0",
max_delta_s="0.1",
max_passes="10",
min_passes="1",
min_converged="1",
pct_refinement="30",
basis_order="1",
),
eigenmode=Dict(
name="Setup",
min_freq_ghz="1",
n_modes="1",
max_delta_f="0.5",
max_passes="10",
min_passes="1",
min_converged="1",
pct_refinement="30",
basis_order="-1",
),
),
name = 'aedt_hfss'
aedt_hfss_options = Dict(
# spacing between port and inductor if junction is drawn both ways
port_inductor_gap='10um')
"""aedt HFSS Options"""
def __init__(self,
multilayer_design: 'MultiPlanar',
renderer_type: str = 'HFSS',
project_name: Union[str, None] = None,
design_name: Union[str, None] = None,
initiate=False,
options: Dict = None):
"""Create a QRenderer for HFSS simulations using pyaedt and multiplanar design.
QHFSSPyaedt is subclassed from QPyaedt, subclassed from QRendererAnalysis and
subclassed from QRenderer. The default_setup options are expected to be defined by
child class of QHFSSPyaedt for driven-modal and eigenmode solution types.
Args:
multilayer_design (MultiPlanar): Use QGeometry within MultiPlanar to obtain elements for Ansys.
renderer_type (str): Choose string from list ['HFSS_DM', 'HFSS_EM', 'Q3D'] for the type of
design to insert into project.
project_name (Union[str, None], optional): Give a name, or one will be made based on class name of renderer.
Defaults to None.
design_name (Union[str, None], optional): Give a name, or one will be made based on class name of renderer.
Defaults to None.
initiate (bool, optional): True to initiate the renderer. Defaults to False.
options (Dict, optional): Used to override all options. Defaults to None.
"""
super().__init__(multilayer_design,
renderer_type=renderer_type,
project_name=project_name,
design_name=design_name,
initiate=initiate,
options=options)
self.current_sweep = None
# Check if user entered valid data
self.port_list_is_valid = None # bool
self.jj_to_port_is_valid = None #bool
self.ignored_jjs_is_valid = None #bool
# Reformat the port_list and enter into port_list_dict
self.port_list_dict = Dict()
# Reformat the jj_to_port and enter int jj_to_port_dict
self.jj_to_port_dict = Dict()
# Keep copy of ignored_jjs to use after rendering.
self.ignored_jjs = []
# QRenderer has a "cls" method called load()
if_registered_in_design = QHFSSPyaedt.load()
#make a class to read in pandas table.
self.tables = None
[docs]
def render_design(self,
selection: Union[list, None] = None,
open_pins: Union[list, None] = None,
port_list: Union[list, None] = None,
jj_to_port: Union[list, None] = None,
ignored_jjs: Union[list, None] = None,
box_plus_buffer: bool = True):
"""
This render_design will add additional logic for HFSS within project.
In particular, when used by BOTH solution types of eigenmode and drivenmodal.
Initiate rendering of components in design contained in selection,
assuming they're valid. Components are rendered before the chips they
reside on, and subtraction of negative shapes is performed at the very
end.
First obtain a list of IDs of components to render and a corresponding case, denoted by self.qcomp_ids
and self.case, respectively. If self.case == 1, all components in QDesign are to be rendered.
If self.case == 0, a strict subset of components in QDesign are to be rendered. Otherwise, if
self.case == 2, one or more component names in selection cannot be found in QDesign.
Chip_subtract_dict consists of component names (keys) and a set of all elements within each component that
will eventually be subtracted from the ground plane. Add objects that are perfect conductors and/or have
meshing to self.assign_perfE and self.assign_mesh, respectively; both are initialized as empty lists. Note
that these objects are "refreshed" each time render_design is called (as opposed to in the init function)
to clear QAnsysRenderer of any leftover items from the last call to render_design.
Among the components selected for export, there may or may not be unused (unconnected) pins.
The second parameter, open_pins, contains tuples of the form (component_name, pin_name) that
specify exactly which pins should be open rather than shorted during the simulation. Both the
component and pin name must be specified because the latter could be shared by multiple
components. All pins in this list are rendered with an additional endcap in the form of a
rectangular cutout, to be subtracted from its respective plane.
In driven modal solutions, the Ansys design must include one or more
ports. This is done by adding all port designations and their
respective impedances in Ohms as (qcomp, pin, impedance) to port_list.
Note that an open endcap must separate the two sides of each pin before
inserting a lumped port in between, so behind the scenes all pins in
port_list are also added to open_pins. Practically, however, port_list
and open_pins are inputted as mutually exclusive lists.
Also in driven modal solutions, one may want to render junctions as
lumped ports and/or inductors, or omit them altogether. To do so,
tuples of the form (component_name, element_name, impedance)
are added to the list jj_to_port. For example,
('Q1', 'rect_jj', 70) indicates that rect_jj of component Q1 is
to be rendered as both a lumped port with an impedance of 70 Ohms.
Alternatively for driven modal solutions, one may want to disregard
select junctions in the Metal design altogether to simulate the
capacitive effect while keeping the qubit in an "off" state. Such
junctions are specified in the form (component_name, element_name)
in the list ignored_jjs.
The final parameter, box_plus_buffer, determines how the chip is drawn. When set to True, it takes the
minimum rectangular bounding box of all rendered components and adds a buffer of x_buffer_width_mm and
y_buffer_width_mm horizontally and vertically, respectively, to the chip size. The center of the chip
lies at the midpoint x/y coordinates of the minimum rectangular bounding box and may change depending
on which components are rendered and how they're positioned. If box_plus_buffer is False, however, the
chip position and dimensions are taken from the chip info dictionary found in self.design, irrespective
of what's being rendered. While this latter option is faster because it doesn't require calculating a
bounding box, it runs the risk of rendered components being too close to the edge of the chip or even
falling outside its boundaries.
Args:
selection (Union[list, None], optional): List of components to render. Defaults to None.
open_pins (Union[list, None], optional): List of tuples of pins that are open. Defaults to None.
port_list (Union[list, None], optional): List of tuples of pins to
be rendered as ports. Defaults to None.
jj_to_port (Union[list, None], optional): List of tuples of jj's to
be rendered as ports. Defaults to None.
ignored_jjs (Union[list, None], optional): List of tuples of jj's
that shouldn't be rendered.
Defaults to None.
box_plus_buffer (bool, optional): Either calculate a bounding box based on the
location of rendered geometries or use chip size
from design class.
"""
# Draw in fill = True pieces. based on either full chip or box_plus
# They are reset for each time render_design happens.
super().render_design(selection, box_plus_buffer)
if self.case == 2:
self.logger.warning(
'Unable to proceed with rendering. Please check selection.')
return
self.reset_hfss_arguments()
self.ignored_jjs = ignored_jjs
#Every time one chooses to render, we have to get the chip names since the
#qgeometry tables can be updated between each render_design().
chip_list = self.get_chip_names()
if not self.valid_input_arguments(open_pins, port_list, jj_to_port,
ignored_jjs):
self.logger.error(
f'Check the arguments to render_design, invalid name was probably used.'
)
else:
# Ansys default units is 'mm'?????, but metal using 'meter'.
# pyaedt.generic.constants.SI_UNITS.Length is meter.
self.activate_user_project_design()
# Add something to clear design.
self.draw_sample_holder()
self.aedt_render_by_layer_then_tables(skip_junction=False,
open_pins=open_pins,
port_list=port_list,
jj_to_port=jj_to_port,
ignored_jjs=ignored_jjs)
# self.assign_thin_conductor()
# self.assign_nets()
[docs]
def aedt_render_by_layer_then_tables(self,
open_pins: Union[list, None],
port_list: Union[list, None],
jj_to_port: Union[list, None],
ignored_jjs: Union[list, None],
skip_junction: bool = False,
data_type: int = 0):
"""_summary_
Args:
skip_junction (bool, optional): Should the renderer add junctions to HFSS.
Defaults to False.
open_pins (Union[list, None], optional): Which pins will have small rectangle of substrate
subtracted to be an endcap. Use definition from render_design.
Defaults to None.
port_list (Union[list, None], optional): Use definition from render_design.
Defaults to None.
jj_to_port (Union[list, None], optional): Use definition from render_design.
Defaults to None.
ignored_jjs (Union[list, None], optional): Use definition from render_design.
Defaults to None.
datatype (int, optional): This comes for the layer_stack file; regarding
layer and datatype. Defaults to 0.
"""
super().aedt_render_by_layer_then_tables(skip_junction=skip_junction,
open_pins=open_pins,
port_list=port_list,
jj_to_port=jj_to_port,
ignored_jjs=ignored_jjs,
data_type=data_type)
for layer_num in sorted(
self.design.qgeometry.get_all_unique_layers_for_all_tables(
self.qcomp_ids)):
# Create subsets per layer for port_list, jj_to)port, and open_pins.
result_subset = self.get_subset_based_on_layer(
open_pins, port_list, jj_to_port, ignored_jjs, layer_num)
open_pins_subset, port_list_subset, jj_to_port_subset, ignored_jjs_subset = result_subset
for table_type in self.design.qgeometry.get_element_types():
if table_type != 'junction' or not skip_junction:
#At this point, we know table type,
self.render_components(table_type, layer_num,
port_list_subset, jj_to_port_subset,
ignored_jjs_subset)
#For each layer, add the endcaps and
# add polyline to subtract to self.chip_subtract_dict[layer_num].
if isinstance(open_pins_subset, list) and self.open_pins_is_valid:
# Only use this method if user defines open pins.
if open_pins_subset:
#Confirm there is something in the list.
self.add_endcaps(open_pins_subset, layer_num)
solution_type = self.current_app.solution_type
if solution_type == 'Modal' and self.port_list_is_valid and isinstance(
port_list_subset, list):
# Only use this method if user defines drivenmodal solution_type,
# and port list is provided by user.
if port_list_subset:
#Confirm there is something in the list.
self.port_list_dict_populate(port_list_subset)
self.add_ports(port_list_subset, self.port_list_dict,
layer_num)
# The layer is obtained from qgeometry tables, but layer_stack
# can have multiple data_types. For now, the subtract will
# happen from data_type==0.
self.subtract_from_ground(layer_num, data_type=data_type)
#yapf: disable
[docs]
def get_subset_based_on_layer(self,
open_pins: Union[list, None],
port_list: Union[list, None],
jj_to_port: Union[list, None],
ignored_jjs: Union[list,None],
layer_num: int) -> Tuple[
Union[list, None],
Union[list, None],
Union[list, None],
Union[list, None]]:
#yapf: enable
"""When user adds arguments through render_design, they do not identify the layer that the
component is on. So This method returns a subset of the components which are to be
rendered for the layer_num passed to this method.
Args:
open_pins (Union[list, None]): Use definition from render_design.
port_list (Union[list, None]): Use definition from render_design.
jj_to_port (Union[list, None]): Use definition from render_design.
ignored_jjs (Union[list,None]): Use definition from render_design.
layer_num (int): Using this layer number, identify the components
in the remaining arguments
Returns:
Tuple[ Union[list, None], Union[list, None], Union[list, None], Union[list, None]]: Subset
of the components which are to be rendered for the layer_num passed to this method.
"""
open_pins_subset = None
port_list_subset = None
jj_to_port_subset = None
ignored_jjs_subset = None
if open_pins:
open_pins_subset = self.get_open_pins_in_layer(open_pins, layer_num)
if port_list:
port_list_subset = self.get_port_list_in_layer(port_list, layer_num)
if jj_to_port:
jj_to_port_subset = self.get_jj_to_port_in_layer(
jj_to_port, layer_num)
if ignored_jjs:
ignored_jjs_subset = self.get_ignored_jjs_in_layer(
ignored_jjs, layer_num)
return open_pins_subset, port_list_subset, jj_to_port_subset, ignored_jjs_subset
[docs]
def get_port_list_in_layer(self, port_list: list, layer_num: int) -> list:
"""Reduce the port_list list and only return the port_list for layer_nums.
Args:
port_list (list): The list given by user for render_design.
layer_num (int): The layer number being rendered.
Returns:
list: A subset of port_list for the denoted layer_num.
"""
layer_port_list = list()
mask_comp_element = self.path_poly_and_junction_with_valid_comps[
'layer'] == layer_num
path_poly_junction_layer = self.path_poly_and_junction_with_valid_comps[
mask_comp_element]
for comp_name, element, impedance in port_list:
id = self.design.components[comp_name].id
comp_element_layer_mask = (
path_poly_junction_layer['component'] == id)
valid_df = path_poly_junction_layer[comp_element_layer_mask]
if not valid_df.empty:
layer_port_list.append((comp_name, element, impedance))
return layer_port_list
[docs]
def get_jj_to_port_in_layer(self, jj_to_port: list, layer_num: int) -> list:
"""Reduce the port_list list and only return the port_list for layer_nums.
This is very similar to get_port_list_in_layer. This format is different
than what was done with renderer with comm port. The inductance and creation
of extra sheet has been dropped.
Args:
jj_to_port (list): Use definition from render_design.
layer_num (int): The layer number to render.
Returns:
list: Use definition from render_design.
"""
layer_jj_to_port = list()
mask_comp_element = self.path_poly_and_junction_with_valid_comps[
'layer'] == layer_num
path_poly_junction_layer = self.path_poly_and_junction_with_valid_comps[
mask_comp_element]
for comp_name, element, impedance in jj_to_port:
id = self.design.components[comp_name].id
comp_element_layer_mask = (
path_poly_junction_layer['component'] == id)
valid_df = path_poly_junction_layer[comp_element_layer_mask]
if not valid_df.empty:
layer_jj_to_port.append((comp_name, element, impedance))
return layer_jj_to_port
[docs]
def get_ignored_jjs_in_layer(self, ignored_jjs, layer_num: int) -> list:
"""Reduce the ignored_jjs list and only return the ignored_jjs for layer_nums.
Args:
ignored_jjs (_type_): Use definition from render_design.
layer_num (int): The layer number to render.
Returns:
list: Use definition from render_design.
"""
layer_ignored_jjs = list()
mask_comp_element = self.path_poly_and_junction_with_valid_comps[
'layer'] == layer_num
path_poly_junction_layer = self.path_poly_and_junction_with_valid_comps[
mask_comp_element]
for comp_name, element in ignored_jjs:
id = self.design.components[comp_name].id
comp_element_layer_mask = (
path_poly_junction_layer['component'] == id)
valid_df = path_poly_junction_layer[comp_element_layer_mask]
if not valid_df.empty:
layer_ignored_jjs.append((comp_name, element))
return layer_ignored_jjs
# yapf: disable
[docs]
def render_element(self,
qgeom: pd.Series,
is_junction: bool,
port_list: Union[list, None],
jj_to_port: Union[list, None],
ignored_jjs: Union[list, None]):
# yapf: enable
"""Render an individual shape whose properties are listed in a row of
QGeometry table. Junction elements are handled separately from non-
junction elements, as the former consist of two rendered shapes, not
just one.
Args:
qgeom (pd.Series): GeoSeries of element properties.
is_junction (bool): Whether or not qgeom belongs to junction table.
port_list (Union[list, None], optional): _description_. Defaults to None.
jj_to_port (Union[list, None], optional): _description_. Defaults to None.
ignored_jjs (Union[list, None], optional): _description_. Defaults to None.
"""
qc_shapely = qgeom.geometry
if is_junction:
if self.should_render_junction(qgeom, port_list, jj_to_port,
ignored_jjs):
self.render_element_junction(qgeom, port_list, jj_to_port)
else:
if isinstance(qc_shapely, shapely.geometry.Polygon):
self.render_element_poly(qgeom)
elif isinstance(qc_shapely, shapely.geometry.LineString):
self.render_element_path(qgeom)
[docs]
def should_render_junction(self, qgeom: pd.Series, port_list: Union[list,
None],
jj_to_port: Union[list, None],
ignored_jjs: Union[list, None]) -> bool:
"""Expected to be replaced by grandchild of this class.
Args:
qgeom (pd.Series): _description_
port_list (Union[list, None]): _description_
jj_to_port (Union[list, None]): _description_
ignored_jjs (Union[list, None]): _description_
Returns:
bool: _description_
"""
pass
[docs]
def render_element_junction(self,
qgeom: pd.Series,
port_list: Union[list, None] = None,
jj_to_port: Union[list, None] = None):
"""
Render a Josephson junction consisting of
1. A rectangle of length pad_gap and width inductor_width. Defines lumped element
RLC boundary condition.
2. A line that is later used to calculate the voltage in post-processing analysis.
At this method, ASSUME that junction is not in the ignored_jjs
Args:
qgeom (pd.Series): GeoSeries of element properties.
"""
#ansys_options = dict(transparency=0.0)
qc_name = "Lj_" + str(qgeom["component"])
qc_elt = get_clean_name(qgeom["name"])
qc_shapely = qgeom.geometry
# Hardcode in datatype since it is not passed in qgeometry tables.
result = self.design.ls.get_properties_for_layer_datatype(
['thickness', 'z_coord', 'material', 'fill'],
qgeom['layer'],
datatype=0)
if result:
thickness, z_coord, material, fill_value = result
else:
self.design.ls.layer_stack_handler_pilot_error()
qc_width = parse_entry(qgeom.width)
name = f"{qc_name}{QAnsysRenderer.NAME_DELIM}{qc_elt}"
endpoints = parse_entry(list(qc_shapely.coords))
endpoints_3d = to_vec3D(endpoints, (z_coord + (thickness / 2))).tolist()
x0, y0, z0 = endpoints_3d[0]
x1, y1, z0 = endpoints_3d[1]
self.assign_mesh.append("JJ_rect_" + name)
# Draw line
poly_jj_name = "JJ_" + name + "_"
poly_jj = self.current_app.modeler.create_polyline(
endpoints_3d,
#[endpoints_3d[0], endpoints_3d[1]],
name=poly_jj_name,
close_surface=False)
poly_jj.show_arrow = True
poly_jj.color = (128, 0, 128)
jj_name = "JJ_rect_" + name
poly_sheet = self.current_app.modeler.create_polyline(
endpoints_3d,
#[endpoints_3d[0], endpoints_3d[1]],
name=jj_name,
close_surface=False,
xsection_type='Line',
xsection_width=qc_width)
solution_type = self.current_app.solution_type
# If junction not in the port list or ignore list, THEN sheet inductance, if not in jj_to_port.
# At this method, ASSUME that junction is not in the ignored_jjs
if solution_type == 'Modal' and self.jj_to_port_is_valid and isinstance(
jj_to_port, list) and jj_to_port:
# Only use this logic if user defines drivenmodal solution_type,
# and jj_to_port list is provided by user.
self.jj_to_port_list_dict_populate(jj_to_port)
search_junction = (self.design._components[qgeom["component"]].name,
qgeom['name'])
if search_junction in self.jj_to_port_dict.keys():
impedance = self.jj_to_port_dict[search_junction]
jj_name_port = "JJ_rect_" + "R_" + str(
qgeom["component"]) + "_" + str(qgeom['name'])
jj_to_port_boundary = self.current_app.create_lumped_port_to_sheet(
sheet_name=poly_sheet.name,
portname=jj_name_port,
axisdir=endpoints_3d,
impedance=impedance)
else:
# Looks QGeometry table to pull Lj and Cj
jj_geom_table = self.design.qgeometry.tables['junction']
jj_of_interest = jj_geom_table[jj_geom_table['name'] == qc_elt]
Lvalue = parse_entry(jj_of_interest['aedt_hfss_inductance'][0])
Cvalue = parse_entry(jj_of_interest['aedt_hfss_capacitance'][0])
lumped_rlc_boundary = self.current_app.assign_lumped_rlc_to_sheet(
sheet_name=poly_sheet.name,
sourcename=f'rlc_{poly_sheet.name}',
axisdir=endpoints_3d,
Lvalue=Lvalue,
Cvalue=Cvalue)
[docs]
def draw_sample_holder(self):
"""Adds a vacuum box to HFSS design. The xy coordinates are determined by
results of using box_plus_buffer. The z coordinate of the box is determined
by default_options of this class. This the user can update the options prior
to render_design.
Note: the height of the box is NOT found by going to Multiplanar Design.
"""
if "sample_holder_top" in self.design.variables.keys():
p = self.design.variables
else:
p = self.options
top = parse_entry(p["sample_holder_top"],
convert_to_unit=self.current_app.modeler.model_units)
bottom = parse_entry(
p["sample_holder_bottom"],
convert_to_unit=self.current_app.modeler.model_units)
vac_height = top - bottom
half_vac_height = (vac_height / 2)
half_z_coord = top - half_vac_height
(minx, miny, maxx, maxy) = self.box_for_ansys
# yapf: disable
vacuum_rect = [[minx, miny, half_z_coord],
[maxx, miny, half_z_coord],
[maxx, maxy, half_z_coord],
[minx, maxy, half_z_coord],
[minx, miny, half_z_coord]]
# yapf: enable
box_name = "vacuum_box"
sample_holder_rectangle = self.current_app.modeler.create_polyline(
vacuum_rect,
name=box_name,
cover_surface=True,
close_surface=True,
matname="vacuum")
self.current_app.modeler.thicken_sheet(sample_holder_rectangle.id,
thickness=vac_height,
bBothSides=True)
[docs]
def add_ports(self, port_list: list, port_list_dict: dict, layer_num: int):
# Should be overwritten by drivenmodal, since eigenmode does not have ports.
pass
[docs]
def add_jj_to_ports(self, jj_to_port: list, jj_to_port_dict: dict,
layer_num: int):
# Should be overwritten by drivenmodal, since eigenmode does not have ports.
pass
# yapf: disable
[docs]
def reset_hfss_arguments(self):
"""Reset the value to None each time render_design is started.
"""
self.open_pins_is_valid = None
self.port_list_is_valid = None
self.jj_to_port_is_valid = None
self.ignored_jjs_is_valid = None
self.ignored_jjs = []
[docs]
def confirm_jj_to_port_has_valid_request(self, jj_to_port: list,
pair_used: list) -> bool:
""" The jj_to_port, make sure the component_nam and element type are in design.
Args:
jj_to_port (list): List of tuples denoted (component_name, element_name, impedance).
pair_used (list): Passed to multiple checks to ensure (component_name, pin)
are not accidentally reused.
Returns:
bool: True if pair in junction table.
False if any entry in list not found in design.
"""
# (component_name, element_name, impedance)
# are added to the list jj_to_port. For example,
# ('Q1', 'rect_jj', 70)
for component_name, element_name, impedance in jj_to_port:
if self.qcomp_ids:
# User wants to render a subset, ensure part of selection.
if not self.design.components[
component_name].id in self.qcomp_ids:
self.design.logger.error(
f'You entered the component_name='
f'{self.design.components[component_name].name} and the corresponding'
f' name is not selection.'
f'The geometries will not be rendered to Ansys.')
return False
junction_table = self.design.qgeometry.tables['junction']
if self.design.components[component_name]:
comp_id = self.design.components[component_name].id
else:
return False
mask_comp_element = (junction_table['component'] == comp_id) & (
junction_table['name'] == element_name)
result_found_df = junction_table[mask_comp_element]
if len(result_found_df) == 0:
return False
if not (isinstance(impedance, int) or isinstance(impedance, float)):
self.logger.error(f'Impedance should be either float or int. '
f'You have impedance={impedance}')
return False
item = (component_name, element_name)
if item in pair_used:
self.logger.error(
f'You entered the component_name={component_name} and element_name={element_name} pair twice. '
f'The geometries will not be rendered to Ansys.')
return False
pair_used.append(item)
return True
[docs]
def confirm_ignored_jjs_has_valid_request(self, ignored_jjs: list,
pair_used: list) -> bool:
"""Check within the qgeometry junction table to confirm the component_name
and element_name are paired on a row.
Args:
ignored_jjs (list): List of tuples of (component name, element_name)
pair_used (list): Passed to multiple checks to ensure (component_name, pin)
are not accidentally reused.
Returns:
bool: True if pair in junction table.
False if any entry in list not found in design.
"""
for item in ignored_jjs:
component_name, element_name = item
if self.qcomp_ids:
# User wants to render a subset, ensure part of selection.
if not self.design.components[
component_name].id in self.qcomp_ids:
self.design.logger.error(
f'You entered the component_name='
f'{self.design.components[component_name].name} and the corresponding'
f' name is not selection.'
f'The geometries will not be rendered to Ansys.')
return False
junction_table = self.design.qgeometry.tables['junction']
if self.design.components[component_name]:
comp_id = self.design.components[component_name].id
else:
return False
mask_comp_element = (junction_table['component'] == comp_id) & (
junction_table['name'] == element_name)
result_found_df = junction_table[mask_comp_element]
if len(result_found_df) == 0:
return False
if item in pair_used:
self.logger.error(
f'You entered the component_name={component_name} and element_name={element_name} pair twice. '
f'The geometries will not be rendered to Ansys.')
return False
pair_used.append(item)
return True
[docs]
def confirm_port_list_have_valid_request(self, port_list: list,
pair_used: list) -> bool:
"""Check if all names of ports are within design. Otherwise log an error.
Args:
port_list (list): The user input for ports. List of tuples of pins to
be rendered as ports.
pair_used (list): Passed to multiple checks to ensure (component_name, pin)
are not accidentally reused.
Returns:
bool: True if all ports found in design.
False if any one component or pin is not found in design.
"""
for comp_name, pin, impedance in port_list:
if self.qcomp_ids:
# User wants to render a subset, ensure part of selection.
if not self.design.components[comp_name].id in self.qcomp_ids:
self.design.logger.error(
f'You entered the component_name='
f'{self.design.components[comp_name].name} and the corresponding'
f' name is not selection.'
f'The geometries will not be rendered to Ansys.')
return False
if comp_name not in self.design.components.keys():
self.port_list_names_not_valid(comp_name, pin)
return False
if pin not in self.design.components[comp_name].pins.keys():
self.port_list_names_not_valid(comp_name, pin)
return False
if not (isinstance(impedance, int) or isinstance(impedance, float)):
self.logger.error(f'Impedance should be either float or int. '
f'You have impedance={impedance}')
return False
search_item = (comp_name, pin)
if search_item in pair_used:
self.logger.error(
f'You entered the component_name={comp_name} and pin={pin} pair twice. '
f'The geometries will not be rendered to Ansys.')
return False
pair_used.append(search_item)
return True
[docs]
def port_list_dict_populate(self, port_list: list):
"""Convert the port_list to a searchable dict.
Args:
port_list (list): _description_
"""
# This should be overwritten by drivenmodal class.
pass
[docs]
def jj_to_port_list_dict_populate(self, jj_to_port: list):
"""Convert jj_to_port to a searchable dict.
Args:
jj_to_port (list): _description_
"""
# This should be overwritten by drivenmodal class.
pass
[docs]
def set_variable(self, name: str, value: str):
"""
Sets project-level variable in ANSYS.
Example usage:
self.set_variable('Lj_1', '10nH')
self.set_variable('Cj', '0fF')
self.set_variable('dimensionless', 1/137)
Args:
name (str): Name of variable.
value (str): Amount and unit associated w/ variable.
"""
variable_manager = self.current_app._variable_manager
variable_manager[name] = value
######### Warnings and Errors##################################################
[docs]
def port_list_names_not_valid(self, comp_name: str, pin_name: str):
"""Stop and give error if the component name or pin name from port_list
are not valid within design.
Args:
comp_name (str): Name of component which should have an open pin.
pin_name (str): Name of pin which should be open.
"""
self.logger.error(
f'\n The component name or pin name within list'
f'\nprovided to render_design are not valid.'
f'\nYou provided: {comp_name} and {pin_name}'
f'\n Valid components: {self.design.components.keys()}'
f'\n Valid pins:{self.design.components[comp_name].pins.keys()}'
f'\n ALL tuples within port_list will not be addressed.')