Exemple #1
0
 def init_plot(self):
     plot = ManualPlotter("CR" + str.lower(self.cal_type.name) + "Fit",
                          x_label=str.lower(self.cal_type.name),
                          y_label='$<Z_{' + self.qubit_names[1] + '}>$')
     plot.add_data_trace("Data 0")
     plot.add_fit_trace("Fit 0")
     plot.add_data_trace("Data 1")
     plot.add_fit_trace("Fit 1")
     return plot
 def init_plots(self):
     self.re_plot = ManualPlotter("Fidelity - Real",
                                  x_label='Bins',
                                  y_label='Real Quadrature')
     self.im_plot = ManualPlotter("Fidelity - Imag",
                                  x_label='Bins',
                                  y_label='Imag Quadrature')
     self.re_plot.add_trace("Excited",
                            matplotlib_kwargs={
                                'color': 'r',
                                'linestyle': '-',
                                'linewidth': 2
                            })
     self.re_plot.add_trace("Excited Gaussian Fit",
                            matplotlib_kwargs={
                                'color': 'r',
                                'linestyle': '--',
                                'linewidth': 2
                            })
     self.re_plot.add_trace("Ground",
                            matplotlib_kwargs={
                                'color': 'b',
                                'linestyle': '-',
                                'linewidth': 2
                            })
     self.re_plot.add_trace("Ground Gaussian Fit",
                            matplotlib_kwargs={
                                'color': 'b',
                                'linestyle': '--',
                                'linewidth': 2
                            })
     self.im_plot.add_trace("Excited",
                            matplotlib_kwargs={
                                'color': 'r',
                                'linestyle': '-',
                                'linewidth': 2
                            })
     self.im_plot.add_trace("Excited Gaussian Fit",
                            matplotlib_kwargs={
                                'color': 'r',
                                'linestyle': '--',
                                'linewidth': 2
                            })
     self.im_plot.add_trace("Ground",
                            matplotlib_kwargs={
                                'color': 'b',
                                'linestyle': '-',
                                'linewidth': 2
                            })
     self.im_plot.add_trace("Ground Gaussian Fit",
                            matplotlib_kwargs={
                                'color': 'b',
                                'linestyle': '--',
                                'linewidth': 2
                            })
     self.add_manual_plotter(self.re_plot)
     self.add_manual_plotter(self.im_plot)
Exemple #3
0
 def init_plot(self):
     plot = ManualPlotter("Ramsey Fit",
                          x_label='Time (us)',
                          y_label='Amplitude (Arb. Units)')
     plot.add_data_trace("Data")
     plot.add_fit_trace("Fit")
     return plot
Exemple #4
0
 def init_plot(self):
     plot = ManualPlotter("Qubit Search",
                          x_label='Frequency (GHz)',
                          y_label='Amplitude (Arb. Units)')
     plot.add_data_trace("Data")
     plot.add_fit_trace("Fit")
     return plot
Exemple #5
0
    def init_plots(self):
        """Initialize manual plotters for the mixer calibration. Three plot tabs open, one for I,Q offset, one for
        phase skew, one for amplitude imbalance.
        """
        self.plt1 = ManualPlotter(name="Mixer offset calibration",
                                  x_label='{} {} offset (V)'.format(
                                      self.channel, self.mixer),
                                  y_label='Power (dBm)')
        self.plt1.add_data_trace("I-offset", {'color': 'C1'})
        self.plt1.add_data_trace("Q-offset", {'color': 'C2'})
        self.plt1.add_fit_trace("Fit I-offset",
                                {'color': 'C1'})  #TODO: fix axis labels
        self.plt1.add_fit_trace("Fit Q-offset", {'color': 'C2'})

        self.plt2 = ManualPlotter(name="Mixer amp calibration",
                                  x_label='{} {} amplitude (V)'.format(
                                      self.channel, self.mixer),
                                  y_label='Power (dBm)')
        self.plt2.add_data_trace("amplitude_factor", {'color': 'C4'})
        self.plt2.add_fit_trace("Fit amplitude_factor", {'color': 'C4'})

        self.plt3 = ManualPlotter(name="Mixer phase calibration",
                                  x_label='{} {} phase (rad)'.format(
                                      self.channel, self.mixer),
                                  y_label='Power (dBm)')
        self.plt3.add_data_trace("phase_skew", {'color': 'C3'})
        self.plt3.add_fit_trace("Fit phase_skew", {'color': 'C3'})

        self.plotters = [self.plt1, self.plt2, self.plt3]
        return self.plotters
