Paths
A Paths.Path
is an ordered collection of Paths.Node
s, each of which has a Paths.Segment
and a Paths.Style
. The nodes are linked to each other, so each node knows what the previous and next nodes are.
Segments
Each subtype of Segment
can represent a class of parametric functions t->Point(x(t),y(t))
. By "class" we mean e.g. Paths.Straight
or Paths.Turn
, which are used frequently. Instances of these subtypes of Segment
have some captured variables to specify a particular path in the plane. Instances of Turn
, for example, will capture an initial and final angle, a radius, and an origin. All circular turns may be parameterized with these variables.
Note
This package assumes that the parametric functions are implemented such that $\sqrt{((dx/dt)^2 + (dy/dt)^2)} = 1$. In other words, t
ranges from zero to the path length of the segment.
Styles
Each subtype of Style
describes how to render a segment. You can create the most common styles using the constructors Paths.Trace
(a trace with some width) and Paths.CPW
(a coplanar waveguide style).
One can implement new styles by writing methods for render!
that dispatch on different style types. In this way, the rendering code can be specialized for the task at hand, improving performance and shrinking generated file sizes (ideally).
Tapers
As a convenience, this package provides functions for the automatic tapering of both Paths.Trace
and Paths.CPW
via the Paths.Taper
constructor. Alternatively, one can specify the tapers concretely by calling their respective constructors.
The following example illustrates the use of automatic tapering. First, we construct a taper with two different traces surrounding it:
using Devices, Devices.PreferMicrons p = Path(μm) straight!(p, 10μm, Paths.Trace(2.0μm)) straight!(p, 10μm, Paths.Taper()) straight!(p, 10μm, Paths.Trace(4.0μm))
The taper is automatically chosen to be a Paths.Trace
, with appropriate initial (2.0 μm
) and final (4.0 μm
) widths. The next segment shows that we can even automatically taper between the current Paths.Trace
and a hard-coded taper (of concrete type Paths.TaperTrace
), matching to the dimensions at the beginning of the latter taper.
straight!(p, 10μm, Paths.Taper()) straight!(p, 10μm, Paths.TaperTrace(2.0μm, 1.0μm))
As a final example, Paths.Taper
can also be used in turn!
segments, and as a way to automatically transition from a Paths.Taper
to a Paths.CPW
, or vice-versa:
turn!(p, -π/2, 10μm, Paths.Taper()) straight!(p, 10μm, Paths.Trace(2.0μm)) straight!(p, 10μm, Paths.Taper()) straight!(p, 10μm, Paths.CPW(2.0μm, 1.0μm)) c = Cell("tapers", nm) render!(c, p, GDSMeta(0))
Documenter.Documents.RawHTML("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
Corners
Sharp turns in a path can be accomplished with Paths.corner!
. Sharp turns pose a challenge to the path abstraction in that they have zero length, and when rendered effectively take up some length of the neighboring segments. These details are automatically accounted for by tweaking segment lengths and other captured segment variables just before rendering a path.
Attachments
attach!
is one of the most useful functions defined in this package.
When you call attach!
, you are defining a coordinate system local to somewhere along the target Path
, saying that a CellReference
should be placed at the origin of that coordinate system (or slightly away from it if you want the cell to be one one side of the path or the other). The local coordinate system will rotate as the path changes orientations. The origin of the CellReference
corresponds how the referenced cell should be displaced with respect to the origin of the local coordinate system. This differs from the usual meaning of the origin of a CellReference
, which is how the referenced cell should be displaced with respect to the origin of a containing Cell
.
The same CellReference
can be attached to multiple points along multiple paths. If the cell reference is modified (e.g. rotation, origin, magnification) before rendering, the changes should be reflected at all attachment points. The attachment of the cell reference is not a perfect abstraction: a CellReference
must ultimately live inside a Cell
, but an unrendered Path
does not live inside any cell. If the path is modified further before rendering, the attachment points will follow the path modifications, moving the origins of the local coordinate systems. The origin fields of the cell references do not change as the path is modified.
Attachments are implemented by introducing a Paths.DecoratedStyle
, which is kind of a meta-Style
: it remembers where to attach CellReferences
, but how the path itself is actually drawn is deferred to a different Style
object that it retains a reference to. One can repeat a DecoratedStyle
with one attachment to achieve a periodic placement of CellReferences
(like a CellArray
, but along the path). Or, one long segment with a DecoratedStyle
could have several attachments to achieve a similar effect.
When a Path
is rendered, it is turned into Polygons
living in some Cell
. The attachments remain CellReferences
, now living inside of a Cell
and not tied to an abstract path. The notion of local coordinate systems along the path no longer makes sense because the abstract path has been made concrete, and the polygons are living in the coordinate system of the containing cell. Each attachment to the former path now must have its origin referenced to the origin of the containing cell, not to local path coordinate systems. Additionally, the references may need to rotate according to how the path was locally oriented. As a result, even if the same CellReference
was attached multiple times to a path, now we need distinct CellReference
objects for each attachment, as well as for each time a corresponding DecoratedStyle
is rendered.
Suppose we want the ability to transform between coordinate systems, especially between the coordinate system of a referenced cell and the coordinate system of a parent cell. At first glance it would seem like we could simply define a transform function, taking the parent cell and the cell reference we are interested in. But how would we actually identify the particular cell reference we want? Looking in the tree of references for an attached CellReference
will not work: distinct CellReferences
needed to be made after the path was rendered, and so the particular CellReference
object initially attached is not actually in the Cell
containing the rendered path.
To overcome this problem, we make searching for the appropriate CellReference
easier. Suppose a path with attachments has been rendered to a Cell
, which is bound to symbol aaa
. A CellReference
referring to a cell named "bbb" was attached twice. To recall the second attachment: aaa["bbb",2]
(the index defaults to 1 if unspecified). We can go deeper if we want to refer to references inside that attachment: aaa["bbb",2]["ccc"]
. In this manner, it is easy to find the right CellReference
to use with Cells.transform(::Cell, ::Cells.CellRef)
.
Path API
Path construction
#
Devices.Paths.Path
— Type.
mutable struct Path{T<:Coordinate} <: AbstractVector{Node{T}} p0::Point{T} α0::Float64 nodes::Array{Node{T},1} end
Type for abstracting an arbitrary styled path in the plane. Iterating returns Paths.Node
objects, essentially
#
Devices.Paths.Path
— Method.
Path(p0::Point=Point(0.0,0.0); α0=0.0) Path(p0x::Real, p0y::Real; kwargs...) Path{T<:Length}(p0::Point{T}; α0=0.0) Path{T<:Length}(p0x::T, p0y::T; kwargs...) Path(p0x::Length, p0y::Length; kwargs...) Path(u::LengthUnits; α0=0.0)
Convenience constructors for Path{T}
object.
Path interrogation
#
Devices.Paths.direction
— Function.
direction(s, t)
Returns the angle at which some function t->Point(x(t),y(t))
is pointing.
#
Devices.Paths.pathlength
— Function.
pathlength(p::Path) pathlength{T}(array::AbstractArray{Node{T}}) pathlength{T<:Segment}(array::AbstractArray{T}) pathlength(node::Node)
Physical length of a path. Note that length
will return the number of segments in a path, not the physical length of the path.
#
Devices.Paths.p0
— Function.
p0{T}(s::Segment{T})
Return the first point in a segment (calculated).
p0(p::Path)
First point of a path.
#
Devices.Paths.α0
— Function.
α0(s::Segment)
Return the first angle in a segment (calculated).
α0(p::Path)
First angle of a path.
#
Devices.Paths.p1
— Function.
p1{T}(s::Segment{T})
Return the last point in a segment (calculated).
p1(p::Path)
Last point of a path.
#
Devices.Paths.α1
— Function.
α1(s::Segment)
Return the last angle in a segment (calculated).
α1(p::Path)
Last angle of a path.
#
Devices.Paths.style0
— Function.
style0(p::Path)
Style of the first segment of a path.
#
Devices.Paths.style1
— Function.
style1(p::Path)
Style of the last segment of a path.
#
Devices.Paths.discretestyle1
— Function.
discretestyle1{T}(p::Path{T})
Returns the last-used discrete style in the path.
#
Devices.Paths.contstyle1
— Function.
contstyle1(p::Path)
Returns the last-used discrete style in the path.
Path manipulation
#
Devices.Paths.setp0!
— Function.
setp0!(s::Straight, p::Point)
Set the p0 of a straight segment.
setp0!(s::Turn, p::Point)
Set the p0 of a turn.
#
Devices.Paths.setα0!
— Function.
setα0!(s::Straight, α0′)
Set the angle of a straight segment.
setα0!(s::Turn, α0′)
Set the starting angle of a turn.
#
Base.append!
— Method.
append!(p::Path, p′::Path)
Given paths p
and p′
, path p′
is appended to path p
. The p0 and initial angle of the first segment from path p′
is modified to match the last point and last angle of path p
.
#
Devices.Paths.adjust!
— Function.
adjust!(p::Path, n::Integer=1)
Adjust a path's parametric functions starting from index n
. Used internally whenever segments are inserted into the path.
#
Devices.Paths.attach!
— Function.
attach!(p::Path, c::CellReference, t::Coordinate; i::Integer=length(p), location::Integer=0) attach!(p::Path, c::CellReference, t; i::Integer=length(p), location=zeros(Int, length(t)))
Attach c
along a path. The second method permits ranges or arrays of t
and location
to be specified (if the lengths do not match, location
is cycled).
By default, the attachment(s) occur at t ∈ [zero(pathlength(s)),pathlength(s)]
along the most recent path segment s
, but a different path segment index can be specified using i
. The reference is oriented with zero rotation if the path is pointing at 0°, otherwise it is rotated with the path.
The origin of the cell reference tells the method where to place the cell with respect to a coordinate system that rotates with the path. Suppose the path is a straight line with angle 0°. Then an origin of Point(0.,10.)
will put the cell at 10 above the path, or 10 to the left of the path if it turns left by 90°.
The location
option is for convenience. If location == 0
, nothing special happens. If location == -1
, then the point of attachment for the reference is on the leftmost edge of the waveguide (the rendered polygons; the path itself has no width). Likewise if location == 1
, the point of attachment is on the rightmost edge. This option does not automatically rotate the cell reference, apart from what is already done as described in the first paragraph. You can think of this option as setting a special origin for the coordinate system that rotates with the path. For instance, an origin for the cell reference of Point(0.,10.)
together with location == -1
will put the cell at 10 above the edge of a rendered (finite width) path with angle 0°.
#
Devices.Paths.corner!
— Function.
corner!{T<:Coordinate}(p::Path{T}, α, sty::DiscreteStyle=discretestyle1(p))
Append a sharp turn or "corner" to path p
with angle α
.
The style chosen for this corner, if not specified, is the last DiscreteStyle
used in the path.
#
Devices.Paths.meander!
— Function.
meander!(p::Path, len, straightlen, r, α)
Alternate between going straight with length straightlen
and turning with radius r
and angle α
. Each turn goes the opposite direction of the previous. The total length is len
. Useful for making resonators.
The straight and turn segments are combined into a CompoundSegment
and appended to the path p
.
#
Devices.Paths.simplify
— Function.
simplify(p::Path, inds::UnitRange=1:length(p))
At inds
, segments of a path are turned into a CompoundSegment
and styles of a path are turned into a CompoundStyle
. The method returns a tuple, (segment, style)
.
- Indexing the path becomes more sane when you can combine several path
segments into one logical element. A launcher would have several indices in a path unless you could simplify it.
- You don't need to think hard about boundaries between straights and turns
when you want a continuous styling of a very long path.
#
Devices.Paths.simplify!
— Function.
simplify!(p::Path, inds::UnitRange=1:length(p))
In-place version of simplify
.
#
Devices.Paths.straight!
— Function.
straight!{T<:Coordinate}(p::Path{T}, l::Coordinate, sty::ContinuousStyle=contstyle1(p))
Extend a path p
straight by length l
in the current direction. By default, we take the last continuous style in the path.
#
Devices.Paths.turn!
— Function.
turn!{T<:Coordinate}(p::Path{T}, α, r::Coordinate, sty::Style=contstyle1(p))
Turn a path p
by angle α
with a turning radius r
in the current direction. Positive angle turns left. By default, we take the last continuous style in the path.
turn!{T<:Coordinate}(p::Path{T}, s::String, r::Coordinate, sty::ContinuousStyle=contstyle1(p))
Turn a path p
with direction coded by string s
:
- "l": turn by π/2 radians (left)
- "r": turn by -π/2 radians (right)
- "lrlrllrrll": do those turns in that order
By default, we take the last continuous style in the path.
Node API
Node construction
#
Devices.Paths.Node
— Type.
Node{T}(a::Segment{T}, b::Style)
Create a node with segment a
and style b
.
Node methods
#
Devices.Paths.previous
— Function.
previous(x::Node)
Return the node before x
in a doubly linked list.
#
Devices.Paths.next
— Function.
next(x::Node)
Return the node after x
in a doubly linked list.
#
Devices.Paths.segment
— Function.
segment(x::Node)
Return the segment associated with node x
.
#
Devices.Paths.style
— Function.
style(x::Node)
Return the style associated with node x
.
#
Devices.Paths.setsegment!
— Function.
setsegment!(x::Node, s::Segment)
Set the segment associated with node x
to s
.
#
Devices.Paths.setstyle!
— Function.
setstyle!(x::Node, s::Style)
Set the style associated with node x
to s
.
Segment API
Abstract types
#
Devices.Paths.Segment
— Type.
abstract type Segment{T<:Coordinate} end
Path segment in the plane. All Segment objects should have the implement the following methods:
pathlength
p0
α0
setp0!
setα0!
α1
Concrete types
#
Devices.Paths.Straight
— Type.
mutable struct Straight{T} <: ContinuousSegment{T} l::T p0::Point{T} α0::typeof(0.0°) end
A straight line segment is parameterized by its length. It begins at a point p0
with initial angle α0
.
The parametric function describing the line segment is given by t -> p0 + Point(t*cos(α),t*sin(α))
where t
is a length from 0 to l
.
#
Devices.Paths.Turn
— Type.
mutable struct Turn{T} <: ContinuousSegment{T} α::typeof(1.0°) r::T p0::Point{T} α0::typeof(1.0°) end
A circular turn is parameterized by the turn angle α
and turning radius r
. It begins at a point p0
with initial angle α0
.
The center of the circle is given by:
cen = p0 + Point(r*cos(α0+sign(α)*π/2), r*sin(α0+sign(α)*π/2))
The parametric function over t ∈ [0,1]
describing the turn is given by:
t -> cen + Point(r*cos(α0-sign(α)*π/2+α*t), r*sin(α0-sign(α)*π/2+α*t))
#
Devices.Paths.Corner
— Type.
mutable struct Corner{T} <: DiscreteSegment{T} α::Float64 p0::Point{T} α0::Float64 extent::T Corner(a) = new(a, Point(zero(T), zero(T)), 0.0, zero(T)) Corner(a,b,c,d) = new(a,b,c,d) end
A corner, or sudden kink in a path. The only parameter is the angle α
of the kink. The kink begins at a point p0
with initial angle α0
. It will also end at p0
, since the corner has zero path length. However, during rendering, neighboring segments will be tweaked slightly so that the rendered path is properly centered about the path function (the rendered corner has a finite width).
#
Devices.Paths.CompoundSegment
— Type.
struct CompoundSegment{T} <: ContinuousSegment{T} segments::Vector{Segment{T}} CompoundSegment(segments) = begin if any(x->isa(x,Corner), segments) error("cannot have corners in a `CompoundSegment`. You may have ", "tried to simplify a path containing `Corner` objects.") else new(deepcopy(Array(segments))) end end end
Consider an array of segments as one contiguous segment. Useful e.g. for applying styles, uninterrupted over segment changes. The array of segments given to the constructor is copied and retained by the compound segment.
Note that Corner
s introduce a discontinuity in the derivative of the path function, and are not allowed in a CompoundSegment
.
Style API
Constructors and methods
#
Devices.Paths.Trace
— Type.
Trace(width) Trace(width::Coordinate) Trace(width_start::Coordinate, width_end::Coordinate)
Constructor for Trace styles. Automatically chooses SimpleTrace
, GeneralTrace
, and TaperTrace
as appropriate.
#
Devices.Paths.CPW
— Type.
CPW(trace::Coordinate, gap::Coordinate) CPW(trace, gap::Coordinate) CPW(trace::Coordinate, gap) CPW(trace, gap) CPW(trace_start::Coordinate, gap_start::Coordinate, trace_end::Coordinate, gap_end::Coordinate)
Constructors for CPW styles. Automatically chooses between SimpleCPW
, GeneralCPW
, or TaperCPW
styles as appropriate.
#
Devices.Paths.Taper
— Type.
Taper()
Constructor for generic Taper style. Will automatically create a linearly tapered region between an initial CPW
or Trace
and an end CPW
or Trace
of different dimensions.
#
Devices.Paths.Strands
— Type.
Strands(offset::Coordinate, width::Coordinate, spacing::Coordinate, num::Int) Strands(offset, width::Coordinate, spacing::Coordinate, num::Int) Strands(offset::Coordinate, width, spacing::Coordinate, num::Int) Strands(offset::Coordinate, width::Coordinate, spacing, num::Int) Strands(offset::Coordinate, width, spacing, num::Int) Strands(offset, width::Coordinate, spacing, num::Int) Strands(offset, width, spacing::Coordinate, num::Int) Strands(offset, width, spacing, num::Int) example for num = 2 ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| <-><---><-><-----------|-----------><-><---><-> w s w offset w s w
Constructors for Strands styles. Automatically chooses between SimpleStrands
or GeneralStrands
styles as appropriate.
#
Devices.Paths.undecorated
— Function.
undecorated(s::DecoratedStyle) undecorated(s::Style)
Returns the underlying, undecorated style if decorated; otherwise just return the style.
Abstract types
#
Devices.Paths.Style
— Type.
abstract type Style end
How to render a given path segment. All styles should implement the following methods:
extent
width
#
Devices.Paths.ContinuousStyle
— Type.
abstract type ContinuousStyle <: Style end
Any style that applies to segments which have non-zero path length.
#
Devices.Paths.DiscreteStyle
— Type.
abstract type DiscreteStyle <: Style end
Any style that applies to segments which have zero path length.
Concrete types
#
Devices.Paths.SimpleTrace
— Type.
struct SimpleTrace{T<:Coordinate} <: Trace width::T end
A single trace with fixed width as a function of path length.
#
Devices.Paths.GeneralTrace
— Type.
struct GeneralTrace{T} <: Trace width::T end
A single trace with variable width as a function of path length. width
is callable.
#
Devices.Paths.SimpleCPW
— Type.
struct SimpleCPW{T<:Coordinate} <: CPW trace::T gap::T end
A CPW with fixed trace and gap as a function of path length.
#
Devices.Paths.GeneralCPW
— Type.
struct GeneralCPW{S,T} <: CPW trace::S gap::T end
A CPW with variable trace and gap as a function of path length. trace
and gap
are callable.
#
Devices.Paths.TaperTrace
— Type.
struct TaperTrace{T<:Coordinate} <: Trace width_start::T width_end::T end
A single trace with a linearly tapered width as a function of path length.
#
Devices.Paths.TaperCPW
— Type.
struct TaperCPW{T<:Coordinate} <: CPW trace_start::T gap_start::T trace_end::T gap_end::T end
A CPW with a linearly tapered trace and gap as a function of path length.
#
Devices.Paths.SimpleStrands
— Type.
struct SimpleStrands{T<:Coordinate} <: Strands offset::T width::T spacing::T num::Int end example for num = 2 ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| <-><---><-><-----------|-----------><-><---><-> w s w offset w s w
Strands with fixed center offset, width, and spacing as a function of path length.
#
Devices.Paths.GeneralStrands
— Type.
struct GeneralStrands{S,T,U} <: Strands offset::S width::T spacing::U num::Int end example for num = 2 ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| ||| <-><---><-><-----------|-----------><-><---><-> w s w offset w s w
Strands with variable center offset, width, and spacing as a function of path length. offset
, width
, and spacing
are callable.
#
Devices.Paths.CompoundStyle
— Type.
struct CompoundStyle <: ContinuousStyle styles::Vector{Style} grid::Vector{Float64} end
Combines styles together, typically for use with a CompoundSegment
.
styles
: Array of styles making up the object. This is shallow-copied
by the outer constructor.
grid
: An array oft
values needed for rendering the parameteric path.
#
Devices.Paths.DecoratedStyle
— Type.
mutable struct DecoratedStyle{T<:FloatCoordinate} <: ContinuousStyle s::Style ts::Array{Float64,1} dirs::Array{Int,1} refs::Array{CellReference,1} end
Style with decorations, like structures periodically repeated along the path, etc.