Example #1
0
class RoutePointShape(__RouteSimple__):

    width = NumberParameter(default=100)
    angles = Parameter(fdef_name='create_angles')

    def get_path(self):
        try:
            return self.__path__
        except:
            raise ValueError('Path not set for {}'.format(
                self.__class__.__name__))

    def set_path(self, value):
        self.__path__ = np.asarray(value)

    path = FunctionParameter(get_path, set_path)

    def create_midpoint1(self):
        return self.path[0]

    def create_midpoint2(self):
        return self.path[-1]

    def create_width1(self):
        return self.width

    def create_width2(self):
        return self.width

    def create_orientation1(self):
        # return self.angles[0]*180/pi+90
        return self.angles[0] * 180 / pi + 90

    def create_orientation2(self):
        # return self.angles[-1]*180/pi-90
        return self.angles[-1] * 180 / pi - 90

    def create_angles(self):
        dxdy = self.path[1:] - self.path[:-1]
        angles = (np.arctan2(dxdy[:, 1], dxdy[:, 0])).tolist()
        angles = np.array([angles[0]] + angles + [angles[-1]])
        return angles

    def create_points(self, points):
        diff_angles = (self.angles[1:] - self.angles[:-1])
        mean_angles = (self.angles[1:] + self.angles[:-1]) / 2
        dx = self.width / 2 * np.cos((mean_angles - pi / 2)) / np.cos(
            (diff_angles / 2))
        dy = self.width / 2 * np.sin((mean_angles - pi / 2)) / np.cos(
            (diff_angles / 2))
        left_points = self.path.T - np.array([dx, dy])
        right_points = self.path.T + np.array([dx, dy])
        points = np.concatenate([left_points.T, right_points.T[::-1]])
        return points
Example #2
0
class __Element__(Transformable, ParameterInitializer, metaclass=MetaElement):
    """ Base class for all transformable elements. """
    def get_node_id(self):
        if self.__id__:
            return self.__id__
        else:
            return self.__str__()

    def set_node_id(self, value):
        self.__id__ = value

    node_id = FunctionParameter(get_node_id, set_node_id)

    location_name = StringParameter(default='')

    def __init__(self, transformation=None, **kwargs):
        super().__init__(transformation=transformation, **kwargs)

    def __add__(self, other):
        if isinstance(other, list):
            l = spira.ElementList([self])
            l.extend(other)
            return l
        elif isinstance(other, __Element__):
            return spira.ElementList([self, other])
        else:
            raise TypeError(
                "Wrong type of argument for addition in __Element__: " +
                str(type(other)))

    def __radd__(self, other):
        if isinstance(other, list):
            l = spira.ElementList(other)
            l.append(self)
            return l
        elif isinstance(other, __Element__):
            return spira.ElementList([other, self])
        else:
            raise TypeError(
                "Wrong type of argument for addition in __Element__: " +
                str(type(other)))

    def flatten(self):
        return [self]

    def dependencies(self):
        return None
