QDO QuickStart
This quick start guide takes the example of a single transmon-resonator system to exemplify the setup of the optimization project. Based on this concept more complex optimization problems can be constructed.
Installation
For the installation we refer back to the installation guide Installation.
Project structure
Every optimization project requires a set of files defining the optimization problem:
project_root/
├── design_variables.json
├── design_variable_names.py
├── design_constants.py
├── design.py
├── main.ipynb
├── mini_studies.py
├── optimization_targets.py
├── plot_settings.py
├── parameter_targets.py
Hereafter, we will refer to each file individually and highlight the important concepts to set up the project.
Project setup
Mode Names and Component Names
mode_name_identifier
composed by the convenience function mode(mode_type, identifier)
in utils.names_parameters.py
, for example qubit_1
. The mode type can for example be resonator
, qubit
, cavity
, or coupler
. As an identifier we suggest a count of the component group or of the component in a group of components.name_identifier
, for example name_qubit_1
or name_tee_1
. The identifier can refer to mode name such as qubit_1
or resonator_1
. A collection of common Qiskit Metal component names can be directly called from utils.names_qiskit_components
or custom-made in the project file names.py
.Design Variable Names and Values
design_var_
followed by an identifier which indicates what the design variable controls, for example design_var_length_resonator_1
. A collection of common design variable names can be directly called from utils.names_design_variables.py
. User specific component names can be added in and called from names.py
in the project folder.design_variables.json
in the project folder and provided to the optimizer.Design
RouteMeander
resonator. In this example, we show that the design variable substitutes the otherwise commonly static design definition for the total resonator length.import design_variable_names as u
from qiskit_metal.qlibrary.tlines.meandered import RouteMeander
resonator_options = dict(
total_length=n.design_var_length(resonator),
trace_width='20um',
trace_gap='20um',
)
RouteMeander(design, n.name_mode(resonator), options=resonator_options)
Finally, the design can be instantiated by the create_chip_base
method and rendered with the components and the design variables. A wrapper function (by convention called render_qiskit_metal_design
), must be created such that it can be passed into the optimizer. A minimal example looks like this:
import design as d
import names as n
from qdesignoptimizer.utils.utils_design import create_chip_base, ChipType
CHIP_NAME = "transmon_chip"
OPEN_GUI = True
chip_type = ChipType(size_x="10mm", size_y="10mm", size_z="-300um")
design, gui = create_chip_base(chip_name=CHIP_NAME, chip_type=chip_type, open_gui=OPEN_GUI)
n.add_design_variables_to_design(design, dv)
def render_qiskit_metal_design(design, gui):
d.add_transmon_plus_resonator(design, group=n.NBR_1)
gui.rebuild()
gui.autoscale()
render_qiskit_metal_design(design, gui)
# This line will render the qiskit design in the gui, which is useful when developing the design.
Optimization Target
OptTarget
is a required core component of the qdesignoptimizer. It relates the parameter target (e.g. frequency, kappa, capacitance, or Purcell limited T1) with the involved modes (e.g. resonator
or qubit
), the design variable (e.g. design_var_length_resonator_1
) and the physical relation used during optimization (e.g. 1/design_var_length_resonator_1
in case of the resonator frequency).OptTarget
must be created for each target parameter the user wants to optimize for. The names of the involved eigenmodes and parameter names are by convention called from names.py
in the project folder.from qdesignoptimizer.design_analysis_types import OptTarget
import design_constants as dc
import design_variable_names as u
def get_opt_target_res_freq_via_length(
resonator: Mode,
design_var_res_length: Callable = n.design_var_length,
) -> OptTarget:
return OptTarget(
target_param_type=n.FREQ,
involved_modes=[resonator],
design_var=design_var_res_length(resonator),
design_var_constraint={"larger_than": "500um", "smaller_than": "15000um"},
prop_to=lambda p, v: 1 / v[design_var_res_length(resonator)],
independent_target=True,
)
More involved and dependent physical relations can be formulated using parameters p
and design variables v
in the proportionality statement of the OptTarget
. An example for a more detailed relation can be formulated for the nonlinear parameter \(\chi\):
prop_to = lambda p, v: np.abs(v[design_var_res_qb_coupl_length(resonator, qubit)] / v[design_var_qubit_width(qubit)] * p[param_nonlin(qubit, qubit)] / (p[param(qubit, FREQ)] - p[param(resonator, FREQ)] - p[param_nonlin(qubit, qubit)] ))
Caution
Ensure that the units of the design variable match the unit of the constraint in the optimization target and the parameters in the proportionality statement prop_to. For consistency we suggest to use the units \(um\) for measures of length, \(nH\) for inductances and \(fF\) for capacitances.
Physical relation
One strength of the qdesignoptimizer arises from the integration of physical relations between the design variable and the parameter targets, which boosts the efficiency of the optimization. Note that the OptTarget
only requires an expression which is proportional to the target quantity, since it only uses relative values in the update step. Hence, the user only needs to provide the part of the function which varies and to the level of detail which is known to the user. The more accurate the user specified model is, the faster and more robust the optimizer will be. The table below contains an example set of suggested physical relations for the optimization targets for Hamiltonian and dissipative parameters in a dispersively coupled qubit-resonator cQED system.:
Quantity |
Symbol |
Proportional to |
Design variable |
Independence |
---|---|---|---|---|
Resonator frequency |
\(f_{res}\) |
\(1 / l_{res}\) |
\(l_{res}\) |
True |
Qubit frequency |
\(f_{qb}\) |
\(1 / \sqrt{L_{J,qb} \cdot w_{qb}}\) |
\(L_{qb}, w_{qb}\) |
False |
Anharmonicity |
\(\alpha\) |
\(1 / w_{qb}\) |
\(w_{qb}\) |
True |
Dispersive shift |
\(\chi\) |
\(w_{res-qb} \cdot \alpha / (f_{qb}-f_{res}-\alpha)\) |
\(w_{res-qb}\) |
False |
Resonator decay rate |
\(\kappa_{res}\) |
\(l_{res-tl}\) |
\(l_{res-tl}\) |
True |
Caution
An OptTarget can be marked as independent_target=True if the target only depends on a single design variable and not on any system parameter. This allows the optimizer to solve this OptTarget independently, making it faster and more robust. If the criterion of independence is not fulfilled, the OptTarget must be independent_target=False (as the default).
Parameter Targets
dict
per target parameter. Three types of parameter targets can be defined, (1) parameters param
with mode and parameter type, (2) nonlinear parameters param_nonlin
between two modes, and (3) capacitance targets param_capacitance
between two component names. Note that the nonlinear parameters are self-Kerr or anharmonicity \(\alpha\) and cross-Kerr or \(\chi\) parameters. They follow the bosonic definition of Qiskit Metal.import names as n
from qdesignoptimizer.utils.names_parameters import (
param,
param_capacitance,
param_nonlin,
)
PARAM_TARGETS = {
param(n.QUBIT_1, n.FREQ): 4e9,
param(n.QUBIT_1, n.PURCELL_LIMIT_T1): 20e-3,
param(n.RESONATOR_1, n.FREQ): 6e9,
param(n.RESONATOR_1, n.KAPPA): 1e6,
param_nonlin(n.QUBIT_1, n.QUBIT_1): 200e6, # Qubit anharmonicity
param_nonlin(n.QUBIT_1, n.RESONATOR_1): 1e6, # Qubit resonator chi
param_capacitance("prime_cpw_name_tee1", "second_cpw_name_tee1"): -3, # fF
}
Caution
Make sure that all frequencies and rates are defined in units of Hz.
Mini Studies
MiniStudy
is to break down the quantum chip design into smaller problems which are more tractable to simulate on a classical computer, (un?)fortunately brute forcing quantum mechanics seems to be hard. However, if your chip design is not too large, you might be able to optimize your full chip design using a single MiniStudy
.import name as n
from qdesignoptimizer.design_analysis_types import MiniStudy
from qdesignoptimizer.utils.utils_design_variables import junction_setup
CONVERGENCE = dict(nbr_passes=7, delta_f=0.03)
MiniStudy(
qiskit_component_names=[
n.name_mode(n.QUBIT_1),
n.name_mode(n.RESONATOR_1),
n.name_tee(n.NBR_1),
],
port_list=[
(n.name_tee(n.NBR_1), "prime_end", 50),
(n.name_tee(n.NBR_1), "prime_start", 50),
],
open_pins=[],
modes=[n.QUBIT_1, n.RESONATOR_1],
jj_setup={**junction_setup(n.RESONATOR_1)},
design_name="get_mini_study_qb_res",
adjustment_rate=1,
build_fine_mesh=False,
**CONVERGENCE
)
Caution
The order of modes defined in the MiniStudy must match the order of modes resulting from the HFSS eigenmode simulation, which goes from lowest to highest frequency.
Plot Settings
from qdesignoptimizer.utils.sim_plot_progress import OptPltSet
from qdesignoptimizer.utils.names_parameters import (
param,
param_capacitance,
param_nonlin,
)
PLOT_SETTINGS = {
"RES": [
OptPltSet(n.ITERATION, param(n.RESONATOR_1, n.FREQ), y_label="RES Freq (Hz)"),
OptPltSet(n.design_var_length(n.RESONATOR_1), param(n.RESONATOR_1, n.FREQ), y_label="RES Freq (Hz)"),
OptPltSet(n.ITERATION, param(n.RESONATOR_1, n.KAPPA), y_label="RES Kappa (Hz)"),
OptPltSet(n.ITERATION, param_nonlin(n.RESONATOR_1, n.RESONATOR_1), y_label="RES Kerr (Hz)"),
],
"QUBIT": [
OptPltSet(n.ITERATION, param(n.QUBIT_1, n.FREQ), y_label="QB Freq (Hz)"),
OptPltSet(n.ITERATION, param_nonlin(n.QUBIT_1, n.QUBIT_1), y_label="QB Anharm. (Hz)"
),
],
"COUPLINGS": [
OptPltSet(n.ITERATION, param_nonlin(n.RESONATOR_1, n.QUBIT_1), y_label="RES-QB Chi (Hz)"),
],
}
Optimization Workflow
resonator
, qubit
, and feedline
as a tile of a larger chip design). Subsequently, the user can optimize linking Qiskit Metal components between the groups that have been studied initially (e.g. qubit_1
, coupler
, qubit_2
).# select MiniStudy
MINI_STUDY_GROUP = n.NBR_1
MINI_STUDY = ms.get_mini_study_qb_res(group=MINI_STUDY_GROUP)
RENDER_QISKIT_METAL = lambda design: render_qiskit_metal_design(design, gui)
# select OptTarget
opt_targets = [get_opt_target_res_freq_via_length(branch)]
# initialization
design_analysis_state = DesignAnalysisState(
design, RENDER_QISKIT_METAL, pt.PARAM_TARGETS
)
design_analysis = DesignAnalysis(
design_analysis_state,
mini_study=MINI_STUDY,
opt_targets=opt_targets,
save_path="out/" + CHIP_NAME + "_" + time.strftime("%Y%m%d-%H%M%S"),
update_design_variables=False,
plot_settings=ps.PLOT_SETTINGS,
)
# optimization
nbr_iterations = 10
group_passes = 14
delta_f = 0.001
for i in range(nbr_iterations):
design_analysis.update_nbr_passes(group_passes)
design_analysis.update_delta_f(delta_f)
design_analysis.optimize_target({}, {})
design_analysis.screenshot(gui=gui, run=i)
The optimizer outputs a .npy
file with the target parameters and design variables evaluated after each iteration. In addition, the optimizer can output a new .json
file with the updated design parameters and a snapshot of the Qiskit Metal gui to visually follow the progress. The user can also choose to update the initial design_variables.json
file by running design_analysis.overwrite_parameters()
.