Tutorial 2: Simulating Interpolated Designs#
In this tutorial we will learn how to simulate designs obtained from the SQuADDS Database. We will be using Ansys HFSS as the simulator backend in this tutorial.
Warning: This tutorial will not run on Mac OS since Ansys HFSS is not available on Mac
[1]:
%load_ext autoreload
%autoreload 2
[ ]:
from squadds import SQuADDS_DB
db = SQuADDS_DB()
db.select_system(["qubit","cavity_claw"])
db.select_qubit("TransmonCross")
db.select_cavity_claw("RouteMeander")
db.select_resonator_type("quarter")
db.show_selections()
df = db.create_system_df()
# df
[ ]:
from squadds import Analyzer
[ ]:
analyzer = Analyzer(db)
[ ]:
analyzer.selected_system
[ ]:
target_params = {
"qubit_frequency_GHz": 4,
"cavity_frequency_GHz": 6.2,
"kappa_kHz": 5,
"resonator_type":"half",
"anharmonicity_MHz": -200,
"g_MHz": 70}
[ ]:
results = analyzer.find_closest(target_params=target_params,
num_top=3,
metric="Euclidean",
display=True)
results
Capacitance Matrix Simulation for Qubits#
Find the best geometries for your desired qubit Hamiltonian parameters the same way as in Tutorial 1:
[ ]:
from squadds import SQuADDS_DB
db = SQuADDS_DB()
db.select_system("qubit")
db.select_qubit("TransmonCross")
df = db.create_system_df()
[ ]:
from squadds import Analyzer
analyzer = Analyzer(db)
target_params={"qubit_frequency_GHz": 4, "anharmonicity_MHz": -200}
results = analyzer.find_closest(target_params=target_params,
num_top=3,
metric="Euclidean",
display=True)
results
[ ]:
best_device = results.iloc[0]
best_device
Simulate the Target Design#
Once you have the target design (in this case in Qiskit Metal format), you can then simulate it in whichever application you choose. However, we provide a native API to make it easier to get started with simulation on Ansys HFSS (support for AWS Palace will be provided hopefully in the near future!).
We use the AnsysSimulator
class to run simulations:
[5]:
from squadds import AnsysSimulator
The AnsysSimulator
object takes the analyzer from before and the target “best device” that you want to simulate as parameters.
[ ]:
ansys_simulator = AnsysSimulator(analyzer, best_device)
[ ]:
best_device
We can view the geometry and simulation setup from the database entry for our best device as follows:
[ ]:
best_device_geometry = best_device["design_options"]
best_device_geometry
Now, let’s say we want to change some parameter in the design, for example, the cross_length
, just for demonstration purposes.
[ ]:
best_device_geometry["cross_length"] = '310um'
best_device_geometry
[ ]:
best_device_sim_setup = best_device["setup"]
best_device_sim_setup
Then, we simulate our device with Ansys HFSS. In this case, we are simulating a TransmonCross
object to get its corresponding capacitance matrix.
[ ]:
ansys_results = ansys_simulator.simulate(best_device)
ansys_results
After the simulation is finished, we have the option to take screenshots of our design in the renderer and in Qiskit Metal to verify our geometry visually, as follows:
[ ]:
ansys_simulator.get_renderer_screenshot()
[ ]:
ansys_simulator.get_design_screenshot()
Now, we analyze the simulated capacitance matrix results using the get_xmon_info()
function, which returns the Hamiltonian parameters - qubit_anharmonicity_MHz
and qubit_frequency_GHz
.
[ ]:
ansys_simulator.get_xmon_info(ansys_results)
Extracting the data needed for contributing to the dataset#
Suppose that we wanted to contribute this “new” design and simulation results to SQuADDS_DB
.
We need to first, extract the correct data that will be neccessary for contributing the results.
First, we get the design
dictionary:
[ ]:
design_dict = ansys_results["design"]
design_dict
Then, we can get the sim_options
dictionary:
[ ]:
sim_options_dict = ansys_results["sim_options"]
sim_options_dict
And then the renderer options:
[ ]:
renderer_options_dict = ansys_results["sim_options"]["renderer_options"]
renderer_options_dict
And finally, the sim_results
dictionary:
In this particular simulation, all of our results have units of femtoFarads, so we can just add a global units
key with value fF
.
[ ]:
sim_results_dict = ansys_results["sim_results"]
sim_results_dict.update({"units": "fF"})
We can then take the dictionaries that we have just created, and send them to a JSON file that we will use later on in Tutorial 3.
[ ]:
final_dict = dict(
sim_options = dict(
renderer_options = renderer_options_dict,
setup = sim_options_dict["setup"],
simulator = sim_options_dict["simulator"]
),
design = design_dict,
sim_results = sim_results_dict
)
final_dict
Let’s save the dictionaries to a JSON file so that we can use it later on in Tutorial 3, where go over this process in more detail.
[ ]:
import json
with open("examples/single_xmon_lom.json", 'w') as outfile:
json.dump(final_dict, outfile, indent=4)
Simulating an Interpolated Qubit + Cavity device#
This time, we look for a complete interpolated device, which includes a TransmonCross object coupled to a RouteMeander CPW and CoupledLineTee, as in Tutorial 1 - which does not exist in the simulation database.
[23]:
db.select_system(["qubit","cavity_claw"])
db.select_qubit("TransmonCross")
db.select_cavity_claw("RouteMeander")
db.select_resonator_type("quarter")
merged_df = db.create_system_df()
analyzer = Analyzer(db)
[16]:
target_params = {
"qubit_frequency_GHz": 4.2,
"cavity_frequency_GHz": 9,
"kappa_kHz": 210,
"resonator_type":"quarter",
"anharmonicity_MHz": -200,
"g_MHz": 70}
[ ]:
from squadds.interpolations.physics import ScalingInterpolator
# Create an instance of ScalingInterpolator
interpolator = ScalingInterpolator(analyzer, target_params)
design_df = interpolator.get_design()
# Get the device from the design_df
device_interp = design_df.iloc[0]
[ ]:
device_interp
After getting our best result, we once again initialize our AnsysSimulator object, this time on our new system, and then simulate.
[ ]:
ansys_simulator = AnsysSimulator(analyzer, device_interp)
[ ]:
ansys_results = ansys_simulator.simulate(device_interp)
ansys_results
To extract the simulated Hamiltonian parameters, we look at the sim_results
key in our ansys_results
dictionary:
[ ]:
hamiltonian_results = ansys_results["sim_results"]
hamiltonian_results
We can similarly get screenshots from Qiskit Metal and the Ansys renderer. Notice that the TransmonCross object and the cavity+claw object are rendered into the same design in Qiskit Metal. This is done purely out of convenience, and has no effect on the simulation results. This is shown in the renderer screenshots below, which show the TransmonCross and cavity+claw being rendered and simulated separately.
[ ]:
ansys_simulator.get_design_screenshot()
[ ]:
ansys_simulator.get_renderer_screenshot()
We can also plot our complete custom QubitCavity device in Qiskit Metal.
[ ]:
ansys_simulator.plot_device(ansys_results)
Finally, we can once again get relevant results dictionaries that will be useful if/when you decide to contribute your results to SQuADDS!
[ ]:
design_dict = ansys_results["design"]
design_dict
[ ]:
sim_options_dict = ansys_results["sim_options"]
sim_options_dict
[ ]:
sim_results_dict = ansys_results["sim_results"]
sim_results_dict
We can once again update the units for our results. However, this time, the units are not all the same, so we must specify individual units.
[ ]:
sim_results_dict.update({
"cavity_frequency_GHz_unit": "GHz",
"g_MHz_unit": "MHz",
"anharmonicity_MHz_unit": "MHz",
"kappa_kHz_unit": "kHz",
"qubit_frequency_GHz_unit": "GHz",
})
sim_results_dict
Simulating an Interdigitated Capacitor Device#
We can now explore running a set of simulations sweeping over certain specified parameters for a device. In this case, we choose an interdigitated coupler device. Notice how the total_length
parameter under cpw_opts
in our geometry_dict
is a list
instead of a String
. This lets our AnsysSimulator know that we wish to simulate this device [in this case] three total times, i.e. with the CPW total_length=2000um
, with CPW total_length = 4000um
, and with CPW
total_length = 7000um
. We can similarly specify sweeping over other parameters, using the same list syntax to denote which parameters should be swept over, and with respect to what dimensions, and the sweep will be done combinatorically with every combination of parameters specified.
[ ]:
ncap_sweep_dict = {
"coupler_type": "NCAP",
"geometry_dict": {
"claw_opts": {
"connection_pads": {
"readout": {
"connector_location": "90",
"connector_type": "0",
"claw_length": "50um",
"ground_spacing": "10um",
"claw_gap": "5.1um",
"claw_width": "7um",
"claw_cpw_width": "11.7um",
"claw_cpw_length": "0um"
}
},
"cross_width": "30um",
"cross_length": "300um",
"cross_gap": "30um",
"orientation": "-90",
"pos_x": "-1000um"
},
"cpw_opts": {
"fillet": "49.9um",
"total_length": ["2000um", "4000um", "7000um"],
"trace_width": "11.7um",
"trace_gap": "5.1um",
"lead": {
"start_straight": "50um"
},
"pin_inputs": {
"start_pin": {
"component": "cplr",
"pin": "second_end"
},
"end_pin": {
"component": "claw",
"pin": "readout"
}
}
},
"cplr_opts": {
'prime_width': '11.7um',
'prime_gap': '5.1um',
'second_width': '11.7um',
'second_gap': '5.1um',
'cap_gap': '5um',
'cap_width': '10um',
'cap_gap_ground': '5.1um',
'finger_length': '30um',
'finger_count': ['3','5'],
'cap_distance': '50.9um',
}
}
}
We can also specify a setup for the sweep, though a default high-accuracy one will be assigned. Currently, we only support eigenmode and LOM sweeps for TransmonCross, CavityClaw, and InterdigitatedCavityClaw objects, but more support will be added as necessary. (Note that in our example setup, the max_passes
parameter is set to 1
; this is for the sake of speed/demonstration only, it is HIGHLY recommended to run more than 1 pass for your more important simulations)
[ ]:
example_eigenmode_setup = {
"setup": {
'basis_order': 1,
'max_delta_f': 0.05,
'max_passes': 1,
'min_converged': 1,
'min_converged_passes': 1,
'min_freq_ghz': 1,
'min_passes': 1,
'n_modes': 1,
'name': 'default_eigenmode_setup',
'pct_refinement': 30,
'reuse_selected_design': True,
'reuse_setup': True,
'vars': {'Cj': '0fF', 'Lj': '0nH'}
}
}
[ ]:
example_LOM_setup = {
"setup": {
'name': 'default_LOM_setup',
'reuse_selected_design': False,
'reuse_setup': False,
'freq_ghz': 5.0,
'save_fields': False,
'enabled': True,
'max_passes': 2,
'min_passes': 2,
'min_converged_passes': 2,
'percent_error': 0.1,
'percent_refinement': 30,
'auto_increase_solution_order': True,
'solution_order': 'High',
'solver_type': 'Iterative',
}
}
Finally, call ansys_simulator.sweep()
on our sweep dictionary, and include your setup
dictionary if desired.
[ ]:
ansys_simulator.sweep(ncap_sweep_dict, emode_setup=example_eigenmode_setup, lom_setup=example_LOM_setup)
The sweeper code saves the resuts of every iteration of the sweep into a json
file, which can be found in the same folder as this Tutorial.
License#
This code is a part of SQuADDS
Developed by Sadman Ahmed Shanto
This tutorial is written by Andre Kuo and Sadman Ahmed Shanto
© Copyright Sadman Ahmed Shanto & Eli Levenson-Falk 2023.
This code is licensed under the MIT License. You may obtain a copy of this license in the LICENSE.txt file in the root directory of this source tree.
Any modifications or derivative works of this code must retain thiscopyright notice, and modified files need to carry a notice indicatingthat they have been altered from the originals.