@@ -8,6 +8,9 @@ import .SchematicDrivenLayout.ExamplePDK
8
8
import . SchematicDrivenLayout. ExamplePDK: LayerVocabulary, L1_TARGET, add_bridges!
9
9
using . ExamplePDK. Transmons, . ExamplePDK. ReadoutResonators
10
10
import . ExamplePDK. SimpleJunctions: ExampleSimpleJunction
11
+ import DeviceLayout: uconvert
12
+
13
+ using PRIMA
11
14
12
15
"""
13
16
single_transmon(
@@ -34,34 +37,47 @@ function single_transmon(;
34
37
cap_width= 24 μm,
35
38
cap_length= 620 μm,
36
39
cap_gap= 30 μm,
40
+ total_length= 5000 μm,
37
41
n_meander_turns= 5 ,
38
42
hanger_length= 500 μm,
39
43
bend_radius= 50 μm,
40
44
save_mesh:: Bool = false ,
41
- save_gds:: Bool = false
45
+ save_gds:: Bool = false ,
46
+ mesh_order= 2
42
47
)
43
48
# ### Reset name counter for consistency within a Julia session
44
49
reset_uniquename! ()
45
- # Compute implicit parameters
46
- w_grasp = cap_width + 2 * cap_gap
47
- total_height = 444 μm + hanger_length + (3 + n_meander_turns * 2 ) * bend_radius
48
50
49
- # ### Create abstract components
51
+ # ### Assemble schematic graph
52
+ # ## Compute additional/implicit parameters
50
53
cpw_width = 10 μm
51
54
cpw_gap = 6 μm
52
55
PATH_STYLE = Paths. SimpleCPW (cpw_width, cpw_gap)
53
56
BRIDGE_STYLE = ExamplePDK. bridge_geometry (PATH_STYLE)
54
-
57
+ coupling_gap = 5 μm
58
+ w_grasp = cap_width + 2 * cap_gap
59
+ arm_length = 428 μm # straight length from meander exit to claw
60
+ total_height =
61
+ arm_length +
62
+ coupling_gap +
63
+ Paths. extent (PATH_STYLE) +
64
+ hanger_length +
65
+ (3 + n_meander_turns * 2 ) * bend_radius
66
+ # ## Create abstract components
67
+ # # Transmon
55
68
qubit = ExampleRectangleTransmon (;
56
69
jj_template= ExampleSimpleJunction (),
57
70
name= " qubit" ,
58
71
cap_length,
59
72
cap_gap,
60
73
cap_width
61
74
)
75
+ # # Resonator
62
76
rres = ExampleClawedMeanderReadout (;
63
77
name= " rres" ,
64
78
coupling_length= 400 μm,
79
+ coupling_gap,
80
+ total_length,
65
81
w_shield,
66
82
w_claw,
67
83
l_claw,
@@ -73,14 +89,6 @@ function single_transmon(;
73
89
bend_radius,
74
90
bridge= BRIDGE_STYLE
75
91
)
76
-
77
- # #### Build schematic graph
78
- g = SchematicGraph (" single-transmon" )
79
- qubit_node = add_node! (g, qubit)
80
- rres_node = fuse! (g, qubit_node, rres)
81
- # Equivalent to `fuse!(g, qubit_node=>:readout, rres=>:qubit)`
82
- # because `matching_hooks` was implemented for that component pair
83
-
84
92
# # Readout path
85
93
readout_length = 2700 μm
86
94
p_readout = Path (
@@ -92,22 +100,31 @@ function single_transmon(;
92
100
straight! (p_readout, readout_length / 2 , PATH_STYLE)
93
101
straight! (p_readout, readout_length / 2 , PATH_STYLE)
94
102
95
- # # Readout lumped ports - one at each end
103
+ # Readout lumped ports - squares on CPW trace, one at each end
96
104
csport = CoordinateSystem (uniquename (" port" ), nm)
97
105
render! (
98
106
csport,
99
107
only_simulated (centered (Rectangle (cpw_width, cpw_width))),
100
108
LayerVocabulary. PORT
101
109
)
110
+ # Attach with port center `cpw_width` from the end (instead of `cpw_width/2`) to avoid corner effects
102
111
attach! (p_readout, sref (csport), cpw_width, i= 1 ) # @ start
103
112
attach! (p_readout, sref (csport), readout_length / 2 - cpw_width, i= 2 ) # @ end
113
+
114
+ # ### Build schematic graph
115
+ g = SchematicGraph (" single-transmon" )
116
+ qubit_node = add_node! (g, qubit)
117
+ rres_node = fuse! (g, qubit_node, rres)
118
+ # Equivalent to `fuse!(g, qubit_node=>:readout, rres=>:qubit)`
119
+ # because `matching_hooks` was implemented for that component pair
104
120
p_readout_node = add_node! (g, p_readout)
121
+
105
122
# # Attach resonator to feedline
106
123
# Instead of `fuse!` we use a schematic-based `attach!` method to place it along the path
107
124
# Syntax is a mix of `fuse!` and how we attached the ports above
108
125
attach! (g, p_readout_node, rres_node => :feedline , 0 mm, location= 1 )
109
126
110
- # # Position the components.
127
+ # ### Create the schematic (position the components)
111
128
floorplan = plan (g)
112
129
add_bridges! (floorplan, BRIDGE_STYLE, spacing= 300 μm) # Add bridges to paths
113
130
@@ -120,8 +137,11 @@ function single_transmon(;
120
137
chip = centered (Rectangle (substrate_x, substrate_y), on_pt= center_xyz)
121
138
sim_area = centered (Rectangle (substrate_x, substrate_y), on_pt= center_xyz)
122
139
140
+ # Define bounds for bounding simulation box
123
141
render! (floorplan. coordinate_system, sim_area, LayerVocabulary. SIMULATED_AREA)
142
+ # postrendering operations in solidmodel target define metal = (WRITEABLE_AREA - METAL_NEGATIVE) + METAL_POSITIVE
124
143
render! (floorplan. coordinate_system, sim_area, LayerVocabulary. WRITEABLE_AREA)
144
+ # Define rectangle that gets extruded to generate substrate volume
125
145
render! (floorplan. coordinate_system, chip, LayerVocabulary. CHIP_AREA)
126
146
127
147
check! (floorplan)
@@ -134,37 +154,43 @@ function single_transmon(;
134
154
# resolution near edges of the geometry.
135
155
meshing_parameters = SolidModels. MeshingParameters (
136
156
mesh_scale= 1.0 ,
137
- mesh_order= 2 ,
157
+ α_default= 0.9 ,
158
+ mesh_order= mesh_order,
138
159
options= Dict (" General.Verbosity" => 1 ) # General Gmsh option input
139
160
)
140
161
render! (sm, floorplan, tech, meshing_parameters= meshing_parameters)
141
162
142
163
if save_mesh
164
+ # SolidModels.gmsh.option.set_number("General.NumThreads", 1) # Force single-threaded (deterministic) meshing
143
165
SolidModels. gmsh. model. mesh. generate (3 ) # runs without error
144
166
save (joinpath (@__DIR__ , " single_transmon.msh2" ), sm)
145
167
end
146
168
147
169
if save_gds
148
- # Render to GDS as well.
170
+ # Render to GDS as well, may be useful to debug SolidModel generation
149
171
c = Cell (" single_transmon" , nm)
150
- render! (c, floorplan, L1_TARGET, strict= :no )
172
+ # Use simulation=true to render simulation-only geometry, `strict=:no` to continue from errors
173
+ render! (c, floorplan, L1_TARGET, strict= :no , simulation= true )
151
174
flatten! (c)
152
175
save (joinpath (@__DIR__ , " single_transmon.gds" ), c)
153
176
end
154
177
return sm
155
178
end
156
179
157
180
"""
158
- configfile(sm::SolidModel; palace_build=nothing)
181
+ configfile(sm::SolidModel; palace_build=nothing, solver_order=2, amr=0 )
159
182
160
183
Given a `SolidModel`, assemble a dictionary defining a configuration file for use within
161
184
Palace.
162
185
163
186
- `sm`: The `SolidModel`from which to construct the configuration file
164
187
- `palace_build = nothing`: Path to a Palace build directory, used to perform validation of
165
188
the configuration file. If not present, no validation is performed.
189
+ - `solver_order = 2`: Finite element order (degree) for the solver. Palace supports arbitrary
190
+ high-order spaces.
191
+ - `amr = 0`: Maximum number of adaptive mesh refinement (AMR) iterations.
166
192
"""
167
- function configfile (sm:: SolidModel ; palace_build= nothing )
193
+ function configfile (sm:: SolidModel ; palace_build= nothing , solver_order = 2 , amr = 0 )
168
194
attributes = SolidModels. attributes (sm)
169
195
170
196
config = Dict (
@@ -175,9 +201,9 @@ function configfile(sm::SolidModel; palace_build=nothing)
175
201
),
176
202
" Model" => Dict (
177
203
" Mesh" => joinpath (@__DIR__ , " single_transmon.msh2" ),
178
- " L0" => DeviceLayout . ustrip (m, 1 SolidModels . STP_UNIT) , # um is Palace default; record it anyway
204
+ " L0" => 1e-6 , # um is Palace default; record it anyway
179
205
" Refinement" => Dict (
180
- " MaxIts" => 0 # Increase to enable AMR
206
+ " MaxIts" => amr # Nonzero to enable AMR
181
207
)
182
208
),
183
209
" Domains" => Dict (
@@ -231,8 +257,8 @@ function configfile(sm::SolidModel; palace_build=nothing)
231
257
]
232
258
),
233
259
" Solver" => Dict (
234
- " Order" => 1 ,
235
- " Eigenmode" => Dict (" N" => 2 , " Tol" => 1.0e-6 , " Target" => 2 , " Save" => 2 ),
260
+ " Order" => solver_order ,
261
+ " Eigenmode" => Dict (" N" => 2 , " Tol" => 1.0e-6 , " Target" => 1 , " Save" => 2 ),
236
262
" Linear" => Dict (" Type" => " Default" , " Tol" => 1.0e-7 , " MaxIts" => 500 )
237
263
)
238
264
)
@@ -257,12 +283,12 @@ Given a configuration dictionary, write and optionally run the Palace simulation
257
283
258
284
Writes `config.json` to `@__DIR__` in order to pass the configuration into Palace.
259
285
260
- - `config` - A configuration file defining the required fields for a Palace configuration file
261
- - `palace_build` - Path to a Palace build.
262
- - `np = 0` - Number of MPI processes to use in the call to Palace. If greater than 0 attempts
286
+ - `config`: A configuration file defining the required fields for a Palace configuration file
287
+ - `palace_build`: Path to a Palace build.
288
+ - `np = 0`: Number of MPI processes to use in the call to Palace. If greater than 0 attempts
263
289
to call palace from within the Julia shell. Requires correct specification of `ENV[PATH]`.
264
- - `nt = 1` - Number of OpenMp threads to use in the call to Palace (requires Palace built with
265
- OpenMp )
290
+ - `nt = 1`: Number of OpenMP threads to use in the call to Palace (requires Palace built with
291
+ OpenMP )
266
292
"""
267
293
function palace_job (config:: Dict ; palace_build, np= 0 , nt= 1 )
268
294
# Write the configuration file to json, ready for Palace ingestion
@@ -293,24 +319,136 @@ function palace_job(config::Dict; palace_build, np=0, nt=1)
293
319
freq = CSV. File (joinpath (postprodir, " eig.csv" ); header= 1 ) |> DataFrame
294
320
295
321
println (" Eigenmode Frequencies (GHz): " , freq[:, 2 ])
322
+ return freq[:, 2 ]
296
323
end
297
324
return nothing
298
325
end
299
326
300
327
"""
301
- main (palace_build, np=0 )
328
+ compute_eigenfrequencies (palace_build, np=1; solver_order=2, mesh_order=2, cap_length=620μm, total_length=5000μm) )
302
329
303
330
Given a build of Palace found at `palace_build`, assemble a `SolidModel` of the single
304
331
transmon example, mesh the geometry, define a configuration file for the geometry, and if
305
- `np > 0`, launch Palace from the provided `palace_build`.`
332
+ `np > 0`, launch Palace from the provided `palace_build`. (If `0`, use `palace_build`
333
+ only to validate config.)
334
+
335
+ # Keyword arguments
336
+
337
+ - `solver_order = 2`: Finite element order (degree) for the solver. Palace supports arbitrary
338
+ high-order spaces.
339
+ - `mesh_order = 2`: Polynomial order used to represent the element geometries in the mesh.
340
+ - `cap_length = 620μm`: Length of transmon island capacitor.
341
+ - `total_length = 5000μm`: Total length of readout resonator.
306
342
"""
307
- function main (palace_build, np= 0 )
343
+ function compute_eigenfrequencies (
344
+ palace_build,
345
+ np= 1 ;
346
+ solver_order= 2 ,
347
+ mesh_order= 2 ,
348
+ cap_length= 620 μm,
349
+ total_length= 5000 μm
350
+ )
308
351
# Construct the SolidModel
309
- @time " SolidModel + Meshing" sm = single_transmon (save_mesh= true )
352
+ @time " SolidModel + Meshing" sm = single_transmon (
353
+ save_mesh= true ;
354
+ cap_length= cap_length,
355
+ total_length= total_length,
356
+ mesh_order= mesh_order
357
+ )
310
358
# Assemble the configuration
311
- @time " Configuration" config = configfile (sm; palace_build)
359
+ @time " Configuration" config = configfile (sm; palace_build, solver_order = solver_order )
312
360
# Call Palace
313
- @time " Palace" palace_job (config; palace_build, np)
361
+ @time " Palace" freqs = palace_job (config; palace_build, np)
362
+ return freqs
363
+ end
364
+
365
+ """
366
+ frequency_targeting_errfunc(targets_GHz, palace_build, np, solver_order, mesh_order, freq_log, param_log)
367
+
368
+ Create an error function that can be minimized by an optimization routine to reach target frequencies.
369
+
370
+ The returned function takes parameters that control the transmon's capacitor length and resonator's total length.
371
+ It runs `compute_eigenfrequencies` and returns the mean squared relative error between the computed
372
+ eigenfrequencies and `targets_GHz`.
373
+ It also pushes frequencies and parameter values to the provided arrays `freq_log` and `param_log`.
374
+ """
375
+ function frequency_targeting_errfunc (
376
+ targets_GHz,
377
+ palace_build,
378
+ np,
379
+ solver_order,
380
+ mesh_order,
381
+ freq_log,
382
+ param_log
383
+ )
384
+ return function errfunc (x, p= ()) # Many optimizer interfaces require a second argument for fixed parameters
385
+ freqs = compute_eigenfrequencies (
386
+ palace_build,
387
+ np;
388
+ cap_length= (1 / x[1 ]^ 2 ) * 620 μm, # Transform so that frequency(x) is approximately linear
389
+ total_length= (1 / x[2 ]) * 5000 μm, # ... and x_i are all ~1
390
+ solver_order,
391
+ mesh_order
392
+ )[1 : 2 ] # [1:2] because technically Palace can find more than two eigenfrequencies
393
+ push! (freq_log, freqs) # Log for later convenience
394
+ push! (param_log, copy (x)) # Copy because `x` gets reused
395
+ return sum ((freqs .- targets_GHz) .^ 2 ./ targets_GHz .^ 2 ) / 2 # Mean squared relative error
396
+ end
397
+ end
398
+
399
+ """
400
+ run_optimization(palace_build, np=1; targets_GHz=[3.0, 4.0], reltol=1e-2, solver_order=2, mesh_order=2)
401
+
402
+ Optimize the transmon and resonator parameters to achieve target frequencies.
403
+
404
+ The objective function generates a new schematic, SolidModel, and mesh;
405
+ generates and validates a new Palace configuration file; runs Palace using the mesh
406
+ and configuration file; and returns the mean squared relative error between the
407
+ computed eigenfrequencies and `targets_GHz`.
408
+ The optimization routine stops when the mean squared relative error is less than `reltol^2`.
409
+
410
+ `np`, `solver_order`, and `mesh_order` are used for configuring and running Palace as in
411
+ `compute_eigenfrequencies`.
412
+ """
413
+ function run_optimization (
414
+ palace_build,
415
+ np= 1 ;
416
+ targets_GHz= [3.0 , 4.0 ],
417
+ reltol= 1e-2 ,
418
+ solver_order= 2 ,
419
+ mesh_order= 2
420
+ )
421
+ freq_log = []
422
+ param_log = []
423
+ errfunc = frequency_targeting_errfunc ( # Create the objective function for optimization
424
+ targets_GHz,
425
+ palace_build,
426
+ np,
427
+ solver_order,
428
+ mesh_order,
429
+ freq_log,
430
+ param_log
431
+ )
432
+ final_params, info = prima ( # Run the optimization
433
+ errfunc,
434
+ [1.0 , 1.0 ]; # Initial parameters
435
+ ftarget= reltol^ 2 , # Stop when `errfunc(x) < reltol^2`
436
+ xl= [0.6 , 0.6 ], # Lower bounds
437
+ xu= [1.4 , 1.4 ], # Upper bounds
438
+ rhobeg= 0.2 # Initial trust region radius
439
+ )
440
+ println ("""
441
+ Number of Palace runs: $(info. nf)
442
+ Initial parameters:
443
+ Transmon capacitor_length = 620.0μm
444
+ Resonator total_length = 5000.0μm
445
+ Initial frequencies: $(round .(first (freq_log), digits= 3 )) GHz
446
+ Final parameters:
447
+ Transmon capacitor_length = $(round (μm, 620.0 μm/ final_params[1 ]^ 2 , digits= 3 ))
448
+ Resonator total_length = $(round (μm, 5000.0 μm/ final_params[2 ], digits= 3 ))
449
+ Final frequencies: $(round .(last (freq_log), digits= 3 )) GHz
450
+ """ )
451
+ return final_params, info, freq_log, param_log
314
452
end
315
453
316
454
end # module
0 commit comments