.. _release-v2.0.0:

==========================
What's New in Version 2.0
==========================

Version 2.0 reworks the core data structures around a single symmetry-blocked
storage model (see :ref:`v2-symmetry-blocked`), 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 /
:term:`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
:ref:`Breaking changes and migration <v2-breaking-changes>` section covers the
cases that need changes and the new APIs that replace them.

.. contents:: On this page
   :local:
   :depth: 1


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 :ref:`v2-symmetry-blocked`).
- **Consolidated wavefunction containers.** The five v1 containers collapse into :class:`~qdk_chemistry.data.StateVectorContainer` and :class:`~qdk_chemistry.data.AmplitudeContainer`.
- **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 :term:`ROHF` gains geometric direct minimization.
- **New simulation primitives.** Block-encoding / :term:`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 :class:`~qdk_chemistry.data.MajoranaMapping`, including a Verstraete-Cirac local encoding.
- **Explicit term grouping and expectation estimation.** Pauli grouping is now an opt-in ``term_grouper`` step, and the estimator generalizes to any :class:`~qdk_chemistry.data.QubitOperator`.
- **Resource estimation integration.** Generated circuits hand off directly to the QDK Quantum Resource Estimator via ``Circuit.estimate()`` and ``Circuit.get_qre_application()`` (see :ref:`v2-resource-estimation`).


.. _v2-symmetry-blocked:

The Symmetry-Blocked Data Model
===============================

