Source code for qiskit_metal.toolbox_metal.layer_stack_handler

# -*- coding: utf-8 -*-

import logging
import os
from re import search
from typing import List, Optional, Tuple, Union

import pandas as pd
from addict import Dict

from qiskit_metal.toolbox_metal.parsing import FALSE_STR, TRUE_STR


[docs] class LayerStackHandler: """Use DataFrame to hold information for multiple chips. For all the chips, do NOT reuse the layer-number. However, within the dataframe, we can repeat layer numbers since a layer can have multiple datatypes. """ Col_Names = [ "chip_name", "layer", "datatype", "material", "thickness", "z_coord", "fill", ] def __init__( self, multi_planar_design: "MultiPlanar", fname: Optional[str] = None ) -> None: """Use the information in MultiPlanar design to parse_value and get the name of filename for layer stack. Args: multi_planar_design (MultiPlanar): Class that is child of QDesign. """ self.multi_planar_design = multi_planar_design self.logger = self.multi_planar_design.logger # type: logging.Logger self.filename_csv_df = None if ( hasattr(self.multi_planar_design, "ls_filename") and self.multi_planar_design.ls_filename ): # populate pandas table from data in this file. self.filename_csv_df = self.multi_planar_design.ls_filename elif fname: self.filename_csv_df = fname self.ls_df = None # self.column_names = [ # 'chip_name', 'layer', 'datatype', 'material', 'thickness', # 'z_coord', 'fill' # ] self.layer_stack_default = Dict( chip_name=["main", "main"], layer=[1, 3], datatype=[0, 0], material=["pec", "silicon"], thickness=["2um", "-750um"], z_coord=["0um", "0um"], fill=["true", "true"], ) self._init_dataframe() self.is_layer_data_unique() def _init_dataframe(self) -> None: """Must check if filename for layerstack is valid before trying to import to a pandas table.""" if self.filename_csv_df: # populate pandas table from data in this file. abs_path = os.path.abspath(self.filename_csv_df) if os.path.isfile(abs_path): self._read_csv_df(abs_path) else: self.logger.error( f"Not able to read file." f"File:{abs_path} not read. Check the name and path." ) else: # enter very basic default data for pandas table. self.ls_df = pd.DataFrame(data=self.layer_stack_default)
[docs] def get_layer_datatype_when_fill_is_true(self) -> Optional[dict]: """Return layer/datatype rows where ``fill`` is True. Returns: dict | None: Mapping of (layer, datatype) -> dict of parsed column values, or None if no rows have ``fill=True``. """ if self.ls_df is None: abs_path = os.path.abspath(self.filename_csv_df) self.logger.error( f"Not able to read file." f"File:{abs_path} not read. Check the name and path." ) mask = ( self.ls_df["fill"] .astype(str) .str.replace("[']", "", regex=True) .isin(TRUE_STR) ) search_result_df = self.ls_df[mask] if len(search_result_df) > 0: try: layer_datatype_fill = dict() for row in search_result_df.itertuples(): a_key = (row.layer, row.datatype) layer_datatype_fill[a_key] = dict() layer_datatype_fill[a_key]["layer"] = row.layer layer_datatype_fill[a_key]["datatype"] = row.datatype layer_datatype_fill[a_key]["thickness"] = ( self.multi_planar_design.parse_value(row.thickness.strip("'")) ) layer_datatype_fill[a_key]["z_coord"] = ( self.multi_planar_design.parse_value(row.z_coord.strip("'")) ) layer_datatype_fill[a_key]["material"] = row.material.strip("'") layer_datatype_fill[a_key]["chip_name"] = row.chip_name.strip("'") return layer_datatype_fill except Exception as ex: self.logger.error( f"User is not using LayerStackHandler.get_layer_datatype_when_fill_is_true correctly. Check your input file." f"\nERROR:{ex}" ) return None
[docs] def get_properties_for_layer_datatype( self, properties: List[str], layer_number: int, datatype: int = 0 ) -> Optional[Tuple[Union[float, str, bool]]]: """When user provides a layer and datatype, they can get properties from the layer_stack file. The allowed options for properties must be in Col_Names. If any of the properties are not in Col_Names, None will be returned. Otherwise a Tuple will be returned with properties in the same order as provided in input variable properties. Args: properties (List[str]): The column(s) within the layer stack that you want for a row based on layer, and datatype. layer_number (int): The layer number within the column denoted by layer. datatype (int, optional): The datatype within the column denoted by datatype. Defaults to 0. Returns: Optional[Tuple[Union[float, str, bool]]]: If the search data provided in the arguments are not in the layer_stack file, None will be returned. If the search values are found in layer_stack file, then a Tuple will be returned with the requested properties in the same order as provided in input variable denoted by properties. """ if not properties: return None # Check if parameter is not a subset if Col_Names T. if not set(properties).issubset(set(self.Col_Names)): self._warning_properties(properties) return None props = Dict() thickness = 0.0 z_coord = 0.0 material = None fill_value = None chip_name = None if self.ls_df is None: abs_path = os.path.abspath(self.filename_csv_df) self.logger.error( f"Not able to read file." f"File:{abs_path} not read. Check the name and path." ) mask = (self.ls_df["layer"] == layer_number) & ( self.ls_df["datatype"] == datatype ) search_result_df = self.ls_df[mask] if len(search_result_df) > 0: try: thickness = self.multi_planar_design.parse_value( search_result_df.thickness.iloc[0].strip("'") ) z_coord = self.multi_planar_design.parse_value( search_result_df.z_coord.iloc[0].strip("'") ) material = search_result_df.material.iloc[0].strip("'") chip_name = search_result_df.chip_name.iloc[0].strip("'") value = search_result_df.fill.iloc[0].strip("'") if value in TRUE_STR: fill_value = True elif value in FALSE_STR: fill_value = False else: self.logger.warning( f'The "fill" value is neither True nor False.' f"You have:{value}. " f"Will return NULL for fill value." ) props["thickness"] = thickness props["z_coord"] = z_coord props["material"] = material props["fill"] = fill_value props["chip_name"] = chip_name except Exception as ex: self._warning_search_minus_chip(layer_number, datatype, ex) result = list() for item in properties: result.append(props[item]) return tuple(result)
[docs] def is_layer_data_unique(self) -> bool: """For each layer number make sure the datatypes are unique. A layers can #have multiple datatypes. Returns: bool: True if empty dataframe, True if for each layer, there is ONLY one datatype. Otherwise, False. """ layer_nums = self.get_unique_layer_ints() if layer_nums: for num in layer_nums: mask = self.ls_df["layer"] == num search_result_num = self.ls_df[mask] if not search_result_num.datatype.is_unique: self.logger.warning( f"There WILL BE PROBLEMS since layer {num} does not have unique datatypes." ) return False return True
def _read_csv_df(self, abs_path: str) -> None: # ASSUME that self.filename_csv_df is valid file path and name. # https://best-excel-tutorial.com/59-tips-and-tricks/485-how-to-display-a-single-quote-in-a-cell try: self.ls_df = pd.read_csv(abs_path) except BaseException as error: self.logger.warning( f"Not able to create pandas dataframe." f"File:{abs_path} not imported. Expected a CSV formatted file." ) self.logger.error(f"The exception: {error}")
[docs] def get_unique_chip_names(self) -> set: """Get a set of unique names used in the layer_stack dataframe. Returns: set: Unique names used in the layer stack used as either default or provided by user. """ names = self.ls_df["chip_name"] result = set(names.str.strip("'")) return result
[docs] def get_unique_layer_ints(self) -> set: """Get a set of unique layer ints used in the layer_stack dataframe. Returns: set: Unique layer numbers used in the layer stack used as either default or provided by user. """ layers = self.ls_df["layer"] return set(layers.unique())
def _warning_properties(self, properties: list): """_Give warning if the properties is Args: parameters (list): _description_ """ self.logger.warning( f"The list for properties: {properties} is not a" f"subset of expected column names: {self.Col_Names}" ) def _warning_search( self, chip_name: str, layer_number: int, data_type: int, ex: Exception ): """Give warning when the layerstack pandas table doesn't have requested chip_name, layer, and datatype information. Args: chip_name (str): Name of chip which has the layer of interest. layer_number (int): The unique layer number through out all chips. The same layer number can not be used on multiple chips. datatype (int, optional): Datatype of the layer number. """ self.logger.warning( f"\nERROR: {ex}" f"\nThere is an error searching in layer_stack dataframe using " f"chip_name={chip_name}, layer={layer_number}, datatype={data_type}" ) def _warning_search_minus_chip( self, layer_number: int, data_type: int, ex: Exception ): """Give warning when the layerstack pandas table doesn't have requested layer, and datatype information. Args: layer_number (int): The unique layer number through out all chips. The same layer number can not be used on multiple chips. datatype (int, optional): Datatype of the layer number. """ self.logger.warning( f"\nERROR: {ex}" f"\nThere is an error searching in layer_stack dataframe using " f" layer={layer_number}, datatype={data_type}" )
[docs] def layer_stack_handler_pilot_error(self): """The handler will return None if incorrect arguments are passed.""" self.logger.error( "User is not using LayerStackHandler.get_properties_for_chip_layer_datatype correctly." )