Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7be089d
initial commit
YingrongChen May 6, 2026
1f36805
fix pre-commit
YingrongChen May 6, 2026
4415b95
add tests for circuit builder
YingrongChen May 6, 2026
04cee6c
run pre-commit
YingrongChen May 6, 2026
8042f51
add QiskitStandardPhaseEstimationBuilder
YingrongChen May 6, 2026
69ca7ef
rm create_iteration_circuit and get_circuits
YingrongChen May 6, 2026
a5359e0
rename the circuit builder and update _run_impl
RushiGong May 7, 2026
e701ae1
Merge branch 'main' into feature/cyr/qpe_builder
RushiGong May 13, 2026
9fd8f31
resolve AI code review comments
RushiGong May 14, 2026
4cf8ecc
merge main
RushiGong May 26, 2026
2c1c66a
add num bits check in standard qpe
RushiGong May 26, 2026
b657adf
Merge branch 'main' into feature/cyr/qpe_builder
RushiGong May 26, 2026
cf51a2a
apply AI code review comment
RushiGong May 27, 2026
59e59f4
Merge branch 'main' into feature/cyr/qpe_builder
RushiGong May 29, 2026
ece7e3b
apply AI code review comment
RushiGong May 29, 2026
a028f81
separate circuit builders
RushiGong Jun 1, 2026
b4550d3
fix test
RushiGong Jun 2, 2026
706284d
fix example and notebook
RushiGong Jun 2, 2026
293966d
fix tests
RushiGong Jun 2, 2026
fbc9cb4
fix standard qpe test and AI comments
RushiGong Jun 2, 2026
453a1c0
merge main
RushiGong Jun 2, 2026
4bd8d46
fix pre-commit and tests
RushiGong Jun 2, 2026
56559c0
add documentation
RushiGong Jun 2, 2026
7230ed6
fix failed example python script
RushiGong Jun 2, 2026
a5760ff
fix AI comments and skipped tests
RushiGong Jun 2, 2026
d03d8d6
add qdk to algorithm name
RushiGong Jun 3, 2026
3e06838
merge main
RushiGong Jun 3, 2026
ea88920
apply AI code review comment
RushiGong Jun 3, 2026
2231222
Merge branch 'main' into feature/cyr/qpe_builder
RushiGong Jun 3, 2026
f2c6b6c
Merge branch 'main' into feature/cyr/qpe_builder
RushiGong Jun 5, 2026
8ee92ef
apply AI code review comments
RushiGong Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 47 additions & 16 deletions docs/source/_static/examples/python/phase_estimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,49 @@
from qdk_chemistry.algorithms import create

# Create the default (iterative) phase estimation algorithm
iqpe = create("phase_estimation", "iterative")
iqpe = create("phase_estimation", "qdk_iterative")

# Or create the standard QFT-based variant (requires Qiskit)
qpe = create("phase_estimation", "qiskit_standard")
# Or create the standard QFT-based variant
qpe = create("phase_estimation", "qdk_standard")
# end-cell-create
################################################################################

################################################################################
# start-cell-configure-iqpe
# Configure iterative phase estimation
iqpe = create("phase_estimation", "iterative")
iqpe.settings().set("num_bits", 10)
iqpe.settings().set("shots_per_bit", 10)
from qdk_chemistry.data import AlgorithmRef

# Create iterative qpe circuit builder
iqpe_circuit_builder = AlgorithmRef(
"qpe_circuit_builder",
"qdk_iterative",
num_bits=10,
controlled_circuit_mapper=AlgorithmRef(
"controlled_circuit_mapper", "pauli_sequence"
),
unitary_builder=AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1),
)
iqpe = create("phase_estimation", "qdk_iterative", shots_per_bit=10)
iqpe.settings().set("qpe_circuit_builder", iqpe_circuit_builder)
# end-cell-configure-iqpe
################################################################################