Exemple #6
0
class MixerCalibration(Calibration):
    """A calibration experiment that determines the optimal I and Q offsets, amplitude imbalance and phase imbalance
    for single-sideband modulation of  a mixer. Please see Analog Devices Application Note AN-1039 for an explanation
    of the optimization of a mixer for maximum sideband supression.
    """

    MIN_OFFSET = -0.4
    MAX_OFFSET = 0.4
    MIN_AMPLITUDE = 0.2
    MAX_AMPLITUDE = 1.5
    MIN_PHASE = -0.3
    MAX_PHASE = 0.3

    def __init__(self,
                 channel,
                 spectrum_analyzer,
                 mixer="control",
                 first_cal="phase",
                 offset_range=(-0.2, 0.2),
                 amp_range=(0.4, 0.8),
                 phase_range=(-np.pi / 2, np.pi / 2),
                 nsteps=101,
                 use_fit=True,
                 plot=True):
        """A mixer calibration for a specific LogicalChannel and mixer.

        Args:
            channel:                    The Auspex LogicalChannel for which to calibrate the mixer.
            spectrum_analyzer:          The spectrum analyzer instrument to use for calibration.
            mixer (optional):           'control' or 'measure', the corresponding mixer to calibrate.
                                        Defaults to 'control'.
            first_cal (optional):       'phase' or 'amplitude'. Calibrate the phase skew or amplitude imbalance first.
                                        Defaults to 'phase'.
            offset_range (optional):    I and Q offset range to sweep over. Defaults to (-0.2, 0.2).
            amp_range (optional):       Amplitude imbalance range to sweep over. Defaults to (0.4, 0.8).
            phase_range (optional):     Phase range to sweep over. Defaults to (-pi/2, pi/2).
            nsteps (optional):          Number of points to sweep over for each calibraton.
            use_fit (optional):         If true, find power minumum using a fit; otherwise use minimum value from data.
                                        Defaults to True.
            plot (optional):            Plot the calibration as it happens.
        """

        self.channel = channel
        self.spectrum_analyzer = spectrum_analyzer
        self.mixer = mixer
        self.do_plotting = plot
        self.first_cal = first_cal
        self.offset_range = offset_range
        self.amp_range = amp_range
        self.phase_range = phase_range
        self.nsteps = nsteps
        self.use_fit = use_fit
        super(MixerCalibration, self).__init__()

    def init_plots(self):
        """Initialize manual plotters for the mixer calibration. Three plot tabs open, one for I,Q offset, one for
        phase skew, one for amplitude imbalance.
        """
        self.plt1 = ManualPlotter(name="Mixer offset calibration",
                                  x_label='{} {} offset (V)'.format(
                                      self.channel, self.mixer),
                                  y_label='Power (dBm)')
        self.plt1.add_data_trace("I-offset", {'color': 'C1'})
        self.plt1.add_data_trace("Q-offset", {'color': 'C2'})
        self.plt1.add_fit_trace("Fit I-offset",
                                {'color': 'C1'})  #TODO: fix axis labels
        self.plt1.add_fit_trace("Fit Q-offset", {'color': 'C2'})

        self.plt2 = ManualPlotter(name="Mixer amp calibration",
                                  x_label='{} {} amplitude (V)'.format(
                                      self.channel, self.mixer),
                                  y_label='Power (dBm)')
        self.plt2.add_data_trace("amplitude_factor", {'color': 'C4'})
        self.plt2.add_fit_trace("Fit amplitude_factor", {'color': 'C4'})

        self.plt3 = ManualPlotter(name="Mixer phase calibration",
                                  x_label='{} {} phase (rad)'.format(
                                      self.channel, self.mixer),
                                  y_label='Power (dBm)')
        self.plt3.add_data_trace("phase_skew", {'color': 'C3'})
        self.plt3.add_fit_trace("Fit phase_skew", {'color': 'C3'})

        self.plotters = [self.plt1, self.plt2, self.plt3]
        return self.plotters

    def _calibrate(self):
        """Run the actual calibration routine.
        """
        offset_pts = np.linspace(self.offset_range[0], self.offset_range[1],
                                 self.nsteps)
        amp_pts = np.linspace(self.amp_range[0], self.amp_range[1],
                              self.nsteps)
        phase_pts = np.linspace(self.phase_range[0], self.phase_range[1],
                                self.nsteps)
        first_cal = self.first_cal

        config_dict = {
            "I_offset": 0.0,
            "Q_offset": 0.0,
            "phase_skew": 0.0,
            "amplitude_factor": 1.0,
            "sideband_modulation": False
        }

        I1_amps = self.run_sweeps("I_offset", offset_pts, config_dict)
        try:
            I1_offset, xpts, ypts = find_null_offset(offset_pts[1:],
                                                     I1_amps[1:],
                                                     use_fit=self.use_fit)
        except:
            raise ValueError("Could not find null offset")
        self.plt1["I-offset"] = (offset_pts, I1_amps)
        self.plt1["Fit I-offset"] = (xpts, ypts)
        logger.info("Found first pass I offset of {}.".format(I1_offset))
        config_dict['I_offset'] = I1_offset

        Q1_amps = self.run_sweeps("Q_offset", offset_pts, config_dict)
        try:
            Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:],
                                                     Q1_amps[1:],
                                                     use_fit=self.use_fit)
        except:
            raise ValueError("Could not find null offset")
        self.plt1["Q-offset"] = (offset_pts, Q1_amps)
        self.plt1["Fit Q-offset"] = (xpts, ypts)
        logger.info("Found first pass Q offset of {}.".format(Q1_offset))
        config_dict['Q_offset'] = Q1_offset

        I2_amps = self.run_sweeps("I_offset", offset_pts, config_dict)
        try:
            I2_offset, xpts, ypts = find_null_offset(offset_pts[1:],
                                                     I2_amps[1:],
                                                     use_fit=self.use_fit)
        except:
            raise ValueError("Could not find null offset")
        self.plt1["I-offset"] = (offset_pts, I2_amps)
        self.plt1["Fit I-offset"] = (xpts, ypts)
        logger.info("Found second pass I offset of {}.".format(I2_offset))
        config_dict['I_offset'] = I2_offset

        #this is a bit hacky but OK...
        cals = {"phase": "phase_skew", "amplitude": "amplitude_factor"}
        cal_pts = {"phase": phase_pts, "amplitude": amp_pts}
        correct_plotter = {"phase": self.plt3, "amplitude": self.plt2}
        cal_defaults = {"phase": 0.0, "amplitude": 1.0}
        if first_cal not in cals.keys():
            raise ValueError(
                "First calibration should be one of ('phase, amplitude'). Instead got {}"
                .format(first_cal))
        second_cal = list(set(cals.keys()).difference({
            first_cal,
        }))[0]

        config_dict['sideband_modulation'] = True

        amps1 = self.run_sweeps(cals[first_cal], cal_pts[first_cal],
                                config_dict)
        try:
            offset1, xpts, ypts = find_null_offset(
                cal_pts[first_cal][1:],
                amps1[1:],
                default=cal_defaults[first_cal],
                use_fit=self.use_fit)
        except:
            raise ValueError("Could not find null offset")
        correct_plotter[first_cal][cals[first_cal]] = (cal_pts[first_cal],
                                                       amps1)
        correct_plotter[first_cal]["Fit " + cals[first_cal]] = (xpts, ypts)
        logger.info("Found {} of {}.".format(
            str.replace(cals[first_cal], '_', ' '), offset1))
        config_dict[cals[first_cal]] = offset1

        amps2 = self.run_sweeps(cals[second_cal], cal_pts[second_cal],
                                config_dict)
        try:
            offset2, xpts, ypts = find_null_offset(
                cal_pts[second_cal][1:],
                amps2[1:],
                default=cal_defaults[second_cal],
                use_fit=self.use_fit)
        except:
            raise ValueError("Could not find null offset")
        correct_plotter[second_cal][cals[second_cal]] = (cal_pts[second_cal],
                                                         amps2)
        correct_plotter[second_cal]["Fit " + cals[second_cal]] = (xpts, ypts)
        logger.info("Found {} of {}.".format(
            str.replace(cals[first_cal], '_', ' '), offset2))
        config_dict[cals[second_cal]] = offset2

        # if write_to_file:
        #     mce.write_to_file()
        logger.info(
            ("Mixer calibration: I offset = {}, Q offset = {}, "
             "Amplitude Imbalance = {}, Phase Skew = {}").format(
                 config_dict["I_offset"], config_dict["Q_offset"],
                 config_dict["amplitude_factor"], config_dict["phase_skew"]))

        assert config_dict["I_offset"] > self.MIN_OFFSET and config_dict[
            "I_offset"] < self.MAX_OFFSET, "I_offset looks suspicious."
        assert config_dict["Q_offset"] > self.MIN_OFFSET and config_dict[
            "Q_offset"] < self.MAX_OFFSET, "Q_offset looks suspicious."
        assert config_dict[
            "amplitude_factor"] > self.MIN_AMPLITUDE and config_dict[
                "amplitude_factor"] < self.MAX_AMPLITUDE, "amplitude_factor looks suspicious."
        assert config_dict["phase_skew"] > self.MIN_PHASE and config_dict[
            "phase_skew"] < self.MAX_PHASE, "phase_skew looks suspicious."

        self.succeeded = True
        self.config_dict = config_dict

    def update_settings(self):
        """Update the channel library settings to those found by calibration routine.
        """
        self.exp._phys_chan.amp_factor = self.config_dict["amplitude_factor"]
        self.exp._phys_chan.phase_skew = self.config_dict["phase_skew"]
        self.exp._phys_chan.I_channel_offset = self.config_dict["I_offset"]
        self.exp._phys_chan.Q_channel_offset = self.config_dict["Q_offset"]

    def run_sweeps(self, sweep_parameter, pts, config_dict):
        """Run the calibration sweeps.
        """
        self.exp = MixerCalibrationExperiment(self.channel,
                                              self.spectrum_analyzer,
                                              config_dict,
                                              mixer=self.mixer)
        self.exp.add_sweep(getattr(self.exp, sweep_parameter), pts)
        self.exp.run_sweeps()
        return self.exp.buff.get_data()[0]