Example #3
0
class Polygon(__ShapeElement__):
    """
    Element that connects shapes to the GDSII file format.
    Polygon are objects that represents the shapes in a layout.

    Examples
    --------
    >>> layer = spira.Layer(number=99)
    >>> rect_shape = spira.RectangleShape(p1=[0,0], p2=[1,1])
    >>> ply = spira.Polygon(shape=rect_shape, layer=layer)
    """

    _next_uid = 0

    def get_alias(self):
        if not hasattr(self, '__alias__'):
            self.__alias__ = self.layer.process.symbol
        return self.__alias__

    def set_alias(self, value):
        if isinstance(value, str):
            self.__alias__ = value
        elif isinstance(value, list):
            self.__alias__ = ':'.join(value)

    alias = FunctionParameter(
        get_alias,
        set_alias,
        doc='Functions to generate an alias for cell name.')

    def __init__(self, shape, layer, transformation=None, **kwargs):
        super().__init__(shape=shape,
                         layer=layer,
                         transformation=transformation,
                         **kwargs)

        self.uid = Polygon._next_uid
        Polygon._next_uid += 1

    def __repr__(self):
        if self is None:
            return 'Polygon is None!'
        layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer]
        class_string = "[SPiRA: Polygon \'{}\'] (center {}, vertices {}, process {}, purpose {})"
        return class_string.format(self.alias, self.center, self.count,
                                   self.layer.process.symbol,
                                   self.layer.purpose.symbol)

    def __str__(self):
        return self.__repr__()

    def __hash__(self):
        return hash(self.__repr__())

    # NOTE: We are not copying the ports, so they
    # can be re-calculated for the transformed shape.
    # Specifically after a expand_flat_copy.
    # FIXME: But this causes problems with the netlist extraction.
    def __deepcopy__(self, memo):
        # ports = self.ports.transform_copy(self.transformation)
        # return self.__class__(
        return Polygon(
            alias=self.alias,
            shape=deepcopy(self.shape),
            layer=deepcopy(self.layer),
            ports=deepcopy(self.ports),
            # FIXME: Deepcopy removes the Stretching from Compound Transform.
            # transformation=deepcopy(self.transformation)
            transformation=self.transformation)

    def short_string(self):
        # return "Polygon [{}, {}, {}]".format(self.center, self.layer.process.symbol, self.layer.purpose.symbol)
        # NOTE: We want to ignore the purpose for CIRCUIT_METAL ot DEVICE_METAL net connections.
        layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer]
        return "Polygon [{}, {}]".format(self.center, layer.process.symbol)

    def flat_copy(self, level=-1):
        """ Flatten a copy of the polygon. """
        S = Polygon(shape=self.shape,
                    layer=self.layer,
                    transformation=self.transformation)
        S.expand_transform()
        return S

    def flat_container(self, cc, name_tree=[]):
        """ Flatten the polygon without creating a copy. """
        self.alias = name_tree
        self.alias += ':' + self.layer.process.symbol
        cc += self
        # cc.ports += self.edge_ports
        return cc

    def nets(self, lcar):
        from spira.yevon.geometry.nets.net import Net
        from spira.yevon.vmodel.geometry import GmshGeometry
        from spira.yevon import filters

        if self.layer.purpose.symbol in [
                'METAL', 'DEVICE_METAL', 'CIRCUIT_METAL'
        ]:

            if RDD.ENGINE.GEOMETRY == 'GMSH_ENGINE':
                geometry = GmshGeometry(
                    lcar=0.5,
                    # geometry = GmshGeometry(lcar=lcar,
                    process=self.layer.process,
                    process_polygons=[deepcopy(self)])

            cc = []

            for p in self.ports:
                # if p.purpose == RDD.PURPOSE.PORT.PIN:
                if p.purpose == RDD.PURPOSE.PORT.TERMINAL:
                    c_port = p.transform_copy(self.transformation)
                    cc.append(c_port)
                    # cc.append(p)
                elif p.purpose == RDD.PURPOSE.PORT.CONTACT:
                    c_port = p.transform_copy(self.transformation)
                    cc.append(c_port)
                    # cc.append(p)

            F = filters.ToggledCompositeFilter(filters=[])
            F += filters.NetProcessLabelFilter(
                process_polygons=[deepcopy(self)])
            F += filters.NetEdgeFilter(process_polygons=deepcopy(self))
            F += filters.NetDeviceLabelFilter(device_ports=cc)
            F += filters.NetBranchFilter()

            # if self.layer.purpose.symbol == 'CIRCUIT_METAL':
            #     F += filters.NetDummyFilter()

            net = Net(name=self.layer.process.symbol, geometry=geometry)

            net = F(net)[0]

            return net

        return []
