Performing a Projection Based Wavefunction-in-DFT Embedding Study
In this example, we plot the FAST Variational Quantum Eigensolver (FAST-VQE) calculation result as a function of the number of active orbitals.
The theory behind projection-based embedding is described in the Projection-Based Wavefunction-in-DFT Embedding.
Full Script
The script below shows the full script, which are then explained section by section.
1"""Demonstrate Projection based wavefunction in DFT embedding (Projective-Embedding) on malonaldehyde."""
2
3# pyright: reportUnknownVariableType=false, reportMissingImports=false
4# ruff: noqa
5import logging
6import time
7from pathlib import Path
8
9import matplotlib.pyplot as plt
10import numpy as np
11
12import qrunch as qc
13
14
15
16qc.setup_logger(logging.INFO)
17
18
19def plot_energy_and_time(
20 active_spaces: list[int],
21 energies: list[float],
22 durations: list[float],
23 *,
24 outdir: Path = Path("dist"),
25 show: bool = False,
26) -> None:
27 """
28 Create and save plots for energy and runtime vs. active space size.
29
30 Args:
31 active_spaces: List of active spatial orbital counts.
32 energies: Total energies (Hartree) corresponding to active_spaces.
33 durations: Wall-clock durations in seconds for each run.
34 outdir: Output directory for images and CSV.
35 show: Whether to display the figures interactively after saving.
36
37 """
38 outdir.mkdir(parents=True, exist_ok=True)
39
40 x = np.asarray(active_spaces, dtype=float)
41 e = np.asarray(energies, dtype=float)
42 t = np.asarray(durations, dtype=float)
43
44 # Plot 1: Energy (Hartree) vs active space
45 fig1 = plt.figure(figsize=(6.0, 4.0))
46 plt.plot(x, e, "-o", label="Total energy (Hartree)", color="darkgreen")
47 for xi, yi in zip(x, e, strict=True):
48 plt.annotate(f"{yi:.6f}", (xi, yi), textcoords="offset points", xytext=(0, 6), ha="center")
49 plt.xlabel("Number of active spatial orbitals (number of qubits are 2 times the number of active spatial orbitals)")
50 plt.ylabel("Total energy [Hartree]")
51 plt.title("FAST-VQE-in-DFT - Energy vs. active space")
52 plt.grid(visible=True, alpha=0.3)
53 plt.tight_layout()
54 energy_png = outdir / "energy_vs_active_space.png"
55 fig1.savefig(energy_png, dpi=200)
56
57 # Plot 2: Time (s) vs active space
58 fig2 = plt.figure(figsize=(6.0, 4.0))
59 plt.plot(x, t, "-o", label="Wall time (s)", color="darkgreen")
60 for xi, yi in zip(x, t, strict=True):
61 plt.annotate(f"{yi:.2f}s", (xi, yi), textcoords="offset points", xytext=(0, 6), ha="center")
62 plt.xlabel("Number of active spatial orbitals (number of qubits are 2 times the number of active spatial orbitals)")
63 plt.ylabel("Wall time [s]")
64 plt.title("FAST-VQE-in-DFT - Runtime vs. active space")
65 plt.grid(visible=True, alpha=0.3)
66 plt.tight_layout()
67 time_png = outdir / "time_vs_active_space.png"
68 fig2.savefig(time_png, dpi=200)
69
70 if show:
71 plt.show()
72 else:
73 # Close figures to free memory when running in batch contexts.
74 plt.close(fig1)
75 plt.close(fig2)
76
77
78
79
80def main(max_number_of_active_spatial_orbitals: int = 15, data_path: Path | None = None) -> list[float]:
81 """Run FAST-VQE on LiH as the first script."""
82
83 if data_path is None:
84 directory = Path.cwd() / "data"
85 else:
86 directory = data_path
87
88 cube_generator = (
89 qc.cube_file_generator_creator() # Start creating a cube file generator
90 .projective_embedding() # Narrow: pick Projective Embedding cube file generator
91 .with_base_file_path( # Configure to use a specific base file path
92 path=Path("data") # Directory where cube files will be saved
93 )
94 .with_embedding_components( # Configure which components to generate
95 components=["total"] # Generate total density cube files for the embedded region.
96 )
97 .with_environment_components( # Configure which components to generate
98 components=["alpha"] # Generate alpha density cube files for the environment region.
99 )
100 .with_overwriting_policy( # Configure the file overwriting policy
101 policy="rename" # Rename files if filename already exist
102 )
103 .create() # Create the cube file generator instance
104 )
105
106 # Build molecular configuration for the Transition state of the
107 # Malonaldehyde proton transfer reaction.
108 # We embed the two oxygen atoms and the hydrogen atom
109 # involved in the proton transfer reaction.
110 # The geometry was grabbed from the Supplementary materials of the paper:
111 # Tikhonov D. Accurate Tunneling Splittings for Proton Transfer in Malonaldehyde
112 # and Formic Acid Dimer Using the One-Dimensional Schrödinger Equation.
113 # ChemRxiv. 2021; doi:10.26434/chemrxiv.14394080.v1
114 # Corresponding to
115 # MA/b3lyp-d3-def2-nvp/def-svp/scan/120pm
116
117 molecular_configuration = qc.build_molecular_configuration(
118 molecule=[
119 ("C", -0.00915141819825, 1.12354123465448, 0.00106040539915),
120 ("C", 1.15788296226156, 0.34671243078737, 0.00000758720919),
121 ("C", -1.22030979400856, 0.41042248215324, 0.00013737820145),
122 ("O", 1.12051212277652, -0.92879932667030, -0.00173637506493),
123 ("O", -1.25284335513586, -0.86184055825304, -0.00162501071734),
124 ("H", -0.06011523023948, -1.14355253030268, -0.00207685087878),
125 ("H", 0.02269677013415, 2.21233410585677, 0.00263410783148),
126 ("H", 2.15529643150533, 0.82074207219421, 0.00068827649219),
127 ("H", -2.18850348909542, 0.94364908957994, 0.00091048152759),
128 ],
129 basis_set="cc-pvdz",
130 spin_difference=0,
131 charge=0,
132 units="angstrom",
133 embedded_atoms=[3, 4, 5], # Indices of the atoms to be treated with high-level method (0-based indexing)
134 )
135
136 adaptive_vqe_options = qc.options.IterativeVqeOptions(
137 max_iterations=100, # Increase the maximum number of iterations
138 )
139
140 # Build the user-configured FAST-VQE calculator
141 fast_vqe_calculator = (
142 qc.calculator_creator() # Start creating a calculator
143 .vqe() # Narrow: pick Variational quantum eigensolver (VQE)
144 .iterative() # Use the iterative VQE
145 .standard() # Narrow: pick the standard iterative VQE (FAST-VQE)
146 .with_options(adaptive_vqe_options) # Use the user-defined iterative VQE options
147 .choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
148 .quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
149 .choose_data_persister_manager() # Start sub-choice: Choose the data persister manager
150 .file_persister( # Perform the sub-choice selection - Here we pick file persister
151 directory=directory, # Directory where data files will be saved
152 extension=".qdk", # File extension for the data files
153 do_save=True, # Whether to save data to files
154 load_policy="fallback", # Load data if available, otherwise compute
155 overwriting_policy="overwrite", # Overwrite if filename already exist
156 padding_width=3, # Padding width for file numbering (001, 002, ...)
157 )
158 .create() # Create the calculator instance
159 )
160
161 energies: list[float] = []
162 active_spaces: list[int] = []
163 durations: list[float] = []
164 for number_of_active_spatial_orbitals in range(6, max_number_of_active_spatial_orbitals, 2):
165 # Build the ground state problem.
166 problem_builder = (
167 qc.problem_builder_creator() # Start creating a problem builder
168 .ground_state() # Narrow: pick ground state problem
169 .projective_embedding() # Narrow: pick Projective Embedding ground state problem
170 .choose_full_system_solver() # Start sub-choice: Choose the full system solver
171 .dft( # Perform the sub-choice selection - Here we pick DFT as the full system solver
172 options=qc.options.DftCalculatorOptions(
173 functional="b3lyp", # The exchange-correlation functional (default is "b3lyp")
174 convergence_tolerance=1e-9, # Energy convergence tolerance
175 verbose=10, # Set the SCF verbosity level
176 )
177 )
178 .choose_localizer() # Start sub-choice: Choose the orbital localizer
179 .pipek_mezey( # Perform the sub-choice selection - Here we pick Pipek-Mezey localizer
180 population_method="meta-lowdin",
181 options=qc.options.LocalizationOptions(
182 max_iterations=20, # The max. number of iterations in each macro iteration.
183 convergence_tolerance=1e-6, # Converge threshold.
184 ),
185 )
186 .choose_orbital_assigner() # Start sub-choice: Choose the orbital assigner
187 .total_weight( # Perform the sub-choice selection - Here we pick total weight orbital assigner
188 assignment_tolerance="kmeans_midpoint" # Cluster weights into two clusters using a minimal k-means.
189 )
190 .choose_embedded_orbital_calculator() # Start sub-choice: Choose the embedded orbital calculator
191 .moller_plesset_2( # Perform the sub-choice selection - Here we pick MP2 embedded orbital calculator.
192 options=qc.options.MollerPlesset2CalculatorOptions(
193 verbose=10, # Set the verbosity level
194 frozen_virtual_threshold=100.0, # Disregard virtual orbitals with large occupation numbers.
195 )
196 )
197 .choose_projector_builder() # Start sub-choice: Choose the projector builder
198 .manby( # Perform the sub-choice selection - Here we pick Manby's level shift projector
199 level_shift_parameter=1e5 # The level shift parameter
200 )
201 .choose_data_persister_manager() # Start sub-choice: Choose the data persister manager
202 .file_persister( # Perform the sub-choice selection - Here we pick file persister
203 directory=directory, # Directory where data files will be saved
204 extension=".qdk", # File extension for the data files
205 do_save=True, # Whether to save data to files
206 load_policy="fallback", # Load data if available, otherwise compute
207 overwriting_policy="rename", # Rename files if filename already exist
208 padding_width=3, # Padding width for file numbering (001, 002, ...)
209 )
210 .with_cube_generator(
211 cube_generator=cube_generator,
212 )
213 .add_problem_modifier() # Add a problem modifier
214 .active_space( # Specify the problem modifier to add - Here we pick the active space modifier
215 number_of_active_spatial_orbitals=number_of_active_spatial_orbitals,
216 number_of_active_alpha_electrons=number_of_active_spatial_orbitals
217 // 2, # Number of active alpha electrons
218 )
219 .create() # Create the problem builder instance
220 )
221 ground_state_problem = problem_builder.build_unrestricted(molecular_configuration)
222
223 start = time.perf_counter()
224 result = fast_vqe_calculator.calculate(ground_state_problem)
225 end = time.perf_counter()
226
227 energies.append(result.total_energy.value)
228 active_spaces.append(number_of_active_spatial_orbitals)
229 durations.append(float(end - start))
230
231 plot_energy_and_time(
232 energies=energies,
233 active_spaces=active_spaces,
234 durations=durations,
235 show=True,
236 )
237
238 return energies
239
240
241if __name__ == "__main__":
242 main()
Explanation
Import Qrunch
import logging
import time
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import qrunch as qc
This code snippet loads the python packages we need.
import qrunch as qc loads the public and stable API.
This is the only import you need for most tasks, and the only one
that is guaranteed to be supported across versions.
In addition, we use matplotlib, numpy, time, logging, and Path.
In this example, we import time to measure the execution time of different parts of the code,
and logging to provide informative messages during execution.
Logging
qc.setup_logger(logging.INFO)
In this code snippet, we set up logging to provide INFO informative messages during execution.
This is particularly useful for tracking the complex workflow of the projection-based
embedding process.
Other logging levels include DEBUG, CRITICAL.
Plotting
def plot_energy_and_time(
active_spaces: list[int],
energies: list[float],
durations: list[float],
*,
outdir: Path = Path("dist"),
show: bool = False,
) -> None:
"""
Create and save plots for energy and runtime vs. active space size.
Args:
active_spaces: List of active spatial orbital counts.
energies: Total energies (Hartree) corresponding to active_spaces.
durations: Wall-clock durations in seconds for each run.
outdir: Output directory for images and CSV.
show: Whether to display the figures interactively after saving.
"""
outdir.mkdir(parents=True, exist_ok=True)
x = np.asarray(active_spaces, dtype=float)
e = np.asarray(energies, dtype=float)
t = np.asarray(durations, dtype=float)
# Plot 1: Energy (Hartree) vs active space
fig1 = plt.figure(figsize=(6.0, 4.0))
plt.plot(x, e, "-o", label="Total energy (Hartree)", color="darkgreen")
for xi, yi in zip(x, e, strict=True):
plt.annotate(f"{yi:.6f}", (xi, yi), textcoords="offset points", xytext=(0, 6), ha="center")
plt.xlabel("Number of active spatial orbitals (number of qubits are 2 times the number of active spatial orbitals)")
plt.ylabel("Total energy [Hartree]")
plt.title("FAST-VQE-in-DFT - Energy vs. active space")
plt.grid(visible=True, alpha=0.3)
plt.tight_layout()
energy_png = outdir / "energy_vs_active_space.png"
fig1.savefig(energy_png, dpi=200)
# Plot 2: Time (s) vs active space
fig2 = plt.figure(figsize=(6.0, 4.0))
plt.plot(x, t, "-o", label="Wall time (s)", color="darkgreen")
for xi, yi in zip(x, t, strict=True):
plt.annotate(f"{yi:.2f}s", (xi, yi), textcoords="offset points", xytext=(0, 6), ha="center")
plt.xlabel("Number of active spatial orbitals (number of qubits are 2 times the number of active spatial orbitals)")
plt.ylabel("Wall time [s]")
plt.title("FAST-VQE-in-DFT - Runtime vs. active space")
plt.grid(visible=True, alpha=0.3)
plt.tight_layout()
time_png = outdir / "time_vs_active_space.png"
fig2.savefig(time_png, dpi=200)
if show:
plt.show()
else:
# Close figures to free memory when running in batch contexts.
plt.close(fig1)
plt.close(fig2)
In this code snippet, we define a function to plot the results of the projection-based embedding calculation.
This include the FAST-VQE-in-DFT energies as a function of the number of active orbitals, and the execution time as a function of the number of active orbitals.
Cube file generation
cube_generator = (
qc.cube_file_generator_creator() # Start creating a cube file generator
.projective_embedding() # Narrow: pick Projective Embedding cube file generator
.with_base_file_path( # Configure to use a specific base file path
path=Path("data") # Directory where cube files will be saved
)
.with_embedding_components( # Configure which components to generate
components=["total"] # Generate total density cube files for the embedded region.
)
.with_environment_components( # Configure which components to generate
components=["alpha"] # Generate alpha density cube files for the environment region.
)
.with_overwriting_policy( # Configure the file overwriting policy
policy="rename" # Rename files if filename already exist
)
.create() # Create the cube file generator instance
)
This code snippet sets up a cube file generator to generate cube files for the electron density.
This may be useful for visualizing the electron density of the embedded region and the environment.
Molecular configuration
# Build molecular configuration for the Transition state of the
# Malonaldehyde proton transfer reaction.
# We embed the two oxygen atoms and the hydrogen atom
# involved in the proton transfer reaction.
# The geometry was grabbed from the Supplementary materials of the paper:
# Tikhonov D. Accurate Tunneling Splittings for Proton Transfer in Malonaldehyde
# and Formic Acid Dimer Using the One-Dimensional Schrödinger Equation.
# ChemRxiv. 2021; doi:10.26434/chemrxiv.14394080.v1
# Corresponding to
# MA/b3lyp-d3-def2-nvp/def-svp/scan/120pm
molecular_configuration = qc.build_molecular_configuration(
molecule=[
("C", -0.00915141819825, 1.12354123465448, 0.00106040539915),
("C", 1.15788296226156, 0.34671243078737, 0.00000758720919),
("C", -1.22030979400856, 0.41042248215324, 0.00013737820145),
("O", 1.12051212277652, -0.92879932667030, -0.00173637506493),
("O", -1.25284335513586, -0.86184055825304, -0.00162501071734),
("H", -0.06011523023948, -1.14355253030268, -0.00207685087878),
("H", 0.02269677013415, 2.21233410585677, 0.00263410783148),
("H", 2.15529643150533, 0.82074207219421, 0.00068827649219),
("H", -2.18850348909542, 0.94364908957994, 0.00091048152759),
],
basis_set="cc-pvdz",
spin_difference=0,
charge=0,
units="angstrom",
embedded_atoms=[3, 4, 5], # Indices of the atoms to be treated with high-level method (0-based indexing)
)
In this code snippet, we define the molecular configuration for the transition state of the Malonaldehyde proton transfer reaction. We set the molecular geometry, basis set, charge, and spin difference.
Specially for the projection-based embedding, we also define the embedded_atoms, which are the atoms that belong to the embedded region.
In this case the two oxygen atoms and the hydrogen atom involved in the proton transfer reaction.
We use indexing starting from 0 for the atoms. So 3 corresponds to the 4th atom in the XYZ file. The first Oxygen atom.
The calculator
adaptive_vqe_options = qc.options.IterativeVqeOptions(
max_iterations=100, # Increase the maximum number of iterations
)
# Build the user-configured FAST-VQE calculator
fast_vqe_calculator = (
qc.calculator_creator() # Start creating a calculator
.vqe() # Narrow: pick Variational quantum eigensolver (VQE)
.iterative() # Use the iterative VQE
.standard() # Narrow: pick the standard iterative VQE (FAST-VQE)
.with_options(adaptive_vqe_options) # Use the user-defined iterative VQE options
.choose_minimizer() # Start sub-choice: Choose the gate parameter minimizer
.quick_default() # Perform the sub-choice selection - Here we pick the greedy and quick minimizer
.choose_data_persister_manager() # Start sub-choice: Choose the data persister manager
.file_persister( # Perform the sub-choice selection - Here we pick file persister
directory=directory, # Directory where data files will be saved
extension=".qdk", # File extension for the data files
do_save=True, # Whether to save data to files
load_policy="fallback", # Load data if available, otherwise compute
overwriting_policy="overwrite", # Overwrite if filename already exist
padding_width=3, # Padding width for file numbering (001, 002, ...)
)
.create() # Create the calculator instance
)
We build the FAST-VQE calculator with options suitable for projection-based embedding.
Here we set a maximum of 100 iterations, and we use a file_persister, in case we want to restart the calculation later.
The problem builder
# Build the ground state problem.
problem_builder = (
qc.problem_builder_creator() # Start creating a problem builder
.ground_state() # Narrow: pick ground state problem
.projective_embedding() # Narrow: pick Projective Embedding ground state problem
.choose_full_system_solver() # Start sub-choice: Choose the full system solver
.dft( # Perform the sub-choice selection - Here we pick DFT as the full system solver
options=qc.options.DftCalculatorOptions(
functional="b3lyp", # The exchange-correlation functional (default is "b3lyp")
convergence_tolerance=1e-9, # Energy convergence tolerance
verbose=10, # Set the SCF verbosity level
)
)
.choose_localizer() # Start sub-choice: Choose the orbital localizer
.pipek_mezey( # Perform the sub-choice selection - Here we pick Pipek-Mezey localizer
population_method="meta-lowdin",
options=qc.options.LocalizationOptions(
max_iterations=20, # The max. number of iterations in each macro iteration.
convergence_tolerance=1e-6, # Converge threshold.
),
)
.choose_orbital_assigner() # Start sub-choice: Choose the orbital assigner
.total_weight( # Perform the sub-choice selection - Here we pick total weight orbital assigner
assignment_tolerance="kmeans_midpoint" # Cluster weights into two clusters using a minimal k-means.
)
.choose_embedded_orbital_calculator() # Start sub-choice: Choose the embedded orbital calculator
.moller_plesset_2( # Perform the sub-choice selection - Here we pick MP2 embedded orbital calculator.
options=qc.options.MollerPlesset2CalculatorOptions(
verbose=10, # Set the verbosity level
frozen_virtual_threshold=100.0, # Disregard virtual orbitals with large occupation numbers.
)
)
.choose_projector_builder() # Start sub-choice: Choose the projector builder
.manby( # Perform the sub-choice selection - Here we pick Manby's level shift projector
level_shift_parameter=1e5 # The level shift parameter
)
.choose_data_persister_manager() # Start sub-choice: Choose the data persister manager
.file_persister( # Perform the sub-choice selection - Here we pick file persister
directory=directory, # Directory where data files will be saved
extension=".qdk", # File extension for the data files
do_save=True, # Whether to save data to files
load_policy="fallback", # Load data if available, otherwise compute
overwriting_policy="rename", # Rename files if filename already exist
padding_width=3, # Padding width for file numbering (001, 002, ...)
)
.with_cube_generator(
cube_generator=cube_generator,
)
.add_problem_modifier() # Add a problem modifier
.active_space( # Specify the problem modifier to add - Here we pick the active space modifier
number_of_active_spatial_orbitals=number_of_active_spatial_orbitals,
number_of_active_alpha_electrons=number_of_active_spatial_orbitals
// 2, # Number of active alpha electrons
)
.create() # Create the problem builder instance
)
ground_state_problem = problem_builder.build_unrestricted(molecular_configuration)
We build the projection-based embedding problem builder. This problem builder have more options than the standard problem builder, as it needs to handle the embedding process.
The example shows some common options, but all these are optional. You can easily use
problem_builder = ( qc.problem_builder_creator() # Start creating a problem builder .ground_state() # Narrow: pick ground state problem .projective_embedding() # Narrow: pick Projective Embedding ground state problem .create() # Create the problem builder instance )
However, as a teaching exercise, we show how to set some of the more common options.
First we specify that the full system solver is DFT, and we use the
B3LYP functional. We also specify the convergence tolerance and
the verbosity.
Next we specify the orbital localization method. Here we use the Pipek-Mezey method, which is a common choice for projection-based embedding.
Once the occupied orbitals are localized, they must be assigned to either the embedded region or the environment. This is done by specifying the orbital assigner.
In this example, we use the total_weight assigner, which assigns orbitals based on
the total weight of the orbital on the embedded atoms. We use a clustering algorithm,
for the assignment.
In the space of the local occupied orbitals and the virtual orbitals, we run a MP2 calculation as the embedded orbital calculator to obtain the natural orbitals in the embedded region, that will be used to run the FAST-VQE calculation. An alternative would be to use canonical Hartree-Fock (HF) orbitals. Again we set the verbosity, and the frozen_virtual_threshold.
In order to not mix the occupied orbitals of the embedded region and the environment, we
use a projection operator. Here we use the manby projector.
We also use a file persister, in case we want to restart the calculation later. This is usefull, as the code will try to reuse as much data as possible from previous runs. So if you change the embedded orbital calculator from MP2 to HF, you do not need to redo the full system DFT calculation, or the orbital localization.
We include a cube file generator, to generate cube files for the electron density. This can be useful for visualizing the electron density of the embedded region and the environment.
Finally we define the active space sizes we want to run the FAST-VQE calculation for and create the instance, and build the ground state problem.
The FAST-VQE calculation
start = time.perf_counter()
result = fast_vqe_calculator.calculate(ground_state_problem)
end = time.perf_counter()
We run the FAST-VQE calculation, with timings.
Make the plots
# Plot the convergence and error relative to FCI reference.
plot_energies(fast_energies, reference_energy=fci_result.total_energy.value, show=True)
We plot the results of the projection-based embedding calculation.
Running the Example
After saving the script as run_projective_embedding.py,
you can run it directly from the command line:
$ python run_projective_embedding.py
You should see alot of output like how the molecular orbitals are assigned to either the embedded region or the environment.
Note that the logging may change between different versions of the QDK.
$ python run_estimators_and_samplers_example.py
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): TotalWeightBasedOrbitalAssigner:
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): MO index Embedded weight Environment weight Assignment
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 0 0.948513 0.051487 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 1 0.948579 0.051421 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 2 0.019310 0.980690
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 3 0.019591 0.980409
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 4 0.006344 0.993656
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 5 0.500060 0.499940 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 6 0.494727 0.505273 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 7 0.074848 0.925152
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 8 0.099742 0.900258
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 9 0.718247 0.281753 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 10 0.104632 0.895368
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 11 0.075698 0.924302
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 12 0.099099 0.900901
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 13 0.634234 0.365766 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 14 0.151014 0.848986
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 15 0.569419 0.430581 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 16 0.633778 0.366222 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 17 0.717875 0.282125 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 18 0.573391 0.426609 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Alternative assignment_tolerance values:
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): - largest_gap_midpoint : 0.322870 -> embeds 10/19
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): TotalWeightBasedOrbitalAssigner:
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): MO index Embedded weight Environment weight Assignment
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 0 0.948513 0.051487 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 1 0.948582 0.051418 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 2 0.019310 0.980690
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 3 0.019591 0.980409
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 4 0.006344 0.993656
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 5 0.500059 0.499941 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 6 0.494728 0.505272 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 7 0.074847 0.925153
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 8 0.099742 0.900258
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 9 0.718247 0.281753 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 10 0.104631 0.895369
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 11 0.075698 0.924302
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 12 0.099099 0.900901
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 13 0.634235 0.365765 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 14 0.151014 0.848986
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 15 0.569419 0.430581 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 16 0.633778 0.366222 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 17 0.717870 0.282130 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): 18 0.573392 0.426608 Embedded
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Alternative assignment_tolerance values:
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): - largest_gap_midpoint : 0.322871 -> embeds 10/19
And the
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Starting active space problem modification.
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Active space Inactive energy = -130.62088772937943 Hartree
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Active space modification completed resulting in:
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Number of active orbitals = 16.
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Number of active alpha electrons = 8.
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Number of active beta electrons = 8.
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Number of inactive orbitals = 45.
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Number of inactive alpha electrons = 2.
2025-10-14 19:50:53 [INFO] (kvantify.qrunch): Number of inactive beta electrons = 2.
You should see 2 plots:
You should also see the requested cube files generated:
data_embedding_total.cubedata_environment_alpha.cube
There should also be a number of restart files in the data directory.
Re-Running the Example
Re-running the example
$ python run_projective_embedding.py
You should experience that the calculation is much faster and with logging you will find lines like:
2025-10-14 20:46:10 [INFO] (kvantify.qrunch): Data loaded from persister for key full_system_mean_field_result.
2025-10-14 20:46:10 [INFO] (kvantify.qrunch): Data loaded from persister for key local_molecular_orbitals.
2025-10-14 20:46:10 [INFO] (kvantify.qrunch): Loaded the ground state CAS modified problem.
2025-10-14 20:46:11 [INFO] (kvantify.qrunch): Loaded adaptive VQE results.
Indicating that it found and reused data from the previous run. Naturally, in that case the timings will be much shorter as it does not need to actually do the FAST-VQE calculation again.