Пример #1
0
    def add_bezier_to(self,
                      final_coordinates,
                      final_angle,
                      bend_strength,
                      width=None,
                      **kwargs):

        try:
            bs1, bs2 = float(bend_strength[0]), float(bend_strength[1])
        except (KeyError, TypeError):
            bs1 = bs2 = float(bend_strength)

        final_port = Port(final_coordinates, final_angle, self.width)
        p0 = (0, 0)
        p1 = self._current_port.longitudinal_offset(
            bs1).origin - self._current_port.origin
        p2 = final_port.longitudinal_offset(
            -bs2).origin - self._current_port.origin
        p3 = final_coordinates - self._current_port.origin

        tmp_wg = Waveguide.make_at_port(
            self._current_port.copy().set_port_properties(angle=0))
        tmp_wg.add_cubic_bezier_path(p0, p1, p2, p3, width=width, **kwargs)

        self._segments.append(
            (self._current_port.copy(), tmp_wg.get_shapely_object(),
             tmp_wg.get_shapely_outline(), tmp_wg.length,
             tmp_wg.center_coordinates))
        self._current_port = tmp_wg.current_port

        return self
Пример #2
0
    def marker_positions(self):
        center_port = Port(self._origin, self._angle, 1.)

        angle = max((2 + max(self._in_ports, self._out_ports)) / 2. *
                    self._angular_spacing, np.pi / 4)
        radius = max(self._radius, 40)
        return [
            center_port.rotated(angle).longitudinal_offset(radius).origin,
            center_port.rotated(angle).longitudinal_offset(-radius).origin,
            center_port.rotated(-angle).longitudinal_offset(radius).origin,
            center_port.rotated(-angle).longitudinal_offset(-radius).origin
        ]
Пример #3
0
def _example():
    from gdshelpers.geometry import convert_to_gdscad
    import gdsCAD

    cell = gdsCAD.core.Cell('test')

    wg1 = Waveguide.make_at_port(Port((0, 0), 0, 1.))
    wg1.add_straight_segment(100)
    ring1 = RingResonator.make_at_port(wg1.current_port,
                                       1.,
                                       50.,
                                       race_length=30,
                                       straight_feeding=True,
                                       draw_opposite_side_wg=True)

    wg2 = Waveguide.make_at_port(ring1.port)
    wg2.add_straight_segment(100)
    ring2 = RingResonator.make_at_port(wg2.current_port,
                                       1.,
                                       50.,
                                       vertical_race_length=30,
                                       straight_feeding=True,
                                       draw_opposite_side_wg=True)

    wg3 = Waveguide.make_at_port(ring2.port)
    wg3.add_straight_segment(100)
    ring3 = RingResonator.make_at_port(wg3.current_port,
                                       -1.,
                                       50.,
                                       vertical_race_length=30,
                                       straight_feeding=True,
                                       draw_opposite_side_wg=True)

    cell.add(convert_to_gdscad([wg1, ring1, wg2, ring2, wg3, ring3], layer=1))
    cell.show()
Пример #4
0
def _example():
    from gdshelpers.geometry.chip import Cell

    cell = Cell('test')

    wg1 = Waveguide.make_at_port(Port((0, 0), 0, 1.))
    wg1.add_straight_segment(100)
    ring1 = RingResonator.make_at_port(wg1.current_port,
                                       1.,
                                       50.,
                                       race_length=30,
                                       straight_feeding=True,
                                       draw_opposite_side_wg=True)

    wg2 = Waveguide.make_at_port(ring1.port)
    wg2.add_straight_segment(100)
    ring2 = RingResonator.make_at_port(wg2.current_port,
                                       1.,
                                       50.,
                                       vertical_race_length=30,
                                       straight_feeding=True,
                                       draw_opposite_side_wg=True)

    wg3 = Waveguide.make_at_port(ring2.port)
    wg3.add_straight_segment(100)
    ring3 = RingResonator.make_at_port(wg3.current_port,
                                       -1.,
                                       50.,
                                       vertical_race_length=30,
                                       straight_feeding=True,
                                       draw_opposite_side_wg=True)

    cell.add_to_layer(1, wg1, ring1, wg2, ring2, wg3, ring3)
    cell.show()
Пример #5
0
def example():
    start_port = Port((0, 0), 0.5 * np.pi, 1.)
    wg1 = Waveguide.make_at_port(start_port)
    wg1.add_straight_segment(100.)
    # wg1.add_bend(0, 60.)
    detector = SNSPD.make_at_port(wg1.current_port, **snspd_parameters)
    wg = detector.get_waveguide()
    electrodes = detector.get_electrodes()
    pl = detector.get_passivation_layer(passivation_buffer=0.2)

    wg2 = Waveguide.make_at_port(detector.current_port)
    wg2.add_straight_segment(50.)
    # cell = gdsCAD.core.Cell('test')
    cell = Cell('test')
    cell.add_to_layer(3, Point(detector.get_aux_top()[0]).buffer(0.05))
    cell.add_to_layer(4, Point(detector.get_aux_top()[1]).buffer(0.05))
    cell.add_to_layer(3, Point(detector.get_aux_bottom()[0]).buffer(0.05))
    cell.add_to_layer(4, Point(detector.get_aux_bottom()[1]).buffer(0.05))
    cell.add_to_layer(3, wg1)

    cell.add_to_layer(1, detector)
    cell.add_to_layer(2, detector.get_waveguide())
    cell.add_to_layer(6, detector.get_electrodes())
    cell.add_to_layer(5, pl)

    # cell.add_to_layer(6, detector.right_electrode_port.debug_shape)
    # cell.add_to_layer(6, detector.left_electrode_port.debug_shape)
    cell.add_to_layer(3, wg2)
    cell.save('SNSPD_test.gds')
    cell.show()
Пример #6
0
def _example():
    from gdshelpers.geometry.chip import Cell
    from gdshelpers.parts.port import Port
    from gdshelpers.parts.waveguide import Waveguide
    from gdshelpers.parts.mode_converter import StripToSlotModeConverter

    wg_1 = Waveguide.make_at_port(
        Port(origin=(0, 0), angle=0,
             width=1.2))  # scalar as width -> strip waveguide
    wg_1.add_straight_segment(5)

    mc_1 = StripToSlotModeConverter.make_at_port(
        wg_1.current_port, 5, [0.4, 0.2, 0.4], 2,
        0.2)  # array as width -> slot waveguide

    wg_2 = Waveguide.make_at_port(mc_1.out_port)
    wg_2.add_bend(angle=np.pi, radius=5)

    mc_2 = StripToSlotModeConverter.make_at_port(
        wg_2.current_port, 5, 1, 2, 0.2)  # scalar as width -> strip waveguide

    wg_3 = Waveguide.make_at_port(mc_2.out_port)
    wg_3.add_straight_segment(5)

    cell = Cell('CELL')
    cell.add_to_layer(1, wg_1, mc_1, wg_2, mc_2, wg_3)
    cell.show()
Пример #7
0
def _example():
    from gdshelpers.geometry.chip import Cell
    from gdshelpers.parts.splitter import Splitter
    from gdshelpers.parts.coupler import GratingCoupler

    port = Port([0, 0], 0, 1)
    path = Waveguide.make_at_port(port)

    path.add_straight_segment(10)
    path.add_bend(np.pi / 2, 10, final_width=0.5)
    path.add_bend(-np.pi / 2, 10, final_width=1)
    path.add_arc(np.pi * 3 / 4, 10, )

    splitter = Splitter.make_at_root_port(path.current_port, 30, 10)
    path2 = Waveguide.make_at_port(splitter.right_branch_port)
    path2.add_bend(-np.pi / 4, 10)

    n = 10
    path2.add_parameterized_path(lambda t: (n * 10 * t, np.cos(n * 2 * np.pi * t) - 1),
                                 path_derivative=lambda t: (n * 10, -n * 2 * np.pi * np.sin(n * 2 * np.pi * t)),
                                 width=lambda t: np.cos(n * 2 * np.pi * t) * 0.2 + np.exp(-t) * 0.3 + 0.5,
                                 width_function_supports_numpy=True)
    path2.add_straight_segment(10, width=[.5, .5, .5])
    print(path2.length)
    print(path2.length_last_segment)

    path2.add_cubic_bezier_path((0, 0), (5, 0), (10, 10), (5, 10))
    path2.add_bend(-np.pi, 40)

    coupler1 = GratingCoupler([100, 50], 0, 1, np.deg2rad(30), [10, 0.1, 2, 0.1, 2], start_radius_absolute=True)

    path3 = Waveguide((0, -50), np.deg2rad(0), 1)
    path3.add_bezier_to(coupler1.port.origin, coupler1.port.inverted_direction.angle, bend_strength=50)

    splitter2 = Splitter.make_at_left_branch_port(splitter.left_branch_port, 30, 10, wavelength_root=2)

    path4 = Waveguide.make_at_port(splitter2.root_port)
    path4.add_straight_segment(20)
    path4.width = 1
    path4.add_straight_segment(20)

    empty_path = Waveguide.make_at_port(path4.current_port)

    whole_layout = (path, splitter, path2, splitter2, coupler1, path3, path4, empty_path)

    layout = Cell('LIBRARY')
    cell = Cell('TOP')
    cell.add_to_layer(1, *whole_layout)
    cell.add_to_layer(2, empty_path)
    cell.add_to_layer(4, splitter.root_port.debug_shape)

    layout.add_cell(cell)

    cell_df = Cell('TOP_DF')
    cell_df.add_to_layer(1, convert_to_positive_resist(whole_layout, buffer_radius=1.5))
    layout.add_cell(cell_df)

    layout.save('output.gds')
    cell.show()
