Skip to content

Commit

Permalink
Added Window QPE
Browse files Browse the repository at this point in the history
  • Loading branch information
gonfeco committed Nov 11, 2024
1 parent 6950dac commit 1c61105
Show file tree
Hide file tree
Showing 12 changed files with 2,411 additions and 91 deletions.
30 changes: 17 additions & 13 deletions QQuantLib/AE/ae_classical_qpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,16 @@ def __init__(self, oracle: qlm.QRoutine, target: list, index: list, **kwargs):
self._oracle = oracle
self._target = check_list_type(target, int)
self._index = check_list_type(index, int)

self.kwargs = kwargs
# Set the QPU to use
self.linalg_qpu = kwargs.get("qpu", None)
self.linalg_qpu = self.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")
self.auxiliar_qbits_number = kwargs.get("auxiliar_qbits_number", 8)
self.shots = int(kwargs.get("shots", 100))
raise ValueError("Not QPU was provided")
self.auxiliar_qbits_number = self.kwargs.get(
"auxiliar_qbits_number", None)
self.shots = self.kwargs.get("shots")

self.mcz_qlm = kwargs.get("mcz_qlm", True)
self.mcz_qlm = self.kwargs.get("mcz_qlm", True)
# First thing is create the grover operator from the oracle
self._grover_oracle = grover(
self.oracle, self.target, self.index, mcz_qlm=self.mcz_qlm
Expand Down Expand Up @@ -183,15 +183,19 @@ def run(self):
"""
start = time.time()
self.circuit_statistics = {}
dict_pe_qft = {
# dict_pe_qft = {
# "initial_state": self.oracle,
# "unitary_operator": self._grover_oracle,
# "auxiliar_qbits_number": self.auxiliar_qbits_number,
# "shots": self.shots,
# "qpu": self.linalg_qpu,
# }
self.kwargs.update({
"initial_state": self.oracle,
"unitary_operator": self._grover_oracle,
"auxiliar_qbits_number": self.auxiliar_qbits_number,
"shots": self.shots,
"qpu": self.linalg_qpu,
}
})

self.cqpe = CQPE(**dict_pe_qft)
self.cqpe = CQPE(**self.kwargs)
self.cqpe.run()
step_circuit_stats = self.cqpe.circuit.to_circ().statistics()
step_circuit_stats.update({"n_shots": self.shots})
Expand Down
Empty file added QQuantLib/PE/__init__.py
Empty file.
73 changes: 63 additions & 10 deletions QQuantLib/PE/classical_qpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
from QQuantLib.qpu.get_qpu import get_qpu
from QQuantLib.utils.utils import load_qn_gate
from QQuantLib.utils.data_extracting import get_results
from QQuantLib.DL.data_loading import uniform_distribution
from QQuantLib.PE.windows_pe import window_selector
from qat.lang.AQASM.gates import ParamGate
from qat.lang.AQASM.routines import QRoutine


class CQPE:
"""
Expand Down Expand Up @@ -58,23 +63,54 @@ def __init__(self, **kwargs):
# Setting attributes
# In this case we load directly the initial state
# and the grover operator
self.initial_state = kwargs.get("initial_state", None)
self.q_gate = kwargs.get("unitary_operator", None)
self.kwargs = kwargs
self.initial_state = self.kwargs.get("initial_state", None)
self.q_gate = self.kwargs.get("unitary_operator", None)
if (self.initial_state is None) or (self.q_gate is None):
text = "initial_state and grover keys should be provided"
raise KeyError(text)

# Number Of classical bits for estimating phase
self.auxiliar_qbits_number = kwargs.get("auxiliar_qbits_number", 8)
self.auxiliar_qbits_number = self.kwargs.get("auxiliar_qbits_number", None)
if self.auxiliar_qbits_number is None:
raise ValueError("Auxiliary number of qubits not provided")

# Set the QPU to use
self.linalg_qpu = kwargs.get("qpu", None)
self.linalg_qpu = self.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")
raise ValueError("Not QPU was provided")

self.shots = self.kwargs.get("shots", None)
if self.shots is None:
print(
"Be Aware: Not shots povided! \
Exact simulation (shots=0) will be used"
)
self.complete = self.kwargs.get("complete", False)

# Set the window function to use
self.window = self.kwargs.get("window", None)
# Change the sign in the last control
if self.window is None:
self.window_gate = uniform_distribution(self.auxiliar_qbits_number)
self.last_control_change = False
else:
if type(self.window) in [ParamGate, QRoutine]:
self.window_gate = self.window
self.last_control_change = self.kwargs.get(
"last_control_change", None)
if self.last_control_change is None:
raise ValueError(
"If you provide a window AbstractGate \
last_control_change key CAN NOT BE NONE"
)
elif type(self.window) is str:
self.window_gate, self.last_control_change = window_selector(
self.window, **self.kwargs
)
else:
raise ValueError("Window kwarg not ParamGate neither QRoutine")

self.shots = kwargs.get("shots", 10)
self.complete = kwargs.get("complete", False)

#Quantum Routine for QPE
#Auxiliar qbits
Expand All @@ -101,15 +137,31 @@ def run(self):
qpe_routine.apply(self.initial_state, self.registers)
#Creates the auxiliary qbits for phase estimation
self.q_aux = qpe_routine.new_wires(self.auxiliar_qbits_number)
# Apply the window function to auxiliary qubits
# BE AWARE: the probability of the window function
# Should be loaded taking as a domain the Int_lsb!!!
qpe_routine.apply(self.window_gate, self.q_aux)
#Apply controlled Operator an increasing number of times
for i, aux in enumerate(self.q_aux):
for i, aux in enumerate(self.q_aux[:-1]):
#Apply Haddamard to all auxiliary qbits
qpe_routine.apply(qlm.H, aux)
#qpe_routine.apply(qlm.H, aux)
#Power of the unitary operator depending of the position
#of the auxiliary qbit.
step_q_gate = load_qn_gate(self.q_gate, 2**i)
#Controlled application of power of unitary operator
qpe_routine.apply(step_q_gate.ctrl(), aux, self.registers)
# Las Control depends on the type of Window function applied
if self.last_control_change:
step_q_gate = load_qn_gate(
self.q_gate.dag(),
2**(self.auxiliar_qbits_number - 1)
)
else:
step_q_gate = load_qn_gate(
self.q_gate,
2**(self.auxiliar_qbits_number - 1)
)
qpe_routine.apply(step_q_gate.ctrl(), self.q_aux[-1], self.registers)
#Apply the QFT
qpe_routine.apply(qlm.qftarith.QFT(len(self.q_aux)).dag(), self.q_aux)
self.circuit = qpe_routine
Expand All @@ -128,6 +180,7 @@ def run(self):
end = time.time()
self.quantum_times.append(end-start)
del self.result["Amplitude"]
# Transform to lambda. BE AWARE we need to use Int column
self.result["lambda"] = self.result["Int"] / (2**len(self.q_aux))


Expand Down
185 changes: 185 additions & 0 deletions QQuantLib/PE/windows_pe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""
This module contains different window functions. Based on:
Effects of cosine tapering window on quantum phase estimation.
Rendon, Gumaro and Izubuchi, Taku and Kikuchi, Yuta
Phys. Rev. D, 106. 2022
Author: Gonzalo Ferro Costas
"""

import numpy as np
import pandas as pd
from scipy.special import i0
import qat.lang.AQASM as qlm
from QQuantLib.DL.data_loading import load_probability

@qlm.build_gate("cosinewindow", [int], arity=lambda x: x)
def cosine_window(number_qubits: int):
"""
Creates a QLM AbstractGate for loading a Cosine Window Function
into a quantum state.
Parameters
----------
number_qubits : int
Number of qubits for the quantum AbstractGate
Return
----------
window_state: AbstractGate
AbstractGate for loading a cosine
"""

window_state = qlm.QRoutine()
q_bits = window_state.new_wires(number_qubits)
window_state.apply(qlm.H, q_bits[-1])
window_state.apply(
qlm.qftarith.QFT(number_qubits),
q_bits
)
for i, qb in enumerate(q_bits[:-1]):
window_state.apply(qlm.PH(-np.pi * 2 ** i / 2**number_qubits), qb)
window_state.apply(
qlm.PH(np.pi * (2 ** (number_qubits -1)) / 2 ** number_qubits),
q_bits[-1]
)
#window_state.apply(qlm.X, q_bits[0])
return window_state

@qlm.build_gate("sinewindow", [int], arity=lambda x: x)
def sine_window(number_qubits: int):
"""
Creates a QLM AbstractGate for loading a Sine Window Function
into a quantum state.
Parameters
----------
number_qubits : int
Number of qubits for the quantum AbstractGate
Return
----------
window_state: AbstractGate
AbstractGate for loading a sine
"""
window_state = qlm.QRoutine()
q_bits = window_state.new_wires(number_qubits)
window_state.apply(qlm.H, q_bits[-1])
window_state.apply(
qlm.qftarith.QFT(number_qubits),
q_bits
)
for i, qb in enumerate(q_bits[:-1]):
window_state.apply(qlm.PH(-np.pi * 2 ** i / 2**number_qubits), qb)
window_state.apply(
qlm.PH(np.pi * (2 ** (number_qubits -1)) / 2 ** number_qubits),
q_bits[-1]
)
window_state.apply(qlm.X, q_bits[-1])
return window_state

def kaiser_array(number_qubits, alpha=1.0e-5):
"""
Creates the probability discretization of a Kaiser window function
for a given input of number of qubits and a alpha
Parameters
----------
number_qubits : int
Number of qubits for building the Kaiser window function
alpha : float
Parameter for modified Bessel function or order 0.
Return
----------
pdf: pandas DataFrame
pandas DF with the probability discretization of the Kaiser
window function
"""
# Integer domain:
domain_int = np.array(range(-2**(number_qubits-1), 2**(number_qubits-1)))
x_ = domain_int / 2 ** (number_qubits-1)
x_ = np.sqrt(1 - x_ ** 2)
y_ = i0(np.pi * alpha * x_) / i0(np.pi * alpha)
y_ = y_ / 2 ** number_qubits
y_ = y_ ** 2
# Final Probability to load
y_final = y_ / np.sum(y_)
pdf = pd.DataFrame([domain_int, y_final]).T
pdf.rename(columns={0: "Int_neg", 1: "Prob"}, inplace=True)
# Change to positive integers
pdf["Int"] = np.where(
pdf["Int_neg"] < 0,
2 ** number_qubits + pdf["Int_neg"],
pdf["Int_neg"]
)
# Sort by positive integers
pdf.sort_values(["Int"], inplace=True)
pdf.reset_index(drop=True, inplace=True)
return pdf

def kaiser_window(number_qubits, alpha=1.0e-5):
"""
Creates a QLM AbstractGate for loading a Kaiser Window Function
into a quantum state. Uses load_probability function for loading
the discretization of the probability of the Kaiser window function.
Parameters
----------
number_qubits : int
Number of qubits for the quantum AbstractGate
alpha : float
Parameter for modified Bessel function or order 0.
Return
----------
kaiser_state: AbstractGate
AbstractGate for loading a Kaiser Window
"""
pdf = kaiser_array(number_qubits, alpha=alpha)
kaiser_state = load_probability(pdf["Prob"], id_name="KaiserWindow")
return kaiser_state

def window_selector(window_type, **kwargs):
"""
Selector funcion for window functions
Parameters
----------
window_type : str
String with the desired Window function
kwargs : keyword arguments
Keyword arguments for configuring window functions. Mandatory:
auxiliar_qbits_number. For Kaiser window it is mandatory to
provide kaiser_alpha
Return
----------
window gate: AbstractGate
AbstractGate with the desired window function
last_control_change : Bool
last_control_change value
"""
number_qubits = kwargs.get("auxiliar_qbits_number", None)
if number_qubits is None:
raise ValueError("auxiliar_qbits_number is None")

if window_type in ["Cosine", "cosine", "cos"]:
return cosine_window(number_qubits), True
elif window_type in ["Sine", "sine", "sin"]:
return sine_window(number_qubits), False
elif window_type in ["Kaiser", "kaiser", "kais"]:
kaiser_alpha = kwargs.get("kaiser_alpha", None)
if kaiser_alpha is None:
raise ValueError("kaiser_alpha not provided")
return kaiser_window(number_qubits, kaiser_alpha), True
else:
raise ValueError(
"Incorrect window_type provided. Only valid \
[Cosine, cosine, cos] for cosine window, \
[Sine,sine, sin] for sine window \
[Kaiser, kaiser, kais] for Kaiser window"
)
Empty file added QQuantLib/finance/__init__.py
Empty file.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ A series of Jupyter notebooks have been developed in the **misc/notebooks** fold

In the benchmark folder, three Python packages are presented to assess the performance of various quantum algorithms:

1. **compare_ae_probability**: This package enables easy configuration of different amplitude estimation algorithms and their application to a simple amplitude estimation problem (this is getting the probability of a fixed state when a probability density array is loaded into a quantum circuit).
2. **q_ae_price**: This package simplifies the configuration of price estimation problems for different financial derivatives (call and put options, and futures) and solves them using various configurations of quantum amplitude estimation algorithms.
1. **compare_ae_probability**: This package enables easy configuration of different amplitude estimation algorithms and their application to a simple amplitude estimation problem (this is getting the probability of a fixed state when a probability density array is loaded into a quantum circuit). For comparison between AE methods please review notebook CompareAEalgorithmsOnPureProbability.ipynb.
2. **q_ae_price**: This package simplifies the configuration of price estimation problems for different financial derivatives (call and put options, and futures) and solves them using various configurations of quantum amplitude estimation algorithms. For comparison between AE algorithms please refer to: *Compare_AE_algorithms_On_PriceEstimation.ipynb*
3. **qml4var**: This package allows to the user trains **PQC** that can be used as surrogate models of time consuming financial distribution functions.

## Acknowledgements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"ns": [null],

"auxiliar_qbits_number": [10, 12, 14, 16],
"window" : [null],
"kaiser_alpha" : [null],

"cbits_number": [null],

Expand Down
Loading

0 comments on commit 1c61105

Please sign in to comment.