Example #4
0
class GmshGeometry(__Geometry__):
    """ Generate a geometry using the Gmsh library. """

    _ID = 0

    _uid = 0

    lcar = NumberParameter(default=100, doc='Mesh characteristic length.')
    algorithm = IntegerParameter(default=1, doc='Mesh algorithm used by Gmsh.')
    scale_Factor = NumberParameter(default=1e-6,
                                   doc='Mesh coord dimention scaling.')
    coherence_mesh = BoolParameter(defualt=True, doc='Merge similar points.')

    process = ProcessParameter()
    process_polygons = ElementListParameter()

    mesh_data = Parameter(fdef_name='create_mesh_data')

    def get_filename(self):
        if not hasattr(self, '__alias__'):
            self.__alias__ = '{}_{}'.format(self.process.symbol,
                                            GmshGeometry._uid)
            GmshGeometry._uid += 1
        return self.__alias__

    def set_filename(self, value):
        if value is not None:
            self.__alias__ = value

    filename = FunctionParameter(
        get_filename,
        set_filename,
        doc='Functions to generate an alias for cell name.')

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.geom = pygmsh.opencascade.Geometry(
            characteristic_length_min=self.lcar,
            characteristic_length_max=self.lcar)
        self.geom.add_raw_code('Mesh.Algorithm = {};'.format(self.algorithm))
        self.geom.add_raw_code('Mesh.ScalingFactor = {};'.format(
            self.scale_Factor))
        if self.coherence_mesh is True:
            self.geom.add_raw_code('Coherence Mesh;')

    def __physical_surfaces__(self):
        """ Creates physical surfaces that is compatible
        with the GMSH library for mesh generation. """
        import re

        surfaces = []
        for i, ply in enumerate(self.process_polygons):
            shape = ply.shape.transform_copy(ply.transformation)
            layer = RDD.GDSII.EXPORT_LAYER_MAP[ply.layer]
            pts = [[p[0], p[1], 0] for p in shape.points]
            surface_label = '{}_{}_{}_{}'.format(layer.number, layer.datatype,
                                                 GmshGeometry._ID, i)
            gp = self.geom.add_polygon(pts,
                                       lcar=self.lcar,
                                       make_surface=True,
                                       holes=None)

            for j, ll in enumerate(gp.lines):
                pid = ply.shape.segment_labels[j].split(' - hash ')
                if len(pid) > 1:
                    line_label = "{}*{}*{}".format(pid[0], pid[1], j)
                else:
                    line_label = "{}*{}*{}".format(pid[0], None, j)
                self.geom.add_physical(ll, label=line_label)
            self.geom.add_physical(gp.surface, label=surface_label)
            # surfaces.append([gp.surface, gp.line_loop])

            surfaces.append(gp)
            GmshGeometry._ID += 1
        return surfaces

    def create_mesh_data(self):
        """ Generates the mesh data from the created physical surfaces. """

        # if len(self.physical_surfaces) > 1:
        #     self.geom.boolean_union(self.physical_surfaces)

        self.__physical_surfaces__()

        directory = os.getcwd() + '/debug/gmsh/'

        mesh_file = '{}{}.msh'.format(directory, self.filename)
        geo_file = '{}{}.geo'.format(directory, self.filename)
        vtk_file = '{}{}.vtu'.format(directory, self.filename)

        if not os.path.exists(directory):
            os.makedirs(directory)

        mesh_data = pygmsh.generate_mesh(self.geom,
                                         verbose=False,
                                         dim=2,
                                         prune_vertices=False,
                                         remove_faces=False,
                                         geo_filename=geo_file)

        # meshio.write(mesh_file, mesh_data)
        # meshio.write(vtk_file, mesh_data)

        return mesh_data
