PhaseEstimation(precision_bits: int, work_bits: int, control_unitary: CompositeGate, work_state_prep: Optional[CompositeGate] = None, do_swap: bool = False)
Quantum Phase Estimation.
Qubit Arrangement:
|precision_bits>
|work_bits>
|ancilla>
References
[1]: Nielsen, M.A., & Chuang, I.L. (2010). Quantum Computation and Quantum Information, p.225.
Parameters:
-
precision_bits
(int)
–
The number of qubits for representing the phase.
-
work_bits
(int)
–
The number of qubits for storing the eigenstate.
-
control_unitary
(CompositeGate)
–
A controlled version of the uitary whose phase is about to
be estimated. The control bit requires to be the highest bit.
-
work_state_prep
(CompositeGate, default:
None
)
–
Optional eigenstate prepration gate.
-
do_swap
(bool, default:
False
)
–
If True, the iqft stage will include swap gates.
Raises:
ValueError: If control_unitary is not a composite gate.
ValueError: If work_state_prep is not a composite gate or its width is larger the number of
work bits.
Source code in QuICT/algorithm/quantum_algorithm/phase_estimation/qpe.py
| def __init__(
self,
precision_bits: int,
work_bits: int,
control_unitary: CompositeGate,
work_state_prep: Optional[CompositeGate] = None,
do_swap: bool = False
):
"""
Args:
precision_bits (int): The number of qubits for representing the phase.
work_bits (int): The number of qubits for storing the eigenstate.
control_unitary (CompositeGate): A controlled version of the uitary whose phase is about to
be estimated. The control bit requires to be the highest bit.
work_state_prep (CompositeGate): Optional eigenstate prepration gate.
do_swap (bool): If `True`, the iqft stage will include swap gates.
Raises:
ValueError: If `control_unitary` is not a composite gate.
ValueError: If `work_state_prep` is not a composite gate or its width is larger the number of
work bits.
"""
if not isinstance(control_unitary, CompositeGate):
raise ValueError("Input `control_unitary` has to be a composite gate.")
required_q_cu = max(control_unitary.qubits) - len(control_unitary.ancilla_qubits)
if required_q_cu > work_bits:
raise ValueError("Not enough work qubits for control unitary. "
f"Given {work_bits}, but the control unitary requires: {required_q_cu}.")
num_ancilla = len(control_unitary.ancilla_qubits)
self._work_state_prep = None
if work_state_prep is not None:
if not isinstance(work_state_prep, CompositeGate):
raise ValueError("Input `work_state_prep` has to be a composite gate.")
self._work_state_prep = work_state_prep
num_ancilla = max(num_ancilla, len(work_state_prep.ancilla_qubits))
required_q_sp = max(work_state_prep.qubits) + 1 - len(work_state_prep.ancilla_qubits)
if required_q_sp > work_bits:
raise ValueError("Not enough work qubits for the state preparation. "
f"Given {work_bits}, but the state preparation requires: {required_q_sp}.")
self._control_unitary = control_unitary
self._do_swap = do_swap
# Allocate required qubits
reg_manager = QRegManager()
self._precision_reg = reg_manager.alloc(precision_bits)
self._work_reg = reg_manager.alloc(work_bits)
self._ancilla_reg = reg_manager.alloc(num_ancilla)
self._total_qubits = reg_manager.allocated
# Get input composite gates' application indices
gate_mapper = QubitAligner(self._work_reg, self._ancilla_reg)
if work_state_prep is not None:
self._sp_reg = gate_mapper.getMap(self._work_state_prep)
self._cu_reg_without_ctrl = gate_mapper.getMap(self._control_unitary, fix_top=1)
self._circuit = None
self._distribution = None
|
circuit
Build the quantum phase estimation circuit.
Returns:
Source code in QuICT/algorithm/quantum_algorithm/phase_estimation/qpe.py
| def circuit(self) -> Circuit:
""" Build the quantum phase estimation circuit.
Returns:
Circuit: the qpe circuit.
"""
if self._circuit is not None:
return self._circuit
self._circuit = Circuit(self._total_qubits)
num_precision = len(self._precision_reg)
if self._work_state_prep is not None:
self._work_state_prep | self._circuit(self._sp_reg)
for i in self._precision_reg:
H | self._circuit(i)
if self._do_swap:
for i in reversed(range(num_precision)):
ctrl_bit = self._precision_reg[i]
self._control_unitary.exp2(num_precision - 1 - i) | self._circuit(
[ctrl_bit] + self._cu_reg_without_ctrl
)
else:
for i in range(num_precision):
ctrl_bit = self._precision_reg[i]
self._control_unitary.exp2(i) | self._circuit([ctrl_bit] + self._cu_reg_without_ctrl)
IQFT(num_precision, with_swap=self._do_swap) | self._circuit(self._precision_reg)
return self._circuit
|
run
run(backend=StateVectorSimulator(), shots: int = 1, decode_as_float: bool = True) -> Union[Dict[float, int], Dict[str, int]]
Run the quantum phase estimation circuit.
Parameters:
-
backend
(Any, default:
StateVectorSimulator()
)
–
a backend to run the qpe circuit.
-
shots
(int, default:
1
)
–
number of times to run the circuit.
-
decode_as_float
(bool, default:
True
)
–
If True, the running result will be decoded to be the phase/(2*pi)
which is a float between 0 and 1. If False, the result will be presented as bit strings.
Returns:
-
Union[Dict[float, int], Dict[str, int]]
–
Dict[float, int] | Dict[str, int]: result get from running the qpe circuit.
Source code in QuICT/algorithm/quantum_algorithm/phase_estimation/qpe.py
| def run(
self,
backend=StateVectorSimulator(),
shots: int = 1,
decode_as_float: bool = True
) -> Union[Dict[float, int], Dict[str, int]]:
""" Run the quantum phase estimation circuit.
Args:
backend (Any): a backend to run the qpe circuit.
shots (int): number of times to run the circuit.
decode_as_float (bool): If `True`, the running result will be decoded to be the phase/(2*pi)
which is a float between 0 and 1. If `False`, the result will be presented as bit strings.
Returns:
Dict[float, int] | Dict[str, int]: result get from running the qpe circuit.
"""
if shots < 1:
raise ValueError(f"Shots have to be positive.")
if self._circuit is None:
self.circuit()
# run the circuit on designated backend
if self._distribution is None:
final_sv = backend.run(self._circuit)
# calculate the wole distribution if backend is simulator.
final_density_diag = (final_sv * final_sv.conj()).real
# trace out the work and ancilla bits
traced_diag = np.sum(
final_density_diag.reshape(
(1 << len(self._precision_reg), 1 << (len(self._work_reg) + len(self._ancilla_reg)))
),
axis=1
)
if backend.device == "GPU":
traced_diag = traced_diag.get()
self._distribution = traced_diag
sample_array = np.random.choice(a=len(self._distribution), p=self._distribution, size=shots)
unique, counts = np.unique(sample_array, return_counts=True)
if decode_as_float:
return dict(zip((unique / (1 << len(self._precision_reg))), counts))
measure_dict = {}
for i in range(len(unique)):
key_str = np.binary_repr(unique[i], width=len(self._precision_reg))
measure_dict[key_str] = counts[i]
return measure_dict
|