Orbital coefficients and energies, Hamiltonian integrals, and index sets are now
backed by symmetry-blocked containers
(:class:`~qdk_chemistry.data.symmetry.SymmetryBlockedTensorRank2`,
:class:`~qdk_chemistry.data.symmetry.SymmetryBlockedTensorRank1`, and
:class:`~qdk_chemistry.data.symmetry.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 :class:`~qdk_chemistry.data.symmetry.SymmetryLabel`.

The v1 dense accessors on :class:`~qdk_chemistry.data.Orbitals` remain available
as tested facades over this storage, but now emit a ``DeprecationWarning``. See
:ref:`Migrating Orbitals accessors <v2-migrating-orbitals>` 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
:class:`~qdk_chemistry.data.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
:class:`~qdk_chemistry.data.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
:meth:`~qdk_chemistry.data.Wavefunction.get_container_type` now returns
``"state_vector"`` or ``"amplitude"`` instead of the former ``"sd"`` / ``"cas"`` /
``"sci"`` / ``"mp2"`` / ``"cc"`` strings. See
:ref:`Consolidated container names <v2-container-rename>` for the mapping.


Nuclear Gradients and Hessians
==============================

A new ``nuclear_derivative_calculator`` algorithm computes nuclear derivatives of
the electronic energy directly from a :class:`~qdk_chemistry.data.Structure`.
Two variants are available: an analytic ``qdk`` path built on the internal
:term:`SCF` gradients, and a ``qdk_finite_difference`` path that evaluates
central differences and can additionally assemble a Hessian.

.. literalinclude:: ../_static/examples/python/release_notes_v2_0.py
   :language: python
   :start-after: # start-cell-nuclear-derivatives
   :end-before: # end-cell-nuclear-derivatives

The call returns the energy, the nuclear gradients, and optionally a Hessian and
the converged wavefunction.


Stabilized SCF and ROHF Direct Minimization
============================================

A stabilized :term:`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.

.. literalinclude:: ../_static/examples/python/release_notes_v2_0.py
   :language: python
   :start-after: # start-cell-stabilized-scf
   :end-before: # end-cell-stabilized-scf

Its behavior is controlled through settings (which stability checks to run, the
iteration limit, and how to follow an external instability).

The :term:`QDK` :term:`SCF` backend also gains geometric direct minimization
(:term:`GDM`), including a :term:`DIIS`-to-GDM hybrid path, for
:term:`ROHF` references that are difficult to converge with :term:`DIIS` alone.


New Simulation Primitives
=========================

Several new primitives complement the existing Trotter-Suzuki time-evolution
builders.

**Block encoding / LCU.** A linear-combination-of-unitaries (:term:`LCU`) builder
produces a PREPARE-SELECT block encoding of a Hermitian
:class:`~qdk_chemistry.data.QubitOperator`, and can wrap it as a qubitization walk
operator for phase estimation:

.. literalinclude:: ../_static/examples/python/release_notes_v2_0.py
   :language: python
   :start-after: # start-cell-lcu
   :end-before: # end-cell-lcu

**Hadamard test.** The ``hadamard_test`` algorithm (``create("hadamard_test", "qdk")``)
maps a target :class:`~qdk_chemistry.data.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``:

.. literalinclude:: ../_static/examples/python/release_notes_v2_0.py
   :language: python
   :start-after: # start-cell-zassenhaus
   :end-before: # end-cell-zassenhaus

**Real-time dynamics.** Driven, time-dependent Hamiltonians
(:class:`~qdk_chemistry.data.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
<https://github.com/microsoft/qdk-chemistry/blob/v2.0.0/examples/time_evolve_and_measure.ipynb>`_
and `examples/estimation_ising_2d.ipynb
<https://github.com/microsoft/qdk-chemistry/blob/v2.0.0/examples/estimation_ising_2d.ipynb>`_.


Fermion-to-Qubit Mapping as Data
================================

Fermion-to-qubit encodings are now first-class data. A
:class:`~qdk_chemistry.data.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:

.. literalinclude:: ../_static/examples/python/release_notes_v2_0.py
   :language: python
   :start-after: # start-cell-majorana-mapping
   :end-before: # end-cell-majorana-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
:class:`~qdk_chemistry.data.LatticeGraph` for lattice models. The
:term:`QDK` mapper dispatches to specialized native paths for sparse
(lattice-model) and Cholesky-factorized Hamiltonians, avoiding dense four-center
expansion without changing results. See
:ref:`Fermion-to-qubit mapping <v2-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
:class:`~qdk_chemistry.data.TermPartition` (a
:class:`~qdk_chemistry.data.FlatPartition` or
:class:`~qdk_chemistry.data.LayeredPartition`) to a
:class:`~qdk_chemistry.data.QubitOperator` for downstream consumers:

.. literalinclude:: ../_static/examples/python/release_notes_v2_0.py
   :language: python
   :start-after: # start-cell-term-grouper
   :end-before: # end-cell-term-grouper

Built-in variants are ``qubit_wise_commuting``, ``commuting``, and ``identity``
(one term per group); a NetworkX plugin adds graph-coloring variants. The
:class:`~qdk_chemistry.algorithms.ExpectationEstimator` (registry key
``"expectation_estimator"``) estimates the expectation value of any
:class:`~qdk_chemistry.data.QubitOperator` from the grouping it is given;
pre-group its input to recover the grouping v1 applied automatically.


.. _v2-resource-estimation:

Resource Estimation
===================

Circuits produced by the circuit builders integrate directly with the QDK
Quantum Resource Estimator. A :class:`~qdk_chemistry.data.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:

.. code-block:: bash

   pip install 'qdk-chemistry[qre]'

Circuits from any of the circuit builders can be estimated this way. See
`examples/estimation_ising_2d.ipynb
<https://github.com/microsoft/qdk-chemistry/blob/v2.0.0/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 :term:`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 <https://github.com/microsoft/qdk-chemistry/blob/v2.0.0/examples/qpe_stretched_n2.ipynb>`_.
- **Natural-orbital localizer.** ``orbital_localizer`` / ``qdk_natural_orbitals`` diagonalizes the active-space one-particle :term:`RDM` to produce occupation-ordered natural orbitals.
- **Cholesky MO integrals persisted.** Cholesky Hamiltonians now serialize their :term:`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 its :class:`~qdk_chemistry.data.Settings`, 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 :class:`~qdk_chemistry.data.QuantumErrorProfile` adds a qubit-loss error type.


.. _v2-breaking-changes:

Breaking Changes and Migration
==============================

.. _v2-migrating-orbitals:

Migrating ``Orbitals`` accessors
--------------------------------

Coefficients and energies are returned as symmetry-blocked tensors. Select a
spin channel with a :class:`~qdk_chemistry.data.symmetry.SymmetryLabel` and read
the block (for tensors) or the stored indices (for index sets) directly.

.. code-block:: python

   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:

.. code-block:: python

   # v1 (deprecated)
   n_active = len(orbitals.get_active_space_indices()[0])

   # v2
   n_active = len(orbitals.active_indices().indices(SymmetryLabel([axes.alpha()])))


.. _v2-container-rename:

Consolidated container names
----------------------------

The removed container names remain importable as deprecated aliases:

.. code-block:: python

   # v1 (deprecated alias, emits DeprecationWarning)
   from qdk_chemistry.data import CasWavefunctionContainer

   # v2
   from qdk_chemistry.data import StateVectorContainer

``SlaterDeterminantContainer``, ``CasWavefunctionContainer``, and
``SciWavefunctionContainer`` map to
:class:`~qdk_chemistry.data.StateVectorContainer`; ``MP2Container`` and
``CoupledClusterContainer`` map to
:class:`~qdk_chemistry.data.AmplitudeContainer`.


.. _v2-two-rdm-ordering:

Two-particle RDM spin ordering (action required)
------------------------------------------------

.. warning::

   This is a silent change: the same accessor returns differently ordered data.

The :class:`~qdk_chemistry.data.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 :class:`~qdk_chemistry.data.Hamiltonian`
(also ``(aaaa, aabb, bbbb)`` in alpha-alpha-beta-beta index order), so integrals
and RDMs now share one spin-block convention. The
:class:`~qdk_chemistry.data.Wavefunction` constructor takes the blocks in the same
new order. Update any positional unpacking:

.. code-block:: python

   # 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:

.. code-block:: python

   from qdk_chemistry.data import Configuration

   # v1
   # config = Configuration("2020")

   # v2
   config = Configuration.from_spin_half_string("2020")

The Python factories are
:meth:`~qdk_chemistry.data.Configuration.from_spin_half_string` and
:meth:`~qdk_chemistry.data.Configuration.from_bitstring`.


.. _v2-qubit-mapping:

Fermion-to-qubit mapping
------------------------

The qubit mapper no longer takes an ``encoding`` string. Build a
:class:`~qdk_chemistry.data.MajoranaMapping` and pass it to ``run()``:

.. code-block:: python

   # 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``.


.. _v2-renames:

Renamed classes and keys
------------------------

The following names changed. The former names remain importable or usable as
deprecated aliases that emit a ``DeprecationWarning``:

- ``QubitHamiltonian`` → :class:`~qdk_chemistry.data.QubitOperator`. The class now
  represents any weighted sum of Pauli strings, not only molecular Hamiltonians.
- ``EnergyEstimator`` / ``QdkEnergyEstimator`` →
  :class:`~qdk_chemistry.algorithms.ExpectationEstimator` /
  ``QdkExpectationEstimator``, and the ``"energy_estimator"`` registry key →
  ``"expectation_estimator"``.
- ``TimeEvolutionUnitary`` → :class:`~qdk_chemistry.data.UnitaryRepresentation`,
  and ``TimeEvolutionUnitaryContainer`` →
  :class:`~qdk_chemistry.data.UnitaryContainer`.


Serialization format
--------------------

.. note::

   The following on-disk schemas were bumped from ``0.1.0`` to ``0.2.0``:
   :class:`~qdk_chemistry.data.Orbitals` (and
   :class:`~qdk_chemistry.data.ModelOrbitals`); the
   :class:`~qdk_chemistry.data.CanonicalFourCenterHamiltonianContainer`,
   :class:`~qdk_chemistry.data.SparseHamiltonianContainer`, and
   :class:`~qdk_chemistry.data.CholeskyHamiltonianContainer` Hamiltonian
   containers; :class:`~qdk_chemistry.data.Wavefunction` together with its
   :class:`~qdk_chemistry.data.StateVectorContainer`; and
   :class:`~qdk_chemistry.data.UnitaryRepresentation`. The
   :class:`~qdk_chemistry.data.AmplitudeContainer` wavefunction container, the
   top-level :class:`~qdk_chemistry.data.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
   :doc:`../user/migrating-data-files`) 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_file`` was removed (the public
  ``Hamiltonian::to_fcidump_file(...)`` remains), and
  ``HamiltonianContainer::to_fcidump_file`` became 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 a
  ``std::shared_ptr<const SymmetryProduct>``-based constructor and a
  ``SymmetryBlockedIndexSet``-based constructor.
- The ``NuclearDerivativeCalculator`` run signature takes a single
  ``n_inactive_orbitals`` argument 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
=========

- Fixed an ``int32`` overflow in two-body integral and RDM indexing for large
  active spaces.
- Added missing effective-core-potential (:term:`ECP`) contributions in the
  affected integral path.
- Fixed a bug in the :term:`SCF` stability checker, and a separate
  :term:`ROHF` stability-check bug in the solver.


Infrastructure and Packaging
============================

- **Windows support.** Native builds with MSVC / clang-cl via vcpkg, plus Windows CI coverage.
- **Algorithm result caching.** Passing ``cache=`` to ``run()`` content-addresses a run and returns a stored result on a hit (``FolderCache``, ``TieredCache``; ``force_rerun=True`` bypasses), enabling checkpoint/restart-style reuse across a pipeline.
- **Data-file migration tool.** ``python -m qdk_chemistry.migrate old.hamiltonian.h5 new.hamiltonian.h5`` (and ``qdk_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.
