What’s New in Version 2.0
Version 2.0 reworks the core data structures around a single symmetry-blocked storage model (see The Symmetry-Blocked Data Model), consolidates the wavefunction containers, and standardizes the fermion-to-qubit mapping workflow. Building on those foundations, it adds nuclear gradients and Hessians, more robust SCF convergence, and a broader set of quantum-simulation primitives: block-encoding / LCU qubitization, real-time dynamics for time-dependent Hamiltonians, and direct integration with the QDK Quantum Resource Estimator.
Most v1 code keeps working through tested facades and deprecated aliases; the Breaking changes and migration section covers the cases that need changes and the new APIs that replace them.
Highlights
Symmetry-blocked storage. Orbital coefficients and energies, Hamiltonian integrals, and index sets share one symmetry-blocked representation, so restricted and unrestricted data use the same accessors (discussed in The Symmetry-Blocked Data Model).
Consolidated wavefunction containers. The five v1 containers collapse into
StateVectorContainerandAmplitudeContainer.Nuclear gradients and Hessians. A new nuclear-derivative algorithm returns energies, gradients, and optionally a Hessian and the converged wavefunction.
More robust SCF. A stabilized SCF solver follows internal and external instabilities to a stable reference, and ROHF gains geometric direct minimization.
New simulation primitives. Block-encoding / LCU qubitization, the Hadamard test, Zassenhaus product formulas, and real-time dynamics for driven Hamiltonians join the existing Trotter-Suzuki builders.
Fermion-to-qubit mapping as data. Encodings are carried by
MajoranaMapping, including a Verstraete-Cirac local encoding.Explicit term grouping and expectation estimation. Pauli grouping is now an opt-in
term_grouperstep, and the estimator generalizes to anyQubitOperator.Resource estimation integration. Generated circuits hand off directly to the QDK Quantum Resource Estimator via
Circuit.estimate()andCircuit.get_qre_application()(see Resource Estimation).
The Symmetry-Blocked Data Model
Orbital coefficients and energies, Hamiltonian integrals, and index sets are now
backed by symmetry-blocked containers
(SymmetryBlockedTensorRank2,
SymmetryBlockedTensorRank1, and
SymmetryBlockedIndexSet). Each block
carries its spin (and, in general, other single-particle symmetries) explicitly,
so one representation serves both restricted and unrestricted data, and a spin
channel is selected with a SymmetryLabel.
The v1 dense accessors on Orbitals remain available
as tested facades over this storage, but now emit a DeprecationWarning. See
Migrating Orbitals accessors for the replacement
API.
Consolidated Wavefunction Containers
The five v1 wavefunction containers were merged into two. The single-determinant,
complete-active-space, and selected-configuration-interaction containers were
structurally identical apart from a type tag, and are now the single
StateVectorContainer, which stores a coefficient
vector over a set of determinants and records the physical sector it describes.
The Møller-Plesset and coupled-cluster containers are now the single
AmplitudeContainer, which carries an AmplitudeType
tag (moller_plesset or coupled_cluster) recording how its amplitudes
should be interpreted. Containers of the same type are therefore distinguished by
their stored contents rather than by their class.
The removed names remain importable as deprecated aliases, and
get_container_type() now returns
"state_vector" or "amplitude" instead of the former "sd" / "cas" /
"sci" / "mp2" / "cc" strings. See
Consolidated container names for the mapping.
Nuclear Gradients and Hessians
A new nuclear_derivative_calculator algorithm computes nuclear derivatives of
the electronic energy directly from a Structure.
Two variants are available: an analytic qdk path built on the internal
SCF gradients, and a qdk_finite_difference path that evaluates
central differences and can additionally assemble a Hessian.
# Analytic gradients
grad_calc = create("nuclear_derivative_calculator", "qdk")
energy, gradients, _hessian, wavefunction = grad_calc.run(
structure, charge=0, spin_multiplicity=1, seed_or_basis="sto-3g"
)
# Numeric Hessian (central finite differences)
hess_calc = create(
"nuclear_derivative_calculator", "qdk_finite_difference", compute_hessian=True
)
energy, gradients, hessian, wavefunction = hess_calc.run(
structure, charge=0, spin_multiplicity=1, seed_or_basis="sto-3g"
)
The call returns the energy, the nuclear gradients, and optionally a Hessian and the converged wavefunction.
Stabilized SCF and ROHF Direct Minimization
A stabilized SCF solver wraps an inner solver and a stability checker: after convergence it tests the wavefunction for internal and external instabilities, rotates the orbitals along any instability direction, and re-runs until the reference is stable or an iteration limit is reached.
scf_solver = create("scf_solver", "qdk_stabilized")
energy, wavefunction = scf_solver.run(structure, 0, 1, "sto-3g")
Its behavior is controlled through settings (which stability checks to run, the iteration limit, and how to follow an external instability).
The QDK SCF backend also gains geometric direct minimization (GDM), including a DIIS-to-GDM hybrid path, for ROHF references that are difficult to converge with DIIS alone.
New Simulation Primitives
Several new primitives complement the existing Trotter-Suzuki time-evolution builders.
Block encoding / LCU. A linear-combination-of-unitaries (LCU) builder
produces a PREPARE-SELECT block encoding of a Hermitian
QubitOperator, and can wrap it as a qubitization walk
operator for phase estimation:
builder = create("hamiltonian_unitary_builder", "lcu", quantum_walk=True)
walk = builder.run(qubit_operator)
Hadamard test. The hadamard_test algorithm (create("hadamard_test", "qdk"))
maps a target UnitaryRepresentation into a controlled
circuit and returns the measurement data for an overlap or expectation value.
Zassenhaus product formulas. A Zassenhaus builder expands the time-evolution
operator with higher-order commutator corrections; the step count is set with
num_divisions or selected from a target_accuracy:
builder = create(
"hamiltonian_unitary_builder", "zassenhaus", order=2, num_divisions=4, time=1.0
)
unitary = builder.run(qubit_operator)
Real-time dynamics. Driven, time-dependent Hamiltonians
(TimeDependentQubitHamiltonian) can be evolved with the
Euler evolution-circuit builder (evolution_circuit_builder / euler) and a
first-order Magnus propagator (propagator / magnus); a companion
hamiltonian_simulation / euler_integrator measures observables on the
evolved state. See
examples/time_evolve_and_measure.ipynb
and examples/estimation_ising_2d.ipynb.
Fermion-to-Qubit Mapping as Data
Fermion-to-qubit encodings are now first-class data. A
MajoranaMapping describes how fermionic Majorana
operators map to Pauli strings, and is passed to the qubit mapper at run()
time instead of selecting an encoding by string:
mapping = MajoranaMapping.jordan_wigner(num_modes=n_spin_orbitals)
mapper = create("qubit_mapper", "qdk")
qubit_operator = mapper.run(hamiltonian, mapping)
Built-in constructors cover Jordan-Wigner, Bravyi-Kitaev, Bravyi-Kitaev tree,
parity, and symmetry-conserving Bravyi-Kitaev encodings, plus a new
Verstraete-Cirac local encoding built from a
LatticeGraph for lattice models. The
QDK mapper dispatches to specialized native paths for sparse
(lattice-model) and Cholesky-factorized Hamiltonians, avoiding dense four-center
expansion without changing results. See
Fermion-to-qubit mapping for the migration from the v1
encoding-string workflow.
Term Grouping and Expectation Estimation
Pauli-term grouping is now an explicit term_grouper algorithm rather than an
implicit step inside the estimator. A grouper attaches a
TermPartition (a
FlatPartition or
LayeredPartition) to a
QubitOperator for downstream consumers:
grouper = create("term_grouper", "qubit_wise_commuting")
grouped = grouper.run(qubit_operator)
Built-in variants are qubit_wise_commuting, commuting, and identity
(one term per group); a NetworkX plugin adds graph-coloring variants. The
ExpectationEstimator (registry key
"expectation_estimator") estimates the expectation value of any
QubitOperator from the grouping it is given;
pre-group its input to recover the grouping v1 applied automatically.
Resource Estimation
Circuits produced by the circuit builders integrate directly with the QDK
Quantum Resource Estimator. A Circuit exposes
estimate(), which runs the estimator and returns an EstimatorResult, and
get_qre_application(), which wraps the circuit as a qdk.qre application
(Q#, OpenQASM, or QIR) for use with qdk.qre.estimate() and custom hardware and
factory models. Resource estimation is enabled by the optional qre extra:
pip install 'qdk-chemistry[qre]'
Circuits from any of the circuit builders can be estimated this way. See examples/estimation_ising_2d.ipynb for an end-to-end dynamics resource-estimation walkthrough, including the impact of lattice-aware term grouping on fault-tolerant cost.
Other Improvements
Composable phase estimation. Standard and iterative QPE split into a reusable circuit builder and executor, composed via nested algorithm references; the builder can also generate circuits on its own for resource estimation (
create("qpe_circuit_builder", "qdk_iterative")). See examples/qpe_stretched_n2.ipynb.Natural-orbital localizer.
orbital_localizer/qdk_natural_orbitalsdiagonalizes the active-space one-particle RDM to produce occupation-ordered natural orbitals.Cholesky MO integrals persisted. Cholesky Hamiltonians now serialize their MO three-center integrals, so factorized Hamiltonians round-trip without expanding to dense tensors.
Declarative algorithm nesting. An algorithm can hold sub-algorithms as declarative
(type, variant, settings)references in itsSettings, resolved by the factory at run time.Dense solver path for MACIS CAS. Complete-active-space calculations can use a dense eigensolver in addition to the iterative path.
Double-d-shell valence selection. Automated valence active-space selection can optionally include the double-d-shell orbitals important for transition-metal chemistry.
Qubit loss and richer noise profiles. The QDK sparse-state simulator accepts per-gate noise configuration, and
QuantumErrorProfileadds a qubit-loss error type.
Breaking Changes and Migration
Migrating Orbitals accessors
Coefficients and energies are returned as symmetry-blocked tensors. Select a
spin channel with a SymmetryLabel and read
the block (for tensors) or the stored indices (for index sets) directly.
from qdk_chemistry.data.symmetry import SymmetryLabel, axes
# v1 (deprecated): tuple of dense alpha/beta arrays
alpha_c, beta_c = orbitals.get_coefficients()
alpha_e, beta_e = orbitals.get_energies()
alpha_active, beta_active = orbitals.get_active_space_indices()
# v2: symmetry-blocked tensors, selected per spin channel by SymmetryLabel
alpha = SymmetryLabel([axes.alpha()])
beta = SymmetryLabel([axes.beta()])
coefficients = orbitals.coefficients() # SymmetryBlockedTensorRank2
energies = orbitals.energies() # SymmetryBlockedTensorRank1
alpha_c = coefficients.block([alpha, alpha]) # alpha block
beta_c = coefficients.block([beta, beta]) # beta block
alpha_e = energies.block([alpha])
alpha_active = orbitals.active_indices().indices(alpha)
For a model (symmetry-free) system the trivial label SymmetryLabel([]) selects
the single block instead of an alpha/beta label. For the common case of counting
active or inactive orbitals, take the length of the per-channel index list:
# v1 (deprecated)
n_active = len(orbitals.get_active_space_indices()[0])
# v2
n_active = len(orbitals.active_indices().indices(SymmetryLabel([axes.alpha()])))
Consolidated container names
The removed container names remain importable as deprecated aliases:
# v1 (deprecated alias, emits DeprecationWarning)
from qdk_chemistry.data import CasWavefunctionContainer
# v2
from qdk_chemistry.data import StateVectorContainer
SlaterDeterminantContainer, CasWavefunctionContainer, and
SciWavefunctionContainer map to
StateVectorContainer; MP2Container and
CoupledClusterContainer map to
AmplitudeContainer.
Two-particle RDM spin ordering (action required)
Warning
This is a silent change: the same accessor returns differently ordered data.
The Wavefunction spin-resolved two-particle RDM
accessor get_active_two_rdm_spin_dependent() now returns its blocks as
(aaaa, aabb, bbbb) (previously (aabb, aaaa, bbbb)), and the aabb
block is now stored in alpha-alpha-beta-beta index order (previously
alpha-beta-alpha-beta). This reorders the blocks to match the two-electron
integral block order used throughout Hamiltonian
(also (aaaa, aabb, bbbb) in alpha-alpha-beta-beta index order), so integrals
and RDMs now share one spin-block convention. The
Wavefunction constructor takes the blocks in the same
new order. Update any positional unpacking:
# v1
aabb, aaaa, bbbb = wavefunction.get_active_two_rdm_spin_dependent()
# v2
aaaa, aabb, bbbb = wavefunction.get_active_two_rdm_spin_dependent()
Configuration factories
The string and bitset constructors were replaced by explicit factory methods:
from qdk_chemistry.data import Configuration
# v1
# config = Configuration("2020")
# v2
config = Configuration.from_spin_half_string("2020")
The Python factories are
from_spin_half_string() and
from_bitstring().
Fermion-to-qubit mapping
The qubit mapper no longer takes an encoding string. Build a
MajoranaMapping and pass it to run():
# v1
# mapper = create("qubit_mapper", "qdk") # configured via encoding="jordan-wigner"
# qubit_operator = mapper.run(hamiltonian)
# v2
mapping = MajoranaMapping.jordan_wigner(num_modes)
mapper = create("qubit_mapper", "qdk")
qubit_operator = mapper.run(hamiltonian, mapping)
The standalone EncodingMismatchError and validate_encoding_compatibility
helpers were removed in favor of this mapping-centric flow; importing them still
works but emits a DeprecationWarning.
Renamed classes and keys
The following names changed. The former names remain importable or usable as
deprecated aliases that emit a DeprecationWarning:
QubitHamiltonian→QubitOperator. The class now represents any weighted sum of Pauli strings, not only molecular Hamiltonians.EnergyEstimator/QdkEnergyEstimator→ExpectationEstimator/QdkExpectationEstimator, and the"energy_estimator"registry key →"expectation_estimator".TimeEvolutionUnitary→UnitaryRepresentation, andTimeEvolutionUnitaryContainer→UnitaryContainer.
Serialization format
Note
The following on-disk schemas were bumped from 0.1.0 to 0.2.0:
Orbitals (and
ModelOrbitals); the
CanonicalFourCenterHamiltonianContainer,
SparseHamiltonianContainer, and
CholeskyHamiltonianContainer Hamiltonian
containers; Wavefunction together with its
StateVectorContainer; and
UnitaryRepresentation. The
AmplitudeContainer wavefunction container, the
top-level Hamiltonian, and all other schemas are
unchanged at 0.1.0. An affected file no longer loads directly from data
written by an earlier release; upgrade it with the shipped converter
(python -m qdk_chemistry.migrate old.hamiltonian.h5 new.hamiltonian.h5; see
Migrating data files between serialization versions) or re-generate it.
C++ API changes
These changes affect the C++ library directly; Python users are insulated by the bindings above.
The redundant private
Hamiltonian::_to_fcidump_filewas removed (the publicHamiltonian::to_fcidump_file(...)remains), andHamiltonianContainer::to_fcidump_filebecame a concrete virtual.The Cholesky Hamiltonian container now derives from
HamiltonianContainer(no longer from the four-center container) and its dense constructor takes three-center integrals plus optional AO Cholesky vectors.ModelOrbitals(size_t, bool)was replaced by astd::shared_ptr<const SymmetryProduct>-based constructor and aSymmetryBlockedIndexSet-based constructor.The
NuclearDerivativeCalculatorrun signature takes a singlen_inactive_orbitalsargument in place of the earlier active-electron pair.
Deprecation Policy
Deprecated v1 accessors and import aliases continue to work in this release and are
covered by tests; using them emits a DeprecationWarning (for renamed classes
such as QubitHamiltonian, on construction) to flag the migration path.
They are transitional and scheduled for removal in a future release.
Bug Fixes
Infrastructure and Packaging
Windows support. Native builds with MSVC / clang-cl via vcpkg, plus Windows CI coverage.
Algorithm result caching. Passing
cache=torun()content-addresses a run and returns a stored result on a hit (FolderCache,TieredCache;force_rerun=Truebypasses), enabling checkpoint/restart-style reuse across a pipeline.Data-file migration tool.
python -m qdk_chemistry.migrate old.hamiltonian.h5 new.hamiltonian.h5(andqdk_chemistry.migrate.convert_file) upgrade serialized files across serialization versions and between JSON and HDF5.Updated backend compatibility. Support for newer
qdk(Q# 1.29+) API migrations and newer PyQIR versions.Release examples bundle. The release pipeline packages the
examples/directory as a downloadable archive.