vqe-pennylane 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qpe/hamiltonian.py ADDED
@@ -0,0 +1,100 @@
1
+ """
2
+ common.hamiltonian.py
3
+ ==================
4
+
5
+ Shared molecular configuration + Hamiltonian construction for QPE.
6
+
7
+ This module mirrors the VQE architecture and ensures that QPE uses
8
+ the *exact same Hamiltonian pipeline* as VQE, guaranteeing consistent
9
+ chemistry, reproducibility, and shared geometry generation.
10
+
11
+ Provides:
12
+ • get_molecule_config(name)
13
+ • generate_geometry(name, param)
14
+ • build_hamiltonian(symbols, coordinates, charge, basis)
15
+ """
16
+
17
+ from __future__ import annotations
18
+ import numpy as np
19
+ import pennylane as qml
20
+ from pennylane import qchem
21
+ from typing import Dict, Any, Tuple
22
+
23
+ # ---------------------------------------------------------------------
24
+ # Import from VQE: SINGLE SOURCE OF TRUTH
25
+ # ---------------------------------------------------------------------
26
+ from vqe.hamiltonian import (
27
+ MOLECULES as VQE_MOLECULES,
28
+ generate_geometry as vqe_generate_geometry,
29
+ )
30
+
31
+ # Expose VQE molecule registry to QPE
32
+ MOLECULES: Dict[str, Dict[str, Any]] = VQE_MOLECULES
33
+
34
+
35
+ def get_molecule_config(name: str) -> Dict[str, Any]:
36
+ """
37
+ Retrieve molecular configuration from the unified registry.
38
+ """
39
+ if name not in MOLECULES:
40
+ raise KeyError(
41
+ f"Unknown molecule '{name}'. Available: {list(MOLECULES.keys())}"
42
+ )
43
+ return MOLECULES[name]
44
+
45
+
46
+ # ---------------------------------------------------------------------
47
+ # Geometry variation (bond scans, angles)
48
+ # ---------------------------------------------------------------------
49
+ def generate_geometry(name: str, param: float):
50
+ """
51
+ Geometry generation wrapper for QPE.
52
+
53
+ Calls the VQE geometry generator directly.
54
+ """
55
+ return vqe_generate_geometry(name, param)
56
+
57
+
58
+ # ---------------------------------------------------------------------
59
+ # Hamiltonian Builder
60
+ # ---------------------------------------------------------------------
61
+ def build_hamiltonian(
62
+ symbols: list[str],
63
+ coordinates: np.ndarray,
64
+ charge: int,
65
+ basis: str,
66
+ ) -> Tuple[qml.Hamiltonian, int]:
67
+ """
68
+ Build the molecular Hamiltonian using PennyLane-qchem,
69
+ with OpenFermion fallback.
70
+ """
71
+ try:
72
+ H, n_qubits = qchem.molecular_hamiltonian(
73
+ symbols=symbols,
74
+ coordinates=coordinates,
75
+ charge=charge,
76
+ basis=basis,
77
+ )
78
+ return H, n_qubits
79
+
80
+ except Exception as e_primary:
81
+ print("⚠️ PennyLane-qchem failed — retrying with OpenFermion backend...")
82
+
83
+ try:
84
+ H, n_qubits = qchem.molecular_hamiltonian(
85
+ symbols=symbols,
86
+ coordinates=coordinates,
87
+ charge=charge,
88
+ basis=basis,
89
+ method="openfermion",
90
+ )
91
+ return H, n_qubits
92
+
93
+ except Exception as e_fallback:
94
+ raise RuntimeError(
95
+ "Failed to construct Hamiltonian.\n"
96
+ f"Primary error:\n {e_primary}\n"
97
+ f"OpenFermion fallback error:\n {e_fallback}\n"
98
+ "Try installing OpenFermion:\n"
99
+ " pip install openfermion openfermionpyscf\n"
100
+ )
qpe/io_utils.py ADDED
@@ -0,0 +1,132 @@
1
+ """
2
+ qpe/io_utils.py
3
+ ================
4
+ Unified result persistence, caching, and filename utilities for QPE.
5
+
6
+ This version is fully aligned with the VQE I/O stack:
7
+
8
+ results/
9
+ ├── vqe/
10
+ └── qpe/
11
+
12
+ All QPE JSON output files live in: results/qpe/
13
+
14
+ All PNG figures must be saved through common.plotting.save_plot(),
15
+ which stores everything under: plots/
16
+
17
+ This file intentionally contains:
18
+ • No plotting
19
+ • No PennyLane logic
20
+ • Only JSON I/O + persistent directory management
21
+ """
22
+
23
+ from __future__ import annotations
24
+ import os
25
+ import json
26
+ import hashlib
27
+ from typing import Any, Dict
28
+
29
+ from vqe_qpe_common.plotting import save_plot, build_filename
30
+
31
+
32
+ # ---------------------------------------------------------------------
33
+ # Base Directories (mirrors VQE)
34
+ # ---------------------------------------------------------------------
35
+ BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
36
+ RESULTS_DIR = os.path.join(BASE_DIR, "results", "qpe")
37
+
38
+
39
+ def ensure_dirs() -> None:
40
+ """Ensure that the QPE results directory exists."""
41
+ os.makedirs(RESULTS_DIR, exist_ok=True)
42
+
43
+
44
+ # ---------------------------------------------------------------------
45
+ # Hashing: repeatable, human-stable, JSON-safe
46
+ # ---------------------------------------------------------------------
47
+ def signature_hash(
48
+ *,
49
+ molecule: str,
50
+ n_ancilla: int,
51
+ t: float,
52
+ shots: int | None,
53
+ noise: Dict[str, float] | None,
54
+ ) -> str:
55
+ """
56
+ Generate a reproducible hash key for a QPE configuration.
57
+
58
+ All important run parameters are included.
59
+ """
60
+ key = json.dumps(
61
+ {
62
+ "molecule": molecule,
63
+ "ancilla_qubits": n_ancilla,
64
+ "time_param": round(float(t), 10),
65
+ "shots": shots,
66
+ "noise_params": noise or {},
67
+ },
68
+ sort_keys=True,
69
+ separators=(",", ":"),
70
+ )
71
+ return hashlib.sha256(key.encode("utf-8")).hexdigest()[:12]
72
+
73
+
74
+ def cache_path(molecule: str, key: str) -> str:
75
+ """Create the canonical JSON file path for a cached QPE result."""
76
+ ensure_dirs()
77
+ safe_mol = molecule.replace("+", "plus").replace(" ", "_")
78
+ return os.path.join(RESULTS_DIR, f"{safe_mol}_QPE_{key}.json")
79
+
80
+
81
+ # ---------------------------------------------------------------------
82
+ # JSON Save / Load
83
+ # ---------------------------------------------------------------------
84
+ def save_qpe_result(result: Dict[str, Any]) -> str:
85
+ """
86
+ Save a QPE result to JSON using the canonical naming convention.
87
+
88
+ Fields that MUST be present in result:
89
+ molecule, n_ancilla, t, shots, noise
90
+ """
91
+ ensure_dirs()
92
+
93
+ key = signature_hash(
94
+ molecule=result["molecule"],
95
+ n_ancilla=result["n_ancilla"],
96
+ t=result["t"],
97
+ shots=result.get("shots", None),
98
+ noise=result.get("noise", {}),
99
+ )
100
+
101
+ path = cache_path(result["molecule"], key)
102
+
103
+ with open(path, "w", encoding="utf-8") as f:
104
+ json.dump(result, f, indent=2)
105
+
106
+ print(f"💾 Saved QPE result → {path}")
107
+ return path
108
+
109
+
110
+ def load_qpe_result(molecule: str, key: str) -> Dict[str, Any] | None:
111
+ """
112
+ Load a cached QPE JSON result if it exists, otherwise return None.
113
+ """
114
+ path = cache_path(molecule, key)
115
+ if os.path.exists(path):
116
+ with open(path, "r", encoding="utf-8") as f:
117
+ return json.load(f)
118
+ return None
119
+
120
+
121
+ # ---------------------------------------------------------------------
122
+ # Unified PNG Save Wrapper
123
+ # ---------------------------------------------------------------------
124
+ def save_qpe_plot(filename: str) -> str:
125
+ """
126
+ Save a QPE plot using the unified project-wide plotting logic.
127
+
128
+ This stores all images under the root-level plots/ directory.
129
+
130
+ filename should be produced by build_filename().
131
+ """
132
+ return save_plot(filename)
qpe/noise.py ADDED
@@ -0,0 +1,47 @@
1
+ """
2
+ qpe/noise.py
3
+ ============
4
+ Noise utility functions for Quantum Phase Estimation (QPE) circuits.
5
+
6
+ Currently supports:
7
+ • Depolarizing channel
8
+ • Amplitude damping channel
9
+
10
+ Both can be applied together to all wires in a circuit segment.
11
+ """
12
+
13
+ from __future__ import annotations
14
+ import pennylane as qml
15
+ from typing import Iterable
16
+
17
+ def apply_noise_all(
18
+ wires: Iterable[int],
19
+ p_dep: float = 0.0,
20
+ p_amp: float = 0.0,
21
+ ) -> None:
22
+ """Apply depolarizing and/or amplitude damping noise to the given wires.
23
+
24
+ This function is intended to be called *inside* a QNode, typically
25
+ after a unitary operation or ansatz to simulate mixed noise channels.
26
+
27
+ Parameters
28
+ ----------
29
+ wires : Iterable[int]
30
+ Wires (qubit indices) on which to apply noise.
31
+ p_dep : float, optional
32
+ Depolarizing probability per wire (default = 0.0).
33
+ p_amp : float, optional
34
+ Amplitude damping probability per wire (default = 0.0).
35
+
36
+ Example
37
+ -------
38
+ >>> apply_noise_all([0, 1, 2], p_dep=0.02, p_amp=0.01)
39
+ """
40
+ if p_dep <= 0.0 and p_amp <= 0.0:
41
+ return # Skip if no noise configured
42
+
43
+ for w in wires:
44
+ if p_dep > 0.0:
45
+ qml.DepolarizingChannel(p_dep, wires=w)
46
+ if p_amp > 0.0:
47
+ qml.AmplitudeDamping(p_amp, wires=w)
qpe/visualize.py ADDED
@@ -0,0 +1,212 @@
1
+ """
2
+ qpe.visualize
3
+ ==============
4
+ High-quality plotting utilities for Quantum Phase Estimation (QPE),
5
+ fully unified with the project's global plotting system in
6
+ `common.plotting`.
7
+
8
+ Provides:
9
+ • plot_qpe_distribution – histogram of ancilla measurement outcomes
10
+ • plot_qpe_sweep – generic sweep plotting (noise, ancillas, t, etc.)
11
+
12
+ All plots are saved via:
13
+ common.plotting.build_filename
14
+ common.plotting.save_plot
15
+
16
+ This guarantees:
17
+ • uniform PNG naming across VQE + QPE
18
+ • safe molecule names
19
+ • plots stored in /plots/
20
+ • consistent DPI / formatting for publication-quality figures
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from typing import Dict, Any, Sequence, Optional
26
+
27
+ import matplotlib.pyplot as plt
28
+
29
+ from vqe_qpe_common.plotting import (
30
+ build_filename,
31
+ save_plot,
32
+ format_molecule_name,
33
+ )
34
+
35
+
36
+ # ---------------------------------------------------------------------
37
+ # QPE Probability Distribution Plot
38
+ # ---------------------------------------------------------------------
39
+ def plot_qpe_distribution(
40
+ result: Dict[str, Any],
41
+ *,
42
+ show: bool = True,
43
+ save: bool = True,
44
+ ) -> None:
45
+ """
46
+ Plot the ancilla probability distribution from a QPE run.
47
+
48
+ Parameters
49
+ ----------
50
+ result : dict
51
+ QPE result dictionary produced by run_qpe().
52
+ show : bool
53
+ Display figure window.
54
+ save : bool
55
+ Save figure via common plotting system.
56
+ """
57
+ probs = result.get("probs", {})
58
+ if not probs:
59
+ print("⚠️ No probability data found in QPE result — skipping plot.")
60
+ return
61
+
62
+ molecule = format_molecule_name(result.get("molecule", "QPE"))
63
+ n_anc = int(result.get("n_ancilla", 0))
64
+
65
+ noise = result.get("noise", {})
66
+ p_dep = float(noise.get("p_dep", 0.0))
67
+ p_amp = float(noise.get("p_amp", 0.0))
68
+
69
+ # Sort by probability descending
70
+ items = sorted(probs.items(), key=lambda kv: -kv[1])
71
+ xs = [f"|{b}⟩" for b, _ in items]
72
+ ys = [float(p) for _, p in items]
73
+
74
+ # Figure
75
+ plt.figure(figsize=(8, 4))
76
+ plt.bar(xs, ys, alpha=0.85, edgecolor="black")
77
+
78
+ plt.xlabel("Ancilla State", fontsize=11)
79
+ plt.ylabel("Probability", fontsize=11)
80
+
81
+ noise_suffix = ""
82
+ if p_dep > 0 or p_amp > 0:
83
+ noise_suffix = f" • noise(p_dep={p_dep}, p_amp={p_amp})"
84
+
85
+ plt.title(
86
+ f"{molecule} QPE Distribution ({n_anc} ancilla){noise_suffix}",
87
+ fontsize=12
88
+ )
89
+
90
+ plt.xticks(rotation=45, ha="right")
91
+ plt.grid(axis="y", linestyle="--", alpha=0.4)
92
+ plt.tight_layout()
93
+
94
+ if save:
95
+ fname = build_filename(
96
+ molecule=molecule,
97
+ topic="qpe_distribution",
98
+ extras={
99
+ "anc": n_anc,
100
+ "pdep": p_dep,
101
+ "pamp": p_amp,
102
+ },
103
+ )
104
+ save_plot(fname)
105
+
106
+ if show:
107
+ plt.show()
108
+ else:
109
+ plt.close()
110
+
111
+
112
+ # ---------------------------------------------------------------------
113
+ # Generic QPE Sweep Plot
114
+ # ---------------------------------------------------------------------
115
+ def plot_qpe_sweep(
116
+ x_values: Sequence[float],
117
+ y_means: Sequence[float],
118
+ y_stds: Optional[Sequence[float]] = None,
119
+ *,
120
+ molecule: str = "?",
121
+ sweep_label: str = "Sweep parameter",
122
+ ylabel: str = "Energy (Ha)",
123
+ title: str = "QPE Sweep",
124
+ ref_value: Optional[float] = None,
125
+ ref_label: str = "Reference",
126
+ ancilla: Optional[int] = None,
127
+ noise_params: Optional[Dict[str, float]] = None,
128
+ show: bool = True,
129
+ save: bool = True,
130
+ ) -> None:
131
+ """
132
+ A general-purpose plotting routine for QPE sweeps:
133
+ • sweep over noise strengths
134
+ • sweep over t parameter
135
+ • sweep over number of ancilla qubits
136
+ • sweep over geometry/bond length (rare for QPE, but possible)
137
+
138
+ Parameters
139
+ ----------
140
+ x_values : list
141
+ Parameter values for sweep
142
+ y_means : list
143
+ Mean energies or phases
144
+ y_stds : list, optional
145
+ Standard deviations
146
+ molecule : str
147
+ Molecule label
148
+ sweep_label : str
149
+ X-axis label
150
+ ylabel : str
151
+ Y-axis label (energy or phase)
152
+ title : str
153
+ Plot title
154
+ ref_value : float, optional
155
+ Add horizontal reference line
156
+ ancilla : int, optional
157
+ Ancilla count for filename metadata
158
+ noise_params : dict, optional
159
+ Noise info for filename metadata
160
+ """
161
+
162
+ molecule = format_molecule_name(molecule)
163
+ p_dep = float((noise_params or {}).get("p_dep", 0.0))
164
+ p_amp = float((noise_params or {}).get("p_amp", 0.0))
165
+
166
+ plt.figure(figsize=(6.5, 4.5))
167
+
168
+ if y_stds is not None:
169
+ plt.errorbar(
170
+ x_values,
171
+ y_means,
172
+ yerr=y_stds,
173
+ fmt="o-",
174
+ capsize=4,
175
+ label="QPE mean ± std",
176
+ )
177
+ else:
178
+ plt.plot(x_values, y_means, "o-", label="QPE mean")
179
+
180
+ if ref_value is not None:
181
+ plt.axhline(
182
+ ref_value,
183
+ linestyle="--",
184
+ color="gray",
185
+ label=ref_label,
186
+ alpha=0.8,
187
+ )
188
+
189
+ plt.xlabel(sweep_label)
190
+ plt.ylabel(ylabel)
191
+ plt.title(f"{molecule} – {title}")
192
+ plt.grid(True, linestyle="--", alpha=0.5)
193
+ plt.legend()
194
+ plt.tight_layout()
195
+
196
+ if save:
197
+ fname = build_filename(
198
+ molecule=molecule,
199
+ topic="qpe_sweep",
200
+ extras={
201
+ "anc": ancilla,
202
+ "pdep": p_dep,
203
+ "pamp": p_amp,
204
+ "tag": title.replace(" ", "_").lower(),
205
+ },
206
+ )
207
+ save_plot(fname)
208
+
209
+ if show:
210
+ plt.show()
211
+ else:
212
+ plt.close()
vqe/__init__.py ADDED
@@ -0,0 +1,118 @@
1
+ """
2
+ vqe
3
+ ---
4
+ A modular Variational Quantum Eigensolver (VQE) and SSVQE toolkit built on PennyLane.
5
+
6
+ Public API:
7
+ - run_vqe
8
+ - run_ssvqe
9
+ - run_vqe_noise_sweep
10
+ - run_vqe_optimizer_comparison
11
+ - run_vqe_ansatz_comparison
12
+ - run_vqe_multi_seed_noise
13
+ - run_vqe_geometry_scan
14
+ - run_vqe_mapping_comparison
15
+
16
+ - get_ansatz, init_params, ANSATZES
17
+ - get_optimizer
18
+ - build_hamiltonian, generate_geometry
19
+ - make_run_config_dict, run_signature, save_run_record, ensure_dirs
20
+
21
+ - Plotting helpers (visualize.*)
22
+ """
23
+
24
+ __version__ = "0.2.0"
25
+
26
+ # ------------------------------------------------------------------
27
+ # Core VQE APIs
28
+ # ------------------------------------------------------------------
29
+ from .core import (
30
+ run_vqe,
31
+ run_vqe_noise_sweep,
32
+ run_vqe_optimizer_comparison,
33
+ run_vqe_ansatz_comparison,
34
+ run_vqe_multi_seed_noise,
35
+ run_vqe_geometry_scan,
36
+ run_vqe_mapping_comparison,
37
+ )
38
+
39
+ # ------------------------------------------------------------------
40
+ # Ansatz registry & utilities
41
+ # ------------------------------------------------------------------
42
+ from .ansatz import get_ansatz, init_params, ANSATZES
43
+
44
+ # ------------------------------------------------------------------
45
+ # Optimizers
46
+ # ------------------------------------------------------------------
47
+ from .optimizer import get_optimizer
48
+
49
+ # ------------------------------------------------------------------
50
+ # Hamiltonian & geometry
51
+ # ------------------------------------------------------------------
52
+ from .hamiltonian import build_hamiltonian, generate_geometry
53
+
54
+ # ------------------------------------------------------------------
55
+ # I/O utilities (config, hashing, results)
56
+ # ------------------------------------------------------------------
57
+ from .io_utils import (
58
+ make_run_config_dict,
59
+ run_signature,
60
+ save_run_record,
61
+ ensure_dirs,
62
+ )
63
+
64
+ # ------------------------------------------------------------------
65
+ # Visualization utilities
66
+ # ------------------------------------------------------------------
67
+ from .visualize import (
68
+ plot_convergence,
69
+ plot_ssvqe_convergence_multi,
70
+ plot_optimizer_comparison,
71
+ plot_ansatz_comparison,
72
+ plot_noise_statistics,
73
+ )
74
+
75
+ # ------------------------------------------------------------------
76
+ # SSVQE
77
+ # ------------------------------------------------------------------
78
+ from .ssvqe import run_ssvqe
79
+
80
+
81
+ __all__ = [
82
+ # Core VQE API
83
+ "run_vqe",
84
+ "run_vqe_noise_sweep",
85
+ "run_vqe_optimizer_comparison",
86
+ "run_vqe_ansatz_comparison",
87
+ "run_vqe_multi_seed_noise",
88
+ "run_vqe_geometry_scan",
89
+ "run_vqe_mapping_comparison",
90
+
91
+ # Ansatz tools
92
+ "get_ansatz",
93
+ "init_params",
94
+ "ANSATZES",
95
+
96
+ # Optimizers
97
+ "get_optimizer",
98
+
99
+ # Hamiltonian
100
+ "build_hamiltonian",
101
+ "generate_geometry",
102
+
103
+ # I/O
104
+ "make_run_config_dict",
105
+ "run_signature",
106
+ "save_run_record",
107
+ "ensure_dirs",
108
+
109
+ # SSVQE
110
+ "run_ssvqe",
111
+
112
+ # Visualization
113
+ "plot_convergence",
114
+ "plot_optimizer_comparison",
115
+ "plot_ansatz_comparison",
116
+ "plot_noise_statistics",
117
+ "plot_ssvqe_convergence_multi",
118
+ ]