<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 References
GDS allows defining the component once in memory and reference to that structure in other components.
Step1:  Adding a component reference
As we build complex circuit we'll make circuits that combine reference to other simpler circuits. Adding a reference is like having a pointer to the other devices.
Step2:  We have two ways to add a reference to our device
Step3:  or we can do it in a single line (my preference)
Step4:  in both cases we can move the reference wr after created
Step5:  Adding a reference array
We can also add an array of references for periodic structures. Lets create a Distributed Bragg Reflector
As PHIDL tutorial says, in GDS, there's a type of structure called a "CellArray" which takes a cell and repeats it NxM times on a fixed grid spacing. For convenience, PHIDL includes this functionality with the add_array() function.  Note that CellArrays are not compatible with ports (since there is no way to access/modify individual elements in a GDS cellarray)
Step6:  Finally we need to add ports to the new component
Step7:  Connecting references
connect
We have seen that once you create a reference you can manipulate the reference to move it to a location. Here we are going to connect that reference to a port. Remeber that we follow that a certain reference source connects to a destination port
Step8:  component_sequence
When you have repetitive connections you can describe the connectivity as an ASCII map
Step10:  As the sequence is defined as a string you can use the string operations to build complicated sequences
Step14:  component_lattice
You can also define a 2D map
<ASSISTANT_TASK:>
Python Code:
from IPython.display import Image
Image("../docs/images/gds.png")
Explanation: References
GDS allows defining the component once in memory and reference to that structure in other components.
End of explanation
import pp
Explanation: Adding a component reference
As we build complex circuit we'll make circuits that combine reference to other simpler circuits. Adding a reference is like having a pointer to the other devices.
End of explanation
c = pp.Component()
w = pp.c.waveguide(width=0.6)
wr = w.ref()
c.add(wr)
pp.qp(c)
Explanation: We have two ways to add a reference to our device:
create the reference and add it to the component
End of explanation
c = pp.Component()
wr = c << pp.c.waveguide(width=0.6)
pp.qp(c)
Explanation: or we can do it in a single line (my preference)
End of explanation
c = pp.Component()
wr1 = c << pp.c.waveguide(width=0.6)
wr2 = c << pp.c.waveguide(width=0.6)
wr2.movey(10)
pp.qp(c)
Explanation: in both cases we can move the reference wr after created
End of explanation
@pp.autoname
def dbr_cell(w1=0.5, w2=0.6, l1=0.2, l2=0.4, waveguide_function=pp.c.waveguide):
    c = pp.Component()
    c1 = c << waveguide_function(length=l1, width=w1)
    c2 = c << waveguide_function(length=l2, width=w2)
    c2.connect(port="W0", destination=c1.ports["E0"])
    c.add_port("W0", port=c1.ports["W0"])
    c.add_port("E0", port=c2.ports["E0"])
    return c