Пример #8
0
    def __init__(self, origin, angle, width, num, gap, inner_gap):
        """
        Creates a Spiral around the given origin

        :param origin: position of the center of the spiral
        :param angle: angle of the outer two waveguides
        :param width: width of the waveguide
        :param num: number of turns
        :param gap: gap between two waveguides
        :param inner_gap: inner radius of the spiral
        """
        self._origin_port = Port(origin, angle, width)
        self.gap = gap
        self.inner_gap = inner_gap
        self.num = num
        self.wg_in = None
        self.wg_out = None
Пример #9
0
    def port(self):
        """
        The port of the coupler.

        :return: The coupler port.
        :rtype: Port
        """
        return Port(self._origin, self._angle, self._width).inverted_direction
Пример #10
0
    def __init__(self, origin, angle, width, taper_length, final_width,
                 pre_taper_length, pre_taper_width):
        """

        :param origin: origin of the mode converter
        :param angle: angle of the mode converter
        :param width: width of the mdoe converter. Scalar if strip to slot, array if slot to strip
        :param taper_length: length of the taper
        :param final_width: final width of the mode converter. Array if strip to slot, scalar if slot to strip
        :param pre_taper_length: length of the pre taper
        :param pre_taper_width: width of the pre taper
        """
        self._taper_length = taper_length
        self._in_port = Port(origin, angle, width)
        self._final_width = final_width
        self._pre_taper_length = pre_taper_length
        self._pre_taper_width = pre_taper_width
        self._waveguide = None
Пример #11
0
    def __init__(self,
                 origin,
                 angle,
                 width,
                 gap=0.15,
                 l_taper=10,
                 w_taper=0.9,
                 el_l_straight=8,
                 el_l_taper=2.5,
                 el_final_width=2,
                 el_l_fine=1.4,
                 el_radius=0.1,
                 n_points=128):
        assert gap >= 0, 'Gap must not be negative'
        assert el_radius > 0, 'The electrode radius must not be negative'

        self._origin_port = Port(origin, angle, width)
        cnt_port = self._origin_port.copy().longitudinal_offset(l_taper)
        cnt_port.width = w_taper
        self._cnt_port = cnt_port
        self._out_port = self._origin_port.copy().longitudinal_offset(2 *
                                                                      l_taper)

        self.gap = gap
        self.points = n_points
        self.el_l_taper = el_l_taper
        self.radius = el_radius
        self.el_l_straight = el_l_straight
        self.final_width = el_final_width
        self.el_l_fine = el_l_fine
        self.l_taper = l_taper

        self.el_shift_x = 0
        self.el_shift_y = self._cnt_port.width / 2 + self.gap + self.radius
        self.angle = self._origin_port.angle + math.pi / 2
        self.sample_distance = 0.015

        self.electrodes = None
        self.waveguide = []

        self._make_waveguide()
        self._make_electrodes()
Пример #12
0
    def __init__(self,
                 origin,
                 angle,
                 width,
                 gap,
                 radius,
                 race_length=0,
                 draw_opposite_side_wg=False,
                 res_wg_width=None,
                 n_points=None,
                 straight_feeding=False,
                 vertical_race_length=0):
        assert race_length >= 0, 'The race track length must not be negative'
        assert vertical_race_length >= 0, 'The vertical race track length must not be negative'
        assert radius > 0, 'The bend radius must not be negative'

        # Let's directly create a port object. This simplifies later creation of the geometry
        # as well as checking the user parameters while creating the port.
        self._origin_port = Port(origin, angle, width)

        # Ring gap
        try:
            self.gap, self.opposite_gap = gap
            if np.sign(self.gap) != np.sign(self.opposite_gap):
                raise ValueError
        except TypeError:
            self.gap = gap
            self.opposite_gap = gap

        # Remember all the other stuff we got
        self.radius = radius
        self.race_length = race_length
        self.res_wg_width = res_wg_width if res_wg_width else width
        self.points = n_points
        self.draw_opposite_side_wg = draw_opposite_side_wg
        self.straight_feeding = straight_feeding
        self.vertical_race_length = vertical_race_length
Пример #13
0
    def __init__(self, origin, angle, width, nano_wire_width, nano_wire_gap, nano_wire_length, waveguide_tapering,
                 passivation_buffer):
        if nano_wire_width <= 0:
            raise ValueError("The nano-wire width must be greater than 0")
        if nano_wire_length < 0:
            raise ValueError("The nano-wire length must be positive")
        if nano_wire_gap <= 0:
            raise ValueError("The nano-wire gap must be greater than 0")
        if nano_wire_length < 0:
            raise ValueError("The distance for the passivation layer must be positive")

        self._origin_port = Port(origin, angle, width)
        self.nano_wire_gap = nano_wire_gap
        self.nano_wire_width = nano_wire_width
        self.nano_wire_length = nano_wire_length
        self.waveguide_tapering = waveguide_tapering
        self.passivation_buffer = passivation_buffer

        self._waveguide = None
        self._nano_wire = None
        self._wings = None
Пример #14
0
def _cnt_example():
    import gdsCAD.core
    from gdshelpers.geometry import convert_to_gdscad
    from gdshelpers.parts.waveguide import Waveguide

    # photonics
    start_port = Port((0, 0), 0, 1.1)
    wg = Waveguide.make_at_port(start_port)
    wg.add_straight_segment(20)
    cnt_port = wg.current_port
    cnt = CNT.make_at_port(cnt_port, gap=0.4, l_taper=100, w_taper=0.1)
    wg2 = Waveguide.make_at_port(cnt.out_port)
    wg2.add_bend(np.pi / 4, 100)

    cnt2 = CNT.make_at_port(wg2.current_port, gap=0.15)

    union = geometric_union([wg, cnt, wg2, cnt2])

    # electrodes
    el1_l = Waveguide.make_at_port(cnt.left_electrode_port)
    el1_l.add_straight_segment(100, 100)
    el1_r = Waveguide.make_at_port(cnt.right_electrode_port)
    # el1_r.add_straight_segment(100)

    port = cnt2.left_electrode_port
    port.width = 20
    el2_l = Waveguide.make_at_port(port)
    el2_l.add_straight_segment(30)

    el = geometric_union(
        [cnt.electrodes, cnt2.electrodes, el1_l, el1_r, el2_l])

    cell = gdsCAD.core.Cell('test')
    cell.add(convert_to_gdscad(union))
    cell.add(convert_to_gdscad(el, layer=2))
    layout = gdsCAD.core.Layout()
    layout.add(cell)
    layout.show()
    layout.save('CNT_Device_Test.gds')
Пример #15
0
    def __init__(self,
                 origin,
                 angle,
                 width,
                 nw_width,
                 nw_gap,
                 nw_length,
                 wing_span,
                 wing_height,
                 electrodes_pitch,
                 electrodes_gap,
                 electrodes_height,
                 waveguide_tapering,
                 n_points=128):
        assert nw_gap > 0., 'Nanowire gap must be a positive value'
        assert nw_length > 0., 'Nanowire length must be a positive value'
        assert nw_width > 0., 'Nanowire width must be a positive value'

        # Let's directly create a port object. This simplifies later creation of the geometry
        # as well as checking the user parameters while creating the port.
        self._origin_port = Port(origin, angle, width)

        # Remember all the other stuff we got
        self.nw_gap = nw_gap
        self.nw_width = nw_width
        self.points = n_points
        self.nw_length = nw_length
        self.wing_span = wing_span
        self.wing_height = wing_height
        self.electrodes_pitch = electrodes_pitch
        self.electrodes_gap = electrodes_gap
        self.electrodes_height = electrodes_height
        self.waveguide_tapering = waveguide_tapering
        self.make_nanowire()
        self.make_waveguide()
        self.make_electrodes()