Example #5
0
class Vector(Transformable, ParameterInitializer):
    """ Vector consisting of a point and an orientation. """

    midpoint = CoordParameter(default=(0.0, 0.0), doc='Position of the point.')

    @property
    def x(self):
        return self.midpoint.x

    @property
    def y(self):
        return self.midpoint.y

    def get_angle_rad(self):
        if hasattr(self, '__angle__'):
            return constants.DEG2RAD * self.__angle__
        else:
            return 0.0

    def set_angle_rad(self, value):
        self.__angle__ = (constants.RAD2DEG * value) % 360.0

    angle_rad = FunctionParameter(
        get_angle_rad,
        set_angle_rad,
        doc=
        "The outward facing orientation of the port in radians (stored in degrees by default, converted to radians if needed)"
    )

    def get_angle_deg(self):
        if hasattr(self, '__angle__'):
            return self.__angle__
        else:
            return 0.0

    def set_angle_deg(self, value):
        self.__angle__ = value % 360.0

    orientation = FunctionParameter(
        get_angle_deg,
        set_angle_deg,
        doc="The outward facing orientation of the port.")

    def cos(self):
        return cos(constants.DEG2RAD * self.__angle__)

    def sin(self):
        return sin(constants.DEG2RAD * self.__angle__)

    def tan(self):
        return tan(constants.DEG2RAD * self.__angle__)

    def flip(self):
        return Vector(midpoint=self.midpoint,
                      orientation=(self.__angle__ + 180.0) % 360.0)

    def __getitem__(self, key):
        if key == 0:
            return self.midpoint[0]
        if key == 1:
            return self.midpoint[1]
        else:
            raise IndexError(
                "Vector supports only subscription[0] and [1], not " +
                str(key))

    def __eq__(self, other):
        if not isinstance(other, Vector):
            return False
        return self.midpoint == other.midpoint and (self.angle_deg
                                                    == other.angle_deg)

    def __ne__(self, other):
        return self.midpoint != other.midpoint or (self.angle_deg !=
                                                   other.angle_deg)

    def __repr__(self):
        return "<Vector (%f, %f), a=%f>" % (self.x, self.y, self.angle_deg)

    def transform(self, transformation):
        self.midpoint = transformation.apply_to_coord(self.midpoint)
        self.angle_deg = transformation.apply_to_angle(self.angle_deg)
        return self
