Source code for qdk_chemistry.plugins.qiskit.conversion

"""Conversion utilities for QDK Chemistry to Qiskit interoperability.

This module provides functions to convert QDK Chemistry objects into Qiskit-compatible
representations, particularly for quantum circuit simulation and state preparation.
"""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import numpy as np

from qdk_chemistry import data

__all__ = ["create_statevector_from_wavefunction"]


[docs] def create_statevector_from_wavefunction(wavefunction: data.Wavefunction, normalize: bool = True) -> np.ndarray: """Create a Qiskit-compatible statevector from a QDK Chemistry wavefunction. This function converts a QDK Chemistry wavefunction into a dense statevector representation suitable for use with Qiskit quantum circuit simulators. The encoding uses a little-endian qubit ordering convention where each spatial orbital is mapped to two qubits (one for alpha spin, one for beta spin): - Lower qubits (0 to num_orbitals-1): alpha spin orbitals - Upper qubits (num_orbitals to 2*num_orbitals-1): beta spin orbitals Each determinant in the wavefunction is mapped to its corresponding basis state index, and the wavefunction coefficient is placed at that index in the statevector. Args: wavefunction: The wavefunction to convert to statevector representation. Must have a defined active space with the same number of alpha and beta orbitals. normalize: Whether to normalize the resulting statevector to unit norm. Default is True. Returns: numpy.ndarray: Dense complex statevector of size 2^(2*num_active_orbitals). The dtype is always complex128, even if the wavefunction has real coefficients. Examples: >>> from qiskit.quantum_info import Statevector >>> # Assuming we have a wavefunction already >>> sv_array = create_statevector_from_wavefunction(wavefunction) >>> qiskit_sv = Statevector(sv_array) >>> print(f"Statevector dimension: {len(sv_array)}") """ orbitals = wavefunction.get_orbitals() indices, _ = orbitals.get_active_space_indices() num_orbs = len(indices) num_qubits = num_orbs * 2 dim = 1 << num_qubits # 2^num_qubits # Initialize statevector as complex array statevector = np.zeros(dim, dtype=np.complex128) # Get determinants and coefficients determinants = wavefunction.get_active_determinants() coefficients = wavefunction.get_coefficients() coeffs_array = np.array(coefficients) # Fill statevector for i, det in enumerate(determinants): # Convert configuration to statevector index index = _configuration_to_statevector_index(det, num_orbs) statevector[index] = coeffs_array[i] # Normalize if requested if normalize: norm = np.linalg.norm(statevector) if norm > 1e-15: statevector /= norm return statevector
def _configuration_to_statevector_index(configuration: data.Configuration, num_orbitals: int) -> int: """Convert a Configuration to its corresponding integer index in the statevector array. This function maps an electronic configuration (orbital occupation pattern) to its position in a dense statevector representation. The encoding uses little-endian qubit ordering where alpha electrons occupy lower-indexed qubits and beta electrons occupy higher-indexed qubits. The qubit layout for n spatial orbitals is: Qubits: [2n-1, 2n-2, ..., n+1, n] [n-1, n-2, ..., 1, 0] beta orbitals alpha orbitals Example: Configuration "2ud0" with 4 orbitals maps to: - Orbital 0: doubly occupied - Orbital 1: alpha - Orbital 2: beta - Orbital 3: empty Qubit layout: Qubits: 7 6 5 4 | 3 2 1 0 beta | alpha 3 2 1 0 | 3 2 1 0 0 1 0 1 | 0 0 1 1 As binary (little-endian): 01010011 = 64 + 16 + 2 + 1 = 83 Args: configuration (Configuration): The electronic configuration to convert. This object encodes the occupation of each orbital (unoccupied, alpha, beta, or doubly occupied). num_orbitals (int): Number of spatial orbitals to use from the configuration. This allows extracting a subset for active space calculations. Returns: int: The statevector index (0 to 2^(2*num_orbitals) - 1) corresponding to this configuration in the computational basis. Raises: RuntimeError: If num_orbitals exceeds the configuration's capacity. """ # Get binary strings for alpha and beta alpha_str, beta_str = configuration.to_binary_strings(num_orbitals) index = 0 # Process alpha electrons (lower bits) # Little-endian: bit i corresponds to qubit i for i, bit in enumerate(alpha_str): if bit == "1": index |= 1 << i # Process beta electrons (upper bits, offset by num_orbitals) for i, bit in enumerate(beta_str): if bit == "1": index |= 1 << (num_orbitals + i) return index