Ejemplo n.º 1
0
    def test_qgeometry_q_element_delete_component_id(self):
        """Test delete_component_id in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.clear_all_tables()

        transmon_pocket = TransmonPocket(design, 'my_id')
        transmon_pocket.make()
        transmon_pocket.get_template_options(design)
        a_linestring = draw.LineString([[0, 0], [0, 1]])
        a_poly = draw.rectangle(2, 2, 0, 0)
        qgt.add_qgeometry('path',
                          'my_id', {'n_sprial': a_linestring},
                          width=4000)
        qgt.add_qgeometry('poly',
                          'my_id', {'n_spira_etch': a_poly},
                          subtract=True)

        self.assertEqual(len(qgt.tables['path']), 1)
        self.assertEqual(len(qgt.tables['poly']), 1)

        qgt.delete_component_id('my_id')

        self.assertEqual(len(qgt.tables['path']), 0)
        self.assertEqual(len(qgt.tables['poly']), 0)
Ejemplo n.º 2
0
    def test_qgeometry_q_element_create_tables(self):
        """Test create_tables in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.create_tables()

        actual = qgt.tables

        self.assertEqual(len(actual), 3)
        self.assertTrue('path' in actual)
        self.assertTrue('poly' in actual)
        self.assertTrue('junction' in actual)

        self.assertEqual(actual['path'].dtypes['component'], object)
        self.assertEqual(actual['path'].dtypes['name'], object)
        self.assertEqual(actual['path'].dtypes['subtract'], bool)
        self.assertEqual(actual['path'].dtypes['helper'], bool)
        self.assertEqual(actual['path'].dtypes['chip'], object)
        self.assertEqual(actual['path'].dtypes['fillet'], object)

        self.assertEqual(actual['poly'].dtypes['component'], object)
        self.assertEqual(actual['poly'].dtypes['name'], object)
        self.assertEqual(actual['poly'].dtypes['subtract'], bool)
        self.assertEqual(actual['poly'].dtypes['helper'], bool)
        self.assertEqual(actual['poly'].dtypes['chip'], object)
        self.assertEqual(actual['poly'].dtypes['fillet'], object)

        self.assertEqual(actual['junction'].dtypes['component'], object)
        self.assertEqual(actual['junction'].dtypes['name'], object)
        self.assertEqual(actual['junction'].dtypes['subtract'], bool)
        self.assertEqual(actual['junction'].dtypes['helper'], bool)
        self.assertEqual(actual['junction'].dtypes['chip'], object)
        self.assertEqual(actual['junction'].dtypes['width'], float)
Ejemplo n.º 3
0
    def test_qgeometry_q_element_add_renderer_extension(self):
        """Test add_renderer_extension in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.add_renderer_extension(
            'new_name',
            dict(base=dict(color=str, klayer=int),
                 path=dict(thickness=float),
                 poly=dict(material=str)))

        e_c = qgt.ELEMENT_COLUMNS

        self.assertTrue('color' in e_c['base']['__renderers__']['new_name'])
        self.assertTrue('klayer' in e_c['base']['__renderers__']['new_name'])
        self.assertTrue('thickness' in e_c['path']['__renderers__']['new_name'])
        self.assertTrue('material' in e_c['poly']['__renderers__']['new_name'])

        self.assertEqual(e_c['base']['__renderers__']['new_name']['color'], str)
        self.assertEqual(e_c['base']['__renderers__']['new_name']['klayer'],
                         int)
        self.assertEqual(e_c['path']['__renderers__']['new_name']['thickness'],
                         float)
        self.assertEqual(e_c['poly']['__renderers__']['new_name']['material'],
                         str)
Ejemplo n.º 4
0
    def test_qgeometry_q_element_get_rname(self):
        """Test get_rname in QGeometryTables class in element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)

        actual = qgt.get_rname('some_name', 'aloha')

        self.assertEqual(actual, 'some_name_aloha')
Ejemplo n.º 5
0
    def test_qgeometry_q_element_check_element_type(self):
        """Test check_element_type in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        TransmonPocket(design, 'my_id')

        self.assertTrue(qgt.check_element_type('path', log_issue=False))
        self.assertFalse(qgt.check_element_type('not-there', log_issue=False))
Ejemplo n.º 6
0
    def test_qgeometry_q_element_get_component_bounds(self):
        """Test get_component_bounds in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.clear_all_tables()
        TransmonPocket(design, 'my_id')

        four_zeros = qgt.get_component_bounds('my_id')
        self.assertEqual(len(four_zeros), 4)
        for i in range(4):
            self.assertEqual(four_zeros[i], 0)
Ejemplo n.º 7
0
    def test_qgeometry_q_element_get_element_types(self):
        """Test get_element_types in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)

        expected = ['path', 'poly', 'junction']
        actual = qgt.get_element_types()

        self.assertEqual(len(expected), len(actual))
        for i in range(3):
            self.assertEqual(expected[i], actual[i])
Ejemplo n.º 8
0
    def test_qgeometry_q_element_get_component(self):
        """Test get_component in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.clear_all_tables()
        TransmonPocket(design, 'Q1')

        rect = draw.rectangle(500, 300, 0, 0)
        geom = {'my_polygon': rect}
        qgt.add_qgeometry('poly', 'Q1', geom)

        # success results
        actual = qgt.get_component('Q1')
        self.assertEqual(len(actual), 3)
        self.assertTrue(isinstance(actual['path'], GeoDataFrame))
        self.assertTrue(isinstance(actual['poly'], GeoDataFrame))
        self.assertTrue(isinstance(actual['junction'], GeoDataFrame))

        # failure results
        actual = qgt.get_component('not-real')
        self.assertEqual(len(actual), 3)
        self.assertEqual(actual['path'], None)
        self.assertEqual(actual['poly'], None)
        self.assertEqual(actual['junction'], None)
