"""
========================================================================================================================
SimulationConfig
========================================================================================================================
"""
from datetime import datetime
from qiskit_metal.analyses.quantization import EPRanalysis, LOManalysis
from .sweeper_helperfunctions import extract_QSweep_parameters
from .utils import *
[docs]
class SimulationConfig:
"""
Represents the configuration for a simulation.
Args:
design_name (str): The name of the design.
renderer_type (str): The type of renderer.
sim_type (str): The type of simulation.
setup_name (str): The name of the setup.
max_passes (int): The maximum number of passes.
max_delta_f (float): The maximum delta frequency.
min_converged_passes (int): The minimum number of converged passes.
Lj (float): The value of Lj.
Cj (float): The value of Cj.
"""
def __init__(self, design_name="CavitySweep", renderer_type="hfss", sim_type="eigenmode",
setup_name="Setup", max_passes=49, max_delta_f=0.05, min_converged_passes=2, Lj=0, Cj=0):
"""
Initialize the Simulation object.
Args:
design_name (str): The name of the design.
renderer_type (str): The type of renderer to be used.
sim_type (str): The type of simulation.
setup_name (str): The name of the setup.
max_passes (int): The maximum number of passes.
max_delta_f (float): The maximum change in frequency.
min_converged_passes (int): The minimum number of converged passes.
Lj (float): The value of inductance.
Cj (float): The value of capacitance.
"""
self.design_name = design_name
self.renderer_type = renderer_type
self.sim_type = sim_type
self.setup_name = setup_name
self.max_passes = max_passes
self.max_delta_f = max_delta_f
self.min_converged_passes = min_converged_passes
self.Lj = Lj
self.Cj = Cj
[docs]
def simulate_whole_device(design, device_dict, eigenmode_options, LOM_options, open_gui=False):
"""
Simulates the whole device by running eigenmode and LOM simulations.
Args:
design (metal.designs.design_planar.DesignPlanar): The design object.
cross_dict (dict): Dictionary containing qubit options.
cavity_dict (dict): Dictionary containing cavity options.
LOM_options (dict): Dictionary containing LOM setup options.
eigenmode_options (dict): Dictionary containing eigenmode setup options.
open_gui (bool, optional): If True, the Metal GUI is opened. Default is False.
Returns:
tuple: A tuple containing the simulation results, LOM analysis object, and eigenmode analysis object.
"""
cross_dict = device_dict["design_options_qubit"]
cavity_dict = device_dict["design_options_cavity_claw"]
cpw_opts_key, cplr_opts_key = get_cavity_claw_options_keys(cavity_dict)
design.delete_all_components()
if device_dict["coupler_type"].upper() == "CLT":
emode_df, emode_obj = run_eigenmode(design, cavity_dict, eigenmode_options, cross_dict=cross_dict)
lom_df, lom_obj = run_xmon_LOM(design, cross_dict, LOM_options)
try:
data = get_sim_results(emode_df = emode_df, lom_df = lom_df)
except:
return None, None, None
elif device_dict["coupler_type"].lower() == "ncap":
emode_df, emode_obj = run_eigenmode(design, cavity_dict, eigenmode_options)
ncap_lom_df, ncap_lom_obj = run_capn_LOM(design, cavity_dict[cplr_opts_key], LOM_options)
lom_df, lom_obj = run_xmon_LOM(design, cross_dict, LOM_options)
data = get_sim_results(emode_df = emode_df, lom_df = lom_df, ncap_lom_df=ncap_lom_df)
device_dict_format = Dict(
cavity_options = Dict(
coupler_type = device_dict["coupler_type"],
coupler_options = cavity_dict[cplr_opts_key],
cpw_opts = Dict (
left_options = cavity_dict[cpw_opts_key],
)
),
qubit_options = cross_dict
)
design = metal.designs.design_planar.DesignPlanar()
if open_gui:
gui = metal.MetalGUI(design)
else:
pass
design.overwrite_enabled = True
QC = create_qubitcavity(device_dict_format, design)
if data is None:
print("No simulations were ran. Check to see if Ansys HFSS is installed.")
return None, None, None
else:
return_df = dict(
sim_options = dict(
setup = dict(
eigenmode_setup = eigenmode_options,
LOM_setup = LOM_options
),
renderer_options = dict(
eigenmode_renderer_options = emode_obj.sim.renderer.options,
lom_renderer_options = lom_obj.sim.renderer.options
),
simulator = "Ansys HFSS"
),
sim_results = data,
design = dict(
design_options = device_dict_format
)
)
return return_df, lom_obj, emode_obj
[docs]
def simulate_single_design(design, device_dict, emode_options={}, lom_options={}, coupler_type="CLT"):
"""
Simulates a single design using the provided parameters.
Args:
design (Design): The design object representing the design.
device_dict (dict): A dictionary containing device options.
emode_options (dict): A dictionary containing the eigenmode simulation options.
lom_options (dict): A dictionary containing the LOM simulation options.
coupler_type (str): The type of coupler to be used.
sim_options (dict): A dictionary containing simulation options.
Returns:
dict or tuple: The simulation results. If eigenmode simulation is performed, returns a dictionary
containing the eigenmode results. If LOM simulation is performed, returns a tuple containing the
LOM dataframe and LOM object.
"""
design.delete_all_components()
emode_df = {}
lom_df = {}
return_df = {}
emode_obj = None
lom_obj = None
cpw_opts_key, cplr_opts_key = get_cavity_claw_options_keys(device_dict)
if cpw_opts_key in device_dict.keys():
emode_df, emode_obj = run_eigenmode(design, device_dict, emode_options)
if coupler_type.lower() == "ncap":
# emode_df, emode_obj = run_eigenmode(design, device_dict, sim_options)
ncap_lom_df, lom_obj = run_capn_LOM(design, device_dict[cplr_opts_key], lom_options)
f_est, kappa_est = find_kappa(emode_df["sim_results"]["cavity_frequency"], ncap_lom_df["sim_results"]["C_top2ground"], ncap_lom_df["sim_results"]["C_top2bottom"])
# emode_df["sim_results"]["cavity_frequency"] = f_est
# emode_df["sim_results"]["kappa"] = kappa_est
return_df.update({"lom_df": ncap_lom_df})
return_df.update({"eigenmode_df": emode_df})
return_df["final_sim_results"] = {}
return_df["final_sim_results"]["cavity_frequency"] = f_est
return_df["final_sim_results"]["kappa"] = kappa_est
return_df["final_sim_results"].update({"cavity_frequency_unit": "GHz"})
return_df["final_sim_results"].update({"kappa_unit": "kHz"})
else:
lom_df, lom_obj = run_xmon_LOM(design, device_dict["design_options"], lom_options) if "cross_length" in device_dict["design_options"] else run_capn_LOM(design, device_dict, lom_options)
return_df = lom_df
return return_df, emode_obj, lom_obj
[docs]
def get_sim_results(emode_df = {}, lom_df = {}, ncap_lom_df = {}):
"""
Retrieves simulation results from the provided dataframes and calculates additional parameters.
Args:
emode_df (dict): Dataframe containing eigenmode simulation results.
lom_df (dict): Dataframe containing lumped element model simulation results.
ncap_lom_df (dict): Dataframe containing lumped element model simulation results for NCap coupler.
Returns:
dict: A dictionary containing the calculated simulation results.
"""
# if the arguments are None, return and print error message
if emode_df == None and lom_df == None:
print("No simulation results available.")
return None
data_emode = {} if emode_df == {} else emode_df["sim_results"]
data_lom = {} if lom_df == {} else lom_df["sim_results"]
data = {}
cross2cpw = abs(lom_df["sim_results"]["cross_to_claw"]) * 1e-15
cross2ground = abs(lom_df["sim_results"]["cross_to_ground"]) * 1e-15
f_r = emode_df["sim_results"]["cavity_frequency"]
Lj = lom_df["design"]["design_options"]["aedt_q3d_inductance"] * (1 if lom_df["design"]["design_options"]["aedt_q3d_inductance"] > 1e-9 else 1e-9)
# print(Lj)
gg, aa, ff_q = find_g_a_fq(cross2cpw, cross2ground, f_r, Lj, N=4)
kappa = emode_df["sim_results"]["kappa"]
Q = emode_df["sim_results"]["Q"]
if ncap_lom_df != {}:
f_r, kappa = find_kappa(emode_df["sim_results"]["cavity_frequency"], ncap_lom_df["sim_results"]["C_top2ground"], ncap_lom_df["sim_results"]["C_top2bottom"])
data = dict(
cavity_frequency_GHz = f_r,
Q = Q,
kappa_kHz = kappa,
g_MHz = gg,
anharmonicity_MHz = aa,
qubit_frequency_GHz = ff_q
)
return data
[docs]
def run_eigenmode(design, geometry_dict, sim_options, **kwargs):
"""
Runs the eigenmode simulation for a given design using Ansys HFSS.
Args:
design (str): The name of the design.
geometry_dict (dict): A dictionary containing the geometry options for the simulation.
sim_options (dict): A dictionary containing the simulation options.
Returns:
tuple: A tuple containing the simulation results and the EPRAnalysis object.
The simulation results are stored in a dictionary with the following structure:
{
"design": {
"coupler_type": "CLT",
"design_options": geometry_dict,
"design_tool": "Qiskit Metal"
},
"sim_options": {
"sim_type": "epr",
"setup": setup,
"simulator": "Ansys HFSS"
},
"sim_results": {
"cavity_frequency": f_rough,
"Q": Q,
"kappa": kappa
},
"misc": data
}
The EPRAnalysis object is returned for further analysis or post-processing.
"""
# coupler_type from kwargs
coupler_type = kwargs.get("coupler_type", "CLT")
cross_dict = kwargs.get("cross_dict", {})
cpw_opts_key, cplr_opts_key = get_cavity_claw_options_keys(geometry_dict)
# update the cpw_opts with the total length
cpw_length = int(string_to_float(geometry_dict[cpw_opts_key]["total_length"]))
geometry_dict[cpw_opts_key]["total_length"] = f"{cpw_length}um"
# update the claw dims
claw_opts = geometry_dict["claw_opts"]
if cross_dict is not None:
print(cross_dict)
claw_opts["cross_gap"] = cross_dict["cross_gap"]
claw_opts["cross_width"] = cross_dict["cross_width"]
claw_opts["cross_length"] = cross_dict["cross_length"]
claw = create_claw(geometry_dict["claw_opts"], cpw_length, design)
if coupler_type == "CLT":
coupler = create_clt_coupler(geometry_dict[cplr_opts_key], design)
else:
coupler = create_ncap_coupler(geometry_dict[cplr_opts_key], design)
cpw = create_cpw(geometry_dict[cpw_opts_key], coupler, design)
config = SimulationConfig(min_converged_passes=3)
try:
epra, hfss = start_simulation(design, config)
hfss.clean_active_design()
# setup = set_simulation_hyperparameters(epra, config)
# ["setup"]
epra.sim.setup = Dict(sim_options)
epra.sim.setup.name = "test_setup"
epra.sim.renderer.options.max_mesh_length_port = '7um'
setup = epra.sim.setup
# print(setup)
# print(type(setup))
# print(type(sim_options["setup"]))
mesh_lengths = {}
coupler_type = "CLT"
# "finger_count" in geometry_dict["cplr_opts"]
if geometry_dict[cplr_opts_key].get('finger_count') is not None :
coupler_type = "NCap"
render_simulation_no_ports(epra, [cpw,claw], [(cpw.name, "start")], config.design_name, setup.vars)
mesh_lengths = {'mesh1': {"objects": [f"trace_{cpw.name}", f"readout_connector_arm_{claw.name}"], "MaxLength": '4um'}}
else:
render_simulation_with_ports(epra, config.design_name, setup.vars, coupler)
mesh_lengths = {'mesh1': {"objects": [f"prime_cpw_{coupler.name}", f"second_cpw_{coupler.name}", f"trace_{cpw.name}", f"readout_connector_arm_{claw.name}"], "MaxLength": '7um'}}
modeler = hfss.pinfo.design.modeler
#add_ground_strip_and_mesh(modeler, coupler, mesh_lengths=mesh_lengths)
print(mesh_lengths)
mesh_objects(modeler, mesh_lengths)
f_rough, Q, kappa = get_freq_Q_kappa(epra, hfss)
data = epra.get_data()
data_df = {
"design": {
"coupler_type": coupler_type,
"design_options": geometry_dict,
"design_tool": "Qiskit Metal"
},
"sim_options": {
"sim_type": "epr",
"setup": setup,
"renderer_options": epra.sim.renderer.options,
"simulator": "Ansys HFSS"
},
"sim_results": {
"cavity_frequency": f_rough,
"cavity_frequency_unit": "GHz",
"Q": Q,
"kappa": kappa,
"kappa_unit": "kHz"
},
"misc": data
}
except:
# raise OS warning
print("Ansys HFSS simulation failed. Are you sure the Ansys HFSS is installed?")
return None, None
return data_df, epra
[docs]
def run_capn_LOM(design, param, sim_options):
"""
Run capacitance analysis using Qiskit Metal and Ansys HFSS.
Args:
design (metal.designs.design_planar.DesignPlanar): The design object.
param (dict): Design options for the coupler.
sim_options (dict): Simulation options.
Returns:
tuple: A tuple containing the following:
- data_df (dict): Dictionary containing design, simulation options, simulation results, and miscellaneous data.
- loma (LOManalysis): The LOManalysis object.
"""
# design = metal.designs.design_planar.DesignPlanar()
# gui = metal.MetalGUI(design)
# design.overwrite_enabled = True
coupler = create_ncap_coupler(param, design)
loma = LOManalysis(design, "q3d")
loma.sim.setup.reuse_selected_design = False
loma.sim.setup.reuse_setup = False
# example: update single setting
loma.sim.setup.max_passes = 33
loma.sim.setup.min_converged_passes = 3
loma.sim.setup.percent_error = 0.1
loma.sim.setup.auto_increase_solution_order = 'False'
loma.sim.setup.solution_order = 'Medium'
loma.sim.setup.name = 'lom_setup'
loma.sim.setup = sim_options["setup"]
loma.sim.run(name = 'LOMv2.01', components=[coupler.name],
open_terminations=[(coupler.name, pin_name) for pin_name in coupler.pin_names])
cap_df = loma.sim.capacitance_matrix
data = loma.get_data()
setup = loma.sim.setup
data_df = {
"design": {
"coupler_type": "NCap",
"design_options": param,
"design_tool": "Qiskit Metal"
},
"sim_options": {
"sim_type": "lom",
"setup": setup,
"simulator": "Ansys HFSS"
},
"sim_results": {
"C_top2top" : abs(cap_df[f"cap_body_0_{coupler.name}"].values[0]),
"C_top2bottom" : abs(cap_df[f"cap_body_0_{coupler.name}"].values[1]),
"C_top2ground" : abs(cap_df[f"cap_body_0_{coupler.name}"].values[2]),
"C_bottom2bottom" : abs(cap_df[f"cap_body_1_{coupler.name}"].values[1]),
"C_bottom2ground" : abs(cap_df[f"cap_body_1_{coupler.name}"].values[2]),
"C_ground2ground" : abs(cap_df[f"ground_main_plane"].values[2]),
},
"misc": data
}
return data_df, loma
[docs]
def run_xmon_LOM(design, cross_dict, sim_options):
"""
Runs the XMON LOM simulation.
Args:
design (metal.designs.design_planar.DesignPlanar): The design object.
cross_dict (dict): The dictionary containing cross connection information.
sim_options (dict): The simulation options.
Returns:
tuple: A tuple containing the simulation data and the LOManalysis object.
"""
# design = metal.designs.design_planar.DesignPlanar()
# gui = metal.MetalGUI(design)
# design.overwrite_enabled = True
c1 = LOManalysis(design, "q3d")
c1.sim.setup.reuse_selected_design = False
c1.sim.setup.reuse_setup = False
c1.sim.setup.max_passes = 50
c1.sim.setup.min_converged_passes = 2
c1.sim.setup.percent_error = 0.1
c1.sim.setup.name = 'sweep_setup'
c1.sim.setup = sim_options #["setup"]
qname = 'xmon'
cnames = cross_dict["connection_pads"].keys()
cname = list(cnames)[0]
temp_arr = np.repeat(qname, len(cnames))
ports_zip = list(zip(temp_arr, cnames))
q = TransmonCross(design, qname, options=cross_dict)
design.rebuild()
selection = [qname]
open_pins = ports_zip
# print(q.options)
try:
c1.sim.renderer.clean_active_design()
c1.sim.run(name = 'LOMv2.0', components=selection,
open_terminations=open_pins)
cap_df = c1.sim.capacitance_matrix
# print("#"*100)
# print(c1.sim.renderer.options)
data = {
"design": {
"design_options": design.components[qname].options,
"design_tool": "Qiskit Metal"
},
"sim_options": {
"sim_type": "lom",
"setup": c1.sim.setup,
"renderer_options": c1.sim.renderer.options,
"simulator": "Ansys HFSS"
},
"sim_results": {
"cross_to_ground": 0 if 'ground_main_plane' not in cap_df.loc[f'cross_{qname}'] else abs(cap_df.loc[f'cross_{qname}']['ground_main_plane']),
"claw_to_ground": 0 if 'ground_main_plane' not in cap_df.loc[f'{cname}_connector_arm_{qname}'] else abs(cap_df.loc[f'{cname}_connector_arm_{qname}']['ground_main_plane']),
"cross_to_claw": abs(cap_df.loc[f'cross_{qname}'][f'{cname}_connector_arm_{qname}']),
"cross_to_cross": abs(cap_df.loc[f'cross_{qname}'][f'cross_{qname}']),
"claw_to_claw": abs(cap_df.loc[f'{cname}_connector_arm_{qname}'][f'{cname}_connector_arm_{qname}']),
"ground_to_ground": 0 if 'ground_main_plane' not in cap_df.loc[f'cross_{qname}'] else abs(cap_df.loc['ground_main_plane']['ground_main_plane']),
"units": "fF"
},
}
# save_simulation_data_to_json(data, filename = f"qubitonly_num{i}_{comp_id}_v{version}")
except:
print("Ansys HFSS simulation failed. Are you sure the Ansys HFSS is installed?")
return None, None
return data, c1
[docs]
def run_sweep(design, sweep_opts, emode_options, lom_options, filename="default_sweep"):
'''
Runs a parameter sweep for the specified design.
Args:
design (Design): The design object.
sweep_opts (dict): The sweep options.
emode_options (dict): The eigenmode setup options.
lom_options (dict): The LOM setup options.
filename (str): The filename for the simulation results.
Returns:
None
Saves each sweep iteration to a JSON file with the specified filename.
'''
for param in extract_QSweep_parameters(sweep_opts["geometry_dict"]):
print(param)
return_df, _, __ = simulate_single_design(design, param, emode_options, lom_options, sweep_opts["coupler_type"])
filename = f"filename_{datetime.now().strftime('%d%m%Y_%H.%M.%S')}"
save_simulation_data_to_json(return_df, filename)
[docs]
def run_qubit_cavity_sweep(design, device_options, emode_setup=None, lom_setup=None, filename="default_sweep"):
"""
Runs a parameter sweep for the specified design.
Args:
design (Design): The design object.
sweep_opts (dict): The sweep options.
device_dict (dict): The device dictionary containing the design options and setup.
emode_setup (dict): The eigenmode setup options.
lom_setup (dict): The LOM setup options.
filename (str): The filename for the simulation results.
Returns:"""
simulated_params_list = []
for param in extract_QSweep_parameters(device_options):
cpw_claw_qubit_df, _, _ = simulate_whole_device(design, param, emode_setup, lom_setup)
filename = f"filename_{datetime.now().strftime('%d%m%Y_%H.%M.%S')}"
simulated_params_list.append(cpw_claw_qubit_df)
save_simulation_data_to_json(cpw_claw_qubit_df, filename)
return simulated_params_list
[docs]
def start_simulation(design, config):
"""
Starts the simulation with the specified design and configuration.
:param design: The design to be simulated.
:param config: The configuration settings for the simulation.
:return: A tuple containing the EPR analysis object and the HFSS object.
"""
epra = EPRanalysis(design, config.renderer_type)
hfss = epra.sim.renderer
print("Starting the Simulation")
hfss.start()
hfss.new_ansys_design(config.design_name, config.sim_type)
return epra, hfss
[docs]
def set_simulation_hyperparameters(epra, config):
"""
Sets the simulation hyperparameters based on the provided configuration.
:param epra: The EPR analysis object.
:param config: The configuration settings for the simulation.
:return: The setup object with the updated settings.
"""
setup = epra.sim.setup
setup.name = config.setup_name
setup.max_passes = config.max_passes
setup.max_delta_f = config.max_delta_f
setup.min_converged = config.min_converged_passes
setup.n_modes = 1
setup.vars = {'Lj': f'{config.Lj}nH', 'Cj': f'{config.Cj}fF'}
return setup
[docs]
def render_simulation_with_ports(epra, ansys_design_name, setup_vars, coupler):
"""
Renders the simulation into HFSS.
:param epra: The EPR analysis object.
:param ansys_design_name: The name of the Ansys design.
:param setup_vars: The setup variables for the rendering.
:param coupler: The coupler object.
"""
print(epra.sim)
epra.sim.renderer.clean_active_design()
epra.sim._render(name=ansys_design_name,
solution_type='eigenmode',
vars_to_initialize=setup_vars,
open_pins=[(coupler.name, "prime_start"), (coupler.name, "prime_end")],
port_list=[(coupler.name, 'prime_start', 50), (coupler.name, "prime_end", 50)],
box_plus_buffer=True)
print("Sim rendered into HFSS!")
[docs]
def render_simulation_no_ports(epra, components, open_pins, ansys_design_name, setup_vars):
"""
Renders the simulation into HFSS.
:param epra: The EPR analysis object.
:param ansys_design_name: The name of the Ansys design.
:param setup_vars: The setup variables for the rendering.
:param components: List of QComponent object.
"""
epra.sim._render(name=ansys_design_name,
selection=[qcomp.name for qcomp in components],
open_pins=open_pins,
solution_type='eigenmode',
vars_to_initialize=setup_vars,
box_plus_buffer=True)
print("Sim rendered into HFSS!")