Example #6
0
class __Manhattan__(Cell):

    port1 = PortParameter(default=None)
    port2 = PortParameter(default=None)

    length = NumberParameter(default=20)
    layer = LayerParameter(number=13)
    # gds_layer = LayerParameter(number=13)
    # layer = PhysicalLayerParameter(default=RDD.DEF.PDEFAULT)
    # layer = PhysicalLayerParameter()
    # bend_type = StringParameter(default='rectangle')
    bend_type = StringParameter(default='circular')

    b1 = Parameter(fdef_name='create_arc_bend_1')
    b2 = Parameter(fdef_name='create_arc_bend_2')

    p1 = Parameter(fdef_name='create_port1_position')
    p2 = Parameter(fdef_name='create_port2_position')

    quadrant_one = Parameter(fdef_name='create_quadrant_one')
    quadrant_two = Parameter(fdef_name='create_quadrant_two')
    quadrant_three = Parameter(fdef_name='create_quadrant_three')
    quadrant_four = Parameter(fdef_name='create_quadrant_four')

    def get_radius(self):
        if self.port1 and self.port2:
            if hasattr(self, '__radius__'):
                return self.__radius__
            else:
                dx = abs(self.p2[0] - self.p1[0])
                dy = abs(self.p2[1] - self.p1[1])
                if dx <= dy:
                    self.__radius__ = dx * 0.2
                elif dy <= dx:
                    self.__radius__ = dy * 0.2
                # if dx <= dy:
                #     self.__radius__ = dx
                # elif dy <= dx:
                #     self.__radius__ = dy
                return self.__radius__

    def set_radius(self, value):
        self.__radius__ = value

    radius = FunctionParameter(get_radius, set_radius)

    def route_straight(self, p1, p2):
        route_shape = RouteSimple(port1=p1,
                                  port2=p2,
                                  path_type='straight',
                                  width_type='straight')
        # route_shape.apply_merge
        R1 = RouteGeneral(route_shape=route_shape, connect_layer=self.layer)
        S = spira.SRef(R1)
        S.connect(port=S.ports['P1'], destination=p1)
        # S.connect(port=p1, destination=p2)

        # T = spira.Rotation(rotation=p2.orientation, center=p1.midpoint)
        # S.transform(T)
        # r1.rotate(angle=p2.orientation+90, center=R1.port_input.midpoint)
        # r1._rotate(rotation=p2.orientation-90, center=R1.port_input.midpoint)
        # S.move(midpoint=(0,0), destination=p1.midpoint)
        return S

    def create_port1_position(self):
        angle = np.mod(self.port1.orientation, 360)
        if angle == 90:
            p1 = [self.port1.midpoint[0], self.port1.midpoint[1]]
        if angle == 180:
            p1 = [self.port1.midpoint[1], -self.port1.midpoint[0]]
        if angle == 270:
            p1 = [-self.port1.midpoint[0], -self.port1.midpoint[1]]
        if angle == 0:
            p1 = [-self.port1.midpoint[1], self.port1.midpoint[0]]
        return p1

    def create_port2_position(self):
        angle = np.mod(self.port1.orientation, 360)
        if angle == 90:
            p2 = [self.port2.midpoint[0], self.port2.midpoint[1]]
        if angle == 180:
            p2 = [self.port2.midpoint[1], -self.port2.midpoint[0]]
        if angle == 270:
            p2 = [-self.port2.midpoint[0], -self.port2.midpoint[1]]
        if angle == 0:
            p2 = [-self.port2.midpoint[1], self.port2.midpoint[0]]
        return p2

    def create_arc_bend_1(self):
        if self.bend_type == 'circular':
            rs = RouteArcShape(radius=self.radius,
                               width=self.port1.width,
                               start_angle=0,
                               theta=90)
        if self.bend_type == 'rectangle':
            rs = RouteSquareShape(width=self.port1.width,
                                  size=(self.radius, self.radius))
        B1 = RouteGeneral(route_shape=rs, connect_layer=self.layer)
        return spira.SRef(B1)

    def create_arc_bend_2(self):
        if self.bend_type == 'circular':
            rs = RouteArcShape(radius=self.radius,
                               width=self.port1.width,
                               start_angle=0,
                               theta=-90)
        if self.bend_type == 'rectangle':
            rs = RouteSquareShape(width=self.port1.width,
                                  size=(self.radius, self.radius))
        B1 = RouteGeneral(route_shape=rs, connect_layer=self.layer)
        return spira.SRef(B1)