Пример #16
0
class StripToSlotModeConverter:
    """
    Generates a strip to slot mode converter as presented by Palmer et. al https://doi.org/10.1109/JPHOT.2013.2239283.
    If the input port width is a scalar und the final width is an array, a strip to slot mode converter is generated.
    On the other hand, if the input port width is an array and the final width is a scaler, a slot to strip mode
    converter is generated.

    """
    def __init__(self, origin, angle, width, taper_length, final_width,
                 pre_taper_length, pre_taper_width):
        """

        :param origin: origin of the mode converter
        :param angle: angle of the mode converter
        :param width: width of the mdoe converter. Scalar if strip to slot, array if slot to strip
        :param taper_length: length of the taper
        :param final_width: final width of the mode converter. Array if strip to slot, scalar if slot to strip
        :param pre_taper_length: length of the pre taper
        :param pre_taper_width: width of the pre taper
        """
        self._taper_length = taper_length
        self._in_port = Port(origin, angle, width)
        self._final_width = final_width
        self._pre_taper_length = pre_taper_length
        self._pre_taper_width = pre_taper_width
        self._waveguide = None

    @classmethod
    def make_at_port(cls, port, taper_length, final_width, pre_taper_length,
                     pre_taper_width):
        """

        :param port: port of the taper (origin, angle, width)
        :param taper_length: length of the taper
        :param final_width: final width of the mode converter. Array if strip to slot, scalar if slot to strip
        :param pre_taper_length: length of the pre taper
        :param pre_taper_width:  width of the pre taper
        :return:
        """
        return cls(port.origin, port.angle, port.width, taper_length,
                   final_width, pre_taper_length, pre_taper_width)

    @property
    def in_port(self):
        """
        Returns the input port of the mode converter

        :return: port
        """
        return self._in_port.copy()

    @property
    def out_port(self):
        """
        Returns the output port of the mode converter

        :return: port
        """
        port = self._in_port.longitudinal_offset(self._taper_length +
                                                 self._pre_taper_length)
        port.width = self._final_width
        return port

    def get_shapely_object(self):
        """
        Generates the mode converter. If the input port width is a scalar und the final width is an array, a strip to
        slot mode converter is generated. On the other hand, if the input port width is an array and the final width is
        a scaler, a slot to strip mode converter is generated.

        :return: shapely object
        """
        if self._waveguide:
            return self._waveguide.get_shapely_object()

        if np.array(self._in_port.width).size == 1:
            # strip to slot mode converter
            strip_width = self.in_port.width
            slot_width = self._final_width
        else:
            # slot to strip mode converter
            slot_width = self.in_port.width
            strip_width = self._final_width

        def pre_taper_width(t):
            return [
                slot_width[0] * t + self._pre_taper_width * (1 - t),
                slot_width[1] + (slot_width[0] - self._pre_taper_width) *
                (1 - t), strip_width, slot_width[0] + slot_width[1]
            ]

        def taper_width(t):
            return [
                slot_width[0], slot_width[1],
                slot_width[2] * t + strip_width * (1 - t),
                (slot_width[0] + slot_width[1]) * (1 - t)
            ]

        self._waveguide = Waveguide.make_at_port(self._in_port)

        if np.array(self._in_port.width).size == 1:
            # strip to slot mode converter
            self._waveguide.add_parameterized_path(
                lambda t: [t * self._pre_taper_length, 0],
                width=pre_taper_width)
            self._waveguide.add_parameterized_path(
                lambda t: [t * self._taper_length, 0], width=taper_width)
        else:
            # slot to strip mode converter
            self._waveguide.add_parameterized_path(
                lambda t: [t * self._taper_length, 0],
                width=lambda t: taper_width(1 - t))
            self._waveguide.add_parameterized_path(
                lambda t: [t * self._pre_taper_length, 0],
                width=lambda t: pre_taper_width(1 - t))

        return self._waveguide.get_shapely_object()
Пример #17
0
class RingResonator:
    """
    A simple Ring / Race track resonator.

    This part implements a super simple ring resonator with optional race tracks. Several
    helper functions are available to calculate points and ports of interest. The width of the
    feeding waveguide and the ring waveguide may differ.


    :param origin: Origin of the resonator, which is the start of the input waveguide.
    :param angle: Angle of the input waveguide.
    :param width: Width of the angle waveguide.
    :param gap: Gap between ring and waveguide. If positive, the ring will be on the left, and on the right side for
                negative gap values. Can also be a 2-tuple, if input and output gap should be different.
    :param radius: Radius of the bends.
    :param race_length: Length of the race track. Defaults to zero.
    :param draw_opposite_side_wg: If True, draw the opposing waveguides, (a.k.a. drop ports.)
    :param res_wg_width: Width of the resonator waveguide. If None, the width of the input waveguide is assumend.
    :param n_points: Number of points used per quarter circle. If None, it uses the bend default.
    :param straight_feeding: Add straight connections on both sides of the resonator.
    :param vertical_race_length: Length of a vertical race track section. Defaults to zero.
    """
    def __init__(self,
                 origin,
                 angle,
                 width,
                 gap,
                 radius,
                 race_length=0,
                 draw_opposite_side_wg=False,
                 res_wg_width=None,
                 n_points=None,
                 straight_feeding=False,
                 vertical_race_length=0):
        assert race_length >= 0, 'The race track length must not be negative'
        assert vertical_race_length >= 0, 'The vertical race track length must not be negative'
        assert radius > 0, 'The bend radius must not be negative'

        # Let's directly create a port object. This simplifies later creation of the geometry
        # as well as checking the user parameters while creating the port.
        self._origin_port = Port(origin, angle, width)

        # Ring gap
        try:
            self.gap, self.opposite_gap = gap
            if np.sign(self.gap) != np.sign(self.opposite_gap):
                raise ValueError
        except TypeError:
            self.gap = gap
            self.opposite_gap = gap

        # Remember all the other stuff we got
        self.radius = radius
        self.race_length = race_length
        self.res_wg_width = res_wg_width if res_wg_width else width
        self.points = n_points
        self.draw_opposite_side_wg = draw_opposite_side_wg
        self.straight_feeding = straight_feeding
        self.vertical_race_length = vertical_race_length

    @classmethod
    def make_at_port(cls, port, gap, radius, **kwargs):
        default_port_param = dict(port.get_parameters())
        default_port_param.update(kwargs)
        del default_port_param['origin']
        del default_port_param['angle']
        del default_port_param['width']

        return cls(port.origin, port.angle, port.width, gap, radius,
                   **default_port_param)

    ###
    # Let's allow the user to change the values
    # hidden in _origin_port. Hence the internal use
    # of a Port is transparent.
    @property
    def origin(self):
        return self._origin_port.origin

    @origin.setter
    def origin(self, origin):
        self._origin_port.origin = origin

    @property
    def angle(self):
        return self._origin_port.angle

    @angle.setter
    def angle(self, angle):
        self._origin_port.angle = angle

    @property
    def width(self):
        return self._origin_port.width

    @width.setter
    def width(self, width):
        self._origin_port.width = width

    ####
    # Now, lets get to the functions the user is actually
    # interested in.
    @property
    def port(self):
        offset = self.race_length + 2 * self.radius if self.straight_feeding else 0
        return self._origin_port.longitudinal_offset(offset)

    out_port = port

    @property
    def opposite_side_port_out(self):
        return self.port.parallel_offset(
            np.copysign(2 * self.radius +
                        self.vertical_race_length, self.opposite_gap) +
            self._offset + self._offset_opposite)

    @property
    def opposite_side_port_in(self):
        return self._origin_port.parallel_offset(
            np.copysign(2 * self.radius +
                        self.vertical_race_length, self.opposite_gap) +
            self._offset + self._offset_opposite).inverted_direction

    # Conventional naming of ports
    @property
    def in_port(self):
        return self._origin_port.inverted_direction

    @property
    def add_port(self):
        return self.opposite_side_port_out

    @property
    def drop_port(self):
        return self.opposite_side_port_in

    @property
    def through_port(self):
        return self.port

    @property
    def center_coordinates(self):
        return self._origin_port.longitudinal_offset(
            self.race_length / 2.).parallel_offset(
                self._offset +
                np.copysign(self.radius +
                            0.5 * self.vertical_race_length, self.gap)).origin

    @property
    def _offset(self):
        return math.copysign(
            abs(self.gap) + (self.width + self.res_wg_width) / 2., self.gap)

    @property
    def _offset_opposite(self):
        return math.copysign(
            abs(self.opposite_gap) + (self.width + self.res_wg_width) / 2.,
            self.opposite_gap)

    @property
    def circumference(self):
        return 2 * np.pi * self.radius + 2 * self.race_length

    def get_shapely_object(self):
        wg = Waveguide.make_at_port(self._origin_port)
        opposite_wg = Waveguide.make_at_port(
            self.opposite_side_port_in.inverted_direction)

        if self.straight_feeding:
            wg.add_straight_segment(self.radius)
            if self.draw_opposite_side_wg:
                opposite_wg.add_straight_segment(self.radius)

        ring_port = wg.current_port.parallel_offset(self._offset)
        ring_port.width = self.res_wg_width
        ring = Waveguide.make_at_port(ring_port)

        if self.race_length:
            wg.add_straight_segment(self.race_length)

            if self.draw_opposite_side_wg:
                opposite_wg.add_straight_segment(self.race_length)

        if self.straight_feeding:
            wg.add_straight_segment(self.radius)
            if self.draw_opposite_side_wg:
                opposite_wg.add_straight_segment(self.radius)

        # Build the ring
        bend_angle = math.copysign(0.5 * np.pi, self.gap)
        if self.race_length:
            ring.add_straight_segment(self.race_length)

        ring.add_bend(bend_angle, self.radius, n_points=self.points)
        if self.vertical_race_length:
            ring.add_straight_segment(self.vertical_race_length)

        ring.add_bend(bend_angle, self.radius, n_points=self.points)
        if self.race_length:
            ring.add_straight_segment(self.race_length)

        ring.add_bend(bend_angle, self.radius, n_points=self.points)
        if self.vertical_race_length:
            ring.add_straight_segment(self.vertical_race_length)

        ring.add_bend(bend_angle, self.radius, n_points=self.points)

        return geometric_union([ring, wg, opposite_wg])
