What’s New in Version 2.1

Version 2.1 adds geometry optimization as a first-class algorithm type, isolates the vendored Q# utilities in a dedicated context so that importing QDK/Chemistry no longer mutates global interpreter state, and corrects the phase-to-energy convention used when phase estimation runs on a time-evolution unitary.

There are no breaking API changes relative to 2.0, but the phase-convention fix changes the numerical results of time-evolution QPE; see Time-evolution phase convention.

Highlights

  • Geometry optimization. A new geometry_optimizer algorithm type relaxes molecular structures on top of the nuclear derivative calculators introduced in 2.0, with a geomeTRIC-backed implementation shipped as an optional plugin.

  • Isolated Q# context. The vendored Q# utilities load into a private qdk.Context instead of the global qsharp interpreter, and four helpers in qdk_chemistry.utils.qsharp expose that context for user-defined Q# operations.

  • Corrected time-evolution phase convention. Energies recovered from \(U = e^{-iHt}\) now use \(E = -\theta / t\), and the bits_msb_first field of QpeResult from iterative QPE is genuinely most-significant-bit first.

  • Python 3.14 Qiskit support. The qiskit-extras extra now installs on Python 3.14.

Geometry Optimization

Version 2.0 added nuclear gradients and finite-difference Hessians, but left the optimization loop to the caller. Version 2.1 closes that gap with a geometry_optimizer algorithm type that drives a nuclear derivative calculator to a stationary point.

Optimizers follow the usual factory and settings conventions and are created by name:

# Structure coordinates are in Bohr
structure = Structure([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]], [1, 1])

optimizer = create("geometry_optimizer", "geometric", max_iterations=20)

The energy and gradient engine is itself an algorithm reference, so any registered nuclear derivative calculator can drive the optimization:

derivative_ref = AlgorithmRef("nuclear_derivative_calculator", "qdk_finite_difference")
derivative_ref.set("finite_difference_step", 1.0e-2)
optimizer.settings().set("derivative_calculator", derivative_ref)

Shared settings on GeometryOptimizerSettings are derivative_calculator, max_iterations (default 300), and compute_hessian (default False; when True, a Hessian is evaluated at the converged geometry and returned).

energy, optimized_structure, hessian, wavefunction = optimizer.run(
    structure, charge=0, spin_multiplicity=1, input="sto-3g"
)

run() takes an initial Structure, the charge and spin multiplicity, and an input that is either a basis-set name, a BasisSet, an Orbitals, or a Wavefunction. It returns the converged energy, the optimized structure, an optional NuclearHessian, and the optional converged wavefunction. The optional trailing n_inactive_orbitals argument excludes doubly occupied orbitals from the active space used by the derivative calculator.

geomeTRIC plugin

The bundled "geometric" implementation wraps geomeTRIC and is enabled automatically when the package is installed:

pip install 'qdk-chemistry[plugins]'

It adds transition-state search (transition_state), a choice of coordinate system (optimizer, one of tric, tric-p, dlc, hdlc, prim, cart), the five geomeTRIC convergence thresholds (convergence_energy, convergence_rms_gradient, convergence_max_gradient, convergence_rms_displacement, convergence_max_displacement), and print_level. As with geomeTRIC itself, all five convergence criteria must be satisfied together.

Q# Context Management

Previously, importing qdk_chemistry initialized the process-global Q# interpreter and forced its target profile to Base, which silently invalidated Q# code the caller had already imported. The vendored utilities now load into a dedicated qdk.Context owned by the library, and importing qdk_chemistry leaves the caller’s global qsharp state untouched.

QDK requires that composed Q# callables belong to the same context, so qdk_chemistry.utils.qsharp exposes that context directly:

  • get_qsharp_context() returns the shared context; define your own Q# operations against it so they compose with the chemistry circuit builders.

  • create_qsharp_context() builds a fresh context preloaded with the chemistry utilities, forwarding target_profile and other qdk.Context options.

  • set_qsharp_context() installs a caller-supplied context process-wide (pass None to restore the default).

  • use_qsharp_context() is a context manager that overrides the active context on the current thread only.

See Shared Q# context for worked examples of bringing your own Q# operation or your own context.

Bug Fixes

Time-evolution phase convention

For \(U(t) = e^{-iHt}\) an eigenstate of energy \(E\) accumulates phase \(e^{-iEt}\), so phase estimation measures \(\varphi = (-Et / 2\pi) \bmod 1\). PauliProductFormulaContainer.eigenvalue_from_phase previously inverted this as \(E = \theta / t\), dropping the sign. It now returns \(E = -\theta / t\).

This affects any phase estimation run — standard or iterative — whose unitary comes from a product-formula builder; qubitization-based runs, which recover \(E = \lambda\cos(\theta)\), are unchanged. Energies previously reported for time-evolution QPE have the wrong sign and should be recomputed.

Two related iterative QPE defects are fixed alongside it:

  • The feedback phase applied to each iteration circuit is now negated to match the same convention.

  • The bits_msb_first field of QpeResult is now reversed before being stored. Kitaev’s iteration extracts bits least-significant first, so the field previously held them in the opposite order to its name.

Other fixes

  • QpeResult files are now understood by python -m qdk_chemistry.migrate, which drops the obsolete evolution-time field while preserving the result. The qpe_result type tag participates in the name.type.ext filename convention. See Migrating data files between serialization versions.

  • The migration converter rejects v1 TimeEvolutionUnitary files explicitly: they do not carry the scale required by the current UnitaryRepresentation schema and must be regenerated.

  • The geomeTRIC plugin restores the host application’s root logging handlers and level after geomeTRIC reconfigures logging.

  • Fixed a flaky cube-file generation test.

Infrastructure and Packaging

  • Python 3.14 Qiskit support. qiskit-extras now installs on Python 3.14. qiskit-aer is omitted on Linux ARM64 (aarch64) for that version only, because no cp314 manylinux aarch64 wheel is published yet; all other platforms install the full set.

  • New plugins dependency. geometric>=1.0 joins PySCF in the plugins extra.