Example #7
0
class Polygon(__Polygon__):
    """
    Element that connects shapes to the GDSII file format.
    Polygon are objects that represents the shapes in a layout.

    Examples
    --------
    >>> layer = spira.Layer(number=99)
    >>> rect_shape = spira.RectangleShape(p1=[0,0], p2=[1,1])
    >>> ply = spira.Polygon(shape=rect_shape, layer=layer)
    """

    _next_uid = 0

    edges = Parameter(fdef_name='create_edges')

    def get_alias(self):
        if not hasattr(self, '__alias__'):
            self.__alias__ = self.process
        return self.__alias__

    def set_alias(self, value):
        if value is not None:
            self.__alias__ = value

    alias = FunctionParameter(
        get_alias,
        set_alias,
        doc='Functions to generate an alias for cell name.')

    def __init__(self, shape, layer, transformation=None, **kwargs):
        super().__init__(shape=shape,
                         layer=layer,
                         transformation=transformation,
                         **kwargs)

        self.uid = Polygon._next_uid
        Polygon._next_uid += 1

    def __repr__(self):
        if self is None:
            return 'Polygon is None!'
        layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer]
        class_string = "[SPiRA: Polygon \'{}\'] (center {}, vertices {}, process {}, purpose {})"
        return class_string.format(self.alias, self.center, self.count,
                                   self.process, self.purpose)

    def __str__(self):
        return self.__repr__()

    def __hash__(self):
        return hash(self.__repr__())

    # NOTE: We are not copying the ports, so they
    # can be re-calculated for the transformed shape.
    def __deepcopy__(self, memo):
        return self.__class__(shape=deepcopy(self.shape),
                              layer=deepcopy(self.layer),
                              transformation=deepcopy(self.transformation))

    def id_string(self):
        sid = '{} - hash {}'.format(self.__repr__(), self.shape.hash_string)
        return sid

    def create_edges(self):
        """ Generate default edges for this polygon.
        These edges can be transformed using adapters. """
        from spira.yevon.geometry.edges.edges import generate_edges
        return generate_edges(shape=self.shape, layer=self.layer)

    def flat_copy(self, level=-1):
        """ Flatten a copy of the polygon. """
        S = Polygon(shape=self.shape,
                    layer=self.layer,
                    transformation=self.transformation)
        S.expand_transform()
        return S

    def flatten(self, level=-1):
        """ Flatten the polygon without creating a copy. """
        return self.expand_transform()

    def nets(self, lcar):
        from spira.yevon.geometry.nets.net import Net
        from spira.yevon.vmodel.geometry import GmshGeometry
        from spira.yevon.geometry.ports.port import ContactPort
        from spira.yevon import filters

        if self.purpose == 'METAL':

            if RDD.ENGINE.GEOMETRY == 'GMSH_ENGINE':
                geometry = GmshGeometry(lcar=lcar,
                                        process=self.layer.process,
                                        process_polygons=[deepcopy(self)])

            cc = []
            for p in self.ports:
                if isinstance(p, ContactPort):
                    cc.append(p)

            F = filters.ToggledCompoundFilter()
            F += filters.NetProcessLabelFilter(
                process_polygons=[deepcopy(self)])
            F += filters.NetDeviceLabelFilter(device_ports=cc)
            F += filters.NetEdgeFilter(process_polygons=[deepcopy(self)])

            net = Net(name=self.process, geometry=geometry)

            return F(net)

            # # from spira.yevon.utils.netlist import combine_net_nodes
            # # net.g = combine_net_nodes(g=net.g, algorithm='d2d')
            # # net.g = combine_net_nodes(g=net.g, algorithm='s2s')

            # from spira.yevon.geometry.nets.net import CellNet
            # cn = CellNet()
            # cn.g = net.g
            # # cn.generate_branches()
            # # cn.detect_dummy_nodes()
            # return cn

        return []