Пример #18
0
    def _calculate(self, do_in_wgs=True, do_out_wgs=True):
        angular_spacing = self._angular_spacing
        assert angular_spacing * self._out_ports < np.pi, 'Not enough space for output ports'
        assert angular_spacing * self._in_ports < np.pi, 'Not enough space for input ports'

        if do_out_wgs:
            # Do the output side
            out_origin_port = Port(self._origin, self._angle,
                                   1.).longitudinal_offset(
                                       -self._displacement / 2)
            out_fanout_wgs = [
                Waveguide.make_at_port(
                    out_origin_port.rotated(angle).longitudinal_offset(
                        self._radius))
                for angle in (np.arange(self._out_ports) -
                              (self._out_ports - 1) / 2.) * angular_spacing
            ]

            for wg in out_fanout_wgs:
                wg.add_parameterized_path(
                    path=lambda t: [t * self._taper_length,
                                    np.zeros_like(t)],
                    path_derivative=lambda t:
                    [np.ones_like(t) * self._taper_length,
                     np.zeros_like(t)],
                    path_function_supports_numpy=True,
                    width=self._taper_function,
                    **self._taper_options)

            if self._minimal_final_spacing is None:
                for wg in out_fanout_wgs:
                    wg.add_bend(normalize_phase(-wg.angle + self._angle),
                                self._wg_bend_radius)

            else:
                offsets = (
                    np.arange(self._out_ports) -
                    (self._out_ports - 1) / 2.) * self._minimal_final_spacing
                final_port_heights = [
                    out_origin_port.parallel_offset(offset)
                    for offset in offsets
                ]

                for wg, final_port_height, offset in zip(
                        out_fanout_wgs, final_port_heights, offsets):
                    if np.isclose(offset, 0):
                        continue

                    try:
                        wg.add_route_single_circle_to_port(
                            final_port_height.inverted_direction,
                            on_line_only=True)
                    except AssertionError:
                        # No curve possible, use normal bend
                        wg.add_bend(normalize_phase(-wg.angle + self._angle),
                                    self._wg_bend_radius)

            final_ports = [wg.current_port for wg in out_fanout_wgs]
            for wg in out_fanout_wgs:
                wg.add_straight_segment_until_level_of_port(final_ports)
            self._out_wgs = out_fanout_wgs

        if do_in_wgs:
            # Do the input side
            in_origin_port = Port(self._origin, self._angle + np.pi,
                                  1.).longitudinal_offset(-self._displacement /
                                                          2)
            in_fanout_wgs = [
                Waveguide.make_at_port(
                    in_origin_port.rotated(angle).longitudinal_offset(
                        self._radius))
                for angle in (np.arange(self._in_ports) -
                              (self._in_ports - 1) / 2.) * angular_spacing
            ]

            for wg in in_fanout_wgs:
                wg.add_parameterized_path(
                    path=lambda t: [t * self._taper_length,
                                    np.zeros_like(t)],
                    path_derivative=lambda t:
                    [np.ones_like(t) * self._taper_length,
                     np.zeros_like(t)],
                    path_function_supports_numpy=True,
                    width=self._taper_function,
                    **self._taper_options)

            if self._minimal_final_spacing is None:
                for wg in in_fanout_wgs:
                    wg.add_bend(
                        normalize_phase(-wg.angle + self._angle - np.pi),
                        self._wg_bend_radius)

            else:
                offsets = (
                    np.arange(self._in_ports) -
                    (self._in_ports - 1) / 2.) * self._minimal_final_spacing
                final_port_heights = [
                    in_origin_port.parallel_offset(offset)
                    for offset in offsets
                ]

                for wg, final_port_height, offset in zip(
                        in_fanout_wgs, final_port_heights, offsets):
                    if np.isclose(offset, 0):
                        continue

                    # wg.add_route_single_circle_to_port(final_port_height.inverted_direction, on_line_only=True)

                    try:
                        wg.add_route_single_circle_to_port(
                            final_port_height.inverted_direction,
                            on_line_only=True)
                    except AssertionError:
                        # No curve possible, use normal bend
                        wg.add_bend(
                            normalize_phase(-wg.angle + self._angle - np.pi),
                            self._wg_bend_radius)

            final_ports = [wg.current_port for wg in in_fanout_wgs]
            for wg in in_fanout_wgs:
                wg.add_straight_segment_until_level_of_port(final_ports)

            self._in_wgs = in_fanout_wgs
Пример #19
0
 def port(self):
     return Port(self.origin, self.angle, self.width)
Пример #20
0
 def port(self):
     port = Port(self.origin, self.angle, self.width)
     return port.longitudinal_offset(2 * self.splitter_length + 4 * self.bend_radius + self.horizontal_length)