Ejemplo n.º 9
0
 def test_qgeometry_instantiate_q_geometry_tables(self):
     """Test instantiation of QGeometryTables."""
     try:
         design = designs.DesignPlanar()
         QGeometryTables(design)
     except Exception:
         self.fail("QGeometryTables failed")
Ejemplo n.º 10
0
    def test_qgeometry_q_element_constants(self):
        """Test that constants in QGeometryTables class in element_handler.py
        were not accidentally changed."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)

        self.assertEqual(qgt.__i_am_qgeometry_table__, True)
        self.assertEqual(qgt.name_delimiter, '_')
Ejemplo n.º 11
0
    def test_qgeometry_q_element_clear_all_tables(self):
        """Test clear_all_tables in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.clear_all_tables()

        # add something to the tables to check for after clear
        a_poly = draw.rectangle(2, 2, 0, 0)
        qgt.add_qgeometry('poly', 'my_id', dict(cl_metal=a_poly))

        qgt.clear_all_tables()
        self.assertEqual(len(qgt.tables['poly'].geometry), 0)
Ejemplo n.º 12
0
    def test_qgeometry_get_all_unique_layers(self):
        """Test get_all_unique_layers functionality in elment_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.clear_all_tables()

        transmon_pocket = TransmonPocket(design, 'my_id')
        transmon_pocket.make()
        transmon_pocket.get_template_options(design)
        a_linestring = draw.LineString([[0, 0], [0, 1]])
        a_poly = draw.rectangle(2, 2, 0, 0)
        qgt.add_qgeometry('path',
                          'my_id', {'n_sprial': a_linestring},
                          width=4000)
        qgt.add_qgeometry('poly',
                          'my_id', {'n_spira_etch': a_poly},
                          subtract=True)

        self.assertEqual(qgt.get_all_unique_layers('main'), [1])
        self.assertEqual(qgt.get_all_unique_layers('fake'), [])
Ejemplo n.º 13
0
    def test_renderer_get_chip_names(self):
        """Test functionality of get_chip_names in gds_renderer.py."""
        design = designs.DesignPlanar()
        renderer = QGDSRenderer(design)

        qgt = QGeometryTables(design)
        qgt.clear_all_tables()

        transmon_pocket_1 = TransmonPocket(design, 'my_id')
        transmon_pocket_1.make()
        transmon_pocket_1.get_template_options(design)

        a_linestring = draw.LineString([[0, 0], [0, 1]])
        a_poly = draw.rectangle(2, 2, 0, 0)
        qgt.add_qgeometry('path',
                          'my_id', {'n_sprial': a_linestring},
                          width=4000)
        qgt.add_qgeometry('poly',
                          'my_id', {'n_spira_etch': a_poly},
                          subtract=True)

        result = renderer._get_chip_names()
        self.assertEqual(result, {'main': {}})
Ejemplo n.º 14
0
    def test_qgeometry_q_element_add_qgeometry(self):
        """Test add_qgeometry in QGeometryTables class in
        element_handler.py."""
        design = designs.DesignPlanar()
        qgt = QGeometryTables(design)
        qgt.clear_all_tables()

        a_poly = draw.rectangle(2, 2, 0, 0)
        qgt.add_qgeometry('poly', 'my_id', dict(cl_metal=a_poly))
        table = qgt.tables

        self.assertTrue('poly' in table)
        self.assertEqual(table['poly']['component'][0], 'my_id')
        self.assertEqual(table['poly']['name'][0], 'cl_metal')
        self.assertEqual(table['poly']['geometry'][0], a_poly)
        self.assertEqual(table['poly']['layer'][0], 1)
        self.assertEqual(table['poly']['subtract'][0], False)
        self.assertEqual(table['poly']['helper'][0], False)
        self.assertEqual(table['poly']['chip'][0], 'main')
        self.assertEqual(str(table['poly']['fillet'][0]), str(np.nan))
Ejemplo n.º 15
0
    def __init__(self,
                 metadata: dict = None,
                 overwrite_enabled: bool = False,
                 enable_renderers: bool = True):
        """Create a new Metal QDesign.

        Arguments:
            metadata (Dict): Dictionary of metadata.  Defaults to None.

            overwrite_enabled (bool): When True - If the string name, used for component, already
                            exists in the design, the existing component will be
                            deleted from design, and new component will be generated
                            with the same name and newly generated component_id,
                            and then added to design.
                        When False - If the string name, used for component, already
                            exists in the design, the existing component will be
                            kept in the design, and current component will not be generated,
                            nor will be added to the design. The 'NameInUse' will be returned
                            during component generation.
                        Either True or False - If string name, used for component, is NOT
                            being used in the design, a component will be generated and
                            added to design using the name.

            enable_renderers: Enable the renderers during the init() of design.
                        The list in config.renderers_to_load() to determine
                        which renderers to enable.
        """

        # _qcomponent_latest_assigned_id -- Used to keep a tally and ID of all components within an
        # instantiation of a design.
        # A qcomponent is added to a design by base._add_to_design with init of a qcomponent.
        # During init of component, design class provides an unique id for each instance of
        # component being added to design.  Note, if a component is removed from the design,
        # the ID of removed component should not be used again.  However, if a component is
        # renamed with an unique name, then the ID should continute to be used.
        self._qcomponent_latest_assigned_id = 0

        # Dictionary that keeps the latest ID for each unique type of component
        self._qcomponent_latest_name_id = Dict()

        # Key attributes related to physical content of the design. These will be saved

        # Where components are actually stored.
        # i.e.  key=id and part of value (_components[id].name)
        self._components = Dict()

        # User-facing interface for user to view components by using name (str) for key access to
        # QComponents, instead of id (int).
        self.components = Components(self)

        self.overwrite_enabled = overwrite_enabled

        # Cache for component ids.  Hold the reverse of _components dict,
        self.name_to_id = Dict()

        self._variables = Dict()
        self._chips = Dict()

        self._metadata = self._init_metadata()
        if metadata:
            self.update_metadata(metadata)

        self.save_path = None  # type: str

        self.logger = logger  # type: logging.Logger
        self.build_logs = LogStore("Build Logs", 30)

        self._qgeometry = QGeometryTables(self)

        # Used for QComponents, and QRenderers
        self._template_options = DefaultMetalOptions()

        self.variables.update(self.template_options.qdesign.variables)

        # Can't really use this until DefaultOptionsRenderer.default_draw_substrate.color_plane
        # is resolved.
        # Presently, self._template_options holds the templates_options for each renderers.
        # key is the unique name of renderer.
        # Also, renderer_base.options holds the latest options for each instance
        # of renderer.
        self._template_renderer_options = DefaultOptionsRenderer(
        )  # use for renderer

        self._qnet = QNet()

        # Dict used to populate the columns of QGeometry table i.e. path,
        # junction, poly etc.
        self.renderer_defaults_by_table = Dict()

        # Instantiate and register renderers to Qdesign.renderers
        self._renderers = Dict()
        if enable_renderers:
            self._start_renderers()

        # Take out of the QGeometryTables init().
        # Add add_renderer_extension() during renderer's init().
        # Need to add columns to Junction tables before create_tables().
        self._qgeometry.create_tables()
Ejemplo n.º 16
0
class QDesign(
):  # pylint disable=too-many-public-methods, too-many-instance-attributes
    """QDesign is the base class for Qiskit Metal Designs.

    A design is the most top-level object in all of Qiskit Metal.
    """

    # Dummy private attribute used to check if an instanciated object is
    # indeed a QDesign class. The problem is that the `isinstance`
    # built-in method fails when this module is reloaded.
    # Used by `is_design` to check.
    __i_am_design__ = True

    def __init__(self,
                 metadata: dict = None,
                 overwrite_enabled: bool = False,
                 enable_renderers: bool = True):
        """Create a new Metal QDesign.

        Arguments:
            metadata (Dict): Dictionary of metadata.  Defaults to None.

            overwrite_enabled (bool): When True - If the string name, used for component, already
                            exists in the design, the existing component will be
                            deleted from design, and new component will be generated
                            with the same name and newly generated component_id,
                            and then added to design.
                        When False - If the string name, used for component, already
                            exists in the design, the existing component will be
                            kept in the design, and current component will not be generated,
                            nor will be added to the design. The 'NameInUse' will be returned
                            during component generation.
                        Either True or False - If string name, used for component, is NOT
                            being used in the design, a component will be generated and
                            added to design using the name.

            enable_renderers: Enable the renderers during the init() of design.
                        The list in config.renderers_to_load() to determine
                        which renderers to enable.
        """

        # _qcomponent_latest_assigned_id -- Used to keep a tally and ID of all components within an
        # instantiation of a design.
        # A qcomponent is added to a design by base._add_to_design with init of a qcomponent.
        # During init of component, design class provides an unique id for each instance of
        # component being added to design.  Note, if a component is removed from the design,
        # the ID of removed component should not be used again.  However, if a component is
        # renamed with an unique name, then the ID should continute to be used.
        self._qcomponent_latest_assigned_id = 0

        # Dictionary that keeps the latest ID for each unique type of component
        self._qcomponent_latest_name_id = Dict()

        # Key attributes related to physical content of the design. These will be saved

        # Where components are actually stored.
        # i.e.  key=id and part of value (_components[id].name)
        self._components = Dict()

        # User-facing interface for user to view components by using name (str) for key access to
        # QComponents, instead of id (int).
        self.components = Components(self)

        self.overwrite_enabled = overwrite_enabled

        # Cache for component ids.  Hold the reverse of _components dict,
        self.name_to_id = Dict()

        self._variables = Dict()
        self._chips = Dict()

        self._metadata = self._init_metadata()
        if metadata:
            self.update_metadata(metadata)

        self.save_path = None  # type: str

        self.logger = logger  # type: logging.Logger
        self.build_logs = LogStore("Build Logs", 30)

        self._qgeometry = QGeometryTables(self)

        # Used for QComponents, and QRenderers
        self._template_options = DefaultMetalOptions()

        self.variables.update(self.template_options.qdesign.variables)

        # Can't really use this until DefaultOptionsRenderer.default_draw_substrate.color_plane
        # is resolved.
        # Presently, self._template_options holds the templates_options for each renderers.
        # key is the unique name of renderer.
        # Also, renderer_base.options holds the latest options for each instance
        # of renderer.
        self._template_renderer_options = DefaultOptionsRenderer(
        )  # use for renderer

        self._qnet = QNet()

        # Dict used to populate the columns of QGeometry table i.e. path,
        # junction, poly etc.
        self.renderer_defaults_by_table = Dict()

        # Instantiate and register renderers to Qdesign.renderers
        self._renderers = Dict()
        if enable_renderers:
            self._start_renderers()

        # Take out of the QGeometryTables init().
        # Add add_renderer_extension() during renderer's init().
        # Need to add columns to Junction tables before create_tables().
        self._qgeometry.create_tables()

    def _init_metadata(self) -> Dict:  # pylint disable=no-self-use
        """Initialize default metadata dictionary.

        Returns:
            Dict: default metadata dictioanry
        """
        now = datetime.now()  # current date and time
        return Dict(design_name='my_design',
                    notes='',
                    time_created=now.strftime("%m/%d/%Y, %H:%M:%S"))

    def update_metadata(self, new_metadata: dict):
        """Update the metadata dictionary of the design with a new metadata
        dictionary. This will overwrite only the new keys that you pass in. All
        other keys will be unaffected.

        Args:
            new_metadata (dict): New metadatadata dict to update
        """
        self._metadata.update(new_metadata)

#########PROPERTIES##################################################

    @property
    def variables(self) -> Dict_[str, str]:
        """Return the Dict object that keeps track of all variables in the
        design."""
        return self._variables

    @property
    def template_options(self) -> Dict:
        """Return default_options dictionary, which contain default options
        used in creating Metal component, and in calling other drawing and key
        functions."""
        return self._template_options

    @property
    def renderers(self) -> Dict:
        """Return a Dict of all the renderers registered within QDesign."""

        return self._renderers

    @property
    def chips(self) -> Dict:
        """Return a Dict of information regarding chip."""
        return self._chips

    @property
    def template_renderer_options(self) -> Dict:
        """Return default_renderer_options dictionary, which contain default
        options used in creating Metal renderer."""
        return self._template_renderer_options.default_options

    @property
    def metadata(self) -> Dict:
        """Return the metadata Dict object that keeps track of all metadata in
        the design."""
        return self._metadata

    @property
    def qgeometry(self) -> 'QGeometryTables':
        """Returns the QGeometryTables (Use for advanced users only)"""
        return self._qgeometry

    @property
    def qcomponent_latest_assigned_id(self) -> int:
        """Return unique number for each instance.

        For user of the design class to know the lastest id assigned to
        QComponent.
        """
        return self._qcomponent_latest_assigned_id

    @property
    def net_info(self) -> pd.core.frame.DataFrame:
        """Provides a copy of net_info table which holds all the connections,
        of pins, within a design. An advanced user can use methods within the
        class of design._qnet. Also, an advanced user can also directly edit
        the table at design._qnet._net_info.

        Returns:
            pd.core.frame.DataFrame: copy of net_info table.
        """
        return self._qnet._net_info.copy(
            deep=True)  # pylint disable=protected_access

#########Proxy properties##################################################

    def get_chip_size(self, chip_name: str = 'main') -> dict:
        """Utility function to get a dictionary containing chip dimensions
        (size and center).

        Args:
            chip_name (str): Name of the chip.

        Returns:
            dict: Dictionary of chip dimensions,
            including central coordinates and widths along x, y, and z axes.
        """
        return self._chips[chip_name]['size']

    def get_chip_z(self, chip_name: str = 'main') -> str:
        """Utility function to return the z value of a chip.

        Args:
            chip_name (str): Returns the size of the given chip.  Defaults to 'main'.

        Returns:
            str: String representation of the chip height.
        """
        chip_info = self.get_chip_size(chip_name)
        return chip_info['center_z']

    def get_chip_layer(self, chip_name: str = 'main') -> int:
        """Return the chip layer number for the ground plane.

        Args:
            chip_name (str, optional): User can overwrite name of chip.  Defaults to 'main'.

        Returns:
            int: Layer of ground plane
        """
        if chip_name in self.chips:
            if 'layer_ground_plane' in self.chips:
                return int(self.chips['layer_ground_plane'])
        return 0

#########General methods###################################################

    def rename_variable(self, old_key: str, new_key: str):
        """Renames a variable in the variables dictionary. Preserves order.

        Args:
            old_key (str): Previous variable name
            new_key (str): New variable name
        """

        keys = list(self._variables.keys())
        values = list(self._variables.values())

        keys[keys.index(old_key)] = new_key
        self._variables = Dict(zip(keys, values))

    def delete_all_pins(self) -> 'QNet':
        """Clear all pins in the net_Info and update the pins in components.

        Returns:
            QNet: QNet with all pins removed
        """
        df_net_info = self._qnet._net_info  # pylint disable=protected_access
        for (index, netID, comp_id, pin_name) in df_net_info.itertuples(
        ):  # pylint disable=unused_variable, invalid_name, line-too-long
            self._components[comp_id].pins[pin_name].net_id = 0

        # remove rows, but save column names
        self._qnet._net_info = self._qnet._net_info.iloc[
            0:0]  # pylint disable=protected_access
        return self._qnet

    def connect_pins(self, comp1_id: int, pin1_name: str, comp2_id: int,
                     pin2_name: str) -> int:
        """Will generate an unique net_id and placed in a net_info table.
        Update the components.pin_name with the net_id.

        Component's pin will know if pin is connected to another component,
        if there is a non-zero net_id.

        Args:
            comp1_id (int):  Unique id of component used for pin1_name.
            pin1_name (str): Name of pin in comp1_id.
            comp2_id (int): Unique id of component used for pin2_name.
            pin2_name (str): Name of pin in comp2_id.

        Returns:
            int: Unique net_id of connection used in the netlist.

        Note: If not added to netlist, the net_id will be 0 (zero).
        """
        net_id = 0
        net_id = self._qnet.add_pins_to_table(comp1_id, pin1_name, comp2_id,
                                              pin2_name)
        if net_id:
            # update the components to hold net_id
            self._components[comp1_id].pins[pin1_name].net_id = net_id
            self._components[comp2_id].pins[pin2_name].net_id = net_id
        else:
            logger.warning(
                f'NetId was not added for {comp1_id}, {pin1_name},'
                f' {comp2_id}, {pin2_name} and will not be added to components.'
            )
        return net_id

    #  This is replaced by design.components.find_id()
    # def get_component(self, search_name: str) -> 'QComponent':
    #     """The design contains a dict of all the components, which is correlated to
    #     a net_list connections, and qgeometry table. The key of the components dict are
    #     unique integers.  This method will search through the dict to find the component with search_name.

    #     Args:
    #         search_name (str): Name of the component

    #     Returns:
    #         QComponent: A component within design with the name search_name.

    #     *Note:* If None is returned the component wass not found. A warning through logger.warning().

    #     *Note:* If multiple components have the same name, only the first component found in the search
    #     will be returned, ALONG with logger.warning().
    #     """
    #     alist = [(value.name, key)
    #              for key, value in self._components.items() if value.name == search_name]

    #     length = len(alist)
    #     if length == 1:
    #         return_component = self._components[alist[0][1]]
    #     elif length == 0:
    #         self.logger.warning(
    #             f'Name of component:{search_name} not found. Returned None')
    #         return_component = None
    #     else:
    #         self.logger.warning(
    #             f'Component:{search_name} is used multiple times, return the first component in list: (name, component_id) {str(alist)}')
    #         return_component = self._components[alist[0][1]]

    #     return return_component

    def all_component_names_id(self) -> list:
        """Get the text names and corresponding unique ID  of each component
        within this design.

        Returns:
            list[tuples]: Each tuple has the text name of component and UNIQUE integer ID for component.  # pylint disable=line-too-long
        """
        alist = [(value.name, key) for key, value in self._components.items()]
        return alist

    def _delete_all_pins_for_component(self, comp_id: int) -> set:
        """Remove component from self._qnet._net_info.

        Args:
            comp_id (int): Component ID for which pins are to be removed

        Returns:
            Set: Set of net IDs removed
        """
        all_net_id_removed = self._qnet.delete_all_pins_for_component(comp_id)

        # reset all pins to be 0 (zero),
        pins_dict = self._components[comp_id].pins
        for key, _ in pins_dict.items():
            self._components[comp_id].pins[key].net_id = 0

        return all_net_id_removed

    def delete_all_components(self):
        """Clear all components in the design dictionary.

        Also clears all pins and netlist.
        """
        # clear all the dictionaries and element tables.

        # Need to remove pin connections before clearing the components.
        self.delete_all_pins()
        self.name_to_id.clear()
        self._components.clear()

        self._qgeometry.clear_all_tables()

    def _get_new_qcomponent_id(self):
        """Give new id that QComponent can use.

        Returns:
            int: ID of the qcomponent
        """
        self._qcomponent_latest_assigned_id += 1
        return self._qcomponent_latest_assigned_id

    def _get_new_qcomponent_name_id(self, prefix):
        """Give new id that an auto-named QComponent can use based on the type
        of the component.

        Returns:
            int: ID of the qcomponent
        """
        if prefix in self._qcomponent_latest_name_id:
            self._qcomponent_latest_name_id[prefix] += 1
        else:
            self._qcomponent_latest_name_id[prefix] = 1

        return self._qcomponent_latest_name_id[prefix]

    def rebuild(self):  # remake_all_components
        """Remakes all components with their current parameters."""
        for _, obj in self._components.items():  # pylint: disable=unused-variable
            obj.rebuild()

    def reload_and_rebuild_components(self, qis_abs_path: str):
        """
        Reload the module and class of a given component and updates
        all class instances. Then rebuilds all QComponents of that class

        Arguments:
            qis_abs_path: Absolute to the QComponent source file to be reloaded

        Raises:
            QiskitMetalDesignError: The given name is a magic method not in the dictionary
        """

        try:
            for comp in self.components:  # runs so few times, it's fine to reload object multiple times
                imported_module = importlib.import_module(
                    self.components[comp].__module__)
                reloaded_module = importlib.reload(imported_module)

                new_class = getattr(reloaded_module,
                                    self.components[comp].__class__.__name__)

                print(f"template options: {self.template_options}")
                if self.components[comp].__class__._get_unique_class_name(
                ) in self.template_options:
                    print(f"popping: {new_class}")
                    self.template_options.pop(new_class._get_unique_class_name(
                    ))  # pylint disable=protected-access, line-too-long

                self.components[comp].__class__ = new_class

                self.logger.debug(
                    f'Finished reloading '
                    f'component_class_name={new_class.__name__}; component_module_name={imported_module}'
                )
            self.rebuild()

        except QiskitMetalDesignError as e:  # pylint disable=broad-except
            self.logger.error(
                f"Failed to refresh/rebuild {qis_abs_path} due to: {e}")

    def rename_component(self, component_id: int, new_component_name: str):
        """Rename component.  The component_id is expected.  However, if user
        passes a string for component_id, the method assumes the component_name
        was passed.  Then will look for the id using the component_name.

        Arguments:
            component_id (int): id of component within design, can pass a string for component_name
            new_component_name (str): New name

        Returns:
            int: Results

        Results:
            1: True name is changed. (True)

            -1: Failed, new component name exists.

            -2: Failed, invalid new name; it is already being used by another component.

            -3: Failed, component_id does not exist.
        """
        # We are using component_id,
        # and assuming id is created as being unique.
        # We also want the string (name) to be unique.

        if isinstance(component_id, int):
            a_component_id = component_id
        elif isinstance(component_id, str):
            component_name = str(component_id)
            a_component_id = self.name_to_id[component_name]
            if a_component_id is None:
                return -3
        else:
            logger.warning(
                f'Called rename_component, component_id={component_id}, but component_id'
                f' is not an integer, nor a string.')
            return -3

        if a_component_id in self._components:
            # Check if name is already being used.
            if new_component_name in self.name_to_id:  # pylint disable=line-too-long
                logger.warning(
                    f'Called design.rename_component,'
                    f' component_id({self.name_to_id[new_component_name]}'
                    f',  is already using {new_component_name}.')
                return -2

            # Do rename
            a_component = self._components[a_component_id]

            # Remove old name from cache, add new name
            self.name_to_id.pop(a_component.name, None)
            self.name_to_id[
                new_component_name] = a_component.id  # pylint disable=line-too-long

            # do rename
            self._components[
                component_id]._name = new_component_name  # pylint disable=protected-access, line-too-long

            return True
        logger.warning(
            f'Called rename_component, component_id={component_id}, but component_id'
            f' is not in design.components dictionary.')
        return -3

    def delete_component(
            self,
            component_name: str,
            force: bool = False) -> bool:  #pylint disable=unused-argument
        """Deletes component and pins attached to said component.

        If no component by that name is present, then just return True
        If component has dependencices return false and do not delete,
        unless force=True.

        Arguments:
            component_name (str): Name of component to delete.
            force (bool): Force delete component even if it has children.  Defaults to False.

        Returns:
            bool: Is there no such component
        """

        # Nothing to delete if name not in components
        if component_name not in self.name_to_id:
            self.logger.info(
                f'Called delete_component {component_name}, but such a '
                f'component is not in the design cache dictionary of components.'
            )
            return True
        component_id = self.name_to_id[component_name]

        # check if components has dependencies
        #   if it does, then do not delete, unless force=true
        #       logger.error('Cannot delete component{component_name}. It has dependencies. ')
        #          return false
        #   if it does not then delete

        # Do delete component ruthlessly
        return self._delete_component(component_id)

    def _delete_component(self, component_id: int) -> bool:
        """Delete component without doing any checks.

        Args:
            component_id (int): ID of component to delete

        Returns:
            bool: True if component_id not in design.
        """
        # Remove pins - done inherently from deleting the component, though needs checking
        # if is on the net list or not

        return_response = False

        if component_id in self._components:
            # id in components dict
            # Need to remove pins before popping component.

            # For components to delete, which  connected to any other component,
            # need to set the net_id to zero of OTHER component
            #  before deleting from net_id table.
            for pin_name in self._components[component_id].pins:
                # make net_id be zero for every component which is connected to it.
                net_id_search = self._components[component_id].pins[
                    pin_name].net_id
                df_subset_based_on_net_id = self.net_info[(
                    self.net_info['net_id'] == net_id_search)]
                delete_this_pin = df_subset_based_on_net_id[(
                    df_subset_based_on_net_id['component_id'] != component_id)]

                # If Component is connected to anything, meaning it is part of net_info
                # table.
                if not delete_this_pin.empty:
                    edit_component = list(delete_this_pin['component_id'])[0]
                    edit_pin = list(delete_this_pin['pin_name'])[0]

                    if self._components[edit_component]:
                        if self._components[edit_component].pins[edit_pin]:
                            self._components[edit_component].pins[
                                edit_pin].net_id = 0

            # pins of component to delete.
            self._qnet.delete_all_pins_for_component(component_id)

            # Even though the qgeometry table has string for component_id, dataframe is
            # storing as an integer.
            self._qgeometry.delete_component_id(component_id)

            # Before poping component from design registry, remove name from cache
            component_name = self._components[component_id].name
            self.name_to_id.pop(component_name, None)

            # remove from design dict of components
            self._components.pop(component_id, None)
        else:
            # if not in components dict
            logger.warning(
                f'Called _delete_complete, component_id: {component_id}, '
                'but component_id is not in design.components dictionary.')
            return_response = True
            return return_response

        return return_response

    def copy_multiple_qcomponents(  # pylint disable=dangerous-default-value
        self,
        original_qcomponents: list,
        new_component_names: list,
        all_options_superimpose: list = list()) -> Dict:
        """The lists in the arguments are all used in parallel.  If the length
        of original_qcomponents and new_component_names are not the same, no
        copies will be made and an empty Dict will be returned. The length of
        all_options_superimposes needs to be either empty or exactly the length
        of original_qcomponents, otherwise, an empty dict will be returned.

        Args:
            original_qcomponents (list): Must be a list of original QComponents.
            new_component_names (list): Must be a list of QComponent names.
            all_options_superimpose (list, optional): Must be list of dicts
              with options to superimpose on options from original_qcomponents.
              The list can be of both populated and empty dicts.
              Defaults to empty list().

        Returns:
            Dict: Number of keys will be the same length of original_qcomponent.
                Each key will be the new_component_name.
                Each value will be either a QComponent or None.
                If the copy did not happen, the value will be None, and the key will extracted from new_componet_names.
        """
        copied_info = dict()
        length = len(original_qcomponents)
        if length != len(new_component_names):
            return copied_info

        num_options = len(all_options_superimpose)

        if num_options > 0 and num_options != length:
            return copied_info

        for index, item in enumerate(original_qcomponents):
            if num_options > 0:
                a_copy = self.copy_qcomponent(item, new_component_names[index],
                                              all_options_superimpose[index])
            else:
                a_copy = self.copy_qcomponent(item, new_component_names[index])

            copied_info[new_component_names[index]] = a_copy

        return copied_info

    def copy_qcomponent(
        self,
        original_qcomponent: 'QComponent',
        new_component_name: str,
        options_superimpose: dict = dict(
        )  # pylint disable=dangerous-default-value
    ) -> Union['QComponent', None]:
        """Copy a qcomponent in QDesign and
        add it to QDesign._components using
        options_overwrite.

        Args:
            original_class (QComponent): The QComponent to copy.
            new_component_name (str): The name should not already
              be in QDesign, if it is, the copy fill fail.
            options_superimpose (dict): Can use different options
              for copied QComponent. Will start with the options
              in original QComponent, and then superimpose with
              options_superimpose. An example would be x and y locations.

        Returns:
            union['QComponent', None]: None if not copied, otherwise, a QComponent instance.
        """

        # overwrite original option with new options
        options = {**original_qcomponent.options, **options_superimpose}
        path_class_name = original_qcomponent.class_name
        module_path = path_class_name[:path_class_name.rfind('.')]
        class_name = path_class_name[path_class_name.rfind('.') + 1:]
        if new_component_name not in self.name_to_id:
            if importlib.util.find_spec(module_path):
                qcomponent_class = getattr(
                    importlib.import_module(module_path), class_name, None)
                a_qcomponent = qcomponent_class(self,
                                                new_component_name,
                                                options=options)
                return a_qcomponent
            return None
        else:
            # The new name is already in QDesign.
            return None

