# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
import pandas as pd
from pint import UnitRegistry
from typing import Optional
from qiskit_metal.designs import QDesign
from qiskit_metal.analyses.core import QAnalysis
from qiskit_metal.analyses.simulation import LumpedElementsSim
from qiskit_metal.analyses.quantization.constants import Ic_from_Lj
from qiskit_metal import Dict, config
if not config.is_building_docs():
from .lumped_capacitive import extract_transmon_coupled_Noscillator
# TODO: eliminate every reference to "renderer" in this file
[docs]
class LOManalysis(QAnalysis):
"""Performs Lumped Oscillator Model analysis on a simulated or user-provided capacitance matrix.
Default Setup:
* junctions (Dict)
* Lj (float): Junction inductance (in nH)
* Cj (float): Junction capacitance (in fF)
* freq_readout (float): Coupling readout frequency (in GHz).
* freq_bus (Union[list, float]): Coupling bus frequencies (in GHz).
* freq_bus can be a list with the order they appear in the capMatrix.
Data Labels:
* lumped_oscillator (pd.DataFrame): Lumped oscillator result at the last simulation pass
* lumped_oscillator_all (dict): of pd.DataFrame. Lumped oscillator resulting
at every pass of the simulation
"""
default_setup = Dict(
junctions=Dict(Lj=12, Cj=2), freq_readout=7.0, freq_bus=[6.0, 6.2]
)
"""Default setup."""
# supported labels for data generated from the simulation
data_labels = ["lumped_oscillator", "lumped_oscillator_all"]
"""Default data labels."""
def __init__(
self, design: Optional["QDesign"] = None, renderer_name: Optional[str] = None
):
"""Initialize the Lumped Oscillator Model analysis.
Args:
design (QDesign): Pointer to the main qiskit-metal design.
Used to access the QRenderer. Defaults to None.
renderer_name (str, optional): Which renderer to use. Valid entries: 'q3d'.
Defaults to None.
"""
# QAnalysis are expected to either run simulation or use pre-saved sim outputs
# we use a Dict() to store the sim outputs previously saved. Its key names need
# to match those found in the correspondent simulation class.
self.sim = (
Dict()
if renderer_name is None
else LumpedElementsSim(design, renderer_name)
)
super().__init__()
@property
def lumped_oscillator(self) -> dict:
"""Getter
Returns:
dict: Lumped oscillator result at the last simulation pass.
"""
return self.get_data("lumped_oscillator")
@lumped_oscillator.setter
def lumped_oscillator(self, data: dict):
"""Setter
Args:
data (dict): Lumped oscillator result at the last simulation pass.
"""
if not isinstance(data, dict):
self.logger.warning(
"Unsupported type %s. Only accepts dict. Please try again.",
{type(data)},
)
return
self.set_data("lumped_oscillator", data)
@property
def lumped_oscillator_all(self) -> pd.DataFrame:
"""Getter
Returns:
pd.DataFrame: each line corresponds to a simulation pass number
and the remainder of the data is the respective lump oscillator information.
"""
return self.get_data("lumped_oscillator_all")
@lumped_oscillator_all.setter
def lumped_oscillator_all(self, data: pd.DataFrame):
"""Setter
Args:
data (pd.DataFrame): each line corresponds to a simulation pass number
and the remainder of the data is the respective lump oscillator information.
"""
if not isinstance(data, pd.DataFrame):
self.logger.warning(
"Unsupported type %s. Only accepts pd.DataFrame. Please try again.",
{type(data)},
)
return
self.set_data("lumped_oscillator_all", data)
[docs]
def run(self, *args, **kwargs):
"""Executes sequentially the system capacitance simulation (if a renderer was provided
at creation of this object) and lom extraction by executing the methods
LumpedElementsSim.run_sim(`*args`, `**kwargs`) and LOManalysis.run_lom().
For input parameter, see documentation for LumpedElementsSim.run_sim().
Returns:
(dict): Pass numbers (keys) and respective lump oscillator information (values).
"""
if isinstance(self.sim, LumpedElementsSim):
self.sim.run(*args, **kwargs)
return self.run_lom()
[docs]
def run_lom(self):
"""Executes the lumped oscillator extraction from the capacitance matrix,
and based on the setup values.
Returns:
dict: Pass numbers (keys) and their respective capacitance matrices (values).
"""
# wipe data from the previous run (if any)
self.clear_data()
s = self.setup
if not isinstance(self.sim.capacitance_matrix, pd.DataFrame):
if self.sim.capacitance_matrix == {}:
self.logger.warning(
"Please initialize the capacitance_matrix before executing this method."
"`self.sim.capacitance_matrix = pd.DataFrame(...)`"
)
return
if self.sim.capacitance_all_passes == {}:
self.sim.capacitance_all_passes[1] = self.sim.capacitance_matrix.values
ureg = UnitRegistry()
ic_amps = Ic_from_Lj(s.junctions.Lj, "nH", "A")
cj = ureg(f"{s.junctions.Cj} fF").to("farad").magnitude
fread = ureg(f"{s.freq_readout} GHz").to("GHz").magnitude
fbus = [ureg(f"{freq} GHz").to("GHz").magnitude for freq in s.freq_bus]
# derive number of coupling pads
num_cpads = 2
if isinstance(fread, list):
num_cpads += len(fread) - 1
if isinstance(fbus, list):
num_cpads += len(fbus) - 1
# get the LOM for every pass
all_res = {}
for idx_cmat, df_cmat in self.sim.capacitance_all_passes.items():
res = extract_transmon_coupled_Noscillator(
df_cmat,
ic_amps,
cj,
num_cpads,
fbus,
fread,
g_scale=1,
print_info=bool(idx_cmat == len(self.sim.capacitance_all_passes)),
)
all_res[idx_cmat] = res
self.lumped_oscillator = all_res[len(self.sim.capacitance_all_passes)]
all_res = pd.DataFrame(all_res).transpose()
all_res["χr MHz"] = abs(all_res["chi_in_MHz"].apply(lambda x: x.iloc[0]))
all_res["gr MHz"] = abs(all_res["gbus"].apply(lambda x: x.iloc[0]))
self.lumped_oscillator_all = all_res
return self.lumped_oscillator_all
[docs]
def plot_convergence(self, *args, **kwargs):
"""Plots alpha and frequency versus pass number, as well as convergence of delta (in %).
It accepts the same inputs as run_lom(), to allow regenerating the LOM
results before plotting them.
"""
if self.lumped_oscillator_all is None or args or kwargs:
self.run_lom(*args, **kwargs)
# TODO: copy plot_convergence_main() from pyEPR and move it here
self.sim.renderer.plot_convergence_main(self.lumped_oscillator_all)
[docs]
def plot_convergence_chi(self, *args, **kwargs):
"""Plot convergence of chi and g, both in MHz, as a function of pass number.
It accepts the same inputs as run_lom(), to allow regenerating the LOM
results before plotting them.
"""
if self.lumped_oscillator_all is None or args or kwargs:
self.run_lom(*args, **kwargs)
# TODO: copy plot_convergence_main() from pyEPR and move it here
self.sim.renderer.plot_convergence_chi(self.lumped_oscillator_all)
[docs]
def load_simulation_data(self, data_name: str, data):
"""Load simulation data for the following analysis. This will override any data found
Args:
data_name (str): name of the variable
data (Any): simulation output
"""
self.sim[data_name] = data