Пример #21
0
class CNT(object):
    """
    Creates a CNT source (Electrodes + tapered waveguide) at waveguide port.

    This class implements the electrodes and tapered waveguide for an integrated CNT source.
    It provides the electrodes ports and the connecting waveguide port.
    The Electrode is made up of three part: the round head (defined by the radius) followed by a
    straight fine line of length el_l_fine, followed by a taper to final_width with length el_l_taper and
    a box with length el_l_straight.

    :param origin: Origin of the resonator, which is the start of the input waveguide.
    :param angle: Angle of the input waveguide.
    :param width: Width of the angle waveguide.
    :param gap: Gap between electrode tip and waveguide.
    :param l_taper: length of the waveguide taper used before and after the cnt. i.a. 2*l_taper will be
        added to the waveguide
    :param w_taper: width of waveguide at the cnts position
    :param el_l_straight: length of electrode part with largest thickness
    :param el_l_taper: length over which 2*el_radius is tapered to el_final_width
    :param el_final_width: largest width of the electrode
    :param el_l_fine: length of small strip to the tip of the electrode
    :param el_radius: radius of electrodes tip
    :param n_points: number of points used to make electrode tip polygon
    """
    def __init__(self,
                 origin,
                 angle,
                 width,
                 gap=0.15,
                 l_taper=10,
                 w_taper=0.9,
                 el_l_straight=8,
                 el_l_taper=2.5,
                 el_final_width=2,
                 el_l_fine=1.4,
                 el_radius=0.1,
                 n_points=128):
        assert gap >= 0, 'Gap must not be negative'
        assert el_radius > 0, 'The electrode radius must not be negative'

        self._origin_port = Port(origin, angle, width)
        cnt_port = self._origin_port.copy().longitudinal_offset(l_taper)
        cnt_port.width = w_taper
        self._cnt_port = cnt_port
        self._out_port = self._origin_port.copy().longitudinal_offset(2 *
                                                                      l_taper)

        self.gap = gap
        self.points = n_points
        self.el_l_taper = el_l_taper
        self.radius = el_radius
        self.el_l_straight = el_l_straight
        self.final_width = el_final_width
        self.el_l_fine = el_l_fine
        self.l_taper = l_taper

        self.el_shift_x = 0
        self.el_shift_y = self._cnt_port.width / 2 + self.gap + self.radius
        self.angle = self._origin_port.angle + math.pi / 2
        self.sample_distance = 0.015

        self.electrodes = None
        self.waveguide = []

        self._make_waveguide()
        self._make_electrodes()

    @classmethod
    def make_at_port(cls, port, **kwargs):
        default_port_param = dict(port.get_parameters())
        default_port_param.update(kwargs)
        del default_port_param['origin']
        del default_port_param['angle']
        del default_port_param['width']

        return cls(port.origin, port.angle, port.width, **default_port_param)

    @property
    def left_electrode_port(self):
        offset = self.el_shift_y + self.el_l_fine + self.el_l_taper + self.el_l_straight
        port = self._cnt_port.parallel_offset(offset)
        port.width = self.final_width
        port.angle = self.angle
        return port

    @property
    def right_electrode_port(self):
        offset = -(self.el_shift_y + self.el_l_fine + self.el_l_taper +
                   self.el_l_straight)
        port = self._cnt_port.parallel_offset(offset)
        port.width = self.final_width
        port.angle = self.angle + math.pi
        return port

    @property
    def out_port(self):
        return self._out_port.copy()

    @property
    def in_port(self):
        return self._origin_port.copy().inverted_direction

    def _make_waveguide(self):
        wg = Waveguide.make_at_port(self._origin_port)
        wg.add_bezier_to_port(self._cnt_port.inverted_direction,
                              3,
                              sample_distance=self.sample_distance)
        wg.add_bezier_to_port(self._out_port.inverted_direction,
                              3,
                              sample_distance=self.sample_distance)
        self.waveguide.append(wg)

    def _make_electrodes(self):
        phi = np.linspace(math.pi, 2 * math.pi, self.points)

        circle_points = np.array(
            [self.radius * np.cos(phi), self.radius * np.sin(phi)]).T

        first_part_points = [
            circle_points[0], (-self.radius, self.el_l_fine),
            (self.radius, self.el_l_fine), circle_points[-1]
        ]

        taper_points = [
            first_part_points[1],
            (-self.final_width / 2, self.el_l_fine + self.el_l_taper),
            (self.final_width / 2, self.el_l_fine + self.el_l_taper),
            first_part_points[-2]
        ]

        last_part_points = [
            taper_points[1],
            (-self.final_width / 2,
             self.el_l_fine + self.el_l_taper + self.el_l_straight),
            (self.final_width / 2,
             self.el_l_fine + self.el_l_taper + self.el_l_straight),
            taper_points[-2]
        ]

        cirlce_polygon = shapely.geometry.Polygon(circle_points)
        first_part_polygon = shapely.geometry.Polygon(first_part_points)
        taper_polygon = shapely.geometry.Polygon(taper_points)
        last_part_polygon = shapely.geometry.Polygon(last_part_points)

        polygon = geometric_union([
            cirlce_polygon, taper_polygon, first_part_polygon,
            last_part_polygon
        ])

        upper_electrode = shapely.affinity.translate(polygon, self.el_shift_x,
                                                     self.el_shift_y)
        lower_electrode = shapely.affinity.scale(upper_electrode,
                                                 xfact=-1,
                                                 yfact=-1,
                                                 origin=(0, 0))

        electrode = geometric_union([lower_electrode, upper_electrode])
        electrode = shapely.affinity.rotate(electrode,
                                            self._cnt_port.angle,
                                            use_radians=True)
        electrode = shapely.affinity.translate(electrode,
                                               self._cnt_port.origin[0],
                                               self._cnt_port.origin[1])
        self.electrodes = electrode

    def get_shapely_object(self):
        return geometric_union(self.waveguide)
Пример #22
0
 def __init__(self, origin, angle, width):
     self._current_port = Port(origin, angle, width)
     self._in_port = self._current_port.inverted_direction.copy()
     self._segments = list()
Пример #23
0
class Spiral:
    def __init__(self, origin, angle, width, num, gap, inner_gap):
        """
        Creates a Spiral around the given origin

        :param origin: position of the center of the spiral
        :param angle: angle of the outer two waveguides
        :param width: width of the waveguide
        :param num: number of turns
        :param gap: gap between two waveguides
        :param inner_gap: inner radius of the spiral
        """
        self._origin_port = Port(origin, angle, width)
        self.gap = gap
        self.inner_gap = inner_gap
        self.num = num
        self.wg_in = None
        self.wg_out = None

    @classmethod
    def make_at_port(cls, port, num, gap, inner_gap, **kwargs):
        default_port_param = dict(port.get_parameters())
        default_port_param.update(kwargs)
        del default_port_param['origin']
        del default_port_param['angle']
        del default_port_param['width']

        return cls(
            port.parallel_offset(-num * (port.width + gap) - inner_gap).origin,
            port.angle, port.width, num, gap, inner_gap, **default_port_param)

    ###
    # Let's allow the user to change the values
    # hidden in _origin_port. Hence the internal use
    # of a Port is transparent.
    @property
    def origin(self):
        return self._origin_port.origin

    @origin.setter
    def origin(self, origin):
        self._origin_port.origin = origin

    @property
    def angle(self):
        return self._origin_port.angle

    @angle.setter
    def angle(self, angle):
        self._origin_port.angle = angle

    @property
    def width(self):
        return self._origin_port.width

    @width.setter
    def width(self, width):
        self._origin_port.width = width

    @property
    def in_port(self):
        return self._origin_port.inverted_direction.parallel_offset(
            -self.num * (self._origin_port.width + self.gap) - self.inner_gap)

    @property
    def out_port(self):
        return self._origin_port.parallel_offset(
            -self.num * (self._origin_port.width + self.gap) - self.inner_gap)

    @property
    def length(self):
        if not self.wg_in or not self.wg_out:
            self._generate()
        return self.wg_in.length + self.wg_out.length

    def _generate(self):
        def path(a):
            return (self.num * (self.width + self.gap) * np.abs(1 - a) +
                    self.inner_gap) * np.array((np.sin(
                        np.pi * a * self.num), np.cos(np.pi * a * self.num)))

        self.wg_in = Waveguide.make_at_port(self._origin_port)
        self.wg_in.add_parameterized_path(path)

        self.wg_out = Waveguide.make_at_port(
            self._origin_port.inverted_direction)
        self.wg_out.add_parameterized_path(path)

        self.wg_in.add_route_single_circle_to_port(
            self._origin_port.rotated(-np.pi * (self.num % 2)))
        self.wg_in.add_route_single_circle_to_port(self.wg_out.port)

    def get_shapely_object(self):
        if not self.wg_in or not self.wg_out:
            self._generate()
        return geometric_union([self.wg_in, self.wg_out])