w1 = 0.5
w2 = 0.6
l1 = 0.2
l2 = 0.4
n = 3
waveguide_function = pp.c.waveguide
c = pp.Component()
cell = dbr_cell(w1=w1, w2=w2, l1=l1, l2=l2, waveguide_function=waveguide_function)
pp.qp(cell)
cell_array = c.add_array(device=cell, columns=n, rows=1, spacing=(l1 + l2, 100))
pp.qp(c)
Explanation: Adding a reference array
We can also add an array of references for periodic structures. Lets create a Distributed Bragg Reflector
As PHIDL tutorial says, in GDS, there's a type of structure called a "CellArray" which takes a cell and repeats it NxM times on a fixed grid spacing. For convenience, PHIDL includes this functionality with the add_array() function.  Note that CellArrays are not compatible with ports (since there is no way to access/modify individual elements in a GDS cellarray)
End of explanation
p0 = c.add_port("W0", port=cell.ports["W0"])
p1 = c.add_port("E0", port=cell.ports["E0"])
p1.midpoint = [(l1 + l2) * n, 0]
pp.qp(c)
Explanation: Finally we need to add ports to the new component
End of explanation
pp.qp(pp.c.bend_circular())
c = pp.Component("sample_reference_connect")
mmi = c << pp.c.mmi1x2()
b = c << pp.c.bend_circular()
b.connect("W0", destination=mmi.ports["E1"])
pp.qp(c)
Explanation: Connecting references
connect
We have seen that once you create a reference you can manipulate the reference to move it to a location. Here we are going to connect that reference to a port. Remeber that we follow that a certain reference source connects to a destination port
End of explanation
import pp
bend180 = pp.c.bend_circular180()
wg_heater = pp.c.waveguide_heater()
wg = pp.c.waveguide()
# Define a map between symbols and (component, input port, output port)
string_to_device_in_out_ports = {
    "A": (bend180, "W0", "W1"),
    "B": (bend180, "W1", "W0"),
    "H": (wg_heater, "W0", "E0"),
    "-": (wg, "W0", "E0"),
}
# Generate a sequence
# This is simply a chain of characters. Each of them represents a component
# with a given input and and a given output
sequence = "AB-H-H-H-H-BA"
component = pp.c.component_sequence(sequence, string_to_device_in_out_ports)
pp.qp(component)
pp.show(component)
Explanation: component_sequence
When you have repetitive connections you can describe the connectivity as an ASCII map
End of explanation
from pp.components.waveguide import _arbitrary_straight_waveguide
@pp.autoname
def phase_modulator_waveguide(length, wg_width=0.5, cladding=3.0, si_outer_clad=1.0):
    
    Phase modulator waveguide mockup
    
    a = wg_width / 2
    b = a + cladding
    c = b + si_outer_clad
    windows = [
        (-c, -b, pp.LAYER.WG),
        (-b, -a, pp.LAYER.SLAB90),
        (-a, a, pp.LAYER.WG),
        (a, b, pp.LAYER.SLAB90),
        (b, c, pp.LAYER.WG),
    ]
    component = _arbitrary_straight_waveguide(length=length, windows=windows)
    return component
@pp.autoname
def test_cutback_phase(straight_length=100.0, bend_radius=10.0, n=2):
    bend180 = pp.c.bend_circular(radius=bend_radius, start_angle=-90, theta=180)
    pm_wg = phase_modulator_waveguide(length=straight_length)
    wg_short = pp.c.waveguide(length=1.0)
    wg_short2 = pp.c.waveguide(length=2.0)
    wg_heater = pp.c.waveguide_heater(length=10.0)
    taper = pp.c.taper_strip_to_ridge()
    # Define a map between symbols and (component, input port, output port)
    string_to_device_in_out_ports = {
        "I": (taper, "1", "wg_2"),
        "O": (taper, "wg_2", "1"),
        "S": (wg_short, "W0", "E0"),
        "P": (pm_wg, "W0", "E0"),
        "A": (bend180, "W0", "W1"),
        "B": (bend180, "W1", "W0"),
        "H": (wg_heater, "W0", "E0"),
        "-": (wg_short2, "W0", "E0"),
    }
    # Generate a sequence
    # This is simply a chain of characters. Each of them represents a component
    # with a given input and and a given output
    repeated_sequence = "SIPOSASIPOSB"
    heater_seq = "-H-H-H-H-"
    sequence = repeated_sequence * n + "SIPO" + heater_seq
    return pp.c.component_sequence(sequence, string_to_device_in_out_ports)
c = test_cutback_phase(n=1)
pp.qp(c)
c = test_cutback_phase(n=2)
pp.qp(c)
Explanation: As the sequence is defined as a string you can use the string operations to build complicated sequences
End of explanation
import pp
components = {
    "C": pp.routing.package_optical2x2(component=pp.c.coupler, port_spacing=40.0),
    "X": pp.c.crossing45(port_spacing=40.0),
    "-": pp.c.compensation_path(crossing45=pp.c.crossing45(port_spacing=40.0)),
}
lattice = 
        CX
        CX
c = pp.c.component_lattice(lattice=lattice, components=components)
pp.qp(c)
lattice = 
        CCX
        CCX
c = pp.c.component_lattice(lattice=lattice, components=components)
pp.qp(c)
lattice = 
        C-X
        CXX
        CXX
        C-X
c = pp.c.component_lattice(lattice=lattice, components=components)
pp.qp(c)
Explanation: component_lattice
You can also define a 2D map
End of explanation
<END_TASK>