#########I/O###############################################################

    @classmethod
    def load_design(cls, path: str):
        """Load a Metal design from a saved Metal file. Will also update
        default dictionaries. (Class method).

        Arguments:
            path (str): Path to saved Metal design.

        Returns:
            QDesign: Loaded metal design.
        """
        logger.warning("Loading is a beta feature.")
        design = load_metal_design(path)
        return design

    def save_design(self, path: str = None):
        """Save the metal design to a Metal file. If no path is given, then
        tried to use self.save_path if it is set.

        Arguments:
            path (str): Path to save the design to.  Defaults to None.

        Returns:
            bool: True if successful; False if failure
        """

        self.logger.warning("Saving is a beta feature.")

        if path is None:
            if self.save_path is None:
                self.logger.error(
                    'Cannot save design since you did not provide a path to'
                    'save to yet. Once you save the design to a path, the then you call save '
                    'without an argument.')
            else:
                path = self.save_path

        self.save_path = str(path)

        # Do the actual saving
        self.logger.info(f'Saving design to {path}')
        result = save_metal(path, self)
        if result:
            self.logger.info('Saving successful.')
        else:
            self.logger.error('Saving failed.')

        return result

#########Creating Components##############################################

    def parse_value(self, value: Union[Any, List, Dict, Iterable]) -> Any:
        """Main parsing function. Parse a string, mappable (dict, Dict),
        iterable (list, tuple) to account for units conversion, some basic
        arithmetic, and design variables.

        Arguments:
            value (str): String to parse *or*
            variable_dict (dict): dict pointer of variables

        Return:
            str, float, list, tuple, or ast eval: Parsed value

        Handled Inputs:

            Strings:
                Strings of numbers, numbers with units; e.g., '1', '1nm', '1 um'
                    Converts to int or float.
                    Some basic arithmetic is possible, see below.
                Strings of variables 'variable1'.
                    Variable interpertation will use string method
                    isidentifier 'variable1'.isidentifier()

            Dictionaries:
                Returns ordered `Dict` with same key-value mappings, where the values have
                been subjected to parse_value.

            Iterables(list, tuple, ...):
                Returns same kind and calls itself `parse_value` on each element.

            Numbers:
                Returns the number as is. Int to int, etc.


        Arithemetic:
            Some basic arithemetic can be handled as well, such as `'-2 * 1e5 nm'`
            will yield float(-0.2) when the default units are set to `mm`.

        Default units:
            User units can be set in the design. The design will set config.DEFAULT.units

        Examples:
            See the docstring for this module.
                qiskit_metal.toolbox_metal.parsing
        """
        return parse_value(value, self.variables)

    def parse_options(self, params: dict, param_names: str) -> dict:
        """Extra utility function that can call parse_value on individual
        options. Use self.parse_value to parse only some options from a params
        dictionary.

        Arguments:
            params (dict): Input dict to pull form
            param_names (str): Keys of dictionary to parse and return as a dictionary.
                               Example value: 'x,y,z,cpw_width'

        Returns:
            dict: Dictionary of the keys contained in `param_names` with values that are parsed.
        """
        return parse_options(params, param_names, variable_dict=self.variables)

    def get_design_name(self) -> str:
        """Get the name of the design from the metadata.

        Returns:
            str: Name of design
        """
        if 'design_name' not in self.metadata:
            self.update_metadata({'design_name': 'Unnamed'})
        return self.metadata.design_name

    def set_design_name(self, name: str):
        """Set the name of the design in the metadata.

        Args:
            name (str) : Name of design
        """
        self.update_metadata({'design_name': name})

    def get_units(self):
        """Gets the units of the design.

        Returns:
            str: units
        """
        return self.template_options.units