Example #8
0
class Cell(__Cell__):
    """ A Cell encapsulates a set of elements that
    describes the layout being generated. """

    _next_uid = 0

    lcar = NumberParameter(default=100)
    name = Parameter(fdef_name='create_name', doc='Name of the cell instance.')

    def get_alias(self):
        if not hasattr(self, '__alias__'):
            self.__alias__ = self.name.split('__')[0]
        return self.__alias__

    def set_alias(self, value):
        self.__alias__ = value

    alias = FunctionParameter(
        get_alias,
        set_alias,
        doc='Functions to generate an alias for cell name.')

    def __init__(self,
                 name=None,
                 elements=None,
                 ports=None,
                 nets=None,
                 library=None,
                 **kwargs):
        __Cell__.__init__(self, **kwargs)

        if name is not None:
            s = '{}_{}'.format(name, Cell._next_uid)
            self.__dict__['__name__'] = s
            Cell.name.__set__(self, s)

        self.uid = Cell._next_uid
        Cell._next_uid += 1

        if library is not None:
            self.library = library
        if elements is not None:
            self.elements = ElementList(elements)
        if ports is not None:
            self.ports = PortList(ports)

    def __repr__(self):
        class_string = "[SPiRA: Cell(\'{}\')] (elements {}, ports {})"
        return class_string.format(self.name, self.elements.__len__(),
                                   self.ports.__len__())

    def __str__(self):
        return self.__repr__()

    def create_name(self):
        if not hasattr(self, '__name__'):
            self.__name__ = self.__name_generator__(self)
        return self.__name__

    def expand_transform(self):
        for S in self.elements.sref:
            S.expand_transform()
            S.reference.expand_transform()
        return self

    def expand_flat_copy(self, exclude_devices=False):
        from spira.yevon.gdsii.pcell import Device
        from spira.yevon.gdsii.polygon import Polygon
        from spira.yevon.gdsii.sref import SRef
        from spira.core.transforms.translation import Translation

        name = ''
        S = self.expand_transform()
        C = Cell(name=S.name + '_ExpandedCell')

        def _traverse_polygons(subj, cell, name):
            c_name = deepcopy(name)
            # print(cell)
            for e in cell.elements:
                if isinstance(e, SRef):
                    if e.alias is not None:
                        c_name += e.alias + ':'
                    else:
                        c_name += ':'
                    subj = _traverse_polygons(subj=subj,
                                              cell=e.reference,
                                              name=c_name)
                    # if exclude_devices is True:
                    #     if isinstance(e.reference, Device):
                    #         subj += e
                    #     else:
                    #         subj = _traverse_polygons(subj=subj, cell=e.reference, name=c_name)
                    # else:
                    #     subj = _traverse_polygons(subj=subj, cell=e.reference, name=c_name)
                elif isinstance(e, Polygon):
                    e.location_name = c_name
                    # e.transform(expand_transform)
                    # e.shape.move(expand_transform)
                    subj += e
                    # print(e.transformation)
                c_name = name
            # print('')
            return subj

        D = _traverse_polygons(C, S, name)

        return D

    def move(self, midpoint=(0, 0), destination=None, axis=None):
        """  """
        from spira.yevon.geometry.ports.base import __Port__

        if destination is None:
            destination = midpoint
            midpoint = [0, 0]

        if issubclass(type(midpoint), __Port__):
            o = midpoint.midpoint
        elif isinstance(midpoint, Coord):
            o = midpoint
        elif np.array(midpoint).size == 2:
            o = midpoint
        elif midpoint in obj.ports:
            o = obj.ports[midpoint].midpoint
        else:
            raise ValueError("[PHIDL] [DeviceReference.move()] ``midpoint`` " +
                             "not array-like, a port, or port name")

        if issubclass(type(destination), __Port__):
            d = destination.midpoint
        elif isinstance(destination, Coord):
            d = destination
        elif np.array(destination).size == 2:
            d = destination
        elif destination in obj.ports:
            d = obj.ports[destination].midpoint
        else:
            raise ValueError(
                "[PHIDL] [DeviceReference.move()] ``destination`` " +
                "not array-like, a port, or port name")

        if axis == 'x':
            d = (d[0], o[1])
        if axis == 'y':
            d = (o[0], d[1])

        d = Coord(d[0], d[1])
        o = Coord(o[0], o[1])

        for e in self.elements:
            e.move(midpoint=o, destination=d)

        for p in self.ports:
            mc = np.array(p.midpoint) + np.array(d) - np.array(o)
            p.move(midpoint=p.midpoint, destination=mc)

        return self

    def stretch_p2p(self, port, destination):
        """
        The element by moving the subject port, without
        distorting the entire element. Note: The opposite
        port position is used as the stretching center.
        """
        from spira.core.transforms import stretching
        from spira.yevon.geometry import bbox_info
        from spira.yevon.gdsii.polygon import Polygon
        opposite_port = bbox_info.bbox_info_opposite_boundary_port(self, port)
        T = stretching.stretch_element_by_port(self, opposite_port, port,
                                               destination)
        if port.bbox is True:
            self = T(self)
        else:
            for i, e in enumerate(self.elements):
                if isinstance(e, Polygon):
                    if e.id_string() == port.local_pid:
                        self.elements[i] = T(e)
        return self

    def nets(self, lcar):
        return self.elements.nets(lcar=lcar)

    def create_netlist(self):
        net = self.nets(lcar=self.lcar).disjoint()
        return net