Source code for laygo2.object.grid.core

#!/usr/bin/python
########################################################################################################################
#
# Copyright (c) 2020, Nifty Chips Laboratory, Hanyang University
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
#   disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
#    following disclaimer in the documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
########################################################################################################################

__author__ = "Jaeduk Han"
__maintainer__ = "Jaeduk Han"
__status__ = "Prototype"

import numpy as np
import laygo2.object

# import laygo2.util.conversion as cv


# Internal functions.
def _extend_index_dim(input_index, new_index, new_index_max):
    """
    A helper function to be used for the multi-dimensional circular array
    indexing of CircularMappingArray. It extends the dimension of the input
    array (input_index) that contains indexing information, with the
    additional indexing variable (new_index) provided. The new_index_max
    variable is specified in case of the new_index does not contain the
    maximum index information (perhaps when an open-end slice is given for
    the new_index).

    Parameters
    ----------
    input_index : iterable
        The iterable object to be extended.
    new_index : iterable
        The iterable object to be added to input_ind to extend its dimension.
    new_index_max : iterable
        The maximum index of new_index when its upper boundary is not
        provided.

    Returns
    -------
    Iterable: The extended index.

    Example
    -------
    >>> import laygo2
    >>> # 0-dim to 1-dim
    >>> laygo2.object.grid._extend_index_dim(None, [3, 4, 6], None)
    [(3,), (4,), (6,)]
    >>> # 0-dim to 1-dim (with slicing input)
    >>> laygo2.object.grid._extend_index_dim(None, slice(3, 8, 2), None)
    [(3,), (5,), (7,)]
    >>> # 0-dim to 1-dim, with maximum index given
    >>> laygo2.object.grid._extend_index_dim(None, slice(3, None, 2), 8)
    [(3,), (4,), (6,)]
    >>> # 1-dim to 2-dim
    >>> laygo2.object.grid._extend_index_dim([(3,), (4,), (6,)], [1, 2], None)
    [[(3, 1), (3, 2)], [(4, 1), (4, 2)], [(6, 1), (6, 2)]]
    """
    # Construct an iterator from new_index
    if isinstance(new_index, (int, np.integer)):
        it = [new_index]
    else:
        if isinstance(new_index, slice):
            # slices don't work very well with multi-dimensional circular mappings.
            it = _conv_slice_to_list(slice_obj=new_index, stop_def=new_index_max)
        else:
            it = new_index
    # Index extension
    if input_index is None:
        output = []
        for i in it:
            output.append(tuple([i]))
        return output
    else:
        output = []
        for _i in input_index:
            output_row = []
            for i in it:
                output_row.append(tuple(list(_i) + [i]))
            output.append(output_row)
        return output


def _conv_slice_to_list(slice_obj, start_def=0, stop_def=100, step_def=1):
    """Convert slice to a list.

    Parameters
    ----------
    slice_obj : slice
        The slice object to be converted.
    start_def : int, optional
        The default starting index if the slice object has no lower boundary.
    stop_def : int, optional
        The default stopping index if the slice object has no upper boundary.
    step_def : int, optional
        The default stepping index if the slice object has no step specified.

    Example
    -------
    >>> import laygo2
    >>> laygo2.object.grid._conv_slice_to_list(slice(0, 10, 2))
    [0, 2, 4, 6, 8]
    """
    if slice_obj.start is None:
        start = start_def
    else:
        start = slice_obj.start
    if slice_obj.stop is None:
        stop = stop_def
    else:
        stop = slice_obj.stop
    if slice_obj.step is None:
        step = step_def
    else:
        step = slice_obj.step
    return list(range(start, stop, step))


def _conv_bbox_to_array(bbox):
    """
    Convert a bbox object to a 2-d array.

    Parameters
    ----------
    bbox : numpy.ndarray
        The bounding box to be converted.

    Example
    -------
    >>> import laygo2
    >>> import numpy as np
    >>> laygo2.object.grid._conv_bbox_to_array(np.array([[0, 0], [1, 2]]))
    array([[[0, 0], [1, 0]],
           [[0, 1], [1, 1]],
           [[0, 2], [1, 2]]])
    """
    array = list()
    for r in range(bbox[0, 1], bbox[1, 1] + 1):
        row = list()
        for c in range(bbox[0, 0], bbox[1, 0] + 1):
            row.append([c, r])
        array.append(row)
    return np.array(array)


def _conv_bbox_to_list(bbox):
    """
    Convert a bbox object to a 1-d list.

    Parameters
    ----------
    bbox : numpy.ndarray
        The bounding box to be converted.

    Example
    -------
    >>> import laygo2
    >>> import numpy as np
    >>> laygo2.object.grid._conv_bbox_to_list(np.array([[0, 0], [1, 2]]))
    [[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2]]
    """
    array = list()
    for r in range(bbox[0, 1], bbox[1, 1] + 1):
        for c in range(bbox[0, 0], bbox[1, 0] + 1):
            array.append([c, r])
    return array


