Source code for qiskit_metal.renderers.renderer_gds.make_cheese

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

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""For GDS export, separate the logic for cheesing."""

import logging
from typing import Union

import gdstk
import numpy as np
import shapely


[docs] class Cheesing: """Create a cheese cell based on input of no-cheese locations.""" # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-arguments # pylint: disable=too-many-locals # pylint: disable=too-few-public-methods # To be used by QGDSRenderer only. # Number of instance attributes is acceptable for this case. def __init__( self, multi_poly: shapely.geometry.multipolygon.MultiPolygon, all_nocheese_gds: list, lib: gdstk.Library, minx: float, miny: float, maxx: float, maxy: float, chip_name: str, edge_nocheese: float, layer: int, is_neg_mask: bool, datatype_cheese: int, datatype_keepout: int, fab: bool, logger: logging.Logger, max_points: int, precision: float, cheese_shape: int = 0, # For rectangle shape_0_x: float = 0.000050, shape_0_y: float = 0.000050, # For Circle shape_1_radius: float = 0.000025, # delta spacing for holes delta_x: float = 0.00010, delta_y: float = 0.00010, ): """Create cheesing polygons based on the no-cheese regions. Args: multi_poly: No-cheese areas per layer (shapely multipolygon). all_nocheese_gds: Same geometry in a gdstk-friendly list. lib: Library that will hold all generated cells. minx: Chip minimum x location. miny: Chip minimum y location. maxx: Chip maximum x location. maxy: Chip maximum y location. chip_name: User-defined chip name. edge_nocheese: Buffer around the chip perimeter to leave un-cheesed. layer: Layer number for calculating the cheese. is_neg_mask: If True, export a negative mask; otherwise positive. datatype_cheese: Datatype for cheese polygons. datatype_keepout: Datatype for keepout regions. fab: If True, output fabrication-ready layers; if False, include intermediate/debug layers. max_points: Maximum number of points for a polygon in gdstk. precision: gdstk precision to use. logger: Logger to emit warnings/errors. cheese_shape: 0 for rectangle, 1 for circle. shape_0_x: Rectangle width (centered at 0,0). shape_0_y: Rectangle height (centered at 0,0). shape_1_radius: Circle radius. delta_x: Spacing between holes in x. delta_y: Spacing between holes in y. """ # All the no-cheese locations. self.multi_poly = multi_poly self.nocheese_gds = all_nocheese_gds self.lib = lib # chip boundary, layer and datatype, buffer for perimeter self.minx = minx self.miny = miny self.maxx = maxx self.maxy = maxy self.chip_name = chip_name self.edge_nocheese = edge_nocheese self.layer = layer self.is_neg_mask = is_neg_mask self.datatype_cheese = datatype_cheese self.datatype_keepout = datatype_keepout self.max_points = max_points self.precision = precision self.fab = fab self.logger = logger # Expect to mostly cheese a square, but allow for expansion. self.cheese_shape = cheese_shape self.shape_0_x = shape_0_x self.shape_0_y = shape_0_y self.shape_1_radius = shape_1_radius # Create a shapely the size of chip. self.boundary = shapely.geometry.Polygon([(minx, miny), (minx, maxy), (maxx, maxy)]) self.delta_x = delta_x self.delta_y = delta_y self.cheese_cell = None # max dimension of grid is chip size reduced by self.edge_nocheese self.grid_minx = self.minx + self.edge_nocheese self.grid_miny = self.miny + self.edge_nocheese self.grid_maxx = self.maxx - self.edge_nocheese self.grid_maxy = self.maxy - self.edge_nocheese if self.grid_maxx <= self.grid_minx or self.grid_maxy <= self.grid_miny: self.logger.warning( "When edge_nocheese is applied to decrease the chip size, of where cheesing ", "will happen, the resulting size is not realistic.", ) self.one_hole_cell = None self.hole = None
[docs] def apply_cheesing(self) -> gdstk.Library: """Prototype, not complete. Need to populate self.lib with cheese holes. """ if self._error_checking_hole_delta() == 0: # Place hole into self.hole self._make_one_hole_at_zero_zero() _ = self._hole_to_lib() self._cell_with_grid() else: self.logger.warning("Cheesing not implemented.") return self.lib
def _error_checking_hole_delta(self) -> int: """Check ratio of hole size vs hole spacing. Returns: int: Observation based on hole size and spacing. * 0 No issues detected. * 1 Delta spacing less than or equal to hole * 2 cheese_shape is unknown to Cheesing class. """ observe = -1 if self.cheese_shape == 0: observe = (1 if self.delta_x <= self.shape_0_x or self.delta_y <= self.shape_0_y else 0) elif self.cheese_shape == 1: diameter = 2 * self.shape_1_radius return 1 if self.delta_x <= diameter or self.delta_y <= diameter else 0 else: self.logger.warning( f"The cheese_shape={self.cheese_shape} is unknown in Cheesing class." ) return 2 if observe == 1: self.logger.warning( "The size of delta spacing is same as or smaller than hole.") return observe def _make_one_hole_at_zero_zero(self): """This method will create just one hole used for cheesing defined by a shapely object. It will be placed in self.hole. """ if self.cheese_shape == 0: width, height = self.shape_0_x, self.shape_0_y self.hole = shapely.geometry.box(-width / 2, -height / 2, width / 2, height / 2) elif self.cheese_shape == 1: self.hole = shapely.geometry.Point(0, 0).buffer(self.shape_1_radius) else: self.logger.warning( f"The cheese_shape={self.cheese_shape} is unknown in Cheesing class." ) def _hole_to_lib(self) -> gdstk.Polygon: """Convert the self.hole to a gds cell and add to self.lib. Put the hole on datatype_cheese +2. This is expected to change when we agree to some convention. Returns: gdstk.Polygon: The gdstk polygon for single hole for cheesing. None is not made. """ a_poly = None if isinstance(self.hole, shapely.geometry.Polygon): exterior_poly = gdstk.Polygon( list(self.hole.exterior.coords), layer=self.layer, datatype=self.datatype_cheese + 2, ) # If polygons have a holes, need to remove (subtract) it for gdstk. all_interiors = list() geom = self.hole if geom.interiors: for inside in geom.interiors: interior_coords = list(inside.coords) all_interiors.append(interior_coords) a_poly_set = gdstk.PolygonSet(all_interiors, layer=self.layer, datatype=self.datatype_cheese + 2) a_poly = gdstk.boolean( exterior_poly, a_poly_set, "not", precision=self.precision, layer=self.layer, datatype=self.datatype_cheese + 2, ) else: a_poly = exterior_poly.fracture(max_points=self.max_points, precision=self.precision) else: hole_type = type(self.hole) self.logger.warning(f"The self.hole was not converted to gdstk; " f"the type '{hole_type}' was not handled.") # convert a_poly to cell, then use cell reference to add to all the cheese in chip_rect_gds chip_layer_only_top_name = f"TOP_{self.chip_name}_{self.layer}" cheese_one_hole_cell_name = f"TOP_{self.chip_name}_{self.layer}_one_hole" self.one_hole_cell = self.lib.new_cell(cheese_one_hole_cell_name) self.one_hole_cell.add(*a_poly) if self.one_hole_cell.bounding_box() is not None: next( (c for c in self.lib.cells if c.name == chip_layer_only_top_name )).add(gdstk.Reference(self.one_hole_cell)) else: self.lib.remove(self.one_hole_cell) return a_poly def _cell_with_grid(self): """Use the hole at self.one_hole_cell to create a grid. Then use the no_cheese cell to remove the holes from grid. The difference will be used subtract from the ground layer with geometry. The cells are added to the Top_<chip_name>. """ gather_holes_cell = self._get_all_holes() diff_holes_cell = self._subtract_keepout_from_hole_grid( gather_holes_cell) self.lib.remove(gather_holes_cell) if self.is_neg_mask: # negative mask for given chip and layer self._move_to_under_top_chip_layer_name(diff_holes_cell) if self.fab: self._both_pos_and_neg_mask_fab() # Need the diff cell for negative mask. # self._remove_cheese_diff_cell() else: # positive mask for given chip and layer if self.fab: self._subtract_from_ground_and_move_under_top_chip_layer( diff_holes_cell) self._both_pos_and_neg_mask_fab() # This is something special still to do. self._remove_cheese_diff_cell() self._remove_ground_chip_layer() else: self._subtract_from_ground_and_move_under_top_chip_layer( diff_holes_cell) def _both_pos_and_neg_mask_fab(self): """For both positive and negative mask need to have this cell removed when user has fabricate.fab=True. """ self._remove_cell_one_hole() def _remove_cell_one_hole(self): """Remove cell with just one hole.""" cheese_one_hole_cell_name = f"TOP_{self.chip_name}_{self.layer}_one_hole" cheese_one_hole_cell = next( (c for c in self.lib.cells if c.name == cheese_one_hole_cell_name), None) if cheese_one_hole_cell: self.lib.remove(cheese_one_hole_cell) def _subtract_from_ground_and_move_under_top_chip_layer( self, diff_holes_cell: gdstk.Cell): """Get the existing chip_only_top_name cell, then add the holes to it. Also, add ground_cheesed_cell under chip_only_top_name Args: diff_holes_cell (gdstk.Cell): New cell with cheesed ground """ chip_only_top_layer_name = f"TOP_{self.chip_name}_{self.layer}" chip_only_top_layer_cell = next( (c for c in self.lib.cells if c.name == chip_only_top_layer_name), None) if chip_only_top_layer_cell: if diff_holes_cell.bounding_box() is not None: chip_only_top_layer_cell.add(gdstk.Reference(diff_holes_cell)) ground_cheese_cell = self._subtract_holes_from_ground( diff_holes_cell) # Move to under Top_main_layer (Top_chipname_#) self._move_to_under_top_chip_layer_name(ground_cheese_cell) else: self.lib.remove(diff_holes_cell) def _subtract_keepout_from_hole_grid( self, gather_holes_cell: gdstk.Cell) -> gdstk.Cell: """Given a cell with all the holes, subtract the keepout region. Then return a new cell with the result. Args: gather_holes_cell (gdstk.Cell): Holds a grid of all the holes for cheesing. Returns: gdstk.Cell: Newly created cell that holds the difference of holes minus the keep=out region. """ # subtact the keepout, note, Based on user options, # the keepout (no_cheese) cell may not be in self.lib. temp_keepout_chip_layer_cell = f"temp_keepout_{self.chip_name}_{self.layer}" temp_keepout_cell = self.lib.new_cell(temp_keepout_chip_layer_cell) temp_keepout_cell.add(*self.nocheese_gds) diff_holes = gdstk.boolean( gather_holes_cell.get_polygons(), temp_keepout_cell.get_polygons(), "not", precision=self.precision, layer=self.layer, datatype=self.datatype_cheese + 1, ) diff_holes_cell_name = f"TOP_{self.chip_name}_{self.layer}_Cheese_diff" diff_holes_cell = self.lib.new_cell(diff_holes_cell_name) diff_holes_cell.add(*diff_holes) self.lib.remove(temp_keepout_cell) return diff_holes_cell def _get_all_holes(self) -> gdstk.Cell: """Return a cell with a grid of holes. The keepout has not been applied yet. Returns: gdstk.Cell: Cell containing all the holes. """ gather_holes_cell_name = f"Gather_holes_{self.chip_name}_{self.layer}" gather_holes_cell = self.lib.new_cell(gather_holes_cell_name) x_holes = np.arange(self.grid_minx, self.grid_maxx, self.delta_x, dtype=float).tolist() y_holes = np.arange(self.grid_miny, self.grid_maxy, self.delta_y, dtype=float).tolist() if self.one_hole_cell is not None: for x_loc in x_holes: for y_loc in y_holes: gather_holes_cell.add( gdstk.Reference(self.one_hole_cell, origin=(x_loc, y_loc))) return gather_holes_cell def _subtract_holes_from_ground( self, diff_holes_cell: gdstk.Cell) -> Union[gdstk.Cell, None]: """Get reference to ground cell and then subtract the holes from ground. Place the difference into a new cell, which will eventually be added under Top. Args: diff_holes_cell ([type]): Cell which contains all the holes. Returns: Union[gdstk.Cell, None]: If worked, the new cell with cheesed ground, otherwise, None. """ # Still need to 'not' with Top_main_1 (ground) top_chip_layer_name = f"TOP_{self.chip_name}_{self.layer}" ground_cell_name = f"ground_{self.chip_name}_{self.layer}" if next((c for c in self.lib.cells if c.name == top_chip_layer_name), None): ground_cell = next( (c for c in self.lib.cells if c.name == ground_cell_name)) # Need to keep the depth at 0, otherwise all the # cell references (junctions) will be added for boolean. ground_cheese = gdstk.boolean( ground_cell.get_polygons(depth=0), diff_holes_cell.get_polygons(), "not", precision=self.precision, layer=self.layer, datatype=self.datatype_cheese, ) ground_cheese_cell_name = ( f"TOP_{self.chip_name}_{self.layer}_Cheese_{self.datatype_cheese}" ) ground_cheese_cell = self.lib.new_cell(ground_cheese_cell_name) return ground_cheese_cell.add(*ground_cheese) self.logger.warning( f"The cell:{top_chip_layer_name} was not found in self.lib. " f"Cheesing not implemented.") return None def _move_to_under_top_chip_layer_name(self, a_cell: gdstk.Cell): """Move the cell to under TOP_<chip name>_<layer number>. Args: a_cell (gdstk.Cell): A GDSTK cell. """ chip_only_top_chip_layer_name = f"TOP_{self.chip_name}_{self.layer}" chip_only_top_chip_layer_cell = next( (c for c in self.lib.cells if c.name == chip_only_top_chip_layer_name), None) if chip_only_top_chip_layer_cell: if a_cell.bounding_box() is not None: chip_only_top_chip_layer_cell.add(gdstk.Reference(a_cell)) else: self.lib.remove(a_cell) def _remove_cheese_diff_cell(self): """For a lib, chip and layer, remove the Cheese_diff cell.""" cell_name = f"TOP_{self.chip_name}_{self.layer}_Cheese_diff" cell = next((c for c in self.lib.cells if c.name == cell_name), None) if cell: self.lib.remove(cell) def _remove_ground_chip_layer(self): """[For a lib, chip and layer, remove the ground cell which is created for positive mask. """ cell_name = f"ground_{self.chip_name}_{self.layer}" cell = next((c for c in self.lib.cells if c.name == cell_name), None) if cell: self.lib.remove(cell)