####################################################################################
# Dependencies

    def add_dependency(self, parent: str, child: str):
        """Add a dependency between one component and another.

        Arguments:
            parent (str): The component on which the child depends.
            child (str): The child cannot live without the parent.
        """
        pass  # pylint disable=unnecessary-pass

    def remove_dependency(self, parent: str, child: str):
        """Remove a dependency between one component and another.

        Arguments:
            parent (str): The component on which the child depends.
            child (str): The child cannot live without the parent.
        """
        pass  # pylint disable=unnecessary-pass

    def update_component(self, component_name: str, dependencies: bool = True):
        """Update the component and any dependencies it may have. Mediator type
        function to update all children.

        Arguments:
            component_name (str): Component name to update
            dependencies (bool): True to update all dependencies.  Defaults to True.
        """
        # Get dependency graph
        # Remake components in order
        pass  # pylint disable=unnecessary-pass

######### Renderers ###############################################################

    def _start_renderers(self):
        """Start the renderers.

        First import the renderers identified in
        config.renderers_to_load. Then register them into QDesign.
        Finally populate self.renderer_defaults_by_table
        """

        for renderer_key, import_info in config.renderers_to_load.items():
            if 'path_name' in import_info:
                path_name = import_info.path_name
            else:
                self.logger.warning(
                    f'Renderer={renderer_key} is not registered in QDesign.  '
                    f'Looking for key="path_name" and value in config.renderers_to_load.'
                )
                continue

            if 'class_name' in import_info:
                class_name = import_info.class_name
            else:
                self.logger.warning(
                    f'Renderer={renderer_key} is not registered in QDesign.  '
                    f'Looking for key="class_name" and value in config.renderers_to_load.'
                )
                continue

            # check if module_name exists
            if importlib.util.find_spec(path_name):
                class_renderer = getattr(importlib.import_module(path_name),
                                         class_name, None)

                # check if class_name is in module
                if class_renderer is not None:
                    a_renderer = class_renderer(self)

                    # register renderers here.
                    self._renderers[renderer_key] = a_renderer
                else:
                    self.logger.warning(
                        f'Renderer={renderer_key} is not registered in QDesign.  '
                        f'The class_name={class_name} was not found.')
                    continue
            else:
                self.logger.warning(
                    f'Renderer={renderer_key} is not registered in QDesign.  '
                    f'The module_name={path_name} was not found.')
                continue

        for _, a_render in self._renderers.items():
            a_render.add_table_data_to_QDesign(a_render.name)

    def add_default_data_for_qgeometry_tables(self, table_name: str,
                                              renderer_name: str,
                                              column_name: str,
                                              column_value) -> set:
        """Populate the dict (self.renderer_defaults_by_table) which will hold
        the data until a component's get_template_options(design) is executed.

        Note that get_template_options(design) will populate the columns
        of QGeometry table i.e. path, junction, poly etc.

        Example of data format is:
        self.renderer_defaults_by_table[table_name][renderer_name][column_name] = column_value  # pylint disable=line-too-long
        The type for default value placed in a table column is determined by populate_element_extenstions() on line:  # pylint disable=line-too-long
        cls.element_extensions[table][col_name] = type(col_value)  # pylint disable=line-too-long
        in renderer_base.py.

        Dict layout and examples within parenthesis:
            key: Only if need to add data to components,
            for each type of table (path, poly, or junction).
            value: Dict which has

                  keys: render_name (gds), value: Dict which has
                          keys: 'filename', value: (path/filename)
                  keys: render_name (hfss), value: Dict which has
                          keys: 'inductance', value: (inductance_value)

        Args:
            table_name (str): Table used within QGeometry tables i.e. path, poly, junction.
            renderer_name (str): The name of software to export QDesign, i.e. gds, ansys.
            column_name (str): The column name within the table, i.e. filename, inductance.
            column_value (Object): The type can vary based on column. The data is placed under column_name.

        Returns:
            set: Each integer in the set has different meanings.
                * 1 - added key for table_name
                * 2 - added key for renderer_name
                * 3 - added new key for column_name
                * 4 - since column_name already existed, column_value replaced previous column_value
                * 5 - Column value added
                * 6 - Expected str, got something else.
        """

        status = set()  # Empty Set

        if not isinstance(table_name, str):
            status.add(6)
            return status

        if not isinstance(renderer_name, str):
            status.add(6)
            return status

        if not isinstance(column_name, str):
            status.add(6)
            return status

        if table_name not in self.renderer_defaults_by_table.keys():
            self.renderer_defaults_by_table[table_name] = Dict()
            status.add(1)

        if renderer_name not in self.renderer_defaults_by_table[
                table_name].keys():
            self.renderer_defaults_by_table[table_name][renderer_name] = Dict()
            status.add(2)

        if column_name not in self.renderer_defaults_by_table[table_name][
                renderer_name].keys():
            self.renderer_defaults_by_table[table_name][renderer_name][
                column_name] = column_value
            status.add(3)
            status.add(5)
        else:
            self.renderer_defaults_by_table[table_name][renderer_name][
                column_name] = column_value
            status.add(4)
            status.add(5)

        return status

    def get_list_of_tables_in_metadata(self, a_metadata: dict) -> list:
        """Look at the metadata dict to get list of tables the component uses.

        Args:
            a_metadata (dict): Use dict from gather_all_childern for metadata.

        Returns:
            list: List of tables, the component-developer, denoted as being used in metadata.
        """

        uses_table = list()
        for table_name in self.qgeometry.get_element_types():
            search = f'_qgeometry_table_{table_name}'
            table_status = search in a_metadata.keys()
            if table_status:
                if is_true(self.parse_value(a_metadata[search])):
                    uses_table.append(table_name)

        return uses_table