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.