Example #1
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])
Example #2
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])
Example #3
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