################################################################################
# start-cell-configure-standard
# Configure standard QFT-based phase estimation
qpe = create("phase_estimation", "qiskit_standard")
qpe.settings().set("num_bits", 10)
qpe_circuit_builder = AlgorithmRef(
"qpe_circuit_builder",
"qiskit_standard",
num_bits=10,
qft_do_swaps=True,
controlled_circuit_mapper=AlgorithmRef(
"controlled_circuit_mapper", "pauli_sequence"
),
unitary_builder=AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1),
)
qpe = create("phase_estimation", "qdk_standard")
qpe.settings().set("shots", 100)
qpe.settings().set("qft_do_swaps", True)
qpe.settings().set("qpe_circuit_builder", qpe_circuit_builder)
# end-cell-configure-standard
################################################################################

Expand Down Expand Up @@ -77,16 +97,27 @@
# 7. Create and run IQPE with nested algorithm settings
from qdk_chemistry.data import AlgorithmRef

iqpe = create("phase_estimation", "iterative", num_bits=10, shots_per_bit=10)

# Configure nested algorithms — kwargs override the algorithm's defaults
iqpe = create("phase_estimation", "qdk_iterative", shots_per_bit=3)

# 8. Configure nested algorithms — the circuit builder holds num_bits, unitary_builder, and circuit_mapper
iqpe_circuit_builder = AlgorithmRef(
"qpe_circuit_builder",
"qdk_iterative",
num_bits=10,
controlled_circuit_mapper=AlgorithmRef(
"controlled_circuit_mapper", "pauli_sequence"
),
unitary_builder=AlgorithmRef(
"hamiltonian_unitary_builder", "trotter", order=2, time=0.1
),
)
iqpe.settings().set(
"unitary_builder",
AlgorithmRef("hamiltonian_unitary_builder", "trotter", order=2, time=0.1),
"qpe_circuit_builder",
iqpe_circuit_builder,
)
iqpe.settings().set(
"circuit_executor",
AlgorithmRef("circuit_executor", "qiskit_aer_simulator", seed=42),
AlgorithmRef("circuit_executor", "qdk_full_state_simulator", seed=42),
)

