Source code for nanotune.fit.coulomboscillationfit

import copy
import logging
import os
from typing import Dict, List, Optional, Tuple

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import scipy as sc

import nanotune as nt
from nanotune.data.plotting import (colors_dict, default_plot_params,
                                    plot_params_type)
from nanotune.fit.datafit import DataFit

logger = logging.getLogger(__name__)
AxesTuple = Tuple[matplotlib.axes.Axes, matplotlib.colorbar.Colorbar]


[docs]class CoulombOscillationFit(DataFit): """Data fitting class for Coulomb oscillations. Attributes: relative_height_threshold: threshold above which a peak is considered a Coulomb peak. Compared to normalized signal. peak_indx: indices of the peaks found. peak_distances: distances between peaks. """ def __init__( self, qc_run_id: int, db_name: str, db_folder: Optional[str] = None, relative_height_threshold: float = 0.5, **kwargs, ) -> None: if db_folder is None: db_folder = nt.config["db_folder"] DataFit.__init__( self, qc_run_id, db_name, db_folder=db_folder, **kwargs, ) self.relative_height_threshold = relative_height_threshold self.peak_indx: Dict[str, List[int]] = {} self.peak_distances: Dict[str, List[float]] = {} @property def range_update_directives(self) -> List[str]: """""" raise NotImplementedError
[docs] def find_fit(self) -> None: """Finds peaks and extracts distances in voltage space between them. """ self.peak_indx = self.find_peaks() self.peak_distances = self.calculate_peak_distances(self.peak_indx) self.peak_locations = self.get_peak_locations() self._retain_fit_result() self.save_features()
def _retain_fit_result(self): self._features = {} for read_meth in self.readout_methods.keys(): self._features[read_meth] = {} self._features[read_meth]["peak_indx"] = self.peak_indx[read_meth] temp = self.peak_locations[read_meth] self._features[read_meth]["peak_locations"] = temp temp = self.peak_distances[read_meth] self._features[read_meth]["peak_distances"] = temp
[docs] def calculate_voltage_distances(self) -> Dict[str, float]: """Calculates voltage spacing between successive Coulomb peaks. Returns: dict: mapping readout method to maximum of peak distances found for this method. """ voltage_distances = {} for read_meth in self.readout_methods.keys(): voltage_distances[read_meth] = np.max(self.peak_distances[read_meth]) return voltage_distances
[docs] def get_peak_locations(self) -> Dict[str, List[float]]: """Determines peaks for each trace in the dataset and retains their indices. Returns: dict: mapping readout method to a list of voltages at which peaks were detected. """ peak_locations = {} for read_meth in self.readout_methods.keys(): v_x = self.data[read_meth].voltage_x.values peak_idx = self.peak_indx[read_meth] peak_locations[read_meth] = v_x[peak_idx].tolist() return peak_locations
[docs] def find_peaks( self, absolute_height_threshold: Optional[float] = None, minimal_index_distance: int = 3, ) -> Dict[str, List[int]]: """Locates peaks by using scipy.signal.peaks.""" peaks = {} for read_meth in self.readout_methods.keys(): if absolute_height_threshold is None: absolute_height_threshold = self.relative_height_threshold smooth_curr = self.filtered_data[read_meth].values absolute_height_threshold *= np.max(smooth_curr) self.absolute_height_threshold = absolute_height_threshold found_peaks, _ = sc.signal.find_peaks( self.filtered_data[read_meth].values, height=[self.absolute_height_threshold, None], distance=minimal_index_distance, ) peaks[read_meth] = found_peaks.tolist() return peaks
[docs] def calculate_peak_distances( self, peak_indx: Dict[str, List[int]], ) -> Dict[str, List[float]]: """""" peak_distances: Dict[str, List[float]] = {} for read_meth in self.readout_methods.keys(): voltage = self.data[read_meth].voltage_x.values peak_distances[read_meth] = [] peaks = peak_indx[read_meth] if len(peaks) > 1: for ip in range(len(peaks) - 1): peak = peaks[ip] next_peak = peaks[ip + 1] d = voltage[peak] - voltage[next_peak] peak_distances[read_meth].append(abs(d)) return peak_distances
[docs] def plot_fit( self, ax: Optional[matplotlib.axes.Axes] = None, save_figures: bool = True, filename: Optional[str] = None, file_location: Optional[str] = None, plot_params: Optional[plot_params_type] = None, plot_format: str = "png", ) -> AxesTuple: """""" if plot_params is None: plot_params = default_plot_params matplotlib.rcParams.update(plot_params) fig_title = f"Coulomboscillation fit {self.guid}" if not self.peak_indx: self.find_fit() if ax is None: fig_size = copy.deepcopy(plot_params["figure.figsize"]) fig_size[1] *= len(self.data) * 0.8 # type: ignore fig, ax = plt.subplots(len(self.data), 1, squeeze=False, figsize=fig_size) for r_i, read_meth in enumerate(self.readout_methods.keys()): voltage = self.data[read_meth]["voltage_x"].values signal = self.data[read_meth].values smooth_sig = self.filtered_data[read_meth].values ax[r_i, 0].plot( voltage, signal, color=colors_dict["blue"], label="signal", zorder=6, ) ax[r_i, 0].set_xlabel(self.get_plot_label(read_meth, 0)) ax[r_i, 0].set_ylabel(self.get_plot_label(read_meth, 1)) ax[r_i, 0].set_title(fig_title) ax[r_i, 0].plot( voltage, smooth_sig, color=colors_dict["orange"], label="smooth", zorder=2, ) ax[r_i, 0].plot( voltage[self.peak_indx[read_meth]], smooth_sig[self.peak_indx[read_meth]], "x", color=colors_dict["teal"], label="peaks", ) ax[r_i, 0].vlines( x=voltage[self.peak_indx[read_meth]], ymin=0, ymax=smooth_sig[self.peak_indx[read_meth]], color=colors_dict["teal"], linestyles="dashed", ) height = self.absolute_height_threshold ax[r_i, 0].plot( voltage, np.zeros_like(smooth_sig) + height, "--", color="gray", label="threshold", ) ax[r_i, 0].legend( loc="upper right", bbox_to_anchor=(1, 1), frameon=False, ) ax[r_i, 0].set_ylabel("normalized signal") ax[r_i, 0].set_aspect("auto") ax[r_i, 0].figure.tight_layout() fig.tight_layout() if save_figures: if file_location is None: file_location = os.path.join( nt.config["db_folder"], "tuning_results", self.device_name ) if not os.path.exists(file_location): os.makedirs(file_location) if filename is None: filename = f"coulomboscillationfit_{self.guid}" else: filename = os.path.splitext(filename)[0] path = os.path.join(file_location, filename + "."+ plot_format) plt.savefig(path, format=plot_format, dpi=600, bbox_inches="tight") return ax, None