# External functions
[docs] def copy(obj): """Make a copy of the input grid object. Parameters ---------- obj : laygo2.object.grid.Grid The input grid object to be copied. Returns ------- laygo2.object.grid.Grid or derived: the copied grid object. Example ------- >>> import laygo2 >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2_copy = laygo2.object.grid.copy(g2) >>> print(g2) <laygo2.object.grid.core.Grid object at 0x000002002EBA67A0> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60])], >>> print(g2_copy) <laygo2.object.grid.core.Grid object at 0x0000020040C35240> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60])], """ return obj.copy()
[docs] def vflip(obj): """Make a vertically-flipped copy of the input grid object. Parameters ---------- obj : laygo2.object.grid.Grid The input grid object to be copied and flipped. Returns ------- laygo2.object.grid.Grid or derived: the generated grid object. Example ------- >>> import laygo2 >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2_copy = laygo2.object.grid.vflip(g2) >>> print(g2) <laygo2.object.grid.core.Grid object at 0x000001EE82660BE0> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60])], >>> print(g2_copy) <laygo2.object.grid.core.Grid object at 0x000001EE947152D0> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([40, 50, 60, 80, 90])], """ return obj.vflip(copy=True)
[docs] def hflip(obj): """Make a horizontally-flipped copy of the input grid object. Parameters ---------- obj : laygo2.object.grid.Grid The input grid object to be copied and flipped. Returns ------- laygo2.object.grid.Grid: the generated grid object. Example ------- >>> import laygo2 >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2_copy = laygo2.object.grid.hflip(g2) >>> print(g2) <laygo2.object.grid.core.Grid object at 0x000001ACECC30BE0> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60])], >>> print(g2_copy) <laygo2.object.grid.core.Grid object at 0x000001ACFED15300> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 50, 60, 80, 90, 100]), array([10, 20, 40, 50, 60])], """ return obj.hflip(copy=True)
[docs] def vstack(obj): """Stack grid(s) in vertical direction. Parameters ---------- obj : list of laygo2.object.grid.Grid The list containing grid objects to be stacked. Returns ------- laygo2.object.grid.Grid: the generated grid object. Example ------- >>> import laygo2 >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2_copy = laygo2.object.grid.copy(g2) >>> g2_stack = laygo2.object.grid.vstack([g2, g2_copy]) >>> print(g2) <laygo2.object.grid.core.Grid object at 0x000001799FAA0BE0> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60])], >>> print(g2_stack) <laygo2.object.grid.core.Grid object at 0x00000179B1B05870> name: test, class: Grid, scope: [[0, 0], [100, 200]], elements: [array([ 0, 10, 20, 40, 50]), array([ 10, 20, 40, 50, 60, 110, 120, 140, 150, 160])], """ return obj[0].vstack(obj[1:], copy=True)
[docs] def hstack(obj): """Stack grid(s) in horizontal direction. Parameters ---------- obj : list of laygo2.object.grid.Grid The list containing grid objects to be stacked. Returns ------- laygo2.object.grid.Grid: the generated grid object. Example ------- >>> import laygo2 >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2_copy = laygo2.object.grid.copy(g2) >>> g2_stack = laygo2.object.grid.hstack([g2, g2_copy]) >>> print(g2) <laygo2.object.grid.core.Grid object at 0x000001799FAA0BE0> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60])], >>> print(g2_stack) <laygo2.object.grid.core.Grid object at 0x0000015BD8C85570> name: test, class: Grid, scope: [[0, 0], [200, 100]], elements: [array([ 0, 10, 20, 40, 50, 100, 110, 120, 140, 150]), array([10, 20, 40, 50, 60])], """ return obj[0].hstack(obj[1:], copy=True)
# Internal classes
[docs] class CircularMapping: """ Basic circular mapping class (index number expands infinitely). Example ------- >>> from laygo2.object.grid import Circularmapping >>> map = CircularMapping(elements=[100, 200, 300]) >>> print(map[0]) 100 >>> print(map[2]) 300 >>> print(map[4]) 200 >>> print(map[-3]) 100 >>> print(map[[2, 3, -2]) [300, 100, 200] >>> print(map[2:7]) [300, 100, 200, 300, 100] """ _elements = None """list: Array consisting of the elements of circular mapping. Example ------- >>> from laygo2.object.grid import CircularMapping >>> elements = [0, 35, 85, 130, 180] >>> cm = CircularMapping(elements) >>> cm.elements [0, 35, 85, 130, 180] .. image:: ../assets/img/object_grid_CircularMapping_elements.png :height: 250 """ dtype = int """type: Data type of the circular mapping. Example ------- >>> from laygo2.object.grid import CircularMapping >>> elements = [0, 35, 85, 130, 180] >>> cm = CircularMapping(elements) >>> cm.dtype int .. image:: ../assets/img/object_grid_CircularMapping_dtype.png :height: 250 """
[docs] def get_elements(self): """numpy.ndarray: getter of elements.""" return self._elements
[docs] def set_elements(self, value): """numpy.ndarray: setter of elements.""" self._elements = np.asarray(value, dtype=self.dtype)
elements = property(get_elements, set_elements) """numpy.ndarray: the array that contains the physical coordinates of the grid.""" @property def shape(self): """numpy.ndarray: The shape of circular mapping. Example ------- >>> from laygo2.object.grid import CircularMapping >>> elements = [0, 35, 85, 130, 180] >>> cm = CircularMapping(elements) >>> cm.shape array([5]) .. image:: ../assets/img/object_grid_CircularMapping_shape.png :height: 250 """ return np.array(self.elements.shape)
[docs] def __init__(self, elements=np.array([0]), dtype=int): """ Constructor function of CircularMapping class. Parameters ---------- elements : list elements. dtype : type data type of elements. Example ------- >>> from laygo2.object.grid import CircularMapping >>> elements = [0, 35, 85, 130, 180] >>> cm = CircularMapping(elements) >>> cm.shape [5] >>> cm[5] 35 >>> cm[0:10] [0, 35, 85, 130, 0, 35, 85, 130, 0, 35] .. image:: ../assets/img/object_grid_CircularMapping_init.png :height: 250 """ self.dtype = dtype self.elements = np.asarray(elements, dtype=dtype)
# indexing and slicing def __getitem__(self, pos): """Element access function of circular mapping.""" if isinstance(pos, (int, np.integer)): return self.elements[pos % self.shape[0]] elif isinstance(pos, slice): return self.__getitem__( pos=_conv_slice_to_list(slice_obj=pos, stop_def=self.shape[0]) ) elif isinstance(pos, np.ndarray): return np.array([self.__getitem__(pos=p) for p in pos]) elif isinstance(pos, list): return [self.__getitem__(pos=p) for p in pos] elif pos is None: return None else: raise TypeError("CircularMapping received an invalid index:%s" % str(pos)) # Iterators def __iter__(self): """Iteration function of circular mapping.""" return self.elements.__iter__() def __next__(self): """Next element access function of circular mapping.""" # Check if numpy.ndarray implements __next__() return self.elements.__next__() # Informative functions def __str__(self): return self.summarize()
[docs] def summarize(self): """Return the summary of the object information.""" return ( self.__repr__() + " " "class: " + self.__class__.__name__ + ", " + "elements: " + str(self.elements) )
# Regular member functions
[docs] def append(self, elem): """Append elements to the mapping.""" if not isinstance(elem, list): elem = [elem] self.elements = np.array(self.elements.tolist() + elem)
[docs] def flip(self): """Flip the elements of the object.""" self.elements = np.flip(self.elements, axis=0)
[docs] def copy(self): """Copy the object.""" return CircularMapping(self.elements.copy(), dtype=self.dtype)
[docs] def concatenate(self, obj): self.elements = np.concatenate((self.elements, obj.elements))
# for e in elements: # self.elements = np.concatenate((self.elements, obj.elements)) # self.range[1] += obj.range[1] - obj.range[0]
[docs] class CircularMappingArray(CircularMapping): """ Multi-dimensional circular mapping class (index number expands infinitely). """ def __getitem__(self, pos): """ Element access function. Parameters ---------- pos : int index number being accessed. Returns ------- numpy.ndarray Example ------- >>> from laygo2.object.grid import CircularMappingArray >>> elements = [[0, 0], [35, 0], [85, 0], [130, 0]] >>> cm = CircularMappingArray(elements = elements) >>> cm[1, :] array([[35, 0]]) >>> cm[3, 0] 130 .. image:: ../assets/img/object_grid_CircularMappingArray_getitem.png :height: 250 """ if isinstance(pos, list): # pos is containing multiple indices as a list return [self.__getitem__(pos=p) for p in pos] elif pos is None: return None elif np.all(np.array([isinstance(p, (int, np.integer)) for p in pos])): # pos is mapped to a single element (pos is composed of integer indices). # just do rounding. idx = [] for p, s in zip(pos, self.shape): idx.append(p % s) return self.elements[tuple(idx)] else: # pos is mapped to multiple indices. (possible Example include ([0:5, 3], [[1,2,3], 3], ...). # Create a list containing the indices to iterate over, and return a numpy.ndarray containing items # corresponding to the indices in the list. # When the indices don't specify the lower boundary (e.g., [:5]), it iterates from 0. # When the indices don't specify the upper boundary (e.g., [3:]), it iterates to the maximum index defined. idx = None for i, p in enumerate(pos): # iterate over input indices (x and y). idx = _extend_index_dim(idx, p, self.shape[i]) idx = np.asarray(idx) # iterate and generate the list to return item = np.empty( idx.shape[:-1], dtype=self.dtype ) # -1 because the tuples in idx are flatten. for i, _null in np.ndenumerate(item): item[i] = self.__getitem__(pos=tuple(idx[i])) return np.asarray(item) # Regular member functions
[docs] def flip(self, axis): """Flip the elements of the object.""" self.elements = np.flip(self.elements, axis=axis)
[docs] def copy(self): """Copy the object.""" return CircularMappingArray(self.elements.copy(), dtype=self.dtype)
class _AbsToPhyGridConverter: """ An internal class that converts abstract coordinates into physical coordinates. Conversely, conditional operators convert physical coordinates into abstract coordinates. .. image:: ../assets/img/user_guide_abs2phy.png """ master = None """laygo2.Grid or laygo2.OneDimGrid: Coordinate system to which _AbsToPhyGridConverter object belongs. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> print(g1_x.abs2phy) <laygo2.object.grid._AbsToPhyGridConverter object> >>> print(g2.xy) <laygo2.object.grid._AbsToPhyGridConverter object> >>> print(g1_x.abs2phy.master) <laygo2.object.grid.OneDimGrid object> >>> print(g2.xy.master) <laygo2.object.grid.Grid object> """ # Constructor def __init__(self, master): """Constructor function of _AbsToPhyGridConverter class.""" self.master = master # Access functions. def __call__(self, pos): """ Convert abstract coordinates of the master grid into corresponding physical coordinates. Parameters ---------- pos : int abstract coordinates. Returns ------- int or numpy.ndarray physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0,30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> g1_x.abs2phy(0) 0 >>> g2.xy(0,0) [0, 0] .. image:: ../assets/img/object_grid_AbsToPhyGridConverter_call.png :height: 250 """ return self.__getitem__(pos) def __getitem__(self, pos): """ Convert abstract coordinates of the master grid into corresponding physical coordinates. Parameters ---------- pos : int abstract coordinates. Returns ------- int or numpy.ndarray physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> g1_x.abs2phy(0) 0 >>> g2.xy(0,0) [0, 0] .. image:: ../assets/img/object_grid_AbsToPhyGridConverter_getitem.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._getitem_1d(pos) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._getitem_2d(pos) else: return None def _getitem_1d(self, pos): """An internal function of __getitem__() for 1-d grids.""" # Check if pos has multiple elements. if isinstance(pos, slice): return self._getitem_1d( _conv_slice_to_list(slice_obj=pos, stop_def=self.master.shape[0]) ) elif isinstance(pos, np.ndarray): return self._getitem_1d(pos.tolist()) elif isinstance(pos, list): return np.array([self._getitem_1d(p) for p in pos]) elif pos is None: raise TypeError( "_AbsToPhyConverter._getitem_1d does not accept None as its input." ) else: # pos is a single element. Compute quotient and modulo for grid extension. quo = 0 mod = int(round(pos)) if pos >= self.master.shape[0]: mod = int(round(pos % self.master.shape[0])) quo = int(round((pos - mod) / self.master.shape[0])) elif pos < 0: mod = int(round(pos % self.master.shape[0])) quo = int(round((pos - mod)) / self.master.shape[0]) return quo * self.master.range[1] + self.master.elements[mod] # the following command cannot handle the size extension of the grid, disabled. # return self.master.elements.take(pos, mode='wrap') def _getitem_2d(self, pos): """An internal function of __getitem__() for 2-d grids.""" if isinstance(pos, list): if isinstance(pos[0], (int, np.integer)): # single point return self[pos[0], pos[1]] else: return [self[p] for p in pos] elif isinstance(pos, np.ndarray): if isinstance(pos[0], (int, np.integer)): # single point return np.array(self[pos[0], pos[1]]) else: return np.array([self[p] for p in pos]) # compute coordinates from OneDimGrids of its master. x = self.master.x[pos[0]] y = self.master.y[pos[1]] # TODO: Refactor the following code to avoid the use of double for loops and list comprehensions. if (not isinstance(x, np.ndarray)) and ( not isinstance(y, np.ndarray) ): # x and y are scalars. return np.array([x, y]) if not isinstance(x, np.ndarray): # x is a scalar. return np.array([np.array([x, _y]) for _y in y]) elif not isinstance(y, np.ndarray): # y is a scalar. return np.array([np.array([_x, y]) for _x in x]) else: xy = [] for _x in x: # vectorize this operation. row = [] for _y in y: row.append(np.array([_x, _y])) xy.append(np.array(row)) return np.array(xy) # Reverse-access operators (comparison operators are used for reverse-access). def __eq__(self, other): """ Convert physical coordinates into abstract coordinates of the master grid satisfying conditional operations. Parameters ---------- other : int physical coordinates. Returns ------- int or numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> g1_x.abs2phy == 35 1 >>> g2.xy == [35, 35] [1, None] .. image:: ../assets/img/object_grid_AbsToPhyGridConverter_eq.png :height: 250 """ return self.master.phy2abs(pos=other) def __lt__(self, other): """ Convert physical coordinates into abstract coordinates of the master grid satisfying conditional operations. Parameters ---------- other : int physical coordinates. Returns ------- int or numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> g1_x.abs2phy < 35 0 >>> g2.xy < [35, 35] [0, 1] .. image:: ../assets/img/object_grid_AbsToPhyGridConverter_lt.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._lt_1d(other) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._lt_2d(other) else: return None @staticmethod def _phy2abs_operator(other, elements, width, shape, op): def phy2abs(x): if x > 0: quo_coarce = 0 + x // width msb_sub = 1 else: quo_coarce = 0 + x // width msb_sub = 0 remain = x % width # positive msb = quo_coarce * shape - 1 for i, e in np.ndenumerate(elements): # print("e: %d r:%d, m:%d, i:%d off:%d phy:%d " %(e, remain, msb + i[0], i[0], lsb_offset, quo_coarce*width + e )) # print(comp( e , remain )) if comp(e, remain) == True: # find maximum less then remain , e < r pass else: # when it is False, latest true index return msb + i[0] + lsb_offset return msb + shape + lsb_offset if op == "<": ## max lesser comp = lambda e, r: e < r lsb_offset = 0 elif op == "<=": ## eq or max lesser eq comp = lambda e, r: e <= r lsb_offset = 0 elif op == ">": ## min greater comp = lambda e, r: e <= r lsb_offset = 1 elif op == ">=": ## eq or min greater comp = lambda e, r: e < r lsb_offset = 1 if isinstance(other, (int, np.integer)): return phy2abs(other) else: list_return = [] for o in other: list_return.append(phy2abs(o)) return np.array(list_return) def _lt_1d(self, other): return self._phy2abs_operator( other, self.master.elements, self.master.width, self.master.elements.shape[0], "<", ) def _lt_2d(self, other): if isinstance(other[0], (int, np.integer)): return np.array([self.master.x < other[0], self.master.y < other[1]]) else: return np.array([self._lt_2d(o) for o in other]) def __le__(self, other): """ Convert physical coordinates into abstract coordinates of the master grid satisfying conditional operations. Parameters ---------- other : int physical coordinates. Returns ------- int or numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0,30], elements=[0]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> g1_x.abs2phy <= 35 1 >>> g2.xy <= [35,35] [1,1] .. image:: ../assets/img/object_grid_AbsToPhyGridConverter_le.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._le_1d(other=other) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._le_2d(other=other) def _le_1d(self, other): return self._phy2abs_operator( other, self.master.elements, self.master.width, self.master.elements.shape[0], "<=", ) def _le_2d(self, other): if isinstance(other[0], (int, np.integer)): return np.array([self.master.x <= other[0], self.master.y <= other[1]]) else: return np.array([self._le_2d(o) for o in other]) def __gt__(self, other): """ Convert physical coordinates into abstract coordinates of the master grid satisfying conditional operations. Parameters ---------- other : int physical coordinates. Returns ------- int or numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> g1_x.abs2phy > 35 2 >>> g2.xy > [35, 35] [2, 2] .. image:: ../assets/img/object_grid_AbsToPhyGridConverter_gt.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._gt_1d(other=other) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._gt_2d(other=other) def _gt_1d(self, other): return self._phy2abs_operator( other, self.master.elements, self.master.width, self.master.elements.shape[0], ">", ) def _gt_2d(self, other): if isinstance(other[0], (int, np.integer)): return np.array([self.master.x > other[0], self.master.y > other[1]]) else: return np.array([self._gt_2d(o) for o in other]) def __ge__(self, other): """ Convert physical coordinates into abstract coordinates of the master grid satisfying conditional operations. Parameters ---------- other : int physical coordinates. Returns ------- int or numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0,30], elements=[0]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> g1_x.abs2phy >= 35 1 >>> g2.xy >= [35, 35] [1, 2] .. image:: ../assets/img/object_grid_AbsToPhyGridConverter_ge.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._ge_1d(other=other) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._ge_2d(other=other) def _ge_1d(self, other): return self._phy2abs_operator( other, self.master.elements, self.master.width, self.master.elements.shape[0], ">=", ) def _ge_2d(self, other): if isinstance(other[0], (int, np.integer)): return np.array([self.master.x >= other[0], self.master.y >= other[1]]) else: return np.array([self._ge_2d(o) for o in other]) class _PhyToAbsGridConverter: """ A class that converts physical coordinates into abstract coordinates. Conversely, conditional operators convert abstract coordinates into physical coordinates. .. image:: ../assets/img/user_guide_phy2abs.png """ master = None """laygo2.Grid or laygo2.OneDimGrid: Coordinate system to which _PhyToAbsGridConverter object belongs. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> print(g1_x.phy2abs) <laygo2.object.grid._PhyToAbsGridConverter object> >>> print(g2.mn) <laygo2.object.grid._PhyToAbsGridConverter object> >>> print(g1_x.phy2abs.master) <laygo2.object.grid.OneDimGrid object> >>> print(g2.mn.master) <laygo2.object.grid.Grid object> """ # Constructor def __init__(self, master): """Constructor function of _PhyToAbsGridConverter class.""" self.master = master # Access functions. def __call__(self, pos): """ Convert physical coordinates into the corresponding abstract coordinates of the master grid. Parameters ---------- pos : int physical coordinates. Returns ------- int or numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> g1_x.phy2abs(35) 1 >>> g2.mn([[35, 35]]) [1, None] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_call.png :height: 250 """ return self.__getitem__(pos) def __getitem__(self, pos): """ Convert physical coordinates into the corresponding abstract coordinates of the master grid. Parameters ---------- pos : int physical coordinates. Returns ------- int or numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> g1_x.phy2abs(35) 1 >>> g2.mn( [[35, 35]]) [1, None] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_getItem.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._getitem_1d(pos) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._getitem_2d(pos) else: return None def _getitem_1d(self, pos): """An internal function of __getitem__() for 1-d grids.""" # Check if pos has multiple elements. if isinstance(pos, OneDimGrid): return self._getitem_1d(pos=pos.elements) elif isinstance(pos, slice): return self._getitem_1d( _conv_slice_to_list(slice_obj=pos, stop_def=self.master.shape[0]) ) elif isinstance(pos, np.ndarray): return self._getitem_1d(pos.tolist()) elif isinstance(pos, list): return np.array([self._getitem_1d(p) for p in pos]) elif pos is None: raise TypeError( "_AbsToPhyConverter._getitem_1d does not accept None as its input." ) else: # pos is a single element. for i, e in np.ndenumerate(self.master.elements): if (pos - e) % self.master.width == 0: return ( int(round((pos - e) / self.master.width)) * self.master.elements.shape[0] + i[0] ) return None # no matched coordinate def _getitem_2d(self, pos): """An internal function of __getitem__() for 2-d grid.""" # If pos contains multiple coordinates (or objects), convert recursively. if isinstance(pos, list): if isinstance( pos[0], (int, np.integer) ): # It's actually a single coordinate. return self[pos[0], pos[1]] else: return [self[p] for p in pos] elif isinstance(pos, np.ndarray): if isinstance( pos[0], (int, np.integer) ): # It's actually a single coordinate. return np.array(self[pos[0], pos[1]]) else: return np.array([self[p] for p in pos]) # If pos contains only one physical object, convert its bounding box to abstract coordinates if (pos.__class__.__name__ == "PhysicalObject") or ( issubclass(pos.__class__, laygo2.object.PhysicalObject) ): return self.bbox(pos) # If pos contains only one coordinate, convert it to abstract grid. m = self.master.x == pos[0] n = self.master.y == pos[1] # refactor the following code to avoid the use of double for-loops and list comprehensions. if (not isinstance(m, np.ndarray)) and ( not isinstance(n, np.ndarray) ): # x and y are scalars. return np.array([m, n]) if not isinstance(m, np.ndarray): # x is a scalar. return np.array([np.array([m, _n]) for _n in n]) elif not isinstance(n, np.ndarray): # y is a scalar. return np.array([np.array([_m, n]) for _m in m]) else: mn = [] for _m in m: # vectorize this operation. row = [] for _n in n: row.append(np.array([_m, _n])) mn.append(np.array(row)) return np.array(mn) # Reverse-access operators (comparison operators are used for reverse-access). def __eq__(self, other): """ Convert abstract coordinates into physical coordinates satisfying conditional operations in the master grid. Parameters ---------- other : int abstract coordinates. Returns ------- int or numpy.ndarray physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> g1_x.phy2abs == 1 35 >>> g2.mn == [1, 1] [35, 30] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_eq.png :height: 250 """ return self.master.abs2phy(pos=other) """ if (self.master.__class__.__name__ == 'OneDimGrid') or (issubclass(self.master.__class__, OneDimGrid)): return self._eq_1d(other=other) if (self.master.__class__.__name__ == 'Grid') or (issubclass(self.master.__class__, Grid)): return self._eq_2d(other=other) def _eq_1d(self, other): return self._getitem_1d(pos=other) def _eq_2d(self, other): # If other is a physical object, convert its bounding box to abstract coordinates. if (other.__class__.__name__ == 'PhysicalObject') or (issubclass(other.__class__, laygo2.object.PhysicalObject)): mn0 = self.master >= other.bbox[0] mn1 = self.master <= other.bbox[1] return np.array([mn0, mn1]) if isinstance(other[0], (int, np.integer)): return np.array([self.master.m[other[0]], self.master.n[other[1]]]) else: return np.array([self._eq_2d(o) for o in other]) """ def __lt__(self, other): """ Convert abstract coordinates into physical coordinates satisfying conditional operations in the master grid. Parameters ---------- other : int abstract coordinates. Returns ------- int or numpy.ndarray physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> g1_x.phy2abs < 1 0 >>> g2.mn < [1, 1] [0, 0] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_lt.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._lt_1d(other=other) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._lt_2d(other=other) def _lt_1d(self, other): if isinstance(other, (int, np.integer)): return self.master.abs2phy.__getitem__(pos=other - 1) return np.array([self._lt_1d(o) for o in other]) def _lt_2d(self, other): if isinstance(other[0], (int, np.integer)): return self.master.abs2phy.__getitem__(pos=(other[0] - 1, other[1] - 1)) return np.array([self._lt_2d(o) for o in other]) def __le__(self, other): """ Convert abstract coordinates into physical coordinates satisfying conditional operations in the master grid. Parameters ---------- other : int abstract coordinates. Returns ------- int or numpy.ndarray physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> g1_x.phy2abs <= 1 35 >>> g2.mn <= [1, 1] [35, 30] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_le.png :height: 250 """ return self.master.abs2phy(pos=other) def __gt__(self, other): """ Convert abstract coordinates into physical coordinates satisfying conditional operations in the master grid. Parameters ---------- other : int abstract coordinates. Returns ------- int or numpy.ndarray physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> g1_x.phy2abs > 1 85 >>> g2.mn > [1, 1] [85, 60] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_gt.png :height: 250 """ if (self.master.__class__.__name__ == "OneDimGrid") or ( issubclass(self.master.__class__, OneDimGrid) ): return self._gt_1d(other) if (self.master.__class__.__name__ == "Grid") or ( issubclass(self.master.__class__, Grid) ): return self._gt_2d(other) else: return None def _gt_1d(self, other): if isinstance(other, (int, np.integer)): return self.master.abs2phy.__getitem__(pos=other + 1) return np.array([self._gt_1d(o) for o in other]) def _gt_2d(self, other): if isinstance(other[0], (int, np.integer)): return self.master.abs2phy.__getitem__(pos=(other[0] + 1, other[1] + 1)) return np.array([self._gt_2d(o) for o in other]) def __ge__(self, other): """ Convert abstract coordinates into physical coordinates satisfying conditional operations in the master grid. Parameters ---------- other : int abstract coordinates. Returns ------- int or numpy.ndarray physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 180]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 30], elements=[0]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> g1_x.phy2abs >= 1 35 >>> g2.mn >=[1, 1] [35, 30] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_ge.png :height: 250 """ return self.master.abs2phy.__getitem__(pos=other) def bbox(self, obj): """ Convert the bounding box of the object into the abstract coordinates of the master grid. Parameters ---------- obj : laygo2.physical object having physical coordinate. Returns ------- numy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0 = physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0’) >>> phy2abs.bbox(rect0) [[0, 0] , [4, 4]] >>> g2.mn.bbox(rect0) [[0, 0] , [4, 4]] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_bbox.png :height: 250 """ if (obj.__class__.__name__ == "PhysicalObject") or ( issubclass(obj.__class__, laygo2.object.PhysicalObject) ): obj = obj.bbox # phy -> abs mn0 = self.master.xy >= obj[0] ## ge than lower left mn1 = self.master.xy <= obj[1] ## le than upper right\ return np.array([mn0, mn1]) def bottom_left(self, obj): """ Convert an object's physical corner coordinates into abstract coordinates of the master grid. Parameters ---------- obj : laygo2.physical object having physical coordinate. Returns ------- numy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name='test', vgrid=g1_x, hgrid=g1_y) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0 = physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0’) >>> phy2abs.bottom_left(rect0) [0, 0] >>> g2.mn.bottom_left(rect0) [0, 0] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_bottom_left.png :height: 250 """ if (obj.__class__.__name__ == "PhysicalObject") or ( issubclass(obj.__class__, laygo2.object.PhysicalObject) ): return self.bottom_left(obj.bbox) else: _i = self.bbox(obj) return _i[0] def bottom_right(self, obj): """ Convert an object's physical corner coordinates into abstract coordinates of the master grid. Parameters ---------- obj : laygo2.physical object having physical coordinate. Returns ------- numy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0 = physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0’) >>> phy2abs.bottom_right(rect0) [4, 0] >>> g2.mn.bottom_right(rect0) [4, 0] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_bottom_right.png :height: 250 """ if (obj.__class__.__name__ == "PhysicalObject") or ( issubclass(obj.__class__, laygo2.object.PhysicalObject) ): return self.bottom_right(obj.bbox) else: _i = self.bbox(obj) return np.array([_i[1, 0], _i[0, 1]]) def top_left(self, obj): """ Convert an object's physical corner coordinates into abstract coordinates of the master grid. Parameters ---------- obj : laygo2.physical object having physical coordinate. Returns ------- numy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0 = physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0’) >>> phy2abs.top_left(rect0) [0, 4] >>> g2.mn.top_left(rect0) [0, 4] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_top_left.png :height: 250 """ if (obj.__class__.__name__ == "PhysicalObject") or ( issubclass(obj.__class__, laygo2.object.PhysicalObject) ): return self.top_left(obj.bbox) else: _i = self.bbox(obj) return np.array([_i[0, 0], _i[1, 1]]) def top_right(self, obj): """ Convert an object's physical corner coordinates into abstract coordinates of the master grid. Parameters ---------- obj : laygo2.physical object having physical coordinate. Returns ------- numy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0 = physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0’) >>> phy2abs.top_right(rect0) [4, 4] >>> g2.mn.top_right(rect0) [4, 4] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_top_right.png :height: 250 """ if (obj.__class__.__name__ == "PhysicalObject") or ( issubclass(obj.__class__, laygo2.object.PhysicalObject) ): return self.top_right(obj.bbox) else: _i = self.bbox(obj) return _i[1] def width(self, obj): """Return the width of an object on this grid.""" if (obj.__class__.__name__ == "PhysicalObject") or ( issubclass(obj.__class__, laygo2.object.PhysicalObject) ): return self.width(obj.bbox) else: _i = self.bbox(obj) return abs(_i[1, 0] - _i[0, 0]) def height(self, obj): """Return the height of an object on this grid.""" if (obj.__class__.__name__ == "PhysicalObject") or ( issubclass(obj.__class__, laygo2.object.PhysicalObject) ): return self.height(obj.bbox) else: _i = self.bbox(obj) return abs(_i[1, 1] - _i[0, 1]) def height_vec(self, obj): """numpy.ndarray(dtype=int): Return np.array([0, height]).""" return np.array([0, self.height(obj)]) def width_vec(self, obj): """numpy.ndarray(dtype=int): Return np.array([width, 0]).""" return np.array([self.width(obj), 0]) def size(self, obj): """ Convert an object's size ([width, height]) into abstract coordinates of the master grid. Parameters ---------- obj : laygo2.physical object having physical coordinate. Returns ------- numpy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0 = physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0’) >>> phy2abs.size(rect0) [4, 4] >>> g2.mn.size(rect0) [4, 4] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_size.png :height: 250 """ return np.array([self.width(obj), self.height(obj)]) def crossing(self, *args): """ Convert the physical intersections of objects into abstract coordinates of the master grid. Parameters ---------- args : laygo2.Physical physical object having bbox. Returns ------- numpy.ndarray(int, int) abstract points. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 10], elements=[0]) >>> g1_y = OneDimGrid(name='yg’, scope=[0, 120], elements=[0, 20, 40, 80, 100, 120]) >>> g2 = Grid(name='g', vgrid = g1_x, hgrid = g1_y ) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0= physical.Rect(xy=[[0, 0], [60, 90]]) >>> rect1= physical.Rect(xy=[[30, 30], [120, 120]]) >>> phy2abs.crossing(rect0, rect1) [3, 2] >>> g2.mn.crossing(rect0, rect1) [3, 2] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_crossing.png :height: 250 """ return self.overlap(*args, type="point") def overlap(self, *args, type="bbox"): """ Convert the overlapping area of objects into abstract coordinates of the master grid and return in a format specified in type. A bounding box is returned if type='bbox' All coordinates in the overlapped region are returned in a two-dimensional array if type='array' An one-dimensional list is returned if type='list'. Parameters ---------- args : laygo2.Physical physical object having bbox. Returns ------- numpy.ndarray bbox abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 10], elements=[0]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 120], elements=[0, 20, 40, 80, 100, 120]) >>> g2 = Grid(name='g', vgrid = g1_x, hgrid = g1_y ) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0= physical.Rect(xy=[[0, 0], [60, 90]]) >>> rect1= physical.Rect(xy=[[30, 30], [120, 120]]) >>> phy2abs.overlap(rect0, rect1) [[3, 2], [6,4]] >>> g2.mn.overlap(rect0, rect1) [[3, 2], [6,4]] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_overlap.png :height: 250 """ _ib = None for _obj in args: if _ib is None: _ib = self.bbox(_obj) ## shaped else: _b = self.bbox(_obj) _x = np.sort(np.array([_b[:, 0], _ib[:, 0]]), axis=None) _y = np.sort(np.array([_b[:, 1], _ib[:, 1]]), axis=None) _ib = np.array([[_x[1], _y[1]], [_x[2], _y[2]]]) if type == "bbox": return _ib elif type == "point": return _ib[0] elif type == "list": return _conv_bbox_to_list(_ib) elif type == "array": return _conv_bbox_to_array(_ib) else: raise ValueError( "overlap() should receive a valid value for its type (bbox, point, array, ...)" ) def union(self, *args): """ Convert the bounding box containing all objects into abstract coordinates of the master grid. Parameters ---------- args : laygo2.Physical physical object having bbox. Returns ------- numpy.ndarray bbox abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 10], elements=[0]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 120], elements=[0, 20, 40, 80, 100, 120 ) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> rect0= physical.Rect(xy=[[0, 0], [60, 90]]) >>> rect1= physical.Rect(xy=[[30, 30], [120, 120]]) >>> g2.mn.union(rect0, rect1) [[0, 0], [12,7]] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_union.png :height: 250 """ _ub = None for _obj in args: if _ub is None: _ub = self.bbox(_obj) else: _b = self.bbox(_obj) _x = np.sort(np.array([_b[:, 0], _ub[:, 0]]), axis=None) _y = np.sort(np.array([_b[:, 1], _ub[:, 1]]), axis=None) _ub = np.array([[_x[0], _y[0]], [_x[3], _y[3]]]) return _ub def center(self, obj): """ Convert an object's physical center coordinates into abstract coordinates of the master grid. Parameters ---------- obj : laygo2.physical object having physical coordinate. Returns ------- numy.ndarray abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g1_y = OneDimGrid(name='yg', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name='g', vgrid=g1_x, hgrid=g1_y) >>> phy2abs = _PhyToAbsGridConverter(master=g2) >>> rect0 = physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0’) >>> phy2abs.center(rect0) [3, 3] >>> g2.mn.center(rect0) [3, 3] .. image:: ../assets/img/object_grid_PhyToAbsGridConverter_center.png :height: 250 """ mn0 = self.master.xy >= obj.center mn1 = self.master.xy <= obj.center point_list = [ self.master.xy[mn0], self.master.xy[mn1], self.master.xy[mn0[0], mn1[1]], self.master.xy[mn1[0], mn0[1]], ] # 4 physical points near the center coordinate. dist_list = [] idx = 0 for point in point_list: dist_list.append( [idx, np.linalg.norm(point - obj.center)] ) # Calculate Euclidean distances. idx += 1 dist_sorted = sorted( dist_list, key=lambda distance: distance[1] ) # Sort distances in ascending order. return self.master.mn( point_list[dist_sorted[0][0]] ) # Convert the closest point to abstract coordinate and then return. def left(self, obj): """ Convert an object's physical left-center coordinate into abstract coordinate of the master grid. """ return np.array([self.bottom_left(obj)[0], self.center(obj)[1]]) def right(self, obj): """ Convert an object's physical right-center coordinate into abstract coordinate of the master grid. """ return np.array([self.bottom_right(obj)[0], self.center(obj)[1]]) def top(self, obj): """ Convert an object's physical upper-center coordinate into abstract coordinate of the master grid. """ return np.array([self.center(obj)[0], self.top_left(obj)[1]]) def bottom(self, obj): """ Convert an object's physical lower-center coordinate into abstract coordinate of the master grid. """ return np.array([self.center(obj)[0], self.bottom_left(obj)[1]])
[docs] class OneDimGrid(CircularMapping): """ Class implementing one-dimensional abstract coordinates. """ # Member variables and properties name = None """str: Coordinate system name. Example ------- >>> from laygo2.object.grid import OneDimGrid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 50]) >>> g1_x.name "xgrid" .. image:: ../assets/img/object_grid_OneDimGrid_name.png :height: 250 """ range = None """str: Region in which the coordinate system is defined Coordinates in the defined region are repeatedly expanded. Example ------- >>> from laygo2.object.grid import OneDimGrid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 50]) >>> g1_x.range [0, 180] .. image:: ../assets/img/object_grid_OneDimGrid_range.png :height: 250 """ phy2abs = None """self.phy2abs (laygo2._PhyToAbsGridConverter): Object that converts physical coordinates into abstract coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 50]) >>> g1_x.phy2abs <_PhyToAbsGridConverter object> """ abs2phy = None """self.abs2phy (laygo2._AbsToPhyGridConverter): Object that converts abstract coordinates into physical coordinates. Example ------- >>> from laygo2.object.grid import OneDimGrid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 50]) >>> g1_x.abs2phy <_AbsToPhyGridConverter object> """ @property def width(self): """int: The size of the region in which the coordinate system is defined. Example ------- >>> from laygo2.object.grid import OneDimGrid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 50]) >>> g1_x.width 180 .. image:: ../assets/img/object_grid_OneDimGrid_width.png :height: 250 """ return abs(self.range[1] - self.range[0]) # Constructor
[docs] def __init__(self, name, scope, elements=np.array([0])): """ Constructor function of OneDimGrid class. Parameters ---------- name : str scope : numpy.ndarray scope of one-dimensional coordinate system elements: numpy.ndarray members of one-dimensional coordinate system Returns ------- laygo2.OneDimGrid Example ------- >>> from laygo2.object.grid import OneDimGrid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 50]) >>> print(g1_x) OneDimGrid object name: xgrid, class: OneDimGrid, scope: [0, 180], elements: [0, 35, 85, 130 50] .. image:: ../assets/img/object_grid_OneDimGrid_init.png :height: 250 """ self.name = name self.range = np.asarray(scope) self.phy2abs = _PhyToAbsGridConverter(master=self) self.abs2phy = _AbsToPhyGridConverter(master=self) CircularMapping.__init__(self, elements=elements)
# self.elements = np.asarray(elements) # commented out because asarray does not woke well with Object arrays. # Indexing and slicing functions def __getitem__(self, pos): """Return the physical coordinate corresponding to the abstract coordinate pos.""" return self.abs2phy([pos]) # Comparison operators def __eq__(self, other): """Return the abstract grid coordinate that matches to other.""" return self.abs2phy.__eq__(other) def __lt__(self, other): """Return the abstract grid coordinate that is the largest but less than other.""" return self.abs2phy.__lt__(other) def __le__(self, other): """Return the index of the grid coordinate that is the largest but less than or equal to other.""" return self.abs2phy.__le__(other) def __gt__(self, other): """Return the abstract grid coordinate that is the smallest but greater than other.""" return self.abs2phy.__gt__(other) def __ge__(self, other): """Return the index of the grid coordinate that is the smallest but greater than or equal to other.""" return self.abs2phy.__ge__(other) # Informative functions def __str__(self): """Return the string representation of the object.""" return self.summarize()
[docs] def summarize(self): """Return the summary of the object information.""" return ( self.__repr__() + " " "name: " + self.name + ", " + "class: " + self.__class__.__name__ + ", " + "scope: " + str(self.range) + ", " + "elements: " + str(self.elements) )
# I/O functions
[docs] def export_to_dict(self): """ Return dict object containing grid information. Parameters ---------- None Returns ------- dict Example ------- >>> from laygo2.object.grid import OneDimGrid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 180], elements=[0, 35, 85, 130, 50]) >>> g1_x.export_to_dict() {'scope': [0, 180], 'elements': [0, 35, 85, 130, 50]} .. image:: ../assets/img/object_grid_OneDimGrid_export_to_dict.png :height: 250 """ export_dict = { "scope": self.range.tolist(), "elements": self.elements.tolist(), } return export_dict
[docs] def flip(self): """Flip the elements of the object.""" # self.elements = self.range[1]*np.ones(self.elements.shape) - np.flip(self.elements) + self.range[0]*np.ones(self.elements.shape) self.elements = np.flip(self.elements) * (-1) + self.range[1] + self.range[0]
[docs] def copy(self): """Copy the object.""" return OneDimGrid(self.name, self.range.copy(), elements=self.elements.copy())
[docs] def concatenate(self, obj): objelem = obj.elements - obj.range[0] + self.range[1] self.elements = np.concatenate((self.elements, objelem)) self.range[1] += obj.range[1] - obj.range[0]
# for e in elements: # self.elements = np.concatenate((self.elements, obj.elements)) # self.range[1] += obj.range[1] - obj.range[0]
[docs] class Grid: """ A base class having conversion operators and the mapping information (element) between two-dimensional physical coordinates and abstract coordinates. Examplar grid conversions between abstract and physical coordinates are summarized in the following figure. .. image:: ../assets/img/user_guide_grid_conversion.png """ name = None """str: the name of the grid.""" _xy = None """List[OneDimGrid]: the list contains the 1d-grid objects for x and y axes."""
[docs] def _get_vgrid(self): return self._xy[0]
[docs] def _set_vgrid(self, value): self._xy[0] = value
vgrid = property(_get_vgrid, _set_vgrid)
[docs] def _get_hgrid(self): return self._xy[1]
[docs] def _set_hgrid(self, value): self._xy[1] = value
hgrid = property(_get_hgrid, _set_hgrid) @property def elements(self): """numpy.ndarray: Two-dimensional element of a coordinate system. x elements, y elements Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.elements [ array([0, 10, 20, 40, 50]), array( [10, 20, 40, 50, 60] ) ] .. image:: ../assets/img/object_grid_Grid_Elements.png :height: 250 """ return [self._xy[0].elements, self._xy[1].elements] phy2abs = None """PhyToAbsGridConverter(master=self)""" abs2phy = None """AbsToPhyGridConverter(master=self)""" @property def xy(self): """_AbsToPhyGridConverter: Two-dimensional _AbsToPhyConverter of a coordinate system. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.xy[10,10] [200 210] >>> g2.xy([10, 10]) [200 210] >>> g2.xy < [10,10] [0,-1] >>> g2.xy <= [10,10] [1,0] >>> g2.xy > [10,10] [2,1] >>> g2.xy >= [10,10] [1,0] """ return self.abs2phy @property def x(self): """_AbsToPhyGridConverter: One-dimensional _AbsToPhyGridConverter of the x-coordinate system. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.x[10] 200 >>> g2.x < 10 [0] >>> g2.x <= 10 [1] >>> g2.x > 10 [2] >>> g2.x >= 10 [1] """ return self._xy[0].abs2phy @property def y(self): """_AbsToPhyGridConverter: One-dimensional _AbsToPhyGridConverter of the y-coordinate system. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.y[10] 210 >>> g2.y < 10 [-1] >>> g2.y <= 10 [0] >>> g2.y > 10 [1] >>> g2.y >= 10 [0] """ return self._xy[1].abs2phy @property def v(self): """OneDimGrid: OneDimGrid of the x-coordinate system (=self.x). Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.v g1_x """ return self.x @property def h(self): """OneDimGrid: OneDimGrid of the y-coordinate system (=self.y). Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.h g1_y """ return self.y @property def mn(self): """laygo2._PhyToAbsGridConverter: Two-dimensional _PhyToAbsConverter of a coordinate system. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.mn[40,40] [3, 2] >>> g2.mn([40, 40]) [3, 2] >>> g2.mn < [40,40] [750, 760] >>> g2.mn <= [40,40] [800, 810] >>> g2.mn > [40,40] [810, 820] >>> g2.mn >= [40,40] [800, 810] """ return self.phy2abs @property def m(self): """_PhyToAbsGridConverter: One-dimensional _PhyToAbsConverter of the x-coordinate system. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.n[40] 2 >>> g2.n(40) 2 >>> g2.n < 40 760 >>> g2.n <= 40 810 >>> g2.n > 40 820 >>> g2.n >= 40 810 """ return self._xy[0].phy2abs @property def n(self): """_PhyToAbsGridConverter: One-dimensional _PhyToAbsConverter of the y-coordinate system. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.n[40] 2 >>> g2.n(40) 2 >>> g2.n < 40 760 >>> g2.n <= 40 810 >>> g2.n > 40 820 >>> g2.n >= 40 810 """ return self._xy[1].phy2abs @property def shape(self): """numpy.ndarray: Two-dimensional element length in a coordinate system. length of x-axis elements, length of y-axis elements Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.shape [5, 5] """ return np.hstack([self._xy[0].shape, self._xy[1].shape])
[docs] def get_range(self): return np.transpose(np.vstack((self._xy[0].range, self._xy[1].range)))
[docs] def set_range(self, value): self._xy[0].range = np.transpose(value)[0] self._xy[1].range = np.transpose(value)[1]
range = property(get_range, set_range) """numpy.ndarray: Region in which the coordinate system is defined. bbox of the respective Grid Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.range [ [0, 0], [100, 100 ]] """ @property def width(self): """numpy.int32: Width of the region in which the coordinate system is defined. x scope Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.width 100 """ return self._xy[0].width @property def height(self): """numpy.int32: Height of the region in which the coordinate system is defined. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.height 100 """ return self._xy[1].width @property def height_vec(self): """numpy.ndarray: Return the height vector [0, h]. Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.height_vec [0, 100] """ return np.array([0, self.height]) @property def width_vec(self): """numpy.ndarray: Return width as a list. length of the respective axis and zero Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.width_vec [100, 0] """ return np.array([self.width, 0])
[docs] def __init__(self, name, vgrid, hgrid): """ Constructor function of Grid class. Parameters ---------- name : str vgrid : laygo2.object.grid.OndDimGrid OneDimGrid object of the x-coordinate system hgrid : laygo2.object.grid.OndDimGrid OneDimGrid object of the y-coordinate system Returns ------- laygo2.object.grid.Grid Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> print(g2) <laygo2.object.grid.Grid object> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60]) """ self.name = name self._xy = [vgrid, hgrid] self.phy2abs = _PhyToAbsGridConverter(master=self) self.abs2phy = _AbsToPhyGridConverter(master=self)
@property def elements(self): """list: return elements of subgrids ([_xy[0].elements, _xy[1].elements]). """ return [self._xy[0].elements, self._xy[1].elements] # Indexing and slicing functions def __call__(self, other): return self.mn(other) def __getitem__(self, pos): return self.abs2phy.__getitem__(pos) # Comparison operators def __eq__(self, other): """Return the physical grid coordinate that matches to other.""" return self.abs2phy.__eq__(other) def __lt__(self, other): """Return the index of the grid coordinate that is the largest but less than other. """ return self.abs2phy.__lt__(other) def __le__(self, other): """Return the index of the grid coordinate that is the largest but less than or equal to other. """ return self.abs2phy.__le__(other) def __gt__(self, other): """Return the index of the grid coordinate that is the smallest but greater than other. """ return self.abs2phy.__gt__(other) def __ge__(self, other): """Return the index of the grid coordinate that is the smallest but greater than or equal to other. """ return self.abs2phy.__ge__(other)
[docs] def bbox(self, obj): """ Return the abstract grid coordinates corresponding to the 'internal' bounding box of obj. See Also -------- _PhyToAbsGridConverter.bbox """ return self.phy2abs.bbox(obj)
[docs] def bottom_left(self, obj): """ Return the abstract grid coordinates corresponding to the bottom-left corner of obj. See Also -------- _PhyToAbsGridConverter.bottom_left """ return self.phy2abs.bottom_left(obj)
[docs] def bottom_right(self, obj): """ Return the abstract grid coordinates corresponding to the bottom-right corner of obj. See Also -------- _PhyToAbsGridConverter.bottom_right """ return self.phy2abs.bottom_right(obj)
[docs] def top_left(self, obj): """ Return the abstract grid coordinates corresponding to the top-left corner of obj. See Also -------- _PhyToAbsGridConverter.top_left """ return self.phy2abs.top_left(obj)
[docs] def top_right(self, obj): """ Return the abstract grid coordinates corresponding to the top-right corner of obj. See Also -------- _PhyToAbsGridConverter.top_right """ return self.phy2abs.top_right(obj)
[docs] def crossing(self, *args): """ Return the abstract grid coordinates corresponding to the crossing point of args. See Also -------- laygo2.object.grid._PhyToAbsGridConverter.crossing """ return self.phy2abs.crossing(*args)
[docs] def overlap(self, *args, type="bbox"): """ Return the abstract grid coordinates corresponding to the overlap of args. See Also -------- laygo2.object.grid._PhyToAbsGridConverter.overlap """ return self.phy2abs.overlap(*args, type=type)
[docs] def union(self, *args): """ Return the abstract grid coordinates corresponding to union of args. See Also -------- laygo2.object.grid._PhyToAbsGridConverter.union """ return self.phy2abs.union(*args)
[docs] def center(self, obj): """ Return the abstract grid coordinates corresponding to the center point of obj. Parameters ---------- obj : laygo2.object.physical.PhysicalObject The object of which center coordinate is computed. See Also -------- laygo2.object.grid._PhyToAbsGridConverter.center """ return self.phy2abs.center(obj)
[docs] def left(self, obj): """ Return the abstract grid coordinates corresponding to the left point of obj. """ return self.phy2abs.left(obj)
[docs] def right(self, obj): """ Return the abstract grid coordinates corresponding to the right point of obj. """ return self.phy2abs.right(obj)
[docs] def top(self, obj): """ Return the abstract grid coordinates corresponding to the top point of obj. """ return self.phy2abs.top(obj)
[docs] def bottom(self, obj): """ Return the abstract grid coordinates corresponding to the bottom point of obj. """ return self.phy2abs.bottom(obj)
[docs] def copy(self): """ Make a copy of the current Grid object Returns ------- laygo2.object.grid.Grid : the copied Grid object. See Also -------- laygo2.object.grid.copy """ name = self.name vgrid = self.vgrid.copy() hgrid = self.hgrid.copy() g = Grid( name=name, vgrid=vgrid, hgrid=hgrid, ) return g
[docs] def vflip(self, copy=True): """Flip the grid in vertical direction. Parameters ---------- copy: optional, boolean If True, make a copy and flip the copied grid (default). If False, flip the current grid object. Returns -------- laygo2.object.grid.Grid : the flipped Grid object. See Also -------- laygo2.object.grid.vflip """ if copy: g = self.copy() else: g = self g.hgrid.flip() # Flip vertically means filpping the horizontal grid. return g
[docs] def hflip(self, copy=True): """Flip the grid in horizontal direction. Parameters ---------- copy: optional, boolean If True, make a copy and flip the copied grid (default). If False, flip the current grid object. Returns -------- laygo2.object.grid.Grid : the flipped Grid object. See Also -------- laygo2.object.grid.hflip """ if copy: g = self.copy() else: g = self g.vgrid.flip() return g
[docs] def vstack(self, obj, copy=True): """Stack grid(s) on top of the current grid in vertical direction. """ if copy: g = self.copy() else: g = self if isinstance(obj, list): # multiple stack obj_list = obj else: # single stack obj_list = [obj] # compute the grid range first grid_ofst = g.hgrid.width for _obj in obj_list: g.hgrid.range[1] += _obj.hgrid.width # stack for _obj in obj_list: for i, h in enumerate(_obj.hgrid): # Check if the new grid element exist in the current grid already. val = (h - _obj.hgrid.range[0]) + grid_ofst val = val % (g.hgrid.width) # modulo if not (val in g.hgrid): # Unique element g.hgrid.append(val + g.hgrid.range[0]) grid_ofst += _obj.hgrid.width # increse offset return g
[docs] def hstack(self, obj, copy=True): """Stack grid(s) on top of the current grid in horizontal direction.""" if copy: g = self.copy() else: g = self if isinstance(obj, list): # Multiple stack. for o in obj: g = g.hstack(o, copy=copy) return g for i, v in enumerate(obj.vgrid): # Check if the new grid element exist in the current grid already. val = (v - obj.vgrid.range[0]) + g.vgrid.width val = val % (g.vgrid.width + obj.vgrid.width) # modulo if not (val in g.vgrid): # Unique element g.vgrid.append(v + g.vgrid.range[1]) g.vgrid.range[1] += obj.vgrid.width return g
# Iterators def __iter__(self): # TODO: fix this to iterate over the full coordinates return np.array([self._xy[0].__iter__(), self._xy[1].__iter__()]) def __next__(self): # TODO: fix this to iterate over the full coordinates return np.array([self._xy[0].__next__(), self._xy[1].__next__()]) # Informative functions def __str__(self): """Return the string representation of the object.""" return self.summarize()
[docs] def summarize(self): """ Output the information of the respective grid. Parameters ---------- None Returns ------- str Example ------- >>> from laygo2.object.grid import OneDimGrid, Grid >>> g1_x = OneDimGrid(name='xgrid', scope=[0, 100], elements=[0, 10, 20, 40, 50 ]) >>> g1_y = OneDimGrid(name='ygrid', scope=[0, 100], elements=[10, 20, 40, 50, 60 ]) >>> g2 = Grid(name="test", vgrid = g1_x, hgrid = g1_y ) >>> g2.summarize() <laygo2.object.grid.Grid object> name: test, class: Grid, scope: [[0, 0], [100, 100]], elements: [array([ 0, 10, 20, 40, 50]), array([10, 20, 40, 50, 60]) """ return ( self.__repr__() + " \n" + " name: " + self.name + ", \n" + " class: " + self.__class__.__name__ + ", \n" + " scope: " + str(self.range.tolist()) + ", \n" + " elements: " + str(self.elements) + ", \n" )
''' class ParameterizedGrid(Grid): """A parameterized grid to support flexible templates.""" # TODO: implement this. pass class ParameterizedPlacementGrid(Grid): # TODO: implement this. pass class ParameterizedRoutingGrid(Grid): # TODO: implement this. pass '''