From f2ffd1fd07c20f1b55fd17cc706dc3b44987b825 Mon Sep 17 00:00:00 2001 From: gonfeco Date: Wed, 19 Feb 2025 12:36:26 +0100 Subject: [PATCH 1/6] Added BayesianQAE code --- QQuantLib/AE/bayesian_ae.py | 972 ++++++++++ ...2_BayesianQuantumAmplitudeEstimation.ipynb | 1700 +++++++++++++++++ 2 files changed, 2672 insertions(+) create mode 100644 QQuantLib/AE/bayesian_ae.py create mode 100644 misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb diff --git a/QQuantLib/AE/bayesian_ae.py b/QQuantLib/AE/bayesian_ae.py new file mode 100644 index 0000000..959c2b2 --- /dev/null +++ b/QQuantLib/AE/bayesian_ae.py @@ -0,0 +1,972 @@ +""" +This module contains the BAYESQAE class. Given a quantum oracle operator, +this class estimates the **probability** of a given target state using +the Bayesian Quantum Amplitude Estimation algorithm based on the paper: + + Alexandra Ramôa and Luis Paulo Santos + Bayesian Quantum Amplitude Estimation + https://arxiv.org/abs/2412.04394 (2024) + +Author: Gonzalo Ferro Costas & Alexandra Ramôa + +""" + +import time +#from copy import deepcopy +import numpy as np +import pandas as pd +import qat.lang.AQASM as qlm +from QQuantLib.qpu.get_qpu import get_qpu +from QQuantLib.AA.amplitude_amplification import grover +from QQuantLib.utils.data_extracting import get_results +from QQuantLib.utils.utils import check_list_type, measure_state_probability +from QQuantLib.AE.mlae_utils import likelihood, log_likelihood + +class BAYESQAE: + """ + Class for Bayesian Quantum Amplitude Estimation algorithm (BAYESQAE) + + Parameters + ---------- + oracle: QLM gate + QLM gate with the Oracle for implementing the + Grover operator + target : list of ints + python list with the target for the amplitude estimation + index : list of ints + qubits which mark the register to do the amplitude + estimation + + kwars : dictionary + dictionary that allows the configuration of the BAYESQAE algorithm: \\ + Implemented keys: + + qpu : QLM solver + solver for simulating the resulting circuits + shots : int + number of measurements on each iteration + mcz_qlm : bool + for using or not QLM implementation of the multi controlled Z + gate + """ + + def __init__(self, oracle: qlm.QRoutine, target: list, index: list, **kwargs): + """ + + Method for initializing the class + """ + # Setting attributes + self._oracle = oracle + self._target = check_list_type(target, int) + self._index = check_list_type(index, int) + + # Set the QPU to use + self.linalg_qpu = kwargs.get("qpu", None) + if self.linalg_qpu is None: + print("Not QPU was provide. PyLinalg will be used") + self.linalg_qpu = get_qpu("python") + # Default setting in BAE paper + self.mcz_qlm = kwargs.get("mcz_qlm", True) + + # Creating the grover operator + self._grover_oracle = grover( + self._oracle, self.target, self.index, mcz_qlm=self.mcz_qlm + ) + + # Loading Algorithm configuration + # Stop Configuration + self.epsilon = kwargs.get("epsilon", 1.0e-3) + self.alpha = kwargs.get("alpha", 0.05) + self.max_iterations = kwargs.get("max_iterations", 500) + # Utility function + self.utility_function = kwargs.get( + "utility_function", variance_function) + # Control configuration + self.n_evals = kwargs.get("n_evals", 50) + self.k_0 = kwargs.get("k_0", 2) + self.R = kwargs.get("R", 3) + self.T = kwargs.get("T", 3) + # Particle Configuration + self.particles = kwargs.get("particles", 1000) + # Threshold for resampling + self.threshold = kwargs.get("threshold", 0.5) + # Kernel Selection + self.kernel = kwargs.get("kernel", "Metro") #Metro, LW + # Kernel LW configuration + self.alpha_lw = kwargs.get("alpha_lw", 0.9) + # Kernel Metropoli configuration + self.c = kwargs.get("c", 2.38) + # Frecuency for saving SMC probabilities + self.save_smc_prob = kwargs.get("save_smc_prob", 10) + self.print_info = kwargs.get("print_info", 10) + # Shots + self.shots = kwargs.get("shots", 1) + self.warm_shots = kwargs.get("warm_shots", 1) + self.bayes_shots = kwargs.get("bayes_shots", 1) + self.control_bayes_shots = kwargs.get("control_bayes_shots", 1) + # Good theta: For fake simulation + self.fake = kwargs.get("fake", False) + self.theta_good = kwargs.get("theta_good", None) + + + + # Estimation result of the algorithm + self.ae_l = None + self.ae_u = None + self.ae = None + # Theta estimation of the algorithm + self.theta_l = None + self.theta_u = None + self.theta = None + + self.circuit_statistics = None + self.time_pdf = None + self.run_time = None + self.schedule = {} + self.oracle_calls = None + self.max_oracle_depth = None + self.schedule_pdf = None + self.quantum_times = [] + self.quantum_time = None + + # Store the SMC distributions + self.pdf_theta = None + self.pdf_weights = None + + # Store Evolution of algorithm + self.outcome_list = None + self.control_list = None + self.shots_list = None + # Store Evolution of estimations + self.mean_a = None + self.lower_a = None + self.upper_a = None + self.pdf_estimation = None + + # Internal variables for optimize control: + # Min value for control domain interval + self.m_min = None + # Max value for control domain interval + self.m_max = None + # Boolean for first control domain interval + self.first_interval = True + # Counter for changing control domain interval + self.interval_change_counter = 0 + + ##################################################################### + @property + def oracle(self): + """ + creating oracle property + """ + return self._oracle + + @oracle.setter + def oracle(self, value): + """ + setter of the oracle property + """ + self._oracle = value + self._grover_oracle = grover( + self._oracle, self.target, self.index, mcz_qlm=self.mcz_qlm + ) + + @property + def target(self): + """ + creating target property + """ + return self._target + + @target.setter + def target(self, value): + """ + setter of the target property + """ + self._target = check_list_type(value, int) + self._grover_oracle = grover( + self._oracle, self.target, self.index, mcz_qlm=self.mcz_qlm + ) + + @property + def index(self): + """ + creating index property + """ + return self._index + + @index.setter + def index(self, value): + """ + setter of the index property + """ + self._index = check_list_type(value, int) + self._grover_oracle = grover( + self._oracle, self.target, self.index, mcz_qlm=self.mcz_qlm + ) + + def quantum_measure_step(self, m_k, n_k): + r""" + Create the quantum routine and execute the measurement. + + Parameters + ---------- + m_k : int + number of Grover operator applications + n_k : int + number of shots + + Returns + ------- + p_k: float + probability of getting the Good state + routine : QLM Routine object + qlm routine for the QAE step + """ + + routine = qlm.QRoutine() + wires = routine.new_wires(self.oracle.arity) + routine.apply(self.oracle, wires) + for j in range(m_k): + routine.apply(self._grover_oracle, wires) + results, circuit, _, _ = get_results( + routine, linalg_qpu=self.linalg_qpu, shots=n_k, qubits=self.index + ) + # time_pdf["m_k"] = k + p_k = measure_state_probability(results, self.target) + return p_k, routine + + def fake_quantum_measure_step(self, m_k, n_k, theta_good=None): + """ + Simulates a Fake Quantum Amplitude Estimation experiment + + Parameters + ---------- + m_k : int + number of Grover operator applications + n_k : int + number of shots + + Returns + ------- + + p_k : float + probability of getting the Good state + """ + if theta_good is None: + raise ValueError("theta_good parameter needed for fake simulation") + # Exact probability + p_m = np.sin((2 * m_k + 1) * theta_good) ** 2 + # Binomial experiment using the exact probability + outcome_k = np.random.binomial(n_k, p_m) + # The experiment should return a measured probability + p_k = outcome_k / n_k + return p_k + + def optimize_control(self, thetas, weights, control_bayes_shots, **kwargs): + """ + Get the optimal control that minimizes an input utility function + given an SMC prior probability + + Parameters + ---------- + + thetas : numpy array + SMC Prior distribution of the desired parameter. Parameter Values + weights : numpy array + SMC Prior distribution of the desired parameter. Weights. + control_shots : int + Shots for simulating virtual QAE experiments mandatory for + computing the expected values of the utility function + + Returns + ------- + + m_opt : int + Optimal control based on the minimization of an utility + function and a domain interval control + """ + # Number of evaluations for otpimize the control + n_evals = kwargs.get("n_evals", self.n_evals) + # Multiplicative coeeficient for first interval + k_0 = kwargs.get("k_0", self.k_0) + # Condition for changing the domain interval control: + + # R is the window where we want as condition for changing the + # domain interval control. If the optimal control is between + # the R highest posible domain controls then we want, maybe, + # change the limits of the domain control + window_r = kwargs.get("R", self.R) + + # T is the maximum number of times we allow that the optimize + # control is between the R highest posible domain controls. + # If we reach this T then we are going to increase the limits + # of the domain control + window_t = kwargs.get("T", self.T) + + if self.first_interval: + # first step + self.m_min = 0 + self.m_max = k_0 * n_evals + # We chose n_evals controls randomly between the limits + test_controls = np.random.randint(self.m_min, self.m_max, n_evals) + # We compute the expected value of the utiltiy function + # for each possible control + values_ = [average_expectation( + thetas, weights, m_, control_bayes_shots, **kwargs + ) for m_ in test_controls] + # Get the optimal control that gives the minimum + # of the utility function + m_opt = test_controls[np.argmin(values_)] + # Test if the optimal control is into the R highest posible + # domain controls used for computing utility function + if np.sum(test_controls >= m_opt) <= window_r: + # Increase the counter for interval change + self.interval_change_counter = self.interval_change_counter + 1 + # Test if we need to change the domain control interval + if window_t == self.interval_change_counter: + if self.first_interval: + self.first_interval = False + # New interval limits for searching new optimal control + self.m_min = self.m_max + self.m_max = 2 * self.m_min + # Reset the internal counter for changing interval + self.interval_change_counter = 0 + return m_opt + + def bayesqae(self, **kwargs): + """ + This function implements BAE algorithm. + + Parameters + ---------- + kwargs : dictionary + Configuration of the BAYESQAE algorithm + + Returns + ---------- + a_m : float + mean for the probability to be estimated + a_l : float + lower bound for the probability to be estimated + a_u : float + upper bound for the probability to be estimated + + """ + # Initialization of attributes related with optimize_control: + self.first_interval = True + self.interval_change_counter = 0 + # Initialized quantum times + self.quantum_times = [] + + # Number of particles for SMC + particles = kwargs.get("particles", self.particles) + # Number of shots for measuring the quantum device + shots = kwargs.get("shots", self.shots) + # Number of Shots for Non amplification step. Default BAE paper + warm_shots = kwargs.get("warm_shots", self.warm_shots) + # Shots for Bayesian Updating. Default BAE paper + bayes_shots = kwargs.get("bayes_shots", self.bayes_shots) + # Shots for Control optimization. Default BAE paper + control_bayes_shots = kwargs.get( + "control_bayes_shots", self.control_bayes_shots) + + # Confidence for the desired epsilon + alpha = kwargs.get("alpha", self.alpha) + # desired interval estimation epsilon (arround mean) + epsilon = kwargs.get("epsilon", self.epsilon) + # Maximum number of iterations if desired epsilon and alpha + # confidence is not achieved + max_iterations = kwargs.get("max_iterations", self.max_iterations) + + # Save SMC probability frecuency + save_smc_prob = kwargs.get("save_smc_prob", self.save_smc_prob) + print_info = kwargs.get("print_info", self.print_info) + # Fake simulation + fake = kwargs.get("fake", self.fake) + theta_good = kwargs.get("theta_good", self.theta_good) + + # Configuration keywords for bayesian update and control + conf = { + "threshold" : kwargs.get("threshold", self.threshold), + "kernel" : kwargs.get("kernel", self.kernel), + "c" : kwargs.get("c", self.c), + "alpha_lw" : kwargs.get("alpha_lw", self.alpha_lw), + "n_evals" : kwargs.get("n_evals", self.n_evals), + "k_0" : kwargs.get("k_0", self.k_0), + "R" : kwargs.get("R", self.R), + "T" : kwargs.get("T", self.T), + "utility_function" : kwargs.get( + "utility_function", self.utility_function), + } + + # SMC Prior probability distribution + theta_prior = np.random.uniform(0.0, 0.5 * np.pi, particles) + weights_prior = np.ones(len(theta_prior)) / len(theta_prior) + + # Saving Data + self.pdf_theta = pd.DataFrame(theta_prior, columns=["prior"]) + self.pdf_weights = pd.DataFrame(weights_prior, columns=["prior"]) + + # Initialize list for storing experiment results + self.outcome_list = [] + self.control_list = [] + self.shots_list = [] + + # Initialized list for storing estimation results + self.mean_a = [] + self.lower_a = [] + self.upper_a = [] + + ################## WARM UP STEP: START ################### + warm_control = int(0) + self.control_list.append(warm_control) + # Gives a probability + + start = time.time() + if fake: + warm_outcome = self.fake_quantum_measure_step( + warm_control, warm_shots, theta_good) + else: + warm_outcome, routine = self.quantum_measure_step( + warm_control, warm_shots) + end = time.time() + self.quantum_times.append(end-start) + + # Transform to counts + warm_outcome = round(warm_outcome * warm_shots) + self.outcome_list.append(warm_outcome) + self.shots_list.append(warm_shots) + # Updated SMC posterior probability + assert len(self.control_list) == 1 + assert len(self.shots_list) == 1 + assert len(self.outcome_list) == 1 + theta_posterior, weights_posterior = bayesian_update( + theta_prior, weights_prior, + self.control_list, + self.shots_list, + self.outcome_list, + **conf + ) + self.pdf_theta = pd.DataFrame( + theta_posterior, columns=["WarmPosterior"]) + self.pdf_weights = pd.DataFrame( + weights_posterior, columns=["WarmPosterior"]) + + # Computes lower and higher values for a + theta_l, theta_u = confidence_intervals( + theta_posterior, weights_posterior, alpha + ) + a_l = np.sin(theta_l) ** 2 + self.lower_a.append(a_l) + a_u = np.sin(theta_u) ** 2 + self.upper_a.append(a_u) + # Computes mean value for a + self.mean_a.append( + (np.sin(theta_posterior) ** 2) @ weights_posterior + ) + ################## WARM UP STEP: END ################### + + ################## LOOP STEPS: START ######################## + + # Amplification Loop + counter = 0 + failure_test = True + + # print("{}: m: {}. o:{}, meas_ep: {}. estim: {}".format( + # counter, + # 0, + # self.outcome_list[-1], + # 0.5 * (a_u - a_l), + # self.mean_a[-1] + # )) + + while (a_u - a_l > 2 * epsilon) and (failure_test == True): + #optimize control for next iteration + optimal_control = self.optimize_control( + theta_posterior, weights_posterior, + control_bayes_shots, **conf + ) + # Quantum execution using optimal control + start = time.time() + if fake: + p_m = self.fake_quantum_measure_step( + optimal_control, shots, theta_good) + else: + p_m, routine = self.quantum_measure_step( + optimal_control, shots) + end = time.time() + self.quantum_times.append(end-start) + + self.control_list.append(optimal_control) + self.shots_list.append(bayes_shots) + self.outcome_list.append(round(p_m * bayes_shots)) + + # Update SMC Posterior probability + theta_posterior, weights_posterior = bayesian_update( + theta_posterior, weights_posterior, + self.control_list, self.shots_list, self.outcome_list, **conf + ) + # Computes mean of the parameter a using SMC posterior + self.mean_a.append(weights_posterior @ np.sin(theta_posterior) ** 2) + # Computes lower and upper bounds for desired confidence alpha + theta_l, theta_u = confidence_intervals( + theta_posterior, weights_posterior, alpha) + a_l = np.sin(theta_l) ** 2 + self.lower_a.append(a_l) + a_u = np.sin(theta_u) ** 2 + self.upper_a.append(a_u) + # Saving Posterior SMC probabilities + if counter % save_smc_prob == 0: + self.pdf_theta["c_{}".format(counter)] = theta_posterior + self.pdf_weights["c_{}".format(counter)] = weights_posterior + if counter % print_info == 0: + print("{}: optimal_control: {}. output:{}, interval: {}".format( + counter, + optimal_control, + self.outcome_list[-1], + 0.5 * (a_u - a_l) + )) + if counter >= max_iterations: + failure_test = False + counter = counter + 1 + # Store the final SMC posterior distribution + self.pdf_theta["final_posterior"] = theta_posterior + self.pdf_weights["final_posterior"] = weights_posterior + + ################## LOOP STEPS: END ######################## + a_l = self.lower_a[-1] + a_u = self.upper_a[-1] + a_m = self.mean_a[-1] + return a_m, a_l, a_u + + def run(self): + r""" + run method for the class. + + Returns + ---------- + + self.ae : + amplitude estimation parameter + + """ + bayes_dict = { + "epsilon" : self.epsilon, + "alpha" : self.alpha, + "max_iterations" : self.max_iterations, + "utility_function" : self.utility_function, + "n_evals" : self.n_evals, + "k_0" : self.k_0, + "R" : self.R, + "T" : self.T, + "particles" : self.particles, + "threshold" : self.threshold, + "kernel" : self.kernel, + "alpha_lw" : self.alpha_lw, + "c" : self.c, + "save_smc_prob" : self.save_smc_prob, + "print_info" : self.print_info, + "shots" : self.shots, + "warm_shots" : self.warm_shots, + "bayes_shots" : self.bayes_shots, + "control_bayes_shots" : self.control_bayes_shots, + "theta_good" : self.theta_good, + "fake" : self.fake + } + start = time.time() + self.ae, self.ae_l, self.ae_u = self.bayesqae() + end = time.time() + self.run_time = end - start + self.schedule_pdf = pd.DataFrame( + [self.control_list, self.shots_list, self.outcome_list], + index=["m_k", "shots", "h_k"] + ).T + # self.schedule_pdf = pd.DataFrame.from_dict( + # self.schedule, + # columns=['shots'], + # orient='index' + # ) + # self.schedule_pdf.reset_index(inplace=True) + # self.schedule_pdf.rename(columns={'index': 'm_k'}, inplace=True) + self.oracle_calls = np.sum( + self.schedule_pdf['shots'] * (2 * self.schedule_pdf['m_k'] + 1)) + self.max_oracle_depth = np.max(2 * self.schedule_pdf['m_k']+ 1) + self.quantum_time = sum(self.quantum_times) + self.pdf_estimation = pd.DataFrame( + [self.mean_a, self.lower_a, self.upper_a], + index = ["mean", "lower", "upper"], + ).T + return self.ae + + + +def posterior_weights(thetas, weights, m_k, n_k, o_k): + """ + Compute posterior probability weights given an input QAE experiment + and a SMC prior probability distribution. + + Parameters + ---------- + + thetas : numpy array + SMC Prior distribution of the desired parameter. Parameter Values + weights : numpy array + SMC Prior distribution of the desired parameter. Weights. + m_k : int + Exponent of the Grover operator of the QAE experiment + n_k : int + Number of shots of the QAE experiment + o_k : int + Outcome of the QAE experiment + + Returns + ------- + + posterior_weights_ : numpy array + Posterior distribution given the outcome of the QAE experiment + """ + like_ = likelihood(thetas, m_k, n_k, o_k) + posterior_weights_ = like_ * weights + # Renormalize weights + posterior_weights_ = posterior_weights_ / (posterior_weights_.sum()) + return posterior_weights_ + +def ess(weights): + """ + Compute the Effective Sample Size for an input weights + + Parameters + ---------- + weights : numpy array + array with the weights + + Returns + ------- + + ess_ : float + efective sample size + """ + + #ess_ = np.sum(weights) ** 2 / np.sum(weights**2) + ess_ = np.sum(weights) ** 2 / np.sum(weights**2) + return ess_ + +def Metropolis_kernel(theta, m_k, n_k, o_k, **kwargs): + """ + Metropolis kernel. Perturbation kernel used for enhancing resampling + + Parameters + ---------- + + theta : numpy array + Input parameters from a SMC probability distribution. + m_k : list + Exponent of the Grover operator of the QAE experiment + n_k : list + Number of shots of the QAE experiment + o_k : list + Outcome of the QAE experiment + kwargs: c : float + coeficient for standard deviation multiplication + + Returns + ------- + new_theta : numpy array + Perturbed parameters for SMC probability distribution using + Metropoli kernel + + """ + c_ = kwargs.get("c", 2.38) + # First a proposal is needed: for each input theta we sample + # from a Gaussian distribution with a mean equals to the theta + new_theta = np.random.normal(theta, c_ * theta.std()) + # Change from second quadrant to first quadrant + new_theta = np.where(new_theta > np.pi * 0.5, np.pi -new_theta, new_theta) + # Change from forth quadrant to first quadrant + new_theta = np.where( + (new_theta < 0) & (new_theta >= -np.pi * 0.5), -new_theta, new_theta) + # Change from third quadrant to first quadrant + new_theta = np.where(new_theta < -np.pi * 0.5, np.pi + new_theta, new_theta) + # Test: new_theta MUST BE in first quadrant + condition = (new_theta < 0) | (new_theta > 0.5* np.pi) + if condition.any(): + raise ValueError("Metropolis Kernel: new thetas outside firs quadrant") + + # Compute likelihood for input value distribution. + like_old = np.array([ + log_likelihood(theta, m_k_, n_k_, o_k_) + for m_k_, n_k_, o_k_ in zip(m_k, n_k, o_k) + ]).sum(axis=0) + + # Compute likelihood for new value distribution. + like_new = np.array([ + log_likelihood(new_theta, m_k_, n_k_, o_k_) + for m_k_, n_k_, o_k_ in zip(m_k, n_k, o_k) + ]).sum(axis=0) + # Compute probability for using input theta or new_theta. + # We need to clip probabilities between 0 and 1 + keep_old_probability = np.exp(like_old - like_new).clip(max=1.0, min=0.0) + + #print("Acceptance: {}".format( + # round((1.0 -keep_old_probability.mean()) * 100))) + + # We select which theta (new or input) depending on the + # keep_old_probability + samples = np.random.binomial(1, keep_old_probability) + new_theta = np.where(samples == 1, theta, new_theta) + return new_theta + +def LW_kernel(theta, **kwargs): + """ + Liu-West kernel. Perturbation kernel used for enhancing resampling + + Parameters + ---------- + theta : numpy array + Input parameters from a SMC probability distribution. + kwargs: alpha_lw : float + coeficient for creating new thetas + + Returns + ------- + new_theta : numpy array + Perturbed parameters for SMC probability distribution using + Liu-West kernel + """ + alpha_lw = kwargs.get("alpha_lw", 0.2) + mean_ = np.mean(theta) + new_mean = alpha_lw * theta + (1.0 - alpha_lw) * mean_ + new_std = np.sqrt(1.0 - alpha_lw ** 2) * theta.std() + # Only mean and standard deviation is used + new_theta = np.random.normal(new_mean, new_std) + return new_theta + +def bayesian_update(thetas, weights, m_k, n_k, o_k, resample=True, **kwargs): + """ + Given a SMC prior probabilty distribution and a outcome of QAE experiment + returns the SMC posterior probabilty distribution. It can performs resampling + + Parameters + ---------- + + thetas : numpy array + SMC Prior distribution of the desired parameter. Parameter Values + weights : numpy array + SMC Prior distribution of the desired parameter. Weights. + m_k : list + Exponent of the Grover operator of the QAE experiment + n_k : list + Number of shots of the QAE experiment + o_k : list + Outcome of the QAE experiment + kwargs: threshold : float + Threshold for resampling the SMC probability distribution + + Returns + ------- + + new_thetas : numpy array + SMC Posterior distribution of the desired parameter. Parameter Values + posterior_weights_ : numpy array + SMC Posterior distribution of the desired parameter. Weights. + """ + threshold = kwargs.get("threshold", 0.5) + kernel = kwargs.get("kernel", "Metro") + # Compute posterior weights: We only use the last experiment result + posterior_weights_ = posterior_weights( + thetas, weights, m_k[-1], n_k[-1], o_k[-1]) + + # We select when we want resampling. In general resampling in the computations + # for optimize control are not mandatory + if resample: + # Compute effective sample size + ess_ = ess(np.array(posterior_weights_)) + # Condition for resampling + if ess_ < threshold * len(posterior_weights_): + thetas_ = np.random.choice( + thetas, + p=posterior_weights_, + size=len(thetas) + ) + #print(pd.value_counts(thetas_)) + # uniform weights + posterior_weights_ = np.ones(len(posterior_weights_)) + posterior_weights_ = posterior_weights_ / len(posterior_weights_) + + if kernel == "Metro": + # We need all the experiment results!! + new_thetas = Metropolis_kernel(thetas_, m_k, n_k, o_k, **kwargs) + elif kernel == "LW": + new_thetas = LW_kernel(thetas_, **kwargs) + else: + raise ValueError( + "Plese select a kernel for resamplin: Metro or LW") + #print(pd.value_counts(new_thetas)) + return new_thetas, posterior_weights_ + else: + return thetas, posterior_weights_ + else: + return thetas, posterior_weights_ + +def weighted_expectaction(thetas, weights, m_k, n_k, o_k, **kwargs): + """ + Computes the expected value of an input utility function for a given outcome + of a QAE experiment (m_k, n_k, h_k) and a SMC prior probability + weighted by the total probability getting the o_k outcome. + + Parameters + ---------- + + thetas : list + SMC Prior distribution of the desired parameter. Parameter Values + weights : list + SMC Prior distribution of the desired parameter. Weights. + m_k : int + Exponent of the Grover operator of the QAE experiment + n_k : int + Number of shots of the QAE experiment + o_k : int + Outcome of the QAE experiment + kwargs: utility_function : function + Desired utility function for computing the expected value + + Returns + ------- + + weighted_expectation_ : float + product of the expected value function for a given QAE experiment + result and the probability of the given result + + """ + + utility_function = kwargs.get("utility_function", None) + if utility_function is None: + raise ValueError("Provide a utility_function") + # Computes the SMC posterior probability for the QAE experiment + thetas_h_k_posterior, weights_h_k_posterior = bayesian_update( + thetas, weights, # + [m_k], [n_k], [o_k], False, **kwargs + ) + # Compute the conditional expectaction of the utility function given + # the control (m_k) the number of shots (n_k) and the outcome (o_k) + # under the SMC posterior probability distribution: + # E_{P(\theta/D;m)}[U(\theta, D;m)] + conditional_uf = utility_function( + thetas_h_k_posterior, + weights_h_k_posterior + ) + # Computes the probability of obtaining the outcome o_k for the QAE + # experiment with control m_k and number of shots n_k given a SMC + # priori probabilty distribution (input thetas and weights): + # E_{P(\theta)}[P(D;m)] + outcome_probability = likelihood(thetas, m_k, n_k, o_k) @ weights + # We need the weighted expectaction of the utility function + weighted_expectation_ = conditional_uf * outcome_probability + return weighted_expectation_ + +def average_expectation(thetas, weights, m_k, n_k, **kwargs): + """ + Computes the average expected value of a function for all posible + outcomes froma QAE experiment with control m_k and number of shots + n_k given a SMC prior probability function. + + Parameters + ---------- + + thetas : list + SMC Prior distribution of the desired parameter. Parameter Values + weights : list + SMC Prior distribution of the desired parameter. Weights. + m_k : int + Exponent of the Grover operator of the QAE experiment + n_k : int + Number of shots of the QAE experiment + kwargs: utility_function : function + Function for computing its average expectation + + Returns + ------- + + value_: float + Desired average expectation + """ + value_ = np.sum([ + weighted_expectaction(thetas, weights, m_k, n_k, o_k, **kwargs) + for o_k in range(n_k+1) + ]) + return value_ + +def variance_function(thetas, weights, **kwargs): + """ + Computes the exprected value of the variance for a SMC probability + distribution + + Parameters + ---------- + + thetas : list + SMC Prior distribution of the desired parameter. Parameter Values + weights : list + SMC Prior distribution of the desired parameter. Weights. + + Returns + ------- + + variance: float + Expected value of the variance under the input SMC probability + distribution + """ + # Computes the expected value for the mean + mean_expectation = thetas @ weights + # Computes the square error with respect to the expected value of + # the mean + square_error = (thetas - mean_expectation) ** 2 + variance = square_error @ weights + return variance + +def confidence_intervals(thetas, weights, delta): + """ + Computes the confidence intervals for a confidence level delta of a + given SMC probability distribution + + Parameters + ---------- + + thetas : list + SMC Prior distribution of the desired parameter. Parameter Values + weights : list + SMC Prior distribution of the desired parameter. Weights. + delta : float + Desired confidence level + + Returns + ------- + + theta_lower: float + Lower value for confidence interval + theta_upper: float + Upper value for confidence interval + """ + if (weights == 1.0/ weights.size).all(): + # In this case all the weights has the same probability + # So we order thetas + index_ = (weights.cumsum() > 0.5 * delta) \ + & (weights.cumsum() < (1.0 -0.5 * delta)) + covered_thetas = np.sort(thetas)[index_] + else: + # Weights has different probability + # Ordering data by ascending probability + sort_index = np.argsort(weights)[::-1] + sort_thetas = thetas[sort_index] + sort_weights = weights[sort_index] + # Compute cumulative sum + cumulative_prob = np.cumsum(sort_weights) + # Compute thetas that enclose desired alpha + covered_thetas = sort_thetas[ + (cumulative_prob > 0.5 * delta) + & (cumulative_prob < (1.0 - 0.5 * delta)) + ] + theta_l = covered_thetas.min() + theta_u = covered_thetas.max() + return theta_l, theta_u diff --git a/misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb b/misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb new file mode 100644 index 0000000..acd8c14 --- /dev/null +++ b/misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb @@ -0,0 +1,1700 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ac8d53e0", + "metadata": {}, + "source": [ + "# Bayesian Quantum Amplitude Estimation (BAYESQAE) module" + ] + }, + { + "cell_type": "markdown", + "id": "bb91ed55", + "metadata": {}, + "source": [ + "The present notebook reviews the **Bayesian Quantum Amplitude Estimation (BAYESQAE)** algorithm.\n", + "\n", + "The **BAYESQAE** algorithm was implemented into the module *bayesian_ae* of the package *AE* of the library *QQuantLib* (**QQuantLib/AE/bayesian_ae.py**). This algorithm is encapsulated in a Python class called `BAYESQAE`.\n", + "\n", + "The present notebook and modules are based on the following reference:\n", + "\n", + "Alexandra Ramôa and Luis Paulo Santos . Bayesian Quantum Amplitude Estimation. https://arxiv.org/abs/2412.04394 (2024).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff0ab600", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.append(\"../../\")\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c0f929c", + "metadata": {}, + "outputs": [], + "source": [ + "## QPU\n", + "from QQuantLib.qpu.get_qpu import get_qpu\n", + "my_qpus = [\"python\", \"c\", \"qlmass_linalg\", \"qlmass_mps\", \"linalg\", \"mps\"]\n", + "linalg_qpu = get_qpu(my_qpus[1])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "584d84ed", + "metadata": {}, + "outputs": [], + "source": [ + "from QQuantLib.DL.data_loading import load_probability" + ] + }, + { + "cell_type": "markdown", + "id": "a23c4a95", + "metadata": {}, + "source": [ + "## 1. Oracle generation" + ] + }, + { + "cell_type": "markdown", + "id": "f851d971", + "metadata": {}, + "source": [ + "Before performing any amplitude estimation, we need to load some data into the quantum circuit. Since this step is auxiliary and intended to illustrate how the algorithm works, we will simply load a discrete probability distribution. In this case, we will use a circuit with $ n = 3 $ qubits, resulting in a total of $ N = 2^n = 8 $ states. The discrete probability distribution we will load is defined as:\n", + "\n", + "$$\n", + "p_d = \\frac{(0, 1, 2, 3, 4, 5, 6, 7)}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}.\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e546068", + "metadata": {}, + "outputs": [], + "source": [ + "n_qbits = 3\n", + "x = np.arange(2**n_qbits)\n", + "probability = x/np.sum(x)" + ] + }, + { + "cell_type": "markdown", + "id": "0df53a96", + "metadata": {}, + "source": [ + "Note that this probability distribution is properly normalized. To load this probability distribution into the quantum circuit, we will use the `load_probability` function from the **QQuantLib/DL/data_loading** module. The resulting quantum state can be expressed as:\n", + "\n", + "$$\n", + "|\\Psi\\rangle = \\frac{1}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aaed4936", + "metadata": {}, + "outputs": [], + "source": [ + "from QQuantLib.DL.data_loading import load_probability" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fdd4552b", + "metadata": {}, + "outputs": [], + "source": [ + "oracle = load_probability(probability)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd4cc5b6", + "metadata": {}, + "outputs": [], + "source": [ + "%qatdisplay oracle --depth 1 --svg" + ] + }, + { + "cell_type": "markdown", + "id": "4ae9391a", + "metadata": {}, + "source": [ + "For more information on loading data into the quantum circuit, see the notebook `01_DataLoading_Module_Use.ipynb`" + ] + }, + { + "cell_type": "markdown", + "id": "7c2f075d", + "metadata": {}, + "source": [ + "## 2. BAYESQAE algorithm." + ] + }, + { + "cell_type": "markdown", + "id": "89427b48", + "metadata": {}, + "source": [ + "### 2.1 The Amplitude Estimation Problem\n", + "\n", + "The problem of amplitude estimation can be stated as follows. Given an oracle operator $\\mathcal{A}$:\n", + "$$\n", + "\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle,\n", + "$$\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, the goal is to estimate the value of $\\sqrt{a}$. We can associate an angle $\\theta$ with $\\sqrt{a}$ such that $\\sin^2{\\theta} = a$, rewriting the problem as:\n", + "$$\n", + "\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sin(\\theta)|\\Psi_0\\rangle + \\cos(\\theta)|\\Psi_1\\rangle. \\tag{1}\n", + "$$\n", + "\n", + "The foundation of any amplitude estimation algorithm lies in the Grover-like operator $\\mathcal{Q}$ derived from the oracle operator $\\mathcal{A}$:\n", + "$$\n", + "\\mathcal{Q}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^\\dagger \\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right).\n", + "$$\n", + "This operator acts on the state $|\\Psi\\rangle$ as follows:\n", + "$$\n", + "\\mathcal{Q}^{m_k}|\\Psi\\rangle = \\mathcal{Q}^{m_k} \\mathcal{A} |0\\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle + \\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle.\n", + "$$\n", + "\n", + "For more information about the Grover operator and the amplitude amplification algorithm, refer to the notebook `02_AmplitudeAmplification_Operators.ipynb`." + ] + }, + { + "cell_type": "markdown", + "id": "87e1a040", + "metadata": {}, + "source": [ + "### 2.2 BAYESQAE Algorithm Summary\n", + "\n", + "Given an error tolerance $\\epsilon$ and a confidence level $\\alpha$, the **BAYESQAE** algorithm estimates an interval $(a_l, a_u)$ such that the parameter $a$ in the Amplitude Estimation problem satisfies:\n", + "$$\n", + "P\\big[a \\in [a_l, a_u]\\big] > 1 - \\alpha,\n", + "$$\n", + "and\n", + "$$\n", + "\\frac{a_u - a_l}{2} \\leq \\epsilon.\n", + "$$\n", + "\n", + "To achieve this estimation, the **BAYESQAE** algorithm combines quantum Grover-like circuits ($\\mathcal{Q}^{m_k}|\\Psi\\rangle$) with a statistical inference framework. The core idea is to start with a prior probability distribution for the value of $a$, and then update it iteratively using the **Bayes rule**, based on the measurement outcomes of $\\mathcal{Q}^{m_k}|\\Psi\\rangle$. When the algorithm terminates, the value of $a$ can be estimated using the final posterior probability distribution.\n", + "\n", + "For the statistical inference, the **BAYESQAE** algorithm operates within the framework of **Sequential Monte Carlo (SMC)** methods. The goal is to use SMC to compute an optimal control for the next **Quantum Amplitude Estimation (QAE)** experiment (i.e., the choice of $m_k$ for the Grover circuit), given the current probability distribution and the results of previous QAE experiments. \n", + "\n", + "The computation of the optimal control involves minimizing the expected value of a **utility function** over a dynamically evolving range of possible controls." + ] + }, + { + "cell_type": "markdown", + "id": "b25e272f", + "metadata": {}, + "source": [ + "### 2.3 Creating an Object from the BAYESQAE Class\n", + "\n", + "We have implemented a Python class called `BAYESQAE` in the **QQuantLib/AE/bayesian_ae** module, which enables the use of the **BAYESQAE** algorithm. To create an instance of the **BAYESQAE** class, the conventions used in the **MLAE** class (from the **QQuantLib/AE/maximum_likelihood_ae** module) should be followed.\n", + "\n", + "#### Mandatory Inputs:\n", + "1. `Oracle`: A myQLM AbstractGate or QRoutine object that implements the oracle for constructing the Grover operator.\n", + "2. `Target`: The marked state in binary representation, provided as a Python list.\n", + "3. `Index`: A list of qubits affected by the Grover operator.\n", + "\n", + "Several additional inputs can be provided as keyword arguments (using a Python dictionary) to configure different parts of the algorithm.\n", + "\n", + "---\n", + "\n", + "#### Quantum Parts Configuration:\n", + "The following arguments can be used to configure the quantum components of the algorithm:\n", + "- `qpu`: The myQLM solver to be used.\n", + "- `mcz_qlm`: A boolean flag indicating whether to use the myQLM multi-controlled Z gate (`True`, default) or a multiplexor implementation (`False`).\n", + "\n", + "---\n", + "\n", + "#### Stopping Condition:\n", + "The stopping condition for the **BAYESQAE** algorithm loop is controlled by the following keywords:\n", + "- `epsilon` ($\\epsilon$): The precision parameter. Ensures that $|a_u - a_l|$ is at most $2\\epsilon$.\n", + "- `alpha` ($\\alpha$): The accuracy parameter. Ensures that the probability of $a$ lying outside the given $\\epsilon$ interval is at most $\\alpha$ (default: 0.05).\n", + "- `max_iterations`: The maximum number of iterations for the **BAYESQAE** algorithm loop. If the algorithm does not satisfy the $\\epsilon$ and $\\alpha$ requirements within this limit, the loop is truncated.\n", + "\n", + "---\n", + "\n", + "#### Sequential Monte Carlo (SMC) Method Configuration:\n", + "The following keywords can be used to configure the SMC method used in the **BAYESQAE** algorithm:\n", + "- `particles`: The number of particles used for executing the SMC simulation.\n", + "- `threshold`: A float (between 0 and 1) that defines the threshold for triggering resampling of the SMC probability distribution.\n", + "- `kernel`: A string specifying the type of perturbation kernel used during resampling. Options are:\n", + " - `LW`: Liu-West perturbation kernel.\n", + " - `Metro`: Metropolis perturbation kernel.\n", + "- `alpha_lw`: A float between 0 and 1 for configuring the Liu-West perturbation kernel (used when `kernel=\"LW\"`).\n", + "- `c`: A float for configuring the Metropolis perturbation kernel (used when `kernel=\"Metro\"`).\n", + "\n", + "---\n", + "\n", + "#### Dynamic Evolution of Control Domains:\n", + "The following keywords can be used to configure the dynamic evolution of the domain controls for minimizing the **utility** function:\n", + "- `n_evals`: The number of evaluations for optimizing the expected value of the utility function.\n", + "- `k_0`: Together with `n_evals`, defines the upper limit for the initial domain interval for control optimization.\n", + "- `R`: when the optimal control is among the top `R` highest possible controls then it can be necessary to enlarge the domain for the controls. An internal counter is increased by one when this happens.\n", + "- `T`: If the optimal control is among the top `R` highest possible controls during `T` iterations, the domain interval for control optimization is enlarged (when the internal counter hits `T`)\n", + "\n", + "---\n", + "\n", + "#### Utility Function:\n", + "In the original **BAYESQAE** paper, the utility function used for minimization is the variance. By default, the function `variance_function` from the **QQuantLib/AE/bayesian_ae** module is used. To use a user-defined utility function, the following keyword can be provided:\n", + "- `utility_function`: A Python function used as the *utility function* for the **BAYESQAE** algorithm. The default is the variance function. A custom *utility function* must accept two numpy arrays as inputs:\n", + " - The first array contains the values of the SMC probability distribution.\n", + " - The second array contains the corresponding weights.\n", + " Additionally, the function may accept a `kwargs` argument.\n", + "\n", + "---\n", + "\n", + "#### Shots Configuration:\n", + "The **BAYESQAE** algorithm requires performing virtual QAE experiments and updating probabilities using Bayes' rule. The number of shots for these tasks can be configured using the following keywords:\n", + "- `shots`: The number of shots used for measuring the quantum part of the algorithm (this is the number of shots used for the complete Grover circuit). Default in the original paper: 1.\n", + "- `warm_shots`: The number of shots selected for the warm-up phase (when the number of Grover operator applications is 0). Default in the original paper: 10.\n", + "- `bayes_shots`: The number of shots used for Bayesian computations required to update the SMC prior probability distribution to the posterior one. Default in the original paper: 1.\n", + "- `control_bayes_shots`: The number of shots used for updating the SMC posterior probabilities during optimization computations.\n", + "\n", + "---\n", + "\n", + "#### Fake Simulation Configuration:\n", + "For very low $\\epsilon$, the number of Grover operator applications can become prohibitively large, making quantum circuit simulation unfeasible. In such cases, a fake simulation can be used, where **QAE** experiment results are sampled from a binomial distribution with the appropriate probability. The following keywords enable this fake simulation:\n", + "- `fake`: A boolean flag. If `False`, the quantum circuit is used for the quantum part of the algorithm. If `True`, the quantum circuit is simulated by sampling from a binomial distribution (the true $\\theta$ must be provided).\n", + "- `theta_good`: A float representing the true $\\theta$ value for fake simulations (i.e., random binomial sampling).\n", + "\n", + "---\n", + "\n", + "#### Other Configuration:\n", + "Additional configuration options include:\n", + "- `save_smc_prob`: An integer setting the frequency (in number of iterations) for storing the SMC probabilities.\n", + "- `print_info`: An integer setting the frequency (in number of iterations) for printing information about the algorithm's evolution.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbf7dbe5", + "metadata": {}, + "outputs": [], + "source": [ + "#import the class\n", + "from QQuantLib.AE.bayesian_ae import BAYESQAE" + ] + }, + { + "cell_type": "markdown", + "id": "b86496b9", + "metadata": {}, + "source": [ + "### 2.4 Toy problem\n", + "\n", + "To show how our class and the algorithm work, we will define the following amplitude estimation problem using the generated oracle operator $\\mathcal{A}$ (see Section 2.1)\n", + "\n", + "$$|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right] \\tag{2}$$\n", + "\n", + "So comparing (2) with (1):\n", + "\n", + "$$\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle$$\n", + "\n", + "and \n", + "\n", + "$$\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$\n", + "\n", + "The target state, in this case, is $|1\\rangle$. In order to provide a binary representation the `bitfield` function from **QQuantLib.utils.utils** can be used. This has to be passed to the `target` variable as a list. Moreover, we have to provide the list of qubits where we are acting (to the `index` variable), in this case is just the whole register." + ] + }, + { + "cell_type": "markdown", + "id": "00c2bcc3", + "metadata": {}, + "source": [ + "### 2.4 Toy Problem\n", + "\n", + "To demonstrate how our class and the **BAYESQAE** algorithm work, we will define the following amplitude estimation problem using the generated oracle operator $\\mathcal{A}$ (see Section 2.1):\n", + "\n", + "$$\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\frac{1}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", + "$$\n", + "\n", + "By comparing Equation (2) with Equation (1), we can identify the components as follows:\n", + "\n", + "$$\n", + "\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\frac{\\sqrt{1}}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} |1\\rangle,\n", + "$$\n", + "\n", + "and\n", + "\n", + "$$\n", + "\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\frac{1}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$\n", + "\n", + "In this example, the target state is $|1\\rangle$. To provide its binary representation, the `bitfield` function from the **QQuantLib.utils.utils** module can be used. This binary representation must be passed to the `target` variable as a list. Additionally, we need to specify the list of qubits on which the operation is being performed (via the `index` variable). In this case, the operation acts on the entire register." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f79c857a", + "metadata": {}, + "outputs": [], + "source": [ + "from QQuantLib.utils.utils import bitfield" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6656dcd9", + "metadata": {}, + "outputs": [], + "source": [ + "index = [i for i in range(oracle.arity)]\n", + "# Qubits where the operator should acts\n", + "print(\"Operator should act over the following qubits: {}\".format(index))\n", + "# Definition of the state\n", + "state = 1\n", + "# State conversion to binary representation\n", + "target = bitfield(state, n_qbits)\n", + "print(\"State: |{}>. Corresponding target: {}\".format(state, target))\n", + "# Probability and theta that we want ot estiamte\n", + "a_good = probability[state]\n", + "theta_good = np.arcsin(np.sqrt(a_good))\n", + "print('Real Value of a: ', a_good)\n", + "print('theta_good: ', theta_good)" + ] + }, + { + "cell_type": "markdown", + "id": "c3108b8f", + "metadata": {}, + "source": [ + "Now that we have the mandatory inputs for instantiating the `BAYESQAE` class (the `oracle`, the `target`, and the `index`), we can proceed to configure the **BAYESQAE** algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80f8c574", + "metadata": {}, + "outputs": [], + "source": [ + "#Shots\n", + "shots = 1\n", + "#Stoping condition of the algorithm\n", + "epsilon = 1.0e-4\n", + "alpha = 0.05\n", + "\n", + "bayes_conf = {\n", + " # Quantum parts related\n", + " 'qpu': linalg_qpu,\n", + " 'mcz_qlm': True,\n", + " # Stoping condition\n", + " \"epsilon\" : epsilon,\n", + " \"alpha\": alpha,\n", + " \"max_iterations\": 1000,\n", + " # Configuration of the SMC method\n", + " \"particles\": 2000, \n", + " \"threshold\": 0.5, \n", + " \"kernel\" : \"LW\", #\"Metro\", #LW\n", + " \"alpha_lw\":0.9, # Liu-West kernel configuration\n", + " \"c\" : 2.38, # Metropoli kernel configuration \n", + " # Dinamyc evolution of the domain controls\n", + " \"k_0\":2, \n", + " \"T\" : 3,\n", + " \"R\" : 3, \n", + " \"n_evals\" : 50, \n", + " # Shots configuration\n", + " \"shots\" : shots,\n", + " \"warm_shots\":10,\n", + " \"bayes_shots\": shots,\n", + " \"control_bayes_shots\" : shots,\n", + " # Fake configuration\n", + " \"fake\": False,\n", + " \"theta_good\": theta_good, \n", + " # Other configuration\n", + " \"save_smc_prob\" : 1,\n", + " \"print_info\" : 1, \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9b73625", + "metadata": {}, + "outputs": [], + "source": [ + "#instantiate the Class\n", + "bayes = BAYESQAE(\n", + " oracle,\n", + " target=target,\n", + " index=index,\n", + " **bayes_conf\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7a0bdd34", + "metadata": {}, + "source": [ + "### 2.5 BAYESQAE Workflow\n", + "\n", + "In this section, we explain the workflow of the **BAYESQAE** algorithm using the `BAYESQAE` class and other implemented functions from the **QQuantLib/AE/bayesian_ae** module." + ] + }, + { + "cell_type": "markdown", + "id": "39a67331", + "metadata": {}, + "source": [ + "#### 2.5.1 Building the SMC Prior Probability Distribution\n", + "\n", + "The first step in the **BAYESQAE** algorithm is to construct the prior probability distribution. In **Sequential Monte Carlo (SMC)** methods, the probability distribution is represented by two arrays:\n", + "- One array contains the possible values of the parameter to be estimated: $\\{\\theta^{prior}_i\\}$.\n", + "- The second array contains the corresponding weights: $\\{W^{prior}_i\\}$, which depend on the desired prior probability distribution.\n", + "\n", + "The weights **must** be normalized such that:\n", + "$$\n", + "\\sum_i W^{prior}_i = 1.\n", + "$$\n", + "\n", + "The index $i$ runs from 1 to the desired number of particles, which is an input parameter of the algorithm (`particles`).\n", + "\n", + "Typically, a uniform prior probability distribution is used unless prior knowledge about the parameter suggests otherwise. In our implementation, we always start with a uniform probability distribution for the $\\theta$ value to estimate (recall that $\\sin^2{\\theta} = a$) over the interval $\\left[0, \\frac{\\pi}{2}\\right]$.\n", + "\n", + "The following cell builds the **SMC** prior probability distribution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e7a0ff6", + "metadata": {}, + "outputs": [], + "source": [ + "# We use the particles attribute of the class\n", + "theta_prior = np.random.uniform(0.0, 0.5 * np.pi, bayes.particles)\n", + "# All the particles will have associated the same weights\n", + "weights_prior = np.ones(len(theta_prior)) / len(theta_prior)" + ] + }, + { + "cell_type": "markdown", + "id": "fee5a19b", + "metadata": {}, + "source": [ + "#### 2.5.2 Warm-Up Measurement\n", + "\n", + "The second step in the **BAYESQAE** algorithm is the warm-up phase. During this phase, we measure the bare oracle operator $\\mathcal{A}$ to obtain initial results. This is achieved using the `quantum_measure_step` method of the **BAYESQAE** object.\n", + "\n", + "To perform this measurement, we need to specify:\n", + "- The number of controls, $m_k$, which should be set to 0 during the warm-up phase.\n", + "- The number of shots for measuring the circuit, which is determined by the `warm_shots` input parameter.\n", + "\n", + "The output of this step is the probability of measuring the *target* state. Additionally, the method provides the quantum circuit used for the measurement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6507a41e", + "metadata": {}, + "outputs": [], + "source": [ + "m_0 = 0\n", + "warm_outcome, routine = bayes.quantum_measure_step(m_0, bayes.warm_shots)\n", + "print(\"Probability measured for warm up phase: {}\".format(warm_outcome))\n", + "# Transform to measures\n", + "warm_outcome = round(warm_outcome * bayes.warm_shots)\n", + "print(\"Measured events for warm up phase: {}\".format(warm_outcome))\n", + "\n", + "# For storing all QAE experiments\n", + "control_list = []\n", + "shots_list = []\n", + "outcome_list = []\n", + "\n", + "control_list.append(m_0)\n", + "shots_list.append(bayes.warm_shots)\n", + "outcome_list.append(warm_outcome)" + ] + }, + { + "cell_type": "markdown", + "id": "2df58264", + "metadata": {}, + "source": [ + "#### 2.5.3 Updating the SMC Posterior Probability with Warm-Up Results\n", + "\n", + "After obtaining the results from the warm-up **QAE** experiment, we update the prior probability distribution to the posterior probability using Bayes' theorem. This can be achieved using the `bayesian_update` function from the **QQuantLib/AE/bayesian_ae** module.\n", + "\n", + "This function accepts the following arguments:\n", + "- `thetas`: The values of the SMC prior distribution ($\\{\\theta^{prior}_i\\}$).\n", + "- `weights`: The weights of the SMC prior distribution ($\\{W^{prior}_i\\}$).\n", + "- `m_k`: A list of all the controls used in the **QAE** experiments up to this point.\n", + "- `n_k`: A list of all the shots used in the **QAE** experiments up to this point.\n", + "- `o_k`: A list of all the outcomes obtained from the **QAE** experiments up to this point.\n", + "- `resample`: A boolean flag indicating whether resampling should be performed.\n", + "- `kwargs`: Additional arguments for configuring the resampling kernels.\n", + "\n", + "The workflow of the `bayesian_update` function is as follows:\n", + "\n", + "1. **Compute New Weights Using the Last QAE Experiment**:\n", + " - For each possible value of $\\theta$ in the input prior distribution, compute the likelihood based on the result of the last **QAE** experiment:\n", + " $$\n", + " L(\\theta | h_k; m_k, n_k) = \\sin^2\\left((2m_k+1)\\theta\\right)^{h_k} \\cos^2\\left((2m_k+1)\\theta\\right)^{n_k-h_k}.\n", + " $$\n", + " - Update the new weights using the computed likelihoods:\n", + " $$\n", + " w^{posterior}_i = L(\\theta^{prior}_i | h_k; m_k, n_k) \\cdot W^{prior}_i.\n", + " $$\n", + " - Normalize the new weights:\n", + " $$\n", + " W^{posterior}_i = \\frac{w^{posterior}_i}{\\sum w^{posterior}_i}.\n", + " $$\n", + "\n", + "2. **Construct the Posterior Distribution**:\n", + " - The posterior distribution is defined by the prior values ($\\{\\theta^{posterior}_i\\} = \\{\\theta^{prior}_i\\}$) and the updated weights ($W^{posterior}_i$).\n", + " - A potential issue arises when many weights are close to zero, making the probability distribution less useful for subsequent steps. To address this, the Bayesian update procedure is typically followed by a resampling protocol.\n", + "\n", + "3. **Resampling Protocol**:\n", + " - Resampling occurs if the **effective sample size (ESS)** of the weights falls below a specified `threshold` multiplied by the number of `particles`. The ESS is calculated as (the `ess` function from **QQuantLib.AE.bayesian_ae** executes this computation):\n", + " $$\n", + " ESS = \\frac{\\left(\\sum_i W^{posterior}_i\\right)^2}{\\sum_i {W^{posterior}_i}^2}.\n", + " $$\n", + "\n", + "4. **Resampling Procedure**:\n", + " - New $\\theta^{posterior}$ values are selected from the prior $\\theta^{prior}$ based on the probabilities given by their associated posterior weights ($W^{posterior}_i$).\n", + " - This process eliminates $\\theta$ values with very low weights and duplicates those with higher weights.\n", + "\n", + "5. **Apply Perturbation Kernel**:\n", + " - A perturbation kernel is applied to the resampled $\\theta^{posterior}$ values to introduce slight variations around the original values. Two kernel methods are supported:\n", + " - **Liu-West Kernel**: Set the `kernel` attribute to `\"LW\"` and configure the `alpha_lw` parameter appropriately (a value between 0 and 1).\n", + " - **Metropolis Kernel**: Set the `kernel` attribute to `\"Metro\"` and configure the `c` parameter appropriately (a value of 2.38 often works well).\n", + "\n", + "6. **Reset Weights After Resampling**:\n", + " - If resampling takes place, the new weights are reset to a uniform distribution:\n", + " $$\n", + " W^{posterior}_i = \\frac{1}{\\text{particles}}.\n", + " $$\n", + "\n", + "This process ensures that the posterior distribution remains robust and well-distributed for subsequent iterations of the algorithm.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09203d71", + "metadata": {}, + "outputs": [], + "source": [ + "from QQuantLib.AE.bayesian_ae import bayesian_update, ess" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c11194ec", + "metadata": {}, + "outputs": [], + "source": [ + "# first update the SMC posterior without allowing resampling\n", + "\n", + "theta_posterior, weights_posterior = bayesian_update(\n", + " theta_prior, weights_prior, # prior SMC \n", + " control_list, shots_list, outcome_list, # QAE experiment until the moment\n", + " resample=False, # Not resampliing\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06e70a50", + "metadata": {}, + "outputs": [], + "source": [ + "plt.plot(theta_prior, weights_prior, 'o')\n", + "plt.plot(theta_posterior, weights_posterior, 'o')\n", + "plt.legend([\"SMC prior\", \"SMC posterior\"])\n", + "plt.xlabel(r\"$\\theta$\")\n", + "plt.ylabel(r\"Weights($W$)\")" + ] + }, + { + "cell_type": "markdown", + "id": "4d22d7d9", + "metadata": {}, + "source": [ + "As can be observed, there are many $\\theta$ values with weights close to zero. The **effective sample size (ESS)** can be used to determine whether resampling is necessary. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36788d2a", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Resampling needed: {}\".format(ess(weights_posterior) < bayes.threshold * bayes.particles))" + ] + }, + { + "cell_type": "markdown", + "id": "91197db7", + "metadata": {}, + "source": [ + "If resampling is determined to be necessary, we can specify which kernel should be used for perturbing the resampled particles. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31d2bb68", + "metadata": {}, + "outputs": [], + "source": [ + "# Liu-West Kernel\n", + "lw_conf = {\"kernel\" : \"LW\", \"alpha_lw\" : bayes.alpha_lw}\n", + "theta_posterior_lw, weights_posterior_lw = bayesian_update(\n", + " theta_prior, weights_prior, # prior SMC \n", + " [m_0], [bayes.warm_shots], [warm_outcome], # QAE experiment until the moment\n", + " resample=True, # Not resampling\n", + " **lw_conf\n", + ")\n", + "#Metropoli Kernel\n", + "metro_conf = {\"kernel\" : \"Metro\", \"c\" : bayes.c}\n", + "theta_posterior_metro, weights_posterior_metro = bayesian_update(\n", + " theta_prior, weights_prior, # prior SMC \n", + " [m_0], [bayes.warm_shots], [warm_outcome], # QAE experiment until the moment\n", + " resample=True, # Not resampling\n", + " **metro_conf\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5d9308cb", + "metadata": {}, + "source": [ + "Now, we can visualize how the different kernels impact the SMC posterior probability distribution. By plotting the results, we can observe the effects of the perturbation introduced by each kernel on the resampled particles. This allows us to compare the behavior of the **Liu-West** and **Metropolis** kernels and understand their influence on the final distribution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6849a1ab", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(theta_prior)\n", + "plt.hist(theta_posterior_lw, alpha=0.8)\n", + "plt.hist(theta_posterior_metro, alpha=0.8)\n", + "plt.legend([\"Prior\", \"Posterior LW\", \"Posterior Metro\"])" + ] + }, + { + "cell_type": "markdown", + "id": "26f80a35", + "metadata": {}, + "source": [ + "It is important to remember that when resampling is performed, the weights of the particles are reinitialized to have equal probabilities. This ensures that all resampled particles contribute equally to the subsequent steps of the algorithm, maintaining a balanced representation of the probability distribution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39f383d8", + "metadata": {}, + "outputs": [], + "source": [ + "plt.plot(theta_posterior_lw, weights_posterior_lw, 'o')\n", + "plt.plot(theta_posterior_metro, weights_posterior_metro, 'o')\n", + "plt.legend([\"LW\", \"Metro\"])\n", + "plt.xlabel(r\"$\\theta$\")\n", + "plt.ylabel(r\"Weights($W$)\")" + ] + }, + { + "cell_type": "markdown", + "id": "2af03e5c", + "metadata": {}, + "source": [ + "Now that we have the SMC posterior distribution ($\\{\\theta^{posterior}_i\\}$ and $\\{W^{posterior}_i\\}$), we can estimate the desired parameter ($a$ or $\\theta$) by computing the **expected value of the mean** under the SMC posterior distribution.\n", + "\n", + "In the **SMC** framework, the expected value of any function $f$ under an SMC distribution is computed as follows:\n", + "$$\n", + "E_{\\theta, W}\\left[f(x)\\right] = \\sum_i f(\\theta^{posterior}_i) W^{posterior}_i\n", + "$$\n", + "\n", + "Thus, the desired parameter estimation, $a$, can be computed as:\n", + "\n", + "$$\n", + "\\hat{a} = \\sum_i \\sin^2(\\theta^{posterior}_i) W_i^{posterior}\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57b12207", + "metadata": {}, + "outputs": [], + "source": [ + "# We use Metro Kernel results\n", + "theta_posterior, weights_posterior = theta_posterior_metro, weights_posterior_metro\n", + "# Computing the estimations\n", + "a_estimation = (np.sin(theta_posterior) ** 2) @ weights_posterior\n", + "print(\"Estimation of the a: {}. Real a value: {}\".format(a_estimation, a_good))" + ] + }, + { + "cell_type": "markdown", + "id": "163e2ed1", + "metadata": {}, + "source": [ + "The confidence interval for a given confidence level $\\alpha$ can be computed using the SMC posterior distribution. The confidence interval $\\left[\\theta^{\\alpha}_l, \\theta^{\\alpha}_u\\right]$ is defined such that:\n", + "\n", + "$$\n", + "P_{\\theta, W}[\\theta > \\theta^{\\alpha}_l \\; \\text{and} \\; \\theta < \\theta^{\\alpha}_u] \\geq 1.0 - \\alpha\n", + "$$\n", + "\n", + "This confidence interval serves as the **stopping condition** for the algorithm. Specifically, we require the size of the confidence interval to be smaller than an input threshold $\\epsilon$ (specified via the `epsilon` keyword):\n", + "\n", + "$$\n", + "\\left|a^{\\alpha}_u - a^{\\alpha}_l\\right| < \\frac{\\epsilon}{2}\n", + "$$\n", + "\n", + "Here, $a^{\\alpha}_{u, l}$ is derived from the bounds of the confidence interval for $\\theta$ as follows:\n", + "$$\n", + "a^{\\alpha}_{u, l} = \\sin^2{\\theta^{\\alpha}_{u, l}}\n", + "$$\n", + "\n", + "The confidence interval can be computed using the `confidence_intervals` function from the **QQuantLib.AE.bayesian_ae** module." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "919b8327", + "metadata": {}, + "outputs": [], + "source": [ + "from QQuantLib.AE.bayesian_ae import confidence_intervals" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a9dde61", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Cofidence level: {}\".format(bayes.alpha))\n", + "theta_l, theta_u = confidence_intervals(theta_posterior, weights_posterior, bayes.alpha)\n", + "a_l = np.sin(theta_l) ** 2\n", + "a_u = np.sin(theta_u) ** 2\n", + "print(\"confidence inteval: ({}, {})\".format(a_l, a_u))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3dcb86fa", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(np.sin(theta_posterior) ** 2)\n", + "plt.axvline(a_l, c=\"r\")\n", + "plt.axvline(a_u, c=\"r\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71d92d48", + "metadata": {}, + "outputs": [], + "source": [ + "lenght_interval = np.abs(a_u-a_l)\n", + "print(\"The length of the confidence interval for confidence level {} is: {}\".format(\n", + " bayes.alpha, lenght_interval,\n", + "))\n", + "print(\"The desired epsilon is: {}\".format(bayes.epsilon))\n", + "print(\"Stop condition: {}\".format(lenght_interval < bayes.epsilon))" + ] + }, + { + "cell_type": "markdown", + "id": "0148ccb8", + "metadata": {}, + "source": [ + "#### 2.5.4 Improving Estimation Using Grover Circuits\n", + "\n", + "The **BAYESQAE** algorithm now begins to iteratively use Grover circuits to refine the estimation. In each iteration, the algorithm computes an optimal control $m^{*}_k$ based on the SMC posterior distribution from the previous iteration (i.e., the posterior distribution from the previous iteration becomes the prior distribution for the current iteration). The algorithm then performs a **Quantum Amplitude Estimation (QAE)** experiment with $m^{*}_k$ and updates the SMC posterior probability using the results. These iterations continue until the stopping condition is satisfied.\n", + "\n", + "The workflow for iteration $k$ is as follows:\n", + "\n", + "1. **Convert Old Posterior into New Priors**:\n", + " - Set $\\theta^{prior}_{i,k} \\leftarrow \\theta^{posterior}_{i, k-1}$.\n", + " - Set $W^{prior}_{i, k} \\leftarrow W^{posterior}_{i, k-1}$.\n", + "\n", + "2. **Compute Optimal Control**:\n", + " - Compute the optimal control $m^*_k$ using $\\theta^{prior}_{i,k}$, $W^{prior}_{i,k}$, and the results of all **QAE** experiments performed up to iteration $k$.\n", + "\n", + "3. **Execute QAE Experiment**:\n", + " - Perform the **QAE** experiment: $\\mathcal{Q}^{m^*_k}|\\Psi\\rangle$, using $n_k$ shots, and obtain the number of good states measured: $o_k$.\n", + "\n", + "4. **Update SMC Posterior Probability**:\n", + " - Use the `bayesian_update` function to update the SMC posterior probability:\n", + " $$\n", + " \\theta^{prior}_{i,k}, W^{prior}_{i,k}, m^*_k, n_k, o_k \\rightarrow \\theta^{posterior}_{i, k}, W^{posterior}_{i, k}.\n", + " $$\n", + "\n", + "5. **Check Stopping Condition**:\n", + " - Evaluate whether the stopping condition is satisfied. If not, return to step 1; otherwise, terminate the algorithm.\n", + "\n", + "This iterative process ensures that the estimation progressively improves until the desired precision is achieved." + ] + }, + { + "cell_type": "markdown", + "id": "9fa4be65", + "metadata": {}, + "source": [ + "#### 2.5.5 Computing the Optimal Control\n", + "\n", + "From the workflow presented in the previous section, the only step that remains to be explained is the second one: **Compute Optimal Control**.\n", + "\n", + "Before diving into this step, we need an important ingredient: a function that computes the **Utility** of an experiment for a given control $m_k$ and an input SMC probability distribution.\n", + "\n", + "---\n", + "\n", + "##### Utility of an Experiment\n", + "\n", + "The **Utility** of an experiment is computed using the following formula:\n", + "\n", + "$$\n", + "U^{f}(m_k) = \\sum_{D} E_{P(\\theta)}[P(D; m_k)] \\cdot E_{P(\\theta|D, m_k)}[f(\\theta, D; m_k)]\n", + "$$\n", + "\n", + "Where:\n", + "- $f$: An input utility function defined by the user. In the original paper, this is typically the variance.\n", + "- $E_{P(\\theta)}[P(D; m_k)]$: The probability of obtaining the outcome $D$ for a **QAE** experiment with control $m_k$. This is computed using Bayes' rule:\n", + " - $E_{P(\\theta)}[P(D; m_k)] = \\int L(\\theta|D; m_k) P(\\theta) d\\theta$\n", + " - $L(\\theta|D; m_k)$: The likelihood of obtaining the outcome $D$ for a **QAE** experiment with control $m_k$.\n", + " - If the input SMC probability is given by $\\{\\theta^{prior}_i, W^{prior}_i\\}$, this computation simplifies to:\n", + " $$\n", + " E_{P(\\theta)}[P(D; m_k)] = \\sum_i L(\\theta^{prior}_i|D; m_k) W^{prior}_i\n", + " $$\n", + "\n", + "- $E_{P(\\theta|D, m_k)}[f(\\theta, D; m_k)]$: The expected value of the *utility function* for an outcome $D$ of a **QAE** experiment with control $m_k$.\n", + " - If the input SMC probability is given by $\\{\\theta^{prior}_i, W^{prior}_i\\}$, the computation involves performing a hypothetical Bayesian update to compute the corresponding posterior distribution $\\{\\theta^{posterior}_i, W^{posterior}_i\\}$ using the possible outcome $D$ from a **QAE** experiment with control $m_k$.\n", + " $$\n", + " \\theta^{prior}_{i}, W^{prior}_{i}, m_k, D \\rightarrow \\theta^{posterior}_{i}, W^{posterior}_{i}.\n", + " $$ \n", + " - The computation is done as follows:\n", + " $$\n", + " E_{P(\\theta|D, m_k)}[f(\\theta, D; m_k)] = \\sum_i f(\\theta^{posterior}_i) \\cdot W^{posterior}_i\n", + " $$\n", + "\n", + "---\n", + "\n", + "To compute the **utility** of the experiment ($U^{f}(m_k)$), the `average_expectation` function from the **QQuantLib.AE.bayesian_ae** module is used. This function accepts the following inputs:\n", + "- `thetas`: The values of the SMC prior distribution ($\\{\\theta^{prior}_i\\}$).\n", + "- `weights`: The weights of the SMC prior distribution ($\\{W^{prior}_i\\}$).\n", + "- `m_k`: A possible input control $m_k$.\n", + "- `n_k`: The number of shots $n_k$ for a hypothetical **QAE** experiment.\n", + "- `kwargs`: Other keyword arguments (It must have the `utility_function` one, that pass a python function)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c7a4f90", + "metadata": {}, + "outputs": [], + "source": [ + "from QQuantLib.AE.bayesian_ae import average_expectation, variance_function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a991bb7", + "metadata": {}, + "outputs": [], + "source": [ + "m_k = 2\n", + "n_k = 1\n", + "conf_u = {\"utility_function\": variance_function}\n", + "utility = average_expectation(\n", + " theta_posterior, weights_posterior, m_k, n_k, **conf_u\n", + ")\n", + "print(\"For m_k:{} and n_k:{} the utility will be: {}\".format(\n", + " m_k, n_k, utility\n", + "))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad79242f", + "metadata": {}, + "outputs": [], + "source": [ + "m_k = 10\n", + "n_k = 1\n", + "conf_u = {\"utility_function\": variance_function}\n", + "utility = average_expectation(\n", + " theta_posterior, weights_posterior, m_k, n_k, **conf_u\n", + ")\n", + "print(\"For m_k:{} and n_k:{} the utility will be: {}\".format(\n", + " m_k, n_k, utility\n", + "))" + ] + }, + { + "cell_type": "markdown", + "id": "68cc0603", + "metadata": {}, + "source": [ + "##### Control Optimization Using the **Utility**\n", + "\n", + "The **Utility** of an experiment is used to compute the **Optimal Control** for the next iteration. This is achieved by minimizing the **Utility** over a predefined control domain. The `optimize_control` method of the `BAYESQAE` class performs this minimization and outputs the optimal control $m^*_k$.\n", + "\n", + "Initially, the predefined control domain is initialized to the interval $\\left[m_{min}, m_{max}\\right]$, where:\n", + "- $m_{min} = 0$\n", + "- $m_{max} = k_0 \\cdot n_{evals}$\n", + "\n", + "Here, $k_0$ and $n_{evals}$ are hyperparameters of the algorithm. The **Utility** is computed for $n_{evals}$ possible controls within this interval, and the $m^*_k$ that minimizes the **Utility** is selected.\n", + "\n", + "However, as the algorithm progresses through multiple iterations, the initial domain interval may no longer provide a suitable minimum for the **Utility**. In such cases, it becomes necessary to enlarge the control domain interval. This enlargement is controlled by two hyperparameters: $R$ and $T$.\n", + "\n", + "- Each time the selected $m^*_k$ falls within the top $R$ tested controls, an internal counter is incremented by one.\n", + "- When the counter reaches the threshold $T$, the control domain interval is enlarged by setting:\n", + " - $m_{min} \\leftarrow m_{max}$\n", + " - $m_{max} \\leftarrow 2 \\cdot m_{max}$\n", + "- The internal counter is reset to zero, and the iteration process continues using the new control domain interval for computing $m^*_k$.\n", + "\n", + "---\n", + "\n", + "### Inputs of the `optimize_control` Method:\n", + "- `thetas`: The values of the SMC prior distribution ($\\{\\theta^{prior}_i\\}$).\n", + "- `weights`: The weights of the SMC prior distribution ($\\{W^{prior}_i\\}$).\n", + "- `control_bayes_shots`: The number of shots used for the hypothetical **QAE** experiment required to compute the minimum of the **Utility** of an experiment. In the original paper, this is set to 1.\n", + "- `kwargs`: Keyword arguments for configuring the `optimize_control` method:\n", + " - `n_evals`: Number of evaluations ($n_{evals}$) for testing controls within the domain.\n", + " - `k_0`: Scaling factor ($k_0$) for initializing the control domain.\n", + " - `R`: Threshold for determining when the selected $m^*_k$ is among the top tested controls.\n", + " - `T`: Counter threshold for triggering the enlargement of the control domain interval.\n", + " - `utility_function`: Function for computing the **Utility** of an experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eee3ad79", + "metadata": {}, + "outputs": [], + "source": [ + "# We use the last step SMC posterior probability for computing new \n", + "\n", + "# Configure the optimal control\n", + "conf = {\n", + " \"R\": bayes.R, \"T\": bayes.T, \"k_0\": bayes.k_0, \"n_evals\": bayes.n_evals,\n", + " \"utility_function\": bayes.utility_function\n", + "}\n", + "# Control for next iteration\n", + "optimal_control = bayes.optimize_control(\n", + " theta_posterior, weights_posterior, \n", + " bayes.control_bayes_shots, **conf\n", + ")\n", + "print(\"The optimal control for the following iteration is: {}\".format(optimal_control))" + ] + }, + { + "cell_type": "markdown", + "id": "2ee2267e", + "metadata": {}, + "source": [ + "The attributes `m_min` and `m_max` of the `BAYESQAE` object store the interval for the domain control ($m_{min}$ and $m_{max}$).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f80e1080", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Domain interval control: [{}, {}]\".format(bayes.m_min, bayes.m_max))" + ] + }, + { + "cell_type": "markdown", + "id": "8ced731f", + "metadata": {}, + "source": [ + "Now, we can iteratively execute the workflow presented in Section 2.5.5 until the desired `epsilon` is achieved. \n", + "\n", + "The following cell can be run multiple times until the stopping condition is satisfied. Each execution refines the estimation by updating the SMC posterior distribution and computing a new optimal control $m_k^*$, bringing the algorithm closer to the desired precision." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ce5c0c3", + "metadata": {}, + "outputs": [], + "source": [ + "# Steps 1 and 2 of section 2.5.5\n", + "optimal_control = bayes.optimize_control(\n", + " theta_posterior, weights_posterior, \n", + " bayes.control_bayes_shots, **conf\n", + ")\n", + "print(\"The optimal control for the following iteration is: {}\".format(optimal_control))\n", + "print(\"Domain interval control: [{}, {}]\".format(bayes.m_min, bayes.m_max))\n", + "# Step 3 of section 2.5.5\n", + "p_m, routine = bayes.quantum_measure_step(\n", + " optimal_control, bayes.shots\n", + ")\n", + "\n", + "control_list.append(optimal_control)\n", + "shots_list.append(bayes.shots)\n", + "outcome_list.append(round(p_m * bayes.shots))\n", + "# Step 4 of section 2.5.5\n", + "theta_posterior, weights_posterior = bayesian_update(\n", + " theta_posterior, weights_posterior,\n", + " control_list, shots_list, outcome_list, **metro_conf\n", + "\n", + ") \n", + "# Step 5 of section 2.5.5\n", + "a_estimation = (np.sin(theta_posterior) ** 2) @ weights_posterior\n", + "theta_l, theta_u = confidence_intervals(theta_posterior, weights_posterior, bayes.alpha)\n", + "a_l = np.sin(theta_l) ** 2\n", + "a_u = np.sin(theta_u) ** 2\n", + "print(\"Estimation of the a: {}. \\n\\t confidence inteval: ({}, {})\".format(\n", + " a_estimation, a_l, a_u)\n", + ")\n", + "\n", + "stop_condition = (a_u-a_l) < 2 * bayes.epsilon\n", + "print(\"Stop condition: {}\".format(stop_condition))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "635dee6b", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Absolute Error: {}\".format(np.abs(a_estimation-a_good)))" + ] + }, + { + "cell_type": "markdown", + "id": "f985e603", + "metadata": {}, + "source": [ + "We can plot the final SMC posterior probability:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b4080a7", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(np.sin(theta_posterior)**2)" + ] + }, + { + "cell_type": "markdown", + "id": "80d45150", + "metadata": {}, + "source": [ + "## 3. Complete Execution of BAYESQAE\n", + "\n", + "Section 2 provided a detailed explanation of the **BAYESQAE** algorithm, with an emphasis on the inner workings and the use of various functions and methods from **QQuantLib.AE.bayesian_ae** for pedagogical purposes. The `BAYESQAE` class encapsulates the entire workflow described in Section 2, abstracting away the complexities and providing a user-friendly interface.\n", + "\n", + "For a complete **BAYESQAE** execution, users are expected to interact with only the following methods:\n", + "- `bayesqae`\n", + "- `run`\n", + "\n", + "The follwoing sub sections explain them." + ] + }, + { + "cell_type": "markdown", + "id": "31a15610", + "metadata": {}, + "source": [ + "### 3.1 The `bayesqae` Method\n", + "\n", + "To execute the complete **BAYESQAE** algorithm, the `bayesqae` method is used. The user must first initialize the class by providing the minimum mandatory inputs (`oracle`, `target`, and `index`). It is recomended too provided info about the quantum part of the algorithm (keyword `qpu`). Once the class is initialized, the `bayesqae` method can be executed by supplying a Python dictionary containing all the necessary configuration parameters.\n", + "\n", + "This approach allows users to provide a comprehensive set of inputs in a single step, ensuring that the algorithm is fully configured before execution. The method handles the entire workflow internally, abstracting away the complexities of the underlying processes.\n", + "\n", + "This method populates the following attributes:\n", + "\n", + "- `mean_a`: evolution of the expected value of the mean throughout the entire algorithm's execution.\n", + "- `lower_a`: evolution of the lower value for confidence interval throughout the entire algorithm's execution.\n", + "- `upper_a`: evolution of the upper value for confidence interval throughout the entire algorithm's execution.\n", + "- `control_list`: evolution of the different controls used for the **QAE** experiments performed throughout the entire algorithm's execution.\n", + "- `shots_list`: number of shots used for the **QAE** experiments performed throughout the entire algorithm's execution.\n", + "- `outcome_list`: evolution of the different outcomes from the **QAE** experiments performed throughout the entire algorithm's execution.\n", + "- `pdf_theta`: evolution of the probability distribution values throughout the entire algorithm's execution.\n", + "- `pdf_weights`: evolution of the probability distribution weights throughout the entire algorithm's execution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a25cf6fc", + "metadata": {}, + "outputs": [], + "source": [ + "#instantiate the Class\n", + "\n", + "# It is recomeded to initialize the quantum part when the class is instantiated\n", + "qpu_conf = {\n", + " # Quantum parts related\n", + " 'qpu': linalg_qpu,\n", + "}\n", + "\n", + "bayes = BAYESQAE(\n", + " oracle,\n", + " target=target,\n", + " index=index,\n", + " **qpu_conf\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fdd2ad6c", + "metadata": {}, + "outputs": [], + "source": [ + "#Shots\n", + "shots = 1\n", + "#Stoping condition of the algorithm\n", + "epsilon = 1.0e-3\n", + "alpha = 0.05\n", + "\n", + "bayes_conf = {\n", + " # Stoping condition\n", + " \"epsilon\" : epsilon,\n", + " \"alpha\": alpha,\n", + " \"max_iterations\": 1000,\n", + " # Configuration of the SMC method\n", + " \"particles\": 2000, \n", + " \"threshold\": 0.5, \n", + " \"kernel\" : \"LW\", #\"Metro\", #LW\n", + " \"alpha_lw\":0.9, # Liu-West kernel configuration\n", + " \"c\" : 2.38, # Metropoli kernel configuration \n", + " # Dinamyc evolution of the domain controls\n", + " \"k_0\":2, \n", + " \"T\" : 3,\n", + " \"R\" : 3, \n", + " \"n_evals\" : 50, \n", + " # Shots configuration\n", + " \"shots\" : shots,\n", + " \"warm_shots\":10,\n", + " \"bayes_shots\": shots,\n", + " \"control_bayes_shots\" : shots,\n", + " # Fake configuration\n", + " \"fake\": False,\n", + " \"theta_good\": theta_good, \n", + " # Other configuration\n", + " \"save_smc_prob\" : 1,\n", + " \"print_info\" : 1, \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b47baae", + "metadata": {}, + "outputs": [], + "source": [ + "a_mean, a_l, a_u = bayes.bayesqae(**bayes_conf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90524ccb", + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Estimation: {}. Confidence intervals: [{},{}]\".format(\n", + " a_mean, a_l, a_u\n", + "))\n", + "print(\"Error: {}\".format(abs(a_mean-a_good)))" + ] + }, + { + "cell_type": "markdown", + "id": "b6feb3a2", + "metadata": {}, + "source": [ + "\n", + "The class attributes `mean_a`, `lower_a`, and `upper_a` store the evolution of the expected value of the mean and the confidence intervals throughout the entire algorithm's execution. These attributes provide insight into how the estimates for $a$ improve iteratively as the **BAYESQAE** algorithm progresses." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2eee08b0", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "plt.plot(bayes.lower_a, '-o')\n", + "plt.plot(bayes.mean_a, '-o')\n", + "plt.plot(bayes.upper_a, '-o')\n", + "plt.legend([\"a_l\", \"a_mean\", \"a_u\"])\n", + "plt.ylabel(r\"a\")\n", + "plt.xlabel(\"iteration\")" + ] + }, + { + "cell_type": "markdown", + "id": "91aff1a2", + "metadata": {}, + "source": [ + "Additionally, the attributes `outcome_list`, `control_list`, and `shots_list` store the details of the different **QAE** experiments performed during the algorithm's execution. These attributes provide a record of the outcomes, controls, and shot configurations used in each experiment, allowing for a detailed analysis of the algorithm's behavior and results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ffc26d4", + "metadata": {}, + "outputs": [], + "source": [ + "bayes.outcome_list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ef97c0c", + "metadata": {}, + "outputs": [], + "source": [ + "bayes.control_list" + ] + }, + { + "cell_type": "markdown", + "id": "e3cfaeac", + "metadata": {}, + "source": [ + "The class attributes `pdf_theta` and `pdf_weights` are Pandas DataFrames that store the SMC probability distribution (values and weights, respectively) at various stages of the algorithm's iterations. The frequency with which these distributions are saved is controlled by the keyword argument `save_smc_prob`. This allows users to analyze the evolution of the SMC probability distribution over time, providing insights into how the algorithm refines its estimates." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2d4e95a1", + "metadata": {}, + "outputs": [], + "source": [ + "bayes.pdf_theta " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0942875a", + "metadata": {}, + "outputs": [], + "source": [ + "bayes.pdf_weights" + ] + }, + { + "cell_type": "markdown", + "id": "788b2bc2", + "metadata": {}, + "source": [ + "The following cell generates an animation illustrating the evolution of the SMC probability distribution across different iterations of the algorithm. This visualization provides insight into how the distribution refines and converges as the **BAYESQAE** process progresses." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f80ded2e", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_theta = bayes.pdf_theta\n", + "%matplotlib notebook\n", + "from matplotlib.animation import FuncAnimation\n", + "fig, ax = plt.subplots()\n", + "def update(frame):\n", + " \n", + " # Limpiar el eje para evitar superposición\n", + " ax.cla()\n", + " \n", + " # Seleccionar la columna correspondiente al cuadro actual\n", + " column_name = pdf_theta.columns[frame]\n", + " data = pdf_theta[column_name]\n", + " \n", + " # Dibujar el histograma\n", + " #ax.hist(pdf_theta[\"prior\"], bins=20, edgecolor='black')\n", + " ax.hist(data, bins=20, color='skyblue', edgecolor='black')\n", + " \n", + " # Añadir título y etiquetas\n", + " ax.set_title(f'Histograma of {column_name}')\n", + " #ax.set_xlabel('Valores')\n", + " #ax.set_ylabel('Frecuencia')\n", + " \n", + " # Devolver el eje\n", + " return ax\n", + "# Número de frames igual al número de columnas\n", + "num_frames = len(pdf_theta.columns)\n", + "ani = FuncAnimation(fig, update, frames=num_frames, interval=1000, repeat=True)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8f10d9d", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "b6ae58ec", + "metadata": {}, + "source": [ + "We can update the input dictionary and perform the algorithm again" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85de817d", + "metadata": {}, + "outputs": [], + "source": [ + "# Changing the shots\n", + "bayes_conf.update({\n", + " \"shots\" : 1000,\n", + " \"warm_shots\":100,\n", + " \"bayes_shots\": 100,\n", + " \"control_bayes_shots\" : 1,\n", + " \"epsilon\" : 1.0e-4, \n", + "})\n", + "a_mean, a_l, a_u = bayes.bayesqae(**bayes_conf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79d504f4", + "metadata": {}, + "outputs": [], + "source": [ + "# Here we use fake binomial sampling for simulating quantum step\n", + "bayes_conf.update({\n", + " \"shots\" : 1,\n", + " \"warm_shots\":1,\n", + " \"bayes_shots\": 1,\n", + " \"control_bayes_shots\" : 1,\n", + " \"epsilon\" : 1.0e-10, \n", + " \"save_smc_prob\" : 10,\n", + " \"print_info\" : 10, \n", + " \"fake\":True\n", + "})\n", + "a_mean, a_l, a_u = bayes.bayesqae(**bayes_conf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aced85bb", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "plt.plot(bayes.lower_a, '-o')\n", + "plt.plot(bayes.mean_a, '-o')\n", + "plt.plot(bayes.upper_a, '-o')\n", + "plt.legend([\"a_l\", \"a_mean\", \"a_u\"])\n", + "plt.ylabel(r\"a\")\n", + "plt.xlabel(\"iteration\")\n", + "plt.xscale(\"log\")\n", + "plt.yscale(\"log\")" + ] + }, + { + "cell_type": "markdown", + "id": "a047199d", + "metadata": {}, + "source": [ + "### 3.1 The `run` Method\n", + "\n", + "The `run` method executes the **BAYESQAE** algorithm directly using the configuration provided during the instantiation of the `BAYESQAE` class or the default configuration if none is specified. This method automates the entire workflow, handling all intermediate steps and iterations until the stopping condition is satisfied.\n", + "\n", + "Upon completion, the `run` method populates the following attributes:\n", + "\n", + "- `ae` : final exepected mean value of the parameter $a$ (computed using the final posterior distribution)\n", + "- `ae_l` : final lower value of the confidence interval for aprameter $a$ (computed using the final posterior distribution)\n", + "- `ae_u`: final upper value of the confidence interval for aprameter $a$ (computed using the final posterior distribution)\n", + "- `schedule_pdf`: A Pandas DataFrame that summarizes the **QAE** experiments and their results performed during the execution of the algorithm.\n", + "- `oracle_calls`: The total number of oracle calls made during the complete execution of the algorithm, accounting for the number of shots used in each experiment.\n", + "- `max_oracle_depth`: The maximum number of applications of the oracle during the execution of the algorithm (not accounting for shots). This indicates the depth of the largest quantum circuit used.\n", + "- `pdf_estimation`: A Pandas DataFrame that tracks the evolution of the mean estimation and the upper and lower bounds of the confidence intervals throughout the algorithm's execution.\n", + "- `run_time`: the elapsed time of the complete **BAYESQAE** algorithm\n", + "- `quantum_time`: total time the algorithm expends in the pure quantum part.\n", + "\n", + "These attributes provide valuable insights into the performance and behavior of the algorithm, enabling users to analyze its efficiency and accuracy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "af50aa5b", + "metadata": {}, + "outputs": [], + "source": [ + "#Shots\n", + "shots = 1\n", + "#Stoping condition of the algorithm\n", + "epsilon = 1.0e-4\n", + "alpha = 0.05\n", + "\n", + "bayes_conf = {\n", + " 'qpu': linalg_qpu, \n", + " # Stoping condition\n", + " \"epsilon\" : epsilon,\n", + " \"alpha\": alpha,\n", + " \"max_iterations\": 1000,\n", + " # Configuration of the SMC method\n", + " \"particles\": 2000, \n", + " \"threshold\": 0.5, \n", + " \"kernel\" : \"LW\", #\"Metro\", #LW\n", + " \"alpha_lw\":0.9, # Liu-West kernel configuration\n", + " \"c\" : 2.38, # Metropoli kernel configuration \n", + " # Dinamyc evolution of the domain controls\n", + " \"k_0\":2, \n", + " \"T\" : 3,\n", + " \"R\" : 3, \n", + " \"n_evals\" : 50, \n", + " # Shots configuration\n", + " \"shots\" : shots,\n", + " \"warm_shots\":10,\n", + " \"bayes_shots\": shots,\n", + " \"control_bayes_shots\" : 1,\n", + " # Fake configuration\n", + " \"fake\": False,\n", + " \"theta_good\": theta_good, \n", + " # Other configuration\n", + " \"save_smc_prob\" : 10,\n", + " \"print_info\" : 10, \n", + "}\n", + "bayes = BAYESQAE(\n", + " oracle,\n", + " target=target,\n", + " index=index,\n", + " **bayes_conf\n", + ")\n", + "a_estimated = bayes.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "622892aa", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "plt.plot(bayes.lower_a, '-o')\n", + "plt.plot(bayes.mean_a, '-o')\n", + "plt.plot(bayes.upper_a, '-o')\n", + "plt.legend([\"a_l\", \"a_mean\", \"a_u\"])\n", + "plt.ylabel(r\"a\")\n", + "plt.xlabel(\"iteration\")\n", + "plt.xscale(\"log\")\n", + "plt.yscale(\"log\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d594c2b", + "metadata": {}, + "outputs": [], + "source": [ + "print('a_estimated: ', a_estimated)\n", + "print('Real Value of a: ', a_good)\n", + "print('Bounds for a: [bayes.ae_l, bayes.ae_u] = [{}, {}]'.format(bayes.ae_l, bayes.ae_u))\n", + "print('Estimated a: bayes.ae = ', bayes.ae)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b155f319", + "metadata": {}, + "outputs": [], + "source": [ + "print(' a_real-iqbayesae.ae: ', abs(bayes.ae-a_good))\n", + "print('Epsilon required: ', bayes.epsilon)\n", + "print('Bayes lenght of confidence intervals ', 0.5 * (bayes.ae_u-bayes.ae_l))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9285d1f7", + "metadata": {}, + "outputs": [], + "source": [ + "#Total number of oracle calls\n", + "print(\"The total number of the oracle calls was: {}\".format(bayes.oracle_calls))\n", + "#Total number of oracle calls\n", + "print(\"The maximum depth oracle circuit was: {}\".format(bayes.max_oracle_depth))\n", + "\n", + "print(\"Elapsed time for the run method: \", bayes.run_time)\n", + "print(\"Time of the quantum parts: \", bayes.quantum_time)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ace1b447", + "metadata": {}, + "outputs": [], + "source": [ + "sum(bayes.quantum_times)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0cb013bc", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "bayes.schedule_pdf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04cd0352", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "bayes.pdf_estimation" + ] + }, + { + "cell_type": "markdown", + "id": "7365adbf", + "metadata": {}, + "source": [ + "Finally we can use the evolution of the **QAE** experiments, `schedule_pdf`, and the evolution of the $a$ estimations, `pdf_estimation`, to plot the evolution of the error ($\\epsilon$) of the estimation versus the number of calls ($N_A$) to the oracle and compare with MonteCarlo behaviour, $\\epsilon = \\frac{1}{\\sqrt{N_A}}$, and with the Heisenberg limit $\\epsilon = \\frac{1}{N_A}$\n", + "\n", + "Finally, we can utilize the evolution of the **QAE** experiments (`schedule_pdf`) and the evolution of the $a$ estimations (`pdf_estimation`) to plot the relationship between the estimation error ($\\epsilon$) and the number of oracle calls ($N_A$). This allows us to compare the performance of the **BAYESQAE** algorithm with two benchmark behaviors:\n", + "- The **Monte Carlo behavior**, where $\\epsilon = \\frac{1}{\\sqrt{N_A}}$.\n", + "- The **Heisenberg limit**, where $\\epsilon = \\frac{1}{N_A}$.\n", + "\n", + "This comparison provides insight into the efficiency and convergence rate of the **BAYESQAE** algorithm relative to classical and quantum limits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7eb90038", + "metadata": {}, + "outputs": [], + "source": [ + "oracle_calls_ev = (bayes.schedule_pdf[\"m_k\"] * 2 + 1) * bayes.schedule_pdf[\"shots\"].cumsum()\n", + "error_ev = np.abs(bayes.pdf_estimation[\"mean\"] - a_good)\n", + "\n", + "plt.plot(\n", + " oracle_calls_ev, \n", + " error_ev,\n", + " 'o'\n", + ")\n", + "\n", + "domain_oracle = np.linspace(oracle_calls_ev.min(), oracle_calls_ev.max())\n", + "\n", + "plt.plot(\n", + " domain_oracle, \n", + " np.sqrt(1.0/domain_oracle),\n", + " '-'\n", + ")\n", + "plt.plot(\n", + " domain_oracle, \n", + " 1.0/domain_oracle,\n", + " '-'\n", + ")\n", + "\n", + "plt.xlabel(r\"Oracle Calls ($N_A$)\")\n", + "plt.ylabel(r\"Absolute Error (\\epsilon)\")\n", + "\n", + "plt.xscale(\"log\")\n", + "plt.yscale(\"log\")\n", + "plt.legend([\"BAYESQAE\", r\"$\\epsilon = \\frac{1}{\\sqrt{N_{A}}}$\", r\"$\\epsilon = \\frac{1}{N_{A}}$\"])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 36fc92d17af82e805c4fdf0e618eddf0685ca320 Mon Sep 17 00:00:00 2001 From: gonfeco Date: Wed, 19 Feb 2025 13:53:09 +0100 Subject: [PATCH 2/6] Added JSON configuration files for Bayesian --- QQuantLib/AE/ae_class.py | 8 ++++ .../jsons/ae_configuration_bayesqae.json | 43 +++++++++++++++++ .../q_ae_price/jsons/bayes_configuration.json | 48 +++++++++++++++++++ .../sine_integral/jsons/qae_sine_bayes.json | 40 ++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json create mode 100644 benchmark/q_ae_price/jsons/bayes_configuration.json create mode 100644 benchmark/sine_integral/jsons/qae_sine_bayes.json diff --git a/QQuantLib/AE/ae_class.py b/QQuantLib/AE/ae_class.py index 6939b03..5b366e9 100644 --- a/QQuantLib/AE/ae_class.py +++ b/QQuantLib/AE/ae_class.py @@ -18,6 +18,7 @@ from QQuantLib.AE.extended_real_quantum_ae import eRQAE from QQuantLib.AE.modified_real_quantum_ae import mRQAE from QQuantLib.AE.montecarlo_ae import MCAE +from QQuantLib.AE.bayesian_ae import BAYESQAE from QQuantLib.utils.utils import text_is_none class AE: @@ -172,6 +173,13 @@ def create_ae_solver(self): index=self.index, **self.solver_dict ) + elif self.ae_type == "BAYESQAE": + self.solver_ae = BAYESQAE( + self.oracle, + target=self.target, + index=self.index, + **self.solver_dict + ) else: raise ValueError("AE algorithm IS NOT PROVIDED in ae_type parameter \ Please use: MLAE, CQPEAE, IQPEAE, IQAE, RQAE") diff --git a/benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json b/benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json new file mode 100644 index 0000000..d413d15 --- /dev/null +++ b/benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json @@ -0,0 +1,43 @@ +[ + { + "ae_type": ["BAYESQAE"], + + "schedule": [null], + "delta": [null], + "ns": [null], + + "auxiliar_qbits_number": [null], + "window" : [null], + "kaiser_alpha" : [null], + + "cbits_number": [null], + + "epsilon": [1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6], + + "alpha": [0.05], + + "gamma": [null], + "q": [null], + "erqae_schedule" : [null], + + "particles" : [2000], + "threshold" : [0.5], + "kernel" : ["LW", "Metro"], + "alpha_lw" : [0.9], + "c" : [2.38], + "k_0": [2], + "T" : [3], + "R" : [3], + "n_evals" : [50], + "shots": [1], + "warm_shots": [10], + "bayes_shots": [1], + "control_bayes_shots" : [1], + + "multiplexor": [true], + "mcz_qlm": [false], + "file": ["BAYESQAE"] + + } +] + diff --git a/benchmark/q_ae_price/jsons/bayes_configuration.json b/benchmark/q_ae_price/jsons/bayes_configuration.json new file mode 100644 index 0000000..1ca5d24 --- /dev/null +++ b/benchmark/q_ae_price/jsons/bayes_configuration.json @@ -0,0 +1,48 @@ + +[ + { + "ae_type": ["BAYESQAE"], + "file": ["BAYESQAE"], + + + "schedule": [null], + "delta": [null], + "ns": [null], + + "auxiliar_qbits_number": [null], + "window" : [null], + "kaiser_alpha" : [null], + + "cbits_number": [null], + + "epsilon": [1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5], + + "alpha": [0.05], + + "gamma": [null], + "q": [null], + + "erqae_schedule" :[null], + + "mcz_qlm": [false], + "particles" : [2000], + "threshold" : [0.5], + "kernel" : ["LW", "Metro"], + "alpha_lw" : [0.9], + "c" : [2.38], + "k_0": [2], + "T" : [3], + "R" : [3], + "n_evals" : [50], + "warm_shots": [10], + "bayes_shots": [1], + "control_bayes_shots" : [1], + + "encoding" : [0, 2], + "multiplexor": [true], + + "mcz_qlm": [false], + "shots": [1], + "number_of_tests": [10] + } +] diff --git a/benchmark/sine_integral/jsons/qae_sine_bayes.json b/benchmark/sine_integral/jsons/qae_sine_bayes.json new file mode 100644 index 0000000..fbdd25d --- /dev/null +++ b/benchmark/sine_integral/jsons/qae_sine_bayes.json @@ -0,0 +1,40 @@ +[ + { + "ae_type": ["BAYESQAE"], + + "schedule": [null], + "delta": [null], + "ns": [null], + + "auxiliar_qbits_number": [null], + "window" : [null], + "kaiser_alpha" : [null], + + "cbits_number": [null], + + "epsilon": [0.01, 0.001], + + "alpha": [0.05], + + "gamma": [null], + "q": [null], + "erqae_schedule" : [null], + + "multiplexor": [true], + + "mcz_qlm": [false], + "particles" : [2000], + "threshold" : [0.5], + "kernel" : ["LW", "Metro"], + "alpha_lw" : [0.9], + "c" : [2.38], + "k_0": [2], + "T" : [3], + "R" : [3], + "n_evals" : [50], + "warm_shots": [10], + "bayes_shots": [1], + "control_bayes_shots" : [1], + "shots": [1] + } +] From f956158e4f1968b31d525598052e0063fa48faa8 Mon Sep 17 00:00:00 2001 From: gonfeco Date: Mon, 24 Feb 2025 16:31:16 +0100 Subject: [PATCH 3/6] Minor corrections --- .../jsons/ae_configuration_bayesqae.json | 3 + .../probability_estimation.py | 1 + .../q_ae_price/jsons/bayes_configuration.json | 3 + .../sine_integral/jsons/qae_sine_bayes.json | 5 +- benchmark/sine_integral/qae_sine_integral.py | 2 + ...2_BayesianQuantumAmplitudeEstimation.ipynb | 58 +++++-------------- 6 files changed, 29 insertions(+), 43 deletions(-) diff --git a/benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json b/benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json index d413d15..c5cfb17 100644 --- a/benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json +++ b/benchmark/compare_ae_probability/jsons/ae_configuration_bayesqae.json @@ -33,6 +33,9 @@ "warm_shots": [10], "bayes_shots": [1], "control_bayes_shots" : [1], + "save_smc_prob" : [200], + "print_info" : [200], + "max_iterations" : [200], "multiplexor": [true], "mcz_qlm": [false], diff --git a/benchmark/compare_ae_probability/probability_estimation.py b/benchmark/compare_ae_probability/probability_estimation.py index 2ffdba8..d043555 100644 --- a/benchmark/compare_ae_probability/probability_estimation.py +++ b/benchmark/compare_ae_probability/probability_estimation.py @@ -125,6 +125,7 @@ def get_amplitude_estimation(**kwargs): result = ae_obj.ae_pdf pdf = pd.concat([pdf, result], axis=1) pdf["oracle_calls"] = ae_obj.oracle_calls + pdf["max_oracle_depth"] = ae_obj.max_oracle_depth pdf["schedule_pdf"] = [ae_obj.schedule_pdf.to_dict()] pdf["measured_epsilon"] = (pdf["ae_u"] - pdf["ae_l"]) / 2.0 pdf["absolute_error"] = np.abs(pdf["ae"] - pdf["Value"]) diff --git a/benchmark/q_ae_price/jsons/bayes_configuration.json b/benchmark/q_ae_price/jsons/bayes_configuration.json index 1ca5d24..653afbb 100644 --- a/benchmark/q_ae_price/jsons/bayes_configuration.json +++ b/benchmark/q_ae_price/jsons/bayes_configuration.json @@ -37,6 +37,9 @@ "warm_shots": [10], "bayes_shots": [1], "control_bayes_shots" : [1], + "save_smc_prob" : [200], + "print_info" : [200], + "max_iterations" : [200], "encoding" : [0, 2], "multiplexor": [true], diff --git a/benchmark/sine_integral/jsons/qae_sine_bayes.json b/benchmark/sine_integral/jsons/qae_sine_bayes.json index fbdd25d..776d0f1 100644 --- a/benchmark/sine_integral/jsons/qae_sine_bayes.json +++ b/benchmark/sine_integral/jsons/qae_sine_bayes.json @@ -35,6 +35,9 @@ "warm_shots": [10], "bayes_shots": [1], "control_bayes_shots" : [1], - "shots": [1] + "shots": [1], + "save_smc_prob" : [200], + "print_info" : [200], + "max_iterations" : [200] } ] diff --git a/benchmark/sine_integral/qae_sine_integral.py b/benchmark/sine_integral/qae_sine_integral.py index 945049e..da1e514 100644 --- a/benchmark/sine_integral/qae_sine_integral.py +++ b/benchmark/sine_integral/qae_sine_integral.py @@ -99,6 +99,7 @@ def sine_integral(n_qbits, interval, ae_dictionary): absolute_error = np.abs(estimator_s["ae"] - exact_integral) relative_error = absolute_error / exact_integral oracle_calls = solver_object.oracle_calls + max_oracle_depth = solver_object.max_oracle_depth end_time = time.time() elapsed_time = end_time - start_time @@ -143,6 +144,7 @@ def sine_integral(n_qbits, interval, ae_dictionary): pdf["absolute_riemann_error"] = np.abs( pdf["riemann_sum"] - pdf["exact_integral"]) pdf["oracle_calls"] = oracle_calls + pdf["max_oracle_depth"] = max_oracle_depth pdf["elapsed_time"] = elapsed_time pdf["run_time"] = solver_object.run_time pdf["quantum_time"] = solver_object.quantum_time diff --git a/misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb b/misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb index acd8c14..907a8bf 100644 --- a/misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb +++ b/misc/notebooks/22_BayesianQuantumAmplitudeEstimation.ipynb @@ -19,7 +19,7 @@ "\n", "The present notebook and modules are based on the following reference:\n", "\n", - "Alexandra Ramôa and Luis Paulo Santos . Bayesian Quantum Amplitude Estimation. https://arxiv.org/abs/2412.04394 (2024).\n" + "- Alexandra Ramôa and Luis Paulo Santos . Bayesian Quantum Amplitude Estimation. https://arxiv.org/abs/2412.04394 (2024).\n" ] }, { @@ -197,7 +197,7 @@ "\n", "For the statistical inference, the **BAYESQAE** algorithm operates within the framework of **Sequential Monte Carlo (SMC)** methods. The goal is to use SMC to compute an optimal control for the next **Quantum Amplitude Estimation (QAE)** experiment (i.e., the choice of $m_k$ for the Grover circuit), given the current probability distribution and the results of previous QAE experiments. \n", "\n", - "The computation of the optimal control involves minimizing the expected value of a **utility function** over a dynamically evolving range of possible controls." + "The computation of the optimal control involves minimizing a function called the **Utility of an experiment** over a dynamically evolving range of possible controls (i.e., $m_k$ for the Grover circuits)." ] }, { @@ -246,7 +246,7 @@ "---\n", "\n", "#### Dynamic Evolution of Control Domains:\n", - "The following keywords can be used to configure the dynamic evolution of the domain controls for minimizing the **utility** function:\n", + "The following keywords can be used to configure the dynamic evolution of the domain controls for minimizing the **Utility of an experiment** function:\n", "- `n_evals`: The number of evaluations for optimizing the expected value of the utility function.\n", "- `k_0`: Together with `n_evals`, defines the upper limit for the initial domain interval for control optimization.\n", "- `R`: when the optimal control is among the top `R` highest possible controls then it can be necessary to enlarge the domain for the controls. An internal counter is increased by one when this happens.\n", @@ -255,8 +255,10 @@ "---\n", "\n", "#### Utility Function:\n", - "In the original **BAYESQAE** paper, the utility function used for minimization is the variance. By default, the function `variance_function` from the **QQuantLib/AE/bayesian_ae** module is used. To use a user-defined utility function, the following keyword can be provided:\n", - "- `utility_function`: A Python function used as the *utility function* for the **BAYESQAE** algorithm. The default is the variance function. A custom *utility function* must accept two numpy arrays as inputs:\n", + "\n", + "\n", + "In the original **BAYESQAE** paper, the **Utility of an experiment** function for minimization uses as base function the *variance* (in fact the expected value of the variance). By default, the function `variance_function` from the **QQuantLib/AE/bayesian_ae** module is used. To use a user-defined utility function, the following keyword can be provided:\n", + "- `utility_function`: A Python function used for computing the **Utility of an experiment** for the **BAYESQAE** algorithm. The default is the variance function. A custom *utility function* must accept two numpy arrays as inputs:\n", " - The first array contains the values of the SMC probability distribution.\n", " - The second array contains the corresponding weights.\n", " Additionally, the function may accept a `kwargs` argument.\n", @@ -318,34 +320,6 @@ "The target state, in this case, is $|1\\rangle$. In order to provide a binary representation the `bitfield` function from **QQuantLib.utils.utils** can be used. This has to be passed to the `target` variable as a list. Moreover, we have to provide the list of qubits where we are acting (to the `index` variable), in this case is just the whole register." ] }, - { - "cell_type": "markdown", - "id": "00c2bcc3", - "metadata": {}, - "source": [ - "### 2.4 Toy Problem\n", - "\n", - "To demonstrate how our class and the **BAYESQAE** algorithm work, we will define the following amplitude estimation problem using the generated oracle operator $\\mathcal{A}$ (see Section 2.1):\n", - "\n", - "$$\n", - "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\frac{1}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", - "$$\n", - "\n", - "By comparing Equation (2) with Equation (1), we can identify the components as follows:\n", - "\n", - "$$\n", - "\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\frac{\\sqrt{1}}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} |1\\rangle,\n", - "$$\n", - "\n", - "and\n", - "\n", - "$$\n", - "\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\frac{1}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", - "$$\n", - "\n", - "In this example, the target state is $|1\\rangle$. To provide its binary representation, the `bitfield` function from the **QQuantLib.utils.utils** module can be used. This binary representation must be passed to the `target` variable as a list. Additionally, we need to specify the list of qubits on which the operation is being performed (via the `index` variable). In this case, the operation acts on the entire register." - ] - }, { "cell_type": "code", "execution_count": null, @@ -881,20 +855,20 @@ "\n", "From the workflow presented in the previous section, the only step that remains to be explained is the second one: **Compute Optimal Control**.\n", "\n", - "Before diving into this step, we need an important ingredient: a function that computes the **Utility** of an experiment for a given control $m_k$ and an input SMC probability distribution.\n", + "Before diving into this step, we need an important ingredient: a function that computes the **Utility of an experiment** for a given control $m_k$ and an input SMC probability distribution.\n", "\n", "---\n", "\n", "##### Utility of an Experiment\n", "\n", - "The **Utility** of an experiment is computed using the following formula:\n", + "The **Utility of an experiment** is computed using the following formula:\n", "\n", "$$\n", "U^{f}(m_k) = \\sum_{D} E_{P(\\theta)}[P(D; m_k)] \\cdot E_{P(\\theta|D, m_k)}[f(\\theta, D; m_k)]\n", "$$\n", "\n", "Where:\n", - "- $f$: An input utility function defined by the user. In the original paper, this is typically the variance.\n", + "- $f$: An input utility function defined by the user (keyword argument: `utility_function`). In the original paper, this is typically the variance\n", "- $E_{P(\\theta)}[P(D; m_k)]$: The probability of obtaining the outcome $D$ for a **QAE** experiment with control $m_k$. This is computed using Bayes' rule:\n", " - $E_{P(\\theta)}[P(D; m_k)] = \\int L(\\theta|D; m_k) P(\\theta) d\\theta$\n", " - $L(\\theta|D; m_k)$: The likelihood of obtaining the outcome $D$ for a **QAE** experiment with control $m_k$.\n", @@ -915,7 +889,7 @@ "\n", "---\n", "\n", - "To compute the **utility** of the experiment ($U^{f}(m_k)$), the `average_expectation` function from the **QQuantLib.AE.bayesian_ae** module is used. This function accepts the following inputs:\n", + "To compute the **utility of the experiment** ($U^{f}(m_k)$), the `average_expectation` function from the **QQuantLib.AE.bayesian_ae** module is used. This function accepts the following inputs:\n", "- `thetas`: The values of the SMC prior distribution ($\\{\\theta^{prior}_i\\}$).\n", "- `weights`: The weights of the SMC prior distribution ($\\{W^{prior}_i\\}$).\n", "- `m_k`: A possible input control $m_k$.\n", @@ -977,15 +951,15 @@ "source": [ "##### Control Optimization Using the **Utility**\n", "\n", - "The **Utility** of an experiment is used to compute the **Optimal Control** for the next iteration. This is achieved by minimizing the **Utility** over a predefined control domain. The `optimize_control` method of the `BAYESQAE` class performs this minimization and outputs the optimal control $m^*_k$.\n", + "The **Utility of an experiment** is used to compute the **Optimal Control** for the next iteration. This is achieved by minimizing the **Utility of an experiment** over a predefined control domain. The `optimize_control` method of the `BAYESQAE` class performs this minimization and outputs the optimal control $m^*_k$.\n", "\n", "Initially, the predefined control domain is initialized to the interval $\\left[m_{min}, m_{max}\\right]$, where:\n", "- $m_{min} = 0$\n", "- $m_{max} = k_0 \\cdot n_{evals}$\n", "\n", - "Here, $k_0$ and $n_{evals}$ are hyperparameters of the algorithm. The **Utility** is computed for $n_{evals}$ possible controls within this interval, and the $m^*_k$ that minimizes the **Utility** is selected.\n", + "Here, $k_0$ and $n_{evals}$ are hyperparameters of the algorithm. The **Utility of an experiment** is computed for $n_{evals}$ possible controls within this interval, and the $m^*_k$ that minimizes it is selected.\n", "\n", - "However, as the algorithm progresses through multiple iterations, the initial domain interval may no longer provide a suitable minimum for the **Utility**. In such cases, it becomes necessary to enlarge the control domain interval. This enlargement is controlled by two hyperparameters: $R$ and $T$.\n", + "However, as the algorithm progresses through multiple iterations, the initial domain interval may no longer provide a suitable minimum for the **Utility of an experiment**. In such cases, it becomes necessary to enlarge the control domain interval. This enlargement is controlled by two hyperparameters: $R$ and $T$.\n", "\n", "- Each time the selected $m^*_k$ falls within the top $R$ tested controls, an internal counter is incremented by one.\n", "- When the counter reaches the threshold $T$, the control domain interval is enlarged by setting:\n", @@ -1004,7 +978,7 @@ " - `k_0`: Scaling factor ($k_0$) for initializing the control domain.\n", " - `R`: Threshold for determining when the selected $m^*_k$ is among the top tested controls.\n", " - `T`: Counter threshold for triggering the enlargement of the control domain interval.\n", - " - `utility_function`: Function for computing the **Utility** of an experiment." + " - `utility_function`: Base function for computing the **Utility of an experiment**." ] }, { @@ -1139,7 +1113,7 @@ "- `bayesqae`\n", "- `run`\n", "\n", - "The follwoing sub sections explain them." + "The following sub sections explain them." ] }, { From 7ca2d8189065baf5da4d6e9e1883c0ce6fb98e12 Mon Sep 17 00:00:00 2001 From: gonfeco Date: Mon, 24 Feb 2025 16:34:10 +0100 Subject: [PATCH 4/6] Added JSONS for cliquet --- .../q_ae_cliquet/bayesqae_configuration.json | 48 +++++++++++++++++++ .../q_ae_cliquet/iqae_configuration.json | 32 +++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 benchmark/q_ae_cliquet/bayesqae_configuration.json create mode 100644 benchmark/q_ae_cliquet/iqae_configuration.json diff --git a/benchmark/q_ae_cliquet/bayesqae_configuration.json b/benchmark/q_ae_cliquet/bayesqae_configuration.json new file mode 100644 index 0000000..d1ee3dc --- /dev/null +++ b/benchmark/q_ae_cliquet/bayesqae_configuration.json @@ -0,0 +1,48 @@ +[ + { + "ae_type": ["mIQAE"], + "file": ["mIQAE"], + + "schedule": [null], + "delta": [null], + "ns": [null], + + "auxiliar_qbits_number": [null], + "window" : [null], + "kaiser_alpha" : [null], + + "cbits_number": [null], + + "epsilon": [1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5], + + "alpha": [0.05], + + "gamma": [null], + "q": [null], + + "erqae_schedule" :[null], + + "particles" : [2000], + "threshold" : [0.5], + "kernel" : ["LW", "Metro"], + "alpha_lw" : [0.9], + "c" : [2.38], + "k_0": [2], + "T" : [3], + "R" : [3], + "n_evals" : [50], + "shots": [1], + "warm_shots": [10], + "bayes_shots": [1], + "control_bayes_shots" : [1], + "save_smc_prob" : [200], + "print_info" : [200], + "max_iterations" : [200], + + "encoding" : [0, 2], + "multiplexor": [true], + + "mcz_qlm": [false], + "shots": [100] + } +] diff --git a/benchmark/q_ae_cliquet/iqae_configuration.json b/benchmark/q_ae_cliquet/iqae_configuration.json new file mode 100644 index 0000000..24e34b8 --- /dev/null +++ b/benchmark/q_ae_cliquet/iqae_configuration.json @@ -0,0 +1,32 @@ +[ + { + "ae_type": ["IQAE"], + "file": ["IQAE"], + + "schedule": [null], + "delta": [null], + "ns": [null], + + "auxiliar_qbits_number": [null], + "window" : [null], + "kaiser_alpha" : [null], + + "cbits_number": [null], + + "epsilon": [1.0e-2], + + "alpha": [0.05], + + "gamma": [null], + "q": [null], + + "erqae_schedule" :[null], + + "encoding" : [2], + "multiplexor": [true], + + "mcz_qlm": [false], + "shots": [100], + "number_of_tests": [10] + } +] From 4db261ddc2c760d4eb07f30e48cf0f1f7d231197 Mon Sep 17 00:00:00 2001 From: gonfeco Date: Wed, 26 Feb 2025 12:59:13 +0100 Subject: [PATCH 5/6] Corrections and improvement of tutorial notebooks --- ...CompareAEalgorithmsOnPureProbability.ipynb | 20 + .../q_ae_cliquet/QAE_CliquetOptions.ipynb | 22 + .../00_AboutTheNotebooksAndQPUs.ipynb | 95 ++- .../01_Data_Loading_Module_Use.ipynb | 344 +++++---- ...02_Amplitude_Amplification_Operators.ipynb | 406 ++++++++--- ...ikelihood_Amplitude_Estimation_Class.ipynb | 409 +++++++---- ...2_Classical_Phase_Estimation_Windows.ipynb | 324 ++++----- .../04_Classical_Phase_Estimation_Class.ipynb | 587 +++++++-------- ...ative_Quantum_Phase_Estimation_Class.ipynb | 687 +++++++++--------- ...e_Quantum_Amplitude_Estimation_class.ipynb | 434 ++++++----- ...on_Real_Quantum_Amplitude_Estimation.ipynb | 120 +-- ...l_Quantum_Amplitude_Estimation_class.ipynb | 369 +++++----- .../08_AmplitudeEstimation_Class.ipynb | 191 ++--- misc/notebooks/09_DataEncodingClass.ipynb | 129 ++-- ...ationTo_Finance_01_IntegralComputing.ipynb | 228 +++--- ...cationTo_Finance_02_ClassicalFinance.ipynb | 215 +++--- ...ationTo_Finance_03_AEPriceEstimation.ipynb | 259 ++++--- misc/notebooks/images/Grover.svg | 115 +++ misc/notebooks/images/StateReflection.svg | 91 +++ 19 files changed, 2825 insertions(+), 2220 deletions(-) create mode 100644 misc/notebooks/images/Grover.svg create mode 100644 misc/notebooks/images/StateReflection.svg diff --git a/benchmark/compare_ae_probability/CompareAEalgorithmsOnPureProbability.ipynb b/benchmark/compare_ae_probability/CompareAEalgorithmsOnPureProbability.ipynb index 0f07a74..88f920e 100644 --- a/benchmark/compare_ae_probability/CompareAEalgorithmsOnPureProbability.ipynb +++ b/benchmark/compare_ae_probability/CompareAEalgorithmsOnPureProbability.ipynb @@ -404,6 +404,26 @@ "pdf[results_info]" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "b99187e5", + "metadata": {}, + "outputs": [], + "source": [ + "len(pdf.columns)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a53de931", + "metadata": {}, + "outputs": [], + "source": [ + "pdf[[\"oracle_calls\", \"max_oracle_depth\"]]" + ] + }, { "cell_type": "markdown", "id": "67384c33", diff --git a/benchmark/q_ae_cliquet/QAE_CliquetOptions.ipynb b/benchmark/q_ae_cliquet/QAE_CliquetOptions.ipynb index e665209..6b4e822 100644 --- a/benchmark/q_ae_cliquet/QAE_CliquetOptions.ipynb +++ b/benchmark/q_ae_cliquet/QAE_CliquetOptions.ipynb @@ -958,6 +958,28 @@ "]]" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb43a2d7", + "metadata": {}, + "outputs": [], + "source": [ + "ae_dict.update({\n", + " \"ae_type\" : \"BAYESQAE\",\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19b93006", + "metadata": {}, + "outputs": [], + "source": [ + "pdf_parts = ae_cliquet_estimation_step_po(**ae_dict)" + ] + }, { "cell_type": "markdown", "id": "2e4a2494", diff --git a/misc/notebooks/00_AboutTheNotebooksAndQPUs.ipynb b/misc/notebooks/00_AboutTheNotebooksAndQPUs.ipynb index 28dbbfe..36db00d 100644 --- a/misc/notebooks/00_AboutTheNotebooksAndQPUs.ipynb +++ b/misc/notebooks/00_AboutTheNotebooksAndQPUs.ipynb @@ -5,9 +5,10 @@ "id": "ad639bf4", "metadata": {}, "source": [ - "# 1. About the notebooks\n", "\n", - "The notebooks located in the folder: **misc/notebooks/** can be used as tutorials for understanding the working and the capabilities of the **FinancialApplications** software package and the **QQuantLib** Python library developed inside it. \n" + "# 1. About the Notebooks\n", + "\n", + "The notebooks located in the folder **misc/notebooks/** serve as tutorials to help users understand the functionality and capabilities of the **FinancialApplications** software package and the **QQuantLib** Python library developed within it. These notebooks provide hands-on examples and explanations, making it easier to explore and utilize the tools effectively." ] }, { @@ -15,9 +16,9 @@ "id": "5b633e42", "metadata": {}, "source": [ - "# 2. About the QQuantLib library\n", + "# 2. About the QQuantLib Library\n", "\n", - "The main idea of the **FinancialApplications** software and the **QQuantLib** Python library is to develop several state-of-the-art quantum algorithms and routines for price estimation of financial derivatives using **myQLM** quantum software stack and/or for the Qaptiva™ Appliance developed by **Eviden**. \n" + "The primary objective of the **FinancialApplications** software and the **QQuantLib** Python library is to implement advanced quantum algorithms and routines for estimating the prices of financial derivatives. These tools leverage the **myQLM** quantum software stack and/or the Qaptiva™ Appliance developed by **Eviden**, enabling cutting-edge quantum computing capabilities in the field of finance." ] }, { @@ -25,18 +26,19 @@ "id": "8775aba8", "metadata": {}, "source": [ - "# 3. About the myQLM and Qaptiva Access environment\n", - "\n", - "Meanwhile the **myQLM** (https://myqlm.github.io/index.html#) is an open-source library that can be installed in a local computer the Qaptiva™ Appliance was designed for executing the quantum programs in a **ATOS** **Quantum Learning Machine** (https://atos.net/en/solutions/quantum-learning-machine). \n", + "# 3. About the myQLM and Qaptiva Access Environment\n", "\n", + "While **myQLM** (https://myqlm.github.io/index.html#) is an open-source library that can be installed on a local machine, the **Qaptiva™ Appliance** is designed to execute quantum programs on the **ATOS Quantum Learning Machine** (https://atos.net/en/solutions/quantum-learning-machine). \n", "\n", - "Additionally, **Eviden** has developed a library called **Qaptiva Access** (a.k.a. QLMaaS or QLM as a Service) that allows to the user submit **myQLM** computations to remote **Quantum Learning Machine**. \n", + "Additionally, **Eviden** has developed a library called **Qaptiva Access** (also known as QLMaaS or QLM as a Service), which allows users to submit **myQLM** computations to a remote **Quantum Learning Machine**.\n", "\n", - "The **QQuantLib** library was developed completely in **myQLM** but the code can be used straight locally in a **Quantum Learning Machine** or by submitting to it using the **Qaptiva Access**.\n", + "The **QQuantLib** library was fully developed using **myQLM**, and its code can be executed either locally on a **Quantum Learning Machine** or submitted remotely via **Qaptiva Access**.\n", "\n", - "For understanding how to use these different ways in the **QQuantLib** we have to build a **Quantum Procces Unit** (or **QPU**). In the **myQLM** and **Qaptiva Access** framework a **QPU** is a Python object that executes a Job and returns a Result (see https://myqlm.github.io/02_user_guide/02_execute/03_qpu.html#qpu).\n", + "To understand how to use these different execution methods within **QQuantLib**, it is essential to build a **Quantum Processing Unit** (**QPU**). In the **myQLM** and **Qaptiva Access** framework, a **QPU** is a Python object that executes a Job and returns a Result (see https://myqlm.github.io/02_user_guide/02_execute/03_qpu.html#qpu).\n", "\n", - "A **QPU** can be a Quantum Emulator (a classical software emulating the behaviour of a physical QPU) or a Physical system. \n" + "A **QPU** can represent either:\n", + "- A **Quantum Emulator**: A classical software tool that simulates the behavior of a physical quantum processor.\n", + "- A **Physical System**: An actual quantum hardware device capable of executing quantum computations." ] }, { @@ -46,7 +48,7 @@ "source": [ "# 4. QQuantLib and QPUs\n", "\n", - "From the point of view of the **QQuantLib** library the **QPU**s are used for simulating (or executing in a quantum device in the future) the quantum circuits generated by the different functions of the library. Depending on the library to use (**myQLM** or Qaptiva™ Appliance) different **QPU**s can be used with the **QQuantLib** library.\n" + "From the perspective of the **QQuantLib** library, **QPUs** are utilized to simulate (or execute on quantum hardware in the future) the quantum circuits generated by the library's functions. Depending on the underlying framework being used (**myQLM** or the **Qaptiva™ Appliance**), various types of **QPUs** can be employed with **QQuantLib**.\n" ] }, { @@ -56,10 +58,11 @@ "source": [ "## 4.1 myQLM\n", "\n", - "**QQuantLib** can only use the two following **myQLM QPU**s, that are linear algebra simulators:\n", + "The **QQuantLib** library can utilize two **myQLM QPU** options, both of which are linear algebra simulators:\n", + "\n", + "- `PyLinalg`: This simulator is entirely written in Python and leverages the **NumPy** library for its computations.\n", "\n", - "* PyLinalg : It is entirely written in Python, and is based in particular on the Numpy library.\n", - "* CLinalg : it si a Linear-algebra simulator written in C++, with a python (pybind11) interface." + "- `CLinalg`: This is a high-performance linear algebra simulator implemented in C++ with a Python interface (via **pybind11**). It offers faster execution times compared to PyLinalg.\n" ] }, { @@ -69,13 +72,23 @@ "source": [ "## 4.2 Qaptiva™ Appliance\n", "\n", - "For Qaptiva™ Appliance the **QPU** zoo is more complicated because several categories arise:\n", + "For the **Qaptiva™ Appliance**, the range of available **QPUs** is more diverse, as they are categorized into distinct types based on their functionality:\n", + "\n", + "### 1. **Ideal Computation**\n", + "In this category, the quantum circuit is simulated under ideal conditions without introducing noise. Two main approaches are available:\n", + "\n", + "- **Exact Representation**:\n", + " - **LinAlg QPU**: A linear algebra simulator specifically designed for the **Quantum Learning Machine**. This simulator computes the state vector exactly, without any approximations.\n", "\n", - "* **Ideal Computation**: in this case, the circuit is simulated ideally. Two different approaches can be used:\n", - " * *Exact Representation*: **LinAlg QPU**: linear algebra simulator that was programing specifically for **Quantum Learning Machine**. The state vector is computed without any approximation.\n", - " * *Approximate Representation*: **MPS QPU** using matrix product state for simulating in an approximated way the state vector.\n", - "* **Noisy Computation**: in this case, the **QPU** can be configured with a noise model for doing a noisy simulation of the circuits. The **NoisyQProc QPU** is used for this task.\n", - "\n" + "- **Approximate Representation**:\n", + " - **MPS QPU**: Utilizes matrix product states (MPS) to simulate the state vector in an approximate manner. This approach is particularly useful for handling larger systems where exact representation becomes computationally expensive.\n", + "\n", + "### 2. **Noisy Computation**\n", + "In this category, the **QPU** incorporates a noise model to simulate realistic behavior, accounting for imperfections in quantum hardware. The following **QPU** is used for this purpose:\n", + "\n", + "- **NoisyQProc QPU**: Configured with a noise model, this simulator provides a more accurate representation of how circuits would behave on actual quantum devices.\n", + "\n", + "These categories allow users to choose the most appropriate simulation method based on their specific requirements, whether it be precision, scalability, or realism in the presence of noise." ] }, { @@ -83,13 +96,12 @@ "id": "88e10ff3", "metadata": {}, "source": [ - "## 4.3 Accesing ways Qaptiva™ Appliance **QPU**s\n", + "## 4.3 Accessing Qaptiva™ Appliance QPU Modes\n", "\n", + "The **QPU**s provided by the **Qaptiva™ Appliance** can be accessed in two distinct ways:\n", "\n", - "Additionally, the Qaptiva™ Appliance **QPU**s can be used in two different ways:\n", - "\n", - "1. Locally connected to the **Quantum Learning Machine**.\n", - "2. Connect to the remote QPUs of a **Quantum Learning Machine** using the **Qaptiva Access** library.\n" + "1. **Local Connection**: Users should be connected to an available **Quantum Learning Machine**.\n", + "2. **Remote Access via Qaptiva Access**: Users can also connect to remote **Quantum Learning Machine** using the **Qaptiva Access** library (also known as QLMaaS or QLM as a Service).\n" ] }, { @@ -97,16 +109,16 @@ "id": "7eb69e24", "metadata": {}, "source": [ - "## 5. How QQuantLib deals with QPUs?\n", + "## 5. How QQuantLib Deals with QPUs?\n", "\n", - "As explained before there are several **QPU**s and different ways to access them in the different **Eviden** environments. The **QQuantLib** allows the user to select them in an easy way using the *get_qpu* function from **QQuantLib.qpu.get_qpu** module. This function allows to the user select different ideal **QPU**s by providing a specific string:\n", + "As explained earlier, there are several **QPUs** and multiple ways to access them across different **Eviden** environments. The **QQuantLib** library simplifies the selection process by providing the `get_qpu` function from the **QQuantLib.qpu.get_qpu** module. This function allows users to easily choose a specific **QPU** by passing a corresponding string identifier. Below is a list of available options:\n", "\n", - "* *qlmass_linalg*: this is for getting the **LinAlg QPU** but using the **Qaptiva Access** library for a remote connection to a **Quantum Learning Machine**.\n", - "* *qlmass_mps*: this is for getting the **MPS QPU** but using the **Qaptiva Access** library for a remote connection to a **Quantum Learning Machine**.\n", - "* *linalg*: this is for getting the **LinAlg QPU** when locally connected to a **Quantum Learning Machine** (uses driectly the Qaptiva™ Appliance software)\n", - "* *mps*: this is for getting the **MPS QPU** when locally connected to a **Quantum Learning Machine** (uses driectly the Qaptiva™ Appliance software)\n", - "* *c*: this is for getting the **CLinalg QPU** (uses directly the **myQLM** software).\n", - "* *python*: this is for getting the **PyLinalg QPU** (uses directly the **myQLM** software)." + "- `qlmass_linalg`: Retrieves the **LinAlg QPU** via the **Qaptiva Access** library for remote execution on a **Quantum Learning Machine**.\n", + "- `qlmass_mps`: Retrieves the **MPS QPU** via the **Qaptiva Access** library for remote execution on a **Quantum Learning Machine**.\n", + "- `linalg`: Retrieves the **LinAlg QPU** for local execution on a **Quantum Learning Machine**, using the **Qaptiva™ Appliance** software directly.\n", + "- `mps`: Retrieves the **MPS QPU** for local execution on a **Quantum Learning Machine**, using the **Qaptiva™ Appliance** software directly.\n", + "- `c`: Retrieves the **CLinalg QPU**, utilizing the **myQLM** software directly.\n", + "- `python`: Retrieves the **PyLinalg QPU**, utilizing the **myQLM** software directly.\n" ] }, { @@ -210,19 +222,22 @@ "id": "d3f7ac3c", "metadata": {}, "source": [ - "## 6. What about noisy QPUs?\n", + "## 6. What About Noisy QPUs?\n", + "\n", + "The **Eviden** Qaptiva™ Appliance enables users to create and configure various noise models, which can be easily integrated into a **QPU**. However, configuring noise models is a complex task, as numerous implementations and configurations are possible.\n", "\n", - "**Eviden** Qaptiva™ Appliance allows to the user create and configure different noisy models and add to the **QPU** in an easy way. The main problem is that configuring noise models is a very complicated subject and a lot of different implementations can be used. \n", + "To simplify this process, **QQuantLib** provides a predefined noisy model that can be configured to some extent, leveraging the capabilities of the **Eviden** Qaptiva™ Appliance library. This model offers a practical starting point for incorporating noise into quantum simulations.\n", "\n", - "In the **QQuantLib** a fixed noisy model, that can be configured to some extent, was created using the **Eviden** Qaptiva™ Appliance library. For more information about the model and how to use we refer to the **QQuantLib/qpu/NoisyModels.ipynb** notebooks" + "For more detailed information about the noise model, its configuration, and usage, please refer to the notebook: \n", + "[**QQuantLib/qpu/NoisyModels.ipynb**](./QQuantLib/qpu/NoisyModels.ipynb).\n" ] } ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -234,7 +249,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/01_Data_Loading_Module_Use.ipynb b/misc/notebooks/01_Data_Loading_Module_Use.ipynb index 915ca22..00c21f8 100644 --- a/misc/notebooks/01_Data_Loading_Module_Use.ipynb +++ b/misc/notebooks/01_Data_Loading_Module_Use.ipynb @@ -13,17 +13,18 @@ "id": "663cbbc4", "metadata": {}, "source": [ - "The present notebook reviews the *dataloading* module from the package *DL* of the library *QQuantLib* (**QQuantLib/DL/data_loading.py**). \n", + "The present notebook reviews the **dataloading** module from the *DL* package of the **QQuantLib** library (**QQuantLib/DL/data_loading.py**). This module focuses on loading data into quantum states or circuits, enabling the preparation of quantum states that correspond to specific probability distributions.\n", "\n", - "This module deals with loading the data into the quantum states (or circuits). \n", + "The content of this notebook and the associated module are based on the following references:\n", "\n", - "The present notebook and module are based on the following references:\n", + "- **Grover, Lov, and Rudolph, Terry**. *Creating superpositions that correspond to efficiently integrable probability distributions*. arXiv (2002). \n", + " [https://arxiv.org/abs/quant-ph/0208112](https://arxiv.org/abs/quant-ph/0208112)\n", "\n", - "* *Grover, Lov and Rudolph, Terry*. Creating superposition that correspond to efficiently integrable probability distributions. arXiv (2002). https://arxiv.org/abs/quant-ph/0208112\n", + "- **V.V. Shende, S.S. Bullock, and I.L. Markov**. *Synthesis of quantum-logic circuits*. IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems, 25(6):1000–1010, Jun 2006. \n", + " [https://arxiv.org/abs/quant-ph/0406176v5](https://arxiv.org/abs/quant-ph/0406176v5)\n", "\n", - "* *V.V. Shende, S.S. Bullock, and I.L. Markov*. Synthesis of quantum-logic circuits. IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems, 25(6):1000–1010, Jun 2006. https://arxiv.org/abs/quant-ph/0406176v5\n", - "\n", - "* NEASQC deliverable: *D5.1: Review of state-of-the-art for Pricing and Computation of VaR https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf*" + "- **NEASQC Deliverable**: *D5.1: Review of state-of-the-art for Pricing and Computation of VaR*. \n", + " [https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf](https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf)" ] }, { @@ -69,34 +70,39 @@ }, { "cell_type": "markdown", - "id": "a77c4bbe", + "id": "0877b2d7", "metadata": {}, "source": [ - "The following cell loads the *get_results* function from **QQuantLib/utils/data_extracting**. The present library uses this function frequently so here we explain the use, the input and the outputs.\n", + "The following cell loads the `get_results` function from **QQuantLib/utils/data_extracting**. This function is frequently used in the current library, so we will explain its purpose, inputs, and outputs.\n", + "\n", + "The `get_results` function accepts a QLM object (a QLM Gate, QRoutine, or Program) and creates the associated QLM program, circuit, and job. It then simulates the quantum program, retrieves the results, and post-processes them.\n", "\n", - "*get_results* function receives a QLM object (a QLM gate, QRoutine or Program), and creates the associated QLM program, circuit and job. Additionally runs the QLM job (simulates the quantum program), gets the results and post-processes them.\n", + "---\n", "\n", - "Inputs for *get_results*:\n", + "### Inputs for `get_results`:\n", + "- `quantum_object`: A QLM Gate, Routine, or Program.\n", + "- `linalg_qpu`: A QLM solver (e.g., a Quantum Processing Unit).\n", + "- `shots`: An integer specifying the number of shots for the generated job.\n", + "- `qubits`: A list of qubits to measure during simulation.\n", + "- `complete`: A boolean flag. If `True`, returns the complete basis states, which is useful when `shots` is not zero and all possible basis states are required.\n", "\n", - "* quantum_object : QLM Gate, Routine or Program\n", - "* linalg_qpu : QLM solver\n", - "* shots : int (number of shots for the generated job)\n", - "* qubits : list (list with the qubits for doing the measurement when simulating)\n", - "* complete : bool. For returning the complete basis state. Useful when shots are not 0 and all the possible basis states are required\n", + "---\n", "\n", - "The outputs of *get_results* will be:\n", - "* pdf : pandas DataFrame. DataFrame with the results of the simulation\n", - "* circuit : QLM circuit of the QLM input object\n", - "* q_prog : QLM Program of the QLM input object\n", - "* job : QLM job of the QLM input object\n", + "### Outputs of `get_results`:\n", + "- `pdf`: A Pandas DataFrame containing the results of the simulation.\n", + "- `circuit`: The QLM circuit corresponding to the input QLM object.\n", + "- `q_prog`: The QLM Program corresponding to the input QLM object.\n", + "- `job`: The QLM Job corresponding to the input QLM object.\n", "\n", - "The main output of this function is the pandas DataFrame. The columns provided in the DataFrame are:\n", + "The primary output of this function is the Pandas DataFrame (`pdf`). The columns provided in the DataFrame are as follows:\n", "\n", - "* **States**: Possible quantum states of the measurements done on the circuit\n", - "* **Int_lsb**: conversion from the quantum state to an integer following **lsb** (bit farthest to the right will be least significant)\n", - "* **Probability**: Computed frequency of the quantum state when *shots* is not zero. When *shots* is zero computed probabilities are providing\n", - "* **Amplitude**: Amplitude of the quantum states. Only providing if *shots* is zero.\n", - "* **Int**: conversion from the quantum state to an integer (the bit farthest to the right will be most significant)\n" + "- `States`: Possible quantum states resulting from measurements on the circuit.\n", + "- `Int_lsb`: Conversion of the quantum state to an integer using **lsb** encoding (the bit farthest to the right is the least significant).\n", + "- `Probability`: Computed frequency of the quantum state when `shots` is not zero. When `shots` is zero, theoretical probabilities are provided.\n", + "- `Amplitude`: Amplitude of the quantum states (only provided if `shots` is zero).\n", + "- `Int`: Conversion of the quantum state to an integer using standard encoding (the bit farthest to the right is the most significant).\n", + "\n", + "This function simplifies the process of simulating quantum circuits and extracting meaningful results, making it a valuable tool for analyzing quantum computations." ] }, { @@ -122,7 +128,9 @@ "id": "eac27931", "metadata": {}, "source": [ - "Typically, when we want to load some data into the quantum circuit, we will want to load a discrete probability distribution $p_d$ and an array $f$. First thing we need to define the dimension of the circuit and what we want to load. Here $n$ is the number of qubits and $N = 2^n$ is the size of the discretized probability distribution and the size of the array. In this specific example $n = 3$ and $N = 8$." + "Typically, when loading data into a quantum circuit, the goal is to encode a discrete probability distribution $ p_d $ and an array $ f $. The first step is to define the dimension of the circuit and specify the data to be loaded. Here, $ n $ represents the number of qubits, and $ N = 2^n $ denotes the size of the discretized probability distribution as well as the size of the array.\n", + "\n", + "In this specific example, we set $ n = 3 $, which results in $ N = 8 $. This means the circuit will consist of 3 qubits, and both the probability distribution and the array will have 8 elements." ] }, { @@ -143,13 +151,15 @@ "metadata": {}, "source": [ "Next, we define a discrete probability distribution:\n", + "$$\n", + "p_d = \\left(p_0, p_1, p_2, p_3, p_4, p_5, p_6, p_7\\right).\n", + "$$\n", "\n", - "$$p_d = \\left(p_0,p_1,p_2,p_3,p_4,p_5,p_6,p_7\\right).$$\n", - "\n", - "In this specific example we are going to generate the following probability distribution:\n", - "\n", - "$$p_d = \\dfrac{1}{0+1+2+3+4+5+6+7}\\left(0,1,2,3,4,5,6,7\\right),$$\n", - "which is saved in the variable *probability*." + "In this specific example, we generate the following probability distribution:\n", + "$$\n", + "p_d = \\frac{1}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7} \\left(0, 1, 2, 3, 4, 5, 6, 7\\right),\n", + "$$\n", + "which is stored in the variable `probability`." ] }, { @@ -168,15 +178,26 @@ "metadata": {}, "source": [ "Finally, we define an array:\n", - "$$f = \\left(f_0,f_1,f_2,f_3,f_4,f_5,f_6,f_7\\right).$$\n", - "Later, it will become useful to have a normalised version of this function that we will call $\\hat{f}$. This new function $\\hat{f}$ has the main characteristic that where the maximum absolute value of the function is one $||\\hat{f}||_{\\infty} = 1$:\n", - "$$\\hat{f} = \\dfrac{f}{||f||_{\\infty}} = \\left(\\hat{f}_0,\\hat{f}_1,\\hat{f}_2,\\hat{f}_3,\\hat{f}_4,\\hat{f}_5,\\hat{f}_6,\\hat{f}_7\\right).$$\n", - "In the code, this is the reason why we introduce the variable *normalization_constant* $=||f||_{\\infty}$.\n", + "$$\n", + "f = \\left(f_0, f_1, f_2, f_3, f_4, f_5, f_6, f_7\\right).\n", + "$$\n", + "\n", + "Later, it will be useful to work with a normalized version of this function, denoted as $\\hat{f}$. The key characteristic of $\\hat{f}$ is that its maximum absolute value is scaled to 1, i.e., $||\\hat{f}||_{\\infty} = 1$:\n", + "$$\n", + "\\hat{f} = \\frac{f}{||f||_{\\infty}} = \\left(\\hat{f}_0, \\hat{f}_1, \\hat{f}_2, \\hat{f}_3, \\hat{f}_4, \\hat{f}_5, \\hat{f}_6, \\hat{f}_7\\right).\n", + "$$\n", "\n", - "In this specific example, we choose $f$ to simply be:\n", - "$$f = \\left(0,1,2,3,4,5,6,7\\right).$$\n", - "Hence, $\\hat{f}$ is:\n", - "$$\\hat{f} = \\dfrac{f}{||f||_{\\infty}} = \\dfrac{1}{7}\\left(0,1,2,3,4,5,6,7\\right).$$\n" + "In the code, this normalization is implemented using the variable `normalization_constant`, which corresponds to $||f||_{\\infty}$.\n", + "\n", + "In this specific example, we choose $f$ to be:\n", + "$$\n", + "f = \\left(0, 1, 2, 3, 4, 5, 6, 7\\right).\n", + "$$\n", + "\n", + "Thus, the normalized function $\\hat{f}$ becomes:\n", + "$$\n", + "\\hat{f} = \\frac{f}{||f||_{\\infty}} = \\frac{1}{7} \\left(0, 1, 2, 3, 4, 5, 6, 7\\right).\n", + "$$" ] }, { @@ -225,18 +246,18 @@ }, { "cell_type": "markdown", - "id": "8ca4a398", + "id": "a4389c9d", "metadata": {}, "source": [ - "To load a discrete probability distribution we just need the function *load_probability*, which is inside the **DL/data_loading** module. The inputs are:\n", + "To load a discrete probability distribution into a quantum state, the function `load_probability` from the **DL/data_loading** module can be used. The inputs to this function are as follows:\n", "\n", - "* numpy array with the probability distribution that we want to load into the quantum state (**MANDATORY**). In this case, the probability distribution is the variable *probability*.\n", - "* id_name: string for giving a name to the Abstract Gate created by the function. If no name is provided then the CPU time will be added to the name.\n", - "* method: string for selecting the internal implementation for the controlled rotation by state (which is the basis of our data loading method). Two possible values:\n", - " * multiplexor: using quantum multiplexors for the data loading. This is the default value.\n", - " * brute_force: using a direct implementation of the controlled rotation by state (longer circuits)\n", + "- **Probability Distribution (mandatory)**: A NumPy array containing the probability distribution to be loaded into the quantum state. In this case, the variable `probability` holds the desired probability distribution.\n", + "- `id_name`: A string that provides a name for the Abstract Gate created by the function. If no name is specified, the current CPU time will be appended to the gate's name.\n", + "- `method`: A string that specifies the internal implementation for the controlled rotation by state, which forms the basis of the data loading method. Two options are available:\n", + " - `multiplexor`: Uses quantum multiplexors for data loading. This is the default option.\n", + " - `brute_force`: Implements the controlled rotation by state directly, resulting in longer circuits.\n", "\n", - "The output of the function is a **qlm** *AbstractGate* with arity *n*. " + "The output of the function is a **qlm** `AbstractGate` with arity *n*, where *n* is the number of qubits in the circuit." ] }, { @@ -256,17 +277,21 @@ }, { "cell_type": "markdown", - "id": "9f2d5768", + "id": "1940147a", "metadata": {}, "source": [ - "Now, our quantum state has the form:\n", - "\n", - "$$\\left[\\sqrt{p_0}|0\\rangle+\\sqrt{p_1}|1\\rangle+\\sqrt{p_2}|2\\rangle+\\sqrt{p_3}|3\\rangle+\\sqrt{p_4}|4\\rangle+\\sqrt{p_5}|5\\rangle+\\sqrt{p_6}|6\\rangle+\\sqrt{p_7}|7\\rangle\\right]$$\n", + "Now, the quantum state has the following form:\n", + "$$\n", + "\\sqrt{p_0}|0\\rangle + \\sqrt{p_1}|1\\rangle + \\sqrt{p_2}|2\\rangle + \\sqrt{p_3}|3\\rangle + \\sqrt{p_4}|4\\rangle + \\sqrt{p_5}|5\\rangle + \\sqrt{p_6}|6\\rangle + \\sqrt{p_7}|7\\rangle\n", + "$$\n", "\n", - "Last, we use the function *get_results* from **data_extracting** to obtain the probabilities loaded into the quantum circuit. By the quantum properties, what we are measuring is:\n", + "Finally, we use the function `get_results` from the **data_extracting** module to retrieve the probabilities loaded into the quantum circuit. Due to the properties of quantum mechanics, the measurement outcomes correspond to the squared amplitudes of the quantum state:\n", "\n", + "$$\n", + "\\left(\\sqrt{p_0}^2, \\sqrt{p_1}^2, \\sqrt{p_2}^2, \\sqrt{p_3}^2, \\sqrt{p_4}^2, \\sqrt{p_5}^2, \\sqrt{p_6}^2, \\sqrt{p_7}^2\\right) = \\left(p_0, p_1, p_2, p_3, p_4, p_5, p_6, p_7\\right)\n", + "$$\n", "\n", - "$$\\left(\\sqrt{p_0}^2,\\sqrt{p_1}^2,\\sqrt{p_2}^2,\\sqrt{p_3}^2,\\sqrt{p_4}^2,\\sqrt{p_5}^2,\\sqrt{p_6}^2,\\sqrt{p_7}^2\\right) = \\left(p_0,p_1,p_2,p_3,p_4,p_5,p_6,p_7\\right)$$" + "This confirms that the original probability distribution $ \\{p_0, p_1, \\dots, p_7\\} $ has been successfully encoded into the quantum state." ] }, { @@ -369,15 +394,9 @@ "id": "48d7ddad", "metadata": {}, "source": [ - "As can be seen the Quantum multiplexors implementation is lower in depth." - ] - }, - { - "cell_type": "markdown", - "id": "b88b6c28", - "metadata": {}, - "source": [ - "In order to work properly, the function *load_probabilities* has to be the first gate of the whole circuit." + "As can be observed, the quantum multiplexors implementation results in a circuit with lower depth compared to alternative methods. This efficiency makes it a preferred choice for loading probability distributions into quantum states.\n", + "\n", + "To ensure proper functionality, the `load_probabilities` function must be the first gate applied in the entire circuit. " ] }, { @@ -393,7 +412,9 @@ "id": "ae5c6477", "metadata": {}, "source": [ - "To load an array we need more steps than in the previous example. We first need to load a probability distribution in the state and reserve an extra qubit for loading the function. Here we don't want to focus on the part of loading a probability distribution as it has been already treated in the previous subsection, for that reason we will simply call the function *uniform_distribution* **DL/data_loading** module" + "To load an array into a quantum circuit, additional steps are required compared to loading a probability distribution alone. First, a probability distribution must be loaded into the quantum state, and an extra qubit must be reserved for encoding the array values.\n", + "\n", + "In this context, we will not delve into the details of loading a probability distribution, as it has already been covered in the previous subsection. Instead, we will use the `uniform_distribution` function from the **DL/data_loading** module to simplify this step. This function allows us to focus on the process of loading the array while assuming a uniform probability distribution for the quantum state." ] }, { @@ -438,18 +459,23 @@ }, { "cell_type": "markdown", - "id": "439ae5a2", + "id": "72571a1c", "metadata": {}, "source": [ - "The state now loaded in the circuit is:\n", + "The state currently loaded into the circuit is:\n", + "$$\n", + "\\frac{1}{\\sqrt{N}} |0\\rangle \\left[ |0\\rangle + |1\\rangle + |2\\rangle + |3\\rangle + |4\\rangle + |5\\rangle + |6\\rangle + |7\\rangle \\right]\n", + "$$\n", + "\n", + "The next step is to load the array into the quantum state. For this purpose, the function `load_array` from the **DL/data_loading** module can be used. This function accepts the following arguments:\n", "\n", - "$$\\dfrac{1}{\\sqrt{N}}|0\\rangle\\left[|0\\rangle+|1\\rangle+|2\\rangle+|3\\rangle+|4\\rangle+|5\\rangle+|6\\rangle+|7\\rangle\\right]$$\n", + "1. **Normalized Array**: An array with an infinity norm ($||f||_{\\infty}$) equal to or less than $1$. This ensures that the array values are scaled appropriately for encoding into the quantum state.\n", + " \n", + "2. **`method`**: Specifies the implementation approach for loading the array. By default, this argument is set to `multiplexors`, which is more efficient in terms of quantum gates. Alternatively, the value `brute_force` can be used, though it is significantly less efficient.\n", "\n", - "The next step is loading our array. For that we have the function *load_array* inside **DL/data_loading** module, this function takes following arguments:\n", + "3. **`id_name`**: A string used to assign a name to the Abstract Gate created by the function. If no name is provided, the current CPU time will be appended to the gate's name.\n", "\n", - "1. Normalised array, one which has norm infinity equal to or less than $1$. \n", - "2. Argument called *method*. By default, this second argument is set to *multiplexors*, but it can also have the value *brute_force*. This second option is much less efficient in terms of quantum gates.\n", - "3. id_name: string for giving a name to the Abstract Gate created by the function. If no name is provided then the CPU time will be added to the name." + "This function enables the efficient encoding of the array into the quantum state, building upon the uniform probability distribution already loaded into the circuit." ] }, { @@ -522,7 +548,7 @@ "id": "0628cce3", "metadata": {}, "source": [ - "Finally, we measure the probabilities that we have loaded." + "Finally, we measure the probabilities that we have loaded using `get_results`function." ] }, { @@ -538,21 +564,23 @@ }, { "cell_type": "markdown", - "id": "77d50f66", + "id": "9bf9dd01", "metadata": {}, "source": [ - "By the quantum properties, the result stored in the variable *quantum_probabilities* is the absolute value of the square of $\\hat{f}$ divided by $N$, that is:\n", - "$$p = \\dfrac{1}{N}\\left(\\hat{f}_0^2,\\hat{f}_1^2,\\hat{f}_2^2,\\hat{f}_3^2,\\hat{f}_4^2,\\hat{f}_5^2,\\hat{f}_6^2,\\hat{f}_7^2,...\\right)$$\n", - "There is more information stored in the variable, but we don't need it." - ] - }, - { - "cell_type": "markdown", - "id": "67715d00", - "metadata": {}, - "source": [ - "If we want to recover the function $f$ we need to compute the square root of the probabilities and multiplicate by $N$ and by the normalization constant:\n", - "$$f_i = N||f||_{\\infty}\\sqrt{p_i}$$" + "By the properties of quantum mechanics, the result stored in the variable `quantum_probabilities` corresponds to the absolute square of the normalized array $\\hat{f}$, divided by $N$. Specifically, it is given by:\n", + "$$\n", + "p = \\frac{1}{N} \\left( \\hat{f}_0^2, \\hat{f}_1^2, \\hat{f}_2^2, \\hat{f}_3^2, \\hat{f}_4^2, \\hat{f}_5^2, \\hat{f}_6^2, \\hat{f}_7^2, \\dots \\right)\n", + "$$\n", + "\n", + "While additional information may be stored in the variable, it is not required for the current analysis.\n", + "\n", + "To recover the original function $ f $ from the probabilities stored in the variable `quantum_probabilities`, we need to compute the square root of each probability $ p_i $, multiply it by $ N $ (the size of the array), and scale it by the normalization constant $ ||f||_{\\infty} $. The formula is as follows:\n", + "\n", + "$$\n", + "f_i = N \\cdot ||f||_{\\infty} \\cdot \\sqrt{p_i}\n", + "$$\n", + "\n", + "This process reverses the normalization applied during the encoding of $ f $ into the quantum state, allowing us to reconstruct the original array." ] }, { @@ -579,10 +607,12 @@ }, { "cell_type": "markdown", - "id": "13d6b494", + "id": "e5d165df", "metadata": {}, "source": [ - "Now we are going to load a discrete probability distribution $p_d$ and an array $f$ altogether. This case is a combination of the two previous cases. First, we start by loading the discrete probability distribution $p_d$. Note that, for loading the normalised array $\\hat{f}$ we need an extra qubit. The probability function is loaded just in the first three registers: \n" + "Now, we will load both a discrete probability distribution $ p_d $ and an array $ f $ simultaneously. This case combines the approaches from the two previous examples. \n", + "\n", + "First, we begin by loading the discrete probability distribution $ p_d $. Note that when loading the normalized array $ \\hat{f} $, an additional qubit is required. The probability distribution $ p_d $ is encoded only in the first three registers of the quantum state." ] }, { @@ -600,19 +630,15 @@ }, { "cell_type": "markdown", - "id": "bfb5f0c0", + "id": "6de331bf", "metadata": {}, "source": [ - "Now our quantum state is:\n", - "$$|0\\rangle\\left[\\sqrt{p_0}|0\\rangle+\\sqrt{p_1}|1\\rangle+\\sqrt{p_2}|2\\rangle+\\sqrt{p_3}|3\\rangle+\\sqrt{p_4}|4\\rangle+\\sqrt{p_5}|5\\rangle+\\sqrt{p_6}|6\\rangle+\\sqrt{p_7}|7\\rangle\\right]$$" - ] - }, - { - "cell_type": "markdown", - "id": "225d9d40", - "metadata": {}, - "source": [ - "Next, we compute the angles and load the function. Instead of loading $\\hat{f}$ we are going to load $\\sqrt{\\hat{f}}$ to have everything at the same place." + "At this stage, our quantum state is represented as:\n", + "$$\n", + "|0\\rangle \\left[ \\sqrt{p_0}|0\\rangle + \\sqrt{p_1}|1\\rangle + \\sqrt{p_2}|2\\rangle + \\sqrt{p_3}|3\\rangle + \\sqrt{p_4}|4\\rangle + \\sqrt{p_5}|5\\rangle + \\sqrt{p_6}|6\\rangle + \\sqrt{p_7}|7\\rangle \\right]\n", + "$$\n", + "\n", + "Next, we compute the necessary angles and proceed to load the function into the quantum state. Instead of loading the normalized array $ \\hat{f} $, we will load $ \\sqrt{\\hat{f}} $. This adjustment ensures that all components are consistently encoded within the same framework." ] }, { @@ -629,12 +655,15 @@ }, { "cell_type": "markdown", - "id": "617f923c", + "id": "ee1e58f7", "metadata": {}, "source": [ - "Now our quantum state is:\n", - "$$|0\\rangle\\left[\\sqrt{p_0\\hat{f}_0}|0\\rangle+\\sqrt{p_1\\hat{f}_1}|1\\rangle+\\sqrt{p_2\\hat{f}_2}|2\\rangle+\\sqrt{p_3\\hat{f}_3}|3\\rangle+\\sqrt{p_4\\hat{f}_4}|4\\rangle+\\sqrt{p_5\\hat{f}_5}|5\\rangle+\\sqrt{p_6\\hat{f}_6}|6\\rangle+\\sqrt{p_7\\hat{f}_7}|7\\rangle\\right]+...$$\n", - "If we measure again, we can compare our result with the element-wise product of $p_d$ and $f$" + "At this point, our quantum state is represented as:\n", + "$$\n", + "|0\\rangle \\left[ \\sqrt{p_0 \\hat{f}_0}|0\\rangle + \\sqrt{p_1 \\hat{f}_1}|1\\rangle + \\sqrt{p_2 \\hat{f}_2}|2\\rangle + \\sqrt{p_3 \\hat{f}_3}|3\\rangle + \\sqrt{p_4 \\hat{f}_4}|4\\rangle + \\sqrt{p_5 \\hat{f}_5}|5\\rangle + \\sqrt{p_6 \\hat{f}_6}|6\\rangle + \\sqrt{p_7 \\hat{f}_7}|7\\rangle \\right] + \\dots\n", + "$$\n", + "\n", + "If we perform a measurement on this state, the resulting probabilities can be compared to the element-wise product of the probability distribution $ p_d $ and the normalized array $ \\hat{f} $. " ] }, { @@ -666,7 +695,7 @@ "id": "0dac70a0", "metadata": {}, "source": [ - "If we wanted to compute the scalar product from the previous technique, we can use a neat trick. If we just measure the last qubit (the one that is more on the left in the state or the one that is at the bottom of the circuit) we are effectively computing this amount:" + "If we wish to compute the scalar product using the previous technique, there is an elegant approach we can employ. By measuring only the last qubit (the one furthest to the left in the quantum state representation or at the bottom of the circuit), we effectively calculate the following quantity:" ] }, { @@ -704,10 +733,12 @@ }, { "cell_type": "markdown", - "id": "16a5adb9", + "id": "e8645ad2", "metadata": {}, "source": [ - "In our final example, we are going to load two arrays $f$ and $g = p_d$. To load two arrays we need two extra qubits, one for the first array and another for the second one. We start again by defining our base routine with size $n+2$. As we always need to load a base distribution we load a uniform probability distribution." + "In our final example, we will load two arrays, $ f $ and $ g = p_d $. To encode two arrays into the quantum state, we require two additional qubits: one for the first array ($ f $) and another for the second array ($ g $). \n", + "\n", + "We begin by defining a base routine with a size of $ n + 2 $, where $ n $ is the number of qubits required to represent the original state. Since a base probability distribution must always be loaded, we initialize the state with a uniform probability distribution." ] }, { @@ -726,11 +757,16 @@ }, { "cell_type": "markdown", - "id": "ba3174e6", + "id": "5f7bb32d", "metadata": {}, "source": [ - "We already have defined the normalised version of $f$, $\\hat{f}$ which is stored in the variable *f_normalised*. However the discrete probability distribution $p_d$ is not normalised. For this reason, we define the normalised version as:\n", - "$$\\hat{g} = \\dfrac{g}{||g||_{\\infty}}.$$" + "We have already defined the normalized version of $ f $, denoted as $ \\hat{f} $, which is stored in the variable `f_normalised`. However, the discrete probability distribution $ p_d $ (denoted as $ g $) is not yet normalized. To address this, we define its normalized version as follows:\n", + "\n", + "$$\n", + "\\hat{g} = \\frac{g}{||g||_{\\infty}},\n", + "$$\n", + "\n", + "where $ ||g||_{\\infty} $ represents the infinity norm of $ g $, ensuring that the maximum absolute value of $ \\hat{g} $ is equal to 1." ] }, { @@ -809,15 +845,22 @@ }, { "cell_type": "markdown", - "id": "ce80fe2b", + "id": "dc8b9146", "metadata": {}, "source": [ - "If we want to compute the scalar product from our state we simply need to do a Hadamard transform. The first coefficient of the Hadamard transform is the sum of the input vector, in other words, our previous state:\n", + "To compute the scalar product from our quantum state, we can apply a Hadamard transform. The first coefficient of the Hadamard transform corresponds to the sum of the input vector, which represents our previous state:\n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{N}} |0\\rangle |0\\rangle \\left[ f_0 g_0 |0\\rangle + f_1 g_1 |1\\rangle + f_2 g_2 |2\\rangle + f_3 g_3 |3\\rangle + f_4 g_4 |4\\rangle + f_5 g_5 |5\\rangle + f_6 g_6 |6\\rangle + f_7 g_7 |7\\rangle \\right] + \\dots\n", + "$$\n", + "\n", + "After applying the Hadamard transform, this state transforms into:\n", + "\n", + "$$\n", + "\\frac{1}{N} |0\\rangle |0\\rangle \\left[ f_0 g_0 + f_1 g_1 + f_2 g_2 + f_3 g_3 + f_4 g_4 + f_5 g_5 + f_6 g_6 + f_7 g_7 \\right] |0\\rangle + \\dots\n", + "$$\n", "\n", - "$$\\dfrac{1}{\\sqrt{N}}|0\\rangle|0\\rangle\\left[f_0 g_0|0\\rangle+f_1 g_1|1\\rangle+f_2 g_2|2\\rangle+f_3 g_3|3\\rangle+f_4 g_4|4\\rangle+f_5 g_5|5\\rangle+f_6 g_6|6\\rangle+f_7 g_7|7\\rangle\\right]+...,$$\n", - "transforms to:\n", - "$$\\dfrac{1}{N}|0\\rangle|0\\rangle\\left[f_0 g_0+f_1 g_1+f_2 g_2+f_3 g_3+f_4 g_4+f_5 g_5+f_6 g_6+f_7 g_7\\right|0\\rangle+...]+...$$\n", - "The rest of the coefficients depend on which specific transformation we are doing, as there are three different versions of this transformation. Note that instead of a factor $\\dfrac{1}{\\sqrt{N}}$ now we have a factor $\\dfrac{1}{N}$." + "The remaining coefficients depend on the specific transformation being applied, as there are three distinct versions of this transformation. Note that, instead of the factor $\\frac{1}{\\sqrt{N}}$ present in the original state, the resulting state now includes a factor of $\\frac{1}{N}$." ] }, { @@ -867,12 +910,12 @@ }, { "cell_type": "markdown", - "id": "abd03a2f", + "id": "34032086", "metadata": {}, "source": [ - "Here we are following the ordering convention $|q_n...q_1q_0\\rangle$ with $q_0$ being the least significant qubit. However, in plain QLM they follow the opposite convention where $|q_0q_1...q_n\\rangle$. Here we show how to use our functions to follow the QLM convention, indeed it is not difficult.\n", + "In this context, we are following the qubit ordering convention $ |q_n \\dots q_1 q_0\\rangle $, where $ q_0 $ represents the least significant qubit. However, in the **QLM** framework, the opposite convention is used, where the state is represented as $ |q_0 q_1 \\dots q_n\\rangle $. Below, we demonstrate how to adapt our functions to align with the **QLM** convention, which is relatively straightforward.\n", "\n", - "First, for loading a distribution we just need to apply the loading gate in opposite order:" + "To load a distribution while adhering to the **QLM** qubit ordering:" ] }, { @@ -1028,10 +1071,10 @@ }, { "cell_type": "markdown", - "id": "3b39cab4", + "id": "f47a1bda", "metadata": {}, "source": [ - "When we load an array, under the hood we are really loading some angles into the quantum circuit. In this appendix, we show how to use the angles to load a properly normalised array $\\hat{f}$. As always we start by loading a base discrete probability distribution." + "In this appendix, we explain how to use the angles to load a properly normalized array $\\hat{f}$ into a quantum circuit. Under the hood, loading an array involves encoding specific angles into the quantum state. As with previous examples, we begin by loading a base discrete probability distribution." ] }, { @@ -1048,12 +1091,21 @@ }, { "cell_type": "markdown", - "id": "df451af8", + "id": "5094e688", "metadata": {}, "source": [ - " If we want to load the array $\\hat{f}$ using angles, we first have to compute the associated angles:\n", - "$$ \\left(\\theta_0,\\theta_1,\\theta_2,\\theta_3,\\theta_4,\\theta_5,\\theta_6,\\theta_7\\right)= \\theta =2\\arccos\\left(\\hat{f}\\right)=\\left(2\\arccos\\left(\\hat{f}_0\\right),2\\arccos\\left(\\hat{f}_1\\right),2\\arccos\\left(\\hat{f}_2\\right),2\\arccos\\left(\\hat{f}_3\\right),2\\arccos\\left(\\hat{f}_4\\right),2\\arccos\\left(\\hat{f}_5\\right),2\\arccos\\left(\\hat{f}_6\\right),2\\arccos\\left(\\hat{f}_7\\right)\\right)$$\n", - "This is the reason why we need to work with the normalised version of function $f$." + "To load the normalized array $\\hat{f}$ into the quantum circuit using angles, we first need to compute the associated angles. These angles are calculated as follows:\n", + "\n", + "$$\n", + "\\theta = \\left(\\theta_0, \\theta_1, \\theta_2, \\theta_3, \\theta_4, \\theta_5, \\theta_6, \\theta_7\\right) = 2 \\arccos\\left(\\hat{f}\\right)\n", + "$$\n", + "\n", + "Explicitly, this corresponds to:\n", + "$$\n", + "\\theta = \\left(2 \\arccos\\left(\\hat{f}_0\\right), 2 \\arccos\\left(\\hat{f}_1\\right), 2 \\arccos\\left(\\hat{f}_2\\right), 2 \\arccos\\left(\\hat{f}_3\\right), 2 \\arccos\\left(\\hat{f}_4\\right), 2 \\arccos\\left(\\hat{f}_5\\right), 2 \\arccos\\left(\\hat{f}_6\\right), 2 \\arccos\\left(\\hat{f}_7\\right)\\right)\n", + "$$\n", + "\n", + "This step highlights the importance of working with the normalized version of the function $f$, as it ensures that the computed angles are well-defined and suitable for encoding into the quantum state." ] }, { @@ -1068,18 +1120,14 @@ }, { "cell_type": "markdown", - "id": "d45fb917", + "id": "fe49207f", "metadata": {}, "source": [ - "To load angles we will use the function **load_angles**, which is inside the **data_loading** module. The input should be a numpy array with the angles associated to the normalised function $\\hat{f}$. In this case, the probability distribution is the variable *probability*. The output of the function is a **qlm** *AbstractGate* with arity *n*." - ] - }, - { - "cell_type": "markdown", - "id": "6247e3de", - "metadata": {}, - "source": [ - "Next, we load the angles into the quantum state. For that, we have the function *load_angles*, these function admits a second argument called *method*. By default, it is set to *multiplexors*, but it can also have the value *brute_force*. The second option is much less efficient in terms of quantum gates." + "To load the angles into the quantum circuit, we use the function `load_angles` from the **data_loading** module. This function requires a NumPy array containing the angles associated with the normalized function $\\hat{f}$ as input. In this case, the probability distribution is stored in the variable `probability`. The output of the function is a **qlm** `AbstractGate` with arity *n*, where *n* is the number of qubits in the circuit.\n", + "\n", + "The `load_angles` function also accepts a second argument called `method`, which determines the implementation approach:\n", + "- By default, the `method` is set to `multiplexors`, which is more efficient in terms of quantum gates.\n", + "- Alternatively, the `method` can be set to `brute_force`, though this option is significantly less efficient and results in longer circuits." ] }, { @@ -1104,14 +1152,22 @@ }, { "cell_type": "markdown", - "id": "926c474a", + "id": "feac78c3", "metadata": {}, "source": [ - "Now, our quantum state has the form:\n", - "$$\\dfrac{1}{\\sqrt{N}}|0\\rangle\\left[\\cos\\left(\\dfrac{\\theta_0}{2}\\right)|0\\rangle+\\cos\\left(\\dfrac{\\theta_1}{2}\\right)|1\\rangle+\\cos\\left(\\dfrac{\\theta_2}{2}\\right)|2\\rangle+\\cos\\left(\\dfrac{\\theta_3}{2}\\right)|3\\rangle+\\cos\\left(\\dfrac{\\theta_4}{2}\\right)|4\\rangle+\\cos\\left(\\dfrac{\\theta_5}{2}\\right)|5\\rangle+\\cos\\left(\\dfrac{\\theta_6}{2}\\right)|6\\rangle+\\cos\\left(\\dfrac{\\theta_7}{2}\\right)|7\\rangle\\right]+...,$$\n", - "substituting the value of the angles we have the state\n", - "$$\\dfrac{1}{\\sqrt{N}}|0\\rangle\\left[\\hat{f}_0|0\\rangle+\\hat{f}_1|1\\rangle+\\hat{f}_2|2\\rangle+\\hat{f}_3|3\\rangle+\\hat{f}_4|4\\rangle+\\hat{f}_5|5\\rangle+\\hat{f}_6|6\\rangle+\\hat{f}_7|7\\rangle\\right]+...$$\n", - "The associated circuit is:" + "Now, the quantum state has the following form:\n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{N}} |0\\rangle \\left[ \\cos\\left(\\frac{\\theta_0}{2}\\right)|0\\rangle + \\cos\\left(\\frac{\\theta_1}{2}\\right)|1\\rangle + \\cos\\left(\\frac{\\theta_2}{2}\\right)|2\\rangle + \\cos\\left(\\frac{\\theta_3}{2}\\right)|3\\rangle + \\cos\\left(\\frac{\\theta_4}{2}\\right)|4\\rangle + \\cos\\left(\\frac{\\theta_5}{2}\\right)|5\\rangle + \\cos\\left(\\frac{\\theta_6}{2}\\right)|6\\rangle + \\cos\\left(\\frac{\\theta_7}{2}\\right)|7\\rangle \\right] + \\dots\n", + "$$\n", + "\n", + "By substituting the computed values of the angles $\\theta_i = 2 \\arccos(\\hat{f}_i)$, the state simplifies to:\n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{N}} |0\\rangle \\left[ \\hat{f}_0|0\\rangle + \\hat{f}_1|1\\rangle + \\hat{f}_2|2\\rangle + \\hat{f}_3|3\\rangle + \\hat{f}_4|4\\rangle + \\hat{f}_5|5\\rangle + \\hat{f}_6|6\\rangle + \\hat{f}_7|7\\rangle \\right] + \\dots\n", + "$$\n", + "\n", + "The corresponding quantum circuit for this state preparation is as follows:" ] }, { @@ -1280,9 +1336,9 @@ ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1294,7 +1350,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/02_Amplitude_Amplification_Operators.ipynb b/misc/notebooks/02_Amplitude_Amplification_Operators.ipynb index a72b3af..0b1ef8b 100644 --- a/misc/notebooks/02_Amplitude_Amplification_Operators.ipynb +++ b/misc/notebooks/02_Amplitude_Amplification_Operators.ipynb @@ -10,16 +10,15 @@ }, { "cell_type": "markdown", - "id": "d9fcccc0", + "id": "c334655b", "metadata": {}, "source": [ - "The present notebook reviews the *amplitude_amplification* module from package *AA* of the library *QQuantLib* (**QQuantLib/AA/amplitude_amplification.py**). All mandatory functions for creating Grover-like operators for **amplitude amplification** procedures are developed in this module.\n", + "The present notebook reviews the *amplitude_amplification* module from the *AA* package in the *QQuantLib* library (**QQuantLib/AA/amplitude_amplification.py**). This module implements all the necessary functions for constructing Grover-like operators, which are essential for **amplitude amplification** procedures.\n", "\n", + "This notebook and the associated module are based on the following references:\n", "\n", - "The present notebook and module are based on the following references:\n", - "\n", - "* *Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). Quantum amplitude amplification and estimation.AMS Contemporary Mathematics Series, 305. https://arxiv.org/abs/quant-ph/0005055v1*\n", - "* NEASQC deliverable: *D5.1: Review of state-of-the-art for Pricing and Computation of VaR https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf*" + "- Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). Quantum Amplitude Amplification and Estimation. *AMS Contemporary Mathematics Series*, 305. [arXiv:quant-ph/0005055v1](https://arxiv.org/abs/quant-ph/0005055v1)\n", + "- NEASQC Deliverable: *D5.1: Review of State-of-the-Art for Pricing and Computation of VaR*. [PDF Link](https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf)" ] }, { @@ -84,16 +83,19 @@ }, { "cell_type": "markdown", - "id": "8d7a6fd9", + "id": "947deec0", "metadata": {}, "source": [ - "Before doing any amplification we want to load some data into the quantum circuit, as this step is only auxiliary to see the effect of an amplification, we are just going to load a discrete probability distribution. In this case, we will have a circuit with $n=3$ qubits which makes a total of $N = 2^n = 8$ states. The discrete probability distribution that we are going to load is:\n", - "$$p_d = \\dfrac{(0,1,2,3,4,5,6,7)}{0+1+2+3+4+5+6+7+8}.$$\n", - "\n", - "Note that this probability distribution is properly normalised. \n", + "Before performing any amplitude amplification, we need to load data into the quantum circuit. As this step is auxiliary and intended only to demonstrate the effect of amplification, we will load a discrete probability distribution. In this case, we will use a circuit with $ n = 3 $ qubits, which results in a total of $ N = 2^n = 8 $ computational basis states. The discrete probability distribution we will load is defined as:\n", + "$$\n", + "p_d = \\frac{(0, 1, 2, 3, 4, 5, 6, 7)}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}.\n", + "$$\n", + "Note that this probability distribution is properly normalized.\n", "\n", - "For that purpose we will use the function *load_probability* from **QQuantLib/DL/data_loading**. The state that we are going to get is:\n", - " $$|\\Psi\\rangle = \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$" + "To achieve this, we will use the `load_probability` function from **QQuantLib/DL/data_loading**. The resulting quantum state can be expressed as:\n", + "$$\n", + "|\\Psi\\rangle = \\frac{1}{\\sqrt{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}} \\left( \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right).\n", + "$$" ] }, { @@ -161,7 +163,7 @@ "id": "4caf9fd3", "metadata": {}, "source": [ - "See that the information stored in the quantum state is the same as the one stored in array $p$. For more information about loading data into the quantum circuit see the notebook *data_loading_use*." + "Observe that the information stored in the quantum state is equivalent to the data contained in the array $ p $. For further details on how to load data into a quantum circuit, refer to the notebook *01_Data_Loading_Module_Use.ipynb*." ] }, { @@ -174,12 +176,12 @@ }, { "cell_type": "markdown", - "id": "8b0059ab", + "id": "8b2125cf", "metadata": {}, "source": [ - "**Reflections** are the mandatory ingredients for amplification. A reflection rotates the phase of a state $\\dfrac{\\pi}{2}$. In other words, for real numbers **it changes the sign of the state**. \n", + "**Reflections** are essential components for amplitude amplification. A reflection effectively rotates the phase of a quantum state by $ \\frac{\\pi}{2} $. In simpler terms, for real-valued amplitudes, **it changes the sign of the state**. \n", "\n", - "The function *reflection* from **QQuantLib/AA/amplitude_amplification** module is used for creating **reflections**." + "The function `reflection` from the **QQuantLib/AA/amplitude_amplification** module is used to create these reflections." ] }, { @@ -194,18 +196,29 @@ }, { "cell_type": "markdown", - "id": "e5305de6", + "id": "f62710bd", "metadata": {}, "source": [ - "Creating a reflection over a quantum state using the above-mentioned function is a two steps process:\n", + "Creating a reflection over a quantum state using the aforementioned function involves a two-step process:\n", "\n", - "1. **Creating the reflection QLM Abstract Gate**. This is done by calling the *reflection* function indicating the quantum state for doing the reflection. The state is a Python list and should provided when the user calls the function. **Ex: state = [1, 0, 0, 1] RG=reflection(state)**. The function will create a QLM AbstractGate.\n", - "2. Applying the resulting **reflection QLM Abstract Gate** over a QLM Program or QRoutine *specifying the qubits that will be affected by it*: **Ex: qlm_routine.apply(RG, [qbit[0], qbit[1], qbit[2],...])**\n", + "1. **Creating the Reflection QLM Abstract Gate** \n", + " This is achieved by calling the `reflection` function and specifying the quantum state over which the reflection will be applied. The state must be provided as a Python list when the function is called. For example:\n", + " ```python\n", + " state = [1, 0, 0, 1]\n", + " RG = reflection(state)\n", + " ```\n", + " The function generates a QLM AbstractGate that represents the reflection operation.\n", + "2. **Applying the Reflection QLM Abstract Gate**\n", + " The resulting reflection gate can then be applied to a QLM Program or QRoutine by specifying the qubits it will act upon. For example:\n", + " ```python\n", + " qlm_routine.apply(RG, [qbit[0], qbit[1], qbit[2], ...])\n", + " ```\n", "\n", - "Let's give some examples.\n", + "Let us now explore some examples to illustrate this process.\n", "\n", - "**NOTE**\n", - "By default, the reflection function uses the default QLM construction for the mandatory multi-controlled Z gate. If we pass **mcz_qlm=False** a multiplexor version of the multi-controlled Z gate will be used." + "### NOTE\n", + "\n", + "By default, the `reflection` function uses the standard QLM implementation for the mandatory multi-controlled Z gate. If the argument `mcz_qlm` is set to `False`, a multiplexor-based version of the multi-controlled Z gate will be used instead" ] }, { @@ -221,7 +234,7 @@ "id": "3cc54e31", "metadata": {}, "source": [ - "We will start with a very simple example, in this case, we want to flip the sign of state $|7\\rangle$. For that, we will use the function *reflection*. This function takes a list as a parameter, the list specifies which combination of qubits is going to be reflected. In this case, the binary representation of the state $|7>$ is $111$, so the argument that we have to pass to the function is $[1,1,1]$." + "We will begin with a simple example. In this case, our goal is to flip the sign of the state $ |7\\rangle $. To achieve this, we will use the function `reflection`. This function accepts a list as a parameter, where the list specifies which combination of qubits will undergo reflection. Since the binary representation of the state $ |7\\rangle $ is $ 111 $, we need to pass the argument $ [1, 1, 1] $ to the function." ] }, { @@ -309,7 +322,13 @@ "id": "75017d19", "metadata": {}, "source": [ - "In this example, we want to do something more difficult. We want to flip the sign of all states that start with one. Those states are $00\\mathbf{1}\\longrightarrow |1\\rangle$, $01\\mathbf{1}\\longrightarrow |3\\rangle$, $10\\mathbf{1}\\longrightarrow |5\\rangle$ and $11\\mathbf{1}\\longrightarrow |7\\rangle$. Again, we have to use the function *reflection*. This time we have to apply the reflection to the first qubit, so we just apply it to the first register." + "In this example, we tackle a more complex task: flipping the sign of all states that start with a `1`. These states are:\n", + "- $ 00\\mathbf{1} \\longrightarrow |1\\rangle $\n", + "- $ 01\\mathbf{1} \\longrightarrow |3\\rangle $\n", + "- $ 10\\mathbf{1} \\longrightarrow |5\\rangle $\n", + "- $ 11\\mathbf{1} \\longrightarrow |7\\rangle $\n", + "\n", + "To achieve this, we again use the `reflection` function. However, this time we apply the reflection operation only to the first qubit, as it determines whether the state starts with a `1`. By targeting the first register, we ensure that the sign flip is applied to all states where the first qubit is in the state $ |1\\rangle $." ] }, { @@ -397,9 +416,15 @@ "id": "2a2916f1", "metadata": {}, "source": [ - "In this example, we will do a reflection in a more specific state. In this case, we want to do the reflection of the states $|1\\rangle,|3\\rangle,|5\\rangle,|6\\rangle,|7\\rangle$. We can divide the process of the reflection into two sub-reflections.\n", - "- First, note that the reflection over states $|1\\rangle,|3\\rangle,|5\\rangle,|7\\rangle$ is the one implemented in Section 2.2.\n", - "- Second, the remaining state $|6\\rangle$ can be reflected using the same strategy as in Section 2.1." + "In this example, we will perform a reflection over a more specific set of states: $ |1\\rangle, |3\\rangle, |5\\rangle, |6\\rangle, |7\\rangle $. To achieve this, we can break the process into two sub-reflections:\n", + "\n", + "1. **Reflection over $ |1\\rangle, |3\\rangle, |5\\rangle, |7\\rangle $:** \n", + " This reflection was already implemented in **Section 2.2**, where the sign of all states starting with `1` (i.e., $ |1\\rangle, |3\\rangle, |5\\rangle, |7\\rangle $) was flipped.\n", + "\n", + "2. **Reflection over $ |6\\rangle $:** \n", + " The remaining state $ |6\\rangle $ can be reflected using the same strategy outlined in **Section 2.1**, where a reflection is applied to a single target state.\n", + "\n", + "By combining these two sub-reflections, we can effectively perform the desired reflection over the specified set of states." ] }, { @@ -489,26 +514,33 @@ }, { "cell_type": "markdown", - "id": "9a6b2dd7", + "id": "1835bb5e", "metadata": {}, "source": [ - "In general, the Grover operator works in the following way. Let's say that we have an operator (a routine) that performs the following operation:\n", - "$$\\mathcal{O}|0\\rangle = |\\Psi \\rangle = \\sin(\\theta)|\\Psi_0\\rangle +\\cos(\\theta)|\\Psi_1\\rangle,$$\n", - "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states. Now, the Grover operator $\\mathcal{G}$ does the following transformation:\n", - "$$ |\\Psi \\rangle \\longrightarrow \\mathcal{G}^k|\\Psi\\rangle= \\sin\\left((2k+1)\\theta\\right)|\\Psi_0\\rangle +\\cos\\left((2k+1)\\theta\\right)|\\Psi_1\\rangle.$$ \n", - "This operator is known in the literature as **amplitude amplification** as, in principle, it *increases* the probability of obtaining the state $|\\phi\\rangle$. Note that, when the angle $(2k+1)\\theta$ goes over $\\dfrac{\\pi}{2}$, the probability instead of increase starts decreasing. Whenever we use the term amplification keep in mind that it can have this effect.\n", - "\n", - "The Grover operator $\\mathcal{G}$ can be decomposed in 2 different operators:\n", - "\n", - "$$\\hat{Q}=\\hat{U}_{|\\Psi\\rangle} \\hat{U}_{|\\Psi_{0}\\rangle}$$\n", + "In general, the Grover operator operates as follows. Suppose we have an operator (or routine) that performs the transformation:\n", + "$$\n", + "\\mathcal{O}|0\\rangle = |\\Psi \\rangle = \\sin(\\theta)|\\Psi_0\\rangle + \\cos(\\theta)|\\Psi_1\\rangle,\n", + "$$\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states. The Grover operator $\\mathcal{G}$ then applies the following transformation:\n", + "$$\n", + "|\\Psi \\rangle \\longrightarrow \\mathcal{G}^k|\\Psi\\rangle = \\sin\\left((2k+1)\\theta\\right)|\\Psi_0\\rangle + \\cos\\left((2k+1)\\theta\\right)|\\Psi_1\\rangle.\n", + "$$\n", "\n", - "Where $\\hat{U}_{|\\Psi\\rangle}$ y $\\hat{U}_{|\\Psi_{0}\\rangle}$ are:\n", + "This operator is commonly referred to as **amplitude amplification** in the literature, as it *increases* the probability of obtaining the state $|\\Psi_0\\rangle$. However, note that when the angle $(2k+1)\\theta$ exceeds $\\frac{\\pi}{2}$, the probability begins to decrease instead of increasing. Thus, when referring to \"amplification,\" keep in mind that it can have this diminishing effect.\n", "\n", - "$$\\hat{U}_{|\\Psi_{0}\\rangle } = \\hat{I} - 2|\\Psi_{0}\\rangle \\langle \\Psi_{0}|$$\n", - "$$\\hat{U}_{|\\Psi\\rangle } = \\hat{I} - 2|\\Psi\\rangle\\langle \\Psi|$$\n", + "The Grover operator $\\mathcal{G}$ can be decomposed into two distinct operators:\n", + "$$\n", + "\\mathcal{G} = \\hat{U}_{|\\Psi\\rangle} \\hat{U}_{|\\Psi_{0}\\rangle},\n", + "$$\n", + "where $\\hat{U}_{|\\Psi_{0}\\rangle}$ and $\\hat{U}_{|\\Psi\\rangle}$ are defined as:\n", + "$$\n", + "\\hat{U}_{|\\Psi_{0}\\rangle} = \\hat{I} - 2|\\Psi_{0}\\rangle \\langle \\Psi_{0}|,\n", + "$$\n", + "$$\n", + "\\hat{U}_{|\\Psi\\rangle} = \\hat{I} - 2|\\Psi\\rangle \\langle \\Psi|.\n", + "$$\n", "\n", - "In this section, we are going to review all the operators programmed for creating the Grover operator.\n", - "\n" + "In this section, we will review all the operators implemented for constructing the Grover operator." ] }, { @@ -521,33 +553,47 @@ }, { "cell_type": "markdown", - "id": "ba6fa534", + "id": "6941eb52", "metadata": {}, "source": [ - "The first mandatory operator is:\n", + "The first mandatory operator is defined as:\n", + "$$\n", + "\\hat{U}_{|\\Psi_{0}\\rangle} = \\hat{I} - 2|\\Psi_{0}\\rangle \\langle \\Psi_{0}|.\n", + "$$\n", "\n", - "$$\\hat{U}_{|\\Psi_{0}\\rangle } = \\hat{I} - 2|\\Psi_{0}\\rangle \\langle \\Psi_{0}|$$\n", + "When this operator is applied to the state $|\\Psi\\rangle$, the result is:\n", + "$$\n", + "\\hat{U}_{|\\Psi_{0}\\rangle} |\\Psi\\rangle = -\\sin(\\theta)|\\Psi_{0}\\rangle + \\cos(\\theta)|\\Psi_{1}\\rangle.\n", + "$$\n", "\n", - "When we apply this operator on state $|\\Psi\\rangle$:\n", + "Thus, the operator $\\hat{U}_{|\\Psi_{0}\\rangle}$ represents a reflection of the state $|\\Psi_{0}\\rangle$ around the state $|\\Psi_{1}\\rangle$. Graphically, this can be visualized as follows:\n", "\n", - "$$\\hat{U}_{|\\Psi_{0}\\rangle} |\\Psi\\rangle = -\\sin(\\theta)|\\Psi_{0}\\rangle+\\cos(\\theta)|\\Psi_{1}\\rangle$$\n", "\n", - "So operator $\\hat{U}_{|\\Psi_{0}\\rangle }$ is a reflection of the state $|\\Psi_{0}\\rangle$ around state $|\\Psi_{1}\\rangle$. Or in a graphic view:\n", "\n", - "![title](images/OraculeReflection.png)\n", "\n", - "The function *create_u0_gate* from **QQuantLib/AA/amplitude_amplification** module allows us to create an Abstract Gate for doing the operation. As in the case of the reflection, the application of the $\\hat{U}_{|\\Psi_{0}\\rangle}$ to a quantum state will be a two steps process:\n", + "\n", "\n", - "1. **Creating the $\\hat{U}_{|\\Psi_{0}\\rangle}$ QLM AbstractGate** using the *create_u0_gate* and providing it, as input, the state that we want to flip the sign. This is done with the following three arguments:\n", - " * The first one is always the oracle (this is the QLM QRoutine of the Program)\n", - " * The second one is a list containing the binary representation of the state that we want to flip (marked state).\n", - " * The third one is a list of the registers over which we want to act. \n", - "2. **Applying $\\hat{U}_{|\\Psi_{0}\\rangle}$ QLM AbstractGate** to the registers that will be affected.\n", + "### Implementation in **QQuantLib**\n", "\n", - "In this sense, the syntax is very similar to that of the reflections. Indeed, it is implemented using the function *reflection*. Next, we give two examples.\n", + "The function `create_u0_gate` from the **QQuantLib/AA/amplitude_amplification** module allows us to create an Abstract Gate that performs this operation. Similar to the reflection process, applying $\\hat{U}_{|\\Psi_{0}\\rangle}$ to a quantum state involves a two-step process:\n", "\n", - "**NOTE**\n", - "By default, the QLM construction for the multi-controlled Z gate is used. If we pass **mcz_qlm=False** a multiplexor version of the multi-controlled Z gate will be used." + "1. **Creating the $\\hat{U}_{|\\Psi_{0}\\rangle}$ QLM AbstractGate** \n", + " This is achieved using the `create_u0_gate` function, with the following inputs:\n", + " - The first argument is always the `oracle` (i.e., the QLM QRoutine or Program).\n", + " - The second argument is a list containing the binary representation of the state whose sign we want to flip (the marked state, `target`).\n", + " - The third argument is a list of the qubit registers over which the operation will be applied (`index`).\n", + "\n", + "2. **Applying the $\\hat{U}_{|\\Psi_{0}\\rangle}$ QLM AbstractGate** \n", + " Once the gate is created, it can be applied to the specified qubit registers.\n", + "\n", + "This process closely mirrors the syntax used for reflections, as `create_u0_gate` is internally implemented using the `reflection` function.\n", + "\n", + "---\n", + "\n", + "### **NOTE**\n", + "By default, the standard QLM construction for the multi-controlled Z gate is used. If the argument `mcz_qlm=False` is passed, a multiplexor-based version of the multi-controlled Z gate will be used instead.\n", + "\n", + "Next, we provide two examples to illustrate this process." ] }, { @@ -573,10 +619,13 @@ "id": "4fc93839", "metadata": {}, "source": [ - "In this first example, we want to mark the state $|7\\rangle$. Its binary representation is $111$, and we want to act on the register [0,1,2]. So following the above-mentioned arguments we call the *create_u0_gate* with the following arguments:\n", - "* First Input: Oracle that will be the QLM Routine with the loaded probability\n", - "* Second input: state we want to mark in binary representation: [1,1,1] \n", - "* Third input: Registers over which we want to act: [0,1,2]." + "In this first example, our goal is to mark the state $ |7\\rangle $. Its binary representation is $ 111 $, and we want to apply the operation to the qubit registers `[0, 1, 2]`. Following the arguments outlined above, we call the `create_u0_gate` function with the following inputs:\n", + "\n", + "- **First Input**: The `oracle`, which is the QLM Routine containing the loaded probability distribution.\n", + "- **Second Input**: The state (`target`) we want to mark, provided in binary representation: `[1, 1, 1]`.\n", + "- **Third Input**: The registers (`index`) over which we want to apply the operation: `[0, 1, 2]`.\n", + "\n", + "This configuration ensures that the sign of the state $ |7\\rangle $ is flipped while leaving the other states unaffected." ] }, { @@ -608,9 +657,9 @@ "id": "4b8a5013", "metadata": {}, "source": [ - "If we display the circuit with one more layer of depth, we will see that it is implemented as a reflection([1,1,1]) acting on the register [0,1,2].\n", + "If we visualize the circuit with one additional layer of depth, we will observe that it is implemented as a `reflection([1, 1, 1])` acting on the qubit registers `[0, 1, 2]`.\n", "\n", - "Last, we will show the amplitudes stored in the quantum circuit and we will see that only the last element is affected" + "Finally, by examining the amplitudes stored in the quantum circuit, we can confirm that only the last element (corresponding to the state $ |7\\rangle $) is affected by the operation." ] }, { @@ -664,14 +713,22 @@ }, { "cell_type": "markdown", - "id": "d8ffd461", + "id": "e187c372", "metadata": {}, "source": [ - "For the second example we want flip the sign/mark all states that start with one. Those states are $00\\mathbf{1}\\longrightarrow |1\\rangle$, $01\\mathbf{1}\\longrightarrow |3\\rangle$, $10\\mathbf{1}\\longrightarrow |5\\rangle$ and $11\\mathbf{1}\\longrightarrow |7\\rangle$. Again, we have to use the function *U0*, but this time we indicate that we are only acting upon the first register. So the arguments when calling *U0* function will be:\n", + "In the second example, our goal is to flip the sign (or \"mark\") all states that begin with the digit `1`. These states are:\n", + "- $ 00\\mathbf{1} \\longrightarrow |1\\rangle $, \n", + "- $ 01\\mathbf{1} \\longrightarrow |3\\rangle $, \n", + "- $ 10\\mathbf{1} \\longrightarrow |5\\rangle $, and \n", + "- $ 11\\mathbf{1} \\longrightarrow |7\\rangle $. \n", + "\n", + "To accomplish this, we will use the `create_u0_gate` function again, but this time we specify that the operation acts only on the first qubit register. The arguments passed to the `U0` function will be as follows:\n", "\n", - "* First Input: Oracle that will be the QLM Routine with the loaded probability\n", - "* Second input: state we want to mark in binary representation: [1] \n", - "* Third input: Registers over which we want to act: [0]." + "- **First Input**: The oracle (`oracle`), which is the QLM Routine containing the loaded probability distribution.\n", + "- **Second Input**: The state we want to mark (`taget`), represented in binary form: `[1]`.\n", + "- **Third Input**: The registers (`index`) over which the operation will act: `[0]`.\n", + "\n", + "This configuration ensures that the sign of all states starting with `1` is flipped while leaving the other states unaffected." ] }, { @@ -695,7 +752,7 @@ "%qatdisplay U0_2_gate --depth 0 --svg\n", "routine_U0_2.apply(U0_2_gate,register_U0_2)\n", "#Apply U0_gate\n", - "%qatdisplay routine_U0_2 --depth 2 --svg" + "%qatdisplay routine_U0_2 --depth 1 --svg" ] }, { @@ -703,7 +760,7 @@ "id": "9d1e6b10", "metadata": {}, "source": [ - "If we display the circuit with one more layer of depth, we will see that it is implemented as a *reflection([1])* acting on the register *[0]*.\n", + "If we display the circuit with one more layer of depth, we will see that it is implemented as a `reflection([1])` acting on the register `[0]`.\n", "\n", "Last, we will show the amplitudes stored in the quantum circuit and we will see that only states $|1\\rangle$, $|3\\rangle$, $|5\\rangle$ and $|7\\rangle$ are affected." ] @@ -754,7 +811,7 @@ "id": "ff13bdce", "metadata": {}, "source": [ - "**Note that we cannot use operator *U0* to mark states that require more than one reflection. This is the case for example of the set of states $|1\\rangle,|3\\rangle,|5\\rangle,|6\\rangle,|7\\rangle$.**" + "**Note that we cannot use operator `create_u0_gate` to mark states that require more than one reflection. This is the case for example of the set of states $|1\\rangle,|3\\rangle,|5\\rangle,|6\\rangle,|7\\rangle$.**" ] }, { @@ -767,26 +824,33 @@ }, { "cell_type": "markdown", - "id": "515d7d3b", + "id": "6c157aa6", "metadata": {}, "source": [ - "The $\\hat{U}_{|\\Psi\\rangle}$ (**diffusor**):\n", - "$$\\hat{U}_{|\\Psi\\rangle } = \\hat{I} - 2|\\Psi\\rangle\\langle \\Psi|,$$\n", - "is a reflection of state $|\\Psi\\rangle$ around the state $|\\Psi\\rangle^{\\perp}$ (where $|\\Psi\\rangle^{\\perp} \\perp |\\Psi\\rangle$).\n", + "The **diffusion operator** $\\hat{U}_{|\\Psi\\rangle}$ is defined as:\n", "\n", - "Using the graphic representation:\n", + "$$\n", + "\\hat{U}_{|\\Psi\\rangle} = \\hat{I} - 2 |\\Psi\\rangle\\langle\\Psi|,\n", + "$$\n", "\n", - "![title](images/StateReflection.png)\n", + "which represents a reflection of the state $|\\Psi\\rangle$ around the orthogonal state $|\\Psi\\rangle^{\\perp}$, where $|\\Psi\\rangle^{\\perp} \\perp |\\Psi\\rangle$.\n", "\n", - "So:\n", + "Using the graphical representation, we can see that:\n", "\n", - "$$\\hat{U}_{|\\Psi\\rangle } |\\Psi\\rangle = \\hat{I}|\\Psi\\rangle - 2|\\Psi\\rangle \\langle \\Psi|\\Psi\\rangle = |\\Psi\\rangle - 2|\\Psi\\rangle = -|\\Psi\\rangle$$\n", "\n", + "\n", "\n", - "For implementing this operator the function *create_u_gate* from **QQuantLib/AA/amplitude_amplification** module will be used. It only needs as input the oracle\n", "\n", - "**NOTE**\n", - "By default, the QLM construction for the multi-controlled Z gate is used. If we pass **mcz_qlm=False** a multiplexor version of the multi-controlled Z gate will be used." + "$$\n", + "\\hat{U}_{|\\Psi\\rangle} |\\Psi\\rangle = \\hat{I} |\\Psi\\rangle - 2 |\\Psi\\rangle \\langle\\Psi|\\Psi\\rangle = |\\Psi\\rangle - 2 |\\Psi\\rangle = -|\\Psi\\rangle.\n", + "$$\n", + "\n", + "This shows that the diffusion operator inverts the amplitude of the state $|\\Psi\\rangle$.\n", + "\n", + "For implementing this operator, the function `create_u_gate` from the **QQuantLib/AA/amplitude_amplification** module will be used. This function requires the oracle as input.\n", + "\n", + "#### Note:\n", + "By default, the QLM (Quantum Learning Machine) construction for the multi-controlled Z gate is used. If the parameter `mcz_qlm=False` is passed, a multiplexor-based version of the multi-controlled Z gate will be utilized instead." ] }, { @@ -880,6 +944,25 @@ "By default, the QLM construction for the multi-controlled Z gate is used. If we pass **mcz_qlm=False** a multiplexor version of the multi-controlled Z gate will be used." ] }, + { + "cell_type": "markdown", + "id": "b09fb95e", + "metadata": {}, + "source": [ + "Now that we have all the necessary components, we can define the **Grover operator** as:\n", + "\n", + "$$\n", + "\\hat{Q} = \\hat{U}_{|\\Psi\\rangle} \\hat{U}_{|\\Psi_0\\rangle},\n", + "$$\n", + "\n", + "where $\\hat{U}_{|\\Psi\\rangle}$ is the diffusion operator and $\\hat{U}_{|\\Psi_0\\rangle}$ is the oracle reflection operator. The Grover operator combines these two operations to amplify the amplitude of the desired state in quantum search algorithms.\n", + "\n", + "The Grover operator can be implemented using the function `grover` from the **QQuantLib/AA/amplitude_amplification** module. Let's explore some examples.\n", + "\n", + "#### Note:\n", + "By default, the QLM (Quantum Learning Machine) construction for the multi-controlled Z gate is used. If the parameter `mcz_qlm=False` is passed, a multiplexor-based version of the multi-controlled Z gate will be utilized instead." + ] + }, { "cell_type": "code", "execution_count": null, @@ -905,6 +988,23 @@ "\n" ] }, + { + "cell_type": "markdown", + "id": "7343c04e", + "metadata": {}, + "source": [ + "Similar to the $\\hat{U}_{|\\Psi_0\\rangle}$ operator, applying the Grover operator to a quantum state involves a two-step process:\n", + "\n", + "1. **Creating the Grover QLM AbstractGate** using the `grover` function and providing it with the necessary inputs to define the state we want to amplify. This requires the following three arguments:\n", + " - **Oracle (QLM QRoutine):** The first argument is always the oracle, which is represented as a QLM QRoutine in the program (`oracle`).\n", + " - **Marked State (Binary Representation):** The second argument is a list containing the binary representation of the state that we aim to amplify (the marked state, `target`).\n", + " - **Target Registers:** The third argument is a list of the registers over which the Grover operator will act (`index`)\n", + "\n", + "2. **Applying the Grover QLM AbstractGate** to the specified registers. Once the AbstractGate is created, it is applied to the registers that will be affected by the operation.\n", + "\n", + "This two-step process ensures that the Grover operator is correctly configured and applied to achieve amplitude amplification in quantum search algorithms." + ] + }, { "cell_type": "markdown", "id": "6ddb6db6", @@ -960,6 +1060,26 @@ "Now we measure the amplified probabilities" ] }, + { + "cell_type": "markdown", + "id": "2d7680ac", + "metadata": {}, + "source": [ + "The critical part of the process is the function call:\n", + "\n", + "```python\n", + "grover(probability_routine, [0, 0, 1], [0, 1, 2])\n", + "```\n", + "\n", + "- The first argument of the function is always the routine that corresponds to the `oracle`, which identifies the state we want to amplify. In this case, it is probability_routine.\n", + "- The second argument specifies the `target` state that we aim to amplify. Since we want to amplify the state ∣1⟩, we provide its binary representation, which is `[0,0,1]`.\n", + "- The third argument is an `index` list that specifies the subset of the quantum register over which the Grover operator will act. Here, since all three qubits in the quantum register are involved, we include all three indices: `[0,1,2]`.\n", + "\n", + "In the following examples, the functionality of these arguments will become clearer.\n", + "\n", + "Now, let's proceed to measure the amplified probabilities." + ] + }, { "cell_type": "code", "execution_count": null, @@ -974,25 +1094,33 @@ }, { "cell_type": "markdown", - "id": "0130d02e", + "id": "887cb312", "metadata": {}, "source": [ - "Let's explain what we have done so far. In our example, we do the following identifications:\n", + "Let's summarize what we have done so far. In our example, we make the following identifications:\n", + "\n", "$$\n", - " \\begin{array}{l}\n", - " &\\mathcal{O}\\longrightarrow \\mathcal{P}.\\\\\n", - " & |\\Psi\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", - " & \\sin(\\theta)|\\Psi_0\\rangle \\longrightarrow \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle.\\\\\n", - " & \\cos(\\theta)|\\Psi_1\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", - " \\end{array}\n", + "\\begin{aligned}\n", + "& \\mathcal{O} \\longrightarrow \\mathcal{P}, \\\\\n", + "& |\\Psi\\rangle \\longrightarrow \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right], \\\\\n", + "& \\sin(\\theta)|\\Psi_0\\rangle \\longrightarrow \\frac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}} |1\\rangle, \\\\\n", + "& \\cos(\\theta)|\\Psi_1\\rangle \\longrightarrow \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "After performing the amplification and measuring the probabilities, we obtain the new probability $ p_a = \\sin^2(3\\theta) $ for the state in position $ |1\\rangle $. The original probability was $ p_o = \\sin^2(\\theta) $.\n", + "\n", + "To verify that the amplified probability and the original probability are computed correctly, we can recover $\\theta$ from the amplified probability using the formula:\n", "$$\n", - "When we do the amplification and measure the probabilities we will obtain in position $1$ the new probability $p_{a} = \\sin^2(3\\theta)$. The original probability was $p_o = \\sin^2(\\theta)$. To check if the amplified probability and the original probability are correctly computed, we simply use the fact that, we can recover $\\theta$ from the amplified probability by doing the operation:\n", - "$$ \\theta = \\dfrac{\\arcsin\\left(\\sqrt{p_a}\\right)}{3}.$$\n", - "Then we relate it with the original one substituting the angle, hence:\n", + "\\theta = \\frac{\\arcsin\\left(\\sqrt{p_a}\\right)}{3}.\n", + "$$\n", + "\n", + "Substituting this angle back into the expression for the original probability, we get:\n", "$$\n", - "p_o = \\sin ^2\\left(\\dfrac{\\arcsin\\left(\\sqrt{p_a}\\right)}{3}\\right)\n", + "p_o = \\sin^2\\left(\\frac{\\arcsin\\left(\\sqrt{p_a}\\right)}{3}\\right).\n", "$$\n", - "In the next cell, we check that the amplification is done correctly" + "\n", + "In the next cell, we will confirm that the amplification process has been carried out correctly." ] }, { @@ -1033,6 +1161,27 @@ "Before doing an amplification we are going to do the unamplified version." ] }, + { + "cell_type": "markdown", + "id": "199bc998", + "metadata": {}, + "source": [ + "In this second example, we will demonstrate how to amplify a more complex state. Specifically, we aim to amplify the state composed of $|0\\rangle$, $|1\\rangle$, $|2\\rangle$, and $|3\\rangle$. Below, we outline the correspondence of this new setup with the Grover framework:\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "& \\mathcal{O} \\longrightarrow \\mathcal{P}, \\\\\n", + "& |\\psi\\rangle \\longrightarrow \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right], \\\\\n", + "& \\sin(\\theta)|\\phi\\rangle \\longrightarrow \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle \\right], \\\\\n", + "& \\cos(\\theta)|\\phi^\\dagger\\rangle \\longrightarrow \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[ \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "In binary representation, the states $|0\\rangle$, $|1\\rangle$, $|2\\rangle$, and $|3\\rangle$ correspond to $|\\mathbf{0}00\\rangle$, $|\\mathbf{0}01\\rangle$, $|\\mathbf{0}10\\rangle$, and $|\\mathbf{0}11\\rangle$, respectively. Here, we are interested in the joint probability of obtaining these states, which is equivalent to asking for the probability that the leftmost qubit is in the state $|0\\rangle$.\n", + "\n", + "Before proceeding with the amplification process, we will first compute the unamplified version of the probabilities." + ] + }, { "cell_type": "code", "execution_count": null, @@ -1059,18 +1208,21 @@ }, { "cell_type": "markdown", - "id": "77942701", + "id": "8e6916b0", "metadata": {}, "source": [ - "When we only measure the leftmost qubit ($q_2$) we are effectively performing the following operation:\n", - "$$ \n", - " \\begin{array}{l}\n", - "p_{|0\\rangle} = \\sin^2\\left(\\theta\\right) = \\dfrac{1}{0+1+2+3+4+5+6+7+8} \\left[|\\sqrt{0}|^2+|\\sqrt{1}|^2+|\\sqrt{2}|^2+|\\sqrt{3}|^2\\right].\\\\\n", - "p_{|1\\rangle} = \\cos^2\\left(\\theta\\right) = \\dfrac{1}{0+1+2+3+4+5+6+7+8} \\left[|\\sqrt{4}|^2+|\\sqrt{5}|^2+|\\sqrt{6}|^2+|\\sqrt{7}|^2\\right].\\\\\n", - "\\end{array}\n", + "When we measure only the leftmost qubit ($q_2$), we are effectively performing the following operation to compute the probabilities of its possible outcomes:\n", + "\n", "$$\n", + "\\begin{aligned}\n", + "p_{|0\\rangle} &= \\sin^2(\\theta) = \\frac{1}{0+1+2+3+4+5+6+7+8} \\left[ |\\sqrt{0}|^2 + |\\sqrt{1}|^2 + |\\sqrt{2}|^2 + |\\sqrt{3}|^2 \\right], \\\\\n", + "p_{|1\\rangle} &= \\cos^2(\\theta) = \\frac{1}{0+1+2+3+4+5+6+7+8} \\left[ |\\sqrt{4}|^2 + |\\sqrt{5}|^2 + |\\sqrt{6}|^2 + |\\sqrt{7}|^2 \\right].\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "Here, $p_{|0\\rangle}$ represents the probability of measuring the state $|0\\rangle$ for the leftmost qubit, while $p_{|1\\rangle}$ corresponds to the probability of measuring the state $|1\\rangle$. These probabilities are derived from the amplitudes of the respective subspaces.\n", "\n", - "In the next cell, we check it with the classical probability" + "In the next cell, we will verify these results by comparing them with the classical probability calculations." ] }, { @@ -1092,6 +1244,21 @@ "Next, we will amplify the probability of the state marked with the rightmost qubit being $0$. For that, we again use the *grover* function. Apart from the oracle we need to indicate the state that we want to amplify." ] }, + { + "cell_type": "markdown", + "id": "12bcf213", + "metadata": {}, + "source": [ + "Next, we will amplify the probability of the state where the rightmost qubit is $ |0\\rangle $. To achieve this, we once again utilize the `grover` function. In addition to providing the oracle, we need to specify the state that we aim to amplify.\n", + "\n", + "Specifically:\n", + "- The `oracle` identifies the state(s) to be amplified.\n", + "- The `target` state is defined by the binary representation of the desired outcome. In this case, we focus on states where the rightmost qubit is $ |0\\rangle $.\n", + "- The `index`refers to the qubit afected that in this case is $q_2$ (so `[2]`).\n", + "\n", + "By applying the Grover operator, we enhance the probability amplitude of the target state, effectively increasing its likelihood of being measured." + ] + }, { "cell_type": "code", "execution_count": null, @@ -1142,7 +1309,9 @@ "id": "b52c3773", "metadata": {}, "source": [ - "We clearly see that we have increased the probability of obtaining $|0\\rangle$ at the cost of decreasing the probability of obtaining $|1\\rangle$. Again we can see the correspondence of this amplified probability with the unamplified one." + "We clearly observe that the probability of obtaining $ |0\\rangle $ has been increased, while the probability of obtaining $ |1\\rangle $ has correspondingly decreased. This trade-off is a direct result of the amplification process performed by the Grover operator.\n", + "\n", + "Moreover, we can establish a correspondence between the amplified probabilities and their unamplified counterparts. This comparison highlights how the Grover algorithm effectively enhances the likelihood of measuring the desired state, in this case, $ |0\\rangle $, by redistributing the probability amplitudes." ] }, { @@ -1165,7 +1334,14 @@ "source": [ "### NOTE\n", "\n", - "In the module *utils* of package *utils* of the library *QQuantLib* (**QQuantLib/utils/utils.py**) we have developed the *load_qn_gate* function that allows the use to create several applications of one given gate. We can use it for doing a multiple application of a Grover-like operator:" + "In the module *utils* of package *utils* of the library *QQuantLib* (**QQuantLib/utils/utils.py**) we have developed the *load_qn_gate* function that allows the use to create several applications of one given gate. We can use it for doing a multiple application of a Grover-like operator:\n", + "\n", + "\n", + "### Note\n", + "\n", + "In the `utils` module of the **utils** package within the *QQuantLib* library (**QQuantLib/utils/utils.py**), we have implemented the `load_qn_gate` function, which enables users to create multiple applications of a given quantum gate. This functionality can be particularly useful for applying a Grover-like operator repeatedly in various quantum algorithms.\n", + "\n", + "For instance, this function allows you to efficiently define and apply the Grover operator multiple times without having to reconstruct it from scratch each time, streamlining the implementation of amplitude amplification processes." ] }, { @@ -1197,13 +1373,21 @@ "source": [ "%qatdisplay n_grover --depth 1 --svg" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e62fcc3", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1215,7 +1399,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb b/misc/notebooks/03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb index 762fe61..44ba3cd 100644 --- a/misc/notebooks/03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb +++ b/misc/notebooks/03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb @@ -10,24 +10,23 @@ }, { "cell_type": "markdown", - "id": "7d6cb636", + "id": "0dd1e8c8", "metadata": {}, "source": [ - "The present notebook reviews the **Maximum Likelihood Amplitude Estimation** algorithm (**MLAE**) which was implemented into the module *maximum_likelihood_ae* of package *AE* of the library *QQuantLib* (**QQuantLib/AE/maximum_likelihood_ae.py**). \n", + "The present notebook provides an overview of the **Maximum Likelihood Amplitude Estimation (MLAE)** algorithm, which has been implemented in the `maximum_likelihood_ae` module of the **AE** package within the *QQuantLib* library (**QQuantLib/AE/maximum_likelihood_ae.py**). \n", "\n", - "Inside this module, we have implemented the **MLAE** as a Python class." - ] - }, - { - "cell_type": "markdown", - "id": "bcf02895", - "metadata": {}, - "source": [ - "The present notebook and module are based on the following references:\n", + "Within this module, the **MLAE** algorithm is encapsulated as a Python class, allowing for modular and reusable implementation. This approach facilitates its integration into larger quantum computing workflows and applications.\n", + "\n", + "The content of this notebook and the associated module are based on the following references:\n", "\n", - "* *Suzuki, Y., Uno, S., Raymond, R., Tanaka, T., Onodera, T., & Yamamoto, N.*. Amplitude estimation without phase estimation. Quantum Information Processing, 19(2), 2020. https://arxiv.org/abs/1904.10246\n", + "- **Suzuki, Y., Uno, S., Raymond, R., Tanaka, T., Onodera, T., & Yamamoto, N.** \n", + " *Amplitude Estimation without Phase Estimation.* \n", + " Quantum Information Processing, 19(2), 2020. \n", + " [https://arxiv.org/abs/1904.10246](https://arxiv.org/abs/1904.10246)\n", "\n", - "* NEASQC deliverable: *D5.1: Review of state-of-the-art for Pricing and Computation of VaR https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf*" + "- **NEASQC Deliverable: D5.1** \n", + " *Review of State-of-the-Art for Pricing and Computation of VaR.* \n", + " [https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf](https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf)" ] }, { @@ -116,8 +115,15 @@ "id": "71279f09", "metadata": {}, "source": [ - "Before doing any amplitude estimation, we want to load some data into the quantum circuit, as this step is only auxiliary to see how the algorithm works, we are just going to load a discrete probability distribution. In this case, we will have a circuit with $n=3$ qubits which makes a total of $N = 2^n = 8$ states. The discrete probability distribution that we are going to load is:\n", - "$$p_d = \\dfrac{(0,1,2,3,4,5,6,7)}{0+1+2+3+4+5+6+7+8}.$$\n" + "Before performing any amplitude estimation, we first need to load data into the quantum circuit. As this step is auxiliary and intended to demonstrate how the algorithm works, we will simply load a discrete probability distribution. \n", + "\n", + "In this example, we will use a quantum circuit with $ n = 3 $ qubits, which corresponds to a total of $ N = 2^n = 8 $ computational basis states. The discrete probability distribution we aim to load is defined as:\n", + "\n", + "$$\n", + "p_d = \\frac{(0, 1, 2, 3, 4, 5, 6, 7)}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}.\n", + "$$\n", + "\n", + "This distribution assigns probabilities proportional to the integers $ 0 $ through $ 7 $, normalized by their sum to ensure that the total probability equals 1." ] }, { @@ -138,7 +144,7 @@ "id": "716492db", "metadata": {}, "source": [ - "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function *load_probability* from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", + "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function `load_probability` from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", " $$|\\Psi\\rangle = \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$" ] }, @@ -190,19 +196,22 @@ }, { "cell_type": "markdown", - "id": "c977b4c6", + "id": "d0f8dff6", "metadata": {}, "source": [ - "The problem of amplitude estimation is the following. Given an oracle operator $\\mathcal{A}$ :\n", + "The problem of amplitude estimation can be formulated as follows. Given an oracle operator $\\mathcal{A}$, we have:\n", "\n", - "$$\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle +\\sqrt{1-a}|\\Psi_1\\rangle,$$\n", + "$$\n", + "\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle,\n", + "$$\n", "\n", - "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, we want to estimate $\\sqrt{a}$. We can define an associated angle to $\\sqrt{a}$ as $\\sin^2{\\theta} = a$, and the problem is thus rewritten as:\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states. The goal is to estimate the value of $\\sqrt{a}$. To simplify the problem, we associate an angle $\\theta$ with $\\sqrt{a}$ such that $\\sin^2{\\theta} = a$. Consequently, the state $|\\Psi\\rangle$ can be rewritten as:\n", "\n", - "$$\\mathcal{A}|0\\rangle = |\\Psi \\rangle = \\sin(\\theta)|\\Psi_0\\rangle +\\cos(\\theta)|\\Psi_1\\rangle, \\tag{1}$$\n", + "$$\n", + "\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sin(\\theta)|\\Psi_0\\rangle + \\cos(\\theta)|\\Psi_1\\rangle. \\tag{1}\n", + "$$\n", "\n", - "\n", - "We have implemented and Python class called **MLAE** into the **QQuantLib/AE/maximum_likelihood_ae** module that allows us to implement the **MLAE** algorithm. In this section, we are going to describe the class step by step and explain the basics of the **MLAE** algorithm." + "We have implemented a Python class named `MLAE` in the **QQuantLib/AE/maximum_likelihood_ae** module, which provides tools to implement the **MLAE** algorithm. In this section, we will describe the structure of the class step by step and provide an overview of the fundamental principles behind the **MLAE** algorithm." ] }, { @@ -218,23 +227,26 @@ }, { "cell_type": "markdown", - "id": "d8721bad", + "id": "7d4d68ff", "metadata": {}, "source": [ - "For creating the corresponding object from class **MLAE** the following mandatory arguments should be provided:\n", + "To create an object from the **MLAE** class, the following mandatory arguments must be provided:\n", + "\n", + "1. `oracle`: A QLM `AbstractGate` or `QRoutine` that implements the oracle required for constructing the Grover operator.\n", + "2. `target`: The marked state in its binary representation, specified as a Python list.\n", + "3. `index`: A list of qubits that will be affected by the Grover operator.\n", + "\n", + "The `MLAE` class internally creates the Grover operator using the `grover` function from the module **QQuantLib/AA/amplitude_amplification**. Consequently, the arguments required for the `MLAE` class are similar to those of the `grover` function (for a detailed explanation, see the notebook **02_AmplitudeAmplification_Operators.ipynb**).\n", "\n", - "1. Oracle: QLM AbstractGate or QRoutine with the implementation of the Oracle for creating the Grover operator.\n", - "2. target: this is the marked state in binary representation as a Python list\n", - "3. index: list of the qubits affected by the Grover operator.\n", + "Additionally, an optional dictionary can be provided to configure the algorithm further. The keys for this dictionary include:\n", "\n", - "The MLAE class creates the Grover operator using the function *grover* from module **QQuantLib/AA/amplitude_amplification**. So the arguments of the **MLAE** class are similar to the arguments of this function (see notebook **02_AmplitudeAmplification_Operators.ipynb** for a detailed explanation)\n", + "- `qpu`: Specifies the QLM solver to be used. If not provided, the default solver will be used.\n", + "- `delta`: A float value to set the tolerance threshold for avoiding division-by-zero warnings during computations.\n", + "- `optimizer`: The optimizer to be used for solving the optimization problem within the MLAE algorithm (see below for more details).\n", + "- `schedule`: A scheduler that determines how the Grover operator is applied throughout the MLAE algorithm (see below for more details).\n", + "- `mcz_qlm`: A boolean flag indicating whether to use the QLM multi-controlled Z gate (`True`, default) or the multiplexor-based implementation (`False`).\n", "\n", - "Additionally, a dictionary with other arguments to configure the algorithm can be provided. Keys for the dictionary can be:\n", - "* qpu: QLM solver that will be used (if not provide default on will be used)\n", - "* delta: float for setting the tolerance for avoiding division by zero warnings\n", - "* optimizer: optimizer for solving the optimization problem of the MLAE algorithm (see below)\n", - "* schedule: scheduler for applying the Grover operator in the MLAE algorithm (see below)\n", - "* mcz_qlm: for using QLM multi-controlled Z gate (True, default) or using multiplexor implementation (False)" + "These configurations allow for fine-tuning the behavior of the MLAE algorithm to suit specific use cases and computational environments." ] }, { @@ -247,28 +259,35 @@ }, { "cell_type": "markdown", - "id": "d94f4a5d", + "id": "28b25b73", "metadata": {}, "source": [ - "To show how our class and the algorithm work, we will define the following amplitude estimation problem:\n", + "To demonstrate how the **MLAE** class and the algorithm work, we will define the following amplitude estimation problem:\n", "\n", - "$$|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right] \\tag{2}$$\n", + "$$\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", + "$$\n", "\n", - "So comparing (2) with (1):\n", + "By comparing equation (2) with equation (1):\n", "\n", - "$$\\sin(\\theta)|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle$$\n", + "$$\n", + "\\sin(\\theta)|\\Psi_0\\rangle = \\frac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}} |1\\rangle,\n", + "$$\n", "\n", - "and \n", + "and\n", "\n", - "$$\\cos(\\theta)|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$\n", + "$$\n", + "\\cos(\\theta)|\\Psi_1\\rangle = \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$\n", "\n", + "In this example, the target state is $ |1\\rangle $, whose binary representation is $ [0, 0, 1] $. This binary representation must be passed as a list to the `target` variable. Additionally, we need to specify the list of qubits on which the Grover operator will act. In this case, it is the entire register: $ [0, 1, 2] $.\n", "\n", - "The target state, in this case, is $|1\\rangle$. Its binary representation is $001$. This has to be passed to the target variable as a list. Moreover, we have to provide the list of qubits where we are acting, in this case is just $[0,1,2]$, the whole register. So mandatory arguments will be:\n", - "* oracle: QLM AbstractGate\n", - "* target: [0, 0, 1]\n", - "* index: [0, 1, 2]\n", + "Thus, the mandatory arguments for creating an instance of the **MLAE** class are:\n", + "- `oracle`: A QLM `AbstractGate` or `QRoutine` that implements the oracle.\n", + "- `target`: `[0, 0, 1]` (binary representation of the target state $ |1\\rangle $).\n", + "- `index`: `[0, 1, 2]` (list of qubits affected by the Grover operator).\n", "\n", - "Additionally, we will provide the qlm solver into the *qpu* key of an input Python dictionary" + "Furthermore, we will provide the QLM solver via the `qpu` key in an input Python dictionary to configure the algorithm." ] }, { @@ -305,23 +324,30 @@ "id": "9a2a5c94", "metadata": {}, "source": [ - "The foundation of any amplitude estimation algorithm is the Grover-like operator $\\mathcal{Q}$ of the oracle operator $\\mathcal{A}$:\n", + "The foundation of any amplitude estimation algorithm lies in the Grover-like operator $\\mathcal{Q}$, which is constructed from the oracle operator $\\mathcal{A}$ as follows:\n", "\n", - "$$\\mathcal{Q}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^{\\dagger}\\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right)$$\n", + "$$\n", + "\\mathcal{Q}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^\\dagger \\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right).\n", + "$$\n", "\n", - "This Grover-like operator has the following effect over our state $|\\Psi\\rangle$:\n", + "This operator has a specific effect on the state $|\\Psi\\rangle$, which can be expressed as:\n", "\n", - "$$\\mathcal{Q}^{m_k}|\\Psi\\rangle = \\mathcal{Q}^{m_k} \\mathcal{A} |0\\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle +\\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle,$$\n", + "$$\n", + "\\mathcal{Q}^{m_k} |\\Psi\\rangle = \\mathcal{Q}^{m_k} \\mathcal{A} |0\\rangle = \\sin\\left((2m_k + 1)\\theta\\right)|\\Psi_0\\rangle + \\cos\\left((2m_k + 1)\\theta\\right)|\\Psi_1\\rangle,\n", + "$$\n", "\n", - "for more information about the grover operator and the amplitude amplification algorithm check the notebook **02_AmplitudeAmplification_Operators.ipynb**.\n", + "where $m_k$ represents the number of times the Grover-like operator is applied. For a deeper understanding of the Grover operator and the amplitude amplification algorithm, refer to the notebook **02_AmplitudeAmplification_Operators.ipynb**.\n", "\n", - "The process of generating the corresponding Grover oracle is handled automatically in the class when we instance it. To see the Grover operator in action we can call the method *run_step* of the class. The input of the method is:\n", + "The process of generating the corresponding Grover operator is handled automatically within the **MLAE** class when an instance is created. To observe the Grover operator in action, you can use the method `run_step` of the class. The inputs for this method are:\n", "\n", - "* m_k: the number of times the Grover-like operator will be applied.\n", - "* n_k : number of shots.\n", + "- `m_k`: The number of times the Grover-like operator will be applied.\n", + "- `n_k`: The number of measurement shots.\n", "\n", "The method returns:\n", - "* h_k: number of positive outcomes" + "\n", + "- `h_k`: The number of positive outcomes (i.e., the number of times the target state $|\\Psi_0\\rangle$ is measured).\n", + "\n", + "This functionality allows for the iterative application of the Grover operator, enabling the estimation of the desired amplitude through repeated measurements and statistical analysis." ] }, { @@ -352,10 +378,15 @@ "id": "1dfc0347", "metadata": {}, "source": [ - "From the number of positive outcomes it is straightforward to estimate the probability of getting the state $|1\\rangle$ (the positive outcome):\n", - "$$ \\sin^2\\left((2m_k+1)\\hat{\\theta}\\right) = \\dfrac{h_k}{n_k} = \\hat{p}(|\\Psi_0\\rangle)\\approx p(|\\Psi_0\\rangle).$$\n", - "Here we use the $\\hat{}$ to distinguish between the estimated values from the real ones.\n", - "So we have a first estimation of our target amplitude:" + "From the number of positive outcomes, it is straightforward to estimate the probability of obtaining the state $ |1\\rangle $ (the positive outcome):\n", + "\n", + "$$\n", + "\\sin^2\\left((2m_k + 1)\\hat{\\theta}\\right) = \\frac{h_k}{n_k} = \\hat{p}(|\\Psi_0\\rangle) \\approx p(|\\Psi_0\\rangle).\n", + "$$\n", + "\n", + "Here, we use the $\\hat{}$ notation to distinguish between estimated values ($\\hat{\\theta}$, $\\hat{p}(|\\Psi_0\\rangle)$) and their true counterparts ($\\theta$, $p(|\\Psi_0\\rangle)$).\n", + "\n", + "Thus, we obtain an initial estimation of our target amplitude:" ] }, { @@ -398,34 +429,41 @@ }, { "cell_type": "markdown", - "id": "96f7aaab", + "id": "cd958155", "metadata": {}, "source": [ - "To improve our results we will throw different experiments and combine their information into a single result. To do so we will use the likelihood. First, what is the **likelihood**?\n", - "\n", - "In general, the likelihood $L(a|b)$ is the probability of obtaining $b$ conditioned to $a$, that is $L(a|b) = p(b|a)$.\n", + "To enhance our results, we will conduct multiple experiments and combine their information into a single, more accurate result. This process relies on the concept of **likelihood**. But first, what exactly is likelihood?\n", "\n", - "In our specific case, given the result of an experiment $h_k$ we want to know the probability of an angle $\\theta$ being the one that has generated it:\n", - "$$p(\\theta|h_k).$$\n", - "From all the possible values of theta, we will propose as a solution the one with the most probability.\n", + "In general, the **likelihood** $ L(a|b) $ represents the probability of observing $ b $ given $ a $. Mathematically, this can be expressed as:\n", + "$$\n", + "L(a|b) = p(b|a).\n", + "$$\n", "\n", - "As computing $p(\\theta|h_k)$ is not completely straightforward we will compute instead $p(h_k|\\theta)$ which is proportional to the other one by Bayes's theorem. The associated likelihood is $L(\\theta|h_k) = p(h_k|\\theta)$. The value of this likelihood is:\n", + "In our specific case, given the result of an experiment $ h_k $, we are interested in determining the probability that a particular angle $ \\theta $ generated this result:\n", + "$$\n", + "p(\\theta|h_k).\n", + "$$\n", "\n", - "$$L(\\theta|h_k) = \\sin^2\\left((2m_k+1)\\theta\\right)^{h_k}\\cos^2\\left((2m_k+1)\\theta\\right)^{n_k-h_k}.$$\n", + "From all possible values of $ \\theta $, we propose the one with the highest probability as our solution. However, computing $ p(\\theta|h_k) $ directly is not straightforward. Instead, we compute $ p(h_k|\\theta) $, which is proportional to $ p(\\theta|h_k) $ by Bayes' theorem. The associated likelihood is defined as:\n", + "$$\n", + "L(\\theta|h_k) = p(h_k|\\theta).\n", + "$$\n", "\n", - "This is because each measurement is independent of the other measurements, the probability of obtaining the state $|1\\rangle$ is given by $\\sin^2\\left((2m_k+1)\\theta\\right)$ and the probability of the state $|0\\rangle$ is given by $\\cos^2\\left((2m_k+1)\\theta\\right)$.\n", + "The value of this likelihood is given by:\n", + "$$\n", + "L(\\theta|h_k) = \\sin^2\\left((2m_k + 1)\\theta\\right)^{h_k} \\cos^2\\left((2m_k + 1)\\theta\\right)^{n_k - h_k}.\n", + "$$\n", "\n", - "To compute the likelihood for a given experiment we can use the function *likelihood*. The input of the method is:\n", + "This formula arises because each measurement is independent, the probability of obtaining the state $ |1\\rangle $ is $ \\sin^2\\left((2m_k + 1)\\theta\\right) $, and the probability of obtaining the state $ |0\\rangle $ is $ \\cos^2\\left((2m_k + 1)\\theta\\right) $.\n", "\n", - "* angle : the angle.\n", - "* m_k: the number of times the Grover-like operator will be applied.\n", - "* n_k : number of shots.\n", - "* h_k: number of positive outcomes\n", + "To compute the likelihood for a given experiment, you can use the function `likelihood`. The inputs for this function are:\n", + "- `angle`: The angle $ \\theta $ for which the likelihood is being computed.\n", + "- `m_k`: The number of times the Grover-like operator is applied.\n", + "- `n_k`: The total number of measurement shots.\n", + "- `h_k`: The number of positive outcomes (i.e., the number of times the target state $ |1\\rangle $ is measured).\n", "\n", - "The method returns:\n", - "* l_k: likelihood of the angle\n", - "\n", - "\n" + "The function returns:\n", + "- `l_k`: The likelihood of the given angle $ \\theta $." ] }, { @@ -475,28 +513,46 @@ }, { "cell_type": "markdown", - "id": "900d83ab", + "id": "727295ec", "metadata": {}, "source": [ - "As we see in the last graph, multiple values maximize the likelihood function. If we combine the information from different $(m_k,h_k)$ we can get better estimations.\n", + "As we observed in the previous graph, multiple values of $ \\theta $ can maximize the likelihood function. However, by combining information from different experiments $(m_k, h_k)$, we can achieve more accurate estimations.\n", + "\n", + "To achieve this, we define a **combined likelihood** as follows:\n", + "\n", + "$$\n", + "L(\\theta, \\mathbf{h}) = \\prod_{k=0}^M l_k(\\theta, h_k),\n", + "$$\n", + "\n", + "where:\n", + "$$\n", + "\\mathbf{h} = (h_0, h_1, \\dots, h_M).\n", + "$$\n", + "\n", + "Rather than directly solving the maximization problem, we reformulate it as an equivalent minimization problem by introducing the **cost function** $ C(\\theta) $, which is defined as:\n", + "\n", + "$$\n", + "C(\\theta) = -\\log\\left(L(\\theta, \\mathbf{h})\\right).\n", + "$$\n", "\n", - "Therefore we define a combined likelihood as:\n", - "$$L(\\theta,\\mathbf{h}) = \\prod_{k = 0}^M l_k(\\theta,h_k).$$\n", - "$$\\mathbf{h} = (h_0, h_1,...,h_M)$$\n", + "The process for computing the **cost function** involves the following steps:\n", "\n", - "Instead of dealing with the maximization problem, we define the equivalent minimization problem which substitutes the combined likelihood with the **cost function** $C$:\n", + "1. **Selecting the schedule of experiments**:\n", + " - First, we need to define the set of experiments $(m_k, h_k)$ using a **schedule**, which is a Python list containing two elements:\n", + " - **1st element**: A list specifying the number of applications of the Grover operator, e.g., $ m_k = [1, 2, 3, 5, 7] $.\n", + " - **2nd element**: A list specifying the number of measurement shots for each corresponding value in $ m_k $, e.g., $ n_k = [100, 200, 50, 50, 100] $.\n", + " - For example, the schedule would be:\n", + " $$\n", + " \\text{schedule} = [m_k, n_k] = [[1, 2, 3, 5, 7], [100, 200, 50, 50, 100]].\n", + " $$\n", "\n", - "$$C(\\theta) = -\\log\\left(L(\\theta,\\mathbf{h})\\right)$$\n", + "2. **Running the experiments**:\n", + " - For each pair $(m_k, n_k)$ in the schedule, execute the `run_step` method to obtain the corresponding number of positive outcomes $ h_k $. This step is automated by the `run_schedule` method of the class.\n", "\n", - "For computing the **Cost Function** following steps should be followed:\n", + "3. **Computing the cost function**:\n", + " - Using the results $(m_k, n_k, h_k)$ obtained from the experiments, compute the total cost function for different values of $ \\theta \\in [0, \\frac{\\pi}{2}] $ using the `cost_function` static method.\n", "\n", - "1. First we need to select the different $(m_k,h_k)$ experiments. This is done using the **schedule** which will be a Python list of 2 elements:\n", - " * 1st element: list with the number of applications of the Grover operator, for example m_k = [1, 2, 3, 5, 7] \n", - " * 2nd element list with the number of shots used for each application of the 1st element, for example n_k = [100, 200, 50, 50, 100]\n", - " * In the before examples the schedule will be: schedule =[m_k, n_k] = [[1, 2, 3, 5, 7], [100, 200, 50, 50, 100]]\n", - "2. For each pair ($m_k$, $n_k$) of the schedule run the *run_step* method and get the correspondent $h_k$. This will be done by the *run_schedule* method of the class \n", - "3. Using ($m_k$, $n_k$, $h_k$) we can compute the total cost function for different $\\theta \\in [0, \\frac{\\pi}{2}]$ using the *cost_function* static method.\n", - "\n" + "This approach allows us to systematically evaluate and minimize the cost function, leading to a more precise estimation of the target amplitude." ] }, { @@ -509,12 +565,17 @@ }, { "cell_type": "markdown", - "id": "4ea96084", + "id": "3ff0898c", "metadata": {}, "source": [ - "As we said, we want to combine the information from different experiments. In general, each experiment can be characterised by the number of applications of the Grover oracle $m_k$ and the number of shots $n_k$. A list of both is what we call a **schedule**.\n", + "As we mentioned earlier, our goal is to combine information from multiple experiments to improve the accuracy of our amplitude estimation. Each experiment can be characterized by two key parameters: \n", "\n", - "The schedule can be given when the class **MLAE** using the key *schedule*. " + "1. **$m_k$**: The number of applications of the Grover oracle.\n", + "2. **$n_k$**: The number of measurement shots.\n", + "\n", + "A collection of these pairs $(m_k, n_k)$ is referred to as a **schedule**. The schedule provides a structured way to define and organize the sequence of experiments.\n", + "\n", + "When creating an instance of the **MLAE** class, the schedule can be specified using the keyword argument `schedule`. This allows the algorithm to systematically execute the defined sequence of experiments and combine their results for more precise estimations." ] }, { @@ -605,7 +666,7 @@ "id": "2d7c8bf3", "metadata": {}, "source": [ - "Once we have the schedule properly configured we can use the *run_schedule* method for executing the application of the Grover operator following the schedule order. This method needs the schedule and provide and a array with the different results of $h_k$ for each pair ($m_k$, $n_k$) of the schedule.\n" + "Once the schedule is properly configured, we can utilize the `run_schedule` method to execute the application of the Grover operator according to the specified order in the schedule. This method requires the schedule as input and returns an array containing the results of $ h_k $ for each pair $(m_k, n_k)$ defined in the schedule.\n" ] }, { @@ -658,23 +719,22 @@ }, { "cell_type": "markdown", - "id": "c306146b", + "id": "092fe78a", "metadata": {}, "source": [ - "Now we have all the necessary input ($m_k$, $n_k$, $h_k$) for computing the desired **cost function** $C$. We can use the static method of the class **cost_function**. This method receives:\n", + "Now that we have all the necessary inputs ($m_k$, $n_k$, $h_k$), we can compute the desired **cost function** $C$. To do this, we use the static method `cost_function` of the **MLAE** class. This method accepts the following parameters:\n", "\n", - "* $\\theta$\n", - "* $m_k$\n", - "* $n_k$\n", - "* $h_k$\n", + "- `theta`: The angle for which the cost function is being evaluated.\n", + "- `m_k`: The number of applications of the Grover operator for each experiment.\n", + "- `n_k`: The number of measurement shots for each experiment.\n", + "- `h_k`: The number of positive outcomes (i.e., the number of times the target state was measured) for each experiment.\n", "\n", - "and computes the corresponding **Cost Function** for angle $\\theta$. \n", + "The method computes the **Cost Function** $C(\\theta)$ for the given angle $\\theta$.\n", "\n", - "So we can define an array of $\\theta$ between $[0, \\frac{\\pi}{2}]$ and get the **Cost Function** for each value\n", + "To evaluate the cost function over a range of angles, we can define an array of $\\theta$ values between $[0, \\frac{\\pi}{2}]$ and compute the cost function for each value in this array.\n", "\n", - "**Note** \n", - "\n", - "To avoid problems with angles 0 and $\\frac{\\pi}{2}$ we use the delta property of the **MLAE** class" + "#### Note:\n", + "To avoid numerical issues at the boundary angles $0$ and $\\frac{\\pi}{2}$, we utilize the `delta` property of the **MLAE** class. This ensures that the computations remain stable by introducing a small tolerance around these critical points." ] }, { @@ -710,10 +770,14 @@ }, { "cell_type": "markdown", - "id": "e09b85c5", + "id": "6a0b8f47", "metadata": {}, "source": [ - "We are going to use a little trick: We are going to use the *partial* function from *functools* package to create a new cost_function based on the *cost_function* method of the **MLAE** class. For this new cost_function, we are going to provide the obtained $m_k$, $n_k$ and $h_k$ as fixed values. So we only need to provide the angle to this new function to obtain the cost_function and the obtained $m_k$, $n_k$ and $h_k$ will be used as constants" + "We will employ a useful technique by using the `partial` function from the `functools` package to create a new cost function based on the `cost_function` method of the `MLAE` class. In this new cost function, we will fix the values of $m_k$, $n_k$, and $h_k$ that were obtained from the experiments. \n", + "\n", + "This approach allows us to simplify the computation process: when calling the new function, we only need to provide the angle $\\theta$ as a variable input. The previously obtained values of $m_k$, $n_k$, and $h_k$ will act as constants within this new function, ensuring they are consistently used in the calculation of the cost function.\n", + "\n", + "By doing so, we streamline the evaluation of the cost function for different angles $\\theta$, making it easier to find the optimal value that minimizes the cost." ] }, { @@ -769,16 +833,27 @@ "source": [ "### 2.5 Optimization of the Cost Function\n", "\n", - "So far we have the **cost function** $C$:\n", + "So far, we have defined the **cost function** $ C $ as:\n", + "\n", + "$$\n", + "C(\\theta) = -\\log\\left(L(\\theta, \\mathbf{h})\\right),\n", + "$$\n", + "\n", + "where $ L(\\theta, \\mathbf{h}) $ is the combined likelihood function based on the experimental results.\n", "\n", - "$$C(\\theta) = -\\log\\left(L(\\theta,\\mathbf{h})\\right)$$\n", + "The goal is to find the optimal angle $\\theta^*$ that minimizes the cost function:\n", "\n", - "The idea is to find the $\\theta^*$ that minimizes the cost function:\n", - "$$\\theta^* = \\arg \\min_{\\theta} C(\\theta)$$\n", + "$$\n", + "\\theta^* = \\arg \\min_{\\theta} C(\\theta).\n", + "$$\n", "\n", - "With this $\\theta^*$ we can compute the desired $a=sin^2(\\theta^*)$. \n", + "Once $\\theta^*$ is determined, we can compute the desired value of $ a $ using:\n", "\n", - "This can be done straightforwardly by finding the lower value of the cost_function array and the correspondent angle:" + "$$\n", + "a = \\sin^2(\\theta^*).\n", + "$$\n", + "\n", + "This computation can be performed straightforwardly by identifying the minimum value in the `cost_function` array and retrieving the corresponding angle $\\theta$. This approach allows us to efficiently estimate the target amplitude $ a $ based on the experimental data." ] }, { @@ -816,13 +891,13 @@ "id": "f2673c98", "metadata": {}, "source": [ - "So this straightforward approximation give us a good approximation but depends on the number of $\\theta$ we use to compute the cost function. \n", + "The straightforward approach described earlier provides a good approximation of the optimal angle $\\theta^*$, but its accuracy depends on the granularity of the $\\theta$ values used to compute the cost function. To achieve a more precise result, a better approach is to employ an optimization routine to find the minimum of the `cost_function`.\n", "\n", - "A better approach is to use an optimization routine for getting the minimum of the new *cost_function* function.\n", + "One effective method is to use the **brute force optimization** routine from the **SciPy optimization module**. This routine systematically evaluates the cost function over a specified range and step size, ensuring a thorough search for the global minimum. For more details on the `brute` optimization routine, you can refer to the official SciPy documentation:\n", "\n", - "For example from **scipy optimization** module we can use the **brute** optimization routine in order to do this as can be seen in the following cells:\n", + "[SciPy Optimize Brute Documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brute.html)\n", "\n", - "https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.brute.html\n" + "In the following cells, we demonstrate how to apply the `brute` optimization routine to find the minimum of the `cost_function` efficiently." ] }, { @@ -885,20 +960,21 @@ "source": [ "### 2.6 Complete Algorithm execution\n", "\n", - "**MLAE** class implements the step-by-step parts explained in the before sections in an automated way by using the *mlae* method. Once the class is created, the user can execute the *mlae* method to do a complete execution of **MLAE** algorithm. \n", - "\n", - "The inputs of this method are:\n", + "The **MLAE** class automates the step-by-step process explained in the previous sections through the `mlae` method. Once an instance of the class is created, users can execute the `mlae` method to perform a complete execution of the **MLAE** algorithm.\n", "\n", - "* schedule.\n", - "* optimizer: the optimizer should be passed as a **lambda function** which input will be a function on one variable (the angle to optimize).\n", + "#### Inputs:\n", + "- `schedule`: A predefined list specifying the number of Grover operator applications ($m_k$) and the corresponding number of measurement shots ($n_k$) for each experiment.\n", + "- `optimizer`: The optimization routine to be used, which should be passed as a **lambda function**. This function must accept another function (with one variable, the angle to optimize) as its input.\n", "\n", - "The outputs of this method are:\n", + "#### Outputs:\n", + "- `result`: The result returned by the optimizer, containing details about the optimization process, such as the optimal angle $\\theta^*$.\n", + "- `h_k`: A list of positive outcomes ($h_k$) obtained from each experiment conducted according to the schedule.\n", + "- `cost_function_partial`: A Python function representing the cost function where the values of $m_k$, $n_k$, and $h_k$ are fixed to the results obtained during the experiments.\n", "\n", - "* result: optimizer result\n", - "* h_k : list with positive outcomes of each experiment done according to the schedule\n", - "* cost_function_partial : python function with the cost function where the $m_k$, $n_k$ and the $h_k$ are fixed to the results obtained.\n", + "#### Additional Feature:\n", + "This method also updates the `optimizer_time` property of the class, which measures the time taken for the optimization process. This allows users to evaluate the computational efficiency of the algorithm.\n", "\n", - "Additionally, this method overwrites the **optimizer_time** property of the class that measures the time for the optimization process." + "By encapsulating all these functionalities into a single method, the `MLAE` class provides a streamlined and user-friendly interface for performing amplitude estimation with maximum likelihood techniques.\n" ] }, { @@ -1020,19 +1096,17 @@ }, { "cell_type": "markdown", - "id": "a09dc8b9", + "id": "31549aef", "metadata": {}, "source": [ - "To find the angle that minimizes the cost function we need to use a minimization algorithm. When the class is created the optimizer can be provided using the key *optimizer* and can be accessed with property *optimizer*. By default, the *optimizer* property will be initialized to the **brute** optimization algorithm from *scipy.optimize*.\n", - "\n", - "Additionally, we can define the optimizer after initialization by simply assigning the attribute *optimizer* to the optimizer we want.\n", + "To determine the angle that minimizes the cost function, a minimization algorithm must be employed. When creating an instance of the class, the optimizer can be specified using the `optimizer` key and can later be accessed via the `optimizer` property. By default, the `optimizer` is initialized to the **brute** optimization algorithm from the `scipy.optimize` module.\n", "\n", - "**NOTE**\n", + "Additionally, the optimizer can be redefined after initialization by simply assigning a new value to the `optimizer` attribute. This flexibility allows users to choose the most suitable optimization method for their specific problem.\n", "\n", - "The optimizer that we have to pass to the class needs to be a function of just one variable: the angle. This is straightforward using lambda functions. \n", + "#### Note:\n", + "The optimizer provided to the class must be a function of a single variable: the angle $\\theta$. This can be easily achieved using **lambda functions**, which allow for concise definition of the required optimization function.\n", "\n", - "\n", - "In this case, we will do an example with a differential-evolution algorithm from the **Scipy** library." + "In this example, we will demonstrate the use of the **differential evolution** algorithm from the **SciPy** library as the optimizer. Differential evolution is a global optimization technique that is particularly effective for complex cost functions." ] }, { @@ -1218,6 +1292,23 @@ "First, given an Oracle user should create the class:" ] }, + { + "cell_type": "markdown", + "id": "36f9bd7f", + "metadata": {}, + "source": [ + "In Section 2, the complete algorithm was explained in detail through a step-by-step breakdown. The methods described in that section were presented for pedagogical purposes only and are **not intended for direct use** by end users.\n", + "\n", + "For practical applications, users should exclusively utilize one of the following two methods provided by the class:\n", + "\n", + "- **`mlae` method**: In this case the method needs the schedule and optimizer as inputs.\n", + "- **`run` method**: This method executes the algorithm using the configuration provided when the `MLAE`class was instantiated..\n", + "\n", + "#### Creating an Instance of the Class:\n", + "\n", + "First, given an Oracle, users should create an instance of the class. This initialization step sets up the necessary components, such as the Oracle operator, target state, and qubit indices, ensuring the algorithm is properly configured for execution." + ] + }, { "cell_type": "code", "execution_count": null, @@ -1288,22 +1379,23 @@ }, { "cell_type": "markdown", - "id": "f54bc986", + "id": "a5c809f3", "metadata": {}, "source": [ "### 3.2 *run* method\n", "\n", - "Additionally, the user can configure all the properties of th **MLAE** class, and use directly the *run* method. This method executes the **mlae** method using the attributes of the *MLAE* class (*schedule* and *optimizer*) and returns the desired $a=sin^2(\\theta^*)$ value. Additionally, the *run* method populates following properties:\n", + "Additionally, users can configure all the properties of the **MLAE** class and directly invoke the `run` method. This method executes the `mlae` method using the attributes of the **MLAE** class, such as the `schedule` and `optimizer`, and returns the desired value $ a = \\sin^2(\\theta^*) $. \n", "\n", - "* *h_k*: positive outcomes for the schedule used.\n", - "* *theta*: estimated angle from a complete **MLAE** algorithm execution.\n", - "* *ae* amplitude estimation looked parameter extracted from *theta* property: $a=sin^2(\\theta^*)$.\n", - "* *partial_cost_function*: this is the cost_function where the $m_k$, $n_k$ and $h_k$ variables are fixed to the correspondent values of the used schedule and the obtained $m_k$ values. Only angle $\\theta$ can de given to this function.\n", - "* *run_time*: this is the elapsed time of a complete *run* method\n", + "Upon execution, the `run` method populates the following properties of the class:\n", "\n", - "**WARNING**\n", + "- `h_k`: A list of positive outcomes ($h_k$) corresponding to the schedule used.\n", + "- `theta`: The estimated angle $\\theta^*$ obtained from a complete execution of the **MLAE** algorithm.\n", + "- `ae`: The amplitude estimation parameter extracted from the `theta` property, calculated as $ a = \\sin^2(\\theta^*) $.\n", + "- `partial_cost_function`: A cost function where the variables $m_k$, $n_k$, and $h_k$ are fixed to their respective values from the used schedule. This function accepts only the angle $\\theta$ as input.\n", + "- `run_time`: The elapsed time for a complete execution of the `run` method.\n", "\n", - "*run* method was implemented for using the **brute** optimization algorithm from **scipy.optimize** (i.e. the default optimizer of the **MLAE** class). If the user wants to use other optimizer algorithms the method to use **should be** the *mlae* one!!!" + "#### Warning:\n", + "The `run` method was specifically designed to work with the **brute** optimization algorithm from the **scipy.optimize** module, which is the default optimizer for the **MLAE** class. If users wish to use alternative optimization algorithms, they **should use the `mlae` method instead** to ensure compatibility and correct functionality." ] }, { @@ -1402,12 +1494,17 @@ "id": "02ddd8eb", "metadata": {}, "source": [ - "When the *run* method is executed following class attributes are populated:\n", + "Upon executing the `run` method, the following class attributes are populated to provide detailed information about the algorithm's execution:\n", + "\n", + "- `circuit_statistics`: A Python dictionary containing statistics for each quantum circuit used during the algorithm's execution. Each key in the dictionary corresponds to a specific $m_k$ value, and its associated value is another dictionary that holds the complete statistical information of the circuit created for that $m_k$.\n", + "\n", + "- `schedule_pdf`: A pandas DataFrame that stores the complete schedule, including the values of $m_k$ (number of Grover operator applications) and $n_k$ (number of measurement shots), along with the corresponding measurements ($h_k$).\n", + "\n", + "- `oracle_calls`: The total number of oracle calls made during the entire execution of the algorithm.\n", + "\n", + "- `max_oracle_depth`: The maximum number of applications of the oracle throughout the algorithm's execution, representing the deepest level of Grover operator applications.\n", "\n", - "* *circuit_statistics*: Python dictionary with the statistics of each circuit used during the algorithm execution. Each key of the dictionary corresponds with a $m_K$ used and its associated value is a Python dictionary with the complete statistical information of the circuit created for the $m_k$ value.\n", - "* *schedule_pdf*: pandas DataFrame where the complete schedule ($m_k$ and $n_k$) and the correspondent measurements ($h_k$) are stored.\n", - "* *oracle_calls*: number of total oracle calls for a complete execution of the algorithm\n", - "* *max_oracle_depth*: maximum number of applications of the oracle for the complete execution of the algorithm.\n" + "These attributes provide valuable insights into the performance and resource usage of the algorithm, enabling users to analyze and optimize its behavior effectively." ] }, { @@ -1478,9 +1575,9 @@ ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1492,7 +1589,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/04-02_Classical_Phase_Estimation_Windows.ipynb b/misc/notebooks/04-02_Classical_Phase_Estimation_Windows.ipynb index cf29101..a45bf73 100644 --- a/misc/notebooks/04-02_Classical_Phase_Estimation_Windows.ipynb +++ b/misc/notebooks/04-02_Classical_Phase_Estimation_Windows.ipynb @@ -14,17 +14,25 @@ }, { "cell_type": "markdown", - "id": "c4f4d8f0", + "id": "36c31b35", "metadata": {}, "source": [ - "This notebook explains how to use Windows initialization with classical Quantum Phase Estimation to enhance the probability of measuring the eigenvalues.\n", "\n", - "This notebook uses the modules **QQuantLib/PE/windows_pe.py** and **QQuantLib/PE/classical_pe.py**.\n", + "This notebook explains how to use window initialization in combination with classical Quantum Phase Estimation (QPE) techniques to enhance the probability of accurately measuring eigenvalues.\n", "\n", - "The present notebook is based on the following references:\n", + "The implementation in this notebook relies on the modules **QQuantLib/PE/windows_pe.py** and **QQuantLib/PE/classical_pe.py**.\n", "\n", - "* *Gumaro Rendon, Taku Izubuchi and Yuta Kikuchi* (2021). Effects of Cosine Tapering Window on Quantum Phase Estimation. Phys. Rev. D **106**, https://link.aps.org/doi/10.1103/PhysRevD.106.034503\n", - "* *Sean Greenaway, William Pol and Sukin Sim* (2024). A case study against QSVT: assessment of quantum phase estimation improved by signal processing techniques. arXiv: https://arxiv.org/abs/2404.01396" + "The theoretical foundation for this work is based on the following references:\n", + "\n", + "- **Gumaro Rendon, Taku Izubuchi, and Yuta Kikuchi** \n", + " (2021). *Effects of Cosine Tapering Window on Quantum Phase Estimation.* \n", + " Phys. Rev. D_ **106**, \n", + " [DOI: 10.1103/PhysRevD.106.034503](https://link.aps.org/doi/10.1103/PhysRevD.106.034503)\n", + " \n", + " \n", + "- **Sean Greenaway, William Pol, and Sukin Sim** \n", + " *A Case Study Against QSVT: Assessment of Quantum Phase Estimation Improved by Signal Processing Techniques.*\n", + " arXiv preprint, [arXiv:2404.01396](https://arxiv.org/abs/2404.01396)(2024)." ] }, { @@ -67,19 +75,19 @@ "source": [ "## 1. Vanilla QPE\n", "\n", - "As explained in *04_Classical_Phase_Estimation_Class.ipynb* given a unitary operator $\\mathcal{Q}$ acting on an initial eigenestate $\\ket{\\Psi}$ such that:\n", - "\n", - "$$\\mathcal{Q} \\ket{\\Psi} = e^{2\\pi i\\theta}\\ket{\\Psi}$$\n", - "\n", - "the **QPE** algorithm allows us to estimate the eigenvalue $\\theta$.\n", - "\n", - "If the **QPE** algorithm is performed using $m$ auxiliary qubits the probability of measuring a state $\\ket{J}$ on them is given by:\n", - "\n", - "$$\\mathbf{P}_{\\ket{J}} = \\frac{1}{2^{2m}}\\frac{\\sin^2\\left( \\pi \\left(J-2^m \\theta\\right)\\right)}{\\sin^2\\left(\\frac{ \\pi}{2^m} \\left(J-2^m \\theta\\right)\\right)}$$\n", + "As explained in *04_Classical_Phase_Estimation_Class.ipynb*, given a unitary operator $\\mathcal{Q}$ acting on an initial eigenstate $\\ket{\\Psi}$ such that:\n", + "$$\n", + "\\mathcal{Q} \\ket{\\Psi} = e^{2\\pi i \\theta} \\ket{\\Psi},\n", + "$$\n", + "the **Quantum Phase Estimation (QPE)** algorithm allows us to estimate the eigenvalue $\\theta$.\n", "\n", - "Where $J={0, 1, \\cdots, 2^m-1}$.\n", + "If the **QPE** algorithm is performed using $m$ auxiliary qubits, the probability of measuring a state $\\ket{J}$ on them is given by:\n", + "$$\n", + "\\mathbf{P}_{\\ket{J}} = \\frac{1}{2^{2m}} \\frac{\\sin^2\\left( \\pi \\left(J - 2^m \\theta\\right)\\right)}{\\sin^2\\left(\\frac{\\pi}{2^m} \\left(J - 2^m \\theta\\right)\\right)},\n", + "$$\n", + "where $J \\in \\{0, 1, \\dots, 2^m - 1\\}$.\n", "\n", - "We can plot this probability function for $x=\\pi \\left(J-2^m \\theta\\right)$:\n" + "We can plot this probability function for $x = \\pi \\left(J - 2^m \\theta\\right)$:" ] }, { @@ -100,26 +108,21 @@ }, { "cell_type": "markdown", - "id": "dca334fc", + "id": "18e611b9", "metadata": {}, "source": [ - "As can be seen, when $x=0 \\rightarrow J \\sim 2^m\\theta$ the probability is very high and decreases fast when $x \\neq 0$.\n", + "As can be seen, when $x = 0 \\rightarrow J \\sim 2^m \\theta$, the probability is very high and decreases rapidly when $x \\neq 0$.\n", "\n", - "In a quantum device, the $m$ measured auxiliary qubits are converted to a discrete value so we can only estimate $\\theta$ with precision equal to $\\frac{1}{2^m}$.\n", + "In a quantum device, the $m$ measured auxiliary qubits are converted to a discrete value, so we can only estimate $\\theta$ with a precision equal to $\\frac{1}{2^m}$.\n", + "\n", + "Thus, two different cases arise:\n", + "\n", + "1. The eigenvalue $\\theta$ has an exact representation using the $m$ qubits.\n", + "2. The eigenvalue $\\theta$ does not have an exact representation using the $m$ qubits.\n", "\n", - "So 2 different cases appear here:\n", - "1. The $\\theta$ eigenvalue has an exact representation using the $m$ qubits\n", - "2. The $\\theta$ eigenvalue has not an exact representation using the $m$ qubits" - ] - }, - { - "cell_type": "markdown", - "id": "7f5c59fe", - "metadata": {}, - "source": [ "### 1.1 Exact $\\theta$\n", "\n", - "In the case that $\\theta$ has an exact representation using the $m$ auxiliary qubits then the **QPE** algorithm will have probability of 1 of returning a state $\\ket{J}$ such that $J = 2^m \\theta$ \n" + "In the case where $\\theta$ has an exact representation using the $m$ auxiliary qubits, the **QPE** algorithm will have a probability of 1 of returning a state $\\ket{J}$ such that $J = 2^m \\theta$." ] }, { @@ -170,18 +173,18 @@ }, { "cell_type": "markdown", - "id": "6bca166d", + "id": "9d3f39e4", "metadata": {}, "source": [ - "### 1.2 Non Exact $\\theta$\n", - "\n", - "In the case that $\\theta$ can not be represented using the $m$ auxiliary qubits, **QPE** can always bound the estimations in the following way:\n", + "### 1.2 Non-Exact $\\theta$\n", "\n", - "$$\\theta \\in \\left[\\theta_j, \\theta_{j+1}\\right]$$\n", - "\n", - "where $\\theta_i = \\frac{i}{2^m}$. In this case the precision of the estimation will be: $\\frac{1}{2^m}$\n", + "In the case where $\\theta$ cannot be represented using the $m$ auxiliary qubits, the **QPE** algorithm can still bound the estimations as follows:\n", + "$$\n", + "\\theta \\in \\left[\\theta_j, \\theta_{j+1}\\right],\n", + "$$\n", + "where $\\theta_i = \\frac{i}{2^m}$. In this case, the precision of the estimation will be $\\frac{1}{2^m}$.\n", "\n", - "The probability of measurement $\\theta_j$ won't be 1 anymore. In fact, the worst case scenario is when $\\theta$ is just in the middle of $\\theta_j$ and $\\theta_{j+1}$ in this case the probability of measure $\\theta_j$ decreases to $\\frac{4}{\\pi^2} \\sim 0.4$" + "The probability of measuring $\\theta_j$ will no longer be 1. In fact, the worst-case scenario occurs when $\\theta$ is exactly in the middle of $\\theta_j$ and $\\theta_{j+1}$. In this situation, the probability of measuring $\\theta_j$ decreases to $\\frac{4}{\\pi^2} \\sim 0.4$." ] }, { @@ -227,30 +230,30 @@ "\n", "**Can we increase this measurement probability when $\\theta$ is not exactly represented?**\n", "\n", - "Indeed we can, using window functions over the $m$ auxiliary qubits of the **QPE** algorithm." + "Indeed, we can, by using window functions over the $m$ auxiliary qubits of the **QPE** algorithm." ] }, { "cell_type": "markdown", - "id": "eb5b7598", + "id": "3b2c62e2", "metadata": {}, "source": [ - "## 2. Boosting Probability using window functions\n", - "\n", - "\n", - "In the **QPE** algorithm the $m$ auxiliary qubits are initialized with **Haddamard** gates (*04_Classical_Phase_Estimation_Class.ipynb*). So, before the controlled applications gates, we have an equiprobable superposition of states. The window function techniques apply to these qubits a different initialization, so the probability of measuring a $\\theta_m$ can be boosted.\n", + "## 2. Boosting Probability using Window Functions\n", "\n", + "In the **QPE** algorithm, the $m$ auxiliary qubits are initialized with **Hadamard** gates (see *04_Classical_Phase_Estimation_Class.ipynb*). Thus, before the controlled gate applications, we have an equiprobable superposition of states. The window function techniques modify this initialization, so the probability of measuring a $\\theta_m$ can be enhanced.\n", "\n", - "### 2.1 QPE with cosine window function. Theoric Probability.\n", + "### 2.1 QPE with Cosine Window Function: Theoretical Probability\n", "\n", - "One possible initialization for the $m$ auxiliary qubits is a cosine Window function. In this case the superposition of the different states is not equiprobable and will follow a cosine distribution (so $P_{\\ket{0}} = \\frac{\\sqrt{2} \\cos(0)}{\\sqrt{2^m}}$, $P_{\\ket{1}} =\\frac{\\sqrt{2} \\cos(\\frac{\\pi 1}{2^m})}{\\sqrt{2^m}}$ and so on).\n", + "One possible initialization for the $m$ auxiliary qubits is a cosine window function. In this case, the superposition of the different states is no longer equiprobable and instead follows a cosine distribution. For example:\n", + "- $P_{\\ket{0}} = \\frac{\\sqrt{2} \\cos(0)}{\\sqrt{2^m}}$,\n", + "- $P_{\\ket{1}} = \\frac{\\sqrt{2} \\cos\\left(\\frac{\\pi \\cdot 1}{2^m}\\right)}{\\sqrt{2^m}}$, and so on.\n", "\n", - "In the case of a Cosine window initialization the **QPE** algorithm provides a probability of measuring the state $\\ket{J}$ in the auxiliary qubits that is given by:\n", - "\n", - "$$\\mathbf{P}^{cos}_{\\ket{J}} = \\frac{2}{2^{2m}} \\sum_{x=-2^{m-1}}^{2^{m-1}-1}\n", - "\\sum_{y=-2^{m-1}}^{2^{m-1}-1} \\cos\\left(\\frac{\\pi x}{2^m}\\right)\\cos\\left(\\frac{\\pi y}{2^m}\\right)\\cos\\left(\\frac{2 \\pi (J-2^m \\theta)(x-y)}{2^m}\\right)$$\n", + "In the case of a cosine window initialization, the **QPE** algorithm provides a probability of measuring the state $\\ket{J}$ in the auxiliary qubits given by:\n", + "$$\n", + "\\mathbf{P}^{cos}_{\\ket{J}} = \\frac{2}{2^{2m}} \\sum_{x=-2^{m-1}}^{2^{m-1}-1} \\sum_{y=-2^{m-1}}^{2^{m-1}-1} \\cos\\left(\\frac{\\pi x}{2^m}\\right)\\cos\\left(\\frac{\\pi y}{2^m}\\right)\\cos\\left(\\frac{2 \\pi (J - 2^m \\theta)(x-y)}{2^m}\\right).\n", + "$$\n", "\n", - "We can compare this probability with the obtained with the classical one when the $\\theta$ is in the middle of $\\theta_m$ and $\\theta_{m+1}$:" + "We can compare this probability with the one obtained using the classical approach when $\\theta$ is in the middle of $\\theta_m$ and $\\theta_{m+1}$." ] }, { @@ -308,76 +311,71 @@ }, { "cell_type": "markdown", - "id": "0d109d82", + "id": "4d995e97", "metadata": {}, "source": [ - "### 2.2 Succes Probability and Failure Probability Definitions.\n", - "\n", - "The **success probability** is defined as the sum of the probabilities corresponding to the 2 nearest fixed points to $\\theta$. So, for a **QPE** with $m$ auxiliary qubits then \n", - "\n", - "$$\\theta \\in \\left[\\theta^m_j, \\theta^m_{j+1}\\right]$$ with $j=\\{0, 1, 2,\\cdots, 2^m-1\\}$\n", - "\n", - "Then \n", - "\n", - "$$P_{succes} = P(\\theta^m_j) + P(\\theta^m_{j+1})$$\n", + "### 2.2 Success Probability and Failure Probability Definitions\n", "\n", - "The **failure probability** will be defined as:\n", - "\n", - "$$P_{failure} = 1- P_{succes}$$\n", - "\n", - "\n", - "For a $m$ auxiliary qubits **QPE** if we perform a $m$-bit estimation of $\\theta$, $\\theta^{m,*}$, the probability of $|\\theta^{m,*}- \\theta| \\leq \\frac{1}{2^m}$ is given by $P_{succes}$:\n", - "\n", - "$$P_{succes} = P(\\theta^m_j) + P(\\theta^m_{j+1}) = P(|\\theta^{m,*}- \\theta| \\leq \\frac{1}{2^m})$$\n", - "\n", - "\n", - "In the case of classical **QPE** (this is **Haddamard** initialization) this **success probability** is given by: \n", + "The **success probability** is defined as the sum of the probabilities corresponding to the two nearest fixed points to $\\theta$. For a **QPE** with $m$ auxiliary qubits:\n", + "$$\n", + "\\theta \\in \\left[\\theta^m_j, \\theta^m_{j+1}\\right],\n", + "$$\n", + "where $j \\in \\{0, 1, 2, \\cdots, 2^m - 1\\}$.\n", "\n", - "$$P_{succes} > \\frac{8}{\\pi^2} \\sim 0.81$$\n", + "Then:\n", + "$$\n", + "P_{\\text{success}} = P(\\theta^m_j) + P(\\theta^m_{j+1}).\n", + "$$\n", "\n", + "The **failure probability** is defined as:\n", + "$$\n", + "P_{\\text{failure}} = 1 - P_{\\text{success}}.\n", + "$$\n", "\n", - "### 2.3 Boosting Probability using additional qubits.\n", + "For an $m$-auxiliary-qubit **QPE**, if we perform an $m$-bit estimation of $\\theta$, denoted as $\\theta^{m,*}$, the probability that $|\\theta^{m,*} - \\theta| \\leq \\frac{1}{2^m}$ is given by $P_{\\text{success}}$:\n", + "$$\n", + "P_{\\text{success}} = P(\\theta^m_j) + P(\\theta^m_{j+1}) = P\\left(|\\theta^{m,*} - \\theta| \\leq \\frac{1}{2^m}\\right).\n", + "$$\n", "\n", - "One way of boosting this **success probability** for $m$ auxiliary qubits is using $p$ additional qubits so that the number of auxiliary qubits will be then $m+p$. Then when using the **QPE** algorithm, instead of creating a measurement histogram of $2 ^{m+p}$ bins, a histogram of $2^m$ bins will be built and the **success probability** of obtaining $|\\theta^{m,*}- \\theta| \\leq \\frac{1}{2^m}$ will be boosted over the original limit.\n", + "In the case of classical **QPE** (i.e., **Hadamard** initialization), this **success probability** is given by:\n", + "$$\n", + "P_{\\text{success}} > \\frac{8}{\\pi^2} \\sim 0.81.\n", + "$$\n", "\n", - "For the **QPE** if we want a $P_{succes} = 1 - \\delta$ the theory says that the number of additional qubits required depends on the window function:\n", + "### 2.3 Boosting Probability Using Additional Qubits\n", "\n", - "1. Haddamard window function: $p \\sim \\log\\left(\\frac{1}{\\delta}\\right)$.\n", - "2. Cosine and Sine window functions: $p \\sim \\log\\left(\\frac{1}{\\delta^{\\frac{1}{3}}}\\right)$\n", - "3. Kaiser window functions: $p \\sim \\log\\log\\left(\\frac{1}{\\delta}\\right)$\n", + "One way to boost the **success probability** for $m$ auxiliary qubits is to use $p$ additional qubits, making the total number of auxiliary qubits $m + p$. When using the **QPE** algorithm, instead of creating a measurement histogram with $2^{m+p}$ bins, a histogram with $2^m$ bins is built. This approach boosts the **success probability** of obtaining $|\\theta^{m,*} - \\theta| \\leq \\frac{1}{2^m}$ beyond the original limit.\n", "\n", + "For the **QPE**, if we aim for a $P_{\\text{success}} = 1 - \\delta$, the theory indicates that the number of additional qubits required depends on the window function:\n", + "1. **Hadamard window function**: $p \\sim \\log\\left(\\frac{1}{\\delta}\\right)$,\n", + "2. **Cosine and Sine window functions**: $p \\sim \\log\\left(\\frac{1}{\\delta^{\\frac{1}{3}}}\\right)$,\n", + "3. **Kaiser window functions**: $p \\sim \\log\\log\\left(\\frac{1}{\\delta}\\right)$.\n", "\n", - "The **CQPE** class from **QQuantLib/PE/classical_qpe** module allows the user to change the auxiliary qubit initialization for dealing with window functions. In the rest of the notebook, we explain how to provide windows functions to the **CQPE** class.\n" + "The **CQPE** class from the **QQuantLib/PE/classical_qpe** module allows users to change the auxiliary qubit initialization to incorporate window functions. In the remainder of this notebook, we explain how to provide window functions to the **CQPE** class." ] }, { "cell_type": "markdown", - "id": "ba35f866", + "id": "69f2156b", "metadata": {}, "source": [ - "## 3. Pre-implemented Window functions\n", - "\n", - "Under the module **QQuantLib/PE/windows_pe** we have implemented three different windows functions:\n", + "## 3. Pre-implemented Window Functions\n", "\n", + "Under the module **QQuantLib/PE/windows_pe**, we have implemented three different window functions:\n", "1. Cosine window\n", "2. Sine window\n", "3. Kaiser window\n", "\n", - "Present section explains how to use them." - ] - }, - { - "cell_type": "markdown", - "id": "69f2156b", - "metadata": {}, - "source": [ - "### 3.1 Cosine Window function.\n", + "The present section explains how to use them.\n", "\n", - "When using a **Cosine Window** over $m$ qubits the state at the end will be:\n", + "### 3.1 Cosine Window Function\n", "\n", - "$$\\sum_{x=-2^{m-1}}^{2^{m-1}-1} \\frac{\\sqrt{2} \\cos\\left( \\frac{\\pi x}{2 ^m}\\right)}{\\sqrt{2^m}}\\ket{x}$$\n", + "When using a **Cosine Window** over $m$ qubits, the state at the end will be:\n", + "$$\n", + "\\sum_{x=-2^{m-1}}^{2^{m-1}-1} \\frac{\\sqrt{2} \\cos\\left( \\frac{\\pi x}{2^m}\\right)}{\\sqrt{2^m}}\\ket{x}.\n", + "$$\n", "\n", - "The **cosine_window** from **QQuantLib.PE.windows_pe** function creates an **AbstractGate** that implements this functionality into a quantum circuit given an input number of qubits." + "The `cosine_window` function from **QQuantLib.PE.windows_pe** creates an `AbstractGate` that implements this functionality into a quantum circuit, given an input number of qubits." ] }, { @@ -485,22 +483,17 @@ }, { "cell_type": "markdown", - "id": "265f39f8", + "id": "3c2bb89c", "metadata": {}, "source": [ - "### 3.2 Sine Window function.\n", - "\n", - "When using a **Sine Window** over $m$ qubits the state at the end will be:\n", + "### 3.2 Sine Window Function\n", "\n", - "$$\\sum_{x=0}^{2^m-1}\n", - "\\sin\\left(\n", - "\\frac{\\pi x}{2^m +1}\n", - "\\right)\n", - "\\ket{x}\n", + "When using a **Sine Window** over $m$ qubits, the state at the end will be:\n", + "$$\n", + "\\sum_{x=0}^{2^m-1} \\sin\\left( \\frac{\\pi x}{2^m + 1} \\right) \\ket{x}.\n", "$$\n", "\n", - "\n", - "The **sine_window** from **QQuantLib.PE.windows_pe** function creates an **AbstractGate** that implements this functionality into a quantum circuit given an input number of qubits." + "The `sine_window` function from **QQuantLib.PE.windows_pe** creates an `AbstractGate` that implements this functionality into a quantum circuit, given an input number of qubits." ] }, { @@ -583,46 +576,39 @@ }, { "cell_type": "markdown", - "id": "9e611e21", + "id": "bc5d82f9", "metadata": {}, "source": [ - "### 3.3 Kaiser Window function.\n", + "### 3.3 Kaiser Window Function\n", "\n", - "When using a **Kaiser Window** over $m$ qubits the state at the end will be:\n", - "\n", - "$$\\sum_{x=-2^{m-1}}^{2^{m-1}}\n", - "\\frac{1}{2^m}\n", - "\\frac{I_0 \\left(\\pi \\alpha \\sqrt{1-\\left(x/2^{m-1}\\right)^2} \\right)}{I_0(\\pi\\alpha)} \\ket{x}\n", + "When using a **Kaiser Window** over $m$ qubits, the state at the end will be:\n", "$$\n", + "\\sum_{x=-2^{m-1}}^{2^{m-1}} \\frac{1}{2^m} \\frac{I_0 \\left(\\pi \\alpha \\sqrt{1-\\left(x/2^{m-1}\\right)^2} \\right)}{I_0(\\pi\\alpha)} \\ket{x},\n", + "$$\n", + "where $\\alpha$ is an input parameter and $I_0$ is the modified Bessel function of the first kind of order 0.\n", "\n", - "Where $\\alpha$ is an input parameter and $I_0$ is the modified Bessel function of the first kind of order 0.\n", - "\n", - "The **kaiser_window** from **QQuantLib.PE.windows_pe** function creates an **AbstractGate** that implements this functionality into a quantum circuit given an input number of qubits and a parameter $\\alpha$.\n", - "\n", - "The **kaiser_window** uses the *load_probability* from **QQuantLib.DL** for loading the corresponding Kaiser probabilities. \n", - "\n", - "The **kaiser_array** from **QQuantLib.PE.windows_pe** creates these probabilities. This function returns a pandas DataFrame with the following columns:\n", + "The `kaiser_window` function from **QQuantLib.PE.windows_pe** creates an `AbstractGate` that implements this functionality into a quantum circuit, given an input number of qubits and a parameter $\\alpha$.\n", "\n", - "* **Int_neg**: the domain in positive and negative integers (this is the default domain presented in the formula)\n", - "* **Prob**: the probability of the Kaiser window function (this is the square of the Amplitudes of the formula properly normalized)\n", - "* **Int**: the domain but only in positive integers.\n", + "The `kaiser_window` uses the `load_probability` from **QQuantLib.DL** for loading the corresponding Kaiser probabilities. The `kaiser_array` function from **QQuantLib.PE.windows_pe** creates these probabilities. This function returns a pandas DataFrame with the following columns:\n", "\n", - "To move between the completely positive integer domain and the positive and negative integers domain the following transformation must be used:\n", + "- `Int_neg`: The domain in positive and negative integers (this is the default domain presented in the formula).\n", + "- `Prob`: The probability of the Kaiser window function (this is the square of the Amplitudes of the formula, properly normalized).\n", + "- `Int`: The domain but only in positive integers.\n", "\n", - "$$\\ket{y}= \\left\\{\n", - "\\begin{array}{ll}\n", - " \\ket{x=y} & \\left(0 \\leq y \\leq \\frac{2^m}{2} -1 \\right) \\\\\n", - " \\ket{x=y-2^m} & \\left(\\frac{2^m}{2} \\leq y \\leq 2^m -1 \\right) \\\\\n", - "\\end{array} \n", - "\\right.$$\n", - "\n", - "with $y \\in \\{0, 1, 2, \\cdots, 2^m-1\\}$\n", - "\n", - "So the *Int_neg* column represents the $\\ket{x}$ basis and the *Int* column represents the $\\ket{y}$ basis.\n", + "To move between the completely positive integer domain ($\\ket{y}$) and the positive and negative integers domain ($\\ket{x}$), the following transformation must be used:\n", + "$$\n", + "\\ket{y}= \n", + "\\begin{cases} \n", + "\\ket{x=y}, & \\text{if } 0 \\leq y \\leq \\frac{2^m}{2} - 1, \\\\\n", + "\\ket{x=y-2^m}, & \\text{if } \\frac{2^m}{2} \\leq y \\leq 2^m - 1.\n", + "\\end{cases}\n", + "$$\n", + "with $y \\in \\{0, 1, 2, \\cdots, 2^m-1\\}$.\n", "\n", - "**BE AWARE**\n", + "Thus, the `Int_neg` column represents the $\\ket{x}$ basis, and the `Int` column represents the $\\ket{y}$ basis.\n", "\n", - "The order of the pandas DataFrame is given by the *Int* column so **IT IS NOT ORDERED USING THE DEFAULT DOMAIN**" + "**BE AWARE**: \n", + "The order of the pandas DataFrame is given by the `Int` column, so **IT IS NOT ORDERED USING THE DEFAULT DOMAIN**." ] }, { @@ -665,7 +651,9 @@ "id": "d402ad3a", "metadata": {}, "source": [ - "Now we can build the quantum circuit using the *kaiser_window* function. Under the hood this function uses the *kaiser_array* function for building the pandas DataFrame with the Kaiser window probabilities (properly normalized) and provide them to the *load_probability* function from **QQuantLib.DL** for building the corresponding Quantum Routine. The probability provided to the function are ordered following the *Int* column (this is completely positive integer domain). **BE AWARE** this is not the default order in the Kaiser window definition." + "Now we can build the quantum circuit using the `kaiser_window` function. Under the hood, this function uses the `kaiser_array` function to build a pandas DataFrame with the Kaiser window probabilities (properly normalized) and provides them to the `load_probability` function from **QQuantLib.DL** to build the corresponding Quantum Routine. The probabilities provided to the function are ordered following the *Int* column (this is the completely positive integer domain). \n", + "\n", + "**BE AWARE**: This is not the default order in the Kaiser window definition." ] }, { @@ -699,7 +687,7 @@ "source": [ "#### BE AWARE\n", "\n", - "When using window functions we are going to use the **Int_lsb** field of the output result dataframe as the correct domain!!!\n" + "When using window functions we are going to use the `Int_lsb` field of the output result dataframe as the correct domain!!!\n" ] }, { @@ -722,17 +710,15 @@ "id": "be21e479", "metadata": {}, "source": [ - "## 4. Using Window functions with CQPE class\n", - "\n", + "## 4. Using Window Functions with CQPE Class\n", "\n", - "The window functions can be used with the **CQPE** class from **QQuantLib.PE.classical_qpe** in an easy way. The only modification is added to the input dicitionary the *window* key. \n", + "The window functions can be used with the **CQPE** class from **QQuantLib.PE.classical_qpe** in a straightforward manner. The only modification required is to add the `window` key to the input dictionary.\n", "\n", - "The following values can be provided to this *window* key:\n", + "The following values can be provided for this *window* key:\n", + "1. An `AbstractGate` containing the quantum implementation of the desired window function.\n", + "2. A string. In this case, one of the pre-implemented window `AbstractGates` will be used.\n", "\n", - "1. *AbstractGate* with the quantum implementation of loading the window function.\n", - "2. String. In this case, some of the pre implemented window AbstractGates are used.\n", - "\n", - "We are going to explain the different options but first we are going to set a simple **QPE** problem." + "We will explain the different options below. First, let us set up a simple **QPE** problem." ] }, { @@ -835,7 +821,7 @@ "source": [ "#### 4.2.1 Original Behavior\n", "\n", - "If not *window* key is provided the **Classical QPE** is used." + "If not `window` key is provided the **Classical QPE** is used." ] }, { @@ -872,22 +858,22 @@ }, { "cell_type": "markdown", - "id": "eb329168", + "id": "4611b8b7", "metadata": {}, "source": [ "#### 4.2.2 Providing an AbstractGate\n", "\n", - "An AbstractGate that implements the desired window function should be passed to the *window* key of the input dictionary.\n", - "\n", - "**BE AWARE**\n", + "An `AbstractGate` that implements the desired window function should be passed to the `window` key of the input dictionary.\n", "\n", - "The domain of the desired window function **MUST BE** the *Int_lsb* not the *Int*. If the window function loads a probability with domain *Int* the **QPE WON'T WORK**\n", + "**BE AWARE**: \n", + "The domain of the desired window function **MUST BE** `Int_lsb`, not `Int`. If the window function loads a probability with domain `Int`, the **QPE WON'T WORK**.\n", "\n", - "In addition to the *window* key another key should be provided to the input dictionary: the *last_control_change*.\n", + "In addition to the `window` key, another key should be provided in the input dictionary: `last_control_change`. \n", "\n", - "When the window function is defined over negative and positive integers (like Kaiser or Cosine) the value of this key **MUST BE** se to *True*. If the window function is defined over positive integers (like sine or the Haddamard) this should be set to *False* (this is the default behaviour).\n", + "- When the window function is defined over negative and positive integers (e.g., Kaiser or Cosine), the value of this key **MUST BE** set to `True`. \n", + "- If the window function is defined over positive integers (e.g., Sine or Hadamard), this should be set to `False` (this is the default behavior). \n", "\n", - "This is because the last controlled operation should be inverted when the function is defined over negative numbers.\n" + "This is because the last controlled operation should be inverted when the function is defined over negative numbers." ] }, { @@ -1053,11 +1039,11 @@ "\n", "If the user wants to use one of the 3 pre-implemented window functions (cosine, sine or Kaiser) a string can be passed to the *window* key. In this case, the string can be:\n", "\n", - "* \"Cosine\", \"cosine\", \"cos\": for using the Cosine window.\n", - "* \"Sine\", \"sine\", \"sin\": for using the Sine window\n", - "* \"Kaiser\", \"kaiser\", \"kais\": for using Kaiser window. In this case, an additional *kaiser_alpha* key with the desired $\\alpha$ should be provided.\n", + "* `Cosine`, `cosine`, `cos`: for using the Cosine window.\n", + "* `Sine`, `sine`, `sin`: for using the Sine window\n", + "* `Kaiser`, `kaiser`, `kais`: for using Kaiser window. In this case, an additional `kaiser_alpha` key with the desired $\\alpha$ should be provided.\n", "\n", - "If a string is provided then the *last_control_change* is not mandatory anymore (code deals with this depending on the input string).\n" + "If a string is provided then the `last_control_change` is not mandatory anymore (code deals with this depending on the input string).\n" ] }, { @@ -1218,20 +1204,20 @@ }, { "cell_type": "markdown", - "id": "8ca54f7e", + "id": "675fb27a", "metadata": {}, "source": [ - "## 5. Boosting Succes Probability\n", + "## 5. Boosting Success Probability\n", "\n", - "Now we can use the window functions for increasing the **success probability** for a fixed precision $\\epsilon = \\frac{1}{2^m}$. The procedure is:\n", + "Now we can use the window functions to increase the **success probability** for a fixed precision $\\epsilon = \\frac{1}{2^m}$. The procedure is as follows:\n", "\n", "1. Fix a desired window function.\n", "2. Fix the desired precision using $m$ qubits.\n", "3. Execute **window QPE** with additional $p$ qubits.\n", - "4. The result will have $2^{m+p}$ posible solutions. We need to resample to the original $2^m$ solutions.\n", + "4. The result will have $2^{m+p}$ possible solutions. We need to resample to the original $2^m$ solutions.\n", "5. Compute the success probability using the resampled $2^m$ solutions.\n", "\n", - "Instead of using the **success probability** we are going to use the **failure probability** (because it has better visualization but the conclusions are the same)." + "Instead of using the **success probability**, we are going to use the **failure probability** (since it provides better visualization, but the conclusions remain the same)." ] }, { @@ -1681,7 +1667,7 @@ "source": [ "### 6.2 Cosine Window QPE\n", "\n", - "The input dictionary for the **CQPEAE** class will have the same keys than the **CQPE**." + "The input dictionary for the `CQPEAE` class will have the same keys than the `CQPE`." ] }, { @@ -1718,7 +1704,7 @@ "source": [ "### 6.3 Sine Window QPE\n", "\n", - "The input dictionary for the **CQPEAE** class will have the same keys than the **CQPE**." + "The input dictionary for the `CQPEAE` class will have the same keys than the `CQPE`." ] }, { @@ -1756,7 +1742,7 @@ "source": [ "### 6.4 Kaiser Window QPE\n", "\n", - "The input dictionary for the **CQPEAE** class will have the same keys than the **CQPE**." + "The input dictionary for the `CQPEAE` class will have the same keys than the `CQPE`." ] }, { diff --git a/misc/notebooks/04_Classical_Phase_Estimation_Class.ipynb b/misc/notebooks/04_Classical_Phase_Estimation_Class.ipynb index 0ed33df..7050d47 100644 --- a/misc/notebooks/04_Classical_Phase_Estimation_Class.ipynb +++ b/misc/notebooks/04_Classical_Phase_Estimation_Class.ipynb @@ -2,31 +2,24 @@ "cells": [ { "cell_type": "markdown", - "id": "91740961", + "id": "2f1c9d88", "metadata": {}, "source": [ - "# Classical Phase Estimation (with QFT) module" - ] - }, - { - "cell_type": "markdown", - "id": "c4f4d8f0", - "metadata": {}, - "source": [ - "The present notebook reviews the classical **Quantum Phase Estimation Algorithm**, which using the inverse of **Quantum Fourier Transform** ($\\mathcal{QFT}^{\\dagger}$), allows the estimation of the self values (phases) of a unitary operator. We will call this algorithm **CQPE** from now. The **CQPE** was developed into module *classical_qpe.py* of the package *PE* of the present library *QQuantLib* (**QQuantLib/PE/classical_pe.py**). \n", + "# Classical Phase Estimation (with QFT) Module\n", "\n", - "This algorithm was developed as a Python class called: *CQPE* inside the **QQuantLib/PE/classical_qpe.py**." - ] - }, - { - "cell_type": "markdown", - "id": "f57f5478", - "metadata": {}, - "source": [ - "The present notebook and module are based on the following references:\n", + "The present notebook reviews the classical **Quantum Phase Estimation Algorithm**, which utilizes the inverse of the **Quantum Fourier Transform** ($\\mathcal{QFT}^\\dagger$) to estimate the eigenvalues (phases) of a unitary operator. From now on, we will refer to this algorithm as **CQPE**. The **CQPE** algorithm is implemented in the module `classical_qpe.py` of the package `PE` within the *QQuantLib* library (**QQuantLib/PE/classical_qpe.py**).\n", + "\n", + "This algorithm was developed as a Python class named `CQPE` inside the file **QQuantLib/PE/classical_qpe.py**.\n", + "\n", + "---\n", "\n", - "* *Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000). Quantum amplitude amplification and estimation.AMS Contemporary Mathematics Series, 305. https://arxiv.org/abs/quant-ph/0005055v1*\n", - "* NEASQC deliverable: *D5.1: Review of state-of-the-art for Pricing and Computation of VaR https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf*\n" + "## References\n", + "\n", + "The content of this notebook and its associated module are based on the following references:\n", + "\n", + "- **Brassard, G., Hoyer, P., Mosca, M., & Tapp, A. (2000).** Quantum amplitude amplification and estimation. AMS Contemporary Mathematics Series, 305. [arXiv:quant-ph/0005055v1](https://arxiv.org/abs/quant-ph/0005055v1)\n", + " \n", + "- **NEASQC Deliverable:** D5.1: Review of state-of-the-art for Pricing and Computation of VaR. [PDF](https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf)" ] }, { @@ -97,73 +90,60 @@ }, { "cell_type": "markdown", - "id": "d3c17b2d", - "metadata": {}, - "source": [ - "## 1. Initial Inputs" - ] - }, - { - "cell_type": "markdown", - "id": "c7cdcc17", + "id": "b3ded3e0", "metadata": {}, "source": [ - "For using the *ClassicalPE* python class inside the **QQuantLib/PE/classical_pe** module 2 mandatory inputs should be provided:\n", + "## 1. Example to use\n", "\n", - "* 1. Initial State: this will be the initial quantum state needed for applying the Unitary Operator.\n", - "* 2. Unitary operator: the operator whose phase we want to estimate.\n", + "To illustrate how the `CQPE` class works, we will use the **Iterative Quantum Phase Estimation (IQPE)** example from the Qiskit textbook:\n", "\n", - "To explain how the *ClassicalPE* class works we are going to use the **IQPE** example from the Qiskit textbook:\n", + "- [Qiskit Tutorial: IQPE](https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb)\n", "\n", - "https://qiskit.org/textbook/ch-labs/Lab04_IterativePhaseEstimation.html\n", + "This notebook contains 2 different examples:\n", + "- **IPE example with a 1-qubit gate for U**\n", + "- **IPE example with a 2-qubit gate**\n", "\n", - "https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb\n", + "The sections 2 and 3 will explain, step by step, how to use the `CQPE` class for solve the **IPE example with a 1-qubit gate for U** example. \n", "\n", - "We are going to reproduce the section **IPE example with a 1-qubit gate for U** from the Qiskit example.\n", - "\n", - "In this section, we are going to create these two mandatory inputs!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "020cdbc6", - "metadata": {}, - "outputs": [], - "source": [ - "#Number Of Qubits\n", - "n_qbits = 1" - ] - }, - { - "cell_type": "markdown", - "id": "ea46b380", - "metadata": {}, - "source": [ - "### 1.1 Initial State" + "Section 4 will explain how to use the library solve the **IPE example with a 2-qubit gate**." ] }, { "cell_type": "markdown", - "id": "47b0209a", + "id": "22dbece5", "metadata": {}, "source": [ - "Initial State can be:\n", - "1. QLM QRoutine\n", - "2. QLM gate (or abstract gate)\n", + "## 2. State and Unitary operator.\n", "\n", - "In the Qiskit example, the initial state will be $|1\\rangle$. \n", + "In the **IPE example with a 1-qubit gate for U** from the Qiskit textbook, the main problem is to find the phase for the 1-qubit unitary operator. They use the $\\mathcal{S}$ gate that has the following behaviour:\n", "\n", - "The following cell creates this initial state:" + "$$\n", + "\\mathcal{S}|1\\rangle = e^{i\\frac{\\pi}{2}}|1\\rangle.\n", + "$$\n", + "\n", + "This means that the $\\mathcal{S}$ gate introduces a phase shift of $\\frac{\\pi}{2}$ radians to the state $|1\\rangle$, leaving it unchanged otherwise.\n", + "\n", + "To use the `CQPE` Python class from the module **QQuantLib/PE/classical_pe**, two mandatory inputs must be provided:\n", + "\n", + "1. **Initial State**: This is the initial quantum state required for applying the unitary operator ($|1\\rangle$ in the Qiskit example). The initial state can be provided as:\n", + " - A QLM QRoutine.\n", + " - A QLM gate (or abstract gate).\n", + "2. **Unitary Operator**: This is the operator whose phase we aim to estimate ($\\mathcal{S}$ gate in the Qiskit example). The unitary operator can be provided as:\n", + " - A QLM QRoutine.\n", + " - A QLM gate (or abstract gate). \n", + "\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "b532fe74", + "id": "3367bb1f", "metadata": {}, "outputs": [], "source": [ + "# Initial quantum state\n", + "#Number Of Qubits\n", + "n_qbits = 1\n", "initial_state = qlm.QRoutine()\n", "q_bits = initial_state.new_wires(n_qbits)\n", "for i in range(n_qbits):\n", @@ -173,52 +153,30 @@ { "cell_type": "code", "execution_count": null, - "id": "7ea6baae", + "id": "4b499f06", "metadata": { - "scrolled": true + "scrolled": false }, "outputs": [], "source": [ "%qatdisplay initial_state --svg" ] }, - { - "cell_type": "markdown", - "id": "131043e7", - "metadata": {}, - "source": [ - "### 1.2 Unitary operator." - ] - }, - { - "cell_type": "markdown", - "id": "3e2c5873", - "metadata": {}, - "source": [ - "The unitary operator can be:\n", - "\n", - "1. QLM QRoutine\n", - "2. QLM gate (or abstract gate)\n", - "\n", - "In the Qiskit example, the unitary operator is the $\\mathcal{S}$ gate. So the application of the unitary operator over the initial state will be:\n", - "\n", - "$$\\mathcal{S}|1\\rangle = e^{i\\frac{\\pi}{2}}|1\\rangle$$" - ] - }, { "cell_type": "code", "execution_count": null, - "id": "43667149", + "id": "b30c1e91", "metadata": {}, "outputs": [], "source": [ + "# unitary operator\n", "unitary_operator = qlm.PH(np.pi/2.0) " ] }, { "cell_type": "code", "execution_count": null, - "id": "8daa3929", + "id": "c096de02", "metadata": {}, "outputs": [], "source": [ @@ -227,52 +185,40 @@ }, { "cell_type": "markdown", - "id": "0d586763", - "metadata": {}, - "source": [ - "## 2. Class CQPE: classical Quantum Phase Estimation algorithm step by step " - ] - }, - { - "cell_type": "markdown", - "id": "cf18d7f8", + "id": "f047e0c8", "metadata": {}, "source": [ - "The problem of phase estimation can be stated as follows. Given an initial state $\\left|\\Psi \\right\\rangle$ and a phase operator $\\mathcal{Q}$ such that:\n", + "## 3. Class CQPE: Classical Quantum Phase Estimation Algorithm Step by Step\n", "\n", - "$$\\mathcal{Q}\\left|\\Psi \\right\\rangle = e^{2\\pi i\\lambda}\\left|\\Psi \\right\\rangle,$$\n", + "The problem of phase estimation can be formulated as follows. Given an initial state $|\\Psi\\rangle$ and a phase operator $\\mathcal{Q}$ such that:\n", "\n", - "our goal is estimating $\\lambda$." - ] - }, - { - "cell_type": "markdown", - "id": "493af5b5", - "metadata": {}, - "source": [ - "So far we have the initial state $\\left|\\Psi \\right\\rangle = |1\\rangle$ and the unitary operator whose phase we want to estimate $\\mathcal{Q} = \\mathcal{S}$. In this section, we are going to describe the class step by step and explain the basics of the **CQPE** algorithm.\n" - ] - }, - { - "cell_type": "markdown", - "id": "e914b883", - "metadata": {}, - "source": [ - "### 2.1 Calling the *CQPE* class\n", + "$$\n", + "\\mathcal{Q}|\\Psi\\rangle = e^{2\\pi i \\lambda}|\\Psi\\rangle,\n", + "$$\n", + "\n", + "our objective is to estimate the value of $\\lambda$.\n", + "\n", + "So far, we have the initial state $|\\Psi\\rangle = |1\\rangle$ and the unitary operator whose phase we aim to estimate, $\\mathcal{Q} = \\mathcal{S}$. In this section, we will describe the class step by step and explain the fundamentals of the **CQPE** algorithm.\n", "\n", - "The *CQPE* is inside **QQuantLib/PE/classical_qpe** module. \n", + "---\n", "\n", - "In order to instantiate the class we need to provide a Python dictionary. Mandatory keys that the user should provide are, as explained in Section 1:\n", + "### 3.1 Calling the `CQPE` Class\n", "\n", - "* initial_state : QLM routine or gate with an initial state $|\\Psi\\rangle$ was loaded (created in Section 1).\n", - "* unitary_operator : QLM gate or routine with a Unitary operator ready to be applied to initial state $|\\Psi\\rangle$ (created in Section 1)\n", + "The `CQPE` class is located in the **QQuantLib/PE/classical_qpe** module.\n", "\n", - "Additionally, there are other keys that are important for configuring the method:\n", + "To instantiate the class, the user must provide a Python dictionary with the following mandatory keys:\n", "\n", - "* auxiliar_qbits_number : int. The number of auxiliary qubits ($m$) that are used for phase estimation (default 8). This number of qubits provided the precision of the phase estimation returned by the **QPE**: $\\frac{1}{2^m}$\n", - "* qpu : QLM solver. If not provided class try to create a PyLinalg solver. It is recommended to give this key to the class.\n", - "* shots: int number of shots for the quantum job (default 10).\n", - "* complete : for shots different from zero all the possible states will be returned. " + "- `initial_state`: A QLM routine or gate with an initial state $|\\Psi\\rangle$ (created in Section 1).\n", + "- `unitary_operator`: A QLM gate or routine representing the unitary operator to be applied to the initial state $|\\Psi\\rangle$ (also created in Section 1).\n", + "\n", + "Additionally, the following optional keys can be used to configure the method:\n", + "\n", + "- `auxiliar_qbits_number`: The number of auxiliary qubits ($m$) used for phase estimation (default: 8). This determines the precision of the phase estimation, which scales as $\\frac{1}{2^m}$.\n", + "- `qpu`: myQLM or QLM QPU. If not provided, the class attempts to create a PyLinalg solver. It is highly recommended to specify this key.\n", + "- `shots`: The number of measurement shots for the quantum job (default: 10).\n", + "- `complete`: If set to `True`, all possible states will be returned when `shots` is greater than zero.\n", + "\n", + "These parameters allow users to customize the behavior of the algorithm according to their specific requirements." ] }, { @@ -338,30 +284,35 @@ }, { "cell_type": "markdown", - "id": "c3ba441c", + "id": "47d54163", "metadata": {}, "source": [ - "### 2.2 Classical Quantum Phase Estimation algorithm\n", + "### 3.2 Classical Quantum Phase Estimation Algorithm\n", "\n", - "The classical quantum phase estimation algorithm (**QPE**) is composed of the following steps:\n", + "The classical **QPE** algorithm consists of the following steps:\n", "\n", - "1. Allocate a number of qubits equal to the number of qubits needed by the initial state operator $\\mathcal{Q}$-\n", - "2. Allocate $m$ additional qubits where the phase will be codified.\n", - "3. Apply a Hadamard gate to each auxiliary qubit\n", - "4. Each auxiliary qubit will apply a controlled $2^{i}$ power of the unitary operator to the initial state. The $i$ will be related to the position of the auxiliary qubit.\n", - "5. Apply the inverse of the Quantum Fourier Transform $\\mathcal{QFT}^{\\dagger}$ to the auxiliary qbits.\n", - "6. Measure the auxiliary qubits and transform the measurement to an integer value $y$.\n", - "7. Compute the angle using: $\\lambda = \\frac{y}{2^m}$\n", + "1. **Allocate State Qubits**: Determine the number of qubits required for the initial state operator $\\mathcal{Q}$.\n", + "2. **Allocate Auxiliary Qubits**: Allocate $m$ additional qubits, where the phase information will be encoded.\n", + "3. **Apply Hadamard Gates**: Apply a Hadamard gate to each auxiliary qubit to create a superposition of states.\n", + "4. **Controlled Unitary Operations**: Each auxiliary qubit applies a controlled $2^i$ power of the unitary operator to the initial state. Here, $i$ corresponds to the position of the auxiliary qubit.\n", + "5. **Inverse Quantum Fourier Transform**: Apply the inverse of the Quantum Fourier Transform ($\\mathcal{QFT}^\\dagger$) to the auxiliary qubits.\n", + "6. **Measurement**: Measure the auxiliary qubits and convert the measurement outcomes into an integer value $y$.\n", + "7. **Compute Phase**: Calculate the estimated phase using the formula:\n", + " $$\n", + " \\lambda = \\frac{y}{2^m}.\n", + " $$\n", "\n", - "For executing this workflow the **run** method of the class should be executed. The corresponding measurements are stored as a pandas DataFrame into the class attribute **result** which has the following columns:\n", + "To execute this workflow, the **run** method of the class should be invoked. The resulting measurements are stored in a pandas DataFrame within the class attribute `result`, which includes the following columns:\n", "\n", - "* **States**: Possible quantum states of the auxiliary qubits\n", - "* **Int_lsb**: conversion from the quantum state to an integer following **lsb** (bit farthest to the right will be least significant)\n", - "* **Probability**: Computed frequency of the quantum state.\n", - "* **Int**: conversion from the quantum state to an integer (the bit farthest to the right will be most significant)\n", - "* **lambda**: is the estimated obtained phase.\n", + "- `States`: Possible quantum states of the auxiliary qubits.\n", + "- `Int_lsb`: Conversion of the quantum state to an integer following the **least significant bit (LSB)** convention (the rightmost bit is the least significant).\n", + "- `Probability`: Computed frequency of each quantum state.\n", + "- `Int`: Conversion of the quantum state to an integer following the **most significant bit (MSB)** convention (the rightmost bit is the most significant).\n", + "- `lambda`: The estimated phase obtained from the measurements.\n", "\n", - "Depending on the values of the input variables *shots* and *complete* the results showed in the **result** DataFrame can contain all the possible quantum states or only the measured states." + "The contents of the `result` DataFrame depend on the values of the input variables `shots` and `complete`:\n", + "- If `shots` is set to zero or `complete` is `True`, the DataFrame will include all possible quantum states.\n", + "- If `shots` is greater than zero and `complete` is `False`, only the measured states will be included in the results." ] }, { @@ -402,13 +353,25 @@ "id": "d80f484e", "metadata": {}, "source": [ - "In the case of the used *Qiskit* example:\n", + "In the case of the *Qiskit* example provided:\n", "\n", - "$$i\\frac{\\pi}{2} = 2 \\pi i \\lambda$$\n", + "$$\n", + "i\\frac{\\pi}{2} = 2\\pi i \\lambda,\n", + "$$\n", "\n", - "So the desired solution will be: $\\lambda = \\frac{1}{4}$\n", + "the desired solution corresponds to:\n", "\n", - "In the before case we have fixed the *complete* variable to **True**, so all the possible states are returned. In this case the $\\lambda$ value will be the one with the higher probability: $\\lambda = 0.25$\n" + "$$\n", + "\\lambda = \\frac{1}{4}.\n", + "$$\n", + "\n", + "In the previous scenario, we set the *complete* variable to **True**, ensuring that all possible states are returned. In this context, the $\\lambda$ value is determined by selecting the state with the highest probability, yielding:\n", + "\n", + "$$\n", + "\\lambda = 0.25.\n", + "$$\n", + "\n", + "This result aligns with the expected outcome from the *Qiskit* example, confirming the accuracy of our phase estimation process." ] }, { @@ -426,7 +389,7 @@ "id": "f935db20", "metadata": {}, "source": [ - "If *shots* is different of zero and *complete* is set to **False** only the measured states will be returned!!" + "If `shots` is different of zero and `complete` is set to `False` only the measured states will be returned!!" ] }, { @@ -456,7 +419,7 @@ "id": "9bdd8b0b", "metadata": {}, "source": [ - "Addtitionally the attribute **quantum_times** measures the time for the quantum part of the algorithm (or simulated)" + "Addtitionally the attribute `quantum_times` measures the time for the quantum part of the algorithm (or simulated)" ] }, { @@ -474,21 +437,29 @@ "id": "d0665fbc", "metadata": {}, "source": [ - "## Another example\n", + "## 4. IPE example with a 2-qubit gate\n", "\n", - "We are going to reproduce now the Qiskit example under the section *IPE example with a 2-qubit gate* (in https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb). \n", + "We will now reproduce the Qiskit example from the section **IPE example with a 2-qubit gate** (available in [Qiskit Tutorials](https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb)).\n", "\n", - "In this case, they use 2 qubits and want to estimate the phase for a unitary operator $\\mathcal{cT}$ operator. This operator adds a $\\frac{\\pi}{4}$ phase to state $|11\\rangle$ and leaves unchanged other states. \n", + "In this scenario, the example uses **2 qubits** to estimate the phase of a unitary operator $\\mathcal{cT}$. This operator introduces a phase shift of $\\frac{\\pi}{4}$ radians specifically to the state $|11\\rangle$, leaving all other states unchanged. Mathematically, this behavior is expressed as:\n", "\n", - "In this case:\n", + "$$\n", + "\\mathcal{cT} |11\\rangle = e^{i\\frac{\\pi}{4}} |11\\rangle.\n", + "$$\n", "\n", - "$$\\mathcal{cT} |11\\rangle = e^{i\\frac{\\pi}{4}} |11\\rangle$$\n", + "From the phase estimation convention, we can derive the relationship between the phase shift and the parameter $\\lambda$:\n", + "\n", + "$$\n", + "i\\frac{\\pi}{4} = 2\\pi i \\lambda.\n", + "$$\n", "\n", - "So:\n", + "Solving for $\\lambda$, we obtain:\n", "\n", - "$$i\\frac{\\pi}{4} = 2 \\pi i \\lambda$$\n", + "$$\n", + "\\lambda = \\frac{1}{8}.\n", + "$$\n", "\n", - "So $\\lambda = \\frac{1}{8}$" + "This value of $\\lambda$ represents the desired solution for the phase estimation problem in this example." ] }, { @@ -590,103 +561,128 @@ }, { "cell_type": "markdown", - "id": "c5624363", + "id": "4786981c", "metadata": {}, "source": [ - "## 3. Application to Amplitude Estimation" - ] - }, - { - "cell_type": "markdown", - "id": "542adf13", - "metadata": {}, - "source": [ - "The problem of *Amplitude Estimation* is the following: given an oracle operator $\\mathcal{A}$ with the following behaviour:\n", + "## 5. Application to Amplitude Estimation\n", "\n", - "$$|\\Psi\\rangle= \\mathcal{A}|0\\rangle = \\sqrt{a}|\\Psi_0\\rangle +\\sqrt{1-a}|\\Psi_1\\rangle,$$\n", + "The problem of **Amplitude Estimation** can be formulated as follows: Given an oracle operator $\\mathcal{A}$ with the following behavior:\n", "\n", - "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, we want to estimate $\\sqrt{a}$. One naive solution will be measuring $N$ times and getting the probabilities of obtaining $|\\Psi_0\\rangle$. In this case the error in the estimation of $\\sqrt{a}$ will scale with: $\\epsilon_{a} \\sim \\frac{1}{\\sqrt{N}}$. Usually, it is useful to express this error in function of the number of oracle calls: $N_{\\mathcal{A}}$. In this naive case, it can be demonstrated that:\n", + "$$\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle,\n", + "$$\n", "\n", - "$$\\epsilon_{a} \\sim \\frac{1}{\\sqrt{N_{\\mathcal{A}}}}$$\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, our goal is to estimate $\\sqrt{a}$. \n", "\n", - "Classical *Phase Estimation* algorithm with *QFT* can be used for obtaining a quadratic speed up in *Amplitude Estimation* problems. \n", + "A naive solution involves measuring the system $N$ times to estimate the probability of obtaining $|\\Psi_0\\rangle$. In this case, the error in estimating $\\sqrt{a}$ scales as:\n", "\n", - "To apply this algorithm to *Amplitude Estimation* problems following steps should be done:\n", + "$$\n", + "\\epsilon_a \\sim \\frac{1}{\\sqrt{N}}.\n", + "$$\n", + "\n", + "It is often useful to express this error in terms of the number of oracle calls, $N_{\\mathcal{A}}$. For the naive approach, it can be shown that:\n", "\n", - "1. Doing the following substitution:\n", + "$$\n", + "\\epsilon_a \\sim \\frac{1}{\\sqrt{N_{\\mathcal{A}}}}.\n", + "$$\n", "\n", - "$$\\sqrt{a} = \\sin\\left(\\theta\\right)$$\n", + "However, the classical **Phase Estimation** algorithm with **QFT** can achieve a quadratic speedup in solving **Amplitude Estimation** problems.\n", "\n", - "2. The action of the oracle operator will be now:\n", + "To apply the **Phase Estimation** algorithm to **Amplitude Estimation**, the following steps should be followed:\n", "\n", - "$$|\\Psi\\rangle= \\mathcal{A}|0\\rangle = \\sqrt{a} |\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle = \\sin\\left(\\theta\\right) |\\Psi_0\\rangle + \\cos\\left(\\theta\\right) |\\Psi_1\\rangle \\tag{1}$$\n", + "1. **Substitution**: Replace $\\sqrt{a}$ with $\\sin(\\theta)$:\n", + " $$\n", + " \\sqrt{a} = \\sin(\\theta).\n", + " $$\n", "\n", - "3. Creation of the Grover-like operator of the oracle operator $\\mathcal{A}$:\n", + "2. **Rewriting the Oracle Operator**: The action of the oracle operator becomes:\n", + " $$\n", + " |\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle = \\sin(\\theta)|\\Psi_0\\rangle + \\cos(\\theta)|\\Psi_1\\rangle. \\tag{1}\n", + " $$\n", "\n", - "$$\\mathbf{G}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^{\\dagger}\\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right)$$\n", + "3. **Creating the Grover-like Operator**: Construct the Grover-like operator for the oracle operator $\\mathcal{A}$:\n", + " $$\n", + " \\mathbf{G}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^\\dagger \\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right).\n", + " $$\n", "\n", - "Now the classical *Phase Estimation* algorithm can be used straightforward by doing the following substitutions:\n", + "With these substitutions, the classical **Phase Estimation** algorithm can now be applied directly by replacing:\n", "\n", "$$\n", - " \\begin{array}{l}\n", - " & |\\Psi\\rangle \\longrightarrow \\mathcal{A}|0\\rangle \\\\\n", - " & \\mathcal{Q} \\longrightarrow \\mathbf{G}(\\mathcal{A})\n", - " \\end{array}\n", + "\\begin{aligned}\n", + "& |\\Psi\\rangle \\longrightarrow \\mathcal{A}|0\\rangle, \\\\\n", + "& \\mathcal{Q} \\longrightarrow \\mathbf{G}(\\mathcal{A}).\n", + "\\end{aligned}\n", "$$\n", "\n", - "It can be demonstrated that the $\\mathbf{G}(\\mathcal{A})$ operator can be expressed as a rotation in the plane of an angle $\\theta$, this is:\n", + "It can be demonstrated that the $\\mathbf{G}(\\mathcal{A})$ operator represents a rotation in the plane by an angle $2\\theta$, expressed as:\n", "\n", "$$\n", - "\\mathbf{G}(\\mathcal{A}) = \n", + "\\mathbf{G}(\\mathcal{A}) =\n", "\\begin{pmatrix}\n", - "\\cos 2\\theta & -\\sin 2\\theta\\\\\n", - "\\sin 2\\theta & \\cos 2\\theta \\\\\n", - "\\end{pmatrix}\n", + "\\cos(2\\theta) & -\\sin(2\\theta) \\\\\n", + "\\sin(2\\theta) & \\cos(2\\theta)\n", + "\\end{pmatrix}.\n", "$$\n", "\n", - "So the autovalues of the $\\mathbf{G}(\\mathcal{A})$ will be $e^{\\pm i2\\theta}$\n", + "Thus, the eigenvalues of $\\mathbf{G}(\\mathcal{A})$ are given by $e^{\\pm i2\\theta}$.\n", "\n", - "So using our phase estimation convention:\n", + "Using the phase estimation convention:\n", "\n", - "$$i2\\theta = 2 \\pi i \\lambda$$\n", + "$$\n", + "i2\\theta = 2\\pi i \\lambda,\n", + "$$\n", "\n", - "We arrive to: $\\theta = \\pi \\lambda$.\n", + "we arrive at:\n", "\n", - "It can be demostrated that using the classical *Phase Estimation* algorithm the error of the estimation in $\\theta$ (and in $\\sqrt{a}$) will scale as:\n", + "$$\n", + "\\theta = \\pi \\lambda.\n", + "$$\n", "\n", - "$$\\epsilon_{\\theta} \\sim \\frac{1}{N_{\\mathcal{A}}}$$\n", + "It can be shown that when using the classical **Phase Estimation** algorithm, the error in estimating $\\theta$ (and consequently $\\sqrt{a}$) scales as:\n", "\n", - "So a quadratic speed up is obtained with respect to a naive solution!!" - ] - }, - { - "cell_type": "markdown", - "id": "99f806e2", - "metadata": {}, - "source": [ - "### 3.1 Using *CQPE* class for *Amplitude Estimation*" + "$$\n", + "\\epsilon_\\theta \\sim \\frac{1}{N_{\\mathcal{A}}}.\n", + "$$\n", + "\n", + "This quadratic improvement in error scaling demonstrates the significant advantage of the **Phase Estimation** algorithm over the naive solution." ] }, { "cell_type": "markdown", - "id": "699dcf0b", + "id": "c9ec3665", "metadata": {}, "source": [ - "In this section, we explain how to use the **CQPE** class for solving *Amplitude Estimation* problems. \n", + "### 5.1 Using *CQPE* class for *Amplitude Estimation*\n", + "\n", + "In this section, we will demonstrate how to use the **CQPE** class to solve **Amplitude Estimation** problems.\n", + "\n", + "#### Defining the Amplitude Estimation Problem\n", + "\n", + "We begin by defining the following amplitude estimation problem:\n", "\n", - "First, we will define the following amplitude estimation problem:\n", + "$$\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", + "$$\n", "\n", - "$$|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right] \\tag{2}$$\n", + "By comparing equation (2) with the general form of the state in equation (1):\n", "\n", - "So comparing (2) with (1):\n", + "$$\n", + "\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\frac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle,\n", + "$$\n", "\n", - "$$\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle$$\n", + "and\n", "\n", - "and \n", + "$$\n", + "\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$\n", "\n", - "$$\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$\n", + "Here:\n", + "- The target state $|\\Psi_0\\rangle$ corresponds to $|1\\rangle$, and its amplitude is proportional to $\\sin(\\theta)$.\n", + "- The remaining states collectively form $|\\Psi_1\\rangle$, with an amplitude proportional to $\\cos(\\theta)$.\n", "\n", - "Following cells creates the initial state $|\\Psi\\rangle = \\mathcal{A}|0\\rangle$" + "#### Creating the Initial State\n", + "\n", + "The subsequent cells will construct the initial state $|\\Psi\\rangle = \\mathcal{A}|0\\rangle$, which serves as the starting point for our amplitude estimation process. This involves loading the specified probability distribution into a quantum state using the appropriate functions from the library." ] }, { @@ -721,7 +717,7 @@ "id": "6bff697c", "metadata": {}, "source": [ - "Now we need to compute the Grover-like operator from the oracle operator $\\mathcal{A}$. The functions from *AA* module will be used (see notebook **02_Amplitude_Amplification_Operators.ipynb** for more information)" + "Now we need to compute the Grover-like operator from the oracle operator $\\mathcal{A}$. The functions from `AA` module will be used (see notebook **02_Amplitude_Amplification_Operators.ipynb** for more information)" ] }, { @@ -744,7 +740,7 @@ "id": "13a39fd4", "metadata": {}, "source": [ - "Now that we have translated our amplitude amplification problem to an phase estimation problem we proceed to use our class normally. We provide the *oracle* as the **initial_state** and the correspondent Grover-like operator as the **unitary_operator**. Additionally auxiliar_qbits_number for estimating the phase should be provided." + "Now that we have translated our amplitude amplification problem to an phase estimation problem we proceed to use our class normally. We provide the *oracle* as the **initial_state** and the correspondent Grover-like operator as the **unitary_operator**. Additionally `auxiliar_qbits_number` for estimating the phase should be provided." ] }, { @@ -869,11 +865,19 @@ "id": "2f66f32f", "metadata": {}, "source": [ - "Finally, we need to compute the desired value of $a$. Following the equations presented in the notebook this can be done by $a =\\sin(\\theta)^2$.\n", + "Finally, we need to compute the desired value of $a$. Based on the equations presented in the notebook, this can typically be calculated as:\n", + "\n", + "$$\n", + "a = \\sin(\\theta)^2.\n", + "$$\n", + "\n", + "**However, in our implementation of the Quantum Phase Estimation (QPE) algorithm, the convention has been modified. The following relationship MUST BE USED instead:**\n", "\n", - "**In our implementation of the QPE the convention is changed and the following relationship MUST BE used**:\n", + "$$\n", + "a = \\cos(\\theta)^2.\n", + "$$\n", "\n", - "$$a = \\cos(\\theta)^2$$" + "This adjustment ensures consistency with the internal logic and conventions adopted in the implementation of the **CQPE** class. By using this modified relationship, we align the estimated amplitude $a$ with the expected results from the algorithm." ] }, { @@ -902,18 +906,24 @@ }, { "cell_type": "markdown", - "id": "5a4c894a", + "id": "28dc4916", "metadata": {}, "source": [ - "### What *a* should be returned?\n", + "### What *a* Should Be Returned?\n", + "\n", + "From the perspective of the **QPE** algorithm applied to **AE**, one straightforward solution would be to return the most frequently measured value of $a$. However, in practice, this approach can be problematic due to imperfections in actual quantum computers. Current quantum hardware is prone to noise and errors, which may prevent the **QPE** algorithm from executing perfectly. As a result, the measured outcomes might deviate from the expected results of the ideal algorithm.\n", "\n", - "From the point of view of the **QPE** algorithm for **AE** one possible solution would be to return the most frequently $a$ measured. But from an actual computer perspective this question is tricky because the computer used could not be perfect (actual quantum computers are far away from being good) and the **QPE** algorithm can not be executed properly and the results can be different from the expected result of the bare algorithm. \n", + "To address this challenge, we propose using the **mean** of the probability distribution obtained from the measurements. This approach provides a more robust estimate of $a$, even in the presence of experimental uncertainties. The mean value $\\tilde{a}$ is computed as follows:\n", "\n", - "We are going to use the mean of the probability distribution obtained from the measures. This is:\n", + "$$\n", + "\\tilde{a} = \\int a \\cdot p(a) \\, da = \\sum_{i} a[i] \\cdot P(a[i]),\n", + "$$\n", "\n", - "$$\\tilde{a} = \\int a * p(a) * da = \\sum_{i} a[i] * P(a[i])$$\n", + "where:\n", + "- $p(a)$ represents the probability of obtaining the value $a$.\n", + "- Experimentally, $P(a[i])$ corresponds to the frequency with which each value $a[i]$ was observed, normalized by the total number of measurement shots executed.\n", "\n", - "where $p(a)$ will be the probability of obtaining $a$ (experimentally this will be the number of times the $a$ value was obtained divided by the number of shots executed)." + "By calculating the mean of the measured distribution, we obtain a more reliable estimate of the amplitude $a$, accounting for potential inaccuracies introduced by the quantum hardware." ] }, { @@ -943,22 +953,42 @@ }, { "cell_type": "markdown", - "id": "b4a4c253", + "id": "7b636409", "metadata": {}, "source": [ - "### Asociated error of *a*\n", + "### Associated Error of $a$\n", "\n", - "Additionally, we can compute an error associated with the estimator of $\\tilde{a}$, $\\epsilon_a$. For the **QPE** algorithm in an ideal quantum computer the error will depend on the number of auxiliary qubits, $m$:\n", + "In addition to estimating the value of $a$, we can compute an error associated with the estimator $\\tilde{a}$, denoted as $\\epsilon_a$. This error accounts for uncertainties in the estimation process and helps assess the reliability of the result.\n", "\n", - "$$\\epsilon_a^{QPE} \\lt \\frac{2\\pi}{2 ^ m}$$\n", + "#### Ideal Quantum Computer:\n", + "For the **Quantum Phase Estimation (QPE)** algorithm executed on an ideal quantum computer, the error depends on the number of auxiliary qubits ($m$) used for phase estimation. Specifically, the error is bounded by:\n", "\n", - "But in an actual quantum computer, the errors will be greater. For this case, the most direct way for computing it will be to use the square root of the variance of the probability density measured (this will be the experimental error):\n", + "$$\n", + "\\epsilon_a^{QPE} < \\frac{2\\pi}{2^m}.\n", + "$$\n", + "\n", + "This bound reflects the precision achievable with $m$ auxiliary qubits, where the phase estimation resolution improves exponentially with $m$.\n", + "\n", + "#### Actual Quantum Computer:\n", + "On real quantum hardware, errors are more pronounced due to noise and imperfections. In such cases, the most straightforward approach to estimate the experimental error is to calculate the square root of the variance of the measured probability density. This experimental error, $\\epsilon_a^{P(a)}$, is given by:\n", "\n", - "$$\\epsilon_a^{P(a)} = \\sqrt{\\int p(a) (a-\\tilde{a}) ^2 * da} = \\sqrt{\\sum_{i} (a[i] -\\tilde{a})^2 * P(a[i])}$$\n", + "$$\n", + "\\epsilon_a^{P(a)} = \\sqrt{\\int p(a) (a - \\tilde{a})^2 \\, da} = \\sqrt{\\sum_{i} (a[i] - \\tilde{a})^2 \\cdot P(a[i])},\n", + "$$\n", "\n", - "In order to deal with all the situations, a perfect quantum computer (i.e. noiseless simulation) or an actual quantum computer the best approach is taking the maximum of these two errors:\n", + "where:\n", + "- $p(a)$ represents the probability density function of $a$,\n", + "- $\\tilde{a}$ is the estimated value of $a$,\n", + "- $P(a[i])$ is the experimentally measured frequency of each value $a[i]$.\n", "\n", - "$$\\epsilon_a = \\max (\\epsilon_a^{QPE}, \\epsilon_a^{P(a)} )$$" + "#### Combined Error:\n", + "To account for both theoretical and experimental sources of error, the best approach is to take the maximum of the two errors:\n", + "\n", + "$$\n", + "\\epsilon_a = \\max(\\epsilon_a^{QPE}, \\epsilon_a^{P(a)}).\n", + "$$\n", + "\n", + "This ensures that the computed error $\\epsilon_a$ captures the worst-case scenario, whether it arises from limitations in the algorithm (ideal case) or noise in the quantum hardware (realistic case). By using this combined error metric, we can provide a robust estimate of the uncertainty in our amplitude estimation results." ] }, { @@ -977,34 +1007,23 @@ }, { "cell_type": "markdown", - "id": "0c4e48cc", - "metadata": {}, - "source": [ - "## 4. Amplitude Estimation with classical Quantum Phase Estimation" - ] - }, - { - "cell_type": "markdown", - "id": "1e5bd569", + "id": "0abf5bb5", "metadata": {}, "source": [ - "In order to simplify **Amplitude Estimation** calculations done with the **CQPE** class (see Section 3) the **CQPEAE** (stands for **C**lassical **Q**uantum **P**hase **E**stimation **A**mplitude **Estimation**) class was created. The idea behind this class is dealing with the initial overheating shown in Section 3 for using **CQPE** in a straightforward way for *Amplitude Estimation* calculations. The class is under the module **ae_classical_qpe.py** of the *Amplitude Estimation* package of the *QQuantLib* library (**QQuantLib/AE/ae_classical_qpe**)\n", + "## 6. Amplitude Estimation with classical Quantum Phase Estimation\n", "\n", - "**Note**\n", + "In order to simplify **Amplitude Estimation** calculations performed with the **CQPE** class (see Section 3), the **CQPEAE** class (which stands for **C**lassical **Q**uantum **P**hase **E**stimation **A**mplitude **Estimation**) was developed. This class addresses the initial complexities outlined in Section 5, enabling a more streamlined approach for using **CQPE** in *Amplitude Estimation* calculations. The `CQPEAE` class is implemented in the module **ae_classical_qpe** of the *Amplitude Estimation* package within the *QQuantLib* library (**QQuantLib/AE/ae_classical_qpe**).\n", "\n", - "The idea is that the **CQPEAE** have a similar scheme like the **MLAE class** from **QQuantLib/AE/maximum_likelihood_ae** module" - ] - }, - { - "cell_type": "markdown", - "id": "0abf5bb5", - "metadata": {}, - "source": [ - "To create **CQPEAE** associated object following inputs are mandatory:\n", + "#### Note:\n", + "The design of the `CQPEAE` class follows a similar structure to the `MLAE` class from the **QQuantLib/AE/maximum_likelihood_ae** module.\n", + "\n", + "To create an instance of the `CQPEAE` class, the following inputs are mandatory:\n", "\n", - "1. Oracle: QLM AbstractGate or QRoutine with the implementation of the Oracle for creating the Grover operator.\n", - "2. target: this is the marked state in binary representation as a Python list\n", - "3. index: list of the qubits affected by the Grover operator." + "1. `oracle`: A QLM `AbstractGate` or `QRoutine` that implements the oracle required for constructing the Grover operator.\n", + "2. `target`: The marked state in its binary representation, specified as a Python list.\n", + "3. `index`: A list of qubits that will be affected by the Grover operator.\n", + "\n", + "These inputs ensure that the class is properly configured for executing amplitude estimation tasks efficiently and accurately." ] }, { @@ -1040,15 +1059,17 @@ }, { "cell_type": "markdown", - "id": "ada6702c", + "id": "8f07a06f", "metadata": {}, "source": [ - "Now we have all mandatory inputs so we created the *CQPEAE* object. Following **MLAE class** convention other parameters can be provided with a Python dictionary. In this case, the following keys can be provided:\n", + "Now that we have all the mandatory inputs, we can create the `CQPEAE` object. Following the conventions of the `MLAE` class, additional parameters can be provided through a Python dictionary. In this case, the following keys are available for configuration:\n", + "\n", + "- `auxiliar_qbits_number`: The number of qubits used for phase estimation (default: 8).\n", + "- `shots`: The number of measurement shots to execute (default: 100).\n", + "- `qpu`: The QPU solver to be used.\n", + "- `mcz_qlm`: A boolean flag indicating whether to use the QLM multi-controlled Z gate (`True`, default) or the multiplexor implementation (`False`).\n", "\n", - "* *auxiliar_qbits_number*: number of qubits for doing phase estimation (default: 8)\n", - "* *shots*: number of shots (default: 100)\n", - "* *qpu*: qpu solver\n", - "* mcz_qlm: for using QLM multi-controlled Z gate (True, default) or using multiplexor implementation (False)" + "These parameters allow users to customize the behavior of the `CQPEAE` class according to their specific requirements and computational resources." ] }, { @@ -1090,7 +1111,7 @@ "id": "d10c338c", "metadata": {}, "source": [ - "When instantiated the *CQPEAE* class the *Grover* operator associated to the oracle operator is created. It can be access using attribute *_grover_oracle*." + "When instantiated the `CQPEAE` class the *Grover* operator associated to the oracle operator is created. It can be access using attribute `_grover_oracle`." ] }, { @@ -1106,22 +1127,22 @@ }, { "cell_type": "markdown", - "id": "260676b3", + "id": "2dc6cee4", "metadata": {}, "source": [ - "Now the *ae_pe_qft* object has all mandatory inputs for calling the *CQPE* class and executing the algorithm. The **run** method from the **CQPEAE** class does this step. Additionally when the *run* method is executed following properties from the **CQPEAE** class are populated:\n", + "Now the `ae_pe_qft` object has all the mandatory inputs required to call the `CQPE` class and execute the algorithm. The `run` method of the `CQPEAE` class performs this step. Additionally, when the `run` method is executed, the following properties of the `CQPEAE` class are populated:\n", "\n", - "* *cqpe*: this property is an object from the **CQPE** class that was initialized with corresponding arguments created by the **CQPEAE** class.\n", - "* *final_results*: this is the final_results obtained from the *pe_qft* method of the property object *cqpe* (see section 2.3).\n", - "* *theta*: estimated $\\theta$ obtained from **CQPE** algorithm (used the meanb of the probability distribution)\n", - "* *ae* amplitude estimated solution based on $a=cos^2 \\theta$\n", - "* *run_time*: elapsed time of a complete execution of the **run** method\n", + "- `cqpe`: This property is an object from the **CQPE** class, initialized with the corresponding arguments created by the **CQPEAE** class.\n", + "- `final_results`: These are the final results obtained from the `pe_qft` method of the `cqpe` object (see Section 2.3).\n", + "- `theta`: The estimated value of $\\theta$ obtained from the **CQPE** algorithm, computed as the mean of the probability distribution.\n", + "- `ae`: The amplitude estimation solution based on $a = \\cos^2(\\theta)$.\n", + "- `run_time`: The elapsed time for a complete execution of the **`run`** method.\n", "\n", - "Additionally, the different computed errors for $a$ can be accessed with the following attributes:\n", + "Additionally, the computed errors for $a$ can be accessed using the following attributes:\n", "\n", - "* *epsilon_qpe* : error due to the **QPE** algorithm\n", - "* *epsilon_prob*: statistical error of the measurements performed\n", - "* *epsilon*: final error resulting in the maximum of the two aforementioned errors.\n" + "- `epsilon_qpe`: Error due to the **QPE** algorithm.\n", + "- `epsilon_prob`: Statistical error derived from the performed measurements.\n", + "- `epsilon`: Final error, defined as the maximum of the two aforementioned errors ($\\epsilon_qpe$ and $\\epsilon_prob$)." ] }, { @@ -1246,7 +1267,7 @@ "id": "6eeb8d8f", "metadata": {}, "source": [ - "As explained before, the **cqpe** attribute from *CQPEAE* class is an object created from class **CQPE** so all the methods and attributes of this class can be accessed using the **cqpe** attribute (**NOT RECOMMENDED**).\n", + "As explained before, the `cqpe` attribute from `CQPEAE` class is an object created from class `CQPE` so all the methods and attributes of this class can be accessed using the **cqpe** attribute (**NOT RECOMMENDED**).\n", "\n", "We can access to the quantum circuit used by the **CQPE**" ] @@ -1270,11 +1291,11 @@ "id": "a34faccc", "metadata": {}, "source": [ - "When the *run* method is executed following class attributes are populated:\n", + "When the `run` method is executed following class attributes are populated:\n", "\n", - "* *circuit_statistics*: python dictionary with the complete statistical information of the circuit.\n", - "* *oracle_calls*: number of total oracle calls for a complete execution of the algorithm\n", - "* *max_oracle_depth*: maximum number of applications of the oracle for the complete execution of the algorithm." + "* `circuit_statistics`: python dictionary with the complete statistical information of the circuit.\n", + "* `oracle_calls`: number of total oracle calls for a complete execution of the algorithm\n", + "* `max_oracle_depth`: maximum number of applications of the oracle for the complete execution of the algorithm." ] }, { @@ -1334,9 +1355,9 @@ ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1348,7 +1369,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/05_Iterative_Quantum_Phase_Estimation_Class.ipynb b/misc/notebooks/05_Iterative_Quantum_Phase_Estimation_Class.ipynb index 8aaddfb..fc00433 100644 --- a/misc/notebooks/05_Iterative_Quantum_Phase_Estimation_Class.ipynb +++ b/misc/notebooks/05_Iterative_Quantum_Phase_Estimation_Class.ipynb @@ -2,36 +2,22 @@ "cells": [ { "cell_type": "markdown", - "id": "91740961", + "id": "14ca457e", "metadata": {}, "source": [ - "# Iterative Quantum Phase Estimation (IQPE) module" - ] - }, - { - "cell_type": "markdown", - "id": "c4f4d8f0", - "metadata": {}, - "source": [ - "The present notebook reviews the **Iterative Quantum Phase Estimation** (**IQPE**) algorithm which allows the estimation of the phase of a unitary operator without the use of the **Quantum Fourier Transform**. This **IQPE** was developed into module *iterative_quantum_pe* of the package *PE* of the present library *QQuantLib* (**QQuantLib/PE/iterative_quantum_pe.py**). \n", + "# Iterative Quantum Phase Estimation (IQPE) module\n", "\n", - "This **IQPE** algorithm was developed as a Python class called: *IQPE* inside the **QQuantLib/PE/iterative_quantum_pe.py**." - ] - }, - { - "cell_type": "markdown", - "id": "f57f5478", - "metadata": {}, - "source": [ - "The present notebook and module are based on the following references:\n", + "The present notebook reviews the **Iterative Quantum Phase Estimation (IQPE)** algorithm, which allows for the estimation of the phase of a unitary operator without using the **Quantum Fourier Transform (QFT)**. This **IQPE** algorithm has been implemented as the Python class `IQPE` within the module *iterative_quantum_pe* of the package **PE** in the current library `QQuantLib`(**QQuantLib/PE/iterative_quantum_pe**).\n", "\n", - "* *Dobšíček, Miroslav and Johansson, Göran and Shumeiko, Vitaly and Wendin, Göran*. Arbitrary accuracy iterative quantum phase estimation algorithm using a single ancillary qubit: A two-qubit benchmark. Physical Review A 3(76), 2007. https://arxiv.org/abs/quant-ph/0610214\n", + "This notebook and its associated module are based on the following references:\n", "\n", - "* *Griffiths, Robert B. and Niu, Chi-Sheng*. Semiclassical Fourier Transform for Quantum Computation. Physical Review Letters, 17 (76), 1996. https://arxiv.org/abs/quant-ph/9511007\n", + "- **Dobšíček, Miroslav**, **Johansson, Göran**, **Shumeiko, Vitaly**, and **Wendin, Göran**. *Arbitrary accuracy iterative quantum phase estimation algorithm using a single ancillary qubit: A two-qubit benchmark*. Physical Review A 76(3), 2007. [https://arxiv.org/abs/quant-ph/0610214](https://arxiv.org/abs/quant-ph/0610214)\n", "\n", - "* *A. Y. Kitaev*. Quantum measurements and the Abelian stabilizer problem. Electronic Colloquium on Computational Complexity, 3(3):1–22, 1996. https://arxiv.org/abs/quant-ph/9511026\n", + "- **Griffiths, Robert B.** and **Niu, Chi-Sheng**. *Semiclassical Fourier Transform for Quantum Computation*. Physical Review Letters 76(17), 1996. [https://arxiv.org/abs/quant-ph/9511007](https://arxiv.org/abs/quant-ph/9511007)\n", "\n", - "* *Monz, Thomas and Nigg, Daniel and Martinez, Esteban A. and Brandl, Matthias F. and Schindler, Philipp and Rines, Richard and Wang, Shannon X. and Chuang, Isaac L. and Blatt, Rainer*. Realization of a scalable Shor algorithm. Science 6277 (351). 2016. https://arxiv.org/abs/1507.08852" + "- **Kitaev, A. Y.**. *Quantum measurements and the Abelian stabilizer problem*. Electronic Colloquium on Computational Complexity, 3(3):1–22, 1996. [https://arxiv.org/abs/quant-ph/9511026](https://arxiv.org/abs/quant-ph/9511026)\n", + "\n", + "- **Monz, Thomas**, **Nigg, Daniel**, **Martinez, Esteban A.**, **Brandl, Matthias F.**, **Schindler, Philipp**, **Rines, Richard**, **Wang, Shannon X.**, **Chuang, Isaac L.**, and **Blatt, Rainer**. *Realization of a scalable Shor algorithm*. Science 351(6277), 2016. [https://arxiv.org/abs/1507.08852](https://arxiv.org/abs/1507.08852)" ] }, { @@ -70,14 +56,6 @@ "%matplotlib inline" ] }, - { - "cell_type": "markdown", - "id": "147202cf", - "metadata": {}, - "source": [ - "### BE AWARE myqlm 1.7.1 have problems with CLinalg and Intermediate Measurements. Please DO NOT USE CLinalg (c QPU) in this notebook." - ] - }, { "cell_type": "code", "execution_count": null, @@ -99,179 +77,105 @@ }, { "cell_type": "markdown", - "id": "d3c17b2d", + "id": "55637829", "metadata": {}, "source": [ - "## 1. Initial Inputs" - ] - }, - { - "cell_type": "markdown", - "id": "c7cdcc17", - "metadata": {}, - "source": [ - "For using the *IQPE* python class inside the **QQuantLib/PE/iterative_quantum_pe** module 2 mandatory QLM objects should be provided:\n", + "## 1. Example to use\n", "\n", - "* 1. Initial State: this will be the initial quantum state needed for applying the Unitary Operator.\n", - "* 2. Unitary operator: the operator whose phase we want to estimate.\n", + "To illustrate how the `IQPE` class works, we will use the **Iterative Quantum Phase Estimation (IQPE)** example from the Qiskit textbook:\n", "\n", - "To explain how the *IterativeQuantumPE* class works we are going to use the **IQPE** example from the Qiskit textbook:\n", + "- [Qiskit Tutorial: IQPE](https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb)\n", "\n", - "https://qiskit.org/textbook/ch-labs/Lab04_IterativePhaseEstimation.html\n", + "This notebook contains 2 different examples:\n", + "- **IPE example with a 1-qubit gate for U**\n", + "- **IPE example with a 2-qubit gate**\n", "\n", - "https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb\n", + "The sections 2 and 3 will explain, step by step, how to use the `IQPE` class for solve the **IPE example with a 1-qubit gate for U** example. \n", "\n", - "We are going to reproduce the section **IPE example with a 1-qubit gate for U** from the Qiskit example" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "020cdbc6", - "metadata": {}, - "outputs": [], - "source": [ - "#Number Of Qbits\n", - "n_qbits = 1" - ] - }, - { - "cell_type": "markdown", - "id": "ea46b380", - "metadata": {}, - "source": [ - "### 1. Initial State" + "Section 4 will explain how to use the library solve the **IPE example with a 2-qubit gate**." ] }, { "cell_type": "markdown", - "id": "47b0209a", + "id": "b0a4bbf3", "metadata": {}, "source": [ - "Initial State can be:\n", - "1. QLM QRoutine\n", - "2. QLM gate (or abstract gate)\n", + "## 2. State and Unitary operator.\n", "\n", - "In the Qiskit example, the initial state will be $|1\\rangle$. \n", + "In the **IPE example with a 1-qubit gate for U** from the Qiskit textbook, the main problem is to find the phase for the 1-qubit unitary operator. They use the $\\mathcal{S}$ gate that has the following behaviour:\n", "\n", - "The following cell creates this initial state:" + "$$\n", + "\\mathcal{S}|1\\rangle = e^{i\\frac{\\pi}{2}}|1\\rangle.\n", + "$$\n", + "\n", + "This means that the $\\mathcal{S}$ gate introduces a phase shift of $\\frac{\\pi}{2}$ radians to the state $|1\\rangle$, leaving it unchanged otherwise.\n", + "\n", + "To use the `IQPE` Python class from the module **QQuantLib/PE/iterative_quantum_pe**, two mandatory inputs must be provided:\n", + "\n", + "1. **Initial State**: This is the initial quantum state required for applying the unitary operator ($|1\\rangle$ in the Qiskit example). The initial state can be provided as:\n", + " - A QLM QRoutine.\n", + " - A QLM gate (or abstract gate).\n", + "2. **Unitary Operator**: This is the operator whose phase we aim to estimate ($\\mathcal{S}$ gate in the Qiskit example). The unitary operator can be provided as:\n", + " - A QLM QRoutine.\n", + " - A QLM gate (or abstract gate). \n", + "\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "b532fe74", + "id": "378fc25a", "metadata": {}, "outputs": [], "source": [ + "# Initial quantum state\n", + "#Number Of Qubits\n", + "n_qbits = 1\n", "initial_state = qlm.QRoutine()\n", "q_bits = initial_state.new_wires(n_qbits)\n", "for i in range(n_qbits):\n", - " initial_state.apply(qlm.X, q_bits[i])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ea6baae", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ + " initial_state.apply(qlm.X, q_bits[i])\n", + " \n", "%qatdisplay initial_state --svg" ] }, - { - "cell_type": "markdown", - "id": "131043e7", - "metadata": {}, - "source": [ - "### 2. Unitary operator." - ] - }, - { - "cell_type": "markdown", - "id": "3e2c5873", - "metadata": {}, - "source": [ - "The unitary operator can be:\n", - "\n", - "1. QLM QRoutine\n", - "2. QLM gate (or abstract gate)\n", - "\n", - "In the Qiskit example, the unitary operator is the $\\mathcal{S}$ gate. So the application of the unitary operator over the initial state will be:\n", - "\n", - "$$\\mathcal{S}|1\\rangle = e^{i\\frac{\\pi}{2}}|1\\rangle$$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "43667149", - "metadata": {}, - "outputs": [], - "source": [ - "unitary_operator = qlm.PH(np.pi/2.0) " - ] - }, { "cell_type": "code", "execution_count": null, - "id": "8daa3929", + "id": "2a19f1dd", "metadata": {}, "outputs": [], "source": [ + "# unitary operator\n", + "unitary_operator = qlm.PH(np.pi/2.0) \n", "%qatdisplay unitary_operator --svg" ] }, { "cell_type": "markdown", - "id": "0d586763", - "metadata": {}, - "source": [ - "## 2. Class IQPE: algorithm step by step " - ] - }, - { - "cell_type": "markdown", - "id": "cf18d7f8", - "metadata": {}, - "source": [ - "The problem of phase estimation can be stated as follows. Given an initial state $\\left|\\Psi \\right\\rangle$ and a phase operator $\\mathcal{P}$ such that:\n", - "\n", - "$$\\mathcal{P}\\left|\\Psi \\right\\rangle = e^{2\\pi i\\lambda}\\left|\\Psi \\right\\rangle,$$\n", - "\n", - "our goal is estimating $\\lambda$." - ] - }, - { - "cell_type": "markdown", - "id": "493af5b5", - "metadata": {}, - "source": [ - "So far we have the initial state $\\left|\\Psi \\right\\rangle = |1\\rangle$ and the unitary operator whose phase we want to estimate $\\mathcal{P} = \\mathcal{S}$. In this section, we are going to describe the class step by step and explain the basics of the **IPE** algorithm" - ] - }, - { - "cell_type": "markdown", - "id": "e914b883", + "id": "39ad47c9", "metadata": {}, "source": [ - "### 2.1 Calling the **IQPE** class\n", + "## 3. Class `IQPE`: IQPE algorithm step by step \n", "\n", - "The *IterativeQuantumPE* is inside **QQuantLib/PE/iterative_quantum_pe** module. \n", + "The problem of phase estimation can be stated as follows. Given an initial state $|\\Psi \\rangle$ and a phase operator $\\mathcal{P}$ such that: \n", + "$$\n", + "\\mathcal{P} |\\Psi \\rangle = e^{2\\pi i \\lambda} |\\Psi \\rangle,\n", + "$$ \n", + "our goal is to estimate $\\lambda$. \n", "\n", - "To instantiate the class we need to provide a Python dictionary. Mandatory keys are:\n", + "So far, we have the initial state $|\\Psi \\rangle = |1\\rangle$ and the unitary operator whose phase we want to estimate, $\\mathcal{P} = \\mathcal{S}$. In this section, we will describe the class step by step and explain the basics of the **IQPE** algorithm. \n", "\n", - "* initial_state : QLM routine or gate with an initial state $|\\Psi\\rangle$ was loaded. \n", - "* unitary_operator : QLM gate or routine with a Unitary operator ready to be applied to initial state $|\\Psi\\rangle$.\n", + "### 3.1 Calling the **IQPE** class \n", "\n", - "Other important keys are:\n", + "The *IterativeQuantumPE* (`IQPE`) class is located in the **QQuantLib/PE/iterative_quantum_pe** module. To instantiate the class, you need to provide a Python dictionary. The mandatory keys are: \n", + "* `initial_state`: A QLM routine or gate with the initial state $|\\Psi \\rangle$ loaded. \n", + "* `unitary_operator`: A QLM gate or routine representing the Unitary operator to be applied to the initial state $|\\Psi \\rangle$. \n", "\n", - "* cbits_number : int with the number of classical bits needed for phase estimation\n", - "* qpu : QLM solver. If not provided class try to create a PyLinalg solver. It is recommended to give this key to the class.\n", - "* shots : int number of shots for the quantum job." + "Other important keys include: \n", + "* `cbits_number`: An integer specifying the number of classical bits needed for phase estimation. \n", + "* `qpu`: A QLM solver. If not provided, the class attempts to create a `PyLinalg` solver. It is recommended to explicitly provide this key when instantiating the class. \n", + "* `shots`: An integer specifying the number of shots for the quantum job. " ] }, { @@ -308,7 +212,7 @@ "id": "3e8af83a", "metadata": {}, "source": [ - "When the class is instantiated the properties *initial_state* and *q_gate* are overwritten with the given keys **initial_state** and **unitary_operator** respectively" + "When the class is instantiated the properties `initial_state` and `q_gate` are overwritten with the given keys *initial_state* and *unitary_operator* respectively" ] }, { @@ -338,22 +242,17 @@ "id": "c3ba441c", "metadata": {}, "source": [ - "### 2.2 IPE Algorithm step by step\n", + "### 3.2 IPE Algorithm step by step\n", "\n", - "Now we are going to review step by step the **IPE** algorithm using different programmed methods of the **IterativeQuantumPE** class" - ] - }, - { - "cell_type": "markdown", - "id": "383ffac5", - "metadata": {}, - "source": [ - "#### 2.2.1. Initialize the quantum program.\n", + "Now we are going to review step by step the **IQPE** algorithm using different programmed methods of the `IQPE` class.\n", "\n", - "The first thing is calling the method **init_iqpe**. The following actions are done by this method:\n", - "1. Creation of QLM program from *initial_state* QLM routine (or AbstractGate). The QLM program is stored in the *q_prog* property.\n", - "2. Allocation of an auxiliary qubit is mandatory for the **IPE** algorithm. It is stored in the *q_aux* property.\n", - "3. Allocation of the auxiliary classical bits where the estimated phase will be stored. Property: *c_bits*." + "#### 3.2.1 Initialize the quantum program\n", + "\n", + "The first step is to call the method `init_iqpe`. This method performs the following actions:\n", + "\n", + "1. Creation of a QLM program from the `initial_state` QLM routine (or `AbstractGate`). The QLM program is stored in the `q_prog` property.\n", + "2. Allocation of an auxiliary qubit, which is mandatory for the **IQPE** algorithm. This qubit is stored in the `q_aux` property.\n", + "3. Allocation of the auxiliary classical bits where the estimated phase will be stored. These bits are stored in the `c_bits` property." ] }, { @@ -381,46 +280,33 @@ "%qatdisplay circuit --depth 0 --svg" ] }, - { - "cell_type": "markdown", - "id": "75deafbc", - "metadata": {}, - "source": [ - "#### 2.2.2. IQPE Algorithm" - ] - }, - { - "cell_type": "markdown", - "id": "aba65dea", - "metadata": {}, - "source": [ - "We are going to decompose the **IQPE** algorithm in 2 parts. A first part where the main variable $l$ will be 0 ($l=0$) and a second recursive part where the variable $l$ will be greater than 0 ($l\\gt 0$)." - ] - }, { "cell_type": "markdown", "id": "a8de4522", "metadata": {}, "source": [ - "#### First Part ($l=0$)\n", + "#### 3.2.2 IQPE Algorithm\n", "\n", - "The first step of the IPE algorithm ($l=0$) has the following parts:\n", + "We are going to decompose the **IQPE** algorithm into two parts: a first part where the main variable $ l $ is 0 ($ l = 0 $), and a second recursive part where the variable $ l $ is greater than 0 ($ l > 0 $).\n", "\n", - "1. Reset the auxiliary qubit\n", - "2. Apply a Hadamard gate to the auxiliary qubit\n", - "3. Apply the phase operator $\\mathcal{P}$ controlled by auxiliary the qubit $2^{m-1}$ ($\\mathcal{P} ^{2^{m-1}}$) times. Here $m$ is the number of classical qubits allocated for estimating $\\theta$ \n", - "4. Apply a Hadamard gate to the auxiliary qubit\n", - "5. Measuring the auxiliary qubit and storing the result into a classical bit array in position $c_l$ ($l=0$)\n", + "##### First Part ($ l = 0 $)\n", "\n", - "This can be done by calling the *step_iqpe* method with the following arguments:\n", + "The first step of the **IQPE** algorithm ($ l = 0 $) consists of the following parts:\n", "\n", - "* Quantum Program with initial_state\n", - "* Quantum Routine or AbstractGate with phase operator $\\mathcal{P}$\n", - "* Auxiliary Qubit\n", - "* Auxiliary classical bits\n", - "* l=0\n", + "1. Reset the auxiliary qubit.\n", + "2. Apply a Hadamard gate to the auxiliary qubit.\n", + "3. Apply the phase operator $\\mathcal{P}$, controlled by the auxiliary qubit, $2^{m-1}$ times ($\\mathcal{P}^{2^{m-1}}$). Here, $ m $ is the number of classical bits allocated for estimating $\\theta$.\n", + "4. Apply a Hadamard gate to the auxiliary qubit.\n", + "5. Measure the auxiliary qubit and store the result in a classical bit array at position $ c_l $ ($ l = 0 $).\n", "\n", - "This *step_iqpe* method returns the quantum program with the operations explained in this part." + "This can be achieved by calling the `step_iqpe` method with the following arguments:\n", + "- Quantum Program with the `initial_state`.\n", + "- Quantum Routine or `AbstractGate` with the phase operator $\\mathcal{P}$.\n", + "- Auxiliary Qubit.\n", + "- Auxiliary classical bits.\n", + "- $ l = 0 $.\n", + "\n", + "The `step_iqpe` method returns the quantum program with the operations explained in this part." ] }, { @@ -461,23 +347,26 @@ }, { "cell_type": "markdown", - "id": "41e20eba", + "id": "5cf938ef", "metadata": {}, "source": [ - "#### Second or iterative Part ($l \\gt 0$)\n", + "##### Second or Iterative Part ($ l > 0 $)\n", "\n", - "This part of the algorithm is recursive. The following steps will be repeated for each value of $l=1,2,..m-1$ (m is the number of classical bits for codifying the phase)\n", + "This part of the algorithm is recursive. The following steps will be repeated for each value of $ l = 1, 2, \\dots, m-1 $ (where $ m $ is the number of classical bits used to encode the phase):\n", "\n", - "1. Reset the auxiliary qubit\n", - "2. Apply a Hadamard gate to the auxiliary qubit\n", - "3. Apply the phase operator $\\mathcal{P}$ controlled by the auxiliary qubit $2^{m-1-l}$ ($\\mathcal{Q}^{2^{m-1-l}}$) times, being $m$ is the number of classical bits allocated for estimating $\\theta$.\n", - "4. Apply on the auxiliary qubit a set of controlled rotations by $c_j$ classical bit of angle: $\\frac{\\pi}{2}\\frac{1}{2^{l-j-1}}$ with $j=0,1,..l-1$. \n", - "4. Apply a Hadamard gate to the auxiliary qubit\n", - "5. Measuring the auxiliary qubit and storing the result into a classical bit array in position $c_l$\n", + "1. Reset the auxiliary qubit.\n", + "2. Apply a Hadamard gate to the auxiliary qubit.\n", + "3. Apply the phase operator $\\mathcal{P}$, controlled by the auxiliary qubit, $ 2^{m-1-l} $ times ($ \\mathcal{P}^{2^{m-1-l}} $). Here, $ m $ is the number of classical bits allocated for estimating $\\theta$.\n", + "4. Apply a set of controlled rotations on the auxiliary qubit, conditioned on the classical bits $ c_j $, with angles:\n", + " $$\n", + " \\frac{\\pi}{2} \\cdot \\frac{1}{2^{l-j-1}}, \\quad \\text{for } j = 0, 1, \\dots, l-1.\n", + " $$\n", + "5. Apply a Hadamard gate to the auxiliary qubit.\n", + "6. Measure the auxiliary qubit and store the result in a classical bit array at position $ c_l $.\n", "\n", - "So for a $l$ step de controlled by classical bits rotation will depend on the measurements done on the before $l$ steps.\n", + "Thus, for each step $ l $, the controlled rotations depend on the measurements performed in the previous $ l $ steps.\n", "\n", - "In the following cell we explain how to create the algorithm for the $l=1$ part:" + "In the following cell, we explain how to create the algorithm for the $ l = 1 $ part:" ] }, { @@ -521,19 +410,19 @@ }, { "cell_type": "markdown", - "id": "1efbc6bf", + "id": "14af963e", "metadata": {}, "source": [ - "#### Complete algorithm\n", + "#### Complete Algorithm\n", "\n", - "For a complete **IPE** algorithm following steps should be done:\n", + "For a complete **IQPE** algorithm, the following steps should be performed:\n", "\n", - "1. Create the First part of the algorithm $l=0$.\n", - "2. Iterate the second part of the algorithm from $l=1$ to $l=m-1$ where m is the number of classical bits for estimating the phase\n", + "1. Create the first part of the algorithm for $ l = 0 $.\n", + "2. Iterate the second part of the algorithm from $ l = 1 $ to $ l = m-1 $, where $ m $ is the number of classical bits used for estimating the phase.\n", "\n", - "The measured classical bits are used for estimating the phase of the unitary operator\n", + "The measured classical bits are then used to estimate the phase of the unitary operator.\n", "\n", - "The following cell creates the complete program for the **IQPE** algorithm" + "The following cell creates the complete program for the **IQPE** algorithm." ] }, { @@ -576,19 +465,19 @@ }, { "cell_type": "markdown", - "id": "a45755a0", + "id": "424900cd", "metadata": {}, "source": [ - "#### 2.2.3. IQPE Algorithm execution\n", + "#### 3.2.3 IQPE Algorithm Execution\n", "\n", - "Once the QLM program is constructed the algorithm should be executed. For this the *run* method from the class allow to execute it. The following arguments should be provided:\n", + "Once the myQLM program is constructed, the algorithm should be executed. For this purpose, the `run_qprogram` method of the class allows you to execute it. The following arguments should be provided:\n", "\n", - "* q_prog : with the complete *IPE* algorithm\n", - "* q_aux : the auxiliary qubit \n", - "* shots \n", - "* linalg_qpu: QLM solver\n", + "- `q_prog`: The complete QLM program containing the **IPE** algorithm.\n", + "- `q_aux`: The auxiliary qubit used in the algorithm.\n", + "- `shots`: The number of shots for the quantum job.\n", + "- `linalg_qpu`: The QLM solver (e.g., a linear algebra-based quantum processing unit).\n", "\n", - "This method creates the circuit, and the associated job and executes it. The raw results of the simulation and the circuit are returned\n" + "This method creates the circuit, sets up the associated job, and executes it. The raw results of the simulation, along with the circuit, are returned." ] }, { @@ -615,17 +504,19 @@ }, { "cell_type": "markdown", - "id": "97aacc02", + "id": "0ddbb862", "metadata": {}, "source": [ - "#### 2.2.4. IPE: getting classical bits measurements\n", + "#### 3.2.4 IQPE: Getting Classical Bits Measurements\n", + "\n", + "As explained, the phase is estimated by obtaining the measurements of the classical bits. In the class, this is achieved using the `measure_classical_bits` method. The input to this method is the `raw_results` obtained from the `run_qprogram` method. \n", "\n", - "As explained the phase will be estimated by getting the measurements of the classical bits. In the class, this is done by the **measure_classical_bits** method. The input of this method is the *raw_results* from the *run_qprogram* method. The output will be a pandas DataFrame with the aggregated measurement of the classical bits with the following columns:\n", + "The output is a pandas DataFrame containing the aggregated measurements of the classical bits, with the following columns:\n", "\n", - "* **BitString**: is the result of the classical bits measurement in each step of the algorithm\n", - "* **BitInt**: integer representation of the **BitString**\n", - "* **Phi**: is the estimated obtained phase and it is computed as $\\frac{BitInt}{2^{m}}$ where $m$ is the number of classical bits used for phase estimation\n", - "* **Frequency**: frequency of the obtained **BitString** (number of times the **BitString** divide by the number of shots)" + "- `BitString`: The result of the classical bits measurement at each step of the algorithm.\n", + "- `BitInt`: The integer representation of the `BitString`.\n", + "- `Phi`: The estimated phase, computed as $\\frac{\\text{BitInt}}{2^{m}}$, where $m$ is the number of classical bits used for phase estimation.\n", + "- `Frequency`: The frequency of the obtained `BitString`, calculated as the number of times the `BitString` appears divided by the total number of `shots`." ] }, { @@ -653,33 +544,39 @@ "id": "147a56c6", "metadata": {}, "source": [ - "From *classical_bits* pdf the important column is **Phi**. Following the Qiskit example, this column is $\\varphi$ and the searched phase is: $\\phi=2\\pi\\varphi$. In the Qiskit example $\\varphi=0.25$. \n", + "The most important column fro the `classical_bits` DataFrame is the `Phi`one. Following the Qiskit example, this column corresponds to $\\varphi$, and the searched phase is given by: \n", "\n", - "The *classical_bits* DataFrame gives the aggregated results for all executions of the circuit (variable *shots*). " + "$$\n", + "\\phi = 2\\pi\\varphi.\n", + "$$ \n", + "In the Qiskit example, $\\varphi = 0.25$.\n", + "\n", + "The `classical_bits` DataFrame provides the aggregated results for all executions of the circuit (determined by the variable `shots`)." ] }, { "cell_type": "markdown", - "id": "f749c91c", + "id": "d3f75d8a", "metadata": {}, "source": [ - "#### 2.2.5. IPE: post-processing\n", - "\n", - "Typically the phase in radians is provided in this kind of phase estimation problem. To get this result we use the *post_proccess*. The *classical_bits* should be provided and the output will be another DataFrame with the following columns:\n", + "#### 3.2.5 IQPE: Post-Processing\n", "\n", - "* **BitString**: is the result of the classical bits measurement in each step of the algorithm\n", - "* **BitInt**: integer representation of the **BitString**\n", - "* **Phi**: is the estimated obtained phase and it is computed as $\\frac{BitInt}{2^{c_b}}$ where $c_b$ is the number of classical bits \n", - "* **2*theta**: this is the phase of the unitary operator in radians: $2\\theta = 2\\pi\\varphi$. In this case, we calculate the phase of the unitary operator as the double of an angle $\\theta$.\n", - "* **theta**: this is the half of the phase of the unitary operator: $\\theta = \\pi\\varphi$\n", - "* **theta_90**: is the $\\theta$ between $(0, \\frac{\\pi}{2}$)\n", - "* **Frequency**: Frequency of each **BitString**\n", + "Typically, the phase in radians is provided in this type of phase estimation problem. To obtain this result, we use the `post_process` method. The `classical_bits` DataFrame should be provided as input, and the output will be another DataFrame with the following columns:\n", "\n", - "The returned DataFrame from *post_proccess* was ordered by decreasing values of the frequency (so the first row is for the most frequent **BitString**, the second row for the second most frequent value and so on)\n", + "- `BitString`: The result of the classical bits measurement at each step of the algorithm.\n", + "- `BitInt`: The integer representation of the `BitString`.\n", + "- `Phi`: The estimated phase, computed as $\\frac{\\text{BitInt}}{2^{c_b}}$, where $c_b$ is the number of classical bits.\n", + "- `2*theta`: The phase of the unitary operator in radians, given by $2\\theta = 2\\pi\\varphi$. In this case, the phase of the unitary operator is calculated as twice an angle $\\theta$.\n", + "- `theta`: Half of the phase of the unitary operator, given by $\\theta = \\pi\\varphi$.\n", + "- `theta_90`: The value of $\\theta$ constrained to the interval $(0, \\frac{\\pi}{2})$.\n", + "- `Frequency`: The frequency of each `BitString`.\n", "\n", - "In this module our convention when $|\\Psi\\rangle $ is an eigenvalue of an unitary operator $\\mathcal{Q}$ is:\n", + "The returned DataFrame from `post_process` is ordered by decreasing values of the frequency, so the first row corresponds to the most frequent `BitString`, the second row to the second most frequent value, and so on.\n", "\n", - "$$\\mathcal{Q}|\\Psi\\rangle = e^{2i\\theta}|\\Psi\\rangle$$" + "In this module, our convention when $|\\Psi\\rangle$ is an eigenstate of a unitary operator $\\mathcal{Q}$ is:\n", + "$$\n", + "\\mathcal{Q}|\\Psi\\rangle = e^{2i\\theta}|\\Psi\\rangle\n", + "$$" ] }, { @@ -706,24 +603,19 @@ }, { "cell_type": "markdown", - "id": "aeedbf15", - "metadata": {}, - "source": [ - "### 2.3. Class IQPE: complete execution" - ] - }, - { - "cell_type": "markdown", - "id": "c004c1e3", + "id": "ce1d5f17", "metadata": {}, "source": [ - "In section 2.2 the complete algorithm step by step was explained. The methods shown in this sub-section were shown only for pedagogical purposes but users **SHOULD NOT** use these methods. Instead of these methods user **SHOULD USE** the **iqpe** where all the steps explained in section 2.2 are executed. When using this *method* the following properties are populated:\n", + "### 3.3 Class IQPE: Complete Execution\n", + "\n", + "In Section 2.3, the complete algorithm was explained step by step. The methods described in that subsection were presented for pedagogical purposes only. **Users SHOULD NOT use these methods directly.** Instead, users **SHOULD USE** the `iqpe` method, which executes all the steps outlined in Section 2.2. \n", "\n", + "When using this method, the following properties are populated:\n", "\n", - "* *classical_bits*: the DataFrame with the result of the *measure_classical_bits* method.\n", - "* *final_results*: the DataFrame with the result of the *post_proccess* method.\n", + "- `classical_bits`: A DataFrame containing the results of the `measure_classical_bits` method.\n", + "- `final_results`: A DataFrame containing the results of the `post_process` method.\n", "\n", - "Additionally, the **iqpe** method overwrites the *pdf_time* property of the class where several elapsed times are stored" + "Additionally, the `iqpe` method updates the `pdf_time` property of the class, where several elapsed times are stored." ] }, { @@ -775,7 +667,7 @@ "id": "78d33226", "metadata": {}, "source": [ - "The attribute circuit of the class stores the qlm circuit created" + "The attribute circuit of the class stores the myQLM circuit created" ] }, { @@ -794,11 +686,29 @@ "id": "c90ad5ba", "metadata": {}, "source": [ - "## Another Example\n", + "## 4. IPE example with a 2-qubit gate\n", "\n", - "We are going to reproduce now the Qiskit example under the section *IPE example with a 2-qubit gate* (in https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb). \n", + "We will now reproduce the Qiskit example from the section **IPE example with a 2-qubit gate** (available in [Qiskit Tutorials](https://github.com/Qiskit/qiskit-tutorials/blob/master/tutorials/algorithms/09_IQPE.ipynb)).\n", + "\n", + "In this scenario, the example uses **2 qubits** to estimate the phase of a unitary operator $\\mathcal{cT}$. This operator introduces a phase shift of $\\frac{\\pi}{4}$ radians specifically to the state $|11\\rangle$, leaving all other states unchanged. Mathematically, this behavior is expressed as:\n", + "\n", + "$$\n", + "\\mathcal{cT} |11\\rangle = e^{i\\frac{\\pi}{4}} |11\\rangle.\n", + "$$\n", + "\n", + "From the phase estimation convention, we can derive the relationship between the phase shift and the parameter $\\lambda$:\n", + "\n", + "$$\n", + "i\\frac{\\pi}{4} = 2\\pi i \\lambda.\n", + "$$\n", "\n", - "In this case, they use 2 qubits and want to estimate the phase for a unitary operator $\\mathcal{cT}$ operator. this operator adds a $\\frac{\\pi}{4}$ phase to state $|11\\rangle$ and leave unchanged other states. \n" + "Solving for $\\lambda$, we obtain:\n", + "\n", + "$$\n", + "\\lambda = \\frac{1}{8}.\n", + "$$\n", + "\n", + "This value of $\\lambda$ represents the desired solution for the phase estimation problem in this example.\n" ] }, { @@ -923,39 +833,132 @@ "id": "c5624363", "metadata": {}, "source": [ - "## 3. Application to Amplitude Estimation" + "## 5. Application to Amplitude Estimation\n", + "\n", + "The problem of amplitude estimation is the following. Given an oracle:\n", + "\n", + "$$\\mathcal{0}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle +\\sqrt{1-a}|\\Psi_1\\rangle,$$\n", + "\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, we want to estimate $\\sqrt{a}$.\n", + "\n", + "\n", + "\n", + "\n", + "The *Iterative Quantum Phase Estimation* can be applied to *Amplitude Estimation* problem. The present section explains how we can take advantage of our **IQPE** class for this.\n", + "\n", + "First, we will define the following amplitude estimation problem:\n", + "$$\n", + " \\begin{array}{l}\n", + " & |\\Psi\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", + " & \\sqrt{a}|\\Psi_0\\rangle \\longrightarrow \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle.\\\\\n", + " & \\sqrt{1-a}|\\Psi_1\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", + " \\end{array}\n", + "$$\n", + "\n", + "The following cells create the initial state $|\\Psi\\rangle$" ] }, { "cell_type": "markdown", - "id": "542adf13", + "id": "d0f93be2", "metadata": {}, "source": [ - "The problem of amplitude estimation is the following. Given an oracle:\n", + "## 5. Application to Amplitude Estimation\n", "\n", - "$$\\mathcal{0}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle +\\sqrt{1-a}|\\Psi_1\\rangle,$$\n", + "The problem of **Amplitude Estimation** can be formulated as follows: Given an oracle operator $\\mathcal{A}$ with the following behavior:\n", + "\n", + "$$\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle,\n", + "$$\n", + "\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, our goal is to estimate $\\sqrt{a}$. \n", "\n", - "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, we want to estimate $\\sqrt{a}$." + "The **IQPE** can be applied to **Amplitude Estimation** problem by doing the following steps:\n", + "\n", + "1. **Substitution**: Replace $\\sqrt{a}$ with $\\sin(\\theta)$:\n", + " $$\n", + " \\sqrt{a} = \\sin(\\theta).\n", + " $$\n", + "\n", + "2. **Rewriting the Oracle Operator**: The action of the oracle operator becomes:\n", + " $$\n", + " |\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle = \\sin(\\theta)|\\Psi_0\\rangle + \\cos(\\theta)|\\Psi_1\\rangle. \\tag{1}\n", + " $$\n", + "\n", + "3. **Creating the Grover-like Operator**: Construct the Grover-like operator for the oracle operator $\\mathcal{A}$:\n", + " $$\n", + " \\mathbf{G}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^\\dagger \\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right).\n", + " $$\n", + "\n", + "With these substitutions, the **IQPE** algorithm can now be applied directly by replacing:\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "& |\\Psi\\rangle \\longrightarrow \\mathcal{A}|0\\rangle, \\\\\n", + "& \\mathcal{Q} \\longrightarrow \\mathbf{G}(\\mathcal{A}).\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "It can be demonstrated that the $\\mathbf{G}(\\mathcal{A})$ operator represents a rotation in the plane by an angle $2\\theta$, expressed as:\n", + "\n", + "$$\n", + "\\mathbf{G}(\\mathcal{A}) =\n", + "\\begin{pmatrix}\n", + "\\cos(2\\theta) & -\\sin(2\\theta) \\\\\n", + "\\sin(2\\theta) & \\cos(2\\theta)\n", + "\\end{pmatrix}.\n", + "$$\n", + "\n", + "Thus, the eigenvalues of $\\mathbf{G}(\\mathcal{A})$ are given by $e^{\\pm i2\\theta}$.\n", + "Using the phase estimation convention:\n", + "\n", + "$$\n", + "i2\\theta = 2\\pi i \\lambda,\n", + "$$\n", + "\n", + "we arrive at:\n", + "\n", + "$$\n", + "\\theta = \\pi \\lambda.\n", + "$$\n" ] }, { "cell_type": "markdown", - "id": "74683193", + "id": "99fde3a4", "metadata": {}, "source": [ + "### 5.1 Using `IQPE` class for *Amplitude Estimation*\n", "\n", - "The *Iterative Quantum Phase Estimation* can be applied to *Amplitude Estimation* problem. The present section explains how we can take advantage of our **IQPE** class for this.\n", + "In this section, we will demonstrate how to use the `IQPE` class to solve **Amplitude Estimation** problems.\n", + "\n", + "#### Defining the Amplitude Estimation Problem\n", + "\n", + "We begin by defining the following amplitude estimation problem:\n", "\n", - "First, we will define the following amplitude estimation problem:\n", "$$\n", - " \\begin{array}{l}\n", - " & |\\Psi\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", - " & \\sqrt{a}|\\Psi_0\\rangle \\longrightarrow \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle.\\\\\n", - " & \\sqrt{1-a}|\\Psi_1\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", - " \\end{array}\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", "$$\n", "\n", - "The following cells create the initial state $|\\Psi\\rangle$" + "By comparing equation (2) with the general form of the state in equation (1):\n", + "\n", + "$$\n", + "\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\frac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle,\n", + "$$\n", + "\n", + "and\n", + "\n", + "$$\n", + "\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\frac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$\n", + "\n", + "Here:\n", + "- The target state $|\\Psi_0\\rangle$ corresponds to $|1\\rangle$, and its amplitude is proportional to $\\sin(\\theta)$.\n", + "- The remaining states collectively form $|\\Psi_1\\rangle$, with an amplitude proportional to $\\cos(\\theta)$.\n", + "\n", + "#### Creating the Initial State\n", + "\n", + "The subsequent cells will construct the initial state $|\\Psi\\rangle = \\mathcal{A}|0\\rangle$, which serves as the starting point for our amplitude estimation process. This involves loading the specified probability distribution into a quantum state using the appropriate functions from the library." ] }, { @@ -991,16 +994,7 @@ "id": "6bff697c", "metadata": {}, "source": [ - "Next, we will show how the Phase Estimation problem relates to the Amplitude Estimation problem:\n", - "$$\n", - " \\begin{array}{l}\n", - " & |\\Psi\\rangle \\longrightarrow |\\Psi\\rangle\\\\\n", - " & \\mathcal{P} \\longrightarrow \\mathcal{G}\n", - " \\end{array}\n", - "$$\n", - "The first equation means that, in the phase estimation context, the initial state is $|\\Psi\\rangle$ and the phase operator is $\\mathcal{G}$, the Grover operator corresponding to our amplitude estimation problem. \n", - "\n", - "In the next cell, we define the Grover operator for our problem. " + "Now we need to compute the Grover-like operator from the oracle operator $\\mathcal{A}$. The functions from `AA` module will be used (see notebook **02_Amplitude_Amplification_Operators.ipynb** for more information)" ] }, { @@ -1020,18 +1014,12 @@ }, { "cell_type": "markdown", - "id": "a013c0f9", + "id": "317734bf", "metadata": {}, "source": [ - "Here we have used that our target state $|1\\rangle$ in binary representation is $001$. See notebook *02_AmplitudeAmplification_Operators* for more information about building Grover operators." - ] - }, - { - "cell_type": "markdown", - "id": "13a39fd4", - "metadata": {}, - "source": [ - "Now that we have translated our amplitude amplification problem to a phase estimation problem we proceed to use our class normally. We provide the *oracle* as the **initial_state** and the correspondent Grover-like operator as the **unitary_operator**. Additionally, the number of classical bits (**cbits_number**) for estimating the phase should be provided." + "Now that we have translated our amplitude amplification problem into a phase estimation problem, we can proceed to use our class in the usual way. We provide the `oracle` as the **initial_state** and the corresponding Grover-like operator as the **unitary_operator**. \n", + "\n", + "Additionally, the number of classical bits, `cbits_number` required for estimating the phase should also be specified." ] }, { @@ -1132,34 +1120,26 @@ "In order to increase the precision of the angle estimation more classical bits can be used" ] }, - { - "cell_type": "markdown", - "id": "7b313c99", - "metadata": {}, - "source": [ - "## 4. Amplitude Estimation with IQPE" - ] - }, { "cell_type": "markdown", "id": "302d4e68", "metadata": {}, "source": [ - "In order to simplify **Amplitude Estimation** calculations done with **IQPE** (see Section 3) the **IQPEAE** (stands for **I**terative **Q**uantum **P**hase **E**stimation **A**mplitude **E**stimation) class was created. The idea behind this class is dealing with the initial overheating shown in Section 3 for using **IQPE** in a straightforward way for *Amplitude Estimation* calculations. The class is under the module **ae_iterative_quantum_pe.py** of the *Amplitude Estimation* package of the *QQuantLib* library (**QQuantLib/AE/ae_iterative_quantum_pe**)\n", + "## 6. Amplitude Estimation with IQPE\n", "\n", - "This new class try to reproduce the scheme of the **MLAE class** from **QQuantLib/AE/maximum_likelihood_ae.py** module" - ] - }, - { - "cell_type": "markdown", - "id": "7b3e6cff", - "metadata": {}, - "source": [ - "To create **IQPEAE** object following inputs are mandatory:\n", "\n", - "1. Oracle: QLM AbstractGate or QRoutine with the implementation of the Oracle for creating the Grover operator.\n", - "2. target: this is the marked state in binary representation as a Python list\n", - "3. index: list of the qubits affected by the Grover operator." + "In order to simplify **Amplitude Estimation** calculations performed with the `IQPE` (see Section 3) the `IQPEAE` class (which stands for **I**terative **Q**uantum **P**hase **E**stimation **A**mplitude **Estimation**) was developed. This class addresses the initial complexities outlined in Section 5, enabling a more streamlined approach for using **IQPE** in *Amplitude Estimation* calculations. The `IQPEAE` class is implemented in the module **ae_iterative_quantum_pe** of the *Amplitude Estimation* package within the *QQuantLib* library (**QQuantLib/AE/ae_classical_qpe**).\n", + "\n", + "#### Note:\n", + "The design of the `IQPEAE` class follows a similar structure to the `MLAE` class from the **QQuantLib/AE/maximum_likelihood_ae** module.\n", + "\n", + "To create an instance of the `IQPEAE` class, the following inputs are mandatory:\n", + "\n", + "1. `oracle`: A QLM `AbstractGate` or `QRoutine` that implements the oracle required for constructing the Grover operator.\n", + "2. `target`: The marked state in its binary representation, specified as a Python list.\n", + "3. `index`: A list of qubits that will be affected by the Grover operator.\n", + "\n", + "These inputs ensure that the class is properly configured for executing amplitude estimation tasks efficiently and accurately." ] }, { @@ -1195,15 +1175,18 @@ }, { "cell_type": "markdown", - "id": "61e464a0", + "id": "3a3b0fd9", "metadata": {}, "source": [ - "Now we have all mandatory inputs so we created the *IQPEAE* object. Following **MLAE class** convention other parameters can be provided with a python dictionary. In this case, the following keys can be provided:\n", + "Now that we have all the mandatory inputs, we can create the `IQPEAE` object. Following the conventions of the `MLAE` class, additional parameters can be provided through a Python dictionary. In this case, the following keys are available for configuration:\n", + "\n", "\n", - "* *auxiliar_qbits_number*: number of qubits for doing phase estimation (default: 8)\n", - "* *shots*: number of shots (default: 100)\n", - "* *qpu*: qpu solver\n", - "* mcz_qlm: for using QLM multi-controlled Z gate (True, default) or using multiplexor implementation (False)" + "* `cbits_number`: An integer specifying the number of classical bits needed for phase estimation. \n", + "- `shots`: The number of measurement shots to execute (default: 100).\n", + "- `qpu`: The QPU solver to be used.\n", + "- `mcz_qlm`: A boolean flag indicating whether to use the QLM multi-controlled Z gate (`True`, default) or the multiplexor implementation (`False`).\n", + "\n", + "These parameters allow users to customize the behavior of the `IQPEAE` class according to their specific requirements and computational resources." ] }, { @@ -1243,7 +1226,7 @@ "id": "2955cb2b", "metadata": {}, "source": [ - "When instantiated the **IQPEAE** the *Grover* operator associated to the oracle operator is created. It can be access using attribute *_grover_oracle*." + "When instantiated the `IQPEAE` the *Grover* operator associated to the oracle operator is created. It can be access using attribute `_grover_oracle`." ] }, { @@ -1259,16 +1242,18 @@ }, { "cell_type": "markdown", - "id": "9eeafdcc", + "id": "c6067d05", "metadata": {}, "source": [ - "Now the *ae_iqpe* has all mandatory inputs for calling the **IQPE** class and executing it. The **run** method from **AE_PhaseEstimationwQFT** class does this step. Additionally when the *run* method is executed following properties from **PE_QFTAE** class are populated:\n", + "Now, the `ae_iqpe` object has all the mandatory inputs required to initialize and execute the `IQPE` class. The `run` method performs this step. \n", + "\n", + "Additionally, when the `run` method is executed, the following properties of the `IQPEAE` class are populated:\n", "\n", - "* *iqpe_object*: this property is an object from the **IQPE** class that was initialized with corresponding arguments created by the **IQPEAE** class.\n", - "* *final_results*: this is the final_results obtained from the *iqpe* method of the property object *iqpe_object* (see section 2.3).\n", - "* *theta*: estimated $\\theta$ obtained from **IQPE** algorithm.\n", - "* *ae*: estimated solution of the *Amplitude Amplification* problem using **IQPE** algorithm $a=cos^2 \\theta$\n", - "* *run_time*: elapsed time of a complete **run** method" + "- `iqpe_object`: This property is an instance of the `IQPE` class, initialized with the corresponding arguments created by the `IQPEAE` class.\n", + "- `final_results`: These are the results obtained from the `iqpe` method of the `iqpe_object` property (see Section 2.3).\n", + "- `theta`: The estimated $\\theta$ value obtained from the **IQPE** algorithm.\n", + "- `ae`: The estimated solution of the *Amplitude Estimation* problem using the **IQPE** algorithm, given by $ a = \\cos^2(\\theta) $.\n", + "- `run_time`: The elapsed time for the complete execution of the `run` method." ] }, { @@ -1372,17 +1357,11 @@ "id": "feb8acc0", "metadata": {}, "source": [ - "The **run** method of the *IQPEAE* creates and configures an object from *IQPE* class. This object is stored on the **pe_iqpe** attribute of the *IQPEAE*. All the attributes and methods from the *IQPE* class can be accessed from this **pe_iqpe** attribute. For example, we can get the complete quantum circuit executed!!!" - ] - }, - { - "cell_type": "markdown", - "id": "a41b5f38", - "metadata": {}, - "source": [ - "As explained before, the **iqpe_object** attribute from the *IQPEAE* class is an object created from class **IQPE** so all the methods and attributes of this class can be accessed using the **iqpe_object** attribute (**NOT RECOMMENDED**).\n", + "The `run` method of the `IQPEAE` class creates and configures an object from the `IQPE` class. This object is stored in the `pe_iqpe` attribute of the `IQPEAE` class. All attributes and methods of the `IQPE` class can be accessed through this `pe_iqpe` attribute. For example, you can retrieve the complete quantum circuit that was executed!\n", + "\n", + "As explained earlier, the `iqpe_object` attribute of the `IQPEAE` class is an instance of the `IQPE` class. Therefore, all methods and attributes of the `IQPE` class can be accessed via the **iqpe_object** attribute, although this is **not recommended**.\n", "\n", - "We can access the quantum circuit used by the **IQPE**" + "You can also access the quantum circuit used by the **IQPE** algorithm through these attributes." ] }, { @@ -1405,11 +1384,11 @@ "id": "f7d0885d", "metadata": {}, "source": [ - "When the *run* method is executed following class attributes are populated:\n", + "When the `run` method is executed following class attributes are populated:\n", "\n", - "* *circuit_statistics*: python dictionary with the complete statistical information of the circuit.\n", - "* *oracle_calls*: number of total oracle calls for a complete execution of the algorithm\n", - "* *max_oracle_depth*: maximum number of applications of the oracle for the complete execution of the algorithm." + "* `circuit_statistics`: python dictionary with the complete statistical information of the circuit.\n", + "* `oracle_calls`: number of total oracle calls for a complete execution of the algorithm\n", + "* `max_oracle_depth`: maximum number of applications of the oracle for the complete execution of the algorithm." ] }, { @@ -1467,9 +1446,9 @@ ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1481,7 +1460,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/06_Iterative_Quantum_Amplitude_Estimation_class.ipynb b/misc/notebooks/06_Iterative_Quantum_Amplitude_Estimation_class.ipynb index d47e3c6..dd69828 100644 --- a/misc/notebooks/06_Iterative_Quantum_Amplitude_Estimation_class.ipynb +++ b/misc/notebooks/06_Iterative_Quantum_Amplitude_Estimation_class.ipynb @@ -2,34 +2,26 @@ "cells": [ { "cell_type": "markdown", - "id": "3db5dafe", + "id": "9fb07d3a", "metadata": {}, "source": [ - "# Iterative Quantum Amplitude Estimation (IQAE) module" - ] - }, - { - "cell_type": "markdown", - "id": "7d6cb636", - "metadata": {}, - "source": [ - "The present notebook reviews the **Iterative Quantum Amplitude Estimation** (**IQAE**) algorithm. \n", + "# Iterative Quantum Amplitude Estimation (IQAE) module\n", "\n", - "**BE AWARE** this algorithm is different from the **Iterative Quantum Phase Estimation** (**IQPE**). The second one is an algorithm for pure *phase estimation* of a unitary operator meanwhile the first one is an algorithm for direct solving of **Amplitude Estimation** problem based on the *amplification* capabilities of a Grover operator. \n", + "The present notebook reviews the **Iterative Quantum Amplitude Estimation (IQAE)** algorithm. \n", "\n", - "The **IQAE** algorithm was implemented into the module *iterative_quantum_ae* of the package *AE* of the library *QQuantLib* (**QQuantLib/AE/iterative_quantum_ae.py**). This algorithm was developed as a Python class called: *IQAE*" - ] - }, - { - "cell_type": "markdown", - "id": "bcf02895", - "metadata": {}, - "source": [ - "The present notebook and modules are based on the following references:\n", + "**BE AWARE**: This algorithm is different from the **Iterative Quantum Phase Estimation (IQPE)**. The latter is an algorithm for pure *phase estimation* of a unitary operator, while the former is an algorithm for directly solving the **Amplitude Estimation** problem based on the *amplification* capabilities of a Grover operator. \n", + "\n", + "The **IQAE** algorithm has been implemented in the module *iterative_quantum_ae* of the package **AE** in the library `QQuantLib` (**QQuantLib/AE/iterative_quantum_ae**). This algorithm is encapsulated in a Python class called `IQAE`. \n", + "\n", + "The present notebook and modules are based on the following references: \n", "\n", - "* *Grinko, D., Gacon, J., Zoufal, C. & Woerner, S.*. Iterative Quantum Amplitude Estimation. npj Quantum Information 7, 2021. https://www.nature.com/articles/s41534-021-00379-1\n", + "- **Grinko, D., Gacon, J., Zoufal, C., & Woerner, S.** \n", + " Iterative Quantum Amplitude Estimation. npj Quantum Information **7**, (2021). \n", + " [https://www.nature.com/articles/s41534-021-00379-1](https://www.nature.com/articles/s41534-021-00379-1) \n", "\n", - "* NEASQC deliverable: *D5.1: Review of state-of-the-art for Pricing and Computation of VaR https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf*" + "- NEASQC deliverable: \n", + " *D5.1: Review of state-of-the-art for Pricing and Computation of VaR* \n", + " [https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf](https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf)" ] }, { @@ -104,11 +96,18 @@ }, { "cell_type": "markdown", - "id": "71279f09", + "id": "8a27ad6c", "metadata": {}, "source": [ - "Before doing any amplitude estimation we want to load some data into the quantum circuit, as this step is only auxiliary to see how the algorithm works, we are just going to load a discrete probability distribution. In this case, we will have a circuit with $n=3$ qubits which makes a total of $N = 2^n = 8$ states. The discrete probability distribution that we are going to load is:\n", - "$$p_d = \\dfrac{(0,1,2,3,4,5,6,7)}{0+1+2+3+4+5+6+7+8}.$$\n" + "Before performing any amplitude estimation, we first need to load data into the quantum circuit. As this step is auxiliary and intended to demonstrate how the algorithm works, we will simply load a discrete probability distribution. \n", + "\n", + "In this example, we will use a quantum circuit with $ n = 3 $ qubits, which corresponds to a total of $ N = 2^n = 8 $ computational basis states. The discrete probability distribution we aim to load is defined as:\n", + "\n", + "$$\n", + "p_d = \\frac{(0, 1, 2, 3, 4, 5, 6, 7)}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}.\n", + "$$\n", + "\n", + "This distribution assigns probabilities proportional to the integers $ 0 $ through $ 7 $, normalized by their sum to ensure that the total probability equals 1." ] }, { @@ -125,10 +124,10 @@ }, { "cell_type": "markdown", - "id": "716492db", + "id": "175f110e", "metadata": {}, "source": [ - "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function *load_probability* from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", + "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function `load_probability` from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", " $$|\\Psi\\rangle = \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$" ] }, @@ -162,101 +161,96 @@ }, { "cell_type": "markdown", - "id": "a08e43a5", - "metadata": {}, - "source": [ - "## 2. IQAE algorithm." + "id": "54dfee71", + "metadata": {}, + "source": [ + "## 2. IQAE Algorithm\n", + "\n", + "The problem of amplitude estimation can be stated as follows. Given an oracle operator $\\mathcal{A}$:\n", + "$$\n", + "\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle,\n", + "$$\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, the goal is to estimate $\\sqrt{a}$. We can define an associated angle $\\theta$ such that $\\sin^2{\\theta} = a$, rewriting the problem as:\n", + "$$\n", + "\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sin(\\theta)|\\Psi_0\\rangle + \\cos(\\theta)|\\Psi_1\\rangle. \\tag{1}\n", + "$$\n", + "\n", + "The foundation of any amplitude estimation algorithm is the Grover-like operator $\\mathcal{Q}$ derived from the oracle operator $\\mathcal{A}$:\n", + "$$\n", + "\\mathcal{Q}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^\\dagger \\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right).\n", + "$$\n", + "This Grover-like operator has the following effect on the state $|\\Psi\\rangle$:\n", + "$$\n", + "\\mathcal{Q}^{m_k}|\\Psi\\rangle = \\mathcal{Q}^{m_k} \\mathcal{A} |0\\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle + \\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle,\n", + "$$\n", + "where $m_k$ is an integer parameter.\n", + "\n", + "Using these ingredients, the **IQAE** algorithm, given an input error $\\epsilon$ and a confidence interval $\\alpha$, allows us to estimate $(\\theta_l, \\theta_u)$ such that the angle $\\theta$ of the **Amplitude Estimation (AE)** problem satisfies:\n", + "$$\n", + "P\\big[\\theta \\in [\\theta_l, \\theta_u]\\big] > 1 - \\alpha\n", + "$$\n", + "and\n", + "$$\n", + "\\frac{\\theta_u - \\theta_l}{2} \\leq \\epsilon.\n", + "$$\n", + "\n", + "This result can be directly extended to $a = \\sin^2{\\theta}$, so the **IQAE** algorithm provides $(a_l, a_u)$ that satisfies:\n", + "$$\n", + "P\\big[a \\in [a_l, a_u]\\big] > 1 - \\alpha\n", + "$$\n", + "and\n", + "$$\n", + "\\frac{a_u - a_l}{2} \\leq \\epsilon.\n", + "$$" ] }, { "cell_type": "markdown", - "id": "c977b4c6", + "id": "029e0cf4", "metadata": {}, "source": [ - "### 2.1 The Amplitude Estimation Problem\n", + "### 2.1 The `IQAE` Class\n", "\n", - "The problem of amplitude estimation is the following. Given an oracle operator $\\mathcal{A}$ :\n", + "We have implemented a Python class called `IQAE` in the **QQuantLib/AE/iterative_quantum_ae** module, which allows us to use the **IQAE** algorithm.\n", "\n", - "$$\\mathcal{A}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle +\\sqrt{1-a}|\\Psi_1\\rangle,$$\n", + "When creating the `IQAE` class, we followed the conventions used in the `MLAE` class from the **QQuantLib/AE/maximum_likelihood_ae** module. The following are the mandatory inputs for initializing the `IQAE` class:\n", "\n", - "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, we want to estimate $\\sqrt{a}$. We can define an associated angle to $\\sqrt{a}$ as $\\sin^2{\\theta} = a$, and the problem is thus rewritten as:\n", + "1. `oracle`: A QLM `AbstractGate` or `QRoutine` that implements the Oracle for constructing the Grover operator.\n", + "2. `target`: The marked state in binary representation, provided as a Python list.\n", + "3. `index`: A list of qubits affected by the Grover operator.\n", "\n", - "$$\\mathcal{A}|0\\rangle = |\\Psi \\rangle = \\sin(\\theta)|\\Psi_0\\rangle +\\cos(\\theta)|\\Psi_1\\rangle, \\tag{1}$$\n", + "Additionally, there are optional inputs for configuring the algorithm, which can be provided as a Python dictionary:\n", + "- `qpu`: The QLM solver to be used (default: PyLinalg).\n", + "- `epsilon` ($\\epsilon$): The desired precision. Ensures that the width of the interval is at most $2\\epsilon$ (default: 0.01).\n", + "- `alpha` ($\\alpha$): The confidence level. Ensures that the probability of $a$ not lying within the given interval is at most $\\alpha$ (default: 0.05).\n", + "- `shots`: The number of shots for each iteration of the algorithm (default: 100).\n", + "- `mcz_qlm`: A flag to use the QLM multi-controlled Z gate (`True`, default) or a multiplexor implementation (`False`).\n", "\n", + "---\n", "\n", - "The foundation of any amplitude estimation algorithm is the Grover-like operator $\\mathcal{Q}$ of the oracle operator $\\mathcal{A}$:\n", + "#### Example\n", "\n", - "$$\\mathcal{Q}(\\mathcal{A}) = \\mathcal{A} \\left(\\hat{I} - 2|0\\rangle\\langle 0|\\right) \\mathcal{A}^{\\dagger}\\left(\\hat{I} - 2|\\Psi_0\\rangle\\langle \\Psi_0|\\right)$$\n", + "To demonstrate how the `IQAE` class and the algorithm work, consider the following amplitude estimation problem:\n", "\n", - "This Grover-like operator has the following effect over our state $|\\Psi\\rangle$:\n", + "$$\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", + "$$\n", "\n", - "$$\\mathcal{Q}^{m_k}|\\Psi\\rangle = \\mathcal{Q}^{m_k} \\mathcal{A} |0\\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle +\\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle,$$\n", + "By comparing Equation (2) with Equation (1):\n", "\n", - "for more information about the grover operator and the amplitude amplification algorithm check the notebook **02_AmplitudeAmplification_Operators.ipynb**.\n", - "\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "bdc418d8", - "metadata": {}, - "source": [ - "### 2.2 IQAE algorithm summary\n", - "\n", - "Given an error $\\epsilon$ and a confident interval $\\alpha$, the **IAQE** algorithm allows to estimate $(\\theta_l, \\theta_u)$ such that the $\\theta$ angle of the Amplitude Estimation problem satisfies that:\n", - "\n", - "$$P\\big[\\theta \\in [\\theta_l, \\theta_u]\\big] \\gt 1-\\alpha$$\n", + "$$\n", + "\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle,\n", + "$$\n", "\n", "and\n", "\n", - "$$\\frac{\\theta_u-\\theta_l}{2} \\leq \\epsilon$$\n", - "\n", - "\n", - "This result can be extended in a straightforward way to $a=\\sin^2{\\theta}$, so the **IQPE** algorithm will provide $(a_l, a_u)$ that satisfies:\n", - "\n", - "$$P\\big[a \\in [a_l, a_u]\\big] \\gt 1-\\alpha$$\n", - "\n", - "and\n", - "\n", - "$$\\frac{a_u-a_l}{2} \\leq \\epsilon$$\n" - ] - }, - { - "cell_type": "markdown", - "id": "585ab1ff", - "metadata": {}, - "source": [ - "### 2.3 Creating object from IQAE class" - ] - }, - { - "cell_type": "markdown", - "id": "c4c29943", - "metadata": {}, - "source": [ - "We have implemented a Python class called **IQAE** into the **QQuantLib/AE/iterative_quantum_ae** module that allows us to use the **IQAE** algorithm." - ] - }, - { - "cell_type": "markdown", - "id": "35d74afa", - "metadata": {}, - "source": [ - "For creating the **IQAE** class the conventions used in **MLAE class** from **QQuantLib/AE/maximum_likelihood_ae.py** module should be followed: \n", - "\n", - "We have some mandatory inputs:\n", + "$$\n", + "\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$\n", "\n", - "1. Oracle: QLM AbstractGate or QRoutine with the implementation of the Oracle for creating the Grover operator.\n", - "2. target: this is the marked state in binary representation as a Python list\n", - "3. index: list of the qubits affected by the Grover operator.\n", + "In this case, the target state is $|1\\rangle$, whose binary representation is $001$. This must be passed to the `target` variable as a list (`[0, 0, 1]`). Additionally, we need to provide the list of qubits where the operation is being performed. In this example, it is $[0, 1, 2]$, corresponding to the entire register.\n", "\n", - "And some optional inputs, used for algorithm configuration, that can be given as a Python dictionary:\n", - "* qpu: QLM solver that will be used\n", - "* epsilon ($\\epsilon$): the precision. Ensures that the width of the interval is (see Section 2.2), at most, $2\\epsilon$ (default: 0.01).\n", - "* alpha ($\\alpha$): the accuracy. Ensures that the probability of $a$ not laying within the given interval (see Section 2.2) is, at most, $\\alpha$ (default: 0.05).\n", - "* shots: the number of shots on each iteration of the algorithm (default: 100).\n", - "* mcz_qlm: for using QLM multi-controlled Z gate (True, default) or using multiplexor implementation (False)\n" + "This setup allows us to estimate the amplitude $\\sqrt{a}$ using the **IQAE** algorithm." ] }, { @@ -270,26 +264,6 @@ "from QQuantLib.AE.iterative_quantum_ae import IQAE" ] }, - { - "cell_type": "markdown", - "id": "0428418a", - "metadata": {}, - "source": [ - "To show how our class and the algorithm work, we will define the following amplitude estimation problem:\n", - "\n", - "$$|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right] \\tag{2}$$\n", - "\n", - "So comparing (2) with (1):\n", - "\n", - "$$\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle$$\n", - "\n", - "and \n", - "\n", - "$$\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$\n", - "\n", - "The target state, in this case, is $|1\\rangle$. Its binary representation is $001$. This has to be passed to the target variable as a list. Moreover, we have to provide the list of qubits where we are acting, in this case is just $[0,1,2]$, the whole register." - ] - }, { "cell_type": "code", "execution_count": null, @@ -331,7 +305,7 @@ "id": "22f6678c", "metadata": {}, "source": [ - "When the class is created the based oracle Grover operator is created too and can be acces using the **\\_grover_oracle** property of the class" + "When the class is created the based oracle Grover operator is created too and can be acces using the `_grover_oracle` property of the class" ] }, { @@ -347,65 +321,80 @@ }, { "cell_type": "markdown", - "id": "0f13e5fe", + "id": "2a356220", "metadata": {}, "source": [ - "### 2.4 IQAE algorithm scheme.\n", - "\n", + "### 2.2 IQAE Algorithm Scheme\n", "\n", "As explained, the inputs for the **IQAE** algorithm are:\n", + "- Error in the estimation of the angle $\\theta$: $\\epsilon$.\n", + "- Confidence interval for $\\theta$: $\\alpha$.\n", "\n", - "* Error in the estimation of the angle $\\theta$: $\\epsilon$.\n", - "* Confidence interval for the $\\theta$: $\\alpha$.\n", + "The main steps of the **IQAE** algorithm, in a simplified form, are as follows:\n", "\n", - "The main steps of the **IQAE** algorithm, in a simplified way, are:\n", + "1. **Initialization**: The algorithm initializes the limits for the angle to be estimated, $\\theta$, to $[\\theta_l, \\theta_u] = [0, \\frac{\\pi}{2}]$.\n", "\n", - "1. The algorithm initializes the limits for the angle to estimation, $\\theta$, to $[\\theta_l, \\theta_u] = [0, \\frac{\\pi}{2}]$.\n", - "2. The algorithm calculates the maximum number of iterations $T$ that should be necessary in order to satisfy the error estimation $\\epsilon$:\n", - " * $T(\\epsilon) \\in \\mathcal{N} \\; / \\;T(\\epsilon) \\geq \\log_2(\\frac{\\pi}{8\\epsilon})$\n", - " * In the framework of the **IQAE** algorithm an iteration is a selection of a different integer $k$\n", + "2. **Calculation of Maximum Iterations**: The algorithm calculates the maximum number of iterations $T$ required to satisfy the error estimation $\\epsilon$:\n", + " $$\n", + " T(\\epsilon) \\in \\mathbb{N} \\; / \\; T(\\epsilon) \\geq \\log_2\\left(\\frac{\\pi}{8\\epsilon}\\right).\n", + " $$\n", + " In the context of the **IQAE** algorithm, an iteration corresponds to the selection of a different integer $k$.\n", "\n", - "4. Selection of $k$ in a algorithm iteration. **This is the critical routine of the algorithm**: the routine tries to obtain the biggest $k$ (until some fixed limit) that ensures that $(4*k+2)\\theta_l$ and $(4*k+2)\\theta_u$ are contained totally in the $[0,\\pi]$ or the $[\\pi, 2\\pi]$ semi plane. If this is obtained the selection routine will return the $k$ and the semi plane.\n", - "5. For a selected $k$ the **IQAE** algorithm creates the corresponding circuit for doing:\n", - " * $$\\mathcal{G}^{m}|\\Psi\\rangle = |\\Psi \\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle +\\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle,$$\n", + "3. **Selection of $k$**: This is the critical routine of the algorithm. The routine attempts to find the largest $k$ (up to a fixed limit) such that $(4k+2)\\theta_l$ and $(4k+2)\\theta_u$ are entirely contained within either the $[0, \\pi]$ or $[\\pi, 2\\pi]$ semi-plane. If this condition is met, the selection routine returns the $k$ and the corresponding semi-plane.\n", "\n", - "6. Using $N$ shots compute the probability $a_k$ of obtaining the $|\\Psi_0\\rangle$ that will be:\n", + "4. **Circuit Creation for Selected $k$**: For the selected $k$, the **IQAE** algorithm creates the corresponding quantum circuit to perform:\n", + " $$\n", + " \\mathcal{G}^{m}|\\Psi\\rangle = |\\Psi\\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle + \\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle.\n", + " $$\n", "\n", - "$$P(|\\Psi_0\\rangle, k) = \\sin^2((2*+1)\\theta) = \\frac{1-\\cos((4k+2)\\theta)}{2}=a_k$$\n", + "5. **Probability Estimation**: Using $N$ shots, compute the probability $a_k$ of obtaining $|\\Psi_0\\rangle$, which is given by:\n", + " $$\n", + " P(|\\Psi_0\\rangle, k) = \\sin^2((2k+1)\\theta) = \\frac{1 - \\cos((4k+2)\\theta)}{2} = a_k.\n", + " $$\n", "\n", - "7. Using the number of measurements $N$, $T$ and $\\alpha$ the algorithm calculates $\\epsilon_{a_k}$ using:\n", + "6. **Error Calculation**: Using the number of measurements $N$, $T$, and $\\alpha$, the algorithm calculates $\\epsilon_{a_k}$ using:\n", + " $$\n", + " \\epsilon_{a_k} = \\sqrt{\\frac{1}{2N} \\log\\left(\\frac{2T}{\\alpha}\\right)}.\n", + " $$\n", "\n", - " * $\\epsilon_{a_{k}} = \\sqrt{\\frac{1}{2N}\\log(\\frac{2T}{\\alpha})}$\n", + "7. **Computation of Limits for $a_k$**: Using $\\epsilon_{a_k}$, the algorithm computes the limits for $a_k$: $a_k^{\\text{min}}$ and $a_k^{\\text{max}}$.\n", "\n", - "8. Using the $\\epsilon_{a_{k}}$ the algorithm computes some limits for $a_k$: $a_{k}^{min}$ and $a_{k}^{max}$\n", + "8. **Computation of $\\theta_k^{\\text{min}}$ and $\\theta_k^{\\text{max}}$**: The algorithm computes $\\theta_k^{\\text{min}}$ and $\\theta_k^{\\text{max}}$ from $a_k^{\\text{min}}$ and $a_k^{\\text{max}}$, using:\n", + " $$\n", + " a_k = \\frac{1 - \\cos((4k+2)\\theta)}{2},\n", + " $$\n", + " and the fact that $a_k^{\\text{min}}$ and $a_k^{\\text{max}}$ lie in one of the semi-planes: $[0, \\pi]$ or $[\\pi, 2\\pi]$ (as determined by the selection routine in step 3).\n", "\n", - "9. The algorithm computes $\\theta_{k}^{min}$ and $\\theta_{k}^{max}$ from $a_{k}^{min}$ and $a_{k}^{max}$, using:\n", - " * $a_k = \\frac{1-\\cos((4k+2)\\theta)}{2}$\n", - " * and the fact that $a_{k}^{min}$ and $a_{k}^{max}$ should be in one of the semi-planes: $[0,\\pi]$ or the $[\\pi, 2\\pi]$ (this is given by the selection routine of step 3)\n", + "9. **Updating $\\theta_l$ and $\\theta_u$**: The algorithm updates $\\theta_l$ and $\\theta_u$ using $\\theta_k^{\\text{min}}$ and $\\theta_k^{\\text{max}}$, respectively, and the fact that the rotation due to $k$ applications of the Grover operator is $(4k+2)\\theta$.\n", "\n", - "10. Updating $\\theta_l$ and $\\theta_u$ using $\\theta_{k}^{min}$ and $\\theta_{k}^{max}$ respectively and the fact that the rotation due to $k$ application of Grover algorithm is $(4k+2)\\theta$\n", + "At the end of each iteration, $\\theta_u - \\theta_l$ becomes smaller than at the beginning. The algorithm stops when $\\theta_u - \\theta_l \\leq 2\\epsilon$.\n", "\n", - "At the end of each iteration $\\theta_l-\\theta_u$ is lower than at the beginning. When $\\theta_u-\\theta_l \\leq 2\\epsilon$ the algorithm stops. \n", + "---\n", "\n", - "**NOTE**\n", + "**NOTE**:\n", + "1. To ensure that $\\theta_u - \\theta_l \\leq 2\\epsilon$, the number of iterations should not exceed $T$, where:\n", + " $$\n", + " T(\\epsilon) \\geq \\log_2\\left(\\frac{\\pi}{8\\epsilon}\\right).\n", + " $$\n", "\n", - "1. To ensure that $\\theta_u-\\theta_l \\leq 2\\epsilon$ is necessary that the number of iterations should be at most equal to T ($T(\\epsilon) \\geq \\log_2(\\frac{\\pi}{8\\epsilon})$).\n", - "2. To ensure that $P\\big[\\theta \\in [\\theta_l, \\theta_u]\\big] \\gt 1-\\alpha$ is mandatory that the error of each iteration should be: $\\epsilon_{a_{k}} = \\sqrt{\\frac{1}{2N}\\log(\\frac{2T}{\\alpha})}$\n", - "\n" + "2. To ensure that $P\\big[\\theta \\in [\\theta_l, \\theta_u]\\big] > 1 - \\alpha$, it is mandatory that the error of each iteration satisfies:\n", + " $$\n", + " \\epsilon_{a_k} = \\sqrt{\\frac{1}{2N} \\log\\left(\\frac{2T}{\\alpha}\\right)}.\n", + " $$" ] }, { "cell_type": "markdown", - "id": "cea0ccb5", + "id": "bf9801d3", "metadata": {}, "source": [ - "### 2.5 Example of IQAE workflow\n", + "### 2.3 Example of IQAE Workflow\n", "\n", - "Section 2.4 presents a simple plot of the **IQAE** algorithm scheme. In the present section, we show an example of how this scheme is used for getting some intuition of how the **IQAE** algorithm works. We will split the algorithm in 3 steps:\n", + "Section 2.3 provides a simple illustration of the **IQAE** algorithm scheme. In this section, we present an example to provide intuition about how the **IQAE** algorithm operates. We will divide the algorithm into three main steps:\n", "\n", - "* Initialization.\n", - "* First iteration with $k=0$.\n", - "* Next iterations with $k\\geq 0$" + "- **Initialization**.\n", + "- **First Iteration with $k = 0$**.\n", + "- **Subsequent Iterations with $k \\geq 1$**." ] }, { @@ -413,7 +402,7 @@ "id": "ba870805", "metadata": {}, "source": [ - "#### 2.5.1 Initialization\n", + "#### 2.3.1 Initialization\n", "\n", "We need to do the initialization of the algorithm (setting the initial $\\theta_l$, $\\theta_u$) and getting the maximum number of iterations from $\\epsilon$ ($T(\\epsilon)$)" ] @@ -444,7 +433,7 @@ "id": "4e8364e3", "metadata": {}, "source": [ - "#### 2.5.2 First iteration with $k=0$.\n", + "#### 2.3.2 First iteration with $k=0$.\n", "\n", "In the first iteration, we are going to set $k=0$. Then we execute the complete iteration workflow:" ] @@ -536,30 +525,25 @@ }, { "cell_type": "markdown", - "id": "def39c9d", + "id": "41b144dc", "metadata": {}, "source": [ - "#### 2.5.3 Next iterations with $k\\geq 0$" - ] - }, - { - "cell_type": "markdown", - "id": "52a7a751", - "metadata": {}, - "source": [ - "In the next iteration, the first step is getting the $k$ for the correspondent iteration. As explained in step 3 of Section 2.3 this is the **critical** routine of the algorithm. This **routine** will use the currents $\\theta_l$, $\\theta_u$ and the before step $k$ for computing the biggest $k$ (until some limit) that will ensure that $(4*k+2)\\theta_l$ and $(4*k+2)\\theta_u$ are contained totally in the $[0,\\pi]$ or the $[\\pi, 2\\pi]$ semi plane. This is done by the *find_next_k* method of the class. This method needs as input:\n", + "#### 2.3.3 Next Iterations with $ k \\geq 0 $\n", "\n", - "* k: $k$ of the before iteration\n", - "* theta_lower: $\\theta_l$\n", - "* theta_upper: $\\theta_u$\n", - "* flag: flag for keeping track of the semi plane (True for $[0, \\pi]$)\n", - "* r: parameter of the routine (default 2). \n", + "In the subsequent iterations, the first step is to determine the $ k $ for the current iteration. As explained in Step 3 of Section 2.2, this is the **critical** routine of the algorithm. This **routine** uses the current values of $ \\theta_l $, $ \\theta_u $, and the $ k $ from the previous iteration to compute the largest $ k $ (up to a predefined limit) that ensures $ (4k+2)\\theta_l $ and $ (4k+2)\\theta_u $ are entirely contained within either the $ [0, \\pi] $ or $ [\\pi, 2\\pi] $ semi-plane.\n", + "\n", + "This is achieved using the `find_next_k` method of the class. The method requires the following inputs:\n", + "- `k`: The $ k $ value from the previous iteration.\n", + "- `theta_lower`: The current lower bound $ \\theta_l $.\n", + "- `theta_upper`: The current upper bound $ \\theta_u $.\n", + "- `flag`: A flag for tracking the semi-plane (True for $ [0, \\pi] $).\n", + "- `r`: A parameter of the routine (default value is 2).\n", "\n", "The outputs of the method will be:\n", - "* k: the new $k$ for the current iteration.\n", - "* flag: semi plane where $(4*k+2)\\theta_l$ and $(4*k+2)\\theta_u$ will be contained (True for $[0, \\pi]$)\n", - " \n", - "For executing the complete iteration execute the following cell" + "- `k`: The new $ k $ for the current iteration.\n", + "- `flag`: The semi-plane where $ (4k+2)\\theta_l $ and $ (4k+2)\\theta_u $ will be contained (True for $ [0, \\pi] $).\n", + "\n", + "To execute the complete iteration, run the following cell." ] }, { @@ -567,7 +551,7 @@ "execution_count": null, "id": "1a5a774d", "metadata": { - "scrolled": true + "scrolled": false }, "outputs": [], "source": [ @@ -642,7 +626,7 @@ "execution_count": null, "id": "92960459", "metadata": { - "scrolled": true + "scrolled": false }, "outputs": [], "source": [ @@ -659,32 +643,24 @@ }, { "cell_type": "markdown", - "id": "500c7955", + "id": "2069676c", "metadata": {}, "source": [ - "## 3. IQAE complete execution.\n", - "\n", - "In section 2.4 the basic scheme of the **IQAE** algorithm was plotted for pedagogical purposes. The **IQAE** class deals with the code presented in section 2.4 (in fact implements some improvements for getting a better performance) in a transparent way. It is expected that the user of the **IQAE** class only executes the following methods:\n", + "## 3. IQAE Complete Execution\n", "\n", - "* *iqae* method.\n", - "* *run* method\n", - "* *display_information* method" - ] - }, - { - "cell_type": "markdown", - "id": "039b90b5", - "metadata": {}, - "source": [ - "### 3.1 The *iqae* method\n", + "In Section 2.4, the basic scheme of the **IQAE** algorithm was outlined for pedagogical purposes. The `IQAE` class encapsulates the code presented in Section 2.4 (while implementing additional optimizations for better performance) in a user-friendly manner. It is expected that users of the `IQAE` class will primarily interact with the following methods:\n", + "- `iqae` method\n", + "- `run` method\n", + "- `display_information` method\n", "\n", - "To execute the complete algorithm using the **IQAE** class the *iqae* method is used. This method has the following inputs:\n", + "### 3.1 The `iqae` Method\n", "\n", - "* epsilon ($\\epsilon$): error in the estimation of the angle $\\theta$ (default: 0.01).\n", - "* shots: number of shots for the measurement of the circuit ($N_{shots}$ (default: 100).\n", - "* alpha ($\\alpha$): confidence interval for the $\\theta$ (default: 0.05).\n", + "To execute the complete algorithm using the `IQAE` class, the `iqae` method is used. This method accepts the following inputs:\n", + "- `epsilon` ($\\epsilon$): Error in the estimation of the angle $\\theta$ (default: 0.01).\n", + "- `shots`: Number of shots for the measurement of the circuit ($N_{\\text{shots}}$) (default: 100).\n", + "- `alpha` ($\\alpha$): Confidence interval for $\\theta$ (default: 0.05).\n", "\n", - "This method returns the limits for the $a$ estimation: $(a_{\\min},a_{\\max})$" + "This method returns the limits for the $a$ estimation: $(a_{\\min}, a_{\\max})$." ] }, { @@ -749,7 +725,7 @@ "id": "0840e20a", "metadata": {}, "source": [ - "We can obtain the complete statistics of all the circuits used during the algorithm execution calling the *circuit_statistics* attribute" + "We can obtain the complete statistics of all the circuits used during the algorithm execution calling the `circuit_statistics` attribute" ] }, { @@ -767,9 +743,9 @@ "id": "dca98814", "metadata": {}, "source": [ - "### 3.2 *display_information* method\n", + "### 3.2 `display_information` method\n", "\n", - "The display method gives some information of the inner working of the **IQAE** algorithm. The inputs are the same that for the *iqae* method." + "The `display_information` method gives some information of the inner working of the **IQAE** algorithm. The inputs are the same that for the *iqae* method." ] }, { @@ -788,20 +764,22 @@ }, { "cell_type": "markdown", - "id": "98c50ad8", + "id": "e5a0cf15", "metadata": {}, "source": [ - "### 3.3 The *run* method\n", + "### 3.3 The `run` Method\n", + "\n", + "A `run` method has been implemented for the direct execution of the **IQAE** algorithm. In this case, the user can configure all the properties of the `IQAE` class, and the `run` method will execute the algorithm using the predefined attributes of the class. \n", "\n", - "Finally, a *run* method for direct implementation of the **IQAE** algorithm was implemented. In this case, the user can configure all the properties of the **IQAE** class and the *run* method will execute the method using the fixed attributes of the class. Finally, the method returns the estimation of $a=\\frac{a_u+a_l}{2}$. Additionally, the *run* method populates the following class attributes:\n", + "The method returns the estimation of $ a = \\frac{a_u + a_l}{2} $. Additionally, the `run` method populates the following class attributes:\n", "\n", - "* *ae_l*: the lower limit for a $a_l$.\n", - "* *ae_u*: the upper limit for a $a_u$.\n", - "* *theta_l*: the lower limit for $\\theta$: $\\theta_l$.\n", - "* *theta_u*: the upper limit for $\\theta$: $\\theta_u$.\n", - "* *ae*: the amplitude estimation parameter computing as: $a=\\frac{a_u+a_l}{2}$\n", - "* *theta*: the estimated $\\theta=\\frac{\\theta_u+\\theta_l}{2}$\n", - "* *run_time*: the elpased time of the **run** method." + "- `ae_l`: The lower limit for $ a $: $ a_l $.\n", + "- `ae_u`: The upper limit for $ a $: $ a_u $.\n", + "- `theta_l`: The lower limit for $ \\theta $: $ \\theta_l $.\n", + "- `theta_u`: The upper limit for $ \\theta $: $ \\theta_u $.\n", + "- `ae`: The amplitude estimation parameter, computed as $ a = \\frac{a_u + a_l}{2} $.\n", + "- `theta`: The estimated angle $ \\theta = \\frac{\\theta_u + \\theta_l}{2} $.\n", + "- `run_time`: The elapsed time for the execution of the `run` method." ] }, { @@ -898,10 +876,10 @@ "source": [ "When the *run* method is executed following class attributes are populated:\n", "\n", - "* *circuit_statistics*: Python dictionary with the statistics of each circuit used during the algorithm execution. Each key of the dictionary corresponds with a $k$ application of the Grover-like operator used and its associated value is a Python dictionary with the complete statistical information of the circuit created for each $k$ value.\n", - "* *schedule_pdf*: pandas DataFrame with the complete schedule used in the algorithm execution. The schedule lists the number of applications Grover-like applications and the number of shots used for measurements.\n", - "* *oracle_calls*: number of total oracle calls for a complete execution of the algorithm.\n", - "* *max_oracle_depth*: maximum number of applications of the oracle for the complete execution of the algorithm." + "* `circuit_statistics`: Python dictionary with the statistics of each circuit used during the algorithm execution. Each key of the dictionary corresponds with a $k$ application of the Grover-like operator used and its associated value is a Python dictionary with the complete statistical information of the circuit created for each $k$ value.\n", + "* `schedule_pdf`: pandas DataFrame with the complete schedule used in the algorithm execution. The schedule lists the number of applications Grover-like applications and the number of shots used for measurements.\n", + "* `oracle_calls`: number of total oracle calls for a complete execution of the algorithm.\n", + "* `max_oracle_depth`: maximum number of applications of the oracle for the complete execution of the algorithm." ] }, { @@ -983,23 +961,23 @@ }, { "cell_type": "markdown", - "id": "1b4680bf", + "id": "3550d6d0", "metadata": {}, "source": [ - "## 4. modified IQAE\n", + "## 4. Modified IQAE\n", "\n", - "The modified Iterative Quantum Amplitude Estimation **mIQAE** algorithm is a modification over the **IQAE** that provides an improvement performance over the **IQAE** one. The **mIQAE** algorithm is presented in the following paper:\n", + "The modified Iterative Quantum Amplitude Estimation (**mIQAE**) algorithm represents an improvement over the **IQAE** algorithm, offering enhanced performance. The **mIQAE** algorithm is described in the following paper:\n", "\n", - "* *Fukuzawa, Shion and Ho, Christopher and Irani, Sandy and Zion, Jasen*: Modified Iterative Quantum Amplitude Estimation is Asymptotically Optimal. 2023 Proceedings of the Symposium on Algorithm Engineering and Experiments (ALENEX). Society for Industrial and Applied Mathematics.\n", + "- **Fukuzawa, Shion**, **Ho, Christopher**, **Irani, Sandy**, and **Zion, Jasen**: *Modified Iterative Quantum Amplitude Estimation is Asymptotically Optimal*. 2023 Proceedings of the Symposium on Algorithm Engineering and Experiments (ALENEX). Society for Industrial and Applied Mathematics.\n", "\n", - "The main contribution of the **mIQAE** is to adapt, in each step of the algorithm, the probability of failure, $\\alpha_i$, and the corresponding number of shots (in **IQAE** this failure probability is kept constant in all the steps). With this modification author claims that their algorithm achieves a query performance better than the original **IQAE**:\n", - "* IQAE query complexity: $\\sim \\frac{1}{\\epsilon} \\log \\left( \\frac{1}{\\alpha} \\log \\left(\\frac{1}{\\epsilon}\\right)\\right)$\n", - "* mIQAE query complexity: $\\sim \\frac{1} {\\epsilon} \\log \\frac{1}{\\alpha}$\n", + "The primary contribution of the **mIQAE** algorithm is its ability to adapt the probability of failure, $\\alpha_i$, and the corresponding number of shots at each step of the algorithm. In contrast, the failure probability in the **IQAE** algorithm remains constant across all steps. This modification allows the **mIQAE** algorithm to achieve superior query performance compared to the original **IQAE**:\n", "\n", + "- **IQAE** query complexity: $\\sim \\frac{1}{\\epsilon} \\log \\left( \\frac{1}{\\alpha} \\log \\left(\\frac{1}{\\epsilon}\\right)\\right)$\n", + "- **mIQAE** query complexity: $\\sim \\frac{1}{\\epsilon} \\log \\frac{1}{\\alpha}$\n", "\n", - "The **mIQAE** algorithm was implemented in **QQuantLib.AE** package in module *modified_iterative_quantum_ae* in class **mIQAE**. \n", + "The **mIQAE** algorithm has been implemented in the **QQuantLib.AE** package, specifically in the module *modified_iterative_quantum_ae*, within the class `mIQAE`. \n", "\n", - "The working of the **mIQAE** class is the same that the **IQAE** one." + "The functionality of the `mIQAE` class is identical to that of the `IQAE` class, ensuring a seamless transition for users familiar with the original implementation." ] }, { @@ -1107,7 +1085,7 @@ "id": "0a547554", "metadata": {}, "source": [ - "We can compare the bounds for both methods by calling the method *compute_info* that provides info about the bounds onf the algorithm" + "We can compare the bounds for both methods by calling the method `compute_info` that provides info about the bounds onf the algorithm" ] }, { @@ -1145,9 +1123,9 @@ ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1159,7 +1137,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb b/misc/notebooks/07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb index eaf57cf..0484c98 100644 --- a/misc/notebooks/07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb +++ b/misc/notebooks/07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb @@ -9,14 +9,23 @@ "\n", "Several modifications over the **RQAE** were developed in the **QQuantLib** library. These modifications can be found inside the **QQuantLib.AE** package in the following modules and clasess:\n", "\n", - "* Modified **RQAE** in the **mRQAE** class inside the *modified_real_quantum_ae* module.\n", - "* Shots version of the **RQAE** in the **sRQAE** class inside the *shots_real_quantum_ae* module.\n", - "* Extended **RQAE** in the **eRQAE** class inside the *extended_real_quantum_ae* module.\n", + "* Modified **RQAE** in the `mRQAE` class inside the *modified_real_quantum_ae* module.\n", + "* Shots version of the **RQAE** in the `sRQAE` class inside the *shots_real_quantum_ae* module.\n", + "* Extended **RQAE** in the `eRQAE` class inside the *extended_real_quantum_ae* module.\n", "\n", "\n", "All these modifications use the **RQAE** quantum circuits shown in section 3 of jupyter notebook: *07_Real_Quantum_Amplitude_Estimation_class*. The main difference is how the classical part is configured in each variation. Playing with the classical part the performance of the **RQAE** algorithm can be improved a lot and even can achieved better performances than other **AE** algorithms like **IQAE** or **mIQAE**.\n", "\n", - "All these implementations work in the same way than the original **RQAE** class" + "All these implementations work in the same way than the original **RQAE** class\n", + "\n", + "\n", + "\n", + "The *mIQAE* algorithm can be found in the following reference:\n", + "\n", + "- **Manzano, Alberto; Ferro, Gonzalo; Leitao, Álvaro; Vázquez, Carlos; Gómez, Andrés**\n", + " *Alternative pipeline for option pricing using quantum computers*.\n", + " EPJ Quantum Technology, 28-12 (2025)\n", + " https://doi.org/10.1140/epjqt/s40507-025-00328-3" ] }, { @@ -88,11 +97,21 @@ "source": [ "## 1. Oracle generation\n", "\n", - "Before doing any amplitude estimation we want to load some data into the quantum circuit, as this step is only auxiliary to see how the algorithm works, we are just going to load a discrete probability distribution. In this case, we will have a circuit with $n=3$ qubits which makes a total of $N = 2^n = 8$ states. The discrete probability distribution that we are going to load is:\n", - "$$p_d = \\dfrac{(0,1,2,3,4,5,6,7)}{0+1+2+3+4+5+6+7+8}.$$\n", + "Before performing any amplitude estimation, we need to load some data into the quantum circuit. As this step is only auxiliary to demonstrate how the algorithm works, we will simply load a discrete probability distribution. \n", + "\n", + "In this case, we will use a circuit with $ n = 3 $ qubits, which results in a total of $ N = 2^n = 8 $ states. The discrete probability distribution that we will load is:\n", + "\n", + "$$\n", + "p_d = \\dfrac{(0, 1, 2, 3, 4, 5, 6, 7)}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}.\n", + "$$\n", + "\n", + "Note that this probability distribution is properly normalized. To load this probability into the quantum circuit, we will use the function `load_probability` from the **QQuantLib/DL/data_loading** module. \n", "\n", - "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function *load_probability* from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", - " $$|\\Psi\\rangle = \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$" + "The state that we will obtain is:\n", + "\n", + "$$\n", + "|\\Psi\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7}} \\left[ \\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$" ] }, { @@ -374,30 +393,32 @@ "Last modification provided for **RQAE** algorithm is the *extended RQAE* algorithm implemented in the class **eRQAE** of the module **QQuantLib.AE.extended_real_quantum_ae**. In this case the user can guide the evolution of the number of Grover applications ($k$) and the failure probabiliy ($\\gamma_i$) at each step of the algorithm. For guiding this evolution **eRQAE** uses 2 list (schedules) of the same lenght: one for guiding the evolution of $k$ and another for the evolution of $\\gamma_i$.\n", "\n", "For helping the user to design these schedules four different functions in the **QQuantLib.AE.extended_real_quantum_ae** module were developed:\n", - "1. *schedule_exponential_constant*\n", - "2. *schedule_exponential_exponential*\n", - "3. *schedule_linear_linear*\n", - "4. *schedule_linear_constant*\n", + "1. `schedule_exponential_constant`\n", + "2. `schedule_exponential_exponential`\n", + "3. `schedule_linear_linear`\n", + "4. `schedule_linear_constant`\n", "\n", - "The **eRQAE** class uses these functions for creating the used schedules. The user can select the different functions and their parameters by providing to the **eRQAE** class the keyword argument *erqae_schedule*. This *erqae_schedule* is a Python dictionary with the following format:\n", + "The `eRQAE` class uses these functions for creating the used schedules. The user can select the different functions and their parameters by providing to the `eRQAE` class the keyword argument `erqae_schedule` that is a Python dictionary with the following format:\n", "\n", - "**{\"type\": type, \"ratio_slope_k\": ratio_slope_k, \"ratio_slope_gamma\": ratio_slope_gamma}**\n", + "```python\n", + "{\"type\": type, \"ratio_slope_k\": ratio_slope_k, \"ratio_slope_gamma\": ratio_slope_gamma}**\n", + "```\n", "\n", "Where:\n", "\n", - "* type: a string that indicates the scheduling function to use:\n", - " * \"exp_const\" for *schedule_exponential_constant* function.\n", - " * \"exp_exp\" for *schedule_exponential_exponential* function.\n", - " * \"linear_linear\" for *schedule_linear_linear* function.\n", - " * \"linear_const\" for *schedule_linear_constant* function.\n", - "* ratio_slope_k: ratio or slope for $k$ schedule\n", - "* ratio_slope_gamma: ratio or slope for $\\gamma$ schedule.\n", + "* `type`: a string that indicates the scheduling function to use:\n", + " * `exp_const` for *schedule_exponential_constant* function.\n", + " * `exp_exp` for *schedule_exponential_exponential* function.\n", + " * `linear_linear` for *schedule_linear_linear* function.\n", + " * `linear_const` for *schedule_linear_constant* function.\n", + "* `ratio_slope_k`: ratio or slope for $k$ schedule\n", + "* `ratio_slope_gamma`: ratio or slope for $\\gamma$ schedule.\n", "\n", "\n", - "Under the hood the **eRQAE** class call to a select function called **select_schedule** (in the **QQuantLib.AE.extended_real_quantum_ae** module) that acts as a selector function of the different scheduling functions. The inputs of the **select_schedule** are:\n", - "* *erqae_schedule*: python dictionary with the same format that the keyword argument: **erqae_schedule**\n", - "* epsilon: the desired $\\epsilon$ to achieve.\n", - "* gamma: the desired $\\gamma$ to achieve.\n", + "Under the hood the `eRQAE` class call to a select function called `select_schedule` (in the **QQuantLib.AE.extended_real_quantum_ae** module) that acts as a selector function of the different scheduling functions. The inputs of the `select_schedule` are:\n", + "* `erqae_schedule`: python dictionary with the same format that the keyword argument: `erqae_schedule`\n", + "* `epsilon`: the desired $\\epsilon$ to achieve.\n", + "* `gamma`: the desired $\\gamma$ to achieve.\n", "\n", "In the following subsections we explain the different scheduling functions and how to configure them." ] @@ -420,11 +441,12 @@ "### 5.1 schedule_exponential_constant\n", "\n", "In this case we want a exponential evolution (schedule) for the $k$ and a constant one for the failure probability $\\gamma_i$. In this case the input *erqae_schedule* dictionary will have the following format:\n", - "* type: exp_const\n", - "* ratio_slope_k: desired ratio\n", - "* ratio_slope_gamma: None.\n", "\n", - "We can use the **select_schedule** for getting the obtained schedules:\n" + "* `type`: exp_const\n", + "* `ratio_slope_k`: desired ratio\n", + "* `ratio_slope_gamma`: None.\n", + "\n", + "We can use the `select_schedule` for getting the obtained schedules:\n" ] }, { @@ -489,17 +511,19 @@ "source": [ "### 5.2 schedule_exponential_exponential \n", "\n", - "In this case we want a exponential schedule for the $k$ and for the failure probability $\\gamma_i$. In this case the input *erqae_schedule* dictionary will have the following format:\n", - "* type: exp_exp\n", - "* ratio_slope_k: desired ratio for k\n", - "* ratio_slope_gamma: desired ratio for gamma\n", + "In this case we want a exponential schedule for the $k$ and for the failure probability $\\gamma_i$. In this case the input `erqae_schedule` dictionary will have the following format:\n", + "\n", + "- `type`: `exp_exp`\n", + "- `ratio_slope_k`: desired ratio for k\n", + "- `ratio_slope_gamma`: desired ratio for gamma\n", "\n", "**BE AWARE**\n", + "\n", "The ratio for $\\gamma$ can be positive or negative:\n", - "* When ratio_slope_gamma > 0: lower probability failures at the initial steps. The probability failure is increasing with the different steps\n", - "* When ratio_slope_gamma < 0: higher probability failures at the initial steps. The probability failure is decreasing with the different steps\n", + "* When `ratio_slope_gamma` > 0: lower probability failures at the initial steps. The probability failure is increasing with the different steps\n", + "* When `ratio_slope_gamma` < 0: higher probability failures at the initial steps. The probability failure is decreasing with the different steps\n", "\n", - "We can use the **select_schedule** for getting the obtained schedules:\n" + "We can use the `select_schedule` for getting the obtained schedules:\n" ] }, { @@ -623,16 +647,17 @@ "### 5.3 schedule_linear_linear \n", "\n", "In this case we want a linear schedule for the $k$ and for the failure probability $\\gamma_i$. In this case the input *erqae_schedule* dictionary will have the following format:\n", - "* type: linear_linear\n", - "* ratio_slope_k: desired slope for k\n", - "* ratio_slope_gamma: desired slope for gamma\n", + "\n", + "- `type`: linear_linear\n", + "- `ratio_slope_k`: desired slope for k\n", + "- `ratio_slope_gamma`: desired slope for gamma\n", "\n", "**BE AWARE**\n", "The slope for $\\gamma$ can be positive or negative:\n", - "* When ratio_slope_gamma > 0: lower probability failures at the initial steps. The probability failure is increasing with the different steps\n", - "* When ratio_slope_gamma < 0: higher probability failures at the initial steps. The probability failure is decreasing with the different steps\n", + "* When `ratio_slope_gamma` > 0: lower probability failures at the initial steps. The probability failure is increasing with the different steps\n", + "* When `ratio_slope_gamma` < 0: higher probability failures at the initial steps. The probability failure is decreasing with the different steps\n", "\n", - "We can use the **select_schedule** for getting the obtained schedules:\n" + "We can use the `select_schedule` for getting the obtained schedules:\n" ] }, { @@ -760,12 +785,13 @@ "### 5.4 schedule_linear_constant \n", "\n", "In this case we want a linear schedule for the $k$ and a constant one for the failure probability $\\gamma_i$. In this case the input *erqae_schedule* dictionary will have the following format:\n", - "* type: linear_const\n", - "* ratio_slope_k: desired slope for k\n", - "* ratio_slope_gamma: None\n", + "\n", + "- `type`: `linear_const`\n", + "- `ratio_slope_k`: desired slope for k\n", + "- `ratio_slope_gamma`: None\n", "\n", "\n", - "We can use the **select_schedule** for getting the obtained schedules:\n" + "We can use the `select_schedule` for getting the obtained schedules:\n" ] }, { @@ -863,7 +889,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.7" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/07_Real_Quantum_Amplitude_Estimation_class.ipynb b/misc/notebooks/07_Real_Quantum_Amplitude_Estimation_class.ipynb index 65a5160..ed8eb7a 100644 --- a/misc/notebooks/07_Real_Quantum_Amplitude_Estimation_class.ipynb +++ b/misc/notebooks/07_Real_Quantum_Amplitude_Estimation_class.ipynb @@ -2,32 +2,19 @@ "cells": [ { "cell_type": "markdown", - "id": "7a9496d2", + "id": "56efdb71", "metadata": {}, "source": [ - "# Real Quantum Amplitude Estimation (RQAE) module" - ] - }, - { - "cell_type": "markdown", - "id": "7d6cb636", - "metadata": {}, - "source": [ - "The present notebook reviews the **Real Quantum Amplitude Estimation** (RQAE) algorithms which were implemented into the module *real_quantum_ae* within the package *AE* of the library *QQuantLib* (**QQuantLib/AE/real_quantum_ae.py**).\n", + "# Real Quantum Amplitude Estimation (RQAE) Module\n", "\n", "$$\\newcommand{\\braket}[2]{\\left\\langle{#1}\\middle|{#2}\\right\\rangle}$$\n", "$$\\newcommand{\\ket}[1]{\\left|{#1}\\right\\rangle}$$\n", - "$$\\newcommand{\\bra}[1]{\\left\\langle{#1}\\right|}$$" - ] - }, - { - "cell_type": "markdown", - "id": "bcf02895", - "metadata": {}, - "source": [ - "The present notebook and module are based on the following references:\n", + "$$\\newcommand{\\bra}[1]{\\left\\langle{#1}\\right|}$$\n", "\n", - "* Manzano, A., Musso, D. & Leitao, Á. Real quantum amplitude estimation. EPJ Quantum Technol. 10, 2 (2023) (https://epjquantumtechnology.springeropen.com/articles/10.1140/epjqt/s40507-023-00159-0#citeas)\n" + "The present notebook reviews the **Real Quantum Amplitude Estimation (RQAE)** algorithms, which have been implemented in the *module real_quantum_ae* within the package **AE** of the library `QQuantLib` (**QQuantLib/AE/real_quantum_ae**).\n", + "\n", + "The present notebook and module are based on the following reference:\n", + "* **Manzano, A., Musso, D., & Leitao, Á.** Real quantum amplitude estimation. *EPJ Quantum Technol.*, 10, 2 (2023). [https://epjquantumtechnology.springeropen.com/articles/10.1140/epjqt/s40507-023-00159-0#citeas](https://epjquantumtechnology.springeropen.com/articles/10.1140/epjqt/s40507-023-00159-0#citeas)" ] }, { @@ -108,8 +95,15 @@ "id": "71279f09", "metadata": {}, "source": [ - "Before doing any amplitude estimation we want to load some data into the quantum circuit, as this step is only auxiliary to see how the algorithm works, we are just going to load a discrete probability distribution. In this case, we will have a circuit with $n=3$ qubits which makes a total of $N = 2^n = 8$ states. The discrete probability distribution that we are going to load is:\n", - "$$p_d = \\dfrac{(0,1,2,3,4,5,6,7)}{0+1+2+3+4+5+6+7+8}.$$\n" + "Before performing any amplitude estimation, we first need to load data into the quantum circuit. As this step is auxiliary and intended to demonstrate how the algorithm works, we will simply load a discrete probability distribution. \n", + "\n", + "In this example, we will use a quantum circuit with $ n = 3 $ qubits, which corresponds to a total of $ N = 2^n = 8 $ computational basis states. The discrete probability distribution we aim to load is defined as:\n", + "\n", + "$$\n", + "p_d = \\frac{(0, 1, 2, 3, 4, 5, 6, 7)}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}.\n", + "$$\n", + "\n", + "This distribution assigns probabilities proportional to the integers $ 0 $ through $ 7 $, normalized by their sum to ensure that the total probability equals 1." ] }, { @@ -127,10 +121,10 @@ }, { "cell_type": "markdown", - "id": "716492db", + "id": "0601fe8a", "metadata": {}, "source": [ - "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function *load_probability* from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", + "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function `load_probability` from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", " $$|\\Psi\\rangle = \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$" ] }, @@ -164,36 +158,29 @@ }, { "cell_type": "markdown", - "id": "208218b4", - "metadata": {}, - "source": [ - "## 2. RQAE class" - ] - }, - { - "cell_type": "markdown", - "id": "e82c9c6e", + "id": "fefc93cd", "metadata": {}, "source": [ - "### 2.1 The Amplitude Estimation Problem\n", + "## 2. RQAE algorithm\n", "\n", - "The **RQAE** algorithm solves the **amplitude estimation** problem when a little variation is added. In this case, given an oracle:\n", + "The **RQAE** algorithm solves the **amplitude estimation** problem when a little variation is added. In this case, given an oracle, $\\mathcal{0}$:\n", "\n", "$$\\mathcal{0}|0\\rangle = |\\Psi\\rangle = a|\\Psi_0\\rangle +\\sqrt{1-a^2}|\\Psi_1\\rangle, \\tag{1}$$\n", "\n", "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, *we want to estimate the real parameter $a$ (so $a$ can take values in the domain $[-1,1]$)*\n", "\n", - "**BE AWARE** \n", + "### BE AWARE\n", + "-----------------\n", "\n", - "In Notebooks: *03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb*, *04_Classical_Phase_Estimation_Class.ipynb*, *05_Iterative_Quantum_Phase_Estimation_Class.ipynb*, *06_Iterative_Quantum_Amplitude_Estimation_class.ipynb* we want to estimate $\\sqrt{a}$ meanwhile in this new problem we want to estimate $a$\n" - ] - }, - { - "cell_type": "markdown", - "id": "6dcb170e", - "metadata": {}, - "source": [ - "### 2.2 RQAE algorithm output\n", + "In Notebooks: \n", + "- *03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb*,\n", + "- *04_Classical_Phase_Estimation_Class.ipynb*\n", + "- *05_Iterative_Quantum_Phase_Estimation_Class.ipynb*\n", + "- *06_Iterative_Quantum_Amplitude_Estimation_class.ipynb* \n", + "\n", + "we want to estimate $\\sqrt{a}$ meanwhile in this new problem we want to estimate $a$\n", + "\n", + "---\n", "\n", "Given an error $\\epsilon$ and a confident interval $\\gamma$, the **RQAE** algorithm allows to estimate the $a$, from the amplitude estimation problem presented in *Section 2.1*, satisfying:\n", "\n", @@ -201,38 +188,60 @@ "\n", "and\n", "\n", - "$$\\frac{a_u-a_l}{2} \\leq \\epsilon$$\n" + "$$\\frac{a_u-a_l}{2} \\leq \\epsilon$$" ] }, { "cell_type": "markdown", - "id": "d2da4da9", + "id": "6ab189a2", "metadata": {}, "source": [ - "### 2.3 Creating object from the RQAE class\n", + "### 2.1 The `RQAE` Class\n", + "\n", + "We have implemented a Python class called `RQAE` in the **QQuantLib/AE/real_quantum_ae** module, which allows us to use the **RQAE** algorithm. \n", + "\n", + "When creating the `RQAE` class, the conventions used in the **MLAE** class from the **QQuantLib/AE/maximum_likelihood_ae** module should be followed. The class has the following mandatory inputs:\n", + "\n", + "1. `oracle`: A QLM `AbstractGate` or `QRoutine` that implements the Oracle for constructing the Grover operator.\n", + "2. `target`: The marked state in binary representation, provided as a Python list.\n", + "3. `index`: A list of qubits affected by the Grover operator.\n", + "\n", + "Additionally, there are optional inputs for configuring the algorithm, which can be provided as a Python dictionary:\n", + "- `qpu`: The QPU solver to be used.\n", + "- `epsilon` ($\\epsilon$): The precision. Ensures that the width of the interval is at most $2\\epsilon$ (default: 0.01).\n", + "- `gamma` ($\\gamma$): The confidence level. Ensures that the probability of $a$ not lying within the given interval is at most $\\gamma$ (default: 0.05).\n", + "- `ratio`: The amplification ratio (default: 2).\n", + "- `mcz_qlm`: A flag to use the QLM multi-controlled Z gate (`True`, default) or a multiplexor implementation (`False`).\n", + "\n", + "\n", "\n", - "We have implemented a Python class called **RQAE** into the **QQuantLib/AE/real_quantum_ae** module that allows us to use the **RQAE** algorithm.\n", + "#### Example\n", "\n", - "For creating the **RQAE** class the conventions used in **MLAE class** from **QQuantLib/AE/maximum_likelihood_ae.py** module should be followed: \n", + "To demonstrate how the `RQAE` class and the algorithm work, consider the following amplitude estimation problem:\n", "\n", - "We have some mandatory inputs:\n", + "$$\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", + "$$\n", "\n", - "1. Oracle: QLM AbstractGate or QRoutine with the implementation of the Oracle for creating the Grover operator.\n", - "2. target: this is the marked state in binary representation as a Python list. \n", - "3. index: list of the qubits affected by the Grover operator. \n", + "By comparing Equation (2) with Equation (1):\n", "\n", - "And some optional inputs, used for algorithm configuration, that can be given as a Python dictionary:\n", - "* qpu: QLM solver that will be used\n", - "* epsilon ($\\epsilon$): the precision. Ensures that the width of the interval is (see Section 2.2), at most, $2\\epsilon$ (default: 0.01).\n", - "* gamma ($\\gamma$): the accuracy (or final failure probability). Ensures that the probability of $a$ not laying within the given interval (see Section 2.2) is, at most, $\\gamma$ (default: 0.05).\n", - "* ratio: the amplification ratio (default: 2).\n", - "* mcz_qlm: for using QLM multi-controlled Z gate (True, default) or using multiplexor implementation (False)" + "$$\n", + "\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle,\n", + "$$\n", + "\n", + "and\n", + "\n", + "$$\n", + "\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$\n", + "\n", + "In this case, the target state is $|1\\rangle$, whose binary representation is $001$. This must be passed to the `target` variable as a list (`[0, 0, 1]`). Additionally, we need to provide the list of qubits (`index`) where the operation is being performed. In this example, it is `[0, 1, 2]`, corresponding to the entire register." ] }, { "cell_type": "code", "execution_count": null, - "id": "c3dc1ade", + "id": "58b34bb8", "metadata": {}, "outputs": [], "source": [ @@ -240,26 +249,6 @@ "from QQuantLib.AE.real_quantum_ae import RQAE" ] }, - { - "cell_type": "markdown", - "id": "94fce353", - "metadata": {}, - "source": [ - "To show how our **RQAE** class works, we will define the following amplitude estimation problem:\n", - "\n", - "$$|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right] \\tag{2}$$\n", - "\n", - "So comparing (2) with (1):\n", - "\n", - "$$a|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle$$\n", - "\n", - "and \n", - "\n", - "$$\\sqrt{1-a^2}|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$\n", - "\n", - "The target state, in this case, is $|1\\rangle$. Its binary representation is $001$. This has to be passed to the target variable as a list. Moreover, we have to provide the list of qubits where we are acting, in this case is just $[0,1,2]$, the whole register." - ] - }, { "cell_type": "code", "execution_count": null, @@ -286,19 +275,18 @@ }, { "cell_type": "markdown", - "id": "e5c5e726", + "id": "0e0f64d9", "metadata": {}, "source": [ - "### 2.4 The *rqae* method\n", + "### 2.2 The `rqae` Method\n", "\n", - "To execute the complete algorithm using the **RQAE** class the *rqae* method can be used. \n", + "To execute the complete algorithm using the `RQAE` class, the `rqae` method can be used. This method accepts the following inputs:\n", "\n", - " This method has the following inputs:\n", - "* ratio: the amplification ratio\n", - "* epsilon ($\\epsilon$): error in the estimation of $a$ (default: 0.01).\n", - "* gamma ($\\gamma$): confidence interval (failure probability) for the $a$ estimation (default: 0.05).\n", + "- `ratio`: The amplification ratio.\n", + "- `epsilon` ($\\epsilon$): Error in the estimation of $a$ (default: 0.01).\n", + "- `gamma` ($\\gamma$): Confidence interval (failure probability) for the $a$ estimation (default: 0.05).\n", "\n", - "This method returns the limits for the $a$ estimation: $(a_{\\min},a_{\\max})$" + "This method returns the limits for the $a$ estimation: $(a_{\\min}, a_{\\max})$." ] }, { @@ -344,37 +332,17 @@ " print(\"Incorrect\")" ] }, - { - "cell_type": "markdown", - "id": "20021cdb", - "metadata": {}, - "source": [ - "Additionally, the **rqae** method populates the *time_pdf* property where several times for each iteration of the algorithm are stored. \n", - "\n", - "The **rqae_overheating** column in *time_pdf* property refers to pure **rqae** step algorithm times" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9af0fa55", - "metadata": {}, - "outputs": [], - "source": [ - "rqae.circuit_statistics" - ] - }, { "cell_type": "markdown", "id": "d5ecd185", "metadata": {}, "source": [ - "### 2.5 The *display_information* method\n", + "### 2.3 The *display_information* method\n", "\n", "This method provides technical information about the **RQAE** algorithm for a fixed configuration of:\n", - "* ratio ($q$ in the **RQAE** paper): amplification between steps\n", - "* epsilon ($\\epsilon$): desired error in the estimation of $a$\n", - "* gamma ($\\gamma$): confidence level (failure probability)" + "- `ratio` ($q$ in the **RQAE** paper): amplification between steps\n", + "- `epsilon` ($\\epsilon$): desired error in the estimation of $a$\n", + "- `gamma` ($\\gamma$): confidence level (failure probability)" ] }, { @@ -389,17 +357,19 @@ }, { "cell_type": "markdown", - "id": "5fa049fb", + "id": "293cbf33", "metadata": {}, "source": [ - "### 2.6 The *run* method\n", + "### 2.4 The `run` Method\n", + "\n", + "A `run` method has been implemented for the direct execution of the **RQAE** algorithm. In this case, the user can configure all the properties of the `RQAE` class, and the `run` method will execute the algorithm using the predefined attributes of the class. \n", "\n", - "Finally, a *run* method for direct implementation of the **RQAE** algorithm was implemented. In this case, the user can configure all the properties of the **RQAR** class and the *run* method will execute the method using the fixed attributes of the class. Finally, the method returns the estimation of $a=\\frac{a_u+a_l}{2}$. Additionally, the *run* method populates the following class attributes:\n", + "The method returns the estimation of $ a = \\frac{a_u + a_l}{2} $. Additionally, the `run` method populates the following class attributes:\n", "\n", - "* *ae_l*: the lower limit for a $a_l$.\n", - "* *ae_u*: the upper limit for a $a_u$.\n", - "* *ae*: the amplitude estimation parameter calculated as: $a=\\frac{a_u+a_l}{2}$\n", - "* *run_time*: elapsed time for a complete execution of the **run** method.\n" + "- `ae_l`: The lower limit for $ a_l $.\n", + "- `ae_u`: The upper limit for $ a_u $.\n", + "- `ae`: The amplitude estimation parameter, calculated as $ a = \\frac{a_u + a_l}{2} $.\n", + "- `run_time`: The elapsed time for the complete execution of the `run` method." ] }, { @@ -485,24 +455,13 @@ "id": "1583e54c", "metadata": {}, "source": [ - "When the *run* method is executed the following class attributes are populated:\n", + "When the `run` method is executed, the following class attributes are populated:\n", "\n", - "* *circuit_statistics*: Python dictionary with the statistics of each circuit used during the algorithm execution. Each key of the dictionary corresponds with a $k$ application of the Grover-like operator used and its associated value is a Python dictionary with the complete statistical information of the circuit created for each $k$ value.\n", - "* *schedule_pdf*: pandas DataFrame with the complete schedule used in the algorithm execution. The schedule lists the number of applications Grover-like applications and the number of shots used for measurements.\n", - "* *oracle_calls*: number of total oracle calls for a complete algorithm's execution.\n", - "* *max_oracle_depth*: maximum number of applications of the oracle for the complete algorithm's execution." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "453350d9", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "rqae_.circuit_statistics" + "- `schedule_pdf`: A pandas DataFrame containing the complete schedule used during the algorithm's execution. The schedule lists the number of Grover-like operator applications and the number of shots used for measurements.\n", + "- `oracle_calls`: The total number of oracle calls required for the complete execution of the algorithm.\n", + "- `max_oracle_depth`: The maximum number of oracle applications performed during the complete execution of the algorithm.\n", + "- `quantum_time`: The time taken to execute all the quantum routines demanded by the algorithm.\n", + "- `run_time`: The total time taken to execute the entire **RQAE** algorithm." ] }, { @@ -559,24 +518,24 @@ }, { "cell_type": "markdown", - "id": "aef4821e", + "id": "b58676c1", "metadata": {}, "source": [ "## 3. The RQAE Algorithm\n", "\n", - "In this section, we provide an insight into how the **RQAE** algorithm works. We are going to use the *oracle*, the *target* and the *index* from section 1. \n", - "\n", - "The **RQAE** algorithm has 2 well different parts:\n", - "* First step: where the sign of the amplitude $a$ can be estimated.\n", - "* The following interactions where the Grover operators are used for boosting the estimation.\n", + "In this section, we provide insight into how the **RQAE** algorithm works. We will use the `oracle`, `target`, and `index` defined in Section 1.\n", "\n", - "You should remember that we have an operator $\\mathcal{0}$ such that:\n", + "The **RQAE** algorithm consists of two distinct parts:\n", + "- **First Step**: Estimation of the sign of the amplitude $ a $.\n", + "- **Subsequent Iterations**: Use of Grover operators to refine and boost the estimation.\n", "\n", - "$$\\mathcal{0}|0\\rangle = |\\Psi\\rangle = a|\\Psi_0\\rangle +\\sqrt{1-a^2}|\\Psi_1\\rangle, \\tag{1}$$\n", - "\n", - "with $a\\in[-1, 1]$ and we want an estimation of $a$, $\\hat{a}$\n", + "Recall that we have an operator $\\mathcal{O}$ such that:\n", + "$$\n", + "\\mathcal{O}|0\\rangle = |\\Psi\\rangle = a|\\Psi_0\\rangle + \\sqrt{1-a^2}|\\Psi_1\\rangle, \\tag{1}\n", + "$$\n", + "where $ a \\in [-1, 1] $, and our goal is to estimate $ a $, denoted as $\\hat{a}$.\n", "\n", - "To explain the algorithm we are going to use the following distribution:" + "To explain the algorithm, we will use the following distribution:" ] }, { @@ -687,25 +646,38 @@ }, { "cell_type": "markdown", - "id": "c34b8c95", + "id": "f0f6d3dd", "metadata": {}, "source": [ - "### 3.1 First step\n", + "### 3.1 First Step\n", + "\n", + "The first iteration of the **RQAE** algorithm aims to obtain an initial estimation of the amplitude and its corresponding sign.\n", "\n", - "The first iteration of the **RQAE** aims to get a first estimation of the amplitude and the corresponding sign. \n", + "To achieve this, instead of using the $\\mathcal{O}$ operator (the `oracle` object in the code), the **RQAE** algorithm uses a modified version of the operator, denoted as $\\mathcal{O}_b$, which depends on $\\mathcal{O}$. The parameter $b$ in $\\mathcal{O}_b$ is a number within the range $[-0.5, 0.5]$, referred to as the `shift`. While $\\mathcal{O}$ acts on $n$ qubits, $\\mathcal{O}_b$ acts on $n+1$ qubits. The circuit implementation is illustrated in the following figure:\n", "\n", - "For doing this instead of using the $\\mathcal{0}$ operator (the *oracle* object in the code) the **RQAE** will use a version of the operator $\\mathcal{0}_b$ that depends on $\\mathcal{0}$. The $b$ from the $\\mathcal{0}_b$ is a number between $[-0.5, 0.5]$ that it is called the **shift**. Meanwhille the $\\mathcal{0}$ acts upon $n$ qubits the $\\mathcal{0}_b$ acts upon $n+1$ qutbits. The circuit implementation is shown in the following figure:\n", + "![Circuit Diagram](images/rqae.svg)\n", "\n", - "![title](images/rqae.svg)\n", + "This $\\mathcal{O}_b$ operator is composed of three distinct components (represented by the three boxes in the diagram):\n", "\n", - "This $\\mathcal{0}_b$ is composed of 3 different operators (the three boxes in the diagram):\n", - "* $R_y(2 \\theta_b)$: a rotation around axis y of $2 \\theta_b$ with $\\theta_b = \\arccos(b)$. Main behaviour is: $$R_y(2\\theta_b) \\ket{0} = \\cos(\\theta_b) \\ket{0} + \\sin(\\theta_b) \\ket{1}$$\n", - "* The *Mask* operator. This operator is an operator whose main behaviour is the following:\n", + "1. **$R_y(2\\theta_b)$**: A rotation around the Y-axis by an angle of $2\\theta_b$, where $\\theta_b = \\arccos(b)$. Its primary behavior is:\n", + " $$\n", + " R_y(2\\theta_b) \\ket{0} = \\cos(\\theta_b) \\ket{0} + \\sin(\\theta_b) \\ket{1}.\n", + " $$\n", "\n", - "$$Mask \\ket{0}\\otimes \\ket{0}^n \\rightarrow \\ket{0} \\otimes \\ket{\\Psi_0}$$\n", - "$$Mask \\ket{0}\\otimes \\ket{i \\ne 0}^n \\rightarrow \\text{any state that can not be} \\ket{0} \\otimes \\ket{\\Psi_0}$$\n", - "* The oracle operator $\\mathcal{0}$\n", - "$$\\mathcal{0}|0\\rangle = |\\Psi\\rangle = a|\\Psi_0\\rangle +\\sqrt{1-a^2}|\\Psi_1\\rangle$$" + "2. **The Mask Operator**: This operator has the following main behavior:\n", + " $$\n", + " \\text{Mask} \\; \\ket{0} \\otimes \\ket{0}^n \\rightarrow \\ket{0} \\otimes \\ket{\\Psi_0},\n", + " $$\n", + " $$\n", + " \\text{Mask} \\; \\ket{0} \\otimes \\ket{i \\neq 0}^n \\rightarrow \\text{any state that cannot be } \\ket{0} \\otimes \\ket{\\Psi_0}.\n", + " $$\n", + "\n", + "3. **The Oracle Operator ($\\mathcal{O}$)**: This operator is defined as:\n", + " $$\n", + " \\mathcal{O}|0\\rangle = |\\Psi\\rangle = a|\\Psi_0\\rangle + \\sqrt{1-a^2}|\\Psi_1\\rangle.\n", + " $$\n", + "\n", + "By combining these components, the $\\mathcal{O}_b$ operator enables the estimation of both the magnitude and the sign of the amplitude $a$ during the first step of the algorithm." ] }, { @@ -758,15 +730,9 @@ "\n", "In this iteration the sign of $a$ can be recovered (if $P_{\\ket{1} \\otimes \\ket{\\Psi_0}} > P_{\\ket{0} \\otimes \\ket{\\Psi_0}}$ then $\\hat{a}^{1st} < 0$).\n", "\n", - "Once the $\\hat{a}^{1st}$ is estimated the corresponding bounds, $[\\hat{a}^{1st}_L, \\hat{a}^{1st}_U]$ can be obtained by using the *Chebyshev* inequality. For this computation the desired failure probability, $\\gamma_i$ and the number of shots used for obtaining the $\\hat{a}^{1st}$ must be provided." - ] - }, - { - "cell_type": "markdown", - "id": "c152c2d6", - "metadata": {}, - "source": [ - "The $\\mathcal{0}_b$, also called the *shifted oracle*, can be constructed by assigning to the *shifted_oracle* attribute a desired shift ($b$). For obtaining the corresponding $\\mathcal{0}_b$ we can use the attribute *_shifted_oracle*" + "Once the $\\hat{a}^{1st}$ is estimated the corresponding bounds, $[\\hat{a}^{1st}_L, \\hat{a}^{1st}_U]$ can be obtained by using the *Chebyshev* inequality. For this computation the desired failure probability, $\\gamma_i$ and the number of shots used for obtaining the $\\hat{a}^{1st}$ must be provided.\n", + "\n", + "The $\\mathcal{0}_b$, also called the *shifted oracle*, can be constructed by assigning to the `shifted_oracle` attribute a desired `shift` ($b$). For obtaining the corresponding $\\mathcal{0}_b$ we can use the attribute *_shifted_oracle*" ] }, { @@ -803,10 +769,10 @@ "id": "2c0af6f3", "metadata": {}, "source": [ - "The method *first_step* allows the user to execute the first step of the algorithm by providing:\n", - "* shift: the $b$ for creating the *shifted oracle*\n", - "* shots: number of shots for doing the measures\n", - "* gamma: desired failure probability for computing the bounds $[\\hat{a}^{1st}_L, \\hat{a}^{1st}_U]$\n", + "The method `first_step` allows the user to execute the first step of the algorithm by providing:\n", + "* `shift`: the $b$ for creating the *shifted oracle*\n", + "* `shots`: number of shots for doing the measures\n", + "* `gamma`: desired failure probability for computing the bounds $[\\hat{a}^{1st}_L, \\hat{a}^{1st}_U]$\n", "\n", "This method returns directly the bounds: $[\\hat{a}^{1st}_L, \\hat{a}^{1st}_U]$ and the circuit used." ] @@ -852,35 +818,41 @@ "source": [ "**NOTE**\n", "\n", - "The **RQAE** algorithm sets the shift, the number of shots and the desired failure probability for the first step automatically to obtain a performance that can be compatible with other state-of-art Amplitude Estimation algorithms (like Grinko's **IQAE**)" + "The **RQAE** algorithm sets the `shift`, the number of `shots` and the desired failure probability (`gamma`) for the first step automatically to obtain a performance that can be compatible with other state-of-art Amplitude Estimation algorithms (like Grinko's **IQAE**)" ] }, { "cell_type": "markdown", - "id": "fad74d3c", + "id": "278259bb", "metadata": {}, "source": [ - "### 3.2 Following iterations\n", - "\n", - "Once the first estimation, $[\\hat{a}^{1st}_L, \\hat{a}^{1st}_U]$, is obtained then the following iterations try to reduce this interval by exploiting the amplification capabilities of the *Grover* operator. \n", - "\n", - "For a step $t$ of the iterative process, first a *shited oracle* is created using as shift the lower bound of the last step $b=a^{t-1}_L$, using this *shited oracle* operator, $\\mathcal{O}_{b=a^{t-1}_L}$ the corresponding Grover operator $\\mathcal{G}(\\mathcal{O}_{b=a^{t-1}_L})$ is created. Then the following circuit is executed:\n", + "### 3.2 Following Iterations\n", "\n", - "$$\\mathcal{G}^k (\\mathcal{O}_{b=a^{t-1}_L})\\mathcal{O}_{b=a^{t-1}_L} \\ket{0}^n \\otimes \\ket{0}$$\n", + "Once the first estimation, $[\\hat{a}^{1st}_L, \\hat{a}^{1st}_U]$, is obtained, the subsequent iterations aim to reduce this interval by leveraging the amplification capabilities of the *Grover* operator.\n", "\n", - "Where $k$ is the number of times the Grover operator should be applied (depends on the step $t$ of the algorithm). \n", + "For a step $t$ in the iterative process:\n", + "1. A *shifted oracle* is created using the lower bound of the previous step as the shift: $b = a^{t-1}_L$. This results in the operator $\\mathcal{O}_{b=a^{t-1}_L}$.\n", + "2. Using this *shifted oracle*, the corresponding Grover operator $\\mathcal{G}(\\mathcal{O}_{b=a^{t-1}_L})$ is constructed.\n", + "3. The following circuit is executed:\n", + " $$\n", + " \\mathcal{G}^k (\\mathcal{O}_{b=a^{t-1}_L}) \\mathcal{O}_{b=a^{t-1}_L} \\ket{0}^n \\otimes \\ket{0},\n", + " $$\n", + " where $k$ is the number of times the Grover operator should be applied, which depends on the step $t$ of the algorithm.\n", "\n", - "Then the probability of the state $\\ket{0} \\otimes \\ket{\\Psi_0}$ should be measured. The estimation of the step $t$ is then: $\\hat{a}^t = P_{\\ket{0} \\otimes \\ket{\\Psi_0}}$\n", + "The probability of measuring the state $\\ket{0} \\otimes \\ket{\\Psi_0}$ is then estimated. The result of step $t$ is given by:\n", + "$$\n", + "\\hat{a}^t = P_{\\ket{0} \\otimes \\ket{\\Psi_0}}.\n", + "$$\n", "\n", - "Again using the *Chebyshev* inequality the corresponding bounds, $[\\hat{a}^{t}_L, \\hat{a}^{t}_U]$, can be obtained.\n", + "Using the *Chebyshev* inequality, the bounds for step $t$, $[\\hat{a}^{t}_L, \\hat{a}^{t}_U]$, can be computed.\n", "\n", - "A $t$ step is executed using the *run_step* method. The inputs are:\n", - "* shift\n", - "* shots\n", - "* gamma: desired step failure probability\n", - "* k: amplification\n", + "A single step $t$ is executed using the `run_step` method. The inputs to this method are:\n", + "- `shift`: The shift value ($b = a^{t-1}_L$).\n", + "- `shots`: The number of measurement shots.\n", + "- `gamma`: The desired failure probability for the step.\n", + "- `k`: The amplification factor (number of Grover operator applications).\n", "\n", - "Again the *run_step* method returns the bounds: $[\\hat{a}^{t}_L, \\hat{a}^{t}_U]$ and the circuit used." + "The `run_step` method returns the bounds $[\\hat{a}^{t}_L, \\hat{a}^{t}_U]$ and the quantum circuit used for the step." ] }, { @@ -923,27 +895,28 @@ "source": [ "**NOTE**\n", "\n", - "The **RQAE** algorithm sets the shift, the number of shots, the desired failure probability and the amplification $k$ for each step automatically to obtain a performance that can be compatible with other state-of-art Amplitude Estimation algorithms (like Grinko's **IQAE**)" + "The **RQAE** algorithm sets the `shift`, the number of `shots`, the desired failure probability (`gamma`) and the amplification $k$ for each step automatically to obtain a performance that can be compatible with other state-of-art Amplitude Estimation algorithms (like Grinko's **IQAE**)" ] }, { "cell_type": "markdown", - "id": "6aab8472", + "id": "171a0c4c", "metadata": {}, "source": [ - "## 4. Other RQAE algorithms\n", + "## 4. Other RQAE Algorithms\n", "\n", - "In the **QQuantLib.AE** package several variations over the **RQAE** algorithm were developed. These new **RQAE** versions can have better asymptotic query behaviour and even can have better experimental performance than the original **RQAE** algorithm (this is lower number of oracle calls for the same desired $\\epsilon$). \n", + "In the **QQuantLib.AE** package, several variations of the **RQAE** algorithm have been developed. These new versions of **RQAE** may exhibit better asymptotic query behavior and can even demonstrate improved experimental performance compared to the original **RQAE** algorithm. Specifically, they may require a lower number of oracle calls for the same desired precision $\\epsilon$.\n", "\n", - "In the notebook: *07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb* these **RQAE** modifications are reviewed and explained." + "These modifications to the **RQAE** algorithm are reviewed and explained in detail in the notebook: \n", + "*07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb*." ] } ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -955,7 +928,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/08_AmplitudeEstimation_Class.ipynb b/misc/notebooks/08_AmplitudeEstimation_Class.ipynb index beb346c..e59605d 100644 --- a/misc/notebooks/08_AmplitudeEstimation_Class.ipynb +++ b/misc/notebooks/08_AmplitudeEstimation_Class.ipynb @@ -2,15 +2,16 @@ "cells": [ { "cell_type": "markdown", - "id": "5d4ffe0f", + "id": "53ef2f03", "metadata": {}, "source": [ - "# MonteCarlo AE and Amplitude Estimation class\n", + "# Monte Carlo AE and Amplitude Estimation Class\n", "\n", - "This notebook presents two different python classes:\n", + "This notebook introduces two distinct Python classes:\n", "\n", - "* **MCAE** class implemented into the module *montecarlo_ae* of the package *AE* of library *QQuantLib* (**QQuantLib/AE/montecarlo_ae.py**). In this class the MonteCarlo approach for computing amplitude is presented. This procedure computes the amplitude of a particular state for a given oracle in a naive way (basically doing measurements and compute the probability of getting the state).\n", - "* **AE** class implemented in the *ae_class* module within the package *AE* of library *QQuantLib* (**QQuantLib/AE/ae_class.py**): this class give easy access to the different **AE** algorithms implemented into the package *AE*." + "1. `MCAE` class: Implemented in the module *montecarlo_ae* of the package **AE** in the library `QQuantLib` (**QQuantLib/AE/montecarlo_ae**). This class presents a Monte Carlo approach for computing amplitudes. The procedure estimates the amplitude of a specific state for a given oracle in a straightforward manner—by performing measurements and calculating the probability of obtaining the desired state.\n", + "\n", + "2. `AE` class: Implemented in the module *ae_class* within the package **AE** of the library `QQuantLib` (**QQuantLib/AE/ae_class.py**). This class provides easy access to the various **Amplitude Estimation (AE)** algorithms implemented in the **AE** package." ] }, { @@ -73,8 +74,15 @@ "source": [ "## 0. Oracle generation\n", "\n", - "Before doing any amplitude estimation we want to load some data into the quantum circuit, as this step is only auxiliary to see how the algorithm works, we are just going to load a discrete probability distribution. In this case we will have a circuit with $n=3$ qubits which makes a total of $N = 2^n = 8$ states. The discrete probability distribution that we are going to load is:\n", - "$$p_d = \\dfrac{(0,1,2,3,4,5,6,7)}{0+1+2+3+4+5+6+7+8}.$$\n" + "Before performing any amplitude estimation, we first need to load data into the quantum circuit. As this step is auxiliary and intended to demonstrate how the algorithm works, we will simply load a discrete probability distribution. \n", + "\n", + "In this example, we will use a quantum circuit with $ n = 3 $ qubits, which corresponds to a total of $ N = 2^n = 8 $ computational basis states. The discrete probability distribution we aim to load is defined as:\n", + "\n", + "$$\n", + "p_d = \\frac{(0, 1, 2, 3, 4, 5, 6, 7)}{0 + 1 + 2 + 3 + 4 + 5 + 6 + 7}.\n", + "$$\n", + "\n", + "This distribution assigns probabilities proportional to the integers $ 0 $ through $ 7 $, normalized by their sum to ensure that the total probability equals 1.\n" ] }, { @@ -95,7 +103,7 @@ "id": "329f5375", "metadata": {}, "source": [ - "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function *load_probability* from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", + "Note that this probability distribution is properly normalised. For loading this probability into the quantum circuit we will use the function `load_probability` from **QQuantLib/DL/data_loading** module. The state that we are going to get is:\n", " $$|\\Psi\\rangle = \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].$$" ] }, @@ -146,30 +154,29 @@ }, { "cell_type": "markdown", - "id": "6fe0257a", + "id": "7806ce24", "metadata": {}, "source": [ "## 1. Monte Carlo Amplitude Estimation (MCAE)\n", "\n", - "In a general way an amplitude estimation problem is the following: given an oracle:\n", - "\n", - "$$\\mathcal{O}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle +\\sqrt{1-a}|\\Psi_1\\rangle,$$\n", - "\n", - "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, we want to estimate $a$. \n", - "\n", - "The **MCAE** class computes in a naive way this $a$ amplitude. The class creates the correspondent quantum circuit of the oracle, performs several measurements and computes the probability of the $|\\Psi_0\\rangle$ state.\n", + "In general, an amplitude estimation problem can be described as follows: given an oracle:\n", + "$$\n", + "\\mathcal{O}|0\\rangle = |\\Psi\\rangle = \\sqrt{a}|\\Psi_0\\rangle + \\sqrt{1-a}|\\Psi_1\\rangle,\n", + "$$\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, our goal is to estimate $a$.\n", "\n", - "The inputs for creating the class are:\n", + "The `MCAE` class computes this $a$ amplitude in a straightforward manner. It creates the corresponding quantum circuit for the oracle, performs multiple measurements, and calculates the probability of the $|\\Psi_0\\rangle$ state.\n", "\n", - "1. Oracle: QLM AbstractGate or QRoutine with the implementation of the Oracle.\n", - "2. target: this is the marked state in binary representation as a Python list\n", - "3. index: list of the qubits affected by the Oracle operator.\n", + "### Inputs for Creating the Class\n", "\n", - "Additionally, a dictionary with other arguments to configure the algorithm can be provided. Keys for the dictionary can be:\n", + "1. `Oracle`: A QLM `AbstractGate` or `QRoutine` that implements the Oracle.\n", + "2. `target`: The marked state in binary representation, provided as a Python list.\n", + "3. `index`: A list of qubits affected by the Oracle operator.\n", "\n", - "* qpu: QLM solver that will be used (if not provided PyLinalg will be used)\n", - "* mcz_qlm: for using QLM multi-controlled Z gate (True, default) or using multiplexor implementation (False)\n", - "* shots: int. Number of measurements that will be done over the quantum circuit." + "Additionally, a dictionary can be provided to configure the algorithm with the following optional keys:\n", + "- `qpu`: The QLM solver to be used (if not provided, `PyLinalg` will be used by default).\n", + "- `mcz_qlm`: A flag to use the QLM multi-controlled Z gate (`True`, default) or a multiplexor implementation (`False`).\n", + "- `shots`: An integer specifying the number of measurements to be performed on the quantum circuit." ] }, { @@ -216,21 +223,21 @@ }, { "cell_type": "markdown", - "id": "b256d71b", + "id": "a63f1c80", "metadata": {}, "source": [ - "### run method\n", + "### `run` Method\n", "\n", - "For computing the amplitude of the target state the **run** method should be used. The method return the amplitude of the desired state, this is $a$. \n", + "To compute the amplitude of the target state, the **`run`** method should be used. This method returns the amplitude of the desired state, denoted as $ a $.\n", "\n", - "Additionally, other object attributes are populated:\n", + "Additionally, the following object attributes are populated:\n", "\n", - "* *ae* amplitude of the desired state: $a$\n", - "* *ae_l*: the lower limit for a $a_l = a - \\frac{1}{\\sqrt{\\text{shots}}}$ \n", - "* *ae_u*: the upper limit for a $a_u= a - \\frac{1}{\\sqrt{\\text{shots}}}$.\n", - "* *schedule_pdf*: pandas DataFrame with the schedule used by the method. In this class is the number of shots used for the computation.\n", - "* *oracle_calls*: number of total oracle calls for a complete execution of the algorithm.\n", - "* *max_oracle_depth*: maximum number of applications of the oracle for the complete execution of the algorithm." + "- `ae`: The amplitude of the desired state, $ a $.\n", + "- `ae_l`: The lower limit for $ a $, calculated as $ a_l = a - \\frac{1}{\\sqrt{\\text{shots}}} $.\n", + "- `ae_u`: The upper limit for $ a $, calculated as $ a_u = a + \\frac{1}{\\sqrt{\\text{shots}}} $.\n", + "- `schedule_pdf`: A pandas DataFrame containing the schedule used by the method. In this class, it represents the number of shots used for the computation.\n", + "- `oracle_calls`: The total number of oracle calls required for the complete execution of the algorithm.\n", + "- `max_oracle_depth`: The maximum number of applications of the oracle during the complete execution of the algorithm." ] }, { @@ -361,9 +368,9 @@ "id": "73bf5076", "metadata": {}, "source": [ - "## 2. AE class\n", + "## 2. `AE` class\n", "\n", - "The **AE** class allows the user to solve an **Amplitude Estimation** problem by providing and oracle and the type of **AE** algorithm user want to use." + "The `AE` class allows the user to solve an **Amplitude Estimation** problem by providing and oracle and the type of **AE** algorithm user want to use." ] }, { @@ -385,38 +392,38 @@ "The foundation of any amplitude estimation algorithm is the Grover operator $\\mathcal{Q}$, built onto the oracle $\\mathcal{O}$, that has the following effect over our state $|\\Psi\\rangle$:\n", "\n", "$$\\mathcal{G}^{m}|\\Psi\\rangle = |\\Psi \\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle +\\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle,$$\n", - "\n", - "for more information about the grover operator and the amplitude amplification algorithm check the notebook **02_AmplitudeAmplification_Operators.ipynb**.\n" + "\n" ] }, { "cell_type": "markdown", - "id": "6692dbf9", + "id": "8f5a0d10", "metadata": {}, "source": [ - "### 2.2 Creating object from AE class\n", + "### 2.2 Creating an Object from the AE Class\n", "\n", - "We have implemented a python class called **AE** into the **QQuantLib/AE/ae_class** module that allows us to use all the AE algorithms implemented in the **QQuantLib.AE** package in a transparent way for the user. \n", + "We have implemented a Python class called `AE` in the **QQuantLib/AE/ae_class** module, which allows users to access all amplitude estimation (AE) algorithms implemented in the **QQuantLib.AE** package in a transparent and user-friendly manner.\n", "\n", - "When the class is created following mandatory inputs **MUST** be provided:\n", + "When creating an instance of the class, the following mandatory inputs **MUST** be provided:\n", "\n", - "1. Oracle: QLM AbstractGate or QRoutine with the implementation of the Oracle for creating the Grover operator.\n", - "2. target: this is the marked state in binary representation as a Python list\n", - "3. index: list of the qubits affected by the Grover operator.\n", - "4. ae_type: str. String for selecting the algorithm to use. Valid values\n", - " * **MLAE**: for using Maximum Likelihood algorithm (see Notebook 03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb)\n", - " * **CQPEAE**: for using classical Phase Estimation with Quantum Fourier Transformation (**QFT**) for **AE** (see Notebook 04_Classical_Phase_Estimation_Class.ipynb). It can use window QPE (see Notebook 04-02_Classical_Phase_Estimation_Windows.ipynb)\n", - " * **IQPEAE**: for using classical Phase Estimation with iterative Quantum Fourier Transformation (**QFT**) for **AE** (see Notebook 05_Iterative_Quantum_Phase_Estimation_Class.ipynb)\n", - " * **IQAE**: for using Iterative Quantum Amplitude Estimation (see Notebook 06_Iterative_Quantum_Amplitude_Estimation_class.ipynb). The modified **IQAE** can be used by the **AE** class. We need to provided following string:\n", - " * **mIQAE**\n", - " * **RQAE**: for using Real Quantum Amplitude Estimation (see Notebook 07_Real_Quantum_Amplitude_Estimation_class.ipynb). The different vartiations on **RQAE** can be used by the **AE** class. We need to provided following strings:\n", - " * **eRQAE** for extended RQAE (see 07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb)\n", - " * **mRQAE** for modified RQAE (see 07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb)\n", - " * **sRQAE** for RQAE with shots (see 07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb)\n", - " * **MCAE**: for using naive Monte-Carlo measurements (see the first part of the present notebook)\n", - " \n", - "If *Oracle*, *target* or *index* is not provided a ValueError is raised.\n", - "Additionally, other optional inputs can be provided as a Python dictionary for configuring the selected algorithms (see the Notebooks before for each **AE** algorithm parameter. " + "1. `oracle`: A QLM `AbstractGate` or `QRoutine` that implements the Oracle for constructing the Grover operator.\n", + "2. `target`: The marked state in binary representation, provided as a Python list.\n", + "3. `index`: A list of qubits affected by the Grover operator.\n", + "4. `ae_type`: A string specifying the algorithm to use. Valid values include:\n", + " - **MLAE**: For using the Maximum Likelihood Amplitude Estimation algorithm (see Notebook *03_Maximum_Likelihood_Amplitude_Estimation_Class.ipynb*).\n", + " - **CQPEAE**: For using classical Phase Estimation with Quantum Fourier Transform (**QFT**) for Amplitude Estimation (see Notebook *04_Classical_Phase_Estimation_Class.ipynb*). It can also use windowed QPE (see Notebook *04-02_Classical_Phase_Estimation_Windows.ipynb*).\n", + " - **IQPEAE**: For using classical Phase Estimation with iterative Quantum Fourier Transform (**QFT**) for Amplitude Estimation (see Notebook *05_Iterative_Quantum_Phase_Estimation_Class.ipynb*).\n", + " - **IQAE**: For using Iterative Quantum Amplitude Estimation (see Notebook *06_Iterative_Quantum_Amplitude_Estimation_class.ipynb*). The modified **IQAE** can also be used by providing the string:\n", + " - **mIQAE**\n", + " - **RQAE**: For using Real Quantum Amplitude Estimation (see Notebook *07_Real_Quantum_Amplitude_Estimation_class.ipynb*). Variations of **RQAE** can also be used by providing the following strings:\n", + " - **eRQAE**: Extended RQAE (see *07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb*).\n", + " - **mRQAE**: Modified RQAE (see *07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb*).\n", + " - **sRQAE**: RQAE with shots (see *07-02_Improvements_on_Real_Quantum_Amplitude_Estimation.ipynb*).\n", + " - **MCAE**: For using the naive Monte Carlo approach for amplitude estimation (see the first part of the present notebook).\n", + "\n", + "If any of the mandatory inputs (`Oracle`, `target`, or `index`) are missing, a `ValueError` is raised.\n", + "\n", + "Additionally, other optional inputs can be provided as a Python dictionary to configure the selected algorithm. Refer to the respective notebooks for details on the parameters of each **AE** algorithm." ] }, { @@ -434,18 +441,27 @@ "id": "8d17d198", "metadata": {}, "source": [ - "As an example, we are going to use the example of section 1 where the estimation problem where the estimation problem will be:\n", + "#### Example\n", + "\n", + "To demonstrate how the `AE` class works, consider the following amplitude estimation problem:\n", "\n", "$$\n", - " \\begin{array}{l}\n", - " &\\mathcal{O}\\longrightarrow \\mathcal{P}.\\\\\n", - " & |\\Psi\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{1}|1\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", - " & \\sqrt{a}|\\Psi_0\\rangle \\longrightarrow \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle.\\\\\n", - " & \\sqrt{1-a}|\\Psi_1\\rangle \\longrightarrow \\scriptstyle \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}}\\left[\\sqrt{0}|0\\rangle+\\sqrt{2}|2\\rangle+\\sqrt{3}|3\\rangle+\\sqrt{4}|4\\rangle+\\sqrt{5}|5\\rangle+\\sqrt{6}|6\\rangle+\\sqrt{7}|7\\rangle\\right].\\\\\n", - " \\end{array}\n", + "|\\Psi\\rangle = \\mathcal{A}|0\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{1}|1\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right]. \\tag{2}\n", + "$$\n", + "\n", + "By comparing Equation (2) with Equation (1):\n", + "\n", + "$$\n", + "\\sqrt{a}|\\Psi_0\\rangle = \\sin(\\theta)|\\Psi_0\\rangle = \\dfrac{\\sqrt{1}}{\\sqrt{0+1+2+3+4+5+6+7+8}}|1\\rangle,\n", "$$\n", "\n", - "The target state, in this case, is $|1\\rangle$. Its binary representation is $001$. This has to be passed to the target variable as a list. Moreover, we have to provide the list of qubits where we are acting, in this case is just $[0,1,2]$, the whole register." + "and\n", + "\n", + "$$\n", + "\\sqrt{1-a}|\\Psi_1\\rangle = \\cos(\\theta)|\\Psi_1\\rangle = \\dfrac{1}{\\sqrt{0+1+2+3+4+5+6+7+8}} \\left[\\sqrt{0}|0\\rangle + \\sqrt{2}|2\\rangle + \\sqrt{3}|3\\rangle + \\sqrt{4}|4\\rangle + \\sqrt{5}|5\\rangle + \\sqrt{6}|6\\rangle + \\sqrt{7}|7\\rangle \\right].\n", + "$$\n", + "\n", + "In this case, the target state is $|1\\rangle$, whose binary representation is $001$. This must be passed to the `target` variable as a list (`[0, 0, 1]`). Additionally, we need to provide the list of qubits where the operation is being performed. In this example, it is $[0, 1, 2]$, corresponding to the entire register.\n" ] }, { @@ -473,10 +489,10 @@ }, { "cell_type": "markdown", - "id": "f21f853c", + "id": "f8f835b6", "metadata": {}, "source": [ - "The Python dictionary can be a very general one where all the parameters, for configuring any algorithm can be provided. The class selects for each algorithm the parameters it needs. If there is a parameter not provided for a method the default parameter of the method will be used!!" + "The Python dictionary can be a very general one, allowing all parameters for configuring any algorithm to be provided. The class automatically selects the relevant parameters required by each specific algorithm. If a parameter is not provided for a particular method, the default parameter of that method will be used." ] }, { @@ -545,19 +561,18 @@ }, { "cell_type": "markdown", - "id": "608d4500", + "id": "72ac5243", "metadata": {}, "source": [ - "### 2.3 The create_ae_solver method\n", + "### 2.3 The `create_ae_solver` Method\n", "\n", - "The *create_ae_solver* method allows the user to select the **AE** class. When this method is called following attributes are created:\n", + "The `create_ae_solver` method enables the user to instantiate the desired **AE** class. When this method is called, the following attributes are created:\n", "\n", - "* *solver_dict*: python dictionary with the parameters for configuring the selected **AE** class (for selecting the class use the *ae_type* attribute). If the selected **ae** class needs any parameter that is not included in the input python dictionary the default value of the class will be used.\n", - "* *solver_ae*: this is an object created from the different **AE** classes of the **QQuantLib.AE** package (for selecting the class use the *ae_type* attribute). For configuring the methods the *solver_dict* will be used.\n", + "- **`solver_dict`**: A Python dictionary containing the parameters for configuring the selected **AE** class (the class is determined by the `ae_type` attribute). If the selected **AE** class requires any parameter not included in the input dictionary, the default value of the class will be used.\n", + " \n", + "- **`solver_ae`**: An object instantiated from one of the **AE** classes in the **QQuantLib.AE** package (the class is determined by the `ae_type` attribute). The configuration of this object is based on the parameters provided in the `solver_dict`.\n", "\n", - "\n", - "If *ae_type* is None the *create_ae_solver* method will raise an Error.\n", - "\n" + "If the `ae_type` attribute is set to `None`, the `create_ae_solver` method will raise an error.\n" ] }, { @@ -684,15 +699,15 @@ }, { "cell_type": "markdown", - "id": "a80f5441", + "id": "c329365e", "metadata": {}, "source": [ "### 2.4 Executing the Algorithm\n", "\n", - "The *solver_ae* attribute is an object of the desired **AE** class so we can use directly its **run** method for executing the algorithm.\n", + "The `solver_ae` attribute is an object of the desired **AE** class, so its `run` method can be used directly to execute the algorithm.\n", "\n", - "**BE AWARE!!**\n", - "This is not the recommended way to use the **AE** class. The **AE** class has its proper *run* method which is the recommended way for executing the class (see section 3). This section is included for pedagogical purposes." + "**BE AWARE!!** \n", + "This is not the recommended way to use the **AE** class. The **AE** class has its own `run` method, which is the preferred and recommended way to execute the algorithm (see Section 3). This section is included for pedagogical purposes only." ] }, { @@ -869,14 +884,17 @@ }, { "cell_type": "markdown", - "id": "036ba597", + "id": "d6ce7012", "metadata": {}, "source": [ "## 3. AE complete execution\n", "\n", - "**BE AWARE!!**\n", + "### BE AWARE\n", + "-----------------\n", + "\n", "In section 2 the complete **AE** class was explained in a detailed way for pedagogical purposes for understanding what is the design of the **AE** class. The users **SHOULD NOT** use the procedures provided in section 2 for working with the class. Instead, Section 3 shows how the user **SHOULD** interact with the **AE** class.\n", "\n", + "----\n", "\n", "The **AE** class has a *run* method that executes, in a transparent way, the *run* method of the solver object and does some post-process over the obtained results. The following attributes are populated when the method is executed:\n", "\n", @@ -890,7 +908,7 @@ " * CQPEAE and IQPEAE: None.\n", " * IQAE, RQAE: pandas DataFrame with the times the Grover-like operator was applied (m_k column) and the correspondent number of shots (shots column)\n", " * MLAE: pandas DataFrame with the times the Grover-like operator was applied (m_k column) the correspondent number of shots (n_k column) and the number of positive outcomes obtained (h_k)\n", - " * MCAE: pandas DataFrame with the number of shots used.\n" + " * MCAE: pandas DataFrame with the number of shots used." ] }, { @@ -1407,7 +1425,10 @@ "source": [ "### 3.5 RQAE\n", "\n", - "**BE AWARE** The RQAE algorithm provides directly the **Amplitude** of the state so we need to compare vs the square root!! This is applied to the **RQAE** variations (i.e. **mRQAE**, **sRQAE** and **eRQAE**)\n", + "### BE AWARE\n", + "-----------------\n", + "\n", + "The RQAE algorithm provides directly the **Amplitude** of the state so we need to compare vs the square root! This is applied to the **RQAE** variations (i.e. **mRQAE**, **sRQAE** and **eRQAE**)\n", "\n", "$$\n", " \\begin{array}{l}\n", diff --git a/misc/notebooks/09_DataEncodingClass.ipynb b/misc/notebooks/09_DataEncodingClass.ipynb index ed36b7e..3c210b5 100644 --- a/misc/notebooks/09_DataEncodingClass.ipynb +++ b/misc/notebooks/09_DataEncodingClass.ipynb @@ -2,14 +2,14 @@ "cells": [ { "cell_type": "markdown", - "id": "1a164c1a", + "id": "fb97d4f8", "metadata": {}, "source": [ "# Encoding Data Class\n", "\n", - "The present notebook reviews the **Encoding** class from the *encoding_protocols* module from the **QQuantLib.DL.encoding_protocols** file.\n", + "The present notebook reviews the `Encoding` class from the *encoding_protocols* module in the **QQuantLib.DL.encoding_protocols** file. \n", "\n", - "The **Encoding** class is a Python one whose main objective is to create a quantum circuit for encoding some input data, in a transparent way for the user of the library. The output quantum circuit can be used as an oracle for **Amplitude Estimation** problems. In this class up to 3 different encoding methods were implemented." + "The `Encoding` class is a Python class designed to create a quantum circuit for encoding input data in a manner that is transparent to the user of the library. The resulting quantum circuit can be used as an oracle for **Amplitude Estimation** problems. This class implements up to three different encoding methods." ] }, { @@ -57,91 +57,86 @@ }, { "cell_type": "markdown", - "id": "9efd1508", + "id": "9ae1900f", "metadata": {}, "source": [ "## 1. Encoding Problem\n", "\n", - "The main objective behind the **Encoding** class is to create, in a transparent way for the user, a quantum oracle circuit that can be used for computing integrals using **Amplitude Estimation** algorithms. \n", + "The primary objective of the **Encoding** class is to create, in a user-transparent manner, a quantum oracle circuit that can be utilized for computing integrals using **Amplitude Estimation (AE)** algorithms. \n", "\n", - "Let $p(x)$ a probability distribution and $f(x)$ a function defined over an interval $[a, b] \\subset \\mathbf{R}$, our main idea is to compute the expected value of the function $f(x)$ when $x$ follows a probability distribution $p(x)$ between the interval $[a, b]$ using **AE** techniques (and of course the **AE** class created in the **QQuantLib.AE.ae_class**). The expected value can be computed as the following integral:\n", + "Let $ p(x) $ be a probability distribution and $ f(x) $ a function defined over an interval $[a, b] \\subset \\mathbf{R}$. Our goal is to compute the expected value of the function $ f(x) $ when $ x $ follows the probability distribution $ p(x) $ over the interval $[a, b]$ using **AE** techniques (and, of course, the **AE** class created in **QQuantLib.AE.ae_class**). The expected value can be expressed as the following integral:\n", + "$$\n", + "I = \\int_a^b p(x)f(x)dx\n", + "$$\n", "\n", - "$$I = \\int_a^bp(x)f(x)dx$$\n", - "\n", - " \n", - "In this case, we are going to approximate this integral as a Riemann sum so:\n", + "To approximate this integral, we use a Riemann sum. Let:\n", + "$$\n", + "P = \\{[x_0, x_1], [x_1, x_2], \\dots, [x_{n-1}, x_n]\\},\n", + "$$\n", + "such that:\n", + "$$\n", + "a = x_0 < x_1 < x_2 < \\dots < x_n = b.\n", + "$$\n", + "Then:\n", + "$$\n", + "I = \\int_a^b p(x)f(x)dx \\approx S = \\sum_{i=0}^{n-1} p(x_i)f(x_i) \\cdot \\Delta x_i,\n", + "$$\n", + "where $ \\Delta x_i = x_{i+1} - x_i $.\n", "\n", + "The final objective is to create a quantum circuit where the above Riemann sum is encoded into the amplitude of one of the eigenstates of the $ |\\Psi\\rangle $ state. To achieve this, several encoding algorithms have been identified and implemented in the Python class.\n", "\n", - "$$P=\\{[x_0, x_1], [x_1, x_2], ..., [x_{n-1}, x_n]\\}$$ \n", + "The functions $ p(x) $ and $ f(x) $ are encoded as arrays, so a discretization process is required.\n", "\n", - "such that:\n", + "---\n", "\n", - "$$a = x_0 < x_1 < x_2 < ... < x_n=b$$\n", + "### Instantiate the Class\n", "\n", - "Then \n", + "To create the encoding quantum circuit, you can instantiate the `Encoding` class from the *encoding_protocols* module. This class accepts the following inputs:\n", "\n", - "$$I= \\int_a^bp(x)f(x)dx \\approx S = \\sum_i^np(x_i)f(x_i)*\\Delta x_i$$\n", + "- `array_function`: A NumPy array containing the discretized values of the function $ f(x) $. This is mandatory.\n", + "- `array_probability`: A NumPy array containing the discretized values of the probability $ p(x) $. If set to `None`, the uniform distribution will be used by default.\n", + "- `encoding`: An integer indicating the encoding procedure to use.\n", "\n", - "The final objective is to create a quantum circuit, where the before Riemann sum is codified into the amplitude of one of the *eigen-state* of the $|\\Psi\\rangle$ state.\n", + "Additionally, a Python dictionary can be provided to configure other settings. The main key in this dictionary is:\n", + "- `multiplexor`: A boolean variable specifying whether to use multiplexors.\n", "\n", - "For doing this several algorithms were identified and implemented in our Python class.\n", + "When the class is instantiated, the following attributes are created:\n", "\n", - "The $p(x)$ and the $f(x)$ functions will be encoded as arrays so a discretization is needed. \n" + "- `oracle`: Stores the *QLM AbstractGate* of the desired oracle.\n", + "- `co_target`: A list of integers representing the binary-encoded state that the oracle marks.\n", + "- `co_index`: A list of integers specifying the registers on which the oracle acts.\n", + "- `p_gate`: Stores the *QLM AbstractGate* of the operator $ \\mathbf{U}_p $.\n", + "- `function_gate`: Stores the *QLM AbstractGate* of the operator $ \\mathbf{U}_f $.\n", + "- `encoding_normalization`: Represents the normalization factor introduced by the encoding procedure. To recover the integral from the amplitude of the state where it is encoded, you must **ALWAYS** multiply the amplitude by this attribute." ] }, { "cell_type": "markdown", - "id": "28c99cc2", + "id": "38e44693", "metadata": {}, "source": [ - "### Instantiate the class\n", - "\n", - "For creating the encoding quantum circuit we can instantiate the **Encoding** class from the *encoding_protocols* module. This class have the following inputs:\n", - "\n", - "* *array_function*: numpy array with the discretization of the of the function $f(x)$. This is mandatory.\n", - "* *array_probability*: numpy array with the discretization of the probability $p(x)$. This can be None, then the uniform distribution will be used.\n", - "* *encoding*: integer for indicating the encoding procedure to use.\n", - "\n", - "Additionally, a Python dictionary with other configuration settings can be provided to the class. The main key of this dictionary will be:\n", + "### BE AWARE!! Input restrictions\n", + "---------------------------------------\n", "\n", - "* *multiplexor*: boolean variable for using (or not) multiplexors.\n", + "There are some **MANDATORY** conditions that the numpy inputs arrays provided to the class should be satisfied:\n", "\n", - "Additionally, some attributes are created when the class is instantiated:\n", + "* All the arrays must be of dimension $2^n$ where $n$ is an integer number. In this case, the $n$ will be related to the number of qubits used for creating the quantum circuit.\n", + "* $f(x)$ **MUST BE** properly normalised: $f(x_i) \\leq 1 \\forall i$\n", + "* $p(x)$, if provided, **MUST BE** properly normalised: $\\sum_{i=0}^{2^{n}} p(x_i) = 1$\n", "\n", - "* *oracle*: where the *QLM AbstractGate* of the desired oracle will be stored.\n", - "* *co_target*: list of ints with the state the oracle marks in binary representation.\n", - "* *co_index*: list of ints with the registers over the oracle will act.\n", - "* *p_gate*; where the *QLM AbstractGate* of the operator $\\mathbf{U}_p$ will be stored.\n", - "* *function_gate*; where the *QLM AbstractGate* of the operator $\\mathbf{U}_f$ will be stored.\n", - "* **encoding_normalization**: this will be the normalisation due to the encoding procedure. So to recover the integral of the amplitude of the state where we want to codify it we need **ALWAYS** multiply the amplitude by this attribute!!" + "If any of these conditions is not satisfied an Error will be raised!!!" ] }, { "cell_type": "code", "execution_count": null, - "id": "0ec79337", + "id": "51aa2277", "metadata": {}, "outputs": [], "source": [ "from QQuantLib.DL.encoding_protocols import Encoding" ] }, - { - "cell_type": "markdown", - "id": "38e44693", - "metadata": {}, - "source": [ - "### BE AWARE!! Input restrictions\n", - "\n", - "There are some **MANDATORY** conditions that the numpy inputs arrays provided to the class should be satisfied:\n", - "\n", - "* All the arrays must be of dimension $2^n$ where $n$ is an integer number. In this case, the $n$ will be related to the number of qubits used for creating the quantum circuit.\n", - "* $f(x)$ **MUST BE** properly normalised: $f(x_i) \\leq 1 \\forall i$\n", - "* $p(x)$, if provided, **MUST BE** properly normalised: $\\sum_{i=0}^{2^{n}} p(x_i) = 1$\n", - "\n", - "If any of these conditions is not satisfied an Error will be raised!!!" - ] - }, { "cell_type": "code", "execution_count": null, @@ -421,7 +416,7 @@ "\n", "$$\\mathbf{P}_{|0 \\rangle} = \\sum_{j=0}^{2^n-1} \\left| p(x_j)*f(x_j) \\right| \\tag{7}$$\n", "\n", - "For selecting this encoding procedure in the **Encode** class the *encoding* attribute of the class must be set to **0**. For getting the oracle corresponding to the encode the *oracle_encoding_0* method should be used.\n", + "For selecting this encoding procedure in the `Encode` class the `encoding` attribute of the class must be set to **0**. For getting the oracle corresponding to the encode the `oracle_encoding_0` method should be used.\n", "\n", "**BE AWARE In this case the *encoding_normalization* attribute will be 1.0**\n" ] @@ -630,18 +625,22 @@ }, { "cell_type": "markdown", - "id": "36546084", + "id": "9129c4ea", "metadata": {}, "source": [ - "### BE AWARE!! Encoding problems of Procedure 0\n", + "### BE AWARE!! Encoding Problems of Procedure 0\n", "\n", - "One of the main problems of the first encoding procedure is that the function must be positive definite. If any of the values of the input array $f(x_i)$ is negative their contribution to the final sum $\\mathbf{P}_{|0 \\rangle} = \\sum_{j=0}^{2^n-1} \\left| p(x_j)*f(x_j) \\right|$ will not be done in the proper way.\n", + "One of the main limitations of the first encoding procedure is that the function $ f(x) $ must be positive definite. If any value in the input array $ f(x_i) $ is negative, its contribution to the final sum \n", + "$$\n", + "\\mathbf{P}_{|0 \\rangle} = \\sum_{j=0}^{2^n-1} \\left| p(x_j) \\cdot f(x_j) \\right|\n", + "$$ \n", + "will not be handled correctly.\n", "\n", - "Instead of raising an Error in this type of situation, we prefer raising a Warning and allowing the complete oracle creation when the *oracle_encoding_0* method is called.\n", + "Instead of raising an error in such cases, a warning is issued, and the oracle creation process continues when the `oracle_encoding_0` method is called. \n", "\n", - "For take into account these cases the operator $\\mathbf{U}_f$ (step 3 of the procedure) wil encode $\\sqrt{|f(x_i)|}$ instead of $\\sqrt{f(x_i)}$. \n", + "To account for this issue, the operator $\\mathbf{U}_f$ (step 3 of the procedure) encodes $\\sqrt{|f(x_i)|}$ instead of $\\sqrt{f(x_i)}$. \n", "\n", - "Here we developed an explicit example:" + "Below is an explicit example to illustrate this behavior:" ] }, { @@ -817,7 +816,7 @@ "\n", "$$\\sum_{i=0}^{2^{n}-1} p(x_i)f(x_i) = {2^n} \\sqrt{\\mathbf{P}_{|0\\rangle_n|0\\rangle|0\\rangle}} \\tag{21}$$\n", "\n", - "For selecting this encoding procedure in the **Encode** class the *encoding* attribute of the class must be set to **1**. For getting the oracle corresponding to the encode the *oracle_encoding_1* method should be used.\n", + "For selecting this encoding procedure in the `Encode` class the `encoding` attribute of the class must be set to **1**. For getting the oracle corresponding to the encode the `oracle_encoding_1` method should be used.\n", "\n", "**BE AWARE In this case the *encoding_normalization* attribute will be: $2^n$**\n", "\n", @@ -1019,7 +1018,7 @@ "\n", "$$\\sum_{i=0}^{2^{n}-1} p(x_i)f(x_i) = \\sqrt{\\mathbf{P}_{|0\\rangle_n|0\\rangle}} \\tag{33}$$\n", "\n", - "For selecting this encoding procedure in the **Encode** class the *encoding* attribute of the class must be set to **2**. For getting the oracle corresponding to the encode the *oracle_encoding_2* method should be used.\n", + "For selecting this encoding procedure in the `Encode` class the `encoding` attribute of the class must be set to **2**. For getting the oracle corresponding to the encode the `oracle_encoding_2` method should be used.\n", "\n", "**BE AWARE In this case the *encoding_normalization* attribute will be: 1.0**" ] @@ -1191,9 +1190,9 @@ ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -1205,7 +1204,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/10_ApplicationTo_Finance_01_IntegralComputing.ipynb b/misc/notebooks/10_ApplicationTo_Finance_01_IntegralComputing.ipynb index b714dad..6e086e3 100644 --- a/misc/notebooks/10_ApplicationTo_Finance_01_IntegralComputing.ipynb +++ b/misc/notebooks/10_ApplicationTo_Finance_01_IntegralComputing.ipynb @@ -2,42 +2,33 @@ "cells": [ { "cell_type": "markdown", - "id": "140c4007", + "id": "a90446e8", "metadata": {}, "source": [ - "# Application of Amplitude Estimation to Finances: Computing Integrals" - ] - }, - { - "cell_type": "markdown", - "id": "8c15c443", - "metadata": {}, - "source": [ - "The reference for this notebook will be:\n", - "\n", - "* NEASQC deliverable: *D5.1: Review of state-of-the-art for Pricing and Computation of VaR https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf*" - ] - }, - { - "cell_type": "markdown", - "id": "89c063a9", - "metadata": {}, - "source": [ - "In finance one of the most important tasks is computing the \"fair\" price of a **derivative contract** whose definition (following Investopedia) is the following:\n", + "# Application of Amplitude Estimation to Finance: Computing Integrals\n", "\n", - "*A derivative is a contract between two or more parties whose value is based on an agreed-upon underlying financial asset (like a security) or set of assets (like an index).*\n", + "The reference for this notebook is:\n", "\n", - "Usually, the problem of computing the price of a **derivative contract** can be reduced to computing an expectation of a given input function $f(x)$ when $x$ follows a probability density $p(x)$. \n", + "* NEASQC Deliverable: *D5.1: Review of State-of-the-Art for Pricing and Computation of VaR* \n", + " [https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf](https://www.neasqc.eu/wp-content/uploads/2021/06/NEASQC_D5.1_Review-of-state-of-the-art-for-Pricing-and-Computation-of-VaR_R2.0_Final.pdf)\n", "\n", - "$$\\mathbb{E}[f]=\\int_a^bp(x)f(x)dx$$\n", + "In finance, one of the most critical tasks is calculating the \"fair\" price of a **derivative contract**, which can be defined as follows (per Investopedia):\n", + "*A derivative is a contract between two or more parties whose value is based on an agreed-upon underlying financial asset (e.g., a security) or set of assets (e.g., an index).*\n", "\n", - "This integral can be approximated by the Riemann sum:\n", + "Typically, the problem of pricing a **derivative contract** can be reduced to computing the expectation of a given function $ f(x) $, where $ x $ follows a probability density $ p(x) $:\n", + "$$\n", + "\\mathbb{E}[f] = \\int_a^b p(x)f(x)dx.\n", + "$$\n", "\n", - "$$\\mathbb{E}[f] = \\sum_{i=0}^{2^n-1} p(x_i)f(x_i)dx$$\n", + "This integral can be approximated using the Riemann sum:\n", + "$$\n", + "\\mathbb{E}[f] \\approx \\sum_{i=0}^{2^n-1} p(x_i)f(x_i)\\Delta x,\n", + "$$\n", + "where $ \\Delta x $ represents the width of each interval.\n", "\n", - "This value can be computed using **AE** techniques. In this notebook, we are going to review the function **q_solve_integral** from the module *quantum_integration* of the package *finance* of the *QQuantLib* library(**QQuantLib/finance/quantum_integration.py**).\n", + "This value can be computed using **Amplitude Estimation (AE)** techniques. In this notebook, we review the function `q_solve_integral` from the module *quantum_integration* in the package **finance** of the `QQuantLib` library (**QQuantLib/finance/quantum_integration**).\n", "\n", - "This **q_solve_integral** uses the **Encoding** class from **QQuantLib.DL.encoding_protocols** and the **AE** class from **QQuantLib/AE/ae_class** for loading an input probability distribution $p(x)$ and a function $f(x)$ and computing the before integral using the different **AE** algorithms implemented in the **QQuantLib**." + "The `q_solve_integral` function leverages the `Encoding` class from **QQuantLib.DL.encoding_protocols** and the `AE` class from **QQuantLib/AE/ae_class** to load an input probability distribution $ p(x) $ and a function $ f(x) $. It then computes the aforementioned integral using the various **AE** algorithms implemented in **QQuantLib**.\n" ] }, { @@ -105,34 +96,45 @@ }, { "cell_type": "markdown", - "id": "20303f08", + "id": "d2193465", "metadata": {}, "source": [ - "## 1. Defining the problem\n", - "\n", - "The first thing we need to do, for transforming our expectation computation in an **amplitude estimation** problem, is to define the density probability $p(x)$ and the function to evaluate $f(x)$. We cannot work with continuous variables so we need to discretise $p(x)$ and $f(x)$. \n", + "## 1. Defining the Problem\n", "\n", - "We are going to define the following toy problem:\n", + "The first step in transforming our expectation computation into an **Amplitude Estimation** problem is to define the probability density $ p(x) $ and the function $ f(x) $. Since we cannot work with continuous variables, we must discretize both $ p(x) $ and $ f(x) $.\n", "\n", - "* Domain: our $x$ will be a set of $2^{n}$ integers numbers.\n", + "We will define the following toy problem:\n", "\n", - "$$x \\in \\{0, 1, 2, ..., 2^n-1\\}$$\n", + "- **Domain**: Our variable $ x $ will be a set of $ 2^n $ integer numbers:\n", + " $$\n", + " x \\in \\{0, 1, 2, \\dots, 2^n - 1\\}.\n", + " $$\n", "\n", - "* $p(x)$: Over our domain, we are going to define a properly normalised density distribution in the form:\n", + "- **$ p(x) $**: Over this domain, we define a properly normalized probability distribution in the form:\n", + " $$\n", + " p(x) = \\frac{x}{\\sum_{i=0}^{2^n - 1} i}.\n", + " $$\n", "\n", - "$$p(x)=\\frac{x}{\\sum_{i=0}^{2^{n}-1}i}$$\n", + "- **$ f(x) $**: Over the same domain, we define the following properly normalized function:\n", + " $$\n", + " f(x) = \\frac{x}{2^n - 1}.\n", + " $$\n", "\n", - "* $f(x)$: Over our domain, we are going to define the following properly normalised function:\n", + "---\n", "\n", - "$$f(x) = \\frac{x}{2^n-1}$$\n", + "### **BE AWARE**\n", "\n", + "It is **MANDATORY** that both $ p(x) $ and $ f(x) $ are properly normalized. The following conditions **must** be satisfied:\n", "\n", - "**BE AWARE**\n", + "- For $ p(x) $, it is mandatory that:\n", + " $$\n", + " \\sum_{i=0}^{2^n - 1} p(x_i) = 1.\n", + " $$\n", "\n", - "Is **MANDATORY** that $p(x)$ and $f(x)$ are properly normalised. Following conditions **must be** satisfied:\n", - "\n", - "* For $p(x)$ is mandatory that: $\\sum_{i=0}^{2^{n}} p(x_i) = 1$\n", - "* For $f(x)$ is mandatory that: $f(x_i) \\leq 1 \\forall i$" + "- For $ f(x) $, it is mandatory that:\n", + " $$\n", + " f(x_i) \\leq 1 \\quad \\forall i.\n", + " $$" ] }, { @@ -209,35 +211,40 @@ }, { "cell_type": "markdown", - "id": "8ee530b6", + "id": "3e9ef3b0", "metadata": {}, "source": [ - "## 2. q_solve_integral function\n", + "## 2. q_solve_integral Function\n", + "\n", + "The `q_solve_integral` function from **QQuantLib.finance.quantum_integration** requires a Python dictionary as input, containing various keys for configuration. This function relies on the following classes:\n", "\n", - "The *q_solve_integral* function from **QQuantLib.finance.quantum_integration** needs as input a Python dictionary with different keys. The function needs the following classes:\n", + "- The `AE` class from **QQuantLib.AE.ae_class** (see Notebook **08_AmplitudeEstimation_Class**).\n", + "- The `Encoding` class from **QQuantLib.DL.encoding_protocols** (see Notebook **09_DataEncodingClass**).\n", "\n", - "* *AE* class from **QQuantLib.AE.ae_class** (see Notebook **08_AmplitudeEstimation_Class**)\n", - "* *Encoding* class from **QQuantLib.AE.ae_class** (see Notebook **09_DataEncodingClass**)\n", + "Thus, all keys used to configure these classes can also be used as keys in the input dictionary for the `q_solve_integral` function.\n", "\n", - "So all the keys for configuring these classes can be used as keys of the input dictionary for the *q_solve_integral* function. \n", + "### Mandatory Keys\n", "\n", - "Additionally, some keys are mandatory:\n", + "Additionally, the following keys are mandatory for the input dictionary:\n", "\n", - "* *array_function*: numpy array with the desired function for encoding (this is $f(x)$).\n", - "* *array_probability*: numpy array with the desired probability (this is $p(x)$). This can be None (in this case a uniform distribution will be used).\n", - "* *encoding*: integer for selecting the encoding procedure to use (see Notebook **09_DataEncodingClass**).\n", - "* *ae_type*: string for selecting the AE algorithm to use for solving integral (see Notebook **08_AmplitudeEstimation_Class**)\n", + "- `array_function`: A NumPy array representing the function $ f(x) $ to be encoded.\n", + "- `array_probability`: A NumPy array representing the probability distribution $ p(x) $. If set to `None`, a uniform distribution will be used.\n", + "- `encoding`: An integer specifying the encoding procedure to use (see Notebook **09_DataEncodingClass**).\n", + "- `ae_type`: A string indicating the Amplitude Estimation (AE) algorithm to use for solving the integral (see Notebook **08_AmplitudeEstimation_Class**).\n", "\n", - "The following cell creates a base Python dictionary for given to the *q_solve_integral* function\n", + "The following cell creates a base Python dictionary to serve as input for the `q_solve_integral` function.\n", "\n", - "The return of the *q_solve_integral* function will be:\n", + "### Return Values\n", "\n", - "* *ae_estimation*: pandas DataFrame with the desired integral computed using **AE** techniques. In this case, normalisation due to the different encodes are managed by the function and the desired integral is returned transparently. There are 3 columns:\n", - " * *ae*: integral value\n", - " * *ae_l*: lower bound of the integral value (only **IQAE** and **RQAE**)\n", - " * *ae_u*: upper bound of the integral value (only **IQAE** and **RQAE**)\n", - "* *solver_ae*: object created from the *AE* class and properly configured.\n", - "* *encode_class* : object created from the *Encode* class and properly configured." + "The `q_solve_integral` function returns the following:\n", + "\n", + "- `ae_estimation`: A pandas DataFrame containing the computed integral using **AE** techniques. The function automatically manages normalization due to the encoding procedures, ensuring the desired integral is returned transparently. The DataFrame has three columns:\n", + " - `ae`: The computed integral value.\n", + " - `ae_l`: The lower bound of the integral value (applicable only for **IQAE** and **RQAE**).\n", + " - `ae_u`: The upper bound of the integral value (applicable only for **IQAE** and **RQAE**).\n", + "\n", + "- `solver_ae`: An object created from the **AE** class, properly configured for the computation.\n", + "- `encode_class`: An object created from the **Encoding** class, properly configured for data encoding." ] }, { @@ -303,22 +310,23 @@ }, { "cell_type": "markdown", - "id": "5fa9f4a1", + "id": "87fd217d", "metadata": {}, "source": [ - "The idea of the *q_solve_integral* function is converting the desired integral computation in an amplitude estimation (**AE**) problem and solving it with the different available **AE** techniques in **QQuantLib**.\n", - "\n", - "In a general, an **AE** problem is the following, given an oracle $\\mathcal{O}$ in the form:\n", - "\n", - "$$\\mathcal{O}|0\\rangle = |\\Psi\\rangle = \\sin(\\theta) |\\Psi_0\\rangle +\\cos(\\theta)|\\Psi_1\\rangle,\\tag{1}$$\n", + "The goal of the `q_solve_integral` function is to transform the desired integral computation into an amplitude estimation (**AE**) problem and solve it using the various **AE** techniques available in **QQuantLib**.\n", "\n", - "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ we want an estimation of $\\sin^2(\\theta)$ by making measurements of the $|\\Psi_0\\rangle$ state. \n", + "In general, an **AE** problem can be described as follows: given an oracle $\\mathcal{O}$ in the form:\n", + "$$\n", + "\\mathcal{O}|0\\rangle = |\\Psi\\rangle = \\sin(\\theta) |\\Psi_0\\rangle + \\cos(\\theta)|\\Psi_1\\rangle, \\tag{1}\n", + "$$\n", + "where $|\\Psi_0\\rangle$ and $|\\Psi_1\\rangle$ are orthogonal states, the objective is to estimate $\\sin^2(\\theta)$ by measuring the probability of obtaining the state $|\\Psi_0\\rangle$.\n", "\n", - "In general **Phase Estimation** algorithms (like **CQPEAE** and **IQPEAE**) allow to compute the $\\sin^2(\\theta)$ straightforwardly, but **QFT** is needed and quantum circuits become very complex. **AE** algorithms try to take advantage of the corresponding oracle Grover-like operator, $\\mathcal{G}$, that acts:\n", + "While **Phase Estimation** algorithms (such as **CQPEAE** and **IQPEAE**) can directly compute $\\sin^2(\\theta)$, they typically require the use of the **Quantum Fourier Transform (QFT)**, which leads to more complex quantum circuits. In contrast, **AE** algorithms aim to leverage the Grover-like operator $\\mathcal{G}$ associated with the oracle, which acts as:\n", + "$$\n", + "\\mathcal{G}^{m_k}|\\Psi\\rangle = |\\Psi\\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle + \\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle.\n", + "$$\n", "\n", - "$$\\mathcal{G}^{m_k}|\\Psi\\rangle = |\\Psi \\rangle = \\sin\\left((2m_k+1)\\theta\\right)|\\Psi_0\\rangle +\\cos\\left((2m_k+1)\\theta\\right)|\\Psi_1\\rangle,$$\n", - "\n", - "If $m_k$ is selected in such a way than $(2m_k+1)\\theta \\sim \\frac{\\pi}{2}$ then the measurement probability of $|\\Psi_0\\rangle$ state is near 1. The main problem is that the optimal $m_k$ depends on $\\theta$. **AE** algorithms create systematic strategies for selecting $m_k$ for obtaining best $\\sin^2(\\theta)$.\n" + "If $m_k$ is chosen such that $(2m_k+1)\\theta \\sim \\frac{\\pi}{2}$, the measurement probability of the $|\\Psi_0\\rangle$ state approaches 1. However, the optimal value of $m_k$ depends on $\\theta$, which is unknown a priori. To address this, **AE** algorithms employ systematic strategies for selecting $m_k$ to achieve the best possible estimation of $\\sin^2(\\theta)$." ] }, { @@ -807,31 +815,33 @@ }, { "cell_type": "markdown", - "id": "e38748d6", + "id": "7d5821c8", "metadata": {}, "source": [ - "## 4. Encoding protocol 1\n", - "\n", - "We are going to use the second encoding protocol (*encoding=1*) as explained in Notebook **09_DataEncodingClass**) and then we are going to test all the possible implemented **AE** algorithms.\n", + "## 4. Encoding Protocol 1\n", "\n", - "As explained in the notebook *09_DataEncodingClass* the desired integral is codified following ($3)$\n", + "We will now use the second encoding protocol (`encoding=1`), as explained in Notebook **09_DataEncodingClass**, and test all the implemented **AE** algorithms.\n", "\n", + "As described in Notebook *09_DataEncodingClass*, the desired integral is encoded following equation $(3)$:\n", "\n", - "$$|\\Psi\\rangle = \\frac{1}{2^n} \\sum_{i=0}^{2^{n}-1} p(x_i)f(x_i) |0\\rangle \\otimes |0\\rangle \\otimes |0\\rangle_n \\; + \\; ... \\tag{3}$$\n", + "$$\n", + "|\\Psi\\rangle = \\frac{1}{2^n} \\sum_{i=0}^{2^n-1} p(x_i)f(x_i) |0\\rangle \\otimes |0\\rangle \\otimes |0\\rangle_n \\; + \\; \\dots \\tag{3}\n", + "$$\n", "\n", - "So comparing $(3)$ and $(1)$\n", + "By comparing $(3)$ with $(1)$, we can identify:\n", + "$$\n", + "|\\Psi_0\\rangle = |0\\rangle \\otimes |0\\rangle \\otimes |0\\rangle_n\n", + "$$\n", + "and\n", + "$$\n", + "\\mathbf{P}_{|\\Psi_0\\rangle} = \\sin^2(\\theta) = \\left| \\frac{1}{2^n} \\sum_{i=0}^{2^n-1} p(x_i)f(x_i) \\right|^2.\n", + "$$\n", "\n", - "$$|\\Psi_{0}\\rangle = |0\\rangle \\otimes |0\\rangle \\otimes |0\\rangle_n $$\n", - "\n", - "and \n", + "The output of the procedure will automatically handle the normalization introduced by the encoding process, ensuring that the pure Riemann sum is returned.\n", "\n", - "$$\\mathbf{P}_{|\\Psi_{0}\\rangle} = \\sin^2(\\theta) = \\left| \\frac{1}{2^n} \\sum_{i=0}^{2^{n}-1} p(x_i)f(x_i) \\right|^2$$\n", + "This approach allows for functions that are not strictly positive definite (within the defined domain, of course). \n", "\n", - "Again the output will take care of the encoding normalisation and the pure Riemman sum will be returned.\n", - "\n", - "This procedure allows functions that are not strictly positive definite (into the domain, of course). \n", - "\n", - "The following cells will use the new non-strictly positive define function with the encoding 1 and the different **AE** algorithms" + "The following cells will demonstrate the use of this new non-strictly positive definite function with `encoding=1` and the various **AE** algorithms." ] }, { @@ -1126,28 +1136,32 @@ }, { "cell_type": "markdown", - "id": "7ae48c1d", + "id": "79a69b2a", "metadata": {}, "source": [ - "## 5. Encoding protocol 2\n", + "## 5. Encoding Protocol 2\n", "\n", - "We are going to use the third encoding protocol (*encoding=2*) as explained in Notebook **09_DataEncodingClass**) and then we are going to test all the possible implemented **AE** algorithms. In this encoding the desired integral is codified in the following way:\n", + "We will now use the third encoding protocol (`encoding=2`), as explained in Notebook **09_DataEncodingClass**, and test all the implemented **AE** algorithms. \n", "\n", - "$$|\\Psi \\rangle = \\sum_{i=0}^{2^{n}-1} p(x_i) f(x_i) |0\\rangle \\otimes |0\\rangle_{n} \\; + \\; ... \\tag{4}$$\n", + "In this encoding, the desired integral is encoded in the following manner:\n", + "$$\n", + "|\\Psi \\rangle = \\sum_{i=0}^{2^n-1} p(x_i) f(x_i) |0\\rangle \\otimes |0\\rangle_n \\; + \\; \\dots \\tag{4}\n", + "$$\n", "\n", - "So comparing $(4)$ and $(1)$\n", + "By comparing $(4)$ with $(1)$, we can identify:\n", + "$$\n", + "|\\Psi_0\\rangle = |0\\rangle \\otimes |0\\rangle_n\n", + "$$\n", + "and\n", + "$$\n", + "\\mathbf{P}_{|\\Psi_0\\rangle} = \\sin^2(\\theta) = \\left| \\sum_{i=0}^{2^n-1} p(x_i)f(x_i) \\right|^2.\n", + "$$\n", "\n", - "$$|\\Psi_{0}\\rangle = |0\\rangle \\otimes |0\\rangle_n $$\n", - "\n", - "and \n", + "The output of the procedure will automatically handle the normalization introduced by the encoding process, ensuring that the pure Riemann sum is returned.\n", "\n", - "$$\\mathbf{P}_{|\\Psi_{0}\\rangle} = \\sin^2(\\theta) = \\left| \\sum_{i=0}^{2^{n}-1} p(x_i)f(x_i) \\right|^2$$\n", + "This approach allows for functions that are not strictly positive definite (within the defined domain, of course). \n", "\n", - "Again the output will take care of the encoding normalisation and the pure Riemann sum will be returned.\n", - "\n", - "This procedure allows functions that are not strictly positive definite (into the domain, of course). \n", - "\n", - "The following cells will use the new non-strictly positive define function with the encoding 1 and the different **AE** algorithms" + "The following cells will demonstrate the use of this new non-strictly positive definite function with `encoding=2` and the various **AE** algorithms." ] }, { @@ -1446,12 +1460,14 @@ }, { "cell_type": "markdown", - "id": "04566722", + "id": "f3143d13", "metadata": {}, "source": [ - "## 6. Issues of AE algorithms\n", + "## 6. Issues of AE Algorithms\n", + "\n", + "In general, **AE** algorithms estimate the probability of the state $ |\\Psi_0\\rangle $ (e.g., in **MLAE** or **IQAE**), and as such, they only provide positive values as outputs. Consequently, if the integral to be computed is negative, these algorithms will return an incorrect value, leading to an inaccurate result for the integral returned by the `q_solve_integral` function. This issue arises regardless of the encoding procedure used.\n", "\n", - "In general, the **AE** algorithms compute the probability of the state $|\\Psi_{0}\\rangle$ (**MLAE** or **IQAE**) so only positive values will be provided as outputs. So if the integral to compute is negative then these algorithms will return an incorrect value and the integral returned by the *q_solve_integral* will be wrong too (this is independent of the encoding procedure used)!!! We can see this in the following cells where a negative Riemann sum will be loaded into the states " + "This limitation can be observed in the following cells, where a negative Riemann sum is encoded into the quantum states." ] }, { diff --git a/misc/notebooks/11_ApplicationTo_Finance_02_ClassicalFinance.ipynb b/misc/notebooks/11_ApplicationTo_Finance_02_ClassicalFinance.ipynb index 7176c4b..e4f5e16 100644 --- a/misc/notebooks/11_ApplicationTo_Finance_02_ClassicalFinance.ipynb +++ b/misc/notebooks/11_ApplicationTo_Finance_02_ClassicalFinance.ipynb @@ -2,18 +2,18 @@ "cells": [ { "cell_type": "markdown", - "id": "c3c573ed", + "id": "b125e486", "metadata": {}, "source": [ - "# Application of amplitude estimation to Finances: Classical Finance\n", + "# Application of Amplitude Estimation to Finance: Classical Finance\n", "\n", - "In this notebook, different concepts of *fair* price estimation of options will be provided. Additionally, the following modules will be reviewed:\n", + "In this notebook, we will explore various concepts related to the estimation of *fair* option prices. Additionally, the following modules will be reviewed:\n", "\n", - "* *classical_finance*: in this module several functions for computing payoffs for different financial derivatives and probability distributions are implemented.\n", - "* *probability_class*: in this module the **DensityProbability** python class is implemented. This class allows to create of probability distributions needed for computing the price of derivatives options.\n", - "* *payoff_class*: this module implements the **PayOff** python class that computes the payoff for different derivatives options.\n", + "- **classical_finance**: This module implements several functions for computing payoffs for different financial derivatives and probability distributions.\n", + "- **probability_class**: This module includes the implementation of the **DensityProbability** Python class, which allows for the creation of probability distributions required for pricing derivative options.\n", + "- **payoff_class**: This module implements the **PayOff** Python class, which computes the payoff for various types of derivative options.\n", "\n", - "All these modules are inside the package *finance* of the *QQuantLib (**QQuantLib/finance**).\n" + "All these modules are part of the *finance* package within *QQuantLib* (**QQuantLib/finance**)." ] }, { @@ -51,110 +51,118 @@ }, { "cell_type": "markdown", - "id": "ffa02964", + "id": "d5032d7d", "metadata": {}, "source": [ - "## 1. The problem\n", + "## 1. The Problem\n", "\n", - "We have a **financial asset** whose value at a time $t$ is $S(t)$. The **volatility** of the asset will be $\\sigma$. The **risk-free rate** will be $r$. Under these considerations, we are going to create a **derivative contract** based on the evolution of the underlying with a definite duration time, **maturity**, $T$. This product will be characterized by its **return** that will be a function that will depend of the price of the underlying at $T$: $f=f(S_T)$, where $S_T=S(t=T)$. The question is: **What is the 'fair' price of our derivative contract at a time t when underlying is S(t): $V_f(S(t), t)$?**.\n", + "We have a **financial asset** whose value at time $ t $ is $ S(t) $. The **volatility** of the asset is $ \\sigma $, and the **risk-free rate** is $ r $. Under these assumptions, we will create a **derivative contract** based on the evolution of the underlying asset with a fixed duration, known as the **maturity**, $ T $. This product will be characterized by its **return**, which is a function dependent on the price of the underlying at $ T $: $ f = f(S_T) $, where $ S_T = S(t=T) $. \n", "\n", - "So we have:\n", + "The key question is: **What is the 'fair' price of our derivative contract at time $ t $ when the underlying is $ S(t) $: $ V_f(S(t), t) $?**\n", "\n", - "* $S = S(t)$ price of the underlying at $t$.\n", - "* $\\sigma$ volatility of the underlying.\n", - "* *Risk free rate*: $r$\n", - "* Return of the financial product: $f=f(S_T)$.\n", + "Thus, we have:\n", + "- $ S = S(t) $: Price of the underlying at $ t $.\n", + "- $ \\sigma $: Volatility of the underlying.\n", + "- **Risk-free rate**: $ r $.\n", + "- Return of the financial product: $ f = f(S_T) $.\n", "\n", - "And we want:\n", - "\n", - "* $V_f(S(t), t)$\n" + "And we aim to determine:\n", + "- $ V_f(S(t), t) $: The fair price of the derivative contract." ] }, { "cell_type": "markdown", - "id": "b93ca766", + "id": "9c5f384a", "metadata": {}, "source": [ "## 2. Black-Scholes Model\n", "\n", - "One model for obtaining this $V_f(S(t), t)$ is the **Black-Scholes** which relies in the following assumptions:\n", - "\n", - "1. The underlying $S(t)$ follows a log normal random walk.\n", - "2. The risk-free interest rate $r$ is a known function of time $t$.\n", - "3. There are no dividends on the underlying $S$\n", - "4. Dynamic **delta Hedging** (risk elimination strategy)\n", - "5. No arbitrage opportunities.\n", - "\n", - "Under these conditions **Black-Scholes** models the evolution of the asset $S$ using following **Stochastic Differential Equation**:\n", - "\n", - "$$dS = \\mu S dt + \\sigma S dW$$\n", - "\n", - "Where $dW$ represents a Wiener process:\n", + "One model for obtaining $ V_f(S(t), t) $ is the **Black-Scholes** model, which relies on the following assumptions:\n", "\n", - "$$dW \\sim \\mathcal{N}(0, \\delta t) \\; \\mathbf{E}[dW] = 0 \\; \\mathbf{E}[dW^2] = dt$$\n", + "1. The underlying $ S(t) $ follows a log-normal random walk.\n", + "2. The risk-free interest rate $ r $ is a known function of time $ t $.\n", + "3. There are no dividends on the underlying $ S $.\n", + "4. Dynamic **delta hedging** (a risk elimination strategy) is applied.\n", + "5. No arbitrage opportunities exist.\n", "\n", + "Under these conditions, the **Black-Scholes** model describes the evolution of the asset $ S $ using the following **Stochastic Differential Equation (SDE)**:\n", + "$$\n", + "dS = \\mu S dt + \\sigma S dW\n", + "$$\n", + "where $ dW $ represents a Wiener process:\n", + "$$\n", + "dW \\sim \\mathcal{N}(0, \\delta t), \\quad \\mathbf{E}[dW] = 0, \\quad \\mathbf{E}[dW^2] = dt.\n", + "$$\n", "\n", - "For getting a relationship between the underlying $S$ and the price of the financial product $V$ two different approaches can be followed:\n", + "To establish a relationship between the underlying $ S $ and the price of the financial product $ V $, two approaches can be followed:\n", "\n", - "1. Black-Scholes partial differential equation:\n", + "1. **Black-Scholes Partial Differential Equation (PDE):**\n", + " $$\n", + " \\frac{\\partial V}{\\partial t} + \\frac{1}{2} \\sigma^2 S^2 \\frac{\\partial^2 V}{\\partial S^2} + r S \\frac{\\partial V}{\\partial S} - rV = 0\n", + " $$\n", "\n", - "$$\\frac{\\partial V}{\\partial t}+\\frac{1}{2}\\sigma^2S^2\\frac{\\partial^2 V}{\\partial^2 S} + rS\\frac{\\partial V}{ \\partial S} - rV = 0$$\n", - "\n", - "2. Expectation computation of approach (based on the Girsanov theorem):\n", - "\n", - "$$V(t, S(t)) = e^{r(T-t)} \\mathbb{E}[{f(S_T/\\mathcal{F}_t)}]$$ \n", - "\n", - "where $\\mathcal{F}_t$ is the market information until the time $t$" + "2. **Expectation Computation Approach** (based on the Girsanov theorem):\n", + " $$\n", + " V(t, S(t)) = e^{-r(T-t)} \\mathbb{E}[f(S_T \\mid \\mathcal{F}_t)]\n", + " $$\n", + " where $ \\mathcal{F}_t $ represents the market information available up to time $ t $." ] }, { "cell_type": "markdown", - "id": "2c0f15c9", + "id": "e4b51a19", "metadata": {}, "source": [ + "## 3. Price of Derivative Contract\n", "\n", - "## 3. Price of derivative contract\n", - "\n", - "So far we have:\n", + "So far, we have:\n", + "1. A financial asset with value $ S(t) $, volatility $ \\sigma $, and risk-free rate $ r $.\n", + "2. A **derivative contract** based on the financial asset $ S $ with maturity $ T $ and a return function $ f = f(S(t), T) $.\n", "\n", - "1. Financial asset with value $S(t)$, volatility $\\sigma$, and risk free rate $r$\n", - "2. **Derivative contract** over the financial asset $S$ with a maturity $T$ and a return of $f=f(S(t), T)$\n", + "In the *expectation approach*, the desired *fair* price of the option is given by:\n", + "$$\n", + "V(t, S(t)) = e^{-r(T-t)} \\mathbb{E}[f(S_T \\mid \\mathcal{F}_t)]\n", + "$$\n", "\n", - "In the *expectation approach* the desired *fair* price of the option will be:\n", + "In general, to solve this problem, numerous **Monte Carlo** simulations of the evolution of the asset $ S $ between time $ t $ and the *maturity* $ T $ are performed under a specific financial model (such as **Black-Scholes** or **Heston**). The primary goal is to obtain the probability distribution of the asset at the maturity time:\n", + "$$\n", + "p(S_T; t, S(t))\n", + "$$\n", "\n", - "$$V(t, S(t)) = e^{r(T-t)} \\mathbb{E}[{f(S_T/\\mathcal{F}_t)}]$$ \n", - "\n", - "In general for solving this problem a lot of **Monte-Carlo** simulations of the evolution of the asset $S$ between time $t$ and *maturity* $T$ are conducted under one financial model (like **Black-Scholes** or **Heston**). The main goal is to obtain a probability distribution of the asset at maturity time:\n", - "\n", - "$$p(S_T; t, S(t))$$\n", - "\n", - "If this function is obtained then we can plug into the expectation getting the usual way for getting *fair* option prices:\n", - "\n", - "$$V(t, S(t)) = e^{r(T-t)} \\mathbb{E}[{f(S_T/\\mathcal{F}_t)}] = \\int_0^\\infty p(S_T; t, S(t)) f(S_T) dS_T$$ \n" + "Once this function is obtained, it can be plugged into the expectation formula, yielding the standard method for calculating the *fair* price of options:\n", + "$$\n", + "V(t, S(t)) = e^{-r(T-t)} \\mathbb{E}[f(S_T \\mid \\mathcal{F}_t)] = \\int_0^\\infty p(S_T; t, S(t)) f(S_T) dS_T\n", + "$$" ] }, { "cell_type": "markdown", - "id": "7bf5d870", + "id": "e5907bef", "metadata": {}, "source": [ - "## 4. Black-Scholes probability distribution\n", + "## 4. Black-Scholes Probability Distribution\n", "\n", - "In the case of the pure **Black-Scholes** model this probability distribution have a analytical form (the **Black-Scholes** probability distribution):\n", + "In the case of the pure **Black-Scholes** model, this probability distribution has an analytical form (the **Black-Scholes** probability distribution):\n", "\n", - "$$P_{BS} (S_T; t, S(t)) = \\frac{1}{S_T\\sigma\\sqrt{2\\pi(T-t)}}exp\\big({-\\frac{(\\log S_T -\\mu)^2}{2\\sigma^2(T-t)}}\\big)$$\n", + "$$\n", + "P_{BS}(S_T; t, S(t)) = \\frac{1}{S_T \\sigma \\sqrt{2\\pi(T-t)}} \\exp\\left(-\\frac{(\\log S_T - \\mu)^2}{2\\sigma^2(T-t)}\\right)\n", + "$$\n", "\n", - "where \n", + "where\n", "\n", - "$$\\mu = (r-\\frac{1}{2}\\sigma^2)(T-t)+\\log S(t)$$\n", + "$$\n", + "\\mu = \\left(r - \\frac{1}{2}\\sigma^2\\right)(T-t) + \\log S(t)\n", + "$$\n", "\n", - "So the price of the financial product for $t$ and $S(t)$ will be:\n", + "Thus, the price of the financial product at time $ t $ and $ S(t) $ is given by:\n", "\n", - "$$V(t, S(t))= e^{r(T-t)} \\mathbb{E}_{P_{BS}}[f] = e^{r(T-t)} \\int_0^\\infty P_{BS} (S_T; t, S(t)) f(S_T)dS_T$$\n", + "$$\n", + "V(t, S(t)) = e^{-r(T-t)} \\mathbb{E}_{P_{BS}}[f] = e^{-r(T-t)} \\int_0^\\infty P_{BS}(S_T; t, S(t)) f(S_T) dS_T\n", + "$$\n", "\n", - "The *return* of the financial product at the maturity time is the **Payoff** of the product: $f(S_T) = Payoff(S_T)$.\n", + "The *return* of the financial product at the maturity time is the **Payoff** of the product: $ f(S_T) = \\text{Payoff}(S_T) $.\n", "\n", - "This **Black-Scholes** probability density was implemented into the *bs_probability* function from the *classical_finance* module (**QQuantLib/finance/classical_finance.py**)" + "This **Black-Scholes** probability density function is implemented in the `bs_probability` function from the `classical_finance` module (**QQuantLib/finance/classical_finance.py**)." ] }, { @@ -218,27 +226,27 @@ }, { "cell_type": "markdown", - "id": "e1edc337", + "id": "6640c71f", "metadata": {}, "source": [ - "### 4.1 DensityProbability class\n", + "### 4.1 DensityProbability Class\n", "\n", - "The **DensityProbability** class is a Python one implemented for obtaining, in an easy way, **Black-Scholes** probability distribution (the main idea is in future add different probability distributions used in finances). It was implemented under the *probability_class* module from the *finance* package.\n", + "The `DensityProbability` class is a Python implementation designed to easily obtain the **Black-Scholes** probability distribution (with the intention of adding other financial probability distributions in the future). It is implemented in the *probability_class* module within the **finance** package.\n", "\n", "The only mandatory input for creating this class is:\n", "\n", - "* *probability_type*: string with the type of probability density to load. (*Black-Scholes*) (it is expected to add more probability distributions in the future).\n", + "- `probability_type`: A string specifying the type of probability density to load (e.g., \"Black-Scholes\"). More probability distributions are expected to be added in the future.\n", "\n", - "The different parameters for the probability density should be provided as a dictionary. The parameters should be defined according to the definition of the probability density function desired. The most common ones are:\n", + "The parameters required for the probability density should be provided as a dictionary. These parameters must align with the definition of the desired probability density function. The most common parameters include:\n", "\n", - "* *s_0*: Initial value of the asset ($S(t)$).\n", - "* *risk_free_rate*: the risk-free rate.\n", - "* *volatility*: volatility of the asset\n", - "* *maturity*: this will be the time when we want the probability distribution over the asset value.\n", + "- `s_0`: Initial value of the asset ($ S(t) $).\n", + "- `risk_free_rate`: The risk-free rate.\n", + "- `volatility`: Volatility of the asset.\n", + "- `maturity`: The time at which the probability distribution over the asset's value is desired.\n", "\n", - "Additionally, the main attribute of the class will be the **probability**. This property is the desired probability density where the parameters provided to the class are fixed.\n", + "The main attribute of the class is `probability`, which represents the desired probability density function with the provided parameters fixed.\n", "\n", - "In general, the main use of this class is to define the behaviour of an asset and get the probability distribution of the asset in a *maturity* time." + "In general, the primary use of this class is to define the behavior of an asset and obtain its probability distribution at a specified *maturity* time." ] }, { @@ -339,36 +347,41 @@ }, { "cell_type": "markdown", - "id": "c1bc108c", + "id": "e3ddbde2", "metadata": {}, "source": [ "## 5. Options\n", "\n", - "As explained before the price of the derivative financial product for $t$ and $S(t)$ will be:\n", + "As explained earlier, the price of a derivative financial product at time $ t $ and $ S(t) $ is given by:\n", + "\n", + "$$\n", + "V(t, S(t)) = e^{-r(T-t)} \\mathbb{E}_{P_{BS}}[f] = e^{-r(T-t)} \\int_0^\\infty P_{BS}(S_T; t, S(t)) f(S_T) dS_T \\tag{1}\n", + "$$\n", + "\n", + "One of the most popular types of derivative products is **options**, which allow the holder to buy (**call option**) or sell (**put option**) the underlying asset at maturity $ T $ for a specified price called the **strike** ($ K $). From a mathematical perspective, the most important aspect of an option is its return, which is typically a *non-linear* function of the underlying asset and the **strike**.\n", + "\n", + "In the *classical_finance* module, returns for several types of **options** have been implemented. For a clear and consistent management of different return options, a Python class called `PayOff` has been implemented in the *payoff_class* module of the *finance* package (**QQuantLib/finance/payoff_class.py**).\n", "\n", - "$$V(t, S(t))= e^{r(T-t)} \\mathbb{E}_{P_{BS}}[f] = e^{r(T-t)} \\int_0^\\infty P_{BS} (S_T; t, S(t)) f(S_T)dS_T \\tag{1}$$\n", + "To instantiate the class, an input Python dictionary should be provided. The main keys include:\n", "\n", - "One of the most popular derivative products is the options that allow the holder to buy (**call option**) or sell (**put option**) the underlying at maturity $T$ by a given price called **strike** ($K$). From a mathematical point of view the most important entity of an option is its return usually it is a *non-linear* function of the underlying and the **strike**.\n", + "- `pay_off_type*`: This specifies the type of option desired. Currently, the following options are available:\n", + " - `European_Call_Option`\n", + " - `European_Put_Option`\n", + " - `Digital_Call_Option`\n", + " - `Digital_Put_Option`\n", + " - `Futures`\n", "\n", - "Into the *classical_finance* module returns for several **options** were implemented. For a transparent management of the different return options, a Python class called **PayOff** was implemented in the *payoff_class* module of the *finance* package (**QQuantLib\\finance\\payoff_class.py**).\n", + "- `strike`: The strike price of the derivative product.\n", "\n", - "To instantiate the class an input Python dictionary should be provided. The main keys will be:\n", + "- `coupon`: This is relevant for *Digital_Call_Option* and *Digital_Put_Option*.\n", "\n", - "* *pay_off_type*: this will be the type of option we want. At this moment following options are available:\n", - " * European_Call_Option\n", - " * European_Put_Option\n", - " * Digital_Call_Option\n", - " * Digital_Put_Option\n", - " * Futures\n", - "* *strike*: strike of the derivative product.\n", - "* *coupon*: this for *Digital_Call_Option* and *Digital_Put_Option*.\n", + "The payoffs are obtained from the **QQuantLib/utils/classical_finance** module. Therefore, the keys of the input dictionary should match the keys required for configuring the payoffs in that module.\n", "\n", - "The payoffs are obtained from the **QQuantLib/utils/classical_finance** module. So the keys of the input dictionary should be the same keys needed for configuring the payoffs in the before module.\n", + "The class creates the following two properties:\n", "\n", - "The class creates the following 2 properties:\n", + "- `pay_off`: A function representing the desired payoff with the appropriate configuration provided by the input dictionary.\n", "\n", - "* **pay_off**: function with the desired payoff and the proper pay-off configuration given by the input dictionary\n", - "* **pay_off_bs**: gives the exact price of the payoff under the **Black-Scholes** model (the parameter configuration of the **Black-Scholes** should be provided in the input dictionary!!)\n" + "- `pay_off_bs`: Provides the exact price of the payoff under the **Black-Scholes** model (the parameter configuration for the **Black-Scholes** model must be included in the input dictionary!)." ] }, { @@ -813,15 +826,15 @@ "\n", "For computing the integral of this expected value the **Amplitude Estimation** algorithms, developed on the **QQuantLib**, can be used. \n", "\n", - "The **AE** and the **Encoding** classes in addition to the **DensityProbability** and the **PayOff** classes (and the *quantum_integration* module) allow the user to build fast implementations for solving option price estimation problems using quantum **AE** techniques!!" + "The `AE` and the `Encoding` classes in addition to the `DensityProbability` and the `PayOff` classes (and the *quantum_integration* module) allow the user to build fast implementations for solving option price estimation problems using quantum **AE** techniques!!" ] } ], "metadata": { "kernelspec": { - "display_name": "myqlm_tes", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "myqlm_tes" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -833,7 +846,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.9.9" } }, "nbformat": 4, diff --git a/misc/notebooks/12_ApplicationTo_Finance_03_AEPriceEstimation.ipynb b/misc/notebooks/12_ApplicationTo_Finance_03_AEPriceEstimation.ipynb index 4e75263..81aa349 100644 --- a/misc/notebooks/12_ApplicationTo_Finance_03_AEPriceEstimation.ipynb +++ b/misc/notebooks/12_ApplicationTo_Finance_03_AEPriceEstimation.ipynb @@ -2,24 +2,28 @@ "cells": [ { "cell_type": "markdown", - "id": "792a8e12", + "id": "5e2e6127", "metadata": {}, "source": [ "# AE Price Estimation\n", "\n", - "This notebook presents the *ae_estimation_price* function inside the module *ae_price_estimation* of the package *finance* from *QQuantLib* (**QQuantLib/finance/ae_price_estimation.py**).\n", + "This notebook introduces the `ae_estimation_price` function, which is part of the *ae_price_estimation* module in the *finance* package of *QQuantLib* (**QQuantLib/finance/ae_price_estimation**).\n", "\n", - "This function allows the user to solve an input **option price estimation** problem using **amplitude estimation** algorithms. \n", + "This function enables users to solve an input **option price estimation** problem using **amplitude estimation** algorithms. \n", "\n", - "The *ae_estimation_price* functions use the **DensityProbability** and the **PayOff** python classes (explained in notebook **11_ApplicationTo_Finance_02_ClassicalFinance**) for creating the correspondent arrays $p(x)$ and $f(x)$ defined in a domain $x \\in [x_0,x_f]$. Then the functions try to compute the expected value\n", + "The `ae_estimation_price` function utilizes the `DensityProbability` and `PayOff` Python classes (explained in notebook **11_ApplicationTo_Finance_02_ClassicalFinance**) to create the corresponding arrays $ p(x) $ and $ f(x) $ defined over a domain $ x \\in [x_0, x_f] $. Subsequently, the function attempts to compute the expected value:\n", "\n", - "$$\\mathbb{E}[f]=\\int_{x_0}^{x_f}p(x)f(x)dx$$\n", + "$$\n", + "\\mathbb{E}[f] = \\int_{x_0}^{x_f} p(x) f(x) dx\n", + "$$\n", "\n", - "using the **q_solve_integral** function explained in notebook **10_ApplicationTo_Finance_01_IntegralComputing**. \n", + "using the `q_solve_integral` function, as explained in notebook **10_ApplicationTo_Finance_01_IntegralComputing**.\n", "\n", - "Once the **q_solve_integral** function computes the desired expectation the *ae_estimation_price* functions compute the *option* prices estimation using:\n", + "Once the `q_solve_integral` function calculates the desired expectation, the `ae_estimation_price` function estimates the option price using:\n", "\n", - "$$V(t, S(t)) = e^{r(T-t)} \\int_{x_0}^{x_f}p(x)f(x)dx$$ \n" + "$$\n", + "V(t, S(t)) = e^{-r(T-t)} \\int_{x_0}^{x_f} p(x) f(x) dx\n", + "$$" ] }, { @@ -66,44 +70,41 @@ }, { "cell_type": "markdown", - "id": "b5f766aa", + "id": "f986e055", "metadata": {}, "source": [ - "## 1. ae_price_estimation input\n", + "## 1. ae_price_estimation Input\n", "\n", - "The input of the *ae_price_estimation* will be a complete Python dictionary. This Python dictionary needs to have all the mandatory keys for defining an option price estimation problem and solving it with the properly configured **AE** algorithm. \n", + "The input for `ae_price_estimation` is a complete Python dictionary that must include all the necessary keys to define an option price estimation problem and solve it using a properly configured **Amplitude Estimation (AE)** algorithm.\n", "\n", - "For pedagogical reasons, we can split this input dictionary in the following sub-dictionaries:\n", + "For clarity, this input dictionary can be divided into the following sub-dictionaries:\n", "\n", - "1. Domain configuration.\n", - "2. Probability Density configuration \n", - "3. PayOff configuration\n", - "4. AE configuration\n", - "5. Other configuration\n" - ] - }, - { - "cell_type": "markdown", - "id": "15e006c8", - "metadata": {}, - "source": [ - "### 1.1 Domain configuration:\n", + "1. **Domain Configuration**\n", + "2. **Probability Density Configuration**\n", + "3. **PayOff Configuration**\n", + "4. **Encoding Configration**\n", + "5. **AE Configuration**\n", + "6. **Other Configuration**\n", "\n", - "This will be the dictionary for configuring the domain and the discretization of our problem. \n", + "### 1.1 Domain Configuration\n", "\n", - "So in our case, the domain of the problem will be:\n", + "This dictionary configures the domain and discretization of the problem. In this case, the domain of the problem is defined as:\n", "\n", - "$$x \\in [x_0,x_f]$$\n", + "$$\n", + "x \\in [x_0, x_f]\n", + "$$\n", "\n", - "and for its discretization:\n", + "For discretization, the domain is split into:\n", "\n", - "$$[x_0,x_f]=\\{[x_0, x_1]U [x_1, x_2]U ...U [x_{2^n-1}, x_f]\\}$$ \n", + "$$\n", + "[x_0, x_f] = \\{[x_0, x_1] \\cup [x_1, x_2] \\cup \\dots \\cup [x_{2^n-1}, x_f]\\}\n", + "$$\n", "\n", - "The keys for this *domain* dictionary will be:\n", + "The keys for this *domain* dictionary are:\n", "\n", - "* *x0*: the start point of the domain\n", - "* *xf*: the end point of the domain\n", - "* *n_qbits*: for splitting the domain in $2^{n\\_qbits}$ intervals" + "- `x0`: The starting point of the domain.\n", + "- `xf`: The ending point of the domain.\n", + "- `n_qbits`: The number of qubits used to divide the domain into $ 2^{n\\_qbits} $ intervals." ] }, { @@ -123,18 +124,18 @@ }, { "cell_type": "markdown", - "id": "7e071a31", + "id": "e436708c", "metadata": {}, "source": [ - "### 1.2 Probability Density Configuration \n", + "### 1.2 Probability Density Configuration\n", "\n", - "This dictionary will have the information related to the underlying asset of the option, the information of the market and the time when the probability density will be evaluated (in general this will be the *maturity* of the option). This dictionary will need all the mandatory keys for configuring properly the *DensityProbability* (from **QQuantLib.finance.probability_class**).\n", + "This dictionary contains information related to the underlying asset of the option, market conditions, and the time at which the probability density will be evaluated (typically the *maturity* of the option). This dictionary requires all the necessary keys to properly configure the *DensityProbability* class (from **QQuantLib.finance.probability_class**).\n", "\n", - "* *probability_type*: string with probability density type we want to use. Only 'Black-Scholes' is available (it is expected to implement more density probability functions in the future...)\n", - "* *s_0*: Initial value of the asset $S$.\n", - "* *risk_free_rate*: the risk-free rate of the market.\n", - "* *maturity*: this is the time for computing the probability distribution of the asset value. When we work with option this time will be the *maturity* of the option.\n", - "* *volatility*: volatility of the asset up to the initial time.\n" + "- `probability_type`: A string specifying the type of probability density to use. Currently, only 'Black-Scholes' is available (additional probability density functions are expected to be implemented in the future).\n", + "- `s_0`: The initial value of the asset $ S $.\n", + "- `risk_free_rate`: The risk-free rate of the market.\n", + "- `maturity`: The time at which the probability distribution of the asset's value is computed. When working with options, this corresponds to the *maturity* of the option.\n", + "- `volatility`: The volatility of the asset up to the initial time." ] }, { @@ -156,21 +157,23 @@ }, { "cell_type": "markdown", - "id": "9ed7e64b", + "id": "cd29e8ee", "metadata": {}, "source": [ - "### 1.3 PayOff configuration \n", + "### 1.3 PayOff Configuration\n", "\n", - "This dictionary will have the mandatory keys for configuring the *PayOff* (from **QQuantLib.finance.payoff_class**). In general will be related to the type of the option, the strike etc...\n", + "This dictionary includes the mandatory keys for configuring the *PayOff* class (from **QQuantLib.finance.payoff_class**). It generally pertains to the type of option, the strike price, and other relevant parameters.\n", "\n", - "* *pay_off_type*: this will be the type of option we want. At this moment following options are available:\n", - " * European_Call_Option\n", - " * European_Put_Option\n", - " * Digital_Call_Option\n", - " * Digital_Put_Option\n", - " * Futures\n", - "* *strike*: strike of the derivative product.\n", - "* *coupon*: this for *Digital_Call_Option* and *Digital_Put_Option*." + "- `pay_off_type`: Specifies the type of option desired. The following options are currently available:\n", + " - European_Call_Option\n", + " - European_Put_Option\n", + " - Digital_Call_Option\n", + " - Digital_Put_Option\n", + " - Futures\n", + "\n", + "- `strike`: The strike price of the derivative product.\n", + "\n", + "- `coupon`: Relevant for *Digital_Call_Option* and *Digital_Put_Option*." ] }, { @@ -190,15 +193,15 @@ }, { "cell_type": "markdown", - "id": "4f856ab2", + "id": "d1b452fa", "metadata": {}, "source": [ - "### 1.4 Encoding configuration\n", + "### 1.4 Encoding Configuration\n", "\n", - "This dictionary will have all the mandatory keys to configure the encoding of the $p(x)$ and $f(x)$ arrays into the quantum circuit. These keys will be used for configuring properly the *Encoding* class (from **QQuantLib.DL.encoding_protocols**) used inside the *q_solve_integral* function (from **QQuantLib.finance.quantum_integration**):\n", + "This dictionary contains all the necessary keys to configure the encoding of the $ p(x) $ and $ f(x) $ arrays into the quantum circuit. These keys are used to properly set up the *Encoding* class (from **QQuantLib.DL.encoding_protocols**) utilized within the `q_solve_integral` function (from **QQuantLib.finance.quantum_integration**).\n", "\n", - "* *encoding*: type of encoding used. It can be 0, 1 or 2.\n", - "* *multiplexor*: for using multiplexor constructions for controlled by state gates.\n" + "- `encoding`: The type of encoding used. It can take values 0, 1, or 2.\n", + "- `multiplexor`: A flag for using multiplexor constructions for gates controlled by state." ] }, { @@ -217,26 +220,14 @@ }, { "cell_type": "markdown", - "id": "02e9e8fa", + "id": "3fb472b9", "metadata": {}, "source": [ - "### 1.5 AE configuration\n", + "### 1.5 AE Configuration\n", "\n", - "This dictionary will have all the mandatory keys for configuring the *AE* technique used for solving the integral. These keys will be used for configuring properly the *q_solve_integral* function (from **QQuantLib.finance.quantum_integration**) which needs the *AE* class (from **QQuantLib.AE.ae_class**):\n", + "This dictionary contains all the necessary keys for configuring the *Amplitude Estimation (AE)* technique used to solve the integral. These keys are used to properly set up the *q_solve_integral* function (from **QQuantLib.finance.quantum_integration**), which relies on the *AE* class (from **QQuantLib.AE.ae_class**).\n", "\n", - "* *ae_type*: **AE** algorithm used. It can be: *MLAE, IQAE, RQAE, CQPEAE, IQPEAE*.\n", - "* *schedule*: schedule used for the *MLAE*\n", - "* *delta*: parameter for *MLAE*\n", - "* *ns*: parameter for *MLAE*\n", - "* *auxiliar_qbits_number*: number of auxiliary qubits used in *CQPEAE*\n", - "* *cbits_number*: number of classical bits used in *IQPEAE*\n", - "* *epsilon*: desired error for the estimation. Used in *IQAE, RQAE*\n", - "* *alpha*: confidence for *IQAE* method.\n", - "* *gamma*: confidence for *RQAE* method.\n", - "* *q*: amplification of the Grover applications used in *RQAE*\n", - "* *shots*: number of measurements performed in the quantum circuit: used in *IQAE, CQPEAE, IQPEAE*.\n", - "* *mcz_qlm*: if **True** multiplexor version of the multi-controlled Z gate will be used. If **False** QLM default implementation will be used.\n", - "* *erqae_schedule*: dictionary with the configuration of the schedules for eRQAE" + "The keys for the **AE** configuration will depend on **AE** algorithm selected. See the different related Notebooks for their names." ] }, { @@ -294,10 +285,10 @@ "\n", "This dictionary will be related to other configuration keys like:\n", "\n", - "* *qpu*: type of **QPU** used for simulating the **AE** algorithms\n", - "* *save*: boolean for saving or not the results\n", - "* *file_name*: string with the complete path for saving results\n", - "* *number_of_tests*: for doing several repetitions of the solution of a complete give ae problem" + "- `qpu: type of **QPU** used for simulating the **AE** algorithms\n", + "- `save`: boolean for saving or not the results\n", + "- `file_name`: string with the complete path for saving results\n", + "- `number_of_tests`: for doing several repetitions of the solution of a complete give ae problem" ] }, { @@ -343,28 +334,37 @@ }, { "cell_type": "markdown", - "id": "2d2c5331", + "id": "0b30dc97", "metadata": {}, "source": [ - "## 2. ae_price_estimation workflow\n", + "## 2. ae_price_estimation Workflow\n", + "\n", + "The `ae_price_estimation` function executes the following workflow:\n", + "\n", + "1. **Domain Creation**: Creates the discretized domain using the keys from the *domain_configuration* section of the input dictionary.\n", + "\n", + "2. **Probability Density Configuration**: Uses the *probability_configuration* section of the input dictionary to create a properly configured **DensityProbability** object.\n", "\n", - "The *ae_price_estimation* function will execute the following workflow:\n", + "3. **Probability Distribution Array**: Utilizes the `DensityProbability` object and the domain discretization to generate the NumPy array $ p(x) $ for the probability distribution.\n", "\n", - "1. Creates the discretized domain using the keys from the *domain_configuration* of the input dictionary.\n", - "2. Using the *probability_configuration* of the input dictionary creates a properly configured **DensityProbability** object. \n", - "3. Uses the **DensityProbability** object and the domain discretization for creating the numpy array $p(x)$ for the probability distribution.\n", - "4. Using the *payoff_configuration* of the input dictionary creates a properly configured **PayOff** object. \n", - "5. Uses the **PayOff** object and the domain discretization for creating the numpy array $f(x)$ for the payoff of the selected derivative option.\n", - "6. Normalisation of the $p(x)$ and $f(x)$ arrays.\n", - "7. Adding the normalised numpy arrays to the input dictionary.\n", - "8. Execute the *q_solve_integral*. The input of this function will be the Python dictionary of step 7:\n", - " * 8.1 The *q_solve_integral* function uses the *encoding_configuration* for configuring properly the *Encoding* class.\n", - " * 8.2 The *q_solve_integral* function uses the *ae_configuration* for configuring properly the *AE* class.\n", - " * 8.3 The *q_solve_integral* function executes the *ae* algorithm for computing the estimation of the amplitude and the desired integral. \n", - "9. The *q_solve_integral* will return the desired expected value computed using **AE** integral techniques. \n", - "10. Post-process of the results for providing the price estimation solution.\n", + "4. **Payoff Configuration**: Uses the *payoff_configuration* section of the input dictionary to create a properly configured `PayOff`object.\n", "\n", - "The output of the *ae_price_estimation* function will be a pandas DataFrame with all the configuration provided information, and the results.\n" + "5. **Payoff Array**: Utilizes the `PayOff` object and the domain discretization to generate the NumPy array $ f(x) $ for the payoff of the selected derivative option.\n", + "\n", + "6. **Normalization**: Normalizes the $ p(x) $ and $ f(x) $ arrays.\n", + "\n", + "7. **Input Dictionary Update**: Adds the normalized NumPy arrays to the input dictionary.\n", + "\n", + "8. **Quantum Integral Solution**:\n", + " - **Encoding Configuration**: The `q_solve_integral` function uses the *encoding_configuration* section to configure the `Encoding` class appropriately.\n", + " - **AE Configuration**: The `q_solve_integral` function uses the *ae_configuration* section to configure the `AE` class appropriately.\n", + " - **Amplitude Estimation Execution**: The `q_solve_integral` function executes the *AE* algorithm to compute the estimation of the amplitude and the desired integral.\n", + "\n", + "9. **Expected Value Calculation**: The `q_solve_integral` function returns the desired expected value computed using **AE** integral techniques.\n", + "\n", + "10. **Post-Processing**: Processes the results to provide the price estimation solution.\n", + "\n", + "The output of the `ae_price_estimation` function is a pandas DataFrame containing all the configuration information provided, along with the results.\n" ] }, { @@ -491,11 +491,11 @@ "source": [ "The columns related to the **AE** integration are:\n", "\n", - "* *riemman_expectation*: this is the expectation computed as a Riemann, this is the scalar product of the $p(x)$ and the $f(x)$ arrays. \n", - "* *ae_expectation*: expectation computed using **AE** integration techniques.\n", - "* *ae_l_expectation*: lower value of the expectation computed using **AE** integration techniques (for **IQAE** and **RQAE**)\n", - "* *ae_u_expectation*: upper value of the expectation computed using **AE** integration techniques (for **IQAE** and **RQAE**)\n", - "* *absolute_error*: absolute difference between the **AE** integral estimation and the desired integral (*riemann_expectation*)" + "- `riemman_expectation`: this is the expectation computed as a Riemann, this is the scalar product of the $p(x)$ and the $f(x)$ arrays. \n", + "- `ae_expectation`: expectation computed using **AE** integration techniques.\n", + "- `ae_l_expectation`: lower value of the expectation computed using **AE** integration techniques (for **IQAE** and **RQAE**)\n", + "- `ae_u_expectation`: upper value of the expectation computed using **AE** integration techniques (for **IQAE** and **RQAE**)\n", + "- `absolute_error`: absolute difference between the **AE** integral estimation and the desired integral (*riemann_expectation*)" ] }, { @@ -527,11 +527,11 @@ "source": [ "The columns related with the finance quantities allways beginf by *finance* and are:\n", "\n", - "* *finance_exact_price*: the price of the option computed using an analytical Black-Scholes model\n", - "* *finance_riemann_price*: the price of the option when the discount of the risk free ratio is taken into account when using the Riemman aproximation: $$riemann\\_expectation * \\; e ^{ r *T}$$ with $r$, the *risk free ratio* and $T$ the *maturity*\n", - "* *finance_price_estimation*: the price estimation of the option using **AE**. This is with the discount of the risk free ratio: $$ae\\_expectation * \\; e ^{ r *T}$$ with $r$, the *risk free ratio* and $T$ the *maturity*\n", - "* *finance_error_riemann* : error between the estimated price option and the Rieman obtained price: $$| finance\\_price\\_estimation -finance\\_riemann\\_price|$$\n", - "* *finance_error_exact*: error between the estimated price option and the exact Rieman obtained price: $$| finance\\_price\\_estimation - finance\\_exact\\_price |$$" + "- `finance_exact_price`: the price of the option computed using an analytical Black-Scholes model\n", + "- `finance_riemann_price`: the price of the option when the discount of the risk free ratio is taken into account when using the Riemman aproximation: $$riemann\\_expectation * \\; e ^{ r *T}$$ with $r$, the *risk free ratio* and $T$ the *maturity*\n", + "- `finance_price_estimation`: the price estimation of the option using **AE**. This is with the discount of the risk free ratio: $$ae\\_expectation * \\; e ^{ r *T}$$ with $r$, the *risk free ratio* and $T$ the *maturity*\n", + "- `finance_error_riemann` : error between the estimated price option and the Rieman obtained price: $$| finance\\_price\\_estimation -finance\\_riemann\\_price|$$\n", + "- `finance_error_exact`: error between the estimated price option and the exact Rieman obtained price: $$| finance\\_price\\_estimation - finance\\_exact\\_price |$$" ] }, { @@ -569,10 +569,10 @@ "source": [ "Finally, other important information is provided:\n", "\n", - "* *schedule_pdf*: this is the number of Grover-like operator applications and the number of shots for each application (this is valid for **MLAE**, **IQAE** and **RQAE**)\n", - "* *oracle_calls*: total number of calls to the oracle for getting the obtained solution.\n", - "* *max_oracle_depth*: maximum number of Grover-like operator applications for getting the obtained solution.\n", - "* *run_time*: simulation time for getting the obtained solution." + "- `schedule_pdf`: this is the number of Grover-like operator applications and the number of shots for each application (this is valid for **MLAE**, **IQAE** and **RQAE**)\n", + "- `oracle_calls`: total number of calls to the oracle for getting the obtained solution.\n", + "- `max_oracle_depth`: maximum number of Grover-like operator applications for getting the obtained solution.\n", + "- `run_time`: simulation time for getting the obtained solution." ] }, { @@ -1084,38 +1084,31 @@ }, { "cell_type": "markdown", - "id": "83c649fa", + "id": "161fb64d", "metadata": {}, "source": [ - "## 4. Loading PayOffs by parts.\n", - "\n", - "\n", - "As explained here and in other notebooks the main problem for using **AE** estimation for computing integrals (or expected values) arises when we have to integrate functions that are negative-defined in some part of the domain. \n", - "\n", - "Encoding 1 and 2 can circumvent this problem but when the final integral to estimate is negative then all the **AE** algorithms, except **RQAE** ones, fail because they return the probability and not the amplitude.\n", - "\n", - "One way of solving this issue is loading the positive and negative parts of the function separately, executing the **AE** estimation on each two parts and post-processing the obtained results to get the final result. The workflow is the following:\n", - "\n", - "1. Load positive part of $f(x)$ and using the **AE** algorithm to obtain the estimation $a^+$ (and its corresponding semi-difference bounded interval: $\\epsilon_a^+$ if the **AE** algorithm provides it).\n", - "2. Load the negative part of $f(x)$ (in this case we load the absolute value of the function: $\\left|f(x)\\right|$) and use the **AE** algorithm to obtain the estimation $a^-$ (and its corresponding semidifference bounded interval: $\\epsilon_a^-$ if the **AE** algorithm provides it). In this case $a^- >0$.\n", - "3. Post-process the obtained results for getting the final estimation: $a_{final} = a^+ - a^-$\n", - "4. The associated semi-difference bound interval will be the sum of the obtained ones: $\\epsilon_a = \\epsilon_a^+ + \\epsilon_a^-$\n", + "## 4. Loading PayOffs by Parts\n", "\n", - "With this procedure, we can use any **AE** algorithm for estimating the desired integral. \n", + "As explained in this and other notebooks, a key challenge when using **AE** estimation for computing integrals (or expected values) arises when the function to be integrated is negative over some part of the domain. While Encoding 1 and 2 can address this issue, most **AE** algorithms (except **RQAE**) fail if the final integral is negative, as they return the probability rather than the amplitude.\n", "\n", - "Additionally, this procedure can be used with all the encodings: 0 (or square encoding), 1 and 2 (or direct encoding).\n", + "One solution to this problem is to separately load the positive and negative parts of the function, execute the **AE** estimation for each part, and then post-process the results to obtain the final result. The workflow is as follows:\n", "\n", - "The **ae_price_estimation_step_po** from **QQuantLib.finance.ae_price_estimation_step_payoff** module allows the implementation of this procedure with a complete price estimation problem and **AE** algorithm. This function works in the same way as the *ae_price_estimation* function explained in the present notebook.\n", + "1. Load the positive part of $ f(x) $ and use the **AE** algorithm to obtain the estimation $ a^+ $ (and its corresponding semi-difference bounded interval: $ \\epsilon_a^+ $, if provided by the **AE** algorithm).\n", + "2. Load the negative part of $ f(x) $ (in this case, load the absolute value of the function: $ |f(x)| $) and use the **AE** algorithm to obtain the estimation $ a^- $ (and its corresponding semi-difference bounded interval: $ \\epsilon_a^- $, if provided by the **AE** algorithm). Note that $ a^- > 0 $.\n", + "3. Post-process the obtained results to calculate the final estimation: $ a_{final} = a^+ - a^- $.\n", + "4. The associated semi-difference bound interval will be the sum of the obtained intervals: $ \\epsilon_a = \\epsilon_a^+ + \\epsilon_a^- $.\n", "\n", + "With this procedure, any **AE** algorithm can be used to estimate the desired integral. Additionally, this approach is compatible with all encodings: 0 (or square encoding), 1, and 2 (or direct encoding).\n", "\n", - "The **ae_price_estimation_step_po** returns a pandas DataFrame with the same configuration of the returned by the **ae_price_estimation** function but now the negative and positive estimation parts are provided in the following columns:\n", + "The `ae_price_estimation_step_po` function from the **QQuantLib.finance.ae_price_estimation_step_payoff** module implements this procedure for a complete price estimation problem and **AE** algorithm. This function operates similarly to the *ae_price_estimation* function described in this notebook.\n", "\n", - "* ae_positive_part: positive part estimation\n", - "* ae_l_positive_part: lower bound for positive part estimation\n", - "* ae_u_positive_part: upper bound for positive part estimation \n", - "* ae_negative_part: negative part estimation\n", - "* ae_l_negative_part: lower bound for negative part estimation\n", - "* ae_u_negative_part:upper bound for negative part estimation" + "The `ae_price_estimation_step_po` function returns a pandas DataFrame with the same configuration as that returned by the **ae_price_estimation** function, but it includes additional columns for the negative and positive estimation parts:\n", + "- `ae_positive_part`: Estimation of the positive part.\n", + "- `ae_l_positive_part`: Lower bound for the positive part estimation.\n", + "- `ae_u_positive_part`: Upper bound for the positive part estimation.\n", + "- `ae_negative_part`: Estimation of the negative part.\n", + "- `ae_l_negative_part`: Lower bound for the negative part estimation.\n", + "- `ae_u_negative_part`: Upper bound for the negative part estimation." ] }, { diff --git a/misc/notebooks/images/Grover.svg b/misc/notebooks/images/Grover.svg new file mode 100644 index 0000000..3a4710f --- /dev/null +++ b/misc/notebooks/images/Grover.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/misc/notebooks/images/StateReflection.svg b/misc/notebooks/images/StateReflection.svg new file mode 100644 index 0000000..8806ea7 --- /dev/null +++ b/misc/notebooks/images/StateReflection.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 7b0aa762aa1bbf367f82bd02f957e235ae0b17fa Mon Sep 17 00:00:00 2001 From: gonfeco Date: Wed, 26 Feb 2025 13:03:28 +0100 Subject: [PATCH 6/6] Minor deletion --- QQuantLib/AE/rqae_pseudo.py | 198 ------------------------------------ 1 file changed, 198 deletions(-) delete mode 100644 QQuantLib/AE/rqae_pseudo.py diff --git a/QQuantLib/AE/rqae_pseudo.py b/QQuantLib/AE/rqae_pseudo.py deleted file mode 100644 index 2cbe09d..0000000 --- a/QQuantLib/AE/rqae_pseudo.py +++ /dev/null @@ -1,198 +0,0 @@ - - def rqae(self, ratio: float = 2, epsilon: float = 0.01, gamma: float = 0.05): - """ - This function implements the first step of the RQAE paper. The - result is an estimation of the desired amplitude with precision - epsilon and accuracy gamma. - - Parameters - ---------- - ratio : int - amplification ratio - epsilon : int - precision - gamma : float - accuracy - - Returns - ---------- - amplitude_min : float - lower bound for the amplitude to be estimated - amplitude_max : float - upper bound for the amplitude to be estimated - - """ - ###################################### - - epsilon = 0.5 * epsilon - # Always need to clean the circuit statistics property - self.circuit_statistics = {} - # time_list = [] - - - ############### First Step ####################### - shift = 0.5 # shift [-0.5, 0.5] - - knext = schedule_k[1] - Knext = 2 * knext + 1 - # Probability epsilon - epsilon_first_p = np.abs(shift) * np.sin(0.5 * np.pi / (Knext + 2)) - # Maximum probability epsilon - epsilon_first_p_max = 0.5 * np.sin(0.5 * np.arcsin(2 * epsilon)) ** 2 - # Real probabiliy epsilon to use - epsilon_first_p = min(epsilon_first_p, epsilon_first_p_max) - gamma_first = schedule_gamma[0] - # This is shots for each iteration: Ni in the paper - n_first = int( - np.ceil(1 / (2 * epsilon_first_p**2) * np.log(2 / gamma_first)) - ) - # Quantum routine for first step - [amplitude_min, amplitude_max] = self.first_step( - shift=shift, shots=n_first, gamma=gamma_first - ) - epsilon_amplitude = (amplitude_max - amplitude_min) / 2 - - - ############### Consecutive Steps ####################### - i = 1 - while epsilon_amplitude > epsilon: - k_empirical = int(np.floor(np.pi / (4 * np.arcsin(2 * epsilon_amplitude)) - 0.5)) - K_empirical = 2 * k_empirical + 1 - - k_next = schedule_k[i+1] - K_next = 2 * k_next + 1 - k_current = schedule_k[i] - K_current = 2 * k_current + 1 - - # Probability epsilon - epsilon_step_p = 0.5 * np.sin(0.25 * np.pi * K_current / (K_next + 2) ) ** 2 - # Maximum probability epsilon - epsilon_step_p_max = 0.5 * np.sin(0.5 * K_empirical * np.arcsin(2 * epsilon)) ** 2 - # Real probabiliy epsilon to use - epsilon_step_p = min(epsilon_step_p, epsilon_step_p_max) - gamma_step = schedule_gamma[i] - # This is shots for each iteration: Ni in the paper - n_step = int( - np.ceil(1 / (2 * epsilon_step_p**2) * np.log(2 / gamma_step)) - ) - - if K_empirical < K_current: - print("Albeto fails!") - - # Quantum routine for first step - shift = -amplitude_min - shift = min(shift, 0.5) - [amplitude_min, amplitude_max] = self.run_step( - shift=shift, shots=n_step, gamma=gamma_step, k=k_empirical - ) - # time_list.append(time_pdf) - epsilon_amplitude = (amplitude_max - amplitude_min) / 2 - i = i + 1 - - return [2 * amplitude_min, 2 * amplitude_max] - - - - def schedule_exponential_constant(epsilon, gamma, ratio): - - K_max_next = 0.5 / np.arcsin(2 * epsilon) - 2.0 - - k_max_next = np.ceil((K_max_next - 1) / 2) - - k = 0 - K = 2 * k + 1 - k_next = k * ratio - K_next = 2 * k_next + 1 - k_list = [k] - while K_next < K_max_next: - k = k_next - K = 2 * k + 1 - k_next = k * ratio - K_next = 2 * k_next + 1 - k_list.append(k) - - k_list.append(k_max_next) - - gamma_i = gamma / len(k_list) - gamma_list = [gamma_i] * len(k_list) - - - def schedule_exponential_exponential(epsilon, gamma, ratio): - - K_max_next = 0.5 / np.arcsin(2 * epsilon) - 2.0 - - k_max_next = np.ceil((K_max_next - 1) / 2) - - k = 0 - K = 2 * k + 1 - k_next = k * ratio - K_next = 2 * k_next + 1 - k_list = [k] - while K_next < K_max_next: - k = k_next - K = 2 * k + 1 - k_next = k * ratio - K_next = 2 * k_next + 1 - k_list.append(k) - - k_list.append(k_max_next) - - # Hacerlo exponencia usando la lista de ks - # Ojo primer ganma - gamma_i = np.array([i for i in k_list]) - cte = gamma / np.sum(gamma_i) - gamma_i / cte - - - def schedule_linear_linear(epsilon, gamma, slope): - K_max_next = 0.5 / np.arcsin(2 * epsilon) - 2.0 - - k_max_next = np.ceil((K_max_next - 1) / 2) - - k = 0 - K = 2 * k + 1 - k_next = k + slope - K_next = 2 * k_next + 1 - k_list = [k] - while K_next < K_max_next: - k = k_next - K = 2 * k + 1 - k_next = k_next + slope - K_next = 2 * k_next + 1 - k_list.append(k) - - k_list.append(k_max_next) - - # Hacerlo exponencia usando la lista de ks - # Ojo primer ganma - gamma_i = np.array([i for i in k_list]) - cte = gamma / np.sum(gamma_i) - gamma_i / cte - - - - - - - - - - - - - - - - - - - - - - - - - - - -