result = iqpe.run(
Expand All @@ -105,6 +136,6 @@

# List all registered phase estimation implementations
implementations = registry.available("phase_estimation")
print(implementations) # e.g. ['iterative', 'qiskit_standard']
print(implementations) # e.g. ['qdk_iterative', 'qdk_standard']
# end-cell-list-implementations
################################################################################
101 changes: 101 additions & 0 deletions docs/source/_static/examples/python/qpe_circuit_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""QpeCircuitBuilder usage examples."""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

################################################################################
# start-cell-configure-iqpe
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import AlgorithmRef

# Create iterative QPE with circuit builder configuration
controlled_circuit_mapper = AlgorithmRef("controlled_circuit_mapper", "pauli_sequence")
unitary_builder = AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1)
iqpe_circuit_builder = create("qpe_circuit_builder", "qdk_iterative", num_bits=10)
iqpe_circuit_builder.settings().set(
"controlled_circuit_mapper", controlled_circuit_mapper
)
iqpe_circuit_builder.settings().set("unitary_builder", unitary_builder)
# end-cell-configure-iqpe
################################################################################

################################################################################
# start-cell-configure-standard
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import AlgorithmRef

# Create standard QPE with circuit builder configuration
controlled_circuit_mapper = AlgorithmRef("controlled_circuit_mapper", "pauli_sequence")
unitary_builder = AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1)
qpe_circuit_builder = create(
"qpe_circuit_builder", "qiskit_standard", num_bits=10, qft_do_swaps=True
)
qpe_circuit_builder.settings().set(
"controlled_circuit_mapper", controlled_circuit_mapper
)
qpe_circuit_builder.settings().set("unitary_builder", unitary_builder)
# end-cell-configure-standard
################################################################################

################################################################################
# start-cell-run
import numpy as np
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import Structure

# 1. Setup molecule
coords = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]])
symbols = ["H", "H"]
structure = Structure(coords, symbols=symbols)

# 2. SCF
scf_solver = create("scf_solver")
E_scf, wfn_scf = scf_solver.run(
structure, charge=0, spin_multiplicity=1, basis_or_guess="sto-3g"
)

# 3. Hamiltonian construction
hamiltonian_constructor = create("hamiltonian_constructor")
hamiltonian = hamiltonian_constructor.run(wfn_scf.get_orbitals())

# 4. Multi-configuration calculation (reference state)
cas_solver = create("multi_configuration_calculator")
E_cas, wfn_cas = cas_solver.run(hamiltonian, 1, 1)

# 5. Qubit mapping
from qdk_chemistry.data import MajoranaMapping

n_spin_orbitals = 2 * hamiltonian.get_orbitals().get_num_molecular_orbitals()
qubit_mapper = create("qubit_mapper")
qubit_ham = qubit_mapper.run(
hamiltonian, MajoranaMapping.jordan_wigner(n_spin_orbitals)
)

# 6. State preparation
state_prep = create("state_prep", "sparse_isometry_gf2x")
circuit = state_prep.run(wfn_cas)

# 7. Create and run IQPE circuit builder with nested algorithm settings
from qdk_chemistry.data import AlgorithmRef

controlled_circuit_mapper = AlgorithmRef("controlled_circuit_mapper", "pauli_sequence")
unitary_builder = AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1)
iqpe_circuit_builder = create("qpe_circuit_builder", "qdk_iterative", num_bits=4)
iqpe_circuit_builder.settings().set(
"controlled_circuit_mapper", controlled_circuit_mapper
)
iqpe_circuit_builder.settings().set("unitary_builder", unitary_builder)

iqpe_circuits = iqpe_circuit_builder.run(
state_preparation=circuit,
qubit_hamiltonian=qubit_ham,
)

# 8. Print the generated circuits for each bit
for idx, circ in enumerate(iqpe_circuits):
print(f"Bit {idx}:")
print(circ.get_qsharp_circuit())
# end-cell-run
################################################################################
4 changes: 4 additions & 0 deletions docs/source/user/comprehensive/algorithms/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ All algorithms follow a :doc:`factory pattern <factory_pattern>` design, allowin
stability_checker
state_preparation
phase_estimation
qpe_circuit_builder
hamiltonian_unitary_builder
circuit_mapper
circuit_executor
Expand Down Expand Up @@ -75,6 +76,9 @@ The following table summarizes the available algorithm classes in QDK/Chemistry
* - :doc:`PhaseEstimation <phase_estimation>`
- Quantum phase estimation
- Circuit + QubitHamiltonian → QpeResult
* - :doc:`QpeCircuitBuilder <qpe_circuit_builder>`
- Phase estimation circuit composition
- Circuit + QubitHamiltonian → Circuit list
* - :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`
- Hamiltonian simulation unitaries
- QubitHamiltonian → UnitaryRepresentation
Expand Down
66 changes: 35 additions & 31 deletions docs/source/user/comprehensive/algorithms/phase_estimation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Phase estimation
================

The :class:`~qdk_chemistry.algorithms.PhaseEstimation` algorithm in QDK/Chemistry extracts eigenvalues from a quantum state by measuring the phase accumulated under repeated application of a unitary operator.
Following QDK/Chemistry's :doc:`algorithm design principles <../design/index>`, it takes a state-preparation :class:`~qdk_chemistry.data.Circuit` (from :doc:`StatePreparation <state_preparation>`), a :class:`~qdk_chemistry.data.QubitHamiltonian` (from :doc:`QubitMapper <qubit_mapper>` or a :doc:`model Hamiltonian <../model_hamiltonians>`), a unitary builder (e.g., :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`), a :doc:`ControlledCircuitMapper <circuit_mapper>`, and a :doc:`CircuitExecutor <circuit_executor>` as input and returns a :class:`~qdk_chemistry.data.QpeResult` containing the measured phase, reconstructed energy, and alias-resolution metadata.
Following QDK/Chemistry's :doc:`algorithm design principles <../design/index>`, it takes a state-preparation :class:`~qdk_chemistry.data.Circuit` (from :doc:`StatePreparation <state_preparation>`), a :class:`~qdk_chemistry.data.QubitHamiltonian` (from :doc:`QubitMapper <qubit_mapper>` or a :doc:`model Hamiltonian <../model_hamiltonians>`), a :doc:`QpeCircuitBuilder <qpe_circuit_builder>` (which encapsulates the unitary builder and controlled circuit mapper), and a :doc:`CircuitExecutor <circuit_executor>` as input and returns a :class:`~qdk_chemistry.data.QpeResult` containing the measured phase, reconstructed energy, and alias-resolution metadata.

Overview
--------
Expand Down Expand Up @@ -64,25 +64,23 @@ QubitHamiltonian
A :class:`~qdk_chemistry.data.QubitHamiltonian` containing the Pauli-string representation of the Hamiltonian.
This can be obtained from the :doc:`QubitMapper <qubit_mapper>` algorithm, constructed from a :doc:`model Hamiltonian <../model_hamiltonians>`, or built directly by the user.

Unitary builder
An algorithm that constructs the unitary operator :math:`U` whose eigenphase is to be measured.
For chemistry applications this is typically a :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>` that approximates :math:`U(t) = e^{-iHt}` or :math:`U = \frac{H}{\|H\|}`.
The builder produces a :class:`~qdk_chemistry.data.UnitaryRepresentation` which is then converted to a controlled circuit.
Settings
The :class:`~qdk_chemistry.algorithms.PhaseEstimation` is configured via its settings object, which includes:

ControlledCircuitMapper
Converts a :class:`~qdk_chemistry.data.UnitaryRepresentation` into a *controlled* version and synthesises it as an executable :class:`~qdk_chemistry.data.Circuit`.
The default implementation (``"pauli_sequence"``) constructs controlled Pauli rotations from a :class:`~qdk_chemistry.data.PauliProductFormulaContainer`.
See :doc:`circuit_mapper` for details.
- ``qpe_circuit_builder`` — A :class:`~qdk_chemistry.data.AlgorithmRef` to a :doc:`QpeCircuitBuilder <qpe_circuit_builder>` that handles circuit composition.
The circuit builder encapsulates a :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>` and a :class:`~qdk_chemistry.algorithms.ControlledCircuitMapper` as its own nested algorithms.
See :doc:`qpe_circuit_builder` for detailed configuration.

CircuitExecutor
A backend that executes :class:`~qdk_chemistry.data.Circuit` objects and returns measurement bitstrings as :class:`~qdk_chemistry.data.CircuitExecutorData`.
QDK/Chemistry ships native Q# simulators (sparse-state and full-state, with optional :class:`~qdk_chemistry.data.QuantumErrorProfile` noise modelling) and integrates with Qiskit's Aer simulator through the :doc:`plugin system <../plugins>`.
See :doc:`circuit_executor` for details.
- ``circuit_executor`` — A :class:`~qdk_chemistry.data.AlgorithmRef` to a backend that executes :class:`~qdk_chemistry.data.Circuit` objects and returns measurement bitstrings as :class:`~qdk_chemistry.data.CircuitExecutorData`.
QDK/Chemistry ships native Q# simulators (sparse-state and full-state, with optional :class:`~qdk_chemistry.data.QuantumErrorProfile` noise modelling) and integrates with Qiskit's Aer simulator through the :doc:`plugin system <../plugins>`.
See :doc:`circuit_executor` for details.

.. note::

The state preparation circuit and qubit Hamiltonian must be compatible — they should use the same qubit encoding and be derived from the same underlying system.

The ``qpe_circuit_builder`` is configured with nested algorithm references for the unitary builder and controlled circuit mapper, as shown in the configuration examples below.

.. rubric:: Creating a phase estimation algorithm

.. tab:: Python API
Expand Down Expand Up @@ -140,7 +138,7 @@ You can discover available implementations programmatically:
Iterative phase estimation (IQPE)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. rubric:: Factory name: ``"iterative"``
.. rubric:: Factory name: ``"qdk_iterative"``

Kitaev's iterative algorithm :cite:`Kitaev1995` uses a single ancilla qubit to extract phase bits sequentially, from the most significant bit (MSB) to the least significant bit (LSB).
At each iteration :math:`k` (from :math:`0` to :math:`n-1`, where :math:`n` is the total number of bits):
Expand All @@ -161,30 +159,34 @@ Each bit is determined by a majority vote over multiple circuit executions (cont

.. rubric:: Settings

Direct settings on :class:`~qdk_chemistry.algorithms.phase_estimation.iterative_phase_estimation.IterativePhaseEstimation`:

.. list-table::
:header-rows: 1
:widths: 25 15 60

* - Setting
- Type
- Description
* - ``num_bits``
- int
- Number of phase bits to extract. More bits yield higher energy precision.
* - ``evolution_time``
- float
- Time parameter :math:`t` in :math:`U = e^{-iHt}`. Determines the energy scale: :math:`E = 2\pi\phi / t`.
* - ``shots_per_bit``
- int
- Number of circuit executions per bit, used for majority-vote determination. Default is 3.

Nested algorithm configuration (via ``qpe_circuit_builder``):

See :doc:`qpe_circuit_builder` for configuring:

- ``num_bits`` — Number of phase bits to extract
- ``unitary_builder`` → ``time`` — Time parameter :math:`t` in :math:`U = e^{-iHt}`
- ``controlled_circuit_mapper`` — Circuit synthesis strategy


.. _standard-qpe-algorithm:

Standard QFT-based phase estimation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. rubric:: Factory name: ``"qiskit_standard"``
.. rubric:: Factory name: ``"qdk_standard"``

The standard :term:`QPE` algorithm :cite:`Nielsen-Chuang2010-QPE` uses a register of :math:`n` ancilla qubits to extract all phase bits simultaneously.
Comment thread
RushiGong marked this conversation as resolved.
The circuit structure consists of:
Expand All @@ -198,25 +200,27 @@ The phase is extracted from the dominant bitstring in the measurement results.

.. rubric:: Settings

Direct settings on :class:`~qdk_chemistry.algorithms.phase_estimation.standard_phase_estimation.StandardPhaseEstimation`:
Comment thread
RushiGong marked this conversation as resolved.

.. list-table::
:header-rows: 1
:widths: 25 15 60

* - Setting
- Type
- Description
* - ``num_bits``
- int
- Number of ancilla qubits (phase register size). More bits yield higher energy precision.
* - ``evolution_time``
- float
- Time parameter :math:`t` in :math:`U = e^{-iHt}`. Determines the energy scale.
* - ``shots``
- int
- Total measurement shots for the full circuit. Default is 3.
* - ``qft_do_swaps``
- bool
- Whether to include the final swap layer in the inverse QFT. Default is True.

Nested algorithm configuration (via ``qpe_circuit_builder``):

See :doc:`qpe_circuit_builder` for configuring:

- ``num_bits`` — Number of ancilla qubits (phase register size)
- ``unitary_builder`` → ``time`` — Time parameter :math:`t` in :math:`U = e^{-iHt}`
- ``qft_do_swaps`` — Whether to include swap gates in the inverse QFT (Qiskit only)
- ``controlled_circuit_mapper`` — Circuit synthesis strategy


Phase aliasing and energy resolution
Expand Down Expand Up @@ -248,9 +252,9 @@ Further reading
---------------

- The above examples can be downloaded as a complete `Python <../../../_static/examples/python/phase_estimation.py>`_ script.
- :doc:`QpeCircuitBuilder <qpe_circuit_builder>`: Abstract base class for phase estimation circuit builders
- :doc:`HamiltonianUnitaryBuilder <hamiltonian_unitary_builder>`: Hamiltonian simulation via Trotter-Suzuki decomposition or block-encoding methods
- :doc:`CircuitExecutor <circuit_executor>`: Quantum circuit execution backends
- :doc:`ControlledCircuitMapper <circuit_mapper>`: Controlled-unitary circuit synthesis
- :doc:`StatePreparation <state_preparation>`: Load wavefunctions onto qubits as quantum circuits
- :doc:`QubitMapper <qubit_mapper>`: Map fermionic Hamiltonians to qubit operators
- :doc:`QpeResult <../data/qpe_result>`: Phase estimation result data class
Expand Down
Loading
Loading