Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ The format of this changelog is based on
+ Added support for `LineSegment` in SolidModel
+ Added `add_wave_ports!` to automatically place wave port boundaries where specified paths/routes intersect the simulation area
+ Added option to use wave ports instead of lumped ports in the single transmon example
- Fix bug where `Rounded` might incorrectly not apply to a `ClippedPolygon` with a
negative.
- Introduced `selection_tolerance` for `Rounded` which allows a rounding style to not
select a point unless it is within a tolerance of the target. This defaults to infinite,
but in a future major release will be reduced to a value consistent with floating point arithmetic.

## 1.6.0 (2025-10-16)

Expand Down
50 changes: 32 additions & 18 deletions examples/SingleTransmon/SingleTransmon.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ module SingleTransmon
using FileIO, CSV, DataFrames, JSON, JSONSchema
using DeviceLayout, DeviceLayout.SchematicDrivenLayout, DeviceLayout.PreferredUnits
import .SchematicDrivenLayout.ExamplePDK
import .SchematicDrivenLayout.ExamplePDK: LayerVocabulary, L1_TARGET, add_bridges!, add_wave_ports!
import .SchematicDrivenLayout.ExamplePDK:
LayerVocabulary, L1_TARGET, add_bridges!, add_wave_ports!
using .ExamplePDK.Transmons, .ExamplePDK.ReadoutResonators
import .ExamplePDK.SimpleJunctions: ExampleSimpleJunction
import DeviceLayout: uconvert
Expand Down Expand Up @@ -149,13 +150,23 @@ function single_transmon(;
render!(floorplan.coordinate_system, chip, LayerVocabulary.CHIP_AREA)

# Add wave ports
wave_ports && add_wave_ports!(floorplan, [p_readout_node], sim_area, 0.6mm, LayerVocabulary.WAVE_PORT)
wave_ports && add_wave_ports!(
floorplan,
[p_readout_node],
sim_area,
0.6mm,
LayerVocabulary.WAVE_PORT
)

check!(floorplan)

# Need to pass generated physical group names so they can be retained
if wave_ports
tech = ExamplePDK.singlechip_solidmodel_target("wave_port_1", "wave_port_2", "lumped_element")
tech = ExamplePDK.singlechip_solidmodel_target(
"wave_port_1",
"wave_port_2",
"lumped_element"
)
else
tech = ExamplePDK.singlechip_solidmodel_target("port_1", "port_2", "lumped_element")
end
Expand Down Expand Up @@ -201,7 +212,13 @@ Palace.
high-order spaces.
- `amr = 0`: Maximum number of adaptive mesh refinement (AMR) iterations.
"""
function configfile(sm::SolidModel; palace_build=nothing, solver_order=2, amr=0, wave_ports=false)
function configfile(
sm::SolidModel;
palace_build=nothing,
solver_order=2,
amr=0,
wave_ports=false
)
attributes = SolidModels.attributes(sm)

config = Dict(
Expand Down Expand Up @@ -245,22 +262,18 @@ function configfile(sm::SolidModel; palace_build=nothing, solver_order=2, amr=0,
"Attributes" => [attributes["exterior_boundary"]],
"Order" => 1
),
(wave_ports ?
(
wave_ports ?
(
"WavePort" => [
Dict(
"Index" => 1,
"Attributes" => [attributes["wave_port_1"]]
),
Dict(
"Index" => 2,
"Attributes" => [attributes["wave_port_2"]]
),],
)
: ()
Dict("Index" => 1, "Attributes" => [attributes["wave_port_1"]]),
Dict("Index" => 2, "Attributes" => [attributes["wave_port_2"]])
],
) : ()
)...,
"LumpedPort" => [
(wave_ports ? () :
(
wave_ports ? () :
(
Dict(
"Index" => 1,
Expand All @@ -273,7 +286,7 @@ function configfile(sm::SolidModel; palace_build=nothing, solver_order=2, amr=0,
"Attributes" => [attributes["port_2"]],
"R" => 50,
"Direction" => "+X"
),
)
)
)...,
Dict(
Expand All @@ -287,7 +300,8 @@ function configfile(sm::SolidModel; palace_build=nothing, solver_order=2, amr=0,
),
"Solver" => Dict(
"Order" => solver_order,
"Eigenmode" => Dict("N" => 2, "Tol" => 1.0e-6, "Target" => 2.5, "Save" => 2),
"Eigenmode" =>
Dict("N" => 2, "Tol" => 1.0e-6, "Target" => 2.5, "Save" => 2),
"Linear" => Dict("Type" => "Default", "Tol" => 1.0e-7, "MaxIts" => 500)
)
)
Expand Down
14 changes: 5 additions & 9 deletions src/curvilinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -455,19 +455,15 @@ function cornerindices(p::CurvilinearPolygon{T}) where {T}
valid_ind = setdiff(1:length(p.p), curve_bound_ind)
return valid_ind
end
function cornerindices(p::CurvilinearPolygon, p0::Vector{<:Point})
function cornerindices(p::CurvilinearPolygon, p0::Vector{<:Point}; tol)
isempty(p0) && return Int[]
valid_ind = cornerindices(p)
isempty(valid_ind) && return Int[]
# Pick the closest to p0
return valid_ind[map(p0) do px
idx = findfirst(p_idx -> isapprox(px, p_idx), p.p[valid_ind])
!isnothing(idx) && return idx
return findmin(norm.(p.p[valid_ind] .- px))[2]
end]
return isempty(valid_ind) ? Int[] : valid_ind[cornerindices(p.p[valid_ind], p0; tol)]
end
function cornerindices(p::CurvilinearPolygon, r::Polygons.Rounded)
corner_indices = isempty(p0(r)) ? cornerindices(p) : cornerindices(p, p0(r))
corner_indices =
isempty(p0(r)) ? cornerindices(p) :
cornerindices(p, p0(r); tol=r.selection_tolerance)
return r.inverse_selection ? setdiff(cornerindices(p), corner_indices) : corner_indices
end

Expand Down
56 changes: 45 additions & 11 deletions src/polygons.jl
Original file line number Diff line number Diff line change
Expand Up @@ -578,11 +578,16 @@ rounded_rect_discretized_poly = to_polygons(rounded_rect)
- `p0`: set of target points used to select vertices to attempt to round when
applied to a polygon. Selected vertices where `min_side_len` and
`min_angle` are satisfied will be rounded. If empty, all vertices will be selected.
Otherwise, for each point in `p0`, the nearest point in the styled polygon will be
selected. Note that for a `ClippedPolygon`, the same `p0` will be used for every
contour; for different rounding styles on different contours, use `StyleDict`.
Otherwise, for each point in `p0`, the nearest point that satisfies
`selection_tolerance` in the styled polygon will be selected. Note that for a
`ClippedPolygon`, the same `p0` will be used for every contour; for different rounding
styles on different contours, use `StyleDict`.
- `inverse_selection`: If true, the selection from `p0` is inverted;
that is, all corners will be rounded except those selected by `p0`.
- `selection_tolerance`: Selections using `p0` will only be chosen if they are within
`selection_tolerance` distance of `p0`. The current default of infinite reflects the
legacy behaviour of always finding the closest point, and will be replaced with a small
non-zero tolerance to capture floating point precision in future (approximately 1.0nm).
"""
Base.@kwdef struct Rounded{T <: Coordinate} <: GeometryEntityStyle
abs_r::T = zero(T)
Expand All @@ -591,18 +596,34 @@ Base.@kwdef struct Rounded{T <: Coordinate} <: GeometryEntityStyle
min_angle::Float64 = 1e-3
p0::Vector{Point{T}} = []
inverse_selection::Bool = false
selection_tolerance::T = T(Inf) # TODO: Set to floating point accurate in next major release
function Rounded{T}(
abs_r::Coordinate,
rel_r,
min_side_len,
min_angle,
p0,
inverse_selection
inverse_selection,
selection_tolerance
) where {T}
if !iszero(abs_r) && !iszero(rel_r)
throw(ArgumentError("`abs_r` and `rel_r` cannot both be non-zero"))
end
return new{T}(abs_r, rel_r, min_side_len, min_angle, p0, inverse_selection)
if !isempty(p0) && !isfinite(selection_tolerance)
Base.depwarn(
"Non-finite selection tolerance is deprecated, and will be replaced with `1.0nm` in future.",
:Rounded
)
end
return new{T}(
abs_r,
rel_r,
min_side_len,
min_angle,
p0,
inverse_selection,
selection_tolerance
)
end
end
Rounded(r::Coordinate; kwargs...) = Rounded{float(typeof(r))}(; abs_r=r, kwargs...)
Expand Down Expand Up @@ -634,16 +655,19 @@ end
cornerindices(p, x) = cornerindices(points(p), x)
cornerindices(p::Point, x) = cornerindices([p], x)
cornerindices(p::Vector{<:Point}, p0::Point) = cornerindices(p, [p0])
function cornerindices(p::Vector{<:Point}, p0::Vector{<:Point})
return map(p0) do px
function cornerindices(p::Vector{<:Point}, p0::Vector{<:Point}; tol)
# Pick the closest to p0 satisfying tolerance.
return filter!(x -> x > 0, map(p0) do px
idx = findfirst(p_idx -> isapprox(px, p_idx), p)
!isnothing(idx) && return idx
return findmin(norm.(p .- px))[2]
end
d, idx = findmin(norm.(p .- px))
return d < tol ? idx : -1
end)
end

function cornerindices(p::Vector{<:Point}, r)
corner_indices = isempty(p0(r)) ? eachindex(p) : cornerindices(p, p0(r))
function cornerindices(p::Vector{<:Point}, r::Rounded)
corner_indices =
isempty(p0(r)) ? eachindex(p) : cornerindices(p, p0(r); tol=r.selection_tolerance)
return r.inverse_selection ? setdiff(eachindex(p), corner_indices) : corner_indices
end

Expand Down Expand Up @@ -2221,6 +2245,16 @@ function to_polygons(p::ClippedPolygon, s::StyleDict; kwargs...)
return to_polygons(p; kwargs...)
end

function to_polygons(p::ClippedPolygon, s::Rounded; kwargs...)
p = deepcopy(p) # deepcopy so as not to modify
function stylenode!(n::Clipper.PolyNode)
n.contour = points.(to_polygons(Polygon(n.contour), s))
return stylenode!.(n.children)
end
stylenode!.(p.tree.children)
return to_polygons(p; kwargs...)
end

function transform(d::StyleDict, f::Transformation)
newdict = StyleDict(transform(d.default, f))
for (idx, s) in pairs(d.styles)
Expand Down
70 changes: 70 additions & 0 deletions test/test_shapes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,76 @@
rr = Polygons._round_poly(r, 500.0nm; corner_indices=[2, 4])
rrr = Polygons._round_poly(rr, 500.0nm; corner_indices=[1]) # Round point at (0,0)
@test !iszero(points(rrr)[1]) # first point would be (0,0) if rounding failed

# DeviceLayout#115 Applying Rounded to ClippedPolygon
r1 = centered(Rectangle(2.0μm, 2.0μm))
r2 = centered(Rectangle(4.0μm, 4.0μm))
p0 = [Point(1.0μm, 1.0μm)] # should be only point on outer poly
sty = Rounded(1.0μm; p0)

# Removes top right
pp = points(to_polygons(styled(r1, sty)))
@test Point(1.0μm, 1.0μm) ∉ pp
@test Point(-1.0μm, 1.0μm) ∈ pp
@test Point(-1.0μm, -1.0μm) ∈ pp
@test Point(1.0μm, -1.0μm) ∈ pp

# Removes top right
pp = points(to_polygons(styled(r2, sty)))
@test Point(2.0μm, 2.0μm) ∉ pp
@test Point(-2.0μm, 2.0μm) ∈ pp
@test Point(-2.0μm, -2.0μm) ∈ pp
@test Point(2.0μm, -2.0μm) ∈ pp

cc = difference2d(r2, r1)
csty = styled(cc, sty)
pp = points(to_polygons(csty)[1])
@test length(pp) > 11 # There are some rounded points
@test Point(1.0μm, 1.0μm) ∉ pp # rounded
@test Point(-1.0μm, 1.0μm) ∈ pp
@test Point(-1.0μm, -1.0μm) ∈ pp
@test Point(1.0μm, -1.0μm) ∈ pp

@test Point(2.0μm, 2.0μm) ∉ pp # rounded
@test Point(-2.0μm, 2.0μm) ∈ pp
@test Point(-2.0μm, -2.0μm) ∈ pp
@test Point(2.0μm, -2.0μm) ∈ pp

# Rounded style with tight tolerance
sty = Rounded(1.0μm; p0, selection_tolerance=1nm)

# Removes top right
pp = points(to_polygons(styled(r1, sty)))
@test Point(1.0μm, 1.0μm) ∉ pp # rounded
@test Point(-1.0μm, 1.0μm) ∈ pp
@test Point(-1.0μm, -1.0μm) ∈ pp
@test Point(1.0μm, -1.0μm) ∈ pp

# Removes top right
pp = points(to_polygons(styled(r2, sty)))
@test Point(2.0μm, 2.0μm) ∈ pp
@test Point(-2.0μm, 2.0μm) ∈ pp
@test Point(-2.0μm, -2.0μm) ∈ pp
@test Point(2.0μm, -2.0μm) ∈ pp

cc = difference2d(r2, r1)
csty = styled(cc, sty)
pp = points(to_polygons(csty)[1])
@test length(pp) > 11 # There are some rounded points
@test Point(1.0μm, 1.0μm) ∉ pp # rounded
@test Point(-1.0μm, 1.0μm) ∈ pp
@test Point(-1.0μm, -1.0μm) ∈ pp
@test Point(1.0μm, -1.0μm) ∈ pp

@test Point(2.0μm, 2.0μm) ∈ pp # not rounded
@test Point(-2.0μm, 2.0μm) ∈ pp
@test Point(-2.0μm, -2.0μm) ∈ pp
@test Point(2.0μm, -2.0μm) ∈ pp

cs = CoordinateSystem("abc", nm)
@test_nowarn place!(cs, csty, GDSMeta())
c = Cell("main", nm)
@test_nowarn render!(c, cs)
end

@testitem "Curvilinear" setup = [CommonTestSetup] begin
Expand Down
13 changes: 13 additions & 0 deletions test/test_solidmodel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,19 @@
@test_nowarn place!(cs, rs, SemanticMeta(:test))
@test_nowarn render!(sm, cs)

# Targetted rounding with tight tolerance should skip outer
cc = difference2d(centered(Rectangle(4.0μm, 4.0μm)), r)
sty = Polygons.Rounded(
1.0μm,
p0=[Point(1.0μm, 1.0μm), Point(-1.0μm, -1.0μm)],
selection_tolerance=1nm
)
ccs = styled(cc, sty)
cs = CoordinateSystem("test", nm)
sm = SolidModel("test"; overwrite=true)
@test_nowarn place!(cs, ccs, SemanticMeta(:test))
@test_nowarn render!(sm, cs)

# Reference transform should transform p0 too
r = to_polygons(Rectangle(2μm, 1μm))
cs_local = CoordinateSystem("test", nm)
Expand Down