def _generate_fit_guesses( self, user_opt: curve.FitOptions, curve_data: curve.CurveData, ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Create algorithmic guess with analysis options and curve data. Args: user_opt: Fit options filled with user provided guess and bounds. curve_data: Formatted data collection to fit. Returns: List of fit options that are passed to the fitter function. """ max_abs_y, _ = curve.guess.max_height(curve_data.y, absolute=True) user_opt.bounds.set_if_empty( amp=(-2 * max_abs_y, 2 * max_abs_y), tau=(0, np.inf), base=(-max_abs_y, max_abs_y), phase=(-np.pi, np.pi), ) # Default guess values freq_guesses, base_guesses = [], [] for series in ["X", "Y"]: data = curve_data.get_subset_of(series) freq_guesses.append(curve.guess.frequency(data.x, data.y)) base_guesses.append(curve.guess.constant_sinusoidal_offset(data.y)) freq_val = float(np.average(freq_guesses)) user_opt.p0.set_if_empty(base=np.average(base_guesses)) # Guess the exponential decay by combining both curves data_x = curve_data.get_subset_of("X") data_y = curve_data.get_subset_of("Y") decay_data = (data_x.y - user_opt.p0["base"]) ** 2 + (data_y.y - user_opt.p0["base"]) ** 2 user_opt.p0.set_if_empty( tau=-curve.guess.exp_decay(data_x.x, decay_data), amp=0.5, phase=0.0, ) opt_fp = user_opt.copy() opt_fp.p0.set_if_empty(freq=freq_val) opt_fm = user_opt.copy() opt_fm.p0.set_if_empty(freq=-freq_val) return [opt_fp, opt_fm]
def _generate_fit_guesses( self, user_opt: curve.FitOptions ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Compute the initial guesses. Args: user_opt: Fit options filled with user provided guess and bounds. Returns: List of fit options that are passed to the fitter function. """ curve_data = self._data() max_abs_y, _ = curve.guess.max_height(curve_data.y, absolute=True) user_opt.bounds.set_if_empty( amp=(-2 * max_abs_y, 2 * max_abs_y), freq=(0, np.inf), phase=(-np.pi, np.pi), base=(-max_abs_y, max_abs_y), ) user_opt.p0.set_if_empty( freq=curve.guess.frequency(curve_data.x, curve_data.y), base=curve.guess.constant_sinusoidal_offset(curve_data.y), ) user_opt.p0.set_if_empty( amp=curve.guess.max_height(curve_data.y - user_opt.p0["base"], absolute=True)[0], ) options = [] for phase_guess in np.linspace(0, np.pi, 5): new_opt = user_opt.copy() new_opt.p0.set_if_empty(phase=phase_guess) options.append(new_opt) return options
def _generate_fit_guesses( self, user_opt: curve.FitOptions, curve_data: curve.CurveData, ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Create algorithmic guess with analysis options and curve data. Args: user_opt: Fit options filled with user provided guess and bounds. curve_data: Formatted data collection to fit. Returns: List of fit options that are passed to the fitter function. """ # Use a fast Fourier transform to guess the frequency. x_data = curve_data.get_subset_of("series-0").x min_beta, max_beta = min(x_data), max(x_data) # Use the highest-frequency curve to estimate the oscillation frequency. series_label, reps_label = max( ("series-0", "reps0"), ("series-1", "reps1"), ("series-2", "reps2"), key=lambda x: self.options.fixed_parameters[x[1]], ) curve_data = curve_data.get_subset_of(series_label) reps2 = self.options.fixed_parameters[reps_label] freqs_guess = curve.guess.frequency(curve_data.x, curve_data.y) / reps2 user_opt.p0.set_if_empty(freq=freqs_guess) avg_x = (max(x_data) + min(x_data)) / 2 span_x = max(x_data) - min(x_data) beta_bound = max(5 / user_opt.p0["freq"], span_x) ptp_y = np.ptp(curve_data.y) user_opt.bounds.set_if_empty( amp=(-2 * ptp_y, 0), freq=(0, np.inf), beta=(avg_x - beta_bound, avg_x + beta_bound), base=(min(curve_data.y) - ptp_y, max(curve_data.y) + ptp_y), ) base_guess = (max(curve_data.y) - min(curve_data.y)) / 2 user_opt.p0.set_if_empty(base=(user_opt.p0["amp"] or base_guess)) # Drag curves can sometimes be very flat, i.e. averages of y-data # and min-max do not always make good initial guesses. We therefore add # 0.5 to the initial guesses. Note that we also set amp=-0.5 because the cosine function # becomes +1 at zero phase, i.e. optimal beta, in which y data should become zero # in discriminated measurement level. options = [] for amp_factor in (-1, -0.5, -0.25): for beta_guess in np.linspace(min_beta, max_beta, 20): new_opt = user_opt.copy() new_opt.p0.set_if_empty(amp=ptp_y * amp_factor, beta=beta_guess) options.append(new_opt) return options
def _generate_fit_guesses( self, user_opt: curve.FitOptions ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Compute the initial guesses. Args: user_opt: Fit options filled with user provided guess and bounds. Returns: List of fit options that are passed to the fitter function. """ user_opt.bounds.set_if_empty( a=(0, 1), alpha=(0, 1), alpha_c=(0, 1), b=(0, 1), ) # for standard RB curve std_curve = self._data(series_name="Standard") opt_std = user_opt.copy() opt_std = self._initial_guess(opt_std, std_curve.x, std_curve.y, self._num_qubits) # for interleaved RB curve int_curve = self._data(series_name="Interleaved") opt_int = user_opt.copy() if opt_int.p0["alpha_c"] is not None: opt_int.p0["alpha"] = opt_std.p0["alpha"] * opt_int.p0["alpha_c"] opt_int = self._initial_guess(opt_int, int_curve.x, int_curve.y, self._num_qubits) user_opt.p0.set_if_empty( a=np.mean([opt_std.p0["a"], opt_int.p0["a"]]), alpha=opt_std.p0["alpha"], alpha_c=min(opt_int.p0["alpha"] / opt_std.p0["alpha"], 1), b=np.mean([opt_std.p0["b"], opt_int.p0["b"]]), ) return user_opt
def _generate_fit_guesses( self, user_opt: curve.FitOptions ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Compute the initial guesses. Args: user_opt: Fit options filled with user provided guess and bounds. Returns: List of fit options that are passed to the fitter function. """ # Use a fast Fourier transform to guess the frequency. x_data = self._data("series-0").x min_beta, max_beta = min(x_data), max(x_data) freqs_guesses = {} for i in range(3): curve_data = self._data(f"series-{i}") freqs_guesses[f"freq{i}"] = curve.guess.frequency( curve_data.x, curve_data.y) user_opt.p0.set_if_empty(**freqs_guesses) max_abs_y, _ = curve.guess.max_height(self._data().y, absolute=True) freq_bound = max(10 / user_opt.p0["freq0"], max(x_data)) user_opt.bounds.set_if_empty( amp=(-2 * max_abs_y, 0), freq0=(0, np.inf), freq1=(0, np.inf), freq2=(0, np.inf), beta=(-freq_bound, freq_bound), base=(-max_abs_y, max_abs_y), ) user_opt.p0.set_if_empty(base=0.5) # Drag curves can sometimes be very flat, i.e. averages of y-data # and min-max do not always make good initial guesses. We therefore add # 0.5 to the initial guesses. Note that we also set amp=-0.5 because the cosine function # becomes +1 at zero phase, i.e. optimal beta, in which y data should become zero # in discriminated measurement level. options = [] for amp_guess in (0.5, -0.5): for beta_guess in np.linspace(min_beta, max_beta, 20): new_opt = user_opt.copy() new_opt.p0.set_if_empty(amp=amp_guess, beta=beta_guess) options.append(new_opt) return options
def _generate_fit_guesses( self, user_opt: curve.FitOptions ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Compute the initial guesses. Args: user_opt: Fit options filled with user provided guess and bounds. Returns: List of fit options that are passed to the fitter function. Raises: CalibrationError: When ``angle_per_gate`` is missing. """ n_guesses = self._get_option("number_guesses") curve_data = self._data() max_abs_y, _ = curve.guess.max_height(curve_data.y, absolute=True) max_y, min_y = np.max(curve_data.y), np.min(curve_data.y) user_opt.bounds.set_if_empty(d_theta=(-np.pi, np.pi), base=(-max_abs_y, max_abs_y)) user_opt.p0.set_if_empty(base=(max_y + min_y) / 2) if "amp" in user_opt.p0: user_opt.p0.set_if_empty(amp=max_y - min_y) user_opt.bounds.set_if_empty(amp=(-2 * max_abs_y, 2 * max_abs_y)) # Base the initial guess on the intended angle_per_gate. angle_per_gate = self._get_option("angle_per_gate") if angle_per_gate is None: raise CalibrationError( "The angle_per_gate was not specified in the analysis options." ) guess_range = max(abs(angle_per_gate), np.pi / 2) options = [] for d_theta_guess in np.linspace(-guess_range, guess_range, n_guesses): new_opt = user_opt.copy() new_opt.p0.set_if_empty(d_theta=d_theta_guess) options.append(new_opt) return options
def _generate_fit_guesses( self, user_opt: curve.FitOptions, curve_data: curve.CurveData, ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Create algorithmic guess with analysis options and curve data. Args: user_opt: Fit options filled with user provided guess and bounds. curve_data: Formatted data collection to fit. Returns: List of fit options that are passed to the fitter function. """ fixed_params = self.options.fixed_parameters max_abs_y, _ = curve.guess.max_height(curve_data.y, absolute=True) max_y, min_y = np.max(curve_data.y), np.min(curve_data.y) user_opt.bounds.set_if_empty(d_theta=(-0.8 * np.pi, 0.8 * np.pi), base=(-max_abs_y, max_abs_y)) user_opt.p0.set_if_empty(base=(max_y + min_y) / 2) if "amp" in user_opt.p0: user_opt.p0.set_if_empty(amp=max_y - min_y) user_opt.bounds.set_if_empty(amp=(0, 2 * max_abs_y)) amp = user_opt.p0["amp"] else: # Fixed parameter amp = fixed_params.get("amp", 1.0) # Base the initial guess on the intended angle_per_gate and phase offset. apg = user_opt.p0.get("angle_per_gate", fixed_params.get("angle_per_gate", 0.0)) phi = user_opt.p0.get("phase_offset", fixed_params.get("phase_offset", 0.0)) # Prepare logical guess for specific condition (often satisfied) d_theta_guesses = [] offsets = apg * curve_data.x + phi for i in range(curve_data.x.size): xi = curve_data.x[i] yi = curve_data.y[i] if np.isclose(offsets[i] % np.pi, np.pi / 2) and xi > 0: # Condition satisfied: i.e. cos(apg x - phi) = 0 err = -np.sign(np.sin(offsets[i])) * ( yi - user_opt.p0["base"]) / (0.5 * amp) # Validate estimate. This is just the first order term of Maclaurin expansion. if np.abs(err) < 0.5: d_theta_guesses.append(err / xi) else: # Terminate guess generation because larger d_theta x will start to # reduce net y value and underestimate the rotation. break # Add naive guess for more coverage guess_range = max(abs(apg), np.pi / 2) d_theta_guesses.extend(np.linspace(-guess_range, guess_range, 11)) options = [] for d_theta_guess in d_theta_guesses: new_opt = user_opt.copy() new_opt.p0.set_if_empty(d_theta=d_theta_guess) options.append(new_opt) return options
def _generate_fit_guesses( self, user_opt: curve.FitOptions ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Compute the initial guesses. Args: user_opt: Fit options filled with user provided guess and bounds. Returns: List of fit options that are passed to the fitter function. """ curve_data = self._data() user_opt.p0.set_if_empty( amp=0.5, base=curve.guess.constant_sinusoidal_offset(curve_data.y), ) # frequency resolution of this scan df = 1 / ((curve_data.x[1] - curve_data.x[0]) * len(curve_data.x)) if user_opt.p0["freq"] is not None: # If freq guess is provided freq_guess = user_opt.p0["freq"] freqs = [freq_guess] else: freq_guess = curve.guess.frequency(curve_data.x, curve_data.y - user_opt.p0["base"]) # The FFT might be up to 1/2 bin off if freq_guess > df: freqs = [freq_guess - df, freq_guess, freq_guess + df] else: freqs = [0.0, freq_guess] # Set guess for decay parameter based on estimated frequency if freq_guess > df: alpha = curve.guess.oscillation_exp_decay( curve_data.x, curve_data.y - user_opt.p0["base"], freq_guess=freq_guess ) else: # Very low frequency. Assume standard exponential decay alpha = curve.guess.exp_decay(curve_data.x, curve_data.y) if alpha != 0.0: user_opt.p0.set_if_empty(tau=-1 / alpha) else: # Likely there is no slope. Cannot fit constant line with this model. # Set some large enough number against to the scan range. user_opt.p0.set_if_empty(tau=100 * np.max(curve_data.x)) user_opt.bounds.set_if_empty( amp=[0, 1.5], base=[0, 1.5], tau=(0, np.inf), freq=(0, 10 * freq_guess), phi=(-np.pi, np.pi), ) # more robust estimation options = [] for freq in freqs: for phi in np.linspace(-np.pi, np.pi, 5)[:-1]: new_opt = user_opt.copy() new_opt.p0.set_if_empty(freq=freq, phi=phi) options.append(new_opt) return options
def _generate_fit_guesses( self, user_opt: curve.FitOptions ) -> Union[curve.FitOptions, List[curve.FitOptions]]: """Compute the initial guesses. Args: user_opt: Fit options filled with user provided guess and bounds. Returns: List of fit options that are passed to the fitter function. """ user_opt.bounds.set_if_empty(t_off=(0, np.inf), b=(-1, 1)) user_opt.p0.set_if_empty(t_off=self._t_off_initial_guess(), b=1e-9) guesses = defaultdict(list) for control in (0, 1): x_data = self._data(series_name=f"x|c={control}") y_data = self._data(series_name=f"y|c={control}") z_data = self._data(series_name=f"z|c={control}") omega_xyz = [] for data in (x_data, y_data, z_data): ymin, ymax = np.percentile(data.y, [10, 90]) if ymax - ymin < 0.2: # oscillation amplitude might be almost zero, # then exclude from average because of lower SNR continue fft_freq = curve.guess.frequency(data.x, data.y) omega_xyz.append(fft_freq) if omega_xyz: omega = 2 * np.pi * np.average(omega_xyz) else: omega = 1e-3 zmin, zmax = np.percentile(z_data.y, [10, 90]) theta = np.arccos(np.sqrt((zmax - zmin) / 2)) # The FFT might be up to 1/2 bin off df = 2 * np.pi / ((z_data.x[1] - z_data.x[0]) * len(z_data.x)) for omega_shifted in [omega, omega - df / 2, omega + df / 2]: for phi in np.linspace(-np.pi, np.pi, 5): px = omega_shifted * np.cos(theta) * np.cos(phi) py = omega_shifted * np.cos(theta) * np.sin(phi) pz = omega_shifted * np.sin(theta) guesses[control].append( { f"px{control}": px, f"py{control}": py, f"pz{control}": pz, } ) if omega < df: # empirical guess for low frequency case guesses[control].append( { f"px{control}": omega, f"py{control}": omega, f"pz{control}": 0, } ) fit_options = [] # combine all guesses in Cartesian product for p0s, p1s in product(guesses[0], guesses[1]): new_opt = user_opt.copy() new_opt.p0.set_if_empty(**p0s, **p1s) fit_options.append(new_opt) return fit_options