Пример #24
0
class Waveguide:
    def __init__(self, origin, angle, width):
        self._current_port = Port(origin, angle, width)
        self._in_port = self._current_port.inverted_direction.copy()
        self._segments = list()

    @classmethod
    def make_at_port(cls, port, **kargs):
        port_param = port.copy()
        port_param.set_port_properties(**kargs)
        return cls(**port_param.get_parameters())

    @property
    def x(self):
        return self._current_port.origin[0]

    @property
    def y(self):
        return self._current_port.origin[1]

    @property
    def origin(self):
        return self._current_port.origin

    @property
    def angle(self):
        return self._current_port.angle

    @property
    def width(self):
        return self._current_port.width

    @width.setter
    def width(self, width):
        self._current_port.width = width

    @property
    def current_port(self):
        return self._current_port.copy()

    # Add alias for current port
    port = current_port

    @property
    def in_port(self):
        return self._in_port.copy()

    @property
    def length(self):
        return sum((length for port, obj, outline, length, center_coordinates
                    in self._segments))

    @property
    def length_last_segment(self):
        if not len(self._segments):
            return 0
        return self._segments[-1][3]

    @property
    def center_coordinates(self):
        return np.concatenate([
            center_coordinates for port, obj, outline, length,
            center_coordinates in self._segments
        ])

    def get_shapely_object(self):
        """
        Get a shapely object which forms this path.
        """
        return shapely.ops.cascaded_union([
            obj for port, obj, outline, length, center_coordinates in
            self._segments
        ])

    def get_shapely_outline(self):
        """
        Get a shapely object which forms the outline of the path.
        """
        return shapely.ops.cascaded_union([
            outline for port, obj, outline, length, center_coordinates in
            self._segments
        ])

    def get_segments(self):
        """
        Returns the list of tuples, containing their ports and shapely objects.
        """
        return [(port.copy(), obj, length) for port, obj, outline, length,
                center_coordinates in self._segments]

    def add_straight_segment(self, length, final_width=None, **kwargs):
        self._current_port.set_port_properties(**kwargs)
        final_width = final_width if final_width is not None else self.width

        if not np.isclose(length, 0):
            assert length >= 0, 'Length of straight segment must not be negative'

            self.add_parameterized_path(path=lambda t: [t * length, 0],
                                        path_derivative=lambda t: [1, 0],
                                        width=lambda t: np.array(self.width) *
                                        (1 - t) + np.array(final_width) * t,
                                        sample_points=2,
                                        sample_distance=0)
        return self

    def add_arc(self,
                final_angle,
                radius,
                final_width=None,
                n_points=128,
                shortest=True,
                **kwargs):
        delta = final_angle - self.angle
        if not np.isclose(normalize_phase(delta), 0):
            if shortest:
                delta = normalize_phase(delta)
            self.add_bend(delta, radius, final_width, n_points, **kwargs)
        return self

    def add_bend(self,
                 angle,
                 radius,
                 final_width=None,
                 n_points=128,
                 **kwargs):
        # If number of points is None, default to 128
        n_points = n_points if n_points else 128

        self._current_port.set_port_properties(**kwargs)
        sample_points = max(int(abs(angle) / (np.pi / 2) * n_points), 2)
        final_width = final_width if final_width is not None else self.width

        angle = normalize_phase(
            angle, zero_to_two_pi=True) - (0 if angle > 0 else 2 * np.pi)

        self.add_parameterized_path(
            path=lambda t: [
                radius * np.sin(abs(angle) * t),
                np.sign(angle) * -radius * (np.cos(angle * t) - 1)
            ],
            path_function_supports_numpy=True,
            path_derivative=lambda t: [
                radius * np.cos(abs(angle) * t) * abs(angle),
                np.sign(angle) * radius * (np.sin(angle * t) * angle)
            ],
            width=lambda t: np.outer(1 - t, np.array(self.width)) + np.outer(
                t, np.array(final_width)),
            width_function_supports_numpy=True,
            sample_points=sample_points,
            sample_distance=0)

        return self

    def add_parameterized_path(self,
                               path,
                               width=None,
                               sample_distance=0.50,
                               sample_points=100,
                               path_derivative=None,
                               path_function_supports_numpy=False,
                               width_function_supports_numpy=False):
        """
        Generate a parameterized path.

        The path coordinate system is the origin and rotation of the current path. So if you want to continue your path
        start at (0, 0) in y-direction.

        Note, that path is either a list of (x,y) coordinate tuples or a callable function which takes one float
        parameter between 0 and 1. If you use a parameterized function, its first derivative must be continuous.
        When using a list of coordinates, these points will be connected by straight lines. They must be sufficiently
        close together to simulate a first derivative continuous path.

        This function will try to space the final points of the curve equidistantly. To achieve this, it will first
        sample the function and find its first derivative. Afterwards it can calculate the cumulative sum of the length
        of the first derivative. This allows to sample the function nearly equidistantly in a second step. This
        approach might be wasteful for paths like (x**2, y). You can suppress resampling for length by passing zero or
        none as sample_distance parameter.

        The width of the generated waveguide may be constant when passing a number, or variable along the path
        when passing an array or a callable function, using the same parameter as the path.
        For generating slot/coplanar/... waveguides, start with a `Port` which has an array of the form
        `[rail_width_1, gap_width_1, rail_width_2, ...]` set as `width` and which defines the width of each
        rail and the gaps between the rails. This array is also allowed to end with a gap_width for positioning the
        rails asymmetrically to the path which can be useful e.g. for strip-to-slot mode converters.

        Note, that your final direction of the path might not be what you expected. This is caused by the numerical
        procedure which generates numerical errors when calculating the first derivative. You can either append another
        arc to the waveguide to get to you a correct angle or you can also supply a function which is the algebraic
        first derivative. The returned vector is not required to be normed.

        By default, for each parameter point t, the parameterized functions are call. You will notice that this is
        rather slow. To achieve the best performance, write your functions in such a way, that they can handle a
        numpy array as parameter *t*. Once the *path_function_supports_numpy* option is set to True, the function will
        be called only once, speeding up the calculation considerable.

        :param path:
        :param width:
        :param sample_distance:
        :param sample_points:
        :param path_derivative:
        :param path_function_supports_numpy:
        :param width_function_supports_numpy:
        """

        if callable(path):
            presample_t = np.linspace(0, 1, sample_points)

            if path_function_supports_numpy:
                presample_coordinates = np.array(path(presample_t)).T
            else:
                presample_coordinates = np.array(
                    [path(x) for x in presample_t])

            if sample_distance:
                # # Calculate the derivative
                # if path_derivative:
                #     assert callable(path_derivative), 'The derivative of the path function must be callable'
                #     presample_coordinates_d1 = np.array([path_derivative(x) for x in presample_t[:-1]])
                # else:
                #     presample_coordinates_d1 = np.diff(presample_coordinates, axis=0)
                presample_coordinates_d1 = np.diff(presample_coordinates,
                                                   axis=0)
                presample_coordinates_d1_norm = np.linalg.norm(
                    presample_coordinates_d1, axis=1)
                presample_coordinates_d1__cum_norm = np.insert(
                    np.cumsum(presample_coordinates_d1_norm), 0, 0)

                lengths = np.linspace(
                    presample_coordinates_d1__cum_norm[0],
                    presample_coordinates_d1__cum_norm[-1],
                    int(presample_coordinates_d1__cum_norm[-1] /
                        sample_distance))

                # First get the spline representation. This is needed since we manipulate these directly for roots
                # finding.
                spline_rep = scipy.interpolate.splrep(
                    presample_t, presample_coordinates_d1__cum_norm, s=0)

                def find_y(y):
                    interp_result = scipy.interpolate.sproot(
                        (spline_rep[0], spline_rep[1] - y, spline_rep[2]),
                        mest=1)
                    return interp_result[0] if len(interp_result) else None

                # We need a small hack here and exclude lengths[0]==0 since it finds no root there
                sample_t = np.array([
                    0,
                ] + [find_y(length) for length in lengths[1:-1]] + [
                    1,
                ])

                if path_function_supports_numpy:
                    sample_coordinates = np.array(path(sample_t)).T
                else:
                    sample_coordinates = np.array([path(x) for x in sample_t])
            else:
                sample_coordinates = presample_coordinates
                sample_t = presample_t
        else:
            # If we do not have a sample function, we need to "invent a sampling parameter"
            sample_coordinates = np.array(path)
            sample_t = np.linspace(0, 1, sample_coordinates.shape[0])

        rotation_matrix = np.array(((np.cos(self._current_port.angle),
                                     -np.sin(self._current_port.angle)),
                                    (np.sin(self._current_port.angle),
                                     np.cos(self._current_port.angle))))
        sample_coordinates = self._current_port.origin + np.einsum(
            'ij,kj->ki', rotation_matrix, sample_coordinates)

        # Calculate the derivative
        if callable(path_derivative):
            if path_function_supports_numpy:
                sample_coordinates_d1 = np.array(path_derivative(sample_t)).T
            else:
                sample_coordinates_d1 = np.array(
                    [path_derivative(x) for x in sample_t])
            sample_coordinates_d1 = np.einsum('ij,kj->ki', rotation_matrix,
                                              sample_coordinates_d1)
        else:
            if path_derivative is None:
                sample_coordinates_d1 = np.vstack(
                    (rotation_matrix[:, 0], np.diff(sample_coordinates,
                                                    axis=0)))
            else:
                sample_coordinates_d1 = np.array(path_derivative)
                sample_coordinates_d1 = np.einsum('ij,kj->ki', rotation_matrix,
                                                  sample_coordinates_d1)

        sample_coordinates_d1_norm = np.linalg.norm(sample_coordinates_d1,
                                                    axis=1)
        sample_coordinates_d1_normed = sample_coordinates_d1 / sample_coordinates_d1_norm[:,
                                                                                          None]

        # Find the orthogonal vectors to the derivative
        sample_coordinates_d1_normed_ortho = np.vstack(
            (sample_coordinates_d1_normed[:, 1],
             -sample_coordinates_d1_normed[:, 0])).T

        # Calculate the width of the waveguide at the given positions
        if callable(width):
            if width_function_supports_numpy:
                sample_width = width(sample_t)
            else:
                sample_width = np.array([width(x) for x in sample_t])
            if sample_width.ndim == 1:  # -> width returned a scalar for each x
                sample_width = sample_width[..., np.newaxis]
        else:
            if width is None:
                sample_width = np.atleast_1d(self._current_port.width)
                sample_width = sample_width[
                    np.newaxis, ...]  # width constant -> new axis along path
            else:
                sample_width = np.atleast_1d(width)
                if sample_width.ndim == 1:  # -> width is a scalar for each x
                    sample_width = sample_width[..., np.newaxis]

        # Now we have everything to calculate the polygon
        polygons = []
        half_width = np.sum(sample_width, axis=-1) / 2
        for i in range((sample_width.shape[-1] + 1) // 2):
            start = np.sum(sample_width[:, :(2 * i)], axis=-1) - half_width
            stop = start + sample_width[:, 2 * i]
            poly_path_1 = sample_coordinates + start[
                ..., None] * sample_coordinates_d1_normed_ortho
            poly_path_2 = sample_coordinates + stop[
                ..., None] * sample_coordinates_d1_normed_ortho
            poly_path = np.concatenate([poly_path_1, poly_path_2[::-1, :]])

            assert shapely.geometry.LineString(poly_path).is_simple, \
                'Outer lines of parameterized wg intersect. Try using lower bend radii or smaller a smaller wg'

            # Now add the shapely objects and do book keeping
            polygon = shapely.geometry.Polygon(poly_path)
            assert polygon.is_valid, 'Generated polygon path is not valid: %s' % \
                                     shapely.validation.explain_validity(polygon)
            polygons.append(polygon)
        polygon = shapely.geometry.MultiPolygon(polygons)

        outline_poly_path_1 = sample_coordinates - np.sum(
            sample_width,
            axis=-1)[..., None] / 2 * sample_coordinates_d1_normed_ortho
        outline_poly_path_2 = sample_coordinates + np.sum(
            sample_width,
            axis=-1)[..., None] / 2 * sample_coordinates_d1_normed_ortho
        outline = shapely.geometry.Polygon(
            np.concatenate([outline_poly_path_1,
                            outline_poly_path_2[::-1, :]]))

        length = np.sum(
            np.linalg.norm(np.diff(sample_coordinates, axis=0), axis=1))
        self._segments.append((self._current_port.copy(), polygon, outline,
                               length, sample_coordinates))

        self._current_port.origin = sample_coordinates[-1]
        # If the width does not need to be a list, convert it back to a scalar
        self._current_port.width = sample_width[-1]
        self._current_port.angle = np.arctan2(sample_coordinates_d1[-1][1],
                                              sample_coordinates_d1[-1][0])
        return self

    def add_cubic_bezier_path(self, p0, p1, p2, p3, width=None, **kwargs):
        """
        Add a cubic bezier path to the waveguide.

        Coordinates are in the "waveguide tip coordinate system", so the first point will probably be p0 == (0, 0).
        Note that your bezier curve undergoes the same restrictions as a parameterized path. Don't self-intersect it and
        don't use small bend radii.

        :param p0: 2 element tuple like coordinates
        :param p1: 2 element tuple like coordinates
        :param p2: 2 element tuple like coordinates
        :param p3: 2 element tuple like coordinates
        :param width: Width of the waveguide, as passed to :func:add_parameterized_path
        :param kwargs: Optional keyword arguments, passed to :func:add_parameterized_path
        :return: Changed waveguide
        :rtype: Waveguide
        """

        bezier_curve = CubicBezierCurve(p0, p1, p2, p3)

        self.add_parameterized_path(path=bezier_curve.evaluate,
                                    path_derivative=bezier_curve.evaluate_d1,
                                    width=width,
                                    path_function_supports_numpy=True,
                                    **kwargs)
        return self

    def add_bezier_to(self,
                      final_coordinates,
                      final_angle,
                      bend_strength,
                      width=None,
                      **kwargs):

        try:
            bs1, bs2 = float(bend_strength[0]), float(bend_strength[1])
        except (KeyError, TypeError):
            bs1 = bs2 = float(bend_strength)

        final_port = Port(final_coordinates, final_angle, self.width)
        p0 = (0, 0)
        p1 = self._current_port.longitudinal_offset(
            bs1).origin - self._current_port.origin
        p2 = final_port.longitudinal_offset(
            -bs2).origin - self._current_port.origin
        p3 = final_coordinates - self._current_port.origin

        tmp_wg = Waveguide.make_at_port(
            self._current_port.copy().set_port_properties(angle=0))
        tmp_wg.add_cubic_bezier_path(p0, p1, p2, p3, width=width, **kwargs)

        self._segments.append(
            (self._current_port.copy(), tmp_wg.get_shapely_object(),
             tmp_wg.get_shapely_outline(), tmp_wg.length,
             tmp_wg.center_coordinates))
        self._current_port = tmp_wg.current_port

        return self

    def add_bezier_to_port(self, port, bend_strength, width=None, **kwargs):
        if not width and not np.isclose(np.array(self.width),
                                        np.array(port.width)):

            def width(t):
                return t * (port.width - self.width) + self.width

            supports_numpy = True
        else:
            supports_numpy = False

        kwargs['width_function_supports_numpy'] = kwargs.get(
            'width_function_supports_numpy', supports_numpy)

        self.add_bezier_to(port.origin, port.inverted_direction.angle,
                           bend_strength, width, **kwargs)
        return self

    def add_route_single_circle_to(self,
                                   final_coordinates,
                                   final_angle,
                                   final_width=None,
                                   max_bend_strength=None,
                                   on_line_only=False):
        """
        Connect two points by straight lines and one circle.

        Works for geometries like round edges and others. The final straight line can also be omitted so that
        the waveguide only end on the line described by the support vector and angle.

        By default, this method tries to route to the target with the greatest possible circle. But the valid
        bending range may be limited via the `max_bend_strength` parameter.

        This method does not work for geometries which cannot be connected only by straight lines and one
        circle, such as parallel lines etc.

        Still, this method can prove extremely useful for routing to i.e. grating couplers etc.

        :param final_coordinates: Final destination point.
        :param final_angle: Final angle of the waveguide.
        :param final_width: Final width of the waveguide.
        :param max_bend_strength: The maximum allowed bending radius.
        :param on_line_only: Omit the last straight line and only route to described line.
        """
        # We are given an out angle, but the internal math is for inward pointing lines
        final_angle = normalize_phase(final_angle) + np.pi
        final_width = final_width if final_width is not None else self.width
        # We need to to some linear algebra. We first find the intersection of the two waveguides
        r1 = self._current_port.origin
        r2 = np.array(final_coordinates)
        intersection_point, distance = find_line_intersection(
            r1, self.angle, r2, final_angle)

        assert distance[
            0] >= 0, 'Origin waveguide is too close to target. No curve possible'
        assert on_line_only or distance[
            1] >= 0, 'Target position is too close to target. No curve possible'

        # Calculate the angle bisector
        u1 = np.array([np.cos(self.angle), np.sin(self.angle)])
        u2 = np.array([np.cos(final_angle), np.sin(final_angle)])
        u_half = u1 + u2
        half_angle = np.arctan2(u_half[1], u_half[0])
        diff_angle = normalize_phase(self.angle - final_angle - np.pi)

        _, r1 = find_line_intersection(r1, self.angle + np.pi / 2,
                                       intersection_point, half_angle)

        if not on_line_only:
            max_poss_radius = min(
                np.abs([
                    r1[0], distance[0] / np.tan(diff_angle / 2),
                    distance[1] / np.tan(diff_angle / 2)
                ]))
        else:
            max_poss_radius = min(
                np.abs([r1[0], distance[0] / np.tan(diff_angle / 2)]))

        radius = min([max_bend_strength, max_poss_radius
                      ]) if max_bend_strength is not None else max_poss_radius
        d = abs(radius * np.tan(diff_angle / 2))
        if on_line_only:
            segments = [distance[0] - d, radius * diff_angle]
        else:
            segments = [distance[0] - d, radius * diff_angle, distance[1] - d]
        segment_ratio = np.cumsum(segments / sum(segments))
        segment_widths = [(final_width - self.current_port.width) * ratio +
                          self.current_port.width for ratio in segment_ratio]
        tmp_wg = Waveguide.make_at_port(self._current_port)
        tmp_wg.add_straight_segment(length=distance[0] - d,
                                    final_width=segment_widths[0])
        tmp_wg.add_bend(-diff_angle, radius, final_width=segment_widths[1])
        if not on_line_only:
            tmp_wg.add_straight_segment(distance[1] - d,
                                        final_width=segment_widths[2])

        self._segments.append(
            (self._current_port.copy(), tmp_wg.get_shapely_object(),
             tmp_wg.get_shapely_outline(), tmp_wg.length,
             tmp_wg.center_coordinates))
        self._current_port = tmp_wg.current_port

        return self

    def add_route_single_circle_to_port(self,
                                        port,
                                        max_bend_strength=None,
                                        on_line_only=False):
        """
        Connect to port by straight lines and one circle.

        Helper function to conveniently call add_route_single_circle_to.
        :param port: Target port.
        :param max_bend_strength: The maximum allowed bending radius.
        :param on_line_only: Omit the last straight line and only route to line described by port.
        """
        self.add_route_single_circle_to(port.origin,
                                        port.inverted_direction.angle,
                                        port.width, max_bend_strength,
                                        on_line_only)
        return self

    def add_route_straight_to_port(self, port):
        """
        Add a straight segment to a given port. The added segment will keep
        the angle of the current port at the start and use the angle of the
        target port at the end. If the ports are laterally shifted, this
        will result in a trapezoidal shape.

        The width will be linearly tapered to that of the target port.

        :param port: Target port.
        """
        start_width = np.array(self.current_port.width)
        final_width = np.array(port.width)

        c, s = np.cos(-self.current_port.angle), np.sin(
            -self.current_port.angle)
        R = np.array([[c, -s], [s, c]])
        end_point = R @ (np.array(port.origin) -
                         np.array(self.current_port.origin))

        angle_diff = port.inverted_direction.angle - self.current_port.angle
        start_deriv = np.array([1, 0])
        end_deriv = np.array([np.cos(angle_diff), np.sin(angle_diff)])

        self.add_parameterized_path(path=lambda t: np.array([0, 0]) *
                                    (1 - t) + end_point * t,
                                    width=lambda t: start_width *
                                    (1 - t) + final_width * t,
                                    path_derivative=lambda t: start_deriv *
                                    (1 - t) + end_deriv * t,
                                    sample_points=2,
                                    sample_distance=0)

        return self

    def add_straight_segment_to_intersection(self, line_origin, line_angle,
                                             **line_kw):
        """
        Add a straight line until it intersects with an other line.

        The other line is described by the support vector and the line angle.

        :param line_origin: Intersection line support vector.
        :param line_angle: Intersection line angle.
        :param line_kw: Parameters passed on to add_straight_segment.
        :raise ArithmeticError: When there is no intersection due to being parallel or if
                                the intersection is behind the waveguide.
        """
        r1 = self._current_port.origin
        r2 = np.array(line_origin)

        try:
            intersection_point, distance = find_line_intersection(
                r1, self.angle, r2, line_angle)
            if np.isclose(distance[0], 0):
                return self
            elif distance[0] < 0:
                raise ArithmeticError(
                    'No forward intersection of waveguide with given line')
            self.add_straight_segment(distance[0], **line_kw)
        except linalg.LinAlgError:
            # We do not find an intersection if lines are parallel
            raise ArithmeticError(
                'There is no intersection with the given line')
        return self

    def add_straight_segment_until_x(self, x, **line_kw):
        """
        Add straight segment until the given x value is reached.

        :param x: value
        :param line_kw: Parameters passed on to add_straight_segment.
        """
        self.add_straight_segment_to_intersection([x, 0], np.pi / 2, **line_kw)
        return self

    def add_straight_segment_until_y(self, y, **line_kw):
        """
        Add straight segment until the given y value is reached.

        :param y: value
        :param line_kw: Parameters passed on to add_straight_segment.
        """
        self.add_straight_segment_to_intersection([0, y], 0, **line_kw)
        return self

    def add_straight_segment_until_level_of_port(self, port, **line_kw):
        """
        Add a straight line until it is on the same level as the given port. If several ports are given in a list,
        the most distant port is chosen.

        In this context "on the same level" means the intersection of the waveguide with the line
        orthogonal to the given port.

        :param port: The port or a list of ports.
        :param line_kw:
        """

        if isinstance(port, collections.Iterable):
            direction = (np.cos(self.angle), np.sin(self.angle))
            distances = np.array([np.dot(p.origin, direction) for p in port])
            furthest_port_idx = distances.argmax()
            port = port[furthest_port_idx]

        self.add_straight_segment_to_intersection(port.origin,
                                                  port.angle - np.pi / 2,
                                                  **line_kw)
        return self

    def add_left_bend(self, radius, angle=np.pi / 2):
        """
        Add a left turn (90° or as defined by angle) with the given bend radius
        """
        return self.add_bend(angle, radius)

    def add_right_bend(self, radius, angle=np.pi / 2):
        """
        Add a right turn (90° or as defined by angle) with the given bend radius
        """
        return self.add_bend(-angle, radius)
Пример #25
0
    def make_nanowire(self):
        # U-shaped nanowire
        self.nanowire_port = Port(origin=(0, 0), angle=0, width=self.nw_width)
        nw = Waveguide.make_at_port(self.nanowire_port, width=self.nw_width)
        tip_radius = 0.5 * self.nw_gap + 0.5 * self.nw_width
        nw.add_bend(0.5 * np.pi, tip_radius)
        nw.add_straight_segment(self.nw_length - tip_radius)

        # Wing Square Pads which will overlap with the contact pads
        wing_tapering_origin = np.array(nw.current_port.origin)
        self.wing_pad_bottom_left = wing_tapering_origin + (
            (0.5 * self.wing_span - (self.nw_width + 0.5 * self.nw_gap)),
            0.5 * self._origin_port.width)
        self.wing_pad_bottom_right = self.wing_pad_bottom_left + (
            0.5 * self.wing_span, 0)
        self.wing_pad_top_right = self.wing_pad_bottom_right + (
            0, self.wing_height)
        self.wing_pad_top_left = self.wing_pad_bottom_left + (0,
                                                              self.wing_height)
        wing_pad = Polygon([
            self.wing_pad_bottom_left, self.wing_pad_bottom_right,
            self.wing_pad_top_right, self.wing_pad_top_left
        ])

        # Bezier tapering from nanowire to wing pads
        nw_left_side_coord = wing_tapering_origin - (0.5 * self.nw_width, 0)
        nw_right_side_coord = wing_tapering_origin + (0.5 * self.nw_width, 0)

        self.aux_origin_top_line = nw_left_side_coord + np.array(
            [0, 0.2 * (self.wing_pad_top_left[1] - nw_left_side_coord[1])])
        self.aux_dest_top_line = nw_left_side_coord + np.array([
            0.8 * (self.wing_pad_top_left[0] - nw_left_side_coord[0]), 0.4 *
            (self.wing_pad_top_left[1] - nw_left_side_coord[1])
        ])

        wing_tapering_top_line = self._cub_bezier(nw_left_side_coord,
                                                  self.wing_pad_top_left,
                                                  self.aux_origin_top_line,
                                                  self.aux_dest_top_line)

        self.aux_origin_bottom_line = nw_right_side_coord + np.array([
            0.1 * (self.wing_pad_bottom_left[0] - nw_right_side_coord[0]),
            self.wing_pad_bottom_left[1] - nw_right_side_coord[1]
        ])
        self.aux_dest_bottom_line = nw_right_side_coord + np.array(
            [0, 0.5 * (self.wing_pad_bottom_left[1] - nw_right_side_coord[1])])
        wing_tapering_bottom_line = self._cub_bezier(
            self.wing_pad_bottom_left, nw_right_side_coord,
            self.aux_origin_bottom_line, self.aux_dest_bottom_line)

        wing_tapering = Polygon(wing_tapering_top_line +
                                wing_tapering_bottom_line)

        wing = geometric_union([wing_pad, wing_tapering])

        nw = geometric_union([nw, wing])
        nw_l = shapely.affinity.scale(nw,
                                      xfact=-1.0,
                                      yfact=1.0,
                                      zfact=1.0,
                                      origin=[
                                          self.nanowire_port.origin[0],
                                          self.nanowire_port.origin[1], 0
                                      ])
        nw = geometric_union([nw, nw_l])
        nw = shapely.affinity.translate(nw, yoff=0.5 * self.nw_width)
        nw = shapely.affinity.rotate(nw,
                                     self._origin_port.angle - 0.5 * np.pi,
                                     origin=[0, 0],
                                     use_radians=True)
        self.nw = shapely.affinity.translate(nw,
                                             xoff=self._origin_port.origin[0],
                                             yoff=self._origin_port.origin[1])

        re_origin = self._origin_port.origin
        re_angle = self._origin_port.angle - 0.5 * np.pi
        rep = Port(origin=re_origin, angle=re_angle, width=self.wing_height)
        self.rep = rep.longitudinal_offset(
            self.wing_pad_bottom_left[0] + 0.25 *
            self.wing_span).parallel_offset(self.wing_pad_bottom_left[1] +
                                            0.5 * self.wing_height)
        self.lep = self.rep.longitudinal_offset(-1.5 * self.wing_span).rotated(
            np.pi)