Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cb6f450
docstring
v-agamshayit Mar 6, 2026
d020a78
started implementing
v-agamshayit Mar 11, 2026
25774a7
Merge remote-tracking branch 'origin/main' into feature/agamshayit/ti…
v-agamshayit Mar 30, 2026
a31336f
Trotter grouping terms by parallelizable layers, fixes pauli strings …
v-agamshayit Mar 30, 2026
6df5b2f
added missing files
v-agamshayit Mar 30, 2026
476bc5f
pre-commit
v-agamshayit Mar 30, 2026
55a0488
implememnting exponentiate_commuting() naively for now
v-agamshayit Mar 30, 2026
b55ca4a
added qubit-wise diagonalization when exponentiating disjoint commuti…
v-agamshayit Mar 30, 2026
ffe5be0
Revert "added qubit-wise diagonalization when exponentiating disjoint…
v-agamshayit Mar 30, 2026
ec87ae3
fixed tests
v-agamshayit Mar 30, 2026
20f25ed
Merge branch 'main' into feature/agamshayit/time_evolution
v-agamshayit Mar 30, 2026
bdb435b
updated the evolve_and_measure interface to be compatible with the ne…
v-agamshayit Mar 30, 2026
14495b9
fixing docstring
v-agamshayit Mar 30, 2026
3a24e89
fixing pre-commit
v-agamshayit Mar 31, 2026
0439675
added example notebook
v-agamshayit Mar 31, 2026
522ba7e
added example notebook
v-agamshayit Mar 31, 2026
60c4fda
Apply suggestions from code review
v-agamshayit Mar 31, 2026
a8d57c8
combining Pauli product formula containers w/o expanding step_reps
v-agamshayit Mar 31, 2026
a2d6432
copilot comments
v-agamshayit Mar 31, 2026
7e2c8bd
pre-commit
v-agamshayit Mar 31, 2026
3e2ce51
more copilot comments
v-agamshayit Mar 31, 2026
1197294
Apply suggestions from code review
v-agamshayit Mar 31, 2026
6d86368
ran pre-commit
v-agamshayit Mar 31, 2026
b199a79
added new algorithms to registry
v-agamshayit Apr 1, 2026
6033504
Apply suggestions from code review
v-agamshayit Apr 1, 2026
f592b19
more copilot comments, added exact exponentiation to example for refe…
v-agamshayit Apr 1, 2026
d4223bc
Merge remote-tracking branch 'origin/feature/agamshayit/time_evolutio…
v-agamshayit Apr 1, 2026
18b5f08
fixed test
v-agamshayit Apr 1, 2026
4b77439
added Trotter docstring
v-agamshayit Apr 1, 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
328 changes: 328 additions & 0 deletions examples/time_evolve_and_measure.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "694e0a42",
"metadata": {},
"source": [
"# Measure time-evolved expectation values in `qdk-chemistry`\n",
"\n",
"## This notebook demonstrates the `EvolveAndMeasure` algorithm for Hamiltonian time evolution and observable measurement using `qdk-chemistry`. It shows how to:\n",
"\n",
"1. Define a qubit Hamiltonian with time-dependent parameters\n",
"2. Build a Trotter evolution circuit\n",
"3. Run the simulation **without noise** using the QDK full-state simulator\n",
"4. Run the simulation **with noise** using both the QDK and Qiskit Aer backends\n",
"5. Optionally transpile to a target basis gate set before execution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7052dfd",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"from qdk_chemistry.algorithms import create\n",
"from qdk_chemistry.algorithms.time_evolution.builder.trotter import Trotter\n",
"from qdk_chemistry.algorithms.time_evolution.circuit_mapper import PauliSequenceMapper\n",
"from qdk_chemistry.data import LatticeGraph, QuantumErrorProfile, QubitHamiltonian\n",
"from qdk_chemistry.utils.model_hamiltonians import create_ising_hamiltonian\n",
"\n",
"# Reduce logging output for demo\n",
"from qdk_chemistry.utils import Logger\n",
"Logger.set_global_level(Logger.LogLevel.off)"
]
},
{
"cell_type": "markdown",
"id": "974e8334",
"metadata": {},
"source": [
"We define two qubit Hamiltonians representing alternating time-evolution steps (e.g., a driven or Floquet-like protocol). The observable is `ZZ`, measured after the full evolution sequence."
]
},
{
"cell_type": "markdown",
"id": "30741065",
"metadata": {},
"source": [
"The lists `hamiltonians` and `time_steps` define the discretized time-dependent Hamiltonian $H(t)$ as:\n",
"\n",
"$H(t_i) = H_i$, where\n",
"\n",
"$H_i \\in $ `hamiltonians` = $\\left[H_1,\\dots,H_n\\right]$,\n",
"\n",
"$t_i \\in $`time_steps` = $\\left[t_1,\\dots,t_n\\right]$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c9bdd8b5",
"metadata": {},
"outputs": [],
"source": [
"lattice = LatticeGraph.chain(2)\n",
"\n",
"hamiltonian_p = create_ising_hamiltonian(lattice, j=1.0, h=0.5)\n",
"hamiltonian_m = create_ising_hamiltonian(lattice, j=1.0, h=-0.5)\n",
"\n",
"steps = 20\n",
"hamiltonians = [hamiltonian_p, hamiltonian_m] * (steps // 2)\n",
"time_steps = [float((t + 1) / 10) for t in range(steps)]\n",
"\n",
"observable = QubitHamiltonian([\"ZZ\"], np.array([1.0]))"
]
},
{
"cell_type": "markdown",
"id": "d1affab1",
"metadata": {},
"source": [
"## Exact evolution via matrix exponential\n",
"\n",
"Convert the Hamiltonians to sparse matrices and compute the exact time evolution $|\\psi(t)\\rangle = \\prod_i e^{-i H_i \\Delta t_i} |\\psi_0\\rangle$."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a58bb629",
"metadata": {},
"outputs": [],
"source": [
"from scipy.sparse.linalg import expm_multiply\n",
"\n",
"# Convert Hamiltonians to sparse matrices\n",
"H_p = hamiltonian_p.to_matrix(sparse=True)\n",
"H_m = hamiltonian_m.to_matrix(sparse=True)\n",
"\n",
"num_qubits = H_p.shape[0].bit_length() - 1\n",
"\n",
"# Initial state |00...0⟩\n",
"psi = np.zeros(2**num_qubits, dtype=complex)\n",
"psi[0] = 1.0\n",
"\n",
"# Time-evolve: apply exp(-i H_i dt_i) for each step\n",
"dt_list = [time_steps[0]] + [time_steps[i] - time_steps[i - 1] for i in range(1, len(time_steps))]\n",
"\n",
"for ham, dt in zip(hamiltonians, dt_list):\n",
" H_sparse = H_p if ham is hamiltonian_p else H_m\n",
" psi = expm_multiply(-1j * H_sparse * dt, psi)\n",
"\n",
"# Compute exact ⟨ZZ⟩\n",
"Z = np.array([1, -1], dtype=complex)\n",
"ZZ_diag = np.kron(Z, Z)\n",
"zz_exact = np.real(np.conj(psi) @ (ZZ_diag * psi))\n",
"\n",
"print(f\"Exact ⟨ZZ⟩: {zz_exact:.6f}\")"
]
},
{
"cell_type": "markdown",
"id": "57064b4c",
"metadata": {},
"source": [
"## Noiseless simulation (QDK full-state simulator)\n",
"Set up the Trotter builder, circuit mapper, energy estimator, and the `measure_simulation` algorithm."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ef0a94f",
"metadata": {},
"outputs": [],
"source": [
"evolution_builder = Trotter(num_divisions=2, order=1, optimize_term_ordering=True)\n",
"mapper = PauliSequenceMapper()\n",
"energy_estimator = create(\"energy_estimator\", \"qdk\")\n",
"measure_simulation = create(\"measure_simulation\", \"classical_sampling\")"
]
},
{
"cell_type": "markdown",
"id": "2ca695ec",
"metadata": {},
"source": [
"Run the evolution and measure `⟨ZZ⟩` without any noise using the QDK full-state simulator."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a876d78",
"metadata": {},
"outputs": [],
"source": [
"circuit_executor_qdk = create(\"circuit_executor\", \"qdk_full_state_simulator\")\n",
"\n",
"measurements_noiseless = measure_simulation.run(\n",
" hamiltonians,\n",
" times=time_steps,\n",
" observables=[observable],\n",
" evolution_builder=evolution_builder,\n",
" circuit_mapper=mapper,\n",
" circuit_executor=circuit_executor_qdk,\n",
" energy_estimator=energy_estimator,\n",
" shots=10000,\n",
")\n",
"\n",
"zz_noiseless = measurements_noiseless[0][0].energy_expectation_value\n",
"print(f\"Noiseless ⟨ZZ⟩: {zz_noiseless:.6f}\")"
]
},
{
"cell_type": "markdown",
"id": "6e70e127",
"metadata": {},
"source": [
"## Define a noise profile\n",
"\n",
"`QuantumErrorProfile` provides a backend-agnostic way to specify depolarizing noise on individual gates. This profile can be used with both the QDK and Qiskit Aer simulators.\n",
"\n",
"> **Note:** The QDK full-state simulator applies noise at the *native gate level* of the compiled QIR. If the circuit uses high-level Pauli exponentials (e.g. via `PauliSequenceMapper`), those are executed natively without decomposition into `cx`/`rz` gates — so noise on `cx` won't apply. To see noise with the QDK simulator, either use `basis_gates` to force decomposition, or define noise on the native gates (`rzz`, `rx`, etc.)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "56c5fc06",
"metadata": {},
"outputs": [],
"source": [
"qdk_error_profile = QuantumErrorProfile(\n",
" name=\"demo_noise\",\n",
" description=\"Light depolarizing noise on common gates\",\n",
" errors={\n",
" \"cx\": {\"type\": \"depolarizing_error\", \"rate\": 0.01, \"num_qubits\": 2},\n",
" \"cz\": {\"type\": \"depolarizing_error\", \"rate\": 0.01, \"num_qubits\": 2},\n",
" \"rzz\": {\"type\": \"depolarizing_error\", \"rate\": 0.01, \"num_qubits\": 2},\n",
" \"h\": {\"type\": \"depolarizing_error\", \"rate\": 0.001, \"num_qubits\": 1},\n",
" \"rz\": {\"type\": \"depolarizing_error\", \"rate\": 0.001, \"num_qubits\": 1},\n",
" \"rx\": {\"type\": \"depolarizing_error\", \"rate\": 0.001, \"num_qubits\": 1},\n",
" \"s\": {\"type\": \"depolarizing_error\", \"rate\": 0.001, \"num_qubits\": 1},\n",
" \"sdg\": {\"type\": \"depolarizing_error\", \"rate\": 0.001, \"num_qubits\": 1},\n",
" },\n",
")"
]
},
{
"cell_type": "markdown",
"id": "c27af18e",
"metadata": {},
"source": [
"## Noisy simulation — QDK full-state simulator\n",
"\n",
"Because `PauliSequenceMapper` produces native Pauli-exponential operations, and our noise profile includes `rzz` (the native two-qubit gate), the QDK simulator **will** apply noise to those operations."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0742066a",
"metadata": {},
"outputs": [],
"source": [
"measurements_noisy_qdk = measure_simulation.run(\n",
" hamiltonians,\n",
" times=time_steps,\n",
" observables=[observable],\n",
" evolution_builder=evolution_builder,\n",
" circuit_mapper=mapper,\n",
" circuit_executor=circuit_executor_qdk,\n",
" energy_estimator=energy_estimator,\n",
" shots=10000,\n",
" noise=qdk_error_profile,\n",
")\n",
"\n",
"zz_noisy_qdk = measurements_noisy_qdk[0][0].energy_expectation_value\n",
"print(f\"Noisy QDK ⟨ZZ⟩: {zz_noisy_qdk:.6f}\")"
]
},
{
"cell_type": "markdown",
"id": "e0518676",
"metadata": {},
"source": [
"## Noisy simulation — Qiskit Aer simulator\n",
"\n",
"The Qiskit Aer backend transpiles the circuit to primitive gates (`cx`, `rz`, `h`, …) before execution, so noise on those gates fires naturally."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cafb9c98",
"metadata": {},
"outputs": [],
"source": [
"circuit_executor_aer = create(\"circuit_executor\", \"qiskit_aer_simulator\")\n",
"\n",
"measurements_noisy_aer = measure_simulation.run(\n",
" hamiltonians,\n",
" times=time_steps,\n",
" observables=[observable],\n",
" evolution_builder=evolution_builder,\n",
" circuit_mapper=mapper,\n",
" circuit_executor=circuit_executor_aer,\n",
" energy_estimator=energy_estimator,\n",
" shots=10000,\n",
" noise=qdk_error_profile,\n",
")\n",
"\n",
"zz_noisy_aer = measurements_noisy_aer[0][0].energy_expectation_value\n",
"print(f\"Noisy Aer ⟨ZZ⟩: {zz_noisy_aer:.6f}\")"
]
},
{
"cell_type": "markdown",
"id": "fdc8f4be",
"metadata": {},
"source": [
"## Compare results\n",
"\n",
"Side-by-side comparison of the noiseless and noisy expectation values."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8d7b777c",
"metadata": {},
"outputs": [],
"source": [
"print(\"Simulator ⟨ZZ⟩\")\n",
"print(\"─\" * 36)\n",
"print(f\"Exact {zz_exact: .6f}\")\n",
"print(f\"QDK (noiseless) {zz_noiseless: .6f}\")\n",
"print(f\"QDK (noisy) {zz_noisy_qdk: .6f}\")\n",
"print(f\"Aer (noisy) {zz_noisy_aer: .6f}\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "qdk_chemistry_venv",
"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.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
16 changes: 14 additions & 2 deletions python/src/qdk_chemistry/algorithms/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,11 +511,17 @@ def _register_python_factories():
from qdk_chemistry.algorithms.qubit_mapper import QubitMapperFactory # noqa: PLC0415
from qdk_chemistry.algorithms.state_preparation import StatePreparationFactory # noqa: PLC0415
from qdk_chemistry.algorithms.time_evolution.builder import TimeEvolutionBuilderFactory # noqa: PLC0415
from qdk_chemistry.algorithms.time_evolution.circuit_mapper import ( # noqa: PLC0415
EvolutionCircuitMapperFactory,
)
from qdk_chemistry.algorithms.time_evolution.controlled_circuit_mapper import ( # noqa: PLC0415
ControlledEvolutionCircuitMapperFactory,
)
from qdk_chemistry.algorithms.time_evolution.measure_simulation import MeasureSimulationFactory # noqa: PLC0415

register_factory(EnergyEstimatorFactory())
register_factory(EvolutionCircuitMapperFactory())
register_factory(MeasureSimulationFactory())
register_factory(StatePreparationFactory())
register_factory(QubitMapperFactory())
register_factory(QubitHamiltonianSolverFactory())
Expand Down Expand Up @@ -594,9 +600,13 @@ def _register_python_algorithms():
from qdk_chemistry.algorithms.time_evolution.builder.trotter import ( # noqa: PLC0415
Trotter,
)
from qdk_chemistry.algorithms.time_evolution.circuit_mapper import ( # noqa: PLC0415
PauliSequenceMapper as EvolutionPauliSequenceMapper,
)
from qdk_chemistry.algorithms.time_evolution.controlled_circuit_mapper import ( # noqa: PLC0415
PauliSequenceMapper,
PauliSequenceMapper as ControlledPauliSequenceMapper,
)
from qdk_chemistry.algorithms.time_evolution.measure_simulation import EvolveAndMeasure # noqa: PLC0415

register(lambda: QdkEnergyEstimator())
register(lambda: SparseIsometryGF2XStatePreparation())
Expand All @@ -606,7 +616,9 @@ def _register_python_algorithms():
register(lambda: Trotter())
register(lambda: QDrift())
register(lambda: PartiallyRandomized())
register(lambda: PauliSequenceMapper())
register(lambda: EvolutionPauliSequenceMapper())
register(lambda: ControlledPauliSequenceMapper())
register(lambda: EvolveAndMeasure())
register(lambda: QdkFullStateSimulator())
register(lambda: QdkSparseStateSimulator())
register(lambda: IterativePhaseEstimation())
Expand Down
Loading
Loading