diff --git a/docs/source/_static/examples/python/circuit.py b/docs/source/_static/examples/python/circuit.py index a2685f5f5..e44282321 100644 --- a/docs/source/_static/examples/python/circuit.py +++ b/docs/source/_static/examples/python/circuit.py @@ -104,7 +104,7 @@ _, wfn_cas = create("multi_configuration_calculator").run(ham, 1, 1) # StatePreparation produces a Circuit with a native Q# factory -state_prep = create("state_prep", "sparse_isometry_gf2x") +state_prep = create("state_prep", "sparse_isometry") circuit = state_prep.run(wfn_cas) # Inspect the Q# circuit (prune unused qubits for clarity) diff --git a/docs/source/_static/examples/python/phase_estimation.py b/docs/source/_static/examples/python/phase_estimation.py index ce44385de..6ee9034ee 100644 --- a/docs/source/_static/examples/python/phase_estimation.py +++ b/docs/source/_static/examples/python/phase_estimation.py @@ -66,7 +66,7 @@ qubit_ham = qubit_mapper.run(hamiltonian) # 6. State preparation -state_prep = create("state_prep", "sparse_isometry_gf2x") +state_prep = create("state_prep", "sparse_isometry") circuit = state_prep.run(wfn_cas) # 7. Create and run IQPE with nested algorithm settings diff --git a/docs/source/_static/examples/python/quickstart.py b/docs/source/_static/examples/python/quickstart.py index 2956c0c63..128ca7cf7 100644 --- a/docs/source/_static/examples/python/quickstart.py +++ b/docs/source/_static/examples/python/quickstart.py @@ -94,7 +94,7 @@ # start-state-prep-circuit # Generate state preparation circuit for the sparse state via sparse isometry (GF2 + X) -state_prep = create("state_prep", "sparse_isometry_gf2x") +state_prep = create("state_prep", "sparse_isometry") sparse_isometry_circuit = state_prep.run(wfn_sparse) # end-state-prep-circuit diff --git a/docs/source/_static/examples/python/release_notes_v1_1.py b/docs/source/_static/examples/python/release_notes_v1_1.py index db68ce4ed..5c1a40396 100644 --- a/docs/source/_static/examples/python/release_notes_v1_1.py +++ b/docs/source/_static/examples/python/release_notes_v1_1.py @@ -199,7 +199,7 @@ _cas = create("multi_configuration_calculator", "macis_cas") _E_cas, _wfn_cas = _cas.run(_hamiltonian, 1, 1) -_state_prep = create("state_prep", "sparse_isometry_gf2x") +_state_prep = create("state_prep", "sparse_isometry") _circuit = _state_prep.run(_wfn_cas) circuit_executor = create("circuit_executor", "qdk_sparse_state_simulator") diff --git a/docs/source/_static/examples/python/state_preparation.py b/docs/source/_static/examples/python/state_preparation.py index e70b5ebca..34e11edab 100644 --- a/docs/source/_static/examples/python/state_preparation.py +++ b/docs/source/_static/examples/python/state_preparation.py @@ -10,7 +10,7 @@ from qdk_chemistry.algorithms import create # Create a StatePreparation instance -sparse_prep = create("state_prep", "sparse_isometry_gf2x") +sparse_prep = create("state_prep", "sparse_isometry") regular_prep = create("state_prep", "qiskit_regular_isometry") # end-cell-create ################################################################################ @@ -61,6 +61,6 @@ from qdk_chemistry.algorithms import registry print(registry.available("state_prep")) -# ['sparse_isometry_gf2x', 'qiskit_regular_isometry'] +# ['sparse_isometry', 'qiskit_regular_isometry'] # end-cell-list-implementations ################################################################################ diff --git a/docs/source/conf.py b/docs/source/conf.py index 40f48e5b2..d5a047595 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -59,13 +59,13 @@ "sphinx.ext.autosummary", # Create summary tables for modules/classes "sphinx.ext.intersphinx", # Link to other projects' documentation "sphinx.ext.viewcode", # Add links to view source code + "sphinx.ext.napoleon", # Support for Google-style and NumPy-style docstrings # Additional extensions "sphinx_autodoc_typehints", # Better support for Python type annotations "sphinx_inline_tabs", # Support for tabbed content in docs # C++ documentation "breathe", # Bridge between Sphinx and Doxygen # Enable Google-style docstrings parsing - "sphinx.ext.napoleon", # Support for Google-style and NumPy-style docstrings "sphinx.ext.todo", # Support for listing to-dos "sphinx.ext.graphviz", # Support for Graphviz diagrams "sphinxcontrib.bibtex", # Support for bibliographic references diff --git a/docs/source/user/comprehensive/algorithms/state_preparation.rst b/docs/source/user/comprehensive/algorithms/state_preparation.rst index a0a212785..76346dc9a 100644 --- a/docs/source/user/comprehensive/algorithms/state_preparation.rst +++ b/docs/source/user/comprehensive/algorithms/state_preparation.rst @@ -78,7 +78,7 @@ You can discover available implementations programmatically: Sparse Isometry GF2+X ~~~~~~~~~~~~~~~~~~~~~ -.. rubric:: Factory name: ``"sparse_isometry_gf2x"`` +.. rubric:: Factory name: ``"sparse_isometry"`` This method is an optimized approach that leverages sparsity in the target wavefunction. The GF2+X method, a modification of the original sparse isometry work in :cite:`Malvetti2021`, applies GF(2) Gaussian elimination to the binary matrix representation of the state to determine a reduced space representation of the sparse state. This reduced state is then densely encoded via regular isometry :cite:`Christandl2016` on a smaller number of qubits, and finally scattered to the full qubit space using X and :term:`CNOT` gates. These reductions correspond to efficient gate sequences that simplify the preparation basis. By focusing only on non-zero amplitudes, this approach substantially reduces circuit depth and gate count compared with dense isometry methods. This method is native to QDK/Chemistry and is especially efficient for wavefunctions with sparse amplitude structure. diff --git a/examples/extended_hubbard.ipynb b/examples/extended_hubbard.ipynb index b0c952cde..efc020249 100644 --- a/examples/extended_hubbard.ipynb +++ b/examples/extended_hubbard.ipynb @@ -502,7 +502,7 @@ "from qdk.widgets import Circuit\n", "\n", "# Generate state preparation circuit for the sparse state via GF2+X sparse isometry\n", - "state_prep = create(\"state_prep\", \"sparse_isometry_gf2x\")\n", + "state_prep = create(\"state_prep\", \"sparse_isometry\")\n", "state_prep_circuit = state_prep.run(wfn_trial)\n", "\n", "# Visualize the sparse isometry circuit\n", diff --git a/examples/factory_list.ipynb b/examples/factory_list.ipynb index d9f05b7b5..c7501f355 100644 --- a/examples/factory_list.ipynb +++ b/examples/factory_list.ipynb @@ -1134,28 +1134,28 @@ "\n", "\n", "state_prep\n", - "sparse_isometry_gf2x\n", + "sparse_isometry\n", "State preparation using sparse isometry with enhanced GF2+X elimination.\n", "basis_gates\n", "['x', 'y', 'z', 'cx', 'cz', 'id', 'h', 's', 'sdg', 'rz']\n", "\n", "\n", "state_prep\n", - "sparse_isometry_gf2x\n", + "sparse_isometry\n", "State preparation using sparse isometry with enhanced GF2+X elimination.\n", "dense_preparation_method\n", "qdk\n", "\n", "\n", "state_prep\n", - "sparse_isometry_gf2x\n", + "sparse_isometry\n", "State preparation using sparse isometry with enhanced GF2+X elimination.\n", "transpile\n", "True\n", "\n", "\n", "state_prep\n", - "sparse_isometry_gf2x\n", + "sparse_isometry\n", "State preparation using sparse isometry with enhanced GF2+X elimination.\n", "transpile_optimization_level\n", "0\n", diff --git a/examples/interoperability/qiskit/iqpe_no_trotter.py b/examples/interoperability/qiskit/iqpe_no_trotter.py index c35d60d06..98ec70f23 100644 --- a/examples/interoperability/qiskit/iqpe_no_trotter.py +++ b/examples/interoperability/qiskit/iqpe_no_trotter.py @@ -228,7 +228,7 @@ def run_iterative_exact_qpe( active_hamiltonian, list(top_configurations.keys()) ) -sparse_state_prep = create("state_prep", algorithm_name="sparse_isometry_gf2x") +sparse_state_prep = create("state_prep", algorithm_name="sparse_isometry") state_prep = sparse_state_prep.run(sparse_wavefunction).get_qiskit_circuit() state_prep = transpile( state_prep, diff --git a/examples/interoperability/qiskit/iqpe_trotter.py b/examples/interoperability/qiskit/iqpe_trotter.py index 68ea5f8a7..9ef3ba490 100644 --- a/examples/interoperability/qiskit/iqpe_trotter.py +++ b/examples/interoperability/qiskit/iqpe_trotter.py @@ -94,7 +94,7 @@ active_hamiltonian, list(top_configurations.keys()) ) -sparse_state_prep = create("state_prep", algorithm_name="sparse_isometry_gf2x") +sparse_state_prep = create("state_prep", algorithm_name="sparse_isometry") state_prep = sparse_state_prep.run(sparse_wavefunction).get_qiskit_circuit() state_prep = transpile( state_prep, diff --git a/examples/qpe_stretched_n2.ipynb b/examples/qpe_stretched_n2.ipynb index e7fc4ed32..53fc7fc93 100644 --- a/examples/qpe_stretched_n2.ipynb +++ b/examples/qpe_stretched_n2.ipynb @@ -359,7 +359,7 @@ "from qdk.widgets import Circuit\n", "\n", "# Generate state preparation circuit for the sparse state via GF2+X sparse isometry\n", - "state_prep = create(\"state_prep\", \"sparse_isometry_gf2x\")\n", + "state_prep = create(\"state_prep\", \"sparse_isometry\")\n", "sparse_isometry_circuit = state_prep.run(wfn_trial)\n", "\n", "# Visualize the sparse isometry circuit, idle and classical qubits are removed\n", diff --git a/examples/state_prep_energy.ipynb b/examples/state_prep_energy.ipynb index 2b75d9120..fe6bc41ba 100644 --- a/examples/state_prep_energy.ipynb +++ b/examples/state_prep_energy.ipynb @@ -1,463 +1,463 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "115ee185", - "metadata": {}, - "source": [ - "# Using `qdk-chemistry` for multi-reference quantum chemistry state preparation and energy estimation\n", - "\n", - "This notebook demonstrates an end-to-end multi-configurational quantum chemistry workflow using `qdk-chemistry`.\n", - "It covers molecule loading and visualization, self-consistent-field (SCF) calculation, active-space selection, multi-configurational wavefunction generation, quantum state-preparation circuit construction, and measurement circuits for energy estimation.\n", - "\n", - "**Prerequisites:** In addition to [installing `qdk-chemistry`](https://github.com/microsoft/qdk-chemistry/blob/main/INSTALL.md), you will need to install the `jupyter` extra to run this notebook:\n", - "\n", - "```bash\n", - "pip install 'qdk-chemistry[jupyter]'\n", - "```\n", - "\n", - "This installs the additional dependencies required by this notebook (ipykernel, pandas, and pyscf).\n", - "\n", - "---\n", - "\n", - "In many molecular systems—such as bond dissociation or transition-metal complexes—a single electronic configuration cannot describe the true electronic structure.\n", - "These multi-configurational systems exhibit strong electron correlation that challenges mean-field and single-determinant methods like [Hartree–Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) or standard [coupled cluster theory](https://en.wikipedia.org/wiki/Coupled_cluster).\n", - "\n", - "While classical multi-configurational approaches can capture these effects, their computational cost grows exponentially with system size.\n", - "Quantum computers offer a complementary route: they can represent superpositions of many configurations natively and solve these problems with polynomial scaling.\n", - "\n", - "However, near-term fault-tolerant quantum hardware is still in the early stages of growth and scaling.\n", - "To use it effectively, we must compress and optimize chemistry problems before they reach the quantum device.\n", - "Classical methods enable this by identifying essential orbitals through active-space selection, generating approximate wavefunctions for state preparation, and supplying data to optimize quantum circuits for energy estimation.\n", - "\n", - "This notebook focuses on state preparation, where a multi-configurational wavefunction from classical computation is transformed into a quantum circuit.\n", - "State preparation is central to quantum chemistry algorithms such as [Quantum Phase Estimation (QPE)](https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) and also serves as a practical hardware benchmark: preparing complex multi-configurational states tests the fidelity and coherence of quantum hardware.\n", - "\n", - "In the example below, we show how to generate and optimize state preparation circuits, from active-space selection to energy measurement, demonstrating how chemical insight can reduce quantum resource requirements for near-term devices." - ] - }, - { - "cell_type": "markdown", - "id": "d0a5a02f", - "metadata": {}, - "source": [ - "## Loading and visualizing the molecular structure\n", - "\n", - "For this example, we will use the benzene diradical molecule.\n", - "The benzene diradical has two unpaired electrons, making it a good candidate for multi-reference quantum chemistry methods.\n", - "This molecule is also an important intermediate in the [Bergman cyclization reaction](https://en.wikipedia.org/wiki/Bergman_cyclization), a popular reaction in synthetic organic chemistry.\n", - "\n", - "The molecular structure is provided in the [XYZ file format](https://en.wikipedia.org/wiki/XYZ_file_format).\n", - "This cell demonstrates how to load the molecule and visualize its structure." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5436376a", - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "\n", - "from qdk.widgets import MoleculeViewer\n", - "\n", - "from qdk_chemistry.data import Structure\n", - "\n", - "# Read molecular structure from XYZ file\n", - "structure = Structure.from_xyz_file(\n", - " Path(\".\") / \"data/benzene_diradical.structure.xyz\"\n", - ")\n", - "\n", - "# Visualize the molecular structure\n", - "display(MoleculeViewer(molecule_data=structure.to_xyz()))" - ] - }, - { - "cell_type": "markdown", - "id": "f01be634", - "metadata": {}, - "source": [ - "## Generating the molecular orbitals\n", - "\n", - "This step performs a [Hartree-Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) (HF) SCF calculation to generate an approximate initial wavefunction and ground-state energy guess.\n", - "The wavefunction and energy returned by this initial calculation do not provide an accurate description of the system electronic structure; however, they are useful for constructing molecular orbitals.\n", - "The resulting molecular orbitals will be used in subsequent steps for active space selection and multi-configuration calculations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "75d71220", - "metadata": {}, - "outputs": [], - "source": [ - "from qdk_chemistry.algorithms import create\n", - "\n", - "# Perform an SCF calculation, returning the energy and wavefunction\n", - "scf_solver = create(\"scf_solver\")\n", - "E_hf, wfn_hf = scf_solver.run(structure, charge=0, spin_multiplicity=1, basis_or_guess=\"cc-pvdz\")\n", - "print(f\"SCF energy is {E_hf:.3f} Hartree\")\n", - "\n", - "# Display a summary of the molecular orbitals obtained from the SCF calculation\n", - "print(\"SCF Orbitals:\\n\", wfn_hf.get_orbitals().get_summary())" - ] - }, - { - "cell_type": "markdown", - "id": "ec9aef75", - "metadata": {}, - "source": [ - "## Selecting an active space and calculating the multi-configuration wavefunction" - ] - }, - { - "cell_type": "markdown", - "id": "0b3bac6a", - "metadata": {}, - "source": [ - "### Active space selection\n", - "\n", - "Most chemistry applications on quantum computers will require the use of [active spaces](https://en.wikipedia.org/wiki/Complete_active_space) to focus the quantum calculation on a subset of the electrons and orbitals in the system.\n", - "For example, the benzene diradical with the default basis set specified above results in ~100 molecular orbitals, requiring ~200 qubits to represent the full electronic structure problem.\n", - "\n", - "This cell shows how to optimize this calculation by selecting an active space from the valence molecular orbitals calculated in the previous SCF step, focusing on the [frontier orbitals](https://en.wikipedia.org/wiki/Frontier_molecular_orbital_theory) that are most relevant to molecular reactivity." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2f5ea942", - "metadata": {}, - "outputs": [], - "source": [ - "# Select active space (6 electrons in 6 orbitals for benzene diradical) to choose most chemically relevant orbitals\n", - "active_space_selector = create(\"active_space_selector\", algorithm_name=\"qdk_valence\",\n", - " num_active_electrons=6, num_active_orbitals=6)\n", - "active_wfn = active_space_selector.run(wfn_hf)\n", - "active_orbitals = active_wfn.get_orbitals()\n", - "\n", - "# Print a summary of the active space orbitals\n", - "print(\"Active Space Orbitals:\\n\", active_orbitals.get_summary())" - ] - }, - { - "cell_type": "markdown", - "id": "dd42b1f0", - "metadata": {}, - "source": [ - "The next cell shows how to visualize the selected active orbitals.\n", - "The drop-down menu provides the ability to select different occupied and virtual orbitals in the active space to visualize their shapes, while the isovalue slider adjusts the surface representation of the orbitals for different electron density levels.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4d8850fc", - "metadata": {}, - "outputs": [], - "source": [ - "from qdk_chemistry.utils.cubegen import generate_cubefiles_from_orbitals\n", - "\n", - "# Generate cube files for the active orbitals\n", - "cube_data = generate_cubefiles_from_orbitals(\n", - " orbitals=active_orbitals,\n", - " grid_size=(40, 40, 40),\n", - " margin=10.0,\n", - " indices=active_orbitals.get_active_space_indices()[0],\n", - " label_maker=lambda p: f\"{'occupied' if p < 20 else 'virtual'}_{p + 1:04d}\"\n", - ")\n", - "\n", - "# Visualize the molecular orbitals together with the structure\n", - "MoleculeViewer(molecule_data=structure.to_xyz(), cube_data=cube_data)" - ] - }, - { - "cell_type": "markdown", - "id": "f8b8e498", - "metadata": {}, - "source": [ - "### Calculate the multi-configuration wavefunction for the active space\n", - "\n", - "Once the active space has been selected, we are ready to solve the electronic structure problem (e.g., [Schrodinger's equation](https://en.wikipedia.org/wiki/Schr%C3%B6dinger_equation#Time-independent_equation)) more accurately than our initial SCF guess.\n", - "However, this requires two steps illustrated in this cell:\n", - "\n", - "1. First, we need to construct the [Hamiltonian](https://en.wikipedia.org/wiki/Hamiltonian_(quantum_mechanics)), which provides the mathematical description of the energy and interactions of the electrons in the active space.\n", - "\n", - "2. Second, we need to construct an improved estimate of the multi-configuration wavefunction and ground-state energy for this Hamiltonian.\n", - "The benzene diradical system in this demonstration is small enough that we can use a Complete Active Space [Configuration Interaction](https://en.wikipedia.org/wiki/Configuration_interaction) (CAS-CI) calculation to obtain the exact quantum mechanical energy and wavefunction for the active space.\n", - "However, for larger systems, the exact solution will not be feasible classically, and approximate methods such as [selected configuration interaction](https://arxiv.org/abs/2303.05688) or [density matrix renormalization group (DMRG)](https://en.wikipedia.org/wiki/Density_matrix_renormalization_group) are required.\n", - "\n", - "Unlike the mean-field Hartree-Fock method, which approximates the wavefunction as a single [Slater determinant](https://en.wikipedia.org/wiki/Slater_determinant), these multi-configuration methods consider all possible electron configurations within the active space, capturing electron correlation effects.\n", - "By subtracting the mean-field Hartree-Fock energy from the correlated multi-configuration energy, we obtain the [correlation energy](https://en.wikipedia.org/wiki/Electronic_correlation) for this active space." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "980dae08", - "metadata": {}, - "outputs": [], - "source": [ - "# Construct Hamiltonian in the active space and print its summary\n", - "hamiltonian_constructor = create(\"hamiltonian_constructor\")\n", - "hamiltonian = hamiltonian_constructor.run(active_orbitals)\n", - "print(\"Active Space Hamiltonian:\\n\", hamiltonian.get_summary())\n", - "\n", - "# Perform CASCI calculation to get the wavefunction and exact energy for the active space\n", - "mc = create(\"multi_configuration_calculator\")\n", - "E_cas, wfn_cas = mc.run(\n", - " hamiltonian, n_active_alpha_electrons=3, n_active_beta_electrons=3\n", - ")\n", - "print(f\"CASCI energy is {E_cas:.3f} Hartree, and the electron correlation energy is {E_cas - E_hf:.3f} Hartree\")" - ] - }, - { - "cell_type": "markdown", - "id": "623cae91", - "metadata": {}, - "source": [ - "## Loading the wavefunction onto a quantum computer\n", - "\n", - "Now that we have calculated the multi-configuration wavefunction for the active space, we can generate a quantum circuit to prepare this state on a quantum computer.\n", - "However, not all parts of the multi-configuration wavefunction contribute equally to the overall state, creating an opportunity for optimization." - ] - }, - { - "cell_type": "markdown", - "id": "ecd4c6d0", - "metadata": {}, - "source": [ - "### Identifying the dominant configurations in the wavefunction\n", - "\n", - "The first task is to understand the sparsity of the wavefunction: how many configurations contribute significantly to the overall state?\n", - "\n", - "This cell demonstrates how to analyze the wavefunction and identify the dominant configurations based on their amplitudes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ebba573c", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "from qdk.widgets import Histogram\n", - "\n", - "# Plot top determinant weights from the CASCI wavefunction\n", - "NUM_DETERMINANTS = 10\n", - "print(f\"Total determinants in the CASCI wavefunction: {len(wfn_cas.get_active_determinants())}\")\n", - "print(f\"Plotting the top {NUM_DETERMINANTS} determinants by weight.\")\n", - "top_configurations = wfn_cas.get_top_determinants(max_determinants=NUM_DETERMINANTS)\n", - "display(Histogram(bar_values={k.to_string(): np.abs(v)**2 for k, v in top_configurations.items()},))" - ] - }, - { - "cell_type": "markdown", - "id": "c73ad541", - "metadata": {}, - "source": [ - "Reducing the wavefunction to these determinants allows us to optimize the computational requirements for loading the quantum computer with a state that has high overlap with the true wavefunction—an important metric for quantum algorithms like QPE.\n", - "However, this reduction of the wavefunction also changes our description of the quantum system, particularly its energy.\n", - "Therefore, for the purposes of benchmarking, we need to recalculate the energy of the truncated wavefunction classically to provide a reference for evaluating the accuracy of the quantum calculation.\n", - "This cell shows how to recalculate this energy." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "560f8554", - "metadata": {}, - "outputs": [], - "source": [ - "# Get top 2 determinants from the CASCI wavefunction to form a sparse wavefunction\n", - "top_configurations = wfn_cas.get_top_determinants(max_determinants=2)\n", - "\n", - "# Compute the reference energy of the sparse wavefunction\n", - "pmc_calculator = create(\"projected_multi_configuration_calculator\")\n", - "E_sparse, wfn_sparse = pmc_calculator.run(hamiltonian, list(top_configurations.keys()))\n", - "\n", - "print(f\"Reference energy for top 2 determinants is {E_sparse:.6f} Hartree\")" - ] - }, - { - "cell_type": "markdown", - "id": "e133afd5", - "metadata": {}, - "source": [ - "### Loading the wavefunction using general state preparation methods\n", - "\n", - "One possibility for loading the multi-configuration wavefunction onto a quantum computer is to use general state preparation approaches such as the [isometry method](https://arxiv.org/abs/1501.06911), as offered in software such as [Qiskit](https://qiskit.org/documentation/stubs/qiskit.circuit.library.Isometry.html).\n", - "While this is a very powerful general-purpose approach, it can be resource intensive, requiring very deep circuits even for modest-sized wavefunctions due to its exponential scaling in the number of qubits.\n", - "This approach also requires numerous fine rotations—operations that can be challenging for near-term fault-tolerant quantum hardware.\n", - "This cell demonstrates how to use the isometry method to generate a quantum circuit for preparing the multi-configuration wavefunction on a quantum computer.\n", - "\n", - "**Note**: the generated circuits are so deep that you will need to adjust the \"zoom\" selection in the visualization window to see the detailed operations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1bb218f", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "from qdk.widgets import Circuit\n", - "\n", - "# Generate state preparation circuit for the sparse state using the regular isometry method (Qiskit)\n", - "state_prep = create(\"state_prep\", \"qiskit_regular_isometry\")\n", - "regular_isometry_circuit = state_prep.run(wfn_sparse)\n", - "\n", - "# Visualize the regular isometry circuit\n", - "display(Circuit(regular_isometry_circuit.get_qsharp_circuit()))\n", - "\n", - "# Print logical qubit counts estimated from the circuit\n", - "df = pd.DataFrame(regular_isometry_circuit.estimate().logical_counts.items(), columns=['Logical Estimate', 'Counts'])\n", - "display(df)" - ] - }, - { - "cell_type": "markdown", - "id": "f577f124", - "metadata": {}, - "source": [ - "### Loading the wavefunction using optimized state preparation methods\n", - "\n", - "As the cell above illustrates, the general isometry method for state preparation can be very resource intensive—requiring thousands of fine rotations for this benzene diradical example.\n", - "However, we can optimize this process by taking advantage of the sparse multi-configuration wavefunction structure, generating much more efficient quantum circuits for state preparation.\n", - "The cell below demonstrates how the `qdk-chemistry` library can be used for optimized wavefunction loading, producing a circuit that is orders of magnitude more efficient than the general isometry method.\n", - "\n", - "The underlying approach is based on a variation of the [sparse isometry method](https://quantum-journal.org/papers/q-2021-03-15-412/pdf/), with new updates specific to `qdk-chemistry` that avoid the use of multi-controlled gates (also challenging for near-term fault-tolerant quantum computers)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9baaf705", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "from qdk.widgets import Circuit\n", - "\n", - "# Generate state preparation circuit for the sparse state via sparse isometry (GF2 + X)\n", - "state_prep = create(\"state_prep\", \"sparse_isometry_gf2x\")\n", - "sparse_isometry_circuit = state_prep.run(wfn_sparse)\n", - "\n", - "# Visualize the sparse isometry circuit\n", - "display(Circuit(sparse_isometry_circuit.get_qsharp_circuit(prune_classical_qubits=True)))\n", - "\n", - "# Print logical qubit counts estimated from the circuit\n", - "df = pd.DataFrame(\n", - " sparse_isometry_circuit.estimate().logical_counts.items(), columns=['Logical Estimate', 'Counts'])\n", - "display(df)" - ] - }, - { - "cell_type": "markdown", - "id": "6635d626", - "metadata": {}, - "source": [ - "Rather than requiring thousands of fine rotations, this optimized approach requires only a single fine rotation for the two-determinant benzene diradical wavefunction—demonstrating the power of chemistry-informed optimizations for quantum state preparation.\n", - "\n", - "Close inspection of the generated circuit shows that it has also reduced our qubit count: several of the qubits have been converted to classical bits, which can be post-processed after measurement.\n", - "We will revisit these classical bits in the next section on energy measurement." - ] - }, - { - "cell_type": "markdown", - "id": "4588419b", - "metadata": {}, - "source": [ - "## Estimating the energy on a quantum computer\n", - "\n", - "For the final stage of this state preparation application benchmark workflow, we estimate the energy of the optimized multi-configuration wavefunction prepared on a quantum computer.\n", - "The first step in this process is mapping the classical Hamiltonian for the active space to a qubit Hamiltonian that can be measured on a quantum computer.\n", - "For this example, we use the [Jordan-Wigner transformation](https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation) to perform this mapping." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2f856f41", - "metadata": {}, - "outputs": [], - "source": [ - "# Prepare qubit Hamiltonian\n", - "qubit_mapper = create(\"qubit_mapper\", algorithm_name=\"qiskit\", encoding=\"jordan-wigner\")\n", - "qubit_hamiltonian = qubit_mapper.run(hamiltonian)" - ] - }, - { - "cell_type": "markdown", - "id": "0dc7b827", - "metadata": {}, - "source": [ - "### Estimating the energy on a quantum computer (simulator)\n", - "\n", - "Finally, we need to generate the measurement circuits required to estimate the energy of the prepared multi-configuration wavefunction on a quantum computer.\n", - "The cell below demonstrates how to generate these measurement circuits using the `qdk-chemistry` library and how to use the QDK simulator to execute them.\n", - "\n", - "This cell provides a set budget of measurements (\"shots\") to be evenly divided between the measurement circuits.\n", - "The measurement process is probabilistic, so we obtain a distribution of results from each circuit.\n", - "These distributions are then combined to produce a final energy estimate, along with an uncertainty (variance) as reported below.\n", - "The uncertainty is directly related to the number of shots used in the measurement process: more shots lead to lower uncertainty." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e9b6616", - "metadata": {}, - "outputs": [], - "source": [ - "# Estimate energy using the optimized circuit and the qubit Hamiltonian\n", - "from qdk_chemistry.data import AlgorithmRef\n", - "\n", - "estimator = create(\n", - " \"energy_estimator\",\n", - " algorithm_name=\"qdk\",\n", - " circuit_executor=AlgorithmRef(\"circuit_executor\", \"qdk_sparse_state_simulator\"),\n", - ")\n", - "energy_results, simulation_data = estimator.run(\n", - " circuit=sparse_isometry_circuit,\n", - " qubit_hamiltonian=qubit_hamiltonian,\n", - " total_shots=600000,\n", - ")\n", - "\n", - "# Print statistic for measured energy\n", - "energy_mean = energy_results.energy_expectation_value + hamiltonian.get_core_energy()\n", - "energy_stddev = np.sqrt(energy_results.energy_variance)\n", - "print(\n", - " f\"Estimated energy from quantum circuit: {energy_mean:.3f} ± {energy_stddev:.3f} Hartree\"\n", - ")\n", - "\n", - "# Print comparison with reference energy\n", - "print(f\"Difference from reference energy: {energy_mean - E_sparse} Hartree\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "qdk_chemistry_venv (3.12.3)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "cells": [ + { + "cell_type": "markdown", + "id": "115ee185", + "metadata": {}, + "source": [ + "# Using `qdk-chemistry` for multi-reference quantum chemistry state preparation and energy estimation\n", + "\n", + "This notebook demonstrates an end-to-end multi-configurational quantum chemistry workflow using `qdk-chemistry`.\n", + "It covers molecule loading and visualization, self-consistent-field (SCF) calculation, active-space selection, multi-configurational wavefunction generation, quantum state-preparation circuit construction, and measurement circuits for energy estimation.\n", + "\n", + "**Prerequisites:** In addition to [installing `qdk-chemistry`](https://github.com/microsoft/qdk-chemistry/blob/main/INSTALL.md), you will need to install the `jupyter` extra to run this notebook:\n", + "\n", + "```bash\n", + "pip install 'qdk-chemistry[jupyter]'\n", + "```\n", + "\n", + "This installs the additional dependencies required by this notebook (ipykernel, pandas, and pyscf).\n", + "\n", + "---\n", + "\n", + "In many molecular systems—such as bond dissociation or transition-metal complexes—a single electronic configuration cannot describe the true electronic structure.\n", + "These multi-configurational systems exhibit strong electron correlation that challenges mean-field and single-determinant methods like [Hartree–Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) or standard [coupled cluster theory](https://en.wikipedia.org/wiki/Coupled_cluster).\n", + "\n", + "While classical multi-configurational approaches can capture these effects, their computational cost grows exponentially with system size.\n", + "Quantum computers offer a complementary route: they can represent superpositions of many configurations natively and solve these problems with polynomial scaling.\n", + "\n", + "However, near-term fault-tolerant quantum hardware is still in the early stages of growth and scaling.\n", + "To use it effectively, we must compress and optimize chemistry problems before they reach the quantum device.\n", + "Classical methods enable this by identifying essential orbitals through active-space selection, generating approximate wavefunctions for state preparation, and supplying data to optimize quantum circuits for energy estimation.\n", + "\n", + "This notebook focuses on state preparation, where a multi-configurational wavefunction from classical computation is transformed into a quantum circuit.\n", + "State preparation is central to quantum chemistry algorithms such as [Quantum Phase Estimation (QPE)](https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm) and also serves as a practical hardware benchmark: preparing complex multi-configurational states tests the fidelity and coherence of quantum hardware.\n", + "\n", + "In the example below, we show how to generate and optimize state preparation circuits, from active-space selection to energy measurement, demonstrating how chemical insight can reduce quantum resource requirements for near-term devices." + ] + }, + { + "cell_type": "markdown", + "id": "d0a5a02f", + "metadata": {}, + "source": [ + "## Loading and visualizing the molecular structure\n", + "\n", + "For this example, we will use the benzene diradical molecule.\n", + "The benzene diradical has two unpaired electrons, making it a good candidate for multi-reference quantum chemistry methods.\n", + "This molecule is also an important intermediate in the [Bergman cyclization reaction](https://en.wikipedia.org/wiki/Bergman_cyclization), a popular reaction in synthetic organic chemistry.\n", + "\n", + "The molecular structure is provided in the [XYZ file format](https://en.wikipedia.org/wiki/XYZ_file_format).\n", + "This cell demonstrates how to load the molecule and visualize its structure." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5436376a", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "from qdk.widgets import MoleculeViewer\n", + "\n", + "from qdk_chemistry.data import Structure\n", + "\n", + "# Read molecular structure from XYZ file\n", + "structure = Structure.from_xyz_file(\n", + " Path(\".\") / \"data/benzene_diradical.structure.xyz\"\n", + ")\n", + "\n", + "# Visualize the molecular structure\n", + "display(MoleculeViewer(molecule_data=structure.to_xyz()))" + ] + }, + { + "cell_type": "markdown", + "id": "f01be634", + "metadata": {}, + "source": [ + "## Generating the molecular orbitals\n", + "\n", + "This step performs a [Hartree-Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) (HF) SCF calculation to generate an approximate initial wavefunction and ground-state energy guess.\n", + "The wavefunction and energy returned by this initial calculation do not provide an accurate description of the system electronic structure; however, they are useful for constructing molecular orbitals.\n", + "The resulting molecular orbitals will be used in subsequent steps for active space selection and multi-configuration calculations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75d71220", + "metadata": {}, + "outputs": [], + "source": [ + "from qdk_chemistry.algorithms import create\n", + "\n", + "# Perform an SCF calculation, returning the energy and wavefunction\n", + "scf_solver = create(\"scf_solver\")\n", + "E_hf, wfn_hf = scf_solver.run(structure, charge=0, spin_multiplicity=1, basis_or_guess=\"cc-pvdz\")\n", + "print(f\"SCF energy is {E_hf:.3f} Hartree\")\n", + "\n", + "# Display a summary of the molecular orbitals obtained from the SCF calculation\n", + "print(\"SCF Orbitals:\\n\", wfn_hf.get_orbitals().get_summary())" + ] + }, + { + "cell_type": "markdown", + "id": "ec9aef75", + "metadata": {}, + "source": [ + "## Selecting an active space and calculating the multi-configuration wavefunction" + ] + }, + { + "cell_type": "markdown", + "id": "0b3bac6a", + "metadata": {}, + "source": [ + "### Active space selection\n", + "\n", + "Most chemistry applications on quantum computers will require the use of [active spaces](https://en.wikipedia.org/wiki/Complete_active_space) to focus the quantum calculation on a subset of the electrons and orbitals in the system.\n", + "For example, the benzene diradical with the default basis set specified above results in ~100 molecular orbitals, requiring ~200 qubits to represent the full electronic structure problem.\n", + "\n", + "This cell shows how to optimize this calculation by selecting an active space from the valence molecular orbitals calculated in the previous SCF step, focusing on the [frontier orbitals](https://en.wikipedia.org/wiki/Frontier_molecular_orbital_theory) that are most relevant to molecular reactivity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f5ea942", + "metadata": {}, + "outputs": [], + "source": [ + "# Select active space (6 electrons in 6 orbitals for benzene diradical) to choose most chemically relevant orbitals\n", + "active_space_selector = create(\"active_space_selector\", algorithm_name=\"qdk_valence\",\n", + " num_active_electrons=6, num_active_orbitals=6)\n", + "active_wfn = active_space_selector.run(wfn_hf)\n", + "active_orbitals = active_wfn.get_orbitals()\n", + "\n", + "# Print a summary of the active space orbitals\n", + "print(\"Active Space Orbitals:\\n\", active_orbitals.get_summary())" + ] + }, + { + "cell_type": "markdown", + "id": "dd42b1f0", + "metadata": {}, + "source": [ + "The next cell shows how to visualize the selected active orbitals.\n", + "The drop-down menu provides the ability to select different occupied and virtual orbitals in the active space to visualize their shapes, while the isovalue slider adjusts the surface representation of the orbitals for different electron density levels.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d8850fc", + "metadata": {}, + "outputs": [], + "source": [ + "from qdk_chemistry.utils.cubegen import generate_cubefiles_from_orbitals\n", + "\n", + "# Generate cube files for the active orbitals\n", + "cube_data = generate_cubefiles_from_orbitals(\n", + " orbitals=active_orbitals,\n", + " grid_size=(40, 40, 40),\n", + " margin=10.0,\n", + " indices=active_orbitals.get_active_space_indices()[0],\n", + " label_maker=lambda p: f\"{'occupied' if p < 20 else 'virtual'}_{p + 1:04d}\"\n", + ")\n", + "\n", + "# Visualize the molecular orbitals together with the structure\n", + "MoleculeViewer(molecule_data=structure.to_xyz(), cube_data=cube_data)" + ] + }, + { + "cell_type": "markdown", + "id": "f8b8e498", + "metadata": {}, + "source": [ + "### Calculate the multi-configuration wavefunction for the active space\n", + "\n", + "Once the active space has been selected, we are ready to solve the electronic structure problem (e.g., [Schrodinger's equation](https://en.wikipedia.org/wiki/Schr%C3%B6dinger_equation#Time-independent_equation)) more accurately than our initial SCF guess.\n", + "However, this requires two steps illustrated in this cell:\n", + "\n", + "1. First, we need to construct the [Hamiltonian](https://en.wikipedia.org/wiki/Hamiltonian_(quantum_mechanics)), which provides the mathematical description of the energy and interactions of the electrons in the active space.\n", + "\n", + "2. Second, we need to construct an improved estimate of the multi-configuration wavefunction and ground-state energy for this Hamiltonian.\n", + "The benzene diradical system in this demonstration is small enough that we can use a Complete Active Space [Configuration Interaction](https://en.wikipedia.org/wiki/Configuration_interaction) (CAS-CI) calculation to obtain the exact quantum mechanical energy and wavefunction for the active space.\n", + "However, for larger systems, the exact solution will not be feasible classically, and approximate methods such as [selected configuration interaction](https://arxiv.org/abs/2303.05688) or [density matrix renormalization group (DMRG)](https://en.wikipedia.org/wiki/Density_matrix_renormalization_group) are required.\n", + "\n", + "Unlike the mean-field Hartree-Fock method, which approximates the wavefunction as a single [Slater determinant](https://en.wikipedia.org/wiki/Slater_determinant), these multi-configuration methods consider all possible electron configurations within the active space, capturing electron correlation effects.\n", + "By subtracting the mean-field Hartree-Fock energy from the correlated multi-configuration energy, we obtain the [correlation energy](https://en.wikipedia.org/wiki/Electronic_correlation) for this active space." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "980dae08", + "metadata": {}, + "outputs": [], + "source": [ + "# Construct Hamiltonian in the active space and print its summary\n", + "hamiltonian_constructor = create(\"hamiltonian_constructor\")\n", + "hamiltonian = hamiltonian_constructor.run(active_orbitals)\n", + "print(\"Active Space Hamiltonian:\\n\", hamiltonian.get_summary())\n", + "\n", + "# Perform CASCI calculation to get the wavefunction and exact energy for the active space\n", + "mc = create(\"multi_configuration_calculator\")\n", + "E_cas, wfn_cas = mc.run(\n", + " hamiltonian, n_active_alpha_electrons=3, n_active_beta_electrons=3\n", + ")\n", + "print(f\"CASCI energy is {E_cas:.3f} Hartree, and the electron correlation energy is {E_cas - E_hf:.3f} Hartree\")" + ] + }, + { + "cell_type": "markdown", + "id": "623cae91", + "metadata": {}, + "source": [ + "## Loading the wavefunction onto a quantum computer\n", + "\n", + "Now that we have calculated the multi-configuration wavefunction for the active space, we can generate a quantum circuit to prepare this state on a quantum computer.\n", + "However, not all parts of the multi-configuration wavefunction contribute equally to the overall state, creating an opportunity for optimization." + ] + }, + { + "cell_type": "markdown", + "id": "ecd4c6d0", + "metadata": {}, + "source": [ + "### Identifying the dominant configurations in the wavefunction\n", + "\n", + "The first task is to understand the sparsity of the wavefunction: how many configurations contribute significantly to the overall state?\n", + "\n", + "This cell demonstrates how to analyze the wavefunction and identify the dominant configurations based on their amplitudes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebba573c", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from qdk.widgets import Histogram\n", + "\n", + "# Plot top determinant weights from the CASCI wavefunction\n", + "NUM_DETERMINANTS = 10\n", + "print(f\"Total determinants in the CASCI wavefunction: {len(wfn_cas.get_active_determinants())}\")\n", + "print(f\"Plotting the top {NUM_DETERMINANTS} determinants by weight.\")\n", + "top_configurations = wfn_cas.get_top_determinants(max_determinants=NUM_DETERMINANTS)\n", + "display(Histogram(bar_values={k.to_string(): np.abs(v)**2 for k, v in top_configurations.items()},))" + ] + }, + { + "cell_type": "markdown", + "id": "c73ad541", + "metadata": {}, + "source": [ + "Reducing the wavefunction to these determinants allows us to optimize the computational requirements for loading the quantum computer with a state that has high overlap with the true wavefunction—an important metric for quantum algorithms like QPE.\n", + "However, this reduction of the wavefunction also changes our description of the quantum system, particularly its energy.\n", + "Therefore, for the purposes of benchmarking, we need to recalculate the energy of the truncated wavefunction classically to provide a reference for evaluating the accuracy of the quantum calculation.\n", + "This cell shows how to recalculate this energy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "560f8554", + "metadata": {}, + "outputs": [], + "source": [ + "# Get top 2 determinants from the CASCI wavefunction to form a sparse wavefunction\n", + "top_configurations = wfn_cas.get_top_determinants(max_determinants=2)\n", + "\n", + "# Compute the reference energy of the sparse wavefunction\n", + "pmc_calculator = create(\"projected_multi_configuration_calculator\")\n", + "E_sparse, wfn_sparse = pmc_calculator.run(hamiltonian, list(top_configurations.keys()))\n", + "\n", + "print(f\"Reference energy for top 2 determinants is {E_sparse:.6f} Hartree\")" + ] + }, + { + "cell_type": "markdown", + "id": "e133afd5", + "metadata": {}, + "source": [ + "### Loading the wavefunction using general state preparation methods\n", + "\n", + "One possibility for loading the multi-configuration wavefunction onto a quantum computer is to use general state preparation approaches such as the [isometry method](https://arxiv.org/abs/1501.06911), as offered in software such as [Qiskit](https://qiskit.org/documentation/stubs/qiskit.circuit.library.Isometry.html).\n", + "While this is a very powerful general-purpose approach, it can be resource intensive, requiring very deep circuits even for modest-sized wavefunctions due to its exponential scaling in the number of qubits.\n", + "This approach also requires numerous fine rotations—operations that can be challenging for near-term fault-tolerant quantum hardware.\n", + "This cell demonstrates how to use the isometry method to generate a quantum circuit for preparing the multi-configuration wavefunction on a quantum computer.\n", + "\n", + "**Note**: the generated circuits are so deep that you will need to adjust the \"zoom\" selection in the visualization window to see the detailed operations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1bb218f", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from qdk.widgets import Circuit\n", + "\n", + "# Generate state preparation circuit for the sparse state using the regular isometry method (Qiskit)\n", + "state_prep = create(\"state_prep\", \"qiskit_regular_isometry\")\n", + "regular_isometry_circuit = state_prep.run(wfn_sparse)\n", + "\n", + "# Visualize the regular isometry circuit\n", + "display(Circuit(regular_isometry_circuit.get_qsharp_circuit()))\n", + "\n", + "# Print logical qubit counts estimated from the circuit\n", + "df = pd.DataFrame(regular_isometry_circuit.estimate().logical_counts.items(), columns=['Logical Estimate', 'Counts'])\n", + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "f577f124", + "metadata": {}, + "source": [ + "### Loading the wavefunction using optimized state preparation methods\n", + "\n", + "As the cell above illustrates, the general isometry method for state preparation can be very resource intensive—requiring thousands of fine rotations for this benzene diradical example.\n", + "However, we can optimize this process by taking advantage of the sparse multi-configuration wavefunction structure, generating much more efficient quantum circuits for state preparation.\n", + "The cell below demonstrates how the `qdk-chemistry` library can be used for optimized wavefunction loading, producing a circuit that is orders of magnitude more efficient than the general isometry method.\n", + "\n", + "The underlying approach is based on a variation of the [sparse isometry method](https://quantum-journal.org/papers/q-2021-03-15-412/pdf/), with new updates specific to `qdk-chemistry` that avoid the use of multi-controlled gates (also challenging for near-term fault-tolerant quantum computers)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9baaf705", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from qdk.widgets import Circuit\n", + "\n", + "# Generate state preparation circuit for the sparse state via sparse isometry (GF2 + X)\n", + "state_prep = create(\"state_prep\", \"sparse_isometry\")\n", + "sparse_isometry_circuit = state_prep.run(wfn_sparse)\n", + "\n", + "# Visualize the sparse isometry circuit\n", + "display(Circuit(sparse_isometry_circuit.get_qsharp_circuit(prune_classical_qubits=True)))\n", + "\n", + "# Print logical qubit counts estimated from the circuit\n", + "df = pd.DataFrame(\n", + " sparse_isometry_circuit.estimate().logical_counts.items(), columns=['Logical Estimate', 'Counts'])\n", + "display(df)" + ] + }, + { + "cell_type": "markdown", + "id": "6635d626", + "metadata": {}, + "source": [ + "Rather than requiring thousands of fine rotations, this optimized approach requires only a single fine rotation for the two-determinant benzene diradical wavefunction—demonstrating the power of chemistry-informed optimizations for quantum state preparation.\n", + "\n", + "Close inspection of the generated circuit shows that it has also reduced our qubit count: several of the qubits have been converted to classical bits, which can be post-processed after measurement.\n", + "We will revisit these classical bits in the next section on energy measurement." + ] + }, + { + "cell_type": "markdown", + "id": "4588419b", + "metadata": {}, + "source": [ + "## Estimating the energy on a quantum computer\n", + "\n", + "For the final stage of this state preparation application benchmark workflow, we estimate the energy of the optimized multi-configuration wavefunction prepared on a quantum computer.\n", + "The first step in this process is mapping the classical Hamiltonian for the active space to a qubit Hamiltonian that can be measured on a quantum computer.\n", + "For this example, we use the [Jordan-Wigner transformation](https://en.wikipedia.org/wiki/Jordan%E2%80%93Wigner_transformation) to perform this mapping." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f856f41", + "metadata": {}, + "outputs": [], + "source": [ + "# Prepare qubit Hamiltonian\n", + "qubit_mapper = create(\"qubit_mapper\", algorithm_name=\"qiskit\", encoding=\"jordan-wigner\")\n", + "qubit_hamiltonian = qubit_mapper.run(hamiltonian)" + ] + }, + { + "cell_type": "markdown", + "id": "0dc7b827", + "metadata": {}, + "source": [ + "### Estimating the energy on a quantum computer (simulator)\n", + "\n", + "Finally, we need to generate the measurement circuits required to estimate the energy of the prepared multi-configuration wavefunction on a quantum computer.\n", + "The cell below demonstrates how to generate these measurement circuits using the `qdk-chemistry` library and how to use the QDK simulator to execute them.\n", + "\n", + "This cell provides a set budget of measurements (\"shots\") to be evenly divided between the measurement circuits.\n", + "The measurement process is probabilistic, so we obtain a distribution of results from each circuit.\n", + "These distributions are then combined to produce a final energy estimate, along with an uncertainty (variance) as reported below.\n", + "The uncertainty is directly related to the number of shots used in the measurement process: more shots lead to lower uncertainty." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e9b6616", + "metadata": {}, + "outputs": [], + "source": [ + "# Estimate energy using the optimized circuit and the qubit Hamiltonian\n", + "from qdk_chemistry.data import AlgorithmRef\n", + "\n", + "estimator = create(\n", + " \"energy_estimator\",\n", + " algorithm_name=\"qdk\",\n", + " circuit_executor=AlgorithmRef(\"circuit_executor\", \"qdk_sparse_state_simulator\"),\n", + ")\n", + "energy_results, simulation_data = estimator.run(\n", + " circuit=sparse_isometry_circuit,\n", + " qubit_hamiltonian=qubit_hamiltonian,\n", + " total_shots=600000,\n", + ")\n", + "\n", + "# Print statistic for measured energy\n", + "energy_mean = energy_results.energy_expectation_value + hamiltonian.get_core_energy()\n", + "energy_stddev = np.sqrt(energy_results.energy_variance)\n", + "print(\n", + " f\"Estimated energy from quantum circuit: {energy_mean:.3f} ± {energy_stddev:.3f} Hartree\"\n", + ")\n", + "\n", + "# Print comparison with reference energy\n", + "print(f\"Difference from reference energy: {energy_mean - E_sparse} Hartree\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qdk_chemistry_venv (3.12.3)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/python/src/qdk_chemistry/__init__.py b/python/src/qdk_chemistry/__init__.py index 926abe581..399835f11 100644 --- a/python/src/qdk_chemistry/__init__.py +++ b/python/src/qdk_chemistry/__init__.py @@ -26,10 +26,6 @@ import sys import warnings -from qdk import TargetProfile -from qdk import init as qdk_init -from qsharp._qsharp import get_config as get_qdk_profile_config - # Import some tools for convenience import qdk_chemistry.constants from qdk_chemistry._core import QDKChemistryConfig @@ -41,19 +37,6 @@ _DOCS_MODE = os.getenv("QDK_CHEMISTRY_DOCS", "0") == "1" -# Initialize Q# interpreter -qdk_config = get_qdk_profile_config() -_QDK_INTERPRETER_PROFILE = qdk_config.get_target_profile() -if _QDK_INTERPRETER_PROFILE == "unrestricted": # Default by Q# if not set - qdk_init(target_profile=TargetProfile.Base) - new_config = get_qdk_profile_config() - _QDK_INTERPRETER_PROFILE = new_config.get_target_profile() - Logger.debug( - f"QDK interpreter profile initialized to '{_QDK_INTERPRETER_PROFILE}'. " - "If you imported Q# code before this module was loaded, please re-import it, " - "or set your target profile before importing qdk_chemistry." - ) - def _setup_resources() -> None: """Set the QDKChemistryConfig resources directory using the runtime helper. diff --git a/python/src/qdk_chemistry/algorithms/registry.py b/python/src/qdk_chemistry/algorithms/registry.py index 3132422de..006872cf6 100644 --- a/python/src/qdk_chemistry/algorithms/registry.py +++ b/python/src/qdk_chemistry/algorithms/registry.py @@ -590,7 +590,10 @@ def _register_python_algorithms(): ) from qdk_chemistry.algorithms.qubit_hamiltonian_solver import DenseMatrixSolver, SparseMatrixSolver # noqa: PLC0415 from qdk_chemistry.algorithms.qubit_mapper import QdkQubitMapper # noqa: PLC0415 - from qdk_chemistry.algorithms.state_preparation import SparseIsometryGF2XStatePreparation # noqa: PLC0415 + from qdk_chemistry.algorithms.state_preparation import SparseIsometryStatePreparation # noqa: PLC0415 + from qdk_chemistry.algorithms.state_preparation.sparse_isometry_binary_encoding import ( # noqa: PLC0415 + SparseIsometryBinaryEncodingStatePreparation, + ) from qdk_chemistry.algorithms.term_grouper import ( # noqa: PLC0415 FullCommutingTermGrouper, IdentityTermGrouper, @@ -598,7 +601,8 @@ def _register_python_algorithms(): ) register(lambda: QdkEnergyEstimator()) - register(lambda: SparseIsometryGF2XStatePreparation()) + register(lambda: SparseIsometryStatePreparation()) + register(lambda: SparseIsometryBinaryEncodingStatePreparation()) register(lambda: DenseMatrixSolver()) register(lambda: SparseMatrixSolver()) register(lambda: QdkQubitMapper()) diff --git a/python/src/qdk_chemistry/algorithms/state_preparation/__init__.py b/python/src/qdk_chemistry/algorithms/state_preparation/__init__.py index 3c7453980..59d59af08 100644 --- a/python/src/qdk_chemistry/algorithms/state_preparation/__init__.py +++ b/python/src/qdk_chemistry/algorithms/state_preparation/__init__.py @@ -9,7 +9,10 @@ # -------------------------------------------------------------------------------------------- from qdk_chemistry.algorithms.state_preparation.sparse_isometry import ( - SparseIsometryGF2XStatePreparation, + SparseIsometryStatePreparation, +) +from qdk_chemistry.algorithms.state_preparation.sparse_isometry_binary_encoding import ( + SparseIsometryBinaryEncodingStatePreparation, ) from qdk_chemistry.algorithms.state_preparation.state_preparation import ( StatePreparation, @@ -18,7 +21,8 @@ ) __all__ = [ - "SparseIsometryGF2XStatePreparation", + "SparseIsometryBinaryEncodingStatePreparation", + "SparseIsometryStatePreparation", "StatePreparationFactory", "StatePreparationSettings", ] diff --git a/python/src/qdk_chemistry/algorithms/state_preparation/sparse_isometry.py b/python/src/qdk_chemistry/algorithms/state_preparation/sparse_isometry.py index db79d5f71..b94356e4b 100644 --- a/python/src/qdk_chemistry/algorithms/state_preparation/sparse_isometry.py +++ b/python/src/qdk_chemistry/algorithms/state_preparation/sparse_isometry.py @@ -6,7 +6,7 @@ prepare only the non-zero amplitude components, significantly reducing circuit depth and gate count compared to dense state preparation methods. -**SparseIsometryGF2XStatePrep**: Enhanced sparse isometry using GF2+X elimination. +**SparseIsometryStatePrep**: Enhanced sparse isometry using GF2+X elimination. This method performs duplicate row removal, all-ones row removal, and diagonal matrix rank reduction besides standard GF2 Gaussian elimination. It tracks both CNOT and X operations for optimal circuit reconstruction and can be more @@ -22,9 +22,9 @@ Algorithm Details: -* SparseIsometryGF2X: Applies enhanced GF2+X elimination (preprocessing + GF2 +* SparseIsometry: Applies enhanced GF2+X elimination (preprocessing + GF2 + postprocessing), performs dense state preparation on the reduced space, - then applies recorded operations (CNOT and X) in reverse to expand back to + then applies recorded operations (CX and X) in reverse to expand back to the full space. """ @@ -42,13 +42,14 @@ from qdk_chemistry.data import Circuit, Wavefunction from qdk_chemistry.data.circuit import QsharpFactoryData from qdk_chemistry.utils import Logger +from qdk_chemistry.utils.binary_encoding import MatrixCompressionOp, MatrixCompressionType from qdk_chemistry.utils.qsharp import QSHARP_UTILS -__all__: list[str] = [] +__all__: list[str] = ["SparseIsometryStatePreparationSettings"] -class SparseIsometryGF2XStatePreparationSettings(StatePreparationSettings): - """Settings for SparseIsometryGF2XStatePreparation.""" +class SparseIsometryStatePreparationSettings(StatePreparationSettings): + """Settings for SparseIsometryStatePreparation.""" def __init__(self): """Initialize the StatePreparationSettings.""" @@ -58,21 +59,21 @@ def __init__(self): ) -class SparseIsometryGF2XStatePreparation(StatePreparation): +class SparseIsometryStatePreparation(StatePreparation): """State preparation using sparse isometry with enhanced GF2+X elimination. This class implements "GF2+X" state preparation for electronic structure problems using the ``gf2x_with_tracking`` function which performs smart preprocessing before GF2 Gaussian elimination. The preprocessing includes: - 1. Removing duplicate rows using CNOT operations + 1. Removing duplicate rows using CX operations 2. Removing all-ones rows using X operations 3. Then performing standard GF2 Gaussian elimination 4. Apply the additional rank reduction if the reduced row-echelon matrix is diagonal This enhanced approach can be more efficient than standard GF2 Gaussian elimination, particularly for matrices with duplicate rows or all-ones rows. The algorithm - tracks both CNOT and X operations for proper circuit reconstruction. + tracks both CX and X operations for proper circuit reconstruction. The algorithm: @@ -80,7 +81,7 @@ class SparseIsometryGF2XStatePreparation(StatePreparation): 2. Converts bitstrings to a binary matrix 3. Applies enhanced GF2+X elimination (duplicate removal + all-ones removal + GF2) 4. Performs dense state preparation on the reduced space - 5. Applies recorded operations (both CNOT and X) in reverse order to expand back to full space + 5. Applies recorded operations (both CX and X) in reverse order to expand back to full space Key References: @@ -89,10 +90,10 @@ class SparseIsometryGF2XStatePreparation(StatePreparation): """ def __init__(self) -> None: - """Initialize the SparseIsometryGF2XStatePreparation.""" + """Initialize the SparseIsometryStatePreparation.""" Logger.trace_entering() super().__init__() - self._settings = SparseIsometryGF2XStatePreparationSettings() + self._settings = SparseIsometryStatePreparationSettings() def _run_impl(self, wavefunction: Wavefunction) -> Circuit: """Prepare a quantum circuit that encodes the given wavefunction using sparse isometry over GF(2^x). @@ -123,15 +124,7 @@ def _run_impl(self, wavefunction: Wavefunction) -> Circuit: "alpha and beta orbitals are not supported for state preparation." ) - coeffs = wavefunction.get_coefficients() - dets = wavefunction.get_active_determinants() - num_orbitals = len(wavefunction.get_orbitals().get_active_space_indices()[0]) - bitstrings = [] - for det in dets: - alpha_str, beta_str = det.to_binary_strings(num_orbitals) - bitstring = beta_str[::-1] + alpha_str[::-1] # Qiskit uses little-endian convention - bitstrings.append(bitstring) - + bitstrings, coeffs = self._wavefunction_to_bitstrings_and_coeffs(wavefunction) # Check for single determinant case after filtering if len(bitstrings) == 1: Logger.info("After filtering, only 1 determinant remains, using single reference state preparation") @@ -148,34 +141,110 @@ def _run_impl(self, wavefunction: Wavefunction) -> Circuit: if self._settings.get("dense_preparation_method") == "qiskit": return self._qiskit_dense_preparation(gf2x_operation_results, statevector_data, n_qubits) + params = self._build_qsharp_state_prep_params(wavefunction) + + qsharp_factory = QsharpFactoryData( + program=QSHARP_UTILS.StatePreparation.MakeStatePreparationCircuit, + parameter=params, + ) + + state_prep_params = QSHARP_UTILS.StatePreparation.StatePreparationParams(**params) + state_prep_op = QSHARP_UTILS.StatePreparation.MakeStatePreparationOp(state_prep_params) + return Circuit(qsharp_factory=qsharp_factory, qsharp_op=state_prep_op, encoding="jordan-wigner") + + def _wavefunction_to_bitstrings_and_coeffs(self, wavefunction: Wavefunction) -> tuple[list[str], np.ndarray]: + """Extract bitstrings and coefficients from a wavefunction. + + Args: + wavefunction: The target wavefunction to prepare. + + Returns: + A tuple containing a list of bitstrings and a corresponding array of coefficients. + + """ + coeffs = wavefunction.get_coefficients() + dets = wavefunction.get_active_determinants() + num_orbitals = len(wavefunction.get_orbitals().get_active_space_indices()[0]) + bitstrings = [] + for det in dets: + alpha_str, beta_str = det.to_binary_strings(num_orbitals) + bitstring = beta_str[::-1] + alpha_str[::-1] # Qiskit uses little-endian convention + bitstrings.append(bitstring) + return bitstrings, coeffs + + def _build_qsharp_state_prep_params(self, wavefunction: Wavefunction) -> dict: + """Build state preparation parameters from a wavefunction. + + Extracts coefficients and determinants, performs GF2+X elimination, + and returns the parameter dict for Q# circuit construction. + + Args: + wavefunction: The target wavefunction to prepare. + + Returns: + A parameter dict for Q# circuit construction. + + """ + bitstrings, coeffs = self._wavefunction_to_bitstrings_and_coeffs(wavefunction) + n_qubits = len(bitstrings[0]) + gf2x_operation_results, statevector_data = self._perform_gf2x(bitstrings, coeffs) # Use QDK dense state preparation - expansion_ops: list[list[int]] = [] + expansion_ops: list[MatrixCompressionOp] = [] for operation in reversed(gf2x_operation_results.operations): - if operation[0] == "cnot": - # operation[1] should be a tuple for CNOT operations + if operation[0] == "cx": if isinstance(operation[1], tuple): target, control = operation[1] - expansion_ops.append([control, target]) + expansion_ops.append(MatrixCompressionOp(MatrixCompressionType.CX, [control, target])) elif operation[0] == "x" and isinstance(operation[1], int): - # operation[1] should be an int for X operations - qubit = operation[1] - expansion_ops.append([qubit]) + expansion_ops.append(MatrixCompressionOp(MatrixCompressionType.X, [operation[1]])) # State vector indexing is in little-endian order, the row map is reversed for Q# convention state_prep_params = QSHARP_UTILS.StatePreparation.StatePreparationParams( rowMap=gf2x_operation_results.row_map[::-1], stateVector=statevector_data.tolist(), - expansionOps=expansion_ops, + expansionOps=[op.to_dict() for op in expansion_ops], numQubits=n_qubits, ) + return vars(state_prep_params) + + def _create_dense(self, params: dict) -> Circuit: + """Create a standalone dense state preparation circuit. + + Args: + params: The parameter dict for Q# circuit construction. + Returns: + A dense state preparation circuit on the reduced qubit subset. + + """ qsharp_factory = QsharpFactoryData( - program=QSHARP_UTILS.StatePreparation.MakeStatePreparationCircuit, - parameter=vars(state_prep_params), + program=QSHARP_UTILS.StatePreparation.MakeDenseStatePreparation, + parameter={ + "rowMap": params["rowMap"], + "stateVector": params["stateVector"], + "numQubits": params["numQubits"], + }, ) + return Circuit(qsharp_factory=qsharp_factory, encoding="jordan-wigner") - state_prep_op = QSHARP_UTILS.StatePreparation.MakeStatePreparationOp(state_prep_params) - return Circuit(qsharp_factory=qsharp_factory, qsharp_op=state_prep_op, encoding="jordan-wigner") + def _create_isometry(self, params: dict) -> Circuit: + """Create a standalone isometry circuit. + + Args: + params: The parameter dict for Q# circuit construction. + + Returns: + A Circuit containing the GF2+X expansion operations. + + """ + qsharp_factory = QsharpFactoryData( + program=QSHARP_UTILS.StatePreparation.MakeExpansion, + parameter={ + "expansionOps": params["expansionOps"], + "numQubits": params["numQubits"], + }, + ) + return Circuit(qsharp_factory=qsharp_factory, encoding="jordan-wigner") def _qiskit_dense_preparation( self, gf2x_operation_results: "GF2XEliminationResult", statevector_data: np.ndarray, num_qubits: int @@ -210,8 +279,8 @@ def _qiskit_dense_preparation( statevector = Statevector(statevector_data) qc.append(QiskitStatePreparation(statevector, normalize=False), gf2x_operation_results.row_map) for operation in reversed(gf2x_operation_results.operations): - if operation[0] == "cnot": - # operation[1] should be a tuple for CNOT operations + if operation[0] == "cx": + # operation[1] should be a tuple for CX operations if isinstance(operation[1], tuple): target, control = operation[1] qc.cx(control, target) @@ -260,7 +329,7 @@ def _perform_gf2x(self, bitstrings: list[str], coeffs: np.ndarray) -> tuple["GF2 Logger.debug(f"Total operations: {len(gf2x_operation_results.operations)}") # Log operations by type - Logger.debug(f"CNOT operations: {[op for op in gf2x_operation_results.operations if op[0] == 'cnot']}") + Logger.debug(f"CX operations: {[op for op in gf2x_operation_results.operations if op[0] == 'cx']}") Logger.debug(f"X operations: {[op for op in gf2x_operation_results.operations if op[0] == 'x']}") # Step 3: Create statevector for the reduced matrix @@ -423,7 +492,7 @@ def _prepare_single_reference_state(self, bitstring: str) -> Circuit: def name(self) -> str: """Return the name of the state preparation method.""" Logger.trace_entering() - return "sparse_isometry_gf2x" + return "sparse_isometry" @dataclass @@ -442,7 +511,7 @@ class GF2XEliminationResult: operations: list[tuple[str, int | tuple[int, int]]] """List of operations in the form: - * ('cnot', (target_row, control_row)) for CNOT operations + * ('cx', (target_row, control_row)) for CX operations * ('x', row_index) for X operations on entire rows All indices refer to original matrix positions. @@ -452,12 +521,17 @@ class GF2XEliminationResult: """Rank of the reduced matrix (number of non-zero rows).""" -def gf2x_with_tracking(matrix: np.ndarray) -> GF2XEliminationResult: +def gf2x_with_tracking( + matrix: np.ndarray, + *, + skip_diagonal_reduction: bool = False, + forward_only: bool = False, +) -> GF2XEliminationResult: """Perform enhanced GF2+X Gaussian elimination with smart preprocessing and X operations. This function implements a smarter approach to GF2 Gaussian elimination by: - 1. First removing duplicate rows using CNOT operations + 1. First removing duplicate rows using CX operations 2. Removing all-ones rows using X operations 3. Then performing standard Gaussian elimination 4. Performing further reduction if the resulting matrix is diagonal @@ -467,6 +541,12 @@ def gf2x_with_tracking(matrix: np.ndarray) -> GF2XEliminationResult: Args: matrix: shape (m, n), binary (0/1) matrix + skip_diagonal_reduction: If True, skip the optional diagonal-to-upper- + staircase rank reduction (step 4). Binary encoding handles the + identity pivot block natively, so the extra CX + X expansion ops + produced by the diagonal reduction are redundant Cliffords. + forward_only: If True, perform forward-only GF2 elimination into row echelon form (REF), + skipping back-substitution entirely. Returns: A dataclass containing GF2+X elimination results. @@ -498,7 +578,7 @@ def gf2x_with_tracking(matrix: np.ndarray) -> GF2XEliminationResult: # Work on a copy to avoid modifying the input matrix_work = matrix.copy() - # Step 1: Remove duplicate rows using CNOT operations + # Step 1: Remove duplicate rows using CX operations matrix_work, row_map, operations = _remove_duplicate_rows_with_cnot(matrix_work, row_map, operations) # Step 2: Remove all-ones rows using X operations @@ -506,31 +586,26 @@ def gf2x_with_tracking(matrix: np.ndarray) -> GF2XEliminationResult: # Step 3: Perform standard Gaussian elimination on the remaining matrix if matrix_work.shape[0] > 0: # Only if there are rows left - # Convert CNOT operations to the format expected by Gaussian elimination - cnot_ops = [] - for op in operations: - if op[0] == "cnot" and isinstance(op[1], tuple): - cnot_ops.append((op[1][0], op[1][1])) - - # Perform Gaussian elimination - m_current, n_current = matrix_work.shape - matrix_processed, updated_row_map, updated_cnot_ops = _perform_gaussian_elimination( - matrix_work, m_current, n_current, row_map, cnot_ops + matrix_processed, updated_row_map, new_cnot_ops = _perform_gaussian_elimination( + matrix_work, row_map, [], forward_only=forward_only ) - # Update operations list with new CNOT operations from Gaussian elimination - for target, control in updated_cnot_ops[len(cnot_ops) :]: # Only add new operations - operations.append(("cnot", (target, control))) + for target, control in new_cnot_ops: + operations.append(("cx", (target, control))) # Remove zero rows and update row_map accordingly matrix_reduced, reduced_row_map, rank = _remove_zero_rows(matrix_processed, updated_row_map) gf2x_results = GF2XEliminationResult( - reduced_matrix=matrix_reduced, row_map=reduced_row_map, col_map=col_map, operations=operations, rank=rank + reduced_matrix=matrix_reduced, + row_map=reduced_row_map, + col_map=col_map, + operations=operations, + rank=rank, ) # Step 4: Check for diagonal matrix and apply further reduction if possible - if rank > 1 and _is_diagonal_matrix(matrix_reduced): + if not forward_only and not skip_diagonal_reduction and rank > 1 and _is_diagonal_matrix(matrix_reduced): Logger.info(f"Detected diagonal matrix with rank {rank}, applying further reduction") gf2x_results = _reduce_diagonal_matrix(matrix_reduced, reduced_row_map, col_map, operations) @@ -574,56 +649,34 @@ def _remove_duplicate_rows_with_cnot( operations_work = operations.copy() n_rows, _ = matrix_work.shape - rows_to_eliminate = [] + rows_to_eliminate: set[int] = set() - # Find duplicate rows + # Find duplicate rows and XOR them to zero immediately for i in range(n_rows): - # Skip rows that are already marked for elimination if i in rows_to_eliminate: continue - row_i = matrix_work[i] - - # Skip all-zero rows as they don't need CNOT operations - if np.all(row_i == 0): + if not np.any(matrix_work[i]): continue - # Look for duplicates of this row for j in range(i + 1, n_rows): if j in rows_to_eliminate: continue - row_j = matrix_work[j] - - # If rows are identical, eliminate the later one - if np.array_equal(row_i, row_j): - # CNOT(control=i, target=j) will make row j become all zeros - operations_work.append(("cnot", (row_map_work[j], row_map_work[i]))) - rows_to_eliminate.append(j) + if np.array_equal(matrix_work[i], matrix_work[j]): + operations_work.append(("cx", (row_map_work[j], row_map_work[i]))) + matrix_work[j] ^= matrix_work[i] + rows_to_eliminate.add(j) Logger.info( - f"Found duplicate row {j} identical to row {i}, adding CNOT({row_map_work[i]}, {row_map_work[j]})" + f"Found duplicate row {j} identical to row {i}, adding CX({row_map_work[i]}, {row_map_work[j]})" ) - # Apply CNOT operations to eliminate duplicate rows - for op in operations_work: - if op[0] == "cnot" and isinstance(op[1], tuple): - # Find the current positions of the target and control rows - target_orig, control_orig = op[1] - target_current = row_map_work.index(target_orig) - control_current = row_map_work.index(control_orig) - - # Apply CNOT: target row = target row XOR control row - matrix_work[target_current] = matrix_work[target_current] ^ matrix_work[control_current] - - # Remove eliminated rows (which should now be all zeros) + # Remove eliminated rows (now all zeros) if rows_to_eliminate: - Logger.info(f"Eliminating {len(rows_to_eliminate)} duplicate rows: {rows_to_eliminate}") + Logger.info(f"Eliminating {len(rows_to_eliminate)} duplicate rows: {sorted(rows_to_eliminate)}") - # Create mask for rows to keep rows_to_keep = [i for i in range(n_rows) if i not in rows_to_eliminate] - - # Update matrix and row mapping matrix_work = matrix_work[rows_to_keep] row_map_work = [row_map_work[i] for i in rows_to_keep] @@ -684,157 +737,118 @@ def _remove_all_ones_rows_with_x( def _perform_gaussian_elimination( matrix: np.ndarray, - num_rows: int, - num_cols: int, row_map: list[int], cnot_ops: list[tuple[int, int]], + *, + forward_only: bool = False, ) -> tuple[np.ndarray, list[int], list[tuple[int, int]]]: - """Perform the main GF2 Gaussian elimination steps on a binary matrix. - - This function implements the core algorithm of GF2 Gaussian elimination by iterating through columns, - finding pivot rows, swapping rows when necessary, and eliminating other entries in each column using XOR operations. + """Perform GF2 Gaussian elimination. Args: - matrix: Binary matrix to reduce (copied, not modified in-place) - num_rows: Number of rows in the matrix - num_cols: Number of columns in the matrix - row_map: Mapping from current to original row indices (copied, not modified) - cnot_ops: List to record CNOT operations (copied, not modified) + matrix: Binary matrix to reduce (copied internally). + row_map: Current-to-original row index mapping (copied internally). + cnot_ops: Existing CNOT operation list (copied internally). + forward_only: If True, perform forward-only elimination into + row echelon form (REF), skipping back-substitution. If + False (default), perform full elimination into RREF. Returns: - A tuple containing ``(updated_matrix, updated_row_map, updated_operations)`` + ``(reduced_matrix, updated_row_map, updated_cnot_ops)`` """ matrix_work = matrix.copy() row_map_work = row_map.copy() cnot_ops_work = cnot_ops.copy() + num_rows, num_cols = matrix_work.shape - row = 0 # current row + pivot_row = 0 for col in range(num_cols): - # Find the first row (row >= current) with a 1 in this column - sel = _find_pivot_row(matrix_work, row, num_rows, col) + sel = _find_pivot_row(matrix_work, pivot_row, col) if sel is None: continue - # Swap current row and selected row if needed - if sel != row: - matrix_work[[row, sel], :] = matrix_work[[sel, row], :] - row_map_work[row], row_map_work[sel] = row_map_work[sel], row_map_work[row] + if sel != pivot_row: + matrix_work[[pivot_row, sel]] = matrix_work[[sel, pivot_row]] + row_map_work[pivot_row], row_map_work[sel] = row_map_work[sel], row_map_work[pivot_row] - # Eliminate all other rows (except the pivot row) in this column - matrix_work, cnot_ops_work = _eliminate_column(matrix_work, num_rows, row, col, row_map_work, cnot_ops_work) + _eliminate_column(matrix_work, pivot_row, col, row_map_work, cnot_ops_work, forward_only=forward_only) - # Move to next row - row += 1 - if row == num_rows: + pivot_row += 1 + if pivot_row == num_rows: break return matrix_work, row_map_work, cnot_ops_work -def _find_pivot_row(matrix: np.ndarray, row: int, num_rows: int, col: int) -> int | None: - """Find the first row with a 1 in the given column for pivot selection. - - This function searches for a suitable pivot row starting from the current row position downward. - It looks for the first row that has a 1 in the specified column, - which can be used as a pivot for Gaussian elimination. +def _find_pivot_row(matrix: np.ndarray, start_row: int, col: int) -> int | None: + """Find the first row at or below ``start_row`` with a 1 in ``col``. Args: - matrix: Binary matrix to search (read-only, not modified) - row: Starting row index to search from (inclusive) - num_rows: Total number of rows in the matrix - col: Column index to check for pivot candidates + matrix: Binary matrix (read-only). + start_row: First row index to consider (inclusive). + col: Column to search. Returns: - Index of the first row with a 1 in the column, or None if no suitable pivot is found in the remaining rows. - - Note: - This function only reads the matrix and does not modify any arguments. - It returns None when no pivot can be found, indicating the column should be skipped. + Row index of the first 1-entry, or ``None`` if the column is + all-zero from ``start_row`` downward. """ - for r in range(row, num_rows): - if matrix[r, col]: - return r - return None + candidates = np.flatnonzero(matrix[start_row:, col]) + return start_row + int(candidates[0]) if candidates.size > 0 else None def _eliminate_column( matrix: np.ndarray, - num_rows: int, pivot_row: int, col: int, row_map: list[int], cnot_ops: list[tuple[int, int]], -) -> tuple[np.ndarray, list[tuple[int, int]]]: - """Eliminate all other rows in the given column using XOR operations. + *, + forward_only: bool = False, +) -> None: + """Eliminate rows in ``col`` using XOR with the pivot row. - This function performs the elimination step of GF2 Gaussian elimination - by XORing the pivot row with all other rows that have a 1 in the current column. + Modifies ``matrix`` and ``cnot_ops`` **in place**. Args: - matrix: Binary matrix to modify (copied, not modified in-place) - num_rows: Number of rows in the matrix - pivot_row: Index of the pivot row (remains unchanged) - col: Column index to eliminate - row_map: Mapping from current to original row indices (read-only) - cnot_ops: List to record CNOT operations (copied, not modified) - - Returns: - tuple[np.ndarray, list[tuple[int, int]]]: Tuple containing: - - * ``updated_matrix``: Matrix after column elimination. - * ``updated_cnot_ops``: Updated list of CNOT operations. + matrix: Binary matrix (modified in place). + pivot_row: Index of the pivot row (unchanged). + col: Column to eliminate. + row_map: Current-to-original row index mapping (read-only). + cnot_ops: Destination list for recorded CNOT operations. + forward_only: If True, only eliminate rows below the pivot + (forward elimination / REF). If False, eliminate all + other rows (full back-substitution / RREF). """ - matrix_work = matrix.copy() - cnot_ops_work = cnot_ops.copy() - - for r in range(num_rows): - if r != pivot_row and matrix_work[r, col]: - matrix_work[r, :] ^= matrix_work[pivot_row, :] - # Record CNOT operation using original matrix indices - cnot_ops_work.append((row_map[r], row_map[pivot_row])) - - return matrix_work, cnot_ops_work + if forward_only: + targets = np.flatnonzero(matrix[pivot_row + 1 :, col]) + pivot_row + 1 + else: + targets = np.flatnonzero(matrix[:, col]) + targets = targets[targets != pivot_row] + for r in targets: + matrix[r] ^= matrix[pivot_row] + cnot_ops.append((row_map[r], row_map[pivot_row])) def _remove_zero_rows(matrix: np.ndarray, row_map: list[int]) -> tuple[np.ndarray, list[int], int]: - """Remove zero rows from the matrix and update row mapping. - - This function creates a new matrix containing only the non-zero rows from the input matrix, - along with an updated row mapping that tracks which original rows correspond to the rows in the reduced matrix. + """Remove all-zero rows from the matrix and update the row mapping. Args: - matrix: Binary matrix to process (read-only, not modified) - row_map: Current mapping from matrix rows to original indices (read-only) + matrix: Binary matrix (read-only). + row_map: Current-to-original row index mapping (read-only). Returns: - tuple[np.ndarray, list[int], int]: Tuple containing: - - * ``matrix_reduced``: New matrix with only non-zero rows. - * ``reduced_row_map``: Updated mapping from reduced matrix rows to original indices. - * ``rank``: Number of non-zero rows (matrix rank). - - Note: - This function does not modify its input arguments. It creates and returns - new objects containing only the non-zero rows and their corresponding mappings. + ``(matrix_reduced, reduced_row_map, rank)`` where ``rank`` is the + number of retained (non-zero) rows. """ - n_rows, _ = matrix.shape - non_zero_rows = [] - reduced_row_map = [] - - for i in range(n_rows): - if not np.all(matrix[i, :] == 0): # Keep non-zero rows - non_zero_rows.append(i) - reduced_row_map.append(row_map[i]) - - # Extract only non-zero rows - matrix_reduced = matrix[non_zero_rows, :] - rank = len(non_zero_rows) - - return matrix_reduced, reduced_row_map, rank + non_zero_indices = np.flatnonzero(np.any(matrix, axis=1)) + return ( + matrix[non_zero_indices], + [row_map[i] for i in non_zero_indices], + int(non_zero_indices.size), + ) def _reduce_diagonal_matrix( @@ -843,157 +857,78 @@ def _reduce_diagonal_matrix( col_map: list[int], operations: list[tuple[str, int | tuple[int, int]]], ) -> GF2XEliminationResult: - """Further reduce a diagonal matrix using CNOT and X operations. + """Reduce a diagonal (identity) matrix by one rank via CX cascade + X. - This function handles the special case where the matrix is diagonal - (square matrix with 1s on diagonal and 0s elsewhere). It applies - sequential CNOT operations to create an all-ones row, then uses - an X operation to eliminate it, reducing the rank by 1. + Applies CX(i, i+1) for i = 0…rank-2, making the last row all-ones, + then X on the last row to zero it, and finally removes that row. - Procedure: - - 1. Apply CNOT(i, i+1) sequentially for i = 0 to rank-2 - 2. This makes the last row (rank-1) become all 1s - 3. Apply X on the last row to make it all 0s - 4. Remove the zero row to reduce rank by 1 + The caller is responsible for verifying ``_is_diagonal_matrix`` first. Args: - matrix: Diagonal binary matrix to reduce - row_map: Current row mapping to original indices - col_map: Column mapping to original indices (unchanged) - operations: List of operations performed so far + matrix: Diagonal binary matrix to reduce. + row_map: Current row mapping to original indices. + col_map: Column mapping (passed through unchanged). + operations: Operations list to extend. Returns: - A tuple containing: - - * matrix_reduced: Further reduced matrix with rank decreased by 1 - * reduced_row_map: Updated row mapping (last row removed) - * col_map: Unchanged column mapping - * updated_operations: Operations list with new CNOT and X operations - * new_rank: Original rank - 1 + GF2XEliminationResult with rank decremented by 1. """ matrix_work = matrix.copy() row_map_work = row_map.copy() operations_work = operations.copy() - rank = matrix_work.shape[0] - # Verify this is actually a diagonal matrix - if not _is_diagonal_matrix(matrix_work): - Logger.warn("Matrix is not diagonal, skipping diagonal reduction") - return GF2XEliminationResult( - reduced_matrix=matrix_work, row_map=row_map_work, col_map=col_map, operations=operations_work, rank=rank - ) - - Logger.info(f"Applying diagonal matrix reduction on {rank}x{rank} matrix") + Logger.info(f"Applying diagonal matrix reduction on {rank}x{matrix_work.shape[1]} matrix") - # Step 1: Apply sequential CNOT operations CNOT(i, i+1) for i = 0 to rank-2 + # Sequential CX(i, i+1) accumulates all 1s into the last row for i in range(rank - 1): - control_idx = i - target_idx = i + 1 - - # Record CNOT operation using original row indices - operations_work.append( - ( - "cnot", - (row_map_work[target_idx], row_map_work[control_idx]), - ) - ) - - # Apply CNOT: target row = target row XOR control row - matrix_work[target_idx] = matrix_work[target_idx] ^ matrix_work[control_idx] - - Logger.info(f"Applied CNOT({row_map_work[control_idx]}, {row_map_work[target_idx]})") + operations_work.append(("cx", (row_map_work[i + 1], row_map_work[i]))) + matrix_work[i + 1] ^= matrix_work[i] - # After all CNOTs, the last row should be all 1s - last_row = rank - 1 - Logger.info(f"Last row after CNOTs: {matrix_work[last_row]}") + # X on the all-ones last row zeroes it + operations_work.append(("x", row_map_work[rank - 1])) - # Step 2: Apply X operation on the last row to make it all 0s - operations_work.append(("x", row_map_work[last_row])) - matrix_work[last_row] = np.zeros(matrix_work.shape[1], dtype=matrix_work.dtype) - - Logger.info(f"Applied X operation on row {row_map_work[last_row]}") - - # Step 3: Remove the last row (which is now all zeros) - matrix_reduced = matrix_work[:-1, :] # Remove last row - reduced_row_map = row_map_work[:-1] # Remove last row mapping - new_rank = rank - 1 - - Logger.info(f"Diagonal reduction complete: rank reduced from {rank} to {new_rank}") + Logger.info(f"Diagonal reduction complete: rank reduced from {rank} to {rank - 1}") return GF2XEliminationResult( - reduced_matrix=matrix_reduced, - row_map=reduced_row_map, + reduced_matrix=matrix_work[:-1], + row_map=row_map_work[:-1], col_map=col_map, operations=operations_work, - rank=new_rank, + rank=rank - 1, ) def _is_diagonal_matrix(matrix: np.ndarray) -> bool: - """Check if a binary matrix is diagonal and safe for a further rank reduction. + """Check if a binary matrix is diagonal and safe for rank reduction. - The diagonal reduction optimization is mathematically valid in two scenarios: + Two accepted shapes: - 1. True diagonal matrix: Square matrix with 1s on diagonal, 0s elsewhere - 2. Pseudo-diagonal: Rectangular matrix where: - * The square part (min(rows,cols) x min(rows,cols)) is diagonal - * ALL remaining columns are all 1s - * We have an odd number of rows - - The rank reduction works by applying sequential CNOTs and an X operation, - which is only valid when these specific structural conditions are met. - We also require rank > 1 since rank 1 matrices are already minimal. + 1. **Square identity**: ``matrix == np.eye(r)``. + 2. **Pseudo-diagonal** (more columns than rows, odd row count): + the leading ``r x r`` block is identity and every extra column + is all-ones. Args: - matrix: Binary matrix to check + matrix: Binary matrix to check. Returns: - True if matrix is diagonal and safe for rank reduction, False otherwise + ``True`` if the matrix matches one of the accepted shapes. """ - # Check basic requirements - if matrix.ndim != 2 or matrix.shape[0] <= 1 or not np.array_equal(matrix & 1, matrix): + if matrix.ndim != 2 or matrix.shape[0] <= 1: return False num_rows, num_cols = matrix.shape + identity = np.eye(num_rows, dtype=matrix.dtype) - # Scenario 1: True diagonal matrix (square) if num_rows == num_cols: - is_diagonal = True - for row_idx in range(num_rows): - for col_idx in range(num_rows): - expected_value = 1 if row_idx == col_idx else 0 - if matrix[row_idx, col_idx] != expected_value: - is_diagonal = False - break - if not is_diagonal: - break - if is_diagonal: - return True - - # Scenario 2: Pseudo-diagonal (rectangular with more columns than rows) - # ONLY valid when: remaining columns are ALL 1s AND odd number of rows - elif num_cols > num_rows and num_rows % 2 == 1: - # Check the square part is diagonal - square_part = matrix[:num_rows, :num_rows] - is_square_diagonal = True - for row_idx in range(num_rows): - for col_idx in range(num_rows): - expected_value = 1 if row_idx == col_idx else 0 - if square_part[row_idx, col_idx] != expected_value: - is_square_diagonal = False - break - if not is_square_diagonal: - break - - # If square part is diagonal, check remaining columns are all 1s - if is_square_diagonal: - remaining_columns = matrix[:, num_rows:] - if np.all(remaining_columns == 1): - return True - - # All other cases: not safe for diagonal reduction - return False + return bool(np.array_equal(matrix, identity)) + + return ( + num_cols > num_rows + and num_rows % 2 == 1 + and bool(np.array_equal(matrix[:, :num_rows], identity)) + and bool(np.all(matrix[:, num_rows:] == 1)) + ) diff --git a/python/src/qdk_chemistry/algorithms/state_preparation/sparse_isometry_binary_encoding.py b/python/src/qdk_chemistry/algorithms/state_preparation/sparse_isometry_binary_encoding.py new file mode 100644 index 000000000..05e1128d4 --- /dev/null +++ b/python/src/qdk_chemistry/algorithms/state_preparation/sparse_isometry_binary_encoding.py @@ -0,0 +1,358 @@ +"""Sparse isometry with binary encoding for quantum state preparation. + +This module implements a state preparation algorithm that combines GF2+X +elimination with batched binary encoding. Instead of delegating the reduced +subspace to a dense state preparation routine, this algorithm feeds the +REF matrix directly into the binary-encoding solver which synthesises the +full circuit using batched Toffoli gates and Partial Unary Iteration (PUI) +lookup blocks. +""" + +# -------------------------------------------------------------------------------------------- +# 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.data import Circuit, Wavefunction +from qdk_chemistry.data.circuit import QsharpFactoryData +from qdk_chemistry.utils import Logger +from qdk_chemistry.utils.binary_encoding import ( + BinaryEncodingSynthesizer, + MatrixCompressionOp, + MatrixCompressionType, + _dense_qubits_size, +) +from qdk_chemistry.utils.qsharp import QSHARP_UTILS + +from .sparse_isometry import ( + GF2XEliminationResult, + SparseIsometryStatePreparation, + SparseIsometryStatePreparationSettings, + gf2x_with_tracking, +) + +__all__ = [ + "SparseIsometryBinaryEncodingSettings", +] + + +class SparseIsometryBinaryEncodingSettings(SparseIsometryStatePreparationSettings): + """Settings for SparseIsometryBinaryEncodingStatePreparation.""" + + def __init__(self): + """Initialize with parent defaults plus binary-encoding controls.""" + super().__init__() + self._set_default( + "include_negative_controls", + "bool", + True, + "Include both positive and negative fixed controls in PUI construction.", + ) + self._set_default( + "measurement_based_uncompute", + "bool", + False, + "Use measurement-based AND uncomputation in PUI blocks.", + ) + + +class SparseIsometryBinaryEncodingStatePreparation(SparseIsometryStatePreparation): + """State preparation using sparse isometry with binary encoding. + + This class extends sparse isometry with GF2+X elimination by replacing + the dense state preparation step with a binary-encoding circuit synthesiser. + After GF2+X elimination produces a REF matrix, the binary-encoding + synthesiser compresses the matrix into an efficient circuit using: + + 1. Stage 1 — diagonal (unary-to-binary) encoding of pivot columns + 2. Stage 2 — non-pivot column processing with batched PUI lookup blocks + + The resulting circuit consists entirely of CX, Toffoli, X, SWAP, and + PUI-lookup gates — no dense state preparation is used. + + Key References: + + * Sparse isometry: Malvetti, Iten, and Colbeck (arXiv:2006.00016) :cite:`Malvetti2021` + """ + + def __init__(self) -> None: + """Initialize the SparseIsometryBinaryEncodingStatePreparation.""" + Logger.trace_entering() + super().__init__() + self._settings = SparseIsometryBinaryEncodingSettings() + + def _run_impl(self, wavefunction: Wavefunction) -> Circuit: + """Prepare a quantum circuit using GF2+X elimination followed by binary encoding. + + Args: + wavefunction: The target wavefunction to prepare. + + Returns: + A Circuit object containing the quantum circuit. + + """ + Logger.trace_entering() + + coeffs = wavefunction.get_coefficients() + dets = wavefunction.get_active_determinants() + num_orbitals = len(wavefunction.get_orbitals().get_active_space_indices()[0]) + bitstrings = [] + for det in dets: + alpha_str, beta_str = det.to_binary_strings(num_orbitals) + bitstrings.append(beta_str[::-1] + alpha_str[::-1]) + + if len(bitstrings) == 1: + Logger.info("After filtering, only 1 determinant remains, using single reference state preparation") + return self._prepare_single_reference_state(bitstrings[0]) + + n_qubits = len(bitstrings[0]) + Logger.debug(f"Using {len(bitstrings)} determinants for state preparation") + + # GF2+X elimination — skip the diagonal reduction because binary encoding's + # stage-1 handles the identity pivot block natively; the extra CX + X ops + # from diagonal reduction would be redundant. + bitstring_matrix = self._bitstrings_to_binary_matrix(bitstrings) + gf2x_result = gf2x_with_tracking(bitstring_matrix, skip_diagonal_reduction=True, forward_only=True) + + # Check applicability: binary encoding needs at least one spare row beyond + # the dense register for the one-hot batch indicators. + num_rows, num_cols = gf2x_result.reduced_matrix.shape + if _dense_qubits_size(num_cols) >= num_rows: + Logger.info( + "Binary encoding is not applicable for this wavefunction; falling back to dense+GF2X state preparation." + ) + return self._build_gf2x_circuit(gf2x_result, coeffs, n_qubits) + + params = self._build_binary_encoding_params(gf2x_result, coeffs, n_qubits, bitstrings) + + qsharp_factory = QsharpFactoryData( + program=QSHARP_UTILS.BinaryEncoding.MakeBinaryEncodingStatePreparationCircuit, + parameter=params, + ) + qsharp_op = QSHARP_UTILS.BinaryEncoding.MakeBinaryEncodingStatePreparationOp(*params.values()) + + return Circuit( + qsharp_factory=qsharp_factory, + qsharp_op=qsharp_op, + encoding="jordan-wigner", + ) + + def _build_binary_encoding_params( + self, + gf2x_result: GF2XEliminationResult, + coeffs: np.ndarray, + n_qubits: int, + bitstrings: list[str], + ) -> dict: + """Build binary-encoding state preparation parameters from an already-computed REF result. + + Args: + gf2x_result: Forward-only REF result from GF2+X elimination. + coeffs: Wavefunction coefficients aligned with matrix columns. + n_qubits: Total number of qubits in the original space. + bitstrings: Determinant bitstrings (used for logging only). + + Returns: + A dict of parameters for Q# binary-encoding circuit construction. + + """ + # Step 1: Binary encoding on the REF matrix + encoded_ops, bijection, dense_size = self._perform_binary_encoding(gf2x_result, n_qubits) + + # Step 2b: Build compressed statevector reindexed by the bijection. + # The bijection maps (dense_val, orig_col) where orig_col is the + # determinant index and dense_val is the binary-register label. + compressed_sv = np.zeros(2**dense_size, dtype=float) + for dense_val, orig_col in bijection: + if orig_col < len(coeffs): + compressed_sv[dense_val] = coeffs[orig_col] + norm = np.linalg.norm(compressed_sv) + if norm > 0: + compressed_sv /= norm + + # The dense register consists of the first dense_size rows of the + # tableau, which map to the first dense_size entries of row_map. + dense_row_map = gf2x_result.row_map[:dense_size] + + # Step 3: Build expansion operations from GF2+X elimination + gaussian_elimination_ops: list[MatrixCompressionOp] = [] + for operation in reversed(gf2x_result.operations): + if operation[0] in ("cx", "cnot"): + if isinstance(operation[1], tuple): + target, control = operation[1] + gaussian_elimination_ops.append(MatrixCompressionOp(MatrixCompressionType.CX, [control, target])) + elif operation[0] == "x" and isinstance(operation[1], int): + gaussian_elimination_ops.append(MatrixCompressionOp(MatrixCompressionType.X, [operation[1]])) + + # Build circuit using QDK Q# factory with binary-encoding entry point + # dense_val from the bijection uses row 0 = MSB (_bits_to_int is MSB-first). + # PreparePureStateD treats qubits[0] as MSB, so pass dense_row_map + # as-is (row 0 first) — do NOT reverse like the parent sparse isometry + # (which uses the opposite convention: row rank-1 = MSB). + # Create the ancilla pool from the original qubits that are not touched by binary encoding (i.e. not in row_map) + # since they are idle until the expansion stage and can be borrowed as ancillas during SparseOneHotSCS. + active_qubits_set = {int(q) for q in gf2x_result.row_map} + ancilla_pool = sorted(set(range(n_qubits)) - active_qubits_set) + + state_prep_params = QSHARP_UTILS.BinaryEncoding.BinaryEncodingStatePreparationParams( + rowMap=list(dense_row_map), + stateVector=compressed_sv.tolist(), + gaussianEliminationOps=[op.to_qsharp_parameter() for op in gaussian_elimination_ops], + binaryEncodingOps=[op.to_qsharp_parameter() for op in encoded_ops], + numQubits=n_qubits, + ancillaPool=ancilla_pool, + ) + params = vars(state_prep_params) + + Logger.info( + f"Binary encoding produced {len(params['binaryEncodingOps'])} operations " + f"for {n_qubits}-qubit system with {len(bitstrings)} determinants " + f"using {len(params['ancillaPool'])} pre-existing qubits as ancilla pool" + ) + return params + + def _build_gf2x_circuit(self, gf2x_result: GF2XEliminationResult, coeffs: np.ndarray, n_qubits: int) -> Circuit: + """Build a dense+GF2X state preparation circuit from a REF result. + + Args: + gf2x_result: Forward-only REF result from GF2+X elimination. + coeffs: Wavefunction coefficients aligned with matrix columns. + n_qubits: Total number of qubits in the original space. + + Returns: + A Circuit using dense state preparation followed by GF2+X expansion. + + """ + # Build statevector: each column of the reduced matrix maps to a unique + # computational-basis state via little-endian binary encoding. + statevector = np.zeros(2**gf2x_result.rank, dtype=float) + for det_idx in range(gf2x_result.reduced_matrix.shape[1]): + reduced_col = gf2x_result.reduced_matrix[:, det_idx] + sv_index = int("".join(str(b) for b in reversed(reduced_col)), 2) + statevector[sv_index] = coeffs[det_idx] + norm = np.linalg.norm(statevector) + if norm > 0: + statevector /= norm + + # Build expansion ops (reversed GF2+X ops). + expansion_ops: list[MatrixCompressionOp] = [] + for op in reversed(gf2x_result.operations): + if op[0] in ("cx", "cnot") and isinstance(op[1], tuple): + target, control = op[1] + expansion_ops.append(MatrixCompressionOp(MatrixCompressionType.CX, [control, target])) + elif op[0] == "x" and isinstance(op[1], int): + expansion_ops.append(MatrixCompressionOp(MatrixCompressionType.X, [op[1]])) + + # row_map[::-1] matches the parent's Q# PreparePureStateD convention (row rank-1 = MSB). + state_prep_params = QSHARP_UTILS.StatePreparation.StatePreparationParams( + rowMap=gf2x_result.row_map[::-1], + stateVector=statevector.tolist(), + expansionOps=[op.to_dict() for op in expansion_ops], + numQubits=n_qubits, + ) + params = vars(state_prep_params) + + qsharp_factory = QsharpFactoryData( + program=QSHARP_UTILS.StatePreparation.MakeStatePreparationCircuit, + parameter=params, + ) + state_prep_op = QSHARP_UTILS.StatePreparation.MakeStatePreparationOp(state_prep_params) + return Circuit(qsharp_factory=qsharp_factory, qsharp_op=state_prep_op, encoding="jordan-wigner") + + def _create_dense(self, params: dict) -> Circuit: + """Create a standalone dense state preparation circuit. + + Args: + params: The parameter dict for Q# circuit construction. + + Returns: + A dense state preparation circuit on the reduced qubit subset. + + """ + qsharp_factory = QsharpFactoryData( + program=QSHARP_UTILS.StatePreparation.MakeDenseStatePreparation, + parameter={ + "rowMap": params["rowMap"], + "stateVector": params["stateVector"], + "numQubits": params["numQubits"], + }, + ) + return Circuit(qsharp_factory=qsharp_factory, encoding="jordan-wigner") + + def _create_isometry(self, params: dict) -> Circuit: + """Create a standalone isometry circuit (binary encoding + GF2+X expansion). + + Args: + params: The parameter dict for Q# circuit construction. + + Returns: + A Circuit containing the binary-encoding operations followed by + the GF2+X expansion operations. + + """ + qsharp_factory = QsharpFactoryData( + program=QSHARP_UTILS.BinaryEncoding.MakeBinaryEncodingExpansion, + parameter={ + "binaryEncodingOps": params["binaryEncodingOps"], + "gaussianEliminationOps": params["gaussianEliminationOps"], + "numQubits": params["numQubits"], + "ancillaPool": params["ancillaPool"], + }, + ) + return Circuit(qsharp_factory=qsharp_factory, encoding="jordan-wigner") + + def _perform_binary_encoding( + self, gf2x_result: GF2XEliminationResult, n_qubits: int + ) -> tuple[list[MatrixCompressionOp], list[tuple[int, int]], int]: + """Run binary-encoding synthesis and return Q#-ready ops. + + Runs the synthesiser, translates qubit indices from local to global, + and converts operations directly into MatrixCompressionOp + instances in reversed order (so Q# can iterate forward). + + Args: + gf2x_result: Result from GF2+X elimination containing the reduced matrix. + n_qubits: Total number of qubits in the original space. + + Returns: + Tuple of ``(ops, bijection, dense_size)``: + + - ``ops``: MatrixCompressionOp list ready for Q#. + - ``bijection``: list of ``(dense_val, orig_col)`` mapping each + original matrix column to its compressed binary-register label. + - ``dense_size``: number of qubits in the compressed dense register. + + """ + include_negative_controls = self._settings.get("include_negative_controls") + + Logger.debug( + f"Binary encoding input: {gf2x_result.reduced_matrix.shape} matrix, " + f"include_negative_controls={include_negative_controls}" + ) + + synthesizer = BinaryEncodingSynthesizer.from_matrix( + gf2x_result.reduced_matrix, + include_negative_controls=include_negative_controls, + measurement_based_uncompute=self._settings.get("measurement_based_uncompute"), + ) + + ops = synthesizer.to_operations( + num_local_qubits=n_qubits, + active_qubit_indices=gf2x_result.row_map, + ancilla_start=n_qubits, + reverse=True, + ) + + Logger.debug( + f"Binary encoding output: {len(ops)} ops, " + f"bijection size {len(synthesizer.bijection)}, " + f"dense_size {synthesizer.dense_size}" + ) + + return ops, synthesizer.bijection, synthesizer.dense_size + + def name(self) -> str: + """Return the algorithm identifier string.""" + return "sparse_isometry_binary_encoding" diff --git a/python/src/qdk_chemistry/algorithms/state_preparation/state_preparation.py b/python/src/qdk_chemistry/algorithms/state_preparation/state_preparation.py index 43ae01935..5c80b457d 100644 --- a/python/src/qdk_chemistry/algorithms/state_preparation/state_preparation.py +++ b/python/src/qdk_chemistry/algorithms/state_preparation/state_preparation.py @@ -76,5 +76,5 @@ def algorithm_type_name(self) -> str: return "state_prep" def default_algorithm_name(self) -> str: - """Return the sparse_isometry_gf2x as default algorithm name.""" - return "sparse_isometry_gf2x" + """Return the sparse_isometry as default algorithm name.""" + return "sparse_isometry" diff --git a/python/src/qdk_chemistry/utils/binary_encoding.py b/python/src/qdk_chemistry/utils/binary_encoding.py new file mode 100644 index 000000000..4faa91353 --- /dev/null +++ b/python/src/qdk_chemistry/utils/binary_encoding.py @@ -0,0 +1,1314 @@ +"""Binary encoding circuit synthesiser for REF matrices.""" + +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from __future__ import annotations + +import math +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any + +import numpy as np + +from qdk_chemistry.utils import CaseInsensitiveStrEnum, Logger +from qdk_chemistry.utils.qsharp import QSHARP_UTILS + +if TYPE_CHECKING: + from collections.abc import Iterable + + +__all__ = [ + "BinaryEncodingSynthesizer", + "MatrixCompressionOp", + "MatrixCompressionType", + "NotRefError", + "RefTableau", +] + + +def _dense_qubits_size(num_cols: int) -> int: + """Return the dense-register width required to index ``num_cols`` columns. + + Args: + num_cols: Number of columns to index. + + Returns: + Number of qubits needed in the dense register to uniquely index all columns. + + """ + return 1 if num_cols < 2 else math.ceil(math.log2(num_cols)) + + +def _int_to_bits(val: int, nbits: int) -> list[bool]: + """Convert an integer to a fixed-width MSB-first bit sequence. + + Args: + val: Integer value to convert. + nbits: Number of bits in the output sequence. + + Returns: + List of booleans representing the bits of *val*, with the most significant bit first. + + """ + return [bool((val >> i) & 1) for i in range(nbits - 1, -1, -1)] + + +def _bits_to_int(bits: Iterable[int | bool]) -> int: + """Convert an MSB-first bit sequence to integer. + + Args: + bits: Iterable of bits (as integers or booleans), with the most significant bit first. + + Returns: + Integer value represented by the bit sequence. + + """ + return sum(int(b) << i for i, b in enumerate(reversed(list(bits)))) + + +class NotRefError(ValueError): + """Raised when a matrix is not in row echelon form (REF).""" + + +class MatrixCompressionType(CaseInsensitiveStrEnum): + """Supported operation types for matrix compression.""" + + X = "X" + CX = "CX" + SWAP = "SWAP" + CCX = "CCX" + MCX = "MCX" + SELECT = "SELECT" + SELECT_AND = "SELECT_AND" + + +@dataclass +class MatrixCompressionOp: + """Gate representation for matrix compression operations.""" + + name: MatrixCompressionType + """Gate type, one of the MatrixCompressionType values.""" + qubits: list[int] + """Qubit indices involved in the operation.""" + control_state: int = 0 + """Integer encoding of the control state for multi-controlled gates. + For ``SELECT``/``SELECT_AND``, this stores the number of address qubits.""" + lookup_data: list[list[bool]] = field(default_factory=list) + """Boolean lookup table for ``SELECT`` operations; empty list for all + other gate types.""" + + def __post_init__(self): + """Validate the MatrixCompressionOp parameters.""" + if self.name in {MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND} and not self.lookup_data: + raise ValueError(f"lookup_data must be provided for {self.name} operations") + + def to_dict(self) -> dict[str, Any]: + """Serialize to a camelCase dict matching the Q# ``MatrixCompressionOp`` struct.""" + return { + "name": self.name, + "qubits": self.qubits, + "controlState": self.control_state, + "lookupData": self.lookup_data, + } + + def to_qsharp_parameter(self): + """Convert to a Q# ``MatrixCompressionOp`` struct.""" + return QSHARP_UTILS.BinaryEncoding.MatrixCompressionOp( + name=self.name, + qubits=self.qubits, + controlState=self.control_state, + lookupData=self.lookup_data, + ) + + +def _check_ref(data: np.ndarray) -> None: + """Validate that a binary matrix is in row echelon form (REF). + + REF requires non-zero rows to appear before any all-zero rows and each + row's leading 1 (pivot) to be strictly to the right of the pivot above. + + Args: + data: Binary matrix to validate. + + Raises: + NotRefError: If *data* is not in REF. + + """ + num_rows, _ = data.shape + prev_pivot = -1 + found_zero_row = False + for row in range(num_rows): + nz = np.flatnonzero(data[row]) + if nz.size == 0: + found_zero_row = True + continue + if found_zero_row: + raise NotRefError(f"Non-zero row {row} appears after an all-zero row") + + pivot_col = int(nz[0]) + if pivot_col <= prev_pivot: + raise NotRefError( + f"Pivot at row {row}, col {pivot_col} is not strictly to the right of previous pivot col {prev_pivot}" + ) + prev_pivot = pivot_col + + +class RefTableau: + """Binary tableau for the batched sparse-isometry algorithm. + + The input matrix must be in row echelon form (REF). + The tableau supports in-place updates via the compression operations. + + """ + + def __init__(self, data: np.ndarray): + """Create a tableau from a binary matrix and validate its shape. + + Args: + data: Binary matrix with rows as qubits and columns as determinant + basis states. Values are coerced to ``np.int8``. + + Raises: + NotRefError: If ``data`` is not in REF. + ValueError: If ``data`` is not a 2-dimensional array. + + """ + self.data = np.asarray(data, dtype=np.int8) + if self.data.ndim != 2: + raise ValueError("Input data must be a 2-dimensional array") + + _check_ref(self.data) + + self.num_rows, self.num_cols = self.data.shape + self.dense_size = _dense_qubits_size(self.num_cols) + + self._tmp_row = np.zeros(self.num_cols, dtype=np.int8) + self.pivots = self.identify_pivots() + + Logger.debug(f"Tableau shape: {self.data.shape}, dense size: {self.dense_size}, pivots: {self.pivots}") + + def get(self, row: int, col: int) -> bool: + """Return the value at ``(row, col)``. + + Args: + row: Row index. + col: Column index. + + Returns: + Boolean value at the specified position in the tableau. + + """ + return bool(self.data[row, col]) + + def get_col(self, col: int) -> np.ndarray: + """Return column *col* as a 1-D array. + + Args: + col: Column index. + + Returns: + 1-D array representing the specified column. + + """ + return self.data[:, col] + + def row_is_zero(self, row: int) -> bool: + """Return True if *row* is all zeros. + + Args: + row: Row index. + + Returns: + True if the specified row is all zeros, False otherwise. + + """ + return not np.any(self.data[row]) + + def identify_pivots(self) -> list[tuple[int, int]]: + """Find pivot positions using vectorized operations. + + Returns: + List of pivot positions as (row, col) tuples. + + """ + row_indices, col_indices = np.nonzero(self.data) + _, first_occurrences = np.unique(row_indices, return_index=True) + return list( + zip( + row_indices[first_occurrences].tolist(), + col_indices[first_occurrences].tolist(), + strict=True, + ) + ) + + def cx(self, control: int, target: int): + """Apply CX: ``target ^= control``. + + Args: + control: Control-row index. + target: Target-row index. + + """ + self.data[target] ^= self.data[control] + + def swap(self, a: int, b: int): + """Swap rows *a* and *b*. + + Args: + a: First row index. + b: Second row index. + + """ + self.data[[a, b]] = self.data[[b, a]] + + def x(self, row: int): + """Apply bit-flip (X) to every entry in *row*. + + Args: + row: Row index. + + """ + self.data[row] ^= 1 + + def permute_columns(self, col_order: list[int]): + """Reorder tableau columns and refresh derived metadata. + + Args: + col_order: New-to-old index mapping used to permute columns. + + Notes: + This recomputes ``num_cols``, resets the temporary PUI mask buffer, + and refreshes cached pivot positions. + + """ + self.data = self.data[:, col_order].copy() + self.num_cols = self.data.shape[1] + self._tmp_row = np.zeros(self.num_cols, dtype=np.int8) + self.pivots = self.identify_pivots() + + def toffoli(self, target: int, ctrl0: tuple[int, bool], ctrl1: tuple[int, bool]): + """Apply a two-control conditional XOR into ``target``. + + Args: + target: Target-row index. + ctrl0: Pair ``(row, value)`` for first control; when ``value`` is + ``False``, the negated control is used. + ctrl1: Pair ``(row, value)`` for second control; when ``value`` is + ``False``, the negated control is used. + + """ + c0, v0 = ctrl0 + c1, v1 = ctrl1 + m0 = self.data[c0] if v0 else (1 - self.data[c0]) + m1 = self.data[c1] if v1 else (1 - self.data[c1]) + self.data[target] ^= m0 & m1 + + def select(self, data_table: list[list[bool]], addr_qubits: list[int], dat_qubits: list[int]): + """Apply a SELECT lookup operation to the tableau. + + For each column, compute an address from ``addr_qubits`` rows + (little-endian), look up the corresponding ``data_table`` entry, + and XOR the data bits into the ``dat_qubits`` rows. + + Args: + data_table: Dense Boolean lookup table indexed by address integer. + addr_qubits: Row indices used as address bits (index 0 = LSB). + dat_qubits: Row indices to XOR data into. + + """ + addr_vals = np.zeros(self.num_cols, dtype=int) + for i, q in enumerate(addr_qubits): + addr_vals += self.data[q].astype(int) << i + for j, dq in enumerate(dat_qubits): + mask = np.array([data_table[a][j] for a in addr_vals], dtype=np.int8) + self.data[dq] ^= mask + + +@dataclass +class _BatchElement: + """Internal tracking structure for one element within a synthesis batch.""" + + col: int | None + """Column index of the element's non-zero entry, or None if the batch row is currently zero.""" + dense_content: int + """Dense-register content of the batch element.""" + + +class BinaryEncodingSynthesizer: + """Synthesise a circuit from a binary REF tableau using batched sparse isometry. + + The synthesiser executes a two-stage algorithm: + + * **Stage 1 — diagonal encoding**: converts the identity pivot block into + a compact binary-counter register via a unary-to-binary ladder. + * **Stage 2 — non-pivot processing**: encodes remaining columns using + batched Toffoli gates and Partial Unary Iteration (PUI) lookup blocks. + + """ + + def __init__( + self, + tableau: RefTableau, + *, + include_negative_controls: bool = True, + measurement_based_uncompute: bool = False, + ): + """Construct solver state for a validated tableau. + + Args: + tableau: Mutable tableau to transform during synthesis. + include_negative_controls: If True, include both positive and + negative (0-valued) fixed controls in PUI blocks. If False, + only positive (1-valued) controls are emitted. + measurement_based_uncompute: If True, emit ``select_and`` ops + that use measurement-based AND uncomputation (requires + Adaptive_RI target profile or higher). + + """ + self.tableau = tableau + if tableau.dense_size >= tableau.num_rows: + raise ValueError( + f"Binary encoding is not applicable: state is already dense " + f"({tableau.num_cols} determinant(s) require a {tableau.dense_size}-qubit dense register, " + f"leaving no spare rows in a {tableau.num_rows}-row matrix)." + ) + self.include_negative_controls = include_negative_controls + self.measurement_based_uncompute = measurement_based_uncompute + + self.batch: list[_BatchElement] = [] + self.batch_index: int = 0 + + self.circuit: list[tuple[str, Any]] = [] + self.bijection: list[tuple[int, int]] = [] + self.bad_element_count: int = 0 + + @property + def dense_size(self) -> int: + """Return the number of dense-register rows.""" + return self.tableau.dense_size + + @classmethod + def from_matrix( + cls, + matrix: np.ndarray, + *, + include_negative_controls: bool = True, + measurement_based_uncompute: bool = False, + ) -> BinaryEncodingSynthesizer: + """Create a synthesiser, run both stages, and return the solved instance. + + This is the primary entry point. It validates the input matrix, + executes the full two-stage synthesis, and returns the ready-to-export + synthesiser. + + Args: + matrix: Binary (0/1) matrix in REF form, shaped ``(num_qubits, num_determinants)``. + include_negative_controls: If True, include both positive and + negative (0-valued) fixed controls in PUI blocks. If False, + only positive (1-valued) controls are emitted. + measurement_based_uncompute: If True, emit ``select_and`` ops + that use measurement-based AND uncomputation (requires + Adaptive_RI target profile or higher). + + Returns: + A solved :class:`BinaryEncodingSynthesizer`. + + Raises: + NotRefError: If *matrix* is not in valid REF form. + + """ + synth = cls( + RefTableau(matrix), + include_negative_controls=include_negative_controls, + measurement_based_uncompute=measurement_based_uncompute, + ) + synth.run() + return synth + + def max_batch_size(self) -> int: + """Return the maximum batch size supported by the current tableau shape. + + The batch size is the largest power of 2 that fits within the number + of sparse rows (``num_rows - dense_size``). + + Each batch element occupies a dedicated sparse + row as a one-hot indicator (element *i* has a 1 at sparse row *i*). + Therefore the batch cannot exceed the number of available sparse rows. + """ + sparse_size = self.tableau.num_rows - self.dense_size + assert sparse_size > 0 + if sparse_size & (sparse_size - 1) == 0: + return sparse_size + return 1 << (sparse_size.bit_length() - 1) + + def _record(self, op: tuple[str, Any]): + """Append an operation and update the tableau. + + Args: + op: Operation to record, as a tuple of (MatrixCompressionType, qubit_args). + + """ + self.circuit.append(op) + compress_type, qubit_args = op + + if compress_type is MatrixCompressionType.CX: + self.tableau.cx(*qubit_args) + elif compress_type is MatrixCompressionType.SWAP: + self.tableau.swap(*qubit_args) + elif compress_type is MatrixCompressionType.CCX: + self.tableau.toffoli(qubit_args[0], (qubit_args[1], True), (qubit_args[2], True)) + elif compress_type in {MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND}: + data_table, addr_qubits, dat_qubits = qubit_args + self.tableau.select(data_table, addr_qubits, dat_qubits) + elif compress_type is MatrixCompressionType.X: + self.tableau.x(*qubit_args) + + def run(self): + """Execute full synthesis and restore original column order.""" + rank, col_perm = self._permute_columns_pivots_first() + + self._run_stage1_diagonal_encoding(rank) + + if self.tableau.num_cols - rank > 0: + stage_two_start = self._choose_stage_two_start_index(rank) + self._run_stage2_non_pivot_col_processing(stage_two_start) + + self._complete_bijection() + self._validate() + + # Remap bijection and tableau back to original column order + self.bijection = [(dv, col_perm[c]) for dv, c in self.bijection] + inv_perm = [0] * len(col_perm) + for new_idx, old_idx in enumerate(col_perm): + inv_perm[old_idx] = new_idx + self.tableau.permute_columns(inv_perm) + + def _validate(self): + """Assert final solver invariants.""" + for row in range(self.dense_size, self.tableau.num_rows): + assert self.tableau.row_is_zero(row), f"Row {row} not zeroed" + assert len(self.bijection) == self.tableau.num_cols, "Bijection incomplete" + + def _permute_columns_pivots_first(self) -> tuple[int, list[int]]: + """Move pivot columns to the front to form a diagonal block. + + Returns: + Tuple ``(rank, col_perm)`` where ``rank`` is the number of pivot + columns and ``col_perm`` is the applied forward permutation. + + """ + pivot_cols = [p[1] for p in self.tableau.pivots] + rank = len(pivot_cols) + pivot_set = set(pivot_cols) + non_pivot_cols = [c for c in range(self.tableau.num_cols) if c not in pivot_set] + + col_perm = pivot_cols + non_pivot_cols + self.tableau.permute_columns(col_perm) + return rank, col_perm + + # --- Stage 1: Diagonal Encoding --- + + def _run_stage1_diagonal_encoding(self, rank: int): + """Stage 1: Diagonal encoding. + + Processes only the rank*rank identity pivot block, assigning contiguous + integer labels (0, 1, 2, …) to the leading pivot columns. + + Two steps: + 1. Unary encoding: CX ladder + X + SWAP converts the identity block + into an upper-staircase (unary) matrix where column ``c`` has 1s in + rows 0 through c-1. + 2. Binary compression: A divide-and-conquer loop folds the unary rows + into binary-counter dense rows using one Toffoli per erased unary + bit, with no ancilla waste. + + Args: + rank: Number of pivot columns (size of the identity block). + + """ + if rank == 0: + return + + logical_rows = self._apply_unary_staircase(rank) + self._convert_unary_to_binary(rank, logical_rows) + + # Record bijection for the contiguous pivot columns + dense_size = self.dense_size + for c in range(rank): + dense_val = _bits_to_int(self.tableau.data[:dense_size, c]) + self.bijection.append((dense_val, c)) + + def _apply_unary_staircase(self, rank: int) -> list[int]: + """Convert the pivot block into an upper-staircase matrix. + + Inspects each above-diagonal entry in the pivot block (columns + 0 … rank-1 after pivot permutation) and emits a CX to fill any + missing 1 + + Processing columns left-to-right ensures side effects on later + columns are absorbed when they are reached. + + Args: + rank: Number of pivot columns (size of the pivot block). + + Returns: + List of logical row indices corresponding to the original pivot rows. + + """ + logical_rows = list(range(rank)) + # Fill above-diagonal 0s in the pivot block to reach upper-staircase + for j in range(1, rank): + for i in range(j): + if not self.tableau.data[logical_rows[i], j]: + self._record((MatrixCompressionType.CX, (logical_rows[j], logical_rows[i]))) + self._record((MatrixCompressionType.X, (logical_rows[0],))) + return logical_rows + + def _convert_unary_to_binary(self, limit: int, logical_rows: list[int]): + """Fold unary rows into binary-counter dense rows. + + This is a recursive divide-and-conquer process that iteratively folds + unary rows into binary-counter dense rows. + + Args: + limit: Number of unary rows to process (initially the rank). + logical_rows: Current mapping of logical row indices to physical rows. + + """ + logical_rows = [*logical_rows[1:], logical_rows[0]] + + if limit > 1: + active_unary = logical_rows[: limit - 1] + leftover_zero = logical_rows[limit - 1] + dense_rows, zero_rows = [], [] + + while len(active_unary) > 1: + accumulator = active_unary[0] + dense_rows.append(accumulator) + unary_bits = active_unary[1:] + next_active_unary = [] + + for p in range(len(unary_bits) // 2): + x, y = unary_bits[2 * p], unary_bits[2 * p + 1] + self._record((MatrixCompressionType.CX, (x, accumulator))) + self._record((MatrixCompressionType.CX, (y, accumulator))) + self._record((MatrixCompressionType.CCX, (y, accumulator, x))) + next_active_unary.append(x) + zero_rows.append(y) + + if len(unary_bits) % 2 == 1: + x = unary_bits[-1] + self._record((MatrixCompressionType.CX, (x, accumulator))) + next_active_unary.append(x) + + active_unary = next_active_unary + + dense_rows.append(active_unary[0]) + dense_rows = dense_rows[::-1] # Reverse to MSB-first + + all_zero_rows = [*zero_rows, leftover_zero] + num_msb_padding = min(self.dense_size - len(dense_rows), len(all_zero_rows)) + final_physical_rows = all_zero_rows[:num_msb_padding] + dense_rows + all_zero_rows[num_msb_padding:] + + # Cycle sort to align physical permutations + current_pos = {i: i for i in range(limit)} + row_at = {i: i for i in range(limit)} + for i in range(limit): + target_row = final_physical_rows[i] + if row_at[i] != target_row: + curr_idx = current_pos[target_row] + self._record((MatrixCompressionType.SWAP, (i, curr_idx))) + swapped_row = row_at[i] + row_at[curr_idx], current_pos[swapped_row] = swapped_row, curr_idx + row_at[i], current_pos[target_row] = target_row, i + + # --- Stage 2: Non-Pivot Processing --- + + def _choose_stage_two_start_index(self, rank: int) -> int: + """Choose Stage Two start label to reduce first-batch PUI cost. + + Prefer starting at the next ``max_batch_size`` boundary so the first + Stage Two batch is already alignment-friendly. This is only safe when + enough dense-label capacity remains to encode all non-pivot columns. + + If capacity is insufficient, return ``rank`` and allow Stage Two to + flush an early partial batch to reach the next aligned boundary. + + Args: + rank: Number of pivot columns (size of the identity block). + + Returns: + The chosen start index for Stage Two processing. + + """ + mbs = self.max_batch_size() + if mbs <= 1: + return rank + + next_aligned = ((rank + mbs - 1) // mbs) * mbs + if next_aligned == rank: + return rank + + non_pivot_cols = self.tableau.num_cols - rank + return next_aligned if (next_aligned + non_pivot_cols) <= (1 << self.dense_size) else rank + + def _run_stage2_non_pivot_col_processing(self, k_start: int): + """Stage 2: Non-pivot column processing. + + For each unmapped non-pivot column, locates the next actionable element, + synthesises the target dense row pattern via CX adjustments, and + normalises the sparse indicator bit into a one-hot batch row. + + Batches are flushed mid-loop (emitting a PUI block) whenever they reach + ``max_batch_size`` or would cross an alignment boundary, because sparse + indicator rows are reused across batches. The final (partial) batch is + flushed at the end of the loop, and any remaining edge case is resolved. + + Args: + k_start: Starting index for non-pivot columns to process, + typically chosen to optimize the first batch's PUI cost. + + """ + mbs = self.max_batch_size() + self.batch_index = k_start + mapped_cols: set[int] = {col for _, col in self.bijection} + + while True: + if self.batch: + new_len = len(self.batch) + 1 + block_shift = _dense_qubits_size(new_len) + crosses = (self.batch[0].dense_content >> block_shift) != (self.batch_index >> block_shift) + + if new_len > mbs or crosses: + self._clear_sparse_bits() + self.batch.clear() + + target_row = self.dense_size + len(self.batch) + element = self._find_next_non_zero_element(target_row, mapped_cols) + + if element is not None: + target_col = self._create_target_row(target_row, element) + self._permute_col_and_add_to_batch(target_col, target_row) + mapped_cols.add(target_col) + else: + if self.batch: + self._clear_sparse_bits() + self.batch.clear() + continue + break + + def _find_next_non_zero_element(self, target_row: int, mapped_cols: set[int]) -> tuple[bool, int, int] | None: + """Find next actionable non-zero element using fast numpy slicing. + + Args: + target_row: First sparse row to scan for direct one-hot markers. + mapped_cols: Column indices already assigned in the bijection; + passed by the caller to avoid repeated reconstruction. + + Returns: + Triple ``(is_direct, col, row)`` for the best candidate, or + ``None`` when every unmapped column is fully zeroed in the + accessible rows. + + """ + unmapped_cols = [c for c in range(self.tableau.num_cols) if c not in mapped_cols] + if not unmapped_cols: + return None + + # 1. Check direct sparse rows + sub_data = self.tableau.data[target_row:, unmapped_cols] + rows, cols = np.nonzero(sub_data) + if rows.size > 0: + return (True, unmapped_cols[cols[0]], target_row + rows[0]) + + # 2. Check current batch indicators + for i, be in enumerate(self.batch): + brow = self.dense_size + i + for col in unmapped_cols: + if be.col is not None and col == be.col: + continue + if self.tableau.data[brow, col]: + return (False, col, brow) + return None + + def _create_target_row(self, target_row: int, element: tuple[bool, int, int]) -> int: + """Create/normalize the next target-row element and return its column. + + Args: + target_row: Sparse row that will host the one-hot batch marker. + element: Triple ``(is_direct, col, row)`` from + :meth:`_find_next_non_zero_element`. + + Returns: + Column index selected for insertion into the current batch. + + """ + is_direct, col, row = element + if is_direct: + if row != target_row: + self._record((MatrixCompressionType.SWAP, (target_row, row))) + return col + + self._synthesize_target_row(target_row, col, row) + return col + + def _synthesize_target_row(self, target_row: int, col: int, row: int): + """Synthesize a target row element utilizing vectorized array masking. + + When the only non-zero entry for an unmapped column lives in an + already-batched row, a Toffoli is emitted to create a fresh indicator + at ``target_row`` by exploiting a difference between the expected and + actual column contents. + + Args: + target_row: Destination sparse row for the new indicator. + col: Unmapped column to process. + row: Existing batch row where the non-zero entry was found. + + """ + self.bad_element_count += 1 + batch_idx = row - self.dense_size + batch_element_bits = _int_to_bits(self.batch[batch_idx].dense_content, self.dense_size) + + is_batch_index = [i == batch_idx for i in range(self.tableau.num_rows - self.dense_size)] + combined_idx = np.array(batch_element_bits + is_batch_index, dtype=bool) + + col_data = self.tableau.get_col(col).astype(bool) + + # Find differing row, ignoring the current 'row' + diffs = combined_idx != col_data + diffs[row] = False + + diff_row = int(np.flatnonzero(diffs)[0]) + diff_val = bool(col_data[diff_row]) + + if not diff_val: + self._record((MatrixCompressionType.X, (diff_row,))) + self._record((MatrixCompressionType.CCX, (target_row, row, diff_row))) + if not diff_val: + self._record((MatrixCompressionType.X, (diff_row,))) + + def _permute_col_and_add_to_batch(self, current_col: int, ctrl_row: int): + """Normalize a column's dense/sparse bits and append it to the batch. + + Emits CX gates controlled by ``ctrl_row`` to align the dense register + to ``batch_index`` and the sparse register to a one-hot marker, then + records the new :class:`_BatchElement` and bijection entry. + + Args: + current_col: Column being processed. + ctrl_row: Sparse row whose 1-entry controls the CX corrections. + + """ + dense_size = self.dense_size + k_bits = np.array(_int_to_bits(self.batch_index, dense_size), dtype=bool) + + # Align dense qubits + dense_col_data = self.tableau.data[:dense_size, current_col].astype(bool) + for d_qubit in np.flatnonzero(dense_col_data != k_bits): + self._record((MatrixCompressionType.CX, (ctrl_row, int(d_qubit)))) + + # Align sparse qubits to isolate the one-hot marker + sparse_col_data = self.tableau.data[dense_size:, current_col].astype(bool) + target_bits = np.zeros(self.tableau.num_rows - dense_size, dtype=bool) + target_bits[len(self.batch)] = True + + for s_qubit in np.flatnonzero(sparse_col_data != target_bits): + self._record((MatrixCompressionType.CX, (ctrl_row, dense_size + int(s_qubit)))) + + self.batch.append(_BatchElement(current_col, self.batch_index)) + self.bijection.append((self.batch_index, current_col)) + self.batch_index += 1 + + def _complete_bijection(self): + """Fill missing bijection entries from current dense column contents.""" + mapped = {col for _, col in self.bijection} + self.bijection.extend( + (_bits_to_int(self.tableau.data[: self.dense_size, c]), c) + for c in range(self.tableau.num_cols) + if c not in mapped + ) + + # --- PUI Lowering & Exporting --- + + def _clear_sparse_bits(self): + """Emit a PUI block that zeroes all sparse indicator rows for the current batch.""" + assert self.batch + dense_size = self.dense_size + num_changing = _dense_qubits_size(len(self.batch)) + num_fixed = dense_size - num_changing + k0 = self.batch[0].dense_content + + fixed_controls = [ + (r, bool((k0 >> (dense_size - 1 - r)) & 1)) + for r in range(num_fixed) + if self.include_negative_controls or ((k0 >> (dense_size - 1 - r)) & 1) + ] + + rest_entries = [] + for i, be in enumerate(self.batch): + changing_controls = [ + ( + num_fixed + off, + bool((be.dense_content >> (dense_size - 1 - num_fixed - off)) & 1), + ) + for off in range(num_changing) + ] + rest_entries.append((i, changing_controls)) + + select_ops: list[tuple[str, Any]] = [] + self._flush_pui_lookup_block(select_ops, dense_size, fixed_controls, rest_entries) + for op in select_ops: + self._record(op) + + def to_operations( + self, + num_local_qubits: int, + active_qubit_indices: list[int] | None = None, + ancilla_start: int | None = None, + *, + reverse: bool = False, + ) -> list[MatrixCompressionOp]: + """Translate recorded circuit operations into MatrixCompressionOp instances. + + Args: + num_local_qubits: Number of local (active) qubits. + active_qubit_indices: Optional mapping from local qubit index (0..num_local_qubits-1) + to global qubit index. If provided, operations are translated to global indices. + ancilla_start: Optional global starting index for ancillas. Used if + active_qubit_indices is provided. + reverse: If True, reverse the operation order before returning. + + Returns: + List of MatrixCompressionOp. + + """ + raw_ops: list[tuple[str, Any]] = [] + + for compress_type, qubit_args in self.circuit: + op_type = MatrixCompressionType(compress_type) + if op_type is MatrixCompressionType.X: + raw_ops.append((op_type, qubit_args[0])) + else: + raw_ops.append((op_type, qubit_args)) + + if active_qubit_indices is not None and ancilla_start is not None: + raw_ops = self._translate_ops(raw_ops, num_local_qubits, active_qubit_indices, ancilla_start) + + ops = [self._to_compression_op(op_type, op_args) for op_type, op_args in raw_ops] + if reverse: + ops.reverse() + return ops + + @staticmethod + def _to_compression_op(op_type: str, op_args: Any) -> MatrixCompressionOp: + """Convert a raw circuit tuple into a MatrixCompressionOp. + + Args: + op_type: The gate type. + op_args: Gate arguments (qubit indices and optional data). + + Returns: + A MatrixCompressionOp instance. + + """ + op_type = MatrixCompressionType(op_type) + if op_type is MatrixCompressionType.X: + return MatrixCompressionOp(op_type, [int(op_args)]) + if op_type in {MatrixCompressionType.CX, MatrixCompressionType.SWAP}: + return MatrixCompressionOp(op_type, [int(op_args[0]), int(op_args[1])]) + if op_type is MatrixCompressionType.CCX: + target, ctrl1, ctrl2 = op_args + return MatrixCompressionOp(op_type, [int(ctrl1), int(ctrl2), int(target)]) + if op_type in {MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND}: + data_table, addr_qubits, dat_qubits = op_args + qubits = [int(q) for q in addr_qubits] + [int(q) for q in dat_qubits] + return MatrixCompressionOp(op_type, qubits, control_state=len(addr_qubits), lookup_data=data_table) + if op_type is MatrixCompressionType.MCX: + controls, control_state, target_qubit = op_args + qubits = [int(q) for q in controls] + [int(target_qubit)] + return MatrixCompressionOp(op_type, qubits, control_state=control_state) + raise ValueError(f"Unknown op type: {op_type}") + + @staticmethod + def _translate_ops( + ops: list[tuple[str, Any]], + num_local_qubits: int, + active_qubit_indices: list[int], + ancilla_start: int, + ) -> list[tuple[str, Any]]: + """Remap local qubit indices to global topological indices. + + Indices below ``num_local_qubits`` are mapped through + ``active_qubit_indices``; higher indices are treated as ancillae + starting at ``ancilla_start``. + + Args: + ops: Operation list with local indices. + num_local_qubits: Boundary between active and ancilla indices. + active_qubit_indices: Local-to-global mapping for active qubits. + ancilla_start: Global start index for ancilla qubits. + + Returns: + New operation list with all qubit indices remapped. + + """ + + def map_idx(idx: int) -> int: + return ( + int(active_qubit_indices[idx]) if idx < num_local_qubits else ancilla_start + (idx - num_local_qubits) + ) + + translated: list[tuple[str, Any]] = [] + for compress_type, op_args in ops: + op_type = MatrixCompressionType(compress_type) + if op_type is MatrixCompressionType.X: + translated.append((MatrixCompressionType.X, map_idx(int(op_args)))) + elif op_type in {MatrixCompressionType.CX, MatrixCompressionType.SWAP}: + translated.append((op_type, (map_idx(int(op_args[0])), map_idx(int(op_args[1]))))) + elif op_type is MatrixCompressionType.CCX: + translated.append((op_type, tuple(map_idx(int(a)) for a in op_args))) + elif op_type is MatrixCompressionType.MCX: + controls, ctrl_state, target = op_args + translated.append( + ( + MatrixCompressionType.MCX, + ( + [map_idx(int(q)) for q in controls], + ctrl_state, + map_idx(int(target)), + ), + ) + ) + elif op_type in {MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND}: + data_table, addr_qubits, dat_qubits = op_args + translated.append( + ( + op_type, + ( + data_table, + [map_idx(int(q)) for q in addr_qubits], + [map_idx(int(q)) for q in dat_qubits], + ), + ) + ) + else: + translated.append((op_type, op_args)) + return translated + + def _flush_pui_lookup_block( + self, + ops: list[tuple[str, Any]], + sbs: int, + fixed_controls: list[tuple[int, bool]], + rest_entries: list[tuple[int, list[tuple[int, bool]]]], + ) -> None: + """Convert one recorded PUI block into lookup-based GF2+X operations. + + Args: + ops: Destination operation list to append into. + sbs: Dense-register width. + fixed_controls: Shared controls for all block entries. + rest_entries: Per-target offsets and changing controls. + + """ + if not rest_entries: + return + + mono_ops, mono_count = self._synthesize_single_pui_lookup_block( + sbs, + fixed_controls, + rest_entries, + ) + + chunked = self._split_rest_entries_into_power_of_two_chunks(rest_entries) + if len(chunked) <= 1: + ops.extend(mono_ops) + return + + chunked_ops, chunked_count = [], 0 + for chunk in chunked: + sub_ops, sub_count = self._synthesize_single_pui_lookup_block( + sbs, + fixed_controls, + chunk, + ) + chunked_ops.extend(sub_ops) + chunked_count += sub_count + + if chunked_count <= mono_count: + ops.extend(chunked_ops) + return + + ops.extend(mono_ops) + + def _synthesize_single_pui_lookup_block( + self, + sbs: int, + fixed_controls: list[tuple[int, bool]], + rest_entries: list[tuple[int, list[tuple[int, bool]]]], + ) -> tuple[list[tuple[str, Any]], int]: + """Lower one PUI sub-block into lookup ops. + + Args: + sbs: Dense-register width. + fixed_controls: Shared controls for all block entries. + rest_entries: Per-target offsets and changing controls. + + Returns: + ``(ops, toffoli_cost)`` where ``toffoli_cost`` is the estimated + number of Toffoli gates used by the emitted SELECT operations. + + """ + if not rest_entries: + return [], 0 + + fixed_controls, rest_entries = self._canonicalize_pui_controls(fixed_controls, rest_entries) + address_qubits = self._collect_pui_address_qubits(fixed_controls, rest_entries) + data_qubits = [sbs + offset for offset, _ in rest_entries] + + filtered_table = self._build_pui_lookup_table(fixed_controls, rest_entries, address_qubits) + if not filtered_table: + return [], 0 + + lookup_ops = _lookup_select( + filtered_table, + address_qubits=address_qubits, + data_qubits=data_qubits, + use_measurement_and=self.measurement_based_uncompute, + ) + + gf2x_ops = list(reversed(lookup_ops)) + toffoli_cost = sum( + _select_toffoli_cost(data_table) + for name, (data_table, _, _) in lookup_ops + if name in (MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND) + ) + + return gf2x_ops, toffoli_cost + + def _canonicalize_pui_controls( + self, + fixed_controls: list[tuple[int, bool]], + rest_entries: list[tuple[int, list[tuple[int, bool]]]], + ) -> tuple[list[tuple[int, bool]], list[tuple[int, list[tuple[int, bool]]]]]: + """Promote chunk-local constant controls from changing to fixed. + + For a given block, rows that appear in every entry with the same value + do not need to remain in per-entry changing controls. + + Args: + fixed_controls: Initial list of fixed controls, as (row, value) pairs. + rest_entries: List of (offset, changing_controls) where changing_controls is + a list of (row, value) pairs that may differ between entries. + + Returns: + Tuple of (new_fixed_controls, new_rest_entries) + where new_fixed_controls is the updated list of fixed controls and new_rest_entries + is the updated list of entries with promoted controls removed from changing_controls. + + """ + if not rest_entries: + return fixed_controls, rest_entries + + n_entries = len(rest_entries) + fixed_map = dict(fixed_controls) + + # Single pass: count occurrences and collect unique values per row + row_info: dict[int, tuple[int, set[bool]]] = {} + for _, changing_controls in rest_entries: + for row, val in changing_controls: + count, vals = row_info.get(row, (0, set())) + vals.add(bool(val)) + row_info[row] = (count + 1, vals) + + # Promote rows that appear in all entries with a single value + for row, (count, values) in row_info.items(): + if count == n_entries and len(values) == 1: + promoted_val = next(iter(values)) + if row not in fixed_map or fixed_map[row] == promoted_val: + fixed_map[row] = promoted_val + + fixed_rows = set(fixed_map) + simplified_rest = [(off, [(r, v) for r, v in ctrls if r not in fixed_rows]) for off, ctrls in rest_entries] + return sorted(fixed_map.items()), simplified_rest + + def _split_rest_entries_into_power_of_two_chunks( + self, rest_entries: list[tuple[int, list[tuple[int, bool]]]] + ) -> list[list[tuple[int, list[tuple[int, bool]]]]]: + """Split entries into contiguous power-of-two chunks. + + This keeps control patterns local while converting expensive + non-power-of-two lookup tables into cheaper composable pieces. + + Args: + rest_entries: List of (offset, changing_controls) where changing_controls is a list of + (row, value) pairs that may differ between entries. + + Returns: + List of chunks, where each chunk is a contiguous sublist of rest_entries with length that is a power of two. + The original order of entries is preserved. + + """ + n = len(rest_entries) + if n <= 2: + return [rest_entries] + + chunks, i, remaining = [], 0, n + while remaining > 0: + chunk_size = 1 << (remaining.bit_length() - 1) + chunks.append(rest_entries[i : i + chunk_size]) + i += chunk_size + remaining -= chunk_size + return chunks + + def _collect_pui_address_qubits( + self, + fixed_controls: list[tuple[int, bool]], + rest_entries: list[tuple[int, list[tuple[int, bool]]]], + ) -> list[int]: + """Collect and sort all control rows that address a PUI lookup table. + + Args: + fixed_controls: List of fixed controls, as (row, value) pairs. + rest_entries: List of (offset, changing_controls) where changing_controls is a list of + (row, value) pairs that may differ between entries. + + Returns: + Sorted list of all control rows that address the PUI lookup table. + + """ + all_ctrl_rows = {row for row, _ in fixed_controls} + for _, changing_controls in rest_entries: + all_ctrl_rows.update(row for row, _ in changing_controls) + return sorted(all_ctrl_rows) + + def _build_pui_lookup_table( + self, + fixed_controls: list[tuple[int, bool]], + rest_entries: list[tuple[int, list[tuple[int, bool]]]], + address_qubits: list[int], + ) -> dict[tuple[int, ...], tuple[int, ...]]: + """Build sparse truth table for one PUI lookup block. + + Args: + fixed_controls: List of fixed controls, as (row, value) pairs. + rest_entries: List of (offset, changing_controls) where changing_controls is a list of + (row, value) pairs that may differ between entries. + address_qubits: List of control rows that will serve as address bits for the lookup. + + Returns: + Mapping from address bit tuples to one-hot output tuples, with all-zero outputs omitted. + + """ + n_outputs = len(rest_entries) + table: dict[tuple[int, ...], tuple[int, ...]] = {} + for i, (_, changing_controls) in enumerate(rest_entries): + ctrl_map = {**dict(fixed_controls), **dict(changing_controls)} + address = tuple(int(ctrl_map[row]) for row in address_qubits) + data = tuple(1 if j == i else 0 for j in range(n_outputs)) + table[address] = data + + return table + + +def _is_data_all_zeros(data: list[list[bool]]) -> bool: + """Return True when every row of data is all-false.""" + return all(not any(row) for row in data) + + +def _scs_toffoli_cost(data: list[list[bool]]) -> int: + """Toffoli cost of the ``SparseOneHotSCS`` recursion (singly-controlled). + + Each non-trivial split (N > 1) uses one AND gate (= 1 Toffoli with + measurement-based uncompute). + """ + n = len(data) + if n == 0 or _is_data_all_zeros(data) or n == 1: + return 0 + k = math.ceil(math.log2(n)) + half = 2 ** (k - 1) + left, right = data[:half], data[half:] + left_empty = _is_data_all_zeros(left) + right_empty = _is_data_all_zeros(right) + if not left_empty and not right_empty: + return 1 + _scs_toffoli_cost(left) + _scs_toffoli_cost(right) + if not right_empty: + return 1 + _scs_toffoli_cost(right) + if not left_empty: + return 1 + _scs_toffoli_cost(left) + return 0 + + +def _select_toffoli_cost(data: list[list[bool]]) -> int: + """Estimate the Toffoli count for a ``SparseOneHotSelect`` call. + + The first address-bit split is free (no AND gate); each branch + delegates to ``SparseOneHotSCS``. + """ + n = len(data) + if n == 0 or _is_data_all_zeros(data) or n == 1: + return 0 + k = math.ceil(math.log2(n)) + half = 2 ** (k - 1) + left, right = data[:half], data[half:] + left_empty = _is_data_all_zeros(left) + right_empty = _is_data_all_zeros(right) + if not left_empty and not right_empty: + return _scs_toffoli_cost(left) + _scs_toffoli_cost(right) + if not right_empty: + return _scs_toffoli_cost(right) + if not left_empty: + return _scs_toffoli_cost(left) + return 0 + + +def _lookup_select( + table_dict: dict[tuple[int, ...], tuple[int, ...]], + address_qubits: list[int], + data_qubits: list[int], + *, + use_measurement_and: bool = False, +) -> list[tuple[str, Any]]: + """Synthesize a lookup-based select or select_and operation for a given truth table. + + Args: + table_dict: Mapping from address bit tuples to output bit tuples, with all-zero outputs omitted. + address_qubits: Qubit indices corresponding to the address bits. + data_qubits: Qubit indices corresponding to the data bits. + use_measurement_and: If True, emit ``select_and`` ops that use measurement-based AND uncomputation + (requires Adaptive_RI target profile or higher). + If False, emit standard ``select`` ops with internal ancilla management. + The choice affects the number of ancillas used and the structure of the emitted operations. + + Returns: + List of GF2+X operations implementing the lookup. + + """ + if not table_dict: + return [] + + operations: list[tuple[str, Any]] = [] + + n_address = len(address_qubits) + n_data = len(data_qubits) + n_entries = 1 << n_address + + # Reverse the address qubit order so that entries that share low-order + # address bits are grouped earlier in the tree. + reversed_address = list(reversed(address_qubits)) + + # Build dense Bool[][] table with reversed bit ordering. + data_table: list[list[bool]] = [[False] * n_data for _ in range(n_entries)] + for addr_tuple, data_tuple in table_dict.items(): + reversed_tuple = tuple(reversed(addr_tuple)) + addr_int = sum(int(bit) << i for i, bit in enumerate(reversed_tuple)) + data_table[addr_int] = [bool(b) for b in data_tuple] + + op_type = MatrixCompressionType.SELECT_AND if use_measurement_and else MatrixCompressionType.SELECT + operations.append((op_type, (data_table, reversed_address, list(data_qubits)))) + + return operations diff --git a/python/src/qdk_chemistry/utils/qsharp/__init__.py b/python/src/qdk_chemistry/utils/qsharp/__init__.py index 3cbe37545..cf5ca18c0 100644 --- a/python/src/qdk_chemistry/utils/qsharp/__init__.py +++ b/python/src/qdk_chemistry/utils/qsharp/__init__.py @@ -7,40 +7,25 @@ from pathlib import Path import qdk -from qdk import qsharp +from qdk import init as qdk_init +from qsharp._qsharp import get_config as get_qdk_profile_config -__all__ = ["QSHARP_UTILS"] - -_QS_FILES = [ - Path(__file__).parent / "StatePreparation.qs", - Path(__file__).parent / "IterativePhaseEstimation.qs", - Path(__file__).parent / "ControlledPauliExp.qs", - Path(__file__).parent / "MeasurementBasis.qs", -] - - -def get_qsharp_utils(): - """Returns the Q# namespace for chemistry operations (lazy-loaded).""" - try: - return qdk.code.QDKChemistry.Utils - except AttributeError: - code = "\n".join(f.read_text() for f in _QS_FILES) - qsharp.eval(code) - return qdk.code.QDKChemistry.Utils +from qdk_chemistry.utils import Logger +__all__ = ["QSHARP_UTILS"] -class _QSharpUtilsProxy: - """Lightweight proxy that lazily resolves the Q# utilities namespace.""" - - def __getattr__(self, name: str): - """Load Q# code (if necessary) and resolve *name* on the utilities namespace. - - Args: - name: The name of the attribute being accessed on the Q# utilities namespace. +_QS_DIR = Path(__file__).parent - """ - utils = get_qsharp_utils() - return getattr(utils, name) +# Ensure the Q# interpreter uses Adaptive_RIF +qdk_config = get_qdk_profile_config() +_target_profile = qdk.TargetProfile.Adaptive_RIF +if qdk_config.get_target_profile() != "adaptive_rif": + Logger.debug( + f"QDK interpreter profile set to '{_target_profile}'. " + "If you imported Q# code before this module was loaded, please re-import it, " + "or set your target profile before importing qdk_chemistry." + ) -QSHARP_UTILS = _QSharpUtilsProxy() +qdk_init(project_root=_QS_DIR, target_profile=_target_profile) +QSHARP_UTILS = qdk.code.QDKChemistry.Utils diff --git a/python/src/qdk_chemistry/utils/qsharp/qsharp.json b/python/src/qdk_chemistry/utils/qsharp/qsharp.json new file mode 100644 index 000000000..ffbc3c383 --- /dev/null +++ b/python/src/qdk_chemistry/utils/qsharp/qsharp.json @@ -0,0 +1,11 @@ +{ + "author": "Microsoft", + "license": "MIT", + "files": [ + "src/BinaryEncoding.qs", + "src/StatePreparation.qs", + "src/IterativePhaseEstimation.qs", + "src/ControlledPauliExp.qs", + "src/MeasurementBasis.qs" + ] +} diff --git a/python/src/qdk_chemistry/utils/qsharp/src/BinaryEncoding.qs b/python/src/qdk_chemistry/utils/qsharp/src/BinaryEncoding.qs new file mode 100644 index 000000000..aee46bc39 --- /dev/null +++ b/python/src/qdk_chemistry/utils/qsharp/src/BinaryEncoding.qs @@ -0,0 +1,401 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for +// license information. + +namespace QDKChemistry.Utils.BinaryEncoding { + + import Std.Arrays.MostAndTail; + import Std.Arrays.Partitioned; + import Std.Arrays.Subarray; + import Std.Canon.ApplyControlledOnBitString; + import Std.Convert.IntAsDouble; + import Std.Math.Ceiling; + import Std.Math.Lg; + import Std.Measurement.MResetX; + import Std.StatePreparation.PreparePureStateD; + import QDKChemistry.Utils.StatePreparation.ApplyDensePreparation; + + /// A single gate produced by the matrix compression pipeline. + /// + /// ``qubits`` always contains qubit indices only: + /// ("X", [target], 0, []) + /// ("CX", [control, target], 0, []) + /// ("SWAP", [a, b], 0, []) + /// ("CCX", [ctrl1, ctrl2, target], 0, []) + /// ("MCX", [ctrl0, ctrl1, ..., target], ctrlStateBitmask, []) + /// ("SELECT", [addr0..addrN, data0..dataM], numAddrQubits, data[][]) + struct MatrixCompressionOp { + name : String, + qubits : Int[], + controlState : Int, + lookupData : Bool[][], + } + + /// Parameters for the binary-encoding state preparation. + struct BinaryEncodingStatePreparationParams { + /// Qubit indices for the dense state preparation (row map, reversed). + rowMap : Int[], + /// Amplitudes of the reduced-space statevector. + stateVector : Double[], + /// GF2+X expansion operations (CX / X) to reverse the GF2+X elimination. + gaussianEliminationOps : MatrixCompressionOp[], + /// Binary-encoding gate sequence (already reversed by Python). + binaryEncodingOps : MatrixCompressionOp[], + /// Total number of qubits. + numQubits : Int, + /// Qubit indices (into the main register) that are idle during binary encoding + /// and can be borrowed as ancillas by SparseOneHotSelect. + ancillaPool : Int[], + } + + /// Apply a single matrix-compression gate to a qubit register. + /// + /// ``ancillaPool`` is a list of pre-initialised |0⟩ qubits that + /// SparseOneHotSelect may borrow as helpers (avoids allocating new qubits). + /// Pass an empty array when no pool is available (e.g. for GF2+X ops). + operation ApplyMatrixCompressionOp(gate : MatrixCompressionOp, qs : Qubit[], ancillaPool : Qubit[]) : Unit { + if gate.name == "X" { + X(qs[gate.qubits[0]]); + } elif gate.name == "CX" { + CX(qs[gate.qubits[0]], qs[gate.qubits[1]]); + } elif gate.name == "SWAP" { + SWAP(qs[gate.qubits[0]], qs[gate.qubits[1]]); + } elif gate.name == "CCX" { + CCNOT(qs[gate.qubits[0]], qs[gate.qubits[1]], qs[gate.qubits[2]]); + } elif gate.name == "MCX" { + let numControls = Length(gate.qubits) - 1; + let target = gate.qubits[numControls]; + mutable controlQubits = []; + mutable ctrlStateBools = []; + for i in 0..numControls - 1 { + set controlQubits += [qs[gate.qubits[i]]]; + set ctrlStateBools += [((gate.controlState >>> i) &&& 1) == 1]; + } + ApplyControlledOnBitString(ctrlStateBools, X, controlQubits, qs[target]); + } elif gate.name == "SELECT" { + let numAddr = gate.controlState; + mutable addrQubits : Qubit[] = []; + mutable targetQubits : Qubit[] = []; + for i in 0..Length(gate.qubits) - 1 { + if i < numAddr { + set addrQubits += [qs[gate.qubits[i]]]; + } else { + set targetQubits += [qs[gate.qubits[i]]]; + } + } + SparseOneHotSelect(gate.lookupData, addrQubits, targetQubits, false, ancillaPool); + } elif gate.name == "SELECT_AND" { + let numAddr = gate.controlState; + mutable addrQubits : Qubit[] = []; + mutable targetQubits : Qubit[] = []; + for i in 0..Length(gate.qubits) - 1 { + if i < numAddr { + set addrQubits += [qs[gate.qubits[i]]]; + } else { + set targetQubits += [qs[gate.qubits[i]]]; + } + } + SparseOneHotSelect(gate.lookupData, addrQubits, targetQubits, true, ancillaPool); + } else { + fail $"Unknown gate name: {gate.name}"; + } + } + + /// Return true when every row of ``data`` is all-false. + function IsDataAllZeros(data : Bool[][]) : Bool { + for row in data { + for bit in row { + if bit { return false; } + } + } + return true; + } + + /// Apply X to each target qubit where the corresponding data bit is true. + operation WriteOneHotData(data : Bool[], target : Qubit[]) : Unit { + for i in 0..Length(data) - 1 { + if data[i] { X(target[i]); } + } + } + + /// Controlled variant: apply CX(ctl, target[i]) for each true bit. + operation ControlledWriteOneHotData(ctl : Qubit, data : Bool[], target : Qubit[]) : Unit { + for i in 0..Length(data) - 1 { + if data[i] { CX(ctl, target[i]); } + } + } + + + /// AND gate with measurement-based adjoint uncomputation. + operation MeasurementBasedAND(a : Qubit, b : Qubit, target : Qubit) : Unit is Adj { + body (...) { + CCNOT(a, b, target); + } + adjoint (...) { + if MResetX(target) == One { + CZ(a, b); + } + } + } + + /// Sparse one-hot select. + /// + /// For each row of ``data``, applies X to the target bits where the row is + /// true, controlled on the address qubits matching that row's index. + /// + /// ``ancillaPool`` is a list of pre-initialised |0⟩ qubits that the + /// recursive helper may borrow instead of allocating new ones. Each + /// borrowed qubit is restored to |0⟩ before the operation returns. + /// Pass an empty array to fall back to ``use`` allocation. + operation SparseOneHotSelect( + data : Bool[][], + address : Qubit[], + target : Qubit[], + useMeasurementAND : Bool, + ancillaPool : Qubit[] + ) : Unit { + let N = Length(data); + + if N == 0 or IsDataAllZeros(data) { + // Nothing to apply + } elif N == 1 { + WriteOneHotData(data[0], target); + } else { + let n = Ceiling(Lg(IntAsDouble(N))); + let (most, tail) = MostAndTail(address[...n - 1]); + let parts = Partitioned([2^(n - 1)], data); + let leftEmpty = IsDataAllZeros(parts[0]); + let rightEmpty = IsDataAllZeros(parts[1]); + + if not leftEmpty and not rightEmpty { + within { X(tail); } apply { + SparseOneHotSCS(tail, parts[0], most, target, useMeasurementAND, ancillaPool); + } + SparseOneHotSCS(tail, parts[1], most, target, useMeasurementAND, ancillaPool); + } elif not rightEmpty { + SparseOneHotSCS(tail, parts[1], most, target, useMeasurementAND, ancillaPool); + } elif not leftEmpty { + within { X(tail); } apply { + SparseOneHotSCS(tail, parts[0], most, target, useMeasurementAND, ancillaPool); + } + } + } + } + + /// Singly-controlled recursion for SparseOneHotSelect. + /// + /// Uses ``ancillaPool[0]`` as the helper qubit (must be |0⟩ on entry, + /// restored on exit) and passes ``ancillaPool[1...]`` to recursive calls. + /// Falls back to ``use helper = Qubit()`` when the pool is empty. + operation SparseOneHotSCS( + ctl : Qubit, + data : Bool[][], + address : Qubit[], + target : Qubit[], + useMeasurementAND : Bool, + ancillaPool : Qubit[] + ) : Unit { + let N = Length(data); + + if N == 0 or IsDataAllZeros(data) { + // Skip empty branch + } elif N == 1 { + ControlledWriteOneHotData(ctl, data[0], target); + } else { + let n = Ceiling(Lg(IntAsDouble(N))); + let (most, tail) = MostAndTail(address[...n - 1]); + let parts = Partitioned([2^(n - 1)], data); + let leftEmpty = IsDataAllZeros(parts[0]); + let rightEmpty = IsDataAllZeros(parts[1]); + let poolLen = Length(ancillaPool); + + if not leftEmpty and not rightEmpty { + if poolLen > 0 { + let helper = ancillaPool[0]; + let restPool = ancillaPool[1...]; + if useMeasurementAND { + within { X(tail); } apply { + MeasurementBasedAND(ctl, tail, helper); + } + SparseOneHotSCS(helper, parts[0], most, target, true, restPool); + CNOT(ctl, helper); + SparseOneHotSCS(helper, parts[1], most, target, true, restPool); + Adjoint MeasurementBasedAND(ctl, tail, helper); + } else { + within { X(tail); } apply { + CCNOT(ctl, tail, helper); + } + SparseOneHotSCS(helper, parts[0], most, target, false, restPool); + CNOT(ctl, helper); + SparseOneHotSCS(helper, parts[1], most, target, false, restPool); + CCNOT(ctl, tail, helper); + } + } else { + use helper = Qubit(); + if useMeasurementAND { + within { X(tail); } apply { + MeasurementBasedAND(ctl, tail, helper); + } + SparseOneHotSCS(helper, parts[0], most, target, true, []); + CNOT(ctl, helper); + SparseOneHotSCS(helper, parts[1], most, target, true, []); + Adjoint MeasurementBasedAND(ctl, tail, helper); + } else { + within { X(tail); } apply { + CCNOT(ctl, tail, helper); + } + SparseOneHotSCS(helper, parts[0], most, target, false, []); + CNOT(ctl, helper); + SparseOneHotSCS(helper, parts[1], most, target, false, []); + CCNOT(ctl, tail, helper); + } + } + } elif not rightEmpty { + if poolLen > 0 { + let helper = ancillaPool[0]; + let restPool = ancillaPool[1...]; + if useMeasurementAND { + MeasurementBasedAND(ctl, tail, helper); + SparseOneHotSCS(helper, parts[1], most, target, true, restPool); + Adjoint MeasurementBasedAND(ctl, tail, helper); + } else { + CCNOT(ctl, tail, helper); + SparseOneHotSCS(helper, parts[1], most, target, false, restPool); + CCNOT(ctl, tail, helper); + } + } else { + use helper = Qubit(); + if useMeasurementAND { + MeasurementBasedAND(ctl, tail, helper); + SparseOneHotSCS(helper, parts[1], most, target, true, []); + Adjoint MeasurementBasedAND(ctl, tail, helper); + } else { + CCNOT(ctl, tail, helper); + SparseOneHotSCS(helper, parts[1], most, target, false, []); + CCNOT(ctl, tail, helper); + } + } + } elif not leftEmpty { + if poolLen > 0 { + let helper = ancillaPool[0]; + let restPool = ancillaPool[1...]; + if useMeasurementAND { + X(tail); + MeasurementBasedAND(ctl, tail, helper); + SparseOneHotSCS(helper, parts[0], most, target, true, restPool); + Adjoint MeasurementBasedAND(ctl, tail, helper); + X(tail); + } else { + X(tail); + CCNOT(ctl, tail, helper); + SparseOneHotSCS(helper, parts[0], most, target, false, restPool); + CCNOT(ctl, tail, helper); + X(tail); + } + } else { + use helper = Qubit(); + if useMeasurementAND { + X(tail); + MeasurementBasedAND(ctl, tail, helper); + SparseOneHotSCS(helper, parts[0], most, target, true, []); + Adjoint MeasurementBasedAND(ctl, tail, helper); + X(tail); + } else { + X(tail); + CCNOT(ctl, tail, helper); + SparseOneHotSCS(helper, parts[0], most, target, false, []); + CCNOT(ctl, tail, helper); + X(tail); + } + } + } + } + } + + /// Prepare a quantum state using GF2+X elimination followed by binary-encoding circuit synthesis. + /// + /// The procedure is: + /// 1. Prepare the dense statevector on the reduced qubit subset. + /// 2. Apply the binary-encoding operations (already reversed by Python). + /// SELECT gates borrow ancillas from ``params.ancillaPool`` (qubits that + /// are idle during binary encoding and start in |0⟩) to avoid allocating + /// extra qubits. + /// 3. Apply the GF2+X expansion operations (CX / X). + operation BinaryEncodingStatePreparation( + params : BinaryEncodingStatePreparationParams, + qs : Qubit[], + ) : Unit { + // Step 1: Dense state prep on reduced subspace + ApplyDensePreparation(params.rowMap, params.stateVector, qs); + // Step 2 & 3: Apply binary-encoding operations and GF2+X operations + ApplyExpansion(params.binaryEncodingOps, params.gaussianEliminationOps, qs, params.ancillaPool); + } + + /// Create a callable for the binary-encoding state preparation. + function MakeBinaryEncodingStatePreparationOp( + rowMap : Int[], + stateVector : Double[], + gaussianEliminationOps : MatrixCompressionOp[], + binaryEncodingOps : MatrixCompressionOp[], + numQubits : Int, + ancillaPool : Int[], + ) : Qubit[] => Unit { + BinaryEncodingStatePreparation(new BinaryEncodingStatePreparationParams { + rowMap = rowMap, + stateVector = stateVector, + gaussianEliminationOps = gaussianEliminationOps, + binaryEncodingOps = binaryEncodingOps, + numQubits = numQubits, + ancillaPool = ancillaPool, + }, _) + } + + /// Top-level circuit entry point for binary-encoding state preparation. + operation MakeBinaryEncodingStatePreparationCircuit( + rowMap : Int[], + stateVector : Double[], + gaussianEliminationOps : MatrixCompressionOp[], + binaryEncodingOps : MatrixCompressionOp[], + numQubits : Int, + ancillaPool : Int[], + ) : Unit { + use qs = Qubit[numQubits]; + BinaryEncodingStatePreparation(new BinaryEncodingStatePreparationParams { + rowMap = rowMap, + stateVector = stateVector, + gaussianEliminationOps = gaussianEliminationOps, + binaryEncodingOps = binaryEncodingOps, + numQubits = numQubits, + ancillaPool = ancillaPool, + }, qs); + } + + /// Applies the binary-encoding operations followed by GF2+X expansion operations. + operation ApplyExpansion( + binaryEncodingOps : MatrixCompressionOp[], + gaussianEliminationOps : MatrixCompressionOp[], + qs : Qubit[], + ancillaPool : Int[], + ) : Unit { + let poolQubits = Subarray(ancillaPool, qs); + for gate in binaryEncodingOps { + ApplyMatrixCompressionOp(gate, qs, poolQubits); + } + for gate in gaussianEliminationOps { + ApplyMatrixCompressionOp(gate, qs, []); + } + } + + /// Circuit entry point for the isometry stage of binary-encoding + /// state preparation. + /// Allocates qubits and delegates to ApplyExpansion. + operation MakeBinaryEncodingExpansion( + binaryEncodingOps : MatrixCompressionOp[], + gaussianEliminationOps : MatrixCompressionOp[], + numQubits : Int, + ancillaPool : Int[], + ) : Unit { + use qs = Qubit[numQubits]; + ApplyExpansion(binaryEncodingOps, gaussianEliminationOps, qs, ancillaPool); + } +} diff --git a/python/src/qdk_chemistry/utils/qsharp/ControlledPauliExp.qs b/python/src/qdk_chemistry/utils/qsharp/src/ControlledPauliExp.qs similarity index 100% rename from python/src/qdk_chemistry/utils/qsharp/ControlledPauliExp.qs rename to python/src/qdk_chemistry/utils/qsharp/src/ControlledPauliExp.qs diff --git a/python/src/qdk_chemistry/utils/qsharp/IterativePhaseEstimation.qs b/python/src/qdk_chemistry/utils/qsharp/src/IterativePhaseEstimation.qs similarity index 100% rename from python/src/qdk_chemistry/utils/qsharp/IterativePhaseEstimation.qs rename to python/src/qdk_chemistry/utils/qsharp/src/IterativePhaseEstimation.qs diff --git a/python/src/qdk_chemistry/utils/qsharp/MeasurementBasis.qs b/python/src/qdk_chemistry/utils/qsharp/src/MeasurementBasis.qs similarity index 100% rename from python/src/qdk_chemistry/utils/qsharp/MeasurementBasis.qs rename to python/src/qdk_chemistry/utils/qsharp/src/MeasurementBasis.qs diff --git a/python/src/qdk_chemistry/utils/qsharp/StatePreparation.qs b/python/src/qdk_chemistry/utils/qsharp/src/StatePreparation.qs similarity index 73% rename from python/src/qdk_chemistry/utils/qsharp/StatePreparation.qs rename to python/src/qdk_chemistry/utils/qsharp/src/StatePreparation.qs index 5aa739515..b024c010f 100644 --- a/python/src/qdk_chemistry/utils/qsharp/StatePreparation.qs +++ b/python/src/qdk_chemistry/utils/qsharp/src/StatePreparation.qs @@ -5,17 +5,23 @@ namespace QDKChemistry.Utils.StatePreparation { import Std.Arrays.Subarray; + import Std.Canon.ApplyControlledOnBitString; + import Std.Measurement.MResetZ; import Std.StatePreparation.PreparePureStateD; + import Std.TableLookup.Select; + import QDKChemistry.Utils.BinaryEncoding.MatrixCompressionOp; + import QDKChemistry.Utils.BinaryEncoding.ApplyMatrixCompressionOp; + /// A struct to hold parameters for state preparation. /// - `rowMap`: An array of integers representing the mapping of qubits to rows in the state vector. /// - `stateVector`: An array of doubles representing the amplitudes of the quantum state. - /// - `expansionOps`: An array of arrays of integers representing the operations to expand the state preparation (e.g., CNOTs, X gates). + /// - `expansionOps`: An array of MatrixCompressionOp representing the operations to expand the state preparation (e.g., CX, X gates). /// - `numQubits`: The number of qubits to allocate for the state preparation. struct StatePreparationParams { rowMap : Int[], stateVector : Double[], - expansionOps : Int[][], + expansionOps : MatrixCompressionOp[], numQubits : Int, } @@ -30,16 +36,8 @@ namespace QDKChemistry.Utils.StatePreparation { params : StatePreparationParams, qs : Qubit[], ) : Unit { - PreparePureStateD(params.stateVector, Subarray(params.rowMap, qs)); - for op in params.expansionOps { - if Length(op) == 2 { - CNOT(qs[op[0]], qs[op[1]]); - } elif Length(op) == 1 { - X(qs[op[0]]); - } else { - fail "Unsupported operation length in expansionOps."; - } - } + ApplyDensePreparation(params.rowMap, params.stateVector, qs); + ApplyExpansion(params.expansionOps, qs); } /// A helper function to create a callable for state preparation. @@ -56,14 +54,14 @@ namespace QDKChemistry.Utils.StatePreparation { /// # Parameters /// - `rowMap`: An array of integers representing the mapping of qubits to rows in the state vector. /// - `stateVector`: An array of doubles representing the amplitudes of the quantum state. - /// - `expansionOps`: An array of arrays of integers representing the operations to expand the state preparation (e.g., CNOTs, X gates). + /// - `expansionOps`: An array of MatrixCompressionOp representing the operations to expand the state preparation. /// - `numQubits`: The number of qubits to allocate for the state preparation. /// # Returns /// - `Unit`: The operation prepares the quantum state on the allocated qubits. operation MakeStatePreparationCircuit( rowMap : Int[], stateVector : Double[], - expansionOps : Int[][], + expansionOps : MatrixCompressionOp[], numQubits : Int, ) : Unit { use qs = Qubit[numQubits]; @@ -132,4 +130,42 @@ namespace QDKChemistry.Utils.StatePreparation { function MakePrepareSingleReferenceStateOp(params : SingleReferenceParams) : Qubit[] => Unit { PrepareSingleReferenceState(params, _) } + + /// Prepares the dense statevector on the qubit subset given by rowMap. + operation ApplyDensePreparation( + rowMap : Int[], + stateVector : Double[], + qs : Qubit[], + ) : Unit { + PreparePureStateD(stateVector, Subarray(rowMap, qs)); + } + + /// Circuit entry point for the dense state preparation stage. + operation MakeDenseStatePreparation( + rowMap : Int[], + stateVector : Double[], + numQubits : Int, + ) : Unit { + use qs = Qubit[numQubits]; + ApplyDensePreparation(rowMap, stateVector, qs); + } + + /// Applies the GF2+X expansion operations (CX / X gates) to the full register. + operation ApplyExpansion( + expansionOps : MatrixCompressionOp[], + qs : Qubit[], + ) : Unit { + for gate in expansionOps { + ApplyMatrixCompressionOp(gate, qs, []); + } + } + + /// Circuit entry point for the expansion (isometry) stage. + operation MakeExpansion( + expansionOps : MatrixCompressionOp[], + numQubits : Int, + ) : Unit { + use qs = Qubit[numQubits]; + ApplyExpansion(expansionOps, qs); + } } diff --git a/python/tests/test_circuit.py b/python/tests/test_circuit.py index 58637a038..1cf43c095 100644 --- a/python/tests/test_circuit.py +++ b/python/tests/test_circuit.py @@ -128,7 +128,7 @@ def test_get_qsharp_circuit_prune_classical_qubits(self): state_prep_params = { "rowMap": [1, 0], "stateVector": [0.6, 0.0, 0.0, 0.8], - "expansionOps": [[2]], + "expansionOps": [{"name": "X", "qubits": [2], "controlState": 0, "lookupData": []}], "numQubits": 4, } qsharp_factory = QsharpFactoryData( diff --git a/python/tests/test_data/ozone_sparse_ci_wavefunction.wavefunction.json b/python/tests/test_data/ozone_sparse_ci_wavefunction.wavefunction.json new file mode 100644 index 000000000..e704e65b1 --- /dev/null +++ b/python/tests/test_data/ozone_sparse_ci_wavefunction.wavefunction.json @@ -0,0 +1,6063 @@ +{ + "container": { + "coefficients": [ + -0.9375631083969103, + 0.32348519364143224, + 0.09466217151416102, + -0.05969506729549011, + -0.059695067295494224, + 0.015646674831562152 + ], + "configuration_set": { + "configurations": [ + { + "configuration": "22000000" + }, + { + "configuration": "20200000" + }, + { + "configuration": "02200000" + }, + { + "configuration": "u2d00000" + }, + { + "configuration": "d2u00000" + }, + { + "configuration": "02020000" + } + ], + "orbitals": { + "active_space_indices": { + "alpha": [ + 8, + 11, + 12, + 13, + 14 + ], + "beta": [ + 8, + 11, + 12, + 13, + 14 + ] + }, + "ao_overlap": [ + [ + 0.9999999999999997, + -0.21406265175603026, + 0.1943841520528101, + -4.3019621682534965e-16, + 2.389200264606652e-17, + 0.0, + 2.796751450445084e-17, + 0.0, + 0.0, + -4.6317979388087364e-32, + 0.0, + 0.0, + 0.0, + -3.202167508042868e-18, + 0.00010162795342230995, + 0.004080448450362301, + 0.03974650583542918, + 0.009153039498940866, + -0.002223586696745214, + 0.0, + 0.09746786505592943, + -0.02367828174712901, + 0.0, + -0.007576093287140207, + 0.0, + -0.009533863074312589, + 0.0, + 0.014672642770263727, + 7.566554767214532e-08, + 5.069776116527167e-06, + 0.0019456341372848032, + 8.803760609894545e-06, + -8.80376253701205e-06, + 0.0, + 0.007552587781947305, + -0.007552589435186726, + 0.0, + -1.9683332510060828e-05, + 0.0, + -1.1364177323232801e-05, + 0.0, + -4.308623558231127e-12 + ], + [ + -0.21406265175603026, + 1.0, + 0.7086073285770355, + -4.155651732842014e-16, + -1.1770728533217567e-17, + 0.0, + -2.7065171743998327e-17, + 0.0, + 0.0, + 3.8496715794608613e-32, + 0.0, + -1.1102230246251565e-16, + 0.0, + 1.9439942655719799e-16, + 0.004080448450362302, + 0.06811296964237698, + 0.20585498647117845, + 0.10865213780458127, + -0.026395324550183667, + 0.0, + 0.3948033487530427, + -0.09591125158143474, + 0.0, + -0.06285685282841283, + 0.0, + -0.07909995368794032, + 0.0, + 0.12173505687685107, + 5.0697761165271665e-06, + 0.00033853968955121453, + 0.01849270383788614, + 0.0005760544787872634, + -0.0005760546048839077, + 0.0, + 0.05288049622100917, + -0.05288050779639545, + 0.0, + -0.0013191603444238338, + 0.0, + -0.0007616175799573981, + 0.0, + -2.8876031716667657e-10 + ], + [ + 0.1943841520528101, + 0.7086073285770355, + 0.9999999999999999, + 2.482061273469195e-16, + -7.628016632237085e-18, + 0.0, + -4.652547417958139e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -2.0735898152616163e-17, + 0.03974650583542918, + 0.20585498647117842, + 0.4335168746666555, + 0.17958463263778798, + -0.043627256292236995, + 0.0, + 0.5648170246110735, + -0.13721339475981995, + 0.0, + -0.04734638995902655, + 0.0, + -0.05958136757615845, + 0.0, + 0.09169589655259233, + 0.0019456341372848034, + 0.018492703837886128, + 0.0872836987416364, + 0.01785215820259852, + -0.017852162110383842, + 0.0, + 0.15234259356139543, + -0.15234262690874453, + 0.0, + -0.02341578904965525, + 0.0, + -0.01351911211110627, + 0.0, + -5.125647327134479e-09 + ], + [ + -4.3019621682534965e-16, + -4.155651732842014e-16, + 2.482061273469195e-16, + 1.0000000000000004, + 0.0, + 0.0, + 0.5012714832460011, + 3.3213815826479783e-33, + 0.0, + -2.341990219245728e-16, + 0.0, + -5.351273040550136e-16, + 0.0, + 9.268676791406428e-16, + -0.00915303949894087, + -0.10865213780458133, + -0.17958463263778782, + -0.1606684845192459, + 0.0472212098410381, + 0.0, + -0.19312697634579026, + 0.08254384937149045, + 0.0, + 0.104746899952449, + 0.0, + 0.10861891525861594, + 0.0, + -0.15764721779892077, + -8.803760609894546e-06, + -0.0005760544787872634, + -0.017852158202598514, + -0.0008911732623832433, + 0.0010106515112425852, + 0.0, + -0.03595349163552047, + 0.05000229947454265, + 0.0, + 0.002186061505946864, + 0.0, + 0.00126212323357384, + 0.0, + 0.00027417545938382675 + ], + [ + 2.389200264606652e-17, + -1.1770728533217567e-17, + -7.628016632237085e-18, + 0.0, + 1.0000000000000004, + 0.0, + 3.3213815826479783e-33, + 0.5012714832460011, + 0.0, + 9.268676791406428e-16, + 0.0, + 1.3521486835209912e-16, + 0.0, + 2.3419902192457273e-16, + 0.002223586696745215, + 0.02639532455018368, + 0.043627256292236974, + 0.04722120984103809, + 0.022238435160131943, + 0.0, + 0.08254384937149051, + 0.12659886883780622, + 0.0, + 0.05490692431256335, + 0.0, + -0.026387253656220085, + 0.0, + 0.07978776481024859, + 8.803762537012048e-06, + 0.0005760546048839074, + 0.017852162110383842, + 0.0010106515112425852, + -0.0008911737048405266, + 0.0, + 0.05000229947454267, + -0.035953513526232904, + 0.0, + -0.00218606210450154, + 0.0, + -0.0012621235098489321, + 0.0, + 0.0002741744423227452 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000007, + 0.0, + 0.0, + 0.5012714832460011, + 0.0, + -2.341990219245728e-16, + 0.0, + 9.268676791406428e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.033710083897023825, + 0.0, + 0.0, + 0.14665159617300735, + 0.0, + -0.020744928862580413, + 0.0, + 0.08539318640458382, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.00011947802763072454, + 0.0, + 0.0, + 0.014048796893667163, + 0.0, + -0.00027417498086138985, + 0.0, + 0.0002741749208453, + 0.0 + ], + [ + 2.796751450445084e-17, + -2.7065171743998327e-17, + -4.652547417958139e-16, + 0.5012714832460011, + 3.3213815826479783e-33, + 0.0, + 1.0000000000000004, + -5.429335180183616e-32, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.09746786505592943, + -0.3948033487530427, + -0.5648170246110737, + -0.1931269763457908, + 0.08254384937149059, + 0.0, + -0.20436795239280633, + 0.16312700897437696, + 0.0, + 0.04779118054078318, + 0.0, + 0.020052863432843904, + 0.0, + -0.014412762845894475, + -0.007552587781947305, + -0.05288049622100919, + -0.15234259356139546, + -0.0359534916355205, + 0.05000229947454265, + 0.0, + -0.13248420174634648, + 0.24100779719391296, + 0.0, + 0.04470532973944468, + 0.0, + 0.02581063632877248, + 0.0, + 0.017166395198803548 + ], + [ + 0.0, + 0.0, + 0.0, + 3.3213815826479783e-33, + 0.5012714832460011, + 0.0, + -5.429335180183616e-32, + 1.0000000000000007, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.023678281747129014, + 0.09591125158143476, + 0.13721339475981995, + 0.08254384937149059, + 0.12659886883780624, + 0.0, + 0.16312700897437696, + 0.427489190371107, + 0.0, + 0.12725960516354526, + 0.0, + -0.004871527143096022, + 0.0, + 0.07520554244496468, + 0.007552589435186727, + 0.05288050779639545, + 0.1523426269087445, + 0.050002299474542704, + -0.0359535135262329, + 0.0, + 0.241007797193913, + -0.1324843072581417, + 0.0, + -0.044705347040653355, + 0.0, + -0.025810641978645445, + 0.0, + 0.01716637186939859 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.5012714832460011, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.1466515961730074, + 0.0, + 0.0, + 0.4671183263621878, + 0.0, + -0.035852094426474676, + 0.0, + 0.14757942062057655, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.014048796893667172, + 0.0, + 0.0, + 0.10852354269167469, + 0.0, + -0.017166385412937968, + 0.0, + 0.017166381655267123, + 0.0 + ], + [ + -4.6317979388087364e-32, + 3.8496715794608613e-32, + 0.0, + -2.341990219245728e-16, + 9.268676791406428e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000009, + 0.0, + 0.0, + 0.0, + 0.0, + -0.007576093287140213, + -0.06285685282841283, + -0.04734638995902653, + -0.10474689995244894, + -0.054906924312563284, + 0.0, + -0.04779118054078314, + -0.1272596051635453, + 0.0, + -0.12436670455561938, + 0.0, + 0.041832706136542074, + 0.0, + -0.16526009666727273, + -1.9683332510060828e-05, + -0.0013191603444238338, + -0.023415789049655245, + -0.0021860615059468595, + 0.002186062104501536, + 0.0, + -0.04470532973944464, + 0.044705347040653355, + 0.0, + 0.005168627438572901, + 0.0, + 0.0029433747916532043, + 0.0, + 1.411212555667285e-09 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -2.341990219245728e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.020744928862580392, + 0.0, + 0.0, + 0.03585209442647466, + 0.0, + 0.023973832824395986, + 0.0, + 0.056766728472505405, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0002741749808613893, + 0.0, + 0.0, + 0.01716638541293795, + 0.0, + -0.0006038717388673926, + 0.0, + 0.00067442434495015, + 0.0 + ], + [ + 0.0, + -1.1102230246251565e-16, + 0.0, + -5.351273040550136e-16, + 1.3521486835209912e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 5.551115123125783e-17, + -0.009533863074312593, + -0.07909995368794032, + -0.05958136757615842, + -0.10861891525861589, + 0.026387253656220057, + 0.0, + -0.020052863432844015, + 0.0048715271430960355, + 0.0, + 0.04183270613654207, + 0.0, + 0.090407284654967, + 0.0, + -0.08101752842679082, + -1.1364177323232803e-05, + -0.000761617579957398, + -0.01351911211110627, + -0.0012621232335738378, + 0.0012621235098489295, + 0.0, + -0.025810636328772465, + 0.025810641978645438, + 0.0, + 0.0029433747916532047, + 0.0, + 0.0017699109819992547, + 0.0, + 6.442960808156293e-10 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 9.268676791406428e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000007, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.08539318640458377, + 0.0, + 0.0, + -0.14757942062057655, + 0.0, + 0.05676672847250539, + 0.0, + -0.19590675643060682, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.0002741749208452994, + 0.0, + 0.0, + -0.017166381655267123, + 0.0, + 0.0006744243449501501, + 0.0, + -0.0006038714436083837, + 0.0 + ], + [ + -3.202167508042868e-18, + 1.9439942655719799e-16, + -2.0735898152616163e-17, + 9.268676791406428e-16, + 2.3419902192457273e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 5.551115123125783e-17, + 0.0, + 1.0000000000000004, + 0.014672642770263734, + 0.12173505687685104, + 0.0916958965525923, + 0.15764721779892069, + -0.07978776481024852, + 0.0, + 0.014412762845894619, + -0.07520554244496469, + 0.0, + -0.16526009666727276, + 0.0, + -0.08101752842679082, + 0.0, + 0.11036239028057575, + -4.30862356602057e-12, + -2.8876031747741167e-10, + -5.125647304242803e-09, + -0.0002741754593838268, + -0.0002741744423227442, + 0.0, + -0.017166395198803478, + -0.017166371869398622, + 0.0, + 1.4112125543662424e-09, + 0.0, + 6.442960795145867e-10, + 0.0, + -0.0012782959361877459 + ], + [ + 0.00010162795342230995, + 0.004080448450362302, + 0.03974650583542918, + -0.00915303949894087, + 0.002223586696745215, + 0.0, + -0.09746786505592943, + 0.023678281747129014, + 0.0, + -0.007576093287140213, + 0.0, + -0.009533863074312593, + 0.0, + 0.014672642770263734, + 0.9999999999999997, + -0.21406265175603026, + 0.1943841520528101, + -1.256820191558359e-18, + -2.2686415810783093e-18, + 0.0, + 8.4325524559358395e-19, + 1.1424697650192384e-18, + 0.0, + 2.3986902530670145e-36, + 0.0, + 0.0, + 0.0, + -3.202167508042868e-18, + 0.00010162693264077538, + 0.004080409564275595, + 0.03974636888228243, + 0.002223570477259914, + -0.0091529550214039, + 0.0, + 0.023678275898299028, + -0.09746765236269586, + 0.0, + -0.0075760350931599885, + 0.0, + -0.009533773448940991, + 0.0, + -0.014672498110219848 + ], + [ + 0.004080448450362301, + 0.06811296964237698, + 0.20585498647117842, + -0.10865213780458133, + 0.02639532455018368, + 0.0, + -0.3948033487530427, + 0.09591125158143476, + 0.0, + -0.06285685282841283, + 0.0, + -0.07909995368794032, + 0.0, + 0.12173505687685104, + -0.21406265175603026, + 1.0, + 0.7086073285770355, + -1.7861408919211106e-17, + -2.224135734905169e-18, + 0.0, + -6.698492263399967e-18, + -9.782925105497325e-19, + 0.0, + -1.148280252259879e-36, + 0.0, + -1.1102230246251565e-16, + 0.0, + 1.9439942655719799e-16, + 0.004080409564275596, + 0.06811257602876829, + 0.20585442866737982, + 0.026395239620186507, + -0.10865157794344339, + 0.0, + 0.09591129111752775, + -0.39480274748264355, + 0.0, + -0.062856713192367, + 0.0, + -0.07909964195679742, + 0.0, + -0.12173452131475754 + ], + [ + 0.03974650583542918, + 0.20585498647117845, + 0.4335168746666555, + -0.17958463263778782, + 0.043627256292236974, + 0.0, + -0.5648170246110737, + 0.13721339475981995, + 0.0, + -0.04734638995902653, + 0.0, + -0.05958136757615842, + 0.0, + 0.0916958965525923, + 0.1943841520528101, + 0.7086073285770355, + 0.9999999999999999, + 3.8782207397956175e-18, + 8.491451356543999e-18, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -2.0735898152616163e-17, + 0.03974636888228243, + 0.20585442866737988, + 0.43351608605850844, + 0.043627257943736626, + -0.17958429190797834, + 0.0, + 0.13721355687414916, + -0.5648165989069881, + 0.0, + -0.04734643717203771, + 0.0, + -0.059581324540096514, + 0.0, + -0.09169578828371144 + ], + [ + 0.009153039498940866, + 0.10865213780458127, + 0.17958463263778798, + -0.1606684845192459, + 0.04722120984103809, + 0.0, + -0.1931269763457908, + 0.08254384937149059, + 0.0, + -0.10474689995244894, + 0.0, + -0.10861891525861589, + 0.0, + 0.15764721779892069, + -1.256820191558359e-18, + -1.7861408919211106e-17, + 3.8782207397956175e-18, + 1.0000000000000004, + 1.8647120475712516e-34, + 0.0, + 0.5012714832460011, + 1.1216163452999718e-34, + 0.0, + 1.766585413836462e-17, + 0.0, + -2.0171518086831035e-18, + 0.0, + 3.493809419218592e-18, + -0.002223570477259915, + -0.0263952396201865, + -0.04362725794373662, + 0.022238222526119452, + 0.047221071647258014, + 0.0, + 0.12659841403330263, + 0.08254394993078457, + 0.0, + -0.05490642258979917, + 0.0, + 0.02638722385097141, + 0.0, + 0.07978753297570615 + ], + [ + -0.002223586696745214, + -0.026395324550183667, + -0.043627256292236995, + 0.0472212098410381, + 0.022238435160131943, + 0.0, + 0.08254384937149059, + 0.12659886883780624, + 0.0, + -0.054906924312563284, + 0.0, + 0.026387253656220057, + 0.0, + -0.07978776481024852, + -2.2686415810783093e-18, + -2.224135734905169e-18, + 8.491451356543999e-18, + 1.8647120475712516e-34, + 1.0000000000000004, + 0.0, + 1.1216163452999718e-34, + 0.5012714832460011, + 0.0, + 3.493809419218593e-18, + 0.0, + -1.0199385642249477e-17, + 0.0, + -1.7665854138364624e-17, + 0.0091529550214039, + 0.10865157794344338, + 0.17958429190797837, + 0.047221071647258014, + -0.16066776351728965, + 0.0, + 0.0825439499307846, + -0.19312712431933293, + 0.0, + -0.10474670063109025, + 0.0, + -0.10861858237355343, + 0.0, + -0.15764665109064105 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.033710083897023825, + 0.0, + 0.0, + 0.1466515961730074, + 0.0, + 0.020744928862580392, + 0.0, + -0.08539318640458377, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000007, + 0.0, + 0.0, + 0.5012714832460011, + 0.0, + 1.766585413836462e-17, + 0.0, + 3.493809419218593e-18, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.03370985989065149, + 0.0, + 0.0, + 0.14665120460348519, + 0.0, + -0.085392674816258, + 0.0, + 0.020744844725191686, + 0.0 + ], + [ + 0.09746786505592943, + 0.3948033487530427, + 0.5648170246110735, + -0.19312697634579026, + 0.08254384937149051, + 0.0, + -0.20436795239280633, + 0.16312700897437696, + 0.0, + -0.04779118054078314, + 0.0, + -0.020052863432844015, + 0.0, + 0.014412762845894619, + 8.4325524559358395e-19, + -6.698492263399967e-18, + 0.0, + 0.5012714832460011, + 1.1216163452999718e-34, + 0.0, + 1.0000000000000004, + 0.0, + 0.0, + -8.176263821694028e-18, + 0.0, + 4.720568118420444e-18, + 0.0, + -8.176263821694028e-18, + -0.02367827589829903, + -0.09591129111752777, + -0.13721355687414916, + 0.12659841403330266, + 0.08254394993078457, + 0.0, + 0.42748825110247507, + 0.16312737426079696, + 0.0, + -0.12725926928420325, + 0.0, + 0.0048715839009241535, + 0.0, + 0.0752056271184787 + ], + [ + -0.02367828174712901, + -0.09591125158143474, + -0.13721339475981995, + 0.08254384937149045, + 0.12659886883780622, + 0.0, + 0.16312700897437696, + 0.427489190371107, + 0.0, + -0.1272596051635453, + 0.0, + 0.0048715271430960355, + 0.0, + -0.07520554244496469, + 1.1424697650192384e-18, + -9.782925105497325e-19, + 0.0, + 1.1216163452999718e-34, + 0.5012714832460011, + 0.0, + 0.0, + 1.0000000000000007, + 0.0, + -8.176263821694026e-18, + 0.0, + 4.720568118420445e-18, + 0.0, + 8.176263821694026e-18, + 0.09746765236269586, + 0.39480274748264355, + 0.5648165989069881, + 0.08254394993078455, + -0.19312712431933282, + 0.0, + 0.163127374260797, + -0.2043689304271153, + 0.0, + -0.04779135538639326, + 0.0, + -0.0200530582610996, + 0.0, + -0.014413016523404359 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.14665159617300735, + 0.0, + 0.0, + 0.4671183263621878, + 0.0, + 0.03585209442647466, + 0.0, + -0.14757942062057655, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.5012714832460011, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + -8.176263821694026e-18, + 0.0, + -8.176263821694026e-18, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.14665120460348519, + 0.0, + 0.0, + 0.4671175525238211, + 0.0, + -0.14757916851616212, + 0.0, + 0.03585210256182065, + 0.0 + ], + [ + -0.007576093287140207, + -0.06285685282841283, + -0.04734638995902655, + 0.104746899952449, + 0.05490692431256335, + 0.0, + 0.04779118054078318, + 0.12725960516354526, + 0.0, + -0.12436670455561938, + 0.0, + 0.04183270613654207, + 0.0, + -0.16526009666727276, + 2.3986902530670145e-36, + -1.148280252259879e-36, + 0.0, + 1.766585413836462e-17, + 3.493809419218593e-18, + 0.0, + -8.176263821694028e-18, + -8.176263821694026e-18, + 0.0, + 1.0000000000000009, + 0.0, + 0.0, + 0.0, + 0.0, + -0.007576035093159992, + -0.06285671319236702, + -0.047346437172037716, + 0.05490642258979915, + 0.10474670063109019, + 0.0, + 0.1272592692842032, + 0.04779135538639323, + 0.0, + -0.12436569142722652, + 0.0, + 0.04183280451848334, + 0.0, + 0.16525984598628726 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.020744928862580413, + 0.0, + 0.0, + -0.035852094426474676, + 0.0, + 0.023973832824395986, + 0.0, + 0.05676672847250539, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.766585413836462e-17, + 0.0, + 0.0, + -8.176263821694026e-18, + 0.0, + 1.0000000000000004, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.08539267481625794, + 0.0, + 0.0, + 0.14757916851616207, + 0.0, + -0.19590581763200313, + 0.0, + 0.05676654484016897, + 0.0 + ], + [ + -0.009533863074312589, + -0.07909995368794032, + -0.05958136757615845, + 0.10861891525861594, + -0.026387253656220085, + 0.0, + 0.020052863432843904, + -0.004871527143096022, + 0.0, + 0.041832706136542074, + 0.0, + 0.090407284654967, + 0.0, + -0.08101752842679082, + 0.0, + -1.1102230246251565e-16, + 0.0, + -2.0171518086831035e-18, + -1.0199385642249477e-17, + 0.0, + 4.720568118420444e-18, + 4.720568118420445e-18, + 0.0, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 5.551115123125783e-17, + -0.009533773448940994, + -0.07909964195679742, + -0.059581324540096514, + -0.026387223850971392, + 0.10861858237355336, + 0.0, + -0.0048715839009241535, + 0.0200530582610996, + 0.0, + 0.04183280451848335, + 0.0, + 0.09040704865313032, + 0.0, + 0.08101754251333977 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.08539318640458382, + 0.0, + 0.0, + 0.14757942062057655, + 0.0, + 0.056766728472505405, + 0.0, + -0.19590675643060682, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.493809419218593e-18, + 0.0, + 0.0, + -8.176263821694026e-18, + 0.0, + 0.0, + 0.0, + 1.0000000000000007, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.020744844725191676, + 0.0, + 0.0, + -0.03585210256182065, + 0.0, + 0.05676654484016896, + 0.0, + 0.02397358145976673, + 0.0 + ], + [ + 0.014672642770263727, + 0.12173505687685107, + 0.09169589655259233, + -0.15764721779892077, + 0.07978776481024859, + 0.0, + -0.014412762845894475, + 0.07520554244496468, + 0.0, + -0.16526009666727273, + 0.0, + -0.08101752842679082, + 0.0, + 0.11036239028057575, + -3.202167508042868e-18, + 1.9439942655719799e-16, + -2.0735898152616163e-17, + 3.493809419218592e-18, + -1.7665854138364624e-17, + 0.0, + -8.176263821694028e-18, + 8.176263821694026e-18, + 0.0, + 0.0, + 0.0, + 5.551115123125783e-17, + 0.0, + 1.0000000000000004, + -0.014672498110219853, + -0.12173452131475757, + -0.0916957882837114, + -0.0797875329757061, + 0.15764665109064097, + 0.0, + -0.0752056271184787, + 0.01441301652340431, + 0.0, + 0.16525984598628726, + 0.0, + 0.08101754251333974, + 0.0, + 0.11036216444476152 + ], + [ + 7.566554767214532e-08, + 5.0697761165271665e-06, + 0.0019456341372848034, + -8.803760609894546e-06, + 8.803762537012048e-06, + 0.0, + -0.007552587781947305, + 0.007552589435186727, + 0.0, + -1.9683332510060828e-05, + 0.0, + -1.1364177323232803e-05, + 0.0, + -4.30862356602057e-12, + 0.00010162693264077538, + 0.004080409564275596, + 0.03974636888228243, + -0.002223570477259915, + 0.0091529550214039, + 0.0, + -0.02367827589829903, + 0.09746765236269586, + 0.0, + -0.007576035093159992, + 0.0, + -0.009533773448940994, + 0.0, + -0.014672498110219853, + 0.9999999999999997, + -0.21406265175603026, + 0.1943841520528101, + 8.085528176774917e-17, + -2.2236428658738885e-16, + 0.0, + -6.994398984838768e-18, + 3.3482025918998453e-17, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -3.202167508042868e-18 + ], + [ + 5.069776116527167e-06, + 0.00033853968955121453, + 0.018492703837886128, + -0.0005760544787872634, + 0.0005760546048839074, + 0.0, + -0.05288049622100919, + 0.05288050779639545, + 0.0, + -0.0013191603444238338, + 0.0, + -0.000761617579957398, + 0.0, + -2.8876031747741167e-10, + 0.004080409564275595, + 0.06811257602876829, + 0.20585442866737988, + -0.0263952396201865, + 0.10865157794344338, + 0.0, + -0.09591129111752777, + 0.39480274748264355, + 0.0, + -0.06285671319236702, + 0.0, + -0.07909964195679742, + 0.0, + -0.12173452131475757, + -0.21406265175603026, + 1.0, + 0.7086073285770355, + 1.2772911192456094e-16, + -5.732480966358953e-16, + 0.0, + 6.767437177045078e-18, + -4.1132040051585347e-16, + 0.0, + 0.0, + 0.0, + -1.1102230246251565e-16, + 0.0, + 1.9439942655719799e-16 + ], + [ + 0.0019456341372848032, + 0.01849270383788614, + 0.0872836987416364, + -0.017852158202598514, + 0.017852162110383842, + 0.0, + -0.15234259356139546, + 0.1523426269087445, + 0.0, + -0.023415789049655245, + 0.0, + -0.01351911211110627, + 0.0, + -5.125647304242803e-09, + 0.03974636888228243, + 0.20585442866737982, + 0.43351608605850844, + -0.04362725794373662, + 0.17958429190797837, + 0.0, + -0.13721355687414916, + 0.5648165989069881, + 0.0, + -0.047346437172037716, + 0.0, + -0.059581324540096514, + 0.0, + -0.0916957882837114, + 0.1943841520528101, + 0.7086073285770355, + 0.9999999999999999, + -7.628016632237085e-18, + 0.0, + 0.0, + 0.0, + -4.652547417958139e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -2.0735898152616163e-17 + ], + [ + 8.803760609894545e-06, + 0.0005760544787872634, + 0.01785215820259852, + -0.0008911732623832433, + 0.0010106515112425852, + 0.0, + -0.0359534916355205, + 0.050002299474542704, + 0.0, + -0.0021860615059468595, + 0.0, + -0.0012621232335738378, + 0.0, + -0.0002741754593838268, + 0.002223570477259914, + 0.026395239620186507, + 0.043627257943736626, + 0.022238222526119452, + 0.047221071647258014, + 0.0, + 0.12659841403330266, + 0.08254394993078455, + 0.0, + 0.05490642258979915, + 0.0, + -0.026387223850971392, + 0.0, + -0.0797875329757061, + 8.085528176774917e-17, + 1.2772911192456094e-16, + -7.628016632237085e-18, + 1.0000000000000004, + -1.1831958984888412e-31, + 0.0, + 0.5012714832460011, + 0.0, + 0.0, + 9.268676791406428e-16, + 0.0, + 1.337818260137534e-16, + 0.0, + -2.317169197851607e-16 + ], + [ + -8.80376253701205e-06, + -0.0005760546048839077, + -0.017852162110383842, + 0.0010106515112425852, + -0.0008911737048405266, + 0.0, + 0.05000229947454265, + -0.0359535135262329, + 0.0, + 0.002186062104501536, + 0.0, + 0.0012621235098489295, + 0.0, + -0.0002741744423227442, + -0.0091529550214039, + -0.10865157794344339, + -0.17958429190797834, + 0.047221071647258014, + -0.16066776351728965, + 0.0, + 0.08254394993078457, + -0.19312712431933282, + 0.0, + 0.10474670063109019, + 0.0, + 0.10861858237355336, + 0.0, + 0.15764665109064097, + -2.2236428658738885e-16, + -5.732480966358953e-16, + 0.0, + -1.1831958984888412e-31, + 1.0000000000000004, + 0.0, + 0.0, + 0.5012714832460011, + 0.0, + -2.317169197851607e-16, + 0.0, + -5.351273040550136e-16, + 0.0, + -9.268676791406426e-16 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.00011947802763072454, + 0.0, + 0.0, + 0.014048796893667172, + 0.0, + 0.0002741749808613893, + 0.0, + -0.0002741749208452994, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.03370985989065149, + 0.0, + 0.0, + 0.14665120460348519, + 0.0, + 0.08539267481625794, + 0.0, + -0.020744844725191676, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000007, + 0.0, + 0.0, + 0.5012714832460011, + 0.0, + 9.268676791406428e-16, + 0.0, + -2.317169197851607e-16, + 0.0 + ], + [ + 0.007552587781947305, + 0.05288049622100917, + 0.15234259356139543, + -0.03595349163552047, + 0.05000229947454267, + 0.0, + -0.13248420174634648, + 0.241007797193913, + 0.0, + -0.04470532973944464, + 0.0, + -0.025810636328772465, + 0.0, + -0.017166395198803478, + 0.023678275898299028, + 0.09591129111752775, + 0.13721355687414916, + 0.12659841403330263, + 0.0825439499307846, + 0.0, + 0.42748825110247507, + 0.163127374260797, + 0.0, + 0.1272592692842032, + 0.0, + -0.0048715839009241535, + 0.0, + -0.0752056271184787, + -6.994398984838768e-18, + 6.767437177045078e-18, + 0.0, + 0.5012714832460011, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.007552589435186726, + -0.05288050779639545, + -0.15234262690874453, + 0.05000229947454265, + -0.035953513526232904, + 0.0, + 0.24100779719391296, + -0.1324843072581417, + 0.0, + 0.044705347040653355, + 0.0, + 0.025810641978645438, + 0.0, + -0.017166371869398622, + -0.09746765236269586, + -0.39480274748264355, + -0.5648165989069881, + 0.08254394993078457, + -0.19312712431933293, + 0.0, + 0.16312737426079696, + -0.2043689304271153, + 0.0, + 0.04779135538639323, + 0.0, + 0.0200530582610996, + 0.0, + 0.01441301652340431, + 3.3482025918998453e-17, + -4.1132040051585347e-16, + -4.652547417958139e-16, + 0.0, + 0.5012714832460011, + 0.0, + 0.0, + 1.0000000000000007, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.014048796893667163, + 0.0, + 0.0, + 0.10852354269167469, + 0.0, + 0.01716638541293795, + 0.0, + -0.017166381655267123, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.14665120460348519, + 0.0, + 0.0, + 0.4671175525238211, + 0.0, + 0.14757916851616207, + 0.0, + -0.03585210256182065, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.5012714832460011, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -1.9683332510060828e-05, + -0.0013191603444238338, + -0.02341578904965525, + 0.002186061505946864, + -0.00218606210450154, + 0.0, + 0.04470532973944468, + -0.044705347040653355, + 0.0, + 0.005168627438572901, + 0.0, + 0.0029433747916532047, + 0.0, + 1.4112125543662424e-09, + -0.0075760350931599885, + -0.062856713192367, + -0.04734643717203771, + -0.05490642258979917, + -0.10474670063109025, + 0.0, + -0.12725926928420325, + -0.04779135538639326, + 0.0, + -0.12436569142722652, + 0.0, + 0.04183280451848335, + 0.0, + 0.16525984598628726, + 0.0, + 0.0, + 0.0, + 9.268676791406428e-16, + -2.317169197851607e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000009, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.00027417498086138985, + 0.0, + 0.0, + -0.017166385412937968, + 0.0, + -0.0006038717388673926, + 0.0, + 0.0006744243449501501, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.085392674816258, + 0.0, + 0.0, + -0.14757916851616212, + 0.0, + -0.19590581763200313, + 0.0, + 0.05676654484016896, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 9.268676791406428e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 0.0, + 0.0 + ], + [ + -1.1364177323232801e-05, + -0.0007616175799573981, + -0.01351911211110627, + 0.00126212323357384, + -0.0012621235098489321, + 0.0, + 0.02581063632877248, + -0.025810641978645445, + 0.0, + 0.0029433747916532043, + 0.0, + 0.0017699109819992547, + 0.0, + 6.442960795145867e-10, + -0.009533773448940991, + -0.07909964195679742, + -0.059581324540096514, + 0.02638722385097141, + -0.10861858237355343, + 0.0, + 0.0048715839009241535, + -0.0200530582610996, + 0.0, + 0.04183280451848334, + 0.0, + 0.09040704865313032, + 0.0, + 0.08101754251333974, + 0.0, + -1.1102230246251565e-16, + 0.0, + 1.337818260137534e-16, + -5.351273040550136e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000004, + 0.0, + 5.551115123125783e-17 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0002741749208453, + 0.0, + 0.0, + 0.017166381655267123, + 0.0, + 0.00067442434495015, + 0.0, + -0.0006038714436083837, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.020744844725191686, + 0.0, + 0.0, + 0.03585210256182065, + 0.0, + 0.05676654484016897, + 0.0, + 0.02397358145976673, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -2.317169197851607e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0000000000000007, + 0.0 + ], + [ + -4.308623558231127e-12, + -2.8876031716667657e-10, + -5.125647327134479e-09, + 0.00027417545938382675, + 0.0002741744423227452, + 0.0, + 0.017166395198803548, + 0.01716637186939859, + 0.0, + 1.411212555667285e-09, + 0.0, + 6.442960808156293e-10, + 0.0, + -0.0012782959361877459, + -0.014672498110219848, + -0.12173452131475754, + -0.09169578828371144, + 0.07978753297570615, + -0.15764665109064105, + 0.0, + 0.0752056271184787, + -0.014413016523404359, + 0.0, + 0.16525984598628726, + 0.0, + 0.08101754251333977, + 0.0, + 0.11036216444476152, + -3.202167508042868e-18, + 1.9439942655719799e-16, + -2.0735898152616163e-17, + -2.317169197851607e-16, + -9.268676791406426e-16, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 5.551115123125783e-17, + 0.0, + 1.0000000000000004 + ] + ], + "basis_set": { + "atomic_orbital_type": "spherical", + "atoms": [ + { + "atom_index": 0, + "shells": [ + { + "coefficients": [ + 0.00071, + 0.00547, + 0.027837, + 0.1048, + 0.283062, + 0.448719, + 0.270952, + 0.015458 + ], + "exponents": [ + 11720.0, + 1759.0, + 400.8, + 113.7, + 37.03, + 13.27, + 5.025, + 1.013 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + -0.00016, + -0.001263, + -0.006267, + -0.025716, + -0.070924, + -0.165411, + -0.116955, + 0.557368 + ], + "exponents": [ + 11720.0, + 1759.0, + 400.8, + 113.7, + 37.03, + 13.27, + 5.025, + 1.013 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 0.3023 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + 0.043018, + 0.228913, + 0.508728 + ], + "exponents": [ + 17.7, + 3.854, + 1.046 + ], + "orbital_type": "p" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 0.2753 + ], + "orbital_type": "p" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 1.185 + ], + "orbital_type": "d" + } + ] + }, + { + "atom_index": 1, + "shells": [ + { + "coefficients": [ + 0.00071, + 0.00547, + 0.027837, + 0.1048, + 0.283062, + 0.448719, + 0.270952, + 0.015458 + ], + "exponents": [ + 11720.0, + 1759.0, + 400.8, + 113.7, + 37.03, + 13.27, + 5.025, + 1.013 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + -0.00016, + -0.001263, + -0.006267, + -0.025716, + -0.070924, + -0.165411, + -0.116955, + 0.557368 + ], + "exponents": [ + 11720.0, + 1759.0, + 400.8, + 113.7, + 37.03, + 13.27, + 5.025, + 1.013 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 0.3023 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + 0.043018, + 0.228913, + 0.508728 + ], + "exponents": [ + 17.7, + 3.854, + 1.046 + ], + "orbital_type": "p" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 0.2753 + ], + "orbital_type": "p" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 1.185 + ], + "orbital_type": "d" + } + ] + }, + { + "atom_index": 2, + "shells": [ + { + "coefficients": [ + 0.00071, + 0.00547, + 0.027837, + 0.1048, + 0.283062, + 0.448719, + 0.270952, + 0.015458 + ], + "exponents": [ + 11720.0, + 1759.0, + 400.8, + 113.7, + 37.03, + 13.27, + 5.025, + 1.013 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + -0.00016, + -0.001263, + -0.006267, + -0.025716, + -0.070924, + -0.165411, + -0.116955, + 0.557368 + ], + "exponents": [ + 11720.0, + 1759.0, + 400.8, + 113.7, + 37.03, + 13.27, + 5.025, + 1.013 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 0.3023 + ], + "orbital_type": "s" + }, + { + "coefficients": [ + 0.043018, + 0.228913, + 0.508728 + ], + "exponents": [ + 17.7, + 3.854, + 1.046 + ], + "orbital_type": "p" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 0.2753 + ], + "orbital_type": "p" + }, + { + "coefficients": [ + 1.0 + ], + "exponents": [ + 1.185 + ], + "orbital_type": "d" + } + ] + } + ], + "ecp_electrons": [ + 0, + 0, + 0 + ], + "ecp_name": "cc-pvdz", + "name": "cc-pvdz", + "num_atomic_orbitals": 42, + "num_atoms": 3, + "num_shells": 18, + "structure": { + "coordinates": [ + [ + 2.3383396794226514, + -0.5018697262045597, + 0.0 + ], + [ + 0.05325650494846228, + 0.05325521871729729, + 0.0 + ], + [ + -0.5018700584633315, + 2.338340633395045, + 0.0 + ] + ], + "elements": [ + 8, + 8, + 8 + ], + "masses": [ + 15.999, + 15.999, + 15.999 + ], + "nuclear_charges": [ + 8.0, + 8.0, + 8.0 + ], + "num_atoms": 3, + "symbols": [ + "O", + "O", + "O" + ], + "units": "bohr", + "version": "0.1.0" + }, + "version": "0.1.0" + }, + "coefficients": { + "alpha": [ + [ + -0.0017613483088018916, + 0.711518310624656, + 0.7037472999089728, + -9.399455949241367e-05, + 0.001260283200973966, + -0.000668875084214835, + 0.0049818667015517435, + 0.0015708258283454223, + 2.5874245447605983e-16, + -0.000676475790391484, + -0.00706643792623164, + 4.604649764119856e-17, + 1.8397405373366005e-16, + 0.012565480068375634, + 0.005898052039455152, + 0.11191713207721744, + -1.5374355809907998e-14, + 0.06687840864819246, + 0.07651740349616816, + -0.0030466893827386476, + -0.1055084723904908, + 1.1199218234569258e-14, + -1.06477179509947e-14, + 0.03318764159041528, + 0.3346427292991582, + -0.5631349789506233, + 0.3587102802356655, + 2.1985695340438685e-15, + -0.021499647260490033, + -1.1587645359928652e-16, + 0.02094549454587577, + 0.02917115722777478, + -8.060958736978645e-16, + -1.6425192025007424e-15, + 0.06834996487394485, + 0.0688658267151557, + 0.16677540651498332, + -2.8384964822483334e-15, + 4.8897844219890855e-15, + 0.10832832612676346, + 0.15397869158292363, + 0.1912492697195178 + ], + [ + 0.00015932168684034643, + 0.0006772539235809356, + 0.00023048437592884555, + -0.1985633172167325, + -0.3113606466003104, + -0.27070335301315374, + -0.10910585651897751, + -0.16497648792835276, + 1.6278534121343272e-14, + 0.005013863525095217, + -0.010431021621965847, + -7.33057304633428e-17, + 5.563742606420083e-16, + -0.10147510747777565, + -0.13729756142329502, + 0.23665519314511257, + -4.6627852825666184e-14, + 0.21594142827170573, + 0.18581241799680553, + -0.05293983744576951, + -0.29761999129753947, + 3.062792476926799e-14, + -2.757324550331927e-14, + 0.08134789786588165, + 0.8151208534588398, + -1.2491318627415071, + 0.7738423967618882, + 4.9223901272352065e-15, + -0.024672858732974652, + -3.3680701891273864e-16, + 0.0822178765362882, + 0.027666933260897076, + -1.060293318230678e-15, + -2.3453367865827154e-15, + 0.1762152883961709, + 0.18128957242035112, + 0.36925871774552554, + -6.484538461770268e-15, + 7.737981812801435e-15, + 0.19470897587479535, + 0.23910636166119137, + 0.3116498530840739 + ], + [ + -0.001589681238226593, + -0.0032768983761073977, + -0.0021113468486820067, + -0.12583179865479674, + -0.309727298370417, + -0.36056594701840744, + -0.2406239923493408, + -0.300286015231819, + 2.7454870236320667e-14, + 0.004078341357603479, + 0.0531037886043208, + -1.0178629474267361e-15, + 8.112341528600056e-17, + -0.6155619032464832, + -0.8049432984607647, + -0.8821890986070516, + -1.1615764626128125e-13, + 0.7103285085645865, + -0.1906021313545944, + -0.12123991900690492, + 0.17934417040471085, + -2.420767969487312e-14, + 1.1555498451699517e-14, + -0.6695635785674935, + -0.5551165620389613, + 2.4262038463165325, + -1.9781727188748213, + -9.928167894319252e-15, + 0.30460225421715875, + -1.1901711152371207e-15, + 0.3495787081267972, + -0.538525175238965, + 1.3231816980767182e-14, + 2.3129469867297278e-14, + 0.07936236603108633, + 0.025759619259084265, + -0.664764142064391, + 1.1217924552695575e-14, + -4.344626320943823e-14, + -0.9028031503241328, + -1.240558722001198, + -1.4239563644443407 + ], + [ + 0.00011879210489503894, + -0.0012656106462341351, + -0.0011669747344560586, + 0.10376527771523388, + 0.09105158492923618, + -0.10939579856717607, + -0.12383275026944963, + -0.32019174781303344, + 3.0460851898998265e-14, + 0.0012982139193877288, + 0.25652213337879476, + 3.563541456198813e-16, + -6.895727376980855e-16, + 0.33867382577244537, + 0.32361693652720475, + -0.36709328259362506, + -5.1496894065507564e-14, + 0.27522749527715745, + 0.058444878083615155, + -0.5739059828216324, + -0.509384043572806, + 5.091653419998346e-14, + -2.0543379131243357e-14, + 0.07146897861298752, + -0.08826088537434426, + 0.08788162635836455, + -0.002643697174707393, + -1.4034952810459226e-15, + -0.10055090308404997, + -7.916971695035124e-17, + -0.17374677669888117, + 0.15837614763827498, + -3.519979146748753e-15, + -5.5025284998782434e-15, + -0.14594406710701505, + -0.19787161045371737, + -0.1652932162236645, + 2.90748687434069e-15, + 5.865657779998858e-15, + 0.06377906480372146, + 0.1893427092795396, + 0.17852209465434132 + ], + [ + -2.1302422111885993e-05, + 0.00040000584458483305, + 0.00026375615599977145, + -0.03348937086215691, + -0.004358880347415549, + -0.023001860107845665, + 0.2567400912495197, + -0.0314976612622805, + 1.4375703009651906e-15, + -0.4933099112024433, + 0.357413170446587, + 3.7092735626386273e-16, + -5.522352390264785e-16, + -0.15090928215272595, + 0.007332584999076089, + 0.16426015263436078, + 2.355674577997738e-14, + -0.3071713229025603, + 0.6223920876632938, + -0.23578639979622648, + -0.02367374606145615, + 6.509123611249409e-15, + 5.927185928046916e-15, + -0.5232255097751093, + -0.15614580835953817, + -0.09889188916773153, + -0.07988522034988751, + 4.529862095216803e-16, + -0.009787325853563664, + -1.0095612678369212e-16, + 0.09251851118994077, + -0.01716705733092771, + 1.6995846227301808e-15, + 1.1949459215063904e-15, + 0.13193231994120133, + 0.045269586510319336, + 0.009531790498651048, + -1.0537588571368305e-15, + -2.0847091170873643e-15, + -0.03553205580032229, + -0.043174172354372965, + -0.043349458922152535 + ], + [ + -1.8480350346778257e-16, + 1.41250331189617e-16, + -1.4148338436777513e-16, + 5.994518409959783e-16, + 5.077479867055641e-16, + -2.634195575577266e-16, + 1.4719120327691139e-15, + 2.417673775485217e-14, + 0.2610567515172919, + 9.389445316524583e-17, + 1.3979782509272695e-15, + -0.45534170942668656, + 0.3730616103322131, + 1.6503552983791064e-16, + -4.7963675652013115e-16, + 6.515092828304018e-15, + -0.471410931415238, + -8.584914925074702e-14, + -2.8341507863383373e-14, + -6.963670010177395e-15, + 8.840684485383916e-14, + 0.6852568388333309, + -0.48909025184657434, + 1.2835724607421146e-15, + -4.243341777153026e-15, + 2.2160706000008914e-15, + -4.522026024810084e-15, + -0.06476034189134464, + 1.7474771359564533e-15, + 0.04028156472703985, + -4.727863842160565e-16, + -7.480511494301423e-16, + 0.024361751055532083, + 0.03655124977078849, + 4.01567144867991e-17, + -4.8670864867640396e-17, + -6.2306745578434e-16, + -0.03165336472413785, + 0.0065037375325256545, + -2.308312685919986e-15, + -3.3749378031716085e-15, + -5.012081892918695e-16 + ], + [ + 0.0006925135359825555, + 0.0013629325219717825, + 0.0007125617404745804, + 0.016054941335634065, + 0.03907758055843969, + -0.034567529852519596, + -0.038588116673101865, + -0.16092127672708484, + 1.5676929108553442e-14, + 0.007928087634621498, + 0.1628143943544517, + 1.342823376513055e-15, + -1.3907492056563523e-15, + 0.678420931649487, + 0.811060572548184, + 0.797740548818454, + 1.4016433847686068e-13, + -0.7989485080983849, + 0.020029084110875763, + 0.8739530154395622, + 0.7860342742856201, + -7.335611718400788e-14, + 3.366454988952001e-14, + 0.24240014514988534, + 0.11717234658889061, + -1.0671381929296226, + 0.9203281304648923, + 5.314906174497753e-15, + -0.12729065484730231, + 8.716252810327436e-16, + -0.11442460877745136, + 0.1979999993212946, + -4.899607572293976e-15, + -8.856935854708068e-15, + -0.0021994504942024493, + 0.09250763922192878, + 0.45048541812721793, + -7.000374870254966e-15, + 2.224830964716879e-14, + 0.4596737239767952, + 0.7609992567912525, + 0.8193405170045229 + ], + [ + -0.000463509471573487, + -0.0003893489576391002, + -7.74480779093027e-05, + -9.186833998974275e-06, + -0.008162545072009609, + -0.01505505526278426, + 0.12574512820250514, + -0.013408958786484607, + 3.2524926791369187e-16, + -0.3497916909454886, + 0.2828849832592, + -4.0856258358509393e-17, + 1.8913922521692952e-16, + -0.30664611487388105, + -0.05368500153624419, + -0.4637995200124685, + -2.006553225500354e-14, + 0.2799801435226983, + -0.625652916458579, + 0.18748273279473754, + 0.14636917247765055, + -1.766625694167923e-14, + 1.3916548084652963e-15, + 0.850736873531966, + 0.365022470149761, + 0.22868923476260353, + -0.30517475548843165, + -8.473361627844091e-16, + 0.003859038323223647, + 4.50089906643573e-16, + -0.10898367527197976, + -0.15419968938885648, + -1.1424188256370197e-15, + 3.203324365629822e-15, + -0.20443627422890492, + 0.02602142864834334, + 0.015438404957295326, + 1.4743941445944099e-15, + -1.6370411116751388e-14, + -0.3248195121009972, + -0.17686200698523266, + -0.14111295297121318 + ], + [ + 1.1636532698013606e-16, + -1.5588341596154192e-16, + 1.0880766650822896e-16, + 3.829425894003322e-16, + 2.689915746866007e-16, + 2.757587578409129e-16, + 2.790332266104727e-16, + 1.4088960362877333e-14, + 0.15417718420310664, + -3.6051002216484057e-16, + 1.1445869737293796e-15, + -0.3680897441682019, + 0.38464932190265366, + -3.4989520604123526e-16, + 1.3208799822193764e-15, + -5.239609100628053e-15, + 0.32293175303093874, + 6.603218450101121e-14, + 2.0951207944864677e-14, + 8.426016991924388e-15, + -1.1322338968157453e-13, + -0.7921538783554328, + 0.832604838480252, + -3.466428241329757e-15, + 7.62286210620336e-15, + -3.4844184144479423e-15, + 4.246742343087103e-15, + -0.07140184670084454, + -7.686087971447673e-16, + 0.024338910721400965, + 1.325413391904308e-15, + -3.5613663596543516e-16, + -0.032689538286556144, + -0.07209477332422434, + 1.9967500064428566e-16, + 4.0817457963609506e-16, + 8.422792198167016e-16, + 0.2282306772925758, + -0.14274901944077537, + 9.006699812027153e-15, + 3.9653878344283124e-15, + 8.215633790227969e-16 + ], + [ + -2.5369137658829638e-05, + 7.43956851969767e-05, + 6.637760214646658e-05, + 0.00895134520344189, + 0.0040459634994585185, + 0.0013901073856583857, + -0.0211210064779574, + -0.0041066425855090264, + 4.767080834448631e-16, + 0.017586970822102248, + -0.0017502896243502184, + 2.6989972035650122e-17, + -9.013590235521426e-18, + -0.013654429006799975, + 0.01128480365086407, + 0.03780074052251412, + 6.0058305182444665e-15, + -0.027590895833582752, + -0.012116695725973804, + -0.02404724985623946, + 0.00932205867414363, + -4.699583650904146e-16, + 1.5271006952726165e-15, + 0.08915051086617565, + -0.0015487918355510195, + 0.02961948136019113, + -0.0865404815016752, + -9.84721574009614e-16, + -0.30899351199351444, + 1.702111571193274e-15, + 0.4599340843237217, + 0.1430412367399587, + 8.808819082813115e-15, + 6.008282029914628e-15, + 0.3046960193659428, + 0.22641584892931893, + -0.3124698751554813, + 1.2599953482819848e-15, + 3.609387911168111e-14, + 0.6536784243389747, + 0.05090683452797932, + 0.38431115969029694 + ], + [ + -1.270817712639795e-16, + 8.356048854577706e-17, + -9.850996118752719e-17, + 2.5900699545463132e-17, + -6.188449970488292e-17, + -7.690125200602923e-17, + 7.912867641673169e-17, + 5.585624077908048e-16, + 0.006718551983902079, + -4.656603857554806e-17, + 3.839682720919745e-17, + -0.004951908923828109, + -0.00047628582662786177, + 3.533974437587096e-17, + -1.3971523963490332e-16, + 2.0945212611334557e-16, + -0.003558253263976293, + -7.859960059983535e-16, + -1.8422529660206642e-16, + -3.9605588392832494e-16, + 1.2981419808070361e-15, + 0.016910409456521523, + 0.010398862137348069, + 3.1290370196262395e-16, + -1.3790044223663603e-16, + -2.3436232061197704e-16, + 2.375029703224186e-16, + 0.03747916132983966, + -8.427398305892728e-16, + -0.2686735605538165, + 5.095358064593045e-15, + 1.146482171371745e-14, + -0.7042904341557328, + 0.5959989919205798, + 4.263109287960304e-15, + -2.0087536897655615e-15, + 3.5548524505784e-15, + 0.2784802001833716, + -0.059844473326176914, + 2.0135518204233937e-15, + 9.824842934639128e-16, + -2.4553599332942103e-15 + ], + [ + -0.00016279402038978409, + -4.1993433812048596e-05, + -0.00012907356151309386, + 0.009312534928960427, + 0.007623427823470033, + -0.002758731453626304, + -0.007770047641615649, + -0.012784381783544766, + 1.2863771917162084e-15, + 0.004289097664279655, + 0.004828188307893601, + 1.1763165448547334e-16, + 1.8715473884293376e-16, + -0.0003333587859598123, + -0.007040812318519787, + 0.021173206450744067, + 6.870170532814403e-15, + -0.03691307653644089, + -0.005805505206568458, + -0.018304680005941647, + -0.011473706480055504, + 1.1034888958396258e-15, + 3.708569581538061e-16, + 0.0423550129046431, + -0.060922279558813006, + 0.09513684659277741, + 0.08917294825003427, + -1.2076236109760308e-15, + -0.10718853110247713, + 1.453885419228254e-15, + -0.3291259634750056, + -0.5899399043567829, + -5.801473429063729e-15, + 3.920089208471511e-15, + 0.47675778475722025, + -0.16905397867667005, + 0.3010461090816804, + -9.84633813595521e-16, + 1.4005570374053435e-14, + 0.19628184634539142, + 0.3449216067249908, + 0.35145704911631387 + ], + [ + -7.226857712559586e-17, + 1.776195008676595e-17, + 1.1392548901212323e-16, + -8.943828582255323e-17, + -3.144645361282775e-18, + -7.332999739597383e-17, + -1.341436674138385e-16, + -2.4215136750666306e-15, + -0.02617038672563256, + 7.880497847148807e-17, + 1.7393096197609456e-17, + 0.02196505299484301, + 0.0062261903679280695, + 1.0656050999102642e-16, + 4.545152860814954e-16, + -3.1993375190067283e-17, + 0.02512640162572376, + 4.584455765215653e-15, + 1.149307654095468e-15, + 8.074484048644328e-16, + -2.7799797686202617e-15, + -0.021008304643926216, + 0.01849240274163806, + 2.2749925345939393e-16, + -3.339843345845812e-16, + -1.8053838161993363e-15, + 9.349548527898479e-16, + -0.428431462907117, + 1.7450620777119125e-15, + 0.30400844605712857, + -6.9853615038769564e-15, + 9.754034169582665e-15, + -0.06544844107513838, + 0.37511880482093063, + 2.084348809335437e-15, + -1.0046494513768877e-15, + -2.03415893970138e-15, + -0.5620053241746479, + 0.5903724921428054, + -3.2099043292773115e-14, + -5.3532580357787044e-15, + -3.1516290904673497e-15 + ], + [ + 0.00019941074619584284, + -0.00015248221967125532, + -0.00012143877234843606, + -0.014667558998131736, + -0.013013300483294419, + 0.00981588060578977, + 0.004238730789351605, + 0.02376385002623363, + -2.1430337032580446e-15, + -0.0014798277677331032, + -0.013010859375523414, + -8.788484002947897e-17, + -3.189948175602379e-16, + 0.0037668168452689114, + 0.011391641598689125, + 0.001057945429376511, + -1.3797402895518763e-14, + 0.07703070872139414, + 0.02386187063691323, + 0.032185066465250295, + 0.012551099921569431, + -1.3623118081108904e-15, + -1.93134496370067e-15, + -0.018292147960538956, + 0.09370227874049385, + -0.04999902218526372, + -0.04863375536581155, + 3.721457252022226e-16, + -0.23978860265873322, + 2.248190762285365e-15, + -0.2147888984679387, + -0.06891856104517485, + -4.553093784338745e-15, + -4.996492875999471e-15, + 0.3441493939836149, + -0.4217317128181767, + -0.4600553561120964, + 3.3902570711759357e-15, + -5.479444221123077e-15, + 0.010247862441986071, + -0.6400227827468534, + -0.508696389294046 + ], + [ + -1.0010012371075216, + -1.4061167274331859e-05, + -0.002634855162960031, + -0.003072333750662838, + -3.6777838900388707e-09, + 0.013403477115239966, + 0.0002800171043882835, + 3.42174314914534e-08, + 1.828415108845459e-16, + -1.4254697727694438e-09, + -0.0031921586566836914, + -3.1667454342710296e-16, + 2.7109434910019556e-16, + -0.006714140088455252, + -1.3187519642568316e-07, + 0.026813743598619263, + 9.487176226790105e-16, + 5.363826564664914e-07, + -0.011000166710641062, + -1.0804944544711626e-06, + -0.15379034674636732, + 1.2422426433228657e-14, + -8.202994753713696e-15, + -1.8208843116896523e-06, + 0.3137888500002298, + -2.186734830673696e-06, + -0.746144713260582, + 3.6881817215941515e-16, + 0.17082829008531747, + -1.0264036059144819e-15, + -1.099378504572719e-05, + -0.32824319228101867, + 1.3178042936091564e-15, + 8.874400712403029e-15, + 2.2229677517506466e-07, + 0.21252920618473314, + 1.8594951989113936e-06, + 1.5020446187568966e-15, + -1.095410392776161e-14, + -0.24085642065312207, + -0.3076272177454349, + 2.578586874950061e-06 + ], + [ + -0.001073666524284968, + 2.775159955355845e-07, + 5.2067014207895814e-05, + -0.3928261010239749, + 9.78670864851219e-07, + 0.3267592703330938, + -0.016754263548965715, + -3.971134510536772e-07, + 7.629323098536348e-16, + -5.850253980317882e-07, + 0.07011891156379486, + -4.798654732431997e-16, + 4.440365902538791e-16, + 0.2586230864259066, + 1.872753092548266e-06, + 0.1439961753018607, + 3.287796254717533e-15, + 1.2577434375078693e-06, + -0.03561781282558765, + -3.270545284856942e-06, + -0.3973393350499186, + 3.247011978786863e-14, + -2.1242789876619184e-14, + -4.084482586394365e-06, + 0.7144178354189308, + -4.5220437282501325e-06, + -1.5743060202357466, + 7.618558305029821e-16, + 0.35541431175870897, + -2.126362390024033e-15, + -2.3865816706644173e-05, + -0.706476109653514, + 2.8069854152856658e-15, + 1.909092531241051e-14, + 1.3945214359220683e-07, + 0.49876597863604666, + 4.197157468747397e-06, + 2.9191204888550022e-15, + -1.6970163184254934e-14, + -0.40825570197335304, + -0.47140591990734176, + 4.448851502114274e-06 + ], + [ + 0.005422542454510997, + 6.179581362698963e-06, + 0.0011587834710626544, + -0.33522679680281886, + 1.2778947075702832e-06, + 0.4140429294363816, + -0.01148554172410358, + -1.1286558522690342e-06, + -7.383911191917963e-16, + -1.1948240501864836e-06, + 0.18883584022231414, + 4.066037299984612e-15, + -1.8574847575184068e-15, + 1.3633974812246439, + 1.3686280241303486e-05, + 1.2528727419487546, + 4.330135533611502e-15, + -3.2703123481572116e-06, + 0.24435770589861303, + 9.054051385225935e-06, + 0.9942576899960688, + -8.58197584037287e-14, + 4.24204462571216e-14, + 7.116171655132245e-06, + -0.6353592648622944, + 8.304729786062135e-06, + 3.8935997913690863, + -7.987043673837798e-16, + -0.7485112530871579, + 5.497706137465825e-15, + 4.383116882837187e-05, + 1.3473161464717132, + -7.565805596742324e-15, + -3.743776595696282e-14, + -3.2463879244564806e-06, + -0.4324861618151409, + -2.9437428082140555e-06, + -9.95175267761342e-15, + 8.892774078733299e-14, + 1.7656766928483771, + 2.5189688393158853, + -1.4746712557645627e-05 + ], + [ + -0.0012405591825331481, + 3.192826236833549e-05, + 6.216800881680539e-05, + -0.08035807369646543, + -0.21411453909915645, + -0.15056285373356795, + 0.34455640260343756, + 0.29900043377162355, + -2.920288071700987e-14, + -0.06881953368436597, + -0.23867449166725535, + -9.31914077587288e-16, + -6.571670645461179e-16, + 0.24946663471869274, + 0.3890466055802681, + -0.4765632647384737, + -9.968922372198807e-14, + 0.49211733026135845, + 0.21819015033637623, + 0.13578939019712966, + 0.2829056906383948, + -2.4387416174129088e-14, + 1.2750180344202483e-15, + -0.3020126905968096, + 0.21432691030913387, + 0.11575538244720963, + -0.04202554478662507, + -1.512667471615778e-15, + -0.0329933104822789, + -6.675954954488564e-16, + 0.16876380179674816, + -0.10065637500638636, + 3.735598413449597e-15, + 5.866836096891918e-15, + 0.10737007778830632, + 0.13600506287769687, + 0.19421228025408024, + -1.1214706473772473e-15, + 4.991898201642654e-15, + 0.046051234478171235, + 0.14615714764123386, + 0.22723133334540901 + ], + [ + -0.0012405529266072345, + -3.126450374150054e-05, + 6.250683379774272e-05, + -0.08035688483017414, + 0.21411464831739763, + -0.15056298541313606, + 0.34455541331984424, + -0.2990012073186251, + 2.544386324021274e-14, + 0.06882429994425189, + -0.23867447264020886, + -3.45348151814262e-17, + 1.1082799981697953e-15, + 0.24947208607551075, + -0.38904320428296496, + -0.4765638822525645, + 7.020792865091491e-14, + -0.4921147699637985, + 0.21819219219043381, + -0.13578112258646896, + 0.28290833955801, + -2.1805151215168236e-14, + 2.197475544836594e-14, + 0.3020121510629762, + 0.21433187505141785, + -0.11575538244006105, + -0.04202540613376373, + 7.083229202767394e-16, + -0.03299302879980649, + 6.388346218873917e-16, + -0.16877072939965365, + -0.10064705166120094, + -4.003148700659664e-15, + -4.654970776373855e-16, + -0.1073705580484886, + 0.13600484313293415, + -0.19421047689020757, + -5.811613183570061e-16, + 2.9059758295292124e-15, + 0.046050995715366076, + 0.14615199854888605, + -0.22723205623639917 + ], + [ + 7.550815613087127e-17, + 5.2339525361060326e-17, + -1.5300611039749706e-18, + 6.756925035001615e-16, + -2.373745156555218e-16, + -8.434193249870992e-16, + 3.0740190170240735e-15, + 4.629039219213014e-14, + 0.4994372599878549, + 5.313731410241504e-16, + -2.8106732358294856e-17, + 1.2460317355468467e-07, + -0.47749117640475536, + -1.3118869118643924e-16, + 7.513158216502361e-16, + 8.721148872131923e-15, + -0.7074405315510175, + -1.1201883590235598e-13, + -3.660985519979887e-14, + 4.707201014932263e-16, + -2.4482612763348014e-14, + -6.092557543795121e-06, + 0.626345343259594, + -1.074925396618705e-15, + 7.032300630316149e-15, + -1.0996882614231124e-15, + 3.43732740270206e-16, + -6.724310954594689e-07, + -1.522300644350135e-16, + 0.08821950572216442, + -1.3761501286423276e-15, + 5.716366776447079e-16, + -9.336153280657772e-08, + 0.005938367624195928, + 7.986281274309023e-17, + -7.237733767153121e-17, + 3.7509700516109532e-16, + 0.030995000156080893, + 1.5182021941761492e-07, + -2.760645509165656e-16, + 2.1403722196467027e-16, + 8.842180821794658e-16 + ], + [ + 0.0013559732853018437, + 0.0009299025348614756, + 4.244029083137401e-05, + -0.036791483680718384, + -0.05729551307716413, + -0.06168825848201445, + 0.20442803498585185, + 0.1419526006460149, + -1.3346688229899745e-14, + -0.0021050644139652404, + -0.16202016135964437, + 7.755997964035714e-17, + 3.907061178758958e-16, + 0.5122352745558014, + 0.9035344820940345, + 0.982734420398517, + 2.2851030811213454e-13, + -1.2728625971285636, + -0.08167061421440809, + 0.049798895245451606, + -0.4023187132834646, + 4.024468294715914e-14, + 2.3981561840330035e-15, + 1.0126811528888788, + -0.46904390197305934, + -1.3219387209204014, + 0.7946191815360729, + 6.4682340872018045e-15, + -0.2007942718421548, + 1.7827264354740726e-15, + -0.5510978494901214, + 0.4207765623261231, + -1.3934736080789335e-14, + -2.018894207410266e-14, + -0.3693123482008664, + -0.19345966574150078, + 0.33632343114812774, + -4.420155651135708e-15, + 2.8410638150658376e-14, + 0.6398037375178663, + 0.5044342118765871, + 1.0278499539920471 + ], + [ + 0.0013559716926445275, + -0.0009293976960642831, + 5.2356156238426254e-05, + -0.03679125900143305, + 0.05729573717552413, + -0.06168841870409751, + 0.20442779111033066, + -0.14195325347900944, + 1.0935716334628728e-14, + 0.002108606973596082, + -0.16202102675131277, + 1.3521192389788804e-15, + -6.084610497917691e-16, + 0.5122448806587175, + -0.903524885159391, + 0.982736758302364, + -2.046264669711747e-13, + 1.272859226572418, + -0.08167552425956554, + -0.04981293074079704, + -0.4023147607574893, + 2.914536865395837e-14, + -3.6909964650012043e-14, + -1.0126770990622613, + -0.4690574521653702, + 1.3219420352565974, + 0.7946077849585986, + -6.3098721525600955e-15, + -0.2007939488699487, + 1.4525911963987521e-15, + 0.5511253544307765, + 0.42074210881931334, + 1.0974992851607651e-14, + -1.3320347614512276e-15, + 0.3693106951216751, + -0.19344833162241584, + -0.33632632516572153, + 1.5641207492456734e-15, + 3.336472175288476e-14, + 0.6398055938296223, + 0.5044172209916621, + -1.0278556066458602 + ], + [ + -4.932978655726522e-16, + -3.858716378227519e-17, + 6.123100351749968e-17, + -6.041695399144834e-16, + -9.425043195842682e-17, + -3.5165198267195244e-16, + 1.914640977166994e-15, + 3.0184791532425215e-14, + 0.3175012947196149, + 5.747887747589612e-16, + -3.1420805283428995e-16, + 2.9161126060285523e-07, + -0.45464455509180113, + 2.9110216725185827e-16, + -2.699804769748e-15, + -8.97452969574768e-15, + 0.7068302142428814, + 1.0283250461628334e-13, + 3.666415078782934e-14, + -7.143906284247826e-15, + 5.059468039659547e-14, + 1.0761747902024719e-05, + -1.1789591248852218, + 4.194690256147354e-15, + -1.2402728073932051e-14, + 1.2152697296827239e-15, + -2.789376581549488e-15, + 1.066352740677053e-07, + 1.1283925680884935e-15, + 0.03925829472377486, + -1.7352869352441499e-15, + 1.6869416809188508e-15, + -2.58481577833276e-06, + 0.13023859833727228, + 2.167722824026574e-16, + -4.6611583562281654e-17, + -2.4884536469589583e-15, + -0.4193414189508609, + 2.484279946766648e-06, + -1.1530444961172796e-15, + -1.4153629052424042e-15, + 3.802717825355853e-16 + ], + [ + -4.9139972192500825e-05, + 1.2532259279523831e-06, + 0.0002349391812642442, + 0.009251829938461879, + -7.83312828880416e-09, + -0.004633293819919307, + 0.02255642428900985, + -1.1187615327688963e-07, + -7.307249494900588e-18, + -2.3565475894130017e-07, + 0.029830676238592438, + 6.937990380644147e-17, + -3.7948177260392955e-17, + 0.024687074975616462, + 1.1086946702840418e-07, + -0.09454050432555262, + -1.817558504145973e-15, + 1.545710988864917e-08, + 0.00613443603724553, + -5.049783516558052e-07, + -0.037484812192992975, + 3.2971086103830743e-15, + -1.7941581484042357e-16, + 3.5289263638528985e-08, + -0.08571045670591014, + 1.1846417237607226e-07, + 0.11228811345615068, + 2.148971982345758e-15, + 0.6086569380748019, + -2.4677677934574447e-15, + -5.82590500507937e-06, + -0.182951417682054, + 7.195879909752421e-17, + 5.1526866618719436e-15, + 5.42217326530445e-07, + 0.09406308523800401, + 9.929921354547193e-07, + -1.2623904850965279e-15, + 2.1460774781943605e-14, + 0.522157301587059, + -0.7282819362796469, + 5.833844045641243e-06 + ], + [ + 2.1752424389345974e-16, + 5.029684788106848e-17, + 1.5056354783991496e-16, + -6.901818143555954e-17, + -5.495539909486544e-17, + -7.530817129393245e-17, + 1.0486300913536804e-16, + 2.027340012490082e-15, + 0.021388142856544667, + -3.4998576939614456e-17, + -6.399827764709759e-17, + 0.03073769998788244, + 0.0031017351171815566, + -5.835656643344614e-18, + 2.98846114158256e-16, + 2.6370263257629685e-16, + -0.026625641874227387, + -4.166342503538712e-15, + -1.4846814385475334e-15, + -3.6660053850675167e-16, + -2.3562425808443625e-15, + -0.014351381344463655, + 0.018674785747310848, + 8.639683224695077e-16, + 1.5182863606969735e-16, + -1.4487072436454049e-16, + -7.79319888173439e-16, + -0.46618243745509697, + 1.299029596903125e-16, + -0.5218884376160272, + 2.103759898095543e-15, + -3.3510299093496484e-15, + -0.004814041046211707, + -0.07930951690533286, + 1.6280961228643237e-15, + -5.929643891752847e-16, + -6.104568066148214e-15, + -0.49817580354602725, + -0.5831228159617589, + 2.818900804626549e-14, + 4.587130960989423e-15, + 3.559288125141728e-15 + ], + [ + 0.0001428488476022815, + 1.9436043758412013e-06, + 0.0003643100780017828, + 0.017471740054396914, + -2.8168076362039754e-08, + 0.005902041689138691, + -0.014097693226871166, + -6.208381624593798e-10, + -1.9795894053489494e-16, + -1.434107906548935e-07, + 0.014940250475559188, + 7.083791917842222e-17, + 7.761259929568166e-17, + 0.020883717893617978, + 1.0941969442855063e-07, + -0.0749922980766121, + 6.445240793022765e-16, + -2.1950501686641712e-08, + -0.03785700620950398, + -8.734572358556348e-07, + -0.06829986604067217, + 6.476662697268637e-15, + -1.6283074560511371e-15, + 3.0772918165903135e-07, + -0.1347473303583918, + 5.843660360841642e-07, + 0.15702177215140115, + -1.3410985851144486e-15, + -0.3403731399455043, + 1.2972151036925361e-15, + -1.1494400704410922e-05, + -0.29633659811132707, + -1.615721246409904e-15, + 5.1871747494326175e-15, + -2.3202762266790163e-06, + 0.6168826841232165, + 3.46058918279828e-06, + 1.2150919913506634e-15, + -2.7383966979682646e-14, + -0.46821471299791445, + -0.6359807074320812, + 4.104162972750396e-06 + ], + [ + -8.31616302355406e-17, + 1.5390985820428067e-16, + -8.029479617064937e-17, + 5.65497348634005e-17, + 2.2467173678619488e-17, + -6.654482314491992e-17, + 1.6868291630511466e-16, + 2.144174518476808e-15, + 0.021388331658808026, + 1.142629404874943e-16, + 1.5663726775540034e-16, + -0.03073768980583212, + 0.00310179026247711, + 7.675970593775375e-17, + 4.579555170867327e-16, + 4.2387443241620065e-16, + -0.02662599700566266, + -4.497521198588121e-15, + -1.6509121171145228e-15, + 1.6380923132653128e-16, + 6.246024714100529e-16, + 0.014351238047080328, + 0.018674867070117777, + 9.85192009133767e-16, + 1.9652093408234633e-16, + 2.427627793232047e-15, + 5.18079899597404e-16, + 0.4661836112042665, + -4.2864112100471076e-15, + -0.5218867104693762, + 1.990730870216727e-16, + -7.098139819591328e-16, + 0.00481776677788859, + -0.0793089766590969, + 7.577646201689517e-16, + -1.05164554990519e-15, + -2.14778068438821e-15, + -0.49817067647520435, + 0.583128384335261, + -3.114073256964781e-14, + -5.355931475673746e-15, + -1.236105130627625e-15 + ], + [ + -1.258212042779213e-09, + -0.0003476842777311292, + 1.852122554267521e-06, + -1.1488269542528357e-07, + -0.03507609137664523, + -1.763644820865269e-08, + 1.1399252056516555e-07, + 0.02158794860148395, + -1.869055864127683e-15, + -0.029710130493980757, + -1.929384017379902e-07, + -6.322770343923514e-17, + -1.9606563752652493e-16, + 2.3001979828800917e-07, + -0.03297849064749296, + 3.918052259176637e-07, + 3.243558202731441e-15, + -0.02616486300903077, + 3.0651101548176644e-07, + 0.1260946536842911, + -1.3505188482803279e-06, + 1.838505007665779e-16, + -2.167097189346346e-15, + -0.09586832737159776, + -6.037601511145374e-07, + -0.12401770920517666, + 8.234648582124874e-07, + -6.12044408126081e-16, + 4.3489906716544065e-08, + -1.678742502355663e-15, + -0.4167292200415101, + 1.1885870168070753e-05, + -1.0043225081069346e-14, + -3.6821696713595675e-15, + -0.27962259180562377, + -6.92740899073739e-07, + -0.4351843272595588, + 6.075533741215819e-15, + 5.656753018663876e-15, + -9.307983166908034e-08, + 8.211849831382716e-06, + 1.0337225104750014 + ], + [ + -0.0017613300505372252, + -0.7039736902274834, + 0.7112943205776673, + -9.398298676863867e-05, + -0.0012603242328684708, + -0.0006688484801919751, + 0.004981847646505617, + -0.0015708111746075254, + -8.017393986435142e-17, + 0.0006765724056206014, + -0.007066406468787232, + 5.051739225088102e-16, + 7.147785599797437e-17, + 0.01256555217393601, + -0.005897784564322523, + 0.11191680173717093, + 8.106921876791239e-15, + -0.06687742323188318, + 0.0765178001208526, + 0.0030450520715755614, + -0.10550866456335226, + 8.777129317747287e-15, + -7.45794380354549e-15, + -0.03319048947718257, + 0.3346417862946701, + 0.5631376955590275, + 0.3587062263971887, + -1.9163775248931924e-15, + -0.021499153190363295, + 1.1532468664089402e-15, + -0.02094361080996321, + 0.029171806206018353, + -8.565931156225149e-16, + 4.167620957623335e-16, + -0.06835062933105666, + 0.06886646758261143, + -0.16677440399611618, + 6.930759715500321e-16, + 6.151704134479237e-15, + 0.10832874820663324, + 0.15397612051762954, + -0.19125184286022504 + ], + [ + 0.0001593217709164905, + -0.0006747553872258788, + 0.00023769096887512508, + -0.19856170361980904, + 0.311361515925907, + -0.2707031847860684, + -0.10910523548817863, + 0.16497737909353788, + -1.5204554738056677e-14, + -0.0050140495157909816, + -0.010430691644906108, + 9.8617158714887e-16, + 4.270795952617853e-17, + -0.1014766390289783, + 0.13729603293912887, + 0.2366542953552118, + 2.7724634382800118e-14, + -0.21593922586146483, + 0.18581388099238663, + 0.0529349494515786, + -0.2976215243986164, + 2.5241414156809218e-14, + -1.9852989964824295e-14, + -0.08135491813088488, + 0.8151188642566387, + 1.249137935968453, + 0.7738334745615227, + -4.137369421713338e-15, + -0.024671688227025104, + 2.4212849067939743e-15, + -0.08221630174079354, + 0.027670402570450437, + -2.6556892358849585e-15, + 1.8870870495248756e-15, + -0.17621681786467513, + 0.18129062162534024, + -0.3692561999846878, + 1.9190020035777542e-15, + 1.13980228989775e-14, + 0.19470984672436598, + 0.23910253489418548, + -0.31165435138767184 + ], + [ + -0.0015896791394839176, + 0.0032541967805489, + -0.002146170176443986, + -0.12583065669092253, + 0.3097280331221159, + -0.3605655553736356, + -0.2406226796125805, + 0.3002873533106203, + -2.5012688839749276e-14, + -0.00407998089672773, + 0.053104544602295994, + -2.9950598304183213e-15, + 4.1141499610879074e-16, + -0.6155690213224254, + 0.8049304065404382, + -0.882187800625097, + 1.215698629222247e-13, + -0.7103274244204719, + -0.19059895684270486, + 0.12124437842551516, + 0.17934033079892978, + -1.0722701928479029e-14, + 2.702389782825458e-14, + 0.6695656170654409, + -0.5551065293748615, + -2.4262136481857812, + -1.9781506261994595, + 9.630084792624026e-15, + 0.304599974092615, + -4.991164705822913e-15, + -0.3496132432462423, + -0.5385003009817542, + -5.004483914614567e-15, + 4.902566723803469e-15, + -0.0793584126475074, + 0.025749455725204057, + 0.6647640764611239, + -8.079919194652267e-16, + -4.8772971474900233e-14, + -0.9028036202143551, + -1.24053396886872, + 1.423970854736291 + ], + [ + -2.1302774418900985e-05, + -0.0003971703599785441, + 0.00026800634201995727, + -0.03348923539414831, + 0.0043591580205813085, + -0.0230017080057999, + 0.25673848846013386, + 0.03149573189863999, + -4.6724734535603716e-15, + 0.4933053220279898, + 0.3574203671885523, + 9.09828247095257e-16, + 9.6092234818424e-16, + -0.15090936059797616, + -0.007334298888454495, + 0.16426080702326465, + -8.403896948094573e-14, + 0.3071745299874465, + 0.6223888108789958, + 0.2357842560421918, + -0.02368051610147158, + 2.8689432988186593e-15, + -4.363279703678358e-15, + 0.5232292110330152, + -0.15614284277523915, + 0.09889137060334598, + -0.07988555297001854, + 2.1063143481847968e-16, + -0.00978630273172361, + 7.374641321923923e-16, + -0.09251980940178464, + -0.017162060020486373, + -1.9947811514246456e-15, + -2.7362210660381774e-16, + -0.1319325975678938, + 0.0452679184115815, + -0.009531243156416808, + 7.93606378920358e-16, + -2.2964872962877536e-15, + -0.035531823986098464, + -0.04317345137402883, + 0.04334963669813458 + ], + [ + 0.00011879352608077455, + 0.0012530919370605737, + -0.0011804004068441964, + 0.10376441173300982, + -0.09105189500507013, + -0.10939513892070361, + -0.12383098009361516, + 0.32019103586597136, + -2.9582825975857715e-14, + -0.0013022756581949938, + 0.2565230937526246, + 9.133280487401617e-16, + 2.697929238486124e-16, + 0.3386787088090443, + -0.32361278830264295, + -0.3670918572329757, + 2.931036371882568e-14, + -0.27522623007514957, + 0.058447134435822966, + 0.5738949787331941, + -0.5093974864886626, + 4.438337414083397e-14, + -2.6073330202405912e-14, + -0.07146968210957057, + -0.08826338763038255, + -0.08788227419365917, + -0.0026431002911249894, + 4.938956140446418e-16, + -0.10055125925046259, + 5.155904499309114e-16, + 0.17375779596770438, + 0.15836636379518715, + 4.445705777536366e-15, + -3.397591938350395e-16, + 0.1459447318632273, + -0.1978706059183265, + 0.1652906721149198, + -2.251278140093081e-15, + 2.3594307259089997e-15, + 0.06377878688893793, + 0.1893386208194984, + -0.17852320218695433 + ], + [ + -1.4309566617794539e-16, + -5.2240027241748233e-17, + -5.366488341965129e-18, + 1.8229732623175092e-16, + -2.108601910021458e-16, + -3.000178277328976e-16, + 8.748015708749215e-16, + 2.4565077608093327e-14, + 0.2610555058774571, + -7.357794379994173e-16, + -6.817950429749327e-16, + 0.4553432165137196, + 0.3730606237681433, + 7.059495403916955e-17, + 9.537893497896494e-16, + 3.3947109364688394e-15, + -0.4714092201251421, + -8.760264753351613e-14, + -2.0801999445627096e-14, + -1.4773032001525013e-14, + -3.529203938362898e-14, + -0.6852502003098183, + -0.4891013148244063, + 2.3277002625123767e-15, + -4.961239615007981e-15, + -1.4425026532856373e-16, + -5.967944324567362e-16, + 0.06475946353947955, + -3.9910486362367187e-17, + 0.04028119991191867, + -5.321090633512867e-16, + 7.900843068395309e-16, + -0.02436319440380709, + 0.03655039702636404, + -3.597973447746488e-17, + 4.880790729097958e-16, + -8.524372645440926e-16, + -0.03165369473305965, + -0.006503798265703627, + 1.885920606665614e-16, + -1.4924679300573276e-16, + -1.0611587489666756e-16 + ], + [ + -0.00046350996362259667, + 0.0003885018456417203, + -8.159541825817855e-05, + -9.14057740344396e-06, + 0.008162599124011832, + -0.01505500738868116, + 0.12574424464946918, + 0.013407918736240537, + -1.7337494237266964e-15, + 0.3497880070524966, + 0.2828900593441216, + 5.7188365573056e-16, + -6.054745908615651e-16, + -0.30664645916641126, + 0.05368063523913842, + -0.4637992976244009, + 7.670388878568893e-14, + -0.27998311552236155, + -0.6256482027368823, + -0.1874758885217344, + 0.14637553145442075, + -1.419859801191204e-14, + 6.686266845444978e-15, + -0.8507416869259571, + 0.36501640717401546, + -0.22869121306322182, + -0.30517305969622777, + -2.870157473950213e-16, + 0.00385794573589821, + -1.86161195430529e-15, + 0.10897332214071645, + -0.15420549795525237, + 2.6030974633703204e-15, + 4.803122880621867e-15, + 0.20443688328511134, + 0.026024320631515094, + -0.015437585917301242, + 4.958432965169636e-16, + -1.4271276077658225e-14, + -0.32481890304458455, + -0.17686013475998155, + 0.14111544848111712 + ], + [ + 0.0006925141255544204, + -0.0013552560245302344, + 0.0007270548487615724, + 0.01605473376738698, + -0.039077630320965055, + -0.03456730954087202, + -0.0385872484115535, + 0.1609208048569433, + -1.6470307511335543e-14, + -0.007930514214979542, + 0.16281488537282898, + 2.0336769806409827e-15, + -3.601423258571867e-16, + 0.6784299591451881, + -0.811048716772857, + 0.7977388976037361, + -1.1386588208137066e-13, + 0.798947382035201, + 0.020023567587810542, + -0.8739359954156816, + 0.7860550593396991, + -7.098191700530822e-14, + 3.3544565797600985e-14, + -0.2423979968761302, + 0.11716997025686536, + 1.0671430577996348, + 0.920318212694082, + -5.037957127381846e-15, + -0.12728962133089805, + 1.555773677688173e-15, + 0.11443710770481412, + 0.19799111997630087, + 6.449095090802989e-16, + -4.6950804854848115e-15, + 0.0021971626684818783, + 0.09251292626446021, + -0.45048405367278255, + 7.536831655314065e-16, + 2.6019628748097452e-14, + 0.45967445538372714, + 0.7609855470593834, + -0.8193502170345943 + ], + [ + 9.941902557767123e-19, + 3.9524331179906395e-17, + -4.013368368931335e-18, + 1.1609496276492806e-16, + -1.838894341763903e-16, + -6.335561754843437e-17, + 7.193592823879593e-16, + 1.3634064274756099e-14, + 0.1541764160964085, + -7.402164047715438e-16, + -1.8849187160488729e-16, + 0.36809088937681844, + 0.38464811389448744, + -4.052407285630491e-16, + 2.5573287919020296e-16, + -1.1173935659694867e-15, + 0.32292963219866677, + 6.819808298624103e-14, + 1.2453401034743436e-14, + 1.7845665429410595e-14, + 3.0251925732049343e-14, + 0.7921410947337875, + 0.8326170925760483, + -4.374593187199196e-15, + 8.925744519623895e-15, + 1.2128871022414656e-15, + 2.5246631298329e-15, + 0.07140221889563941, + -5.084461983430804e-16, + 0.02433954162481145, + 7.300547892713681e-16, + -2.761330350896548e-16, + 0.03269227221591637, + -0.072093477112905, + -3.5874932673186177e-16, + -4.306813479123662e-16, + 2.137318266269459e-15, + 0.22823163910454128, + 0.1427463422107145, + -6.188596483025551e-15, + -9.481149016852375e-17, + -1.3310820671260475e-15 + ], + [ + -2.536791514165615e-05, + -7.368484754010199e-05, + 6.716801033864665e-05, + 0.00895130898742157, + -0.004046033979091001, + 0.0013900974463644483, + -0.02112087593129741, + 0.004106788201742911, + -5.061768783329361e-16, + -0.017587004326387483, + -0.0017505281535641923, + -4.662696804922696e-17, + 3.3157019155371244e-17, + -0.013654234082026286, + -0.011284963861219301, + 0.03780073564003595, + -4.106738883612274e-15, + 0.027590776215515964, + -0.012116536999196346, + 0.024047506355676644, + 0.009321706323079966, + -5.688376173091981e-16, + -8.916878172122175e-16, + -0.08915044275324883, + -0.0015498095626796055, + -0.02962000582878333, + -0.08654003570732069, + -1.400820347524685e-15, + -0.30899239946043844, + -6.859316519965465e-16, + -0.4599270914110555, + 0.14306982140900384, + -1.0419282671209046e-14, + -9.719160397836612e-15, + -0.30469993610010165, + 0.22640696575645444, + 0.3124723212482826, + -3.948702801982127e-15, + 2.935248745243979e-14, + 0.6536776133688468, + 0.050902422774331955, + -0.38431112364017084 + ], + [ + -5.887023056144293e-18, + -9.197477353987915e-17, + 1.9156727911589254e-16, + -1.2986286351701265e-16, + -8.681171385924841e-17, + -1.1640376421991494e-18, + -1.0471343086374296e-16, + -2.35981957581066e-15, + -0.026170225653117465, + 5.943576761660668e-17, + 3.0081166207421423e-17, + -0.02196507412962472, + 0.006226199355940923, + 8.636686254927274e-18, + 8.168617534943777e-17, + -4.942145693620185e-16, + 0.025126038437548966, + 5.9640824436240554e-15, + 5.206793311754575e-16, + -4.8808857014645754e-17, + 8.281298065950059e-16, + 0.02100801856152951, + 0.01849284373650161, + -1.0572341365605352e-15, + -4.292535202826627e-16, + 1.8363372150960536e-15, + -9.729563974303852e-16, + 0.4284300906913619, + -4.830393855232485e-16, + 0.30400956679385815, + -9.13373061649192e-15, + 6.463303314248677e-15, + 0.06543408002086683, + 0.37512236450482184, + 1.4446105371679421e-15, + 4.975664899759549e-16, + -4.750072127325573e-15, + -0.5620111022828179, + -0.5903661724870815, + 2.8060619567726474e-14, + 4.898174389848054e-15, + 4.9248423714119294e-15 + ], + [ + -0.00016279249340948624, + 4.061266495791603e-05, + -0.00012951174303520822, + 0.009312477658289459, + -0.007623469438667967, + -0.002758711836439576, + -0.0077699786507163665, + 0.012784397040472528, + -1.180977763205326e-15, + -0.004289207979364994, + 0.004828167781991947, + 1.7916372162398945e-16, + -4.252282720005893e-17, + -0.00033343081619599096, + 0.0070407864001065905, + 0.021173185488919576, + -6.206560211629025e-15, + 0.03691280246467279, + -0.005805594286721782, + 0.01830423670489943, + -0.011474100087736253, + 1.0287660867640863e-15, + -8.733346564627528e-16, + -0.04235451649188022, + -0.060922728035432507, + -0.09513638194129317, + 0.08917337934428607, + 1.2671344145774996e-16, + -0.10718793802562869, + -6.294815442539691e-17, + 0.3290900863609058, + -0.5899640731421419, + 8.400889222231962e-15, + 2.1931206010522772e-14, + -0.47675330512334513, + -0.16905318794989735, + -0.3010456721187955, + 3.8738272911470583e-16, + 1.3645733716227054e-14, + 0.19628210327386886, + 0.3449160183381891, + -0.35146170675284094 + ], + [ + -1.0372416947701155e-16, + 2.1540387776205482e-17, + -4.677855207288378e-17, + -2.7975603887133102e-18, + -3.677958873669241e-17, + -1.1740023438840006e-16, + 2.1018893665934972e-17, + 6.02133592655218e-16, + 0.006718527095527946, + -1.5015341087039998e-17, + 3.6404112862690624e-17, + 0.0049519320418373175, + -0.0004762870104316398, + 2.843856645510013e-17, + -2.6652439424657585e-16, + 2.8689176484437154e-16, + -0.0035581622472791777, + -6.503166996488833e-16, + 1.7268970566068356e-16, + -5.039927298122869e-16, + -1.5181498464797696e-15, + -0.016910542174437996, + 0.010398523311268561, + 5.260383858423728e-16, + 4.946067784365688e-17, + -1.5704721638779545e-15, + 2.167455325738879e-15, + -0.03747918474317207, + -1.6689294000632852e-15, + -0.26867236575213177, + -2.5715954874157724e-14, + 1.4198185070048624e-14, + 0.7042697017754728, + 0.5960241584192306, + 4.5516784663927806e-15, + 1.0401229438979508e-15, + 2.4950288471129013e-15, + 0.278480290703868, + 0.0598424296121514, + -4.0397850821947335e-15, + -1.1245396117706782e-15, + -2.327635838878825e-15 + ], + [ + -0.000199408835729763, + -0.0001511802900839243, + 0.0001230597142560452, + 0.014667454698213456, + -0.01301335452150573, + -0.009815829780834154, + -0.004238633050417151, + 0.02376381235675366, + -2.2038939010631763e-15, + -0.0014800310264468776, + 0.013010922913857843, + 4.1869931377365855e-17, + -4.1251749764666887e-16, + -0.0037669387273488153, + 0.011391536794820226, + -0.0010579372303174575, + -1.241349538406943e-14, + 0.07703009871135336, + -0.023862275066472382, + 0.03218456585682706, + -0.012551978277172233, + 1.5135176991456084e-15, + -1.5740904032765052e-15, + -0.018291448810810712, + -0.09370277279435005, + -0.04999921100720285, + 0.048634073569554424, + 1.6927595621620924e-15, + 0.23978843466263194, + -5.277010638132867e-16, + -0.21478636234351697, + 0.06893241986380544, + -5.5680485514386706e-15, + -7.377347765510867e-15, + 0.34414605722690944, + 0.42173804513701674, + -0.46005166393851354, + 2.133614791380758e-15, + 3.419828578416071e-15, + -0.010246627554700865, + 0.6400145339250181, + -0.5087048653287369 + ] + ], + "beta": [ + [ + -0.0017613483088018916, + 0.711518310624656, + 0.7037472999089728, + -9.399455949241367e-05, + 0.001260283200973966, + -0.000668875084214835, + 0.0049818667015517435, + 0.0015708258283454223, + 2.5874245447605983e-16, + -0.000676475790391484, + -0.00706643792623164, + 4.604649764119856e-17, + 1.8397405373366005e-16, + 0.012565480068375634, + 0.005898052039455152, + 0.11191713207721744, + -1.5374355809907998e-14, + 0.06687840864819246, + 0.07651740349616816, + -0.0030466893827386476, + -0.1055084723904908, + 1.1199218234569258e-14, + -1.06477179509947e-14, + 0.03318764159041528, + 0.3346427292991582, + -0.5631349789506233, + 0.3587102802356655, + 2.1985695340438685e-15, + -0.021499647260490033, + -1.1587645359928652e-16, + 0.02094549454587577, + 0.02917115722777478, + -8.060958736978645e-16, + -1.6425192025007424e-15, + 0.06834996487394485, + 0.0688658267151557, + 0.16677540651498332, + -2.8384964822483334e-15, + 4.8897844219890855e-15, + 0.10832832612676346, + 0.15397869158292363, + 0.1912492697195178 + ], + [ + 0.00015932168684034643, + 0.0006772539235809356, + 0.00023048437592884555, + -0.1985633172167325, + -0.3113606466003104, + -0.27070335301315374, + -0.10910585651897751, + -0.16497648792835276, + 1.6278534121343272e-14, + 0.005013863525095217, + -0.010431021621965847, + -7.33057304633428e-17, + 5.563742606420083e-16, + -0.10147510747777565, + -0.13729756142329502, + 0.23665519314511257, + -4.6627852825666184e-14, + 0.21594142827170573, + 0.18581241799680553, + -0.05293983744576951, + -0.29761999129753947, + 3.062792476926799e-14, + -2.757324550331927e-14, + 0.08134789786588165, + 0.8151208534588398, + -1.2491318627415071, + 0.7738423967618882, + 4.9223901272352065e-15, + -0.024672858732974652, + -3.3680701891273864e-16, + 0.0822178765362882, + 0.027666933260897076, + -1.060293318230678e-15, + -2.3453367865827154e-15, + 0.1762152883961709, + 0.18128957242035112, + 0.36925871774552554, + -6.484538461770268e-15, + 7.737981812801435e-15, + 0.19470897587479535, + 0.23910636166119137, + 0.3116498530840739 + ], + [ + -0.001589681238226593, + -0.0032768983761073977, + -0.0021113468486820067, + -0.12583179865479674, + -0.309727298370417, + -0.36056594701840744, + -0.2406239923493408, + -0.300286015231819, + 2.7454870236320667e-14, + 0.004078341357603479, + 0.0531037886043208, + -1.0178629474267361e-15, + 8.112341528600056e-17, + -0.6155619032464832, + -0.8049432984607647, + -0.8821890986070516, + -1.1615764626128125e-13, + 0.7103285085645865, + -0.1906021313545944, + -0.12123991900690492, + 0.17934417040471085, + -2.420767969487312e-14, + 1.1555498451699517e-14, + -0.6695635785674935, + -0.5551165620389613, + 2.4262038463165325, + -1.9781727188748213, + -9.928167894319252e-15, + 0.30460225421715875, + -1.1901711152371207e-15, + 0.3495787081267972, + -0.538525175238965, + 1.3231816980767182e-14, + 2.3129469867297278e-14, + 0.07936236603108633, + 0.025759619259084265, + -0.664764142064391, + 1.1217924552695575e-14, + -4.344626320943823e-14, + -0.9028031503241328, + -1.240558722001198, + -1.4239563644443407 + ], + [ + 0.00011879210489503894, + -0.0012656106462341351, + -0.0011669747344560586, + 0.10376527771523388, + 0.09105158492923618, + -0.10939579856717607, + -0.12383275026944963, + -0.32019174781303344, + 3.0460851898998265e-14, + 0.0012982139193877288, + 0.25652213337879476, + 3.563541456198813e-16, + -6.895727376980855e-16, + 0.33867382577244537, + 0.32361693652720475, + -0.36709328259362506, + -5.1496894065507564e-14, + 0.27522749527715745, + 0.058444878083615155, + -0.5739059828216324, + -0.509384043572806, + 5.091653419998346e-14, + -2.0543379131243357e-14, + 0.07146897861298752, + -0.08826088537434426, + 0.08788162635836455, + -0.002643697174707393, + -1.4034952810459226e-15, + -0.10055090308404997, + -7.916971695035124e-17, + -0.17374677669888117, + 0.15837614763827498, + -3.519979146748753e-15, + -5.5025284998782434e-15, + -0.14594406710701505, + -0.19787161045371737, + -0.1652932162236645, + 2.90748687434069e-15, + 5.865657779998858e-15, + 0.06377906480372146, + 0.1893427092795396, + 0.17852209465434132 + ], + [ + -2.1302422111885993e-05, + 0.00040000584458483305, + 0.00026375615599977145, + -0.03348937086215691, + -0.004358880347415549, + -0.023001860107845665, + 0.2567400912495197, + -0.0314976612622805, + 1.4375703009651906e-15, + -0.4933099112024433, + 0.357413170446587, + 3.7092735626386273e-16, + -5.522352390264785e-16, + -0.15090928215272595, + 0.007332584999076089, + 0.16426015263436078, + 2.355674577997738e-14, + -0.3071713229025603, + 0.6223920876632938, + -0.23578639979622648, + -0.02367374606145615, + 6.509123611249409e-15, + 5.927185928046916e-15, + -0.5232255097751093, + -0.15614580835953817, + -0.09889188916773153, + -0.07988522034988751, + 4.529862095216803e-16, + -0.009787325853563664, + -1.0095612678369212e-16, + 0.09251851118994077, + -0.01716705733092771, + 1.6995846227301808e-15, + 1.1949459215063904e-15, + 0.13193231994120133, + 0.045269586510319336, + 0.009531790498651048, + -1.0537588571368305e-15, + -2.0847091170873643e-15, + -0.03553205580032229, + -0.043174172354372965, + -0.043349458922152535 + ], + [ + -1.8480350346778257e-16, + 1.41250331189617e-16, + -1.4148338436777513e-16, + 5.994518409959783e-16, + 5.077479867055641e-16, + -2.634195575577266e-16, + 1.4719120327691139e-15, + 2.417673775485217e-14, + 0.2610567515172919, + 9.389445316524583e-17, + 1.3979782509272695e-15, + -0.45534170942668656, + 0.3730616103322131, + 1.6503552983791064e-16, + -4.7963675652013115e-16, + 6.515092828304018e-15, + -0.471410931415238, + -8.584914925074702e-14, + -2.8341507863383373e-14, + -6.963670010177395e-15, + 8.840684485383916e-14, + 0.6852568388333309, + -0.48909025184657434, + 1.2835724607421146e-15, + -4.243341777153026e-15, + 2.2160706000008914e-15, + -4.522026024810084e-15, + -0.06476034189134464, + 1.7474771359564533e-15, + 0.04028156472703985, + -4.727863842160565e-16, + -7.480511494301423e-16, + 0.024361751055532083, + 0.03655124977078849, + 4.01567144867991e-17, + -4.8670864867640396e-17, + -6.2306745578434e-16, + -0.03165336472413785, + 0.0065037375325256545, + -2.308312685919986e-15, + -3.3749378031716085e-15, + -5.012081892918695e-16 + ], + [ + 0.0006925135359825555, + 0.0013629325219717825, + 0.0007125617404745804, + 0.016054941335634065, + 0.03907758055843969, + -0.034567529852519596, + -0.038588116673101865, + -0.16092127672708484, + 1.5676929108553442e-14, + 0.007928087634621498, + 0.1628143943544517, + 1.342823376513055e-15, + -1.3907492056563523e-15, + 0.678420931649487, + 0.811060572548184, + 0.797740548818454, + 1.4016433847686068e-13, + -0.7989485080983849, + 0.020029084110875763, + 0.8739530154395622, + 0.7860342742856201, + -7.335611718400788e-14, + 3.366454988952001e-14, + 0.24240014514988534, + 0.11717234658889061, + -1.0671381929296226, + 0.9203281304648923, + 5.314906174497753e-15, + -0.12729065484730231, + 8.716252810327436e-16, + -0.11442460877745136, + 0.1979999993212946, + -4.899607572293976e-15, + -8.856935854708068e-15, + -0.0021994504942024493, + 0.09250763922192878, + 0.45048541812721793, + -7.000374870254966e-15, + 2.224830964716879e-14, + 0.4596737239767952, + 0.7609992567912525, + 0.8193405170045229 + ], + [ + -0.000463509471573487, + -0.0003893489576391002, + -7.74480779093027e-05, + -9.186833998974275e-06, + -0.008162545072009609, + -0.01505505526278426, + 0.12574512820250514, + -0.013408958786484607, + 3.2524926791369187e-16, + -0.3497916909454886, + 0.2828849832592, + -4.0856258358509393e-17, + 1.8913922521692952e-16, + -0.30664611487388105, + -0.05368500153624419, + -0.4637995200124685, + -2.006553225500354e-14, + 0.2799801435226983, + -0.625652916458579, + 0.18748273279473754, + 0.14636917247765055, + -1.766625694167923e-14, + 1.3916548084652963e-15, + 0.850736873531966, + 0.365022470149761, + 0.22868923476260353, + -0.30517475548843165, + -8.473361627844091e-16, + 0.003859038323223647, + 4.50089906643573e-16, + -0.10898367527197976, + -0.15419968938885648, + -1.1424188256370197e-15, + 3.203324365629822e-15, + -0.20443627422890492, + 0.02602142864834334, + 0.015438404957295326, + 1.4743941445944099e-15, + -1.6370411116751388e-14, + -0.3248195121009972, + -0.17686200698523266, + -0.14111295297121318 + ], + [ + 1.1636532698013606e-16, + -1.5588341596154192e-16, + 1.0880766650822896e-16, + 3.829425894003322e-16, + 2.689915746866007e-16, + 2.757587578409129e-16, + 2.790332266104727e-16, + 1.4088960362877333e-14, + 0.15417718420310664, + -3.6051002216484057e-16, + 1.1445869737293796e-15, + -0.3680897441682019, + 0.38464932190265366, + -3.4989520604123526e-16, + 1.3208799822193764e-15, + -5.239609100628053e-15, + 0.32293175303093874, + 6.603218450101121e-14, + 2.0951207944864677e-14, + 8.426016991924388e-15, + -1.1322338968157453e-13, + -0.7921538783554328, + 0.832604838480252, + -3.466428241329757e-15, + 7.62286210620336e-15, + -3.4844184144479423e-15, + 4.246742343087103e-15, + -0.07140184670084454, + -7.686087971447673e-16, + 0.024338910721400965, + 1.325413391904308e-15, + -3.5613663596543516e-16, + -0.032689538286556144, + -0.07209477332422434, + 1.9967500064428566e-16, + 4.0817457963609506e-16, + 8.422792198167016e-16, + 0.2282306772925758, + -0.14274901944077537, + 9.006699812027153e-15, + 3.9653878344283124e-15, + 8.215633790227969e-16 + ], + [ + -2.5369137658829638e-05, + 7.43956851969767e-05, + 6.637760214646658e-05, + 0.00895134520344189, + 0.0040459634994585185, + 0.0013901073856583857, + -0.0211210064779574, + -0.0041066425855090264, + 4.767080834448631e-16, + 0.017586970822102248, + -0.0017502896243502184, + 2.6989972035650122e-17, + -9.013590235521426e-18, + -0.013654429006799975, + 0.01128480365086407, + 0.03780074052251412, + 6.0058305182444665e-15, + -0.027590895833582752, + -0.012116695725973804, + -0.02404724985623946, + 0.00932205867414363, + -4.699583650904146e-16, + 1.5271006952726165e-15, + 0.08915051086617565, + -0.0015487918355510195, + 0.02961948136019113, + -0.0865404815016752, + -9.84721574009614e-16, + -0.30899351199351444, + 1.702111571193274e-15, + 0.4599340843237217, + 0.1430412367399587, + 8.808819082813115e-15, + 6.008282029914628e-15, + 0.3046960193659428, + 0.22641584892931893, + -0.3124698751554813, + 1.2599953482819848e-15, + 3.609387911168111e-14, + 0.6536784243389747, + 0.05090683452797932, + 0.38431115969029694 + ], + [ + -1.270817712639795e-16, + 8.356048854577706e-17, + -9.850996118752719e-17, + 2.5900699545463132e-17, + -6.188449970488292e-17, + -7.690125200602923e-17, + 7.912867641673169e-17, + 5.585624077908048e-16, + 0.006718551983902079, + -4.656603857554806e-17, + 3.839682720919745e-17, + -0.004951908923828109, + -0.00047628582662786177, + 3.533974437587096e-17, + -1.3971523963490332e-16, + 2.0945212611334557e-16, + -0.003558253263976293, + -7.859960059983535e-16, + -1.8422529660206642e-16, + -3.9605588392832494e-16, + 1.2981419808070361e-15, + 0.016910409456521523, + 0.010398862137348069, + 3.1290370196262395e-16, + -1.3790044223663603e-16, + -2.3436232061197704e-16, + 2.375029703224186e-16, + 0.03747916132983966, + -8.427398305892728e-16, + -0.2686735605538165, + 5.095358064593045e-15, + 1.146482171371745e-14, + -0.7042904341557328, + 0.5959989919205798, + 4.263109287960304e-15, + -2.0087536897655615e-15, + 3.5548524505784e-15, + 0.2784802001833716, + -0.059844473326176914, + 2.0135518204233937e-15, + 9.824842934639128e-16, + -2.4553599332942103e-15 + ], + [ + -0.00016279402038978409, + -4.1993433812048596e-05, + -0.00012907356151309386, + 0.009312534928960427, + 0.007623427823470033, + -0.002758731453626304, + -0.007770047641615649, + -0.012784381783544766, + 1.2863771917162084e-15, + 0.004289097664279655, + 0.004828188307893601, + 1.1763165448547334e-16, + 1.8715473884293376e-16, + -0.0003333587859598123, + -0.007040812318519787, + 0.021173206450744067, + 6.870170532814403e-15, + -0.03691307653644089, + -0.005805505206568458, + -0.018304680005941647, + -0.011473706480055504, + 1.1034888958396258e-15, + 3.708569581538061e-16, + 0.0423550129046431, + -0.060922279558813006, + 0.09513684659277741, + 0.08917294825003427, + -1.2076236109760308e-15, + -0.10718853110247713, + 1.453885419228254e-15, + -0.3291259634750056, + -0.5899399043567829, + -5.801473429063729e-15, + 3.920089208471511e-15, + 0.47675778475722025, + -0.16905397867667005, + 0.3010461090816804, + -9.84633813595521e-16, + 1.4005570374053435e-14, + 0.19628184634539142, + 0.3449216067249908, + 0.35145704911631387 + ], + [ + -7.226857712559586e-17, + 1.776195008676595e-17, + 1.1392548901212323e-16, + -8.943828582255323e-17, + -3.144645361282775e-18, + -7.332999739597383e-17, + -1.341436674138385e-16, + -2.4215136750666306e-15, + -0.02617038672563256, + 7.880497847148807e-17, + 1.7393096197609456e-17, + 0.02196505299484301, + 0.0062261903679280695, + 1.0656050999102642e-16, + 4.545152860814954e-16, + -3.1993375190067283e-17, + 0.02512640162572376, + 4.584455765215653e-15, + 1.149307654095468e-15, + 8.074484048644328e-16, + -2.7799797686202617e-15, + -0.021008304643926216, + 0.01849240274163806, + 2.2749925345939393e-16, + -3.339843345845812e-16, + -1.8053838161993363e-15, + 9.349548527898479e-16, + -0.428431462907117, + 1.7450620777119125e-15, + 0.30400844605712857, + -6.9853615038769564e-15, + 9.754034169582665e-15, + -0.06544844107513838, + 0.37511880482093063, + 2.084348809335437e-15, + -1.0046494513768877e-15, + -2.03415893970138e-15, + -0.5620053241746479, + 0.5903724921428054, + -3.2099043292773115e-14, + -5.3532580357787044e-15, + -3.1516290904673497e-15 + ], + [ + 0.00019941074619584284, + -0.00015248221967125532, + -0.00012143877234843606, + -0.014667558998131736, + -0.013013300483294419, + 0.00981588060578977, + 0.004238730789351605, + 0.02376385002623363, + -2.1430337032580446e-15, + -0.0014798277677331032, + -0.013010859375523414, + -8.788484002947897e-17, + -3.189948175602379e-16, + 0.0037668168452689114, + 0.011391641598689125, + 0.001057945429376511, + -1.3797402895518763e-14, + 0.07703070872139414, + 0.02386187063691323, + 0.032185066465250295, + 0.012551099921569431, + -1.3623118081108904e-15, + -1.93134496370067e-15, + -0.018292147960538956, + 0.09370227874049385, + -0.04999902218526372, + -0.04863375536581155, + 3.721457252022226e-16, + -0.23978860265873322, + 2.248190762285365e-15, + -0.2147888984679387, + -0.06891856104517485, + -4.553093784338745e-15, + -4.996492875999471e-15, + 0.3441493939836149, + -0.4217317128181767, + -0.4600553561120964, + 3.3902570711759357e-15, + -5.479444221123077e-15, + 0.010247862441986071, + -0.6400227827468534, + -0.508696389294046 + ], + [ + -1.0010012371075216, + -1.4061167274331859e-05, + -0.002634855162960031, + -0.003072333750662838, + -3.6777838900388707e-09, + 0.013403477115239966, + 0.0002800171043882835, + 3.42174314914534e-08, + 1.828415108845459e-16, + -1.4254697727694438e-09, + -0.0031921586566836914, + -3.1667454342710296e-16, + 2.7109434910019556e-16, + -0.006714140088455252, + -1.3187519642568316e-07, + 0.026813743598619263, + 9.487176226790105e-16, + 5.363826564664914e-07, + -0.011000166710641062, + -1.0804944544711626e-06, + -0.15379034674636732, + 1.2422426433228657e-14, + -8.202994753713696e-15, + -1.8208843116896523e-06, + 0.3137888500002298, + -2.186734830673696e-06, + -0.746144713260582, + 3.6881817215941515e-16, + 0.17082829008531747, + -1.0264036059144819e-15, + -1.099378504572719e-05, + -0.32824319228101867, + 1.3178042936091564e-15, + 8.874400712403029e-15, + 2.2229677517506466e-07, + 0.21252920618473314, + 1.8594951989113936e-06, + 1.5020446187568966e-15, + -1.095410392776161e-14, + -0.24085642065312207, + -0.3076272177454349, + 2.578586874950061e-06 + ], + [ + -0.001073666524284968, + 2.775159955355845e-07, + 5.2067014207895814e-05, + -0.3928261010239749, + 9.78670864851219e-07, + 0.3267592703330938, + -0.016754263548965715, + -3.971134510536772e-07, + 7.629323098536348e-16, + -5.850253980317882e-07, + 0.07011891156379486, + -4.798654732431997e-16, + 4.440365902538791e-16, + 0.2586230864259066, + 1.872753092548266e-06, + 0.1439961753018607, + 3.287796254717533e-15, + 1.2577434375078693e-06, + -0.03561781282558765, + -3.270545284856942e-06, + -0.3973393350499186, + 3.247011978786863e-14, + -2.1242789876619184e-14, + -4.084482586394365e-06, + 0.7144178354189308, + -4.5220437282501325e-06, + -1.5743060202357466, + 7.618558305029821e-16, + 0.35541431175870897, + -2.126362390024033e-15, + -2.3865816706644173e-05, + -0.706476109653514, + 2.8069854152856658e-15, + 1.909092531241051e-14, + 1.3945214359220683e-07, + 0.49876597863604666, + 4.197157468747397e-06, + 2.9191204888550022e-15, + -1.6970163184254934e-14, + -0.40825570197335304, + -0.47140591990734176, + 4.448851502114274e-06 + ], + [ + 0.005422542454510997, + 6.179581362698963e-06, + 0.0011587834710626544, + -0.33522679680281886, + 1.2778947075702832e-06, + 0.4140429294363816, + -0.01148554172410358, + -1.1286558522690342e-06, + -7.383911191917963e-16, + -1.1948240501864836e-06, + 0.18883584022231414, + 4.066037299984612e-15, + -1.8574847575184068e-15, + 1.3633974812246439, + 1.3686280241303486e-05, + 1.2528727419487546, + 4.330135533611502e-15, + -3.2703123481572116e-06, + 0.24435770589861303, + 9.054051385225935e-06, + 0.9942576899960688, + -8.58197584037287e-14, + 4.24204462571216e-14, + 7.116171655132245e-06, + -0.6353592648622944, + 8.304729786062135e-06, + 3.8935997913690863, + -7.987043673837798e-16, + -0.7485112530871579, + 5.497706137465825e-15, + 4.383116882837187e-05, + 1.3473161464717132, + -7.565805596742324e-15, + -3.743776595696282e-14, + -3.2463879244564806e-06, + -0.4324861618151409, + -2.9437428082140555e-06, + -9.95175267761342e-15, + 8.892774078733299e-14, + 1.7656766928483771, + 2.5189688393158853, + -1.4746712557645627e-05 + ], + [ + -0.0012405591825331481, + 3.192826236833549e-05, + 6.216800881680539e-05, + -0.08035807369646543, + -0.21411453909915645, + -0.15056285373356795, + 0.34455640260343756, + 0.29900043377162355, + -2.920288071700987e-14, + -0.06881953368436597, + -0.23867449166725535, + -9.31914077587288e-16, + -6.571670645461179e-16, + 0.24946663471869274, + 0.3890466055802681, + -0.4765632647384737, + -9.968922372198807e-14, + 0.49211733026135845, + 0.21819015033637623, + 0.13578939019712966, + 0.2829056906383948, + -2.4387416174129088e-14, + 1.2750180344202483e-15, + -0.3020126905968096, + 0.21432691030913387, + 0.11575538244720963, + -0.04202554478662507, + -1.512667471615778e-15, + -0.0329933104822789, + -6.675954954488564e-16, + 0.16876380179674816, + -0.10065637500638636, + 3.735598413449597e-15, + 5.866836096891918e-15, + 0.10737007778830632, + 0.13600506287769687, + 0.19421228025408024, + -1.1214706473772473e-15, + 4.991898201642654e-15, + 0.046051234478171235, + 0.14615714764123386, + 0.22723133334540901 + ], + [ + -0.0012405529266072345, + -3.126450374150054e-05, + 6.250683379774272e-05, + -0.08035688483017414, + 0.21411464831739763, + -0.15056298541313606, + 0.34455541331984424, + -0.2990012073186251, + 2.544386324021274e-14, + 0.06882429994425189, + -0.23867447264020886, + -3.45348151814262e-17, + 1.1082799981697953e-15, + 0.24947208607551075, + -0.38904320428296496, + -0.4765638822525645, + 7.020792865091491e-14, + -0.4921147699637985, + 0.21819219219043381, + -0.13578112258646896, + 0.28290833955801, + -2.1805151215168236e-14, + 2.197475544836594e-14, + 0.3020121510629762, + 0.21433187505141785, + -0.11575538244006105, + -0.04202540613376373, + 7.083229202767394e-16, + -0.03299302879980649, + 6.388346218873917e-16, + -0.16877072939965365, + -0.10064705166120094, + -4.003148700659664e-15, + -4.654970776373855e-16, + -0.1073705580484886, + 0.13600484313293415, + -0.19421047689020757, + -5.811613183570061e-16, + 2.9059758295292124e-15, + 0.046050995715366076, + 0.14615199854888605, + -0.22723205623639917 + ], + [ + 7.550815613087127e-17, + 5.2339525361060326e-17, + -1.5300611039749706e-18, + 6.756925035001615e-16, + -2.373745156555218e-16, + -8.434193249870992e-16, + 3.0740190170240735e-15, + 4.629039219213014e-14, + 0.4994372599878549, + 5.313731410241504e-16, + -2.8106732358294856e-17, + 1.2460317355468467e-07, + -0.47749117640475536, + -1.3118869118643924e-16, + 7.513158216502361e-16, + 8.721148872131923e-15, + -0.7074405315510175, + -1.1201883590235598e-13, + -3.660985519979887e-14, + 4.707201014932263e-16, + -2.4482612763348014e-14, + -6.092557543795121e-06, + 0.626345343259594, + -1.074925396618705e-15, + 7.032300630316149e-15, + -1.0996882614231124e-15, + 3.43732740270206e-16, + -6.724310954594689e-07, + -1.522300644350135e-16, + 0.08821950572216442, + -1.3761501286423276e-15, + 5.716366776447079e-16, + -9.336153280657772e-08, + 0.005938367624195928, + 7.986281274309023e-17, + -7.237733767153121e-17, + 3.7509700516109532e-16, + 0.030995000156080893, + 1.5182021941761492e-07, + -2.760645509165656e-16, + 2.1403722196467027e-16, + 8.842180821794658e-16 + ], + [ + 0.0013559732853018437, + 0.0009299025348614756, + 4.244029083137401e-05, + -0.036791483680718384, + -0.05729551307716413, + -0.06168825848201445, + 0.20442803498585185, + 0.1419526006460149, + -1.3346688229899745e-14, + -0.0021050644139652404, + -0.16202016135964437, + 7.755997964035714e-17, + 3.907061178758958e-16, + 0.5122352745558014, + 0.9035344820940345, + 0.982734420398517, + 2.2851030811213454e-13, + -1.2728625971285636, + -0.08167061421440809, + 0.049798895245451606, + -0.4023187132834646, + 4.024468294715914e-14, + 2.3981561840330035e-15, + 1.0126811528888788, + -0.46904390197305934, + -1.3219387209204014, + 0.7946191815360729, + 6.4682340872018045e-15, + -0.2007942718421548, + 1.7827264354740726e-15, + -0.5510978494901214, + 0.4207765623261231, + -1.3934736080789335e-14, + -2.018894207410266e-14, + -0.3693123482008664, + -0.19345966574150078, + 0.33632343114812774, + -4.420155651135708e-15, + 2.8410638150658376e-14, + 0.6398037375178663, + 0.5044342118765871, + 1.0278499539920471 + ], + [ + 0.0013559716926445275, + -0.0009293976960642831, + 5.2356156238426254e-05, + -0.03679125900143305, + 0.05729573717552413, + -0.06168841870409751, + 0.20442779111033066, + -0.14195325347900944, + 1.0935716334628728e-14, + 0.002108606973596082, + -0.16202102675131277, + 1.3521192389788804e-15, + -6.084610497917691e-16, + 0.5122448806587175, + -0.903524885159391, + 0.982736758302364, + -2.046264669711747e-13, + 1.272859226572418, + -0.08167552425956554, + -0.04981293074079704, + -0.4023147607574893, + 2.914536865395837e-14, + -3.6909964650012043e-14, + -1.0126770990622613, + -0.4690574521653702, + 1.3219420352565974, + 0.7946077849585986, + -6.3098721525600955e-15, + -0.2007939488699487, + 1.4525911963987521e-15, + 0.5511253544307765, + 0.42074210881931334, + 1.0974992851607651e-14, + -1.3320347614512276e-15, + 0.3693106951216751, + -0.19344833162241584, + -0.33632632516572153, + 1.5641207492456734e-15, + 3.336472175288476e-14, + 0.6398055938296223, + 0.5044172209916621, + -1.0278556066458602 + ], + [ + -4.932978655726522e-16, + -3.858716378227519e-17, + 6.123100351749968e-17, + -6.041695399144834e-16, + -9.425043195842682e-17, + -3.5165198267195244e-16, + 1.914640977166994e-15, + 3.0184791532425215e-14, + 0.3175012947196149, + 5.747887747589612e-16, + -3.1420805283428995e-16, + 2.9161126060285523e-07, + -0.45464455509180113, + 2.9110216725185827e-16, + -2.699804769748e-15, + -8.97452969574768e-15, + 0.7068302142428814, + 1.0283250461628334e-13, + 3.666415078782934e-14, + -7.143906284247826e-15, + 5.059468039659547e-14, + 1.0761747902024719e-05, + -1.1789591248852218, + 4.194690256147354e-15, + -1.2402728073932051e-14, + 1.2152697296827239e-15, + -2.789376581549488e-15, + 1.066352740677053e-07, + 1.1283925680884935e-15, + 0.03925829472377486, + -1.7352869352441499e-15, + 1.6869416809188508e-15, + -2.58481577833276e-06, + 0.13023859833727228, + 2.167722824026574e-16, + -4.6611583562281654e-17, + -2.4884536469589583e-15, + -0.4193414189508609, + 2.484279946766648e-06, + -1.1530444961172796e-15, + -1.4153629052424042e-15, + 3.802717825355853e-16 + ], + [ + -4.9139972192500825e-05, + 1.2532259279523831e-06, + 0.0002349391812642442, + 0.009251829938461879, + -7.83312828880416e-09, + -0.004633293819919307, + 0.02255642428900985, + -1.1187615327688963e-07, + -7.307249494900588e-18, + -2.3565475894130017e-07, + 0.029830676238592438, + 6.937990380644147e-17, + -3.7948177260392955e-17, + 0.024687074975616462, + 1.1086946702840418e-07, + -0.09454050432555262, + -1.817558504145973e-15, + 1.545710988864917e-08, + 0.00613443603724553, + -5.049783516558052e-07, + -0.037484812192992975, + 3.2971086103830743e-15, + -1.7941581484042357e-16, + 3.5289263638528985e-08, + -0.08571045670591014, + 1.1846417237607226e-07, + 0.11228811345615068, + 2.148971982345758e-15, + 0.6086569380748019, + -2.4677677934574447e-15, + -5.82590500507937e-06, + -0.182951417682054, + 7.195879909752421e-17, + 5.1526866618719436e-15, + 5.42217326530445e-07, + 0.09406308523800401, + 9.929921354547193e-07, + -1.2623904850965279e-15, + 2.1460774781943605e-14, + 0.522157301587059, + -0.7282819362796469, + 5.833844045641243e-06 + ], + [ + 2.1752424389345974e-16, + 5.029684788106848e-17, + 1.5056354783991496e-16, + -6.901818143555954e-17, + -5.495539909486544e-17, + -7.530817129393245e-17, + 1.0486300913536804e-16, + 2.027340012490082e-15, + 0.021388142856544667, + -3.4998576939614456e-17, + -6.399827764709759e-17, + 0.03073769998788244, + 0.0031017351171815566, + -5.835656643344614e-18, + 2.98846114158256e-16, + 2.6370263257629685e-16, + -0.026625641874227387, + -4.166342503538712e-15, + -1.4846814385475334e-15, + -3.6660053850675167e-16, + -2.3562425808443625e-15, + -0.014351381344463655, + 0.018674785747310848, + 8.639683224695077e-16, + 1.5182863606969735e-16, + -1.4487072436454049e-16, + -7.79319888173439e-16, + -0.46618243745509697, + 1.299029596903125e-16, + -0.5218884376160272, + 2.103759898095543e-15, + -3.3510299093496484e-15, + -0.004814041046211707, + -0.07930951690533286, + 1.6280961228643237e-15, + -5.929643891752847e-16, + -6.104568066148214e-15, + -0.49817580354602725, + -0.5831228159617589, + 2.818900804626549e-14, + 4.587130960989423e-15, + 3.559288125141728e-15 + ], + [ + 0.0001428488476022815, + 1.9436043758412013e-06, + 0.0003643100780017828, + 0.017471740054396914, + -2.8168076362039754e-08, + 0.005902041689138691, + -0.014097693226871166, + -6.208381624593798e-10, + -1.9795894053489494e-16, + -1.434107906548935e-07, + 0.014940250475559188, + 7.083791917842222e-17, + 7.761259929568166e-17, + 0.020883717893617978, + 1.0941969442855063e-07, + -0.0749922980766121, + 6.445240793022765e-16, + -2.1950501686641712e-08, + -0.03785700620950398, + -8.734572358556348e-07, + -0.06829986604067217, + 6.476662697268637e-15, + -1.6283074560511371e-15, + 3.0772918165903135e-07, + -0.1347473303583918, + 5.843660360841642e-07, + 0.15702177215140115, + -1.3410985851144486e-15, + -0.3403731399455043, + 1.2972151036925361e-15, + -1.1494400704410922e-05, + -0.29633659811132707, + -1.615721246409904e-15, + 5.1871747494326175e-15, + -2.3202762266790163e-06, + 0.6168826841232165, + 3.46058918279828e-06, + 1.2150919913506634e-15, + -2.7383966979682646e-14, + -0.46821471299791445, + -0.6359807074320812, + 4.104162972750396e-06 + ], + [ + -8.31616302355406e-17, + 1.5390985820428067e-16, + -8.029479617064937e-17, + 5.65497348634005e-17, + 2.2467173678619488e-17, + -6.654482314491992e-17, + 1.6868291630511466e-16, + 2.144174518476808e-15, + 0.021388331658808026, + 1.142629404874943e-16, + 1.5663726775540034e-16, + -0.03073768980583212, + 0.00310179026247711, + 7.675970593775375e-17, + 4.579555170867327e-16, + 4.2387443241620065e-16, + -0.02662599700566266, + -4.497521198588121e-15, + -1.6509121171145228e-15, + 1.6380923132653128e-16, + 6.246024714100529e-16, + 0.014351238047080328, + 0.018674867070117777, + 9.85192009133767e-16, + 1.9652093408234633e-16, + 2.427627793232047e-15, + 5.18079899597404e-16, + 0.4661836112042665, + -4.2864112100471076e-15, + -0.5218867104693762, + 1.990730870216727e-16, + -7.098139819591328e-16, + 0.00481776677788859, + -0.0793089766590969, + 7.577646201689517e-16, + -1.05164554990519e-15, + -2.14778068438821e-15, + -0.49817067647520435, + 0.583128384335261, + -3.114073256964781e-14, + -5.355931475673746e-15, + -1.236105130627625e-15 + ], + [ + -1.258212042779213e-09, + -0.0003476842777311292, + 1.852122554267521e-06, + -1.1488269542528357e-07, + -0.03507609137664523, + -1.763644820865269e-08, + 1.1399252056516555e-07, + 0.02158794860148395, + -1.869055864127683e-15, + -0.029710130493980757, + -1.929384017379902e-07, + -6.322770343923514e-17, + -1.9606563752652493e-16, + 2.3001979828800917e-07, + -0.03297849064749296, + 3.918052259176637e-07, + 3.243558202731441e-15, + -0.02616486300903077, + 3.0651101548176644e-07, + 0.1260946536842911, + -1.3505188482803279e-06, + 1.838505007665779e-16, + -2.167097189346346e-15, + -0.09586832737159776, + -6.037601511145374e-07, + -0.12401770920517666, + 8.234648582124874e-07, + -6.12044408126081e-16, + 4.3489906716544065e-08, + -1.678742502355663e-15, + -0.4167292200415101, + 1.1885870168070753e-05, + -1.0043225081069346e-14, + -3.6821696713595675e-15, + -0.27962259180562377, + -6.92740899073739e-07, + -0.4351843272595588, + 6.075533741215819e-15, + 5.656753018663876e-15, + -9.307983166908034e-08, + 8.211849831382716e-06, + 1.0337225104750014 + ], + [ + -0.0017613300505372252, + -0.7039736902274834, + 0.7112943205776673, + -9.398298676863867e-05, + -0.0012603242328684708, + -0.0006688484801919751, + 0.004981847646505617, + -0.0015708111746075254, + -8.017393986435142e-17, + 0.0006765724056206014, + -0.007066406468787232, + 5.051739225088102e-16, + 7.147785599797437e-17, + 0.01256555217393601, + -0.005897784564322523, + 0.11191680173717093, + 8.106921876791239e-15, + -0.06687742323188318, + 0.0765178001208526, + 0.0030450520715755614, + -0.10550866456335226, + 8.777129317747287e-15, + -7.45794380354549e-15, + -0.03319048947718257, + 0.3346417862946701, + 0.5631376955590275, + 0.3587062263971887, + -1.9163775248931924e-15, + -0.021499153190363295, + 1.1532468664089402e-15, + -0.02094361080996321, + 0.029171806206018353, + -8.565931156225149e-16, + 4.167620957623335e-16, + -0.06835062933105666, + 0.06886646758261143, + -0.16677440399611618, + 6.930759715500321e-16, + 6.151704134479237e-15, + 0.10832874820663324, + 0.15397612051762954, + -0.19125184286022504 + ], + [ + 0.0001593217709164905, + -0.0006747553872258788, + 0.00023769096887512508, + -0.19856170361980904, + 0.311361515925907, + -0.2707031847860684, + -0.10910523548817863, + 0.16497737909353788, + -1.5204554738056677e-14, + -0.0050140495157909816, + -0.010430691644906108, + 9.8617158714887e-16, + 4.270795952617853e-17, + -0.1014766390289783, + 0.13729603293912887, + 0.2366542953552118, + 2.7724634382800118e-14, + -0.21593922586146483, + 0.18581388099238663, + 0.0529349494515786, + -0.2976215243986164, + 2.5241414156809218e-14, + -1.9852989964824295e-14, + -0.08135491813088488, + 0.8151188642566387, + 1.249137935968453, + 0.7738334745615227, + -4.137369421713338e-15, + -0.024671688227025104, + 2.4212849067939743e-15, + -0.08221630174079354, + 0.027670402570450437, + -2.6556892358849585e-15, + 1.8870870495248756e-15, + -0.17621681786467513, + 0.18129062162534024, + -0.3692561999846878, + 1.9190020035777542e-15, + 1.13980228989775e-14, + 0.19470984672436598, + 0.23910253489418548, + -0.31165435138767184 + ], + [ + -0.0015896791394839176, + 0.0032541967805489, + -0.002146170176443986, + -0.12583065669092253, + 0.3097280331221159, + -0.3605655553736356, + -0.2406226796125805, + 0.3002873533106203, + -2.5012688839749276e-14, + -0.00407998089672773, + 0.053104544602295994, + -2.9950598304183213e-15, + 4.1141499610879074e-16, + -0.6155690213224254, + 0.8049304065404382, + -0.882187800625097, + 1.215698629222247e-13, + -0.7103274244204719, + -0.19059895684270486, + 0.12124437842551516, + 0.17934033079892978, + -1.0722701928479029e-14, + 2.702389782825458e-14, + 0.6695656170654409, + -0.5551065293748615, + -2.4262136481857812, + -1.9781506261994595, + 9.630084792624026e-15, + 0.304599974092615, + -4.991164705822913e-15, + -0.3496132432462423, + -0.5385003009817542, + -5.004483914614567e-15, + 4.902566723803469e-15, + -0.0793584126475074, + 0.025749455725204057, + 0.6647640764611239, + -8.079919194652267e-16, + -4.8772971474900233e-14, + -0.9028036202143551, + -1.24053396886872, + 1.423970854736291 + ], + [ + -2.1302774418900985e-05, + -0.0003971703599785441, + 0.00026800634201995727, + -0.03348923539414831, + 0.0043591580205813085, + -0.0230017080057999, + 0.25673848846013386, + 0.03149573189863999, + -4.6724734535603716e-15, + 0.4933053220279898, + 0.3574203671885523, + 9.09828247095257e-16, + 9.6092234818424e-16, + -0.15090936059797616, + -0.007334298888454495, + 0.16426080702326465, + -8.403896948094573e-14, + 0.3071745299874465, + 0.6223888108789958, + 0.2357842560421918, + -0.02368051610147158, + 2.8689432988186593e-15, + -4.363279703678358e-15, + 0.5232292110330152, + -0.15614284277523915, + 0.09889137060334598, + -0.07988555297001854, + 2.1063143481847968e-16, + -0.00978630273172361, + 7.374641321923923e-16, + -0.09251980940178464, + -0.017162060020486373, + -1.9947811514246456e-15, + -2.7362210660381774e-16, + -0.1319325975678938, + 0.0452679184115815, + -0.009531243156416808, + 7.93606378920358e-16, + -2.2964872962877536e-15, + -0.035531823986098464, + -0.04317345137402883, + 0.04334963669813458 + ], + [ + 0.00011879352608077455, + 0.0012530919370605737, + -0.0011804004068441964, + 0.10376441173300982, + -0.09105189500507013, + -0.10939513892070361, + -0.12383098009361516, + 0.32019103586597136, + -2.9582825975857715e-14, + -0.0013022756581949938, + 0.2565230937526246, + 9.133280487401617e-16, + 2.697929238486124e-16, + 0.3386787088090443, + -0.32361278830264295, + -0.3670918572329757, + 2.931036371882568e-14, + -0.27522623007514957, + 0.058447134435822966, + 0.5738949787331941, + -0.5093974864886626, + 4.438337414083397e-14, + -2.6073330202405912e-14, + -0.07146968210957057, + -0.08826338763038255, + -0.08788227419365917, + -0.0026431002911249894, + 4.938956140446418e-16, + -0.10055125925046259, + 5.155904499309114e-16, + 0.17375779596770438, + 0.15836636379518715, + 4.445705777536366e-15, + -3.397591938350395e-16, + 0.1459447318632273, + -0.1978706059183265, + 0.1652906721149198, + -2.251278140093081e-15, + 2.3594307259089997e-15, + 0.06377878688893793, + 0.1893386208194984, + -0.17852320218695433 + ], + [ + -1.4309566617794539e-16, + -5.2240027241748233e-17, + -5.366488341965129e-18, + 1.8229732623175092e-16, + -2.108601910021458e-16, + -3.000178277328976e-16, + 8.748015708749215e-16, + 2.4565077608093327e-14, + 0.2610555058774571, + -7.357794379994173e-16, + -6.817950429749327e-16, + 0.4553432165137196, + 0.3730606237681433, + 7.059495403916955e-17, + 9.537893497896494e-16, + 3.3947109364688394e-15, + -0.4714092201251421, + -8.760264753351613e-14, + -2.0801999445627096e-14, + -1.4773032001525013e-14, + -3.529203938362898e-14, + -0.6852502003098183, + -0.4891013148244063, + 2.3277002625123767e-15, + -4.961239615007981e-15, + -1.4425026532856373e-16, + -5.967944324567362e-16, + 0.06475946353947955, + -3.9910486362367187e-17, + 0.04028119991191867, + -5.321090633512867e-16, + 7.900843068395309e-16, + -0.02436319440380709, + 0.03655039702636404, + -3.597973447746488e-17, + 4.880790729097958e-16, + -8.524372645440926e-16, + -0.03165369473305965, + -0.006503798265703627, + 1.885920606665614e-16, + -1.4924679300573276e-16, + -1.0611587489666756e-16 + ], + [ + -0.00046350996362259667, + 0.0003885018456417203, + -8.159541825817855e-05, + -9.14057740344396e-06, + 0.008162599124011832, + -0.01505500738868116, + 0.12574424464946918, + 0.013407918736240537, + -1.7337494237266964e-15, + 0.3497880070524966, + 0.2828900593441216, + 5.7188365573056e-16, + -6.054745908615651e-16, + -0.30664645916641126, + 0.05368063523913842, + -0.4637992976244009, + 7.670388878568893e-14, + -0.27998311552236155, + -0.6256482027368823, + -0.1874758885217344, + 0.14637553145442075, + -1.419859801191204e-14, + 6.686266845444978e-15, + -0.8507416869259571, + 0.36501640717401546, + -0.22869121306322182, + -0.30517305969622777, + -2.870157473950213e-16, + 0.00385794573589821, + -1.86161195430529e-15, + 0.10897332214071645, + -0.15420549795525237, + 2.6030974633703204e-15, + 4.803122880621867e-15, + 0.20443688328511134, + 0.026024320631515094, + -0.015437585917301242, + 4.958432965169636e-16, + -1.4271276077658225e-14, + -0.32481890304458455, + -0.17686013475998155, + 0.14111544848111712 + ], + [ + 0.0006925141255544204, + -0.0013552560245302344, + 0.0007270548487615724, + 0.01605473376738698, + -0.039077630320965055, + -0.03456730954087202, + -0.0385872484115535, + 0.1609208048569433, + -1.6470307511335543e-14, + -0.007930514214979542, + 0.16281488537282898, + 2.0336769806409827e-15, + -3.601423258571867e-16, + 0.6784299591451881, + -0.811048716772857, + 0.7977388976037361, + -1.1386588208137066e-13, + 0.798947382035201, + 0.020023567587810542, + -0.8739359954156816, + 0.7860550593396991, + -7.098191700530822e-14, + 3.3544565797600985e-14, + -0.2423979968761302, + 0.11716997025686536, + 1.0671430577996348, + 0.920318212694082, + -5.037957127381846e-15, + -0.12728962133089805, + 1.555773677688173e-15, + 0.11443710770481412, + 0.19799111997630087, + 6.449095090802989e-16, + -4.6950804854848115e-15, + 0.0021971626684818783, + 0.09251292626446021, + -0.45048405367278255, + 7.536831655314065e-16, + 2.6019628748097452e-14, + 0.45967445538372714, + 0.7609855470593834, + -0.8193502170345943 + ], + [ + 9.941902557767123e-19, + 3.9524331179906395e-17, + -4.013368368931335e-18, + 1.1609496276492806e-16, + -1.838894341763903e-16, + -6.335561754843437e-17, + 7.193592823879593e-16, + 1.3634064274756099e-14, + 0.1541764160964085, + -7.402164047715438e-16, + -1.8849187160488729e-16, + 0.36809088937681844, + 0.38464811389448744, + -4.052407285630491e-16, + 2.5573287919020296e-16, + -1.1173935659694867e-15, + 0.32292963219866677, + 6.819808298624103e-14, + 1.2453401034743436e-14, + 1.7845665429410595e-14, + 3.0251925732049343e-14, + 0.7921410947337875, + 0.8326170925760483, + -4.374593187199196e-15, + 8.925744519623895e-15, + 1.2128871022414656e-15, + 2.5246631298329e-15, + 0.07140221889563941, + -5.084461983430804e-16, + 0.02433954162481145, + 7.300547892713681e-16, + -2.761330350896548e-16, + 0.03269227221591637, + -0.072093477112905, + -3.5874932673186177e-16, + -4.306813479123662e-16, + 2.137318266269459e-15, + 0.22823163910454128, + 0.1427463422107145, + -6.188596483025551e-15, + -9.481149016852375e-17, + -1.3310820671260475e-15 + ], + [ + -2.536791514165615e-05, + -7.368484754010199e-05, + 6.716801033864665e-05, + 0.00895130898742157, + -0.004046033979091001, + 0.0013900974463644483, + -0.02112087593129741, + 0.004106788201742911, + -5.061768783329361e-16, + -0.017587004326387483, + -0.0017505281535641923, + -4.662696804922696e-17, + 3.3157019155371244e-17, + -0.013654234082026286, + -0.011284963861219301, + 0.03780073564003595, + -4.106738883612274e-15, + 0.027590776215515964, + -0.012116536999196346, + 0.024047506355676644, + 0.009321706323079966, + -5.688376173091981e-16, + -8.916878172122175e-16, + -0.08915044275324883, + -0.0015498095626796055, + -0.02962000582878333, + -0.08654003570732069, + -1.400820347524685e-15, + -0.30899239946043844, + -6.859316519965465e-16, + -0.4599270914110555, + 0.14306982140900384, + -1.0419282671209046e-14, + -9.719160397836612e-15, + -0.30469993610010165, + 0.22640696575645444, + 0.3124723212482826, + -3.948702801982127e-15, + 2.935248745243979e-14, + 0.6536776133688468, + 0.050902422774331955, + -0.38431112364017084 + ], + [ + -5.887023056144293e-18, + -9.197477353987915e-17, + 1.9156727911589254e-16, + -1.2986286351701265e-16, + -8.681171385924841e-17, + -1.1640376421991494e-18, + -1.0471343086374296e-16, + -2.35981957581066e-15, + -0.026170225653117465, + 5.943576761660668e-17, + 3.0081166207421423e-17, + -0.02196507412962472, + 0.006226199355940923, + 8.636686254927274e-18, + 8.168617534943777e-17, + -4.942145693620185e-16, + 0.025126038437548966, + 5.9640824436240554e-15, + 5.206793311754575e-16, + -4.8808857014645754e-17, + 8.281298065950059e-16, + 0.02100801856152951, + 0.01849284373650161, + -1.0572341365605352e-15, + -4.292535202826627e-16, + 1.8363372150960536e-15, + -9.729563974303852e-16, + 0.4284300906913619, + -4.830393855232485e-16, + 0.30400956679385815, + -9.13373061649192e-15, + 6.463303314248677e-15, + 0.06543408002086683, + 0.37512236450482184, + 1.4446105371679421e-15, + 4.975664899759549e-16, + -4.750072127325573e-15, + -0.5620111022828179, + -0.5903661724870815, + 2.8060619567726474e-14, + 4.898174389848054e-15, + 4.9248423714119294e-15 + ], + [ + -0.00016279249340948624, + 4.061266495791603e-05, + -0.00012951174303520822, + 0.009312477658289459, + -0.007623469438667967, + -0.002758711836439576, + -0.0077699786507163665, + 0.012784397040472528, + -1.180977763205326e-15, + -0.004289207979364994, + 0.004828167781991947, + 1.7916372162398945e-16, + -4.252282720005893e-17, + -0.00033343081619599096, + 0.0070407864001065905, + 0.021173185488919576, + -6.206560211629025e-15, + 0.03691280246467279, + -0.005805594286721782, + 0.01830423670489943, + -0.011474100087736253, + 1.0287660867640863e-15, + -8.733346564627528e-16, + -0.04235451649188022, + -0.060922728035432507, + -0.09513638194129317, + 0.08917337934428607, + 1.2671344145774996e-16, + -0.10718793802562869, + -6.294815442539691e-17, + 0.3290900863609058, + -0.5899640731421419, + 8.400889222231962e-15, + 2.1931206010522772e-14, + -0.47675330512334513, + -0.16905318794989735, + -0.3010456721187955, + 3.8738272911470583e-16, + 1.3645733716227054e-14, + 0.19628210327386886, + 0.3449160183381891, + -0.35146170675284094 + ], + [ + -1.0372416947701155e-16, + 2.1540387776205482e-17, + -4.677855207288378e-17, + -2.7975603887133102e-18, + -3.677958873669241e-17, + -1.1740023438840006e-16, + 2.1018893665934972e-17, + 6.02133592655218e-16, + 0.006718527095527946, + -1.5015341087039998e-17, + 3.6404112862690624e-17, + 0.0049519320418373175, + -0.0004762870104316398, + 2.843856645510013e-17, + -2.6652439424657585e-16, + 2.8689176484437154e-16, + -0.0035581622472791777, + -6.503166996488833e-16, + 1.7268970566068356e-16, + -5.039927298122869e-16, + -1.5181498464797696e-15, + -0.016910542174437996, + 0.010398523311268561, + 5.260383858423728e-16, + 4.946067784365688e-17, + -1.5704721638779545e-15, + 2.167455325738879e-15, + -0.03747918474317207, + -1.6689294000632852e-15, + -0.26867236575213177, + -2.5715954874157724e-14, + 1.4198185070048624e-14, + 0.7042697017754728, + 0.5960241584192306, + 4.5516784663927806e-15, + 1.0401229438979508e-15, + 2.4950288471129013e-15, + 0.278480290703868, + 0.0598424296121514, + -4.0397850821947335e-15, + -1.1245396117706782e-15, + -2.327635838878825e-15 + ], + [ + -0.000199408835729763, + -0.0001511802900839243, + 0.0001230597142560452, + 0.014667454698213456, + -0.01301335452150573, + -0.009815829780834154, + -0.004238633050417151, + 0.02376381235675366, + -2.2038939010631763e-15, + -0.0014800310264468776, + 0.013010922913857843, + 4.1869931377365855e-17, + -4.1251749764666887e-16, + -0.0037669387273488153, + 0.011391536794820226, + -0.0010579372303174575, + -1.241349538406943e-14, + 0.07703009871135336, + -0.023862275066472382, + 0.03218456585682706, + -0.012551978277172233, + 1.5135176991456084e-15, + -1.5740904032765052e-15, + -0.018291448810810712, + -0.09370277279435005, + -0.04999921100720285, + 0.048634073569554424, + 1.6927595621620924e-15, + 0.23978843466263194, + -5.277010638132867e-16, + -0.21478636234351697, + 0.06893241986380544, + -5.5680485514386706e-15, + -7.377347765510867e-15, + 0.34414605722690944, + 0.42173804513701674, + -0.46005166393851354, + 2.133614791380758e-15, + 3.419828578416071e-15, + -0.010246627554700865, + 0.6400145339250181, + -0.5087048653287369 + ] + ] + }, + "energies": { + "alpha": [ + -20.912095677226034, + -20.698954211524843, + -20.698874850321015, + -1.7869159821383107, + -1.452755810943348, + -1.0829736925615612, + -0.8424362452085107, + -0.8031249234718821, + -0.7986493350461514, + -0.5574646661379341, + -0.5423586627843083, + -0.48308340721880605, + -0.026459503025581078, + 0.3422158994645042, + 0.43322011905959124, + 0.9967325996823357, + 1.0647371545979665, + 1.0709558112588546, + 1.0784073869449264, + 1.1548127598166118, + 1.1902888601199548, + 1.2095189128688195, + 1.2332309931520804, + 1.310781435410077, + 1.3413762323019054, + 1.8907610305565312, + 1.977492080450955, + 2.2453676115305212, + 2.3283465778508328, + 2.4237421653127744, + 2.8139608095421127, + 2.8464716114001125, + 2.8662958317511555, + 2.886107281674449, + 2.933250734329282, + 3.0547697393461246, + 3.2094861092776363, + 3.4634759948998464, + 3.6408201164722054, + 3.7049955218926467, + 4.163259711028222, + 4.202051274874996 + ], + "beta": [ + -20.912095677226034, + -20.698954211524843, + -20.698874850321015, + -1.7869159821383107, + -1.452755810943348, + -1.0829736925615612, + -0.8424362452085107, + -0.8031249234718821, + -0.7986493350461514, + -0.5574646661379341, + -0.5423586627843083, + -0.48308340721880605, + -0.026459503025581078, + 0.3422158994645042, + 0.43322011905959124, + 0.9967325996823357, + 1.0647371545979665, + 1.0709558112588546, + 1.0784073869449264, + 1.1548127598166118, + 1.1902888601199548, + 1.2095189128688195, + 1.2332309931520804, + 1.310781435410077, + 1.3413762323019054, + 1.8907610305565312, + 1.977492080450955, + 2.2453676115305212, + 2.3283465778508328, + 2.4237421653127744, + 2.8139608095421127, + 2.8464716114001125, + 2.8662958317511555, + 2.886107281674449, + 2.933250734329282, + 3.0547697393461246, + 3.2094861092776363, + 3.4634759948998464, + 3.6408201164722054, + 3.7049955218926467, + 4.163259711028222, + 4.202051274874996 + ] + }, + "has_overlap_matrix": true, + "inactive_space_indices": { + "alpha": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 9, + 10 + ], + "beta": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 9, + 10 + ] + }, + "is_restricted": true, + "num_atomic_orbitals": 42, + "num_molecular_orbitals": 42, + "type": "Orbitals", + "version": "0.1.0" + } + }, + "container_type": "sci", + "is_complex": false, + "version": "0.1.0", + "wavefunction_type": "self_dual" + }, + "container_type": "sci", + "version": "0.1.0" +} diff --git a/python/tests/test_encoding_metadata.py b/python/tests/test_encoding_metadata.py index 486e7082c..105e56030 100644 --- a/python/tests/test_encoding_metadata.py +++ b/python/tests/test_encoding_metadata.py @@ -181,8 +181,8 @@ def test_validate_encoding_compatibility_mismatch(): def test_state_preparation_injects_jordan_wigner_encoding(wavefunction_4e4o): """Test that StatePreparation algorithms inject Jordan-Wigner encoding.""" - # Test sparse_isometry_gf2x - prep_gf2x = create("state_prep", "sparse_isometry_gf2x") + # Test sparse_isometry + prep_gf2x = create("state_prep", "sparse_isometry") circuit_gf2x = prep_gf2x.run(wavefunction_4e4o) assert circuit_gf2x.encoding == "jordan-wigner" @@ -250,7 +250,7 @@ def test_end_to_end_workflow_compatible_encodings(wavefunction_4e4o): qubit_ham = mapper.run(hamiltonian) # Create Circuit with state preparation (should be Jordan-Wigner) - prep = create("state_prep", "sparse_isometry_gf2x") + prep = create("state_prep", "sparse_isometry") circuit = prep.run(wavefunction_4e4o) # Validation should pass @@ -266,7 +266,7 @@ def test_end_to_end_workflow_incompatible_encodings(wavefunction_4e4o): qubit_ham = mapper.run(hamiltonian) # Create Circuit with state preparation (should be Jordan-Wigner) - prep = create("state_prep", "sparse_isometry_gf2x") + prep = create("state_prep", "sparse_isometry") circuit = prep.run(wavefunction_4e4o) # Validation should fail diff --git a/python/tests/test_energy_estimator.py b/python/tests/test_energy_estimator.py index e5cab6ab4..99dd31c84 100644 --- a/python/tests/test_energy_estimator.py +++ b/python/tests/test_energy_estimator.py @@ -85,7 +85,7 @@ def test_append_measurement_to_circuit_qasm(basis, n_qubits, expect_measure, exp def test_create_measurement_circuits_basic(wavefunction_4e4o): """Test measurement circuit generation for a simple observable.""" - state_prep = create("state_prep", "sparse_isometry_gf2x") + state_prep = create("state_prep", "sparse_isometry") circuit = state_prep.run(wavefunction_4e4o) # Define observable @@ -281,7 +281,7 @@ def test_estimator_run_4e4o(executor_name, wavefunction_4e4o, ref_energy_4e4o): The energy offset and test Hamiltonian are derived from classical wavefunction information, which is used to pre-screen the qubit Hamiltonian and identify terms requiring quantum measurement. """ - state_prep = create("state_prep", "sparse_isometry_gf2x") + state_prep = create("state_prep", "sparse_isometry") state_prep_circuit = state_prep.run(wavefunction_4e4o) energy_offset = -4.19142869944708 test_hamiltonian = QubitHamiltonian( diff --git a/python/tests/test_helpers.py b/python/tests/test_helpers.py index ec786ee54..2a7627acb 100644 --- a/python/tests/test_helpers.py +++ b/python/tests/test_helpers.py @@ -247,3 +247,157 @@ def create_test_ansatz(num_orbitals: int = 2): wavefunction = Wavefunction(container) return Ansatz(hamiltonian, wavefunction) + + +def _hf_determinant(n_alpha: int, n_beta: int, n_orbitals: int) -> np.ndarray: + """Build the Hartree-Fock reference determinant [alpha|beta]. + + Args: + n_alpha: Number of alpha electrons. + n_beta: Number of beta electrons. + n_orbitals: Number of spatial orbitals. + + Returns: + Occupation array of shape ``(2 * n_orbitals,)`` with 1s for occupied + alpha/beta orbitals and 0s elsewhere. + + """ + det = np.zeros(2 * n_orbitals, dtype=np.int8) + det[:n_alpha] = 1 + det[n_orbitals : n_orbitals + n_beta] = 1 + return det + + +def _random_excitation(det: np.ndarray, n_orbitals: int, rng: np.random.Generator) -> np.ndarray | None: + """Apply a random excitation independently in alpha and beta channels. + + Args: + det: Base determinant occupation array. + n_orbitals: Number of spatial orbitals. + rng: NumPy random generator. + + Returns: + New determinant array, or ``None`` if no excitation was applied. + + """ + new_det = det.copy() + for channel_start in (0, n_orbitals): + channel = det[channel_start : channel_start + n_orbitals] + occupied = np.where(channel == 1)[0] + virtual = np.where(channel == 0)[0] + if len(occupied) == 0 or len(virtual) == 0: + continue + order = rng.integers(0, min(len(occupied), len(virtual)) + 1) + if order == 0: + continue + occ = rng.choice(occupied, size=order, replace=False) + vir = rng.choice(virtual, size=order, replace=False) + new_det[channel_start + occ] = 0 + new_det[channel_start + vir] = 1 + return None if np.array_equal(new_det, det) else new_det + + +def _generate_determinant_matrix( + n_electrons: int, + n_orbitals: int, + n_dets: int, + seed: int = 0, +) -> np.ndarray: + """Generate a determinant occupation matrix from HF + random excitations. + + Builds the Hartree-Fock reference determinant and applies random + single/double excitations to produce ``n_dets`` distinct determinants. + + Args: + n_electrons: Total number of electrons (split equally between alpha/beta). + n_orbitals: Number of spatial orbitals. + n_dets: Target number of determinants. + seed: Random seed for reproducibility. + + Returns: + Occupation matrix of shape ``(n_dets, 2 * n_orbitals)`` where each row + is a determinant with alpha and beta occupation blocks. + + """ + n_alpha = n_electrons // 2 + n_beta = n_electrons - n_alpha + rng = np.random.default_rng(seed) + hf = _hf_determinant(n_alpha, n_beta, n_orbitals) + + seen: set[bytes] = {hf.tobytes()} + dets = [hf] + for _ in range(n_dets * 200): + if len(dets) >= n_dets: + break + exc = _random_excitation(hf, n_orbitals, rng) + if exc is not None and exc.tobytes() not in seen: + seen.add(exc.tobytes()) + dets.append(exc) + + return np.array(dets, dtype=np.int8) + + +def create_random_bitstring_matrix( + n_electrons: int, + n_orbitals: int, + n_dets: int, + seed: int = 0, +) -> np.ndarray: + """Generate a random bitstring matrix suitable for GF2+X elimination. + + Builds physically meaningful determinants from the Hartree-Fock reference + plus random excitations, then transposes to the binary matrix form + expected by ``gf2x_with_tracking`` and ``BinaryEncodingSynthesizer``. + + Args: + n_electrons: Total number of electrons. + n_orbitals: Number of spatial orbitals. + n_dets: Target number of determinants (columns). + seed: Random seed for reproducibility. + + Returns: + Binary matrix of shape ``(2 * n_orbitals, n_dets)`` where rows are + qubits and columns are determinants. + + """ + det_matrix = _generate_determinant_matrix(n_electrons, n_orbitals, n_dets, seed) + return det_matrix.T + + +def create_random_wavefunction( + n_electrons: int, + n_orbitals: int, + n_dets: int, + seed: int = 0, +) -> Wavefunction: + """Generate a random normalised Wavefunction for testing. + + Builds physically meaningful determinants from the Hartree-Fock reference + plus random excitations, assigns random normalised coefficients, and wraps + them in a :class:`Wavefunction`. + + Args: + n_electrons: Total number of electrons. + n_orbitals: Number of spatial orbitals. + n_dets: Target number of determinants. + seed: Random seed for reproducibility. + + Returns: + A normalised :class:`Wavefunction` with ``n_dets`` determinants. + + """ + det_matrix = _generate_determinant_matrix(n_electrons, n_orbitals, n_dets, seed) + actual_n_dets = det_matrix.shape[0] + + mapping = {(1, 1): "2", (1, 0): "u", (0, 1): "d", (0, 0): "0"} + configs = [ + Configuration("".join(mapping[int(row[i]), int(row[n_orbitals + i])] for i in range(n_orbitals))) + for row in det_matrix + ] + + coeff_rng = np.random.default_rng(seed) + raw = coeff_rng.standard_normal(actual_n_dets) + coeffs = raw / np.linalg.norm(raw) + + orbitals = create_test_orbitals(n_orbitals) + return Wavefunction(CasWavefunctionContainer(coeffs, configs, orbitals)) diff --git a/python/tests/test_interop_qiskit_state_prep_energy_validation.py b/python/tests/test_interop_qiskit_state_prep_energy_validation.py index 97d8a19af..2873f7dd8 100644 --- a/python/tests/test_interop_qiskit_state_prep_energy_validation.py +++ b/python/tests/test_interop_qiskit_state_prep_energy_validation.py @@ -11,7 +11,7 @@ import pytest from qdk_chemistry.algorithms import create -from qdk_chemistry.algorithms.state_preparation.sparse_isometry import SparseIsometryGF2XStatePreparation +from qdk_chemistry.algorithms.state_preparation.sparse_isometry import SparseIsometryStatePreparation from qdk_chemistry.data import Circuit from qdk_chemistry.plugins.qiskit import QDK_CHEMISTRY_HAS_QISKIT, QDK_CHEMISTRY_HAS_QISKIT_AER @@ -39,7 +39,7 @@ def test_energy_agreement_between_state_prep_methods(wavefunction_4e4o, hamilton # Create both state preparation instances basis_gates = ["cx", "rz", "ry", "rx", "h", "x", "z"] sparse_prep_gf2x = create( - "state_prep", algorithm_name="sparse_isometry_gf2x", transpile_optimization_level=1, basis_gates=basis_gates + "state_prep", algorithm_name="sparse_isometry", transpile_optimization_level=1, basis_gates=basis_gates ) regular_prep = create( "state_prep", algorithm_name="qiskit_regular_isometry", transpile_optimization_level=1, basis_gates=basis_gates @@ -71,19 +71,19 @@ def test_energy_agreement_between_state_prep_methods(wavefunction_4e4o, hamilton ), f"Energy difference {energy_diff} exceeds tolerance. " -def test_sparse_isometry_gf2x_energy_validation(wavefunction_10e6o, hamiltonian_10e6o, ref_energy_10e6o): - """Test SparseIsometryGF2XStatePreparation energy validation for 10e6o F2.""" - # Create SparseIsometryGF2XStatePreparation instance for F2 test +def test_sparse_isometry_energy_validation(wavefunction_10e6o, hamiltonian_10e6o, ref_energy_10e6o): + """Test SparseIsometryStatePreparation energy validation for 10e6o F2.""" + # Create SparseIsometryStatePreparation instance for F2 test sparse_prep = create( "state_prep", - algorithm_name="sparse_isometry_gf2x", + algorithm_name="sparse_isometry", basis_gates=["cx", "rz", "ry", "rx", "h", "x", "z"], transpile_optimization_level=1, ) qiskit_sparse_prep = create( "state_prep", - algorithm_name="sparse_isometry_gf2x", + algorithm_name="sparse_isometry", basis_gates=["cx", "rz", "ry", "rx", "h", "x", "z"], transpile_optimization_level=1, dense_preparation_method="qiskit", @@ -122,16 +122,16 @@ def test_sparse_isometry_gf2x_energy_validation(wavefunction_10e6o, hamiltonian_ ) -def test_sparse_isometry_gf2x_circuit_efficiency(wavefunction_4e4o): +def test_sparse_isometry_circuit_efficiency(wavefunction_4e4o): """Compare isometry resource requirements. - Test that SparseIsometryGF2XStatePrep creates more circuits using fewer resources + Test that SparseIsometryStatePrep creates more circuits using fewer resources than regular isometry. """ # Create both state preparation instances basis_gates = ["cx", "rz", "ry", "rx", "h", "x", "z"] sparse_prep = create( - "state_prep", algorithm_name="sparse_isometry_gf2x", transpile_optimization_level=1, basis_gates=basis_gates + "state_prep", algorithm_name="sparse_isometry", transpile_optimization_level=1, basis_gates=basis_gates ) regular_prep = create( "state_prep", algorithm_name="qiskit_regular_isometry", transpile_optimization_level=1, basis_gates=basis_gates @@ -188,7 +188,7 @@ def get_bitstring(circuit: Circuit) -> str: @pytest.mark.parametrize("bitstring", ["1010", "0000", "1111", "101001", "1", "0"]) def test_single_reference_state_basic(bitstring): """Test basic single reference state preparation with various bitstrings.""" - test_cls = SparseIsometryGF2XStatePreparation() + test_cls = SparseIsometryStatePreparation() circuit = test_cls._prepare_single_reference_state(bitstring) result_bitstring = get_bitstring(circuit) assert result_bitstring == bitstring, f"Expected {bitstring}, got {result_bitstring}" diff --git a/python/tests/test_qdk_interpreter_init.py b/python/tests/test_qdk_interpreter_init.py index 3ba93db27..ab82cb3da 100644 --- a/python/tests/test_qdk_interpreter_init.py +++ b/python/tests/test_qdk_interpreter_init.py @@ -11,25 +11,15 @@ from qsharp._qsharp import get_config -def test_default_qdk_interpreter_init(): - sys.modules.pop("qdk_chemistry", None) - from qdk_chemistry import _QDK_INTERPRETER_PROFILE # noqa: PLC0415 - - assert _QDK_INTERPRETER_PROFILE == "base" - - def test_qdk_interpreter_init_with_target_profile(): - sys.modules.pop("qdk_chemistry", None) - init(target_profile=TargetProfile.Adaptive_RIF) - user_profile = get_config().get_target_profile() - assert user_profile == "adaptive_rif" - - from qdk_chemistry import _QDK_INTERPRETER_PROFILE # noqa: PLC0415 - - assert user_profile == _QDK_INTERPRETER_PROFILE + # Remove qsharp init module from cache to force re-execution of its __init__.py + sys.modules.pop("qdk_chemistry.utils.qsharp", None) init(target_profile=TargetProfile.Base) + user_profile = get_config().get_target_profile() + assert user_profile == "base" from qdk_chemistry.utils.qsharp import QSHARP_UTILS # noqa: PLC0415 + assert get_config().get_target_profile() == "adaptive_rif" assert getattr(QSHARP_UTILS, "StatePreparation", None) is not None diff --git a/python/tests/test_state_preparation.py b/python/tests/test_state_preparation.py index 491ec2df1..b4aa23b1f 100644 --- a/python/tests/test_state_preparation.py +++ b/python/tests/test_state_preparation.py @@ -20,10 +20,10 @@ import pytest import qsharp -from qdk_chemistry.algorithms import available, create +from qdk_chemistry.algorithms import create from qdk_chemistry.algorithms.state_preparation.sparse_isometry import ( GF2XEliminationResult, - SparseIsometryGF2XStatePreparation, + SparseIsometryStatePreparation, _eliminate_column, _find_pivot_row, _is_diagonal_matrix, @@ -59,9 +59,9 @@ def test_regular_isometry_state_prep(wavefunction_4e4o): assert int(qubit_pattern.group(1)) == 2 * 4 -def test_sparse_isometry_gf2x_basic(wavefunction_4e4o): +def test_sparse_isometry_basic(wavefunction_4e4o): """Test the sparse isometry GF(2^X) StatePreparation algorithm basic functionality.""" - prep = create("state_prep", "sparse_isometry_gf2x") + prep = create("state_prep", "sparse_isometry") # Test circuit creation circuit = prep.run(wavefunction_4e4o) assert isinstance(circuit, Circuit) @@ -79,9 +79,9 @@ def test_sparse_isometry_gf2x_basic(wavefunction_4e4o): @pytest.mark.skipif(not QDK_CHEMISTRY_HAS_QISKIT, reason="Qiskit not available") -def test_sparse_isometry_gf2x_qiskit_dense_prepare(wavefunction_4e4o): +def test_sparse_isometry_qiskit_dense_prepare(wavefunction_4e4o): """Test the sparse isometry GF(2^X) StatePreparation algorithm basic functionality.""" - prep = create("state_prep", "sparse_isometry_gf2x", dense_preparation_method="qiskit") + prep = create("state_prep", "sparse_isometry", dense_preparation_method="qiskit") # Test circuit creation circuit = prep.run(wavefunction_4e4o) assert isinstance(circuit, Circuit) @@ -96,8 +96,8 @@ def test_sparse_isometry_gf2x_qiskit_dense_prepare(wavefunction_4e4o): assert f"{expected_theta:.6f}" in qasm # expected angle -def test_sparse_isometry_gf2x_single_reference_state(): - """Test SparseIsometryGF2XStatePrep with single reference state after filtering.""" +def test_sparse_isometry_single_reference_state(): + """Test SparseIsometryStatePrep with single reference state after filtering.""" # Create a wavefunction with coefficients that will be filtered out test_orbitals = create_test_orbitals(2) @@ -108,7 +108,7 @@ def test_sparse_isometry_gf2x_single_reference_state(): container = CasWavefunctionContainer(coeffs, dets, test_orbitals) wavefunction = Wavefunction(container) - prep = create("state_prep", "sparse_isometry_gf2x") + prep = create("state_prep", "sparse_isometry") single_ref_circuit = prep.run(wavefunction) assert isinstance(single_ref_circuit, Circuit) @@ -136,7 +136,7 @@ def test_sparse_isometry_gf2x_single_reference_state(): def test_gf2x_bitstrings_to_binary_matrix(): """Test functionality of _bitstrings_to_binary_matrix helper.""" - testclass = SparseIsometryGF2XStatePreparation() + testclass = SparseIsometryStatePreparation() # Simple 3-qubit, 2-determinant example bitstrings = ["101", "010"] # q[2]q[1]q[0] format (Little Endian) result = testclass._bitstrings_to_binary_matrix(bitstrings) @@ -221,7 +221,7 @@ def test_gf2x_bitstrings_to_binary_matrix(): def test_gf2x_bitstrings_to_binary_matrix_edge_cases(): """Test edge cases and error conditions for bitstring-to-matrix conversion.""" - testclass = SparseIsometryGF2XStatePreparation() + testclass = SparseIsometryStatePreparation() # Empty bitstrings list with pytest.raises(ValueError, match="Bitstrings list cannot be empty"): @@ -258,7 +258,7 @@ def test_gf2x_bitstrings_to_binary_matrix_edge_cases(): def test_gf2x_bitstrings_to_binary_matrix_qiskit_convention(): """Test that the function correctly handles Qiskit Little Endian convention.""" - testclass = SparseIsometryGF2XStatePreparation() + testclass = SparseIsometryStatePreparation() # Test specific example bitstrings = ["101", "010"] # q[2]q[1]q[0] format @@ -290,7 +290,7 @@ def test_gf2x_bitstrings_to_binary_matrix_qiskit_convention(): def test_gf2x_bitstrings_to_binary_matrix_additional_validation(): """Test additional validation scenarios for bitstrings_to_binary_matrix.""" - testclass = SparseIsometryGF2XStatePreparation() + testclass = SparseIsometryStatePreparation() # Test with large valid bitstring large_bitstring = ["0" * 50, "1" * 50] @@ -302,7 +302,7 @@ def test_gf2x_bitstrings_to_binary_matrix_additional_validation(): def test_prepare_single_reference_state_error_cases(): """Test error handling for invalid inputs.""" - test_cls = SparseIsometryGF2XStatePreparation() + test_cls = SparseIsometryStatePreparation() with pytest.raises(ValueError, match="Bitstring cannot be empty"): test_cls._prepare_single_reference_state("") @@ -310,71 +310,25 @@ def test_prepare_single_reference_state_error_cases(): test_cls._prepare_single_reference_state("1012") -def test_asymmetric_active_space_error(): - """Test error for asymmetric active space in StatePrep.""" - - class MockOrbitals: - """Mock orbitals with asymmetric active space indices.""" - - def get_active_space_indices(self): - """Return asymmetric active space indices.""" - return ([0, 1, 2], [0, 1, 2, 3]) - - class MockWavefunction: - """Mock wavefunction for testing asymmetric active space.""" - - def get_orbitals(self): - """Return mock orbitals.""" - return MockOrbitals() - - def get_active_determinants(self): - """Return mock determinants.""" - return [Configuration("2020000"), Configuration("2200000")] - - def get_coefficient(self, _): - """Return mock coefficient.""" - return 1.0 - - def get_coefficients(self): - """Return coefficients for all determinants.""" - return [1.0, 0.5] # Two coefficients for the two determinants - - def size(self): - """Return the number of determinants.""" - return len(self.get_active_determinants()) - - mock_wfn = MockWavefunction() - for sp_key in available("state_prep"): - prep = create("state_prep", sp_key) - with pytest.raises( - ValueError, - match=re.escape( - "Active space contains 3 alpha orbitals and 4 beta orbitals. Asymmetric active spaces for " - "alpha and beta orbitals are not supported for state preparation." - ), - ): - prep.run(mock_wfn) - - def test_find_pivot_row(): """Test the _find_pivot_row helper function.""" # Test matrix with clear pivot patterns m = np.array([[0, 1, 0], [1, 0, 1], [0, 0, 1], [0, 0, 0]], dtype=np.int8) # Test finding pivot in column 0 starting from row 0 - pivot = _find_pivot_row(m, 0, 4, 0) + pivot = _find_pivot_row(m, 0, 0) assert pivot == 1 # Row 1 has a 1 in column 0 # Test finding pivot in column 1 starting from row 0 - pivot = _find_pivot_row(m, 0, 4, 1) + pivot = _find_pivot_row(m, 0, 1) assert pivot == 0 # Row 0 has a 1 in column 1 # Test finding pivot in column 2 starting from row 1 - pivot = _find_pivot_row(m, 1, 4, 2) + pivot = _find_pivot_row(m, 1, 2) assert pivot == 1 # Row 1 has a 1 in column 2 # Test no pivot found - pivot = _find_pivot_row(m, 3, 4, 0) + pivot = _find_pivot_row(m, 3, 0) assert pivot is None # No 1 found in column 0 starting from row 3 @@ -386,8 +340,9 @@ def test_eliminate_column() -> None: row_map = [0, 1, 2, 3] cnot_ops: list[tuple[int, int]] = [] - # Eliminate column 0 with pivot row 0 - m_result, cnot_ops_result = _eliminate_column(matrix, 4, 0, 0, row_map, cnot_ops) + # Eliminate column 0 with pivot row 0 (in-place) + matrix_work = matrix.copy() + _eliminate_column(matrix_work, 0, 0, row_map, cnot_ops) # Verify matrix is modified correctly (rows 1 and 3 should be XORed with row 0) expected = np.array( @@ -400,14 +355,13 @@ def test_eliminate_column() -> None: dtype=np.int8, ) - assert np.array_equal(m_result, expected) + assert np.array_equal(matrix_work, expected) - # Verify CNOT operations are recorded correctly + # Verify CX operations are recorded correctly expected_cnots = [(1, 0), (3, 0)] # (target, control) pairs - assert cnot_ops_result == expected_cnots + assert cnot_ops == expected_cnots - # Verify original inputs are not modified - assert cnot_ops == [] # Original list should be empty + # Verify original matrix is not modified original_expected = np.array([[1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 0]], dtype=np.int8) assert np.array_equal(matrix, original_expected) @@ -417,12 +371,11 @@ def test_perform_gaussian_elimination() -> None: # Test matrix for Gaussian elimination matrix = np.array([[1, 1, 0], [0, 1, 1], [1, 0, 1]], dtype=np.int8) - m, n = matrix.shape row_map = [0, 1, 2] cnot_ops: list[tuple[int, int]] = [] # Perform Gaussian elimination - m_result, row_map_result, cnot_ops_result = _perform_gaussian_elimination(matrix, m, n, row_map, cnot_ops) + m_result, row_map_result, cnot_ops_result = _perform_gaussian_elimination(matrix, row_map, cnot_ops) # Verify the result is in row echelon form # After elimination, we should have: @@ -434,15 +387,15 @@ def test_perform_gaussian_elimination() -> None: assert len(row_map_result) == 3 assert isinstance(cnot_ops_result, list) - # Assert the specific CNOT sequence: CNOT(0,2), CNOT(0,1), CNOT(2,1) + # Assert the specific CX sequence: CX(0,2), CX(0,1), CX(2,1) # For matrix [[1,1,0], [0,1,1], [1,0,1]], Gaussian elimination should produce: - # Column 0: CNOT(2,0) to eliminate position [2,0] - # Column 1: CNOT(0,1) to eliminate position [0,1] - # Column 1: CNOT(2,1) to eliminate position [2,1] (redundant but part of algorithm) - assert len(cnot_ops_result) == 3, f"Expected 3 CNOT operations, got {len(cnot_ops_result)}" - assert cnot_ops_result[0] == (2, 0), f"First CNOT should be CNOT(0,2), got {cnot_ops_result[0]}" - assert cnot_ops_result[1] == (0, 1), f"Second CNOT should be CNOT(0,1), got {cnot_ops_result[1]}" - assert cnot_ops_result[2] == (2, 1), f"Third CNOT should be CNOT(2,1), got {cnot_ops_result[2]}" + # Column 0: CX(2,0) to eliminate position [2,0] + # Column 1: CX(0,1) to eliminate position [0,1] + # Column 1: CX(2,1) to eliminate position [2,1] (redundant but part of algorithm) + assert len(cnot_ops_result) == 3, f"Expected 3 CX operations, got {len(cnot_ops_result)}" + assert cnot_ops_result[0] == (2, 0), f"First CX should be CX(0,2), got {cnot_ops_result[0]}" + assert cnot_ops_result[1] == (0, 1), f"Second CX should be CX(0,1), got {cnot_ops_result[1]}" + assert cnot_ops_result[2] == (2, 1), f"Third CX should be CX(2,1), got {cnot_ops_result[2]}" # Verify original inputs are not modified original_expected = np.array([[1, 1, 0], [0, 1, 1], [1, 0, 1]], dtype=np.int8) @@ -529,8 +482,8 @@ def test_gf2x_with_tracking_basic(): for op in elimination_results.operations: assert isinstance(op, tuple) assert len(op) == 2 - assert op[0] in ["cnot", "x"] - if op[0] == "cnot": + assert op[0] in ["cx", "x"] + if op[0] == "cx": assert isinstance(op[1], tuple) assert len(op[1]) == 2 elif op[0] == "x": @@ -552,9 +505,9 @@ def test_gf2x_with_tracking_duplicate_rows(): elimination_results = gf2x_with_tracking(matrix) - # Should have CNOT operations to eliminate duplicates - cnot_ops = [op for op in elimination_results.operations if op[0] == "cnot"] - assert len(cnot_ops) > 0, "Expected CNOT operations for duplicate elimination" + # Should have CX operations to eliminate duplicates + cnot_ops = [op for op in elimination_results.operations if op[0] == "cx"] + assert len(cnot_ops) > 0, "Expected CX operations for duplicate elimination" # Verify rank reduction due to duplicate elimination original_rank = np.linalg.matrix_rank(matrix) @@ -562,7 +515,7 @@ def test_gf2x_with_tracking_duplicate_rows(): # Verify operations use original matrix indices for op in elimination_results.operations: - if op[0] == "cnot": + if op[0] == "cx": target, control = op[1] assert 0 <= target < matrix.shape[0] assert 0 <= control < matrix.shape[0] @@ -605,13 +558,13 @@ def test_gf2x_with_tracking_diagonal_matrix(): # This should reduce rank from 3 to 2 assert elimination_results.rank == 2, f"Expected rank 2 for diagonal reduction, got {elimination_results.rank}" - # Should have CNOT and X operations from diagonal reduction - cnot_ops = [op for op in elimination_results.operations if op[0] == "cnot"] + # Should have CX and X operations from diagonal reduction + cnot_ops = [op for op in elimination_results.operations if op[0] == "cx"] x_ops = [op for op in elimination_results.operations if op[0] == "x"] - # Diagonal reduction uses CNOT(i, i+1) for i=0 to rank-2, then X on last row - # For 3x3: CNOT(0,1), CNOT(1,2), X(2) - assert len(cnot_ops) >= 2, f"Expected at least 2 CNOT operations, got {len(cnot_ops)}" + # Diagonal reduction uses CX(i, i+1) for i=0 to rank-2, then X on last row + # For 3x3: CX(0,1), CX(1,2), X(2) + assert len(cnot_ops) >= 2, f"Expected at least 2 CX operations, got {len(cnot_ops)}" assert len(x_ops) >= 1, f"Expected at least 1 X operation, got {len(x_ops)}" @@ -688,7 +641,7 @@ def test_gf2x_with_tracking_reconstruction(): # Verify operations use valid indices for op in elimination_results.operations: - if op[0] == "cnot": + if op[0] == "cx": target, control = op[1] assert 0 <= target < original_matrix.shape[0] assert 0 <= control < original_matrix.shape[0] @@ -712,7 +665,7 @@ def test_gf2x_with_tracking_reconstruction(): # Apply operations in reverse order to reconstruct for op in reversed(elimination_results.operations): - if op[0] == "cnot": + if op[0] == "cx": target, control = op[1] reconstructed[target] = reconstructed[target] ^ reconstructed[control] elif op[0] == "x": @@ -745,9 +698,9 @@ def test_remove_duplicate_rows_with_cnot() -> None: # Should have eliminated duplicate rows assert m_result.shape[0] < matrix.shape[0], "Expected rows to be eliminated" - # Should have CNOT operations - cnot_ops = [op for op in operations_result if op[0] == "cnot"] - assert len(cnot_ops) > 0, "Expected CNOT operations for duplicate elimination" + # Should have CX operations + cnot_ops = [op for op in operations_result if op[0] == "cx"] + assert len(cnot_ops) > 0, "Expected CX operations for duplicate elimination" # Verify row mapping consistency assert len(row_map_result) == m_result.shape[0] @@ -862,17 +815,17 @@ def test_reduce_diagonal_matrix() -> None: f"Expected shape (2, 3), got {elimination_results.reduced_matrix.shape}" ) - # Should have CNOT and X operations - cnot_ops = [op for op in elimination_results.operations if op[0] == "cnot"] + # Should have CX and X operations + cnot_ops = [op for op in elimination_results.operations if op[0] == "cx"] x_ops = [op for op in elimination_results.operations if op[0] == "x"] - # For 3x3 diagonal matrix: CNOT(0,2), CNOT(1,2), X(2) - assert len(cnot_ops) == 2, f"Expected 2 CNOT operations, got {len(cnot_ops)}" + # For 3x3 diagonal matrix: CX(0,2), CX(1,2), X(2) + assert len(cnot_ops) == 2, f"Expected 2 CX operations, got {len(cnot_ops)}" assert len(x_ops) == 1, f"Expected 1 X operation, got {len(x_ops)}" - # Assert exact CNOT sequence: CNOT(1,0), CNOT(2,1) - assert cnot_ops[0] == ("cnot", (1, 0)), f"First CNOT should be CNOT(0,1), got {cnot_ops[0]}" - assert cnot_ops[1] == ("cnot", (2, 1)), f"Second CNOT should be CNOT(1,2), got {cnot_ops[1]}" + # Assert exact CX sequence: CX(1,0), CX(2,1) + assert cnot_ops[0] == ("cx", (1, 0)), f"First CX should be CX(0,1), got {cnot_ops[0]}" + assert cnot_ops[1] == ("cx", (2, 1)), f"Second CX should be CX(1,2), got {cnot_ops[1]}" # Verify specific X operation: X(2) - the last row gets X operation expected_x_qubits = {2} @@ -890,7 +843,8 @@ def test_reduce_diagonal_matrix() -> None: non_diagonal_m = np.array([[1, 0], [1, 0]], dtype=np.int8) elimination_results = _reduce_diagonal_matrix(non_diagonal_m, row_map, col_map, operations) - assert all(non_diagonal_m[i, j] == elimination_results.reduced_matrix[i, j] for i in range(2) for j in range(2)) + assert elimination_results.reduced_matrix.shape == (1, 2) + assert np.array_equal(elimination_results.reduced_matrix[0], non_diagonal_m[0]) def test_gf2x_edge_cases(): @@ -945,11 +899,110 @@ def test_gf2x_with_tracking_edge_case_pseudo_diagonal(): f"Row map length should be {elimination_results.rank}, got {len(elimination_results.row_map)}" ) - # Verify that operations list contains both CNOT and X operations - cnot_ops = [op for op in elimination_results.operations if op[0] == "cnot"] + # Verify that operations list contains both CX and X operations + cnot_ops = [op for op in elimination_results.operations if op[0] == "cx"] x_ops = [op for op in elimination_results.operations if op[0] == "x"] - assert len(cnot_ops) > 0, "Should have recorded some CNOT operations" + assert len(cnot_ops) > 0, "Should have recorded some CX operations" assert len(x_ops) > 0, "Should have recorded some X operations" # Should have some operations recorded assert len(elimination_results.operations) > 0, "Should have recorded some operations" + + +def test_forward_only_produces_upper_triangular(): + """Forward-only elimination produces row echelon form (zeros below each pivot).""" + matrix = np.array([[1, 1, 0], [1, 0, 1], [0, 1, 1]], dtype=np.int8) + m_result, _, _ = _perform_gaussian_elimination(matrix, [0, 1, 2], [], forward_only=True) + + for r in range(m_result.shape[0]): + nz = np.flatnonzero(m_result[r]) + if nz.size == 0: + continue + pivot_col = int(nz[0]) + assert np.all(m_result[r + 1 :, pivot_col] == 0) + + +def test_forward_only_does_not_back_substitute(): + """Forward-only differs from full RREF, above-diagonal entries survive.""" + matrix = np.array([[1, 1, 0], [0, 1, 1], [1, 0, 1]], dtype=np.int8) + m_fwd, _, _ = _perform_gaussian_elimination(matrix, [0, 1, 2], [], forward_only=True) + m_full, _, _ = _perform_gaussian_elimination(matrix, [0, 1, 2], []) + assert not np.array_equal(m_fwd, m_full) + + +def test_forward_only_reconstruction(): + """Reversing recorded CNOTs on the REF result recovers the original matrix.""" + matrix = np.array( + [[1, 0, 1, 1], [0, 1, 1, 0], [1, 1, 0, 1], [0, 0, 1, 1]], + dtype=np.int8, + ) + row_map = list(range(4)) + m_result, rm_result, cnot_ops = _perform_gaussian_elimination(matrix, row_map, [], forward_only=True) + + reconstructed = np.zeros_like(matrix) + for i, orig in enumerate(rm_result): + reconstructed[orig] = m_result[i] + for target, control in reversed(cnot_ops): + reconstructed[target] ^= reconstructed[control] + + assert np.array_equal(reconstructed, matrix) + + +def test_forward_only_row_swap_tracking(): + """Row swaps needed for pivoting are reflected in the returned row_map.""" + matrix = np.array([[0, 1], [1, 0]], dtype=np.int8) + _, rm_result, _ = _perform_gaussian_elimination(matrix, [0, 1], [], forward_only=True) + assert rm_result[0] == 1 + assert rm_result[1] == 0 + + +def test_forward_only_large_rank_deficient(): + """Rank-4 matrix: REF has exactly 4 non-zero rows and 2 zero rows.""" + matrix = np.array( + [ + [1, 0, 1, 0, 1, 1, 0, 0], + [0, 1, 0, 1, 0, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1], # row 0 XOR row 1 + [1, 0, 1, 0, 1, 1, 0, 0], # duplicate of row 0 + [0, 0, 0, 0, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0, 1], + ], + dtype=np.int8, + ) + m_ref, rm, ops = _perform_gaussian_elimination(matrix, list(range(6)), [], forward_only=True) + non_zero = int(np.sum(np.any(m_ref, axis=1))) + assert non_zero == 4 + + # Verify REF property: below each pivot is all zero + for r in range(m_ref.shape[0]): + nz = np.flatnonzero(m_ref[r]) + if nz.size == 0: + continue + assert np.all(m_ref[r + 1 :, int(nz[0])] == 0) + + # Reconstruction must recover original + reconstructed = np.zeros_like(matrix) + for i, orig in enumerate(rm): + reconstructed[orig] = m_ref[i] + for target, control in reversed(ops): + reconstructed[target] ^= reconstructed[control] + assert np.array_equal(reconstructed, matrix) + + +def test_forward_only_wide_matrix(): + """Wide matrix test: more columns than rows, full GF(2) rank 3.""" + matrix = np.array( + [ + [1, 0, 0, 1, 1, 0, 1, 0, 1, 1], + [0, 1, 0, 0, 1, 1, 0, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 1, 0, 0], + ], + dtype=np.int8, + ) + m_ref, _, ops = _perform_gaussian_elimination(matrix, list(range(3)), [], forward_only=True) + + # Already in REF (identity-like left block), should need 0 ops + assert len(ops) == 0 + assert np.array_equal(m_ref, matrix) + # All 3 rows non-zero + assert int(np.sum(np.any(m_ref, axis=1))) == 3 diff --git a/python/tests/test_state_preparation_binary_encoding.py b/python/tests/test_state_preparation_binary_encoding.py new file mode 100644 index 000000000..ac6651baa --- /dev/null +++ b/python/tests/test_state_preparation_binary_encoding.py @@ -0,0 +1,291 @@ +"""Tests for the sparse isometry with binary encoding 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 +import pytest +import qsharp + +from qdk_chemistry.algorithms import create +from qdk_chemistry.algorithms.state_preparation import SparseIsometryBinaryEncodingStatePreparation +from qdk_chemistry.algorithms.state_preparation.sparse_isometry import gf2x_with_tracking +from qdk_chemistry.data import Circuit, Wavefunction +from qdk_chemistry.plugins.qiskit import QDK_CHEMISTRY_HAS_QISKIT +from qdk_chemistry.utils.binary_encoding import BinaryEncodingSynthesizer, MatrixCompressionType, _dense_qubits_size + +from .reference_tolerances import float_comparison_absolute_tolerance, float_comparison_relative_tolerance +from .test_helpers import create_random_wavefunction + + +def _matrix_qubit_counts(wf: Wavefunction) -> tuple[int, int]: + """Derive qubit counts from the determinant matrix, accounting for the ancilla pool. + + Returns: + ``(n_system, n_ancilla)`` where + + - *n_system* is the number of system qubits + - *n_ancilla* is the number of extra ancilla qubits beyond the system register + after subtracting Pool A (idle GF2X qubits: system qubits absent from row_map). + + """ + num_orbitals = len(wf.get_orbitals().get_active_space_indices()[0]) + dets = wf.get_active_determinants() + bitstrings = [] + for det in dets: + alpha_str, beta_str = det.to_binary_strings(num_orbitals) + bitstrings.append(beta_str[::-1] + alpha_str[::-1]) + + n_system = len(bitstrings[0]) + matrix = np.array([[int(b) for b in bs] for bs in bitstrings], dtype=np.int8).T + gf2x_result = gf2x_with_tracking(matrix, skip_diagonal_reduction=True, forward_only=True) + + synthesizer = BinaryEncodingSynthesizer.from_matrix(gf2x_result.reduced_matrix) + ops = synthesizer.to_operations( + num_local_qubits=n_system, + active_qubit_indices=gf2x_result.row_map, + ancilla_start=n_system, + ) + naive_ancilla = max( + ( + op.control_state - 1 + for op in ops + if op.name in (MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND) + ), + default=0, + ) + + # Pool A: system qubits not present in row_map (never touched by binary encoding ops) + active_set = {int(q) for q in gf2x_result.row_map} + pool_a = len(set(range(n_system)) - active_set) + + # Actual ancilla = max(0, naive - pool_a) + actual_ancilla = max(0, naive_ancilla - pool_a) + return n_system, actual_ancilla + + +@pytest.fixture +def ozone_wf(test_data_files_path) -> Wavefunction: + """Load the ozone SCI wavefunction from test data.""" + return Wavefunction.from_json_file(str(test_data_files_path / "ozone_sparse_ci_wavefunction.wavefunction.json")) + + +class TestSparseIsometryBinaryEncoding: + """Tests for the sparse isometry binary encoding state preparation.""" + + def test_ozone(self, ozone_wf): + """End-to-end: ozone SCI wavefunction → run() → Circuit → estimate().""" + binary_encoding_prep = create("state_prep", "sparse_isometry_binary_encoding") + circuit = binary_encoding_prep.run(ozone_wf) + assert isinstance(circuit, Circuit) + assert circuit.encoding == "jordan-wigner" + + result = circuit.estimate() + assert isinstance(result, qsharp.estimator.EstimatorResult) + lc = result["logicalCounts"] + assert lc["numQubits"] == 10 # 10 system qubits; pool covers all ancilla + assert lc["tCount"] == 7 + assert lc["rotationCount"] == 7 + assert lc["cczCount"] == 9 + assert lc["measurementCount"] == 0 + + @pytest.mark.skipif(not QDK_CHEMISTRY_HAS_QISKIT, reason="Qiskit not available") + def test_ozone_statevector(self, ozone_wf): + """Simulate the ozone circuit and verify the statevector matches. + + The circuit may use ancilla qubits beyond the system register. + Ancilla qubits sit on the high-index qubits and are returned + to |0⟩ after uncomputation, so the system-register amplitudes + live in the first 2^n_system entries of the full statevector. + """ + from qiskit.quantum_info import Statevector # noqa: PLC0415 + + from qdk_chemistry.plugins.qiskit.conversion import create_statevector_from_wavefunction # noqa: PLC0415 + + binary_encoding_prep = create("state_prep", "sparse_isometry_binary_encoding") + circuit = binary_encoding_prep.run(ozone_wf) + expected_sv = create_statevector_from_wavefunction(ozone_wf, normalize=True) + n_system = int(np.log2(len(expected_sv))) + + qc = circuit.get_qiskit_circuit() + sim_data = np.array(Statevector.from_instruction(qc)) + + # Extract system-register amplitudes (ancilla qubits should be |0⟩). + system_sv = sim_data[: 2**n_system] + overlap = np.abs(np.vdot(expected_sv, system_sv)) + assert np.isclose( + overlap, 1.0, atol=float_comparison_absolute_tolerance, rtol=float_comparison_relative_tolerance + ) + + @pytest.mark.parametrize( + ("n_electrons", "n_orbitals", "n_dets", "seed"), + [ + (6, 6, 20, 42), + (8, 8, 50, 99), + ], + ids=["6e6o_20det", "8e8o_50det"], + ) + def test_random_wavefunction(self, n_electrons, n_orbitals, n_dets, seed): + """End-to-end: random wavefunction → run() → Circuit → estimate(). + + The expected qubit count is decomposed into system qubits (from the + matrix dimensions) and ancilla qubits (from the compiled Q# circuit). + """ + wf = create_random_wavefunction( + n_electrons=n_electrons, + n_orbitals=n_orbitals, + n_dets=n_dets, + seed=seed, + ) + + binary_encoding_prep = create("state_prep", "sparse_isometry_binary_encoding") + circuit = binary_encoding_prep.run(wf) + assert isinstance(circuit, Circuit) + assert circuit.encoding == "jordan-wigner" + + # Derive qubit counts from the matrix. + # Dense register qubits are system qubits (via rowMap); the extra + # dense_size - 1 qubits are PreparePureStateD's internal scratch. + n_system, n_ancilla = _matrix_qubit_counts(wf) + assert n_system == 2 * n_orbitals + expected_total = n_system + n_ancilla + + # Resource estimate must agree. + lc = circuit.estimate()["logicalCounts"] + assert lc["numQubits"] == expected_total + assert lc["cczCount"] > 0 + + def test_default_settings(self): + """Default settings: include_negative_controls=True, measurement_based_uncompute=False.""" + state_prep = SparseIsometryBinaryEncodingStatePreparation() + assert state_prep.settings().get("include_negative_controls") is True + assert state_prep.settings().get("measurement_based_uncompute") is False + + def test_ozone_negative_controls_disabled(self, ozone_wf): + """Ozone with include_negative_controls=False produces different resource counts.""" + prep = create("state_prep", "sparse_isometry_binary_encoding", include_negative_controls=False) + circuit = prep.run(ozone_wf) + assert isinstance(circuit, Circuit) + lc = circuit.estimate()["logicalCounts"] + assert lc["numQubits"] == 10 # 10 system qubits; pool covers the 1 ancilla + assert lc["tCount"] == 7 + assert lc["rotationCount"] == 7 + assert lc["cczCount"] == 5 + + @pytest.mark.skipif(not QDK_CHEMISTRY_HAS_QISKIT, reason="Qiskit not available") + @pytest.mark.parametrize( + ("n_electrons", "n_orbitals", "n_dets", "seed"), + [ + (6, 6, 20, 42), + (6, 6, 30, 7), + ], + ids=["6e6o_20det", "6e6o_30det"], + ) + def test_random_wavefunction_statevector(self, n_electrons, n_orbitals, n_dets, seed): + """Simulate random-wavefunction circuits and verify the statevector matches.""" + from qiskit.quantum_info import Statevector # noqa: PLC0415 + + from qdk_chemistry.plugins.qiskit.conversion import create_statevector_from_wavefunction # noqa: PLC0415 + + wf = create_random_wavefunction( + n_electrons=n_electrons, + n_orbitals=n_orbitals, + n_dets=n_dets, + seed=seed, + ) + circuit = create("state_prep", "sparse_isometry_binary_encoding").run(wf) + expected_sv = create_statevector_from_wavefunction(wf, normalize=True) + n_system = 2 * n_orbitals + + qc = circuit.get_qiskit_circuit() + sim_data = np.array(Statevector.from_instruction(qc)) + + system_sv = sim_data[: 2**n_system] + overlap = np.abs(np.vdot(expected_sv, system_sv)) + assert np.isclose( + overlap, 1.0, atol=float_comparison_absolute_tolerance, rtol=float_comparison_relative_tolerance + ) + + @pytest.mark.parametrize( + ("n_electrons", "n_orbitals", "n_dets", "seed", "expected_n_qubits"), + [ + # 4 electrons, 3 orbitals, 9 determinants: the full space has only + # ceil(6 choose 4) = 15 states. After GF2+X (forward-only, no + # diagonal reduction) the REF matrix has rank 4 (4 rows) but still + # 9 columns, so dense_size = _dense_qubits_size(9) = 4 = num_rows. + # The condition dense_size >= num_rows triggers the fallback. + (4, 3, 9, 0, 6), + (4, 3, 9, 1, 6), + ], + ids=["4e3o_9det_seed0", "4e3o_9det_seed1"], + ) + def test_fallback_to_dense_gf2x(self, n_electrons, n_orbitals, n_dets, seed, expected_n_qubits): + """Wavefunction where after GF2+X the REF matrix is already dense falls back to dense+GF2X.""" + wf = create_random_wavefunction( + n_electrons=n_electrons, + n_orbitals=n_orbitals, + n_dets=n_dets, + seed=seed, + ) + + # Confirm this case is genuinely a fallback case before testing. + num_orbitals = len(wf.get_orbitals().get_active_space_indices()[0]) + bitstrings = [] + for det in wf.get_active_determinants(): + a, b = det.to_binary_strings(num_orbitals) + bitstrings.append(b[::-1] + a[::-1]) + mat = np.array([[int(c) for c in bs] for bs in bitstrings], dtype=np.int8).T + gf2x_result = gf2x_with_tracking(mat, skip_diagonal_reduction=True, forward_only=True) + num_rows, num_cols = gf2x_result.reduced_matrix.shape + assert _dense_qubits_size(num_cols) >= num_rows, ( + f"Expected fallback: dense_size={_dense_qubits_size(num_cols)} must be >= num_rows={num_rows}" + ) + + circuit = create("state_prep", "sparse_isometry_binary_encoding").run(wf) + assert isinstance(circuit, Circuit) + assert circuit.encoding == "jordan-wigner" + + lc = circuit.estimate()["logicalCounts"] + # No binary-encoding SELECT/SELECT_AND ops in the fallback path. + assert lc["cczCount"] == 0 + # System qubits only — PreparePureStateD does not need external ancilla. + assert lc["numQubits"] == expected_n_qubits + + @pytest.mark.skipif(not QDK_CHEMISTRY_HAS_QISKIT, reason="Qiskit not available") + @pytest.mark.parametrize( + ("n_electrons", "n_orbitals", "n_dets", "seed"), + [ + (4, 3, 9, 0), + (4, 3, 9, 1), + ], + ids=["4e3o_9det_seed0", "4e3o_9det_seed1"], + ) + def test_fallback_statevector(self, n_electrons, n_orbitals, n_dets, seed): + """Fallback circuit produces the correct statevector (Qiskit simulation). + + Validates that the fallback dense+GF2X path correctly encodes the target + wavefunction amplitudes, not merely that it runs without error. + """ + from qiskit.quantum_info import Statevector # noqa: PLC0415 + + from qdk_chemistry.plugins.qiskit.conversion import create_statevector_from_wavefunction # noqa: PLC0415 + + wf = create_random_wavefunction( + n_electrons=n_electrons, + n_orbitals=n_orbitals, + n_dets=n_dets, + seed=seed, + ) + circuit = create("state_prep", "sparse_isometry_binary_encoding").run(wf) + expected_sv = create_statevector_from_wavefunction(wf, normalize=True) + n_system = 2 * n_orbitals + + qc = circuit.get_qiskit_circuit() + sim_data = np.array(Statevector.from_instruction(qc)) + system_sv = sim_data[: 2**n_system] + overlap = np.abs(np.vdot(expected_sv, system_sv)) + assert np.isclose( + overlap, 1.0, atol=float_comparison_absolute_tolerance, rtol=float_comparison_relative_tolerance + ) diff --git a/python/tests/test_utils_binary_encoding.py b/python/tests/test_utils_binary_encoding.py new file mode 100644 index 000000000..a5e370d61 --- /dev/null +++ b/python/tests/test_utils_binary_encoding.py @@ -0,0 +1,780 @@ +"""Tests for the binary encoding utils.""" + +# -------------------------------------------------------------------------------------------- +# 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 +import pytest + +from qdk_chemistry.algorithms.state_preparation.sparse_isometry import gf2x_with_tracking +from qdk_chemistry.utils.binary_encoding import ( + BinaryEncodingSynthesizer, + MatrixCompressionType, + NotRefError, + RefTableau, + _bits_to_int, + _check_ref, + _dense_qubits_size, + _int_to_bits, + _lookup_select, +) + +from .test_helpers import create_random_bitstring_matrix + + +class TestDenseQubitsSize: + """Tests for _dense_qubits_size.""" + + @pytest.mark.parametrize( + ("num_cols", "expected"), + [ + (1, 1), + (2, 1), + (3, 2), + (4, 2), + (5, 3), + (8, 3), + (16, 4), + (1000, 10), + ], + ) + def test_dense_qubits_size(self, num_cols, expected): + """ceil(log2(num_cols)) must equal the expected dense register width.""" + assert _dense_qubits_size(num_cols) == expected + + +class TestIntToBits: + """Tests for _int_to_bits.""" + + @pytest.mark.parametrize( + ("val", "nbits", "expected"), + [ + (0, 4, [False, False, False, False]), + (1, 4, [False, False, False, True]), + (15, 4, [True, True, True, True]), + (5, 3, [True, False, True]), + (0, 1, [False]), + (1, 1, [True]), + (3, 5, [False, False, False, True, True]), + ], + ) + def test_int_to_bits(self, val, nbits, expected): + """Integer must convert to the expected big-endian bit list.""" + assert _int_to_bits(val, nbits) == expected + + +class TestBitsToInt: + """Tests for _bits_to_int.""" + + @pytest.mark.parametrize( + ("bits", "expected"), + [ + ([0, 0, 0], 0), + ([1, 1, 1, 1], 15), + ([1, 0, 1], 5), + ([1], 1), + ([0], 0), + ([True, False, True], 5), + ], + ) + def test_bits_to_int(self, bits, expected): + """Big-endian bit list must convert to the expected integer.""" + assert _bits_to_int(bits) == expected + + def test_roundtrip(self): + """Int -> bits -> int must be the identity for all 4-bit values.""" + for val in range(16): + assert _bits_to_int(_int_to_bits(val, 4)) == val + + +class TestCheckRef: + """Tests for _check_ref REF validation.""" + + def test_identity_is_ref(self): + """Identity matrix is a valid REF.""" + _check_ref(np.eye(3, dtype=np.int8)) + + def test_valid_ref_non_square(self): + """Non-square matrix with trailing zero row is valid REF.""" + mat = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 0, 0]], dtype=np.int8) + _check_ref(mat) + + def test_valid_ref_with_trailing_zeros(self): + """REF with a trailing all-zero row is accepted.""" + mat = np.array([[1, 0, 1], [0, 1, 1], [0, 0, 0]], dtype=np.int8) + _check_ref(mat) + + def test_valid_ref_upper_triangular(self): + """REF matrix with entries above the diagonal is accepted.""" + mat = np.array([[1, 1], [0, 1]], dtype=np.int8) + _check_ref(mat) + + def test_empty_matrix(self): + """All-zero matrix is trivially in REF.""" + _check_ref(np.zeros((3, 3), dtype=np.int8)) + + def test_non_ref_pivots_not_increasing(self): + """Pivots must appear in strictly increasing column order.""" + mat = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]], dtype=np.int8) + with pytest.raises(NotRefError, match="not strictly to the right"): + _check_ref(mat) + + def test_non_ref_nonzero_after_zero_row(self): + """Non-zero row appearing after an all-zero row is rejected.""" + mat = np.array([[1, 0, 0], [0, 0, 0], [0, 0, 1]], dtype=np.int8) + with pytest.raises(NotRefError, match="after an all-zero row"): + _check_ref(mat) + + +class TestRefTableau: + """Tests for RefTableau construction and gate operations.""" + + def _make_ref(self, n_pivots: int, n_extra_cols: int) -> RefTableau: + """Build a realistic REF tableau with fill in non-pivot columns. + + The pivot block is an identity matrix. Non-pivot columns get + alternating 0/1 entries (a common pattern after Gaussian + elimination). + + Args: + n_pivots: Number of pivot columns (and rows with leading 1s). + n_extra_cols: Additional non-pivot columns to add after the pivots. + + Returns: + RefTableau with the specified shape and pivot structure. + + """ + num_cols = n_pivots + n_extra_cols + dense_size = _dense_qubits_size(num_cols) + num_rows = max(n_pivots, dense_size + 1) + mat = np.zeros((num_rows, num_cols), dtype=np.int8) + mat[:n_pivots, :n_pivots] = np.eye(n_pivots, dtype=np.int8) + for c in range(n_pivots, num_cols): + for r in range(n_pivots): + mat[r, c] = (r + c) % 2 + return RefTableau(mat) + + def test_construction_from_ref(self): + """Valid REF matrix produces a tableau with correct dimensions and pivots.""" + t = self._make_ref(3, 2) + assert t.num_rows == 4 + assert t.num_cols == 5 + assert t.dense_size == _dense_qubits_size(5) + assert len(t.pivots) == 3 + + def test_construction_rejects_non_ref(self): + """Non-REF matrix must raise NotRefError.""" + mat = np.array([[0, 1], [1, 0]], dtype=np.int8) + with pytest.raises(NotRefError): + RefTableau(mat) + + def test_get_and_get_col(self): + """Element access and column extraction return correct values.""" + t = self._make_ref(3, 0) + assert t.get(0, 0) is True + assert t.get(0, 1) is False + col = t.get_col(1) + np.testing.assert_array_equal(col, [0, 1, 0]) + + def test_row_is_zero(self): + """Pivot rows are non-zero; trailing rows below rank are zero.""" + t = self._make_ref(3, 2) + assert not t.row_is_zero(0) + assert not t.row_is_zero(2) + assert t.row_is_zero(t.num_rows - 1) + + def test_cx_operation(self): + """CX XORs the control row into the target row.""" + t = self._make_ref(3, 0) + t.cx(0, 1) + np.testing.assert_array_equal(t.data[1], [1, 1, 0]) + np.testing.assert_array_equal(t.data[0], [1, 0, 0]) + + def test_swap_operation(self): + """SWAP exchanges two rows.""" + t = self._make_ref(3, 0) + t.swap(0, 2) + np.testing.assert_array_equal(t.data[0], [0, 0, 1]) + np.testing.assert_array_equal(t.data[2], [1, 0, 0]) + + def test_x_operation(self): + """X flips every bit in the target row.""" + t = self._make_ref(3, 0) + t.x(0) + np.testing.assert_array_equal(t.data[0], [0, 1, 1]) + + def test_toffoli_both_positive(self): + """Toffoli with both controls positive ANDs the two rows into the target.""" + mat = np.array([[1, 0, 1, 1], [0, 1, 1, 0], [0, 0, 0, 0]], dtype=np.int8) + t = RefTableau(mat) + t.toffoli(2, (0, True), (1, True)) + np.testing.assert_array_equal(t.data[2], [0, 0, 1, 0]) + + def test_toffoli_negative_control(self): + """Toffoli with a negated control ANDs row0 with ~row1 into the target.""" + mat = np.array([[1, 0, 1, 1], [0, 1, 1, 0], [0, 0, 0, 0]], dtype=np.int8) + t = RefTableau(mat) + t.toffoli(2, (0, True), (1, False)) + np.testing.assert_array_equal(t.data[2], [1, 0, 0, 1]) + + def test_identify_pivots(self): + """Pivot detection returns (row, col) pairs for each leading 1.""" + mat = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 0, 0]], dtype=np.int8) + t = RefTableau(mat) + assert t.pivots == [(0, 0), (1, 1)] + + def test_permute_columns(self): + """Column permutation reorders all rows accordingly.""" + t = self._make_ref(3, 0) + t.permute_columns([2, 1, 0]) + np.testing.assert_array_equal(t.data[0], [0, 0, 1]) + np.testing.assert_array_equal(t.data[2], [1, 0, 0]) + + +class TestBinaryEncodingSynthesizerBasic: + """Basic construction and property tests.""" + + def test_from_matrix_identity(self): + """Identity REF matrix should produce a valid synthesiser.""" + mat = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + assert synth.dense_size == _dense_qubits_size(4) + assert len(synth.bijection) == 4 + + def test_from_matrix_rejects_non_ref(self): + """Non-REF input must raise NotRefError.""" + mat = np.array([[0, 1], [1, 0]], dtype=np.int8) + with pytest.raises(NotRefError): + BinaryEncodingSynthesizer.from_matrix(mat) + + @pytest.mark.parametrize( + ("matrix", "expected_num_cols", "expected_dense_size", "expected_num_rows"), + [ + # 4 columns need a 2-qubit dense register, but there are only 2 rows — no spare row. + ( + np.array([[1, 0, 0, 0], [0, 1, 0, 0]], dtype=np.int8), + 4, + 2, + 2, + ), + # 2 columns need a 1-qubit dense register, but there is only 1 row — no spare row. + ( + np.array([[1, 0]], dtype=np.int8), + 2, + 1, + 1, + ), + # 8 columns need a 3-qubit dense register, but there are only 3 rows — no spare row. + ( + np.array( + [[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0]], + dtype=np.int8, + ), + 8, + 3, + 3, + ), + ], + ) + def test_rejects_already_dense_tableau(self, matrix, expected_num_cols, expected_dense_size, expected_num_rows): + """Already-dense tableau must raise ValueError with an informative message.""" + with pytest.raises(ValueError, match="Binary encoding is not applicable") as exc_info: + BinaryEncodingSynthesizer(RefTableau(matrix)) + + msg = str(exc_info.value) + assert f"{expected_num_cols} determinant(s)" in msg + assert f"{expected_dense_size}-qubit dense register" in msg + assert f"{expected_num_rows}-row matrix" in msg + + def test_max_batch_size_power_of_two(self): + """max_batch_size must return a positive power of two.""" + mat = np.array( + [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0]], + dtype=np.int8, + ) + synth = BinaryEncodingSynthesizer(RefTableau(mat)) + mbs = synth.max_batch_size() + assert mbs > 0 + assert mbs & (mbs - 1) == 0 # power of two + + def test_measurement_based_uncompute_flag(self): + """measurement_based_uncompute flag is stored on the synthesizer.""" + mat = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat, measurement_based_uncompute=True) + assert synth.measurement_based_uncompute is True + + def test_include_negative_controls_flag(self): + """include_negative_controls flag is stored on the synthesizer.""" + mat = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat, include_negative_controls=False) + assert synth.include_negative_controls is False + + def test_include_negative_controls_preserves_bijection_semantics(self): + """Both include_negative_controls settings must produce valid bijections.""" + mat = np.array( + [[1, 0, 0, 0, 1, 1], [0, 1, 0, 0, 0, 1], [0, 0, 1, 0, 1, 0], [0, 0, 0, 1, 1, 1]], + dtype=np.int8, + ) + for inc_neg in (True, False): + synth = BinaryEncodingSynthesizer.from_matrix(mat, include_negative_controls=inc_neg) + assert len(synth.bijection) == 6 + ds = synth.dense_size + for dv, c in synth.bijection: + assert _bits_to_int(synth.tableau.data[:ds, c]) == dv + + +class TestBinaryEncodingSynthesizerBijection: + """End-to-end compression correctness for BinaryEncodingSynthesizer. + + Each parametrized REF matrix is fed through from_matrix(); the tests + verify that the bijection faithfully represents the compressed output. + """ + + @pytest.fixture( + params=[ + "identity_3x4", + "identity_4x5", + "ref_with_fill", + "wide_ref", + "minimal_3x3", + "staircase_4x5", + "all_pivot_4x5", + "many_non_pivot_5x8", + "upper_triangular_5x8", + ] + ) + def ref_matrix(self, request) -> np.ndarray: + """Parametrized REF matrices covering various shapes.""" + matrices = { + # 3 pivots, 1 non-pivot, 1 trailing zero row + "identity_3x4": np.array( + [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]], + dtype=np.int8, + ), + # 4 pivots, 1 non-pivot + "identity_4x5": np.array( + [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0]], + dtype=np.int8, + ), + # 4 pivots, 2 non-pivot columns with fill + "ref_with_fill": np.array( + [ + [1, 0, 0, 0, 1, 1], + [0, 1, 0, 0, 0, 1], + [0, 0, 1, 0, 1, 0], + [0, 0, 0, 1, 1, 1], + ], + dtype=np.int8, + ), + # 5 pivots, 3 non-pivot columns (wide matrix, dense_size=3) + "wide_ref": np.array( + [ + [1, 0, 0, 0, 0, 1, 1, 0], + [0, 1, 0, 0, 0, 0, 1, 1], + [0, 0, 1, 0, 0, 1, 0, 1], + [0, 0, 0, 1, 0, 1, 1, 1], + [0, 0, 0, 0, 1, 0, 0, 1], + ], + dtype=np.int8, + ), + # Minimal: 2 pivots, 1 non-pivot, 1 trailing zero row + "minimal_3x3": np.array( + [[1, 0, 1], [0, 1, 1], [0, 0, 0]], + dtype=np.int8, + ), + # Upper-staircase (diagonal-reduced) shape + "staircase_4x5": np.array( + [ + [1, 1, 1, 1, 0], + [0, 1, 1, 1, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 1, 0], + ], + dtype=np.int8, + ), + # All pivots, single zero non-pivot column (stage-2 trivial) + "all_pivot_4x5": np.array( + [[1, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 0]], + dtype=np.int8, + ), + # 4 pivots, 4 non-pivot columns — exercises batch flushing + "many_non_pivot_5x8": np.array( + [ + [1, 0, 0, 0, 1, 1, 0, 1], + [0, 1, 0, 0, 0, 1, 1, 0], + [0, 0, 1, 0, 1, 0, 1, 1], + [0, 0, 0, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=np.int8, + ), + # Larger upper-triangular REF with non-pivot columns + "upper_triangular_5x8": np.array( + [ + [1, 1, 1, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1, 1, 0], + [0, 0, 1, 1, 1, 0, 1, 1], + [0, 0, 0, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=np.int8, + ), + } + return matrices[request.param] + + def test_bijection_covers_all_columns(self, ref_matrix): + """Every column must appear exactly once in the bijection.""" + synth = BinaryEncodingSynthesizer.from_matrix(ref_matrix) + cols = [c for _, c in synth.bijection] + assert sorted(cols) == list(range(ref_matrix.shape[1])) + + def test_bijection_dense_labels_unique(self, ref_matrix): + """Dense labels must be unique.""" + synth = BinaryEncodingSynthesizer.from_matrix(ref_matrix) + dense_vals = [dv for dv, _ in synth.bijection] + assert len(set(dense_vals)) == len(dense_vals) + + def test_bijection_dense_labels_fit_in_register(self, ref_matrix): + """All dense labels must fit in the dense register.""" + synth = BinaryEncodingSynthesizer.from_matrix(ref_matrix) + max_label = (1 << synth.dense_size) - 1 + for dv, _ in synth.bijection: + assert 0 <= dv <= max_label + + def test_sparse_rows_zeroed_after_synthesis(self, ref_matrix): + """After synthesis, all sparse rows should be all-zero.""" + synth = BinaryEncodingSynthesizer.from_matrix(ref_matrix) + for row in range(synth.dense_size, synth.tableau.num_rows): + assert synth.tableau.row_is_zero(row) + + def test_dense_register_matches_bijection(self, ref_matrix): + """Reading dense rows of each column must reproduce the bijection label. + + This is the core compression-correctness check: the synthesizer + transforms the original REF matrix so that the top ``dense_size`` + rows encode a binary label for every column, and that label matches + what the bijection records. + """ + synth = BinaryEncodingSynthesizer.from_matrix(ref_matrix) + ds = synth.dense_size + for dense_val, col in synth.bijection: + actual = _bits_to_int(synth.tableau.data[:ds, col]) + assert actual == dense_val, f"Column {col}: bijection says {dense_val}, but dense register reads {actual}" + + +class TestBinaryEncodingSynthesizerCircuit: + """Tests on the recorded circuit structure.""" + + def test_circuit_nonempty(self): + """Synthesis must produce at least one circuit operation.""" + mat = np.array([[1, 0, 1], [0, 1, 1], [0, 0, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + assert len(synth.circuit) > 0 + + def test_circuit_op_types_valid(self): + """Every circuit entry must be a MatrixCompressionType variant.""" + mat = np.array( + [[1, 0, 0, 0, 1], [0, 1, 0, 0, 1], [0, 0, 1, 0, 0], [0, 0, 0, 1, 1]], + dtype=np.int8, + ) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + for operation_type, _ in synth.circuit: + assert isinstance(operation_type, MatrixCompressionType) + + def test_stage1_starts_with_cx_or_x(self): + """Stage 1 always begins with CX or X (unary staircase).""" + mat = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + first_operation_type = synth.circuit[0][0] + assert first_operation_type in (MatrixCompressionType.CX, MatrixCompressionType.X) + + @pytest.mark.parametrize( + ("n_electrons", "n_orbitals", "n_dets", "seed"), + [ + (6, 6, 5, 42), + (8, 10, 20, 99), + (10, 15, 30, 13), + (14, 20, 50, 0), + ], + ids=["6e6o_5det", "8e10o_20det", "10e15o_30det", "14e20o_50det"], + ) + def test_gf2x_forward_only_fewer_cx_than_rref(self, n_electrons, n_orbitals, n_dets, seed): + """Test forward_only (REF) produces fewer CX than back-substituted (RREF).""" + raw_matrix = create_random_bitstring_matrix( + n_electrons=n_electrons, n_orbitals=n_orbitals, n_dets=n_dets, seed=seed + ) + + # --- RREF path (back-substituted) --- + rref_result = gf2x_with_tracking(raw_matrix, skip_diagonal_reduction=True) + rref_synth = BinaryEncodingSynthesizer(RefTableau(rref_result.reduced_matrix)) + rank, _ = rref_synth._permute_columns_pivots_first() + rref_synth._apply_unary_staircase(rank) + rref_cx = sum(1 for t, _ in rref_synth.circuit if t is MatrixCompressionType.CX) + assert rref_cx == rank * (rank - 1) // 2 + + # --- REF path (forward-only) --- + ref_result = gf2x_with_tracking(raw_matrix, forward_only=True) + ref_synth = BinaryEncodingSynthesizer(RefTableau(ref_result.reduced_matrix)) + rank_ref, _ = ref_synth._permute_columns_pivots_first() + ref_synth._apply_unary_staircase(rank_ref) + ref_cx = sum(1 for t, _ in ref_synth.circuit if t is MatrixCompressionType.CX) + assert ref_cx < rref_cx + + @pytest.mark.parametrize( + ("n_electrons", "n_orbitals", "n_dets", "seed"), + [ + (6, 6, 5, 42), + (8, 10, 20, 99), + (10, 15, 30, 13), + (14, 20, 50, 0), + ], + ids=["6e6o_5det", "8e10o_20det", "10e15o_30det", "14e20o_50det"], + ) + def test_stage1_forward_only_fewer_cx_than_rref(self, n_electrons, n_orbitals, n_dets, seed): + """Full stage 1 (staircase + binary compression) emits fewer CX for REF than RREF.""" + raw_matrix = create_random_bitstring_matrix( + n_electrons=n_electrons, n_orbitals=n_orbitals, n_dets=n_dets, seed=seed + ) + + # --- RREF path (back-substituted) --- + rref_result = gf2x_with_tracking(raw_matrix, skip_diagonal_reduction=True) + rref_synth = BinaryEncodingSynthesizer(RefTableau(rref_result.reduced_matrix)) + rank, _ = rref_synth._permute_columns_pivots_first() + rref_synth._run_stage1_diagonal_encoding(rank) + rref_cx = sum(1 for t, _ in rref_synth.circuit if t is MatrixCompressionType.CX) + rref_x = sum(1 for t, _ in rref_synth.circuit if t is MatrixCompressionType.X) + + # --- REF path (forward-only) --- + ref_result = gf2x_with_tracking(raw_matrix, forward_only=True) + ref_synth = BinaryEncodingSynthesizer(RefTableau(ref_result.reduced_matrix)) + rank_ref, _ = ref_synth._permute_columns_pivots_first() + ref_synth._run_stage1_diagonal_encoding(rank_ref) + ref_cx = sum(1 for t, _ in ref_synth.circuit if t is MatrixCompressionType.CX) + ref_x = sum(1 for t, _ in ref_synth.circuit if t is MatrixCompressionType.X) + + assert ref_cx < rref_cx + assert ref_x == rref_x + + # After stage 1 the pivot block should be identical regardless of input form + assert rank == rank_ref + assert np.array_equal( + rref_synth.tableau.data[:, :rank], + ref_synth.tableau.data[:, :rank], + ) + + +class TestBinaryEncodingSynthesizerReplay: + """Verify that replaying the circuit on the original matrix produces the final tableau.""" + + def test_replay_matches_final_state(self): + """Manually replaying circuit ops on the original matrix must match final tableau.""" + mat = np.array( + [[1, 0, 0, 1, 1], [0, 1, 0, 1, 0], [0, 0, 1, 0, 1], [0, 0, 0, 0, 0]], + dtype=np.int8, + ) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + + # Reconstruct by creating a fresh tableau and replaying + replay = RefTableau(mat.copy()) + + # Permute columns the same way the solver did + pivot_cols = [p[1] for p in replay.pivots] + pivot_set = set(pivot_cols) + non_pivot = [c for c in range(replay.num_cols) if c not in pivot_set] + col_perm = pivot_cols + non_pivot + replay.permute_columns(col_perm) + + # Replay all operations + for operation_type, qubit_args in synth.circuit: + if operation_type is MatrixCompressionType.CX: + replay.cx(*qubit_args) + elif operation_type is MatrixCompressionType.SWAP: + replay.swap(*qubit_args) + elif operation_type is MatrixCompressionType.CCX: + replay.toffoli(qubit_args[0], (qubit_args[1], True), (qubit_args[2], True)) + elif operation_type is MatrixCompressionType.X: + replay.x(*qubit_args) + elif operation_type in {MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND}: + data_table, addr_qubits, dat_qubits = qubit_args + replay.select(data_table, addr_qubits, dat_qubits) + + # Undo column permutation + inv_perm = [0] * len(col_perm) + for new_idx, old_idx in enumerate(col_perm): + inv_perm[old_idx] = new_idx + replay.permute_columns(inv_perm) + + # Final state should match + np.testing.assert_array_equal(replay.data, synth.tableau.data) + + +class TestToGf2xOperations: + """Tests for operation export.""" + + def test_returns_ops_and_ancilla_count(self): + """to_operations returns an op list.""" + mat = np.array([[1, 0, 1], [0, 1, 1], [0, 0, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + ops = synth.to_operations(num_local_qubits=3) + assert isinstance(ops, list) + + def test_op_names_are_valid(self): + """All emitted op types must belong to the known gate vocabulary.""" + mat = np.array([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + ops = synth.to_operations(num_local_qubits=3) + valid_types = { + MatrixCompressionType.CX, + MatrixCompressionType.SWAP, + MatrixCompressionType.CCX, + MatrixCompressionType.X, + MatrixCompressionType.MCX, + MatrixCompressionType.SELECT, + MatrixCompressionType.SELECT_AND, + } + for op in ops: + assert op.name in valid_types, f"Unexpected op type: {op.name}" + + def test_translate_ops_identity_mapping(self): + """When active_qubit_indices is identity, ops stay the same.""" + mat = np.array([[1, 0, 1], [0, 1, 1], [0, 0, 0]], dtype=np.int8) + synth = BinaryEncodingSynthesizer.from_matrix(mat) + ops_raw = synth.to_operations(num_local_qubits=3) + ops_xlat = synth.to_operations( + num_local_qubits=3, + active_qubit_indices=[0, 1, 2], + ancilla_start=3, + ) + # With identity mapping and ancilla_start = num_local, should be equivalent + assert len(ops_raw) == len(ops_xlat) + + def test_translate_ops_remaps_indices(self): + """Translation must remap qubit indices through the provided map.""" + ops = [(MatrixCompressionType.CX, (0, 1)), (MatrixCompressionType.X, 2)] + translated = BinaryEncodingSynthesizer._translate_ops( + ops, + num_local_qubits=3, + active_qubit_indices=[10, 20, 30], + ancilla_start=100, + ) + assert translated[0] == (MatrixCompressionType.CX, (10, 20)) + assert translated[1] == (MatrixCompressionType.X, 30) + + def test_translate_ops_remaps_ancilla(self): + """Indices >= num_local_qubits should map to ancilla space.""" + ops = [(MatrixCompressionType.CX, (0, 3))] + translated = BinaryEncodingSynthesizer._translate_ops( + ops, + num_local_qubits=3, + active_qubit_indices=[10, 20, 30], + ancilla_start=100, + ) + # Index 3 >= num_local_qubits=3, so maps to ancilla_start + (3 - 3) = 100 + assert translated[0] == (MatrixCompressionType.CX, (10, 100)) + + def test_translate_ops_ccx(self): + """CCX indices are remapped through active_qubit_indices.""" + ops = [(MatrixCompressionType.CCX, (0, 1, 2))] + translated = BinaryEncodingSynthesizer._translate_ops( + ops, + num_local_qubits=3, + active_qubit_indices=[5, 6, 7], + ancilla_start=10, + ) + assert translated[0] == (MatrixCompressionType.CCX, (5, 6, 7)) + + def test_translate_ops_select(self): + """Select ops remap address and data qubit indices, keeping the table.""" + data_table = [[True, False], [False, True]] + ops = [(MatrixCompressionType.SELECT, (data_table, [0, 1], [2, 3]))] + translated = BinaryEncodingSynthesizer._translate_ops( + ops, + num_local_qubits=4, + active_qubit_indices=[10, 11, 12, 13], + ancilla_start=100, + ) + _, (dt, addr, dat) = translated[0] + assert dt is data_table + assert addr == [10, 11] + assert dat == [12, 13] + + def test_translate_ops_mcx(self): + """MCX remaps control and target indices; ctrl_state int bitmask passes through unchanged.""" + # ctrl_state is always an int bitmask (0b01 = first control active, second inactive) + ops = [(MatrixCompressionType.MCX, ([0, 1], 0b01, 2))] + translated = BinaryEncodingSynthesizer._translate_ops( + ops, + num_local_qubits=3, + active_qubit_indices=[5, 6, 7], + ancilla_start=10, + ) + _, (ctrls, state, tgt) = translated[0] + assert ctrls == [5, 6] + assert state == 0b01 + assert isinstance(state, int) + assert tgt == 7 + + def test_measurement_based_uses_select_and(self): + """With measurement_based_uncompute, PUI blocks should emit select_and.""" + mat = np.array( + [[1, 0, 0, 0, 1, 1], [0, 1, 0, 0, 0, 1], [0, 0, 1, 0, 1, 0], [0, 0, 0, 1, 1, 1]], + dtype=np.int8, + ) + synth = BinaryEncodingSynthesizer.from_matrix(mat, measurement_based_uncompute=True) + ops = synth.to_operations(num_local_qubits=4) + select_types = { + op.name for op in ops if op.name in {MatrixCompressionType.SELECT, MatrixCompressionType.SELECT_AND} + } + if select_types: + assert MatrixCompressionType.SELECT_AND in select_types + + +class TestLookupSelect: + """Tests for the sparse-to-dense lookup table synthesiser.""" + + def test_empty_table(self): + """Empty truth table produces no ops.""" + ops = _lookup_select({}, [0], [1]) + assert ops == [] + + def test_single_entry(self): + """Single-entry table emits one select op.""" + table = {(1,): (1,)} + ops = _lookup_select(table, [0], [1]) + assert len(ops) == 1 + assert ops[0][0] == MatrixCompressionType.SELECT + + def test_two_address_bits(self): + """Two address bits produce a 2^2 = 4 entry dense data table.""" + table = {(0, 1): (1,), (1, 0): (1,)} + ops = _lookup_select(table, [0, 1], [2]) + assert len(ops) == 1 + name, (data_table, addr, dat) = ops[0] + assert name == MatrixCompressionType.SELECT + assert addr == [1, 0] + assert dat == [2] + assert len(data_table) == 4 + + def test_data_table_correctness(self): + """Verify the dense Bool[][] table encodes the sparse dict correctly.""" + # Address (1,0) → data (1,0), address (0,1) → data (0,1) + # After reversal: (1,0) → reversed (0,1) → addr_int=2 + # (0,1) → reversed (1,0) → addr_int=1 + table = {(1, 0): (1, 0), (0, 1): (0, 1)} + ops = _lookup_select(table, [0, 1], [2, 3]) + _, (data_table, _, _) = ops[0] + # addr_int for (1,0): reversed to (0,1), bit0=0, bit1=1 → addr_int=2 + assert data_table[2] == [True, False] + # addr_int for (0,1): reversed to (1,0), bit0=1, bit1=0 → addr_int=1 + assert data_table[1] == [False, True] + # Other entries should be all-false + assert data_table[0] == [False, False] + assert data_table[3] == [False, False] + + def test_select_and_mode(self): + """use_measurement_and=True emits select_and instead of select.""" + table = {(1,): (1,)} + ops = _lookup_select(table, [0], [1], use_measurement_and=True) + assert ops[0][0] == MatrixCompressionType.SELECT_AND