Exemple #7
0
class SingleShotFidelityExperiment(QubitExperiment):
    """Experiment to measure single-shot measurement fidelity of a qubit."""
    def __init__(self,
                 qubit_names,
                 num_shots=10000,
                 expname=None,
                 meta_file=None,
                 save_data=False,
                 optimize=False,
                 set_threshold=True,
                 stream_type='Raw',
                 **kwargs):
        """Create a single shot fidelity measurement experiment. Assumes that there is a single shot measurement
        filter in the filter pipeline.
        Arguments:
            qubit_names: Names of the qubits to be measured. (str)
            num_shots: Total number of 0 and 1 measurements used to reconstruct fidelity histograms (int)
            expname: Experiment name for data saving.
            meta_file: Meta file for defining custom single-shot fidelity experiment.
            save_data: If true, will save the raw or demodulated data.
            optimize: if true, will set the swept parameters to their optimum values"""

        super(SingleShotFidelityExperiment, self).__init__()
        self.qubit_names = qubit_names if isinstance(qubit_names,
                                                     list) else [qubit_names]
        self.qubit = [QubitFactory(qubit_name)
                      for qubit_name in qubit_names] if isinstance(
                          qubit_names, list) else QubitFactory(qubit_names)
        # make a copy of the settings to restore default
        self.saved_settings = config.load_meas_file(config.meas_file)
        self.settings = deepcopy(self.saved_settings)
        self.save_data = save_data
        self.calibration = True
        self.optimize = optimize
        self.set_threshold = True
        self.name = expname
        self.cw_mode = False
        self.repeats = num_shots
        self.ss_stream_type = stream_type

        if meta_file is None:
            meta_file = SingleShot(self.qubit)

        self._squash_round_robins()

        QubitExpFactory.load_meta_info(self, meta_file)
        QubitExpFactory.load_instruments(self)
        QubitExpFactory.load_qubits(self)
        QubitExpFactory.load_filters(self)
        if 'sweeps' in self.settings:
            QubitExpFactory.load_parameter_sweeps(experiment)
        self.ssf = self.find_single_shot_filter()
        self.leave_plot_server_open = True

    def run_sweeps(self):
        #For now, only update histograms if we don't have a parameter sweep.
        if not self.sweeper.axes:
            self.init_plots()
            self.add_manual_plotter(self.re_plot)
            self.add_manual_plotter(self.im_plot)
        else:
            if any([
                    x.save_kernel.value for x in self.filters.values()
                    if type(x) is SingleShotMeasurement
            ]):
                logger.warning(
                    "Kernel saving is not supported if you have parameter sweeps!"
                )

        super(SingleShotFidelityExperiment, self).run_sweeps()

        if not self.sweeper.axes:
            self._update_histogram_plots()

        if hasattr(self, 'extra_plot_server'):
            try:
                self.extra_plot_server.stop()
            except:
                pass

        if self.set_threshold:
            self.update_threshold()

        if self.sweeper.axes and self.optimize:
            #select the buffers/writers whose sources are singleshot filters
            fid_buffers = [
                buff for buff in self.buffers if self.settings['filters'][
                    buff.name]['source'].strip().split()[1] == 'fidelity'
            ]
            if not fid_buffers:
                raise NameError(
                    "Please connect a buffer to the single-shot filter output in order to optimize fidelity."
                )
            #set sweep parameters to the values that maximize fidelity. Then update the saved_settings with the new values
            for buff in fid_buffers:
                dataset, descriptor = buff.get_data(), buff.get_descriptor()
                opt_ind = np.argmax(dataset['Data'])
                for k, axis in enumerate(self.sweeper.axes):
                    instr_tree = axis.parameter.instr_tree
                    param_key = self.saved_settings['instruments']
                    for key in instr_tree[:-1]:
                        # go through the tree
                        param_key = param_key[key]
                    opt_value = float(dataset[axis.name][opt_ind])
                    # special case to set APS ch12 amplitudes
                    if instr_tree[-1] == 'amplitude' and instr_tree[
                            -2] in self.saved_settings['instruments'].keys():
                        param_key['tx_channels']['12']['1'][
                            'amplitude'] = round(float(opt_value), 5)
                        param_key['tx_channels']['12']['2'][
                            'amplitude'] = round(float(opt_value), 5)
                    else:
                        param_key[instr_tree[-1]] = opt_value
                    logger.info("Set{} to {}.".format(
                        " ".join(str(x) for x in instr_tree), opt_value))

        if self.set_threshold or (
                self.sweeper.axes and
                self.optimize):  # update settings if something was calibrated
            config.dump_meas_file(self.saved_settings, config.meas_file)

    def _update_histogram_plots(self):
        pdf_data = self.get_results()
        self.re_plot.set_data("Ground", pdf_data["I Bins"],
                              pdf_data["Ground I PDF"])
        self.re_plot.set_data("Ground Gaussian Fit", pdf_data["I Bins"],
                              pdf_data["Ground I Gaussian PDF"])
        self.re_plot.set_data("Excited", pdf_data["I Bins"],
                              pdf_data["Excited I PDF"])
        self.re_plot.set_data("Excited Gaussian Fit", pdf_data["I Bins"],
                              pdf_data["Excited I Gaussian PDF"])
        self.im_plot.set_data("Ground", pdf_data["Q Bins"],
                              pdf_data["Ground Q PDF"])
        self.im_plot.set_data("Ground Gaussian Fit", pdf_data["Q Bins"],
                              pdf_data["Ground Q Gaussian PDF"])
        self.im_plot.set_data("Excited", pdf_data["Q Bins"],
                              pdf_data["Excited Q PDF"])
        self.im_plot.set_data("Excited Gaussian Fit", pdf_data["Q Bins"],
                              pdf_data["Excited Q Gaussian PDF"])

    def update_threshold(self):
        if 'I Threshold' in self.ssf[0].pdf_data:
            self.saved_settings['filters'][self.qubit_to_stream_sel[
                self.qubit.label]]['threshold'] = round(
                    float(self.ssf[0].pdf_data['I Threshold']), 6)

    def init_plots(self):
        self.re_plot = ManualPlotter("Fidelity - Real",
                                     x_label='Bins',
                                     y_label='Real Quadrature')
        self.im_plot = ManualPlotter("Fidelity - Imag",
                                     x_label='Bins',
                                     y_label='Imag Quadrature')
        self.re_plot.add_trace("Excited",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.re_plot.add_trace("Excited Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })
        self.re_plot.add_trace("Ground",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.re_plot.add_trace("Ground Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Excited",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Excited Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Ground",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Ground Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })

    def _squash_round_robins(self):
        """Make it so that the round robins are set to 1."""
        digitizers = [
            _ for _ in self.settings['instruments'].keys()
            if 'nbr_round_robins' in self.settings['instruments'][_].keys()
        ]
        for d in digitizers:
            logger.info(
                "Set digitizer {} round robins to 1 for single shot experiment."
                .format(d))
            self.settings['instruments'][d]['nbr_round_robins'] = 1
        # disable averagers
        for _, f in self.settings['filters'].items():
            if f['type'] == 'Averager':
                f['enabled'] = False

    def find_single_shot_filter(self):
        """Make sure there is one single shot measurement filter in the pipeline."""
        ssf = [
            x for x in self.filters.values()
            if type(x) is SingleShotMeasurement
        ]
        if len(ssf) > 1:
            raise NotImplementedError(
                "Single shot fidelity for more than one qubit is not yet implemented."
            )
        elif len(ssf) == 0:
            raise NameError(
                "There do not appear to be any single-shot measurements in your filter pipeline. Please add one!"
            )
        return ssf

    def get_results(self):
        """Get the PDF and fidelity numbers from the filters. Returns a dictionary of PDF data with the
        filter names as keys."""
        ssf = self.find_single_shot_filter()
        if len(ssf) > 1:  # not implemented
            try:
                return {x.name: x.pdf_data for x in ssf}
            except AttributeError:
                raise AttributeError(
                    "Could not find single shot PDF data in results. Did you run the sweeps?"
                )
        else:
            try:
                return ssf[0].pdf_data
            except AttributeError:
                raise AttributeError(
                    "Could not find single shot PDF data in results. Did you run the sweeps?"
                )
        pass

    async def run(self):
        r = np.power(self.amplitude.value, 2) + 0.1 * np.random.random()
        await self.voltage.push(r)
        await asyncio.sleep(0.01)


if __name__ == '__main__':

    exp = TestExperiment()
    exp.leave_plot_server_open = True

    # Create the plotter and the actual traces we'll need
    plt = ManualPlotter("Manual Plotting Test",
                        x_label='X Thing',
                        y_label='Y Thing')
    plt.add_data_trace("Example Data")
    plt.add_fit_trace("Example Fit")
    buff = DataBuffer()

    edges = [(exp.voltage, buff.sink)]
    exp.set_graph(edges)

    # Create a plotter callback
    def plot_me(plot):
        ys = buff.get_data()['voltage']
        xs = buff.descriptor.axes[0].points
        plot["Example Data"] = (xs, ys)
        plot["Example Fit"] = (xs, ys + 0.1)
Exemple #9
0
    def calibrate_mixer(qubit,
                        mixer="control",
                        first_cal="phase",
                        write_to_file=True,
                        offset_range=(-0.2, 0.2),
                        amp_range=(0.6, 1.4),
                        phase_range=(-np.pi / 6, np.pi / 6),
                        nsteps=51):
        """Calibrates IQ mixer offset, amplitude imbalanace, and phase skew.
        See Analog Devices Application note AN-1039. Parses instrument connectivity from
        the experiment settings YAML.
        Arguments:
            qubit: Qubit identifier string.
            mixer: One of ("control", "measure") to select which IQ channel is calibrated.
            first_cal: One of ("phase", "amplitude") to select which adjustment is attempted
            first. You should pick whichever the particular mixer is most sensitive to.
            For example, a mixer with -40dBc sideband supression at 1 degree of phase skew
            and 0.1 dB amplitude imbalance should calibrate the phase first.
        """
        spm = auspex.config.single_plotter_mode
        auspex.config.single_plotter_mode = True

        def sweep_offset(name, pts):
            mce.clear_sweeps()
            mce.add_sweep(getattr(mce, name), pts)
            mce.keep_instruments_connected = True
            mce.run_sweeps()

        offset_pts = np.linspace(offset_range[0], offset_range[1], nsteps)
        amp_pts = np.linspace(amp_range[0], amp_range[1], nsteps)
        phase_pts = np.linspace(phase_range[0], phase_range[1], nsteps)

        buff = DataBuffer()
        plt = ManualPlotter(name="Mixer offset calibration",
                            x_label='{} {} offset (V)'.format(qubit, mixer),
                            y_label='Power (dBm)')
        plt.add_data_trace("I-offset", {'color': 'C1'})
        plt.add_data_trace("Q-offset", {'color': 'C2'})
        plt.add_fit_trace("Fit I-offset",
                          {'color': 'C1'})  #TODO: fix axis labels
        plt.add_fit_trace("Fit Q-offset", {'color': 'C2'})

        plt2 = ManualPlotter(name="Mixer  amp/phase calibration",
                             x_label='{} {} amplitude (V)/phase (rad)'.format(
                                 qubit, mixer),
                             y_label='Power (dBm)')
        plt2.add_data_trace("phase_skew", {'color': 'C3'})
        plt2.add_data_trace("amplitude_factor", {'color': 'C4'})
        plt2.add_fit_trace("Fit phase_skew", {'color': 'C3'})
        plt2.add_fit_trace("Fit amplitude_factor", {'color': 'C4'})

        mce = MixerCalibrationExperiment(qubit, mixer=mixer)
        mce.add_manual_plotter(plt)
        mce.add_manual_plotter(plt2)
        mce.leave_plot_server_open = True
        QubitExpFactory.load_instruments(mce, mce.instruments_to_enable)
        edges = [(mce.amplitude, buff.sink)]
        mce.set_graph(edges)

        sweep_offset("I_offset", offset_pts)
        I1_amps = np.array([x[1] for x in buff.get_data()])
        try:
            I1_offset, xpts, ypts = find_null_offset(offset_pts[1:],
                                                     I1_amps[1:])
        except:
            mce.extra_plot_server.stop()
            return
        plt["I-offset"] = (offset_pts, I1_amps)
        plt["Fit I-offset"] = (xpts, ypts)
        logger.info("Found first pass I offset of {}.".format(I1_offset))
        mce.I_offset.value = I1_offset

        mce.first_exp = False  # slight misnomer to indicate that no new plot is needed
        sweep_offset("Q_offset", offset_pts)
        Q1_amps = np.array([x[1] for x in buff.get_data()])
        try:
            Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:],
                                                     Q1_amps[1:])
        except:
            mce.extra_plot_server.stop()
            return
        plt["Q-offset"] = (offset_pts, Q1_amps)
        plt["Fit Q-offset"] = (xpts, ypts)
        logger.info("Found first pass Q offset of {}.".format(Q1_offset))
        mce.Q_offset.value = Q1_offset

        sweep_offset("I_offset", offset_pts)
        I2_amps = np.array([x[1] for x in buff.get_data()])
        try:
            I2_offset, xpts, ypts = find_null_offset(offset_pts[1:],
                                                     I2_amps[1:])
        except:
            mce.extra_plot_server.stop()
            return
        plt["I-offset"] = (offset_pts, I2_amps)
        plt["Fit I-offset"] = (xpts, ypts)
        logger.info("Found second pass I offset of {}.".format(I2_offset))
        mce.I_offset.value = I2_offset

        #this is a bit hacky but OK...
        cals = {"phase": "phase_skew", "amplitude": "amplitude_factor"}
        cal_pts = {"phase": phase_pts, "amplitude": amp_pts}
        cal_defaults = {"phase": 0.0, "amplitude": 1.0}
        if first_cal not in cals.keys():
            raise ValueError(
                "First calibration should be one of ('phase, amplitude'). Instead got {}"
                .format(first_cal))
        second_cal = list(set(cals.keys()).difference({
            first_cal,
        }))[0]

        mce.sideband_modulation = True

        sweep_offset(cals[first_cal], cal_pts[first_cal])
        amps1 = np.array([x[1] for x in buff.get_data()])
        try:
            offset1, xpts, ypts = find_null_offset(
                cal_pts[first_cal][1:],
                amps1[1:],
                default=cal_defaults[first_cal])
        except:
            mce.extra_plot_server.stop()
            return
        plt2[cals[first_cal]] = (cal_pts[first_cal], amps1)
        plt2["Fit " + cals[first_cal]] = (xpts, ypts)
        logger.info("Found {} of {}.".format(
            str.replace(cals[first_cal], '_', ' '), offset1))
        getattr(mce, cals[first_cal]).value = offset1

        sweep_offset(cals[second_cal], cal_pts[second_cal])
        amps2 = np.array([x[1] for x in buff.get_data()])
        try:
            offset2, xpts, ypts = find_null_offset(
                cal_pts[second_cal][1:],
                amps2[1:],
                default=cal_defaults[second_cal])
        except:
            mce.extra_plot_server.stop()
            return
        plt2[cals[second_cal]] = (cal_pts[second_cal], amps2)
        plt2["Fit " + cals[second_cal]] = (xpts, ypts)
        logger.info("Found {} of {}.".format(
            str.replace(cals[first_cal], '_', ' '), offset2))
        getattr(mce, cals[second_cal]).value = offset2

        mce.disconnect_instruments()
        try:
            mce.extra_plot_server.stop()
        except:
            logger.info('Mixer plot server was not successfully stopped.')

        if write_to_file:
            mce.write_to_file()
        logger.info(("Mixer calibration: I offset = {}, Q offset = {}, "
                     "Amplitude Imbalance = {}, Phase Skew = {}").format(
                         mce.I_offset.value, mce.Q_offset.value,
                         mce.amplitude_factor.value, mce.phase_skew.value))
        auspex.config.single_plotter_mode = spm
class SingleShotFidelityExperiment(QubitExperiment):
    """Experiment to measure single-shot measurement fidelity of a qubit.

        Args:
            qubit:                          qubit object
            output_nodes (optional):        the output node of the filter pipeline to use for single-shot readout. The default is choses, if single output.
            meta_file (string, optional):   path to the QGL sequence meta_file. Default to standard SingleShot sequence
            optimize (bool, optional):      if True and a qubit_sweep is added, set the parameter corresponding to the maximum measured fidelity at the end of the sweep

    """
    def __init__(self,
                 qubit,
                 sample_name=None,
                 output_nodes=None,
                 meta_file=None,
                 optimize=True,
                 set_threshold=True,
                 **kwargs):

        self.pdf_data = []
        self.qubit = qubit
        self.optimize = optimize
        self.set_threshold = set_threshold

        if meta_file:
            self.meta_file = meta_file
        else:
            self.meta_file = self._single_shot_sequence(self.qubit)

        super(SingleShotFidelityExperiment,
              self).__init__(self.meta_file, **kwargs)

        if not sample_name:
            sample_name = self.qubit.label
        if not bbndb.get_cl_session():
            raise Exception("Attempting to load Calibrations database, \
                but no database session is open! Have the ChannelLibrary and PipelineManager been created?"
                            )
        existing_samples = list(bbndb.get_cl_session().query(
            bbndb.calibration.Sample).filter_by(name=sample_name).all())
        if len(existing_samples) == 0:
            logger.info("Creating a new sample in the calibration database.")
            self.sample = bbndb.calibration.Sample(name=sample_name)
            bbndb.get_cl_session().add(self.sample)
        elif len(existing_samples) == 1:
            self.sample = existing_samples[0]
        else:
            raise Exception(
                "Multiple samples found in calibration database with the same name! How?"
            )

    def guess_output_nodes(self, graph):
        output_nodes = []
        for qubit in self.qubits:
            ds = nx.descendants(graph, self.qubit_proxies[qubit.label])
            outputs = [
                d for d in ds
                if isinstance(d, (bbndb.auspex.Write, bbndb.auspex.Buffer))
            ]
            if len(outputs) != 1:
                raise Exception(
                    f"More than one output node found for {qubit}, please explicitly define output node using output_nodes argument."
                )
            output_nodes.append(outputs[0])
        return output_nodes

    def _single_shot_sequence(self, qubit):
        seqs = create_cal_seqs((qubit, ), 1)
        return compile_to_hardware(seqs, 'SingleShot/SingleShot')

    def init_plots(self):
        self.re_plot = ManualPlotter("Fidelity - Real",
                                     x_label='Bins',
                                     y_label='Real Quadrature')
        self.im_plot = ManualPlotter("Fidelity - Imag",
                                     x_label='Bins',
                                     y_label='Imag Quadrature')
        self.re_plot.add_trace("Excited",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.re_plot.add_trace("Excited Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })
        self.re_plot.add_trace("Ground",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.re_plot.add_trace("Ground Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Excited",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Excited Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'r',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Ground",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '-',
                                   'linewidth': 2
                               })
        self.im_plot.add_trace("Ground Gaussian Fit",
                               matplotlib_kwargs={
                                   'color': 'b',
                                   'linestyle': '--',
                                   'linewidth': 2
                               })
        self.add_manual_plotter(self.re_plot)
        self.add_manual_plotter(self.im_plot)

    def _update_histogram_plots(self):
        self.re_plot["Ground"] = (self.pdf_data[-1]["I Bins"],
                                  self.pdf_data[-1]["Ground I PDF"])
        self.re_plot["Ground Gaussian Fit"] = (
            self.pdf_data[-1]["I Bins"],
            self.pdf_data[-1]["Ground I Gaussian PDF"])
        self.re_plot["Excited"] = (self.pdf_data[-1]["I Bins"],
                                   self.pdf_data[-1]["Excited I PDF"])
        self.re_plot["Excited Gaussian Fit"] = (
            self.pdf_data[-1]["I Bins"],
            self.pdf_data[-1]["Excited I Gaussian PDF"])
        self.im_plot["Ground"] = (self.pdf_data[-1]["Q Bins"],
                                  self.pdf_data[-1]["Ground Q PDF"])
        self.im_plot["Ground Gaussian Fit"] = (
            self.pdf_data[-1]["Q Bins"],
            self.pdf_data[-1]["Ground Q Gaussian PDF"])
        self.im_plot["Excited"] = (self.pdf_data[-1]["Q Bins"],
                                   self.pdf_data[-1]["Excited Q PDF"])
        self.im_plot["Excited Gaussian Fit"] = (
            self.pdf_data[-1]["Q Bins"],
            self.pdf_data[-1]["Excited Q Gaussian PDF"])

    def run_sweeps(self):
        if not self.sweeper.axes:
            self.init_plots()
            self.start_manual_plotters()
        else:
            for f in self.filters:
                if isinstance(f, SingleShotMeasurement):
                    f.save_kernel.value = False
        super(SingleShotFidelityExperiment, self).run_sweeps()
        self.get_results()
        if not self.sweeper.axes:
            self._update_histogram_plots()
            self.stop_manual_plotters()
            if self.set_threshold:
                self.stream_selectors[0].threshold = self.get_threshold()[0]
            if self.sample:
                c = bbndb.calibration.Calibration(value=self.get_fidelity()[0],
                                                  sample=self.sample,
                                                  name="Readout fid.",
                                                  category="Readout")
                c.date = datetime.datetime.now()
                bbndb.get_cl_session().add(c)
                bbndb.get_cl_session().commit()
        elif self.optimize:
            fidelities = [f['Max I Fidelity'] for f in self.pdf_data]
            opt_ind = np.argmax(fidelities)
            for k, axis in enumerate(self.sweeper.axes):
                set_pair = axis.parameter.set_pair
                opt_value = axis.points[opt_ind]
                if set_pair[1] == 'amplitude' or set_pair[1] == "offset":
                    # special case for APS chans
                    param = [
                        c for c in self.chan_db.channels
                        if c.label == set_pair[0]
                    ][0]
                    attr = 'amp_factor' if set_pair[
                        1] == 'amplitude' else 'offset'
                    setattr(param, f'I_channel_{attr}', opt_value)
                    setattr(param, f'Q_channel_{attr}', opt_value)
                else:
                    param = [
                        c for c in self.chan_db.all_instruments()
                        if c.label == set_pair[0]
                    ][0]
                    setattr(param, set_pair[1], opt_value)
            logger.info(
                f'Set {set_pair[0]} {set_pair[1]} to optimum value {opt_value}'
            )
            if self.set_threshold:
                self.stream_selectors[0].threshold = self.get_threshold(
                )[opt_ind]
                logger.info(
                    f'Set threshold to {self.stream_selectors[0].threshold}')

    def find_single_shot_filter(self):
        """Make sure there is one single shot measurement filter in the pipeline."""
        ssf = [x for x in self.filters if type(x) is SingleShotMeasurement]
        if len(ssf) > 1:
            raise NotImplementedError(
                "Single shot fidelity for more than one qubit is not yet implemented."
            )
        elif len(ssf) == 0:
            raise NameError(
                "There do not appear to be any single-shot measurements in your filter pipeline. Please add one!"
            )
        return ssf

    def get_fidelity(self):
        if not self.pdf_data:
            raise Exception(
                "Could not find single shot PDF data in results. Did you run the sweeps?"
            )
        return [p["Max I Fidelity"] for p in self.pdf_data]

    def get_threshold(self):
        if not self.pdf_data:
            raise Exception(
                "Could not find single shot PDF data in results. Did you run the sweeps?"
            )
        return [p["I Threshold"] for p in self.pdf_data]

    def get_results(self):
        """Get the PDF and fidelity numbers from the filters. Returns a dictionary of PDF data with the
        filter names as keys."""
        if len(self.pdf_data) == 0:
            ssf = self.find_single_shot_filter()
            while True:
                try:
                    self.pdf_data.append(ssf[0].pdf_data_queue.get(False))
                except queue.Empty as e:
                    break
            if len(self.pdf_data) == 0:
                raise Exception(
                    "Could not find single shot PDF data in results. Did you run the sweeps?"
                )