@@ -8,6 +8,9 @@ import .SchematicDrivenLayout.ExamplePDK
88import . SchematicDrivenLayout. ExamplePDK: LayerVocabulary, L1_TARGET, add_bridges!
99using . ExamplePDK. Transmons, . ExamplePDK. ReadoutResonators
1010import . ExamplePDK. SimpleJunctions: ExampleSimpleJunction
11+ import DeviceLayout: uconvert
12+
13+ using PRIMA
1114
1215"""
1316 single_transmon(
@@ -34,34 +37,47 @@ function single_transmon(;
3437 cap_width= 24 μm,
3538 cap_length= 620 μm,
3639 cap_gap= 30 μm,
40+ total_length= 5000 μm,
3741 n_meander_turns= 5 ,
3842 hanger_length= 500 μm,
3943 bend_radius= 50 μm,
4044 save_mesh:: Bool = false ,
41- save_gds:: Bool = false
45+ save_gds:: Bool = false ,
46+ mesh_order= 2
4247)
4348 # ### Reset name counter for consistency within a Julia session
4449 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
4850
49- # ### Create abstract components
51+ # ### Assemble schematic graph
52+ # ## Compute additional/implicit parameters
5053 cpw_width = 10 μm
5154 cpw_gap = 6 μm
5255 PATH_STYLE = Paths. SimpleCPW (cpw_width, cpw_gap)
5356 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
5568 qubit = ExampleRectangleTransmon (;
5669 jj_template= ExampleSimpleJunction (),
5770 name= " qubit" ,
5871 cap_length,
5972 cap_gap,
6073 cap_width
6174 )
75+ # # Resonator
6276 rres = ExampleClawedMeanderReadout (;
6377 name= " rres" ,
6478 coupling_length= 400 μm,
79+ coupling_gap,
80+ total_length,
6581 w_shield,
6682 w_claw,
6783 l_claw,
@@ -73,14 +89,6 @@ function single_transmon(;
7389 bend_radius,
7490 bridge= BRIDGE_STYLE
7591 )
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-
8492 # # Readout path
8593 readout_length = 2700 μm
8694 p_readout = Path (
@@ -92,22 +100,31 @@ function single_transmon(;
92100 straight! (p_readout, readout_length / 2 , PATH_STYLE)
93101 straight! (p_readout, readout_length / 2 , PATH_STYLE)
94102
95- # # Readout lumped ports - one at each end
103+ # Readout lumped ports - squares on CPW trace, one at each end
96104 csport = CoordinateSystem (uniquename (" port" ), nm)
97105 render! (
98106 csport,
99107 only_simulated (centered (Rectangle (cpw_width, cpw_width))),
100108 LayerVocabulary. PORT
101109 )
110+ # Attach with port center `cpw_width` from the end (instead of `cpw_width/2`) to avoid corner effects
102111 attach! (p_readout, sref (csport), cpw_width, i= 1 ) # @ start
103112 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
104120 p_readout_node = add_node! (g, p_readout)
121+
105122 # # Attach resonator to feedline
106123 # Instead of `fuse!` we use a schematic-based `attach!` method to place it along the path
107124 # Syntax is a mix of `fuse!` and how we attached the ports above
108125 attach! (g, p_readout_node, rres_node => :feedline , 0 mm, location= 1 )
109126
110- # # Position the components.
127+ # ### Create the schematic (position the components)
111128 floorplan = plan (g)
112129 add_bridges! (floorplan, BRIDGE_STYLE, spacing= 300 μm) # Add bridges to paths
113130
@@ -120,8 +137,11 @@ function single_transmon(;
120137 chip = centered (Rectangle (substrate_x, substrate_y), on_pt= center_xyz)
121138 sim_area = centered (Rectangle (substrate_x, substrate_y), on_pt= center_xyz)
122139
140+ # Define bounds for bounding simulation box
123141 render! (floorplan. coordinate_system, sim_area, LayerVocabulary. SIMULATED_AREA)
142+ # postrendering operations in solidmodel target define metal = (WRITEABLE_AREA - METAL_NEGATIVE) + METAL_POSITIVE
124143 render! (floorplan. coordinate_system, sim_area, LayerVocabulary. WRITEABLE_AREA)
144+ # Define rectangle that gets extruded to generate substrate volume
125145 render! (floorplan. coordinate_system, chip, LayerVocabulary. CHIP_AREA)
126146
127147 check! (floorplan)
@@ -134,37 +154,43 @@ function single_transmon(;
134154 # resolution near edges of the geometry.
135155 meshing_parameters = SolidModels. MeshingParameters (
136156 mesh_scale= 1.0 ,
137- mesh_order= 2 ,
157+ α_default= 0.9 ,
158+ mesh_order= mesh_order,
138159 options= Dict (" General.Verbosity" => 1 ) # General Gmsh option input
139160 )
140161 render! (sm, floorplan, tech, meshing_parameters= meshing_parameters)
141162
142163 if save_mesh
164+ # SolidModels.gmsh.option.set_number("General.NumThreads", 1) # Force single-threaded (deterministic) meshing
143165 SolidModels. gmsh. model. mesh. generate (3 ) # runs without error
144166 save (joinpath (@__DIR__ , " single_transmon.msh2" ), sm)
145167 end
146168
147169 if save_gds
148- # Render to GDS as well.
170+ # Render to GDS as well, may be useful to debug SolidModel generation
149171 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 )
151174 flatten! (c)
152175 save (joinpath (@__DIR__ , " single_transmon.gds" ), c)
153176 end
154177 return sm
155178end
156179
157180"""
158- configfile(sm::SolidModel; palace_build=nothing)
181+ configfile(sm::SolidModel; palace_build=nothing, solver_order=2, amr=0 )
159182
160183Given a `SolidModel`, assemble a dictionary defining a configuration file for use within
161184Palace.
162185
163186 - `sm`: The `SolidModel`from which to construct the configuration file
164187 - `palace_build = nothing`: Path to a Palace build directory, used to perform validation of
165188 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.
166192"""
167- function configfile (sm:: SolidModel ; palace_build= nothing )
193+ function configfile (sm:: SolidModel ; palace_build= nothing , solver_order = 2 , amr = 0 )
168194 attributes = SolidModels. attributes (sm)
169195
170196 config = Dict (
@@ -175,9 +201,9 @@ function configfile(sm::SolidModel; palace_build=nothing)
175201 ),
176202 " Model" => Dict (
177203 " 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
179205 " Refinement" => Dict (
180- " MaxIts" => 0 # Increase to enable AMR
206+ " MaxIts" => amr # Nonzero to enable AMR
181207 )
182208 ),
183209 " Domains" => Dict (
@@ -231,8 +257,8 @@ function configfile(sm::SolidModel; palace_build=nothing)
231257 ]
232258 ),
233259 " 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 ),
236262 " Linear" => Dict (" Type" => " Default" , " Tol" => 1.0e-7 , " MaxIts" => 500 )
237263 )
238264 )
@@ -257,12 +283,12 @@ Given a configuration dictionary, write and optionally run the Palace simulation
257283
258284Writes `config.json` to `@__DIR__` in order to pass the configuration into Palace.
259285
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
263289 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 )
266292"""
267293function palace_job (config:: Dict ; palace_build, np= 0 , nt= 1 )
268294 # 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)
293319 freq = CSV. File (joinpath (postprodir, " eig.csv" ); header= 1 ) |> DataFrame
294320
295321 println (" Eigenmode Frequencies (GHz): " , freq[:, 2 ])
322+ return freq[:, 2 ]
296323 end
297324 return nothing
298325end
299326
300327"""
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) )
302329
303330Given a build of Palace found at `palace_build`, assemble a `SolidModel` of the single
304331transmon 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.
306342"""
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+ )
308351 # 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+ )
310358 # Assemble the configuration
311- @time " Configuration" config = configfile (sm; palace_build)
359+ @time " Configuration" config = configfile (sm; palace_build, solver_order = solver_order )
312360 # 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
314452end
315453
316454end # module
0 commit comments